diff --git a/.ci/azure-pipelines-build.yml b/.ci/azure-pipelines-build.yml new file mode 100644 index 00000000000..128fe546055 --- /dev/null +++ b/.ci/azure-pipelines-build.yml @@ -0,0 +1,63 @@ +jobs: +- job: Build + displayName: 'Build' + + strategy: + matrix: + Development: + BuildConfiguration: development + Production: + BuildConfiguration: production + Standalone: + BuildConfiguration: standalone + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' + + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED + + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') + + - script: 'yarn build:development' + displayName: 'Build Development' + condition: eq(variables['BuildConfiguration'], 'development') + + - script: 'yarn build:production' + displayName: 'Build Production' + condition: eq(variables['BuildConfiguration'], 'production') + + - script: 'yarn build:standalone' + displayName: 'Build Standalone' + condition: eq(variables['BuildConfiguration'], 'standalone') + + - script: 'test -d dist' + displayName: 'Check Build' + + - script: 'mv dist jellyfin-web' + displayName: 'Rename Directory' + + - task: ArchiveFiles@2 + displayName: 'Archive Directory' + inputs: + rootFolderOrFile: 'jellyfin-web' + includeRootFolder: true + archiveFile: 'jellyfin-web-$(BuildConfiguration)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Release' + inputs: + targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip' + artifactName: 'jellyfin-web-$(BuildConfiguration)' diff --git a/.ci/azure-pipelines-lint.yml b/.ci/azure-pipelines-lint.yml new file mode 100644 index 00000000000..1e4bddbd047 --- /dev/null +++ b/.ci/azure-pipelines-lint.yml @@ -0,0 +1,29 @@ +jobs: +- job: Lint + displayName: 'Lint' + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' + + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED + + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') + + - script: 'yarn run lint --quiet' + displayName: 'Run ESLint' + + - script: 'yarn run stylelint' + displayName: 'Run Stylelint' diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml new file mode 100644 index 00000000000..bf4234ec97c --- /dev/null +++ b/.ci/azure-pipelines-package.yml @@ -0,0 +1,114 @@ +jobs: +- job: BuildPackage + displayName: 'Build Packages' + + strategy: + matrix: + CentOS: + BuildConfiguration: centos + Debian: + BuildConfiguration: debian + Fedora: + BuildConfiguration: fedora + Portable: + BuildConfiguration: portable + + pool: + vmImage: 'ubuntu-latest' + + steps: + - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment' + displayName: 'Build Dockerfile' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + + - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)' + displayName: 'Run Dockerfile (unstable)' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + + - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)' + displayName: 'Run Dockerfile (stable)' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Release' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + targetPath: '$(Build.SourcesDirectory)/deployment/dist' + artifactName: 'jellyfin-web-$(BuildConfiguration)' + + - task: SSH@0 + displayName: 'Create target directory on repository server' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' + + - task: CopyFilesOverSSH@0 + displayName: 'Upload artifacts to repository server' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + sshEndpoint: repository + sourceFolder: '$(Build.SourcesDirectory)/deployment/dist' + contents: '**' + targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' + +- job: BuildDocker + displayName: 'Build Docker' + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: Docker@2 + displayName: 'Push Unstable Image' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + repository: 'jellyfin/jellyfin-web' + command: buildAndPush + buildContext: '.' + Dockerfile: 'deployment/Dockerfile.docker' + containerRegistry: Docker Hub + tags: | + unstable-$(Build.BuildNumber) + unstable + + - task: Docker@2 + displayName: 'Push Stable Image' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + inputs: + repository: 'jellyfin/jellyfin-web' + command: buildAndPush + buildContext: '.' + Dockerfile: 'deployment/Dockerfile.docker' + containerRegistry: Docker Hub + tags: | + stable-$(Build.BuildNumber) + stable + +- job: CollectArtifacts + displayName: 'Collect Artifacts' + dependsOn: + - BuildPackage + - BuildDocker + condition: and(succeeded('BuildPackage'), succeeded('BuildDocker')) + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: SSH@0 + displayName: 'Update Unstable Repository' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable' + + - task: SSH@0 + displayName: 'Update Stable Repository' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)' diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 48f042d729e..3d7a5f1cde9 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -2,8 +2,7 @@ trigger: batch: true branches: include: - - master - - release-* + - '*' tags: include: - '*' @@ -13,90 +12,6 @@ pr: - '*' jobs: - - job: build - displayName: 'Build' - - pool: - vmImage: 'ubuntu-latest' - - strategy: - matrix: - Development: - BuildConfiguration: development - Production: - BuildConfiguration: production - Standalone: - BuildConfiguration: standalone - maxParallel: 3 - - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '10.x' - - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED - - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') - - - script: 'yarn build:development' - displayName: 'Build Development' - condition: eq(variables['BuildConfiguration'], 'development') - - - script: 'yarn build:production' - displayName: 'Build Bundle' - condition: eq(variables['BuildConfiguration'], 'production') - - - script: 'yarn build:standalone' - displayName: 'Build Standalone' - condition: eq(variables['BuildConfiguration'], 'standalone') - - - script: 'test -d dist' - displayName: 'Check Build' - - - script: 'mv dist jellyfin-web' - displayName: 'Rename Directory' - condition: succeeded() - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Release' - condition: succeeded() - inputs: - targetPath: '$(Build.SourcesDirectory)/jellyfin-web' - artifactName: 'jellyfin-web-$(BuildConfiguration)' - - - job: lint - displayName: 'Lint' - - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '10.x' - - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED - - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') - - - script: 'yarn run lint' - displayName: 'Run ESLint' - - - script: 'yarn run stylelint' - displayName: 'Run Stylelint' +- template: azure-pipelines-build.yml +- template: azure-pipelines-lint.yml +- template: azure-pipelines-package.yml diff --git a/.copr/Makefile b/.copr/Makefile new file mode 120000 index 00000000000..ec3c90dfd91 --- /dev/null +++ b/.copr/Makefile @@ -0,0 +1 @@ +../fedora/Makefile \ No newline at end of file diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 00000000000..02dfd18aacc --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,5 @@ +version: 1 +update_configs: + - package_manager: "javascript" + directory: "/" + update_schedule: "weekly" diff --git a/.editorconfig b/.editorconfig index 81eba8ccc9f..92cf9dc5909 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,6 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf + +[json] +indent_size = 2 diff --git a/.eslintignore b/.eslintignore index 52369be1e3f..8e3aee83fb1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,5 @@ -libraries/ +node_modules +dist +.idea +.vscode +src/libraries diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000000..ab53f0f03d9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,196 @@ +module.exports = { + root: true, + plugins: [ + 'promise', + 'import', + 'eslint-comments' + ], + env: { + node: true, + es6: true, + es2017: true, + es2020: true + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + ecmaFeatures: { + impliedStrict: true + } + }, + extends: [ + 'eslint:recommended', + // 'plugin:promise/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:eslint-comments/recommended', + 'plugin:compat/recommended' + ], + rules: { + 'block-spacing': ['error'], + 'brace-style': ['error'], + 'comma-dangle': ['error', 'never'], + 'comma-spacing': ['error'], + 'eol-last': ['error'], + 'indent': ['error', 4, { 'SwitchCase': 1 }], + 'keyword-spacing': ['error'], + 'max-statements-per-line': ['error'], + 'no-floating-decimal': ['error'], + 'no-multi-spaces': ['error'], + 'no-multiple-empty-lines': ['error', { 'max': 1 }], + 'no-trailing-spaces': ['error'], + 'one-var': ['error', 'never'], + 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], + 'semi': ['error'], + 'space-before-blocks': ['error'], + 'space-infix-ops': 'error' + }, + overrides: [ + { + files: [ + './src/**/*.js' + ], + parser: 'babel-eslint', + env: { + node: false, + amd: true, + browser: true, + es6: true, + es2017: true, + es2020: true + }, + globals: { + // Browser globals + 'MediaMetadata': 'readonly', + // Tizen globals + 'tizen': 'readonly', + 'webapis': 'readonly', + // WebOS globals + 'webOS': 'readonly', + // Dependency globals + '$': 'readonly', + 'jQuery': 'readonly', + 'requirejs': 'readonly', + // Jellyfin globals + 'ApiClient': 'writable', + 'AppInfo': 'writable', + 'chrome': 'writable', + 'ConnectionManager': 'writable', + 'DlnaProfilePage': 'writable', + 'Dashboard': 'writable', + 'DashboardPage': 'writable', + 'Emby': 'readonly', + 'Events': 'writable', + 'getParameterByName': 'writable', + 'getWindowLocationSearch': 'writable', + 'Globalize': 'writable', + 'Hls': 'writable', + 'dfnshelper': 'writable', + 'LibraryMenu': 'writable', + 'LinkParser': 'writable', + 'LiveTvHelpers': 'writable', + 'MetadataEditor': 'writable', + 'pageClassOn': 'writable', + 'pageIdOn': 'writable', + 'PlaylistViewer': 'writable', + 'UserParentalControlPage': 'writable', + 'Windows': 'readonly' + }, + rules: { + // TODO: Fix warnings and remove these rules + 'no-redeclare': ['warn'], + 'no-unused-vars': ['warn'], + 'no-useless-escape': ['warn'], + // TODO: Remove after ES6 migration is complete + 'import/no-unresolved': ['off'] + }, + settings: { + polyfills: [ + // Native Promises Only + 'Promise', + // whatwg-fetch + 'fetch', + // document-register-element + 'document.registerElement', + // resize-observer-polyfill + 'ResizeObserver', + // fast-text-encoding + 'TextEncoder', + // intersection-observer + 'IntersectionObserver', + // Core-js + 'Object.assign', + 'Object.is', + 'Object.setPrototypeOf', + 'Object.toString', + 'Object.freeze', + 'Object.seal', + 'Object.preventExtensions', + 'Object.isFrozen', + 'Object.isSealed', + 'Object.isExtensible', + 'Object.getOwnPropertyDescriptor', + 'Object.getPrototypeOf', + 'Object.keys', + 'Object.entries', + 'Object.getOwnPropertyNames', + 'Function.name', + 'Function.hasInstance', + 'Array.from', + 'Array.arrayOf', + 'Array.copyWithin', + 'Array.fill', + 'Array.find', + 'Array.findIndex', + 'Array.iterator', + 'String.fromCodePoint', + 'String.raw', + 'String.iterator', + 'String.codePointAt', + 'String.endsWith', + 'String.includes', + 'String.repeat', + 'String.startsWith', + 'String.trim', + 'String.anchor', + 'String.big', + 'String.blink', + 'String.bold', + 'String.fixed', + 'String.fontcolor', + 'String.fontsize', + 'String.italics', + 'String.link', + 'String.small', + 'String.strike', + 'String.sub', + 'String.sup', + 'RegExp', + 'Number', + 'Math', + 'Date', + 'async', + 'Symbol', + 'Map', + 'Set', + 'WeakMap', + 'WeakSet', + 'ArrayBuffer', + 'DataView', + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array', + 'Reflect', + // Temporary while eslint-compat-plugin is buggy + 'document.querySelector' + ] + } + } + ] +} diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 377716d53ca..00000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,77 +0,0 @@ -env: - es6: true - browser: true - amd: true - -parserOptions: - ecmaVersion: 6 - sourceType: module - ecmaFeatures: - impliedStrict: true - -globals: - # New browser globals - DataView: readonly - MediaMetadata: readonly - Promise: readonly - # Deprecated browser globals - DocumentTouch: readonly - # Tizen globals - tizen: readonly - webapis: readonly - # WebOS globals - webOS: readonly - # Dependency globals - $: readonly - jQuery: readonly - queryString: readonly - requirejs: readonly - # Jellyfin globals - ApiClient: writable - AppInfo: writable - chrome: writable - ConnectionManager: writable - DlnaProfilePage: writable - Dashboard: writable - DashboardPage: writable - Emby: readonly - Events: writable - getParameterByName: writable - getWindowLocationSearch: writable - Globalize: writable - Hls: writable - humaneDate: writable - humaneElapsed: writable - LibraryMenu: writable - LinkParser: writable - LiveTvHelpers: writable - MetadataEditor: writable - pageClassOn: writable - pageIdOn: writable - PlaylistViewer: writable - UserParentalControlPage: writable - Windows: readonly - -extends: - - eslint:recommended - -rules: - block-spacing: ["error"] - brace-style: ["error"] - comma-dangle: ["error", "never"] - comma-spacing: ["error"] - eol-last: ["error"] - indent: ["error", 4, { "SwitchCase": 1 }] - keyword-spacing: ["error"] - max-statements-per-line: ["error"] - no-floating-decimal: ["error"] - no-multi-spaces: ["error"] - no-multiple-empty-lines: ["error", { "max": 1 }] - no-trailing-spaces: ["error"] - one-var: ["error", "never"] - semi: ["warn"] - space-before-blocks: ["error"] - # TODO: Fix warnings and remove these rules - no-redeclare: ["warn"] - no-unused-vars: ["warn"] - no-useless-escape: ["warn"] diff --git a/.gitattributes b/.gitattributes index 80f9bc36ede..9e495a4df09 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,35 @@ -/CONTRIBUTORS.md merge=union +* text=auto + +CONTRIBUTORS.md merge=union +README.md text +LICENSE text + +*.css text +*.eot binary +*.gif binary +*.html text diff=html +*.ico binary +*.*ignore text +*.jpg binary +*.js text +*.json text +*.lock text -diff +*.map text -diff +*.md text +*.otf binary +*.png binary +*.py text diff=python +*.svg binary +*.ts text +*.ttf binary +*.sass text +*.vue text +*.webp binary +*.woff binary +*.woff2 binary + +.editorconfig text +.gitattributes export-ignore +.gitignore export-ignore + +*.gitattributes linguist-language=gitattributes diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..a35eb9981f0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +.ci @dkanada @EraYaN +.github @jellyfin/core +build.sh @joshuaboniface +deployment @joshuaboniface diff --git a/.gitignore b/.gitignore index 10b2d24f428..36b843f0221 100644 --- a/.gitignore +++ b/.gitignore @@ -1,578 +1,11 @@ +# npm +dist +web +node_modules -# Created by https://www.gitignore.io/api/node,rider,macos,linux,windows,visualstudio,visualstudiocode -# Edit at https://www.gitignore.io/?templates=node,rider,macos,linux,windows,visualstudio,visualstudiocode - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Dependency lockfile -package-lock.json - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -### Rider ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/modules.xml -# .idea/*.iml -# .idea/modules - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ -# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true -**/wwwroot/lib/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- Backup*.rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# End of https://www.gitignore.io/api/node,rider,macos,linux,windows,visualstudio,visualstudiocode +# ide +.idea +.vscode -# dist for webpack output -dist +# log +yarn-error.log diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index aa3ec707e30..73f40aaca1f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -34,6 +34,8 @@ - [Ryan Hartzell](https://github.com/ryan-hartzell) - [Thibault Nocchi](https://github.com/ThibaultNocchi) - [MrTimscampi](https://github.com/MrTimscampi) + - [Sarab Singh](https://github.com/sarab97) + - [Andrei Oanca](https://github.com/OancaAndrei) # Emby Contributors diff --git a/README.md b/README.md index e2aac6b155c..f06e461320b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user ### Dependencies -- Yarn +- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install) - Gulp-cli ### Getting Started @@ -78,4 +78,4 @@ Jellyfin Web is the frontend used for most of the clients available for end user ```sh yarn build:standalone - ``` \ No newline at end of file + ``` diff --git a/build.sh b/build.sh new file mode 100755 index 00000000000..9f5fa602297 --- /dev/null +++ b/build.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +# build.sh - Build Jellyfin binary packages +# Part of the Jellyfin Project + +set -o errexit +set -o pipefail + +usage() { + echo -e "build.sh - Build Jellyfin binary packages" + echo -e "Usage:" + echo -e " $0 -t/--type -p/--platform [-k/--keep-artifacts] [-l/--list-platforms]" + echo -e "Notes:" + echo -e " * BUILD_TYPE can be one of: [native, docker] and must be specified" + echo -e " * native: Build using the build script in the host OS" + echo -e " * docker: Build using the build script in a standardized Docker container" + echo -e " * PLATFORM can be any platform shown by -l/--list-platforms and must be specified" + echo -e " * If -k/--keep-artifacts is specified, transient artifacts (e.g. Docker containers) will be" + echo -e " retained after the build is finished; the source directory will still be cleaned" + echo -e " * If -l/--list-platforms is specified, all other arguments are ignored; the script will print" + echo -e " the list of supported platforms and exit" +} + +list_platforms() { + declare -a platforms + platforms=( + $( find deployment -maxdepth 1 -mindepth 1 -name "build.*" | awk -F'.' '{ $1=""; printf $2; if ($3 != ""){ printf "." $3; }; if ($4 != ""){ printf "." $4; }; print ""; }' | sort ) + ) + echo -e "Valid platforms:" + echo + for platform in ${platforms[@]}; do + echo -e "* ${platform} : $( grep '^#=' deployment/build.${platform} | sed 's/^#= //' )" + done +} + +do_build_native() { + export IS_DOCKER=NO + deployment/build.${PLATFORM} +} + +do_build_docker() { + if ! dpkg --print-architecture | grep -q 'amd64'; then + echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead." + exit 1 + fi + if [[ ! -f deployment/Dockerfile.${PLATFORM} ]]; then + echo "Missing Dockerfile for platform ${PLATFORM}" + exit 1 + fi + if [[ ${KEEP_ARTIFACTS} == YES ]]; then + docker_args="" + else + docker_args="--rm" + fi + + docker build . -t "jellyfin-builder.${PLATFORM}" -f deployment/Dockerfile.${PLATFORM} + mkdir -p ${ARTIFACT_DIR} + docker run $docker_args -v "${SOURCE_DIR}:/jellyfin" -v "${ARTIFACT_DIR}:/dist" "jellyfin-builder.${PLATFORM}" +} + +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + -t|--type) + BUILD_TYPE="$2" + shift + shift + ;; + -p|--platform) + PLATFORM="$2" + shift + shift + ;; + -k|--keep-artifacts) + KEEP_ARTIFACTS=YES + shift + ;; + -l|--list-platforms) + list_platforms + exit 0 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option $1" + usage + exit 1 + ;; + esac +done + +if [[ -z ${BUILD_TYPE} || -z ${PLATFORM} ]]; then + usage + exit 1 +fi + +export SOURCE_DIR="$( pwd )" +export ARTIFACT_DIR="${SOURCE_DIR}/../bin/${PLATFORM}" + +# Determine build type +case ${BUILD_TYPE} in + native) + do_build_native + ;; + docker) + do_build_docker + ;; +esac diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000000..fe1633faec0 --- /dev/null +++ b/build.yaml @@ -0,0 +1,9 @@ +--- +# We just wrap `build` so this is really it +name: "jellyfin-web" +version: "10.6.0" +packages: + - debian.all + - fedora.all + - centos.all + - portable diff --git a/bump_version b/bump_version new file mode 100755 index 00000000000..bc8288b829f --- /dev/null +++ b/bump_version @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +# bump_version - increase the shared version and generate changelogs + +set -o errexit +set -o pipefail + +usage() { + echo -e "bump_version - increase the shared version and generate changelogs" + echo -e "" + echo -e "Usage:" + echo -e " $ bump_version " +} + +if [[ -z $1 ]]; then + usage + exit 1 +fi + +shared_version_file="src/components/apphost.js" +build_file="./build.yaml" + +new_version="$1" + +# Parse the version from shared version file +old_version="$( + grep "appVersion" ${shared_version_file} | head -1 \ + | sed -E 's/var appVersion = "([0-9\.]+)";/\1/' +)" +echo "Old version in appHost is: $old_version" + +# Set the shared version to the specified new_version +old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars +new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" +sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file} + +old_version="$( + grep "version:" ${build_file} \ + | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' +)" +echo "Old version in ${build_file}: $old_version`" + +# Set the build.yaml version to the specified new_version +old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars +sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} + +if [[ ${new_version} == *"-"* ]]; then + new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )" +else + new_version_deb="${new_version}-1" +fi + +# Write out a temporary Debian changelog with our new stuff appended and some templated formatting +debian_changelog_file="debian/changelog" +debian_changelog_temp="$( mktemp )" +# Create new temp file with our changelog +echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium + + * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version} + + -- Jellyfin Packaging Team $( date --rfc-2822 ) +" >> ${debian_changelog_temp} +cat ${debian_changelog_file} >> ${debian_changelog_temp} +# Move into place +mv ${debian_changelog_temp} ${debian_changelog_file} + +# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting +fedora_spec_file="fedora/jellyfin.spec" +fedora_changelog_temp="$( mktemp )" +fedora_spec_temp_dir="$( mktemp -d )" +fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp" +# Make a copy of our spec file for hacking +cp ${fedora_spec_file} ${fedora_spec_temp_dir}/ +pushd ${fedora_spec_temp_dir} +# Split out the stuff before and after changelog +csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 +# Update the version in xx00 +sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 +# Remove the header from xx01 +sed -i '/^%changelog/d' xx01 +# Create new temp file with our changelog +echo -e "%changelog +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}" >> ${fedora_changelog_temp} +cat xx01 >> ${fedora_changelog_temp} +# Reassembble +cat xx00 ${fedora_changelog_temp} > ${fedora_spec_temp} +popd +# Move into place +mv ${fedora_spec_temp} ${fedora_spec_file} +# Clean up +rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} + +# Stage the changed files for commit +git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile* +git status diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000000..50966c3a011 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +jellyfin-web (10.6.0-1) unstable; urgency=medium + + * New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0 + + -- Jellyfin Packaging Team Mon, 16 Mar 2020 11:15:00 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000000..45a4fb75db8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000000..ce7b130efc4 --- /dev/null +++ b/debian/control @@ -0,0 +1,16 @@ +Source: jellyfin-web +Section: misc +Priority: optional +Maintainer: Jellyfin Team +Build-Depends: debhelper (>= 9), + npm | nodejs +Standards-Version: 3.9.4 +Homepage: https://jellyfin.org/ +Vcs-Git: https://github.org/jellyfin/jellyfin-web.git +Vcs-Browser: https://github.org/jellyfin/jellyfin-web + +Package: jellyfin-web +Recommends: jellyfin-server +Architecture: all +Description: Jellyfin is the Free Software Media System. + This package provides the Jellyfin web client. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000000..85548075eb4 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,28 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: jellyfin-web +Source: https://github.com/jellyfin/jellyfin-web + +Files: * +Copyright: 2018-2020 Jellyfin Team +License: GPL-3.0 + +Files: debian/* +Copyright: 2020 Joshua Boniface +License: GPL-3.0 + +License: GPL-3.0 + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 00000000000..60b3d28723d --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,6 @@ +[DEFAULT] +pristine-tar = False +cleaner = fakeroot debian/rules clean + +[import-orig] +filter = [ ".git*", ".hg*", ".vs*", ".vscode*" ] diff --git a/debian/install b/debian/install new file mode 100644 index 00000000000..584fe06a115 --- /dev/null +++ b/debian/install @@ -0,0 +1 @@ +web usr/share/jellyfin/ diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in new file mode 100644 index 00000000000..cef83a3407d --- /dev/null +++ b/debian/po/POTFILES.in @@ -0,0 +1 @@ +[type: gettext/rfc822deb] templates diff --git a/debian/po/templates.pot b/debian/po/templates.pot new file mode 100644 index 00000000000..2cdcae4173c --- /dev/null +++ b/debian/po/templates.pot @@ -0,0 +1,57 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: jellyfin-server\n" +"Report-Msgid-Bugs-To: jellyfin-server@packages.debian.org\n" +"POT-Creation-Date: 2015-06-12 20:51-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: note +#. Description +#: ../templates:1001 +msgid "Jellyfin permission info:" +msgstr "" + +#. Type: note +#. Description +#: ../templates:1001 +msgid "" +"Jellyfin by default runs under a user named \"jellyfin\". Please ensure that the " +"user jellyfin has read and write access to any folders you wish to add to your " +"library. Otherwise please run jellyfin under a different user." +msgstr "" + +#. Type: string +#. Description +#: ../templates:2001 +msgid "Username to run Jellyfin as:" +msgstr "" + +#. Type: string +#. Description +#: ../templates:2001 +msgid "The user that jellyfin will run as." +msgstr "" + +#. Type: note +#. Description +#: ../templates:3001 +msgid "Jellyfin still running" +msgstr "" + +#. Type: note +#. Description +#: ../templates:3001 +msgid "Jellyfin is currently running. Please close it and try again." +msgstr "" diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000000..f3e568225c4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,20 @@ +#! /usr/bin/make -f +export DH_VERBOSE=1 + +%: + dh $@ + +# disable "make check" +override_dh_auto_test: + +# disable stripping debugging symbols +override_dh_clistrip: + +override_dh_auto_build: + npx yarn install + mv $(CURDIR)/dist $(CURDIR)/web + +override_dh_auto_clean: + test -d $(CURDIR)/dist && rm -rf '$(CURDIR)/dist' || true + test -d $(CURDIR)/web && rm -rf '$(CURDIR)/web' || true + test -d $(CURDIR)/node_modules && rm -rf '$(CURDIR)/node_modules' || true diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000000..d3827e75a5c --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 00000000000..b7adf56c676 --- /dev/null +++ b/debian/source/options @@ -0,0 +1,7 @@ +tar-ignore='.git*' +tar-ignore='**/.git' +tar-ignore='**/.hg' +tar-ignore='**/.vs' +tar-ignore='**/.vscode' +tar-ignore='deployment' +tar-ignore='*.deb' diff --git a/deployment/Dockerfile.centos b/deployment/Dockerfile.centos new file mode 100644 index 00000000000..64cffc7eed0 --- /dev/null +++ b/deployment/Dockerfile.centos @@ -0,0 +1,29 @@ +FROM centos:7 + +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist + +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV IS_DOCKER=YES + +# Prepare CentOS environment +RUN yum update -y \ + && yum install -y epel-release \ + && yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel + +# Install recent NodeJS and Yarn +RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ + && rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ + && yum install -y yarn + +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh + +VOLUME ${SOURCE_DIR} + +VOLUME ${ARTIFACT_DIR} + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.debian b/deployment/Dockerfile.debian new file mode 100644 index 00000000000..80ea103b921 --- /dev/null +++ b/deployment/Dockerfile.debian @@ -0,0 +1,27 @@ +FROM debian:10 + +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist + +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y debhelper mmv npm git + +# Prepare Yarn +RUN npm install -g yarn + +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh + +VOLUME ${SOURCE_DIR} + +VOLUME ${ARTIFACT_DIR} + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.docker b/deployment/Dockerfile.docker new file mode 100644 index 00000000000..5d129381857 --- /dev/null +++ b/deployment/Dockerfile.docker @@ -0,0 +1,11 @@ +FROM node:alpine + +ARG SOURCE_DIR=/src +ARG ARTIFACT_DIR=/jellyfin-web + +RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python + +WORKDIR ${SOURCE_DIR} +COPY . . + +RUN yarn install && mv dist ${ARTIFACT_DIR} diff --git a/deployment/Dockerfile.fedora b/deployment/Dockerfile.fedora new file mode 100644 index 00000000000..e6026985d1f --- /dev/null +++ b/deployment/Dockerfile.fedora @@ -0,0 +1,23 @@ +FROM fedora:31 + +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist + +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV IS_DOCKER=YES + +# Prepare Fedora environment +RUN dnf update -y \ + && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel + +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh + +VOLUME ${SOURCE_DIR} + +VOLUME ${ARTIFACT_DIR} + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable new file mode 100644 index 00000000000..0d4c79bb2f9 --- /dev/null +++ b/deployment/Dockerfile.portable @@ -0,0 +1,26 @@ +FROM debian:10 + +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist + +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y mmv npm git + +# Prepare Yarn +RUN npm install -g yarn + +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh + +VOLUME ${SOURCE_DIR} + +VOLUME ${ARTIFACT_DIR} + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/build.centos b/deployment/build.centos new file mode 100755 index 00000000000..25d1e8c1759 --- /dev/null +++ b/deployment/build.centos @@ -0,0 +1,41 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +# move to source directory +pushd ${SOURCE_DIR} + +cp -a yarn.lock /tmp/yarn.lock + +# modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd fedora + + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec + sed -i "/%changelog/q" jellyfin-web.spec + + cat <>jellyfin-web.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID} +EOF + popd +fi + +# build rpm +make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm + +# move the artifacts +mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +rm -f fedora/jellyfin*.tar.gz +cp -a /tmp/yarn.lock yarn.lock + +popd diff --git a/deployment/build.debian b/deployment/build.debian new file mode 100755 index 00000000000..9fe3fb51e92 --- /dev/null +++ b/deployment/build.debian @@ -0,0 +1,39 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +# move to source directory +pushd ${SOURCE_DIR} + +cp -a yarn.lock /tmp/yarn.lock + +# modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <changelog +jellyfin-web (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team $( date --rfc-2822 ) +EOF + popd +fi + +# build deb +dpkg-buildpackage -us -uc --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR} +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR} + +cp -a /tmp/yarn.lock yarn.lock + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.fedora b/deployment/build.fedora new file mode 100755 index 00000000000..60d270ed6cf --- /dev/null +++ b/deployment/build.fedora @@ -0,0 +1,41 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +# move to source directory +pushd ${SOURCE_DIR} + +cp -a yarn.lock /tmp/yarn.lock + +# modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd fedora + + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec + sed -i "/%changelog/q" jellyfin-web.spec + + cat <>jellyfin-web.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID} +EOF + popd +fi + +# build rpm +make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm + +# move the artifacts +mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +rm -f fedora/jellyfin*.tar.gz +cp -a /tmp/yarn.lock yarn.lock + +popd diff --git a/deployment/build.portable b/deployment/build.portable new file mode 100755 index 00000000000..873c145fab7 --- /dev/null +++ b/deployment/build.portable @@ -0,0 +1,30 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +# move to source directory +pushd ${SOURCE_DIR} + +# get version +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi + +# build archives +npx yarn install +mv dist jellyfin-web_${version} +tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version} +rm -rf dist + +# move the artifacts +mkdir -p ${ARTIFACT_DIR} +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/fedora/Makefile b/fedora/Makefile new file mode 100644 index 00000000000..1d3e39ac8bc --- /dev/null +++ b/fedora/Makefile @@ -0,0 +1,21 @@ +VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin-web.spec) + +srpm: + cd fedora/; \ + SOURCE_DIR=.. \ + WORKDIR="$${PWD}"; \ + tar \ + --transform "s,^\.,jellyfin-web-$(VERSION)," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='deployment' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + --exclude='jellyfin-web-$(VERSION).tar.gz' \ + -czf "jellyfin-web-$(VERSION).tar.gz" \ + -C $${SOURCE_DIR} ./ + cd fedora/; \ + rpmbuild -bs jellyfin-web.spec \ + --define "_sourcedir $$PWD/" \ + --define "_srcrpmdir $(outdir)" diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec new file mode 100644 index 00000000000..dcc9d9d2abf --- /dev/null +++ b/fedora/jellyfin-web.spec @@ -0,0 +1,43 @@ +%global debug_package %{nil} + +Name: jellyfin-web +Version: 10.6.0 +Release: 1%{?dist} +Summary: The Free Software Media System web client +License: GPLv3 +URL: https://jellyfin.org +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +Source0: jellyfin-web-%{version}.tar.gz + +%if 0%{?centos} +BuildRequires: yarn +%else +BuildRequires: nodejs-yarn +%endif +BuildArch: noarch + +# Disable Automatic Dependency Processing +AutoReqProv: no + +%description +Jellyfin is a free software media system that puts you in control of managing and streaming your media. + + +%prep +%autosetup -n jellyfin-web-%{version} -b 0 + +%build + +%install +yarn install +%{__mkdir} -p %{buildroot}%{_datadir} +mv dist %{buildroot}%{_datadir}/jellyfin-web +%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE + +%files +%attr(755,root,root) %{_datadir}/jellyfin-web +%{_datadir}/licenses/jellyfin/LICENSE + +%changelog +* Mon Mar 23 2020 Jellyfin Packaging Team +- Forthcoming stable release diff --git a/gulpfile.js b/gulpfile.js index 0eb5593541e..8b407ec2aa5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,5 +1,3 @@ -'use strict'; - const { src, dest, series, parallel, watch } = require('gulp'); const browserSync = require('browser-sync').create(); const del = require('del'); @@ -10,8 +8,8 @@ const htmlmin = require('gulp-htmlmin'); const imagemin = require('gulp-imagemin'); const sourcemaps = require('gulp-sourcemaps'); const mode = require('gulp-mode')({ - modes: ["development", "production"], - default: "development", + modes: ['development', 'production'], + default: 'development', verbose: false }); const stream = require('webpack-stream'); @@ -47,7 +45,7 @@ const options = { query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'] }, copy: { - query: ['src/**/*.json', 'src/**/*.ico'] + query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3'] }, injectBundle: { query: 'src/index.html' @@ -57,12 +55,12 @@ const options = { function serve() { browserSync.init({ server: { - baseDir: "./dist" + baseDir: './dist' }, port: 8080 }); - let events = ['add', 'change']; + const events = ['add', 'change']; watch(options.javascript.query).on('all', function (event, path) { if (events.includes(event)) { @@ -105,7 +103,7 @@ function clean() { return del(['dist/']); } -let pipelineJavascript = lazypipe() +const pipelineJavascript = lazypipe() .pipe(function () { return mode.development(sourcemaps.init({ loadMaps: true })); }) @@ -140,7 +138,7 @@ function apploader(standalone) { .pipe(pipelineJavascript()) .pipe(dest('dist/')) .pipe(browserSync.stream()); - }; + } task.displayName = 'apploader'; @@ -186,16 +184,21 @@ function copy(query) { function injectBundle() { return src(options.injectBundle.query, { base: './src/' }) .pipe(inject( - src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true } + src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { + relative: true, + transform: function (filepath) { + return ``; + } + } )) .pipe(dest('dist/')) .pipe(browserSync.stream()); } function build(standalone) { - return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy), injectBundle); + return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); } -exports.default = build(false); -exports.standalone = build(true); +exports.default = series(build(false), injectBundle); +exports.standalone = series(build(true), injectBundle); exports.serve = series(exports.standalone, serve); diff --git a/package.json b/package.json index 097cf0c750c..132bea64b01 100644 --- a/package.json +++ b/package.json @@ -5,23 +5,29 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.8.6", + "@babel/core": "^7.10.3", + "@babel/plugin-proposal-class-properties": "^7.10.1", + "@babel/plugin-proposal-private-methods": "^7.10.1", + "@babel/plugin-transform-modules-amd": "^7.9.6", "@babel/polyfill": "^7.8.7", - "@babel/plugin-transform-modules-amd": "^7.8.3", - "@babel/preset-env": "^7.8.6", - "autoprefixer": "^9.7.4", + "@babel/preset-env": "^7.10.3", + "autoprefixer": "^9.8.5", + "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", "browser-sync": "^2.26.7", - "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.4.2", + "css-loader": "^3.6.0", "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", - "file-loader": "^5.0.2", + "eslint-plugin-compat": "^3.5.1", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-promise": "^4.2.1", + "file-loader": "^6.0.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", - "gulp-cli": "^2.2.0", + "gulp-cli": "^2.3.0", "gulp-concat": "^2.6.1", "gulp-htmlmin": "^5.0.1", "gulp-if": "^3.0.0", @@ -32,56 +38,112 @@ "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", "gulp-terser": "^1.2.0", - "html-webpack-plugin": "^3.2.0", + "html-webpack-plugin": "^4.3.0", "lazypipe": "^1.0.2", "node-sass": "^4.13.1", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", "style-loader": "^1.1.3", - "stylelint": "^13.1.0", + "stylelint": "^13.6.1", "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", - "stylelint-order": "^4.0.0", + "stylelint-order": "^4.1.0", "webpack": "^4.41.5", - "webpack-cli": "^3.3.10", - "webpack-concat-plugin": "^3.0.0", - "webpack-dev-server": "^3.10.3", "webpack-merge": "^4.2.2", "webpack-stream": "^5.2.1" }, "dependencies": { "alameda": "^1.4.0", - "core-js": "^3.6.4", - "document-register-element": "^1.14.3", + "blurhash": "^1.1.3", + "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", + "core-js": "^3.6.5", + "date-fns": "^2.14.0", + "epubjs": "^0.3.85", + "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", - "hls.js": "^0.13.1", - "howler": "^2.1.3", + "headroom.js": "^0.11.0", + "hls.js": "^0.14.0", + "howler": "^2.2.0", + "intersection-observer": "^0.11.0", + "jellyfin-apiclient": "^1.3.0", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", - "jquery": "^3.4.1", - "jstree": "^3.3.7", + "jquery": "^3.5.1", + "jstree": "^3.3.10", "material-design-icons-iconfont": "^5.0.1", "native-promise-only": "^0.8.0-a", - "page": "^1.11.5", - "query-string": "^6.11.1", + "page": "^1.11.6", + "query-string": "^6.13.1", "resize-observer-polyfill": "^1.5.1", - "shaka-player": "^2.5.9", + "screenfull": "^5.0.2", + "shaka-player": "^2.5.13", "sortablejs": "^1.10.2", - "swiper": "^5.3.1", + "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.0.0" + "whatwg-fetch": "^3.2.0" }, "babel": { - "presets": ["@babel/preset-env"], - "overrides": [{ + "presets": [ + "@babel/preset-env" + ], + "overrides": [ + { "test": [ - "src/components/cardbuilder/cardBuilder.js", - "src/components/filedownloader.js", - "src/components/filesystem.js", - "src/components/input/keyboardnavigation.js", - "src/components/sanatizefilename.js" + "src/components/accessSchedule/accessSchedule.js", + "src/components/actionSheet/actionSheet.js", + "src/components/autoFocuser.js", + "src/components/cardbuilder/cardBuilder.js", + "src/components/cardbuilder/chaptercardbuilder.js", + "src/components/cardbuilder/peoplecardbuilder.js", + "src/components/images/imageLoader.js", + "src/components/indicators/indicators.js", + "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", + "src/components/playback/brightnessosd.js", + "src/components/playback/mediasession.js", + "src/components/playback/nowplayinghelper.js", + "src/components/playback/playbackorientation.js", + "src/components/playback/playerSelectionMenu.js", + "src/components/playback/playersettingsmenu.js", + "src/components/playback/playmethodhelper.js", + "src/components/playback/remotecontrolautoplay.js", + "src/components/playback/volumeosd.js", + "src/components/playmenu.js", + "src/components/sanatizefilename.js", + "src/components/scrollManager.js", + "src/components/syncPlay/groupSelectionMenu.js", + "src/components/syncPlay/playbackPermissionManager.js", + "src/components/syncPlay/syncPlayManager.js", + "src/components/syncPlay/timeSyncManager.js", + "src/controllers/dashboard/logs.js", + "src/controllers/dashboard/plugins/repositories.js", + "src/controllers/user/display.js", + "src/controllers/user/home.js", + "src/controllers/user/playback.js", + "src/controllers/user/subtitles.js", + "src/plugins/bookPlayer/plugin.js", + "src/plugins/bookPlayer/tableOfContents.js", + "src/plugins/photoPlayer/plugin.js", + "src/scripts/deleteHelper.js", + "src/scripts/dfnshelper.js", + "src/scripts/dom.js", + "src/scripts/fileDownloader.js", + "src/scripts/filesystem.js", + "src/scripts/imagehelper.js", + "src/scripts/inputManager.js", + "src/plugins/backdropScreensaver/plugin.js", + "src/components/filterdialog/filterdialog.js", + "src/components/fetchhelper.js", + "src/scripts/keyboardNavigation.js", + "src/scripts/settings/appSettings.js", + "src/scripts/settings/userSettings.js", + "src/scripts/settings/webSettings.js" ], - "plugins": ["@babel/plugin-transform-modules-amd"] - }] + "plugins": [ + "@babel/plugin-transform-modules-amd", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-private-methods" + ] + } + ] }, "browserslist": [ "last 2 Firefox versions", @@ -99,12 +161,12 @@ "Firefox ESR" ], "scripts": { - "serve": "gulp serve", + "serve": "gulp serve --development", "prepare": "gulp --production", "build:development": "gulp --development", "build:production": "gulp --production", "build:standalone": "gulp standalone --development", - "lint": "eslint \"src\"", + "lint": "eslint \".\"", "stylelint": "stylelint \"src/**/*.css\"" } } diff --git a/postcss.config.js b/postcss.config.js index 23159fd2954..bd1651fa194 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,11 +1,16 @@ +const packageConfig = require('./package.json'); const postcssPresetEnv = require('postcss-preset-env'); +const autoprefixer = require('autoprefixer'); const cssnano = require('cssnano'); const config = () => ({ - plugins: [ - postcssPresetEnv(), - cssnano() - ] + plugins: [ + // Explicitly specify browserslist to override ones from node_modules + // For example, Swiper has it in its package.json + postcssPresetEnv({browsers: packageConfig.browserslist}), + autoprefixer({overrideBrowserslist: packageConfig.browserslist}), + cssnano() + ] }); -module.exports = config +module.exports = config; diff --git a/scripts/scdup.py b/scripts/scdup.py index 468e31f14a8..9b9ddf64664 100644 --- a/scripts/scdup.py +++ b/scripts/scdup.py @@ -15,6 +15,8 @@ input('press enter to continue') keysus = [] +missing = [] + with open(langdir + '/' + 'en-us.json') as en: langus = json.load(en) for key in langus: @@ -32,10 +34,19 @@ for key in langjson: if key in keysus: langjnew[key] = langjson[key] + elif key not in missing: + missing.append(key) f.seek(0) f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False)) f.write('\n') f.truncate() f.close() +print(missing) +print('LENGTH: ' + str(len(missing))) +with open('missing.txt', 'w') as out: + for item in missing: + out.write(item + '\n') + out.close() + print('DONE') diff --git a/scripts/scgen.py b/scripts/scgen.py index 0d831426e61..12af27320ae 100644 --- a/scripts/scgen.py +++ b/scripts/scgen.py @@ -34,7 +34,7 @@ def grep(key): print(dep) print('LENGTH: ' + str(len(dep))) -with open('scout.txt', 'w') as out: +with open('unused.txt', 'w') as out: for item in dep: out.write(item + '\n') out.close() diff --git a/src/addplugin.html b/src/addplugin.html index 83640033bb9..81c671d5bb0 100644 --- a/src/addplugin.html +++ b/src/addplugin.html @@ -5,12 +5,11 @@
-

-

-

+

+

@@ -28,7 +27,6 @@

${HeaderInstall}

${ServerRestartNeededAfterPluginInstall}
-

@@ -37,9 +35,6 @@

${HeaderInstall}

diff --git a/src/addserver.html b/src/addserver.html index 763f56851b8..d25a8eef984 100644 --- a/src/addserver.html +++ b/src/addserver.html @@ -1,9 +1,9 @@
-
+

${HeaderConnectToServer}

- +
${LabelServerHostHelp}

diff --git a/src/apikeys.html b/src/apikeys.html index 6f766ae6c97..3cb7cd6de1d 100644 --- a/src/apikeys.html +++ b/src/apikeys.html @@ -4,18 +4,19 @@

${HeaderApiKeys}

${HeaderApiKeysHelp}


+ - - - - + + + + diff --git a/src/assets/audio/silence.mp3 b/src/assets/audio/silence.mp3 new file mode 100644 index 00000000000..29dbef21856 Binary files /dev/null and b/src/assets/audio/silence.mp3 differ diff --git a/src/assets/css/dashboard.css b/src/assets/css/dashboard.css index 894d7332f4e..48e6fe807e1 100644 --- a/src/assets/css/dashboard.css +++ b/src/assets/css/dashboard.css @@ -235,6 +235,15 @@ div[data-role=controlgroup] a.ui-btn-active { width: 50%; } +.localUsers .cardText-secondary { + white-space: pre-wrap; + height: 3em; +} + +.customCssContainer textarea { + resize: none; +} + @media all and (min-width: 70em) { .dashboardSections { -webkit-flex-wrap: wrap; diff --git a/src/assets/css/flexstyles.css b/src/assets/css/flexstyles.css index a5a479f2f54..429ed7a650e 100644 --- a/src/assets/css/flexstyles.css +++ b/src/assets/css/flexstyles.css @@ -30,6 +30,10 @@ align-items: flex-start; } +.align-items-flex-end { + align-items: flex-end; +} + .justify-content-center { justify-content: center; } @@ -38,6 +42,10 @@ justify-content: flex-end; } +.justify-content-space-between { + justify-content: space-between; +} + .flex-wrap-wrap { flex-wrap: wrap; } diff --git a/src/assets/css/ios.css b/src/assets/css/ios.css index 57de0c5fdd5..2b61f49a4e6 100644 --- a/src/assets/css/ios.css +++ b/src/assets/css/ios.css @@ -1,8 +1,3 @@ html { font-size: 82% !important; } - -.formDialogFooter { - position: static !important; - margin: 0 -1em !important; -} diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index e4b5bcf8d69..61815a590fd 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -21,7 +21,7 @@ } .libraryPage { - padding-top: 7em; + padding-top: 7em !important; } .itemDetailPage { @@ -128,10 +128,6 @@ margin-top: 0; } -.layout-mobile .pageTitleWithDefaultLogo { - background-image: url(../img/icon-transparent.png); -} - .headerLeft, .skinHeader { display: -webkit-box; @@ -164,6 +160,7 @@ display: flex; flex-direction: column; contain: layout style paint; + transition: background ease-in-out 0.5s; } .hiddenViewMenuBar .skinHeader { @@ -178,6 +175,10 @@ width: 100%; } +.layout-tv .sectionTabs { + width: 55%; +} + .selectedMediaFolder { background-color: #f2f2f2 !important; } @@ -246,6 +247,7 @@ } @media all and (min-width: 40em) { + .dashboardDocument .adminDrawerLogo, .dashboardDocument .mainDrawerButton { display: none !important; } @@ -271,7 +273,7 @@ } } -@media all and (max-width: 84em) { +@media all and (max-width: 100em) { .withSectionTabs .headerTop { padding-bottom: 0.55em; } @@ -279,9 +281,13 @@ .sectionTabs { font-size: 83.5%; } + + .layout-tv .sectionTabs { + width: 100%; + } } -@media all and (min-width: 84em) { +@media all and (min-width: 100em) { .headerTop { padding: 0.8em 0.8em; } @@ -313,7 +319,7 @@ } .dashboardDocument .mainDrawer-scrollContainer { - margin-top: 4.6em !important; + margin-top: 4.65em !important; } } @@ -437,12 +443,14 @@ background-repeat: no-repeat; background-position: center; background-attachment: fixed; - height: 50vh; + height: 40vh; position: relative; + animation: backdrop-fadein 800ms ease-in normal both; } .layout-mobile .itemBackdrop { background-attachment: scroll; + height: 26.5vh; } .layout-desktop .itemBackdrop::after, @@ -462,10 +470,20 @@ .detailPageContent { display: flex; flex-direction: column; - padding-left: 2%; + padding-left: 32.45vw; padding-right: 2%; } +.layout-mobile .detailPageContent { + padding-left: 5%; + padding-right: 5%; +} + +.layout-desktop .detailPageContent .emby-scroller, +.layout-tv .detailPageContent .emby-scroller { + margin-left: 0; +} + .layout-desktop .noBackdrop .detailPageContent, .layout-tv .noBackdrop .detailPageContent { margin-top: 2.5em; @@ -476,6 +494,10 @@ margin-top: 0; } +.detailSectionContent a { + color: inherit; +} + .personBackdrop { background-size: contain; } @@ -494,7 +516,23 @@ .parentName { display: block; - margin-bottom: 0.5em; + margin: 0 0 0; +} + +.layout-mobile .parentName { + margin: 0.6em 0 0; +} + +.musicParentName { + margin: 0.15em 0 0.2em; +} + +.layout-mobile .musicParentName { + margin: -0.25em 0 0.25em; +} + +.layout-mobile .itemExternalLinks { + display: none; } .mainDetailButtons { @@ -502,8 +540,6 @@ -webkit-box-align: center; -webkit-align-items: center; align-items: center; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; margin: 1em 0; } @@ -516,6 +552,42 @@ .itemName { margin: 0.5em 0; + font-weight: 600; +} + +.itemName.originalTitle { + margin: 0.2em 0 0.2em; +} + +.itemName.parentNameLast { + margin: 0 0 0; +} + +.layout-mobile .itemName.parentNameLast { + margin: 0.4em 0 0.4em; +} + +.layout-mobile h1.itemName, +.layout-mobile h1.parentName { + font-size: 1.6em; +} + +.itemName.parentNameLast.withOriginalTitle { + margin: 0 0 0; +} + +.layout-mobile .itemName.parentNameLast.withOriginalTitle { + margin: 0.6em 0 0; +} + +.layout-mobile .itemName.originalTitle { + margin: 0.5em 0 0.5em; +} + +.nameContainer { + display: flex; + flex-direction: column; + flex-wrap: wrap; } .itemMiscInfo { @@ -533,12 +605,24 @@ .layout-mobile .itemName, .layout-mobile .itemMiscInfo, .layout-mobile .mainDetailButtons { - display: flex; align-items: center; justify-content: center; text-align: center; } +.layout-mobile .mainDetailButtons { + margin-top: 1em; + margin-bottom: 0.5em; +} + +.subtitle { + margin: 0.15em 0 0.2em; +} + +.layout-mobile .subtitle { + margin: 0.2em 0 0.2em; +} + .detailPagePrimaryContainer { display: flex; align-items: center; @@ -549,7 +633,7 @@ .layout-mobile .detailPagePrimaryContainer { display: block; position: relative; - top: 0; + padding: 0.5em 3.3% 0.5em; } .layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, @@ -559,13 +643,14 @@ padding-left: 32.45vw; } -.layout-desktop .detailSticky, -.layout-tv .detailSticky { +.layout-desktop .detailRibbon, +.layout-tv .detailRibbon { margin-top: -7.2em; + height: 7.18em; } -.layout-desktop .noBackdrop .detailSticky, -.layout-tv .noBackdrop .detailSticky { +.layout-desktop .noBackdrop .detailRibbon, +.layout-tv .noBackdrop .detailRibbon { margin-top: 0; } @@ -575,9 +660,11 @@ .infoText { white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; text-align: left; + min-width: 0; + max-width: 100%; + overflow: hidden; } .layout-mobile .infoText { @@ -588,12 +675,29 @@ margin: 1.25em 0; } -.detailImageContainer { - position: relative; - margin-top: -25vh; +.layout-mobile .detailPageSecondaryContainer { + margin: 1em 0; +} + +.layout-mobile .detailImageContainer { + display: none; +} + +.detailImageContainer .card { + position: absolute; + top: 50%; float: left; width: 25vw; z-index: 3; + transform: translateY(-50%); +} + +.detailImageContainer .card.backdropCard { + top: 35%; +} + +.detailImageContainer .card.squareCard { + top: 40%; } .layout-desktop .noBackdrop .detailImageContainer, @@ -606,12 +710,11 @@ } .detailLogo { - width: 67.25vw; - height: 14.5vh; + width: 25vw; + height: 16vh; position: absolute; - top: 15vh; - right: 0; - -webkit-background-size: contain; + top: 10vh; + right: 25vw; background-size: contain; } @@ -619,26 +722,8 @@ display: none; } -@media all and (max-width: 87.5em) { - .detailLogo { - right: 5%; - } -} - -@media all and (max-width: 75em) { - .detailLogo { - right: 2%; - } -} - @media all and (max-width: 68.75em) { .detailLogo { - width: 14.91em; - height: 3.5em; - right: 5%; - bottom: 5%; - top: auto; - background-position: center right; display: none; } } @@ -654,15 +739,11 @@ div.itemDetailGalleryLink.defaultCardBackground { } .itemDetailGalleryLink.defaultCardBackground { - height: 23vw; /* Dirty hack to get it to look somewhat square. Less than ideal. */ -} - -.btnSyncComplete i { - -webkit-border-radius: 100em; - border-radius: 100em; + /* Dirty hack to get it to look somewhat square. Less than ideal. */ + height: 23vw; } -.itemDetailGalleryLink.defaultCardBackground > i { +.itemDetailGalleryLink.defaultCardBackground > .material-icons { font-size: 15vw; margin-top: 50%; transform: translateY(-50%); @@ -673,14 +754,19 @@ div.itemDetailGalleryLink.defaultCardBackground { position: relative; } + .layout-desktop .itemBackdrop, + .layout-tv .itemBackdrop { + height: 40vh; + } + .layout-desktop .detailPageWrapperContainer, .layout-tv .detailPageWrapperContainer { - margin-top: 7.2em; + margin-top: 0.1em; } - .layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, - .layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer { - padding-left: 3.3%; + .layout-desktop .detailImageContainer .card, + .layout-tv .detailImageContainer .card { + top: 10%; } .btnPlaySimple { @@ -694,37 +780,29 @@ div.itemDetailGalleryLink.defaultCardBackground { } } -.btnSyncComplete { - background: #673ab7 !important; -} - .emby-button.detailFloatingButton { position: absolute; - background-color: rgba(0, 0, 0, 0.5) !important; - z-index: 1; - top: 50%; - left: 50%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 3; + top: 100%; + left: 90%; margin: -2.2em 0 0 -2.2em; - padding: 0.4em !important; + padding: 0.4em; color: rgba(255, 255, 255, 0.76); } -.emby-button.detailFloatingButton i { +.emby-button.detailFloatingButton .material-icons { font-size: 3.5em; } @media all and (max-width: 62.5em) { .parentName { - margin-bottom: 1em; + margin-bottom: 0; } .itemDetailPage { padding-top: 0 !important; } - - .detailimg-hidemobile { - display: none; - } } @media all and (min-width: 31.25em) { @@ -819,9 +897,9 @@ div.itemDetailGalleryLink.defaultCardBackground { } .detailImageProgressContainer { - position: absolute; bottom: 0; - width: 22.786458333333332vw; + margin-top: -0.4vw; + width: 100%; } .detailButton-text { @@ -840,21 +918,7 @@ div.itemDetailGalleryLink.defaultCardBackground { } } -@media all and (min-width: 62.5em) { - .headerTop { - padding-left: 0.8em; - padding-right: 0.8em; - } - - .headerTabs { - align-self: center; - width: auto; - align-items: center; - justify-content: center; - margin-top: -4.2em; - position: relative; - } - +@media all and (min-width: 100em) { .detailFloatingButton { display: none !important; } @@ -888,6 +952,10 @@ div.itemDetailGalleryLink.defaultCardBackground { } } +.detailVerticalSection .emby-scrollbuttons { + padding-top: 0.4em; +} + .layout-tv .detailVerticalSection { margin-bottom: 3.4em !important; } @@ -976,6 +1044,10 @@ div.itemDetailGalleryLink.defaultCardBackground { margin-bottom: 2.7em; } +.layout-mobile .verticalSection-extrabottompadding { + margin-bottom: 1em; +} + .sectionTitleButton, .sectionTitleIconButton { margin-right: 0 !important; @@ -1001,7 +1073,13 @@ div.itemDetailGalleryLink.defaultCardBackground { div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { margin: 0; - padding-top: 1.25em; + padding-top: 0.5em; + padding-bottom: 0.2em; +} + +.layout-mobile :not(.sectionTitleContainer-cards) > .sectionTitle-cards { + margin: 0; + padding-top: 0.5em; } .sectionTitleButton { @@ -1154,6 +1232,12 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { .trackSelections .selectContainer .selectLabel { margin: 0 0.2em 0 0; + line-height: 1.75; +} + +.layout-mobile .detailsGroupItem .label, +.layout-mobile .trackSelections .selectContainer .selectLabel { + flex-basis: 4.5em; } .trackSelections .selectContainer .detailTrackSelect { @@ -1166,3 +1250,21 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { margin-top: 0; font-size: 1.4em; } + +.overview-controls { + display: flex; + justify-content: flex-end; +} + +.detail-clamp-text { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 12; + -webkit-box-orient: vertical; +} + +@media all and (min-width: 40em) { + .detail-clamp-text { + -webkit-line-clamp: 6; + } +} diff --git a/src/assets/css/scrollstyles.css b/src/assets/css/scrollstyles.css index 1cb3207e06c..67c6202252d 100644 --- a/src/assets/css/scrollstyles.css +++ b/src/assets/css/scrollstyles.css @@ -12,6 +12,7 @@ .hiddenScrollX, .layout-tv .scrollX { -ms-overflow-style: none; + scrollbar-width: none; } .hiddenScrollX-forced { @@ -40,6 +41,7 @@ .hiddenScrollY, .layout-tv .smoothScrollY { -ms-overflow-style: none; + scrollbar-width: none; /* Can't do this because it not only hides the scrollbar, but also prevents scrolling */ diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 67416663e72..1a8873ba974 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -5,9 +5,26 @@ html { height: 100%; } +.layout-mobile, +.layout-tv { + -webkit-touch-callout: none; + user-select: none; +} + +.clipForScreenReader { + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; +} + .material-icons { /* Fix font ligatures on older WebOS versions */ - -webkit-font-feature-settings: "liga"; + font-feature-settings: "liga"; } .backgroundContainer { @@ -23,16 +40,6 @@ html { line-height: 1.35; } -.layout-mobile, -.layout-tv { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - body { overflow-x: hidden; background-color: transparent !important; @@ -96,3 +103,29 @@ div[data-role=page] { margin-right: auto; width: 85%; } + +.headroom { + will-change: transform; + transition: transform 200ms linear; +} + +.headroom--pinned { + transform: translateY(0%); +} + +.headroom--unpinned { + transform: translateY(-100%); +} + +.drawerContent { + /* make sure the bottom of the drawer is visible when music is playing */ + padding-bottom: 4em; +} + +.force-scroll { + overflow-y: scroll; +} + +.hide-scroll { + overflow-y: hidden; +} diff --git a/src/assets/css/videoosd.css b/src/assets/css/videoosd.css index f4f198325b9..50cb41021b0 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -30,7 +30,7 @@ opacity: 0; } -.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) { +.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) { display: none; } diff --git a/src/bundle.js b/src/bundle.js index 40e4853e66c..83b2f162f18 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -4,105 +4,238 @@ // Use define from require.js not webpack's define var _define = window.define; -// document-register-element -var docRegister = require("document-register-element"); -_define("document-register-element", function() { - return docRegister; +// fetch +var fetch = require('whatwg-fetch'); +_define('fetch', function() { + return fetch; }); -// fetch -var fetch = require("whatwg-fetch"); -_define("fetch", function() { - return fetch +// Blurhash +var blurhash = require('blurhash'); +_define('blurhash', function() { + return blurhash; }); // query-string -var query = require("query-string"); -_define("queryString", function() { +var query = require('query-string'); +_define('queryString', function() { return query; }); // flvjs -var flvjs = require("flv.js/dist/flv").default; -_define("flvjs", function() { +var flvjs = require('flv.js/dist/flv').default; +_define('flvjs', function() { return flvjs; }); // jstree -var jstree = require("jstree"); -require("jstree/dist/themes/default/style.css"); -_define("jstree", function() { +var jstree = require('jstree'); +require('jstree/dist/themes/default/style.css'); +_define('jstree', function() { return jstree; }); // jquery -var jquery = require("jquery"); -_define("jQuery", function() { +var jquery = require('jquery'); +_define('jQuery', function() { return jquery; }); // hlsjs -var hlsjs = require("hls.js"); -_define("hlsjs", function() { +var hlsjs = require('hls.js'); +_define('hlsjs', function() { return hlsjs; }); // howler -var howler = require("howler"); -_define("howler", function() { +var howler = require('howler'); +_define('howler', function() { return howler; }); // resize-observer-polyfill -var resize = require("resize-observer-polyfill").default; -_define("resize-observer-polyfill", function() { +var resize = require('resize-observer-polyfill').default; +_define('resize-observer-polyfill', function() { return resize; }); -// shaka -var shaka = require("shaka-player"); -_define("shaka", function() { - return shaka; -}); - // swiper -var swiper = require("swiper/js/swiper"); -require("swiper/css/swiper.min.css"); -_define("swiper", function() { +var swiper = require('swiper/js/swiper'); +require('swiper/css/swiper.min.css'); +_define('swiper', function() { return swiper; }); // sortable -var sortable = require("sortablejs").default; -_define("sortable", function() { +var sortable = require('sortablejs').default; +_define('sortable', function() { return sortable; }); // webcomponents -var webcomponents = require("webcomponents.js/webcomponents-lite"); -_define("webcomponents", function() { +var webcomponents = require('webcomponents.js/webcomponents-lite'); +_define('webcomponents', function() { return webcomponents; }); +// shaka +var shaka = require('shaka-player'); +_define('shaka', function() { + return shaka; +}); + // material-icons -var material_icons = require("material-design-icons-iconfont/dist/material-design-icons.css"); -_define("material-icons", function() { - return material_icons; +var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); +_define('material-icons', function() { + return materialIcons; }); // noto font -var noto = require("jellyfin-noto"); -_define("jellyfin-noto", function () { +var noto = require('jellyfin-noto'); +_define('jellyfin-noto', function () { return noto; }); +var epubjs = require('epubjs'); +_define('epubjs', function () { + return epubjs; +}); + // page.js -var page = require("page"); -_define("page", function() { +var page = require('page'); +_define('page', function() { return page; }); -var polyfill = require("@babel/polyfill/dist/polyfill"); -_define("polyfill", function () { +// core-js +var polyfill = require('@babel/polyfill/dist/polyfill'); +_define('polyfill', function () { return polyfill; }); + +// domtokenlist-shim +var classlist = require('classlist.js'); +_define('classlist-polyfill', function () { + return classlist; +}); + +// Date-FNS +var dateFns = require('date-fns'); +_define('date-fns', function () { + return dateFns; +}); + +var dateFnsLocale = require('date-fns/locale'); +_define('date-fns/locale', function () { + return dateFnsLocale; +}); + +var fast_text_encoding = require('fast-text-encoding'); +_define('fast-text-encoding', function () { + return fast_text_encoding; +}); + +// intersection-observer +var intersection_observer = require('intersection-observer'); +_define('intersection-observer', function () { + return intersection_observer; +}); + +// screenfull +var screenfull = require('screenfull'); +_define('screenfull', function () { + return screenfull; +}); + +// headroom.js +var headroom = require('headroom.js/dist/headroom'); +_define('headroom', function () { + return headroom; +}); + +// apiclient +var apiclient = require('jellyfin-apiclient'); + +// BEGIN Patches for MPV Shim +// Oh Dear... I didn't think this could get worse, but here we are... +(function() { + let oldLogout = apiclient.ApiClient.prototype.logout; + let oldAuthenticateUserByName = apiclient.ApiClient.prototype.authenticateUserByName; + let oldOpenWebSocket = apiclient.ApiClient.prototype.openWebSocket; + + apiclient.ApiClient.prototype.logout = function() { + // Logout Callback + var xhr = new XMLHttpRequest(); + xhr.open('POST', "/destroy_session", true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send("{}"); + + return oldLogout.bind(this)(); + } + + apiclient.ApiClient.prototype.authenticateUserByName = function(name, password) { + // Password Provider + var xhr = new XMLHttpRequest(); + xhr.open('POST', "/mpv_shim_password", true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.onloadend = function (result) { + var res = JSON.parse(result.target.response); + if (!res.success) alert("MPV Shim Login Failed"); + }; + xhr.send(JSON.stringify({ + server: this.serverAddress(), + username: name, + password: password + })); + + return oldAuthenticateUserByName.bind(this)(name, password); + } + + apiclient.ApiClient.prototype.openWebSocket = function() { + oldOpenWebSocket.bind(this)(); + let oldOnOpen = this._webSocket.onopen; + function onOpen() { + // Auto-Connect + window.require(["playbackManager"], function(playbackManager) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', "/mpv_shim_id", true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.onloadend = function (result) { + var res = JSON.parse(result.target.response); + playbackManager.getTargets().then(function (targets) { + for (var i = 0; i < targets.length; i++) { + if (targets[i].appName == res.appName && + targets[i].deviceName == res.deviceName) + playbackManager.trySetActivePlayer(targets[i].playerName, targets[i]); + } + }); + }; + xhr.send("{}"); + }); + + oldOnOpen(); + } + this._webSocket.onopen = onOpen; + }; +})() +// END Patches for MPV Shim + +_define('apiclient', function () { + return apiclient.ApiClient; +}); + +_define('events', function () { + return apiclient.Events; +}); + +_define('credentialprovider', function () { + return apiclient.Credentials; +}); + +_define('connectionManagerFactory', function () { + return apiclient.ConnectionManager; +}); + +_define('appStorage', function () { + return apiclient.AppStorage; +}); diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js new file mode 100644 index 00000000000..166460a025b --- /dev/null +++ b/src/components/accessSchedule/accessSchedule.js @@ -0,0 +1,98 @@ +/* eslint-disable indent */ + +/** + * Module for controlling user parental control from. + * @module components/accessSchedule/accessSchedule + */ + +import dialogHelper from 'dialogHelper'; +import datetime from 'datetime'; +import globalize from 'globalize'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'formDialogStyle'; + + function getDisplayTime(hours) { + let minutes = 0; + const pct = hours % 1; + + if (pct) { + minutes = parseInt(60 * pct); + } + + return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); + } + + function populateHours(context) { + let html = ''; + + for (let i = 0; i < 24; i++) { + html += ``; + } + + 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)) { + return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd')); + } + + context.submitted = true; + options.schedule = Object.assign(options.schedule, updatedSchedule); + dialogHelper.close(context); + } + + export function show(options) { + return new Promise((resolve, reject) => { + // TODO: remove require + require(['text!./components/accessSchedule/accessSchedule.template.html'], template => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + size: 'small' + }); + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateDocument(template); + dlg.innerHTML = html; + populateHours(dlg); + loadSchedule(dlg, options.schedule); + dialogHelper.open(dlg); + dlg.addEventListener('close', () => { + if (dlg.submitted) { + resolve(options.schedule); + } else { + reject(); + } + }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + dlg.querySelector('form').addEventListener('submit', event => { + submitSchedule(dlg, options); + event.preventDefault(); + return false; + }); + }); + }); + } + +/* eslint-enable indent */ + +export default { + show: show +}; diff --git a/src/components/accessschedule/accessschedule.template.html b/src/components/accessSchedule/accessSchedule.template.html similarity index 94% rename from src/components/accessschedule/accessschedule.template.html rename to src/components/accessSchedule/accessSchedule.template.html index 02f11942edc..493150ae5e8 100644 --- a/src/components/accessschedule/accessschedule.template.html +++ b/src/components/accessSchedule/accessSchedule.template.html @@ -1,6 +1,6 @@
-

${HeaderAccessSchedule} diff --git a/src/components/accessschedule/accessschedule.js b/src/components/accessschedule/accessschedule.js deleted file mode 100644 index 2f4be8b2a73..00000000000 --- a/src/components/accessschedule/accessschedule.js +++ /dev/null @@ -1,89 +0,0 @@ -define(["dialogHelper", "datetime", "emby-select", "paper-icon-button-light", "formDialogStyle"], function (dialogHelper, datetime) { - "use strict"; - - function getDisplayTime(hours) { - var minutes = 0; - var pct = hours % 1; - - if (pct) { - minutes = parseInt(60 * pct); - } - - return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); - } - - function populateHours(context) { - var html = ""; - - for (var i = 0; i < 24; i++) { - html += '"; - } - - html += '"; - context.querySelector("#selectStart").innerHTML = html; - context.querySelector("#selectEnd").innerHTML = html; - } - - function loadSchedule(context, schedule) { - context.querySelector("#selectDay").value = schedule.DayOfWeek || "Sunday"; - context.querySelector("#selectStart").value = schedule.StartHour || 0; - context.querySelector("#selectEnd").value = schedule.EndHour || 0; - } - - function submitSchedule(context, options) { - var updatedSchedule = { - DayOfWeek: context.querySelector("#selectDay").value, - StartHour: context.querySelector("#selectStart").value, - EndHour: context.querySelector("#selectEnd").value - }; - - if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { - return void alert(Globalize.translate("ErrorMessageStartHourGreaterThanEnd")); - } - - context.submitted = true; - options.schedule = Object.assign(options.schedule, updatedSchedule); - dialogHelper.close(context); - } - - return { - show: function (options) { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", "components/accessschedule/accessschedule.template.html", true); - - xhr.onload = function (e) { - var template = this.response; - var dlg = dialogHelper.createDialog({ - removeOnClose: true, - size: "small" - }); - dlg.classList.add("formDialog"); - var html = ""; - html += Globalize.translateDocument(template); - dlg.innerHTML = html; - populateHours(dlg); - loadSchedule(dlg, options.schedule); - dialogHelper.open(dlg); - dlg.addEventListener("close", function () { - if (dlg.submitted) { - resolve(options.schedule); - } else { - reject(); - } - }); - dlg.querySelector(".btnCancel").addEventListener("click", function (e) { - dialogHelper.close(dlg); - }); - dlg.querySelector("form").addEventListener("submit", function (e) { - submitSchedule(dlg, options); - e.preventDefault(); - return false; - }); - }; - - xhr.send(); - }); - } - }; -}); diff --git a/src/components/actionsheet/actionsheet.css b/src/components/actionSheet/actionSheet.css similarity index 100% rename from src/components/actionsheet/actionsheet.css rename to src/components/actionSheet/actionSheet.css diff --git a/src/components/actionSheet/actionSheet.js b/src/components/actionSheet/actionSheet.js new file mode 100644 index 00000000000..2e221119459 --- /dev/null +++ b/src/components/actionSheet/actionSheet.js @@ -0,0 +1,340 @@ +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import dom from 'dom'; +import 'emby-button'; +import 'css!./actionSheet'; +import 'material-icons'; +import 'scrollStyles'; +import 'listViewStyle'; + +function getOffsets(elems) { + + let results = []; + + if (!document) { + return results; + } + + for (const elem of elems) { + let box = elem.getBoundingClientRect(); + + results.push({ + top: box.top, + left: box.left, + width: box.width, + height: box.height + }); + } + + return results; +} + +function getPosition(options, dlg) { + + const windowSize = dom.getWindowSize(); + const windowHeight = windowSize.innerHeight; + const windowWidth = windowSize.innerWidth; + + let pos = getOffsets([options.positionTo])[0]; + + if (options.positionY !== 'top') { + pos.top += (pos.height || 0) / 2; + } + + pos.left += (pos.width || 0) / 2; + + const height = dlg.offsetHeight || 300; + const width = dlg.offsetWidth || 160; + + // Account for popup size + pos.top -= height / 2; + pos.left -= width / 2; + + // Avoid showing too close to the bottom + const overflowX = pos.left + width - windowWidth; + const overflowY = pos.top + height - windowHeight; + + if (overflowX > 0) { + pos.left -= (overflowX + 20); + } + if (overflowY > 0) { + pos.top -= (overflowY + 20); + } + + pos.top += (options.offsetTop || 0); + pos.left += (options.offsetLeft || 0); + + // Do some boundary checking + pos.top = Math.max(pos.top, 10); + pos.left = Math.max(pos.left, 10); + + return pos; +} + +function centerFocus(elem, horiz, on) { + require(['scrollHelper'], function (scrollHelper) { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +export function show(options) { + + // items + // positionTo + // showCancel + // title + let dialogOptions = { + removeOnClose: true, + enableHistory: options.enableHistory, + scrollY: false + }; + + let isFullscreen; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + isFullscreen = true; + dialogOptions.autoFocus = true; + } else { + + dialogOptions.modal = false; + dialogOptions.entryAnimation = options.entryAnimation; + dialogOptions.exitAnimation = options.exitAnimation; + dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140; + dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100; + dialogOptions.autoFocus = false; + } + + let dlg = dialogHelper.createDialog(dialogOptions); + + if (isFullscreen) { + dlg.classList.add('actionsheet-fullscreen'); + } else { + dlg.classList.add('actionsheet-not-fullscreen'); + } + + dlg.classList.add('actionSheet'); + + if (options.dialogClass) { + dlg.classList.add(options.dialogClass); + } + + let html = ''; + + const scrollClassName = layoutManager.tv ? 'scrollY smoothScrollY hiddenScrollY' : 'scrollY'; + let style = ''; + + // Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation + if (options.items.length > 20) { + const minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200; + style += 'min-width:' + minWidth + 'px;'; + } + + let renderIcon = false; + let icons = []; + let itemIcon; + for (const item of options.items) { + + itemIcon = item.icon || (item.selected ? 'check' : null); + + if (itemIcon) { + renderIcon = true; + } + icons.push(itemIcon || ''); + } + + if (layoutManager.tv) { + html += ``; + } + + // If any items have an icon, give them all an icon just to make sure they're all lined up evenly + const center = options.title && (!renderIcon /*|| itemsWithIcons.length != options.items.length*/); + + if (center || layoutManager.tv) { + html += '
'; + } else { + html += '
'; + } + + if (options.title) { + + html += '

' + options.title + '

'; + } + if (options.text) { + html += '

' + options.text + '

'; + } + + let scrollerClassName = 'actionSheetScroller'; + if (layoutManager.tv) { + scrollerClassName += ' actionSheetScroller-tv focuscontainer-x focuscontainer-y'; + } + html += '
'; + + let menuItemClass = 'listItem listItem-button actionSheetMenuItem'; + + if (options.border || options.shaded) { + menuItemClass += ' listItem-border'; + } + + if (options.menuItemClass) { + menuItemClass += ' ' + options.menuItemClass; + } + + if (layoutManager.tv) { + menuItemClass += ' listItem-focusscale'; + } + + if (layoutManager.mobile) { + menuItemClass += ' actionsheet-xlargeFont'; + } + + // 'options.items' is HTMLOptionsCollection, so no fancy loops + for (let i = 0; i < options.items.length; i++) { + const item = options.items[i]; + + if (item.divider) { + + html += '
'; + continue; + } + + const autoFocus = item.selected && layoutManager.tv ? ' autoFocus' : ''; + + // Check for null in case int 0 was passed in + const optionId = item.id == null || item.id === '' ? item.value : item.id; + html += ''; + + itemIcon = icons[i]; + + if (itemIcon) { + html += ``; + } else if (renderIcon && !center) { + html += ''; + } + + html += '
'; + + html += '
'; + html += (item.name || item.textContent || item.innerText); + html += '
'; + + if (item.secondaryText) { + html += `
${item.secondaryText}
`; + } + + html += '
'; + + if (item.asideText) { + html += `
${item.asideText}
`; + } + + html += ''; + } + + if (options.showCancel) { + html += '
'; + html += ``; + html += '
'; + } + html += '
'; + + dlg.innerHTML = html; + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.actionSheetScroller'), false, true); + } + + let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); + if (btnCloseActionSheet) { + btnCloseActionSheet.addEventListener('click', function () { + dialogHelper.close(dlg); + }); + } + + // Seeing an issue in some non-chrome browsers where this is requiring a double click + //var eventName = browser.firefox ? 'mousedown' : 'click'; + let selectedId; + + let timeout; + if (options.timeout) { + timeout = setTimeout(function () { + dialogHelper.close(dlg); + }, options.timeout); + } + + return new Promise(function (resolve, reject) { + + let isResolved; + + dlg.addEventListener('click', function (e) { + + const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem'); + + if (actionSheetMenuItem) { + selectedId = actionSheetMenuItem.getAttribute('data-id'); + + if (options.resolveOnClick) { + + if (options.resolveOnClick.indexOf) { + + if (options.resolveOnClick.indexOf(selectedId) !== -1) { + + resolve(selectedId); + isResolved = true; + } + + } else { + resolve(selectedId); + isResolved = true; + } + } + + dialogHelper.close(dlg); + } + + }); + + dlg.addEventListener('close', function () { + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.actionSheetScroller'), false, false); + } + + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + if (!isResolved) { + if (selectedId != null) { + if (options.callback) { + options.callback(selectedId); + } + + resolve(selectedId); + } else { + reject(); + } + } + }); + + dialogHelper.open(dlg); + + const pos = options.positionTo && dialogOptions.size !== 'fullscreen' ? getPosition(options, dlg) : null; + + if (pos) { + dlg.style.position = 'fixed'; + dlg.style.margin = 0; + dlg.style.left = pos.left + 'px'; + dlg.style.top = pos.top + 'px'; + } + }); +} + +export default { + show: show +}; diff --git a/src/components/actionsheet/actionsheet.js b/src/components/actionsheet/actionsheet.js deleted file mode 100644 index 926d63b637a..00000000000 --- a/src/components/actionsheet/actionsheet.js +++ /dev/null @@ -1,360 +0,0 @@ -define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-button', 'css!./actionsheet', 'material-icons', 'scrollStyles', 'listViewStyle'], function (dialogHelper, layoutManager, globalize, browser, dom) { - 'use strict'; - - function getOffsets(elems) { - - var doc = document; - var results = []; - - if (!doc) { - return results; - } - - var box; - var elem; - - for (var i = 0, length = elems.length; i < length; i++) { - - elem = elems[i]; - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - box = elem.getBoundingClientRect(); - } else { - box = { top: 0, left: 0 }; - } - - results[i] = { - top: box.top, - left: box.left, - width: box.width, - height: box.height - }; - } - - return results; - } - - function getPosition(options, dlg) { - - var windowSize = dom.getWindowSize(); - var windowHeight = windowSize.innerHeight; - var windowWidth = windowSize.innerWidth; - - var pos = getOffsets([options.positionTo])[0]; - - if (options.positionY !== 'top') { - pos.top += (pos.height || 0) / 2; - } - - pos.left += (pos.width || 0) / 2; - - var height = dlg.offsetHeight || 300; - var width = dlg.offsetWidth || 160; - - // Account for popup size - pos.top -= height / 2; - pos.left -= width / 2; - - // Avoid showing too close to the bottom - var overflowX = pos.left + width - windowWidth; - var overflowY = pos.top + height - windowHeight; - - if (overflowX > 0) { - pos.left -= (overflowX + 20); - } - if (overflowY > 0) { - pos.top -= (overflowY + 20); - } - - pos.top += (options.offsetTop || 0); - pos.left += (options.offsetLeft || 0); - - // Do some boundary checking - pos.top = Math.max(pos.top, 10); - pos.left = Math.max(pos.left, 10); - - return pos; - } - - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } - - function show(options) { - - // items - // positionTo - // showCancel - // title - var dialogOptions = { - removeOnClose: true, - enableHistory: options.enableHistory, - scrollY: false - }; - - var backButton = false; - var isFullscreen; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - isFullscreen = true; - backButton = true; - dialogOptions.autoFocus = true; - } else { - - dialogOptions.modal = false; - dialogOptions.entryAnimation = options.entryAnimation; - dialogOptions.exitAnimation = options.exitAnimation; - dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140; - dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100; - dialogOptions.autoFocus = false; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - if (isFullscreen) { - dlg.classList.add('actionsheet-fullscreen'); - } else { - dlg.classList.add('actionsheet-not-fullscreen'); - } - - dlg.classList.add('actionSheet'); - - if (options.dialogClass) { - dlg.classList.add(options.dialogClass); - } - - var html = ''; - - var scrollClassName = layoutManager.tv ? 'scrollY smoothScrollY hiddenScrollY' : 'scrollY'; - var style = ''; - - // Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation - if (options.items.length > 20) { - var minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200; - style += "min-width:" + minWidth + "px;"; - } - - var i; - var length; - var option; - var renderIcon = false; - var icons = []; - var itemIcon; - for (i = 0, length = options.items.length; i < length; i++) { - - option = options.items[i]; - - itemIcon = option.icon || (option.selected ? 'check' : null); - - if (itemIcon) { - renderIcon = true; - } - icons.push(itemIcon || ''); - } - - if (layoutManager.tv) { - html += ''; - } - - // If any items have an icon, give them all an icon just to make sure they're all lined up evenly - var center = options.title && (!renderIcon /*|| itemsWithIcons.length != options.items.length*/); - - if (center || layoutManager.tv) { - html += '
'; - } else { - html += '
'; - } - - if (options.title) { - - html += '

'; - html += options.title; - html += '

'; - } - if (options.text) { - html += '

'; - html += options.text; - html += '

'; - } - - var scrollerClassName = 'actionSheetScroller'; - if (layoutManager.tv) { - scrollerClassName += ' actionSheetScroller-tv focuscontainer-x focuscontainer-y'; - } - html += '
'; - - var menuItemClass = 'listItem listItem-button actionSheetMenuItem'; - - if (options.border || options.shaded) { - menuItemClass += ' listItem-border'; - } - - if (options.menuItemClass) { - menuItemClass += ' ' + options.menuItemClass; - } - - if (layoutManager.tv) { - menuItemClass += ' listItem-focusscale'; - } - - if (layoutManager.mobile) { - menuItemClass += ' actionsheet-xlargeFont'; - } - - for (i = 0, length = options.items.length; i < length; i++) { - - option = options.items[i]; - - if (option.divider) { - - html += '
'; - continue; - } - - var autoFocus = option.selected && layoutManager.tv ? ' autoFocus' : ''; - - // Check for null in case int 0 was passed in - var optionId = option.id == null || option.id === '' ? option.value : option.id; - html += ''; - - itemIcon = icons[i]; - - if (itemIcon) { - - html += '' + itemIcon + ''; - } else if (renderIcon && !center) { - html += ''; - } - - html += '
'; - - html += '
'; - html += (option.name || option.textContent || option.innerText); - html += '
'; - - if (option.secondaryText) { - html += '
'; - html += option.secondaryText; - html += '
'; - } - - html += '
'; - - if (option.asideText) { - html += '
'; - html += option.asideText; - html += '
'; - } - - html += ''; - } - - if (options.showCancel) { - html += '
'; - html += ''; - html += '
'; - } - html += '
'; - - dlg.innerHTML = html; - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.actionSheetScroller'), false, true); - } - - var btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); - if (btnCloseActionSheet) { - dlg.querySelector('.btnCloseActionSheet').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - } - - // Seeing an issue in some non-chrome browsers where this is requiring a double click - //var eventName = browser.firefox ? 'mousedown' : 'click'; - var selectedId; - - var timeout; - if (options.timeout) { - timeout = setTimeout(function () { - dialogHelper.close(dlg); - }, options.timeout); - } - - return new Promise(function (resolve, reject) { - - var isResolved; - - dlg.addEventListener('click', function (e) { - - var actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem'); - - if (actionSheetMenuItem) { - selectedId = actionSheetMenuItem.getAttribute('data-id'); - - if (options.resolveOnClick) { - - if (options.resolveOnClick.indexOf) { - - if (options.resolveOnClick.indexOf(selectedId) !== -1) { - - resolve(selectedId); - isResolved = true; - } - - } else { - resolve(selectedId); - isResolved = true; - } - } - - dialogHelper.close(dlg); - } - - }); - - dlg.addEventListener('close', function () { - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.actionSheetScroller'), false, false); - } - - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - - if (!isResolved) { - if (selectedId != null) { - if (options.callback) { - options.callback(selectedId); - } - - resolve(selectedId); - } else { - reject(); - } - } - }); - - dialogHelper.open(dlg); - - var pos = options.positionTo && dialogOptions.size !== 'fullscreen' ? getPosition(options, dlg) : null; - - if (pos) { - dlg.style.position = 'fixed'; - dlg.style.margin = 0; - dlg.style.left = pos.left + 'px'; - dlg.style.top = pos.top + 'px'; - } - }); - } - - return { - show: show - }; -}); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 05971f01b82..bbb0995063d 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -1,60 +1,63 @@ -define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) { - "use strict"; +define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', 'serverNotifications', 'connectionManager', 'emby-button', 'listViewStyle'], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) { + 'use strict'; function getEntryHtml(entry, apiClient) { - var html = ""; + var html = ''; html += '
'; - var color = "#00a4dc"; - var icon = "notifications"; + var color = '#00a4dc'; + var icon = 'notifications'; - if ("Error" == entry.Severity || "Fatal" == entry.Severity || "Warn" == entry.Severity) { - color = "#cc0000"; - icon = "notification_important"; + if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) { + color = '#cc0000'; + icon = 'notification_important'; } if (entry.UserId && entry.UserPrimaryImageTag) { - html += 'dvr" + }) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">"; } else { - html += '' + icon + ''; + html += ''; } html += '
'; html += '
'; html += entry.Name; - html += "
"; + html += '
'; html += '
'; - var date = datetime.parseISO8601Date(entry.Date, true); - html += datetime.toLocaleString(date).toLowerCase(); - html += "
"; + html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() }); + html += '
'; html += '
'; - html += entry.ShortOverview || ""; - html += "
"; - html += "
"; + html += entry.ShortOverview || ''; + html += '
'; + html += '
'; if (entry.Overview) { - html += ''; + html += ``; } - return html += "
"; + html += '

'; + + return html; } function renderList(elem, apiClient, result, startIndex, limit) { elem.innerHTML = result.Items.map(function (i) { return getEntryHtml(i, apiClient); - }).join(""); + }).join(''); } function reloadData(instance, elem, apiClient, startIndex, limit) { if (null == startIndex) { - startIndex = parseInt(elem.getAttribute("data-activitystartindex") || "0"); + startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0'); } - limit = limit || parseInt(elem.getAttribute("data-activitylimit") || "7"); + limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7'); var minDate = new Date(); - var hasUserId = "false" !== elem.getAttribute("data-useractivity"); + var hasUserId = 'false' !== elem.getAttribute('data-useractivity'); if (hasUserId) { minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back @@ -62,22 +65,22 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back } - ApiClient.getJSON(ApiClient.getUrl("System/ActivityLog/Entries", { + 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); + elem.setAttribute('data-activitystartindex', startIndex); + elem.setAttribute('data-activitylimit', limit); if (!startIndex) { - var activityContainer = dom.parentWithClass(elem, "activityContainer"); + var activityContainer = dom.parentWithClass(elem, 'activityContainer'); if (activityContainer) { if (result.Items.length) { - activityContainer.classList.remove("hide"); + activityContainer.classList.remove('hide'); } else { - activityContainer.classList.add("hide"); + activityContainer.classList.add('hide'); } } } @@ -96,10 +99,10 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific } function onListClick(e) { - var btnEntryInfo = dom.parentWithClass(e.target, "btnEntryInfo"); + var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); if (btnEntryInfo) { - var id = btnEntryInfo.getAttribute("data-id"); + var id = btnEntryInfo.getAttribute('data-id'); var items = this.items; if (items) { @@ -115,7 +118,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific } function showItemOverview(item) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert({ text: item.Overview }); @@ -125,28 +128,28 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific function ActivityLog(options) { this.options = options; var element = options.element; - element.classList.add("activityLogListWidget"); - element.addEventListener("click", onListClick.bind(this)); + element.classList.add('activityLogListWidget'); + element.addEventListener('click', onListClick.bind(this)); var apiClient = connectionManager.getApiClient(options.serverId); reloadData(this, element, apiClient); var onUpdate = onActivityLogUpdate.bind(this); this.updateFn = onUpdate; - events.on(serverNotifications, "ActivityLogEntry", onUpdate); - apiClient.sendMessage("ActivityLogEntryStart", "0,1500"); + events.on(serverNotifications, 'ActivityLogEntry', onUpdate); + apiClient.sendMessage('ActivityLogEntryStart', '0,1500'); } ActivityLog.prototype.destroy = function () { var options = this.options; if (options) { - options.element.classList.remove("activityLogListWidget"); - connectionManager.getApiClient(options.serverId).sendMessage("ActivityLogEntryStop", "0,1500"); + options.element.classList.remove('activityLogListWidget'); + connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500'); } var onUpdate = this.updateFn; if (onUpdate) { - events.off(serverNotifications, "ActivityLogEntry", onUpdate); + events.off(serverNotifications, 'ActivityLogEntry', onUpdate); } this.items = null; diff --git a/src/components/alphapicker/alphapicker.js b/src/components/alphaPicker/alphaPicker.js similarity index 95% rename from src/components/alphapicker/alphapicker.js rename to src/components/alphaPicker/alphaPicker.js index 455a43b46d3..79f74879e5a 100644 --- a/src/components/alphapicker/alphapicker.js +++ b/src/components/alphaPicker/alphaPicker.js @@ -67,7 +67,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b html += '
'; if (options.mode === 'keyboard') { - html += ''; + html += ''; } else { letters = ['#']; html += mapLetters(letters, vertical).join(''); @@ -77,7 +77,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b html += mapLetters(letters, vertical).join(''); if (options.mode === 'keyboard') { - html += ''; + html += ''; html += '
'; letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; @@ -132,7 +132,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b if (alphaPickerButton) { var value = alphaPickerButton.getAttribute('data-value'); - element.dispatchEvent(new CustomEvent("alphavalueclicked", { + element.dispatchEvent(new CustomEvent('alphavalueclicked', { cancelable: false, detail: { value: value @@ -262,7 +262,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b } if (applyValue) { - element.dispatchEvent(new CustomEvent("alphavaluechanged", { + element.dispatchEvent(new CustomEvent('alphavaluechanged', { cancelable: false, detail: { value: value diff --git a/src/components/alphapicker/style.css b/src/components/alphaPicker/style.css similarity index 100% rename from src/components/alphapicker/style.css rename to src/components/alphaPicker/style.css diff --git a/src/components/appfooter/appfooter.css b/src/components/appFooter/appFooter.css similarity index 100% rename from src/components/appfooter/appfooter.css rename to src/components/appFooter/appFooter.css diff --git a/src/components/appfooter/appfooter.js b/src/components/appFooter/appFooter.js similarity index 93% rename from src/components/appfooter/appfooter.js rename to src/components/appFooter/appFooter.js index 07d7701ff25..033a0b008d3 100644 --- a/src/components/appfooter/appfooter.js +++ b/src/components/appFooter/appFooter.js @@ -1,4 +1,4 @@ -define(['browser', 'css!./appfooter'], function (browser) { +define(['browser', 'css!./appFooter'], function (browser) { 'use strict'; function render(options) { diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 62bfb3cf403..58c64e7d43c 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -1,4 +1,4 @@ -define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinManager', 'pluginManager', 'backdrop', 'browser', 'page', 'appSettings', 'apphost', 'connectionManager'], function (loading, globalize, events, viewManager, layoutManager, skinManager, pluginManager, backdrop, browser, page, appSettings, appHost, connectionManager) { +define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdrop', 'browser', 'page', 'appSettings', 'apphost', 'connectionManager'], function (loading, globalize, events, viewManager, skinManager, backdrop, browser, page, appSettings, appHost, connectionManager) { 'use strict'; var appRouter = { @@ -16,7 +16,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM show('/settings/settings.html'); }, showNowPlaying: function () { - show("/nowplaying.html"); + show('/nowplaying.html'); } }; @@ -26,11 +26,11 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM connectionManager.connect({ enableAutoLogin: appSettings.enableAutoLogin() }).then(function (result) { - handleConnectionResult(result, loading); + handleConnectionResult(result); }); } - function handleConnectionResult(result, loading) { + function handleConnectionResult(result) { switch (result.State) { case 'SignedIn': loading.hide(); @@ -200,8 +200,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM var apiClient = this; - if (data.status === 401) { - if (data.errorCode === "ParentalControl") { + if (data.status === 403) { + if (data.errorCode === 'ParentalControl') { var isCurrentAllowed = currentRouteInfo ? (currentRouteInfo.route.anonymous || currentRouteInfo.route.startup) : true; @@ -222,52 +222,18 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } function normalizeImageOptions(options) { - var scaleFactor = browser.tv ? 0.8 : 1; - var setQuality; - if (options.maxWidth) { - options.maxWidth = Math.round(options.maxWidth * scaleFactor); - setQuality = true; - } - - if (options.width) { - options.width = Math.round(options.width * scaleFactor); - setQuality = true; - } - - if (options.maxHeight) { - options.maxHeight = Math.round(options.maxHeight * scaleFactor); - setQuality = true; - } - - if (options.height) { - options.height = Math.round(options.height * scaleFactor); + if (options.maxWidth || options.width || options.maxHeight || options.height) { setQuality = true; } - if (setQuality) { - - var quality = 100; - - var type = options.type || 'Primary'; - - if (browser.tv || browser.slow) { - - if (browser.chrome) { - // webp support - quality = type === 'Primary' ? 40 : 50; - } else { - quality = type === 'Backdrop' ? 60 : 50; - } - } else { - quality = type === 'Backdrop' ? 70 : 90; - } - - options.quality = quality; + if (setQuality && !options.quality) { + options.quality = 90; } } function getMaxBandwidth() { + /* eslint-disable compat/compat */ if (navigator.connection) { var max = navigator.connection.downlinkMax; if (max && max > 0 && max < Number.POSITIVE_INFINITY) { @@ -279,6 +245,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM return max; } } + /* eslint-enable compat/compat */ return null; } @@ -382,7 +349,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM if (firstResult.State !== 'SignedIn' && !route.anonymous) { - handleConnectionResult(firstResult, loading); + handleConnectionResult(firstResult); return; } } @@ -461,7 +428,6 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM return Promise.resolve(); } - var isHandlingBackToDefault; var isDummyBackToHome; function loadContent(ctx, route, html, request) { @@ -539,15 +505,15 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } function param(name, url) { - name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); - var regexS = "[\\?&]" + name + "=([^&#]*)"; - var regex = new RegExp(regexS, "i"); + name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS, 'i'); var results = regex.exec(url || getWindowLocationSearch()); if (results == null) { - return ""; + return ''; } else { - return decodeURIComponent(results[1].replace(/\+/g, " ")); + return decodeURIComponent(results[1].replace(/\+/g, ' ')); } } @@ -577,8 +543,9 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM function showDirect(path) { return new Promise(function(resolve, reject) { - resolveOnNextShow = resolve, page.show(baseUrl()+path) - }) + resolveOnNextShow = resolve; + page.show(baseUrl() + path); + }); } function show(path, options) { @@ -586,8 +553,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM path = '/' + path; } - var baseRoute = baseUrl(); - path = path.replace(baseRoute, ''); + path = path.replace(baseUrl(), ''); if (currentRouteInfo && currentRouteInfo.path === path) { // can't use this with home right now due to the back menu @@ -618,10 +584,11 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } function showItem(item, serverId, options) { + // TODO: Refactor this so it only gets items, not strings. if (typeof (item) === 'string') { var apiClient = serverId ? connectionManager.getApiClient(serverId) : connectionManager.currentApiClient(); - apiClient.getItem(apiClient.getCurrentUserId(), item).then(function (item) { - appRouter.showItem(item, options); + apiClient.getItem(apiClient.getCurrentUserId(), item).then(function (itemObject) { + appRouter.showItem(itemObject, options); }); } else { if (arguments.length === 2) { diff --git a/src/components/apphost.js b/src/components/apphost.js index 5470803846a..f200b9a6426 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -1,17 +1,17 @@ -define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSettings, browser, events, htmlMediaHelper) { - "use strict"; +define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) { + 'use strict'; function getBaseProfileOptions(item) { var disableHlsVideoAudioCodecs = []; if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { - if (browser.edge || browser.msie) { - disableHlsVideoAudioCodecs.push("mp3"); + if (browser.edge) { + disableHlsVideoAudioCodecs.push('mp3'); } - disableHlsVideoAudioCodecs.push("ac3"); - disableHlsVideoAudioCodecs.push("eac3"); - disableHlsVideoAudioCodecs.push("opus"); + disableHlsVideoAudioCodecs.push('ac3'); + disableHlsVideoAudioCodecs.push('eac3'); + disableHlsVideoAudioCodecs.push('opus'); } return { @@ -22,7 +22,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet function getDeviceProfileForWindowsUwp(item) { return new Promise(function (resolve, reject) { - require(["browserdeviceprofile", "environments/windows-uwp/mediacaps"], function (profileBuilder, uwpMediaCaps) { + require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) { var profileOptions = getBaseProfileOptions(item); profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(); @@ -40,26 +40,15 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet } return new Promise(function (resolve) { - require(["browserdeviceprofile"], function (profileBuilder) { + require(['browserdeviceprofile'], function (profileBuilder) { var profile; if (window.NativeShell) { profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); } else { - profile = profileBuilder(getBaseProfileOptions(item)); - - if (item && !options.isRetry && "allcomplexformats" !== appSettings.get("subtitleburnin")) { - if (!browser.orsay && !browser.tizen) { - profile.SubtitleProfiles.push({ - Format: "ass", - Method: "External" - }); - profile.SubtitleProfiles.push({ - Format: "ssa", - Method: "External" - }); - } - } + var builderOpts = getBaseProfileOptions(item); + builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin')); + profile = profileBuilder(builderOpts); } resolve(profile); @@ -68,12 +57,12 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet } function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } function replaceAll(originalString, strReplace, strWith) { var strReplace2 = escapeRegExp(strReplace); - var reg = new RegExp(strReplace2, "ig"); + var reg = new RegExp(strReplace2, 'ig'); return originalString.replace(reg, strWith); } @@ -81,7 +70,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet var keys = []; if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { - var result = replaceAll(btoa(keys.join("|")), "=", "1"); + var result = replaceAll(btoa(keys.join('|')), '=', '1'); return Promise.resolve(result); } @@ -89,7 +78,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet } function getDeviceId() { - var key = "_deviceId2"; + var key = '_deviceId2'; var deviceId = appSettings.get(key); if (deviceId) { @@ -104,18 +93,36 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet function getDeviceName() { var deviceName; - deviceName = browser.tizen ? "Samsung Smart TV" : browser.web0s ? "LG Smart TV" : browser.operaTv ? "Opera TV" : browser.xboxOne ? "Xbox One" : browser.ps4 ? "Sony PS4" : browser.chrome ? "Chrome" : browser.edge ? "Edge" : browser.firefox ? "Firefox" : browser.msie ? "Internet Explorer" : browser.opera ? "Opera" : browser.safari ? "Safari" : "Web Browser"; + if (browser.tizen) { + deviceName = 'Samsung Smart TV'; + } else if (browser.web0s) { + deviceName = 'LG Smart TV'; + } else if (browser.operaTv) { + deviceName = 'Opera TV'; + } else if (browser.xboxOne) { + deviceName = 'Xbox One'; + } else if (browser.ps4) { + deviceName = 'Sony PS4'; + } else if (browser.chrome) { + deviceName = 'Chrome'; + } else if (browser.edge) { + deviceName = 'Edge'; + } else if (browser.firefox) { + deviceName = 'Firefox'; + } else if (browser.opera) { + deviceName = 'Opera'; + } else if (browser.safari) { + deviceName = 'Safari'; + } else { + deviceName = 'Web Browser'; + } if (browser.ipad) { - deviceName += " iPad"; - } else { - if (browser.iphone) { - deviceName += " iPhone"; - } else { - if (browser.android) { - deviceName += " Android"; - } - } + deviceName += ' iPad'; + } else if (browser.iphone) { + deviceName += ' iPhone'; + } else if (browser.android) { + deviceName += ' Android'; } return deviceName; @@ -135,12 +142,12 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet } var element = document.documentElement; - return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement("video").webkitEnterFullscreen; + return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; } function getSyncProfile() { return new Promise(function (resolve) { - require(["browserdeviceprofile", "appSettings"], function (profileBuilder, appSettings) { + require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) { var profile; if (window.NativeShell) { @@ -156,7 +163,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet } function getDefaultLayout() { - return "desktop"; + return 'desktop'; } function supportsHtmlMediaAutoplay() { @@ -173,20 +180,20 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet function supportsCue() { try { - var video = document.createElement("video"); - var style = document.createElement("style"); + var video = document.createElement('video'); + var style = document.createElement('style'); - style.textContent = "video::cue {background: inherit}"; + style.textContent = 'video::cue {background: inherit}'; document.body.appendChild(style); document.body.appendChild(video); - var cue = window.getComputedStyle(video, "::cue").background; + var cue = window.getComputedStyle(video, '::cue').background; document.body.removeChild(style); document.body.removeChild(video); return !!cue.length; } catch (err) { - console.error("error detecting cue support: " + err); + console.error('error detecting cue support: ' + err); return false; } } @@ -194,15 +201,15 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet function onAppVisible() { if (isHidden) { isHidden = false; - console.debug("triggering app resume event"); - events.trigger(appHost, "resume"); + console.debug('triggering app resume event'); + events.trigger(appHost, 'resume'); } } function onAppHidden() { if (!isHidden) { isHidden = true; - console.debug("app is hidden"); + console.debug('app is hidden'); } } @@ -210,90 +217,88 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet var features = []; if (navigator.share) { - features.push("sharing"); + features.push('sharing'); } if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { - features.push("filedownload"); + features.push('filedownload'); } if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { - features.push("exit"); + features.push('exit'); } else { - features.push("exitmenu"); - features.push("plugins"); + features.push('exitmenu'); + features.push('plugins'); } if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { - features.push("externallinks"); - features.push("externalpremium"); + features.push('externallinks'); + features.push('externalpremium'); } if (!browser.operaTv) { - features.push("externallinkdisplay"); + features.push('externallinkdisplay'); } if (supportsVoiceInput()) { - features.push("voiceinput"); - } - - if (!browser.tv && !browser.xboxOne) { - browser.ps4; + features.push('voiceinput'); } if (supportsHtmlMediaAutoplay()) { - features.push("htmlaudioautoplay"); - features.push("htmlvideoautoplay"); + features.push('htmlaudioautoplay'); + features.push('htmlvideoautoplay'); } if (browser.edgeUwp) { - features.push("sync"); + features.push('sync'); } if (supportsFullscreen()) { - features.push("fullscreenchange"); + features.push('fullscreenchange'); } if (browser.chrome || browser.edge && !browser.slow) { if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) { - features.push("imageanalysis"); + features.push('imageanalysis'); } } if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { - features.push("physicalvolumecontrol"); + features.push('physicalvolumecontrol'); } if (!browser.tv && !browser.xboxOne && !browser.ps4) { - features.push("remotecontrol"); + features.push('remotecontrol'); } if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { - features.push("remotevideo"); + features.push('remotevideo'); } - features.push("displaylanguage"); - features.push("otherapppromotions"); - features.push("displaymode"); - features.push("targetblank"); - // allows users to connect to more than one server - //features.push("multiserver"); - features.push("screensaver"); + features.push('displaylanguage'); + features.push('otherapppromotions'); + features.push('displaymode'); + features.push('targetblank'); + features.push('screensaver'); + + webSettings.enableMultiServer().then(enabled => { + if (enabled) features.push('multiserver'); + }); - if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { - features.push("subtitleappearancesettings"); + if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { + features.push('subtitleappearancesettings'); } if (!browser.orsay) { - features.push("subtitleburnsettings"); + features.push('subtitleburnsettings'); } if (!browser.tv && !browser.ps4 && !browser.xboxOne) { - features.push("fileinput"); + features.push('fileinput'); } if (browser.chrome) { - features.push("chromecast"); + features.push('chromecast'); } return features; @@ -314,7 +319,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet window.close(); } } catch (err) { - console.error("error closing application: " + err); + console.error('error closing application: ' + err); } } @@ -328,15 +333,15 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet return; } - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { exitPromise = actionsheet.show({ - title: Globalize.translate("MessageConfirmAppExit"), + title: globalize.translate('MessageConfirmAppExit'), items: [ - {id: "yes", name: Globalize.translate("Yes")}, - {id: "no", name: Globalize.translate("No")} + {id: 'yes', name: globalize.translate('Yes')}, + {id: 'no', name: globalize.translate('No')} ] }).then(function (value) { - if (value === "yes") { + if (value === 'yes') { doExit(); } }).finally(function () { @@ -347,17 +352,15 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet var deviceId; var deviceName; - var appName = "Jellyfin Web"; - var appVersion = "10.5.0"; - var visibilityChange; - var visibilityState; + var appName = 'Jellyfin Web'; + var appVersion = '10.6.0'; var appHost = { getWindowState: function () { - return document.windowState || "Normal"; + return document.windowState || 'Normal'; }, setWindowState: function (state) { - alert("setWindowState is not supported and should not be called"); + alert('setWindowState is not supported and should not be called'); }, exit: function () { if (!!window.appMode && browser.tizen) { @@ -374,14 +377,13 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet return -1 !== supportedFeatures.indexOf(command.toLowerCase()); }, preferVisualCards: browser.android || browser.chrome, - moreIcon: browser.android ? "more_vert" : "more_horiz", getSyncProfile: getSyncProfile, getDefaultLayout: function () { if (window.NativeShell) { return window.NativeShell.AppHost.getDefaultLayout(); } - return getDefaultLayout() + return getDefaultLayout(); }, getDeviceProfile: getDeviceProfile, init: function () { @@ -410,58 +412,44 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet return {}; }, setThemeColor: function (color) { - var metaThemeColor = document.querySelector("meta[name=theme-color]"); + var metaThemeColor = document.querySelector('meta[name=theme-color]'); if (metaThemeColor) { - metaThemeColor.setAttribute("content", color); + metaThemeColor.setAttribute('content', color); } }, setUserScalable: function (scalable) { if (!browser.tv) { - var att = scalable ? "width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes" : "width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"; - document.querySelector("meta[name=viewport]").setAttribute("content", att); + var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; + document.querySelector('meta[name=viewport]').setAttribute('content', att); } } }; - var doc = self.document; var isHidden = false; + var hidden; + var visibilityChange; - if (doc) { - if (void 0 !== doc.visibilityState) { - visibilityChange = "visibilitychange"; - visibilityState = "hidden"; - } else { - if (void 0 !== doc.mozHidden) { - visibilityChange = "mozvisibilitychange"; - visibilityState = "mozVisibilityState"; - } else { - if (void 0 !== doc.msHidden) { - visibilityChange = "msvisibilitychange"; - visibilityState = "msVisibilityState"; - } else { - if (void 0 !== doc.webkitHidden) { - visibilityChange = "webkitvisibilitychange"; - visibilityState = "webkitVisibilityState"; - } - } - } - } + if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */ + hidden = 'hidden'; + visibilityChange = 'visibilitychange'; + } else if (typeof document.webkitHidden !== 'undefined') { + hidden = 'webkitHidden'; + visibilityChange = 'webkitvisibilitychange'; } - if (doc) { - doc.addEventListener(visibilityChange, function () { - if (document[visibilityState]) { - onAppHidden(); - } else { - onAppVisible(); - } - }); - } + document.addEventListener(visibilityChange, function () { + /* eslint-disable-next-line compat/compat */ + if (document[hidden]) { + onAppHidden(); + } else { + onAppVisible(); + } + }, false); if (self.addEventListener) { - self.addEventListener("focus", onAppVisible); - self.addEventListener("blur", onAppHidden); + self.addEventListener('focus', onAppVisible); + self.addEventListener('blur', onAppHidden); } return appHost; diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index 6d99009e676..0a10cabd2f4 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -1,71 +1,73 @@ -define(["focusManager", "layoutManager"], function (focusManager, layoutManager) { - "use strict"; +/* eslint-disable indent */ + +/** + * Module for performing auto-focus. + * @module components/autoFocuser + */ + +import focusManager from 'focusManager'; +import layoutManager from 'layoutManager'; /** * Previously selected element. */ - var activeElement; + let activeElement; /** - * Returns true if AutoFocuser is enabled. + * Returns _true_ if AutoFocuser is enabled. */ - function isEnabled() { + export function isEnabled() { return layoutManager.tv; } /** - * Start AutoFocuser + * Start AutoFocuser. */ - function enable() { + export function enable() { if (!isEnabled()) { return; } - window.addEventListener("focusin", function (e) { + window.addEventListener('focusin', function (e) { activeElement = e.target; }); - console.debug("AutoFocuser enabled"); - } - - /** - * Create an array from some source. - */ - var arrayFrom = Array.prototype.from || function (src) { - return Array.prototype.slice.call(src); + 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. */ - function autoFocus(container) { + export function autoFocus(container) { if (!isEnabled()) { - return; + return null; } container = container || document.body; - var candidates = []; + 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")); + 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(arrayFrom(container.querySelectorAll(".btnResume"))); - candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnPlay"))); + candidates = candidates.concat(Array.from(container.querySelectorAll('.btnResume'))); + candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay'))); - var focusedElement; + let focusedElement; candidates.every(function (element) { if (focusManager.isCurrentlyFocusable(element)) { @@ -79,7 +81,7 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) if (!focusedElement) { // FIXME: Multiple itemsContainers - var itemsContainer = container.querySelector(".itemsContainer"); + const itemsContainer = container.querySelector('.itemsContainer'); if (itemsContainer) { focusedElement = focusManager.autoFocus(itemsContainer); @@ -93,9 +95,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) return focusedElement; } - return { - isEnabled: isEnabled, - enable: enable, - autoFocus: autoFocus - }; -}); +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + enable: enable, + autoFocus: autoFocus +}; diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index ec5b411853c..c15e35524c3 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -1,4 +1,4 @@ -define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings", 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) { +define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) { 'use strict'; function enableAnimation(elem) { @@ -180,7 +180,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings" if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { return item.BackdropImageTags.map(function (imgTag, index) { return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { - type: "Backdrop", + type: 'Backdrop', tag: imgTag, maxWidth: dom.getScreenWidth(), index: index @@ -191,7 +191,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings" if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { return item.ParentBackdropImageTags.map(function (imgTag, index) { return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { - type: "Backdrop", + type: 'Backdrop', tag: imgTag, maxWidth: dom.getScreenWidth(), index: index diff --git a/src/components/backdropscreensaver/plugin.js b/src/components/backdropscreensaver/plugin.js deleted file mode 100644 index c0bd31fb86f..00000000000 --- a/src/components/backdropscreensaver/plugin.js +++ /dev/null @@ -1,56 +0,0 @@ -define(["connectionManager"], function (connectionManager) { - - return function () { - - var self = this; - - self.name = "Backdrop ScreenSaver"; - self.type = "screensaver"; - self.id = "backdropscreensaver"; - self.supportsAnonymous = false; - - var currentSlideshow; - - self.show = function () { - - var query = { - ImageTypes: "Backdrop", - EnableImageTypes: "Backdrop", - IncludeItemTypes: "Movie,Series,MusicArtist", - SortBy: "Random", - Recursive: true, - Fields: "Taglines", - ImageTypeLimit: 1, - StartIndex: 0, - Limit: 200 - }; - - var apiClient = connectionManager.currentApiClient(); - apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) { - - if (result.Items.length) { - - require(["slideshow"], function (slideshow) { - - var newSlideShow = new slideshow({ - showTitle: true, - cover: true, - items: result.Items - }); - - newSlideShow.show(); - currentSlideshow = newSlideShow; - }); - } - }); - }; - - self.hide = function () { - - if (currentSlideshow) { - currentSlideshow.hide(); - currentSlideshow = null; - } - }; - } -}); diff --git a/src/components/cardbuilder/card.css b/src/components/cardbuilder/card.css index 3cd038cd090..ef5ea6604c6 100644 --- a/src/components/cardbuilder/card.css +++ b/src/components/cardbuilder/card.css @@ -167,8 +167,9 @@ button::-moz-focus-inner { position: relative; background-clip: content-box !important; color: inherit; +} - /* This is only needed for scalable cards */ +.cardScalable .cardImageContainer { height: 100%; contain: strict; } @@ -192,38 +193,37 @@ button::-moz-focus-inner { /* Needed in case this is a button */ display: block; - - /* Needed in case this is a button */ margin: 0 !important; - - /* Needed in safari */ - height: 100%; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - outline: none !important; - contain: strict; -} - -.cardContent-button { border: 0 !important; padding: 0 !important; cursor: pointer; color: inherit; width: 100%; - vertical-align: middle; font-family: inherit; font-size: inherit; + + /* Needed in safari */ + height: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + outline: none !important; + contain: strict; } -.cardContent-button:not(.defaultCardBackground) { +.cardContent:not(.defaultCardBackground) { background-color: transparent; } +.cardBox:not(.visualCardBox) .cardPadder { + background-color: #242424; +} + .visualCardBox .cardContent { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.cardContent-shadow { +.cardContent-shadow, +.cardBox:not(.visualCardBox) .cardPadder { box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); } @@ -306,6 +306,10 @@ button::-moz-focus-inner { text-align: left; } +.dialog .cardText { + text-overflow: initial; +} + .cardText-secondary { font-size: 86%; } diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 9d86bc9d7c8..d192bed7f0c 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -286,7 +286,7 @@ import 'programStyles'; * @param {Object} options - Options for handling the items. */ function setCardData(items, options) { - options.shape = options.shape || "auto"; + options.shape = options.shape || 'auto'; const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); @@ -368,9 +368,7 @@ import 'programStyles'; let apiClient; let lastServerId; - for (let i = 0; i < items.length; i++) { - - let item = items[i]; + for (const [i, item] of items.entries()) { let serverId = item.ServerId || options.serverId; if (serverId !== lastServerId) { @@ -503,94 +501,49 @@ import 'programStyles'; const primaryImageAspectRatio = item.PrimaryImageAspectRatio; let forceName = false; let imgUrl = null; + let imgTag = null; let coverImage = false; let uiAspect = null; + let imgType = null; + let itemId = null; if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: width, - tag: item.ImageTags.Thumb - }); - + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Banner", - maxWidth: width, - tag: item.ImageTags.Banner - }); - + imgType = 'Banner'; + imgTag = item.ImageTags.Banner; } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Disc", - maxWidth: width, - tag: item.ImageTags.Disc - }); - + imgType = 'Disc'; + imgTag = item.ImageTags.Disc; } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Logo", - maxWidth: width, - tag: item.ImageTags.Logo - }); - + imgType = 'Logo'; + imgTag = item.ImageTags.Logo; } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { - - imgUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { - type: "Logo", - maxWidth: width, - tag: item.ParentLogoImageTag - }); - + imgType = 'Logo'; + imgTag = item.ParentLogoImageTag; + itemId = item.ParentLogoItemId; } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { - - imgUrl = apiClient.getScaledImageUrl(item.SeriesId, { - type: "Thumb", - maxWidth: width, - tag: item.SeriesThumbImageTag - }); - + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { - - imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, { - type: "Thumb", - maxWidth: width, - tag: item.ParentThumbImageTag - }); - + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Backdrop", - maxWidth: width, - tag: item.BackdropImageTags[0] - }); - + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; forceName = true; - } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { - - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: "Backdrop", - maxWidth: width, - tag: item.ParentBackdropImageTags[0] - }); - - } else if (item.ImageTags && item.ImageTags.Primary) { - + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { + imgType = 'Primary'; + imgTag = item.ImageTags.Primary; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Primary", - maxHeight: height, - maxWidth: width, - tag: item.ImageTags.Primary - }); - if (options.preferThumb && options.showTitle !== false) { forceName = true; } @@ -601,18 +554,16 @@ import 'programStyles'; coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; } } - + } else if (item.SeriesPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; } else if (item.PrimaryImageTag) { - + imgType = 'Primary'; + imgTag = item.PrimaryImageTag; + itemId = item.PrimaryImageItemId; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, { - type: "Primary", - maxHeight: height, - maxWidth: width, - tag: item.PrimaryImageTag - }); - if (options.preferThumb && options.showTitle !== false) { forceName = true; } @@ -624,30 +575,15 @@ import 'programStyles'; } } } else if (item.ParentPrimaryImageTag) { - - imgUrl = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, { - type: "Primary", - maxWidth: width, - tag: item.ParentPrimaryImageTag - }); - } else if (item.SeriesPrimaryImageTag) { - - imgUrl = apiClient.getScaledImageUrl(item.SeriesId, { - type: "Primary", - maxWidth: width, - tag: item.SeriesPrimaryImageTag - }); + imgType = 'Primary'; + imgTag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - + imgType = 'Primary'; + imgTag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - imgUrl = apiClient.getScaledImageUrl(item.AlbumId, { - type: "Primary", - maxHeight: height, - maxWidth: width, - tag: item.AlbumPrimaryImageTag - }); - if (primaryImageAspectRatio) { uiAspect = getDesiredAspect(shape); if (uiAspect) { @@ -655,57 +591,46 @@ import 'programStyles'; } } } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: width, - tag: item.ImageTags.Thumb - }); - + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; } else if (item.BackdropImageTags && item.BackdropImageTags.length) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Backdrop", - maxWidth: width, - tag: item.BackdropImageTags[0] - }); - + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; } else if (item.ImageTags && item.ImageTags.Thumb) { - - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: width, - tag: item.ImageTags.Thumb - }); - + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { - - imgUrl = apiClient.getScaledImageUrl(item.SeriesId, { - type: "Thumb", - maxWidth: width, - tag: item.SeriesThumbImageTag - }); - + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; } else if (item.ParentThumbItemId && options.inheritThumb !== false) { - - imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, { - type: "Thumb", - maxWidth: width, - tag: item.ParentThumbImageTag - }); - + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: "Backdrop", + if (!itemId) { + itemId = item.Id; + } + + if (imgTag && imgType) { + imgUrl = apiClient.getScaledImageUrl(itemId, { + type: imgType, + maxHeight: height, maxWidth: width, - tag: item.ParentBackdropImageTags[0] + tag: imgTag }); - } + let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; + return { imgUrl: imgUrl, + blurhash: (blurHashes[imgType] || {})[imgTag], forceName: forceName, coverImage: coverImage }; @@ -778,7 +703,7 @@ import 'programStyles'; if (text) { html += "
"; html += text; - html += "
"; + html += ''; valid++; if (maxLines && valid >= maxLines) { @@ -835,7 +760,7 @@ import 'programStyles'; airTimeText += ' - ' + datetime.getDisplayTime(date); } } catch (e) { - console.error("error parsing date: " + item.StartDate); + console.error('error parsing date: ' + item.StartDate); } } @@ -869,11 +794,11 @@ import 'programStyles'; if (isOuterFooter && options.cardLayout && layoutManager.mobile) { if (options.cardFooterAside !== 'none') { - html += ''; + html += ''; } } - const cssClass = options.centerText ? "cardText cardTextCentered" : "cardText"; + const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; const serverId = item.ServerId || options.serverId; let lines = []; @@ -907,7 +832,7 @@ import 'programStyles'; } } else { - const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ""; + const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; if (parentTitle || showTitle) { lines.push(parentTitle); @@ -946,7 +871,7 @@ import 'programStyles'; item.AlbumArtists[0].IsFolder = true; lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); } else { - lines.push(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || "")); + lines.push(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')); } } @@ -993,7 +918,7 @@ import 'programStyles'; if (options.showYear || options.showSeriesYear) { if (item.Type === 'Series') { - if (item.Status === "Continuing") { + if (item.Status === 'Continuing') { lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || '')); @@ -1082,11 +1007,7 @@ import 'programStyles'; if (options.showPersonRoleOrType) { if (item.Role) { - lines.push('as ' + item.Role); - } else if (item.Type) { - lines.push(globalize.translate('' + item.Type)); - } else { - lines.push(''); + lines.push(globalize.translate('PersonRole', item.Role)); } } } @@ -1109,7 +1030,7 @@ import 'programStyles'; html = '
' + html; //cardFooter - html += "
"; + html += ''; } } @@ -1195,7 +1116,7 @@ import 'programStyles'; counts.push(childText); } - } else if (item.Type === 'MusicGenre' || options.context === "MusicArtist") { + } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { if (item.AlbumCount) { @@ -1308,7 +1229,7 @@ import 'programStyles'; } if (options.cardClass) { - className += " " + options.cardClass; + className += ' ' + options.cardClass; } if (layoutManager.desktop) { @@ -1325,6 +1246,7 @@ import 'programStyles'; const imgInfo = getCardImageUrl(item, apiClient, options, shape); const imgUrl = imgInfo.imgUrl; + const blurhash = imgInfo.blurhash; const forceName = imgInfo.forceName; @@ -1360,13 +1282,13 @@ import 'programStyles'; if (options.showChannelLogo && item.ChannelPrimaryImageTag) { logoUrl = apiClient.getScaledImageUrl(item.ChannelId, { - type: "Primary", + type: 'Primary', height: logoHeight, tag: item.ChannelPrimaryImageTag }); } else if (options.showLogo && item.ParentLogoImageTag) { logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { - type: "Logo", + type: 'Logo', height: logoHeight, tag: item.ParentLogoImageTag }); @@ -1388,7 +1310,7 @@ import 'programStyles'; } const mediaSourceCount = item.MediaSourceCount || 1; - if (mediaSourceCount > 1) { + if (mediaSourceCount > 1 && options.disableIndicators !== true) { innerCardFooter += '
' + mediaSourceCount + '
'; } @@ -1422,15 +1344,15 @@ import 'programStyles'; const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction'; if (options.centerPlayButton) { - overlayButtons += ''; + overlayButtons += ''; } if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') { - overlayButtons += ''; + overlayButtons += ''; } if (options.overlayMoreButton) { - overlayButtons += ''; + overlayButtons += ''; } } @@ -1445,57 +1367,60 @@ import 'programStyles'; let cardScalableClose = ''; let cardContentClass = 'cardContent'; - if (!options.cardLayout) { - cardContentClass += ' cardContent-shadow'; + + 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 ? ('
') : ('
'); + cardImageContainerOpen = imgUrl ? ('
') : ('
'); cardImageContainerClose = '
'; } else { // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? (''; } let cardScalableClass = 'cardScalable'; - cardImageContainerOpen = '
' + cardImageContainerOpen; + cardImageContainerOpen = '
' + cardImageContainerOpen; cardBoxClose = '
'; cardScalableClose = '
'; - let indicatorsHtml = ''; + if (options.disableIndicators !== true) { + let indicatorsHtml = ''; - if (options.missingIndicator !== false) { - indicatorsHtml += indicators.getMissingIndicator(item); - } + if (options.missingIndicator !== false) { + indicatorsHtml += indicators.getMissingIndicator(item); + } - indicatorsHtml += indicators.getSyncIndicator(item); - indicatorsHtml += indicators.getTimerIndicator(item); + indicatorsHtml += indicators.getSyncIndicator(item); + indicatorsHtml += indicators.getTimerIndicator(item); - indicatorsHtml += indicators.getTypeIndicator(item); + indicatorsHtml += indicators.getTypeIndicator(item); - if (options.showGroupCount) { + if (options.showGroupCount) { - indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { - minCount: 1 - }); - } else { - indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - } + indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { + minCount: 1 + }); + } else { + indicatorsHtml += indicators.getPlayedIndicatorHtml(item); + } - if (item.Type === 'CollectionFolder' || item.CollectionType) { - const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; - indicatorsHtml += '
'; - requireRefreshIndicator(); - } + if (item.Type === 'CollectionFolder' || item.CollectionType) { + const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; + indicatorsHtml += '
'; + requireRefreshIndicator(); + } - if (indicatorsHtml) { - cardImageContainerOpen += '
' + indicatorsHtml + '
'; + if (indicatorsHtml) { + cardImageContainerOpen += '
' + indicatorsHtml + '
'; + } } if (!imgUrl) { @@ -1522,7 +1447,7 @@ import 'programStyles'; let actionAttribute; if (tagName === 'button') { - className += " itemAction"; + className += ' itemAction'; actionAttribute = ' data-action="' + action + '"'; } else { actionAttribute = ''; @@ -1543,8 +1468,8 @@ import 'programStyles'; let additionalCardContent = ''; - if (layoutManager.desktop) { - additionalCardContent += getHoverMenuHtml(item, action); + if (layoutManager.desktop && !options.disableHoverMenu) { + additionalCardContent += getHoverMenuHtml(item, action, options); } return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; @@ -1554,9 +1479,10 @@ import 'programStyles'; * Generates HTML markup for the card overlay. * @param {object} item - Item used to generate the card overlay. * @param {string} action - Action assigned to the overlay. + * @param {Array} options - Card builder options. * @returns {string} HTML markup of the card overlay. */ - function getHoverMenuHtml(item, action) { + function getHoverMenuHtml(item, action, options) { let html = ''; html += '
'; @@ -1564,7 +1490,7 @@ import 'programStyles'; const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light'; if (playbackManager.canPlay(item)) { - html += ''; + html += ''; } html += '
'; @@ -1573,7 +1499,7 @@ import 'programStyles'; if (itemHelper.canMarkPlayed(item)) { require(['emby-playstatebutton']); - html += ''; + html += ''; } if (itemHelper.canRate(item)) { @@ -1581,11 +1507,10 @@ import 'programStyles'; const likes = userData.Likes == null ? '' : userData.Likes; require(['emby-ratingbutton']); - html += ''; + html += ''; } - html += ''; - + html += ''; html += '
'; html += '
'; @@ -1600,27 +1525,35 @@ import 'programStyles'; */ export function getDefaultText(item, options) { if (item.CollectionType) { - return ''; + return ''; } switch (item.Type) { case 'MusicAlbum': - return 'album'; + return ''; case 'MusicArtist': case 'Person': - return 'person'; + return ''; + case 'Audio': + return ''; case 'Movie': - return 'movie'; + return ''; case 'Series': - return 'tv'; + return ''; case 'Book': - return 'book'; + return ''; case 'Folder': - return 'folder'; + return ''; + case 'BoxSet': + return ''; + case 'Playlist': + return ''; + case 'PhotoAlbum': + return ''; } if (options && options.defaultCardImageIcon) { - return '' + options.defaultCardImageIcon + ''; + return ''; } const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item); @@ -1722,7 +1655,7 @@ import 'programStyles'; indicatorsElem = ensureIndicators(card, indicatorsElem); indicatorsElem.appendChild(playedIndicator); } - playedIndicator.innerHTML = 'check'; + playedIndicator.innerHTML = ''; } else { playedIndicator = card.querySelector('.playedIndicator'); @@ -1812,7 +1745,7 @@ import 'programStyles'; const icon = cell.querySelector('.timerIndicator'); if (!icon) { const indicatorsElem = ensureIndicators(cell); - indicatorsElem.insertAdjacentHTML('beforeend', ''); + indicatorsElem.insertAdjacentHTML('beforeend', ''); } cell.setAttribute('data-timerid', newTimerId); } @@ -1853,3 +1786,16 @@ import 'programStyles'; cell.removeAttribute('data-seriestimerid'); } } + +/* eslint-enable indent */ + +export default { + getCardsHtml: getCardsHtml, + getDefaultBackgroundClass: getDefaultBackgroundClass, + getDefaultText: getDefaultText, + buildCards: buildCards, + onUserDataChanged: onUserDataChanged, + onTimerCreated: onTimerCreated, + onTimerCancelled: onTimerCancelled, + onSeriesTimerCancelled: onSeriesTimerCancelled +}; diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 16326b6c59d..c6ee9ba3c54 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -1,13 +1,23 @@ -define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browser'], function (datetime, imageLoader, connectionManager, layoutManager, browser) { - 'use strict'; +/* eslint-disable indent */ - var enableFocusTransform = !browser.slow && !browser.edge; +/** + * Module for building cards from item data. + * @module components/cardBuilder/chaptercardbuilder + */ - function buildChapterCardsHtml(item, chapters, options) { +import datetime from 'datetime'; +import imageLoader from 'imageLoader'; +import connectionManager from 'connectionManager'; +import layoutManager from 'layoutManager'; +import browser from 'browser'; + + const enableFocusTransform = !browser.slow && !browser.edge; + + function buildChapterCardsHtml(item, chapters, options) { // TODO move card creation code to Card component - var className = 'card itemAction chapterCard'; + let className = 'card itemAction chapterCard'; if (layoutManager.tv) { className += ' show-focus'; @@ -17,12 +27,12 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse } } - var mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; - var videoStream = mediaStreams.filter(function (i) { - return i.Type === 'Video'; + const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; + const videoStream = mediaStreams.filter(({Type}) => { + return Type === 'Video'; })[0] || {}; - var shape = (options.backdropShape || 'backdrop'); + let shape = (options.backdropShape || 'backdrop'); if (videoStream.Width && videoStream.Height) { @@ -31,24 +41,24 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse } } - className += ' ' + shape + 'Card'; + className += ` ${shape}Card`; if (options.block || options.rows) { className += ' block'; } - var html = ''; - var itemsInRow = 0; + let html = ''; + let itemsInRow = 0; - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); - for (var i = 0, length = chapters.length; i < length; i++) { + for (let i = 0, length = chapters.length; i < length; i++) { if (options.rows && itemsInRow === 0) { html += '
'; } - var chapter = chapters[i]; + const chapter = chapters[i]; html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); itemsInRow++; @@ -62,50 +72,50 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse return html; } - function getImgUrl(item, chapter, index, maxWidth, apiClient) { + function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) { - if (chapter.ImageTag) { + if (ImageTag) { - return apiClient.getScaledImageUrl(item.Id, { + return apiClient.getScaledImageUrl(Id, { maxWidth: maxWidth * 2, - tag: chapter.ImageTag, - type: "Chapter", - index: index + tag: ImageTag, + type: 'Chapter', + index }); } return null; } - function buildChapterCard(item, apiClient, chapter, index, options, className, shape) { + function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) { - var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient); + const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient); - var cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; - if (options.coverImage) { + let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; + if (coverImage) { cardImageContainerClass += ' coveredImage'; } - var dataAttributes = ' data-action="play" data-isfolder="' + item.IsFolder + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-type="' + item.Type + '" data-mediatype="' + item.MediaType + '" data-positionticks="' + chapter.StartPositionTicks + '"'; - var cardImageContainer = imgUrl ? ('
') : ('
'); + const dataAttributes = ` data-action="play" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-positionticks="${chapter.StartPositionTicks}"`; + let cardImageContainer = imgUrl ? (`
`) : (`
`); if (!imgUrl) { - cardImageContainer += ''; + cardImageContainer += ''; } - var nameHtml = ''; - nameHtml += '
' + chapter.Name + '
'; - nameHtml += '
' + datetime.getDisplayRunningTime(chapter.StartPositionTicks) + '
'; + let nameHtml = ''; + nameHtml += `
${chapter.Name}
`; + nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; - var cardBoxCssClass = 'cardBox'; - var cardScalableClass = 'cardScalable'; + const cardBoxCssClass = 'cardBox'; + const cardScalableClass = 'cardScalable'; - var html = '
'; + const html = `
`; return html; } - function buildChapterCards(item, chapters, options) { + export function buildChapterCards(item, chapters, options) { if (options.parentContainer) { // Abort if the container has been disposed @@ -121,15 +131,16 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse } } - var html = buildChapterCardsHtml(item, chapters, options); + const html = buildChapterCardsHtml(item, chapters, options); options.itemsContainer.innerHTML = html; imageLoader.lazyChildren(options.itemsContainer); } - return { - buildChapterCards: buildChapterCards - }; +/* eslint-enable indent */ + +export default { + buildChapterCards: buildChapterCards +}; -}); diff --git a/src/components/cardbuilder/peoplecardbuilder.js b/src/components/cardbuilder/peoplecardbuilder.js index 5d34d29e6e3..3b9a26a7045 100644 --- a/src/components/cardbuilder/peoplecardbuilder.js +++ b/src/components/cardbuilder/peoplecardbuilder.js @@ -1,7 +1,13 @@ -define(['cardBuilder'], function (cardBuilder) { - 'use strict'; +/* eslint-disable indent */ - function buildPeopleCards(items, options) { +/** + * Module for building cards from item data. + * @module components/cardBuilder/peoplecardbuilder + */ + +import cardBuilder from 'cardBuilder'; + + export function buildPeopleCards(items, options) { options = Object.assign(options || {}, { cardLayout: false, @@ -15,8 +21,8 @@ define(['cardBuilder'], function (cardBuilder) { cardBuilder.buildCards(items, options); } - return { - buildPeopleCards: buildPeopleCards - }; + /* eslint-enable indent */ -}); +export default { + buildPeopleCards: buildPeopleCards +}; diff --git a/src/components/castSenderApi.js b/src/components/castSenderApi.js index a1e7bd87551..b541c1f87a7 100644 --- a/src/components/castSenderApi.js +++ b/src/components/castSenderApi.js @@ -1,7 +1,7 @@ define([], function() { 'use strict'; - if (window.appMode === "cordova" || window.appMode === "android") { + if (window.appMode === 'cordova' || window.appMode === 'android') { return { load: function () { window.chrome = window.chrome || {}; @@ -17,16 +17,16 @@ define([], function() { } return new Promise(function (resolve, reject) { - var fileref = document.createElement("script"); - fileref.setAttribute("type", "text/javascript"); + var fileref = document.createElement('script'); + fileref.setAttribute('type', 'text/javascript'); fileref.onload = function () { ccLoaded = true; resolve(); }; - fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"); - document.querySelector("head").appendChild(fileref); + fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js'); + document.querySelector('head').appendChild(fileref); }); } }; diff --git a/src/components/channelmapper/channelmapper.js b/src/components/channelMapper/channelMapper.js similarity index 58% rename from src/components/channelmapper/channelmapper.js rename to src/components/channelMapper/channelMapper.js index 1b536f440b1..f2ad88e7135 100644 --- a/src/components/channelmapper/channelmapper.js +++ b/src/components/channelMapper/channelMapper.js @@ -1,44 +1,33 @@ -define(["dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function (dialogHelper, loading, connectionManager, globalize, actionsheet) { - "use strict"; +define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'actionsheet', 'emby-input', 'paper-icon-button-light', 'emby-button', 'listViewStyle', 'material-icons', 'formDialogStyle'], function (dom, dialogHelper, loading, connectionManager, globalize, actionsheet) { + 'use strict'; return function (options) { - function parentWithClass(elem, className) { - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - if (!elem) { - return null; - } - } - - return elem; - } - function mapChannel(button, channelId, providerChannelId) { loading.show(); var providerId = options.providerId; connectionManager.getApiClient(options.serverId).ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/ChannelMappings"), + type: 'POST', + url: ApiClient.getUrl('LiveTv/ChannelMappings'), data: { providerId: providerId, tunerChannelId: channelId, providerChannelId: providerChannelId }, - dataType: "json" + dataType: 'json' }).then(function (mapping) { - var listItem = parentWithClass(button, "listItem"); - button.setAttribute("data-providerid", mapping.ProviderChannelId); - listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName); + var listItem = dom.parentWithClass(button, 'listItem'); + button.setAttribute('data-providerid', mapping.ProviderChannelId); + listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName); loading.hide(); }); } function onChannelsElementClick(e) { - var btnMap = parentWithClass(e.target, "btnMap"); + var btnMap = dom.parentWithClass(e.target, 'btnMap'); if (btnMap) { - var channelId = btnMap.getAttribute("data-id"); - var providerChannelId = btnMap.getAttribute("data-providerid"); + var channelId = btnMap.getAttribute('data-id'); + var providerChannelId = btnMap.getAttribute('data-providerid'); var menuItems = currentMappingOptions.ProviderChannels.map(function (m) { return { name: m.Name, @@ -59,56 +48,56 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee function getChannelMappingOptions(serverId, providerId) { var apiClient = connectionManager.getApiClient(serverId); - return apiClient.getJSON(apiClient.getUrl("LiveTv/ChannelMappingOptions", { + return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', { providerId: providerId })); } function getMappingSecondaryName(mapping, providerName) { - return (mapping.ProviderChannelName || "") + " - " + providerName; + return (mapping.ProviderChannelName || '') + ' - ' + providerName; } function getTunerChannelHtml(channel, providerName) { - var html = ""; + var html = ''; html += '
'; - html += 'dvr'; + html += ''; html += '
'; html += '

'; html += channel.Name; - html += "

"; + html += ''; html += '
'; if (channel.ProviderChannelName) { html += getMappingSecondaryName(channel, providerName); } - html += "
"; - html += "
"; - html += ''; - return html += "
"; + html += '
'; + html += '
'; + html += ''; + return html += '
'; } function getEditorHtml() { - var html = ""; - html += '
'; + var html = ''; + html += '
'; html += '
'; html += ''; - html += "

" + globalize.translate("HeaderChannels") + "

"; + html += '

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

'; html += '
'; - html += "
"; - html += ""; - html += "
"; - return html += "
"; + html += '
'; + html += ''; + html += '
'; + return html += '
'; } function initEditor(dlg, options) { getChannelMappingOptions(options.serverId, options.providerId).then(function (result) { currentMappingOptions = result; - var channelsElement = dlg.querySelector(".channels"); + var channelsElement = dlg.querySelector('.channels'); channelsElement.innerHTML = result.TunerChannels.map(function (channel) { return getTunerChannelHtml(channel, result.ProviderName); - }).join(""); - channelsElement.addEventListener("click", onChannelsElementClick); + }).join(''); + channelsElement.addEventListener('click', onChannelsElementClick); }); } @@ -119,27 +108,27 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee var dialogOptions = { removeOnClose: true }; - dialogOptions.size = "small"; + dialogOptions.size = 'small'; var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add("formDialog"); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - var html = ""; - var title = globalize.translate("MapChannels"); + dlg.classList.add('formDialog'); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + var html = ''; + var title = globalize.translate('MapChannels'); html += '
'; - html += ''; + html += ''; html += '

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

"; - html += "
"; + html += ''; + html += '
'; html += getEditorHtml(); dlg.innerHTML = html; initEditor(dlg, options); - dlg.querySelector(".btnCancel").addEventListener("click", function () { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); return new Promise(function (resolve, reject) { - dlg.addEventListener("close", resolve); + dlg.addEventListener('close', resolve); dialogHelper.open(dlg); }); }; diff --git a/src/components/collectioneditor/collectioneditor.js b/src/components/collectionEditor/collectionEditor.js similarity index 87% rename from src/components/collectioneditor/collectioneditor.js rename to src/components/collectionEditor/collectionEditor.js index 79220ac3353..46b06403053 100644 --- a/src/components/collectioneditor/collectioneditor.js +++ b/src/components/collectionEditor/collectionEditor.js @@ -1,25 +1,12 @@ -define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) { +define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dom, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) { 'use strict'; var currentServerId; - function parentWithClass(elem, className) { - - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - function onSubmit(e) { loading.show(); - var panel = parentWithClass(this, 'dialog'); + var panel = dom.parentWithClass(this, 'dialog'); var collectionId = panel.querySelector('#selectCollectionToAddTo').value; @@ -37,7 +24,7 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage function createCollection(apiClient, dlg) { - var url = apiClient.getUrl("Collections", { + var url = apiClient.getUrl('Collections', { Name: dlg.querySelector('#txtNewCollectionName').value, IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, @@ -45,9 +32,9 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage }); apiClient.ajax({ - type: "POST", + type: 'POST', url: url, - dataType: "json" + dataType: 'json' }).then(function (result) { @@ -69,13 +56,13 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage function addToCollection(apiClient, dlg, id) { - var url = apiClient.getUrl("Collections/" + id + "/Items", { + var url = apiClient.getUrl('Collections/' + id + '/Items', { Ids: dlg.querySelector('.fldSelectedItemIds').value || '' }); apiClient.ajax({ - type: "POST", + type: 'POST', url: url }).then(function () { @@ -106,8 +93,8 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage var options = { Recursive: true, - IncludeItemTypes: "BoxSet", - SortBy: "SortName", + IncludeItemTypes: 'BoxSet', + SortBy: 'SortName', EnableTotalRecordCount: false }; @@ -243,13 +230,13 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); html += '
'; - html += ''; + html += ''; html += '

'; html += title; html += '

'; if (appHost.supports('externallinks')) { - html += 'info' + globalize.translate('Help') + ''; + html += '' + globalize.translate('Help') + ''; } html += '
'; diff --git a/src/components/confirm/confirm.js b/src/components/confirm/confirm.js index fa9a156679f..517d6fa9be0 100644 --- a/src/components/confirm/confirm.js +++ b/src/components/confirm/confirm.js @@ -1,4 +1,4 @@ -define(["browser", "dialog", "globalize"], function(browser, dialog, globalize) { +define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize) { 'use strict'; function replaceAll(str, find, replace) { diff --git a/src/components/deletehelper.js b/src/components/deletehelper.js deleted file mode 100644 index 2212fd44379..00000000000 --- a/src/components/deletehelper.js +++ /dev/null @@ -1,57 +0,0 @@ -define(['connectionManager', 'confirm', 'appRouter', 'globalize'], function (connectionManager, confirm, appRouter, globalize) { - 'use strict'; - - function alertText(options) { - - return new Promise(function (resolve, reject) { - - require(['alert'], function (alert) { - alert(options).then(resolve, resolve); - }); - }); - } - - function deleteItem(options) { - - var item = options.item; - var itemId = item.Id; - var parentId = item.SeasonId || item.SeriesId || item.ParentId; - var serverId = item.ServerId; - - var msg = globalize.translate('ConfirmDeleteItem'); - var title = globalize.translate('HeaderDeleteItem'); - var apiClient = connectionManager.getApiClient(item.ServerId); - - return confirm({ - - title: title, - text: msg, - confirmText: globalize.translate('Delete'), - primary: 'delete' - - }).then(function () { - - return apiClient.deleteItem(itemId).then(function () { - - if (options.navigate) { - if (parentId) { - appRouter.showItem(parentId, serverId); - } else { - appRouter.goHome(); - } - } - }, function (err) { - - var result = function () { - return Promise.reject(err); - }; - - return alertText(globalize.translate('ErrorDeletingItem')).then(result, result); - }); - }); - } - - return { - deleteItem: deleteItem - }; -}); diff --git a/src/components/dialog/dialog.template.html b/src/components/dialog/dialog.template.html index bee0ef7f73f..6d4310c0f48 100644 --- a/src/components/dialog/dialog.template.html +++ b/src/components/dialog/dialog.template.html @@ -4,12 +4,8 @@

- -
- -
+

-
-
+
diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index e3410776a8d..8b08cde6783 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -141,7 +141,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', animateDialogOpen(dlg); if (isHistoryEnabled(dlg)) { - appRouter.pushState({ dialogId: hash }, "Dialog", '#' + hash); + appRouter.pushState({ dialogId: hash }, 'Dialog', '#' + hash); window.addEventListener('popstate', onHashChange); } else { diff --git a/src/components/dialogHelper/dialoghelper.css b/src/components/dialogHelper/dialoghelper.css index df2adf075f1..98cfef1c5dd 100644 --- a/src/components/dialogHelper/dialoghelper.css +++ b/src/components/dialogHelper/dialoghelper.css @@ -126,25 +126,10 @@ } @media all and (min-width: 80em) and (min-height: 45em) { - .dialog-medium { - width: 80%; - height: 80%; - } - - .dialog-medium-tall { - width: 80%; - height: 90%; - } - .dialog-small { width: 60%; height: 80%; } - - .dialog-fullscreen-border { - width: 90%; - height: 90%; - } } .noScroll { diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index b71f7bbb051..e08fcc8336e 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -1,4 +1,4 @@ -define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-icon-button-light', 'css!./directorybrowser', 'formDialogStyle', 'emby-button'], function(loading, dialogHelper, dom) { +define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-input', 'paper-icon-button-light', 'css!./directorybrowser', 'formDialogStyle', 'emby-button'], function(loading, dialogHelper, dom, globalize) { 'use strict'; function getSystemInfo() { @@ -7,24 +7,24 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- systemInfo = info; return info; } - ) + ); } function onDialogClosed() { - loading.hide() + loading.hide(); } function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) { if (path && typeof path !== 'string') { - throw new Error("invalid path"); + throw new Error('invalid path'); } loading.show(); var promises = []; - if ("Network" === path) { - promises.push(ApiClient.getNetworkDevices()) + if ('Network' === path) { + promises.push(ApiClient.getNetworkDevices()); } else { if (path) { promises.push(ApiClient.getDirectoryContents(path, fileOptions)); @@ -37,31 +37,31 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- Promise.all(promises).then( function(responses) { var folders = responses[0]; - var parentPath = responses[1] || ""; - var html = ""; + var parentPath = responses[1] || ''; + var html = ''; - page.querySelector(".results").scrollTop = 0; - page.querySelector("#txtDirectoryPickerPath").value = path || ""; + page.querySelector('.results').scrollTop = 0; + page.querySelector('#txtDirectoryPickerPath').value = path || ''; if (path) { - html += getItem("lnkPath lnkDirectory", "", parentPath, "..."); + html += getItem('lnkPath lnkDirectory', '', parentPath, '...'); } for (var i = 0, length = folders.length; i < length; i++) { var folder = folders[i]; - var cssClass = "File" === folder.Type ? "lnkPath lnkFile" : "lnkPath lnkDirectory"; + var cssClass = 'File' === folder.Type ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory'; html += getItem(cssClass, folder.Type, folder.Path, folder.Name); } if (!path) { - html += getItem("lnkPath lnkDirectory", "", "Network", Globalize.translate("ButtonNetwork")); + html += getItem('lnkPath lnkDirectory', '', 'Network', globalize.translate('ButtonNetwork')); } - page.querySelector(".results").innerHTML = html; + page.querySelector('.results').innerHTML = html; loading.hide(); }, function() { if (updatePathOnError) { - page.querySelector("#txtDirectoryPickerPath").value = ""; - page.querySelector(".results").innerHTML = ""; + page.querySelector('#txtDirectoryPickerPath').value = ''; + page.querySelector('.results').innerHTML = ''; loading.hide(); } } @@ -69,74 +69,73 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- } function getItem(cssClass, type, path, name) { - var html = ""; + var html = ''; html += '
'; html += '
'; html += '
'; html += name; - html += "
"; - html += "
"; - html += ''; - html += "
"; + html += '
'; + html += ''; + html += ''; + html += ''; return html; } function getEditorHtml(options, systemInfo) { - var html = ""; + var html = ''; html += '
'; html += '
'; if (!options.pathReadOnly) { - var instruction = options.instruction ? options.instruction + "

" : ""; + var instruction = options.instruction ? options.instruction + '

' : ''; html += '
'; html += instruction; - html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "\\\\server").replace("{1}", "\\\\192.168.1.101"); - if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) { - html += "
"; - html += "
"; - html += Globalize.translate("MessageDirectoryPickerBSDInstruction"); - html += "
"; - } else if ("linux" === systemInfo.OperatingSystem.toLowerCase()) { - html += "
"; - html += "
"; - html += Globalize.translate("MessageDirectoryPickerLinuxInstruction"); - html += "
"; + if ('bsd' === systemInfo.OperatingSystem.toLowerCase()) { + html += '
'; + html += '
'; + html += globalize.translate('MessageDirectoryPickerBSDInstruction'); + html += '
'; + } else if ('linux' === systemInfo.OperatingSystem.toLowerCase()) { + html += '
'; + html += '
'; + html += globalize.translate('MessageDirectoryPickerLinuxInstruction'); + html += '
'; } - html += "
" + html += '
'; } html += '
'; html += '
'; html += '
'; var labelKey; if (options.includeFiles !== true) { - labelKey = "LabelFolder"; + labelKey = 'LabelFolder'; } else { - labelKey = "LabelPath"; + labelKey = 'LabelPath'; } - var readOnlyAttribute = options.pathReadOnly ? " readonly" : ""; - html += ''; - html += "
"; + var readOnlyAttribute = options.pathReadOnly ? ' readonly' : ''; + html += ''; + html += '
'; if (!readOnlyAttribute) { - html += ''; + html += ''; } - html += "
"; + html += ''; if (!readOnlyAttribute) { html += '
'; } if (options.enableNetworkSharePath) { html += '
'; - html += ''; + html += ''; html += '
'; - html += Globalize.translate("LabelOptionalNetworkPathHelp"); - html += "
"; - html += "
"; + html += globalize.translate('LabelOptionalNetworkPathHelp', '\\\\server', '\\\\192.168.1.101'); + html += ''; + html += ''; } html += '
'; - html += '"; - html += "
"; - html += ""; - html += ""; - html += ""; - html += ""; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; return html; } @@ -144,75 +143,74 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- function alertText(text) { alertTextWithOptions({ text: text - }) + }); } function alertTextWithOptions(options) { - require(["alert"], function(alert) { - alert(options) - }) + require(['alert'], function(alert) { + alert(options); + }); } function validatePath(path, validateWriteable, apiClient) { return apiClient.ajax({ - type: "POST", - url: apiClient.getUrl("Environment/ValidatePath"), + type: 'POST', + url: apiClient.getUrl('Environment/ValidatePath'), data: { ValidateWriteable: validateWriteable, Path: path } }).catch(function(response) { if (response) { - // TODO All alerts (across the project), should use Globalize.translate() if (response.status === 404) { - alertText("The path could not be found. Please ensure the path is valid and try again."); + alertText(globalize.translate('PathNotFound')); return Promise.reject(); } if (response.status === 500) { if (validateWriteable) { - alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again."); + alertText(globalize.translate('WriteAccessRequired')); } else { - alertText("The path could not be found. Please ensure the path is valid and try again.") + alertText(globalize.translate('PathNotFound')); } - return Promise.reject() + return Promise.reject(); } } - return Promise.resolve() + return Promise.resolve(); }); } function initEditor(content, options, fileOptions) { - content.addEventListener("click", function(e) { - var lnkPath = dom.parentWithClass(e.target, "lnkPath"); + content.addEventListener('click', function(e) { + var lnkPath = dom.parentWithClass(e.target, 'lnkPath'); if (lnkPath) { - var path = lnkPath.getAttribute("data-path"); - if (lnkPath.classList.contains("lnkFile")) { - content.querySelector("#txtDirectoryPickerPath").value = path; + var path = lnkPath.getAttribute('data-path'); + if (lnkPath.classList.contains('lnkFile')) { + content.querySelector('#txtDirectoryPickerPath').value = path; } else { - refreshDirectoryBrowser(content, path, fileOptions, true) + refreshDirectoryBrowser(content, path, fileOptions, true); } } }); - content.addEventListener("click", function(e) { - if (dom.parentWithClass(e.target, "btnRefreshDirectories")) { - var path = content.querySelector("#txtDirectoryPickerPath").value; + content.addEventListener('click', function(e) { + if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) { + var path = content.querySelector('#txtDirectoryPickerPath').value; refreshDirectoryBrowser(content, path, fileOptions); } }); - content.addEventListener("change", function(e) { - var txtDirectoryPickerPath = dom.parentWithTag(e.target, "INPUT"); - if (txtDirectoryPickerPath && "txtDirectoryPickerPath" === txtDirectoryPickerPath.id) { + content.addEventListener('change', function(e) { + var txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT'); + if (txtDirectoryPickerPath && 'txtDirectoryPickerPath' === txtDirectoryPickerPath.id) { refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions); } }); - content.querySelector("form").addEventListener("submit", function(e) { + content.querySelector('form').addEventListener('submit', function(e) { if (options.callback) { - var networkSharePath = this.querySelector("#txtNetworkPath"); + var networkSharePath = this.querySelector('#txtNetworkPath'); networkSharePath = networkSharePath ? networkSharePath.value : null; - var path = this.querySelector("#txtDirectoryPickerPath").value; + var path = this.querySelector('#txtDirectoryPickerPath').value; validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath)); } e.preventDefault(); @@ -225,11 +223,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- if (options.path) { return Promise.resolve(options.path); } else { - return ApiClient.getJSON(ApiClient.getUrl("Environment/DefaultDirectoryBrowser")).then( + return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then( function(result) { - return result.Path || ""; + return result.Path || ''; }, function() { - return ""; + return ''; } ); } @@ -254,35 +252,35 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- var systemInfo = responses[0]; var initialPath = responses[1]; var dlg = dialogHelper.createDialog({ - size: "medium-tall", + size: 'small', removeOnClose: true, scrollY: false }); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - dlg.classList.add("directoryPicker"); - dlg.classList.add("formDialog"); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('directoryPicker'); + dlg.classList.add('formDialog'); - var html = ""; + var html = ''; html += '
'; - html += ''; + html += ''; html += '

'; - html += options.header || Globalize.translate("HeaderSelectPath"); - html += "

"; - html += "
"; + html += options.header || globalize.translate('HeaderSelectPath'); + html += ''; + html += ''; html += getEditorHtml(options, systemInfo); dlg.innerHTML = html; initEditor(dlg, options, fileOptions); - dlg.addEventListener("close", onDialogClosed); + dlg.addEventListener('close', onDialogClosed); dialogHelper.open(dlg); - dlg.querySelector(".btnCloseDialog").addEventListener("click", function() { - dialogHelper.close(dlg) + dlg.querySelector('.btnCloseDialog').addEventListener('click', function() { + dialogHelper.close(dlg); }); currentDialog = dlg; - dlg.querySelector("#txtDirectoryPickerPath").value = initialPath; - var txtNetworkPath = dlg.querySelector("#txtNetworkPath"); + dlg.querySelector('#txtDirectoryPickerPath').value = initialPath; + var txtNetworkPath = dlg.querySelector('#txtNetworkPath'); if (txtNetworkPath) { - txtNetworkPath.value = options.networkSharePath || ""; + txtNetworkPath.value = options.networkSharePath || ''; } if (!options.pathReadOnly) { refreshDirectoryBrowser(dlg, initialPath, fileOptions, true); @@ -294,9 +292,9 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- if (currentDialog) { dialogHelper.close(currentDialog); } - } + }; } var systemInfo; - return directoryBrowser + return directoryBrowser; }); diff --git a/src/components/displaysettings/displaysettings.js b/src/components/displaySettings/displaySettings.js similarity index 95% rename from src/components/displaysettings/displaysettings.js rename to src/components/displaySettings/displaySettings.js index da407c11f13..c4eb35f49fd 100644 --- a/src/components/displaysettings/displaysettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -1,5 +1,5 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', 'apphost', 'focusManager', 'datetime', 'globalize', 'loading', 'connectionManager', 'skinManager', 'dom', 'events', 'emby-select', 'emby-checkbox', 'emby-button'], function (require, browser, layoutManager, appSettings, pluginManager, appHost, focusManager, datetime, globalize, loading, connectionManager, skinManager, dom, events) { - "use strict"; + 'use strict'; function fillThemes(select, isDashboard) { select.innerHTML = skinManager.getThemes().map(function (t) { @@ -181,11 +181,15 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' 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('#selectLanguage').value = userSettings.language() || ''; context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; + context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); + selectDashboardTheme.value = userSettings.dashboardTheme() || ''; selectTheme.value = userSettings.theme() || ''; @@ -215,10 +219,14 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value); userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); + userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); + userSettingsInstance.skin(context.querySelector('.selectSkin').value); userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); + userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); + userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); if (user.Id === apiClient.getCurrentUserId()) { skinManager.setTheme(userSettingsInstance.theme()); @@ -265,7 +273,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' } function embed(options, self) { - require(['text!./displaysettings.template.html'], function (template) { + require(['text!./displaySettings.template.html'], function (template) { options.element.innerHTML = globalize.translateDocument(template, 'core'); options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); if (options.enableSaveButton) { diff --git a/src/components/displaysettings/displaysettings.template.html b/src/components/displaySettings/displaySettings.template.html similarity index 87% rename from src/components/displaysettings/displaysettings.template.html rename to src/components/displaySettings/displaySettings.template.html index 16bbf0dd8a1..ab01b4b6ae6 100644 --- a/src/components/displaysettings/displaysettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -56,14 +56,14 @@

-
+
+ +
${LabelLibraryPageSizeHelp}
+
+ +
+
${EnableFasterAnimationsHelp}
+
+ +
+ +
${EnableBlurhashHelp}
+
+ +
+ -
${EnableFastImageFadeInHelp}
+
${EnableDetailsBannerHelp}
diff --git a/src/components/dom.js b/src/components/dom.js deleted file mode 100644 index b91e5b16872..00000000000 --- a/src/components/dom.js +++ /dev/null @@ -1,199 +0,0 @@ -define([], function () { - 'use strict'; - - function parentWithAttribute(elem, name, value) { - - while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) { - elem = elem.parentNode; - - if (!elem || !elem.getAttribute) { - return null; - } - } - - return elem; - } - - function parentWithTag(elem, tagNames) { - - // accept both string and array passed in - if (!Array.isArray(tagNames)) { - tagNames = [tagNames]; - } - - while (tagNames.indexOf(elem.tagName || '') === -1) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - - function containsAnyClass(classList, classNames) { - - for (var i = 0, length = classNames.length; i < length; i++) { - if (classList.contains(classNames[i])) { - return true; - } - } - return false; - } - - function parentWithClass(elem, classNames) { - - // accept both string and array passed in - if (!Array.isArray(classNames)) { - classNames = [classNames]; - } - - while (!elem.classList || !containsAnyClass(elem.classList, classNames)) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - - var supportsCaptureOption = false; - try { - var opts = Object.defineProperty({}, 'capture', { - // eslint-disable-next-line getter-return - get: function () { - supportsCaptureOption = true; - } - }); - window.addEventListener("test", null, opts); - } catch (e) { - console.debug('error checking capture support'); - } - - function addEventListenerWithOptions(target, type, handler, options) { - var optionsOrCapture = options || {}; - if (!supportsCaptureOption) { - optionsOrCapture = optionsOrCapture.capture; - } - target.addEventListener(type, handler, optionsOrCapture); - } - - function removeEventListenerWithOptions(target, type, handler, options) { - var optionsOrCapture = options || {}; - if (!supportsCaptureOption) { - optionsOrCapture = optionsOrCapture.capture; - } - target.removeEventListener(type, handler, optionsOrCapture); - } - - var windowSize; - var windowSizeEventsBound; - function clearWindowSize() { - windowSize = null; - } - - function getWindowSize() { - if (!windowSize) { - windowSize = { - innerHeight: window.innerHeight, - innerWidth: window.innerWidth - }; - - if (!windowSizeEventsBound) { - windowSizeEventsBound = true; - addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true }); - addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true }); - } - } - - return windowSize; - } - - var standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680]; - function getScreenWidth() { - var width = window.innerWidth; - var height = window.innerHeight; - - if (height > width) { - width = height * (16.0 / 9.0); - } - - var closest = standardWidths.sort(function (a, b) { - return Math.abs(width - a) - Math.abs(width - b); - })[0]; - - return closest; - } - - var _animationEvent; - function whichAnimationEvent() { - - if (_animationEvent) { - return _animationEvent; - } - - var t; - var el = document.createElement("div"); - var animations = { - "animation": "animationend", - "OAnimation": "oAnimationEnd", - "MozAnimation": "animationend", - "WebkitAnimation": "webkitAnimationEnd" - }; - for (t in animations) { - if (el.style[t] !== undefined) { - _animationEvent = animations[t]; - return animations[t]; - } - } - - _animationEvent = 'animationend'; - return _animationEvent; - } - - function whichAnimationCancelEvent() { - - return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel'); - } - - var _transitionEvent; - function whichTransitionEvent() { - if (_transitionEvent) { - return _transitionEvent; - } - - var t; - var el = document.createElement("div"); - var transitions = { - "transition": "transitionend", - "OTransition": "oTransitionEnd", - "MozTransition": "transitionend", - "WebkitTransition": "webkitTransitionEnd" - }; - for (t in transitions) { - if (el.style[t] !== undefined) { - _transitionEvent = transitions[t]; - return transitions[t]; - } - } - - _transitionEvent = 'transitionend'; - return _transitionEvent; - } - - return { - parentWithAttribute: parentWithAttribute, - parentWithClass: parentWithClass, - parentWithTag: parentWithTag, - addEventListener: addEventListenerWithOptions, - removeEventListener: removeEventListenerWithOptions, - getWindowSize: getWindowSize, - getScreenWidth: getScreenWidth, - whichTransitionEvent: whichTransitionEvent, - whichAnimationEvent: whichAnimationEvent, - whichAnimationCancelEvent: whichAnimationCancelEvent - }; -}); diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index 08c9d9acc5d..38715043e76 100644 --- a/src/components/favoriteitems.js +++ b/src/components/favoriteitems.js @@ -1,41 +1,41 @@ -define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoader", "globalize", "layoutManager", "scrollStyles", "emby-itemscontainer"], function (loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) { - "use strict"; +define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'globalize', 'layoutManager', 'scrollStyles', 'emby-itemscontainer'], function (loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) { + 'use strict'; function enableScrollX() { return !layoutManager.desktop; } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function getPosterShape() { - return enableScrollX() ? "overflowPortrait" : "portrait"; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } function getSquareShape() { - return enableScrollX() ? "overflowSquare" : "square"; + return enableScrollX() ? 'overflowSquare' : 'square'; } function getSections() { return [{ - name: "HeaderFavoriteMovies", - types: "Movie", - id: "favoriteMovies", + name: 'HeaderFavoriteMovies', + types: 'Movie', + id: 'favoriteMovies', shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, { - name: "HeaderFavoriteShows", - types: "Series", - id: "favoriteShows", + name: 'HeaderFavoriteShows', + types: 'Series', + id: 'favoriteShows', shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, { - name: "HeaderFavoriteEpisodes", - types: "Episode", - id: "favoriteEpisode", + name: 'HeaderFavoriteEpisodes', + types: 'Episode', + id: 'favoriteEpisode', shape: getThumbShape(), preferThumb: false, showTitle: true, @@ -44,9 +44,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad overlayText: false, centerText: true }, { - name: "HeaderFavoriteVideos", - types: "Video,MusicVideo", - id: "favoriteVideos", + name: 'HeaderFavoriteVideos', + types: 'Video,MusicVideo', + id: 'favoriteVideos', shape: getThumbShape(), preferThumb: true, showTitle: true, @@ -54,9 +54,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad overlayText: false, centerText: true }, { - name: "HeaderFavoriteArtists", - types: "MusicArtist", - id: "favoriteArtists", + name: 'HeaderFavoriteArtists', + types: 'MusicArtist', + id: 'favoriteArtists', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -66,9 +66,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoriteAlbums", - types: "MusicAlbum", - id: "favoriteAlbums", + name: 'HeaderFavoriteAlbums', + types: 'MusicAlbum', + id: 'favoriteAlbums', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -78,9 +78,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoriteSongs", - types: "Audio", - id: "favoriteSongs", + name: 'HeaderFavoriteSongs', + types: 'Audio', + id: 'favoriteSongs', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -88,7 +88,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad showParentTitle: true, centerText: true, overlayMoreButton: true, - action: "instantmix", + action: 'instantmix', coverImage: true }]; } @@ -96,13 +96,13 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad function loadSection(elem, userId, topParentId, section, isSingleSection) { var screenWidth = dom.getWindowSize().innerWidth; var options = { - SortBy: "SortName", - SortOrder: "Ascending", - Filters: "IsFavorite", + SortBy: 'SortName', + SortOrder: 'Ascending', + Filters: 'IsFavorite', Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', CollapseBoxSetItems: false, - ExcludeLocationTypes: "Virtual", + ExcludeLocationTypes: 'Virtual', EnableTotalRecordCount: false }; @@ -120,7 +120,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad var promise; - if ("MusicArtist" === section.types) { + if ('MusicArtist' === section.types) { promise = ApiClient.getArtists(userId, options); } else { options.IncludeItemTypes = section.types; @@ -128,25 +128,25 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad } return promise.then(function (result) { - var html = ""; + var html = ''; if (result.Items.length) { if (html += '
', !layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { - html += ''; + html += ''; html += '

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

"; - html += ''; - html += "
"; + html += '

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

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

"; + html += '

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

'; } - html += ""; + html += ''; if (enableScrollX()) { - var scrollXClass = "scrollX hiddenScrollX"; + var scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { - scrollXClass += " smoothScrollX"; + scrollXClass += ' smoothScrollX'; } html += '
'; @@ -154,7 +154,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad html += '
'; } - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle; cardLayout = false; html += cardBuilder.getCardsHtml(result.Items, { @@ -172,7 +172,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad allowBottomPadding: !enableScrollX(), cardLayout: cardLayout }); - html += "
"; + html += '
'; } elem.innerHTML = html; @@ -183,7 +183,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad function loadSections(page, userId, topParentId, types) { loading.show(); var sections = getSections(); - var sectionid = getParameterByName("sectionid"); + var sectionid = getParameterByName('sectionid'); if (sectionid) { sections = sections.filter(function (s) { @@ -199,10 +199,10 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad var i; var length; - var elem = page.querySelector(".favoriteSections"); + var elem = page.querySelector('.favoriteSections'); if (!elem.innerHTML) { - var html = ""; + var html = ''; for (i = 0, length = sections.length; i < length; i++) { html += '
'; @@ -215,7 +215,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad for (i = 0, length = sections.length; i < length; i++) { var section = sections[i]; - elem = page.querySelector(".section" + section.id); + elem = page.querySelector('.section' + section.id); promises.push(loadSection(elem, userId, topParentId, section, 1 === sections.length)); } diff --git a/src/components/fetchhelper.js b/src/components/fetchhelper.js index 0b8d451736d..f626abefee5 100644 --- a/src/components/fetchhelper.js +++ b/src/components/fetchhelper.js @@ -1,21 +1,19 @@ -define([], function () { - 'use strict'; +/* eslint-disable indent */ + export function getFetchPromise(request) { - function getFetchPromise(request) { - - var headers = request.headers || {}; + const headers = request.headers || {}; if (request.dataType === 'json') { headers.accept = 'application/json'; } - var fetchRequest = { + const fetchRequest = { headers: headers, method: request.type, credentials: 'same-origin' }; - var contentType = request.contentType; + let contentType = request.contentType; if (request.data) { @@ -33,12 +31,12 @@ define([], function () { headers['Content-Type'] = contentType; } - var url = request.url; + let url = request.url; if (request.query) { - var paramString = paramsToString(request.query); + const paramString = paramsToString(request.query); if (paramString) { - url += '?' + paramString; + url += `?${paramString}`; } } @@ -51,11 +49,11 @@ define([], function () { function fetchWithTimeout(url, options, timeoutMs) { - console.debug('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url); + console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); return new Promise(function (resolve, reject) { - var timeout = setTimeout(reject, timeoutMs); + const timeout = setTimeout(reject, timeoutMs); options = options || {}; options.credentials = 'same-origin'; @@ -63,50 +61,47 @@ define([], function () { fetch(url, options).then(function (response) { clearTimeout(timeout); - console.debug('fetchWithTimeout: succeeded connecting to url: ' + url); + console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); resolve(response); }, function (error) { clearTimeout(timeout); - console.debug('fetchWithTimeout: timed out connecting to url: ' + url); + console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); - reject(); + reject(error); }); }); } + /** + * @param params {Record} + * @returns {string} Query string + */ function paramsToString(params) { - - var values = []; - - for (var key in params) { - - var value = params[key]; - - if (value !== null && value !== undefined && value !== '') { - values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); - } - } - return values.join('&'); + return Object.entries(params) + // eslint-disable-next-line no-unused-vars + .filter(([_, v]) => v !== null && v !== undefined && v !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); } - function ajax(request) { + export function ajax(request) { if (!request) { - throw new Error("Request cannot be null"); + throw new Error('Request cannot be null'); } request.headers = request.headers || {}; - console.debug('requesting url: ' + request.url); + console.debug(`requesting url: ${request.url}`); return getFetchPromise(request).then(function (response) { - console.debug('response status: ' + response.status + ', url: ' + request.url); + console.debug(`response status: ${response.status}, url: ${request.url}`); if (response.status < 400) { if (request.dataType === 'json' || request.headers.accept === 'application/json') { return response.json(); - } else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) { + } else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().startsWith('text/')) { return response.text(); } else { return response; @@ -115,12 +110,8 @@ define([], function () { return Promise.reject(response); } }, function (err) { - console.error('request failed to url: ' + request.url); + console.error(`request failed to url: ${request.url}`); throw err; }); } - return { - getFetchPromise: getFetchPromise, - ajax: ajax - }; -}); +/* eslint-enable indent */ diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js index 7301c656691..041fa82e76f 100644 --- a/src/components/filterdialog/filterdialog.js +++ b/src/components/filterdialog/filterdialog.js @@ -1,52 +1,59 @@ -define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function (dialogHelper, globalize, connectionManager, events, browser, require) { - "use strict"; - +import dom from 'dom'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import connectionManager from 'connectionManager'; +import events from 'events'; +import 'emby-checkbox'; +import 'emby-collapse'; +import 'css!./style.css'; + +/* eslint-disable indent */ function renderOptions(context, selector, cssClass, items, isCheckedFn) { - var elem = context.querySelector(selector); + const elem = context.querySelector(selector); if (items.length) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - var html = ""; + let html = ''; html += '
'; html += items.map(function (filter) { - var itemHtml = ""; - var checkedHtml = isCheckedFn(filter) ? " checked" : ""; - itemHtml += ""; + let itemHtml = ''; + const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; + itemHtml += ''; return itemHtml; - }).join(""); - html += "
"; - elem.querySelector(".filterOptions").innerHTML = html; + }).join(''); + html += ''; + elem.querySelector('.filterOptions').innerHTML = html; } function renderFilters(context, result, query) { if (result.Tags) { result.Tags.length = Math.min(result.Tags.length, 50); } - renderOptions(context, ".genreFilters", "chkGenreFilter", result.Genres, function (i) { - var delimeter = "|"; - return (delimeter + (query.Genres || "") + delimeter).indexOf(delimeter + i + delimeter) != -1; + 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) { - var delimeter = "|"; - return (delimeter + (query.OfficialRatings || "") + delimeter).indexOf(delimeter + i + delimeter) != -1; + 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) { - var delimeter = "|"; - return (delimeter + (query.Tags || "") + delimeter).indexOf(delimeter + i + delimeter) != -1; + 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) { - var delimeter = ","; - return (delimeter + (query.Years || "") + delimeter).indexOf(delimeter + i + delimeter) != -1; + 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", { + return apiClient.getJSON(apiClient.getUrl('Items/Filters', { UserId: userId, ParentId: itemQuery.ParentId, IncludeItemTypes: itemQuery.IncludeItemTypes @@ -55,356 +62,372 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", " }); } + /** + * @param context {HTMLDivElement} Dialog + * @param options {any} Options + */ function updateFilterControls(context, options) { - var elems; - var i; - var length; - var query = options.query; - - if (options.mode == "livetvchannels") { - context.querySelector(".chkFavorite").checked = query.IsFavorite == true; - context.querySelector(".chkLikes").checked = query.IsLiked == true; - context.querySelector(".chkDislikes").checked = query.IsDisliked == true; + const query = options.query; + + if (options.mode === 'livetvchannels') { + context.querySelector('.chkFavorite').checked = query.IsFavorite === true; + context.querySelector('.chkLikes').checked = query.IsLiked === true; + context.querySelector('.chkDislikes').checked = query.IsDisliked === true; } else { - elems = context.querySelectorAll(".chkStandardFilter"); - for (i = 0, length = elems.length; i < length; i++) { - var chkStandardFilter = elems[i]; - var filters = "," + (query.Filters || ""); - var filterName = chkStandardFilter.getAttribute("data-filter"); - chkStandardFilter.checked = filters.indexOf("," + filterName) != -1; + for (const elem of context.querySelectorAll('.chkStandardFilter')) { + const filters = `,${query.Filters || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); } } - elems = context.querySelectorAll(".chkVideoTypeFilter"); - for (i = 0, length = elems.length; i < length; i++) { - var chkVideoTypeFilter = elems[i]; - var filters = "," + (query.VideoTypes || ""); - var filterName = chkVideoTypeFilter.getAttribute("data-filter"); - chkVideoTypeFilter.checked = filters.indexOf("," + filterName) != -1; + for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { + const filters = `,${query.VideoTypes || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); } - context.querySelector(".chk3DFilter").checked = query.Is3D == true; - context.querySelector(".chkHDFilter").checked = query.IsHD == true; - context.querySelector(".chk4KFilter").checked = query.Is4K == true; - context.querySelector(".chkSDFilter").checked = query.IsHD == true; - context.querySelector("#chkSubtitle").checked = query.HasSubtitles == true; - context.querySelector("#chkTrailer").checked = query.HasTrailer == true; - context.querySelector("#chkThemeSong").checked = query.HasThemeSong == true; - context.querySelector("#chkThemeVideo").checked = query.HasThemeVideo == true; - context.querySelector("#chkSpecialFeature").checked = query.HasSpecialFeature == true; - context.querySelector("#chkSpecialEpisode").checked = query.ParentIndexNumber == 0; - context.querySelector("#chkMissingEpisode").checked = query.IsMissing == true; - context.querySelector("#chkFutureEpisode").checked = query.IsUnaired == true; - for (i = 0, length = elems.length; i < length; i++) { - var chkStatus = elems[i]; - var filters = "," + (query.SeriesStatus || ""); - var filterName = chkStatus.getAttribute("data-filter"); - chkStatus.checked = filters.indexOf("," + filterName) != -1; + context.querySelector('.chk3DFilter').checked = query.Is3D === true; + context.querySelector('.chkHDFilter').checked = query.IsHD === true; + context.querySelector('.chk4KFilter').checked = query.Is4K === true; + context.querySelector('.chkSDFilter').checked = query.IsHD === true; + context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true; + context.querySelector('#chkTrailer').checked = query.HasTrailer === true; + context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true; + context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true; + context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true; + context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0; + context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true; + context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true; + for (const elem of context.querySelectorAll('.chkStatus')) { + const filters = `,${query.SeriesStatus || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); } } + /** + * @param instance {FilterDialog} An instance of FilterDialog + */ function triggerChange(instance) { - events.trigger(instance, "filterchange"); - } - - function parentWithClass(elem, className) { - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - if (!elem) { - return null; - } - } - return elem; + 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 (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"); + 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 === '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 === '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 === 'series') { + context.querySelector('.seriesStatus').classList.remove('hide'); } - if (options.mode == "episodes") { - showByClass(context, "episodeFilter"); + if (options.mode === 'episodes') { + showByClass(context, 'episodeFilter'); } } function showByClass(context, className) { - var elems = context.querySelectorAll("." + className); - - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].classList.remove("hide"); + for (const elem of context.querySelectorAll(`.${className}`)) { + elem.classList.remove('hide'); } } function hideByClass(context, className) { - var elems = context.querySelectorAll("." + className); - - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].classList.add("hide"); + for (const elem of context.querySelectorAll(`.${className}`)) { + elem.classList.add('hide'); } } function enableDynamicFilters(mode) { - return mode == "movies" || mode == "series" || mode == "albums" || mode == "albumartists" || mode == "artists" || mode == "songs" || mode == "episodes"; + return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes'; } - return function (options) { - function onFavoriteChange() { - var query = options.query; - query.StartIndex = 0; - query.IsFavorite = !!this.checked || null; - triggerChange(self); + class FilterDialog { + constructor(options) { + /** + * @private + */ + this.options = options; } - function onStandardFilterChange() { - var query = options.query; - var filterName = this.getAttribute("data-filter"); - var filters = query.Filters || ""; - filters = ("," + filters).replace("," + filterName, "").substring(1); + /** + * @private + */ + onFavoriteChange(elem) { + const query = this.options.query; + query.StartIndex = 0; + query.IsFavorite = !!elem.checked || null; + triggerChange(this); + } - if (this.checked) { - filters = filters ? filters + "," + filterName : filterName; + /** + * @private + */ + onStandardFilterChange(elem) { + const query = this.options.query; + const filterName = elem.getAttribute('data-filter'); + let filters = query.Filters || ''; + filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); + + if (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; } query.StartIndex = 0; query.Filters = filters; - triggerChange(self); + triggerChange(this); } - function onVideoTypeFilterChange() { - var query = options.query; - var filterName = this.getAttribute("data-filter"); - var filters = query.VideoTypes || ""; - filters = ("," + filters).replace("," + filterName, "").substring(1); - - if (this.checked) { - filters = filters ? filters + "," + filterName : filterName; + /** + * @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(self); + triggerChange(this); } - function onStatusChange() { - var query = options.query; - var filterName = this.getAttribute("data-filter"); - var filters = query.SeriesStatus || ""; - filters = ("," + filters).replace("," + filterName, "").substring(1); - - if (this.checked) { - filters = filters ? filters + "," + filterName : filterName; + /** + * @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(self); + triggerChange(this); } - function bindEvents(context) { - var elems; - var i; - var length; - var query = options.query; + /** + * @param context {HTMLDivElement} The dialog + */ + bindEvents(context) { + const query = this.options.query; - if (options.mode == "livetvchannels") { - elems = context.querySelectorAll(".chkFavorite"); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("change", onFavoriteChange); + if (this.options.mode === 'livetvchannels') { + for (const elem of context.querySelectorAll('.chkFavorite')) { + elem.addEventListener('change', () => this.onFavoriteChange(elem)); } - context.querySelector(".chkLikes").addEventListener("change", function () { + + const chkLikes = context.querySelector('.chkLikes'); + chkLikes.addEventListener('change', () => { query.StartIndex = 0; - query.IsLiked = this.checked ? true : null; - triggerChange(self); + query.IsLiked = chkLikes.checked ? true : null; + triggerChange(this); }); - context.querySelector(".chkDislikes").addEventListener("change", function () { + const chkDislikes = context.querySelector('.chkDislikes'); + chkDislikes.addEventListener('change', () => { query.StartIndex = 0; - query.IsDisliked = this.checked ? true : null; - triggerChange(self); + query.IsDisliked = chkDislikes.checked ? true : null; + triggerChange(this); }); } else { - elems = context.querySelectorAll(".chkStandardFilter"); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("change", onStandardFilterChange); + for (const elem of context.querySelectorAll('.chkStandardFilter')) { + elem.addEventListener('change', () => this.onStandardFilterChange(elem)); } } - elems = context.querySelectorAll(".chkVideoTypeFilter"); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("change", onVideoTypeFilterChange); + + for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { + elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); } - context.querySelector(".chk3DFilter").addEventListener("change", function () { + const chk3DFilter = context.querySelector('.chk3DFilter'); + chk3DFilter.addEventListener('change', () => { query.StartIndex = 0; - query.Is3D = this.checked ? true : null; - triggerChange(self); + query.Is3D = chk3DFilter.checked ? true : null; + triggerChange(this); }); - context.querySelector(".chk4KFilter").addEventListener("change", function () { + const chk4KFilter = context.querySelector('.chk4KFilter'); + chk4KFilter.addEventListener('change', () => { query.StartIndex = 0; - query.Is4K = this.checked ? true : null; - triggerChange(self); + query.Is4K = chk4KFilter.checked ? true : null; + triggerChange(this); }); - context.querySelector(".chkHDFilter").addEventListener("change", function () { + const chkHDFilter = context.querySelector('.chkHDFilter'); + chkHDFilter.addEventListener('change', () => { query.StartIndex = 0; - query.IsHD = this.checked ? true : null; - triggerChange(self); + query.IsHD = chkHDFilter.checked ? true : null; + triggerChange(this); }); - context.querySelector(".chkSDFilter").addEventListener("change", function () { + const chkSDFilter = context.querySelector('.chkSDFilter'); + chkSDFilter.addEventListener('change', () => { query.StartIndex = 0; - query.IsHD = this.checked ? false : null; - triggerChange(self); + query.IsHD = chkSDFilter.checked ? false : null; + triggerChange(this); }); - elems = context.querySelectorAll(".chkStatus"); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("change", onStatusChange); + for (const elem of context.querySelectorAll('.chkStatus')) { + elem.addEventListener('change', () => this.onStatusChange(elem)); } - context.querySelector("#chkTrailer").addEventListener("change", function () { + const chkTrailer = context.querySelector('#chkTrailer'); + chkTrailer.addEventListener('change', () => { query.StartIndex = 0; - query.HasTrailer = this.checked ? true : null; - triggerChange(self); + query.HasTrailer = chkTrailer.checked ? true : null; + triggerChange(this); }); - context.querySelector("#chkThemeSong").addEventListener("change", function () { + const chkThemeSong = context.querySelector('#chkThemeSong'); + chkThemeSong.addEventListener('change', () => { query.StartIndex = 0; - query.HasThemeSong = this.checked ? true : null; - triggerChange(self); + query.HasThemeSong = chkThemeSong.checked ? true : null; + triggerChange(this); }); - context.querySelector("#chkSpecialFeature").addEventListener("change", function () { + const chkSpecialFeature = context.querySelector('#chkSpecialFeature'); + chkSpecialFeature.addEventListener('change', () => { query.StartIndex = 0; - query.HasSpecialFeature = this.checked ? true : null; - triggerChange(self); + query.HasSpecialFeature = chkSpecialFeature.checked ? true : null; + triggerChange(this); }); - context.querySelector("#chkThemeVideo").addEventListener("change", function () { + const chkThemeVideo = context.querySelector('#chkThemeVideo'); + chkThemeVideo.addEventListener('change', () => { query.StartIndex = 0; - query.HasThemeVideo = this.checked ? true : null; - triggerChange(self); + query.HasThemeVideo = chkThemeVideo.checked ? true : null; + triggerChange(this); }); - context.querySelector("#chkMissingEpisode").addEventListener("change", function () { + const chkMissingEpisode = context.querySelector('#chkMissingEpisode'); + chkMissingEpisode.addEventListener('change', () => { query.StartIndex = 0; - query.IsMissing = this.checked ? true : false; - triggerChange(self); + query.IsMissing = !!chkMissingEpisode.checked; + triggerChange(this); }); - context.querySelector("#chkSpecialEpisode").addEventListener("change", function () { + const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode'); + chkSpecialEpisode.addEventListener('change', () => { query.StartIndex = 0; - query.ParentIndexNumber = this.checked ? 0 : null; - triggerChange(self); + query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null; + triggerChange(this); }); - context.querySelector("#chkFutureEpisode").addEventListener("change", function () { + const chkFutureEpisode = context.querySelector('#chkFutureEpisode'); + chkFutureEpisode.addEventListener('change', () => { query.StartIndex = 0; - if (this.checked) { + if (chkFutureEpisode.checked) { query.IsUnaired = true; query.IsVirtualUnaired = null; } else { query.IsUnaired = null; query.IsVirtualUnaired = false; } - triggerChange(self); + triggerChange(this); }); - context.querySelector("#chkSubtitle").addEventListener("change", function () { + const chkSubtitle = context.querySelector('#chkSubtitle'); + chkSubtitle.addEventListener('change', () => { query.StartIndex = 0; - query.HasSubtitles = this.checked ? true : null; - triggerChange(self); + query.HasSubtitles = chkSubtitle.checked ? true : null; + triggerChange(this); }); - context.addEventListener("change", function (e) { - var chkGenreFilter = parentWithClass(e.target, "chkGenreFilter"); + context.addEventListener('change', (e) => { + const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter'); if (chkGenreFilter) { - var filterName = chkGenreFilter.getAttribute("data-filter"); - var filters = query.Genres || ""; - var delimiter = "|"; - filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1); + const filterName = chkGenreFilter.getAttribute('data-filter'); + let filters = query.Genres || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); if (chkGenreFilter.checked) { filters = filters ? (filters + delimiter + filterName) : filterName; } query.StartIndex = 0; query.Genres = filters; - triggerChange(self); + triggerChange(this); return; } - var chkTagFilter = parentWithClass(e.target, "chkTagFilter"); + const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter'); if (chkTagFilter) { - var filterName = chkTagFilter.getAttribute("data-filter"); - var filters = query.Tags || ""; - var delimiter = "|"; - filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1); + const filterName = chkTagFilter.getAttribute('data-filter'); + let filters = query.Tags || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); if (chkTagFilter.checked) { filters = filters ? (filters + delimiter + filterName) : filterName; } query.StartIndex = 0; query.Tags = filters; - triggerChange(self); + triggerChange(this); return; } - var chkYearFilter = parentWithClass(e.target, "chkYearFilter"); + const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter'); if (chkYearFilter) { - var filterName = chkYearFilter.getAttribute("data-filter"); - var filters = query.Years || ""; - var delimiter = ","; - filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1); + const filterName = chkYearFilter.getAttribute('data-filter'); + let filters = query.Years || ''; + const delimiter = ','; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); if (chkYearFilter.checked) { filters = filters ? (filters + delimiter + filterName) : filterName; } query.StartIndex = 0; query.Years = filters; - triggerChange(self); + triggerChange(this); return; } - var chkOfficialRatingFilter = parentWithClass(e.target, "chkOfficialRatingFilter"); + const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter'); if (chkOfficialRatingFilter) { - var filterName = chkOfficialRatingFilter.getAttribute("data-filter"); - var filters = query.OfficialRatings || ""; - var delimiter = "|"; - filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1); + const filterName = chkOfficialRatingFilter.getAttribute('data-filter'); + let filters = query.OfficialRatings || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); if (chkOfficialRatingFilter.checked) { filters = filters ? (filters + delimiter + filterName) : filterName; } query.StartIndex = 0; query.OfficialRatings = filters; - triggerChange(self); - return; + triggerChange(this); } }); } - var self = this; - - self.show = function () { - return new Promise(function (resolve, reject) { - require(["text!./filterdialog.template.html"], function (template) { - var dlg = dialogHelper.createDialog({ + show() { + return import('text!./filterdialog.template.html').then(({default: template}) => { + return new Promise((resolve) => { + const dlg = dialogHelper.createDialog({ removeOnClose: true, modal: false }); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - dlg.classList.add("formDialog"); - dlg.classList.add("filterDialog"); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('formDialog'); + dlg.classList.add('filterDialog'); dlg.innerHTML = globalize.translateDocument(template); - setVisibility(dlg, options); + setVisibility(dlg, this.options); dialogHelper.open(dlg); - dlg.addEventListener("close", resolve); - updateFilterControls(dlg, options); - bindEvents(dlg); - if (enableDynamicFilters(options.mode)) { - dlg.classList.add("dynamicFilterDialog"); - var apiClient = connectionManager.getApiClient(options.serverId); - loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), options.query); + dlg.addEventListener('close', resolve); + updateFilterControls(dlg, this.options); + this.bindEvents(dlg); + if (enableDynamicFilters(this.options.mode)) { + dlg.classList.add('dynamicFilterDialog'); + const apiClient = connectionManager.getApiClient(this.options.serverId); + loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); } }); }); - }; - }; -}); + } + } + +/* eslint-enable indent */ + +export default FilterDialog; diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 6b08ddab168..c189856e7bd 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -279,7 +279,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', var html = ''; html += '
'; - html += ''; + html += ''; html += '

${Filters}

'; html += '
'; diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 1da58d75c8b..f2022cc445d 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -117,7 +117,7 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) { return false; } - if (elem.getAttribute('tabindex') === "-1") { + if (elem.getAttribute('tabindex') === '-1') { return false; } diff --git a/src/components/formdialog.css b/src/components/formdialog.css index 788331da940..d7cb162e8c4 100644 --- a/src/components/formdialog.css +++ b/src/components/formdialog.css @@ -19,6 +19,10 @@ margin-bottom: 0; } +.formDialogHeaderTitle:first-child { + margin-left: 1em; +} + .formDialogContent:not(.no-grow) { flex-grow: 1; } @@ -46,10 +50,16 @@ right: 0; display: flex; position: absolute; - padding: 1.25em 1em; + padding: 1em 1em; /* Without this emby-checkbox is able to appear on top */ z-index: 1; + align-items: flex-end; + justify-content: center; + flex-wrap: wrap; +} + +.layout-tv .formDialogFooter { align-items: center; justify-content: center; flex-wrap: wrap; @@ -69,8 +79,12 @@ .formDialogFooterItem { margin: 0.5em !important; - flex-grow: 1; text-align: center; + flex-basis: 12em; +} + +.layout-tv .formDialogFooterItem { + flex-grow: 1; flex-basis: 0; } diff --git a/src/components/fullscreenManager.js b/src/components/fullscreenManager.js deleted file mode 100644 index 8ae31073a2b..00000000000 --- a/src/components/fullscreenManager.js +++ /dev/null @@ -1,103 +0,0 @@ -define(['events', 'dom', 'apphost', 'browser'], function (events, dom, appHost, browser) { - 'use strict'; - - function fullscreenManager() { - - } - - fullscreenManager.prototype.requestFullscreen = function (element) { - - element = element || document.documentElement; - - if (element.requestFullscreen) { - element.requestFullscreen(); - return; - } else if (element.mozRequestFullScreen) { - element.mozRequestFullScreen(); - return; - } else if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - return; - } else if (element.msRequestFullscreen) { - element.msRequestFullscreen(); - return; - } - - // Hack - This is only available for video elements in ios safari - if (element.tagName !== 'VIDEO') { - element = document.querySelector('video') || element; - } - if (element.webkitEnterFullscreen) { - element.webkitEnterFullscreen(); - } - }; - - fullscreenManager.prototype.exitFullscreen = function () { - - if (!this.isFullScreen()) { - return; - } - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } - }; - - // TODO: use screenfull.js - fullscreenManager.prototype.isFullScreen = function () { - return document.fullscreen || - document.mozFullScreen || - document.webkitIsFullScreen || - document.msFullscreenElement || /* IE/Edge syntax */ - document.fullscreenElement || /* Standard syntax */ - document.webkitFullscreenElement || /* Chrome, Safari and Opera syntax */ - document.mozFullScreenElement; /* Firefox syntax */ - }; - - var manager = new fullscreenManager(); - - function onFullScreenChange() { - events.trigger(manager, 'fullscreenchange'); - } - - dom.addEventListener(document, 'fullscreenchange', onFullScreenChange, { - passive: true - }); - - dom.addEventListener(document, 'webkitfullscreenchange', onFullScreenChange, { - passive: true - }); - - dom.addEventListener(document, 'mozfullscreenchange', onFullScreenChange, { - passive: true - }); - - function isTargetValid(target) { - return !dom.parentWithTag(target, ['BUTTON', 'INPUT', 'TEXTAREA']); - } - if (appHost.supports("fullscreenchange") && (browser.edgeUwp || -1 !== navigator.userAgent.toLowerCase().indexOf("electron"))) { - - dom.addEventListener(window, 'dblclick', function (e) { - - if (isTargetValid(e.target)) { - if (manager.isFullScreen()) { - manager.exitFullscreen(); - } else { - manager.requestFullscreen(); - } - } - - }, { - passive: true - }); - } - - return manager; -}); diff --git a/src/components/groupedcards.js b/src/components/groupedcards.js index ad638ecdd9f..5f23d8bb186 100644 --- a/src/components/groupedcards.js +++ b/src/components/groupedcards.js @@ -1,28 +1,28 @@ -define(["dom", "appRouter", "connectionManager"], function (dom, appRouter, connectionManager) { - "use strict"; +define(['dom', 'appRouter', 'connectionManager'], function (dom, appRouter, connectionManager) { + 'use strict'; function onGroupedCardClick(e, card) { - var itemId = card.getAttribute("data-id"); - var serverId = card.getAttribute("data-serverid"); + var itemId = card.getAttribute('data-id'); + var serverId = card.getAttribute('data-serverid'); var apiClient = connectionManager.getApiClient(serverId); var userId = apiClient.getCurrentUserId(); - var playedIndicator = card.querySelector(".playedIndicator"); + var playedIndicator = card.querySelector('.playedIndicator'); var playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; var options = { - Limit: parseInt(playedIndicatorHtml || "10"), - Fields: "PrimaryImageAspectRatio,DateCreated", + Limit: parseInt(playedIndicatorHtml || '10'), + Fields: 'PrimaryImageAspectRatio,DateCreated', ParentId: itemId, GroupItems: false }; - var actionableParent = dom.parentWithTag(e.target, ["A", "BUTTON", "INPUT"]); + var 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 (!actionableParent || actionableParent.classList.contains('cardContent')) { + apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { if (1 === items.length) { return void appRouter.showItem(items[0]); } - var url = "itemdetails.html?id=" + itemId + "&serverId=" + serverId; + var url = 'details?id=' + itemId + '&serverId=' + serverId; Dashboard.navigate(url); }); e.stopPropagation(); @@ -32,7 +32,7 @@ define(["dom", "appRouter", "connectionManager"], function (dom, appRouter, conn } function onItemsContainerClick(e) { - var groupedCard = dom.parentWithClass(e.target, "groupedCard"); + var groupedCard = dom.parentWithClass(e.target, 'groupedCard'); if (groupedCard) { onGroupedCardClick(e, groupedCard); diff --git a/src/components/guide/guide-settings.template.html b/src/components/guide/guide-settings.template.html index 02701db3d21..edb2ffa8d3d 100644 --- a/src/components/guide/guide-settings.template.html +++ b/src/components/guide/guide-settings.template.html @@ -1,5 +1,7 @@
- +

${Settings}

diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index a00baaa6f09..2c4a5c7604a 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1,4 +1,4 @@ -define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'registerElement'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { +define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { 'use strict'; function showViewSettings(instance) { @@ -227,7 +227,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', channelQuery.Limit = channelLimit; channelQuery.AddCurrentProgram = false; channelQuery.EnableUserData = false; - channelQuery.EnableImageTypes = "Primary"; + channelQuery.EnableImageTypes = 'Primary'; var categories = self.categoryOptions.categories || []; var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; @@ -261,8 +261,8 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', } if (userSettings.get('livetv-channelorder') === 'DatePlayed') { - channelQuery.SortBy = "DatePlayed"; - channelQuery.SortOrder = "Descending"; + channelQuery.SortBy = 'DatePlayed'; + channelQuery.SortOrder = 'Descending'; } else { channelQuery.SortBy = null; channelQuery.SortOrder = null; @@ -329,7 +329,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', ImageTypeLimit: 1, EnableImages: false, //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", - SortBy: "StartDate", + SortBy: 'StartDate', EnableTotalRecordCount: false, EnableUserData: false }; @@ -415,7 +415,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', var status; if (item.Type === 'SeriesTimer') { - return ''; + return ''; } else if (item.TimerId || item.SeriesTimerId) { status = item.Status || 'Cancelled'; @@ -429,13 +429,13 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', if (item.SeriesTimerId) { if (status !== 'Cancelled') { - return ''; + return ''; } - return ''; + return ''; } - return ''; + return ''; } function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { @@ -502,7 +502,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', var endPercent = (renderEndMs - renderStartMs) / msPerDay; endPercent *= 100; - var cssClass = "programCell itemAction"; + var cssClass = 'programCell itemAction'; var accentCssClass = null; var displayInnerContent = true; @@ -525,11 +525,11 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', } if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { - cssClass += " programCell-" + accentCssClass; + cssClass += ' programCell-' + accentCssClass; } if (now >= startDateLocalMs && now < endDateLocalMs) { - cssClass += " programCell-active"; + cssClass += ' programCell-active'; } var timerAttributes = ''; @@ -545,11 +545,11 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', html += ''; if (displayInnerContent) { - var guideProgramNameClass = "guideProgramName"; + var guideProgramNameClass = 'guideProgramName'; html += '
'; - html += '
'; + html += '
'; html += '
' + program.Name; @@ -577,7 +577,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', html += '
'; if (program.IsHD && options.showHdIcon) { - //html += 'hd'; + //html += ''; if (layoutManager.tv) { html += '
HD
'; } else { @@ -630,7 +630,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', var url = apiClient.getScaledImageUrl(channel.Id, { maxHeight: 220, tag: channel.ImageTags.Primary, - type: "Primary" + type: 'Primary' }); html += '
'; @@ -1105,7 +1105,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', var icon = cell.querySelector('.timerIcon'); if (!icon) { - cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); + cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); } if (newTimerId) { @@ -1252,18 +1252,5 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', }); } - var ProgramCellPrototype = Object.create(HTMLButtonElement.prototype); - - ProgramCellPrototype.detachedCallback = function () { - this.posLeft = null; - this.posWidth = null; - this.guideProgramName = null; - }; - - document.registerElement('emby-programcell', { - prototype: ProgramCellPrototype, - extends: 'button' - }); - return Guide; }); diff --git a/src/components/guide/tvguide.template.html b/src/components/guide/tvguide.template.html index ef3c4b58ca1..730de55909d 100644 --- a/src/components/guide/tvguide.template.html +++ b/src/components/guide/tvguide.template.html @@ -9,8 +9,8 @@
-
@@ -29,10 +29,10 @@
- -
diff --git a/src/components/headroom/headroom.css b/src/components/headroom/headroom.css deleted file mode 100644 index df985892ff1..00000000000 --- a/src/components/headroom/headroom.css +++ /dev/null @@ -1,11 +0,0 @@ -.headroom { - transition: transform 140ms linear; -} - -.headroom--pinned { - transform: none; -} - -.headroom--unpinned:not(.headroomDisabled) { - transform: translateY(-100%); -} diff --git a/src/components/headroom/headroom.js b/src/components/headroom/headroom.js deleted file mode 100644 index 3c0ada3ce9f..00000000000 --- a/src/components/headroom/headroom.js +++ /dev/null @@ -1,343 +0,0 @@ -/*! - * headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it - * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js - * License: MIT - */ - -define(['dom', 'layoutManager', 'browser', 'css!./headroom'], function (dom, layoutManager, browser) { - - 'use strict'; - - /* exported features */ - - var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; - - /** - * Handles debouncing of events via requestAnimationFrame - * @see http://www.html5rocks.com/en/tutorials/speed/animations/ - * @param {Function} callback The callback to handle whichever event - */ - function Debouncer(callback) { - this.callback = callback; - this.ticking = false; - } - Debouncer.prototype = { - constructor: Debouncer, - - /** - * dispatches the event to the supplied callback - * @private - */ - update: function () { - if (this.callback) { - this.callback(); - } - this.ticking = false; - }, - - /** - * Attach this as the event listeners - */ - handleEvent: function () { - if (!this.ticking) { - requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); - this.ticking = true; - } - } - }; - - function onHeadroomClearedExternally() { - this.state = null; - } - - /** - * UI enhancement for fixed headers. - * Hides header when scrolling down - * Shows header when scrolling up - * @constructor - * @param {DOMElement} elem the header element - * @param {Object} options options for the widget - */ - function Headroom(elems, options) { - options = Object.assign(Headroom.options, options || {}); - - this.lastKnownScrollY = 0; - this.elems = elems; - - this.scroller = options.scroller; - - this.debouncer = onScroll.bind(this); - this.offset = options.offset; - this.initialised = false; - - this.initialClass = options.initialClass; - this.unPinnedClass = options.unPinnedClass; - this.pinnedClass = options.pinnedClass; - - this.state = 'clear'; - - this.options = { - offset: 0, - scroller: window, - initialClass: 'headroom', - unPinnedClass: 'headroom--unpinned', - pinnedClass: 'headroom--pinned' - }; - - this.add = function (elem) { - - if (browser.supportsCssAnimation()) { - elem.classList.add(this.initialClass); - elem.addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this)); - this.elems.push(elem); - } - }; - - this.remove = function (elem) { - - elem.classList.remove(this.unPinnedClass); - elem.classList.remove(this.initialClass); - elem.classList.remove(this.pinnedClass); - - var i = this.elems.indexOf(elem); - if (i !== -1) { - this.elems.splice(i, 1); - } - }; - - this.pause = function () { - this.paused = true; - }; - - this.resume = function () { - this.paused = false; - }; - - /** - * Unattaches events and removes any classes that were added - */ - this.destroy = function () { - - this.initialised = false; - - for (var i = 0, length = this.elems.length; i < length; i++) { - - var classList = this.elems[i].classList; - - classList.remove(this.unPinnedClass); - classList.remove(this.initialClass); - classList.remove(this.pinnedClass); - } - - var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll'; - - dom.removeEventListener(this.scroller, scrollEventName, this.debouncer, { - capture: false, - passive: true - }); - }; - - /** - * Attaches the scroll event - * @private - */ - this.attachEvent = function () { - if (!this.initialised) { - this.lastKnownScrollY = this.getScrollY(); - this.initialised = true; - - var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll'; - - dom.addEventListener(this.scroller, scrollEventName, this.debouncer, { - capture: false, - passive: true - }); - - this.update(); - } - }; - - /** - * Unpins the header if it's currently pinned - */ - this.clear = function () { - - if (this.state === 'clear') { - return; - } - - this.state = 'clear'; - - var unpinnedClass = this.unPinnedClass; - var pinnedClass = this.pinnedClass; - - for (var i = 0, length = this.elems.length; i < length; i++) { - var classList = this.elems[i].classList; - - classList.remove(unpinnedClass); - //classList.remove(pinnedClass); - } - }; - - /** - * Unpins the header if it's currently pinned - */ - this.pin = function () { - - if (this.state === 'pin') { - return; - } - - this.state = 'pin'; - - var unpinnedClass = this.unPinnedClass; - var pinnedClass = this.pinnedClass; - - for (var i = 0, length = this.elems.length; i < length; i++) { - var classList = this.elems[i].classList; - - classList.remove(unpinnedClass); - classList.add(pinnedClass); - } - }; - - /** - * Unpins the header if it's currently pinned - */ - this.unpin = function () { - - if (this.state === 'unpin') { - return; - } - - this.state = 'unpin'; - - var unpinnedClass = this.unPinnedClass; - var pinnedClass = this.pinnedClass; - - for (var i = 0, length = this.elems.length; i < length; i++) { - var classList = this.elems[i].classList; - - classList.add(unpinnedClass); - //classList.remove(pinnedClass); - } - }; - - /** - * Gets the Y scroll position - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY - * @return {Number} pixels the page has scrolled along the Y-axis - */ - this.getScrollY = function () { - - var scroller = this.scroller; - - if (scroller.getScrollPosition) { - return scroller.getScrollPosition(); - } - - var pageYOffset = scroller.pageYOffset; - if (pageYOffset !== undefined) { - return pageYOffset; - } - - var scrollTop = scroller.scrollTop; - if (scrollTop !== undefined) { - return scrollTop; - } - - return (document.documentElement || document.body).scrollTop; - }; - - /** - * determine if it is appropriate to unpin - * @param {int} currentScrollY the current y scroll position - * @return {bool} true if should unpin, false otherwise - */ - this.shouldUnpin = function (currentScrollY) { - var scrollingDown = currentScrollY > this.lastKnownScrollY; - var pastOffset = currentScrollY >= this.offset; - - return scrollingDown && pastOffset; - }; - - /** - * determine if it is appropriate to pin - * @param {int} currentScrollY the current y scroll position - * @return {bool} true if should pin, false otherwise - */ - this.shouldPin = function (currentScrollY) { - var scrollingUp = currentScrollY < this.lastKnownScrollY; - var pastOffset = currentScrollY <= this.offset; - - return scrollingUp || pastOffset; - }; - - /** - * Handles updating the state of the widget - */ - this.update = function () { - - if (this.paused) { - return; - } - - var currentScrollY = this.getScrollY(); - - var lastKnownScrollY = this.lastKnownScrollY; - - var isTv = layoutManager.tv; - - if (currentScrollY <= (isTv ? 120 : 10)) { - this.clear(); - } else if (this.shouldUnpin(currentScrollY)) { - this.unpin(); - } else if (this.shouldPin(currentScrollY)) { - - var toleranceExceeded = Math.abs(currentScrollY - lastKnownScrollY) >= 14; - - if (currentScrollY && isTv) { - this.unpin(); - } else if (toleranceExceeded) { - this.clear(); - } - } else if (isTv) { - //this.clear(); - } - - this.lastKnownScrollY = currentScrollY; - }; - - if (browser.supportsCssAnimation()) { - for (var i = 0, length = this.elems.length; i < length; i++) { - this.elems[i].classList.add(this.initialClass); - this.elems[i].addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this)); - } - - this.attachEvent(); - } - } - - function onScroll() { - - if (this.paused) { - return; - } - - requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); - } - - /** - * Default options - * @type {Object} - */ - Headroom.options = { - offset: 0, - scroller: window, - initialClass: 'headroom', - unPinnedClass: 'headroom--unpinned', - pinnedClass: 'headroom--pinned' - }; - - return Headroom; -}); diff --git a/src/components/homescreensettings/homescreensettings.js b/src/components/homeScreenSettings/homeScreenSettings.js similarity index 94% rename from src/components/homescreensettings/homescreensettings.js rename to src/components/homeScreenSettings/homeScreenSettings.js index ccc6fa75f41..9eae944d26f 100644 --- a/src/components/homescreensettings/homescreensettings.js +++ b/src/components/homeScreenSettings/homeScreenSettings.js @@ -1,5 +1,5 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loading', 'connectionManager', 'homeSections', 'dom', 'events', 'listViewStyle', 'emby-select', 'emby-checkbox'], function (require, appHost, layoutManager, focusManager, globalize, loading, connectionManager, homeSections, dom, events) { - "use strict"; + 'use strict'; var numConfigurableSections = 7; @@ -37,18 +37,19 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa var 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('Genres'), + value: 'genres' + }); list.push({ name: globalize.translate('Favorites'), value: 'favorites' @@ -58,7 +59,6 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa value: 'collections' }); } else if (type === 'tvshows') { - list.push({ name: globalize.translate('Shows'), value: 'shows', @@ -68,49 +68,45 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa name: globalize.translate('Suggestions'), value: 'suggestions' }); - list.push({ name: globalize.translate('Latest'), value: 'latest' }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); list.push({ name: globalize.translate('Favorites'), value: 'favorites' }); } else if (type === 'music') { - list.push({ name: globalize.translate('Suggestions'), value: 'suggestions', isDefault: true }); - list.push({ name: globalize.translate('Albums'), value: 'albums' }); - 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('Genres'), value: 'genres' }); } else if (type === 'livetv') { - list.push({ name: globalize.translate('Suggestions'), value: 'suggestions', @@ -149,7 +145,7 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa currentHtml += '
'; - currentHtml += ''; + currentHtml += ''; currentHtml += '
'; @@ -159,8 +155,8 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa currentHtml += '
'; - currentHtml += ''; - currentHtml += ''; + currentHtml += ''; + currentHtml += ''; currentHtml += '
'; @@ -276,7 +272,7 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa updateHomeSectionValues(context, userSettings); var promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); - var promise2 = apiClient.getJSON(apiClient.getUrl("Users/" + user.Id + "/GroupingOptions")); + var promise2 = apiClient.getJSON(apiClient.getUrl('Users/' + user.Id + '/GroupingOptions')); Promise.all([promise1, promise2]).then(function (responses) { @@ -363,17 +359,17 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked; - user.Configuration.LatestItemsExcludes = getCheckboxItems(".chkIncludeInLatest", context, false).map(function (i) { + user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(function (i) { return i.getAttribute('data-folderid'); }); - user.Configuration.MyMediaExcludes = getCheckboxItems(".chkIncludeInMyMedia", context, false).map(function (i) { + user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(function (i) { return i.getAttribute('data-folderid'); }); - user.Configuration.GroupedFolders = getCheckboxItems(".chkGroupFolder", context, true).map(function (i) { + user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(function (i) { return i.getAttribute('data-folderid'); }); @@ -470,7 +466,7 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa function embed(options, self) { - require(['text!./homescreensettings.template.html'], function (template) { + require(['text!./homeScreenSettings.template.html'], function (template) { for (var i = 1; i <= numConfigurableSections; i++) { template = template.replace('{section' + i + 'label}', globalize.translate('LabelHomeScreenSectionValue', i)); diff --git a/src/components/homescreensettings/homescreensettings.template.html b/src/components/homeScreenSettings/homeScreenSettings.template.html similarity index 95% rename from src/components/homescreensettings/homescreensettings.template.html rename to src/components/homeScreenSettings/homeScreenSettings.template.html index d5bae685b84..8515f3f0ad6 100644 --- a/src/components/homescreensettings/homescreensettings.template.html +++ b/src/components/homeScreenSettings/homeScreenSettings.template.html @@ -10,6 +10,13 @@

${HeaderHome}

${LabelPleaseRestart}
+
+ +
+
- ${HideWatchedContentFromLatestMedia} - -
-

${HeaderLibraryFolders}

diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index b0245285493..bba4b9db1bf 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -64,21 +64,21 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } else { var noLibDescription; if (user['Policy'] && user['Policy']['IsAdministrator']) { - noLibDescription = Globalize.translate("NoCreatedLibraries", '', '') + noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); } else { - noLibDescription = Globalize.translate("AskAdminToCreateLibrary"); + noLibDescription = globalize.translate('AskAdminToCreateLibrary'); } html += '
'; - html += '

' + Globalize.translate("MessageNothingHere") + '

'; - html += '

' + noLibDescription + '

' + html += '

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

'; + html += '

' + noLibDescription + '

'; html += '
'; elem.innerHTML = html; - var createNowLink = elem.querySelector("#button-createLibrary") + var createNowLink = elem.querySelector('#button-createLibrary'); if (createNowLink) { - createNowLink.addEventListener("click", function () { - Dashboard.navigate("library.html"); + createNowLink.addEventListener('click', function () { + Dashboard.navigate('library.html'); }); } } @@ -131,7 +131,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews, allSections); } else if (section === 'librarybuttons') { - loadlibraryButtons(elem, apiClient, user, userSettings, userViews, allSections); + loadlibraryButtons(elem, apiClient, user, userSettings, userViews); } else if (section === 'resume') { loadResumeVideo(elem, apiClient, userId); } else if (section === 'resumeaudio') { @@ -168,11 +168,11 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } function getPortraitShape() { - return enableScrollX() ? 'autooverflow' : 'auto'; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } function getLibraryButtonsHtml(items) { - var html = ""; + var html = ''; html += '
'; html += '

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

'; @@ -183,7 +183,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la for (var i = 0, length = items.length; i < length; i++) { var item = items[i]; var icon = imageHelper.getLibraryIcon(item.CollectionType); - html += '' + item.Name + ''; + html += '' + item.Name + ''; } html += '
'; @@ -229,9 +229,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var options = { Limit: limit, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + EnableImageTypes: 'Primary,Backdrop,Thumb', ParentId: parentId }; @@ -243,9 +243,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la return function (items) { var cardLayout = false; var shape; - if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books') { + if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { shape = getPortraitShape(); - } else if (viewType === 'music') { + } else if (viewType === 'music' || viewType === 'homevideos') { shape = getSquareShape(); } else { shape = getThumbShape(); @@ -254,7 +254,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la return cardBuilder.getCardsHtml({ items: items, shape: shape, - preferThumb: viewType !== 'movies' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, + preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, showUnplayedIndicator: false, showChildCountIndicator: true, context: 'home', @@ -282,7 +282,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la html += '

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

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

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

'; @@ -386,9 +386,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var options = { Limit: limit, Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + EnableImageTypes: 'Primary,Backdrop,Thumb', EnableTotalRecordCount: false, MediaTypes: 'Video' }; @@ -459,9 +459,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var options = { Limit: limit, Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + EnableImageTypes: 'Primary,Backdrop,Thumb', EnableTotalRecordCount: false, MediaTypes: 'Audio' }; @@ -524,9 +524,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la IsAiring: true, limit: 24, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Thumb,Backdrop", + EnableImageTypes: 'Primary,Thumb,Backdrop', EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio" + Fields: 'ChannelInfo,PrimaryImageAspectRatio' }); }; } @@ -565,9 +565,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la IsAiring: true, limit: 1, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Thumb,Backdrop", + EnableImageTypes: 'Primary,Thumb,Backdrop', EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio" + Fields: 'ChannelInfo,PrimaryImageAspectRatio' }).then(function (result) { var html = ''; if (result.Items.length) { @@ -630,7 +630,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la html += '

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

'; - html += ''; + html += ''; html += ''; } else { @@ -640,7 +640,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } @@ -667,10 +667,10 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var apiClient = connectionManager.getApiClient(serverId); return apiClient.getNextUpEpisodes({ Limit: enableScrollX() ? 24 : 15, - Fields: "PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo,Path', UserId: apiClient.getCurrentUserId(), ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }); }; @@ -705,7 +705,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la html += '

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

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

' + globalize.translate('HeaderNextUp') + '

'; @@ -714,7 +714,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } @@ -739,7 +739,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la return apiClient.getLiveTvRecordings({ userId: apiClient.getCurrentUserId(), Limit: enableScrollX() ? 12 : 5, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', EnableTotalRecordCount: false, IsLibraryItem: activeRecordingsOnly ? null : false, IsInProgress: activeRecordingsOnly ? true : null @@ -786,7 +786,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 338b8e6fefc..3f17eeb3365 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -2,12 +2,12 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve 'use strict'; function getSavedVolume() { - return appSettings.get("volume") || 1; + return appSettings.get('volume') || 1; } function saveVolume(value) { if (value) { - appSettings.set("volume", value); + appSettings.set('volume', value); } } @@ -31,7 +31,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve } function enableHlsShakaPlayer(item, mediaSource, mediaType) { - + /* eslint-disable-next-line compat/compat */ if (!!window.MediaSource && !!MediaSource.isTypeSupported) { if (canPlayNativeHls()) { @@ -109,7 +109,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve var now = Date.now(); if (window.performance && window.performance.now) { - now = performance.now(); + now = performance.now(); // eslint-disable-line compat/compat } if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { @@ -162,7 +162,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve } } - function seekOnPlaybackStart(instance, element, ticks) { + function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { var seconds = (ticks || 0) / 10000000; @@ -171,13 +171,31 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve // Appending #t=xxx to the query string doesn't seem to work with HLS // For plain video files, not all browsers support it either - var delay = browser.safari ? 2500 : 0; - if (delay) { - setTimeout(function () { - setCurrentTimeIfNeeded(element, seconds); - }, delay); - } else { + + 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 + var events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata']; + var 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.map(function(name) { + element.removeEventListener(name, onMediaChange); + }); + if (onMediaReady) onMediaReady(); + } + }; + events.map(function (name) { + return element.addEventListener(name, onMediaChange); + }); } } } @@ -355,13 +373,13 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve onErrorInternal(instance, 'network'); } } else { - console.debug("fatal network error encountered, try to recover"); + 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"); + console.debug('fatal media error encountered, try to recover'); var currentReject = reject; reject = null; handleHlsJsMediaError(instance, currentReject); @@ -391,7 +409,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve elem.src = ''; elem.innerHTML = ''; - elem.removeAttribute("src"); + elem.removeAttribute('src'); destroyHlsPlayer(instance); destroyFlvPlayer(instance); diff --git a/src/components/humanedate.js b/src/components/humanedate.js deleted file mode 100644 index 26ce26d9421..00000000000 --- a/src/components/humanedate.js +++ /dev/null @@ -1,74 +0,0 @@ -define(["datetime"], function (datetime) { - "use strict"; - - function humaneDate(date_str) { - var format; - var time_formats = [ - [90, "a minute"], - [3600, "minutes", 60], - [5400, "an hour"], - [86400, "hours", 3600], - [129600, "a day"], - [604800, "days", 86400], - [907200, "a week"], - [2628e3, "weeks", 604800], - [3942e3, "a month"], - [31536e3, "months", 2628e3], - [47304e3, "a year"], - [31536e5, "years", 31536e3] - ]; - var dt = new Date(); - var date = datetime.parseISO8601Date(date_str, true); - var seconds = (dt - date) / 1000.0; - var i = 0; - - if (seconds < 0) { - seconds = Math.abs(seconds); - } - // eslint-disable-next-line no-cond-assign - for (; format = time_formats[i++];) { - if (seconds < format[0]) { - if (2 == format.length) { - return format[1] + " ago"; - } - - return Math.round(seconds / format[2]) + " " + format[1] + " ago"; - } - } - - if (seconds > 47304e5) { - return Math.round(seconds / 47304e5) + " centuries ago"; - } - - return date_str; - } - - function humaneElapsed(firstDateStr, secondDateStr) { - // TODO replace this whole script with a library or something - var dateOne = new Date(firstDateStr); - var dateTwo = new Date(secondDateStr); - var delta = (dateTwo.getTime() - dateOne.getTime()) / 1e3; - var days = Math.floor(delta % 31536e3 / 86400); - var hours = Math.floor(delta % 31536e3 % 86400 / 3600); - var minutes = Math.floor(delta % 31536e3 % 86400 % 3600 / 60); - var seconds = Math.round(delta % 31536e3 % 86400 % 3600 % 60); - var elapsed = ""; - elapsed += 1 == days ? days + " day " : ""; - elapsed += days > 1 ? days + " days " : ""; - elapsed += 1 == hours ? hours + " hour " : ""; - elapsed += hours > 1 ? hours + " hours " : ""; - elapsed += 1 == minutes ? minutes + " minute " : ""; - elapsed += minutes > 1 ? minutes + " minutes " : ""; - elapsed += elapsed.length > 0 ? "and " : ""; - elapsed += 1 == seconds ? seconds + " second" : ""; - elapsed += 0 == seconds || seconds > 1 ? seconds + " seconds" : ""; - return elapsed; - } - - window.humaneDate = humaneDate; - window.humaneElapsed = humaneElapsed; - return { - humaneDate: humaneDate, - humaneElapsed: humaneElapsed - }; -}); diff --git a/src/components/imagedownloader/imagedownloader.js b/src/components/imageDownloader/imageDownloader.js similarity index 84% rename from src/components/imagedownloader/imagedownloader.js rename to src/components/imageDownloader/imageDownloader.js index ce53b5cf0d9..a3965279cf8 100644 --- a/src/components/imagedownloader/imagedownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -1,4 +1,4 @@ -define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader', 'browser', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'emby-checkbox', 'paper-icon-button-light', 'emby-button', 'formDialogStyle', 'cardStyle'], function (loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) { +define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader', 'browser', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'emby-checkbox', 'paper-icon-button-light', 'emby-button', 'formDialogStyle', 'cardStyle'], function (dom, loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) { 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; @@ -109,15 +109,15 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' html += ''; var startAtDisplay = totalRecordCount ? startIndex + 1 : 0; - html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount; + html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount); html += ''; if (showControls) { html += '
'; - html += ''; - html += ''; + html += ''; + html += ''; html += '
'; } @@ -126,21 +126,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' return html; } - function parentWithClass(elem, className) { - - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - function downloadRemoteImage(page, apiClient, url, type, provider) { - var options = getBaseRemoteOptions(); options.Type = type; @@ -152,17 +138,16 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' apiClient.downloadRemoteImage(options).then(function () { hasChanges = true; - var dlg = parentWithClass(page, 'dialog'); + var dlg = dom.parentWithClass(page, 'dialog'); dialogHelper.close(dlg); }); } function getDisplayUrl(url, apiClient) { - return apiClient.getUrl("Images/Remote", { imageUrl: url }); + return apiClient.getUrl('Images/Remote', { imageUrl: url }); } function getRemoteImageHtml(image, imageType, apiClient) { - var tagName = layoutManager.tv ? 'button' : 'div'; var enableFooterButtons = !layoutManager.tv; @@ -170,21 +155,21 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' var html = ''; - var cssClass = "card scalableCard imageEditorCard"; + var cssClass = 'card scalableCard imageEditorCard'; var cardBoxCssClass = 'cardBox visualCardBox'; var shape = 'backdrop'; - if (imageType === "Backdrop" || imageType === "Art" || imageType === "Thumb" || imageType === "Logo") { + if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') { shape = 'backdrop'; - } else if (imageType === "Banner") { + } else if (imageType === 'Banner') { shape = 'banner'; - } else if (imageType === "Disc") { + } else if (imageType === 'Disc') { shape = 'square'; } else { - if (currentItemType === "Episode") { + if (currentItemType === 'Episode') { shape = 'backdrop'; - } else if (currentItemType === "MusicAlbum" || currentItemType === "MusicArtist") { + } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { shape = 'square'; } else { shape = 'portrait'; @@ -218,9 +203,9 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' html += '
'; if (layoutManager.tv || !appHost.supports('externallinks')) { - html += '
'; + html += '
'; } else { - html += ''; + html += ''; } html += '
'; @@ -256,18 +241,18 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' html += '
'; - if (image.RatingType === "Likes") { - html += image.CommunityRating + (image.CommunityRating === 1 ? " like" : " likes"); + if (image.RatingType === 'Likes') { + html += image.CommunityRating + (image.CommunityRating === 1 ? ' like' : ' likes'); } else { if (image.CommunityRating) { html += image.CommunityRating.toFixed(1); if (image.VoteCount) { - html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? " vote" : " votes"); + html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? ' vote' : ' votes'); } } else { - html += "Unrated"; + html += 'Unrated'; } } @@ -277,7 +262,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' if (enableFooterButtons) { html += '
'; - html += ''; + html += ''; html += '
'; } @@ -293,7 +278,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' } function initEditor(page, apiClient) { - page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { browsableImageType = this.value; browsableImageStartIndex = 0; @@ -319,14 +303,14 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' page.addEventListener('click', function (e) { - var btnDownloadRemoteImage = parentWithClass(e.target, 'btnDownloadRemoteImage'); + var btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage'); if (btnDownloadRemoteImage) { - var card = parentWithClass(btnDownloadRemoteImage, 'card'); + var card = dom.parentWithClass(btnDownloadRemoteImage, 'card'); downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider')); return; } - var btnImageCard = parentWithClass(e.target, 'btnImageCard'); + var btnImageCard = dom.parentWithClass(e.target, 'btnImageCard'); if (btnImageCard) { downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider')); } @@ -334,10 +318,9 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' } function showEditor(itemId, serverId, itemType) { - loading.show(); - require(['text!./imagedownloader.template.html'], function (template) { + require(['text!./imageDownloader.template.html'], function (template) { var apiClient = connectionManager.getApiClient(serverId); @@ -351,7 +334,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = 'fullscreen-border'; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); @@ -380,7 +363,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' } function onDialogClosed() { - var dlg = this; if (layoutManager.tv) { @@ -397,9 +379,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader' return { show: function (itemId, serverId, itemType, imageType) { - return new Promise(function (resolve, reject) { - currentResolve = resolve; currentReject = reject; hasChanges = false; diff --git a/src/components/imagedownloader/imagedownloader.template.html b/src/components/imageDownloader/imageDownloader.template.html similarity index 94% rename from src/components/imagedownloader/imagedownloader.template.html rename to src/components/imageDownloader/imageDownloader.template.html index dec796fb05c..b88ea739239 100644 --- a/src/components/imagedownloader/imagedownloader.template.html +++ b/src/components/imageDownloader/imageDownloader.template.html @@ -1,11 +1,11 @@
- +

${Search}

-
+
diff --git a/src/components/imageoptionseditor/imageoptionseditor.js b/src/components/imageOptionsEditor/imageOptionsEditor.js similarity index 54% rename from src/components/imageoptionseditor/imageoptionseditor.js rename to src/components/imageOptionsEditor/imageOptionsEditor.js index c9dc6780991..257921dfa28 100644 --- a/src/components/imageoptionseditor/imageoptionseditor.js +++ b/src/components/imageOptionsEditor/imageOptionsEditor.js @@ -1,11 +1,11 @@ -define(["globalize", "dom", "dialogHelper", "emby-checkbox", "emby-select", "emby-input"], function (globalize, dom, dialogHelper) { - "use strict"; +define(['globalize', 'dom', 'dialogHelper', 'emby-checkbox', 'emby-select', 'emby-input'], function (globalize, dom, dialogHelper) { + 'use strict'; function getDefaultImageConfig(itemType, type) { return { Type: type, MinWidth: 0, - Limit: "Primary" === type ? 1 : 0 + Limit: 'Primary' === type ? 1 : 0 }; } @@ -21,27 +21,27 @@ define(["globalize", "dom", "dialogHelper", "emby-checkbox", "emby-select", "emb function setVisibilityOfBackdrops(elem, visible) { if (visible) { - elem.classList.remove("hide"); - elem.querySelector("input").setAttribute("required", "required"); + 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"); + elem.classList.add('hide'); + elem.querySelector('input').setAttribute('required', ''); + elem.querySelector('input').removeAttribute('required'); } } function loadValues(context, itemType, options, availableOptions) { var supportedImageTypes = availableOptions.SupportedImageTypes || []; - setVisibilityOfBackdrops(context.querySelector(".backdropFields"), -1 != supportedImageTypes.indexOf("Backdrop")); - setVisibilityOfBackdrops(context.querySelector(".screenshotFields"), -1 != supportedImageTypes.indexOf("Screenshot")); - Array.prototype.forEach.call(context.querySelectorAll(".imageType"), function (i) { - var imageType = i.getAttribute("data-imagetype"); - var container = dom.parentWithTag(i, "LABEL"); + setVisibilityOfBackdrops(context.querySelector('.backdropFields'), -1 != supportedImageTypes.indexOf('Backdrop')); + setVisibilityOfBackdrops(context.querySelector('.screenshotFields'), -1 != supportedImageTypes.indexOf('Screenshot')); + Array.prototype.forEach.call(context.querySelectorAll('.imageType'), function (i) { + var imageType = i.getAttribute('data-imagetype'); + var container = dom.parentWithTag(i, 'LABEL'); if (-1 == supportedImageTypes.indexOf(imageType)) { - container.classList.add("hide"); + container.classList.add('hide'); } else { - container.classList.remove("hide"); + container.classList.remove('hide'); } if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { @@ -50,31 +50,31 @@ define(["globalize", "dom", "dialogHelper", "emby-checkbox", "emby-select", "emb i.checked = false; } }); - var backdropConfig = getImageConfig(options, availableOptions, "Backdrop", itemType); - context.querySelector("#txtMaxBackdrops").value = backdropConfig.Limit; - context.querySelector("#txtMinBackdropDownloadWidth").value = backdropConfig.MinWidth; - var screenshotConfig = getImageConfig(options, availableOptions, "Screenshot", itemType); - context.querySelector("#txtMaxScreenshots").value = screenshotConfig.Limit; - context.querySelector("#txtMinScreenshotDownloadWidth").value = screenshotConfig.MinWidth; + var backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); + context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; + context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; + var screenshotConfig = getImageConfig(options, availableOptions, 'Screenshot', itemType); + context.querySelector('#txtMaxScreenshots').value = screenshotConfig.Limit; + context.querySelector('#txtMinScreenshotDownloadWidth').value = screenshotConfig.MinWidth; } function saveValues(context, options) { - options.ImageOptions = Array.prototype.map.call(context.querySelectorAll(".imageType:not(.hide)"), function (c) { + options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), function (c) { return { - Type: c.getAttribute("data-imagetype"), + 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 + Type: 'Backdrop', + Limit: context.querySelector('#txtMaxBackdrops').value, + MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value }); options.ImageOptions.push({ - Type: "Screenshot", - Limit: context.querySelector("#txtMaxScreenshots").value, - MinWidth: context.querySelector("#txtMinScreenshotDownloadWidth").value + Type: 'Screenshot', + Limit: context.querySelector('#txtMaxScreenshots').value, + MinWidth: context.querySelector('#txtMinScreenshotDownloadWidth').value }); } @@ -82,23 +82,23 @@ define(["globalize", "dom", "dialogHelper", "emby-checkbox", "emby-select", "emb this.show = function (itemType, options, availableOptions) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); - xhr.open("GET", "components/imageoptionseditor/imageoptionseditor.template.html", true); + xhr.open('GET', 'components/imageOptionsEditor/imageOptionsEditor.template.html', true); xhr.onload = function (e) { var template = this.response; var dlg = dialogHelper.createDialog({ - size: "medium-tall", + size: 'small', removeOnClose: true, scrollY: false }); - dlg.classList.add("formDialog"); + dlg.classList.add('formDialog'); dlg.innerHTML = globalize.translateDocument(template); - dlg.addEventListener("close", function () { + dlg.addEventListener('close', function () { saveValues(dlg, options); }); loadValues(dlg, itemType, options, availableOptions); dialogHelper.open(dlg).then(resolve, resolve); - dlg.querySelector(".btnCancel").addEventListener("click", function () { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); }; diff --git a/src/components/imageoptionseditor/imageoptionseditor.template.html b/src/components/imageOptionsEditor/imageOptionsEditor.template.html similarity index 97% rename from src/components/imageoptionseditor/imageoptionseditor.template.html rename to src/components/imageOptionsEditor/imageOptionsEditor.template.html index 910d8fdd6e2..cbbc3b85f58 100644 --- a/src/components/imageoptionseditor/imageoptionseditor.template.html +++ b/src/components/imageOptionsEditor/imageOptionsEditor.template.html @@ -1,5 +1,5 @@
- +

${HeaderImageOptions}

diff --git a/src/components/imageuploader/imageuploader.js b/src/components/imageUploader/imageUploader.js similarity index 90% rename from src/components/imageuploader/imageuploader.js rename to src/components/imageUploader/imageUploader.js index 400646f6696..417bd94e115 100644 --- a/src/components/imageuploader/imageuploader.js +++ b/src/components/imageUploader/imageUploader.js @@ -58,6 +58,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' var html = [''].join(''); page.querySelector('#imageOutput').innerHTML = html; + page.querySelector('#dropImageText').classList.add('hide'); page.querySelector('#fldUpload').classList.remove('hide'); }; })(file); @@ -74,7 +75,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' return false; } - if (!file.type.startsWith("image/")) { + if (!file.type.startsWith('image/')) { require(['toast'], function (toast) { toast(globalize.translate('MessageImageFileTypeAllowed')); }); @@ -87,9 +88,9 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' var dlg = dom.parentWithClass(this, 'dialog'); var imageType = dlg.querySelector('#selectImageType').value; - if (imageType === "None") { - require(["toast"], function(toast) { - toast(globalize.translate("MessageImageTypeNotSelected")); + if (imageType === 'None') { + require(['toast'], function(toast) { + toast(globalize.translate('MessageImageTypeNotSelected')); }); e.preventDefault(); return false; @@ -112,11 +113,11 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' page.querySelector('form').addEventListener('submit', onSubmit); - page.querySelector('#uploadImage').addEventListener("change", function () { + page.querySelector('#uploadImage').addEventListener('change', function () { setFiles(page, this.files); }); - page.querySelector('.btnBrowse').addEventListener("click", function () { + page.querySelector('.btnBrowse').addEventListener('click', function () { page.querySelector('#uploadImage').click(); }); } @@ -125,7 +126,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' options = options || {}; - require(['text!./imageuploader.template.html'], function (template) { + require(['text!./imageUploader.template.html'], function (template) { currentItemId = options.itemId; currentServerId = options.serverId; @@ -137,7 +138,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = 'fullscreen-border'; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); diff --git a/src/components/imageuploader/imageuploader.template.html b/src/components/imageUploader/imageUploader.template.html similarity index 90% rename from src/components/imageuploader/imageuploader.template.html rename to src/components/imageUploader/imageUploader.template.html index 1b83b50955e..6a9e21ff156 100644 --- a/src/components/imageuploader/imageuploader.template.html +++ b/src/components/imageUploader/imageUploader.template.html @@ -1,11 +1,11 @@
- +

${HeaderUploadImage}

-
+
@@ -14,13 +14,13 @@

${HeaderAddUpdateImage}

-
${LabelDropImageHere}
+
${LabelDropImageHere}
diff --git a/src/components/imageuploader/style.css b/src/components/imageUploader/style.css similarity index 100% rename from src/components/imageuploader/style.css rename to src/components/imageUploader/style.css diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index 20aebf43053..2927a0b1209 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -99,10 +99,10 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', var html = ''; - var cssClass = "card scalableCard imageEditorCard"; + var cssClass = 'card scalableCard imageEditorCard'; var cardBoxCssClass = 'cardBox visualCardBox'; - cssClass += " backdropCard backdropCard-scalable"; + cssClass += ' backdropCard backdropCard-scalable'; if (tagName === 'button') { cssClass += ' btnImageCard'; @@ -132,7 +132,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', var imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize }); - html += '
'; + html += '
'; html += '
'; html += '
'; @@ -152,26 +152,26 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', if (enableFooterButtons) { html += '
'; - if (image.ImageType === "Backdrop" || image.ImageType === "Screenshot") { + if (image.ImageType === 'Backdrop' || image.ImageType === 'Screenshot') { if (index > 0) { - html += ''; + html += ''; } else { - html += ''; + html += ''; } if (index < numImages - 1) { - html += ''; + html += ''; } else { - html += ''; + html += ''; } } else { if (imageProviders.length) { - html += ''; + html += ''; } } - html += ''; + html += ''; html += '
'; } @@ -251,7 +251,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType !== "Screenshot" && i.ImageType !== "Backdrop" && i.ImageType !== "Chapter"; + return i.ImageType !== 'Screenshot' && i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter'; }); renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); @@ -260,7 +260,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType === "Backdrop"; + return i.ImageType === 'Backdrop'; }).sort(function (a, b) { return a.ImageIndex - b.ImageIndex; @@ -277,7 +277,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', function renderScreenshots(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType === "Screenshot"; + return i.ImageType === 'Screenshot'; }).sort(function (a, b) { return a.ImageIndex - b.ImageIndex; @@ -425,7 +425,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', addListeners(context, 'btnDeleteImage', 'click', function () { var type = this.getAttribute('data-imagetype'); var index = this.getAttribute('data-index'); - index = index === "null" ? null : parseInt(index); + index = index === 'null' ? null : parseInt(index); var apiClient = connectionManager.getApiClient(currentItem.ServerId); deleteImage(context, currentItem.Id, type, index, apiClient, true); }); @@ -457,7 +457,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = 'fullscreen-border'; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); diff --git a/src/components/imageeditor/imageeditor.template.html b/src/components/imageeditor/imageeditor.template.html index 37dce874c09..ce24e724bd7 100644 --- a/src/components/imageeditor/imageeditor.template.html +++ b/src/components/imageeditor/imageeditor.template.html @@ -1,21 +1,21 @@
- +

${HeaderEditImages}

-
+

${Images}

@@ -27,10 +27,10 @@

${Images}

${Backdrops}

@@ -42,10 +42,10 @@

${Backdrops}

${Screenshots}

diff --git a/src/components/images/imageFetcher.js b/src/components/images/imageFetcher.js deleted file mode 100644 index 1e13cebc77f..00000000000 --- a/src/components/images/imageFetcher.js +++ /dev/null @@ -1,38 +0,0 @@ -define(['dom'], function (dom) { - 'use strict'; - - function loadImage(elem, url) { - - if (!elem) { - return Promise.reject('elem cannot be null'); - } - - if (elem.tagName !== "IMG") { - - elem.style.backgroundImage = "url('" + url + "')"; - return Promise.resolve(); - - //return loadImageIntoImg(document.createElement('img'), url).then(function () { - // elem.style.backgroundImage = "url('" + url + "')"; - // return Promise.resolve(); - //}); - - } - return loadImageIntoImg(elem, url); - } - - function loadImageIntoImg(elem, url) { - return new Promise(function (resolve, reject) { - - dom.addEventListener(elem, 'load', resolve, { - once: true - }); - elem.setAttribute("src", url); - }); - } - - return { - loadImage: loadImage - }; - -}); diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index 764be06fd18..a10805fdb48 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -1,54 +1,138 @@ -define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', 'userSettings', 'require', 'css!./style'], function (lazyLoader, imageFetcher, layoutManager, browser, appSettings, userSettings, require) { - 'use strict'; +import * as lazyLoader from 'lazyLoader'; +import * as userSettings from 'userSettings'; +import * as blurhash from 'blurhash'; +import 'css!./style'; +/* eslint-disable indent */ - var requestIdleCallback = window.requestIdleCallback || function (fn) { - fn(); - }; - - var self = {}; + export function lazyImage(elem, source = elem.getAttribute('data-src')) { + if (!source) { + return; + } - function fillImage(elem, source, enableEffects) { + fillImageElement(elem, source); + } - if (!elem) { - throw new Error('elem cannot be null'); + function itemBlurhashing(target, blurhashstr) { + if (blurhash.isBlurhashValid(blurhashstr)) { + // Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us, + // improving the performance and reducing the memory usage, while retaining almost full blur quality. + // Lower values had more visible pixelation + let width = 18; + let height = 18; + let pixels; + try { + pixels = blurhash.decode(blurhashstr, width, height); + } catch (err) { + console.error('Blurhash decode error: ', err); + target.classList.add('non-blurhashable'); + return; + } + let canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + let ctx = canvas.getContext('2d'); + let imgData = ctx.createImageData(width, height); + + imgData.data.set(pixels); + ctx.putImageData(imgData, 0, 0); + + requestAnimationFrame(() => { + canvas.classList.add('blurhash-canvas'); + if (userSettings.enableFastFadein()) { + canvas.classList.add('lazy-blurhash-fadein-fast'); + } else { + canvas.classList.add('lazy-blurhash-fadein'); + } + + target.parentNode.insertBefore(canvas, target); + target.classList.add('blurhashed'); + target.removeAttribute('data-blurhash'); + }); } + } - if (!source) { - source = elem.getAttribute('data-src'); + export function fillImage(entry) { + if (!entry) { + throw new Error('entry cannot be null'); } + let target = entry.target; + var source = undefined; - if (!source) { - return; + if (target) { + source = target.getAttribute('data-src'); + } else { + source = entry; } - fillImageElement(elem, source, enableEffects); + if (entry.intersectionRatio > 0) { + if (source) fillImageElement(target, source); + } else if (!source) { + requestAnimationFrame(() => { + emptyImageElement(target); + }); + } } - function fillImageElement(elem, source, enableEffects) { - imageFetcher.loadImage(elem, source).then(function () { - - if (enableEffects !== false) { - fadeIn(elem); - } + function fillImageElement(elem, url) { + if (url === undefined) { + throw new TypeError('url cannot be undefined'); + } - elem.removeAttribute("data-src"); + let preloaderImg = new Image(); + preloaderImg.src = url; + + elem.classList.add('lazy-hidden'); + + preloaderImg.addEventListener('load', () => { + requestAnimationFrame(() => { + if (elem.tagName !== 'IMG') { + elem.style.backgroundImage = "url('" + url + "')"; + } else { + elem.setAttribute('src', url); + } + elem.removeAttribute('data-src'); + + elem.classList.remove('lazy-hidden'); + if (userSettings.enableFastFadein()) { + elem.classList.add('lazy-image-fadein-fast'); + } else { + elem.classList.add('lazy-image-fadein'); + } + }); }); } - function fadeIn(elem) { - if (userSettings.enableFastFadein()) { - elem.classList.add('lazy-image-fadein-fast'); + function emptyImageElement(elem) { + var url; + + if (elem.tagName !== 'IMG') { + url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); + elem.style.backgroundImage = 'none'; } else { - elem.classList.add('lazy-image-fadein'); + url = elem.getAttribute('src'); + elem.setAttribute('src', ''); } - } + elem.setAttribute('data-src', url); - function lazyChildren(elem) { + 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.getElementsByClassName('lazy')) { + const blurhashstr = lazyElem.getAttribute('data-blurhash'); + if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { + itemBlurhashing(lazyElem, blurhashstr); + } else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { + lazyElem.classList.add('non-blurhashable'); + } + } + } lazyLoader.lazyChildren(elem, fillImage); } - function getPrimaryImageAspectRatio(items) { + export function getPrimaryImageAspectRatio(items) { var values = []; @@ -108,7 +192,7 @@ define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', return result; } - function fillImages(elems) { + export function fillImages(elems) { for (var i = 0, length = elems.length; i < length; i++) { var elem = elems[0]; @@ -116,10 +200,18 @@ define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', } } - self.fillImages = fillImages; - self.lazyImage = fillImage; - self.lazyChildren = lazyChildren; - self.getPrimaryImageAspectRatio = getPrimaryImageAspectRatio; + export function setLazyImage(element, url) { + element.classList.add('lazy'); + element.setAttribute('data-src', url); + lazyImage(element); + } - return self; -}); +/* eslint-enable indent */ +export default { + serLazyImage: setLazyImage, + fillImages: fillImages, + fillImage: fillImage, + lazyImage: lazyImage, + lazyChildren: lazyChildren, + getPrimaryImageAspectRatio: getPrimaryImageAspectRatio +}; diff --git a/src/components/images/style.css b/src/components/images/style.css index 2836dd0159a..7e8b01aff25 100644 --- a/src/components/images/style.css +++ b/src/components/images/style.css @@ -1,29 +1,18 @@ .lazy-image-fadein { - opacity: 0; - animation: lazy-image-fadein 330ms ease-in normal both; - -webkit-animation-duration: 0.8s; - -moz-animation-duration: 0.8s; - -o-animation-duration: 0.8s; - animation-duration: 0.8s; - -webkit-animation-name: popInAnimation; - -moz-animation-name: popInAnimation; - -o-animation-name: popInAnimation; - animation-name: popInAnimation; - -webkit-animation-fill-mode: forwards; - -moz-animation-fill-mode: forwards; - -o-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - -moz-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - -o-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - animation-timing-function: cubic-bezier(0, 0, 0.5, 1); + opacity: 1; + transition: opacity 0.5s; } .lazy-image-fadein-fast { - animation: lazy-image-fadein 160ms ease-in normal both; + opacity: 1; + transition: opacity 0.1s; +} + +.lazy-hidden { + opacity: 0; } -@keyframes lazy-image-fadein { +@keyframes fadein { from { opacity: 0; } @@ -33,12 +22,21 @@ } } -@keyframes popInAnimation { - 0% { - opacity: 0; - } +.lazy-blurhash-fadein-fast { + animation: fadein 0.1s; +} - 100% { - opacity: 1; - } +.lazy-blurhash-fadein { + animation: fadein 0.4s; +} + +.blurhash-canvas { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; } diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index e41ccb97755..604f480f1de 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -1,237 +1,183 @@ -define(['datetime', 'itemHelper', 'css!./indicators.css', 'material-icons'], function (datetime, itemHelper) { - 'use strict'; - - function enableProgressIndicator(item) { - if (item.MediaType === 'Video') { - if (item.Type !== 'TvChannel') { - return true; - } - } - - if (item.Type === 'AudioBook' || item.Type === 'AudioPodcast') { - return true; - } - - return false; +import datetime from 'datetime'; +import itemHelper from 'itemHelper'; +import 'emby-progressbar'; +import 'css!./indicators.css'; +import 'material-icons'; + +export function enableProgressIndicator(item) { + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { + return true; } - function getProgressHtml(pct, options) { - var containerClass = 'itemProgressBar'; - if (options) { - if (options.containerClass) { - containerClass += ' ' + options.containerClass; - } - } - - return '
'; + if (item.Type === 'AudioBook' || item.Type === 'AudioPodcast') { + return true; } - function getAutoTimeProgressHtml(pct, options, isRecording, start, end) { - var containerClass = 'itemProgressBar'; - if (options) { - if (options.containerClass) { - containerClass += ' ' + options.containerClass; - } - } - - var foregroundClass = 'itemProgressBarForeground'; - if (isRecording) { - foregroundClass += ' itemProgressBarForeground-recording'; - } + return false; +} - return '
'; +export function getProgressHtml(pct, options) { + let containerClass = 'itemProgressBar'; + if (options && options.containerClass) { + containerClass += ' ' + options.containerClass; } - function getProgressBarHtml(item, options) { - var pct; - if (enableProgressIndicator(item) && item.Type !== "Recording") { - var userData = options ? (options.userData || item.UserData) : item.UserData; - if (userData) { - pct = userData.PlayedPercentage; - if (pct && pct < 100) { - return getProgressHtml(pct, options); - } - } - } - - if ((item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording') && item.StartDate && item.EndDate) { - var startDate = 0; - var endDate = 1; - - try { - startDate = datetime.parseISO8601Date(item.StartDate).getTime(); - endDate = datetime.parseISO8601Date(item.EndDate).getTime(); - } catch (err) { - console.error(err); - } - - var now = new Date().getTime(); - var total = endDate - startDate; - pct = 100 * ((now - startDate) / total); - - if (pct > 0 && pct < 100) { - var isRecording = item.Type === 'Timer' || item.Type === 'Recording' || item.TimerId; - return getAutoTimeProgressHtml(pct, options, isRecording, startDate, endDate); - } - } + return '
'; +} - return ''; +function getAutoTimeProgressHtml(pct, options, isRecording, start, end) { + let containerClass = 'itemProgressBar'; + if (options && options.containerClass) { + containerClass += ' ' + options.containerClass; } - function enablePlayedIndicator(item) { - return itemHelper.canMarkPlayed(item); + let foregroundClass = 'itemProgressBarForeground'; + if (isRecording) { + foregroundClass += ' itemProgressBarForeground-recording'; } - function getPlayedIndicator(item) { - if (enablePlayedIndicator(item)) { - var userData = item.UserData || {}; - if (userData.UnplayedItemCount) { - return '
' + userData.UnplayedItemCount + '
'; - } + return '
'; +} - if (userData.PlayedPercentage && userData.PlayedPercentage >= 100 || (userData.Played)) { - return '
check
'; +export function getProgressBarHtml(item, options) { + let pct; + if (enableProgressIndicator(item) && item.Type !== 'Recording') { + const userData = options && options.userData ? options.userData : item.UserData; + + if (userData) { + pct = userData.PlayedPercentage; + if (pct && pct < 100) { + return getProgressHtml(pct, options); } } - - return ''; } - function getCountIndicatorHtml(count) { - return '
' + count + '
'; - } + if ((item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording') && item.StartDate && item.EndDate) { + let startDate = 0; + let endDate = 1; - function getChildCountIndicatorHtml(item, options) { - var minCount = 0; - if (options) { - minCount = options.minCount || minCount; + try { + startDate = datetime.parseISO8601Date(item.StartDate).getTime(); + endDate = datetime.parseISO8601Date(item.EndDate).getTime(); + } catch (err) { + console.error(err); } - if (item.ChildCount && item.ChildCount > minCount) { - return getCountIndicatorHtml(item.ChildCount); - } + const now = new Date().getTime(); + const total = endDate - startDate; + pct = 100 * ((now - startDate) / total); - return ''; + if (pct > 0 && pct < 100) { + const isRecording = item.Type === 'Timer' || item.Type === 'Recording' || item.TimerId; + return getAutoTimeProgressHtml(pct, options, isRecording, startDate, endDate); + } } - function getTimerIndicator(item) { - var status; - - 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 ''; - } + return ''; +} - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return ''; - } +export function enablePlayedIndicator(item) { + return itemHelper.canMarkPlayed(item); +} - return ''; +export function getPlayedIndicatorHtml(item) { + if (enablePlayedIndicator(item)) { + let userData = item.UserData || {}; + if (userData.UnplayedItemCount) { + return '
' + userData.UnplayedItemCount + '
'; } - return ''; + if (userData.PlayedPercentage && userData.PlayedPercentage >= 100 || (userData.Played)) { + return '
'; + } } - function getSyncIndicator(item) { - if (item.SyncPercent === 100) { - return '
'; - } else if (item.SyncPercent != null) { - return '
'; - } + return ''; +} - return ''; +export function getChildCountIndicatorHtml(item, options) { + const minCount = options && options.minCount ? options.minCount : 0; + + if (item.ChildCount && item.ChildCount > minCount) { + return '
' + item.ChildCount + '
'; } - function getTypeIndicator(item) { - if (item.Type === 'Video') { - return '
videocam
'; - } - if (item.Type === 'Folder') { - return '
folder
'; - } - if (item.Type === 'PhotoAlbum') { - return '
'; - } - if (item.Type === 'Photo') { - return '
photo
'; - } + return ''; +} + +export function getTimerIndicator(item) { + let status; + 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 ''; } - function getMissingIndicator(item) { - if (item.Type === 'Episode' && item.LocationType === 'Virtual') { - if (item.PremiereDate) { - try { - var premiereDate = datetime.parseISO8601Date(item.PremiereDate).getTime(); - if (premiereDate > new Date().getTime()) { - return '
Unaired
'; - } - } catch (err) { - console.error(err); - } - } - return '
Missing
'; + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return ''; } - return ''; + return ''; } - var ProgressBarPrototype = Object.create(HTMLDivElement.prototype); - - function onAutoTimeProgress() { - var start = parseInt(this.getAttribute('data-starttime')); - var end = parseInt(this.getAttribute('data-endtime')); - - var now = new Date().getTime(); - var total = end - start; - var pct = 100 * ((now - start) / total); - - pct = Math.min(100, pct); - pct = Math.max(0, pct); + return ''; +} - var itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); - itemProgressBarForeground.style.width = pct + '%'; +export function getSyncIndicator(item) { + if (item.SyncPercent === 100) { + return '
'; + } else if (item.SyncPercent != null) { + return '
'; } - ProgressBarPrototype.attachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - } + return ''; +} - if (this.getAttribute('data-automode') === 'time') { - this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); - } +export function getTypeIndicator(item) { + const iconT = { + 'Video' : 'videocam', + 'Folder' : 'folder', + 'PhotoAlbum' : 'photo_album', + 'Photo' : 'photo' }; - ProgressBarPrototype.detachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - this.timeInterval = null; + const icon = iconT[item.Type]; + return icon ? '
' : ''; +} + +export function getMissingIndicator(item) { + if (item.Type === 'Episode' && item.LocationType === 'Virtual') { + if (item.PremiereDate) { + try { + const premiereDate = datetime.parseISO8601Date(item.PremiereDate).getTime(); + if (premiereDate > new Date().getTime()) { + return '
Unaired
'; + } + } catch (err) { + console.error(err); + } } - }; + return '
Missing
'; + } - document.registerElement('emby-progressbar', { - prototype: ProgressBarPrototype, - extends: 'div' - }); - - return { - getProgressHtml: getProgressHtml, - getProgressBarHtml: getProgressBarHtml, - getPlayedIndicatorHtml: getPlayedIndicator, - getChildCountIndicatorHtml: getChildCountIndicatorHtml, - enableProgressIndicator: enableProgressIndicator, - getTimerIndicator: getTimerIndicator, - enablePlayedIndicator: enablePlayedIndicator, - getSyncIndicator: getSyncIndicator, - getTypeIndicator: getTypeIndicator, - getMissingIndicator: getMissingIndicator - }; -}); + return ''; +} + +export default { + getProgressHtml: getProgressHtml, + getProgressBarHtml: getProgressBarHtml, + getPlayedIndicatorHtml: getPlayedIndicatorHtml, + getChildCountIndicatorHtml: getChildCountIndicatorHtml, + enableProgressIndicator: enableProgressIndicator, + getTimerIndicator: getTimerIndicator, + enablePlayedIndicator: enablePlayedIndicator, + getSyncIndicator: getSyncIndicator, + getTypeIndicator: getTypeIndicator, + getMissingIndicator: getMissingIndicator +}; diff --git a/src/components/input/keyboardnavigation.js b/src/components/input/keyboardnavigation.js deleted file mode 100644 index bdcb7331792..00000000000 --- a/src/components/input/keyboardnavigation.js +++ /dev/null @@ -1,157 +0,0 @@ -import inputManager from "inputManager"; -import layoutManager from "layoutManager"; - -/** - * Key name mapping. - */ -const KeyNames = { - 13: "Enter", - 19: "Pause", - 27: "Escape", - 32: "Space", - 37: "ArrowLeft", - 38: "ArrowUp", - 39: "ArrowRight", - 40: "ArrowDown", - // MediaRewind (Tizen/WebOS) - 412: "MediaRewind", - // MediaStop (Tizen/WebOS) - 413: "MediaStop", - // MediaPlay (Tizen/WebOS) - 415: "MediaPlay", - // MediaFastForward (Tizen/WebOS) - 417: "MediaFastForward", - // Back (WebOS) - 461: "Back", - // Back (Tizen) - 10009: "Back", - // MediaTrackPrevious (Tizen) - 10232: "MediaTrackPrevious", - // MediaTrackNext (Tizen) - 10233: "MediaTrackNext", - // MediaPlayPause (Tizen) - 10252: "MediaPlayPause" -}; - -/** - * Keys used for keyboard navigation. - */ -const NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]; - -let hasFieldKey = false; -try { - hasFieldKey = "key" in new KeyboardEvent("keydown"); -} catch (e) { - console.error("error checking 'key' field"); -} - -if (!hasFieldKey) { - // Add [a..z] - for (let i = 65; i <= 90; i++) { - KeyNames[i] = String.fromCharCode(i).toLowerCase(); - } -} - -/** - * Returns key name from event. - * - * @param {KeyboardEvent} event keyboard event - * @return {string} key name - */ -export function getKeyName(event) { - return KeyNames[event.keyCode] || event.key; -} - -/** - * Returns _true_ if key is used for navigation. - * - * @param {string} key name - * @return {boolean} _true_ if key is used for navigation - */ -export function isNavigationKey(key) { - return NavigationKeys.indexOf(key) != -1; -} - -export function enable() { - document.addEventListener("keydown", function (e) { - const key = getKeyName(e); - - // Ignore navigation keys for non-TV - if (!layoutManager.tv && isNavigationKey(key)) { - return; - } - - let capture = true; - - switch (key) { - case "ArrowLeft": - inputManager.handle("left"); - break; - case "ArrowUp": - inputManager.handle("up"); - break; - case "ArrowRight": - inputManager.handle("right"); - break; - case "ArrowDown": - inputManager.handle("down"); - break; - - case "Back": - inputManager.handle("back"); - break; - - case "Escape": - if (layoutManager.tv) { - inputManager.handle("back"); - } else { - capture = false; - } - break; - - case "MediaPlay": - inputManager.handle("play"); - break; - case "Pause": - inputManager.handle("pause"); - break; - case "MediaPlayPause": - inputManager.handle("playpause"); - break; - case "MediaRewind": - inputManager.handle("rewind"); - break; - case "MediaFastForward": - inputManager.handle("fastforward"); - break; - case "MediaStop": - inputManager.handle("stop"); - break; - case "MediaTrackPrevious": - inputManager.handle("previoustrack"); - break; - case "MediaTrackNext": - inputManager.handle("nexttrack"); - break; - - default: - capture = false; - } - - if (capture) { - console.debug("disabling default event handling"); - e.preventDefault(); - } - }); -} - -// Gamepad initialisation. No script is required if no gamepads are present at init time, saving a bit of resources. -// Whenever the gamepad is connected, we hand all the control of the gamepad to gamepadtokey.js by removing the event handler -function attachGamepadScript(e) { - console.log("Gamepad connected! Attaching gamepadtokey.js script"); - window.removeEventListener("gamepadconnected", attachGamepadScript); - require(["components/input/gamepadtokey"]); -} - -// No need to check for gamepads manually at load time, the eventhandler will be fired for that -window.addEventListener("gamepadconnected", attachGamepadScript); diff --git a/src/components/itemcontextmenu.js b/src/components/itemContextMenu.js similarity index 56% rename from src/components/itemcontextmenu.js rename to src/components/itemContextMenu.js index bdbcfc782be..96f8f2d3561 100644 --- a/src/components/itemcontextmenu.js +++ b/src/components/itemContextMenu.js @@ -1,5 +1,5 @@ -define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", "playbackManager", "loading", "appSettings", "browser", "actionsheet"], function (appHost, globalize, connectionManager, itemHelper, appRouter, playbackManager, loading, appSettings, browser, actionsheet) { - "use strict"; +define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter', 'playbackManager', 'loading', 'appSettings', 'browser', 'actionsheet'], function (appHost, globalize, connectionManager, itemHelper, appRouter, playbackManager, loading, appSettings, browser, actionsheet) { + 'use strict'; function getCommands(options) { var item = options.item; @@ -10,20 +10,37 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", var commands = []; - if (canPlay && item.MediaType !== "Photo") { + if (canPlay && item.MediaType !== 'Photo') { if (options.play !== false) { commands.push({ - name: globalize.translate("Play"), - id: "resume", - icon: "" + name: globalize.translate('Play'), + id: 'resume', + icon: 'play_arrow' }); } - if (options.playAllFromHere && item.Type !== "Program" && item.Type !== "TvChannel") { + if (options.playAllFromHere && item.Type !== 'Program' && item.Type !== 'TvChannel') { commands.push({ - name: globalize.translate("PlayAllFromHere"), - id: "playallfromhere", - icon: "" + 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' }); } } @@ -31,46 +48,39 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (playbackManager.canQueue(item)) { if (options.queue !== false) { commands.push({ - name: globalize.translate("AddToPlayQueue"), - id: "queue", - icon: "playlist_add" + name: globalize.translate('AddToPlayQueue'), + id: 'queue', + icon: 'playlist_add' }); } if (options.queue !== false) { commands.push({ - name: globalize.translate("PlayNext"), - id: "queuenext", - icon: "playlist_add" + name: globalize.translate('PlayNext'), + id: 'queuenext', + icon: 'playlist_add' }); } - - //if (options.queueAllFromHere) { - // commands.push({ - // name: globalize.translate("QueueAllFromHere"), - // id: "queueallfromhere" - // }); - //} } - if (item.IsFolder || item.Type === "MusicArtist" || item.Type === "MusicGenre") { - if (item.CollectionType !== "livetv") { + if (item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { + if (item.CollectionType !== 'livetv') { if (options.shuffle !== false) { commands.push({ - name: globalize.translate("Shuffle"), - id: "shuffle", - icon: "shuffle" + 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') { if (options.instantMix !== false && !itemHelper.isLocalItem(item)) { commands.push({ - name: globalize.translate("InstantMix"), - id: "instantmix", - icon: "explore" + name: globalize.translate('InstantMix'), + id: 'instantmix', + icon: 'explore' }); } } @@ -84,74 +94,74 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (!restrictOptions) { if (itemHelper.supportsAddingToCollection(item)) { commands.push({ - name: globalize.translate("AddToCollection"), - id: "addtocollection", - icon: "playlist_add" + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'playlist_add' }); } - if (itemHelper.supportsAddingToPlaylist(item)) { + if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { commands.push({ - name: globalize.translate("AddToPlaylist"), - id: "addtoplaylist", - icon: "playlist_add" + name: globalize.translate('AddToPlaylist'), + id: 'addtoplaylist', + icon: 'playlist_add' }); } } - if ((item.Type === "Timer") && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + if ((item.Type === 'Timer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { commands.push({ - name: globalize.translate("CancelRecording"), - id: "canceltimer", - icon: "cancel" + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' }); } - if ((item.Type === "Recording" && item.Status === "InProgress") && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + if ((item.Type === 'Recording' && item.Status === 'InProgress') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { commands.push({ - name: globalize.translate("CancelRecording"), - id: "canceltimer", - icon: "cancel" + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' }); } - if ((item.Type === "SeriesTimer") && 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" + name: globalize.translate('CancelSeries'), + id: 'cancelseriestimer', + icon: 'cancel' }); } if (item.CanDelete && options.deleteItem !== false) { - if (item.Type === "Playlist" || item.Type === "BoxSet") { + if (item.Type === 'Playlist' || item.Type === 'BoxSet') { commands.push({ - name: globalize.translate("Delete"), - id: "delete", - icon: "delete" + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' }); } else { commands.push({ - name: globalize.translate("DeleteMedia"), - id: "delete", - icon: "delete" + name: globalize.translate('DeleteMedia'), + id: 'delete', + icon: 'delete' }); } } // Books are promoted to major download Button and therefor excluded in the context menu - if ((item.CanDownload && appHost.supports("filedownload")) && item.Type !== "Book") { + if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { commands.push({ - name: globalize.translate("Download"), - id: "download", - icon: "file_download" + name: globalize.translate('Download'), + id: 'download', + icon: 'file_download' }); commands.push({ - name: globalize.translate("CopyStreamURL"), - id: "copy-stream", - icon: "content_copy" + name: globalize.translate('CopyStreamURL'), + id: 'copy-stream', + icon: 'content_copy' }); } @@ -163,12 +173,12 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", var canEdit = itemHelper.canEdit(user, item); if (canEdit) { - if (options.edit !== false && item.Type !== "SeriesTimer") { - var text = (item.Type === "Timer" || item.Type === "SeriesTimer") ? globalize.translate("Edit") : globalize.translate("EditMetadata"); + if (options.edit !== false && item.Type !== 'SeriesTimer') { + var text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); commands.push({ name: text, - id: "edit", - icon: "edit" + id: 'edit', + icon: 'edit' }); } } @@ -176,20 +186,20 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (itemHelper.canEditImages(user, item)) { if (options.editImages !== false) { commands.push({ - name: globalize.translate("EditImages"), - id: "editimages", - icon: "image" + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'image' }); } } if (canEdit) { - if (item.MediaType === "Video" && item.Type !== "TvChannel" && item.Type !== "Program" && item.LocationType !== "Virtual" && !(item.Type === "Recording" && item.Status !== "Completed")) { + if (item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' && item.LocationType !== 'Virtual' && !(item.Type === 'Recording' && item.Status !== 'Completed')) { if (options.editSubtitles !== false) { commands.push({ - name: globalize.translate("EditSubtitles"), - id: "editsubtitles", - icon: "closed_caption" + name: globalize.translate('EditSubtitles'), + id: 'editsubtitles', + icon: 'closed_caption' }); } } @@ -198,9 +208,9 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (options.identify !== false) { if (itemHelper.canIdentify(user, item)) { commands.push({ - name: globalize.translate("Identify"), - id: "identify", - icon: "edit" + name: globalize.translate('Identify'), + id: 'identify', + icon: 'edit' }); } } @@ -208,54 +218,54 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (item.MediaSources) { if (options.moremediainfo !== false) { commands.push({ - name: globalize.translate("MoreMediaInfo"), - id: "moremediainfo", - icon: "info" + name: globalize.translate('MoreMediaInfo'), + id: 'moremediainfo', + icon: 'info' }); } } - if (item.Type === "Program" && options.record !== false) { + if (item.Type === 'Program' && options.record !== false) { if (item.TimerId) { commands.push({ - name: Globalize.translate("ManageRecording"), - id: "record", - icon: "fiber_manual_record" + name: globalize.translate('ManageRecording'), + id: 'record', + icon: 'fiber_manual_record' }); } } - if (item.Type === "Program" && options.record !== false) { + if (item.Type === 'Program' && options.record !== false) { if (!item.TimerId) { commands.push({ - name: Globalize.translate("Record"), - id: "record", - icon: "fiber_manual_record" + 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" + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' }); } if (item.PlaylistItemId && options.playlistId) { commands.push({ - name: globalize.translate("RemoveFromPlaylist"), - id: "removefromplaylist", - icon: "remove" + name: globalize.translate('RemoveFromPlaylist'), + id: 'removefromplaylist', + icon: 'remove' }); } if (options.collectionId) { commands.push({ - name: globalize.translate("RemoveFromCollection"), - id: "removefromcollection", - icon: "remove" + name: globalize.translate('RemoveFromCollection'), + id: 'removefromcollection', + icon: 'remove' }); } @@ -263,9 +273,9 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (options.share === true) { if (itemHelper.canShare(item, user)) { commands.push({ - name: globalize.translate("Share"), - id: "share", - icon: "share" + name: globalize.translate('Share'), + id: 'share', + icon: 'share' }); } } @@ -274,26 +284,27 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", if (options.sync !== false) { if (itemHelper.canSync(user, item)) { commands.push({ - name: globalize.translate("Sync"), - id: "sync", - icon: "sync" + name: globalize.translate('Sync'), + id: 'sync', + icon: 'sync' }); } } - if (options.openAlbum !== false && item.AlbumId && item.MediaType !== "Photo") { + if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { commands.push({ - name: Globalize.translate("ViewAlbum"), - id: "album", - icon: "album" + name: globalize.translate('ViewAlbum'), + id: 'album', + icon: 'album' }); } - - if (options.openArtist !== false && item.ArtistItems && item.ArtistItems.length) { + // 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("ViewArtist"), - id: "artist", - icon: "person" + name: globalize.translate('ViewAlbumArtist'), + id: 'artist', + icon: 'person' }); } @@ -317,173 +328,187 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", return new Promise(function (resolve, reject) { switch (id) { - case "addtocollection": - require(["collectionEditor"], function (collectionEditor) { + case 'addtocollection': + require(['collectionEditor'], function (collectionEditor) { new collectionEditor().show({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "addtoplaylist": - require(["playlistEditor"], function (playlistEditor) { + case 'addtoplaylist': + require(['playlistEditor'], function (playlistEditor) { new playlistEditor().show({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "download": - require(["fileDownloader"], function (fileDownloader) { + case 'download': + require(['fileDownloader'], function (fileDownloader) { var downloadHref = apiClient.getItemDownloadUrl(itemId); fileDownloader.download([{ url: downloadHref, itemId: itemId, - serverId: serverId + serverId: serverId, + title: item.Name, + filename: item.Path.replace(/^.*[\\\/]/, '') }]); getResolveFunction(getResolveFunction(resolve, id), id)(); }); break; - case "copy-stream": + case 'copy-stream': var downloadHref = apiClient.getItemDownloadUrl(itemId); var textAreaCopy = function () { - var textArea = document.createElement("textarea"); + var textArea = document.createElement('textarea'); textArea.value = downloadHref; document.body.appendChild(textArea); textArea.focus(); textArea.select(); - if (document.execCommand("copy")) { - require(["toast"], function (toast) { - toast(globalize.translate("CopyStreamURLSuccess")); + + if (document.execCommand('copy')) { + require(['toast'], function (toast) { + toast(globalize.translate('CopyStreamURLSuccess')); }); } else { - prompt(globalize.translate("CopyStreamURL"), downloadHref); + prompt(globalize.translate('CopyStreamURL'), downloadHref); } document.body.removeChild(textArea); }; + + /* eslint-disable-next-line compat/compat */ if (navigator.clipboard === undefined) { textAreaCopy(); } else { + /* eslint-disable-next-line compat/compat */ navigator.clipboard.writeText(downloadHref).then(function () { - require(["toast"], function (toast) { - toast(globalize.translate("CopyStreamURLSuccess")); + require(['toast'], function (toast) { + toast(globalize.translate('CopyStreamURLSuccess')); }); - }, textAreaCopy); + }).catch(function () { + textAreaCopy(); + }); } getResolveFunction(resolve, id)(); break; - case "editsubtitles": - require(["subtitleEditor"], function (subtitleEditor) { + case 'editsubtitles': + require(['subtitleEditor'], function (subtitleEditor) { subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "edit": + case 'edit': editItem(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); break; - case "editimages": - require(["imageEditor"], function (imageEditor) { + case 'editimages': + require(['imageEditor'], function (imageEditor) { imageEditor.show({ itemId: itemId, serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "identify": - require(["itemIdentifier"], function (itemIdentifier) { + case 'identify': + require(['itemIdentifier'], function (itemIdentifier) { itemIdentifier.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "moremediainfo": - require(["itemMediaInfo"], function (itemMediaInfo) { - itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + case 'moremediainfo': + require(['itemMediaInfo'], function (itemMediaInfo) { + itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); }); break; - case "refresh": + case 'refresh': refresh(apiClient, item); getResolveFunction(resolve, id)(); break; - case "open": + case 'open': appRouter.showItem(item); getResolveFunction(resolve, id)(); break; - case "play": + case 'play': play(item, false); getResolveFunction(resolve, id)(); break; - case "resume": + case 'resume': play(item, true); getResolveFunction(resolve, id)(); break; - case "queue": + case 'queue': play(item, false, true); getResolveFunction(resolve, id)(); break; - case "queuenext": + case 'queuenext': play(item, false, true, true); getResolveFunction(resolve, id)(); break; - case "record": - require(["recordingCreator"], function (recordingCreator) { + case 'stopPlayback': + playbackManager.stop(); + break; + case 'clearQueue': + playbackManager.clearQueue(); + break; + case 'record': + require(['recordingCreator'], function (recordingCreator) { recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); break; - case "shuffle": + case 'shuffle': playbackManager.shuffle(item); getResolveFunction(resolve, id)(); break; - case "instantmix": + case 'instantmix': playbackManager.instantMix(item); getResolveFunction(resolve, id)(); break; - case "delete": + case 'delete': deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); break; - case "share": + case 'share': navigator.share({ title: item.Name, text: item.Overview, - url: "https://github.com/jellyfin/jellyfin" + url: `${apiClient.serverAddress()}/web/index.html#!/${appRouter.getRouteUrl(item)}` }); break; - case "album": + case 'album': appRouter.showItem(item.AlbumId, item.ServerId); getResolveFunction(resolve, id)(); break; - case "artist": - appRouter.showItem(item.ArtistItems[0].Id, item.ServerId); + case 'artist': + appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); getResolveFunction(resolve, id)(); break; - case "playallfromhere": + case 'playallfromhere': getResolveFunction(resolve, id)(); break; - case "queueallfromhere": + case 'queueallfromhere': getResolveFunction(resolve, id)(); break; - case "removefromplaylist": + case 'removefromplaylist': apiClient.ajax({ - url: apiClient.getUrl("Playlists/" + options.playlistId + "/Items", { - EntryIds: [item.PlaylistItemId].join(",") + url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', { + EntryIds: [item.PlaylistItemId].join(',') }), - type: "DELETE" + type: 'DELETE' }).then(function () { getResolveFunction(resolve, id, true)(); }); break; - case "removefromcollection": + case 'removefromcollection': apiClient.ajax({ - type: "DELETE", - url: apiClient.getUrl("Collections/" + options.collectionId + "/Items", { + type: 'DELETE', + url: apiClient.getUrl('Collections/' + options.collectionId + '/Items', { - Ids: [item.Id].join(",") + Ids: [item.Id].join(',') }) }).then(function () { getResolveFunction(resolve, id, true)(); }); break; - case "canceltimer": + case 'canceltimer': deleteTimer(apiClient, item, resolve, id); break; - case "cancelseriestimer": + case 'cancelseriestimer': deleteSeriesTimer(apiClient, item, resolve, id); break; default: @@ -494,7 +519,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", } function deleteTimer(apiClient, item, resolve, command) { - require(["recordingHelper"], function (recordingHelper) { + require(['recordingHelper'], function (recordingHelper) { var timerId = item.TimerId || item.Id; recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () { getResolveFunction(resolve, command, true)(); @@ -503,7 +528,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", } function deleteSeriesTimer(apiClient, item, resolve, command) { - require(["recordingHelper"], function (recordingHelper) { + require(['recordingHelper'], function (recordingHelper) { recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () { getResolveFunction(resolve, command, true)(); }); @@ -511,14 +536,14 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", } function play(item, resume, queue, queueNext) { - var method = queue ? (queueNext ? "queueNext" : "queue") : "play"; + var method = queue ? (queueNext ? 'queueNext' : 'queue') : 'play'; var startPosition = 0; if (resume && item.UserData && item.UserData.PlaybackPositionTicks) { startPosition = item.UserData.PlaybackPositionTicks; } - if (item.Type === "Program") { + if (item.Type === 'Program') { playbackManager[method]({ ids: [item.ChannelId], startPositionTicks: startPosition, @@ -536,16 +561,16 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", return new Promise(function (resolve, reject) { var serverId = apiClient.serverInfo().Id; - if (item.Type === "Timer") { - require(["recordingEditor"], function (recordingEditor) { + if (item.Type === 'Timer') { + require(['recordingEditor'], function (recordingEditor) { recordingEditor.show(item.Id, serverId).then(resolve, reject); }); - } else if (item.Type === "SeriesTimer") { - require(["seriesRecordingEditor"], function (recordingEditor) { + } else if (item.Type === 'SeriesTimer') { + require(['seriesRecordingEditor'], function (recordingEditor) { recordingEditor.show(item.Id, serverId).then(resolve, reject); }); } else { - require(["metadataEditor"], function (metadataEditor) { + require(['metadataEditor'], function (metadataEditor) { metadataEditor.show(item.Id, serverId).then(resolve, reject); }); } @@ -554,7 +579,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", function deleteItem(apiClient, item) { return new Promise(function (resolve, reject) { - require(["deleteHelper"], function (deleteHelper) { + require(['deleteHelper'], function (deleteHelper) { deleteHelper.deleteItem({ item: item, navigate: false @@ -566,11 +591,11 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", } function refresh(apiClient, item) { - require(["refreshDialog"], function (refreshDialog) { + require(['refreshDialog'], function (refreshDialog) { new refreshDialog({ itemIds: [item.Id], serverId: apiClient.serverInfo().Id, - mode: item.Type === "CollectionFolder" ? "scan" : null + mode: item.Type === 'CollectionFolder' ? 'scan' : null }).show(); }); } @@ -584,7 +609,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", return actionsheet.show({ items: commands, positionTo: options.positionTo, - resolveOnClick: ["share"] + resolveOnClick: ['share'] }).then(function (id) { return executeCommand(options.item, id, options); }); diff --git a/src/components/itemhelper.js b/src/components/itemHelper.js similarity index 87% rename from src/components/itemhelper.js rename to src/components/itemHelper.js index 8e0883474af..3508866bdb6 100644 --- a/src/components/itemhelper.js +++ b/src/components/itemHelper.js @@ -1,45 +1,43 @@ define(['apphost', 'globalize'], function (appHost, globalize) { 'use strict'; - function getDisplayName(item, options) { + function getDisplayName(item, options = {}) { if (!item) { - throw new Error("null item passed into getDisplayName"); + throw new Error('null item passed into getDisplayName'); } - options = options || {}; - if (item.Type === 'Timer') { item = item.ProgramInfo || item; } var name = ((item.Type === 'Program' || item.Type === 'Recording') && (item.IsSeries || item.EpisodeTitle) ? item.EpisodeTitle : item.Name) || ''; - if (item.Type === "TvChannel") { + if (item.Type === 'TvChannel') { if (item.ChannelNumber) { return item.ChannelNumber + ' ' + name; } return name; } - if (item.Type === "Episode" && item.ParentIndexNumber === 0) { + if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { name = globalize.translate('ValueSpecialEpisodeName', name); - } else if ((item.Type === "Episode" || item.Type === 'Program') && item.IndexNumber != null && item.ParentIndexNumber != null && options.includeIndexNumber !== false) { + } else if ((item.Type === 'Episode' || item.Type === 'Program') && item.IndexNumber != null && item.ParentIndexNumber != null && options.includeIndexNumber !== false) { var displayIndexNumber = item.IndexNumber; var number = displayIndexNumber; - var nameSeparator = " - "; + var nameSeparator = ' - '; if (options.includeParentInfo !== false) { - number = "S" + item.ParentIndexNumber + ":E" + number; + number = 'S' + item.ParentIndexNumber + ':E' + number; } else { - nameSeparator = ". "; + nameSeparator = '. '; } if (item.IndexNumberEnd) { displayIndexNumber = item.IndexNumberEnd; - number += "-" + displayIndexNumber; + number += '-' + displayIndexNumber; } if (number) { @@ -94,14 +92,14 @@ define(['apphost', 'globalize'], function (appHost, globalize) { return false; } - return item.MediaType || item.IsFolder || item.Type === "Genre" || item.Type === "MusicGenre" || item.Type === "MusicArtist"; + return item.MediaType || item.IsFolder || item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'MusicArtist'; } function canEdit(user, item) { var itemType = item.Type; - if (itemType === "UserRootFolder" || itemType === "UserView") { + if (itemType === 'UserRootFolder' || itemType === 'UserView') { return false; } @@ -149,15 +147,15 @@ define(['apphost', 'globalize'], function (appHost, globalize) { var itemType = item.Type; - if (itemType === "Movie" || - itemType === "Trailer" || - itemType === "Series" || - itemType === "BoxSet" || - itemType === "Person" || - itemType === "Book" || - itemType === "MusicAlbum" || - itemType === "MusicArtist" || - itemType === "MusicVideo") { + if (itemType === 'Movie' || + itemType === 'Trailer' || + itemType === 'Series' || + itemType === 'BoxSet' || + itemType === 'Person' || + itemType === 'Book' || + itemType === 'MusicAlbum' || + itemType === 'MusicArtist' || + itemType === 'MusicVideo') { if (user.Policy.IsAdministrator) { @@ -259,11 +257,11 @@ define(['apphost', 'globalize'], function (appHost, globalize) { } } - if (item.Type === "Series" || - item.Type === "Season" || - item.Type === "BoxSet" || - item.MediaType === "Book" || - item.MediaType === "Recording") { + if (item.Type === 'Series' || + item.Type === 'Season' || + item.Type === 'BoxSet' || + item.MediaType === 'Book' || + item.MediaType === 'Recording') { return true; } diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index e33b1273f46..81c84b6a230 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -1,145 +1,145 @@ -define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings", "connectionManager", "loading", "focusManager", "dom", "apphost", "emby-select", "listViewStyle", "paper-icon-button-light", "css!./../formdialog", "material-icons", "emby-button", "flexStyles"], function (dialogHelper, require, layoutManager, globalize, userSettings, connectionManager, loading, focusManager, dom, appHost) { - "use strict"; +define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'connectionManager', 'loading', 'focusManager', 'dom', 'apphost', 'emby-select', 'listViewStyle', 'paper-icon-button-light', 'css!./../formdialog', 'material-icons', 'emby-button', 'flexStyles'], function (dialogHelper, require, layoutManager, globalize, userSettings, connectionManager, loading, focusManager, dom, appHost) { + 'use strict'; function setMediaInfo(user, page, item) { var html = item.MediaSources.map(function (version) { return getMediaSourceHtml(user, item, version); }).join('
'); if (item.MediaSources.length > 1) { - html = "
" + html; + html = '
' + html; } - var mediaInfoContent = page.querySelector("#mediaInfoContent"); + var mediaInfoContent = page.querySelector('#mediaInfoContent'); mediaInfoContent.innerHTML = html; } function getMediaSourceHtml(user, item, version) { - var html = ""; + var html = ''; if (version.Name) { - html += '

' + version.Name + "

"; + html += '

' + version.Name + '

'; } if (version.Container) { - html += createAttribute(globalize.translate("MediaInfoContainer"), version.Container) + "
"; + html += createAttribute(globalize.translate('MediaInfoContainer'), version.Container) + '
'; } if (version.Formats && version.Formats.length) { - html += createAttribute(globalize.translate("MediaInfoFormat"), version.Formats.join(",")) + "
"; + html += createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(',')) + '
'; } if (version.Path && user && user.Policy.IsAdministrator) { - html += createAttribute(globalize.translate("MediaInfoPath"), version.Path) + "
"; + html += createAttribute(globalize.translate('MediaInfoPath'), version.Path) + '
'; } if (version.Size) { - var size = (version.Size / (1024 * 1024)).toFixed(0) + " MB"; - html += createAttribute(globalize.translate("MediaInfoSize"), size) + "
"; + var size = (version.Size / (1024 * 1024)).toFixed(0) + ' MB'; + html += createAttribute(globalize.translate('MediaInfoSize'), size) + '
'; } for (var i = 0, length = version.MediaStreams.length; i < length; i++) { var stream = version.MediaStreams[i]; - if (stream.Type === "Data") { + if (stream.Type === 'Data') { continue; } html += '
'; - var displayType = globalize.translate("MediaInfoStreamType" + stream.Type); - html += '

' + displayType + "

"; + var displayType = globalize.translate('MediaInfoStreamType' + stream.Type); + html += '

' + displayType + '

'; var attributes = []; if (stream.DisplayTitle) { - attributes.push(createAttribute("Title", stream.DisplayTitle)); + attributes.push(createAttribute('Title', stream.DisplayTitle)); } - if (stream.Language && stream.Type !== "Video") { - attributes.push(createAttribute(globalize.translate("MediaInfoLanguage"), stream.Language)); + 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())); + attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase())); } if (stream.CodecTag) { - attributes.push(createAttribute(globalize.translate("MediaInfoCodecTag"), stream.CodecTag)); + attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag)); } if (stream.IsAVC != null) { - attributes.push(createAttribute("AVC", (stream.IsAVC ? "Yes" : "No"))); + attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No'))); } if (stream.Profile) { - attributes.push(createAttribute(globalize.translate("MediaInfoProfile"), stream.Profile)); + attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile)); } if (stream.Level) { - attributes.push(createAttribute(globalize.translate("MediaInfoLevel"), stream.Level)); + attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level)); } if (stream.Width || stream.Height) { - attributes.push(createAttribute(globalize.translate("MediaInfoResolution"), stream.Width + "x" + 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.AspectRatio && stream.Codec !== 'mjpeg') { + attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio)); } - if (stream.Type === "Video") { + if (stream.Type === 'Video') { if (stream.IsAnamorphic != null) { - attributes.push(createAttribute(globalize.translate("MediaInfoAnamorphic"), (stream.IsAnamorphic ? "Yes" : "No"))); + attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No'))); } - attributes.push(createAttribute(globalize.translate("MediaInfoInterlaced"), (stream.IsInterlaced ? "Yes" : "No"))); + attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No'))); } if (stream.AverageFrameRate || stream.RealFrameRate) { - attributes.push(createAttribute(globalize.translate("MediaInfoFramerate"), (stream.AverageFrameRate || stream.RealFrameRate))); + attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate))); } if (stream.ChannelLayout) { - attributes.push(createAttribute(globalize.translate("MediaInfoLayout"), stream.ChannelLayout)); + attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout)); } if (stream.Channels) { - attributes.push(createAttribute(globalize.translate("MediaInfoChannels"), stream.Channels + " ch")); + attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), stream.Channels + ' ch')); } - if (stream.BitRate && stream.Codec !== "mjpeg") { - attributes.push(createAttribute(globalize.translate("MediaInfoBitrate"), (parseInt(stream.BitRate / 1000)) + " kbps")); + if (stream.BitRate && stream.Codec !== 'mjpeg') { + attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), (parseInt(stream.BitRate / 1000)) + ' kbps')); } if (stream.SampleRate) { - attributes.push(createAttribute(globalize.translate("MediaInfoSampleRate"), stream.SampleRate + " Hz")); + attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), stream.SampleRate + ' Hz')); } if (stream.BitDepth) { - attributes.push(createAttribute(globalize.translate("MediaInfoBitDepth"), stream.BitDepth + " bit")); + attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), stream.BitDepth + ' bit')); } if (stream.PixelFormat) { - attributes.push(createAttribute(globalize.translate("MediaInfoPixelFormat"), stream.PixelFormat)); + attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat)); } if (stream.RefFrames) { - attributes.push(createAttribute(globalize.translate("MediaInfoRefFrames"), stream.RefFrames)); + attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames)); } if (stream.NalLengthSize) { - attributes.push(createAttribute("NAL", stream.NalLengthSize)); + attributes.push(createAttribute('NAL', stream.NalLengthSize)); } - if (stream.Type !== "Video") { - attributes.push(createAttribute(globalize.translate("MediaInfoDefault"), (stream.IsDefault ? "Yes" : "No"))); + if (stream.Type !== 'Video') { + attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No'))); } - if (stream.Type === "Subtitle") { - attributes.push(createAttribute(globalize.translate("MediaInfoForced"), (stream.IsForced ? "Yes" : "No"))); - attributes.push(createAttribute(globalize.translate("MediaInfoExternal"), (stream.IsExternal ? "Yes" : "No"))); + if (stream.Type === 'Subtitle') { + 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)); + if (stream.Type === 'Video' && version.Timestamp) { + attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp)); } - html += attributes.join("
"); - html += "
"; + html += attributes.join('
'); + html += '
'; } return html; } function createAttribute(label, value) { - return '' + label + '' + value + "" + return '' + label + '' + value + ''; } function showMediaInfoMore(itemId, serverId, template) { var apiClient = connectionManager.getApiClient(serverId); return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { var dialogOptions = { - size: "small", + size: 'small', removeOnClose: true, scrollY: false }; if (layoutManager.tv) { - dialogOptions.size = "fullscreen"; + dialogOptions.size = 'fullscreen'; } var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add("formDialog"); - var html = ""; - html += globalize.translateDocument(template, "core"); + dlg.classList.add('formDialog'); + var html = ''; + html += globalize.translateDocument(template, 'core'); dlg.innerHTML = html; if (layoutManager.tv) { - dlg.querySelector(".formDialogContent"); + dlg.querySelector('.formDialogContent'); } dialogHelper.open(dlg); - dlg.querySelector(".btnCancel").addEventListener("click", function (e) { + dlg.querySelector('.btnCancel').addEventListener('click', function (e) { dialogHelper.close(dlg); }); apiClient.getCurrentUser().then(function (user) { @@ -152,7 +152,7 @@ define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings", function showMediaInfo(itemId, serverId) { loading.show(); return new Promise(function (resolve, reject) { - require(["text!./itemMediaInfo.template.html"], function (template) { + require(['text!./itemMediaInfo.template.html'], function (template) { showMediaInfoMore(itemId, serverId, template).then(resolve, reject); }); }); diff --git a/src/components/itemMediaInfo/itemMediaInfo.template.html b/src/components/itemMediaInfo/itemMediaInfo.template.html index d5f583dcab1..bc8ce596807 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.template.html +++ b/src/components/itemMediaInfo/itemMediaInfo.template.html @@ -1,6 +1,6 @@

${HeaderMediaInfo}

diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index 6f28de0b3bc..3399386394b 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -1,5 +1,5 @@ -define(["dialogHelper", "loading", "connectionManager", "require", "globalize", "scrollHelper", "layoutManager", "focusManager", "browser", "emby-input", "emby-checkbox", "paper-icon-button-light", "css!./../formdialog", "material-icons", "cardStyle"], function (dialogHelper, loading, connectionManager, require, globalize, scrollHelper, layoutManager, focusManager, browser) { - "use strict"; +define(['dialogHelper', 'loading', 'connectionManager', 'require', 'globalize', 'scrollHelper', 'layoutManager', 'focusManager', 'browser', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'css!./../formdialog', 'material-icons', 'cardStyle'], function (dialogHelper, loading, connectionManager, require, globalize, scrollHelper, layoutManager, focusManager, browser) { + 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; @@ -23,7 +23,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", var i; var length; - var identifyField = page.querySelectorAll(".identifyField"); + var identifyField = page.querySelectorAll('.identifyField'); var value; for (i = 0, length = identifyField.length; i < length; i++) { @@ -31,17 +31,17 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", if (value) { - if (identifyField[i].type === "number") { + if (identifyField[i].type === 'number') { value = parseInt(value); } - lookupInfo[identifyField[i].getAttribute("data-lookup")] = value; + lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; } } var hasId = false; - var txtLookupId = page.querySelectorAll(".txtLookupId"); + var txtLookupId = page.querySelectorAll('.txtLookupId'); for (i = 0, length = txtLookupId.length; i < length; i++) { value = txtLookupId[i].value; @@ -49,12 +49,12 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", if (value) { hasId = true; } - lookupInfo.ProviderIds[txtLookupId[i].getAttribute("data-providerkey")] = value; + lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; } if (!hasId && !lookupInfo.Name) { - require(["toast"], function (toast) { - toast(globalize.translate("PleaseEnterNameOrId")); + require(['toast'], function (toast) { + toast(globalize.translate('PleaseEnterNameOrId')); }); return; } @@ -74,11 +74,11 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", var apiClient = getApiClient(); apiClient.ajax({ - type: "POST", - url: apiClient.getUrl("Items/RemoteSearch/" + currentItemType), + type: 'POST', + url: apiClient.getUrl('Items/RemoteSearch/' + currentItemType), data: JSON.stringify(lookupInfo), - contentType: "application/json", - dataType: "json" + contentType: 'application/json', + dataType: 'json' }).then(function (results) { @@ -89,14 +89,14 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", function showIdentificationSearchResults(page, results) { - var identificationSearchResults = page.querySelector(".identificationSearchResults"); + var 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"); + page.querySelector('.popupIdentifyForm').classList.add('hide'); + identificationSearchResults.classList.remove('hide'); + page.querySelector('.identifyOptionsForm').classList.add('hide'); + page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered'); - var html = ""; + var html = ''; var i; var length; for (i = 0, length = results.length; i < length; i++) { @@ -105,11 +105,11 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", html += getSearchResultHtml(result, i); } - var elem = page.querySelector(".identificationSearchResultList"); + var elem = page.querySelector('.identificationSearchResultList'); elem.innerHTML = html; function onSearchImageClick() { - var index = parseInt(this.getAttribute("data-index")); + var index = parseInt(this.getAttribute('data-index')); var currentResult = results[index]; @@ -122,10 +122,10 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", } } - var searchImages = elem.querySelectorAll(".card"); + var searchImages = elem.querySelectorAll('.card'); for (i = 0, length = searchImages.length; i < length; i++) { - searchImages[i].addEventListener("click", onSearchImageClick); + searchImages[i].addEventListener('click', onSearchImageClick); } if (layoutManager.tv) { @@ -143,13 +143,13 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", function showIdentifyOptions(page, identifyResult) { - var identifyOptionsForm = page.querySelector(".identifyOptionsForm"); + var 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"); + 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; @@ -160,48 +160,48 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", lines.push(identifyResult.ProductionYear); } - var resultHtml = lines.join("
"); + var resultHtml = lines.join('
'); if (identifyResult.ImageUrl) { var displayUrl = getSearchImageDisplayUrl(identifyResult.ImageUrl, identifyResult.SearchProviderName); - resultHtml = '
' + resultHtml + "
"; + resultHtml = '
' + resultHtml + '
'; } - page.querySelector(".selectedSearchResult").innerHTML = resultHtml; + page.querySelector('.selectedSearchResult').innerHTML = resultHtml; - focusManager.focus(identifyOptionsForm.querySelector(".btnSubmit")); + focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit')); } function getSearchResultHtml(result, index) { // TODO move card creation code to Card component - var html = ""; - var cssClass = "card scalableCard"; - var cardBoxCssClass = "cardBox"; + var html = ''; + var cssClass = 'card scalableCard'; + var cardBoxCssClass = 'cardBox'; var padderClass; - if (currentItemType === "Episode") { - cssClass += " backdropCard backdropCard-scalable"; - padderClass = "cardPadder-backdrop"; - } else if (currentItemType === "MusicAlbum" || currentItemType === "MusicArtist") { - cssClass += " squareCard squareCard-scalable"; - padderClass = "cardPadder-square"; + 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"; + cssClass += ' portraitCard portraitCard-scalable'; + padderClass = 'cardPadder-portrait'; } if (layoutManager.tv) { - cssClass += " show-focus"; + cssClass += ' show-focus'; if (enableFocusTransform) { - cssClass += " show-animation"; + cssClass += ' show-animation'; } } - cardBoxCssClass += " cardBox-bottompadded"; + cardBoxCssClass += ' cardBox-bottompadded'; html += '
"; + html += '
'; + html += '
'; - var numLines = 2; - if (currentItemType === "MusicAlbum") { + var numLines = 3; + if (currentItemType === 'MusicAlbum') { numLines++; } var lines = [result.Name]; + lines.push(result.SearchProviderName); + if (result.AlbumArtist) { lines.push(result.AlbumArtist.Name); } @@ -242,19 +244,19 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", } else { html += '
'; } - html += lines[i] || " "; - html += "
"; + html += lines[i] || ' '; + html += '
'; } - html += "
"; - html += ""; + html += '
'; + html += ''; return html; } function getSearchImageDisplayUrl(url, provider) { var apiClient = getApiClient(); - return apiClient.getUrl("Items/RemoteSearch/Image", { imageUrl: url, ProviderName: provider }); + return apiClient.getUrl('Items/RemoteSearch/Image', { imageUrl: url, ProviderName: provider }); } function submitIdentficationResult(page) { @@ -262,16 +264,16 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", loading.show(); var options = { - ReplaceAllImages: page.querySelector("#chkIdentifyReplaceImages").checked + ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked }; var apiClient = getApiClient(); apiClient.ajax({ - type: "POST", - url: apiClient.getUrl("Items/RemoteSearch/Apply/" + currentItem.Id, options), + type: 'POST', + url: apiClient.getUrl('Items/RemoteSearch/Apply/' + currentItem.Id, options), data: JSON.stringify(currentSearchResult), - contentType: "application/json" + contentType: 'application/json' }).then(function () { @@ -292,44 +294,45 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", var apiClient = getApiClient(); - apiClient.getJSON(apiClient.getUrl("Items/" + item.Id + "/ExternalIdInfos")).then(function (idList) { - - var html = ""; + apiClient.getJSON(apiClient.getUrl('Items/' + item.Id + '/ExternalIdInfos')).then(function (idList) { - var providerIds = item.ProviderIds || {}; + var html = ''; for (var i = 0, length = idList.length; i < length; i++) { var idInfo = idList[i]; - var id = "txtLookup" + idInfo.Key; + var id = 'txtLookup' + idInfo.Key; html += '
'; - var idLabel = globalize.translate("LabelDynamicExternalId").replace("{0}", idInfo.Name); + var fullName = idInfo.Name; + if (idInfo.Type) { + fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + } - var value = providerIds[idInfo.Key] || ""; + var idLabel = globalize.translate('LabelDynamicExternalId', fullName); html += ''; - html += "
"; + html += '
'; } - page.querySelector("#txtLookupName").value = ""; + page.querySelector('#txtLookupName').value = ''; - if (item.Type === "Person" || item.Type === "BoxSet") { + if (item.Type === 'Person' || item.Type === 'BoxSet') { - page.querySelector(".fldLookupYear").classList.add("hide"); - page.querySelector("#txtLookupYear").value = ""; + page.querySelector('.fldLookupYear').classList.add('hide'); + page.querySelector('#txtLookupYear').value = ''; } else { - page.querySelector(".fldLookupYear").classList.remove("hide"); - page.querySelector("#txtLookupYear").value = ""; + page.querySelector('.fldLookupYear').classList.remove('hide'); + page.querySelector('#txtLookupYear').value = ''; } - page.querySelector(".identifyProviderIds").innerHTML = html; + page.querySelector('.identifyProviderIds').innerHTML = html; - page.querySelector(".formDialogHeaderTitle").innerHTML = globalize.translate("Identify"); + page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify'); }); } @@ -337,7 +340,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", loading.show(); - require(["text!./itemidentifier.template.html"], function (template) { + require(['text!./itemidentifier.template.html'], function (template) { var apiClient = getApiClient(); @@ -347,30 +350,30 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", currentItemType = currentItem.Type; var dialogOptions = { - size: "fullscreen-border", + size: 'small', removeOnClose: true, scrollY: false }; if (layoutManager.tv) { - dialogOptions.size = "fullscreen"; + dialogOptions.size = 'fullscreen'; } var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add("formDialog"); - dlg.classList.add("recordingDialog"); + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - var html = ""; - html += globalize.translateDocument(template, "core"); + var html = ''; + html += globalize.translateDocument(template, 'core'); dlg.innerHTML = html; // Has to be assigned a z-index after the call to .open() - dlg.addEventListener("close", onDialogClosed); + dlg.addEventListener('close', onDialogClosed); if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector(".formDialogContent"), false); + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } if (item.Path) { @@ -383,26 +386,26 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", dialogHelper.open(dlg); - dlg.querySelector(".popupIdentifyForm").addEventListener("submit", function (e) { + dlg.querySelector('.popupIdentifyForm').addEventListener('submit', function (e) { e.preventDefault(); searchForIdentificationResults(dlg); return false; }); - dlg.querySelector(".identifyOptionsForm").addEventListener("submit", function (e) { + dlg.querySelector('.identifyOptionsForm').addEventListener('submit', function (e) { e.preventDefault(); submitIdentficationResult(dlg); return false; }); - dlg.querySelector(".btnCancel").addEventListener("click", function (e) { + dlg.querySelector('.btnCancel').addEventListener('click', function (e) { dialogHelper.close(dlg); }); - dlg.classList.add("identifyDialog"); + dlg.classList.add('identifyDialog'); showIdentificationForm(dlg, item); loading.hide(); @@ -425,47 +428,47 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", currentItem = null; currentItemType = itemType; - require(["text!./itemidentifier.template.html"], function (template) { + require(['text!./itemidentifier.template.html'], function (template) { var dialogOptions = { - size: "fullscreen-border", + size: 'small', removeOnClose: true, scrollY: false }; if (layoutManager.tv) { - dialogOptions.size = "fullscreen"; + dialogOptions.size = 'fullscreen'; } var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add("formDialog"); - dlg.classList.add("recordingDialog"); + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - var html = ""; - html += globalize.translateDocument(template, "core"); + var html = ''; + html += globalize.translateDocument(template, 'core'); dlg.innerHTML = html; if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector(".formDialogContent"), false); + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } dialogHelper.open(dlg); - dlg.querySelector(".btnCancel").addEventListener("click", function (e) { + dlg.querySelector('.btnCancel').addEventListener('click', function (e) { dialogHelper.close(dlg); }); - dlg.querySelector(".popupIdentifyForm").addEventListener("submit", function (e) { + dlg.querySelector('.popupIdentifyForm').addEventListener('submit', function (e) { e.preventDefault(); searchForIdentificationResults(dlg); return false; }); - dlg.addEventListener("close", function () { + dlg.addEventListener('close', function () { loading.hide(); var foundItem = hasChanges ? currentSearchResult : null; @@ -473,7 +476,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", resolveFunc(foundItem); }); - dlg.classList.add("identifyDialog"); + dlg.classList.add('identifyDialog'); showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); }); @@ -481,20 +484,20 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { - dlg.querySelector("#txtLookupName").value = itemName; + dlg.querySelector('#txtLookupName').value = itemName; - if (itemType === "Person" || itemType === "BoxSet") { + if (itemType === 'Person' || itemType === 'BoxSet') { - dlg.querySelector(".fldLookupYear").classList.add("hide"); - dlg.querySelector("#txtLookupYear").value = ""; + dlg.querySelector('.fldLookupYear').classList.add('hide'); + dlg.querySelector('#txtLookupYear').value = ''; } else { - dlg.querySelector(".fldLookupYear").classList.remove("hide"); - dlg.querySelector("#txtLookupYear").value = itemYear; + dlg.querySelector('.fldLookupYear').classList.remove('hide'); + dlg.querySelector('#txtLookupYear').value = itemYear; } - dlg.querySelector(".formDialogHeaderTitle").innerHTML = globalize.translate("Search"); + dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); } return { diff --git a/src/components/itemidentifier/itemidentifier.template.html b/src/components/itemidentifier/itemidentifier.template.html index 9b3e58a8644..9ea0e945db6 100644 --- a/src/components/itemidentifier/itemidentifier.template.html +++ b/src/components/itemidentifier/itemidentifier.template.html @@ -1,6 +1,6 @@

${Identify}

diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index 21bcdf59335..1428f3468d6 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -45,7 +45,7 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve // Take a guess at initial layout. The consuming app can override if (browser.mobile) { this.setLayout('mobile', false); - } else if (browser.tv || browser.xboxOne) { + } else if (browser.tv || browser.xboxOne || browser.ps4) { this.setLayout('tv', false); } else { this.setLayout(this.defaultLayout || 'tv', false); diff --git a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js new file mode 100644 index 00000000000..7751fe8bc25 --- /dev/null +++ b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js @@ -0,0 +1,67 @@ +/* eslint-disable indent */ + export class LazyLoader { + constructor(options) { + this.options = options; + } + + createObserver() { + const callback = this.options.callback; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + callback(entry); + }); + }, + {rootMargin: '25%'}); + + this.observer = observer; + } + + addElements(elements) { + let observer = this.observer; + + if (!observer) { + this.createObserver(); + observer = this.observer; + } + + Array.from(elements).forEach(element => { + observer.observe(element); + }); + } + + destroyObserver() { + const observer = this.observer; + + if (observer) { + observer.disconnect(); + this.observer = null; + } + } + + 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); + } + +/* eslint-enable indent */ +export default { + LazyLoader: LazyLoader, + lazyChildren: lazyChildren +}; diff --git a/src/components/lazyloader/lazyedgehack.css b/src/components/lazyloader/lazyedgehack.css deleted file mode 100644 index e358872f164..00000000000 --- a/src/components/lazyloader/lazyedgehack.css +++ /dev/null @@ -1,5 +0,0 @@ -.lazy { - /* In edge, intersection observer will not fire on 0px sized elements */ - min-width: 0.1em; - min-height: 0.1em; -} diff --git a/src/components/lazyloader/lazyloader-intersectionobserver.js b/src/components/lazyloader/lazyloader-intersectionobserver.js deleted file mode 100644 index 5f837426015..00000000000 --- a/src/components/lazyloader/lazyloader-intersectionobserver.js +++ /dev/null @@ -1,103 +0,0 @@ -define(['require', 'browser'], function (require, browser) { - 'use strict'; - - function LazyLoader(options) { - - this.options = options; - } - - if (browser.edge) { - require(['css!./lazyedgehack']); - } - - LazyLoader.prototype.createObserver = function () { - - var observerOptions = {}; - var options = this.options; - var loadedCount = 0; - var callback = options.callback; - - observerOptions.rootMargin = "50%"; - - var observerId = 'obs' + new Date().getTime(); - - var self = this; - var observer = new IntersectionObserver(function (entries) { - for (var j = 0, length2 = entries.length; j < length2; j++) { - var entry = entries[j]; - - if (entry.intersectionRatio > 0) { - - // Stop watching and load the image - var target = entry.target; - - observer.unobserve(target); - - if (!target[observerId]) { - target[observerId] = 1; - callback(target); - loadedCount++; - - if (loadedCount >= self.elementCount) { - self.destroyObserver(); - } - } - } - } - }, - observerOptions - ); - - this.observer = observer; - }; - - LazyLoader.prototype.addElements = function (elements) { - - var observer = this.observer; - - if (!observer) { - this.createObserver(); - observer = this.observer; - } - - this.elementCount = (this.elementCount || 0) + elements.length; - - for (var i = 0, length = elements.length; i < length; i++) { - observer.observe(elements[i]); - } - }; - - LazyLoader.prototype.destroyObserver = function (elements) { - - var observer = this.observer; - - if (observer) { - observer.disconnect(); - this.observer = null; - } - }; - - LazyLoader.prototype.destroy = function (elements) { - - this.destroyObserver(); - this.options = null; - }; - - function unveilElements(elements, root, callback) { - - if (!elements.length) { - return; - } - var lazyLoader = new LazyLoader({ - callback: callback - }); - lazyLoader.addElements(elements); - } - - LazyLoader.lazyChildren = function (elem, callback) { - - unveilElements(elem.getElementsByClassName('lazy'), elem, callback); - }; - - return LazyLoader; -}); diff --git a/src/components/lazyloader/lazyloader-scroll.js b/src/components/lazyloader/lazyloader-scroll.js deleted file mode 100644 index d5120146cee..00000000000 --- a/src/components/lazyloader/lazyloader-scroll.js +++ /dev/null @@ -1,189 +0,0 @@ -define(['visibleinviewport', 'dom', 'browser'], function (visibleinviewport, dom, browser) { - 'use strict'; - - var thresholdX; - var thresholdY; - - var requestIdleCallback = window.requestIdleCallback || function (fn) { - fn(); - }; - - function resetThresholds() { - - var threshold = 0.3; - - thresholdX = screen.availWidth * threshold; - thresholdY = screen.availHeight * threshold; - } - - function resetThresholdsOnTimer() { - - setTimeout(resetThresholds, 500); - } - - if (browser.iOS) { - dom.addEventListener(window, "orientationchange", resetThresholdsOnTimer, { passive: true }); - dom.addEventListener(window, 'resize', resetThresholdsOnTimer, { passive: true }); - } else { - dom.addEventListener(window, "orientationchange", resetThresholds, { passive: true }); - dom.addEventListener(window, 'resize', resetThresholds, { passive: true }); - } - resetThresholds(); - - function isVisible(elem) { - return visibleinviewport(elem, true, thresholdX, thresholdY); - } - - var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); - var self = {}; - - function cancelAll(tokens) { - for (var i = 0, length = tokens.length; i < length; i++) { - - tokens[i] = true; - } - } - - function unveilElementsInternal(instance, callback) { - - var unveiledElements = []; - var cancellationTokens = []; - var loadedCount = 0; - - function unveilInternal(tokenIndex) { - - var anyFound = false; - var out = false; - - var elements = instance.elements; - // TODO: This out construct assumes left to right, top to bottom - - for (var i = 0, length = elements.length; i < length; i++) { - - if (cancellationTokens[tokenIndex]) { - return; - } - if (unveiledElements[i]) { - continue; - } - var elem = elements[i]; - if (!out && isVisible(elem)) { - anyFound = true; - unveiledElements[i] = true; - callback(elem); - loadedCount++; - } else { - - if (anyFound) { - out = true; - } - } - } - - if (loadedCount >= elements.length) { - dom.removeEventListener(document, 'focus', unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(document, 'scroll', unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(document, wheelEvent, unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(window, 'resize', unveil, { - capture: true, - passive: true - }); - } - } - - function unveil() { - - cancelAll(cancellationTokens); - - var index = cancellationTokens.length; - cancellationTokens.length++; - - setTimeout(function () { - unveilInternal(index); - }, 1); - } - - dom.addEventListener(document, 'focus', unveil, { - capture: true, - passive: true - }); - dom.addEventListener(document, 'scroll', unveil, { - capture: true, - passive: true - }); - dom.addEventListener(document, wheelEvent, unveil, { - capture: true, - passive: true - }); - dom.addEventListener(window, 'resize', unveil, { - capture: true, - passive: true - }); - - unveil(); - } - - function LazyLoader(options) { - - this.options = options; - } - - LazyLoader.prototype.createObserver = function () { - - unveilElementsInternal(this, this.options.callback); - this.observer = 1; - }; - - LazyLoader.prototype.addElements = function (elements) { - - this.elements = this.elements || []; - - for (var i = 0, length = elements.length; i < length; i++) { - this.elements.push(elements[i]); - } - - var observer = this.observer; - - if (!observer) { - this.createObserver(); - } - - }; - - LazyLoader.prototype.destroyObserver = function (elements) { - - }; - - LazyLoader.prototype.destroy = function (elements) { - - this.destroyObserver(); - this.options = null; - }; - - function unveilElements(elements, root, callback) { - - if (!elements.length) { - return; - } - var lazyLoader = new LazyLoader({ - callback: callback - }); - lazyLoader.addElements(elements); - } - - LazyLoader.lazyChildren = function (elem, callback) { - - unveilElements(elem.getElementsByClassName('lazy'), elem, callback); - }; - - return LazyLoader; -}); diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index a398d7043a1..832a6ffc5ea 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -1,130 +1,136 @@ -define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], function(globalize, dom) { - "use strict"; +define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], function(globalize, dom) { + 'use strict'; function populateLanguages(parent) { return ApiClient.getCultures().then(function(languages) { - populateLanguagesIntoSelect(parent.querySelector("#selectLanguage"), languages); - populateLanguagesIntoList(parent.querySelector(".subtitleDownloadLanguages"), languages); + populateLanguagesIntoSelect(parent.querySelector('#selectLanguage'), languages); + populateLanguagesIntoList(parent.querySelector('.subtitleDownloadLanguages'), languages); }); } function populateLanguagesIntoSelect(select, languages) { - var html = ""; + var html = ''; html += ""; for (var i = 0; i < languages.length; i++) { var culture = languages[i]; - html += ""; + html += "'; } select.innerHTML = html; } function populateLanguagesIntoList(element, languages) { - var html = ""; + var html = ''; for (var i = 0; i < languages.length; i++) { var culture = languages[i]; - html += '"; + html += ''; } element.innerHTML = html; } function populateCountries(select) { return ApiClient.getCountries().then(function(allCountries) { - var html = ""; + var html = ''; html += ""; for (var i = 0; i < allCountries.length; i++) { var culture = allCountries[i]; - html += ""; + html += "'; } select.innerHTML = html; - }) + }); } function populateRefreshInterval(select) { - var html = ""; - html += ""; + var html = ''; + html += "'; html += [30, 60, 90].map(function(val) { - return ""; - }).join(""); + return "'; + }).join(''); select.innerHTML = html; } function renderMetadataReaders(page, plugins) { - var html = ""; - var elem = page.querySelector(".metadataReaders"); + var html = ''; + var elem = page.querySelector('.metadataReaders'); - if (plugins.length < 1) return elem.innerHTML = "", elem.classList.add("hide"), !1; - html += '

' + globalize.translate("LabelMetadataReaders") + "

"; + if (plugins.length < 1) return elem.innerHTML = '', elem.classList.add('hide'), !1; + html += '

' + globalize.translate('LabelMetadataReaders') + '

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

'; html += plugin.Name; - html += "

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

' + globalize.translate("LabelMetadataSavers") + "

"; + var html = ''; + var elem = page.querySelector('.metadataSavers'); + if (!metadataSavers.length) return elem.innerHTML = '', elem.classList.add('hide'), false; + html += '

' + globalize.translate('LabelMetadataSavers') + '

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

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

"; + html += '

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

'; html += '
'; - for (var i = 0; i < plugins.length; i++) { - var plugin = plugins[i]; + + plugins.forEach((plugin, index) => { html += '
'; var isChecked = libraryOptionsForType.MetadataFetchers ? -1 !== libraryOptionsForType.MetadataFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled; - var checkedHtml = isChecked ? ' checked="checked"' : ""; - html += '"; + var checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; html += '
'; html += '

'; html += plugin.Name; - html += "

"; - html += "
"; - i > 0 ? html += '' : plugins.length > 1 && (html += ''), html += "
" - } - html += "
"; - html += '
' + globalize.translate("LabelMetadataDownloadersHelp") + "
"; - html += "
"; + html += ''; + html += '
'; + if (index > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; + } + html += '
'; + }); + + html += '
'; + html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; + html += '
'; return html; } @@ -138,62 +144,62 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct } function renderMetadataFetchers(page, availableOptions, libraryOptions) { - var html = ""; - var elem = page.querySelector(".metadataFetchers"); + var html = ''; + var elem = page.querySelector('.metadataFetchers'); for (var i = 0; i < availableOptions.TypeOptions.length; i++) { var 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"); + 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"); + 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) { - var html = ""; - var elem = page.querySelector(".subtitleFetchers"); + var html = ''; + var elem = page.querySelector('.subtitleFetchers'); var plugins = availableOptions.SubtitleFetchers; plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []); if (!plugins.length) return html; - html += '

' + globalize.translate("LabelSubtitleDownloaders") + "

"; + html += '

' + globalize.translate('LabelSubtitleDownloaders') + '

'; html += '
'; for (var i = 0; i < plugins.length; i++) { var plugin = plugins[i]; html += '
'; var isChecked = libraryOptions.DisabledSubtitleFetchers ? -1 === libraryOptions.DisabledSubtitleFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled; - var checkedHtml = isChecked ? ' checked="checked"' : ""; - html += '"; + var checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; html += '
'; html += '

'; html += plugin.Name; - html += "

"; - html += "
"; + html += ''; + html += '
'; if (i > 0) { - html += ''; + html += ''; } else if (plugins.length > 1) { - html += ''; + html += ''; } - html += "
"; + html += '
'; } - html += "
"; - html += '
' + globalize.translate("SubtitleDownloadersHelp") + "
"; + html += '
'; + html += '
' + globalize.translate('SubtitleDownloadersHelp') + '
'; elem.innerHTML = html; } function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { - var html = ""; + var html = ''; var plugins = availableTypeOptions.ImageFetchers; plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []); @@ -201,60 +207,60 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct html += '
'; html += '
'; - html += '

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

"; + html += '

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

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

'; html += plugin.Name; - html += "

"; - html += "
"; + html += ''; + html += '
'; if (i > 0) { - html += ''; + html += ''; } else if (plugins.length > 1) { - html += ''; + html += ''; } - html += "
"; + html += '
'; } - html += "
"; - html += '
' + globalize.translate("LabelImageFetchersHelp") + "
"; - html += "
"; + html += '
'; + html += '
' + globalize.translate('LabelImageFetchersHelp') + '
'; + html += '
'; return html; } function renderImageFetchers(page, availableOptions, libraryOptions) { - var html = ""; - var elem = page.querySelector(".imageFetchers"); + var html = ''; + var elem = page.querySelector('.imageFetchers'); for (var i = 0; i < availableOptions.TypeOptions.length; i++) { var availableTypeOptions = availableOptions.TypeOptions[i]; html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); } elem.innerHTML = html; if (html) { - elem.classList.remove("hide"); - page.querySelector(".chkDownloadImagesInAdvanceContainer").classList.remove("hide"); - page.querySelector(".chkSaveLocalContainer").classList.remove("hide"); + elem.classList.remove('hide'); + page.querySelector('.chkDownloadImagesInAdvanceContainer').classList.remove('hide'); + page.querySelector('.chkSaveLocalContainer').classList.remove('hide'); } else { - elem.classList.add("hide"); - page.querySelector(".chkDownloadImagesInAdvanceContainer").classList.add("hide"); - page.querySelector(".chkSaveLocalContainer").classList.add("hide"); + elem.classList.add('hide'); + page.querySelector('.chkDownloadImagesInAdvanceContainer').classList.add('hide'); + page.querySelector('.chkSaveLocalContainer').classList.add('hide'); } return true; } function populateMetadataSettings(parent, contentType, isNewLibrary) { - var isNewLibrary = parent.classList.contains("newlibrary"); - return ApiClient.getJSON(ApiClient.getUrl("Libraries/AvailableOptions", { + var isNewLibrary = parent.classList.contains('newlibrary'); + return ApiClient.getJSON(ApiClient.getUrl('Libraries/AvailableOptions', { LibraryContentType: contentType, IsNewLibrary: isNewLibrary })).then(function(availableOptions) { @@ -265,70 +271,80 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct renderMetadataFetchers(parent, availableOptions, {}); renderSubtitleFetchers(parent, availableOptions, {}); renderImageFetchers(parent, availableOptions, {}); - availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide") + availableOptions.SubtitleFetchers.length ? parent.querySelector('.subtitleDownloadSettings').classList.remove('hide') : parent.querySelector('.subtitleDownloadSettings').classList.add('hide'); }).catch(function() { return Promise.resolve(); - }) + }); } function adjustSortableListElement(elem) { - var btnSortable = elem.querySelector(".btnSortable"); - var inner = btnSortable.querySelector("i"); + var btnSortable = elem.querySelector('.btnSortable'); + var inner = btnSortable.querySelector('.material-icons'); if (elem.previousSibling) { - btnSortable.title = globalize.translate("ButtonUp"); - btnSortable.classList.add("btnSortableMoveUp"); - btnSortable.classList.remove("btnSortableMoveDown"); - inner.classList.remove("keyboard_arrow_down"); - inner.classList.add("keyboard_arrow_up"); + btnSortable.title = globalize.translate('ButtonUp'); + 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("ButtonDown"); - btnSortable.classList.remove("btnSortableMoveUp"); - btnSortable.classList.add("btnSortableMoveDown"); - inner.classList.remove("keyboard_arrow_up"); - inner.classList.add("keyboard_arrow_down"); + btnSortable.title = globalize.translate('ButtonDown'); + btnSortable.classList.remove('btnSortableMoveUp'); + btnSortable.classList.add('btnSortableMoveDown'); + inner.classList.remove('keyboard_arrow_up'); + inner.classList.add('keyboard_arrow_down'); } } function showImageOptionsForType(type) { - require(["imageoptionseditor"], function(ImageOptionsEditor) { + require(['imageoptionseditor'], function(ImageOptionsEditor) { var typeOptions = getTypeOptions(currentLibraryOptions, type); - typeOptions || (typeOptions = { - Type: type - }, currentLibraryOptions.TypeOptions.push(typeOptions)); + if (!typeOptions) { + typeOptions = { + Type: type + }; + currentLibraryOptions.TypeOptions.push(typeOptions); + } var availableOptions = getTypeOptions(currentAvailableOptions || {}, type); - (new ImageOptionsEditor).show(type, typeOptions, availableOptions) - }) + var imageOptionsEditor = new ImageOptionsEditor(); + imageOptionsEditor.show(type, typeOptions, availableOptions); + }); } function onImageFetchersContainerClick(e) { - var btnImageOptionsForType = dom.parentWithClass(e.target, "btnImageOptionsForType"); + var btnImageOptionsForType = dom.parentWithClass(e.target, 'btnImageOptionsForType'); if (btnImageOptionsForType) { - return void showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, "imageFetcher").getAttribute("data-type")); + return void showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, 'imageFetcher').getAttribute('data-type')); } onSortableContainerClick.call(this, e); } function onSortableContainerClick(e) { - var btnSortable = dom.parentWithClass(e.target, "btnSortable"); + var btnSortable = dom.parentWithClass(e.target, 'btnSortable'); if (btnSortable) { - var li = dom.parentWithClass(btnSortable, "sortableOption"); - var list = dom.parentWithClass(li, "paperList"); - if (btnSortable.classList.contains("btnSortableMoveDown")) { + var li = dom.parentWithClass(btnSortable, 'sortableOption'); + var list = dom.parentWithClass(li, 'paperList'); + if (btnSortable.classList.contains('btnSortableMoveDown')) { var next = li.nextSibling; - next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling)) + if (next) { + li.parentNode.removeChild(li); + next.parentNode.insertBefore(li, next.nextSibling); + } } else { var prev = li.previousSibling; - prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev)) + if (prev) { + li.parentNode.removeChild(li); + prev.parentNode.insertBefore(li, prev); + } } - Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement) + 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('.metadataReaders').addEventListener('click', onSortableContainerClick); + parent.querySelector('.subtitleFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.metadataFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.imageFetchers').addEventListener('click', onImageFetchersContainerClick); } function embed(parent, contentType, libraryOptions) { @@ -337,15 +353,15 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct }; currentAvailableOptions = null; var isNewLibrary = null === libraryOptions; - isNewLibrary && parent.classList.add("newlibrary"); + isNewLibrary && parent.classList.add('newlibrary'); return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest; - xhr.open("GET", "components/libraryoptionseditor/libraryoptionseditor.template.html", true); + xhr.open('GET', 'components/libraryoptionseditor/libraryoptionseditor.template.html', true); xhr.onload = function(e) { var template = this.response; parent.innerHTML = globalize.translateDocument(template); - populateRefreshInterval(parent.querySelector("#selectAutoRefreshInterval")); - var promises = [populateLanguages(parent), populateCountries(parent.querySelector("#selectCountry"))]; + populateRefreshInterval(parent.querySelector('#selectAutoRefreshInterval')); + var promises = [populateLanguages(parent), populateCountries(parent.querySelector('#selectCountry'))]; Promise.all(promises).then(function() { return setContentType(parent, contentType).then(function() { libraryOptions && setLibraryOptions(parent, libraryOptions); @@ -359,69 +375,69 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct } function setAdvancedVisible(parent, visible) { - var elems = parent.querySelectorAll(".advanced"); + var elems = parent.querySelectorAll('.advanced'); for (var i = 0; i < elems.length; i++) { - visible ? elems[i].classList.remove("advancedHide") : elems[i].classList.add("advancedHide"); + visible ? elems[i].classList.remove('advancedHide') : elems[i].classList.add('advancedHide'); } } function setContentType(parent, contentType) { - if (contentType === "homevideos" || contentType === "photos") { - parent.querySelector(".chkEnablePhotosContainer").classList.remove("hide"); + if (contentType === 'homevideos' || contentType === 'photos') { + parent.querySelector('.chkEnablePhotosContainer').classList.remove('hide'); } else { - parent.querySelector(".chkEnablePhotosContainer").classList.add("hide"); + parent.querySelector('.chkEnablePhotosContainer').classList.add('hide'); } - if (contentType !== "tvshows" && contentType !== "movies" && contentType !== "homevideos" && contentType !== "musicvideos" && contentType !== "mixed") { - parent.querySelector(".chapterSettingsSection").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"); + parent.querySelector('.chapterSettingsSection').classList.remove('hide'); } - if (contentType === "tvshows") { - parent.querySelector(".chkImportMissingEpisodesContainer").classList.remove("hide"); - parent.querySelector(".chkAutomaticallyGroupSeriesContainer").classList.remove("hide"); - parent.querySelector(".fldSeasonZeroDisplayName").classList.remove("hide"); - parent.querySelector("#txtSeasonZeroName").setAttribute("required", "required"); + if (contentType === 'tvshows') { + parent.querySelector('.chkImportMissingEpisodesContainer').classList.remove('hide'); + parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.remove('hide'); + parent.querySelector('.fldSeasonZeroDisplayName').classList.remove('hide'); + parent.querySelector('#txtSeasonZeroName').setAttribute('required', 'required'); } else { - parent.querySelector(".chkImportMissingEpisodesContainer").classList.add("hide"); - parent.querySelector(".chkAutomaticallyGroupSeriesContainer").classList.add("hide"); - parent.querySelector(".fldSeasonZeroDisplayName").classList.add("hide"); - parent.querySelector("#txtSeasonZeroName").removeAttribute("required"); + parent.querySelector('.chkImportMissingEpisodesContainer').classList.add('hide'); + 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"); + if (contentType === 'books' || contentType === 'boxsets' || contentType === 'playlists' || contentType === 'music') { + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.add('hide'); } else { - parent.querySelector(".chkEnableEmbeddedTitlesContainer").classList.remove("hide"); + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.remove('hide'); } - if (contentType === "tvshows") { - parent.querySelector(".chkEnableEmbeddedEpisodeInfosContainer").classList.remove("hide"); + if (contentType === 'tvshows') { + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); } else { - parent.querySelector(".chkEnableEmbeddedEpisodeInfosContainer").classList.add("hide"); + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide'); } return populateMetadataSettings(parent, contentType); } function setSubtitleFetchersIntoOptions(parent, options) { - options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) { - return !elem.checked + options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleFetcher'), function(elem) { + return !elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute('data-pluginname'); }); - options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) { - return elem.getAttribute("data-pluginname") + options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll('.subtitleFetcherItem'), function(elem) { + return elem.getAttribute('data-pluginname'); }); } function setMetadataFetchersIntoOptions(parent, options) { - var sections = parent.querySelectorAll(".metadataFetcher"); + var sections = parent.querySelectorAll('.metadataFetcher'); for (var i = 0; i < sections.length; i++) { var section = sections[i]; - var type = section.getAttribute("data-type"); + var type = section.getAttribute('data-type'); var typeOptions = getTypeOptions(options, type); if (!typeOptions) { typeOptions = { @@ -429,23 +445,23 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct }; options.TypeOptions.push(typeOptions); } - typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkMetadataFetcher"), function(elem) { + typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkMetadataFetcher'), function(elem) { return elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname"); + return elem.getAttribute('data-pluginname'); }); - typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".metadataFetcherItem"), function(elem) { - return elem.getAttribute("data-pluginname"); + typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.metadataFetcherItem'), function(elem) { + return elem.getAttribute('data-pluginname'); }); } } function setImageFetchersIntoOptions(parent, options) { - var sections = parent.querySelectorAll(".imageFetcher"); + var sections = parent.querySelectorAll('.imageFetcher'); for (var i = 0; i < sections.length; i++) { var section = sections[i]; - var type = section.getAttribute("data-type"); + var type = section.getAttribute('data-type'); var typeOptions = getTypeOptions(options, type); if (!typeOptions) { typeOptions = { @@ -454,14 +470,14 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct options.TypeOptions.push(typeOptions); } - typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) { - return elem.checked + typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkImageFetcher'), function(elem) { + return elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute('data-pluginname'); }); - typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) { - return elem.getAttribute("data-pluginname") + typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.imageFetcherItem'), function(elem) { + return elem.getAttribute('data-pluginname'); }); } } @@ -485,40 +501,40 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct function getLibraryOptions(parent) { var options = { EnableArchiveMediaFiles: false, - EnablePhotos: parent.querySelector(".chkEnablePhotos").checked, - EnableRealtimeMonitor: parent.querySelector(".chkEnableRealtimeMonitor").checked, - ExtractChapterImagesDuringLibraryScan: parent.querySelector(".chkExtractChaptersDuringLibraryScan").checked, - EnableChapterImageExtraction: parent.querySelector(".chkExtractChapterImages").checked, - DownloadImagesInAdvance: parent.querySelector("#chkDownloadImagesInAdvance").checked, + EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, + EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, + ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, + EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, + DownloadImagesInAdvance: parent.querySelector('#chkDownloadImagesInAdvance').checked, EnableInternetProviders: true, - ImportMissingEpisodes: parent.querySelector("#chkImportMissingEpisodes").checked, - 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), - EnableEmbeddedTitles: parent.querySelector("#chkEnableEmbeddedTitles").checked, - EnableEmbeddedEpisodeInfos: parent.querySelector("#chkEnableEmbeddedEpisodeInfos").checked, - SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked, - SkipSubtitlesIfAudioTrackMatches: parent.querySelector("#chkSkipIfAudioTrackPresent").checked, - SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked, - RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked, - MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { - return elem.checked + ImportMissingEpisodes: parent.querySelector('#chkImportMissingEpisodes').checked, + 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), + EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, + EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, + SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked, + SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked, + SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked, + RequirePerfectSubtitleMatch: parent.querySelector('#chkRequirePerfectMatch').checked, + MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkMetadataSaver'), function(elem) { + return elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute('data-pluginname'); }), TypeOptions: [] }; - options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll(".localReaderOption"), function(elem) { - return elem.getAttribute("data-pluginname") + options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll('.localReaderOption'), function(elem) { + return elem.getAttribute('data-pluginname'); }); - options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) { - return elem.checked + options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleLanguage'), function(elem) { + return elem.checked; }), function(elem) { - return elem.getAttribute("data-lang") + return elem.getAttribute('data-lang'); }); setSubtitleFetchersIntoOptions(parent, options); setMetadataFetchersIntoOptions(parent, options); @@ -531,7 +547,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct function getOrderedPlugins(plugins, configuredOrder) { plugins = plugins.slice(0); plugins.sort(function(a, b) { - return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0 + return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0; }); return plugins; } @@ -539,29 +555,29 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct 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("#chkDownloadImagesInAdvance").checked = options.DownloadImagesInAdvance; - parent.querySelector("#chkSaveLocal").checked = options.SaveLocalMetadata; - parent.querySelector("#chkImportMissingEpisodes").checked = options.ImportMissingEpisodes; - parent.querySelector(".chkAutomaticallyGroupSeries").checked = options.EnableAutomaticSeriesGrouping; - parent.querySelector("#chkEnableEmbeddedTitles").checked = options.EnableEmbeddedTitles; - parent.querySelector("#chkEnableEmbeddedEpisodeInfos").checked = options.EnableEmbeddedEpisodeInfos; - parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; - parent.querySelector("#chkSaveSubtitlesLocally").checked = options.SaveSubtitlesWithMedia; - parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches; - parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch; - Array.prototype.forEach.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { - elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled") + 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('#chkDownloadImagesInAdvance').checked = options.DownloadImagesInAdvance; + parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; + parent.querySelector('#chkImportMissingEpisodes').checked = options.ImportMissingEpisodes; + parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping; + parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles; + parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked = options.EnableEmbeddedEpisodeInfos; + parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; + parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia; + parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches; + parent.querySelector('#chkRequirePerfectMatch').checked = options.RequirePerfectSubtitleMatch; + Array.prototype.forEach.call(parent.querySelectorAll('.chkMetadataSaver'), function(elem) { + elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute('data-pluginname')) : 'true' === elem.getAttribute('data-defaultenabled'); }); - Array.prototype.forEach.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) { - elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang")) + Array.prototype.forEach.call(parent.querySelectorAll('.chkSubtitleLanguage'), function(elem) { + elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute('data-lang')); }); renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); renderMetadataFetchers(parent, parent.availableOptions, options); @@ -578,5 +594,5 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct getLibraryOptions: getLibraryOptions, setLibraryOptions: setLibraryOptions, setAdvancedVisible: setAdvancedVisible - } + }; }); diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index caa177108d8..92cebc9da4f 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -79,7 +79,7 @@

${HeaderLibrarySettings}

${OptionAutomaticallyGroupSeriesHelp}
diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index 532ba094a85..60340e2a0ec 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -1,4 +1,4 @@ -define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutManager', 'globalize', 'datetime', 'apphost', 'css!./listview', 'emby-ratingbutton', 'emby-playstatebutton'], function (itemHelper, mediaInfo, indicators, connectionManager, layoutManager, globalize, datetime, appHost) { +define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutManager', 'globalize', 'datetime', 'cardBuilder', 'css!./listview', 'emby-ratingbutton', 'emby-playstatebutton'], function (itemHelper, mediaInfo, indicators, connectionManager, layoutManager, globalize, datetime, cardBuilder) { 'use strict'; function getIndex(item, options) { @@ -70,64 +70,56 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan function getImageUrl(item, width) { var apiClient = connectionManager.getApiClient(item.ServerId); + let itemId; var options = { maxWidth: width * 2, - type: "Primary" + type: 'Primary' }; if (item.ImageTags && item.ImageTags.Primary) { - options.tag = item.ImageTags.Primary; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { - + itemId = item.Id; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); + itemId = item.AlbumId; } else if (item.SeriesId && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - return apiClient.getScaledImageUrl(item.SeriesId, options); - + itemId = item.SeriesId; } else if (item.ParentPrimaryImageTag) { - options.tag = item.ParentPrimaryImageTag; - return apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, options); + itemId = item.ParentPrimaryImageItemId; } + if (itemId) { + return apiClient.getScaledImageUrl(itemId, options); + } return null; } function getChannelImageUrl(item, width) { var apiClient = connectionManager.getApiClient(item.ServerId); - var options = { maxWidth: width * 2, - type: "Primary" + type: 'Primary' }; if (item.ChannelId && item.ChannelPrimaryImageTag) { - options.tag = item.ChannelPrimaryImageTag; - return apiClient.getScaledImageUrl(item.ChannelId, options); } - return null; + if (item.ChannelId) { + return apiClient.getScaledImageUrl(item.ChannelId, options); + } } function getTextLinesHtml(textlines, isLargeStyle) { - var html = ''; var largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; - for (var i = 0, length = textlines.length; i < length; i++) { - - var text = textlines[i]; - + for (const [i, text] of textlines.entries()) { if (!text) { continue; } @@ -160,7 +152,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan var button = options.rightButtons[i]; - html += ''; + html += ''; } return html; @@ -219,7 +211,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } - var cssClass = "listItem"; + var cssClass = 'listItem'; if (options.border || (options.highlight !== false && !layoutManager.tv)) { cssClass += ' listItem-border'; @@ -236,7 +228,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan var downloadWidth = 80; if (isLargeStyle) { - cssClass += " listItem-largeImage"; + cssClass += ' listItem-largeImage'; downloadWidth = 500; } @@ -262,9 +254,9 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } if (!clickEntireItem && options.dragHandle) { - //html += ''; + //html += ''; // Firefox and Edge are not allowing the button to be draggable - html += ''; + html += ''; } if (options.image !== false) { @@ -281,12 +273,12 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan imageClass += ' itemAction'; } - var imageAction = playOnImageClick ? 'resume' : action; + var imageAction = playOnImageClick ? 'link' : action; if (imgUrl) { html += '
'; } else { - html += '
'; + html += '
' + cardBuilder.getDefaultText(item, options); } var indicatorsHtml = ''; @@ -297,7 +289,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } if (playOnImageClick) { - html += ''; + html += ''; } var progressHtml = indicators.getProgressBarHtml(item, { @@ -355,7 +347,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan }); if (options.showIndexNumber && item.IndexNumber != null) { - displayName = item.IndexNumber + ". " + displayName; + displayName = item.IndexNumber + '. ' + displayName; } if (options.showParentTitle && options.parentTitleWithTitle) { @@ -426,8 +418,6 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan html += '
'; - var moreIcon = ''; - html += getTextLinesHtml(textlines, isLargeStyle); if (options.mediaInfo !== false) { @@ -475,15 +465,11 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (!clickEntireItem) { if (options.addToListButton) { - html += ''; - } - - if (options.moreButton !== false) { - html += ''; + html += ''; } if (options.infoButton) { - html += ''; + html += ''; } if (options.rightButtons) { @@ -495,14 +481,18 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan var userData = item.UserData || {}; var likes = userData.Likes == null ? '' : userData.Likes; - if (itemHelper.canMarkPlayed(item)) { - html += ''; + if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) { + html += ''; } - if (itemHelper.canRate(item)) { - html += ''; + if (itemHelper.canRate(item) && options.enableRatingButton !== false) { + html += ''; } } + + if (options.moreButton !== false) { + html += ''; + } } html += '
'; diff --git a/src/components/loading/loading.js b/src/components/loading/loading.js index d6c00347c70..9a9aa3ca7d3 100644 --- a/src/components/loading/loading.js +++ b/src/components/loading/loading.js @@ -19,7 +19,7 @@ define(['components/loading/loadingLegacy', 'browser', 'css!./loading'], functio if (!elem) { - elem = document.createElement("div"); + elem = document.createElement('div'); loadingElem = elem; elem.classList.add('docspinner'); diff --git a/src/components/loading/loadingLegacy.js b/src/components/loading/loadingLegacy.js index 757cea0160c..d766a4aca4c 100644 --- a/src/components/loading/loadingLegacy.js +++ b/src/components/loading/loadingLegacy.js @@ -7,7 +7,7 @@ define(['require', 'css!./loadingLegacy'], function (require) { show: function () { var elem = loadingElem; if (!elem) { - elem = document.createElement("img"); + elem = document.createElement('img'); elem.src = require.toUrl('.').split('?')[0] + '/loader.gif'; loadingElem = elem; diff --git a/src/components/loadingdialog/loadingdialog.js b/src/components/loadingdialog/loadingdialog.js deleted file mode 100644 index bd6a80fd5ba..00000000000 --- a/src/components/loadingdialog/loadingdialog.js +++ /dev/null @@ -1,92 +0,0 @@ -define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) { - 'use strict'; - - function showDialog(instance, options, template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - var enableTvLayout = layoutManager.tv; - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - var configuredButtons = []; - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.classList.add('align-items-center'); - dlg.classList.add('justify-items-center'); - - var formDialogContent = dlg.querySelector('.formDialogContent'); - formDialogContent.style['flex-grow'] = 'initial'; - formDialogContent.style['max-width'] = '50%'; - formDialogContent.style['max-height'] = '60%'; - - if (enableTvLayout) { - scrollHelper.centerFocus.on(formDialogContent, false); - dlg.querySelector('.formDialogHeader').style.marginTop = '15%'; - } else { - dlg.classList.add('dialog-fullscreen-lowres'); - } - - //dlg.querySelector('.btnCancel').addEventListener('click', function (e) { - // dialogHelper.close(dlg); - //}); - - dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title; - - dlg.querySelector('.text').innerHTML = options.text; - - instance.dlg = dlg; - - return dialogHelper.open(dlg).then(function () { - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - loading.hide(); - }); - } - - function LoadingDialog(options) { - this.options = options; - } - - LoadingDialog.prototype.show = function () { - var instance = this; - loading.show(); - - return new Promise(function (resolve, reject) { - require(['text!./../dialog/dialog.template.html'], function (template) { - showDialog(instance, instance.options, template); - resolve(); - }); - }); - }; - - LoadingDialog.prototype.setTitle = function (title) { - }; - - LoadingDialog.prototype.setText = function (text) { - }; - - LoadingDialog.prototype.hide = function () { - if (this.dlg) { - dialogHelper.close(this.dlg); - this.dlg = null; - } - }; - - LoadingDialog.prototype.destroy = function () { - this.dlg = null; - this.options = null; - }; - - return LoadingDialog; -}); diff --git a/src/components/logoscreensaver/plugin.js b/src/components/logoscreensaver/plugin.js deleted file mode 100644 index 7716bbf6e56..00000000000 --- a/src/components/logoscreensaver/plugin.js +++ /dev/null @@ -1,192 +0,0 @@ -define(["pluginManager"], function (pluginManager) { - - return function () { - - var self = this; - - self.name = "Logo ScreenSaver"; - self.type = "screensaver"; - self.id = "logoscreensaver"; - self.supportsAnonymous = true; - - var interval; - - function animate() { - - var animations = [ - - bounceInLeft, - bounceInRight, - swing, - tada, - wobble, - rotateIn, - rotateOut - ]; - - var elem = document.querySelector(".logoScreenSaverImage"); - - if (elem && elem.animate) { - var random = getRandomInt(0, animations.length - 1); - - animations[random](elem, 1); - } - } - - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - function bounceInLeft(elem, iterations) { - var keyframes = [ - { transform: "translate3d(-3000px, 0, 0)", opacity: "0", offset: 0 }, - { transform: "translate3d(25px, 0, 0)", opacity: "1", offset: 0.6 }, - { transform: "translate3d(-100px, 0, 0)", offset: 0.75 }, - { transform: "translate3d(5px, 0, 0)", offset: 0.9 }, - { transform: "none", opacity: "1", offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: "cubic-bezier(0.215, 0.610, 0.355, 1.000)" }; - return elem.animate(keyframes, timing); - } - - function bounceInRight(elem, iterations) { - var keyframes = [ - { transform: "translate3d(3000px, 0, 0)", opacity: "0", offset: 0 }, - { transform: "translate3d(-25px, 0, 0)", opacity: "1", offset: 0.6 }, - { transform: "translate3d(100px, 0, 0)", offset: 0.75 }, - { transform: "translate3d(-5px, 0, 0)", offset: 0.9 }, - { transform: "none", opacity: "1", offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: "cubic-bezier(0.215, 0.610, 0.355, 1.000)" }; - return elem.animate(keyframes, timing); - } - - function shake(elem, iterations) { - var keyframes = [ - { transform: "translate3d(0, 0, 0)", offset: 0 }, - { transform: "translate3d(-10px, 0, 0)", offset: 0.1 }, - { transform: "translate3d(10px, 0, 0)", offset: 0.2 }, - { transform: "translate3d(-10px, 0, 0)", offset: 0.3 }, - { transform: "translate3d(10px, 0, 0)", offset: 0.4 }, - { transform: "translate3d(-10px, 0, 0)", offset: 0.5 }, - { transform: "translate3d(10px, 0, 0)", offset: 0.6 }, - { transform: "translate3d(-10px, 0, 0)", offset: 0.7 }, - { transform: "translate3d(10px, 0, 0)", offset: 0.8 }, - { transform: "translate3d(-10px, 0, 0)", offset: 0.9 }, - { transform: "translate3d(0, 0, 0)", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function swing(elem, iterations) { - var keyframes = [ - { transform: "translate(0%)", offset: 0 }, - { transform: "rotate3d(0, 0, 1, 15deg)", offset: 0.2 }, - { transform: "rotate3d(0, 0, 1, -10deg)", offset: 0.4 }, - { transform: "rotate3d(0, 0, 1, 5deg)", offset: 0.6 }, - { transform: "rotate3d(0, 0, 1, -5deg)", offset: 0.8 }, - { transform: "rotate3d(0, 0, 1, 0deg)", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function tada(elem, iterations) { - var keyframes = [ - { transform: "scale3d(1, 1, 1)", offset: 0 }, - { transform: "scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)", offset: 0.1 }, - { transform: "scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)", offset: 0.2 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)", offset: 0.3 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)", offset: 0.4 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)", offset: 0.5 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)", offset: 0.6 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)", offset: 0.7 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)", offset: 0.8 }, - { transform: "scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)", offset: 0.9 }, - { transform: "scale3d(1, 1, 1)", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function wobble(elem, iterations) { - var keyframes = [ - { transform: "translate(0%)", offset: 0 }, - { transform: "translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)", offset: 0.15 }, - { transform: "translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)", offset: 0.45 }, - { transform: "translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)", offset: 0.6 }, - { transform: "translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)", offset: 0.75 }, - { transform: "translateX(0%)", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateIn(elem, iterations) { - var transformOrigin = elem.style["transform-origin"]; - var keyframes = [{ transform: "rotate3d(0, 0, 1, -200deg)", opacity: "0", transformOrigin: "center", offset: 0 }, - { transform: "none", opacity: "1", transformOrigin: "center", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateOut(elem, iterations) { - var transformOrigin = elem.style["transform-origin"]; - var keyframes = [{ transform: "none", opacity: "1", transformOrigin: "center", offset: 0 }, - { transform: "rotate3d(0, 0, 1, 200deg)", opacity: "0", transformOrigin: "center", offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - - } - - function fadeOut(elem, iterations) { - var keyframes = [ - { opacity: "1", offset: 0 }, - { opacity: "0", offset: 1 }]; - var timing = { duration: 400, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function stopInterval() { - if (interval) { - clearInterval(interval); - interval = null; - } - } - - self.show = function () { - - require(["css!" + pluginManager.mapPath(self, "style.css")], function () { - - var elem = document.querySelector(".logoScreenSaver"); - - if (!elem) { - elem = document.createElement("div"); - elem.classList.add("logoScreenSaver"); - document.body.appendChild(elem); - - elem.innerHTML = ''; - } - - stopInterval(); - interval = setInterval(animate, 3000); - }); - }; - - self.hide = function () { - - stopInterval(); - - var elem = document.querySelector(".logoScreenSaver"); - - if (elem) { - - var onAnimationFinish = function () { - elem.parentNode.removeChild(elem); - }; - - if (elem.animate) { - var animation = fadeOut(elem, 1); - animation.onfinish = onAnimationFinish; - } else { - onAnimationFinish(); - } - } - }; - } -}); diff --git a/src/components/maintabsmanager.js b/src/components/maintabsmanager.js index 1d331939f33..f751f40d72d 100644 --- a/src/components/maintabsmanager.js +++ b/src/components/maintabsmanager.js @@ -166,6 +166,7 @@ define(['dom', 'browser', 'events', 'emby-tabs', 'emby-button'], function (dom, }).join('') + '
'; tabsContainerElem.innerHTML = tabsHtml; + window.CustomElements.upgradeSubtree(tabsContainerElem); document.body.classList.add('withSectionTabs'); tabOwnerView = view; diff --git a/src/components/medialibrarycreator/medialibrarycreator.js b/src/components/mediaLibraryCreator/mediaLibraryCreator.js similarity index 56% rename from src/components/medialibrarycreator/medialibrarycreator.js rename to src/components/mediaLibraryCreator/mediaLibraryCreator.js index 0a8741387f0..bbef6e1f07a 100644 --- a/src/components/medialibrarycreator/medialibrarycreator.js +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.js @@ -1,5 +1,5 @@ -define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionseditor/libraryoptionseditor", "emby-toggle", "emby-input", "emby-select", "paper-icon-button-light", "listViewStyle", "formDialogStyle", "emby-button", "flexStyles"], function (loading, dialogHelper, dom, $, libraryoptionseditor) { - "use strict"; +define(['loading', 'dialogHelper', 'dom', 'jQuery', 'components/libraryoptionseditor/libraryoptionseditor', 'globalize', 'emby-toggle', 'emby-input', 'emby-select', 'paper-icon-button-light', 'listViewStyle', 'formDialogStyle', 'emby-button', 'flexStyles'], function (loading, dialogHelper, dom, $, libraryoptionseditor, globalize) { + 'use strict'; function onAddLibrary() { if (isCreating) { @@ -7,10 +7,10 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed } if (pathInfos.length == 0) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert({ - text: Globalize.translate("PleaseAddAtLeastOneFolder"), - type: "error" + text: globalize.translate('PleaseAddAtLeastOneFolder'), + type: 'error' }); }); @@ -19,15 +19,15 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed isCreating = true; loading.show(); - var dlg = dom.parentWithClass(this, "dlg-librarycreator"); - var name = $("#txtValue", dlg).val(); - var type = $("#selectCollectionType", dlg).val(); + var dlg = dom.parentWithClass(this, 'dlg-librarycreator'); + var name = $('#txtValue', dlg).val(); + var type = $('#selectCollectionType', dlg).val(); - if (type == "mixed") { + if (type == 'mixed') { type = null; } - var libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector(".libraryOptions")); + var libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); libraryOptions.PathInfos = pathInfos; ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(function () { hasChanges = true; @@ -35,8 +35,8 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed loading.hide(); dialogHelper.close(dlg); }, function () { - require(["toast"], function (toast) { - toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder")); + require(['toast'], function (toast) { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); }); isCreating = false; @@ -47,50 +47,50 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed function getCollectionTypeOptionsHtml(collectionTypeOptions) { return collectionTypeOptions.map(function (i) { - return '"; - }).join(""); + return ''; + }).join(''); } function initEditor(page, collectionTypeOptions) { - $("#selectCollectionType", page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val("").on("change", function () { + $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () { var value = this.value; - var dlg = $(this).parents(".dialog")[0]; - libraryoptionseditor.setContentType(dlg.querySelector(".libraryOptions"), value == "mixed" ? "" : value); + var dlg = $(this).parents('.dialog')[0]; + libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value == 'mixed' ? '' : value); if (value) { - dlg.querySelector(".libraryOptions").classList.remove("hide"); + dlg.querySelector('.libraryOptions').classList.remove('hide'); } else { - dlg.querySelector(".libraryOptions").classList.add("hide"); + dlg.querySelector('.libraryOptions').classList.add('hide'); } - if (value != "mixed") { + if (value != 'mixed') { var index = this.selectedIndex; if (index != -1) { - var name = this.options[index].innerHTML.replace("*", "").replace("&", "&"); - $("#txtValue", dlg).val(name); + var name = this.options[index].innerHTML.replace('*', '').replace('&', '&'); + $('#txtValue', dlg).val(name); var folderOption = collectionTypeOptions.filter(function (i) { return i.value == value; })[0]; - $(".collectionTypeFieldDescription", dlg).html(folderOption.message || ""); + $('.collectionTypeFieldDescription', dlg).html(folderOption.message || ''); } } }); - page.querySelector(".btnAddFolder").addEventListener("click", onAddButtonClick); - page.querySelector(".btnSubmit").addEventListener("click", onAddLibrary); - page.querySelector(".folderList").addEventListener("click", onRemoveClick); - page.querySelector(".chkAdvanced").addEventListener("change", onToggleAdvancedChange); + page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + page.querySelector('.btnSubmit').addEventListener('click', onAddLibrary); + page.querySelector('.folderList').addEventListener('click', onRemoveClick); + page.querySelector('.chkAdvanced').addEventListener('change', onToggleAdvancedChange); } function onToggleAdvancedChange() { - var dlg = dom.parentWithClass(this, "dlg-librarycreator"); - libraryoptionseditor.setAdvancedVisible(dlg.querySelector(".libraryOptions"), this.checked); + var dlg = dom.parentWithClass(this, 'dlg-librarycreator'); + libraryoptionseditor.setAdvancedVisible(dlg.querySelector('.libraryOptions'), this.checked); } function onAddButtonClick() { - var page = dom.parentWithClass(this, "dlg-librarycreator"); + var page = dom.parentWithClass(this, 'dlg-librarycreator'); - require(["directorybrowser"], function (directoryBrowser) { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ enableNetworkSharePath: true, @@ -106,30 +106,30 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed } function getFolderHtml(pathInfo, index) { - var html = ""; + var html = ''; html += '
'; - html += '
'; - html += '
' + pathInfo.Path + "
"; + html += '
'; + html += '
' + pathInfo.Path + '
'; if (pathInfo.NetworkPath) { - html += '
' + pathInfo.NetworkPath + "
"; + html += '
' + pathInfo.NetworkPath + '
'; } - html += "
"; - html += ''; - html += "
"; + html += '
'; + html += ''; + html += '
'; return html; } function renderPaths(page) { - var foldersHtml = pathInfos.map(getFolderHtml).join(""); - var folderList = page.querySelector(".folderList"); + var foldersHtml = pathInfos.map(getFolderHtml).join(''); + var folderList = page.querySelector('.folderList'); folderList.innerHTML = foldersHtml; if (foldersHtml) { - folderList.classList.remove("hide"); + folderList.classList.remove('hide'); } else { - folderList.classList.add("hide"); + folderList.classList.add('hide'); } } @@ -154,14 +154,14 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed } function onRemoveClick(e) { - var button = dom.parentWithClass(e.target, "btnRemovePath"); - var index = parseInt(button.getAttribute("data-index")); + var button = dom.parentWithClass(e.target, 'btnRemovePath'); + var index = parseInt(button.getAttribute('data-index')); var location = pathInfos[index].Path; var locationLower = location.toLowerCase(); pathInfos = pathInfos.filter(function (p) { return p.Path.toLowerCase() != locationLower; }); - renderPaths(dom.parentWithClass(button, "dlg-librarycreator")); + renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); } function onDialogClosed() { @@ -169,9 +169,9 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed } function initLibraryOptions(dlg) { - libraryoptionseditor.embed(dlg.querySelector(".libraryOptions")).then(function () { - $("#selectCollectionType", dlg).trigger("change"); - onToggleAdvancedChange.call(dlg.querySelector(".chkAdvanced")); + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(function () { + $('#selectCollectionType', dlg).trigger('change'); + onToggleAdvancedChange.call(dlg.querySelector('.chkAdvanced')); }); } @@ -182,25 +182,25 @@ define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionsed currentResolve = resolve; hasChanges = false; var xhr = new XMLHttpRequest(); - xhr.open("GET", "components/medialibrarycreator/medialibrarycreator.template.html", true); + xhr.open('GET', 'components/mediaLibraryCreator/mediaLibraryCreator.template.html', true); xhr.onload = function (e) { var template = this.response; var dlg = dialogHelper.createDialog({ - size: "medium-tall", + size: 'small', modal: false, removeOnClose: true, scrollY: false }); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - dlg.classList.add("dlg-librarycreator"); - dlg.classList.add("formDialog"); - dlg.innerHTML = Globalize.translateDocument(template); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('dlg-librarycreator'); + dlg.classList.add('formDialog'); + dlg.innerHTML = globalize.translateDocument(template); initEditor(dlg, options.collectionTypeOptions); - dlg.addEventListener("close", onDialogClosed); + dlg.addEventListener('close', onDialogClosed); dialogHelper.open(dlg); - dlg.querySelector(".btnCancel").addEventListener("click", function () { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); pathInfos = []; diff --git a/src/components/medialibrarycreator/medialibrarycreator.template.html b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html similarity index 92% rename from src/components/medialibrarycreator/medialibrarycreator.template.html rename to src/components/mediaLibraryCreator/mediaLibraryCreator.template.html index 32c6b254b7c..f92a63e403f 100644 --- a/src/components/medialibrarycreator/medialibrarycreator.template.html +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html @@ -1,5 +1,5 @@
- +

${ButtonAddMediaLibrary}

@@ -26,7 +26,7 @@

${ButtonAddMediaLibrary}

${HeadersFolders}

diff --git a/src/components/medialibraryeditor/medialibraryeditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js similarity index 64% rename from src/components/medialibraryeditor/medialibraryeditor.js rename to src/components/mediaLibraryEditor/mediaLibraryEditor.js index 7c5c8d40800..554cf4cc0f0 100644 --- a/src/components/medialibraryeditor/medialibraryeditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -1,5 +1,5 @@ -define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionseditor/libraryoptionseditor", "emby-button", "listViewStyle", "paper-icon-button-light", "formDialogStyle", "emby-toggle", "flexStyles"], function (jQuery, loading, dialogHelper, dom, libraryoptionseditor) { - "use strict"; +define(['jQuery', 'loading', 'dialogHelper', 'dom', 'components/libraryoptionseditor/libraryoptionseditor', 'globalize', 'emby-button', 'listViewStyle', 'paper-icon-button-light', 'formDialogStyle', 'emby-toggle', 'flexStyles'], function (jQuery, loading, dialogHelper, dom, libraryoptionseditor, globalize) { + 'use strict'; function onEditLibrary() { if (isCreating) { @@ -8,8 +8,8 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed isCreating = true; loading.show(); - var dlg = dom.parentWithClass(this, "dlg-libraryeditor"); - var libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector(".libraryOptions")); + var dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); + var libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions); ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(function () { hasChanges = true; @@ -30,8 +30,8 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed hasChanges = true; refreshLibraryFromServer(page); }, function () { - require(["toast"], function (toast) { - toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder")); + require(['toast'], function (toast) { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); }); }); } @@ -45,8 +45,8 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed hasChanges = true; refreshLibraryFromServer(page); }, function () { - require(["toast"], function (toast) { - toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder")); + require(['toast'], function (toast) { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); }); }); } @@ -55,20 +55,20 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed var button = btnRemovePath; var virtualFolder = currentOptions.library; - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ - title: Globalize.translate("HeaderRemoveMediaLocation"), - text: Globalize.translate("MessageConfirmRemoveMediaLocation"), - confirmText: Globalize.translate("ButtonDelete"), - primary: "delete" + title: globalize.translate('HeaderRemoveMediaLocation'), + text: globalize.translate('MessageConfirmRemoveMediaLocation'), + confirmText: globalize.translate('ButtonDelete'), + primary: 'delete' }).then(function () { var refreshAfterChange = currentOptions.refresh; ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(function () { hasChanges = true; - refreshLibraryFromServer(dom.parentWithClass(button, "dlg-libraryeditor")); + refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); }, function () { - require(["toast"], function (toast) { - toast(Globalize.translate("DefaultErrorMessage")); + require(['toast'], function (toast) { + toast(globalize.translate('DefaultErrorMessage')); }); }); }); @@ -76,39 +76,39 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed } function onListItemClick(e) { - var listItem = dom.parentWithClass(e.target, "listItem"); + var listItem = dom.parentWithClass(e.target, 'listItem'); if (listItem) { - var index = parseInt(listItem.getAttribute("data-index")); + var index = parseInt(listItem.getAttribute('data-index')); var pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; var pathInfo = null == index ? {} : pathInfos[index] || {}; var originalPath = pathInfo.Path || (null == index ? null : currentOptions.library.Locations[index]); - var btnRemovePath = dom.parentWithClass(e.target, "btnRemovePath"); + var btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); if (btnRemovePath) { onRemoveClick(btnRemovePath, originalPath); return; } - showDirectoryBrowser(dom.parentWithClass(listItem, "dlg-libraryeditor"), originalPath, pathInfo.NetworkPath); + showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath); } } function getFolderHtml(pathInfo, index) { - var html = ""; + var html = ''; html += '
'; - html += '
'; + html += '
'; html += '

'; html += pathInfo.Path; - html += "

"; + html += ''; if (pathInfo.NetworkPath) { - html += '
' + pathInfo.NetworkPath + "
"; + html += '
' + pathInfo.NetworkPath + '
'; } - html += "
"; - html += ''; - html += "
"; + html += '
'; + html += ''; + html += '
'; return html; } @@ -137,20 +137,20 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed } if (options.library.CollectionType === 'boxsets') { - page.querySelector(".folders").classList.add("hide"); + page.querySelector('.folders').classList.add('hide'); } else { - page.querySelector(".folders").classList.remove("hide"); + page.querySelector('.folders').classList.remove('hide'); } - page.querySelector(".folderList").innerHTML = pathInfos.map(getFolderHtml).join(""); + page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join(''); } function onAddButtonClick() { - showDirectoryBrowser(dom.parentWithClass(this, "dlg-libraryeditor")); + showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor')); } function showDirectoryBrowser(context, originalPath, networkPath) { - require(["directorybrowser"], function (directoryBrowser) { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ enableNetworkSharePath: true, @@ -173,18 +173,18 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed } function onToggleAdvancedChange() { - var dlg = dom.parentWithClass(this, "dlg-libraryeditor"); - libraryoptionseditor.setAdvancedVisible(dlg.querySelector(".libraryOptions"), this.checked); + var dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); + libraryoptionseditor.setAdvancedVisible(dlg.querySelector('.libraryOptions'), this.checked); } function initEditor(dlg, options) { renderLibrary(dlg, options); - dlg.querySelector(".btnAddFolder").addEventListener("click", onAddButtonClick); - dlg.querySelector(".folderList").addEventListener("click", onListItemClick); - dlg.querySelector(".chkAdvanced").addEventListener("change", onToggleAdvancedChange); - dlg.querySelector(".btnSubmit").addEventListener("click", onEditLibrary); - libraryoptionseditor.embed(dlg.querySelector(".libraryOptions"), options.library.CollectionType, options.library.LibraryOptions).then(function () { - onToggleAdvancedChange.call(dlg.querySelector(".chkAdvanced")); + dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + dlg.querySelector('.folderList').addEventListener('click', onListItemClick); + dlg.querySelector('.chkAdvanced').addEventListener('change', onToggleAdvancedChange); + dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions).then(function () { + onToggleAdvancedChange.call(dlg.querySelector('.chkAdvanced')); }); } @@ -199,26 +199,26 @@ define(["jQuery", "loading", "dialogHelper", "dom", "components/libraryoptionsed currentDeferred = deferred; hasChanges = false; var xhr = new XMLHttpRequest(); - xhr.open("GET", "components/medialibraryeditor/medialibraryeditor.template.html", true); + xhr.open('GET', 'components/mediaLibraryEditor/mediaLibraryEditor.template.html', true); xhr.onload = function (e) { var template = this.response; var dlg = dialogHelper.createDialog({ - size: "medium-tall", + size: 'small', modal: false, removeOnClose: true, scrollY: false }); - dlg.classList.add("dlg-libraryeditor"); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - dlg.classList.add("formDialog"); - dlg.innerHTML = Globalize.translateDocument(template); - dlg.querySelector(".formDialogHeaderTitle").innerHTML = options.library.Name; + dlg.classList.add('dlg-libraryeditor'); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('formDialog'); + dlg.innerHTML = globalize.translateDocument(template); + dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.library.Name; initEditor(dlg, options); - dlg.addEventListener("close", onDialogClosed); + dlg.addEventListener('close', onDialogClosed); dialogHelper.open(dlg); - dlg.querySelector(".btnCancel").addEventListener("click", function () { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); refreshLibraryFromServer(dlg); diff --git a/src/components/medialibraryeditor/medialibraryeditor.template.html b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html similarity index 90% rename from src/components/medialibraryeditor/medialibraryeditor.template.html rename to src/components/mediaLibraryEditor/mediaLibraryEditor.template.html index 172c535fa17..6c814cf2dd4 100644 --- a/src/components/medialibraryeditor/medialibraryeditor.template.html +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html @@ -1,5 +1,5 @@
- +

@@ -20,7 +20,7 @@

${HeadersFolders}

diff --git a/src/components/mediainfo/mediainfo.css b/src/components/mediainfo/mediainfo.css index 1883b78726f..508c9d96adf 100644 --- a/src/components/mediainfo/mediainfo.css +++ b/src/components/mediainfo/mediainfo.css @@ -41,6 +41,8 @@ width: auto !important; height: auto !important; font-size: 1.4em; + margin-right: 0.125em; + color: #f2b01e; } .mediaInfoCriticRating { diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index 188ea9a07c9..7de11c42f78 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -6,7 +6,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater var status; if (item.Type === 'SeriesTimer') { - return ''; + return ''; } else if (item.TimerId || item.SeriesTimerId) { status = item.Status || 'Cancelled'; @@ -20,13 +20,13 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater if (item.SeriesTimerId) { if (status !== 'Cancelled') { - return ''; + return ''; } - return ''; + return ''; } - return ''; + return ''; } function getProgramInfoHtml(item, options) { @@ -57,7 +57,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater miscInfo.push(text); } catch (e) { - console.error("error parsing date: " + item.StartDate); + console.error('error parsing date: ' + item.StartDate); } } @@ -109,7 +109,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater var minutes; var count; - var showFolderRuntime = item.Type === "MusicAlbum" || item.MediaType === 'MusicArtist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; + var showFolderRuntime = item.Type === 'MusicAlbum' || item.MediaType === 'MusicArtist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; if (showFolderRuntime) { @@ -123,7 +123,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater if (item.RunTimeTicks) { miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); } - } else if (item.Type === "PhotoAlbum" || item.Type === "BoxSet") { + } else if (item.Type === 'PhotoAlbum' || item.Type === 'BoxSet') { count = item.ChildCount; @@ -133,7 +133,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater } } - if ((item.Type === "Episode" || item.MediaType === 'Photo') && options.originalAirDate !== false) { + if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false) { if (item.PremiereDate) { @@ -143,7 +143,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater text = datetime.toLocaleDateString(date); miscInfo.push(text); } catch (e) { - console.error("error parsing date: " + item.PremiereDate); + console.error('error parsing date: ' + item.PremiereDate); } } } @@ -171,18 +171,18 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater text = datetime.toLocaleDateString(date); miscInfo.push(text); - if (item.Type !== "Recording") { + if (item.Type !== 'Recording') { text = datetime.getDisplayTime(date); miscInfo.push(text); } } catch (e) { - console.error("error parsing date: " + item.StartDate); + console.error('error parsing date: ' + item.StartDate); } } - if (options.year !== false && item.ProductionYear && item.Type === "Series") { + if (options.year !== false && item.ProductionYear && item.Type === 'Series') { - if (item.Status === "Continuing") { + if (item.Status === 'Continuing') { miscInfo.push(globalize.translate('SeriesYearToPresent', item.ProductionYear)); } else if (item.ProductionYear) { @@ -196,11 +196,11 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater var endYear = datetime.parseISO8601Date(item.EndDate).getFullYear(); if (endYear !== item.ProductionYear) { - text += "-" + datetime.parseISO8601Date(item.EndDate).getFullYear(); + text += '-' + datetime.parseISO8601Date(item.EndDate).getFullYear(); } } catch (e) { - console.error("error parsing date: " + item.EndDate); + console.error('error parsing date: ' + item.EndDate); } } @@ -248,7 +248,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); miscInfo.push(text); } catch (e) { - console.error("error parsing date: " + item.PremiereDate); + console.error('error parsing date: ' + item.PremiereDate); } } else if (item.ProductionYear) { miscInfo.push(item.ProductionYear); @@ -256,7 +256,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater } if (options.year !== false) { - if (item.Type !== "Series" && item.Type !== "Episode" && item.Type !== "Person" && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season') { + if (item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season') { if (item.ProductionYear) { @@ -267,15 +267,15 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater text = datetime.parseISO8601Date(item.PremiereDate).getFullYear(); miscInfo.push(text); } catch (e) { - console.error("error parsing date: " + item.PremiereDate); + console.error('error parsing date: ' + item.PremiereDate); } } } } - if (item.RunTimeTicks && item.Type !== "Series" && item.Type !== 'Program' && !showFolderRuntime && options.runtime !== false) { + if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { - if (item.Type === "Audio") { + if (item.Type === 'Audio') { miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); @@ -284,11 +284,11 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater minutes = minutes || 1; - miscInfo.push(Math.round(minutes) + " mins"); + miscInfo.push(Math.round(minutes) + ' mins'); } } - if (item.OfficialRating && item.Type !== "Season" && item.Type !== "Episode") { + if (item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { miscInfo.push({ text: item.OfficialRating, cssClass: 'mediaInfoOfficialRating' @@ -296,11 +296,11 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater } if (item.Video3DFormat) { - miscInfo.push("3D"); + miscInfo.push('3D'); } if (item.MediaType === 'Photo' && item.Width && item.Height) { - miscInfo.push(item.Width + "x" + item.Height); + miscInfo.push(item.Width + 'x' + item.Height); } if (options.container !== false && item.Type === 'Audio' && item.Container) { @@ -390,7 +390,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater if (item.CommunityRating) { html += '
'; - html += 'star'; + html += ''; html += item.CommunityRating.toFixed(1); html += '
'; } @@ -490,26 +490,26 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater if (i.IsInterlaced) { return '1440i'; } - return '1440P'; + return '1440p'; } if (width >= 1800 || height >= 1000) { if (i.IsInterlaced) { return '1080i'; } - return '1080P'; + return '1080p'; } if (width >= 1200 || height >= 700) { if (i.IsInterlaced) { return '720i'; } - return '720P'; + return '720p'; } if (width >= 700 || height >= 400) { if (i.IsInterlaced) { return '480i'; } - return '480P'; + return '480p'; } } diff --git a/src/components/metadataeditor/metadataeditor.js b/src/components/metadataEditor/metadataEditor.js similarity index 84% rename from src/components/metadataeditor/metadataeditor.js rename to src/components/metadataEditor/metadataEditor.js index e8736258faa..61a4ba7f240 100644 --- a/src/components/metadataeditor/metadataeditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -142,9 +142,9 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi 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) { + Genres: getListValues(form.querySelector('#listGenres')), + Tags: getListValues(form.querySelector('#listTags')), + Studios: getListValues(form.querySelector('#listStudios')).map(function (element) { return { Name: element }; }), @@ -158,7 +158,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi OfficialRating: form.querySelector('#selectOfficialRating').value, CustomRating: form.querySelector('#selectCustomRating').value, People: currentItem.People, - LockData: form.querySelector("#chkLockData").checked, + LockData: form.querySelector('#chkLockData').checked, LockedFields: Array.prototype.filter.call(form.querySelectorAll('.selectLockedField'), function (c) { return !c.checked; }).map(function (c) { @@ -177,14 +177,14 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi item.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; - if (currentItem.Type === "Person") { + if (currentItem.Type === 'Person') { var placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; } - if (currentItem.Type === "Series") { + if (currentItem.Type === 'Series') { // 600000000 var seriesRuntime = form.querySelector('#txtSeriesRuntime').value; @@ -245,50 +245,6 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi }); } - function showMoreMenu(context, button, user) { - - require(['itemContextMenu'], function (itemContextMenu) { - - var 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 afterDeleted(context, item) { - - var parentId = item.ParentId || item.SeasonId || item.SeriesId; - - if (parentId) { - reload(context, parentId, item.ServerId); - } else { - require(['appRouter'], function (appRouter) { - appRouter.goHome(); - }); - } - } - function onEditorClick(e) { var btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); @@ -307,6 +263,12 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi return connectionManager.getApiClient(currentItem.ServerId); } + function bindAll(elems, eventName, fn) { + for (var i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener(eventName, fn); + } + } + function init(context, apiClient) { context.querySelector('.externalIds').addEventListener('click', function (e) { @@ -322,19 +284,16 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi } }); - context.querySelector('.btnCancel').addEventListener('click', function () { + if (!layoutManager.desktop) { + context.querySelector('.btnBack').classList.remove('hide'); + context.querySelector('.btnClose').classList.add('hide'); + } + bindAll(context.querySelectorAll('.btnCancel'), 'click', function (event) { + event.preventDefault(); closeDialog(false); }); - context.querySelector('.btnMore').addEventListener('click', function (e) { - - getApiClient().getCurrentUser().then(function (user) { - showMoreMenu(context, e.target, user); - }); - - }); - context.querySelector('.btnHeaderSave').addEventListener('click', function (e) { context.querySelector('.btnSave').click(); @@ -356,7 +315,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi form.removeEventListener('submit', onSubmit); form.addEventListener('submit', onSubmit); - context.querySelector("#btnAddPerson").addEventListener('click', function (event, data) { + context.querySelector('#btnAddPerson').addEventListener('click', function (event, data) { editPerson(context, {}, -1); }); @@ -403,7 +362,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi function populateCountries(select, allCountries) { - var html = ""; + var html = ''; html += ""; @@ -411,7 +370,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi var culture = allCountries[i]; - html += ""; + html += "'; } select.innerHTML = html; @@ -419,7 +378,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi function populateLanguages(select, languages) { - var html = ""; + var html = ''; html += ""; @@ -427,7 +386,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi var culture = languages[i]; - html += ""; + html += "'; } select.innerHTML = html; @@ -462,10 +421,15 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi var idInfo = externalIds[i]; - var id = "txt1" + idInfo.Key; + var id = 'txt1' + idInfo.Key; var formatString = idInfo.UrlFormatString || ''; - var labelText = globalize.translate('LabelDynamicExternalId').replace('{0}', idInfo.Name); + var fullName = idInfo.Name; + if (idInfo.Type) { + fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + } + + var labelText = globalize.translate('LabelDynamicExternalId', fullName); html += '
'; html += '
'; @@ -477,7 +441,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi html += '
'; if (formatString) { - html += ''; + html += ''; } html += '
'; @@ -539,37 +503,37 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi hideElement('#fldPath', context); } - if (item.Type === "Series" || item.Type === "Movie" || item.Type === "Trailer") { + if (item.Type === 'Series' || item.Type === 'Movie' || item.Type === 'Trailer') { showElement('#fldOriginalName', context); } else { hideElement('#fldOriginalName', context); } - if (item.Type === "Series") { + if (item.Type === 'Series') { showElement('#fldSeriesRuntime', context); } else { hideElement('#fldSeriesRuntime', context); } - if (item.Type === "Series" || item.Type === "Person") { + if (item.Type === 'Series' || item.Type === 'Person') { showElement('#fldEndDate', context); } else { hideElement('#fldEndDate', context); } - if (item.Type === "MusicAlbum") { + if (item.Type === 'MusicAlbum') { showElement('#albumAssociationMessage', context); } else { hideElement('#albumAssociationMessage', context); } - if (item.Type === "Movie" || item.Type === "Trailer") { + if (item.Type === 'Movie' || item.Type === 'Trailer') { showElement('#fldCriticRating', context); } else { hideElement('#fldCriticRating', context); } - if (item.Type === "Series") { + if (item.Type === 'Series') { showElement('#fldStatus', context); showElement('#fldAirDays', context); showElement('#fldAirTime', context); @@ -579,19 +543,19 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi hideElement('#fldAirTime', context); } - if (item.MediaType === "Video" && item.Type !== "TvChannel") { + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { showElement('#fld3dFormat', context); } else { hideElement('#fld3dFormat', context); } - if (item.Type === "Audio") { + if (item.Type === 'Audio') { showElement('#fldAlbumArtist', context); } else { hideElement('#fldAlbumArtist', context); } - if (item.Type === "Audio" || item.Type === "MusicVideo") { + if (item.Type === 'Audio' || item.Type === 'MusicVideo') { showElement('#fldArtist', context); showElement('#fldAlbum', context); } else { @@ -599,29 +563,29 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi hideElement('#fldAlbum', context); } - if (item.Type === "Episode" && item.ParentIndexNumber === 0) { + if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { showElement('#collapsibleSpecialEpisodeInfo', context); } else { hideElement('#collapsibleSpecialEpisodeInfo', context); } - if (item.Type === "Person" || - item.Type === "Genre" || - item.Type === "Studio" || - item.Type === "MusicGenre" || - item.Type === "TvChannel" || - item.Type === "Book") { + 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") { + 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") { + if (item.Type === 'TvChannel') { showElement('#fldOfficialRating', context); } else { hideElement('#fldOfficialRating', context); @@ -637,7 +601,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi showElement('#tagsCollapsible', context); - if (item.Type === "TvChannel") { + if (item.Type === 'TvChannel') { hideElement('#metadataSettingsCollapsible', context); hideElement('#fldPremiereDate', context); hideElement('#fldDateAdded', context); @@ -649,39 +613,39 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi showElement('#fldYear', context); } - if (item.Type === "TvChannel") { + if (item.Type === 'TvChannel') { hideElement('.overviewContainer', context); } else { showElement('.overviewContainer', context); } - if (item.Type === "Person") { + if (item.Type === 'Person') { //todo context.querySelector('#txtProductionYear').label(globalize.translate('LabelBirthYear')); - context.querySelector("#txtPremiereDate").label(globalize.translate('LabelBirthDate')); - context.querySelector("#txtEndDate").label(globalize.translate('LabelDeathDate')); + 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')); + context.querySelector('#txtPremiereDate').label(globalize.translate('LabelReleaseDate')); + context.querySelector('#txtEndDate').label(globalize.translate('LabelEndDate')); hideElement('#fldPlaceOfBirth'); } - if (item.MediaType === "Video" && item.Type !== "TvChannel") { + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { showElement('#fldOriginalAspectRatio'); } else { hideElement('#fldOriginalAspectRatio'); } - if (item.Type === "Audio" || item.Type === "Episode" || item.Type === "Season") { + if (item.Type === 'Audio' || item.Type === 'Episode' || item.Type === 'Season') { showElement('#fldIndexNumber'); - if (item.Type === "Episode") { + if (item.Type === 'Episode') { context.querySelector('#txtIndexNumber').label(globalize.translate('LabelEpisodeNumber')); - } else if (item.Type === "Season") { + } else if (item.Type === 'Season') { context.querySelector('#txtIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === "Audio") { + } else if (item.Type === 'Audio') { context.querySelector('#txtIndexNumber').label(globalize.translate('LabelTrackNumber')); } else { context.querySelector('#txtIndexNumber').label(globalize.translate('LabelNumber')); @@ -690,12 +654,12 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi hideElement('#fldIndexNumber'); } - if (item.Type === "Audio" || item.Type === "Episode") { + if (item.Type === 'Audio' || item.Type === 'Episode') { showElement('#fldParentIndexNumber'); - if (item.Type === "Episode") { + if (item.Type === 'Episode') { context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === "Audio") { + } else if (item.Type === 'Audio') { context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelDiscNumber')); } else { context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelParentNumber')); @@ -704,12 +668,12 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi hideElement('#fldParentIndexNumber', context); } - if (item.Type === "BoxSet") { + if (item.Type === 'BoxSet') { showElement('#fldDisplayOrder', context); hideElement('.seriesDisplayOrderDescription', context); context.querySelector('#selectDisplayOrder').innerHTML = ''; - } else if (item.Type === "Series") { + } else if (item.Type === 'Series') { showElement('#fldDisplayOrder', context); showElement('.seriesDisplayOrderDescription', context); @@ -726,19 +690,19 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi populateRatings(parentalRatingOptions, select, item.OfficialRating); - select.value = item.OfficialRating || ""; + select.value = item.OfficialRating || ''; select = context.querySelector('#selectCustomRating'); populateRatings(parentalRatingOptions, select, item.CustomRating); - select.value = item.CustomRating || ""; + select.value = item.CustomRating || ''; var selectStatus = context.querySelector('#selectStatus'); populateStatus(selectStatus); - selectStatus.value = item.Status || ""; + selectStatus.value = item.Status || ''; - context.querySelector('#select3dFormat', context).value = item.Video3DFormat || ""; + 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; @@ -754,7 +718,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi populateListView(context.querySelector('#listTags'), item.Tags); var lockData = (item.LockData || false); - var chkLockData = context.querySelector("#chkLockData"); + var chkLockData = context.querySelector('#chkLockData'); chkLockData.checked = lockData; if (chkLockData.checked) { hideElement('.providerSettingsContainer', context); @@ -764,33 +728,29 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi 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('#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('#txtSortName').value = item.ForcedSortName || ''; + context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; - context.querySelector('#txtCriticRating').value = item.CriticRating || ""; + 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('#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('#txtAlbum').value = item.Album || ''; context.querySelector('#txtAlbumArtist').value = (item.AlbumArtists || []).map(function (a) { return a.Name; }).join(';'); - if (item.Type === 'Series') { - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - } else { - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - } + context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { return a.Name; @@ -834,17 +794,17 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi context.querySelector('#txtEndDate').value = ''; } - context.querySelector('#txtProductionYear').value = item.ProductionYear || ""; + context.querySelector('#txtProductionYear').value = item.ProductionYear || ''; context.querySelector('#txtAirTime').value = item.AirTime || ''; var 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) { @@ -852,13 +812,13 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); } else { - context.querySelector('#txtSeriesRuntime', context).value = ""; + context.querySelector('#txtSeriesRuntime', context).value = ''; } } function populateRatings(allParentalRatings, select, currentValue) { - var html = ""; + var html = ''; html += ""; @@ -888,18 +848,18 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi rating = ratings[i]; - html += ""; + html += "'; } select.innerHTML = html; } function populateStatus(select) { - var html = ""; + var html = ''; html += ""; - html += ""; - html += ""; + html += "'; + html += "'; select.innerHTML = html; } @@ -917,7 +877,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi for (var i = 0; i < items.length; i++) { html += '
'; - html += ''; + html += ''; html += '
'; @@ -927,7 +887,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi html += '
'; - html += ''; + html += ''; html += '
'; } @@ -948,7 +908,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi html += '
'; - html += 'person'; + html += ''; html += '
'; html += ''; html += '
'; - html += ''; + html += ''; html += '
'; } @@ -994,30 +954,30 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi lockedFields = lockedFields || []; var 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" } + { 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" }); + if (item.Type === 'Person') { + lockedFieldsList.push({ name: globalize.translate('BirthLocation'), value: 'ProductionLocations' }); } else { - lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: "ProductionLocations" }); + lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: 'ProductionLocations' }); } - if (item.Type === "Series") { - lockedFieldsList.push({ name: globalize.translate('Runtime'), value: "Runtime" }); + 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" }); + lockedFieldsList.push({ name: globalize.translate('Studios'), value: 'Studios' }); + lockedFieldsList.push({ name: globalize.translate('Tags'), value: 'Tags' }); var html = ''; - html += "

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

"; - html += "

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

"; + html += '

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

'; + html += '

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

'; html += getLockedFieldsHtml(lockedFieldsList, lockedFields); container.innerHTML = html; } @@ -1046,7 +1006,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi setFieldVisibilities(context, item); fillItemInfo(context, item, metadataEditorInfo.ParentalRatingOptions); - if (item.MediaType === "Video" && item.Type !== "Episode" && item.Type !== "TvChannel") { + if (item.MediaType === 'Video' && item.Type !== 'Episode' && item.Type !== 'TvChannel') { showElement('#fldTagline', context); } else { hideElement('#fldTagline', context); @@ -1066,7 +1026,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi function show(itemId, serverId, resolve, reject) { loading.show(); - require(['text!./metadataeditor.template.html'], function (template) { + require(['text!./metadataEditor.template.html'], function (template) { var dialogOptions = { removeOnClose: true, @@ -1076,7 +1036,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = 'medium-tall'; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); @@ -1123,7 +1083,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi loading.show(); - require(['text!./metadataeditor.template.html'], function (template) { + require(['text!./metadataEditor.template.html'], function (template) { elem.innerHTML = globalize.translateDocument(template, 'core'); diff --git a/src/components/metadataeditor/metadataeditor.template.html b/src/components/metadataEditor/metadataEditor.template.html similarity index 76% rename from src/components/metadataeditor/metadataeditor.template.html rename to src/components/metadataEditor/metadataEditor.template.html index 8e36a824b73..4b4e2cf9659 100644 --- a/src/components/metadataeditor/metadataeditor.template.html +++ b/src/components/metadataEditor/metadataEditor.template.html @@ -1,15 +1,15 @@
- +

${Edit}

-
@@ -57,11 +57,13 @@

-
- -
-
- +
+
+ +
+
+ +
@@ -129,24 +131,28 @@

-
- -
-
- -
-
- +
+
+ +
+
+ +
-
- +
+
+ +
+
+ +
@@ -160,14 +166,16 @@

${HeaderSpecialEpisodeInfo}

-
- -
-
- -
-
- +
+
+ +
+
+ +
+
+ +
@@ -184,7 +192,7 @@

${Genres}

@@ -193,7 +201,7 @@

${People}

@@ -203,7 +211,7 @@

${Studios}

@@ -212,7 +220,7 @@

${Tags}

@@ -240,8 +248,11 @@

${HeaderMetadataSettings}


+
diff --git a/src/components/metadataeditor/personeditor.js b/src/components/metadataEditor/personEditor.js similarity index 96% rename from src/components/metadataeditor/personeditor.js rename to src/components/metadataEditor/personEditor.js index 9fb6fdec6f3..fc6a0595f24 100644 --- a/src/components/metadataeditor/personeditor.js +++ b/src/components/metadataEditor/personEditor.js @@ -11,7 +11,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'require', 'paper-icon-but function show(person) { return new Promise(function (resolve, reject) { - require(['text!./personeditor.template.html'], function (template) { + require(['text!./personEditor.template.html'], function (template) { var dialogOptions = { removeOnClose: true, @@ -21,7 +21,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'require', 'paper-icon-but if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = 'medium-tall'; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); diff --git a/src/components/metadataeditor/personeditor.template.html b/src/components/metadataEditor/personEditor.template.html similarity index 95% rename from src/components/metadataeditor/personeditor.template.html rename to src/components/metadataEditor/personEditor.template.html index ec76f58c700..d2ad6a78d28 100644 --- a/src/components/metadataeditor/personeditor.template.html +++ b/src/components/metadataEditor/personEditor.template.html @@ -1,5 +1,5 @@
- +

${Edit}

@@ -7,7 +7,6 @@

-
@@ -23,6 +22,7 @@

+
${LabelPersonRoleHelp}
@@ -33,6 +33,5 @@

${Save}

-

diff --git a/src/components/multiselect/multiselect.css b/src/components/multiSelect/multiSelect.css similarity index 100% rename from src/components/multiselect/multiselect.css rename to src/components/multiSelect/multiSelect.css diff --git a/src/components/multiselect/multiselect.js b/src/components/multiSelect/multiSelect.js similarity index 70% rename from src/components/multiselect/multiselect.js rename to src/components/multiSelect/multiSelect.js index b4fa87c8a0c..83982413aee 100644 --- a/src/components/multiselect/multiselect.js +++ b/src/components/multiSelect/multiSelect.js @@ -1,5 +1,5 @@ -define(["browser", "appStorage", "apphost", "loading", "connectionManager", "globalize", "appRouter", "dom", "css!./multiselect"], function (browser, appStorage, appHost, loading, connectionManager, globalize, appRouter, dom) { - "use strict"; +define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'globalize', 'appRouter', 'dom', 'css!./multiSelect'], function (browser, appStorage, appHost, loading, connectionManager, globalize, appRouter, dom) { + 'use strict'; var selectedItems = []; var selectedElements = []; @@ -15,12 +15,12 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo selectedItems = []; selectedElements = []; - var elems = document.querySelectorAll(".itemSelectionPanel"); + var elems = document.querySelectorAll('.itemSelectionPanel'); for (var i = 0, length = elems.length; i < length; i++) { var parent = elems[i].parentNode; parent.removeChild(elems[i]); - parent.classList.remove("withMultiSelect"); + parent.classList.remove('withMultiSelect'); } } } @@ -28,13 +28,13 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo function onItemSelectionPanelClick(e, itemSelectionPanel) { // toggle the checkbox, if it wasn't clicked on - if (!dom.parentWithClass(e.target, "chkItemSelect")) { - var chkItemSelect = itemSelectionPanel.querySelector(".chkItemSelect"); + if (!dom.parentWithClass(e.target, 'chkItemSelect')) { + var chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); if (chkItemSelect) { - if (chkItemSelect.classList.contains("checkedInitial")) { - chkItemSelect.classList.remove("checkedInitial"); + if (chkItemSelect.classList.contains('checkedInitial')) { + chkItemSelect.classList.remove('checkedInitial'); } else { var newValue = !chkItemSelect.checked; chkItemSelect.checked = newValue; @@ -50,7 +50,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo function updateItemSelection(chkItemSelect, selected) { - var id = dom.parentWithAttribute(chkItemSelect, "data-id").getAttribute("data-id"); + var id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); if (selected) { @@ -73,7 +73,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo } if (selectedItems.length) { - var itemSelectionCount = document.querySelector(".itemSelectionCount"); + var itemSelectionCount = document.querySelector('.itemSelectionCount'); if (itemSelectionCount) { itemSelectionCount.innerHTML = selectedItems.length; } @@ -88,27 +88,27 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo function showSelection(item, isChecked) { - var itemSelectionPanel = item.querySelector(".itemSelectionPanel"); + var itemSelectionPanel = item.querySelector('.itemSelectionPanel'); if (!itemSelectionPanel) { - itemSelectionPanel = document.createElement("div"); - itemSelectionPanel.classList.add("itemSelectionPanel"); + itemSelectionPanel = document.createElement('div'); + itemSelectionPanel.classList.add('itemSelectionPanel'); - var parent = item.querySelector(".cardBox") || item.querySelector(".cardContent"); - parent.classList.add("withMultiSelect"); + var parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); + parent.classList.add('withMultiSelect'); parent.appendChild(itemSelectionPanel); - var cssClass = "chkItemSelect"; + var 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"; + cssClass += ' checkedInitial'; } - var checkedAttribute = isChecked ? " checked" : ""; + var checkedAttribute = isChecked ? ' checked' : ''; itemSelectionPanel.innerHTML = ''; - var chkItemSelect = itemSelectionPanel.querySelector(".chkItemSelect"); - chkItemSelect.addEventListener("change", onSelectionChange); + var chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + chkItemSelect.addEventListener('change', onSelectionChange); } } @@ -118,27 +118,27 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo if (!selectionCommandsPanel) { - selectionCommandsPanel = document.createElement("div"); - selectionCommandsPanel.classList.add("selectionCommandsPanel"); + selectionCommandsPanel = document.createElement('div'); + selectionCommandsPanel.classList.add('selectionCommandsPanel'); document.body.appendChild(selectionCommandsPanel); currentSelectionCommandsPanel = selectionCommandsPanel; - var html = ""; + var html = ''; - html += ''; + html += ''; html += '

'; - var moreIcon = ""; - html += ''; + const moreIcon = 'more_vert'; + html += ''; selectionCommandsPanel.innerHTML = html; - selectionCommandsPanel.querySelector(".btnCloseSelectionPanel").addEventListener("click", hideSelections); + selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); - var btnSelectionPanelOptions = selectionCommandsPanel.querySelector(".btnSelectionPanelOptions"); + var btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); - dom.addEventListener(btnSelectionPanelOptions, "click", showMenuForSelectedItems, { passive: true }); + dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); } } @@ -146,7 +146,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo return new Promise(function (resolve, reject) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert(options).then(resolve, resolve); }); }); @@ -156,15 +156,15 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo return new Promise(function (resolve, reject) { - var msg = globalize.translate("ConfirmDeleteItem"); - var title = globalize.translate("HeaderDeleteItem"); + var msg = globalize.translate('ConfirmDeleteItem'); + var title = globalize.translate('HeaderDeleteItem'); if (itemIds.length > 1) { - msg = globalize.translate("ConfirmDeleteItems"); - title = globalize.translate("HeaderDeleteItems"); + msg = globalize.translate('ConfirmDeleteItems'); + title = globalize.translate('HeaderDeleteItems'); } - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm(msg, title).then(function () { var promises = itemIds.map(function (itemId) { @@ -173,7 +173,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo Promise.all(promises).then(resolve, function () { - alertText(globalize.translate("ErrorDeletingItem")).then(reject, reject); + alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); }); }, reject); @@ -190,58 +190,58 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo var menuItems = []; menuItems.push({ - name: globalize.translate("AddToCollection"), - id: "addtocollection", - icon: "add" + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'add' }); menuItems.push({ - name: globalize.translate("AddToPlaylist"), - id: "playlist", - icon: "playlist_add" + 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" + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' }); } - if (user.Policy.EnableContentDownloading && appHost.supports("filedownload")) { + if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { menuItems.push({ - name: Globalize.translate("ButtonDownload"), - id: "download", - icon: "file_download" + name: globalize.translate('ButtonDownload'), + id: 'download', + icon: 'file_download' }); } if (user.Policy.IsAdministrator) { menuItems.push({ - name: globalize.translate("GroupVersions"), - id: "groupvideos", - icon: "call_merge" + name: globalize.translate('GroupVersions'), + id: 'groupvideos', + icon: 'call_merge' }); } menuItems.push({ - name: globalize.translate("MarkPlayed"), - id: "markplayed", - icon: "check_box" + name: globalize.translate('MarkPlayed'), + id: 'markplayed', + icon: 'check_box' }); menuItems.push({ - name: globalize.translate("MarkUnplayed"), - id: "markunplayed", - icon: "check_box_outline_blank" + name: globalize.translate('MarkUnplayed'), + id: 'markunplayed', + icon: 'check_box_outline_blank' }); menuItems.push({ - name: globalize.translate("RefreshMetadata"), - id: "refresh", - icon: "refresh" + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' }); require(['actionsheet'], function (actionsheet) { @@ -253,8 +253,8 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo var serverId = apiClient.serverInfo().Id; switch (id) { - case "addtocollection": - require(["collectionEditor"], function (collectionEditor) { + case 'addtocollection': + require(['collectionEditor'], function (collectionEditor) { new collectionEditor().show({ items: items, serverId: serverId @@ -263,8 +263,8 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo hideSelections(); dispatchNeedsRefresh(); break; - case "playlist": - require(["playlistEditor"], function (playlistEditor) { + case 'playlist': + require(['playlistEditor'], function (playlistEditor) { new playlistEditor().show({ items: items, serverId: serverId @@ -273,30 +273,30 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo hideSelections(); dispatchNeedsRefresh(); break; - case "delete": + case 'delete': deleteItems(apiClient, items).then(dispatchNeedsRefresh); hideSelections(); dispatchNeedsRefresh(); break; - case "groupvideos": + case 'groupvideos': combineVersions(apiClient, items); break; - case "markplayed": + case 'markplayed': items.forEach(function (itemId) { apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); }); hideSelections(); dispatchNeedsRefresh(); break; - case "markunplayed": + case 'markunplayed': items.forEach(function (itemId) { apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); }); hideSelections(); dispatchNeedsRefresh(); break; - case "refresh": - require(["refreshDialog"], function (refreshDialog) { + case 'refresh': + require(['refreshDialog'], function (refreshDialog) { new refreshDialog({ itemIds: items, serverId: serverId @@ -321,7 +321,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo [].forEach.call(selectedElements, function (i) { - var container = dom.parentWithAttribute(i, "is", "emby-itemscontainer"); + var container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); if (container && elems.indexOf(container) === -1) { elems.push(container); @@ -337,9 +337,9 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo if (selection.length < 2) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert({ - text: globalize.translate("PleaseSelectTwoItems") + text: globalize.translate('PleaseSelectTwoItems') }); }); return; @@ -349,8 +349,8 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo apiClient.ajax({ - type: "POST", - url: apiClient.getUrl("Videos/MergeVersions", { Ids: selection.join(",") }) + type: 'POST', + url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) }).then(function () { @@ -362,8 +362,8 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo function showSelections(initialCard) { - require(["emby-checkbox"], function () { - var cards = document.querySelectorAll(".card"); + require(['emby-checkbox'], function () { + var cards = document.querySelectorAll('.card'); for (var i = 0, length = cards.length; i < length; i++) { showSelection(cards[i], initialCard === cards[i]); } @@ -379,9 +379,9 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo if (selectedItems.length) { - var card = dom.parentWithClass(target, "card"); + var card = dom.parentWithClass(target, 'card'); if (card) { - var itemSelectionPanel = card.querySelector(".itemSelectionPanel"); + var itemSelectionPanel = card.querySelector('.itemSelectionPanel'); if (itemSelectionPanel) { return onItemSelectionPanelClick(e, itemSelectionPanel); } @@ -393,7 +393,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo } } - document.addEventListener("viewbeforehide", hideSelections); + document.addEventListener('viewbeforehide', hideSelections); return function (options) { @@ -403,7 +403,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo function onTapHold(e) { - var card = dom.parentWithClass(e.target, "card"); + var card = dom.parentWithClass(e.target, 'card'); if (card) { @@ -440,7 +440,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo var element = touch.target; if (element) { - var card = dom.parentWithClass(element, "card"); + var card = dom.parentWithClass(element, 'card'); if (card) { @@ -509,7 +509,7 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo return; } - var card = dom.parentWithClass(touchTarget, "card"); + var card = dom.parentWithClass(touchTarget, 'card'); touchTarget = null; if (card) { @@ -522,27 +522,27 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo // mobile safari doesn't allow contextmenu override if (browser.touch && !browser.safari) { - element.addEventListener("contextmenu", onTapHold); + element.addEventListener('contextmenu', onTapHold); } else { - dom.addEventListener(element, "touchstart", onTouchStart, { + dom.addEventListener(element, 'touchstart', onTouchStart, { passive: true }); - dom.addEventListener(element, "touchmove", onTouchMove, { + dom.addEventListener(element, 'touchmove', onTouchMove, { passive: true }); - dom.addEventListener(element, "touchend", onTouchEnd, { + dom.addEventListener(element, 'touchend', onTouchEnd, { passive: true }); - dom.addEventListener(element, "touchcancel", onTouchEnd, { + dom.addEventListener(element, 'touchcancel', onTouchEnd, { passive: true }); - dom.addEventListener(element, "mousedown", onMouseDown, { + dom.addEventListener(element, 'mousedown', onMouseDown, { passive: true }); - dom.addEventListener(element, "mouseleave", onMouseOut, { + dom.addEventListener(element, 'mouseleave', onMouseOut, { passive: true }); - dom.addEventListener(element, "mouseup", onMouseOut, { + dom.addEventListener(element, 'mouseup', onMouseOut, { passive: true }); } @@ -551,38 +551,38 @@ define(["browser", "appStorage", "apphost", "loading", "connectionManager", "glo initTapHold(container); if (options.bindOnClick !== false) { - container.addEventListener("click", onContainerClick); + container.addEventListener('click', onContainerClick); } self.onContainerClick = onContainerClick; self.destroy = function () { - container.removeEventListener("click", onContainerClick); - container.removeEventListener("contextmenu", onTapHold); + container.removeEventListener('click', onContainerClick); + container.removeEventListener('contextmenu', onTapHold); var element = container; - dom.removeEventListener(element, "touchstart", onTouchStart, { + dom.removeEventListener(element, 'touchstart', onTouchStart, { passive: true }); - dom.removeEventListener(element, "touchmove", onTouchMove, { + dom.removeEventListener(element, 'touchmove', onTouchMove, { passive: true }); - dom.removeEventListener(element, "touchend", onTouchEnd, { + dom.removeEventListener(element, 'touchend', onTouchEnd, { passive: true }); // this fires in safari due to magnifying class //dom.removeEventListener(element, "touchcancel", onTouchEnd, { // passive: true //}); - dom.removeEventListener(element, "mousedown", onMouseDown, { + dom.removeEventListener(element, 'mousedown', onMouseDown, { passive: true }); - dom.removeEventListener(element, "mouseleave", onMouseOut, { + dom.removeEventListener(element, 'mouseleave', onMouseOut, { passive: true }); - dom.removeEventListener(element, "mouseup", onMouseOut, { + dom.removeEventListener(element, 'mouseup', onMouseOut, { passive: true }); }; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 2c3e45b6306..c8480e4f151 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -5,7 +5,9 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir document.removeEventListener('click', onOneDocumentClick); document.removeEventListener('keydown', onOneDocumentClick); - if (window.Notification) { + // don't request notification permissions if they're already granted or denied + if (window.Notification && window.Notification.permission === 'default') { + /* eslint-disable-next-line compat/compat */ Notification.requestPermission(); } } @@ -26,6 +28,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } function resetRegistration() { + /* eslint-disable-next-line compat/compat */ var serviceWorker = navigator.serviceWorker; if (serviceWorker) { serviceWorker.ready.then(function (registration) { @@ -43,7 +46,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir function showNonPersistentNotification(title, options, timeoutMs) { try { - var notif = new Notification(title, options); + var notif = new Notification(title, options); /* eslint-disable-line compat/compat */ if (notif.show) { notif.show(); @@ -94,10 +97,10 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } var notification = { - title: "New " + item.Type, + title: 'New ' + item.Type, body: body, vibrate: true, - tag: "newItem" + item.Id, + tag: 'newItem' + item.Id, data: { //options: { // url: LibraryBrowser.getHref(item) @@ -112,7 +115,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir notification.icon = apiClient.getScaledImageUrl(item.Id, { width: 80, tag: imageTags.Primary, - type: "Primary" + type: 'Primary' }); } @@ -136,11 +139,11 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir Recursive: true, Limit: 3, - Filters: "IsNotFolder", - SortBy: "DateCreated", - SortOrder: "Descending", + Filters: 'IsNotFolder', + SortBy: 'DateCreated', + SortOrder: 'Descending', Ids: newItems.join(','), - MediaTypes: "Audio,Video", + MediaTypes: 'Audio,Video', EnableTotalRecordCount: false }).then(function (result) { @@ -168,20 +171,20 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } var notification = { - tag: "install" + installation.Id, + tag: 'install' + installation.Id, data: {} }; if (status === 'completed') { - notification.title = globalize.translate('PackageInstallCompleted').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); notification.vibrate = true; } else if (status === 'cancelled') { - notification.title = globalize.translate('PackageInstallCancelled').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); } else if (status === 'failed') { - notification.title = globalize.translate('PackageInstallFailed').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); notification.vibrate = true; } else if (status === 'progress') { - notification.title = globalize.translate('InstallingPackage').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); notification.actions = [ @@ -213,25 +216,25 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir }); events.on(serverNotifications, 'PackageInstallationCompleted', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, "completed"); + showPackageInstallNotification(apiClient, data, 'completed'); }); events.on(serverNotifications, 'PackageInstallationFailed', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, "failed"); + showPackageInstallNotification(apiClient, data, 'failed'); }); events.on(serverNotifications, 'PackageInstallationCancelled', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, "cancelled"); + showPackageInstallNotification(apiClient, data, 'cancelled'); }); events.on(serverNotifications, 'PackageInstalling', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, "progress"); + showPackageInstallNotification(apiClient, data, 'progress'); }); events.on(serverNotifications, 'ServerShuttingDown', function (e, apiClient, data) { var serverId = apiClient.serverInfo().Id; var notification = { - tag: "restart" + serverId, + tag: 'restart' + serverId, title: globalize.translate('ServerNameIsShuttingDown', apiClient.serverInfo().Name) }; showNotification(notification, 0, apiClient); @@ -240,7 +243,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir events.on(serverNotifications, 'ServerRestarting', function (e, apiClient, data) { var serverId = apiClient.serverInfo().Id; var notification = { - tag: "restart" + serverId, + tag: 'restart' + serverId, title: globalize.translate('ServerNameIsRestarting', apiClient.serverInfo().Name) }; showNotification(notification, 0, apiClient); @@ -250,7 +253,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir var serverId = apiClient.serverInfo().Id; var notification = { - tag: "restart" + serverId, + tag: 'restart' + serverId, title: globalize.translate('PleaseRestartServerName', apiClient.serverInfo().Name) }; diff --git a/src/components/nowplayingbar/nowplayingbar.css b/src/components/nowPlayingBar/nowPlayingBar.css similarity index 82% rename from src/components/nowplayingbar/nowplayingbar.css rename to src/components/nowPlayingBar/nowPlayingBar.css index b1e77715ff7..a884f657694 100644 --- a/src/components/nowplayingbar/nowplayingbar.css +++ b/src/components/nowPlayingBar/nowPlayingBar.css @@ -56,8 +56,8 @@ text-align: left; flex-grow: 1; font-size: 92%; - margin-right: 2.4em; - margin-left: 1em; + margin-right: 1em; + margin-left: 0.5em; } .nowPlayingBarCenter { @@ -114,8 +114,6 @@ .nowPlayingBarUserDataButtons { display: inline-block; - margin-left: 1em; - margin-right: 1em; } .nowPlayingBarPositionSlider::-webkit-slider-thumb { @@ -133,33 +131,50 @@ .toggleRepeatButton { display: none !important; } -} -@media all and (max-width: 62em) { - .nowPlayingBarCenter .nowPlayingBarCurrentTime { + .nowPlayingBar .btnShuffleQueue { display: none !important; } } -@media all and (max-width: 56em) { - .nowPlayingBarCenter { +@media all and (max-width: 80em) { + .nowPlayingBarCenter .nowPlayingBarCurrentTime, + .nowPlayingBarCenter .stopButton { display: none !important; } + + .nowPlayingBarInfoContainer { + width: 45%; + } } -@media all and (min-width: 56em) { - .nowPlayingBarRight .playPauseButton { - display: none; +.layout-mobile .nowPlayingBarRight button:not(.playPauseButton, .nextTrackButton) { + display: none; +} + +.layout-desktop .nowPlayingBarRight .playPauseButton, +.layout-tv .nowPlayingBarRight .playPauseButton { + display: none; +} + +.layout-mobile .nowPlayingBarRight input, +.layout-mobile .nowPlayingBarRight div { + display: none; +} + +@media all and (max-width: 56em) { + .nowPlayingBarCenter { + display: none !important; } } -@media all and (max-width: 36em) { +@media all and (max-width: 60em) { .nowPlayingBarRight .nowPlayingBarVolumeSliderContainer { display: none !important; } .nowPlayingBarInfoContainer { - width: 70%; + width: 100%; } } diff --git a/src/components/nowplayingbar/nowplayingbar.js b/src/components/nowPlayingBar/nowPlayingBar.js similarity index 69% rename from src/components/nowplayingbar/nowplayingbar.js rename to src/components/nowPlayingBar/nowPlayingBar.js index 8da9b9c0538..95c4372f25b 100644 --- a/src/components/nowplayingbar/nowplayingbar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -1,4 +1,4 @@ -define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 'layoutManager', 'playbackManager', 'nowPlayingHelper', 'apphost', 'dom', 'connectionManager', 'paper-icon-button-light', 'emby-ratingbutton'], function (require, datetime, itemHelper, events, browser, imageLoader, layoutManager, playbackManager, nowPlayingHelper, appHost, dom, connectionManager) { +define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 'layoutManager', 'playbackManager', 'nowPlayingHelper', 'apphost', 'dom', 'connectionManager', 'itemContextMenu', 'paper-icon-button-light', 'emby-ratingbutton'], function (require, datetime, itemHelper, events, browser, imageLoader, layoutManager, playbackManager, nowPlayingHelper, appHost, dom, connectionManager, itemContextMenu) { 'use strict'; var currentPlayer; @@ -42,31 +42,38 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', // The onclicks are needed due to the return false above html += '
'; - html += ''; + html += ''; - html += ''; + html += ''; - html += ''; - html += ''; + html += ''; + if (!layoutManager.mobile) { + html += ''; + } html += '
'; html += '
'; html += '
'; - html += ''; + html += ''; html += '
'; html += ''; html += '
'; - html += ''; + html += ''; + html += ''; html += '
'; html += '
'; - html += ''; - html += ''; + html += ''; + if (layoutManager.mobile) { + html += ''; + } else { + html += ''; + } html += '
'; html += '
'; @@ -117,8 +124,13 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 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) { @@ -134,12 +146,9 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } }); - var i; - var length; - playPauseButtons = elem.querySelectorAll('.playPauseButton'); - for (i = 0, length = playPauseButtons.length; i < length; i++) { - playPauseButtons[i].addEventListener('click', onPlayPauseClick); - } + playPauseButtons.forEach((button) => { + button.addEventListener('click', onPlayPauseClick); + }); elem.querySelector('.nextTrackButton').addEventListener('click', function () { @@ -148,70 +157,59 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } }); - elem.querySelector('.previousTrackButton').addEventListener('click', function () { + 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; + } + 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); + } + } + }); + elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () { if (currentPlayer) { playbackManager.previousTrack(currentPlayer); } }); - elem.querySelector('.remoteControlButton').addEventListener('click', showRemoteControl); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(); + } + }); toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton.addEventListener('click', function () { - - if (currentPlayer) { - - switch (playbackManager.getRepeatMode(currentPlayer)) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne', currentPlayer); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone', currentPlayer); - break; - default: - playbackManager.setRepeatMode('RepeatAll', currentPlayer); - break; - } + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); } }); - toggleRepeatButtonIcon = toggleRepeatButton.querySelector('i'); - - volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); - volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); + toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); - if (appHost.supports('physicalvolumecontrol')) { - volumeSliderContainer.classList.add('hide'); - } else { - volumeSliderContainer.classList.remove('hide'); - } - - var volumeSliderTimer; - - function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; + volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); + volumeSlider.addEventListener('input', (e) => { if (currentPlayer) { - currentPlayer.setVolume(this.value); - } - } - - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); + currentPlayer.setVolume(e.target.value); } - } - - volumeSlider.addEventListener('change', setVolume); - volumeSlider.addEventListener('mousemove', setVolumeDelayed); - volumeSlider.addEventListener('touchmove', setVolumeDelayed); + }); - positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); positionSlider.addEventListener('change', function () { if (currentPlayer) { @@ -240,8 +238,8 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', elem.addEventListener('click', function (e) { - if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT', 'A'])) { - showRemoteControl(0); + if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { + showRemoteControl(); } }); } @@ -262,7 +260,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', return new Promise(function (resolve, reject) { - require(['appFooter-shared', 'itemShortcuts', 'css!./nowplayingbar.css', 'emby-slider'], function (appfooter, itemShortcuts) { + require(['appFooter-shared', 'itemShortcuts', 'css!./nowPlayingBar.css', 'emby-slider'], function (appfooter, itemShortcuts) { var parentContainer = appfooter.element; nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); @@ -275,6 +273,11 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); 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'); @@ -297,22 +300,12 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } function updatePlayPauseState(isPaused) { - var i; - var length; - if (playPauseButtons) { - if (isPaused) { - - for (i = 0, length = playPauseButtons.length; i < length; i++) { - playPauseButtons[i].querySelector('i').innerHTML = ''; - } - - } else { - - for (i = 0, length = playPauseButtons.length; i < length; i++) { - playPauseButtons[i].querySelector('i').innerHTML = 'pause'; - } - } + playPauseButtons.forEach((button) => { + const icon = button.querySelector('.material-icons'); + icon.classList.remove('play_arrow', 'pause'); + icon.classList.add(isPaused ? 'play_arrow' : 'pause'); + }); } } @@ -337,7 +330,8 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', toggleRepeatButton.classList.remove('hide'); } - updateRepeatModeDisplay(playState.RepeatMode); + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onQueueShuffleModeChange(); updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); @@ -356,32 +350,35 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } function updateRepeatModeDisplay(repeatMode) { - - if (repeatMode === 'RepeatAll') { - toggleRepeatButtonIcon.innerHTML = "repeat"; - toggleRepeatButton.classList.add('repeatButton-active'); - } else if (repeatMode === 'RepeatOne') { - toggleRepeatButtonIcon.innerHTML = "repeat_one"; - toggleRepeatButton.classList.add('repeatButton-active'); - } else { - toggleRepeatButtonIcon.innerHTML = "repeat"; - toggleRepeatButton.classList.remove('repeatButton-active'); + 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) { - var pct = positionTicks / runtimeTicks; pct *= 100; positionSlider.value = pct; - } else { - positionSlider.value = 0; } } @@ -391,11 +388,9 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } if (currentTimeElement) { - var timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - if (runtimeTicks) { - timeText += " / " + datetime.getDisplayRunningTime(runtimeTicks); + timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); } currentTimeElement.innerHTML = timeText; @@ -413,11 +408,9 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', showMuteButton = false; } - if (isMuted) { - muteButton.querySelector('i').innerHTML = ''; - } else { - muteButton.querySelector('i').innerHTML = ''; - } + 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; @@ -437,11 +430,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', // See bindEvents for why this is necessary if (volumeSlider) { - if (showVolumeSlider) { - volumeSliderContainer.classList.remove('hide'); - } else { - volumeSliderContainer.classList.add('hide'); - } + volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); if (!volumeSlider.dragging) { volumeSlider.value = volumeLevel || 0; @@ -449,19 +438,6 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } } - function getTextActionButton(item, text, serverId) { - - if (!text) { - text = itemHelper.getDisplayName(item); - } - - var html = ''; - - return html; - } - function seriesImageUrl(item, options) { if (!item) { @@ -473,7 +449,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (options.type === 'Primary') { @@ -511,7 +487,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { @@ -534,21 +510,28 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', var nowPlayingItem = state.NowPlayingItem; var textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; - if (textLines.length > 1) { - textLines[1].secondary = true; - } - var serverId = nowPlayingItem ? nowPlayingItem.ServerId : null; - nowPlayingTextElement.innerHTML = textLines.map(function (nowPlayingName) { - - var cssClass = nowPlayingName.secondary ? ' class="nowPlayingBarSecondaryText"' : ''; - - if (nowPlayingName.item) { - return '' + getTextActionButton(nowPlayingName.item, nowPlayingName.text, serverId) + '

'; + nowPlayingTextElement.innerHTML = ''; + if (textLines) { + let itemText = document.createElement('div'); + let secondaryText = document.createElement('div'); + secondaryText.classList.add('nowPlayingBarSecondaryText'); + if (textLines.length > 1) { + textLines[1].secondary = true; + if (textLines[1].text) { + let text = document.createElement('a'); + text.innerHTML = textLines[1].text; + secondaryText.appendChild(text); + } } - return '' + nowPlayingName.text + '
'; - - }).join(''); + if (textLines[0].text) { + let text = document.createElement('a'); + text.innerHTML = textLines[0].text; + itemText.appendChild(text); + } + nowPlayingTextElement.appendChild(itemText); + nowPlayingTextElement.appendChild(secondaryText); + } var imgHeight = 70; @@ -566,8 +549,12 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', if (url) { imageLoader.lazyImage(nowPlayingImageElement, url); + nowPlayingImageElement.style.display = null; + nowPlayingTextElement.style.marginLeft = null; } else { nowPlayingImageElement.style.backgroundImage = ''; + nowPlayingImageElement.style.display = 'none'; + nowPlayingTextElement.style.marginLeft = '1em'; } } @@ -575,15 +562,32 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', if (isRefreshing) { var apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { - var userData = item.UserData || {}; var likes = userData.Likes == null ? '' : userData.Likes; - - nowPlayingUserData.innerHTML = ''; + if (!layoutManager.mobile) { + let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + let contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + let options = { + play: false, + queue: false, + 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 = ''; @@ -592,25 +596,39 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', function onPlaybackStart(e, state) { console.debug('nowplaying event: ' + e.type); - var player = this; - onStateChanged.call(player, e, state); } - function onRepeatModeChange(e) { - + function onRepeatModeChange() { if (!isEnabled) { return; } - var player = this; + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + } - updateRepeatModeDisplay(playbackManager.getRepeatMode(player)); + function onQueueShuffleModeChange() { + if (!isEnabled) { + return; + } + + let shuffleMode = playbackManager.getQueueShuffleMode(); + let context = nowPlayingBarElement; + const cssClass = 'buttonActive'; + let 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; @@ -714,6 +732,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 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); @@ -762,6 +781,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 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); diff --git a/src/components/packagemanager.js b/src/components/packageManager.js similarity index 100% rename from src/components/packagemanager.js rename to src/components/packageManager.js diff --git a/src/components/photoplayer/plugin.js b/src/components/photoplayer/plugin.js deleted file mode 100644 index 4ebdbbd6d2b..00000000000 --- a/src/components/photoplayer/plugin.js +++ /dev/null @@ -1,46 +0,0 @@ -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager) { - "use strict"; - - function PhotoPlayer() { - - var self = this; - - self.name = 'Photo Player'; - self.type = 'mediaplayer'; - self.id = 'photoplayer'; - - // Let any players created by plugins take priority - self.priority = 1; - } - - PhotoPlayer.prototype.play = function (options) { - - return new Promise(function (resolve, reject) { - - require(['slideshow'], function (slideshow) { - - var index = options.startIndex || 0; - - var newSlideShow = new slideshow({ - showTitle: false, - cover: false, - items: options.items, - startIndex: index, - interval: 11000, - interactive: true - }); - - newSlideShow.show(); - - resolve(); - }); - }); - }; - - PhotoPlayer.prototype.canPlayMediaType = function (mediaType) { - - return (mediaType || '').toLowerCase() === 'photo'; - }; - - return PhotoPlayer; -}); diff --git a/src/components/playback/brightnessosd.js b/src/components/playback/brightnessosd.js index e73fc39689e..5f3becd64bc 100644 --- a/src/components/playback/brightnessosd.js +++ b/src/components/playback/brightnessosd.js @@ -1,171 +1,171 @@ -define(['events', 'playbackManager', 'dom', 'browser', 'css!./iconosd', 'material-icons'], function (events, playbackManager, dom, browser) { - 'use strict'; +import events from 'events'; +import playbackManager from 'playbackManager'; +import dom from 'dom'; +import browser from 'browser'; +import 'css!./iconosd'; +import 'material-icons'; - var currentPlayer; - var osdElement; - var iconElement; - var progressElement; +var currentPlayer; +var osdElement; +var iconElement; +var progressElement; - var enableAnimation; +var enableAnimation; - function getOsdElementHtml() { - var html = ''; +function getOsdElementHtml() { + var html = ''; - html += ''; + html += ''; - html += '
'; + html += '
'; - return html; - } + return html; +} - function ensureOsdElement() { +function ensureOsdElement() { - var elem = osdElement; - if (!elem) { + var elem = osdElement; + if (!elem) { - enableAnimation = browser.supportsCssAnimation(); + enableAnimation = browser.supportsCssAnimation(); - elem = document.createElement('div'); - elem.classList.add('hide'); - elem.classList.add('iconOsd'); - elem.classList.add('iconOsd-hidden'); - elem.classList.add('brightnessOsd'); - elem.innerHTML = getOsdElementHtml(); + elem = document.createElement('div'); + elem.classList.add('hide'); + elem.classList.add('iconOsd'); + elem.classList.add('iconOsd-hidden'); + elem.classList.add('brightnessOsd'); + elem.innerHTML = getOsdElementHtml(); - iconElement = elem.querySelector('i'); - progressElement = elem.querySelector('.iconOsdProgressInner'); + iconElement = elem.querySelector('.material-icons'); + progressElement = elem.querySelector('.iconOsdProgressInner'); - document.body.appendChild(elem); - osdElement = elem; - } + document.body.appendChild(elem); + osdElement = elem; } +} - function onHideComplete() { - this.classList.add('hide'); - } +function onHideComplete() { + this.classList.add('hide'); +} - var hideTimeout; - function showOsd() { +var hideTimeout; +function showOsd() { - clearHideTimeout(); + clearHideTimeout(); - var elem = osdElement; + var elem = osdElement; - dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { - once: true - }); + dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { + once: true + }); - elem.classList.remove('hide'); + elem.classList.remove('hide'); - // trigger reflow - void elem.offsetWidth; + // trigger reflow + void elem.offsetWidth; - requestAnimationFrame(function () { - elem.classList.remove('iconOsd-hidden'); + requestAnimationFrame(function () { + elem.classList.remove('iconOsd-hidden'); - hideTimeout = setTimeout(hideOsd, 3000); - }); - } + hideTimeout = setTimeout(hideOsd, 3000); + }); +} - function clearHideTimeout() { - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } +function clearHideTimeout() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; } +} - function hideOsd() { +function hideOsd() { - clearHideTimeout(); + clearHideTimeout(); - var elem = osdElement; - if (elem) { + var elem = osdElement; + if (elem) { - if (enableAnimation) { - // trigger reflow - void elem.offsetWidth; + if (enableAnimation) { + // trigger reflow + void elem.offsetWidth; - requestAnimationFrame(function () { - elem.classList.add('iconOsd-hidden'); + requestAnimationFrame(function () { + elem.classList.add('iconOsd-hidden'); - dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { - once: true - }); + dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { + once: true }); - } else { - onHideComplete.call(elem); - } + }); + } else { + onHideComplete.call(elem); } } - - function setIcon(iconElement, icon) { - iconElement.classList.remove('brightness_high'); - iconElement.classList.remove('brightness_medium'); - iconElement.classList.remove('brightness_low'); - iconElement.classList.add(icon); - } - - function updateElementsFromPlayer(brightness) { - - if (iconElement) { - if (brightness >= 80) { - setIcon(iconElement, 'brightness_high'); - } else if (brightness >= 20) { - setIcon(iconElement, 'brightness_medium'); - } else { - setIcon(iconElement, 'brightness_low'); - } - } - if (progressElement) { - progressElement.style.width = (brightness || 0) + '%'; +} + +function setIcon(iconElement, icon) { + iconElement.classList.remove('brightness_high', 'brightness_medium', 'brightness_low'); + iconElement.classList.add(icon); +} + +function updateElementsFromPlayer(brightness) { + + if (iconElement) { + if (brightness >= 80) { + setIcon(iconElement, 'brightness_high'); + } else if (brightness >= 20) { + setIcon(iconElement, 'brightness_medium'); + } else { + setIcon(iconElement, 'brightness_low'); } } + if (progressElement) { + progressElement.style.width = (brightness || 0) + '%'; + } +} - function releaseCurrentPlayer() { +function releaseCurrentPlayer() { - var player = currentPlayer; + var player = currentPlayer; - if (player) { - events.off(player, 'brightnesschange', onBrightnessChanged); - events.off(player, 'playbackstop', hideOsd); - currentPlayer = null; - } + if (player) { + events.off(player, 'brightnesschange', onBrightnessChanged); + events.off(player, 'playbackstop', hideOsd); + currentPlayer = null; } +} - function onBrightnessChanged(e) { +function onBrightnessChanged(e) { - var player = this; + var player = this; - ensureOsdElement(); + ensureOsdElement(); - updateElementsFromPlayer(playbackManager.getBrightness(player)); + updateElementsFromPlayer(playbackManager.getBrightness(player)); - showOsd(); - } - - function bindToPlayer(player) { + showOsd(); +} - if (player === currentPlayer) { - return; - } +function bindToPlayer(player) { - releaseCurrentPlayer(); + if (player === currentPlayer) { + return; + } - currentPlayer = player; + releaseCurrentPlayer(); - if (!player) { - return; - } + currentPlayer = player; - hideOsd(); - events.on(player, 'brightnesschange', onBrightnessChanged); - events.on(player, 'playbackstop', hideOsd); + if (!player) { + return; } - events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); - }); + hideOsd(); + events.on(player, 'brightnesschange', onBrightnessChanged); + events.on(player, 'playbackstop', hideOsd); +} +events.on(playbackManager, 'playerchange', function () { bindToPlayer(playbackManager.getCurrentPlayer()); - }); + +bindToPlayer(playbackManager.getCurrentPlayer()); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index c03420c85ac..0f275c88f7e 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -1,45 +1,28 @@ -define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], function (playbackManager, nowPlayingHelper, events, connectionManager) { - "use strict"; - - // no support for mediaSession - if (!navigator.mediaSession && !window.NativeShell) { - return; - } +import playbackManager from 'playbackManager'; +import nowPlayingHelper from 'nowPlayingHelper'; +import events from 'events'; +import connectionManager from 'connectionManager'; +/* eslint-disable indent */ // Reports media playback to the device for lock screen control - var currentPlayer; - var lastUpdateTime = 0; + let currentPlayer; - function seriesImageUrl(item, options) { + function seriesImageUrl(item, options = {}) { + options.type = options.type || 'Primary'; if (item.Type !== 'Episode') { return null; - } - - options = options || {}; - options.type = options.type || "Primary"; - - if (options.type === 'Primary') { - - if (item.SeriesPrimaryImageTag) { - - options.tag = item.SeriesPrimaryImageTag; - - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - } - - if (options.type === 'Thumb') { + } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } else if (options.type === 'Thumb') { if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - if (item.ParentThumbImageTag) { - + } else if (item.ParentThumbImageTag) { options.tag = item.ParentThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); @@ -49,122 +32,102 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f return null; } - function imageUrl(item, options) { - - options = options || {}; - options.type = options.type || "Primary"; + function imageUrl(item, options = {}) { + options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } return null; } - function pushImageUrl(item, imageOptions, list) { - var url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); + function getImageUrl(item, imageOptions = {}) { + const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); if (url) { - var height = imageOptions.height || imageOptions.maxHeight; + const height = imageOptions.height || imageOptions.maxHeight; - list.push({ + return { src: url, sizes: height + 'x' + height - }); + }; + } else { + return null; } } - function getImageUrls(item) { - - var list = []; + function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) { + const list = []; - pushImageUrl(item, {height: 96}, list); - pushImageUrl(item, {height: 128}, list); - pushImageUrl(item, {height: 192}, list); - pushImageUrl(item, {height: 256}, list); - pushImageUrl(item, {height: 384}, list); - pushImageUrl(item, {height: 512}, 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') { + 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; } - var item = state.NowPlayingItem; + const item = state.NowPlayingItem; if (!item) { hideMediaControls(); return; } - if (eventName == 'init') { // transform "init" event into "timeupdate" to restraint update rate + if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate eventName = 'timeupdate'; } - var isVideo = item.MediaType === 'Video'; - var isLocalPlayer = player.isLocalPlayer || false; + const isVideo = item.MediaType === 'Video'; + const isLocalPlayer = player.isLocalPlayer || false; // Local players do their own notifications if (isLocalPlayer && isVideo) { return; } - var playState = state.PlayState || {}; - var parts = nowPlayingHelper.getNowPlayingNames(item); - var artist = parts[parts.length - 1].text; - var title = parts.length === 1 ? '' : parts[0].text; - var albumArtist; - - if (item.AlbumArtists && item.AlbumArtists[0]) { - albumArtist = item.AlbumArtists[0].Name; - } + const playState = state.PlayState || {}; + const parts = nowPlayingHelper.getNowPlayingNames(item); + const artist = parts[parts.length - 1].text; + const title = parts.length === 1 ? '' : parts[0].text; - var album = item.Album || ''; - var itemId = item.Id; + const album = item.Album || ''; + const itemId = item.Id; // Convert to ms - var duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); - var currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0); + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0); - var isPaused = playState.IsPaused || false; - var canSeek = playState.CanSeek || false; + const isPaused = playState.IsPaused || false; + const canSeek = playState.CanSeek || false; - if (navigator.mediaSession) { + if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.metadata = new MediaMetadata({ title: title, artist: artist, album: album, - artwork: getImageUrls(item), - albumArtist: albumArtist, - currentTime: currentTime, - duration: duration, - paused: isPaused, - itemId: itemId, - mediaType: item.MediaType + artwork: getImageUrls(item) }); } else { - var imageUrl = []; - pushImageUrl(item, {maxHeight: 400}, imageUrl); - - if (imageUrl.length) { - imageUrl = imageUrl[0].src; - } else { - imageUrl = null; - } + let itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); window.NativeShell.updateMediaSession({ action: eventName, @@ -175,7 +138,7 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f album: album, duration: duration, position: currentTime, - imageUrl: imageUrl, + imageUrl: itemImageUrl, canSeek: canSeek, isPaused: isPaused }); @@ -183,37 +146,25 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f } function onGeneralEvent(e) { + const state = playbackManager.getPlayerState(this); - var player = this; - var state = playbackManager.getPlayerState(player); - - updatePlayerState(player, state, e.type); + updatePlayerState(this, state, e.type); } function onStateChanged(e, state) { - - var player = this; - updatePlayerState(player, state, 'statechange'); + updatePlayerState(this, state, 'statechange'); } function onPlaybackStart(e, state) { - - var player = this; - - updatePlayerState(player, state, e.type); + updatePlayerState(this, state, e.type); } - function onPlaybackStopped(e, state) { - - var player = this; - + function onPlaybackStopped() { hideMediaControls(); } function releaseCurrentPlayer() { - if (currentPlayer) { - events.off(currentPlayer, 'playbackstart', onPlaybackStart); events.off(currentPlayer, 'playbackstop', onPlaybackStopped); events.off(currentPlayer, 'unpause', onGeneralEvent); @@ -228,9 +179,8 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f } function hideMediaControls() { - lastUpdateTime = 0; - - if (navigator.mediaSession) { + if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.metadata = null; } else { window.NativeShell.hideMediaSession(); @@ -238,7 +188,6 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f } function bindToPlayer(player) { - releaseCurrentPlayer(); if (!player) { @@ -247,7 +196,7 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f currentPlayer = player; - var state = playbackManager.getPlayerState(player); + const state = playbackManager.getPlayerState(player); updatePlayerState(player, state, 'init'); events.on(currentPlayer, 'playbackstart', onPlaybackStart); @@ -261,37 +210,52 @@ define(['playbackManager', 'nowPlayingHelper', 'events', 'connectionManager'], f function execute(name) { playbackManager[name](currentPlayer); } - if (navigator.mediaSession) { + if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('previoustrack', function () { execute('previousTrack'); }); + /* 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) { + let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + // Convert to ms + let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + let wantedTime = object.seekTime * 1000; + playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + }); } events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); }); bindToPlayer(playbackManager.getCurrentPlayer()); -}); + +/* eslint-enable indent */ diff --git a/src/components/playback/nowplayinghelper.js b/src/components/playback/nowplayinghelper.js index b1af977ab07..310edc03c3d 100644 --- a/src/components/playback/nowplayinghelper.js +++ b/src/components/playback/nowplayinghelper.js @@ -1,86 +1,82 @@ -define([], function () { - 'use strict'; +export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { + + var topItem = nowPlayingItem; + var bottomItem = null; + var topText = nowPlayingItem.Name; + + if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') { + topItem = { + Id: nowPlayingItem.AlbumId, + Name: nowPlayingItem.Album, + Type: 'MusicAlbum', + IsFolder: true + }; + } - function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { + if (nowPlayingItem.MediaType === 'Video') { + if (nowPlayingItem.IndexNumber != null) { + topText = nowPlayingItem.IndexNumber + ' - ' + topText; + } + if (nowPlayingItem.ParentIndexNumber != null) { + topText = nowPlayingItem.ParentIndexNumber + '.' + topText; + } + } - var topItem = nowPlayingItem; - var bottomItem = null; - var topText = nowPlayingItem.Name; + var bottomText = ''; - if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') { - topItem = { - Id: nowPlayingItem.AlbumId, - Name: nowPlayingItem.Album, - Type: 'MusicAlbum', - IsFolder: true - }; - } + if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) { - if (nowPlayingItem.MediaType === 'Video') { - if (nowPlayingItem.IndexNumber != null) { - topText = nowPlayingItem.IndexNumber + " - " + topText; - } - if (nowPlayingItem.ParentIndexNumber != null) { - topText = nowPlayingItem.ParentIndexNumber + "." + topText; - } - } + bottomItem = { + Id: nowPlayingItem.ArtistItems[0].Id, + Name: nowPlayingItem.ArtistItems[0].Name, + Type: 'MusicArtist', + IsFolder: true + }; + + bottomText = nowPlayingItem.ArtistItems.map(function (a) { + return a.Name; + }).join(', '); - var bottomText = ''; + } else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { - if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) { + bottomText = nowPlayingItem.Artists.join(', '); + } else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { + bottomText = topText; + topText = nowPlayingItem.SeriesName || nowPlayingItem.Album; - bottomItem = { - Id: nowPlayingItem.ArtistItems[0].Id, - Name: nowPlayingItem.ArtistItems[0].Name, - Type: 'MusicArtist', + bottomItem = topItem; + + if (nowPlayingItem.SeriesId) { + topItem = { + Id: nowPlayingItem.SeriesId, + Name: nowPlayingItem.SeriesName, + Type: 'Series', IsFolder: true }; - - bottomText = nowPlayingItem.ArtistItems.map(function (a) { - return a.Name; - }).join(', '); - - } else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { - - bottomText = nowPlayingItem.Artists.join(', '); - } else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { - bottomText = topText; - topText = nowPlayingItem.SeriesName || nowPlayingItem.Album; - - bottomItem = topItem; - - if (nowPlayingItem.SeriesId) { - topItem = { - Id: nowPlayingItem.SeriesId, - Name: nowPlayingItem.SeriesName, - Type: 'Series', - IsFolder: true - }; - } else { - topItem = null; - } - } else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) { - bottomText = nowPlayingItem.ProductionYear; + } else { + topItem = null; } + } else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) { + bottomText = nowPlayingItem.ProductionYear; + } + + var list = []; - var list = []; + list.push({ + text: topText, + item: topItem + }); + if (bottomText) { list.push({ - text: topText, - item: topItem + text: bottomText, + item: bottomItem }); - - if (bottomText) { - list.push({ - text: bottomText, - item: bottomItem - }); - } - - return list; } - return { - getNowPlayingNames: getNowPlayingNames - }; -}); + return list; +} + +export default { + getNowPlayingNames: getNowPlayingNames +}; diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 8de1ffc190c..797fc39bd2b 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1,6 +1,9 @@ -define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'fullscreenManager'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, fullscreenManager) { +define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'screenfull'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, screenfull) { 'use strict'; + /** Delay time in ms for reportPlayback logging */ + const reportPlaybackLogDelay = 1e3; + function enableLocalPlaylistManagement(player) { if (player.getPlaylist) { @@ -17,9 +20,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function bindToFullscreenChange(player) { - events.on(fullscreenManager, 'fullscreenchange', function () { - events.trigger(player, 'fullscreenchange'); - }); + if (screenfull.isEnabled) { + screenfull.on('change', function () { + events.trigger(player, 'fullscreenchange'); + }); + } } function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) { @@ -38,11 +43,18 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); } + /** Last invoked method */ + let reportPlaybackLastMethod; + + /** Last invoke time of method */ + let reportPlaybackLastTime; + function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { if (!serverId) { // Not a server item // We can expand on this later and possibly report them + events.trigger(playbackManagerInstance, 'reportplayback', [false]); return; } @@ -57,9 +69,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); } - console.debug(method + '-' + JSON.stringify(info)); + const now = (new Date).getTime(); + + if (method !== reportPlaybackLastMethod || now - (reportPlaybackLastTime || 0) >= reportPlaybackLogDelay) { + console.debug(method + '-' + JSON.stringify(info)); + reportPlaybackLastMethod = method; + reportPlaybackLastTime = now; + } + var apiClient = connectionManager.getApiClient(serverId); - apiClient[method](info); + var reportPlaybackPromise = apiClient[method](info); + // Notify that report has been sent + reportPlaybackPromise.then(() => { + events.trigger(playbackManagerInstance, 'reportplayback', [true]); + }); } function getPlaylistSync(playbackManagerInstance, player) { @@ -110,8 +133,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { query.Limit = query.Limit || 300; - query.Fields = "Chapters"; - query.ExcludeLocationTypes = "Virtual"; + query.Fields = 'Chapters'; + query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; @@ -146,7 +169,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla function backdropImageUrl(apiClient, item, options) { options = options || {}; - options.type = options.type || "Backdrop"; + options.type = options.type || 'Backdrop'; // If not resizing, get the original image if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { @@ -203,15 +226,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getParam(name, url) { - name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); - var regexS = "[\\?&]" + name + "=([^&#]*)"; - var regex = new RegExp(regexS, "i"); + name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS, 'i'); var results = regex.exec(url); if (results == null) { - return ""; + return ''; } else { - return decodeURIComponent(results[1].replace(/\+/g, " ")); + return decodeURIComponent(results[1].replace(/\+/g, ' ')); } } @@ -291,13 +314,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (codecProfile.Type === 'Audio') { (codecProfile.Conditions || []).map(function (condition) { if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { - maxAudioBitDepth = condition.Value; - } - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { - maxAudioSampleRate = condition.Value; - } - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { - maxAudioBitrate = condition.Value; + return maxAudioBitDepth = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { + return maxAudioSampleRate = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { + return maxAudioBitrate = condition.Value; } }); } @@ -598,8 +619,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla url: apiClient.getUrl('LiveStreams/Open', query), type: 'POST', data: JSON.stringify(postData), - contentType: "application/json", - dataType: "json" + contentType: 'application/json', + dataType: 'json' }); } @@ -1108,21 +1129,19 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.canPlay = function (item) { - var itemType = item.Type; - if (itemType === "PhotoAlbum" || itemType === "MusicGenre" || itemType === "Season" || itemType === "Series" || itemType === "BoxSet" || itemType === "MusicAlbum" || itemType === "MusicArtist" || itemType === "Playlist") { + if (itemType === 'PhotoAlbum' || itemType === 'MusicGenre' || itemType === 'Season' || itemType === 'Series' || itemType === 'BoxSet' || itemType === 'MusicAlbum' || itemType === 'MusicArtist' || itemType === 'Playlist') { return true; } - if (item.LocationType === "Virtual") { - if (itemType !== "Program") { + if (item.LocationType === 'Virtual') { + if (itemType !== 'Program') { return false; } } - if (itemType === "Program") { - + if (itemType === 'Program') { if (!item.EndDate || !item.StartDate) { return false; } @@ -1518,7 +1537,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.isFullscreen(); } - return fullscreenManager.isFullScreen(); + return screenfull.isFullscreen; }; self.toggleFullscreen = function (player) { @@ -1528,10 +1547,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.toggleFulscreen(); } - if (fullscreenManager.isFullScreen()) { - fullscreenManager.exitFullscreen(); - } else { - fullscreenManager.requestFullscreen(); + if (screenfull.isEnabled) { + screenfull.toggle(); } }; @@ -1633,29 +1650,29 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.supportSubtitleOffset = function(player) { player = player || self._currentPlayer; return player && 'setSubtitleOffset' in player; - } + }; self.enableShowingSubtitleOffset = function(player) { player = player || self._currentPlayer; player.enableShowingSubtitleOffset(); - } + }; self.disableShowingSubtitleOffset = function(player) { player = player || self._currentPlayer; if (player.disableShowingSubtitleOffset) { player.disableShowingSubtitleOffset(); } - } + }; self.isShowingSubtitleOffsetEnabled = function(player) { player = player || self._currentPlayer; return player.isShowingSubtitleOffsetEnabled(); - } + }; self.isSubtitleStreamExternal = function(index, player) { var stream = getSubtitleStream(player, index); return stream ? getDeliveryMethod(stream) === 'External' : false; - } + }; self.setSubtitleOffset = function (value, player) { player = player || self._currentPlayer; @@ -1669,12 +1686,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (player.getSubtitleOffset) { return player.getSubtitleOffset(); } - } + }; self.canHandleOffsetOnCurrentSubtitle = function(player) { var index = self.getSubtitleStreamIndex(player); return index !== -1 && self.isSubtitleStreamExternal(index, player); - } + }; self.seek = function (ticks, player) { @@ -1861,40 +1878,37 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla var queryOptions = options.queryOptions || {}; - if (firstItem.Type === "Program") { + if (firstItem.Type === 'Program') { promise = getItemsForPlayback(serverId, { Ids: firstItem.ChannelId }); - } else if (firstItem.Type === "Playlist") { + } else if (firstItem.Type === 'Playlist') { promise = getItemsForPlayback(serverId, { ParentId: firstItem.Id, SortBy: options.shuffle ? 'Random' : null }); - } else if (firstItem.Type === "MusicArtist") { + } else if (firstItem.Type === 'MusicArtist') { promise = getItemsForPlayback(serverId, { ArtistIds: firstItem.Id, - Filters: "IsNotFolder", + Filters: 'IsNotFolder', Recursive: true, SortBy: options.shuffle ? 'Random' : 'SortName', - MediaTypes: "Audio" + MediaTypes: 'Audio' }); - } else if (firstItem.MediaType === "Photo") { + } else if (firstItem.MediaType === 'Photo') { promise = getItemsForPlayback(serverId, { ParentId: firstItem.ParentId, - Filters: "IsNotFolder", + Filters: 'IsNotFolder', // Setting this to true may cause some incorrect sorting Recursive: false, SortBy: options.shuffle ? 'Random' : 'SortName', - MediaTypes: "Photo,Video", - Limit: 500 - + MediaTypes: 'Photo,Video' }).then(function (result) { - var items = result.Items; var index = items.map(function (i) { @@ -1911,40 +1925,40 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return Promise.resolve(result); }); - } else if (firstItem.Type === "PhotoAlbum") { + } else if (firstItem.Type === 'PhotoAlbum') { promise = getItemsForPlayback(serverId, { ParentId: firstItem.Id, - Filters: "IsNotFolder", + Filters: 'IsNotFolder', // Setting this to true may cause some incorrect sorting Recursive: false, SortBy: options.shuffle ? 'Random' : 'SortName', - MediaTypes: "Photo,Video", + MediaTypes: 'Photo,Video', Limit: 1000 }); - } else if (firstItem.Type === "MusicGenre") { + } else if (firstItem.Type === 'MusicGenre') { promise = getItemsForPlayback(serverId, { GenreIds: firstItem.Id, - Filters: "IsNotFolder", + Filters: 'IsNotFolder', Recursive: true, SortBy: options.shuffle ? 'Random' : 'SortName', - MediaTypes: "Audio" + MediaTypes: 'Audio' }); } else if (firstItem.IsFolder) { promise = getItemsForPlayback(serverId, mergePlaybackQueries({ ParentId: firstItem.Id, - Filters: "IsNotFolder", + Filters: 'IsNotFolder', Recursive: true, // These are pre-sorted SortBy: options.shuffle ? 'Random' : (['BoxSet'].indexOf(firstItem.Type) === -1 ? 'SortName' : null), - MediaTypes: "Audio,Video" + MediaTypes: 'Audio,Video' }, queryOptions)); - } else if (firstItem.Type === "Episode" && items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) { + } else if (firstItem.Type === 'Episode' && items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) { promise = new Promise(function (resolve, reject) { var apiClient = connectionManager.getApiClient(firstItem.ServerId); @@ -1960,7 +1974,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla IsVirtualUnaired: false, IsMissing: false, UserId: apiClient.getCurrentUserId(), - Fields: "Chapters" + Fields: 'Chapters' }).then(function (episodesResult) { @@ -2083,6 +2097,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla state.PlayState.IsMuted = player.isMuted(); state.PlayState.IsPaused = player.paused(); state.PlayState.RepeatMode = self.getRepeatMode(player); + state.PlayState.ShuffleMode = self.getQueueShuffleMode(player); state.PlayState.MaxStreamingBitrate = self.getMaxStreamingBitrate(player); state.PlayState.PositionTicks = getCurrentTicks(player); @@ -2168,7 +2183,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla // Only used internally self.getCurrentTicks = getCurrentTicks; - function playPhotos(items, options, user) { + function playOther(items, options, user) { var playStartIndex = options.startIndex || 0; var player = getPlayer(items[playStartIndex], options); @@ -2197,9 +2212,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return Promise.reject(); } - if (firstItem.MediaType === "Photo") { + if (firstItem.MediaType === 'Photo' || firstItem.MediaType === 'Book') { - return playPhotos(items, options, user); + return playOther(items, options, user); } var apiClient = connectionManager.getApiClient(firstItem.ServerId); @@ -2863,11 +2878,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - self.queue = function (options, player) { + self.queue = function (options, player = this._currentPlayer) { queue(options, '', player); }; - self.queueNext = function (options, player) { + self.queueNext = function (options, player = this._currentPlayer) { queue(options, 'next', player); }; @@ -2955,6 +2970,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { self._playQueueManager.queue(items); } + events.trigger(player, 'playlistitemadd'); } function onPlayerProgressInterval() { @@ -3140,7 +3156,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla AllowVideoStreamCopy: false, AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null - }, true); + }); return; } @@ -3290,6 +3306,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla sendProgressUpdate(player, 'repeatmodechange'); } + function onShuffleQueueModeChange() { + var player = this; + sendProgressUpdate(player, 'shufflequeuemodechange'); + } + function onPlaylistItemMove(e) { var player = this; sendProgressUpdate(player, 'playlistitemmove', true); @@ -3344,6 +3365,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.on(player, 'unpause', onPlaybackUnpause); events.on(player, 'volumechange', onPlaybackVolumeChange); events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); events.on(player, 'playlistitemmove', onPlaylistItemMove); events.on(player, 'playlistitemremove', onPlaylistItemRemove); events.on(player, 'playlistitemadd', onPlaylistItemAdd); @@ -3356,6 +3378,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.on(player, 'unpause', onPlaybackUnpause); events.on(player, 'volumechange', onPlaybackVolumeChange); events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); events.on(player, 'playlistitemmove', onPlaylistItemMove); events.on(player, 'playlistitemremove', onPlaylistItemRemove); events.on(player, 'playlistitemadd', onPlaylistItemAdd); @@ -3378,7 +3401,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla pluginManager.ofType('mediaplayer').map(initMediaPlayer); function sendProgressUpdate(player, progressEventName, reportPlaylist) { - if (!player) { throw new Error('player cannot be null'); } @@ -3689,7 +3711,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; PlaybackManager.prototype.stop = function (player) { - player = player || this._currentPlayer; if (player) { @@ -3762,6 +3783,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; + PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) { + if (player && player.setPlaybackRate) { + player.setPlaybackRate(value); + } + }; + + PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) { + if (player && player.getPlaybackRate) { + return player.getPlaybackRate(); + } + + return null; + }; + PlaybackManager.prototype.instantMix = function (item, player) { player = player || this._currentPlayer; @@ -3784,7 +3819,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); }; - PlaybackManager.prototype.shuffle = function (shuffleItem, player, queryOptions) { + PlaybackManager.prototype.shuffle = function (shuffleItem, player) { player = player || this._currentPlayer; if (player && player.shuffle) { @@ -3836,23 +3871,24 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (player.isLocalPlayer) { var list = [ - "GoHome", - "GoToSettings", - "VolumeUp", - "VolumeDown", - "Mute", - "Unmute", - "ToggleMute", - "SetVolume", - "SetAudioStreamIndex", - "SetSubtitleStreamIndex", - "SetMaxStreamingBitrate", - "DisplayContent", - "GoToSearch", - "DisplayMessage", - "SetRepeatMode", - "PlayMediaSource", - "PlayTrailers" + 'GoHome', + 'GoToSettings', + 'VolumeUp', + 'VolumeDown', + 'Mute', + 'Unmute', + 'ToggleMute', + 'SetVolume', + 'SetAudioStreamIndex', + 'SetSubtitleStreamIndex', + 'SetMaxStreamingBitrate', + 'DisplayContent', + 'GoToSearch', + 'DisplayMessage', + 'SetRepeatMode', + 'SetShuffleQueue', + 'PlayMediaSource', + 'PlayTrailers' ]; if (apphost.supports('fullscreenchange')) { @@ -3872,6 +3908,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (player.supports('SetAspectRatio')) { list.push('SetAspectRatio'); } + if (player.supports('PlaybackRate')) { + list.push('PlaybackRate'); + } } return list; @@ -3881,9 +3920,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return info ? info.supportedCommands : []; }; - PlaybackManager.prototype.setRepeatMode = function (value, player) { - - player = player || this._currentPlayer; + PlaybackManager.prototype.setRepeatMode = function (value, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.setRepeatMode(value); } @@ -3892,9 +3929,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(player, 'repeatmodechange'); }; - PlaybackManager.prototype.getRepeatMode = function (player) { - - player = player || this._currentPlayer; + PlaybackManager.prototype.getRepeatMode = function (player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.getRepeatMode(); } @@ -3902,6 +3937,52 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return this._playQueueManager.getRepeatMode(); }; + PlaybackManager.prototype.setQueueShuffleMode = function (value, player = this._currentPlayer) { + if (player && !enableLocalPlaylistManagement(player)) { + return player.setQueueShuffleMode(value); + } + + this._playQueueManager.setShuffleMode(value); + events.trigger(player, 'shufflequeuemodechange'); + }; + + PlaybackManager.prototype.getQueueShuffleMode = function (player = this._currentPlayer) { + if (player && !enableLocalPlaylistManagement(player)) { + return player.getQueueShuffleMode(); + } + + return this._playQueueManager.getShuffleMode(); + }; + + PlaybackManager.prototype.toggleQueueShuffleMode = function (player = this._currentPlayer) { + let currentvalue; + if (player && !enableLocalPlaylistManagement(player)) { + currentvalue = player.getQueueShuffleMode(); + switch (currentvalue) { + case 'Shuffle': + player.setQueueShuffleMode('Sorted'); + break; + case 'Sorted': + player.setQueueShuffleMode('Shuffle'); + break; + default: + throw new TypeError('current value for shufflequeue is invalid'); + } + } else { + this._playQueueManager.toggleShuffleMode(); + } + events.trigger(player, 'shufflequeuemodechange'); + }; + + PlaybackManager.prototype.clearQueue = function (clearCurrentItem = false, player = this._currentPlayer) { + if (player && !enableLocalPlaylistManagement(player)) { + return player.clearQueue(clearCurrentItem); + } + + this._playQueueManager.clearPlaylist(clearCurrentItem); + events.trigger(player, 'playlistitemremove'); + }; + PlaybackManager.prototype.trySetActiveDeviceName = function (name) { name = normalizeName(name); @@ -3970,6 +4051,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla case 'SetRepeatMode': this.setRepeatMode(cmd.Arguments.RepeatMode, player); break; + case 'SetShuffleQueue': + this.setQueueShuffleMode(cmd.Arguments.ShuffleMode, player); + break; case 'VolumeUp': this.volumeUp(player); break; diff --git a/src/components/playback/playbackorientation.js b/src/components/playback/playbackorientation.js index 5b178dbf080..2078c6f6a80 100644 --- a/src/components/playback/playbackorientation.js +++ b/src/components/playback/playbackorientation.js @@ -1,55 +1,57 @@ -define(['playbackManager', 'layoutManager', 'events'], function (playbackManager, layoutManager, events) { - "use strict"; +import playbackManager from 'playbackManager'; +import layoutManager from 'layoutManager'; +import events from 'events'; - var orientationLocked; +var orientationLocked; - function onOrientationChangeSuccess() { - orientationLocked = true; - } +function onOrientationChangeSuccess() { + orientationLocked = true; +} - function onOrientationChangeError(err) { - orientationLocked = false; - console.error('error locking orientation: ' + err); - } +function onOrientationChangeError(err) { + orientationLocked = false; + console.error('error locking orientation: ' + err); +} - events.on(playbackManager, 'playbackstart', function (e, player, state) { +events.on(playbackManager, 'playbackstart', function (e, player, state) { - var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player); + var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player); - if (isLocalVideo && layoutManager.mobile) { - var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock); + if (isLocalVideo && layoutManager.mobile) { + /* eslint-disable-next-line compat/compat */ + var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock); - if (lockOrientation) { + if (lockOrientation) { - try { - var promise = lockOrientation('landscape'); - if (promise.then) { - promise.then(onOrientationChangeSuccess, onOrientationChangeError); - } else { - // returns a boolean - orientationLocked = promise; - } - } catch (err) { - onOrientationChangeError(err); + try { + var promise = lockOrientation('landscape'); + if (promise.then) { + promise.then(onOrientationChangeSuccess, onOrientationChangeError); + } else { + // returns a boolean + orientationLocked = promise; } + } catch (err) { + onOrientationChangeError(err); } } - }); + } +}); - events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) { +events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) { - if (orientationLocked && !playbackStopInfo.nextMediaType) { + if (orientationLocked && !playbackStopInfo.nextMediaType) { - var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock); + /* eslint-disable-next-line compat/compat */ + var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock); - if (unlockOrientation) { - try { - unlockOrientation(); - } catch (err) { - console.error('error unlocking orientation: ' + err); - } - orientationLocked = false; + if (unlockOrientation) { + try { + unlockOrientation(); + } catch (err) { + console.error('error unlocking orientation: ' + err); } + orientationLocked = false; } - }); + } }); diff --git a/src/components/playback/playerSelectionMenu.js b/src/components/playback/playerSelectionMenu.js index 97e6e462309..91b1ddc20bc 100644 --- a/src/components/playback/playerSelectionMenu.js +++ b/src/components/playback/playerSelectionMenu.js @@ -1,320 +1,325 @@ -define(['appSettings', 'events', 'browser', 'loading', 'playbackManager', 'appRouter', 'globalize', 'apphost'], function (appSettings, events, browser, loading, playbackManager, appRouter, globalize, appHost) { - 'use strict'; +import appSettings from 'appSettings'; +import events from 'events'; +import browser from 'browser'; +import loading from 'loading'; +import playbackManager from 'playbackManager'; +import appRouter from 'appRouter'; +import globalize from 'globalize'; +import appHost from 'apphost'; - function mirrorItem(info, player) { +function mirrorItem(info, player) { - var item = info.item; + var item = info.item; - playbackManager.displayContent({ + playbackManager.displayContent({ - ItemName: item.Name, - ItemId: item.Id, - ItemType: item.Type, - Context: info.context - }, player); - } + ItemName: item.Name, + ItemId: item.Id, + ItemType: item.Type, + Context: info.context + }, player); +} - function mirrorIfEnabled(info) { +function mirrorIfEnabled(info) { - if (info && playbackManager.enableDisplayMirroring()) { + if (info && playbackManager.enableDisplayMirroring()) { - var getPlayerInfo = playbackManager.getPlayerInfo(); + var getPlayerInfo = playbackManager.getPlayerInfo(); - if (getPlayerInfo) { - if (!getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { - mirrorItem(info, playbackManager.getCurrentPlayer()); - } + if (getPlayerInfo) { + if (!getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { + mirrorItem(info, playbackManager.getCurrentPlayer()); } } } +} - function emptyCallback() { - // avoid console logs about uncaught promises - } +function emptyCallback() { + // avoid console logs about uncaught promises +} - function getTargetSecondaryText(target) { +function getTargetSecondaryText(target) { - if (target.user) { - - return target.user.Name; - } + if (target.user) { - return null; + return target.user.Name; } - function getIcon(target) { + return null; +} - var deviceType = target.deviceType; +function getIcon(target) { - if (!deviceType && target.isLocalPlayer) { - if (browser.tv) { - deviceType = 'tv'; - } else if (browser.mobile) { - deviceType = 'smartphone'; - } else { - deviceType = 'desktop'; - } - } + var deviceType = target.deviceType; - if (!deviceType) { + if (!deviceType && target.isLocalPlayer) { + if (browser.tv) { deviceType = 'tv'; - } - - switch (deviceType) { - - case 'smartphone': - return 'smartphone'; - case 'tablet': - return 'tablet'; - case 'tv': - return 'tv'; - case 'cast': - return 'cast'; - case 'desktop': - return 'computer'; - default: - return 'tv'; + } else if (browser.mobile) { + deviceType = 'smartphone'; + } else { + deviceType = 'desktop'; } } - function showPlayerSelection(button) { + if (!deviceType) { + deviceType = 'tv'; + } - var currentPlayerInfo = playbackManager.getPlayerInfo(); + switch (deviceType) { + + case 'smartphone': + return 'smartphone'; + case 'tablet': + return 'tablet'; + case 'tv': + return 'tv'; + case 'cast': + return 'cast'; + case 'desktop': + return 'computer'; + default: + return 'tv'; + } +} - if (currentPlayerInfo) { - if (!currentPlayerInfo.isLocalPlayer) { - showActivePlayerMenu(currentPlayerInfo); - return; - } - } +export function show(button) { - var currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null; + var currentPlayerInfo = playbackManager.getPlayerInfo(); - loading.show(); + if (currentPlayerInfo) { + if (!currentPlayerInfo.isLocalPlayer) { + showActivePlayerMenu(currentPlayerInfo); + return; + } + } - playbackManager.getTargets().then(function (targets) { + var currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null; - var menuItems = targets.map(function (t) { + loading.show(); - var name = t.name; + playbackManager.getTargets().then(function (targets) { - if (t.appName && t.appName !== t.name) { - name += " - " + t.appName; - } + var menuItems = targets.map(function (t) { - return { - name: name, - id: t.id, - selected: currentPlayerId === t.id, - secondaryText: getTargetSecondaryText(t), - icon: getIcon(t) - }; + var name = t.name; - }); + if (t.appName && t.appName !== t.name) { + name += ' - ' + t.appName; + } - require(['actionsheet'], function (actionsheet) { + return { + name: name, + id: t.id, + selected: currentPlayerId === t.id, + secondaryText: getTargetSecondaryText(t), + icon: getIcon(t) + }; - loading.hide(); + }); - var menuOptions = { - title: globalize.translate('HeaderPlayOn'), - items: menuItems, - positionTo: button, + require(['actionsheet'], function (actionsheet) { - resolveOnClick: true, - border: true - }; + loading.hide(); - // Unfortunately we can't allow the url to change or chromecast will throw a security error - // Might be able to solve this in the future by moving the dialogs to hashbangs - if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) { - menuOptions.enableHistory = false; - } + var menuOptions = { + title: globalize.translate('HeaderPlayOn'), + items: menuItems, + positionTo: button, - actionsheet.show(menuOptions).then(function (id) { + resolveOnClick: true, + border: true + }; - var target = targets.filter(function (t) { - return t.id === id; - })[0]; + // Unfortunately we can't allow the url to change or chromecast will throw a security error + // Might be able to solve this in the future by moving the dialogs to hashbangs + if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) { + menuOptions.enableHistory = false; + } - playbackManager.trySetActivePlayer(target.playerName, target); + actionsheet.show(menuOptions).then(function (id) { - mirrorIfEnabled(); + var target = targets.filter(function (t) { + return t.id === id; + })[0]; - }, emptyCallback); - }); - }); - } + playbackManager.trySetActivePlayer(target.playerName, target); - function showActivePlayerMenu(playerInfo) { + mirrorIfEnabled(); - require(['dialogHelper', 'dialog', 'emby-checkbox', 'emby-button'], function (dialogHelper) { - showActivePlayerMenuInternal(dialogHelper, playerInfo); + }, emptyCallback); }); - } - - function disconnectFromPlayer(currentDeviceName) { + }); +} - if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) { +function showActivePlayerMenu(playerInfo) { - require(['dialog'], function (dialog) { + require(['dialogHelper', 'dialog', 'emby-checkbox', 'emby-button'], function (dialogHelper) { + showActivePlayerMenuInternal(dialogHelper, playerInfo); + }); +} - var menuItems = []; +function disconnectFromPlayer(currentDeviceName) { - menuItems.push({ - name: globalize.translate('Yes'), - id: 'yes' - }); - menuItems.push({ - name: globalize.translate('No'), - id: 'no' - }); + if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) { - dialog({ - buttons: menuItems, - //positionTo: positionTo, - text: globalize.translate('ConfirmEndPlayerSession', currentDeviceName) + require(['dialog'], function (dialog) { - }).then(function (id) { - switch (id) { + var menuItems = []; - case 'yes': - playbackManager.getCurrentPlayer().endSession(); - playbackManager.setDefaultPlayerActive(); - break; - case 'no': - playbackManager.setDefaultPlayerActive(); - break; - default: - break; - } - }); + menuItems.push({ + name: globalize.translate('Yes'), + id: 'yes' + }); + menuItems.push({ + name: globalize.translate('No'), + id: 'no' + }); + dialog({ + buttons: menuItems, + //positionTo: positionTo, + text: globalize.translate('ConfirmEndPlayerSession', currentDeviceName) + + }).then(function (id) { + switch (id) { + + case 'yes': + playbackManager.getCurrentPlayer().endSession(); + playbackManager.setDefaultPlayerActive(); + break; + case 'no': + playbackManager.setDefaultPlayerActive(); + break; + default: + break; + } }); - } else { + }); - playbackManager.setDefaultPlayerActive(); - } + } else { + + playbackManager.setDefaultPlayerActive(); } +} - function showActivePlayerMenuInternal(dialogHelper, playerInfo) { +function showActivePlayerMenuInternal(dialogHelper, playerInfo) { - var html = ''; + var html = ''; - var dialogOptions = { - removeOnClose: true - }; + var dialogOptions = { + removeOnClose: true + }; - dialogOptions.modal = false; - dialogOptions.entryAnimationDuration = 160; - dialogOptions.exitAnimationDuration = 160; - dialogOptions.autoFocus = false; + dialogOptions.modal = false; + dialogOptions.entryAnimationDuration = 160; + dialogOptions.exitAnimationDuration = 160; + dialogOptions.autoFocus = false; - var dlg = dialogHelper.createDialog(dialogOptions); + var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('promptDialog'); + dlg.classList.add('promptDialog'); - var currentDeviceName = (playerInfo.deviceName || playerInfo.name); + var currentDeviceName = (playerInfo.deviceName || playerInfo.name); - html += '
'; - html += '

'; - html += currentDeviceName; - html += '

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

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

'; - html += '
'; + html += '
'; - if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { + if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { - html += ''; - } + html += ''; + } - html += '
'; + html += '
'; - html += '
'; + html += '
'; - html += ''; - html += ''; - html += ''; - html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; - html += '
'; - dlg.innerHTML = html; + html += '
'; + dlg.innerHTML = html; - var chkMirror = dlg.querySelector('.chkMirror'); + var chkMirror = dlg.querySelector('.chkMirror'); - if (chkMirror) { - chkMirror.addEventListener('change', onMirrorChange); - } - - var destination = ''; + if (chkMirror) { + chkMirror.addEventListener('change', onMirrorChange); + } - var btnRemoteControl = dlg.querySelector('.btnRemoteControl'); - if (btnRemoteControl) { - btnRemoteControl.addEventListener('click', function () { - destination = 'nowplaying'; - dialogHelper.close(dlg); - }); - } + var destination = ''; - dlg.querySelector('.btnDisconnect').addEventListener('click', function () { - destination = 'disconnectFromPlayer'; + var btnRemoteControl = dlg.querySelector('.btnRemoteControl'); + if (btnRemoteControl) { + btnRemoteControl.addEventListener('click', function () { + destination = 'nowplaying'; dialogHelper.close(dlg); }); + } - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); + dlg.querySelector('.btnDisconnect').addEventListener('click', function () { + destination = 'disconnectFromPlayer'; + dialogHelper.close(dlg); + }); - dialogHelper.open(dlg).then(function () { - if (destination === 'nowplaying') { - appRouter.showNowPlaying(); - } else if (destination === 'disconnectFromPlayer') { - disconnectFromPlayer(currentDeviceName); - } - }, emptyCallback); - } + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); - function onMirrorChange() { - playbackManager.enableDisplayMirroring(this.checked); - } + dialogHelper.open(dlg).then(function () { + if (destination === 'nowplaying') { + appRouter.showNowPlaying(); + } else if (destination === 'disconnectFromPlayer') { + disconnectFromPlayer(currentDeviceName); + } + }, emptyCallback); +} - document.addEventListener('viewshow', function (e) { +function onMirrorChange() { + playbackManager.enableDisplayMirroring(this.checked); +} - var state = e.detail.state || {}; - var item = state.item; +document.addEventListener('viewshow', function (e) { - if (item && item.ServerId) { - mirrorIfEnabled({ - item: item - }); - return; - } - }); + var state = e.detail.state || {}; + var item = state.item; - events.on(appSettings, 'change', function (e, name) { - if (name === 'displaymirror') { - mirrorIfEnabled(); - } - }); + if (item && item.ServerId) { + mirrorIfEnabled({ + item: item + }); + return; + } +}); - events.on(playbackManager, 'pairing', function (e) { - loading.show(); - }); +events.on(appSettings, 'change', function (e, name) { + if (name === 'displaymirror') { + mirrorIfEnabled(); + } +}); - events.on(playbackManager, 'paired', function (e) { - loading.hide(); - }); +events.on(playbackManager, 'pairing', function (e) { + loading.show(); +}); - events.on(playbackManager, 'pairerror', function (e) { - loading.hide(); - }); +events.on(playbackManager, 'paired', function (e) { + loading.hide(); +}); - return { - show: showPlayerSelection - }; +events.on(playbackManager, 'pairerror', function (e) { + loading.hide(); }); + +export default { + show: show +}; diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index 086f98c2d4b..33d252c52a3 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -1,266 +1,272 @@ -define(['connectionManager', 'actionsheet', 'datetime', 'playbackManager', 'globalize', 'appSettings', 'qualityoptions'], function (connectionManager, actionsheet, datetime, playbackManager, globalize, appSettings, qualityoptions) { - 'use strict'; - - function showQualityMenu(player, btn) { - - var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) { - return stream.Type === "Video"; - })[0]; - var videoWidth = videoStream ? videoStream.Width : null; +import connectionManager from 'connectionManager'; +import actionsheet from 'actionsheet'; +import playbackManager from 'playbackManager'; +import globalize from 'globalize'; +import qualityoptions from 'qualityoptions'; + +function showQualityMenu(player, btn) { + + var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) { + return stream.Type === 'Video'; + })[0]; + var videoWidth = videoStream ? videoStream.Width : null; + var videoHeight = videoStream ? videoStream.Height : null; + + var options = qualityoptions.getVideoQualityOptions({ + currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), + isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), + videoWidth: videoWidth, + videoHeight: videoHeight, + enableAuto: true + }); + + var menuItems = options.map(function (o) { + var opt = { + name: o.name, + id: o.bitrate, + asideText: o.secondaryText + }; + + if (o.selected) { + opt.selected = true; + } - var options = qualityoptions.getVideoQualityOptions({ - currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), - isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), - videoWidth: videoWidth, - enableAuto: true - }); + return opt; + }); + + var selectedId = options.filter(function (o) { + return o.selected; + }); + + selectedId = selectedId.length ? selectedId[0].bitrate : null; + + return actionsheet.show({ + items: menuItems, + positionTo: btn + }).then(function (id) { + var bitrate = parseInt(id); + if (bitrate !== selectedId) { + playbackManager.setMaxStreamingBitrate({ + enableAutomaticBitrateDetection: bitrate ? false : true, + maxBitrate: bitrate + }, player); + } + }); +} + +function showRepeatModeMenu(player, btn) { + var menuItems = []; + var currentValue = playbackManager.getRepeatMode(player); + + menuItems.push({ + name: globalize.translate('RepeatAll'), + id: 'RepeatAll', + selected: currentValue === 'RepeatAll' + }); + + menuItems.push({ + name: globalize.translate('RepeatOne'), + id: 'RepeatOne', + selected: currentValue === 'RepeatOne' + }); + + menuItems.push({ + name: globalize.translate('None'), + id: 'RepeatNone', + selected: currentValue === 'RepeatNone' + }); + + return actionsheet.show({ + items: menuItems, + positionTo: btn + }).then(function (mode) { + if (mode) { + playbackManager.setRepeatMode(mode, player); + } + }); +} + +function getQualitySecondaryText(player) { + var state = playbackManager.getPlayerState(player); + var isAutoEnabled = playbackManager.enableAutomaticBitrateDetection(player); + var currentMaxBitrate = playbackManager.getMaxStreamingBitrate(player); + + var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) { + return stream.Type === 'Video'; + })[0]; + + var videoWidth = videoStream ? videoStream.Width : null; + var videoHeight = videoStream ? videoStream.Height : null; + + var options = qualityoptions.getVideoQualityOptions({ + currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), + isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), + videoWidth: videoWidth, + videoHeight: videoHeight, + enableAuto: true + }); + + var menuItems = options.map(function (o) { + var opt = { + name: o.name, + id: o.bitrate, + asideText: o.secondaryText + }; + + if (o.selected) { + opt.selected = true; + } - var menuItems = options.map(function (o) { - var opt = { - name: o.name, - id: o.bitrate, - asideText: o.secondaryText - }; + return opt; + }); - if (o.selected) { - opt.selected = true; - } + var selectedOption = options.filter(function (o) { + return o.selected; + }); - return opt; - }); + if (!selectedOption.length) { + return null; + } - var selectedId = options.filter(function (o) { - return o.selected; - }); + selectedOption = selectedOption[0]; + var text = selectedOption.name; - selectedId = selectedId.length ? selectedId[0].bitrate : null; - - return actionsheet.show({ - items: menuItems, - positionTo: btn - }).then(function (id) { - var bitrate = parseInt(id); - if (bitrate !== selectedId) { - playbackManager.setMaxStreamingBitrate({ - enableAutomaticBitrateDetection: bitrate ? false : true, - maxBitrate: bitrate - }, player); - } - }); + if (selectedOption.autoText) { + if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') { + text += ' - Direct'; + } else { + text += ' ' + selectedOption.autoText; + } } - function showRepeatModeMenu(player, btn) { - var menuItems = []; - var currentValue = playbackManager.getRepeatMode(player); - - menuItems.push({ - name: globalize.translate('RepeatAll'), - id: 'RepeatAll', - selected: currentValue === 'RepeatAll' - }); + return text; +} + +function showAspectRatioMenu(player, btn) { + // each has a name and id + var currentId = playbackManager.getAspectRatio(player); + var menuItems = playbackManager.getSupportedAspectRatios(player).map(function (i) { + return { + id: i.id, + name: i.name, + selected: i.id === currentId + }; + }); + + return actionsheet.show({ + items: menuItems, + positionTo: btn + }).then(function (id) { + if (id) { + playbackManager.setAspectRatio(id, player); + return Promise.resolve(); + } - menuItems.push({ - name: globalize.translate('RepeatOne'), - id: 'RepeatOne', - selected: currentValue === 'RepeatOne' - }); + return Promise.reject(); + }); +} + +function showWithUser(options, player, user) { + var supportedCommands = playbackManager.getSupportedCommands(player); + var mediaType = options.mediaType; + + var menuItems = []; + if (supportedCommands.indexOf('SetAspectRatio') !== -1) { + var currentAspectRatioId = playbackManager.getAspectRatio(player); + var currentAspectRatio = playbackManager.getSupportedAspectRatios(player).filter(function (i) { + return i.id === currentAspectRatioId; + })[0]; menuItems.push({ - name: globalize.translate('None'), - id: 'RepeatNone', - selected: currentValue === 'RepeatNone' - }); - - return actionsheet.show({ - items: menuItems, - positionTo: btn - }).then(function (mode) { - if (mode) { - playbackManager.setRepeatMode(mode, player); - } + name: globalize.translate('AspectRatio'), + id: 'aspectratio', + asideText: currentAspectRatio ? currentAspectRatio.name : null }); } - function getQualitySecondaryText(player) { - var state = playbackManager.getPlayerState(player); - var isAutoEnabled = playbackManager.enableAutomaticBitrateDetection(player); - var currentMaxBitrate = playbackManager.getMaxStreamingBitrate(player); + if (user && user.Policy.EnableVideoPlaybackTranscoding) { + var secondaryQualityText = getQualitySecondaryText(player); - var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) { - return stream.Type === "Video"; - })[0]; - - var videoWidth = videoStream ? videoStream.Width : null; - - var options = qualityoptions.getVideoQualityOptions({ - currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), - isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), - videoWidth: videoWidth, - enableAuto: true + menuItems.push({ + name: globalize.translate('Quality'), + id: 'quality', + asideText: secondaryQualityText }); + } - var menuItems = options.map(function (o) { - var opt = { - name: o.name, - id: o.bitrate, - asideText: o.secondaryText - }; - - if (o.selected) { - opt.selected = true; - } - - return opt; - }); + var repeatMode = playbackManager.getRepeatMode(player); - var selectedOption = options.filter(function (o) { - return o.selected; + if (supportedCommands.indexOf('SetRepeatMode') !== -1 && playbackManager.currentMediaSource(player).RunTimeTicks) { + menuItems.push({ + name: globalize.translate('RepeatMode'), + id: 'repeatmode', + asideText: repeatMode === 'RepeatNone' ? globalize.translate('None') : globalize.translate('' + repeatMode) }); - - if (!selectedOption.length) { - return null; - } - - selectedOption = selectedOption[0]; - var text = selectedOption.name; - - if (selectedOption.autoText) { - if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') { - text += ' - Direct'; - } else { - text += ' ' + selectedOption.autoText; - } - } - - return text; } - function showAspectRatioMenu(player, btn) { - // each has a name and id - var currentId = playbackManager.getAspectRatio(player); - var menuItems = playbackManager.getSupportedAspectRatios(player).map(function (i) { - return { - id: i.id, - name: i.name, - selected: i.id === currentId - }; - }); - - return actionsheet.show({ - items: menuItems, - positionTo: btn - }).then(function (id) { - if (id) { - playbackManager.setAspectRatio(id, player); - return Promise.resolve(); - } - - return Promise.reject(); + if (options.suboffset) { + menuItems.push({ + name: globalize.translate('SubtitleOffset'), + id: 'suboffset', + asideText: null }); } - function showWithUser(options, player, user) { - var supportedCommands = playbackManager.getSupportedCommands(player); - var mediaType = options.mediaType; - - var menuItems = []; - if (supportedCommands.indexOf('SetAspectRatio') !== -1) { - var currentAspectRatioId = playbackManager.getAspectRatio(player); - var currentAspectRatio = playbackManager.getSupportedAspectRatios(player).filter(function (i) { - return i.id === currentAspectRatioId; - })[0]; - - menuItems.push({ - name: globalize.translate('AspectRatio'), - id: 'aspectratio', - asideText: currentAspectRatio ? currentAspectRatio.name : null - }); - } - - if (user && user.Policy.EnableVideoPlaybackTranscoding) { - var secondaryQualityText = getQualitySecondaryText(player); - - menuItems.push({ - name: globalize.translate('Quality'), - id: 'quality', - asideText: secondaryQualityText - }); - } - - var repeatMode = playbackManager.getRepeatMode(player); - - if (supportedCommands.indexOf('SetRepeatMode') !== -1 && playbackManager.currentMediaSource(player).RunTimeTicks) { - menuItems.push({ - name: globalize.translate('RepeatMode'), - id: 'repeatmode', - asideText: repeatMode === 'RepeatNone' ? globalize.translate('None') : globalize.translate('' + repeatMode) - }); - } - - if (options.suboffset) { - menuItems.push({ - name: globalize.translate('SubtitleOffset'), - id: 'suboffset', - asideText: null - }); - } - - if (options.stats) { - menuItems.push({ - name: globalize.translate('PlaybackData'), - id: 'stats', - asideText: null - }); - } - - return actionsheet.show({ - items: menuItems, - positionTo: options.positionTo - }).then(function (id) { - return handleSelectedOption(id, options, player); + if (options.stats) { + menuItems.push({ + name: globalize.translate('PlaybackData'), + id: 'stats', + asideText: null }); } - function show(options) { - var player = options.player; - var currentItem = playbackManager.currentItem(player); + return actionsheet.show({ + items: menuItems, + positionTo: options.positionTo + }).then(function (id) { + return handleSelectedOption(id, options, player); + }); +} - if (!currentItem || !currentItem.ServerId) { - return showWithUser(options, player, null); - } +export function show(options) { + var player = options.player; + var currentItem = playbackManager.currentItem(player); - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - return apiClient.getCurrentUser().then(function (user) { - return showWithUser(options, player, user); - }); + if (!currentItem || !currentItem.ServerId) { + return showWithUser(options, player, null); } - function handleSelectedOption(id, options, player) { - switch (id) { - case 'quality': - return showQualityMenu(player, options.positionTo); - case 'aspectratio': - return showAspectRatioMenu(player, options.positionTo); - case 'repeatmode': - return showRepeatModeMenu(player, options.positionTo); - case 'stats': - if (options.onOption) { - options.onOption('stats'); - } - return Promise.resolve(); - case 'suboffset': - if (options.onOption) { - options.onOption('suboffset'); - } - return Promise.resolve(); - default: - break; - } - - return Promise.reject(); + var apiClient = connectionManager.getApiClient(currentItem.ServerId); + return apiClient.getCurrentUser().then(function (user) { + return showWithUser(options, player, user); + }); +} + +function handleSelectedOption(id, options, player) { + switch (id) { + case 'quality': + return showQualityMenu(player, options.positionTo); + case 'aspectratio': + return showAspectRatioMenu(player, options.positionTo); + case 'repeatmode': + return showRepeatModeMenu(player, options.positionTo); + case 'stats': + if (options.onOption) { + options.onOption('stats'); + } + return Promise.resolve(); + case 'suboffset': + if (options.onOption) { + options.onOption('suboffset'); + } + return Promise.resolve(); + default: + break; } - return { - show: show - }; -}); + return Promise.reject(); +} + +export default { + show: show +}; diff --git a/src/components/playback/playmethodhelper.js b/src/components/playback/playmethodhelper.js index 75af04035c7..62793b9a339 100644 --- a/src/components/playback/playmethodhelper.js +++ b/src/components/playback/playmethodhelper.js @@ -1,24 +1,20 @@ -define([], function () { - 'use strict'; +export function getDisplayPlayMethod(session) { - function getDisplayPlayMethod(session) { - - if (!session.NowPlayingItem) { - return null; - } + if (!session.NowPlayingItem) { + return null; + } - if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect) { - return 'DirectStream'; - } else if (session.PlayState.PlayMethod === 'Transcode') { - return 'Transcode'; - } else if (session.PlayState.PlayMethod === 'DirectStream') { - return 'DirectPlay'; - } else if (session.PlayState.PlayMethod === 'DirectPlay') { - return 'DirectPlay'; - } + if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect) { + return 'DirectStream'; + } else if (session.PlayState.PlayMethod === 'Transcode') { + return 'Transcode'; + } else if (session.PlayState.PlayMethod === 'DirectStream') { + return 'DirectPlay'; + } else if (session.PlayState.PlayMethod === 'DirectPlay') { + return 'DirectPlay'; } +} - return { - getDisplayPlayMethod: getDisplayPlayMethod - }; -}); +export default { + getDisplayPlayMethod: getDisplayPlayMethod +}; diff --git a/src/components/playback/playqueuemanager.js b/src/components/playback/playqueuemanager.js index 429c4027f19..2f411091c60 100644 --- a/src/components/playback/playqueuemanager.js +++ b/src/components/playback/playqueuemanager.js @@ -6,7 +6,7 @@ define([], function () { if (!item.PlaylistItemId) { - item.PlaylistItemId = "playlistItem" + currentId; + item.PlaylistItemId = 'playlistItem' + currentId; currentId++; } } @@ -24,8 +24,10 @@ define([], function () { function PlayQueueManager() { + this._sortedPlaylist = []; this._playlist = []; this._repeatMode = 'RepeatNone'; + this._shuffleMode = 'Sorted'; } PlayQueueManager.prototype.getPlaylist = function () { @@ -56,6 +58,40 @@ define([], function () { } }; + PlayQueueManager.prototype.shufflePlaylist = function () { + this._sortedPlaylist = []; + for (const item of this._playlist) { + this._sortedPlaylist.push(item); + } + const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0]; + + for (let i = this._playlist.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * i); + const temp = this._playlist[i]; + this._playlist[i] = this._playlist[j]; + this._playlist[j] = temp; + } + this._playlist.unshift(currentPlaylistItem); + this._shuffleMode = 'Shuffle'; + }; + + PlayQueueManager.prototype.sortShuffledPlaylist = function () { + this._playlist = []; + for (let item of this._sortedPlaylist) { + this._playlist.push(item); + } + this._sortedPlaylist = []; + this._shuffleMode = 'Sorted'; + }; + + PlayQueueManager.prototype.clearPlaylist = function (clearCurrentItem = false) { + const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0]; + this._playlist = []; + if (!clearCurrentItem) { + this._playlist.push(currentPlaylistItem); + } + }; + function arrayInsertAt(destArray, pos, arrayToInsert) { var args = []; args.push(pos); // where to insert @@ -116,9 +152,7 @@ define([], function () { PlayQueueManager.prototype.removeFromPlaylist = function (playlistItemIds) { - var playlist = this.getPlaylist(); - - if (playlist.length <= playlistItemIds.length) { + if (this._playlist.length <= playlistItemIds.length) { return { result: 'empty' }; @@ -127,8 +161,12 @@ define([], function () { var currentPlaylistItemId = this.getCurrentPlaylistItemId(); var isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1; - this._playlist = playlist.filter(function (item) { - return playlistItemIds.indexOf(item.PlaylistItemId) === -1; + this._sortedPlaylist = this._sortedPlaylist.filter(function (item) { + return !playlistItemIds.includes(item.PlaylistItemId); + }); + + this._playlist = this._playlist.filter(function (item) { + return !playlistItemIds.includes(item.PlaylistItemId); }); return { @@ -176,21 +214,56 @@ define([], function () { PlayQueueManager.prototype.reset = function () { + this._sortedPlaylist = []; this._playlist = []; this._currentPlaylistItemId = null; this._repeatMode = 'RepeatNone'; + this._shuffleMode = 'Sorted'; }; PlayQueueManager.prototype.setRepeatMode = function (value) { - - this._repeatMode = value; + const repeatModes = ['RepeatOne', 'RepeatAll', 'RepeatNone']; + if (repeatModes.includes(value)) { + this._repeatMode = value; + } else { + throw new TypeError('invalid value provided for setRepeatMode'); + } }; PlayQueueManager.prototype.getRepeatMode = function () { - return this._repeatMode; }; + PlayQueueManager.prototype.setShuffleMode = function (value) { + switch (value) { + case 'Shuffle': + this.shufflePlaylist(); + break; + case 'Sorted': + this.sortShuffledPlaylist(); + break; + default: + throw new TypeError('invalid value provided to setShuffleMode'); + } + }; + + PlayQueueManager.prototype.toggleShuffleMode = function () { + switch (this._shuffleMode) { + case 'Shuffle': + this.setShuffleMode('Sorted'); + break; + case 'Sorted': + this.setShuffleMode('Shuffle'); + break; + default: + throw new TypeError('current value for shufflequeue is invalid'); + } + }; + + PlayQueueManager.prototype.getShuffleMode = function () { + return this._shuffleMode; + }; + PlayQueueManager.prototype.getNextItemInfo = function () { var newIndex; diff --git a/src/components/playback/remotecontrolautoplay.js b/src/components/playback/remotecontrolautoplay.js index 90a872cc6ea..c0adb57a454 100644 --- a/src/components/playback/remotecontrolautoplay.js +++ b/src/components/playback/remotecontrolautoplay.js @@ -1,47 +1,45 @@ -define(['events', 'playbackManager'], function (events, playbackManager) { - 'use strict'; +import events from 'events'; +import playbackManager from 'playbackManager'; - function transferPlayback(oldPlayer, newPlayer) { +function transferPlayback(oldPlayer, newPlayer) { + const state = playbackManager.getPlayerState(oldPlayer); + const item = state.NowPlayingItem; - var state = playbackManager.getPlayerState(oldPlayer); - - var item = state.NowPlayingItem; - - if (!item) { - return; - } - - var playState = state.PlayState || {}; - var resumePositionTicks = playState.PositionTicks || 0; + if (!item) { + return; + } - playbackManager.stop(oldPlayer).then(function () { + playbackManager.getPlaylist(oldPlayer).then(playlist => { + const playlistIds = playlist.map(x => x.Id); + const playState = state.PlayState || {}; + const resumePositionTicks = playState.PositionTicks || 0; + const playlistIndex = playlistIds.indexOf(item.Id) || 0; + playbackManager.stop(oldPlayer).then(() => { playbackManager.play({ - ids: [item.Id], + ids: playlistIds, serverId: item.ServerId, - startPositionTicks: resumePositionTicks - + startPositionTicks: resumePositionTicks, + startIndex: playlistIndex }, newPlayer); }); - } - - events.on(playbackManager, 'playerchange', function (e, newPlayer, newTarget, oldPlayer) { - - if (!oldPlayer || !newPlayer) { - return; - } + }); +} - if (!oldPlayer.isLocalPlayer) { - console.debug('Skipping remote control autoplay because oldPlayer is not a local player'); - return; - } +events.on(playbackManager, 'playerchange', (e, newPlayer, newTarget, oldPlayer) => { + if (!oldPlayer || !newPlayer) { + return; + } - if (newPlayer.isLocalPlayer) { - console.debug('Skipping remote control autoplay because newPlayer is a local player'); - return; - } + if (!oldPlayer.isLocalPlayer) { + console.debug('Skipping remote control autoplay because oldPlayer is not a local player'); + return; + } - transferPlayback(oldPlayer, newPlayer); - }); + if (newPlayer.isLocalPlayer) { + console.debug('Skipping remote control autoplay because newPlayer is a local player'); + return; + } + transferPlayback(oldPlayer, newPlayer); }); diff --git a/src/components/playback/volumeosd.js b/src/components/playback/volumeosd.js index 5d2c90ddb71..c2f6761f53e 100644 --- a/src/components/playback/volumeosd.js +++ b/src/components/playback/volumeosd.js @@ -1,158 +1,161 @@ -define(['events', 'playbackManager', 'dom', 'browser', 'css!./iconosd', 'material-icons'], function (events, playbackManager, dom, browser) { - 'use strict'; +import events from 'events'; +import playbackManager from 'playbackManager'; +import dom from 'dom'; +import browser from 'browser'; +import 'css!./iconosd'; +import 'material-icons'; - var currentPlayer; - var osdElement; - var iconElement; - var progressElement; +var currentPlayer; +var osdElement; +var iconElement; +var progressElement; - var enableAnimation; +var enableAnimation; - function getOsdElementHtml() { - var html = ''; +function getOsdElementHtml() { + var html = ''; - html += ''; + html += ''; - html += '
'; + html += '
'; - return html; - } + return html; +} - function ensureOsdElement() { +function ensureOsdElement() { - var elem = osdElement; - if (!elem) { + var elem = osdElement; + if (!elem) { - enableAnimation = browser.supportsCssAnimation(); + enableAnimation = browser.supportsCssAnimation(); - elem = document.createElement('div'); - elem.classList.add('hide'); - elem.classList.add('iconOsd'); - elem.classList.add('iconOsd-hidden'); - elem.classList.add('volumeOsd'); - elem.innerHTML = getOsdElementHtml(); + elem = document.createElement('div'); + elem.classList.add('hide'); + elem.classList.add('iconOsd'); + elem.classList.add('iconOsd-hidden'); + elem.classList.add('volumeOsd'); + elem.innerHTML = getOsdElementHtml(); - iconElement = elem.querySelector('i'); - progressElement = elem.querySelector('.iconOsdProgressInner'); + iconElement = elem.querySelector('.material-icons'); + progressElement = elem.querySelector('.iconOsdProgressInner'); - document.body.appendChild(elem); - osdElement = elem; - } + document.body.appendChild(elem); + osdElement = elem; } +} - function onHideComplete() { - this.classList.add('hide'); - } +function onHideComplete() { + this.classList.add('hide'); +} - var hideTimeout; - function showOsd() { +var hideTimeout; +function showOsd() { - clearHideTimeout(); + clearHideTimeout(); - var elem = osdElement; + var elem = osdElement; - dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { - once: true - }); + dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { + once: true + }); - elem.classList.remove('hide'); + elem.classList.remove('hide'); - // trigger reflow - void elem.offsetWidth; + // trigger reflow + void elem.offsetWidth; - requestAnimationFrame(function () { - elem.classList.remove('iconOsd-hidden'); + requestAnimationFrame(function () { + elem.classList.remove('iconOsd-hidden'); - hideTimeout = setTimeout(hideOsd, 3000); - }); - } + hideTimeout = setTimeout(hideOsd, 3000); + }); +} - function clearHideTimeout() { - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } +function clearHideTimeout() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; } +} - function hideOsd() { +function hideOsd() { - clearHideTimeout(); + clearHideTimeout(); - var elem = osdElement; - if (elem) { + var elem = osdElement; + if (elem) { - if (enableAnimation) { - // trigger reflow - void elem.offsetWidth; + if (enableAnimation) { + // trigger reflow + void elem.offsetWidth; - requestAnimationFrame(function () { - elem.classList.add('iconOsd-hidden'); + requestAnimationFrame(function () { + elem.classList.add('iconOsd-hidden'); - dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { - once: true - }); + dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, { + once: true }); - } else { - onHideComplete.call(elem); - } + }); + } else { + onHideComplete.call(elem); } } +} - function updatePlayerVolumeState(isMuted, volume) { +function updatePlayerVolumeState(isMuted, volume) { - if (iconElement) { - iconElement.innerHTML = isMuted ? '' : ''; - } - if (progressElement) { - progressElement.style.width = (volume || 0) + '%'; - } + if (iconElement) { + iconElement.classList.remove('volume_off', 'volume_up'); + iconElement.classList.add(isMuted ? 'volume_off' : 'volume_up'); + } + if (progressElement) { + progressElement.style.width = (volume || 0) + '%'; } +} - function releaseCurrentPlayer() { +function releaseCurrentPlayer() { - var player = currentPlayer; + var player = currentPlayer; - if (player) { - events.off(player, 'volumechange', onVolumeChanged); - events.off(player, 'playbackstop', hideOsd); - currentPlayer = null; - } + if (player) { + events.off(player, 'volumechange', onVolumeChanged); + events.off(player, 'playbackstop', hideOsd); + currentPlayer = null; } +} - function onVolumeChanged(e) { +function onVolumeChanged(e) { - var player = this; + var player = this; - ensureOsdElement(); + ensureOsdElement(); - updatePlayerVolumeState(player.isMuted(), player.getVolume()); + updatePlayerVolumeState(player.isMuted(), player.getVolume()); - showOsd(); - } + showOsd(); +} - function bindToPlayer(player) { +function bindToPlayer(player) { - if (player === currentPlayer) { - return; - } + if (player === currentPlayer) { + return; + } - releaseCurrentPlayer(); + releaseCurrentPlayer(); - currentPlayer = player; + currentPlayer = player; - if (!player) { - return; - } - - hideOsd(); - events.on(player, 'volumechange', onVolumeChanged); - events.on(player, 'playbackstop', hideOsd); + if (!player) { + return; } - events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); - }); + hideOsd(); + events.on(player, 'volumechange', onVolumeChanged); + events.on(player, 'playbackstop', hideOsd); +} +events.on(playbackManager, 'playerchange', function () { bindToPlayer(playbackManager.getCurrentPlayer()); - }); + +bindToPlayer(playbackManager.getCurrentPlayer()); diff --git a/src/components/playbacksettings/playbacksettings.js b/src/components/playbackSettings/playbackSettings.js similarity index 95% rename from src/components/playbacksettings/playbacksettings.js rename to src/components/playbackSettings/playbackSettings.js index d249b5b9899..06d2e38d2c1 100644 --- a/src/components/playbacksettings/playbacksettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -1,5 +1,5 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'qualityoptions', 'globalize', 'loading', 'connectionManager', 'dom', 'events', 'emby-select', 'emby-checkbox'], function (require, browser, appSettings, appHost, focusManager, qualityoptions, globalize, loading, connectionManager, dom, events) { - "use strict"; + 'use strict'; function fillSkipLengths(select) { @@ -17,15 +17,15 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'quality function populateLanguages(select, languages) { - var html = ""; + var html = ''; - html += ""; + html += "'; for (var i = 0, length = languages.length; i < length; i++) { var culture = languages[i]; - html += ""; + html += "'; } select.innerHTML = html; @@ -154,12 +154,12 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'quality populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures); - context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ""; + context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ''; context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; }); // hide cinema mode options if disabled at server level - apiClient.getNamedConfiguration("cinemamode").then(function (cinemaConfig) { + apiClient.getNamedConfiguration('cinemamode').then(function (cinemaConfig) { if (cinemaConfig.EnableIntrosForMovies || cinemaConfig.EnableIntrosForEpisodes) { context.querySelector('.cinemaModeOptions').classList.remove('hide'); @@ -204,6 +204,9 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'quality fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality')); + var selectChromecastVersion = context.querySelector('.selectChromecastVersion'); + selectChromecastVersion.value = userSettings.chromecastVersion(); + var selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); fillSkipLengths(selectSkipForwardLength); selectSkipForwardLength.value = userSettings.skipForwardLength(); @@ -234,6 +237,7 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'quality userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); + userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); @@ -285,7 +289,7 @@ define(['require', 'browser', 'appSettings', 'apphost', 'focusManager', 'quality function embed(options, self) { - require(['text!./playbacksettings.template.html'], function (template) { + require(['text!./playbackSettings.template.html'], function (template) { options.element.innerHTML = globalize.translateDocument(template, 'core'); diff --git a/src/components/playbacksettings/playbacksettings.template.html b/src/components/playbackSettings/playbackSettings.template.html similarity index 92% rename from src/components/playbacksettings/playbacksettings.template.html rename to src/components/playbackSettings/playbackSettings.template.html index 0c8b726a404..03af5d6faab 100644 --- a/src/components/playbacksettings/playbacksettings.template.html +++ b/src/components/playbackSettings/playbackSettings.template.html @@ -1,12 +1,13 @@
-

${HeaderAudioSettings}

+
+
+
+ +
+
diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 89adf22624d..a65baf3553d 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -1,4 +1,4 @@ -define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, playMethodHelper, layoutManager, serverNotifications) { +define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncPlayManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, syncPlayManager, playMethodHelper, layoutManager, serverNotifications) { 'use strict'; function init(instance) { @@ -18,7 +18,7 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (layoutManager.tv) { button = ''; } else { - button = ''; + button = ''; } var contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content'; @@ -132,7 +132,7 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (videoCodec) { sessionStats.push({ - label: globalize.translate("LabelVideoCodec"), + label: globalize.translate('LabelVideoCodec'), value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() }); } @@ -140,7 +140,7 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (audioCodec) { sessionStats.push({ - label: globalize.translate("LabelAudioCodec"), + label: globalize.translate('LabelAudioCodec'), value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() }); } @@ -157,28 +157,28 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (totalBitrate) { sessionStats.push({ - label: globalize.translate("LabelBitrate"), + label: globalize.translate('LabelBitrate'), value: getDisplayBitrate(totalBitrate) }); } if (session.TranscodingInfo.CompletionPercentage) { sessionStats.push({ - label: globalize.translate("LabelTranscodingProgress"), + label: globalize.translate('LabelTranscodingProgress'), value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' }); } if (session.TranscodingInfo.Framerate) { sessionStats.push({ - label: globalize.translate("LabelTranscodingFramerate"), + label: globalize.translate('LabelTranscodingFramerate'), value: session.TranscodingInfo.Framerate + ' fps' }); } if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { sessionStats.push({ - label: globalize.translate("LabelReasonForTranscoding"), + label: globalize.translate('LabelReasonForTranscoding'), value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') }); } @@ -216,14 +216,14 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (mediaSource.Container) { sessionStats.push({ - label: globalize.translate("LabelProfileContainer"), + label: globalize.translate('LabelProfileContainer'), value: mediaSource.Container }); } if (mediaFileSize) { sessionStats.push({ - label: globalize.translate("LabelSize"), + label: globalize.translate('LabelSize'), value: getReadableSize(mediaFileSize) }); } @@ -231,7 +231,7 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (totalBitrate) { sessionStats.push({ - label: globalize.translate("LabelBitrate"), + label: globalize.translate('LabelBitrate'), value: getDisplayBitrate(totalBitrate) }); } @@ -267,14 +267,14 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (videoInfos.length) { sessionStats.push({ - label: globalize.translate("LabelVideoCodec"), + label: globalize.translate('LabelVideoCodec'), value: videoInfos.join(' ') }); } if (videoStream.BitRate) { sessionStats.push({ - label: globalize.translate("LabelVideoBitrate"), + label: globalize.translate('LabelVideoBitrate'), value: getDisplayBitrate(videoStream.BitRate) }); } @@ -291,35 +291,35 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth if (audioInfos.length) { sessionStats.push({ - label: globalize.translate("LabelAudioCodec"), + label: globalize.translate('LabelAudioCodec'), value: audioInfos.join(' ') }); } if (audioStream.BitRate) { sessionStats.push({ - label: globalize.translate("LabelAudioBitrate"), + label: globalize.translate('LabelAudioBitrate'), value: getDisplayBitrate(audioStream.BitRate) }); } if (audioChannels) { sessionStats.push({ - label: globalize.translate("LabelAudioChannels"), + label: globalize.translate('LabelAudioChannels'), value: audioChannels }); } if (audioStream.SampleRate) { sessionStats.push({ - label: globalize.translate("LabelAudioSampleRate"), + label: globalize.translate('LabelAudioSampleRate'), value: audioStream.SampleRate + ' Hz' }); } if (audioStream.BitDepth) { sessionStats.push({ - label: globalize.translate("LabelAudioBitDepth"), + label: globalize.translate('LabelAudioBitDepth'), value: audioStream.BitDepth }); } @@ -327,6 +327,28 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth return sessionStats; } + function getSyncPlayStats() { + var syncStats = []; + var stats = syncPlayManager.getStats(); + + syncStats.push({ + label: globalize.translate('LabelSyncPlayTimeOffset'), + value: stats.TimeOffset + 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) { var statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); @@ -346,12 +368,12 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth }; baseCategory.stats.unshift({ - label: globalize.translate("LabelPlayMethod"), + label: globalize.translate('LabelPlayMethod'), value: displayPlayMethod }); baseCategory.stats.unshift({ - label: globalize.translate("LabelPlayer"), + label: globalize.translate('LabelPlayer'), value: player.name }); @@ -383,6 +405,13 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth name: 'Original Media Info' }); + if (syncPlayManager.isSyncPlayEnabled()) { + categories.push({ + stats: getSyncPlayStats(), + name: 'SyncPlay Info' + }); + } + return Promise.resolve(categories); }); } diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index c274b407999..56d7142ad37 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -1,24 +1,10 @@ -define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', 'connectionManager', 'userSettings', 'appRouter', 'globalize', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (shell, dialogHelper, loading, layoutManager, playbackManager, connectionManager, userSettings, appRouter, globalize) { +define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', 'connectionManager', 'userSettings', 'appRouter', 'globalize', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, playbackManager, connectionManager, userSettings, appRouter, globalize) { 'use strict'; var currentServerId; - function parentWithClass(elem, className) { - - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - function onSubmit(e) { - - var panel = parentWithClass(this, 'dialog'); + var panel = dom.parentWithClass(this, 'dialog'); var playlistId = panel.querySelector('#selectPlaylistToAddTo').value; var apiClient = connectionManager.getApiClient(currentServerId); @@ -35,11 +21,9 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } function createPlaylist(apiClient, dlg) { - loading.show(); - var url = apiClient.getUrl("Playlists", { - + var url = apiClient.getUrl('Playlists', { Name: dlg.querySelector('#txtNewPlaylistName').value, Ids: dlg.querySelector('.fldSelectedItemIds').value || '', userId: apiClient.getCurrentUserId() @@ -47,12 +31,10 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', }); apiClient.ajax({ - type: "POST", + type: 'POST', url: url, - dataType: "json" - + dataType: 'json' }).then(function (result) { - loading.hide(); var id = result.Id; @@ -63,16 +45,13 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } function redirectToPlaylist(apiClient, id) { - appRouter.showItem(id, apiClient.serverId()); } function addToPlaylist(apiClient, dlg, id) { - var itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; if (id === 'queue') { - playbackManager.queue({ serverId: apiClient.serverId(), ids: itemIds.split(',') @@ -84,18 +63,16 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', loading.show(); - var url = apiClient.getUrl("Playlists/" + id + "/Items", { - + var url = apiClient.getUrl('Playlists/' + id + '/Items', { Ids: itemIds, userId: apiClient.getCurrentUserId() }); apiClient.ajax({ - type: "POST", + type: 'POST', url: url }).then(function () { - loading.hide(); dlg.submitted = true; @@ -108,7 +85,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } function populatePlaylists(editorOptions, panel) { - var select = panel.querySelector('#selectPlaylistToAddTo'); loading.hide(); @@ -116,16 +92,14 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', panel.querySelector('.newPlaylistInfo').classList.add('hide'); var options = { - Recursive: true, - IncludeItemTypes: "Playlist", + IncludeItemTypes: 'Playlist', SortBy: 'SortName', EnableTotalRecordCount: false }; var apiClient = connectionManager.getApiClient(currentServerId); apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) { - var html = ''; if (editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) { @@ -135,7 +109,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', html += ''; html += result.Items.map(function (i) { - return ''; }); @@ -159,7 +132,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } function getEditorHtml(items) { - var html = ''; html += '
'; @@ -195,7 +167,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } function initEditor(content, options, items) { - content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { if (this.value) { content.querySelector('.newPlaylistInfo').classList.add('hide'); @@ -235,7 +206,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } PlaylistEditor.prototype.show = function (options) { - var items = options.items || {}; currentServerId = options.serverId; @@ -258,7 +228,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', var title = globalize.translate('HeaderAddToPlaylist'); html += '
'; - html += ''; + html += ''; html += '

'; html += title; html += '

'; @@ -272,7 +242,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', initEditor(dlg, options, items); dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); }); @@ -281,7 +250,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', } return dialogHelper.open(dlg).then(function () { - if (layoutManager.tv) { centerFocus(dlg.querySelector('.formDialogContent'), false, false); } diff --git a/src/components/playmenu.js b/src/components/playmenu.js index a410da6985c..50c2a7b31f8 100644 --- a/src/components/playmenu.js +++ b/src/components/playmenu.js @@ -1,75 +1,71 @@ -define(['actionsheet', 'datetime', 'playbackManager', 'globalize', 'appSettings'], function (actionsheet, datetime, playbackManager, globalize, appSettings) { - 'use strict'; +import actionsheet from 'actionsheet'; +import datetime from 'datetime'; +import playbackManager from 'playbackManager'; +import globalize from 'globalize'; - function show(options) { +export function show(options) { - var item = options.item; + var item = options.item; - var itemType = item.Type; - var isFolder = item.IsFolder; - var itemId = item.Id; - var channelId = item.ChannelId; - var serverId = item.ServerId; - var resumePositionTicks = item.UserData ? item.UserData.PlaybackPositionTicks : null; + var resumePositionTicks = item.UserData ? item.UserData.PlaybackPositionTicks : null; - var playableItemId = itemType === 'Program' ? channelId : itemId; + var playableItemId = item.Type === 'Program' ? item.ChannelId : item.Id; - if (!resumePositionTicks || isFolder) { - playbackManager.play({ - ids: [playableItemId], - serverId: serverId - }); - return; - } + if (!resumePositionTicks || item.IsFolder) { + playbackManager.play({ + ids: [playableItemId], + serverId: item.ServerId + }); + return; + } - var menuItems = []; + var menuItems = []; - menuItems.push({ - name: globalize.translate('ResumeAt', datetime.getDisplayRunningTime(resumePositionTicks)), - id: 'resume' - }); + menuItems.push({ + name: globalize.translate('ResumeAt', datetime.getDisplayRunningTime(resumePositionTicks)), + id: 'resume' + }); - menuItems.push({ - name: globalize.translate('PlayFromBeginning'), - id: 'play' - }); + menuItems.push({ + name: globalize.translate('PlayFromBeginning'), + id: 'play' + }); - actionsheet.show({ + actionsheet.show({ - items: menuItems, - positionTo: options.positionTo + items: menuItems, + positionTo: options.positionTo - }).then(function (id) { - switch (id) { + }).then(function (id) { + switch (id) { - case 'play': - playbackManager.play({ - ids: [playableItemId], - serverId: serverId - }); - break; - case 'resume': - playbackManager.play({ - ids: [playableItemId], - startPositionTicks: resumePositionTicks, - serverId: serverId - }); - break; - case 'queue': - playbackManager.queue({ - items: [item] - }); - break; - case 'shuffle': - playbackManager.shuffle(item); - break; - default: - break; - } - }); - } + case 'play': + playbackManager.play({ + ids: [playableItemId], + serverId: item.ServerId + }); + break; + case 'resume': + playbackManager.play({ + ids: [playableItemId], + startPositionTicks: resumePositionTicks, + serverId: item.ServerId + }); + break; + case 'queue': + playbackManager.queue({ + items: [item] + }); + break; + case 'shuffle': + playbackManager.shuffle(item); + break; + default: + break; + } + }); +} - return { - show: show - }; -}); +export default { + show: show +}; diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index 2126d73b3c4..fd35d344bfd 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -1,10 +1,10 @@ -define(['events'], function (events) { +define(['events', 'globalize'], function (events, globalize) { 'use strict'; // TODO: replace with each plugin version var cacheParam = new Date().getTime(); - function loadStrings(plugin, globalize) { + function loadStrings(plugin) { var strings = plugin.getTranslations ? plugin.getTranslations() : []; return globalize.loadStrings({ name: plugin.id || plugin.packageName, @@ -25,68 +25,78 @@ define(['events'], function (events) { this.pluginsList = []; } - PluginManager.prototype.loadPlugin = function (url) { + PluginManager.prototype.loadPlugin = function(pluginSpec) { - console.debug('Loading plugin: ' + url); var instance = this; - return new Promise(function (resolve, reject) { + function registerPlugin(plugin) { + instance.register(plugin); - require([url, 'globalize', 'appRouter'], function (pluginFactory, globalize, appRouter) { - - var plugin = new pluginFactory(); - - // See if it's already installed - var existing = instance.pluginsList.filter(function (p) { - return p.id === plugin.id; - })[0]; + if (plugin.getRoutes) { + plugin.getRoutes().forEach(function (route) { + definePluginRoute(instance, route, plugin); + }); + } + + if (plugin.type === 'skin') { + + // translations won't be loaded for skins until needed + return Promise.resolve(plugin); + } else { + return new Promise((resolve, reject) => { + loadStrings(plugin) + .then(function () { + resolve(plugin); + }) + .catch(reject); + }); + } + } - if (existing) { - resolve(url); - return; - } + if (typeof pluginSpec === 'string') { + console.debug('Loading plugin (via deprecated requirejs method): ' + pluginSpec); - plugin.installUrl = url; + return new Promise(function (resolve, reject) { + require([pluginSpec], (pluginFactory) => { + var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory(); - var urlLower = url.toLowerCase(); - if (urlLower.indexOf('http:') === -1 && urlLower.indexOf('https:') === -1 && urlLower.indexOf('file:') === -1) { - if (url.indexOf(appRouter.baseUrl()) !== 0) { + // See if it's already installed + var existing = instance.pluginsList.filter(function (p) { + return p.id === plugin.id; + })[0]; - url = appRouter.baseUrl() + '/' + url; + if (existing) { + resolve(pluginSpec); } - } - - var separatorIndex = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\')); - plugin.baseUrl = url.substring(0, separatorIndex); - var paths = {}; - paths[plugin.id] = plugin.baseUrl; + plugin.installUrl = pluginSpec; - requirejs.config({ - waitSeconds: 0, - paths: paths - }); + var separatorIndex = Math.max(pluginSpec.lastIndexOf('/'), pluginSpec.lastIndexOf('\\')); + plugin.baseUrl = pluginSpec.substring(0, separatorIndex); - instance.register(plugin); + var paths = {}; + paths[plugin.id] = plugin.baseUrl; - if (plugin.getRoutes) { - plugin.getRoutes().forEach(function (route) { - definePluginRoute(instance, route, plugin); + requirejs.config({ + waitSeconds: 0, + paths: paths }); - } - if (plugin.type === 'skin') { - - // translations won't be loaded for skins until needed - resolve(plugin); - } else { - - loadStrings(plugin, globalize).then(function () { - resolve(plugin); - }, reject); - } + registerPlugin(plugin).then(resolve).catch(reject); + }); }); - }); + } else if (pluginSpec.then) { + return pluginSpec.then(pluginBuilder => { + return pluginBuilder(); + }).then(plugin => { + console.debug(`Plugin loaded: ${plugin.id}`); + return registerPlugin(plugin); + }); + } else { + const err = new Error('Plugins have to be a Promise that resolves to a plugin builder function or a requirejs urls (deprecated)'); + console.error(err); + return Promise.reject(err); + } }; // In lieu of automatic discovery, plugins will register dynamic objects diff --git a/src/components/polyfills/objectassign.js b/src/components/polyfills/objectassign.js deleted file mode 100644 index 85f55aa1442..00000000000 --- a/src/components/polyfills/objectassign.js +++ /dev/null @@ -1,24 +0,0 @@ -if (typeof Object.assign != 'function') { - (function () { - Object.assign = function (target) { - 'use strict'; - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; - if (source !== undefined && source !== null) { - for (var nextKey in source) { - // eslint-disable-next-line no-prototype-builtins - if (source.hasOwnProperty(nextKey)) { - output[nextKey] = source[nextKey]; - } - } - } - } - return output; - }; - })(); -} diff --git a/src/components/prompt/prompt.js b/src/components/prompt/prompt.js index 41d40c4a485..a76083cf047 100644 --- a/src/components/prompt/prompt.js +++ b/src/components/prompt/prompt.js @@ -1,4 +1,4 @@ -define(["browser", 'dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function(browser, dialogHelper, layoutManager, scrollHelper, globalize, dom, require) { +define(['browser', 'dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function(browser, dialogHelper, layoutManager, scrollHelper, globalize, dom, require) { 'use strict'; function replaceAll(str, find, replace) { diff --git a/src/components/prompt/prompt.template.html b/src/components/prompt/prompt.template.html index 23b918a1f22..a07629ae7ea 100644 --- a/src/components/prompt/prompt.template.html +++ b/src/components/prompt/prompt.template.html @@ -1,13 +1,13 @@
+

-
@@ -19,7 +19,6 @@

-
diff --git a/src/components/qualityoptions.js b/src/components/qualityOptions.js similarity index 96% rename from src/components/qualityoptions.js rename to src/components/qualityOptions.js index 5ad69cb7905..221e13d4ef1 100644 --- a/src/components/qualityoptions.js +++ b/src/components/qualityOptions.js @@ -5,6 +5,13 @@ define(['globalize'], function (globalize) { var maxStreamingBitrate = options.currentMaxBitrate; var videoWidth = options.videoWidth; + var videoHeight = options.videoHeight; + + // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. + // 4:3 1440x1080 -> 1920x1080 + if (videoWidth / videoHeight < 16 / 9) { + videoWidth = videoHeight * (16 / 9); + } var maxAllowedWidth = videoWidth || 4096; //var maxAllowedHeight = videoHeight || 2304; diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js index e9c174bfd6f..c4bfc430108 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -22,7 +22,7 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' } function setButtonIcon(button, icon) { - var inner = button.querySelector('i'); + var inner = button.querySelector('.material-icons'); inner.classList.remove('fiber_smart_record'); inner.classList.remove('fiber_manual_record'); inner.classList.add(icon); diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index a51fac4305a..ae26d0debc5 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -36,14 +36,14 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c if (imageTags.Primary) { return apiClient.getScaledImageUrl(item.Id, { - type: "Primary", + type: 'Primary', maxHeight: imageHeight, tag: item.ImageTags.Primary }); } else if (imageTags.Thumb) { return apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", + type: 'Thumb', maxHeight: imageHeight, tag: item.ImageTags.Thumb }); diff --git a/src/components/recordingcreator/recordingcreator.template.html b/src/components/recordingcreator/recordingcreator.template.html index b99ead300a6..8d7d75c8f6e 100644 --- a/src/components/recordingcreator/recordingcreator.template.html +++ b/src/components/recordingcreator/recordingcreator.template.html @@ -1,5 +1,5 @@
- +

diff --git a/src/components/recordingcreator/recordingeditor.template.html b/src/components/recordingcreator/recordingeditor.template.html index 48256da0a27..f7a9f98ee6b 100644 --- a/src/components/recordingcreator/recordingeditor.template.html +++ b/src/components/recordingcreator/recordingeditor.template.html @@ -1,5 +1,5 @@
- +

${HeaderRecordingOptions}

diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index 106fec36bf6..b4cbdfab0a1 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -151,7 +151,7 @@ define(['globalize', 'connectionManager', 'serverNotifications', 'require', 'loa var apiClient = connectionManager.getApiClient(options.serverId); var button = dom.parentWithTag(e.target, 'BUTTON'); - var isChecked = !button.querySelector('i').classList.contains('recordingIcon-active'); + var isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); var hasEnabledTimer = this.TimerId && this.Status !== 'Cancelled'; @@ -191,7 +191,7 @@ define(['globalize', 'connectionManager', 'serverNotifications', 'require', 'loa var apiClient = connectionManager.getApiClient(options.serverId); var button = dom.parentWithTag(e.target, 'BUTTON'); - var isChecked = !button.querySelector('i').classList.contains('recordingIcon-active'); + var isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); if (isChecked) { options.parent.querySelector('.recordSeriesContainer').classList.remove('hide'); diff --git a/src/components/recordingcreator/recordingfields.template.html b/src/components/recordingcreator/recordingfields.template.html index ce359607165..25a0c3c30d9 100644 --- a/src/components/recordingcreator/recordingfields.template.html +++ b/src/components/recordingcreator/recordingfields.template.html @@ -2,7 +2,7 @@
@@ -14,7 +14,7 @@
diff --git a/src/components/recordingcreator/seriesrecordingeditor.template.html b/src/components/recordingcreator/seriesrecordingeditor.template.html index fa9f16561b8..582c1a633cc 100644 --- a/src/components/recordingcreator/seriesrecordingeditor.template.html +++ b/src/components/recordingcreator/seriesrecordingeditor.template.html @@ -1,5 +1,5 @@
- +

${HeaderSeriesOptions}

diff --git a/src/components/refreshdialog/refreshdialog.js b/src/components/refreshdialog/refreshdialog.js index b5730e592a8..3edb725c4a0 100644 --- a/src/components/refreshdialog/refreshdialog.js +++ b/src/components/refreshdialog/refreshdialog.js @@ -1,19 +1,6 @@ -define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) { +define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) { 'use strict'; - function parentWithClass(elem, className) { - - while (!elem.classList || !elem.classList.contains(className)) { - elem = elem.parentNode; - - if (!elem) { - return null; - } - } - - return elem; - } - function getEditorHtml() { var html = ''; @@ -65,7 +52,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager' loading.show(); var instance = this; - var dlg = parentWithClass(e.target, 'dialog'); + var dlg = dom.parentWithClass(e.target, 'dialog'); var options = instance.options; var apiClient = connectionManager.getApiClient(options.serverId); @@ -123,7 +110,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager' var title = globalize.translate('RefreshMetadata'); html += '
'; - html += ''; + html += ''; html += '

'; html += title; html += '

'; diff --git a/src/components/remotecontrol/remotecontrol.css b/src/components/remotecontrol/remotecontrol.css index 83a1c48e5f4..c2607995859 100644 --- a/src/components/remotecontrol/remotecontrol.css +++ b/src/components/remotecontrol/remotecontrol.css @@ -1,3 +1,7 @@ +.nowPlayingPage { + padding: 5em 0 0 0 !important; +} + .nowPlayingInfoContainer { display: -webkit-box; display: -webkit-flex; @@ -36,8 +40,30 @@ margin: 0 0 0.5em 0.5em; } +.nowPlayingAlbum a, +.nowPlayingArtist a { + font-weight: normal; + text-align: left !important; + color: inherit !important; +} + +.nowPlayingButtonsContainer { + display: flex; +} + +.nowPlayingInfoContainerMedia { + text-align: left; + margin-bottom: 1em; +} + +.nowPlayingPositionSlider { + width: stretch; +} + .nowPlayingPositionSliderContainer { - margin: 0.7em 0 0.7em 1em; + margin: 0.2em 1em 0.2em 1em; + width: 100%; + z-index: 0; } .nowPlayingInfoButtons { @@ -59,17 +85,32 @@ } .nowPlayingPageImageContainer { - width: 20%; - margin-right: 0.25em; + width: 16%; + margin-right: 1em; position: relative; -webkit-flex-shrink: 0; flex-shrink: 0; } -@media all and (min-width: 50em) { - .nowPlayingPageImageContainer { - width: 16%; - } +.nowPlayingPageImageContainerNoAlbum { + width: 100%; + position: relative; +} + +.nowPlayingPageImageContainerNoAlbum button { + cursor: default; +} + +.nowPlayingPageImageContainerNoAlbum::after { + content: ""; + display: block; + padding-bottom: 100%; +} + +.btnPlayPause { + font-size: xx-large; + padding: 0; + margin: 0; } .nowPlayingInfoControls { @@ -87,14 +128,15 @@ } .nowPlayingPageImage { + display: block; bottom: 0; left: 0; right: 0; + margin: 0 auto; width: 100%; -webkit-box-shadow: 0 0 1.9vh #000; box-shadow: 0 0 1.9vh #000; border: 0.1em solid #222; - user-drag: none; user-select: none; -moz-user-select: none; -webkit-user-drag: none; @@ -102,7 +144,144 @@ -ms-user-select: none; } -@media all and (orientation: portrait) and (max-width: 50em) { +.contextMenuList { + padding: 1.5em 0; +} + +.contextMenuList a { + color: inherit !important; +} + +.contextMenuList .material-icons.listItemIcon { + font-size: x-large; +} + +.nowPlayingSecondaryButtons { + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: flex-end; + z-index: 0; +} + +.layout-mobile .playlistSectionButtonTransparent { + background: rgba(0, 0, 0, 0) !important; +} + +.layout-mobile .playlistSection .playlist, +.layout-mobile .playlistSection .contextMenu { + position: absolute; + top: 12.2em; + bottom: 4.2em; + overflow: scroll; + padding: 0 1em; + display: inline-block; + left: 0; + right: 0; + z-index: 1000; +} + +.layout-mobile .playlistSectionButton { + position: fixed; + bottom: 0; + left: 0; + height: 4.2em; + right: 0; + padding-left: 7.3%; + padding-right: 7.3%; +} + +.layout-desktop .playlistSectionButton, +.layout-tv .playlistSectionButton { + background: none; +} + +.layout-desktop .nowPlayingPlaylist, +.layout-tv .nowPlayingPlaylist { + background: none; +} + +.layout-mobile .playlistSectionButton .btnTogglePlaylist { + font-size: larger; + margin: 0; +} + +.layout-mobile .playlistSectionButton .btnSavePlaylist { + margin: 0; + border-radius: 0; +} + +.layout-mobile .playlistSectionButton .volumecontrol { + margin: 0; + padding-right: 0; + border-radius: 0; +} + +.layout-mobile .playlistSectionButton .btnToggleContextMenu { + font-size: larger; + margin: 0; +} + +.layout-mobile .nowPlayingSecondaryButtons .btnShuffleQueue { + display: none; +} + +.layout-mobile .nowPlayingSecondaryButtons .volumecontrol { + display: none; +} + +.layout-mobile .nowPlayingSecondaryButtons .btnRepeat { + display: none; +} + +.layout-desktop .nowPlayingInfoButtons .btnRepeat, +.layout-tv .nowPlayingInfoButtons .btnRepeat { + display: none; +} + +.layout-desktop .nowPlayingInfoButtons .btnShuffleQueue, +.layout-tv .nowPlayingInfoButtons .btnShuffleQueue { + display: none; +} + +.layout-desktop .playlistSectionButton .volumecontrol, +.layout-tv .playlistSectionButton .volumecontrol { + display: none; +} + +.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle { + display: none; +} + +.layout-mobile .nowPlayingPageUserDataButtons { + display: none; +} + +@media all and (min-width: 63em) { + .nowPlayingPage { + padding: 8em 0 0 0 !important; + } +} + +@media all and (min-width: 80em) { + .nowPlayingPageImageContainer { + margin-right: 0.75em; + } +} + +@media all and (orientation: portrait) and (max-width: 43em) { + .remoteControlContent { + padding-left: 7.3% !important; + padding-right: 7.3% !important; + display: flex; + height: 100%; + flex-direction: column; + } + + .layout-desktop .nowPlayingPageUserDataButtons { + display: none; + } + .nowPlayingInfoContainer { -webkit-box-orient: vertical !important; -webkit-box-direction: normal !important; @@ -111,42 +290,143 @@ -webkit-box-align: center; -webkit-align-items: center; align-items: center; + width: 100%; + height: calc(100% - 4.2em); } .nowPlayingPageTitle { - text-align: center; - margin: 0.5em 0 0.75em; + /* text-align: center; */ + margin: 0; + } + + .nowPlayingAlbum, + .nowPlayingArtist { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .nowPlayingInfoContainerMedia { + text-align: left !important; + width: 80%; } .nowPlayingPositionSliderContainer { - margin: 0.7em 1em; + margin: 0.2em 1em 0.2em 1em; } .nowPlayingInfoButtons { + /* margin: 1.5em 0 0 0; */ -webkit-box-pack: center; -webkit-justify-content: center; justify-content: center; + font-size: x-large; + height: 100%; } .nowPlayingPageImageContainer { - width: auto; - margin-right: 0; + width: 100%; + margin: auto auto 0.5em; + } + + .nowPlayingPageImageContainerNoAlbum .cardImageContainer .cardImageIcon { + font-size: 15em; + color: inherit; } .nowPlayingInfoControls { - margin-top: 1em; - max-width: 100%; + margin: 0.5em 0 1em 0; + width: 100%; + -webkit-box-pack: start !important; + -webkit-justify-content: start !important; + justify-content: start !important; + } + + .nowPlayingSecondaryButtons { + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle { + width: 20%; + font-size: large; + display: unset; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { + padding-top: 0; + padding-right: 0; + margin-right: 0; + float: right; + border-radius: 0; + } + + .nowPlayingInfoButtons .btnRepeat { + position: absolute; + left: 0; + margin-left: 0; + padding-left: 7.3%; + font-size: smaller; + } + + .nowPlayingInfoButtons .btnShuffleQueue { + position: absolute; + right: 0; + margin-right: 0; + padding-right: 7.3%; + font-size: smaller; + } + + .paper-icon-button-light:hover { + color: #fff !important; + background-color: transparent !important; + } + + .btnPlayPause { + padding: 0; + margin: 0; + font-size: 1.7em; + } + + .btnPlayPause:hover { + background-color: transparent !important; } .nowPlayingPageImage { + /* width: inherit; */ + overflow-y: hidden; + overflow: hidden; + margin: 0 auto; + } + + .nowPlayingPageImage.nowPlayingPageImageAudio { + width: 100%; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster { + height: 50%; + overflow: hidden; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster img { + height: 100%; width: auto; - height: 36vh; } -} -@media all and (orientation: portrait) and (max-width: 40em) { - .nowPlayingPageImage { - height: 30vh; + .playlistSectionButton .volumecontrol { + width: 100%; + } + + .remoteControlSection { + margin: 4.2em 0 0 0; + padding: 0 0 4.2em 0; + } + + .nowPlayingButtonsContainer { + display: flex; + height: 100%; + flex-direction: column; } } @@ -158,37 +438,6 @@ margin: 0 1em; } -.nowPlayingSecondaryButtons { - display: -webkit-box; - display: -webkit-flex; - display: flex; - -webkit-box-align: center; - -webkit-align-items: center; - align-items: center; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; - -webkit-box-pack: center; - -webkit-justify-content: center; - justify-content: center; -} - -@media all and (min-width: 50em) { - .nowPlayingSecondaryButtons { - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - flex-grow: 1; - -webkit-box-pack: end; - -webkit-justify-content: flex-end; - justify-content: flex-end; - } -} - -@media all and (min-width: 80em) { - .nowPlayingPageImageContainer { - margin-right: 0.75em; - } -} - .nowPlayingNavButtonContainer { width: 30em; } @@ -206,6 +455,10 @@ background-image: url(../../assets/img/equalizer.gif) !important; } +.playlistIndexIndicatorImage > * { + display: none; +} + .hideVideoButtons .videoButton { display: none; } @@ -214,26 +467,14 @@ width: 9em; } -@media all and (max-width: 50em) { - .nowPlayingInfoButtons .nowPlayingPageUserDataButtons { +@media all and (max-width: 63em) { + .nowPlayingSecondaryButtons .repeatToggleButton, + .nowPlayingInfoButtons .playlist .listItemMediaInfo, + .nowPlayingInfoButtons .btnStop { display: none !important; } - .navigationSection .collapseContent i { + .navigationSection .collapseContent .material-icons { font-size: 4em; } } - -@media all and (max-width: 47em) { - .nowPlayingInfoButtons .repeatToggleButton { - display: none !important; - } -} - -@media all and (max-width: 34em) { - .nowPlayingInfoButtons .btnNowPlayingFastForward, - .nowPlayingInfoButtons .btnNowPlayingRewind, - .nowPlayingInfoButtons .playlist .listItemMediaInfo { - display: none !important; - } -} diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 7b620d536a8..949276337fb 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,5 +1,7 @@ -define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageLoader", "playbackManager", "nowPlayingHelper", "events", "connectionManager", "apphost", "globalize", "layoutManager", "userSettings", "cardStyle", "emby-itemscontainer", "css!./remotecontrol.css", "emby-ratingbutton"], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings) { - "use strict"; +define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) { + 'use strict'; + var showMuteButton = true; + var showVolumeSlider = true; function showAudioMenu(context, player, button, item) { var currentIndex = playbackManager.getAudioStreamIndex(player); @@ -17,7 +19,7 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL return menuItem; }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: button, @@ -45,11 +47,11 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL }); menuItems.unshift({ id: -1, - name: globalize.translate("ButtonOff"), + name: globalize.translate('ButtonOff'), selected: null == currentIndex }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: button, @@ -63,22 +65,22 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { return i.text; - }).join("
"); + }).join('
'); } function seriesImageUrl(item, options) { - if ("Episode" !== item.Type) { + if ('Episode' !== item.Type) { return null; } options = options || {}; - options.type = options.type || "Primary"; - if ("Primary" === options.type && item.SeriesPrimaryImageTag) { + options.type = options.type || 'Primary'; + if ('Primary' === options.type && item.SeriesPrimaryImageTag) { options.tag = item.SeriesPrimaryImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - if ("Thumb" === options.type) { + if ('Thumb' === options.type) { if (item.SeriesThumbImageTag) { options.tag = item.SeriesThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); @@ -95,7 +97,7 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function imageUrl(item, options) { options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { options.tag = item.ImageTags[options.type]; @@ -110,85 +112,163 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL return null; } - function updateNowPlayingInfo(context, state) { + function updateNowPlayingInfo(context, state, serverId) { var item = state.NowPlayingItem; - var displayName = item ? getNowPlayingNameHtml(item).replace("
", " - ") : ""; - context.querySelector(".nowPlayingPageTitle").innerHTML = displayName; - - if (displayName.length > 0) { - context.querySelector(".nowPlayingPageTitle").classList.remove("hide"); - } else { - context.querySelector(".nowPlayingPageTitle").classList.add("hide"); - } + var displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; + if (typeof item !== 'undefined') { + var nowPlayingServerId = (item.ServerId || serverId); + if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { + var songName = item.Name; + if (item.Album != null && item.Artists != null) { + var artistsSeries = ''; + var albumName = item.Album; + if (item.ArtistItems != null) { + for (const artist of item.ArtistItems) { + let artistName = artist.Name; + let artistId = artist.Id; + artistsSeries += `${artistName}`; + if (artist !== item.ArtistItems.slice(-1)[0]) { + artistsSeries += ', '; + } + } + } else if (item.Artists) { + // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback + // to normal item.Artists item. + // TODO: Normalise fields returned by all the players + for (const artist of item.Artists) { + artistsSeries += `${artist}`; + if (artist !== item.Artists.slice(-1)[0]) { + artistsSeries += ', '; + } + } + } + context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; + context.querySelector('.nowPlayingAlbum').innerHTML = '${albumName}`; + } + context.querySelector('.nowPlayingSongName').innerHTML = songName; + } else if (item.Type == 'Episode') { + if (item.SeasonName != null) { + var seasonName = item.SeasonName; + context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; + } + if (item.SeriesName != null) { + var seriesName = item.SeriesName; + if (item.SeriesId != null) { + context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; + } else { + context.querySelector('.nowPlayingSerie').innerHTML = seriesName; + } + } + context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; + } else { + context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; + } - var url = item ? seriesImageUrl(item, { - maxHeight: 300 * 2 - }) || imageUrl(item, { - maxHeight: 300 * 2 - }) : null; + if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { + context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); + } else { + context.querySelector('.nowPlayingPageTitle').classList.add('hide'); + } - console.debug("updateNowPlayingInfo"); - setImageUrl(context, url); - if (item) { - backdrop.setBackdrops([item]); + var url = item ? seriesImageUrl(item, { + maxHeight: 300 * 2 + }) || imageUrl(item, { + maxHeight: 300 * 2 + }) : null; + + let contextButton = context.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const autoFocusContextButton = document.activeElement === contextButton; + let contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = context.querySelector('.btnToggleContextMenu'); + if (autoFocusContextButton) { + contextButton.focus(); + } + const stopPlayback = !!layoutManager.mobile; + var options = { + play: false, + queue: false, + stopPlayback: stopPlayback, + clearQueue: true, + openAlbum: false, + positionTo: contextButton + }; var apiClient = connectionManager.getApiClient(item.ServerId); apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - var userData = fullItem.UserData || {}; - var likes = null == userData.Likes ? "" : userData.Likes; - context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ''; + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: fullItem, + user: user + }, options)); + }); + }); }); - } else { - backdrop.clear(); - context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ""; + setImageUrl(context, state, url); + if (item) { + backdrop.setBackdrops([item]); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + var userData = fullItem.UserData || {}; + var likes = null == userData.Likes ? '' : userData.Likes; + context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + }); + } else { + backdrop.clear(); + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + } } } - function setImageUrl(context, url) { + function setImageUrl(context, state, url) { currentImgUrl = url; - var imgContainer = context.querySelector(".nowPlayingPageImageContainer"); + var item = state.NowPlayingItem; + var imgContainer = context.querySelector('.nowPlayingPageImageContainer'); if (url) { imgContainer.innerHTML = ''; - imgContainer.classList.remove("hide"); + if (item.Type == 'Audio') { + context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); + context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); + } else { + context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); + context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); + } } else { - imgContainer.classList.add("hide"); - imgContainer.innerHTML = ""; + imgContainer.innerHTML = '
'; } } function buttonVisible(btn, enabled) { if (enabled) { - btn.classList.remove("hide"); + btn.classList.remove('hide'); } else { - btn.classList.add("hide"); + btn.classList.add('hide'); } } function updateSupportedCommands(context, commands) { - var all = context.querySelectorAll(".btnCommand"); + var all = context.querySelectorAll('.btnCommand'); for (var i = 0, length = all.length; i < length; i++) { - var enableButton = -1 !== commands.indexOf(all[i].getAttribute("data-command")); + var enableButton = -1 !== commands.indexOf(all[i].getAttribute('data-command')); all[i].disabled = !enableButton; } } var currentImgUrl; return function () { - function toggleRepeat(player) { - if (player) { - switch (playbackManager.getRepeatMode(player)) { - case "RepeatNone": - playbackManager.setRepeatMode("RepeatAll", player); - break; - - case "RepeatAll": - playbackManager.setRepeatMode("RepeatOne", player); - break; - - case "RepeatOne": - playbackManager.setRepeatMode("RepeatNone", player); - } + function toggleRepeat() { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); } } @@ -199,34 +279,46 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL var supportedCommands = playerInfo.supportedCommands; currentPlayerSupportedCommands = supportedCommands; var playState = state.PlayState || {}; - buttonVisible(context.querySelector(".btnToggleFullscreen"), item && "Video" == item.MediaType && -1 != supportedCommands.indexOf("ToggleFullscreen")); + var isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); + buttonVisible(context.querySelector('.btnToggleFullscreen'), item && 'Video' == item.MediaType && supportedCommands.includes('ToggleFullscreen')); updateAudioTracksDisplay(player, context); updateSubtitleTracksDisplay(player, context); - if (-1 != supportedCommands.indexOf("DisplayMessage") && !currentPlayer.isLocalPlayer) { - context.querySelector(".sendMessageSection").classList.remove("hide"); + if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendMessageSection').classList.remove('hide'); } else { - context.querySelector(".sendMessageSection").classList.add("hide"); + context.querySelector('.sendMessageSection').classList.add('hide'); } - if (-1 != supportedCommands.indexOf("SendString") && !currentPlayer.isLocalPlayer) { - context.querySelector(".sendTextSection").classList.remove("hide"); + if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendTextSection').classList.remove('hide'); } else { - context.querySelector(".sendTextSection").classList.add("hide"); + context.querySelector('.sendTextSection').classList.add('hide'); } - if (-1 != supportedCommands.indexOf("Select") && !currentPlayer.isLocalPlayer) { - context.querySelector(".navigationSection").classList.remove("hide"); + if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { + context.querySelector('.navigationSection').classList.remove('hide'); } else { - context.querySelector(".navigationSection").classList.add("hide"); + context.querySelector('.navigationSection').classList.add('hide'); } - buttonVisible(context.querySelector(".btnStop"), null != item); - buttonVisible(context.querySelector(".btnNextTrack"), null != item); - buttonVisible(context.querySelector(".btnPreviousTrack"), null != item); - buttonVisible(context.querySelector(".btnRewind"), null != item); - buttonVisible(context.querySelector(".btnFastForward"), null != item); - var positionSlider = context.querySelector(".nowPlayingPositionSlider"); + if (isSupportedCommands && !currentPlayer.isLocalPlayer) { + context.querySelector('.remoteControlSection').classList.remove('hide'); + } else { + context.querySelector('.remoteControlSection').classList.add('hide'); + } + + buttonVisible(context.querySelector('.btnStop'), null != item); + buttonVisible(context.querySelector('.btnNextTrack'), null != item); + buttonVisible(context.querySelector('.btnPreviousTrack'), null != item); + if (layoutManager.mobile) { + buttonVisible(context.querySelector('.btnRewind'), false); + buttonVisible(context.querySelector('.btnFastForward'), false); + } else { + buttonVisible(context.querySelector('.btnRewind'), null != item); + buttonVisible(context.querySelector('.btnFastForward'), null != item); + } + var positionSlider = context.querySelector('.nowPlayingPositionSlider'); if (positionSlider && item && item.RunTimeTicks) { positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, @@ -243,101 +335,115 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); - if (item && "Video" == item.MediaType) { - context.classList.remove("hideVideoButtons"); + if (item && 'Video' == item.MediaType) { + context.classList.remove('hideVideoButtons'); } else { - context.classList.add("hideVideoButtons"); + context.classList.add('hideVideoButtons'); } - updateRepeatModeDisplay(playState.RepeatMode); + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onShuffleQueueModeChange(false); updateNowPlayingInfo(context, state); } function updateAudioTracksDisplay(player, context) { var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector(".btnAudioTracks"), playbackManager.audioTracks(player).length > 1 && -1 != supportedCommands.indexOf("SetAudioStreamIndex")); + buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && -1 != supportedCommands.indexOf('SetAudioStreamIndex')); } function updateSubtitleTracksDisplay(player, context) { var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector(".btnSubtitles"), playbackManager.subtitleTracks(player).length && -1 != supportedCommands.indexOf("SetSubtitleStreamIndex")); + buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && -1 != supportedCommands.indexOf('SetSubtitleStreamIndex')); } function updateRepeatModeDisplay(repeatMode) { var context = dlg; - var toggleRepeatButton = context.querySelector(".repeatToggleButton"); - - if ("RepeatAll" == repeatMode) { - toggleRepeatButton.innerHTML = "repeat"; - toggleRepeatButton.classList.add("repeatButton-active"); - } else if ("RepeatOne" == repeatMode) { - toggleRepeatButton.innerHTML = ""; - toggleRepeatButton.classList.add("repeatButton-active"); - } else { - toggleRepeatButton.innerHTML = "repeat"; - toggleRepeatButton.classList.remove("repeatButton-active"); + let toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); + const cssClass = 'buttonActive'; + let innHtml = ''; + let repeatOn = true; + + switch (repeatMode) { + case 'RepeatAll': + break; + case 'RepeatOne': + innHtml = ''; + break; + case 'RepeatNone': + default: + repeatOn = false; + break; + } + + for (const toggleRepeatButton of toggleRepeatButtons) { + toggleRepeatButton.classList.toggle(cssClass, repeatOn); + toggleRepeatButton.innerHTML = innHtml; } } function updatePlayerVolumeState(context, isMuted, volumeLevel) { var view = context; var supportedCommands = currentPlayerSupportedCommands; - var showMuteButton = true; - var showVolumeSlider = true; - if (-1 === supportedCommands.indexOf("Mute")) { + if (-1 === supportedCommands.indexOf('Mute')) { showMuteButton = false; } - if (-1 === supportedCommands.indexOf("SetVolume")) { + if (-1 === supportedCommands.indexOf('SetVolume')) { showVolumeSlider = false; } - if (currentPlayer.isLocalPlayer && appHost.supports("physicalvolumecontrol")) { + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { showMuteButton = false; showVolumeSlider = false; } + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + if (isMuted) { - view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Unmute")); - view.querySelector(".buttonMute i").innerHTML = ""; + buttonMute.setAttribute('title', globalize.translate('Unmute')); + buttonMuteIcon.classList.add('volume_off'); } else { - view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Mute")); - view.querySelector(".buttonMute i").innerHTML = ""; + buttonMute.setAttribute('title', globalize.translate('Mute')); + buttonMuteIcon.classList.add('volume_up'); } - if (showMuteButton) { - view.querySelector(".buttonMute").classList.remove("hide"); + if (!showMuteButton && !showVolumeSlider) { + context.querySelector('.volumecontrol').classList.add('hide'); } else { - view.querySelector(".buttonMute").classList.add("hide"); - } + buttonMute.classList.toggle('hide', !showMuteButton); - var nowPlayingVolumeSlider = context.querySelector(".nowPlayingVolumeSlider"); - var nowPlayingVolumeSliderContainer = context.querySelector(".nowPlayingVolumeSliderContainer"); + var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); + var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); - if (nowPlayingVolumeSlider) { - if (showVolumeSlider) { - nowPlayingVolumeSliderContainer.classList.remove("hide"); - } else { - nowPlayingVolumeSliderContainer.classList.add("hide"); - } + if (nowPlayingVolumeSlider) { + + nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - if (!nowPlayingVolumeSlider.dragging) { - nowPlayingVolumeSlider.value = volumeLevel || 0; + if (!nowPlayingVolumeSlider.dragging) { + nowPlayingVolumeSlider.value = volumeLevel || 0; + } } } } function updatePlayPauseState(isPaused, isActive) { var context = dlg; - var btnPlayPause = context.querySelector(".btnPlayPause"); - btnPlayPause.querySelector("i").innerHTML = isPaused ? "" : "pause"; + var btnPlayPause = context.querySelector('.btnPlayPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); + + btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); + btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); + buttonVisible(btnPlayPause, isActive); } function updateTimeDisplay(positionTicks, runtimeTicks) { var context = dlg; - var positionSlider = context.querySelector(".nowPlayingPositionSlider"); + var positionSlider = context.querySelector('.nowPlayingPositionSlider'); if (positionSlider && !positionSlider.dragging) { if (runtimeTicks) { @@ -349,8 +455,8 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } } - context.querySelector(".positionTime").innerHTML = null == positionTicks ? "--:--" : datetime.getDisplayRunningTime(positionTicks); - context.querySelector(".runtime").innerHTML = null != runtimeTicks ? datetime.getDisplayRunningTime(runtimeTicks) : "--:--"; + context.querySelector('.positionTime').innerHTML = null == positionTicks ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + context.querySelector('.runtime').innerHTML = null != runtimeTicks ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; } function getPlaylistItems(player) { @@ -359,36 +465,49 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function loadPlaylist(context, player) { getPlaylistItems(player).then(function (items) { - var html = ""; + var html = ''; + let favoritesEnabled = true; + if (layoutManager.mobile) { + if (items.length > 0) { + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + } else { + context.querySelector('.btnTogglePlaylist').classList.add('hide'); + } + favoritesEnabled = false; + } + html += listView.getListViewHtml({ items: items, smallIcon: true, - action: "setplaylistindex", - enableUserDataButtons: false, + action: 'setplaylistindex', + enableUserDataButtons: favoritesEnabled, rightButtons: [{ - icon: "", - title: globalize.translate("ButtonRemove"), - id: "remove" + icon: 'remove_circle_outline', + title: globalize.translate('ButtonRemove'), + id: 'remove' }], dragHandle: true }); - if (items.length) { - context.querySelector(".playlistSection").classList.remove("hide"); - } else { - context.querySelector(".playlistSection").classList.add("hide"); + var itemsContainer = context.querySelector('.playlist'); + let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); + itemsContainer.innerHTML = html; + if (focusedItemPlaylistId !== null) { + focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); + const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); + if (newFocusedItem !== null) { + newFocusedItem.focus(); + } } - var itemsContainer = context.querySelector(".playlist"); - itemsContainer.innerHTML = html; var playlistItemId = playbackManager.getCurrentPlaylistItemId(player); if (playlistItemId) { - var img = itemsContainer.querySelector('.listItem[data-playlistItemId="' + playlistItemId + '"] .listItemImage'); + var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); if (img) { - img.classList.remove("lazy"); - img.classList.add("playlistIndexIndicatorImage"); + img.classList.remove('lazy'); + img.classList.add('playlistIndexIndicatorImage'); } } @@ -397,14 +516,36 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } function onPlaybackStart(e, state) { - console.debug("remotecontrol event: " + e.type); + console.debug('remotecontrol event: ' + e.type); var player = this; onStateChanged.call(player, e, state); } - function onRepeatModeChange(e) { - var player = this; - updateRepeatModeDisplay(playbackManager.getRepeatMode(player)); + function onRepeatModeChange() { + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + } + + function onShuffleQueueModeChange(updateView = true) { + let shuffleMode = playbackManager.getQueueShuffleMode(this); + let context = dlg; + const cssClass = 'buttonActive'; + let shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + + for (let shuffleButton of shuffleButtons) { + switch (shuffleMode) { + case 'Shuffle': + shuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + shuffleButton.classList.remove(cssClass); + break; + } + } + + if (updateView) { + onPlaylistUpdate(); + } } function onPlaylistUpdate(e) { @@ -413,24 +554,27 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function onPlaylistItemRemoved(e, info) { var context = dlg; - var playlistItemIds = info.playlistItemIds; + if (info !== undefined) { + var playlistItemIds = info.playlistItemIds; - for (var i = 0, length = playlistItemIds.length; i < length; i++) { - var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + for (var i = 0, length = playlistItemIds.length; i < length; i++) { + var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); - if (listItem) { - listItem.parentNode.removeChild(listItem); + if (listItem) { + listItem.parentNode.removeChild(listItem); + } } + } else { + onPlaylistUpdate(); } } function onPlaybackStopped(e, state) { - console.debug("remotecontrol event: " + e.type); + console.debug('remotecontrol event: ' + e.type); var player = this; if (!state.NextMediaType) { updatePlayerState(player, dlg, {}); - loadPlaylist(dlg); Emby.Page.back(); } } @@ -442,7 +586,7 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function onStateChanged(event, state) { var player = this; updatePlayerState(player, dlg, state); - loadPlaylist(dlg, player); + onPlaylistUpdate(); } function onTimeUpdate(e) { @@ -465,16 +609,18 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL var player = currentPlayer; if (player) { - events.off(player, "playbackstart", onPlaybackStart); - events.off(player, "statechange", onStateChanged); - events.off(player, "repeatmodechange", onRepeatModeChange); - events.off(player, "playlistitemremove", onPlaylistUpdate); - events.off(player, "playlistitemmove", onPlaylistUpdate); - 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); + events.off(player, 'playbackstart', onPlaybackStart); + events.off(player, 'statechange', onStateChanged); + events.off(player, 'repeatmodechange', onRepeatModeChange); + events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.off(player, 'playlistitemremove', onPlaylistItemRemoved); + events.off(player, 'playlistitemmove', onPlaylistUpdate); + events.off(player, 'playlistitemadd', onPlaylistUpdate); + 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; } } @@ -483,18 +629,20 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL if (releaseCurrentPlayer(), currentPlayer = player, player) { var state = playbackManager.getPlayerState(player); onStateChanged.call(player, { - type: "init" + type: 'init' }, state); - events.on(player, "playbackstart", onPlaybackStart); - events.on(player, "statechange", onStateChanged); - events.on(player, "repeatmodechange", onRepeatModeChange); - events.on(player, "playlistitemremove", onPlaylistItemRemoved); - events.on(player, "playlistitemmove", onPlaylistUpdate); - 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(player, 'playbackstart', onPlaybackStart); + events.on(player, 'statechange', onStateChanged); + events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.on(player, 'playlistitemremove', onPlaylistItemRemoved); + events.on(player, 'playlistitemmove', onPlaylistUpdate); + events.on(player, 'playlistitemadd', onPlaylistUpdate); + 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); var playerInfo = playbackManager.getPlayerInfo(); var supportedCommands = playerInfo.supportedCommands; currentPlayerSupportedCommands = supportedCommands; @@ -504,11 +652,11 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function onBtnCommandClick() { if (currentPlayer) { - if (this.classList.contains("repeatToggleButton")) { - toggleRepeat(currentPlayer); + if (this.classList.contains('repeatToggleButton')) { + toggleRepeat(); } else { playbackManager.sendCommand({ - Name: this.getAttribute("data-command") + Name: this.getAttribute('data-command') }, currentPlayer); } } @@ -523,7 +671,7 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } function savePlaylist() { - require(["playlistEditor"], function (playlistEditor) { + require(['playlistEditor'], function (playlistEditor) { getSaveablePlaylistItems().then(function (items) { var serverId = items.length ? items[0].ServerId : ApiClient.serverId(); new playlistEditor().show({ @@ -532,67 +680,93 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL }), serverId: serverId, enableAddToPlayQueue: false, - defaultValue: "new" + defaultValue: 'new' }); }); }); } function bindEvents(context) { - var btnCommand = context.querySelectorAll(".btnCommand"); + var btnCommand = context.querySelectorAll('.btnCommand'); + var positionSlider = context.querySelector('.nowPlayingPositionSlider'); for (var i = 0, length = btnCommand.length; i < length; i++) { - btnCommand[i].addEventListener("click", onBtnCommandClick); + btnCommand[i].addEventListener('click', onBtnCommandClick); } - context.querySelector(".btnToggleFullscreen").addEventListener("click", function (e) { + context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { if (currentPlayer) { playbackManager.sendCommand({ - Name: e.target.getAttribute("data-command") + Name: e.target.getAttribute('data-command') }, currentPlayer); } }); - context.querySelector(".btnAudioTracks").addEventListener("click", function (e) { + context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); } }); - context.querySelector(".btnSubtitles").addEventListener("click", function (e) { + context.querySelector('.btnSubtitles').addEventListener('click', function (e) { if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); } }); - context.querySelector(".btnStop").addEventListener("click", function () { + context.querySelector('.btnStop').addEventListener('click', function () { if (currentPlayer) { playbackManager.stop(currentPlayer); } }); - context.querySelector(".btnPlayPause").addEventListener("click", function () { + context.querySelector('.btnPlayPause').addEventListener('click', function () { if (currentPlayer) { playbackManager.playPause(currentPlayer); } }); - context.querySelector(".btnNextTrack").addEventListener("click", function () { + context.querySelector('.btnNextTrack').addEventListener('click', function () { if (currentPlayer) { playbackManager.nextTrack(currentPlayer); } }); - context.querySelector(".btnRewind").addEventListener("click", function () { + context.querySelector('.btnRewind').addEventListener('click', function () { if (currentPlayer) { playbackManager.rewind(currentPlayer); } }); - context.querySelector(".btnFastForward").addEventListener("click", function () { + context.querySelector('.btnFastForward').addEventListener('click', function () { if (currentPlayer) { playbackManager.fastForward(currentPlayer); } }); - context.querySelector(".btnPreviousTrack").addEventListener("click", function () { + for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { + shuffleButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(currentPlayer); + } + }); + } + + 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)) { + 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); + } + } + }); + + context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { if (currentPlayer) { playbackManager.previousTrack(currentPlayer); } }); - context.querySelector(".nowPlayingPositionSlider").addEventListener("change", function () { + positionSlider.addEventListener('change', function () { var value = this.value; if (currentPlayer) { @@ -601,11 +775,11 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } }); - context.querySelector(".nowPlayingPositionSlider").getBubbleText = function (value) { + positionSlider.getBubbleText = function (value) { var state = lastPlayerState; if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return "--:--"; + return '--:--'; } var ticks = currentRuntimeTicks; @@ -614,40 +788,42 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL return datetime.getDisplayRunningTime(ticks); }; - var volumeSliderTimer; - - function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; - - playbackManager.setVolume(this.value, currentPlayer); - } - - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); - } - } + context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); - context.querySelector(".nowPlayingVolumeSlider").addEventListener("change", setVolume); - context.querySelector(".nowPlayingVolumeSlider").addEventListener("mousemove", setVolumeDelayed); - context.querySelector(".nowPlayingVolumeSlider").addEventListener("touchmove", setVolumeDelayed); - context.querySelector(".buttonMute").addEventListener("click", function () { + context.querySelector('.buttonMute').addEventListener('click', function () { playbackManager.toggleMute(currentPlayer); }); - var playlistContainer = context.querySelector(".playlist"); - playlistContainer.addEventListener("action-remove", function (e) { + var playlistContainer = context.querySelector('.playlist'); + playlistContainer.addEventListener('action-remove', function (e) { playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); }); - playlistContainer.addEventListener("itemdrop", function (e) { + playlistContainer.addEventListener('itemdrop', function (e) { var newIndex = e.detail.newIndex; var playlistItemId = e.detail.playlistItemId; playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); }); - context.querySelector(".btnSavePlaylist").addEventListener("click", savePlaylist); + context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); + context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { + if (context.querySelector('.playlist').classList.contains('hide')) { + context.querySelector('.playlist').classList.remove('hide'); + context.querySelector('.btnSavePlaylist').classList.remove('hide'); + context.querySelector('.volumecontrol').classList.add('hide'); + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); + } + } else { + context.querySelector('.playlist').classList.add('hide'); + context.querySelector('.btnSavePlaylist').classList.add('hide'); + if (showMuteButton || showVolumeSlider) { + context.querySelector('.volumecontrol').classList.remove('hide'); + } + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); + } + } + }); } function onPlayerChange() { @@ -657,16 +833,16 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function onMessageSubmit(e) { var form = e.target; playbackManager.sendCommand({ - Name: "DisplayMessage", + Name: 'DisplayMessage', Arguments: { - Header: form.querySelector("#txtMessageTitle").value, - Text: form.querySelector("#txtMessageText", form).value + Header: form.querySelector('#txtMessageTitle').value, + Text: form.querySelector('#txtMessageText', form).value } }, currentPlayer); - form.querySelector("input").value = ""; + form.querySelector('input').value = ''; - require(["toast"], function (toast) { - toast("Message sent."); + require(['toast'], function (toast) { + toast('Message sent.'); }); e.preventDefault(); @@ -677,15 +853,15 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function onSendStringSubmit(e) { var form = e.target; playbackManager.sendCommand({ - Name: "SendString", + Name: 'SendString', Arguments: { - String: form.querySelector("#txtTypeText", form).value + String: form.querySelector('#txtTypeText', form).value } }, currentPlayer); - form.querySelector("input").value = ""; + form.querySelector('input').value = ''; - require(["toast"], function (toast) { - toast("Text sent."); + require(['toast'], function (toast) { + toast('Text sent.'); }); e.preventDefault(); @@ -694,21 +870,41 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } function init(ownerView, context) { + let volumecontrolHtml = '
'; + volumecontrolHtml += ``; + volumecontrolHtml += '
'; + volumecontrolHtml += '
'; + let optionsSection = context.querySelector('.playlistSectionButton'); + if (!layoutManager.mobile) { + context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); + optionsSection.classList.remove('align-items-center', 'justify-content-center'); + optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.playlist').classList.remove('hide'); + context.querySelector('.btnSavePlaylist').classList.remove('hide'); + context.classList.add('padded-bottom'); + } else { + optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); + optionsSection.classList.add('playlistSectionButtonTransparent'); + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); + context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); + } + bindEvents(context); - context.querySelector(".sendMessageForm").addEventListener("submit", onMessageSubmit); - context.querySelector(".typeTextForm").addEventListener("submit", onSendStringSubmit); - events.on(playbackManager, "playerchange", onPlayerChange); + context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); + context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); + events.on(playbackManager, 'playerchange', onPlayerChange); if (layoutManager.tv) { - var positionSlider = context.querySelector(".nowPlayingPositionSlider"); - positionSlider.classList.add("focusable"); + var positionSlider = context.querySelector('.nowPlayingPositionSlider'); + positionSlider.classList.add('focusable'); positionSlider.enableKeyboardDragging(); } } function onDialogClosed(e) { releaseCurrentPlayer(); - events.off(playbackManager, "playerchange", onPlayerChange); + events.off(playbackManager, 'playerchange', onPlayerChange); lastPlayerState = null; } diff --git a/src/components/sanitizefilename.js b/src/components/sanitizeFilename.js similarity index 93% rename from src/components/sanitizefilename.js rename to src/components/sanitizeFilename.js index adfb852e1f3..de7b1a0782d 100644 --- a/src/components/sanitizefilename.js +++ b/src/components/sanitizeFilename.js @@ -16,8 +16,8 @@ function isLowSurrogate(codePoint) { } function getByteLength(string) { - if (typeof string !== "string") { - throw new Error("Input must be string"); + if (typeof string !== 'string') { + throw new Error('Input must be string'); } const charLength = string.length; @@ -49,8 +49,8 @@ function getByteLength(string) { } function truncate(string, byteLength) { - if (typeof string !== "string") { - throw new Error("Input must be string"); + if (typeof string !== 'string') { + throw new Error('Input must be string'); } const charLength = string.length; diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 5fc3729bacd..3b01cf1ad18 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -1,38 +1,46 @@ -define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManager) { - "use strict"; +/* eslint-disable indent */ + +/** + * Module for controlling scroll behavior. + * @module components/scrollManager + */ + +import dom from 'dom'; +import browser from 'browser'; +import layoutManager from 'layoutManager'; /** * Scroll time in ms. */ - var ScrollTime = 270; + const ScrollTime = 270; /** * Epsilon for comparing values. */ - var 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 /** * Returns minimum vertical scroll. * Scroll less than that value will be zeroed. * - * @return {number} minimum vertical scroll + * @return {number} Minimum vertical scroll. */ function minimumScrollY() { - var topMenu = document.querySelector(".headerTop"); + const topMenu = document.querySelector('.headerTop'); if (topMenu) { return topMenu.clientHeight; } return 0; } - var supportsSmoothScroll = "scrollBehavior" in document.documentElement.style; + const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; - var supportsScrollToOptions = false; + let supportsScrollToOptions = false; try { - var elem = document.createElement("div"); + const elem = document.createElement('div'); - var opts = Object.defineProperty({}, "behavior", { + const opts = Object.defineProperty({}, 'behavior', { // eslint-disable-next-line getter-return get: function () { supportsScrollToOptions = true; @@ -41,16 +49,16 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage elem.scrollTo(opts); } catch (e) { - console.error("error checking ScrollToOptions support"); + console.error('error checking ScrollToOptions support'); } /** * Returns value clamped by range [min, max]. * - * @param {number} value clamped value - * @param {number} min begining of range - * @param {number} max ending of range - * @return {number} clamped value + * @param {number} value - Clamped value. + * @param {number} min - Begining of range. + * @param {number} max - Ending of range. + * @return {number} Clamped value. */ function clamp(value, min, max) { return value <= min ? min : value >= max ? max : value; @@ -60,15 +68,15 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage * 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. * - * @param {number} begin1 begining of range 1 - * @param {number} end1 ending of range 1 - * @param {number} begin2 begining of range 2 - * @param {number} end2 ending of range 2 - * @return {number} delta: <0 move range1 to the left, >0 - to the right + * @param {number} begin1 - Begining of range 1. + * @param {number} end1 - Ending of range 1. + * @param {number} begin2 - Begining of range 2. + * @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) { - var delta1 = begin1 - begin2; - var delta2 = end2 - end1; + const delta1 = begin1 - begin2; + const delta2 = end2 - end1; if (delta1 < 0 && delta1 < delta2) { return -delta1; } else if (delta2 < 0) { @@ -80,13 +88,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Ease value. * - * @param {number} t value in range [0, 1] - * @return {number} eased value in range [0, 1] + * @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 + 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. + * @property {number} width - Width. + * @property {number} height - Height. + */ + /** * Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * @@ -100,41 +116,68 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage * * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement */ - function DocumentScroller() { - } - - DocumentScroller.prototype = { + class DocumentScroller { + /** + * Horizontal scroll position. + * @type {number} + */ get scrollLeft() { return window.pageXOffset; - }, + } + set scrollLeft(val) { window.scroll(val, window.pageYOffset); - }, + } + /** + * Vertical scroll position. + * @type {number} + */ get scrollTop() { return window.pageYOffset; - }, + } + 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); - }, + } + /** + * Vertical scroll size (scroll height). + * @type {number} + */ 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); - }, + } + /** + * Vertical client size (client height). + * @type {number} + */ get clientHeight() { return Math.min(document.documentElement.clientHeight, document.body.clientHeight); - }, + } - getBoundingClientRect: function() { + /** + * 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, @@ -142,38 +185,46 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage width: this.clientWidth, height: this.clientHeight }; - }, + } - scrollTo: function() { + /** + * Scrolls window. + * @param {...mixed} args See window.scrollTo. + */ + scrollTo() { window.scrollTo.apply(window, arguments); } - }; + } - var documentScroller = new DocumentScroller(); + /** + * Default (document) scroller. + */ + const documentScroller = new DocumentScroller(); /** - * Returns parent element that can be scrolled. If no such, returns documentElement. + * 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} 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) { - var nameScroll = "scrollWidth"; - var nameClient = "clientWidth"; - var nameClass = "scrollX"; + let nameScroll = 'scrollWidth'; + let nameClient = 'clientWidth'; + let nameClass = 'scrollX'; if (vertical) { - nameScroll = "scrollHeight"; - nameClient = "clientHeight"; - nameClass = "scrollY"; + nameScroll = 'scrollHeight'; + nameClient = 'clientHeight'; + nameClass = 'scrollY'; } - var parent = element.parentElement; + let parent = element.parentElement; while (parent) { // Skip 'emby-scroller' because it scrolls by itself - if (!parent.classList.contains("emby-scroller") && + if (!parent.classList.contains('emby-scroller') && parent[nameScroll] > parent[nameClient] && parent.classList.contains(nameClass)) { return parent; } @@ -187,20 +238,20 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * @typedef {Object} ScrollerData - * @property {number} scrollPos current scroll position - * @property {number} scrollSize scroll size - * @property {number} clientSize client size + * @property {number} scrollPos - Current scroll position. + * @property {number} scrollSize - Scroll size. + * @property {number} clientSize - Client size. */ /** - * Returns scroll data for specified orientation. + * Returns scroller data for specified orientation. * - * @param {HTMLElement} scroller scroller - * @param {boolean} vertical vertical scroll data - * @return {ScrollerData} scroll data + * @param {HTMLElement} scroller - Scroller. + * @param {boolean} vertical - Vertical scroller data. + * @return {ScrollerData} Scroller data. */ function getScrollerData(scroller, vertical) { - var data = {}; + let data = {}; if (!vertical) { data.scrollPos = scroller.scrollLeft; @@ -218,14 +269,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns position of child of scroller for specified orientation. * - * @param {HTMLElement} scroller scroller - * @param {HTMLElement} element child of scroller - * @param {boolean} vertical vertical scroll - * @return {number} child position + * @param {HTMLElement} scroller - Scroller. + * @param {HTMLElement} element - Child of scroller. + * @param {boolean} vertical - Vertical scroll. + * @return {number} Child position. */ function getScrollerChildPos(scroller, element, vertical) { - var elementRect = element.getBoundingClientRect(); - var scrollerRect = scroller.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + const scrollerRect = scroller.getBoundingClientRect(); if (!vertical) { return scroller.scrollLeft + elementRect.left - scrollerRect.left; @@ -237,21 +288,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns scroll position for element. * - * @param {ScrollerData} scrollerData scroller data - * @param {number} elementPos child element position - * @param {number} elementSize child element size - * @param {boolean} centered scroll to center - * @return {number} scroll position + * @param {ScrollerData} scrollerData - Scroller data. + * @param {number} elementPos - Child element position. + * @param {number} elementSize - Child element size. + * @param {boolean} centered - Scroll to center. + * @return {number} Scroll position. */ function calcScroll(scrollerData, elementPos, elementSize, centered) { - var maxScroll = scrollerData.scrollSize - scrollerData.clientSize; + const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; - var scroll; + let scroll; if (centered) { scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; } else { - var delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); + const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); scroll = scrollerData.scrollPos - delta; } @@ -261,19 +312,19 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Calls scrollTo function in proper way. * - * @param {HTMLElement} scroller scroller - * @param {ScrollToOptions} options scroll options + * @param {HTMLElement} scroller - Scroller. + * @param {ScrollToOptions} options - Scroll options. */ function scrollToHelper(scroller, options) { - if ("scrollTo" in scroller) { + if ('scrollTo' in scroller) { if (!supportsScrollToOptions) { - var scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); - var scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); + 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) { + } else if ('scrollLeft' in scroller) { if (options.left !== undefined) { scroller.scrollLeft = options.left; } @@ -286,14 +337,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs built-in scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate - * @param {boolean} smooth smooth scrolling + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} smooth - Smooth scrolling. */ function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - var scrollBehavior = smooth ? "smooth" : "instant"; + const scrollBehavior = smooth ? 'smooth' : 'instant'; if (xScroller !== yScroller) { scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); @@ -303,7 +354,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage } } - var scrollTimer; + /** + * Requested frame for animated scroll. + */ + let scrollTimer; /** * Resets scroll timer to stop scrolling. @@ -316,29 +370,29 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs animated scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. */ function animateScroll(xScroller, scrollX, yScroller, scrollY) { - var ox = xScroller.scrollLeft; - var oy = yScroller.scrollTop; - var dx = scrollX - ox; - var dy = scrollY - oy; + const ox = xScroller.scrollLeft; + const oy = yScroller.scrollTop; + const dx = scrollX - ox; + const dy = scrollY - oy; if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { return; } - var start; + let start; function scrollAnim(currentTimestamp) { start = start || currentTimestamp; - var k = Math.min(1, (currentTimestamp - start) / ScrollTime); + let k = Math.min(1, (currentTimestamp - start) / ScrollTime); if (k === 1) { resetScrollTimer(); @@ -348,8 +402,8 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage k = ease(k); - var x = ox + dx*k; - var y = oy + dy*k; + const x = ox + dx * k; + const y = oy + dy * k; builtinScroll(xScroller, x, yScroller, y, false); @@ -362,11 +416,11 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate - * @param {boolean} smooth smooth scrolling + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} smooth - Smooth scrolling. */ function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { @@ -403,26 +457,26 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns true if scroll manager is enabled. */ - var isEnabled = function() { + 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 + * @param {number} scrollX - Horizontal coordinate. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} [smooth=false] - Smooth scrolling. */ - var scrollTo = function(scrollX, scrollY, smooth) { + export function scrollTo(scrollX, scrollY, smooth) { smooth = !!smooth; // Scroller is document itself by default - var scroller = getScrollableParent(null, false); + const scroller = getScrollableParent(null, false); - var xScrollerData = getScrollerData(scroller, false); - var 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); @@ -433,39 +487,39 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Scrolls the document to a given element. * - * @param {HTMLElement} element target element of scroll task - * @param {boolean} [smooth=false] smooth scrolling + * @param {HTMLElement} element - Target element of scroll task. + * @param {boolean} [smooth=false] - Smooth scrolling. */ - var scrollToElement = function(element, smooth) { + export function scrollToElement(element, smooth) { smooth = !!smooth; - var scrollCenterX = true; - var scrollCenterY = true; + let scrollCenterX = true; + let scrollCenterY = true; - var offsetParent = element.offsetParent; + const offsetParent = element.offsetParent; // In Firefox offsetParent.offsetParent is BODY - var isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === "fixed"); + 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; } - var xScroller = getScrollableParent(element, false); - var yScroller = getScrollableParent(element, true); + const xScroller = getScrollableParent(element, false); + const yScroller = getScrollableParent(element, true); - var elementRect = element.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); - var xScrollerData = getScrollerData(xScroller, false); - var yScrollerData = getScrollerData(yScroller, true); + const xScrollerData = getScrollerData(xScroller, false); + const yScrollerData = getScrollerData(yScroller, true); - var xPos = getScrollerChildPos(xScroller, element, false); - var yPos = getScrollerChildPos(yScroller, element, true); + const xPos = getScrollerChildPos(xScroller, element, false); + const yPos = getScrollerChildPos(yScroller, element, true); - var scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); - var scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); + const scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); + let 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 @@ -483,16 +537,17 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage } if (isEnabled()) { - dom.addEventListener(window, "focusin", function(e) { + dom.addEventListener(window, 'focusin', function(e) { setTimeout(function() { scrollToElement(e.target, useSmoothScroll()); }, 0); }, {capture: true}); } - return { - isEnabled: isEnabled, - scrollTo: scrollTo, - scrollToElement: scrollToElement - }; -}); +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + scrollTo: scrollTo, + scrollToElement: scrollToElement +}; diff --git a/src/components/search/searchfields.template.html b/src/components/search/searchfields.template.html index c1e9c64347e..7da5e240c53 100644 --- a/src/components/search/searchfields.template.html +++ b/src/components/search/searchfields.template.html @@ -1,5 +1,5 @@
- search +
diff --git a/src/components/search/searchresults.js b/src/components/search/searchresults.js index 02f1feefe1f..88c68fdedd2 100644 --- a/src/components/search/searchresults.js +++ b/src/components/search/searchresults.js @@ -5,8 +5,8 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', var options = { - SortBy: "IsFavoriteOrLiked,Random", - IncludeItemTypes: "Movie,Series,MusicArtist", + SortBy: 'IsFavoriteOrLiked,Random', + IncludeItemTypes: 'Movie,Series,MusicArtist', Limit: 20, Recursive: true, ImageTypeLimit: 0, @@ -165,7 +165,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "LiveTvProgram", + IncludeItemTypes: 'LiveTvProgram', IsMovie: true, IsKids: false, IsNews: false @@ -194,7 +194,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Movie" + IncludeItemTypes: 'Movie' }, context, '.movieResults', { @@ -212,7 +212,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Series" + IncludeItemTypes: 'Series' }, context, '.seriesResults', { @@ -231,7 +231,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "LiveTvProgram", + IncludeItemTypes: 'LiveTvProgram', IsSeries: true, IsSports: false, IsKids: false, @@ -262,7 +262,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Episode" + IncludeItemTypes: 'Episode' }, context, '.episodeResults', { @@ -363,7 +363,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "LiveTvProgram", + IncludeItemTypes: 'LiveTvProgram', IsMovie: instance.options.collectionType === 'livetv' ? false : null, IsSeries: instance.options.collectionType === 'livetv' ? false : null, IsSports: instance.options.collectionType === 'livetv' ? false : null, @@ -394,8 +394,8 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - MediaTypes: "Video", - ExcludeItemTypes: "Movie,Episode" + MediaTypes: 'Video', + ExcludeItemTypes: 'Movie,Episode' }, context, '.videoResults', { @@ -439,7 +439,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "MusicAlbum" + IncludeItemTypes: 'MusicAlbum' }, context, '.albumResults', { @@ -456,7 +456,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Audio" + IncludeItemTypes: 'Audio' }, context, '.songResults', { @@ -464,7 +464,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', showTitle: true, overlayText: false, centerText: true, - action: 'play' + overlayPlayButton: true }); @@ -475,7 +475,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - MediaTypes: "Photo" + MediaTypes: 'Photo' }, context, '.photoResults', { @@ -492,7 +492,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "PhotoAlbum" + IncludeItemTypes: 'PhotoAlbum' }, context, '.photoAlbumResults', { @@ -508,7 +508,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Book" + IncludeItemTypes: 'Book' }, context, '.bookResults', { @@ -525,7 +525,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "AudioBook" + IncludeItemTypes: 'AudioBook' }, context, '.audioBookResults', { @@ -541,7 +541,7 @@ define(['layoutManager', 'globalize', 'require', 'events', 'connectionManager', IncludeGenres: false, IncludeStudios: false, IncludeArtists: false, - IncludeItemTypes: "Playlist" + IncludeItemTypes: 'Playlist' }, context, '.playlistResults', { diff --git a/src/components/serverRestartDialog.js b/src/components/serverRestartDialog.js deleted file mode 100644 index ef012fe44aa..00000000000 --- a/src/components/serverRestartDialog.js +++ /dev/null @@ -1,158 +0,0 @@ -define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) { - 'use strict'; - - var currentApiClient; - var currentDlg; - var currentInstance; - - function reloadPageWhenServerAvailable(retryCount) { - var apiClient = currentApiClient; - if (!apiClient) { - return; - } - - // Don't use apiclient method because we don't want it reporting authentication under the old version - apiClient.getJSON(apiClient.getUrl("System/Info")).then(function (info) { - - // If this is back to false, the restart completed - if (!info.IsShuttingDown) { - currentInstance.restarted = true; - dialogHelper.close(currentDlg); - } else { - retryReload(retryCount); - } - - }, function () { - retryReload(retryCount); - }); - } - - function retryReload(retryCount) { - setTimeout(function () { - retryCount = retryCount || 0; - retryCount++; - - if (retryCount < 150) { - reloadPageWhenServerAvailable(retryCount); - } - }, 500); - } - - function startRestart(instance, apiClient, dlg) { - currentApiClient = apiClient; - currentDlg = dlg; - currentInstance = instance; - - apiClient.restartServer().then(function () { - setTimeout(reloadPageWhenServerAvailable, 250); - }); - } - - function showDialog(instance, options, template) { - - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - var enableTvLayout = layoutManager.tv; - - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - var configuredButtons = []; - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.classList.add('align-items-center'); - dlg.classList.add('justify-items-center'); - - var formDialogContent = dlg.querySelector('.formDialogContent'); - formDialogContent.style['flex-grow'] = 'initial'; - - if (enableTvLayout) { - formDialogContent.style['max-width'] = '50%'; - formDialogContent.style['max-height'] = '60%'; - scrollHelper.centerFocus.on(formDialogContent, false); - } else { - formDialogContent.style.maxWidth = (Math.min((configuredButtons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px'; - dlg.classList.add('dialog-fullscreen-lowres'); - } - - dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('HeaderRestartingServer'); - - dlg.querySelector('.text').innerHTML = globalize.translate('RestartPleaseWaitMessage'); - - var i; - var length; - var html = ''; - for (i = 0, length = configuredButtons.length; i < length; i++) { - var item = configuredButtons[i]; - var autoFocus = i === 0 ? ' autofocus' : ''; - var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize'; - - if (item.type) { - buttonClass += ' button-' + item.type; - } - html += ''; - } - - dlg.querySelector('.formDialogFooter').innerHTML = html; - - function onButtonClick() { - dialogHelper.close(dlg); - } - - var buttons = dlg.querySelectorAll('.btnOption'); - for (i = 0, length = buttons.length; i < length; i++) { - buttons[i].addEventListener('click', onButtonClick); - } - - var dlgPromise = dialogHelper.open(dlg); - - startRestart(instance, options.apiClient, dlg); - - return dlgPromise.then(function () { - - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - instance.destroy(); - loading.hide(); - - if (instance.restarted) { - events.trigger(instance, 'restarted'); - } - }); - } - - function ServerRestartDialog(options) { - this.options = options; - } - - ServerRestartDialog.prototype.show = function () { - var instance = this; - loading.show(); - - return new Promise(function (resolve, reject) { - require(['text!./../dialog/dialog.template.html'], function (template) { - showDialog(instance, instance.options, template).then(resolve, reject); - }); - }); - }; - - ServerRestartDialog.prototype.destroy = function () { - currentApiClient = null; - currentDlg = null; - currentInstance = null; - this.options = null; - }; - - return ServerRestartDialog; -}); diff --git a/src/components/serviceworker/notifications.js b/src/components/serviceworker/notifications.js index 33f54bb64da..5f96d01a4dc 100644 --- a/src/components/serviceworker/notifications.js +++ b/src/components/serviceworker/notifications.js @@ -20,7 +20,7 @@ case 'restart': return apiClient.restartServer(); default: - clients.openWindow("/"); + clients.openWindow('/'); return Promise.resolve(); } }); @@ -35,7 +35,7 @@ var action = event.action; if (!action) { - clients.openWindow("/"); + clients.openWindow('/'); event.waitUntil(Promise.resolve()); return; } diff --git a/src/components/skinManager.js b/src/components/skinManager.js index b81e7c3a40f..c38d34181b3 100644 --- a/src/components/skinManager.js +++ b/src/components/skinManager.js @@ -1,4 +1,4 @@ -define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdrop', 'globalize', 'require', 'appSettings'], function (appHost, userSettings, browser, events, pluginManager, backdrop, globalize, require, appSettings) { +define(['apphost', 'userSettings', 'browser', 'events', 'backdrop', 'globalize', 'require', 'appSettings'], function (appHost, userSettings, browser, events, backdrop, globalize, require, appSettings) { 'use strict'; var themeStyleElement; @@ -24,25 +24,25 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr function getThemes() { return [{ - name: "Apple TV", - id: "appletv" + name: 'Apple TV', + id: 'appletv' }, { - name: "Blue Radiance", - id: "blueradiance" + name: 'Blue Radiance', + id: 'blueradiance' }, { - name: "Dark", - id: "dark", + name: 'Dark', + id: 'dark', isDefault: true, isDefaultServerDashboard: true }, { - name: "Light", - id: "light" + name: 'Light', + id: 'light' }, { - name: "Purple Haze", - id: "purplehaze" + name: 'Purple Haze', + id: 'purplehaze' }, { - name: "Windows Media Center", - id: "wmc" + name: 'Windows Media Center', + id: 'wmc' }]; } @@ -57,7 +57,6 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr var selectedTheme; for (var i = 0, length = themes.length; i < length; i++) { - var theme = themes[i]; if (theme[isDefaultProperty]) { defaultTheme = theme; @@ -91,7 +90,7 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr function onThemeLoaded() { document.documentElement.classList.remove('preload'); try { - var color = getComputedStyle(document.querySelector('.skinHeader')).getPropertyValue("background-color"); + var color = getComputedStyle(document.querySelector('.skinHeader')).getPropertyValue('background-color'); if (color) { appHost.setThemeColor(color); } @@ -116,8 +115,8 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr var linkUrl = info.stylesheetPath; unloadTheme(); - var link = document.createElement('link'); + var link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); link.onload = function () { @@ -137,6 +136,8 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr function onViewBeforeShow(e) { if (e.detail && e.detail.type === 'video-osd') { + // This removes the space that the scrollbar takes while playing a video + document.body.classList.remove('force-scroll'); return; } @@ -155,6 +156,9 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr } } } + // This keeps the scrollbar always present in all pages, so we avoid clipping while switching between pages + // that need the scrollbar and pages that don't. + document.body.classList.add('force-scroll'); } document.addEventListener('viewshow', onViewBeforeShow); diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 4d426f2484f..f7026a007e6 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -1,17 +1,37 @@ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'loading', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, loading) { +/** + * Image viewer component + * @module components/slideshow/slideshow + */ +define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) { 'use strict'; + /** + * Name of transition event. + */ + const transitionEndEventName = dom.whichTransitionEvent(); + + /** + * Flag to use fake image to fix blurry zoomed image. + * At least WebKit doesn't restore quality for zoomed images. + */ + const useFakeZoomImage = browser.safari; + + /** + * Retrieves an item's image URL from the API. + * @param {object|string} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's image. + */ function getImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (typeof (item) === 'string') { return apiClient.getScaledImageUrl(item, options); } if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; return apiClient.getScaledImageUrl(item.Id, options); } @@ -27,10 +47,16 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return null; } + /** + * Retrieves a backdrop's image URL from the API. + * @param {object} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's backdrop. + */ function getBackdropImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || "Backdrop"; + options.type = options.type || 'Backdrop'; // If not resizing, get the original image if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { @@ -46,35 +72,45 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return null; } - function getImgUrl(item, original) { - + /** + * Dispatches a request for an item's image to its respective handler. + * @param {object} item - Item used to generate the image URL. + * @returns {string} URL of the item's image. + */ + function getImgUrl(item, user) { var apiClient = connectionManager.getApiClient(item.ServerId); var imageOptions = {}; - if (!original) { - imageOptions.maxWidth = screen.availWidth; - } if (item.BackdropImageTags && item.BackdropImageTags.length) { return getBackdropImageUrl(item, imageOptions, apiClient); } else { - - if (item.MediaType === 'Photo' && original) { + if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { return apiClient.getItemDownloadUrl(item.Id); } - imageOptions.type = "Primary"; + imageOptions.type = 'Primary'; return getImageUrl(item, imageOptions, apiClient); } } + /** + * Generates a button using the specified icon, classes and properties. + * @param {string} icon - Name of the material icon on the button + * @param {string} cssClass - CSS classes to assign to the button + * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. + * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. + * @returns {string} The HTML markup of the button. + */ function getIcon(icon, cssClass, canFocus, autoFocus) { - var tabIndex = canFocus ? '' : ' tabindex="-1"'; autoFocus = autoFocus ? ' autofocus' : ''; - return ''; + return ''; } + /** + * Sets the viewport meta tag to enable or disable scaling by the user. + * @param {boolean} scalable - Flag to set the scalability of the viewport. + */ function setUserScalable(scalable) { - try { appHost.setUserScalable(scalable); } catch (err) { @@ -83,23 +119,31 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } return function (options) { - var self = this; + /** Initialized instance of Swiper. */ var swiperInstance; - var dlg; - var currentTimeout; - var currentIntervalMs; + /** Initialized instance of the dialog containing the Swiper instance. */ + var dialog; + /** Options of the slideshow components */ var currentOptions; - var currentIndex; + /** ID of the timeout used to hide the OSD. */ + var hideTimeout; + /** Last coordinates of the mouse pointer. */ + var lastMouseMoveData; + /** Visibility status of the OSD. */ + var _osdOpen = false; - // small hack since this is not possible anyway - if (browser.chromecast) { - options.interactive = false; - } + // Use autoplay on Chromecast since it is non-interactive. + if (browser.chromecast) options.interactive = false; + /** + * Creates the HTML markup for the dialog and the OSD. + * @param {Object} options - Options used to create the dialog and slideshow. + */ function createElements(options) { + currentOptions = options; - dlg = dialogHelper.createDialog({ + dialog = dialogHelper.createDialog({ exitAnimationDuration: options.interactive ? 400 : 800, size: 'fullscreen', autoFocus: false, @@ -108,23 +152,21 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f removeOnClose: true }); - dlg.classList.add('slideshowDialog'); + dialog.classList.add('slideshowDialog'); var html = ''; - if (options.interactive) { + html += '
'; + if (options.interactive && !layoutManager.tv) { var actionButtonsOnTop = layoutManager.mobile; - html += '
'; - html += '
'; - html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); html += '
'; if (actionButtonsOnTop) { - if (appHost.supports('filedownload')) { + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } if (appHost.supports('sharing')) { @@ -137,8 +179,8 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f if (!actionButtonsOnTop) { html += '
'; - html += getIcon('pause', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload')) { + html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } if (appHost.supports('sharing')) { @@ -148,33 +190,28 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f html += '
'; } - html += '
'; - } else { html += '

'; } - dlg.innerHTML = html; + dialog.innerHTML = html; - if (options.interactive) { - dlg.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { - - dialogHelper.close(dlg); + if (options.interactive && !layoutManager.tv) { + dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { + dialogHelper.close(dialog); }); - dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage); - dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage); - var btnPause = dlg.querySelector('.btnSlideshowPause'); + var btnPause = dialog.querySelector('.btnSlideshowPause'); if (btnPause) { btnPause.addEventListener('click', playPause); } - var btnDownload = dlg.querySelector('.btnDownload'); + var btnDownload = dialog.querySelector('.btnDownload'); if (btnDownload) { btnDownload.addEventListener('click', download); } - var btnShare = dlg.querySelector('.btnShare'); + var btnShare = dialog.querySelector('.btnShare'); if (btnShare) { btnShare.addEventListener('click', share); } @@ -182,81 +219,150 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f setUserScalable(true); - dialogHelper.open(dlg).then(function () { - + dialogHelper.open(dialog).then(function () { setUserScalable(false); - stopInterval(); }); inputManager.on(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - dlg.addEventListener('close', onDialogClosed); + dialog.addEventListener('close', onDialogClosed); - if (options.interactive) { - loadSwiper(dlg); - } + loadSwiper(dialog, options); } + /** + * Handles OSD changes when the autoplay is started. + */ function onAutoplayStart() { - var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); + var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); if (btnSlideshowPause) { - btnSlideshowPause.classList.remove("play_arrow"); - btnSlideshowPause.classList.add("pause"); + btnSlideshowPause.classList.replace('play_arrow', 'pause'); } } + /** + * Handles OSD changes when the autoplay is stopped. + */ function onAutoplayStop() { - var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); + var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); if (btnSlideshowPause) { - btnSlideshowPause.classList.remove("pause"); - btnSlideshowPause.classList.add("play_arrow"); + btnSlideshowPause.classList.replace('pause', 'play_arrow'); } } - function loadSwiper(dlg) { + /** + * Handles zoom changes. + */ + function onZoomChange(scale, imageEl, slideEl) { + const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); + + if (zoomImage) { + zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; + + if (scale > 1) { + if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { + // Await for Swiper style changes + setTimeout(() => { + const callback = () => { + imageEl.removeEventListener(transitionEndEventName, callback); + zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); + }; + + // Swiper set 'transition-duration: 300ms' for auto zoom + // and 'transition-duration: 0s' for touch zoom + const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); + + if (transitionDuration > 0) { + imageEl.addEventListener(transitionEndEventName, callback); + } else { + callback(); + } + }, 0); + } + } else { + zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); + } + } + } + /** + * Initializes the Swiper instance and binds the relevant events. + * @param {HTMLElement} dialog - Element containing the dialog. + * @param {Object} options - Options used to initialize the Swiper instance. + */ + function loadSwiper(dialog, options) { + var slides; if (currentOptions.slides) { - dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.slides.map(getSwiperSlideHtmlFromSlide).join(''); + slides = currentOptions.slides; } else { - dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.items.map(getSwiperSlideHtmlFromItem).join(''); + slides = currentOptions.items; } require(['swiper'], function (Swiper) { - - swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), { - // Optional parameters + swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { direction: 'horizontal', - loop: options.loop !== false, - autoplay: { - delay: options.interval || 8000 + // Loop is disabled due to the virtual slides option not supporting it. + loop: false, + zoom: { + minRatio: 1, + toggle: true + }, + autoplay: !options.interactive, + keyboard: { + enabled: true }, - // Disable preloading of all images - preloadImages: false, - // Enable lazy loading - lazy: true, - loadPrevNext: true, - disableOnInteraction: false, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, initialSlide: options.startIndex || 0, - speed: 240 + speed: 240, + navigation: { + nextEl: '.btnSlideshowNext', + prevEl: '.btnSlideshowPrevious' + }, + // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; + virtual: { + slides: slides, + cache: true, + renderSlide: getSwiperSlideHtml, + addSlidesBefore: 1, + addSlidesAfter: 1 + } }); swiperInstance.on('autoplayStart', onAutoplayStart); swiperInstance.on('autoplayStop', onAutoplayStop); - if (layoutManager.mobile) { - pause(); - } else { - play(); + if (useFakeZoomImage) { + swiperInstance.on('zoomChange', onZoomChange); } }); } - function getSwiperSlideHtmlFromItem(item) { + /** + * Renders the HTML markup of a slide for an item or a slide. + * @param {Object} item - The item used to render the slide. + * @param {number} index - The index of the item in the Swiper instance. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtml(item, index) { + if (currentOptions.slides) { + return getSwiperSlideHtmlFromSlide(item); + } else { + return getSwiperSlideHtmlFromItem(item); + } + } + /** + * Renders the HTML markup of a slide for an item. + * @param {Object} item - Item used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromItem(item) { return getSwiperSlideHtmlFromSlide({ - imageUrl: getImgUrl(item), - originalImage: getImgUrl(item, true), + originalImage: getImgUrl(item, currentOptions.user), //title: item.Name, //description: item.Overview Id: item.Id, @@ -264,11 +370,20 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Renders the HTML markup of a slide for a slide object. + * @param {Object} item - Slide object used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ function getSwiperSlideHtmlFromSlide(item) { - var html = ''; - html += '
'; - html += ''; + html += '
'; + html += '
'; + if (useFakeZoomImage) { + html += `
`; + } + html += ''; + html += '
'; if (item.title || item.subtitle) { html += '
'; html += '
'; @@ -290,42 +405,18 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return html; } - function previousImage() { - if (swiperInstance) { - swiperInstance.slidePrev(); - } else { - stopInterval(); - showNextImage(currentIndex - 1); - } - } - - function nextImage() { - if (swiperInstance) { - - if (options.loop === false) { - - if (swiperInstance.activeIndex >= swiperInstance.slides.length - 1) { - dialogHelper.close(dlg); - return; - } - } - - swiperInstance.slideNext(); - } else { - stopInterval(); - showNextImage(currentIndex + 1); - } - } - + /** + * Fetches the information of the currently displayed slide. + * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. + */ function getCurrentImageInfo() { - if (swiperInstance) { var slide = document.querySelector('.swiper-slide-active'); if (slide) { return { url: slide.getAttribute('data-original'), - shareUrl: slide.getAttribute('data-imageurl'), + shareUrl: slide.getAttribute('data-original'), itemId: slide.getAttribute('data-itemid'), serverId: slide.getAttribute('data-serverid') }; @@ -336,8 +427,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Starts a download for the currently displayed slide. + */ function download() { - var imageInfo = getCurrentImageInfo(); require(['fileDownloader'], function (fileDownloader) { @@ -345,8 +438,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Shares the currently displayed slide using the browser's built-in sharing feature. + */ function share() { - var imageInfo = getCurrentImageInfo(); navigator.share({ @@ -354,20 +449,29 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Starts the autoplay feature of the Swiper instance. + */ function play() { if (swiperInstance.autoplay) { swiperInstance.autoplay.start(); } } + /** + * Pauses the autoplay feature of the Swiper instance; + */ function pause() { if (swiperInstance.autoplay) { swiperInstance.autoplay.stop(); } } + /** + * Toggles the autoplay feature of the Swiper instance. + */ function playPause() { - var paused = !dlg.querySelector('.btnSlideshowPause i').classList.contains("pause"); + var paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); if (paused) { play(); } else { @@ -375,8 +479,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Closes the dialog and destroys the Swiper instance. + */ function onDialogClosed() { - var swiper = swiperInstance; if (swiper) { swiper.destroy(true, true); @@ -384,56 +490,45 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } inputManager.off(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + // Shows page scrollbar + document.body.classList.remove('hide-scroll'); + document.body.classList.add('force-scroll'); } - function startInterval(options) { - - currentOptions = options; - - stopInterval(); - createElements(options); - - if (!options.interactive) { - currentIntervalMs = options.interval || 11000; - showNextImage(options.startIndex || 0, true); - } - } - - var _osdOpen = false; - - function isOsdOpen() { - return _osdOpen; - } - - function getOsdBottom() { - return dlg.querySelector('.slideshowBottomBar'); - } - + /** + * Shows the OSD. + */ function showOsd() { - - var bottom = getOsdBottom(); + var bottom = dialog.querySelector('.slideshowBottomBar'); if (bottom) { slideUpToShow(bottom); startHideTimer(); } } + /** + * Hides the OSD. + */ function hideOsd() { - - var bottom = getOsdBottom(); + var bottom = dialog.querySelector('.slideshowBottomBar'); if (bottom) { slideDownToHide(bottom); } } - var hideTimeout; - + /** + * Starts the timer used to automatically hide the OSD. + */ function startHideTimer() { stopHideTimer(); - hideTimeout = setTimeout(hideOsd, 4000); + hideTimeout = setTimeout(hideOsd, 3000); } + /** + * Stops the timer used to automatically hide the OSD. + */ function stopHideTimer() { if (hideTimeout) { clearTimeout(hideTimeout); @@ -441,71 +536,76 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } - function slideUpToShow(elem) { - - if (!elem.classList.contains('hide')) { + /** + * Shows the OSD by sliding it into view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideUpToShow(element) { + if (!element.classList.contains('hide')) { return; } _osdOpen = true; - elem.classList.remove('hide'); + element.classList.remove('hide'); var onFinish = function () { - focusManager.focus(elem.querySelector('.btnSlideshowPause')); + focusManager.focus(element.querySelector('.btnSlideshowPause')); }; - if (!elem.animate) { + if (!element.animate) { onFinish(); return; } requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } ]; var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - elem.animate(keyframes, timing).onfinish = onFinish; + element.animate(keyframes, timing).onfinish = onFinish; }); } - function slideDownToHide(elem) { - - if (elem.classList.contains('hide')) { + /** + * Hides the OSD by sliding it out of view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideDownToHide(element) { + if (element.classList.contains('hide')) { return; } var onFinish = function () { - elem.classList.add('hide'); + element.classList.add('hide'); _osdOpen = false; }; - if (!elem.animate) { + if (!element.animate) { onFinish(); return; } requestAnimationFrame(function () { - var keyframes = [ { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, - { transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } ]; var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - elem.animate(keyframes, timing).onfinish = onFinish; + element.animate(keyframes, timing).onfinish = onFinish; }); } - var lastMouseMoveData; - - function onPointerMove(e) { - - var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + /** + * Shows the OSD when moving the mouse pointer or touching the screen. + * @param {Event} event - Pointer movement event. + */ + function onPointerMove(event) { + var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); if (pointerType === 'mouse') { - var eventX = e.screenX || 0; - var eventY = e.screenY || 0; + var eventX = event.screenX || 0; + var eventY = event.screenY || 0; var obj = lastMouseMoveData; if (!obj) { @@ -528,125 +628,48 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } - function onInputCommand(e) { - - switch (e.detail.command) { - - case 'left': - if (!isOsdOpen()) { - e.preventDefault(); - e.stopPropagation(); - previousImage(); - } - break; - case 'right': - if (!isOsdOpen()) { - e.preventDefault(); - e.stopPropagation(); - nextImage(); - } - break; + /** + * Dispatches keyboard inputs to their proper handlers. + * @param {Event} event - Keyboard input event. + */ + function onInputCommand(event) { + switch (event.detail.command) { case 'up': case 'down': case 'select': case 'menu': case 'info': + showOsd(); + break; case 'play': - case 'playpause': + play(); + break; case 'pause': - showOsd(); + pause(); + break; + case 'playpause': + playPause(); break; default: break; } } - function showNextImage(index, skipPreload) { - - index = Math.max(0, index); - if (index >= currentOptions.items.length) { - index = 0; - } - currentIndex = index; - - var options = currentOptions; - var items = options.items; - var item = items[index]; - var imgUrl = getImgUrl(item); - - var onSrcLoaded = function () { - var cardImageContainer = dlg.querySelector('.slideshowImage'); - - var newCardImageContainer = document.createElement('div'); - newCardImageContainer.className = cardImageContainer.className; - - if (options.cover) { - newCardImageContainer.classList.add('slideshowImage-cover'); - } - - newCardImageContainer.style.backgroundImage = "url('" + imgUrl + "')"; - newCardImageContainer.classList.add('hide'); - cardImageContainer.parentNode.appendChild(newCardImageContainer); - - if (options.showTitle) { - dlg.querySelector('.slideshowImageText').innerHTML = item.Name; - } else { - dlg.querySelector('.slideshowImageText').innerHTML = ''; - } - - newCardImageContainer.classList.remove('hide'); - var onAnimationFinished = function () { - - var parentNode = cardImageContainer.parentNode; - if (parentNode) { - parentNode.removeChild(cardImageContainer); - } - }; - - if (newCardImageContainer.animate) { - - var keyframes = [ - { opacity: '0', offset: 0 }, - { opacity: '1', offset: 1 } - ]; - var timing = { duration: 1200, iterations: 1 }; - newCardImageContainer.animate(keyframes, timing).onfinish = onAnimationFinished; - } else { - onAnimationFinished(); - } - - stopInterval(); - currentTimeout = setTimeout(function () { - showNextImage(index + 1, true); - - }, currentIntervalMs); - }; - - if (!skipPreload) { - var img = new Image(); - img.onload = onSrcLoaded; - img.src = imgUrl; - } else { - onSrcLoaded(); - } - } - - function stopInterval() { - if (currentTimeout) { - clearTimeout(currentTimeout); - currentTimeout = null; - } - } - + /** + * Shows the slideshow component. + */ self.show = function () { - startInterval(options); + createElements(options); + // Hides page scrollbar + document.body.classList.remove('force-scroll'); + document.body.classList.add('hide-scroll'); }; + /** + * Hides the slideshow element. + */ self.hide = function () { - - var dialog = dlg; if (dialog) { - dialogHelper.close(dialog); } }; diff --git a/src/components/slideshow/style.css b/src/components/slideshow/style.css index 2bea7c9696a..af50eb9cd7b 100644 --- a/src/components/slideshow/style.css +++ b/src/components/slideshow/style.css @@ -40,20 +40,6 @@ text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; } -.swiper-slide-img { - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; - -ms-transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - -moz-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - position: absolute; - left: 50%; - top: 50%; -} - .slideshowButtonIcon { color: #fff; opacity: 0.7; @@ -138,3 +124,19 @@ .slideSubtitle { color: #ccc; } + +.swiper-zoom-fakeimg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: contain; + z-index: 1; + pointer-events: none; +} + +.swiper-zoom-fakeimg-hidden { + display: none; +} diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index a0b40acaf65..9950afb89aa 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -66,7 +66,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana var html = ''; html += '
'; - html += ''; + html += ''; html += '

${Sort}

'; html += '
'; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 1ff30712f65..e9bcc0bfca8 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -48,7 +48,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', var apiClient = connectionManager.getApiClient(currentItem.ServerId); apiClient.ajax({ - type: "POST", + type: 'POST', url: apiClient.getUrl(url) }).then(function () { @@ -87,7 +87,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', apiClient.ajax({ - type: "DELETE", + type: 'DELETE', url: apiClient.getUrl(url) }).then(function () { @@ -132,7 +132,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - itemHtml += ''; + itemHtml += ''; itemHtml += '
'; @@ -149,7 +149,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', if (!layoutManager.tv) { if (s.Path) { - itemHtml += ''; + itemHtml += ''; } } @@ -232,11 +232,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', html += '
'; } html += '

' + provider + '

'; - if (layoutManager.tv) { - html += '
'; - } else { - html += '
'; - } + html += '
'; lastProvider = provider; } @@ -248,7 +244,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; - html += ''; + html += ''; var bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; @@ -281,7 +277,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', html += '
'; if (!layoutManager.tv) { - html += ''; + html += ''; } html += ''; @@ -397,7 +393,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', var items = []; items.push({ - name: Globalize.translate('Download'), + name: globalize.translate('Download'), id: 'download' }); diff --git a/src/components/subtitleeditor/subtitleeditor.template.html b/src/components/subtitleeditor/subtitleeditor.template.html index 8719cc93054..7471972f590 100644 --- a/src/components/subtitleeditor/subtitleeditor.template.html +++ b/src/components/subtitleeditor/subtitleeditor.template.html @@ -1,9 +1,8 @@
- +

${Subtitles}

- info${Help} - + ${Help}
@@ -18,7 +17,7 @@

${SearchForSubtitles}

- +
diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index fdc64f0dfa5..bec8156aca2 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -1,5 +1,5 @@ define([], function () { - "use strict"; + 'use strict'; function getTextStyles(settings, isCue) { diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index 69de536e4c9..d728360d05b 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -1,24 +1,19 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loading', 'connectionManager', 'subtitleAppearanceHelper', 'dom', 'events', 'listViewStyle', 'emby-select', 'emby-input', 'emby-checkbox', 'flexStyles'], function (require, globalize, appSettings, appHost, focusManager, loading, connectionManager, subtitleAppearanceHelper, dom, events) { - "use strict"; + 'use strict'; function populateLanguages(select, languages) { + var html = ''; - var html = ""; - - html += ""; - + html += "'; for (var i = 0, length = languages.length; i < length; i++) { - var culture = languages[i]; - - html += ""; + html += "'; } select.innerHTML = html; } function getSubtitleAppearanceObject(context) { - var appearanceSettings = {}; appearanceSettings.textSize = context.querySelector('#selectTextSize').value; @@ -42,8 +37,8 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi populateLanguages(selectSubtitleLanguage, allCultures); - selectSubtitleLanguage.value = user.Configuration.SubtitleLanguagePreference || ""; - context.querySelector('#selectSubtitlePlaybackMode').value = user.Configuration.SubtitleMode || ""; + selectSubtitleLanguage.value = user.Configuration.SubtitleLanguagePreference || ''; + context.querySelector('#selectSubtitlePlaybackMode').value = user.Configuration.SubtitleMode || ''; context.querySelector('#selectSubtitlePlaybackMode').dispatchEvent(new CustomEvent('change', {})); @@ -102,14 +97,12 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi } function onSubmit(e) { - var self = this; var apiClient = connectionManager.getApiClient(self.options.serverId); var userId = self.options.userId; var userSettings = self.options.userSettings; userSettings.setUserInfo(userId, apiClient).then(function () { - var enableSaveConfirmation = self.options.enableSaveConfirmation; save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); }); @@ -118,6 +111,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi if (e) { e.preventDefault(); } + return false; } @@ -197,9 +191,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi var userSettings = self.options.userSettings; apiClient.getUser(userId).then(function (user) { - userSettings.setUserInfo(userId, apiClient).then(function () { - self.dataLoaded = true; var appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); @@ -214,7 +206,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi }; SubtitleSettings.prototype.destroy = function () { - this.options = null; }; diff --git a/src/components/subtitlesettings/subtitlesettings.template.html b/src/components/subtitlesettings/subtitlesettings.template.html index cc2788397fc..716296a2574 100644 --- a/src/components/subtitlesettings/subtitlesettings.template.html +++ b/src/components/subtitlesettings/subtitlesettings.template.html @@ -1,7 +1,5 @@
-
-

${Subtitles}

@@ -9,6 +7,7 @@

+
@@ -34,7 +34,6 @@

-

${HeaderSubtitleAppearance}

@@ -61,6 +60,7 @@

+
+
+
+
+
+
+ +
0s
+
+ +
diff --git a/src/components/syncPlay/groupSelectionMenu.js b/src/components/syncPlay/groupSelectionMenu.js new file mode 100644 index 00000000000..3c77a67ea54 --- /dev/null +++ b/src/components/syncPlay/groupSelectionMenu.js @@ -0,0 +1,189 @@ +import events from 'events'; +import connectionManager from 'connectionManager'; +import playbackManager from 'playbackManager'; +import syncPlayManager from 'syncPlayManager'; +import loading from 'loading'; +import toast from 'toast'; +import actionsheet from 'actionsheet'; +import globalize from 'globalize'; +import playbackPermissionManager from 'playbackPermissionManager'; + +/** + * Gets active player id. + * @returns {string} The player's id. + */ +function getActivePlayerId () { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; +} + +/** + * Used when user needs to join a group. + * @param {HTMLElement} button - Element where to place the menu. + * @param {Object} user - Current user. + * @param {Object} apiClient - ApiClient. + */ +function showNewJoinGroupSelection (button, user, apiClient) { + const sessionId = getActivePlayerId() || 'none'; + const inSession = sessionId !== 'none'; + const policy = user.localUser ? user.localUser.Policy : {}; + let playingItemId; + try { + const playState = playbackManager.getPlayerState(); + playingItemId = playState.NowPlayingItem.Id; + console.debug('Item', playingItemId, 'is currently playing.'); + } catch (error) { + playingItemId = ''; + console.debug('No item is currently playing.'); + } + + apiClient.getSyncPlayGroups().then(function (response) { + response.json().then(function (groups) { + var menuItems = groups.map(function (group) { + return { + name: group.PlayingItemName, + icon: 'group', + id: group.GroupId, + selected: false, + secondaryText: group.Participants.join(', ') + }; + }); + + if (inSession && policy.SyncPlayAccess === 'CreateAndJoinGroups') { + menuItems.push({ + name: globalize.translate('LabelSyncPlayNewGroup'), + icon: 'add', + id: 'new-group', + selected: true, + secondaryText: globalize.translate('LabelSyncPlayNewGroupDescription') + }); + } + + if (menuItems.length === 0) { + if (inSession && policy.SyncPlayAccess === 'JoinGroups') { + toast({ + text: globalize.translate('MessageSyncPlayCreateGroupDenied') + }); + } else { + toast({ + text: globalize.translate('MessageSyncPlayNoGroupsAvailable') + }); + } + loading.hide(); + return; + } + + var menuOptions = { + title: globalize.translate('HeaderSyncPlaySelectGroup'), + items: menuItems, + positionTo: button, + resolveOnClick: true, + border: true + }; + + actionsheet.show(menuOptions).then(function (id) { + if (id == 'new-group') { + apiClient.createSyncPlayGroup(); + } else { + apiClient.joinSyncPlayGroup({ + GroupId: id, + PlayingItemId: playingItemId + }); + } + }).catch((error) => { + console.error('SyncPlay: unexpected error listing groups:', error); + }); + + loading.hide(); + }); + }).catch(function (error) { + console.error(error); + loading.hide(); + toast({ + text: globalize.translate('MessageSyncPlayErrorAccessingGroups') + }); + }); +} + +/** + * Used when user has joined a group. + * @param {HTMLElement} button - Element where to place the menu. + * @param {Object} user - Current user. + * @param {Object} apiClient - ApiClient. + */ +function showLeaveGroupSelection (button, user, apiClient) { + const sessionId = getActivePlayerId(); + if (!sessionId) { + syncPlayManager.signalError(); + toast({ + text: globalize.translate('MessageSyncPlayErrorNoActivePlayer') + }); + showNewJoinGroupSelection(button, user, apiClient); + return; + } + + const menuItems = [{ + name: globalize.translate('LabelSyncPlayLeaveGroup'), + icon: 'meeting_room', + id: 'leave-group', + selected: true, + secondaryText: globalize.translate('LabelSyncPlayLeaveGroupDescription') + }]; + + var menuOptions = { + title: globalize.translate('HeaderSyncPlayEnabled'), + items: menuItems, + positionTo: button, + resolveOnClick: true, + border: true + }; + + actionsheet.show(menuOptions).then(function (id) { + if (id == 'leave-group') { + apiClient.leaveSyncPlayGroup(); + } + }).catch((error) => { + console.error('SyncPlay: unexpected error showing group menu:', error); + }); + + loading.hide(); +} + +// Register to SyncPlay events +let syncPlayEnabled = false; +events.on(syncPlayManager, 'enabled', function (e, enabled) { + syncPlayEnabled = enabled; +}); + +/** + * Shows a menu to handle SyncPlay groups. + * @param {HTMLElement} button - Element where to place the menu. + */ +export function show (button) { + loading.show(); + + // TODO: should feature be disabled if playback permission is missing? + playbackPermissionManager.check().then(() => { + console.debug('Playback is allowed.'); + }).catch((error) => { + console.error('Playback not allowed!', error); + toast({ + text: globalize.translate('MessageSyncPlayPlaybackPermissionRequired') + }); + }); + + const apiClient = connectionManager.currentApiClient(); + connectionManager.user(apiClient).then((user) => { + if (syncPlayEnabled) { + showLeaveGroupSelection(button, user, apiClient); + } else { + showNewJoinGroupSelection(button, user, apiClient); + } + }).catch((error) => { + console.error(error); + loading.hide(); + toast({ + text: globalize.translate('MessageSyncPlayNoGroupsAvailable') + }); + }); +} diff --git a/src/components/syncPlay/playbackPermissionManager.js b/src/components/syncPlay/playbackPermissionManager.js new file mode 100644 index 00000000000..3c258ad18d6 --- /dev/null +++ b/src/components/syncPlay/playbackPermissionManager.js @@ -0,0 +1,51 @@ +/** + * Creates an audio element that plays a silent sound. + * @returns {HTMLMediaElement} The audio element. + */ +function createTestMediaElement () { + + const elem = document.createElement('audio'); + elem.classList.add('testMediaPlayerAudio'); + elem.classList.add('hide'); + + document.body.appendChild(elem); + + elem.volume = 1; // Volume should not be zero to trigger proper permissions + elem.src = 'assets/audio/silence.mp3'; // Silent sound + + return elem; +} + +/** + * Destroys a media element. + * @param {HTMLMediaElement} elem The element to destroy. + */ +function destroyTestMediaElement (elem) { + elem.pause(); + elem.remove(); +} + +/** + * Class that manages the playback permission. + */ +class PlaybackPermissionManager { + /** + * Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction). + * @returns {Promise} Promise that resolves succesfully if playback permission is allowed. + */ + check () { + return new Promise((resolve, reject) => { + const media = createTestMediaElement(); + media.play().then(() => { + resolve(); + }).catch((error) => { + reject(error); + }).finally(() => { + destroyTestMediaElement(media); + }); + }); + } +} + +/** PlaybackPermissionManager singleton. */ +export default new PlaybackPermissionManager(); diff --git a/src/components/syncPlay/syncPlayManager.js b/src/components/syncPlay/syncPlayManager.js new file mode 100644 index 00000000000..c847fbaede3 --- /dev/null +++ b/src/components/syncPlay/syncPlayManager.js @@ -0,0 +1,835 @@ +/** + * Module that manages the SyncPlay feature. + * @module components/syncPlay/syncPlayManager + */ + +import events from 'events'; +import connectionManager from 'connectionManager'; +import playbackManager from 'playbackManager'; +import timeSyncManager from 'timeSyncManager'; +import toast from 'toast'; +import globalize from 'globalize'; + +/** + * Waits for an event to be triggered on an object. An optional timeout can specified after which the promise is rejected. + * @param {Object} emitter Object on which to listen for events. + * @param {string} eventType Event name to listen for. + * @param {number} timeout Time in milliseconds before rejecting promise if event does not trigger. + * @returns {Promise} A promise that resolves when the event is triggered. + */ +function waitForEventOnce(emitter, eventType, timeout) { + return new Promise((resolve, reject) => { + let rejectTimeout; + if (timeout) { + rejectTimeout = setTimeout(() => { + reject('Timed out.'); + }, timeout); + } + const callback = () => { + events.off(emitter, eventType, callback); + if (rejectTimeout) { + clearTimeout(rejectTimeout); + } + resolve(arguments); + }; + events.on(emitter, eventType, callback); + }); +} + +/** + * Gets active player id. + * @returns {string} The player's id. + */ +function getActivePlayerId() { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; +} + +/** + * Playback synchronization + */ +const MaxAcceptedDelaySpeedToSync = 50; // milliseconds, delay after which SpeedToSync is enabled +const MaxAcceptedDelaySkipToSync = 300; // milliseconds, delay after which SkipToSync is enabled +const SyncMethodThreshold = 2000; // milliseconds, switches between SpeedToSync or SkipToSync +const SpeedToSyncTime = 1000; // milliseconds, duration in which the playback is sped up +const MaxAttemptsSpeedToSync = 3; // attempts before disabling SpeedToSync +const MaxAttemptsSync = 5; // attempts before disabling syncing at all + +/** + * Other constants + */ +const WaitForEventDefaultTimeout = 30000; // milliseconds +const WaitForPlayerEventTimeout = 500; // milliseconds + +/** + * Class that manages the SyncPlay feature. + */ +class SyncPlayManager { + constructor() { + this.playbackRateSupported = false; + this.syncEnabled = false; + this.playbackDiffMillis = 0; // used for stats + this.syncMethod = 'None'; // used for stats + this.syncAttempts = 0; + this.lastSyncTime = new Date(); + this.syncWatcherTimeout = null; // interval that watches playback time and syncs it + + this.lastPlaybackWaiting = null; // used to determine if player's buffering + this.minBufferingThresholdMillis = 1000; + + this.currentPlayer = null; + this.localPlayerPlaybackRate = 1.0; // used to restore user PlaybackRate + + this.syncPlayEnabledAt = null; // Server time of when SyncPlay has been enabled + this.syncPlayReady = false; // SyncPlay is ready after first ping to server + + this.lastCommand = null; + this.queuedCommand = null; + + this.scheduledCommand = null; + this.syncTimeout = null; + + this.timeOffsetWithServer = 0; // server time minus local time + this.roundTripDuration = 0; + this.notifySyncPlayReady = false; + + events.on(playbackManager, 'playbackstart', (player, state) => { + this.onPlaybackStart(player, state); + }); + + events.on(playbackManager, 'playbackstop', (stopInfo) => { + this.onPlaybackStop(stopInfo); + }); + + events.on(playbackManager, 'playerchange', () => { + this.onPlayerChange(); + }); + + this.bindToPlayer(playbackManager.getCurrentPlayer()); + + events.on(this, 'timeupdate', (event) => { + this.syncPlaybackTime(); + }); + + events.on(timeSyncManager, 'update', (event, error, timeOffset, ping) => { + if (error) { + console.debug('SyncPlay, time update issue', error); + return; + } + + this.timeOffsetWithServer = timeOffset; + this.roundTripDuration = ping * 2; + + if (this.notifySyncPlayReady) { + this.syncPlayReady = true; + events.trigger(this, 'ready'); + this.notifySyncPlayReady = false; + } + + // Report ping + if (this.syncEnabled) { + const apiClient = connectionManager.currentApiClient(); + const sessionId = getActivePlayerId(); + + if (!sessionId) { + this.signalError(); + toast({ + text: globalize.translate('MessageSyncPlayErrorMissingSession') + }); + return; + } + + apiClient.sendSyncPlayPing({ + Ping: ping + }); + } + }); + } + + /** + * Called when playback starts. + */ + onPlaybackStart (player, state) { + events.trigger(this, 'playbackstart', [player, state]); + } + + /** + * Called when playback stops. + */ + onPlaybackStop (stopInfo) { + events.trigger(this, 'playbackstop', [stopInfo]); + if (this.isSyncPlayEnabled()) { + this.disableSyncPlay(false); + } + } + + /** + * Called when the player changes. + */ + onPlayerChange () { + this.bindToPlayer(playbackManager.getCurrentPlayer()); + events.trigger(this, 'playerchange', [this.currentPlayer]); + } + + /** + * Called when playback unpauses. + */ + onPlayerUnpause () { + events.trigger(this, 'unpause', [this.currentPlayer]); + } + + /** + * Called when playback pauses. + */ + onPlayerPause() { + events.trigger(this, 'pause', [this.currentPlayer]); + } + + /** + * Called on playback progress. + * @param {Object} e The time update event. + */ + onTimeUpdate (e) { + // NOTICE: this event is unreliable, at least in Safari + // which just stops firing the event after a while. + events.trigger(this, 'timeupdate', [e]); + } + + /** + * Called when playback is resumed. + */ + onPlaying () { + // TODO: implement group wait + this.lastPlaybackWaiting = null; + events.trigger(this, 'playing'); + } + + /** + * Called when playback is buffering. + */ + onWaiting () { + // TODO: implement group wait + if (!this.lastPlaybackWaiting) { + this.lastPlaybackWaiting = new Date(); + } + events.trigger(this, 'waiting'); + } + + /** + * Gets playback buffering status. + * @returns {boolean} _true_ if player is buffering, _false_ otherwise. + */ + isBuffering () { + if (this.lastPlaybackWaiting === null) return false; + return (new Date() - this.lastPlaybackWaiting) > this.minBufferingThresholdMillis; + } + + /** + * Binds to the player's events. + * @param {Object} player The player. + */ + bindToPlayer (player) { + if (player !== this.currentPlayer) { + this.releaseCurrentPlayer(); + this.currentPlayer = player; + if (!player) return; + } + + // FIXME: the following are needed because the 'events' module + // is changing the scope when executing the callbacks. + // For instance, calling 'onPlayerUnpause' from the wrong scope breaks things because 'this' + // points to 'player' (the event emitter) instead of pointing to the SyncPlayManager singleton. + const self = this; + this._onPlayerUnpause = () => { + self.onPlayerUnpause(); + }; + + this._onPlayerPause = () => { + self.onPlayerPause(); + }; + + this._onTimeUpdate = (e) => { + self.onTimeUpdate(e); + }; + + this._onPlaying = () => { + self.onPlaying(); + }; + + this._onWaiting = () => { + self.onWaiting(); + }; + + events.on(player, 'unpause', this._onPlayerUnpause); + events.on(player, 'pause', this._onPlayerPause); + events.on(player, 'timeupdate', this._onTimeUpdate); + events.on(player, 'playing', this._onPlaying); + events.on(player, 'waiting', this._onWaiting); + + // Save player current PlaybackRate value + if (player.supports && player.supports('PlaybackRate')) { + this.localPlayerPlaybackRate = player.getPlaybackRate(); + } + } + + /** + * Removes the bindings to the current player's events. + */ + releaseCurrentPlayer () { + var player = this.currentPlayer; + if (player) { + events.off(player, 'unpause', this._onPlayerUnpause); + events.off(player, 'pause', this._onPlayerPause); + events.off(player, 'timeupdate', this._onTimeUpdate); + events.off(player, 'playing', this._onPlaying); + events.off(player, 'waiting', this._onWaiting); + // Restore player original PlaybackRate value + if (this.playbackRateSupported) { + player.setPlaybackRate(this.localPlayerPlaybackRate); + this.localPlayerPlaybackRate = 1.0; + } + this.currentPlayer = null; + this.playbackRateSupported = false; + } + } + + /** + * Handles a group update from the server. + * @param {Object} cmd The group update. + * @param {Object} apiClient The ApiClient. + */ + processGroupUpdate (cmd, apiClient) { + switch (cmd.Type) { + case 'PrepareSession': + this.prepareSession(apiClient, cmd.GroupId, cmd.Data); + break; + case 'UserJoined': + toast({ + text: globalize.translate('MessageSyncPlayUserJoined', cmd.Data) + }); + break; + case 'UserLeft': + toast({ + text: globalize.translate('MessageSyncPlayUserLeft', cmd.Data) + }); + break; + case 'GroupJoined': + this.enableSyncPlay(apiClient, new Date(cmd.Data), true); + break; + case 'NotInGroup': + case 'GroupLeft': + this.disableSyncPlay(true); + break; + case 'GroupWait': + toast({ + text: globalize.translate('MessageSyncPlayGroupWait', cmd.Data) + }); + break; + case 'GroupDoesNotExist': + toast({ + text: globalize.translate('MessageSyncPlayGroupDoesNotExist') + }); + break; + case 'CreateGroupDenied': + toast({ + text: globalize.translate('MessageSyncPlayCreateGroupDenied') + }); + break; + case 'JoinGroupDenied': + toast({ + text: globalize.translate('MessageSyncPlayJoinGroupDenied') + }); + break; + case 'LibraryAccessDenied': + toast({ + text: globalize.translate('MessageSyncPlayLibraryAccessDenied') + }); + break; + default: + console.error('processSyncPlayGroupUpdate: command is not recognised: ' + cmd.Type); + break; + } + } + + /** + * Handles a playback command from the server. + * @param {Object} cmd The playback command. + * @param {Object} apiClient The ApiClient. + */ + processCommand (cmd, apiClient) { + if (cmd === null) return; + + if (!this.isSyncPlayEnabled()) { + console.debug('SyncPlay processCommand: SyncPlay not enabled, ignoring command', cmd); + return; + } + + if (!this.syncPlayReady) { + console.debug('SyncPlay processCommand: SyncPlay not ready, queued command', cmd); + this.queuedCommand = cmd; + return; + } + + cmd.When = new Date(cmd.When); + cmd.EmittedAt = new Date(cmd.EmitttedAt); + + if (cmd.EmitttedAt < this.syncPlayEnabledAt) { + console.debug('SyncPlay processCommand: ignoring old command', cmd); + return; + } + + // Check if new command differs from last one + if (this.lastCommand && + this.lastCommand.When === cmd.When && + this.lastCommand.PositionTicks === cmd.PositionTicks && + this.Command === cmd.Command + ) { + console.debug('SyncPlay processCommand: ignoring duplicate command', cmd); + return; + } + + this.lastCommand = cmd; + console.log('SyncPlay will', cmd.Command, 'at', cmd.When, 'PositionTicks', cmd.PositionTicks); + + switch (cmd.Command) { + case 'Play': + this.schedulePlay(cmd.When, cmd.PositionTicks); + break; + case 'Pause': + this.schedulePause(cmd.When, cmd.PositionTicks); + break; + case 'Seek': + this.scheduleSeek(cmd.When, cmd.PositionTicks); + break; + default: + console.error('processCommand: command is not recognised: ' + cmd.Type); + break; + } + } + + /** + * Prepares this client to join a group by loading the required content. + * @param {Object} apiClient The ApiClient. + * @param {string} groupId The group to join. + * @param {Object} sessionData Info about the content to load. + */ + prepareSession (apiClient, groupId, sessionData) { + const serverId = apiClient.serverInfo().Id; + playbackManager.play({ + ids: sessionData.ItemIds, + startPositionTicks: sessionData.StartPositionTicks, + mediaSourceId: sessionData.MediaSourceId, + audioStreamIndex: sessionData.AudioStreamIndex, + subtitleStreamIndex: sessionData.SubtitleStreamIndex, + startIndex: sessionData.StartIndex, + serverId: serverId + }).then(() => { + waitForEventOnce(this, 'playbackstart', WaitForEventDefaultTimeout).then(() => { + var sessionId = getActivePlayerId(); + if (!sessionId) { + console.error('Missing sessionId!'); + toast({ + text: globalize.translate('MessageSyncPlayErrorMissingSession') + }); + return; + } + // Get playing item id + let playingItemId; + try { + const playState = playbackManager.getPlayerState(); + playingItemId = playState.NowPlayingItem.Id; + } catch (error) { + playingItemId = ''; + } + // Make sure the server has received the player state + waitForEventOnce(playbackManager, 'reportplayback', WaitForEventDefaultTimeout).then((success) => { + this.localPause(); + if (!success) { + console.warning('Error reporting playback state to server. Joining group will fail.'); + } + apiClient.joinSyncPlayGroup({ + GroupId: groupId, + PlayingItemId: playingItemId + }); + }).catch(() => { + console.error('Timed out while waiting for `reportplayback` event!'); + toast({ + text: globalize.translate('MessageSyncPlayErrorMedia') + }); + return; + }); + }).catch(() => { + console.error('Timed out while waiting for `playbackstart` event!'); + if (!this.isSyncPlayEnabled()) { + toast({ + text: globalize.translate('MessageSyncPlayErrorMedia') + }); + } + return; + }); + }).catch((error) => { + console.error(error); + toast({ + text: globalize.translate('MessageSyncPlayErrorMedia') + }); + }); + } + + /** + * Enables SyncPlay. + * @param {Object} apiClient The ApiClient. + * @param {Date} enabledAt When SyncPlay has been enabled. Server side date. + * @param {boolean} showMessage Display message. + */ + enableSyncPlay (apiClient, enabledAt, showMessage = false) { + this.syncPlayEnabledAt = enabledAt; + this.injectPlaybackManager(); + events.trigger(this, 'enabled', [true]); + + waitForEventOnce(this, 'ready').then(() => { + this.processCommand(this.queuedCommand, apiClient); + this.queuedCommand = null; + }); + + this.syncPlayReady = false; + this.notifySyncPlayReady = true; + + timeSyncManager.forceUpdate(); + + if (showMessage) { + toast({ + text: globalize.translate('MessageSyncPlayEnabled') + }); + } + } + + /** + * Disables SyncPlay. + * @param {boolean} showMessage Display message. + */ + disableSyncPlay (showMessage = false) { + this.syncPlayEnabledAt = null; + this.syncPlayReady = false; + this.lastCommand = null; + this.queuedCommand = null; + this.syncEnabled = false; + events.trigger(this, 'enabled', [false]); + this.restorePlaybackManager(); + + if (showMessage) { + toast({ + text: globalize.translate('MessageSyncPlayDisabled') + }); + } + } + + /** + * Gets SyncPlay status. + * @returns {boolean} _true_ if user joined a group, _false_ otherwise. + */ + isSyncPlayEnabled () { + return this.syncPlayEnabledAt !== null; + } + + /** + * Schedules a resume playback on the player at the specified clock time. + * @param {Date} playAtTime The server's UTC time at which to resume playback. + * @param {number} positionTicks The PositionTicks from where to resume. + */ + schedulePlay (playAtTime, positionTicks) { + this.clearScheduledCommand(); + const currentTime = new Date(); + const playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime); + + if (playAtTimeLocal > currentTime) { + const playTimeout = playAtTimeLocal - currentTime; + this.localSeek(positionTicks); + + this.scheduledCommand = setTimeout(() => { + this.localUnpause(); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true; + }, SyncMethodThreshold / 2); + + }, playTimeout); + + console.debug('Scheduled play in', playTimeout / 1000.0, 'seconds.'); + } else { + // Group playback already started + const serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; + waitForEventOnce(this, 'unpause').then(() => { + this.localSeek(serverPositionTicks); + }); + this.localUnpause(); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true; + }, SyncMethodThreshold / 2); + } + } + + /** + * Schedules a pause playback on the player at the specified clock time. + * @param {Date} pauseAtTime The server's UTC time at which to pause playback. + * @param {number} positionTicks The PositionTicks where player will be paused. + */ + schedulePause (pauseAtTime, positionTicks) { + this.clearScheduledCommand(); + const currentTime = new Date(); + const pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime); + + const callback = () => { + waitForEventOnce(this, 'pause', WaitForPlayerEventTimeout).then(() => { + this.localSeek(positionTicks); + }).catch(() => { + // Player was already paused, seeking + this.localSeek(positionTicks); + }); + this.localPause(); + }; + + if (pauseAtTimeLocal > currentTime) { + const pauseTimeout = pauseAtTimeLocal - currentTime; + this.scheduledCommand = setTimeout(callback, pauseTimeout); + + console.debug('Scheduled pause in', pauseTimeout / 1000.0, 'seconds.'); + } else { + callback(); + } + } + + /** + * Schedules a seek playback on the player at the specified clock time. + * @param {Date} pauseAtTime The server's UTC time at which to seek playback. + * @param {number} positionTicks The PositionTicks where player will be seeked. + */ + scheduleSeek (seekAtTime, positionTicks) { + this.schedulePause(seekAtTime, positionTicks); + } + + /** + * Clears the current scheduled command. + */ + clearScheduledCommand () { + clearTimeout(this.scheduledCommand); + clearTimeout(this.syncTimeout); + + this.syncEnabled = false; + if (this.currentPlayer) { + this.currentPlayer.setPlaybackRate(1); + } + this.clearSyncIcon(); + } + + /** + * Overrides some PlaybackManager's methods to intercept playback commands. + */ + injectPlaybackManager () { + if (!this.isSyncPlayEnabled()) return; + if (playbackManager.syncPlayEnabled) return; + + // TODO: make this less hacky + playbackManager._localUnpause = playbackManager.unpause; + playbackManager._localPause = playbackManager.pause; + playbackManager._localSeek = playbackManager.seek; + + playbackManager.unpause = this.playRequest; + playbackManager.pause = this.pauseRequest; + playbackManager.seek = this.seekRequest; + playbackManager.syncPlayEnabled = true; + } + + /** + * Restores original PlaybackManager's methods. + */ + restorePlaybackManager () { + if (this.isSyncPlayEnabled()) return; + if (!playbackManager.syncPlayEnabled) return; + + playbackManager.unpause = playbackManager._localUnpause; + playbackManager.pause = playbackManager._localPause; + playbackManager.seek = playbackManager._localSeek; + playbackManager.syncPlayEnabled = false; + } + + /** + * Overrides PlaybackManager's unpause method. + */ + playRequest (player) { + var apiClient = connectionManager.currentApiClient(); + apiClient.requestSyncPlayStart(); + } + + /** + * Overrides PlaybackManager's pause method. + */ + pauseRequest (player) { + var apiClient = connectionManager.currentApiClient(); + apiClient.requestSyncPlayPause(); + // Pause locally as well, to give the user some little control + playbackManager._localUnpause(player); + } + + /** + * Overrides PlaybackManager's seek method. + */ + seekRequest (PositionTicks, player) { + var apiClient = connectionManager.currentApiClient(); + apiClient.requestSyncPlaySeek({ + PositionTicks: PositionTicks + }); + } + + /** + * Calls original PlaybackManager's unpause method. + */ + localUnpause(player) { + if (playbackManager.syncPlayEnabled) { + playbackManager._localUnpause(player); + } else { + playbackManager.unpause(player); + } + } + + /** + * Calls original PlaybackManager's pause method. + */ + localPause(player) { + if (playbackManager.syncPlayEnabled) { + playbackManager._localPause(player); + } else { + playbackManager.pause(player); + } + } + + /** + * Calls original PlaybackManager's seek method. + */ + localSeek(PositionTicks, player) { + if (playbackManager.syncPlayEnabled) { + playbackManager._localSeek(PositionTicks, player); + } else { + playbackManager.seek(PositionTicks, player); + } + } + + /** + * Attempts to sync playback time with estimated server time. + * + * When sync is enabled, the following will be checked: + * - check if local playback time is close enough to the server playback time + * If it is not, then a playback time sync will be attempted. + * Two methods of syncing are available: + * - SpeedToSync: speeds up the media for some time to catch up (default is one second) + * - SkipToSync: seeks the media to the estimated correct time + * SpeedToSync aims to reduce the delay as much as possible, whereas SkipToSync is less pretentious. + */ + syncPlaybackTime () { + // Attempt to sync only when media is playing. + if (!this.lastCommand || this.lastCommand.Command !== 'Play' || this.isBuffering()) return; + + const currentTime = new Date(); + + // Avoid overloading the browser + const elapsed = currentTime - this.lastSyncTime; + if (elapsed < SyncMethodThreshold / 2) return; + this.lastSyncTime = currentTime; + + const playAtTime = this.lastCommand.When; + + const currentPositionTicks = playbackManager.currentTime(); + // Estimate PositionTicks on server + const serverPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000; + // Measure delay that needs to be recovered + // diff might be caused by the player internally starting the playback + const diffMillis = (serverPositionTicks - currentPositionTicks) / 10000.0; + + this.playbackDiffMillis = diffMillis; + + if (this.syncEnabled) { + const absDiffMillis = Math.abs(diffMillis); + // TODO: SpeedToSync sounds bad on songs + // TODO: SpeedToSync is failing on Safari (Mojave); even if playbackRate is supported, some delay seems to exist + if (this.playbackRateSupported && absDiffMillis > MaxAcceptedDelaySpeedToSync && absDiffMillis < SyncMethodThreshold) { + // Disable SpeedToSync if it keeps failing + if (this.syncAttempts > MaxAttemptsSpeedToSync) { + this.playbackRateSupported = false; + } + // SpeedToSync method + const speed = 1 + diffMillis / SpeedToSyncTime; + + this.currentPlayer.setPlaybackRate(speed); + this.syncEnabled = false; + this.syncAttempts++; + this.showSyncIcon('SpeedToSync (x' + speed + ')'); + + this.syncTimeout = setTimeout(() => { + this.currentPlayer.setPlaybackRate(1); + this.syncEnabled = true; + this.clearSyncIcon(); + }, SpeedToSyncTime); + } else if (absDiffMillis > MaxAcceptedDelaySkipToSync) { + // Disable SkipToSync if it keeps failing + if (this.syncAttempts > MaxAttemptsSync) { + this.syncEnabled = false; + this.showSyncIcon('Sync disabled (too many attempts)'); + } + // SkipToSync method + this.localSeek(serverPositionTicks); + this.syncEnabled = false; + this.syncAttempts++; + this.showSyncIcon('SkipToSync (' + this.syncAttempts + ')'); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true; + this.clearSyncIcon(); + }, SyncMethodThreshold / 2); + } else { + // Playback is synced + if (this.syncAttempts > 0) { + console.debug('Playback has been synced after', this.syncAttempts, 'attempts.'); + } + this.syncAttempts = 0; + } + } + } + + /** + * Gets SyncPlay stats. + * @returns {Object} The SyncPlay stats. + */ + getStats () { + return { + TimeOffset: this.timeOffsetWithServer, + PlaybackDiff: this.playbackDiffMillis, + SyncMethod: this.syncMethod + }; + } + + /** + * Emits an event to update the SyncPlay status icon. + */ + showSyncIcon (syncMethod) { + this.syncMethod = syncMethod; + events.trigger(this, 'syncing', [true, this.syncMethod]); + } + + /** + * Emits an event to clear the SyncPlay status icon. + */ + clearSyncIcon () { + this.syncMethod = 'None'; + events.trigger(this, 'syncing', [false, this.syncMethod]); + } + + /** + * Signals an error state, which disables and resets SyncPlay for a new session. + */ + signalError () { + this.disableSyncPlay(); + } +} + +/** SyncPlayManager singleton. */ +export default new SyncPlayManager(); diff --git a/src/components/syncPlay/timeSyncManager.js b/src/components/syncPlay/timeSyncManager.js new file mode 100644 index 00000000000..d3b59589ee4 --- /dev/null +++ b/src/components/syncPlay/timeSyncManager.js @@ -0,0 +1,205 @@ +/** + * Module that manages time syncing with server. + * @module components/syncPlay/timeSyncManager + */ + +import events from 'events'; +import connectionManager from 'connectionManager'; + +/** + * Time estimation + */ +const NumberOfTrackedMeasurements = 8; +const PollingIntervalGreedy = 1000; // milliseconds +const PollingIntervalLowProfile = 60000; // milliseconds +const GreedyPingCount = 3; + +/** + * Class that stores measurement data. + */ +class Measurement { + /** + * Creates a new measurement. + * @param {Date} requestSent Client's timestamp of the request transmission + * @param {Date} requestReceived Server's timestamp of the request reception + * @param {Date} responseSent Server's timestamp of the response transmission + * @param {Date} responseReceived Client's timestamp of the response reception + */ + constructor(requestSent, requestReceived, responseSent, responseReceived) { + this.requestSent = requestSent.getTime(); + this.requestReceived = requestReceived.getTime(); + this.responseSent = responseSent.getTime(); + this.responseReceived = responseReceived.getTime(); + } + + /** + * Time offset from server. + */ + getOffset () { + return ((this.requestReceived - this.requestSent) + (this.responseSent - this.responseReceived)) / 2; + } + + /** + * Get round-trip delay. + */ + getDelay () { + return (this.responseReceived - this.requestSent) - (this.responseSent - this.requestReceived); + } + + /** + * Get ping time. + */ + getPing () { + return this.getDelay() / 2; + } +} + +/** + * Class that manages time syncing with server. + */ +class TimeSyncManager { + constructor() { + this.pingStop = true; + this.pollingInterval = PollingIntervalGreedy; + this.poller = null; + this.pings = 0; // number of pings + this.measurement = null; // current time sync + this.measurements = []; + } + + /** + * Gets status of time sync. + * @returns {boolean} _true_ if a measurement has been done, _false_ otherwise. + */ + isReady() { + return !!this.measurement; + } + + /** + * Gets time offset with server. + * @returns {number} The time offset. + */ + getTimeOffset () { + return this.measurement ? this.measurement.getOffset() : 0; + } + + /** + * Gets ping time to server. + * @returns {number} The ping time. + */ + getPing () { + return this.measurement ? this.measurement.getPing() : 0; + } + + /** + * Updates time offset between server and client. + * @param {Measurement} measurement The new measurement. + */ + updateTimeOffset(measurement) { + this.measurements.push(measurement); + if (this.measurements.length > NumberOfTrackedMeasurements) { + this.measurements.shift(); + } + + // Pick measurement with minimum delay + const sortedMeasurements = this.measurements.slice(0); + sortedMeasurements.sort((a, b) => a.getDelay() - b.getDelay()); + this.measurement = sortedMeasurements[0]; + } + + /** + * Schedules a ping request to the server. Triggers time offset update. + */ + requestPing() { + if (!this.poller) { + this.poller = setTimeout(() => { + this.poller = null; + const apiClient = connectionManager.currentApiClient(); + const requestSent = new Date(); + apiClient.getServerTime().then((response) => { + const responseReceived = new Date(); + response.json().then((data) => { + const requestReceived = new Date(data.RequestReceptionTime); + const responseSent = new Date(data.ResponseTransmissionTime); + + const measurement = new Measurement(requestSent, requestReceived, responseSent, responseReceived); + this.updateTimeOffset(measurement); + + // Avoid overloading server + if (this.pings >= GreedyPingCount) { + this.pollingInterval = PollingIntervalLowProfile; + } else { + this.pings++; + } + + events.trigger(this, 'update', [null, this.getTimeOffset(), this.getPing()]); + }); + }).catch((error) => { + console.error(error); + events.trigger(this, 'update', [error, null, null]); + }).finally(() => { + this.requestPing(); + }); + + }, this.pollingInterval); + } + } + + /** + * Drops accumulated measurements. + */ + resetMeasurements () { + this.measurement = null; + this.measurements = []; + } + + /** + * Starts the time poller. + */ + startPing() { + this.requestPing(); + } + + /** + * Stops the time poller. + */ + stopPing() { + if (this.poller) { + clearTimeout(this.poller); + this.poller = null; + } + } + + /** + * Resets poller into greedy mode. + */ + forceUpdate() { + this.stopPing(); + this.pollingInterval = PollingIntervalGreedy; + this.pings = 0; + this.startPing(); + } + + /** + * Converts server time to local time. + * @param {Date} server The time to convert. + * @returns {Date} Local time. + */ + serverDateToLocal(server) { + // server - local = offset + return new Date(server.getTime() - this.getTimeOffset()); + } + + /** + * Converts local time to server time. + * @param {Date} local The time to convert. + * @returns {Date} Server time. + */ + localDateToServer(local) { + // server - local = offset + return new Date(local.getTime() + this.getTimeOffset()); + } +} + +/** TimeSyncManager singleton. */ +export default new TimeSyncManager(); diff --git a/src/components/tabbedview/itemstab.js b/src/components/tabbedview/itemstab.js index 6cfd4084244..884cfa2015b 100644 --- a/src/components/tabbedview/itemstab.js +++ b/src/components/tabbedview/itemstab.js @@ -181,7 +181,8 @@ define(['playbackManager', 'userSettings', 'alphaPicker', 'alphaNumericShortcuts return; } - btnSortIcon.innerHTML = values.sortOrder === 'Descending' ? '' : ''; + btnSortIcon.classList.remove('arrow_downward', 'arrow_upward'); + btnSortIcon.classList.add(values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); } function bindAll(elems, eventName, fn) { diff --git a/src/components/thememediaplayer.js b/src/components/themeMediaPlayer.js similarity index 100% rename from src/components/thememediaplayer.js rename to src/components/themeMediaPlayer.js diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js index 22eb53396c8..7b8e49e4d2f 100644 --- a/src/components/toast/toast.js +++ b/src/components/toast/toast.js @@ -26,7 +26,7 @@ define(['css!./toast'], function () { }; } - var elem = document.createElement("div"); + var elem = document.createElement('div'); elem.classList.add('toast'); elem.innerHTML = options.text; diff --git a/src/components/tunerpicker.js b/src/components/tunerPicker.js similarity index 54% rename from src/components/tunerpicker.js rename to src/components/tunerPicker.js index 4dd5ecd3def..e7c92851a45 100644 --- a/src/components/tunerpicker.js +++ b/src/components/tunerPicker.js @@ -1,38 +1,38 @@ -define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize", "loading", "browser", "focusManager", "scrollHelper", "material-icons", "formDialogStyle", "emby-button", "emby-itemscontainer", "cardStyle"], function (dialogHelper, dom, layoutManager, connectionManager, globalize, loading, browser, focusManager, scrollHelper) { - "use strict"; +define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize', 'loading', 'browser', 'focusManager', 'scrollHelper', 'material-icons', 'formDialogStyle', 'emby-button', 'emby-itemscontainer', 'cardStyle'], function (dialogHelper, dom, layoutManager, connectionManager, globalize, loading, browser, focusManager, scrollHelper) { + 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; function getEditorHtml() { - var html = ""; + var html = ''; html += '
'; html += '
'; html += '
'; - html += "

" + globalize.translate("DetectingDevices") + "...

"; - html += "

" + globalize.translate("MessagePleaseWait") + "

"; - html += "
"; - html += '

' + globalize.translate("HeaderNewDevices") + "

"; + html += '

' + globalize.translate('DetectingDevices') + '...

'; + html += '

' + globalize.translate('MessagePleaseWait') + '

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

' + globalize.translate('HeaderNewDevices') + '

'; html += '
'; - html += "
"; - html += "
"; - return html += "
"; + html += '

'; + html += '
'; + return html += '
'; } function getDeviceHtml(device) { var padderClass; - var html = ""; - var cssClass = "card scalableCard"; - var cardBoxCssClass = "cardBox visualCardBox"; - cssClass += " backdropCard backdropCard-scalable"; - padderClass = "cardPadder-backdrop"; + var html = ''; + var cssClass = 'card scalableCard'; + var cardBoxCssClass = 'cardBox visualCardBox'; + cssClass += ' backdropCard backdropCard-scalable'; + padderClass = 'cardPadder-backdrop'; // TODO move card creation code to Card component if (layoutManager.tv) { - cssClass += " show-focus"; + cssClass += ' show-focus'; if (enableFocusTransform) { - cssClass += " show-animation"; + cssClass += ' show-animation'; } } @@ -41,56 +41,56 @@ define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize" html += '
'; html += '
'; html += '
'; - html += '
dvr
'; - html += "
"; - html += "
"; + html += '
'; + html += '
'; + html += '
'; html += '
'; - html += '
' + getTunerName(device.Type) + "
"; - html += '
' + device.FriendlyName + "
"; + html += '
' + getTunerName(device.Type) + '
'; + html += '
' + device.FriendlyName + '
'; html += '
'; - html += device.Url || " "; - html += "
"; - html += "
"; - html += "
"; - return html += ""; + html += device.Url || ' '; + html += '
'; + html += '
'; + html += '
'; + return html += ''; } function getTunerName(providerId) { switch (providerId = providerId.toLowerCase()) { - case "m3u": - return "M3U"; + case 'm3u': + return 'M3U'; - case "hdhomerun": - return "HDHomerun"; + case 'hdhomerun': + return 'HDHomerun'; - case "hauppauge": - return "Hauppauge"; + case 'hauppauge': + return 'Hauppauge'; - case "satip": - return "DVB"; + case 'satip': + return 'DVB'; default: - return "Unknown"; + return 'Unknown'; } } function renderDevices(view, devices) { var i; var length; - var html = ""; + var html = ''; for (i = 0, length = devices.length; i < length; i++) { html += getDeviceHtml(devices[i]); } if (devices.length) { - view.querySelector(".devicesHeader").classList.remove("hide"); + view.querySelector('.devicesHeader').classList.remove('hide'); } else { - html = "


" + globalize.translate("NoNewDevicesFound") + "

"; - view.querySelector(".devicesHeader").classList.add("hide"); + html = '


' + globalize.translate('NoNewDevicesFound') + '

'; + view.querySelector('.devicesHeader').classList.add('hide'); } - var elem = view.querySelector(".results"); + var elem = view.querySelector('.results'); elem.innerHTML = html; if (layoutManager.tv) { @@ -100,13 +100,13 @@ define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize" function discoverDevices(view, apiClient) { loading.show(); - view.querySelector(".loadingContent").classList.remove("hide"); - return ApiClient.getJSON(ApiClient.getUrl("LiveTv/Tuners/Discvover", { + view.querySelector('.loadingContent').classList.remove('hide'); + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/Tuners/Discvover', { NewDevicesOnly: true })).then(function (devices) { currentDevices = devices; renderDevices(view, devices); - view.querySelector(".loadingContent").classList.add("hide"); + view.querySelector('.loadingContent').classList.add('hide'); loading.hide(); }); } @@ -119,31 +119,31 @@ define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize" }; if (layoutManager.tv) { - dialogOptions.size = "fullscreen"; + dialogOptions.size = 'fullscreen'; } else { - dialogOptions.size = "small"; + dialogOptions.size = 'small'; } var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add("formDialog"); - var html = ""; + dlg.classList.add('formDialog'); + var html = ''; html += '
'; - html += ''; + html += ''; html += '

'; - html += globalize.translate("HeaderLiveTvTunerSetup"); - html += "

"; - html += "
"; + html += globalize.translate('HeaderLiveTvTunerSetup'); + html += ''; + html += '
'; html += getEditorHtml(); dlg.innerHTML = html; - dlg.querySelector(".btnCancel").addEventListener("click", function () { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); var deviceResult; - dlg.querySelector(".results").addEventListener("click", function (e) { - var tunerCard = dom.parentWithClass(e.target, "card"); + dlg.querySelector('.results').addEventListener('click', function (e) { + var tunerCard = dom.parentWithClass(e.target, 'card'); if (tunerCard) { - var deviceId = tunerCard.getAttribute("data-id"); + var deviceId = tunerCard.getAttribute('data-id'); deviceResult = currentDevices.filter(function (d) { return d.DeviceId === deviceId; })[0]; @@ -152,14 +152,14 @@ define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize" }); if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector(".formDialogContent"), false); + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } var apiClient = connectionManager.getApiClient(options.serverId); discoverDevices(dlg, apiClient); if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg.querySelector(".formDialogContent"), false); + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); } return dialogHelper.open(dlg).then(function () { diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js index cf11e5736d6..be1cdf575b4 100644 --- a/src/components/tvproviders/schedulesdirect.js +++ b/src/components/tvproviders/schedulesdirect.js @@ -1,31 +1,31 @@ -define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "emby-select", "emby-button", "flexStyles"], function ($, loading) { - "use strict"; +define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'listViewStyle', 'emby-input', 'emby-select', 'emby-button', 'flexStyles'], function ($, loading, globalize) { + 'use strict'; return function (page, providerId, options) { function reload() { loading.show(); - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { var info = config.ListingProviders.filter(function (i) { return i.Id === providerId; })[0] || {}; listingsId = info.ListingsId; - $("#selectListing", page).val(info.ListingsId || ""); - page.querySelector(".txtUser").value = info.Username || ""; - page.querySelector(".txtPass").value = ""; - page.querySelector(".txtZipCode").value = info.ZipCode || ""; + $('#selectListing', page).val(info.ListingsId || ''); + page.querySelector('.txtUser').value = info.Username || ''; + page.querySelector('.txtPass').value = ''; + page.querySelector('.txtZipCode').value = info.ZipCode || ''; if (info.Username && info.Password) { - page.querySelector(".listingsSection").classList.remove("hide"); + page.querySelector('.listingsSection').classList.remove('hide'); } else { - page.querySelector(".listingsSection").classList.add("hide"); + page.querySelector('.listingsSection').classList.add('hide'); } - page.querySelector(".chkAllTuners").checked = info.EnableAllTuners; + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; if (info.EnableAllTuners) { - page.querySelector(".selectTunersSection").classList.add("hide"); + page.querySelector('.selectTunersSection').classList.add('hide'); } else { - page.querySelector(".selectTunersSection").classList.remove("hide"); + page.querySelector('.selectTunersSection').classList.remove('hide'); } setCountry(info); @@ -34,7 +34,7 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em } function setCountry(info) { - ApiClient.getJSON(ApiClient.getUrl("LiveTv/ListingProviders/SchedulesDirect/Countries")).then(function (result) { + ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { var i; var length; var countryList = []; @@ -42,7 +42,7 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em for (var region in result) { var countries = result[region]; - if (countries.length && "ZZZ" !== region) { + if (countries.length && 'ZZZ' !== region) { for (i = 0, length = countries.length; i < length; i++) { countryList.push({ name: countries[i].fullName, @@ -63,13 +63,13 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em return 0; }); - $("#selectCountry", page).html(countryList.map(function (c) { - return '"; - }).join("")).val(info.Country || ""); - $(page.querySelector(".txtZipCode")).trigger("change"); + $('#selectCountry', page).html(countryList.map(function (c) { + return ''; + }).join('')).val(info.Country || ''); + $(page.querySelector('.txtZipCode')).trigger('change'); }, function () { // ApiClient.getJSON() error handler Dashboard.alert({ - message: Globalize.translate("ErrorGettingTvLineups") + message: globalize.translate('ErrorGettingTvLineups') }); }); loading.hide(); @@ -77,11 +77,11 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em function sha256(str) { if (!self.TextEncoder) { - return Promise.resolve(""); + return Promise.resolve(''); } - var buffer = new TextEncoder("utf-8").encode(str); - return crypto.subtle.digest("SHA-256", buffer).then(function (hash) { + var buffer = new TextEncoder('utf-8').encode(str); + return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { return hex(hash); }); } @@ -93,22 +93,22 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em for (var i = 0; i < view.byteLength; i += 4) { var value = view.getUint32(i); var stringValue = value.toString(16); - var paddedValue = ("00000000" + stringValue).slice(-"00000000".length); + var paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); hexCodes.push(paddedValue); } - return hexCodes.join(""); + return hexCodes.join(''); } function submitLoginForm() { loading.show(); - sha256(page.querySelector(".txtPass").value).then(function (passwordHash) { + sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { var info = { - Type: "SchedulesDirect", - Username: page.querySelector(".txtUser").value, + Type: 'SchedulesDirect', + Username: page.querySelector('.txtUser').value, EnableAllTuners: true, Password: passwordHash, - Pw: page.querySelector(".txtPass").value + Pw: page.querySelector('.txtPass').value }; var id = providerId; @@ -117,56 +117,56 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em } ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/ListingProviders", { + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { ValidateLogin: true }), data: JSON.stringify(info), - contentType: "application/json", - dataType: "json" + contentType: 'application/json', + dataType: 'json' }).then(function (result) { Dashboard.processServerConfigurationUpdateResult(); providerId = result.Id; reload(); }, function () { Dashboard.alert({ // ApiClient.ajax() error handler - message: Globalize.translate("ErrorSavingTvProvider") + message: globalize.translate('ErrorSavingTvProvider') }); }); }); } function submitListingsForm() { - var selectedListingsId = $("#selectListing", page).val(); + var selectedListingsId = $('#selectListing', page).val(); if (!selectedListingsId) { return void Dashboard.alert({ - message: Globalize.translate("ErrorPleaseSelectLineup") + message: globalize.translate('ErrorPleaseSelectLineup') }); } loading.show(); var id = providerId; - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { var info = config.ListingProviders.filter(function (i) { return i.Id === id; })[0]; - info.ZipCode = page.querySelector(".txtZipCode").value; - info.Country = $("#selectCountry", page).val(); + info.ZipCode = page.querySelector('.txtZipCode').value; + info.Country = $('#selectCountry', page).val(); info.ListingsId = selectedListingsId; - info.EnableAllTuners = page.querySelector(".chkAllTuners").checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $(".chkTuner", page).get().filter(function (i) { + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (i) { return i.checked; }).map(function (i) { - return i.getAttribute("data-id"); + return i.getAttribute('data-id'); }); ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/ListingProviders", { + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { ValidateListings: true }), data: JSON.stringify(info), - contentType: "application/json" + contentType: 'application/json' }).then(function (result) { loading.hide(); @@ -174,11 +174,11 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em Dashboard.processServerConfigurationUpdateResult(); } - Events.trigger(self, "submitted"); + Events.trigger(self, 'submitted'); }, function () { loading.hide(); Dashboard.alert({ - message: Globalize.translate("ErrorAddingListingsToSchedulesDirect") + message: globalize.translate('ErrorAddingListingsToSchedulesDirect') }); }); }); @@ -186,79 +186,79 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em function refreshListings(value) { if (!value) { - return void $("#selectListing", page).html(""); + return void $('#selectListing', page).html(''); } loading.show(); ApiClient.ajax({ - type: "GET", - url: ApiClient.getUrl("LiveTv/ListingProviders/Lineups", { + type: 'GET', + url: ApiClient.getUrl('LiveTv/ListingProviders/Lineups', { Id: providerId, Location: value, - Country: $("#selectCountry", page).val() + Country: $('#selectCountry', page).val() }), - dataType: "json" + dataType: 'json' }).then(function (result) { - $("#selectListing", page).html(result.map(function (o) { - return '"; + $('#selectListing', page).html(result.map(function (o) { + return ''; })); if (listingsId) { - $("#selectListing", page).val(listingsId); + $('#selectListing', page).val(listingsId); } loading.hide(); }, function (result) { Dashboard.alert({ - message: Globalize.translate("ErrorGettingTvLineups") + message: globalize.translate('ErrorGettingTvLineups') }); - refreshListings(""); + refreshListings(''); loading.hide(); }); } function getTunerName(providerId) { switch (providerId = providerId.toLowerCase()) { - case "m3u": - return "M3U Playlist"; - case "hdhomerun": - return "HDHomerun"; - case "satip": - return "DVB"; + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; default: - return "Unknown"; + return 'Unknown'; } } function refreshTunerDevices(page, providerInfo, devices) { - var html = ""; + var html = ''; for (var i = 0, length = devices.length; i < length; i++) { var device = devices[i]; html += '
'; var enabledTuners = providerInfo.EnabledTuners || []; var isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id); - var checkedAttribute = isChecked ? " checked" : ""; - html += '"; + var checkedAttribute = isChecked ? ' checked' : ''; + html += ''; html += '
'; html += '
'; html += device.FriendlyName || getTunerName(device.Type); - html += "
"; + html += '
'; html += '
'; html += device.Url; - html += "
"; - html += "
"; - html += "
"; + html += '
'; + html += '
'; + html += '
'; } - page.querySelector(".tunerList").innerHTML = html; + page.querySelector('.tunerList').innerHTML = html; } var listingsId; var self = this; self.submit = function () { - page.querySelector(".btnSubmitListingsContainer").click(); + page.querySelector('.btnSubmitListingsContainer').click(); }; self.init = function () { @@ -267,30 +267,30 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em // Only hide the buttons if explicitly set to false; default to showing if undefined or null // FIXME: rename this option to clarify logic var hideCancelButton = options.showCancelButton === false; - page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton); + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); var hideSubmitButton = options.showSubmitButton === false; - page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton); + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - $(".formLogin", page).on("submit", function () { + $('.formLogin', page).on('submit', function () { submitLoginForm(); return false; }); - $(".formListings", page).on("submit", function () { + $('.formListings', page).on('submit', function () { submitListingsForm(); return false; }); - $(".txtZipCode", page).on("change", function () { + $('.txtZipCode', page).on('change', function () { refreshListings(this.value); }); - page.querySelector(".chkAllTuners").addEventListener("change", function (e) { + page.querySelector('.chkAllTuners').addEventListener('change', function (e) { if (e.target.checked) { - page.querySelector(".selectTunersSection").classList.add("hide"); + page.querySelector('.selectTunersSection').classList.add('hide'); } else { - page.querySelector(".selectTunersSection").classList.remove("hide"); + page.querySelector('.selectTunersSection').classList.remove('hide'); } }); - $(".createAccountHelp", page).html(Globalize.translate("MessageCreateAccountAt", 'http://www.schedulesdirect.org')); + $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', 'http://www.schedulesdirect.org')); reload(); }; }; diff --git a/src/components/tvproviders/schedulesdirect.template.html b/src/components/tvproviders/schedulesdirect.template.html index 3cfba06fe11..abe19b50f58 100644 --- a/src/components/tvproviders/schedulesdirect.template.html +++ b/src/components/tvproviders/schedulesdirect.template.html @@ -1,7 +1,7 @@

Schedules Direct

- ${Help} + ${Help}

diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js index 7e7d381f09d..4c39b144309 100644 --- a/src/components/tvproviders/xmltv.js +++ b/src/components/tvproviders/xmltv.js @@ -1,5 +1,5 @@ -define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "paper-icon-button-light"], function ($, loading) { - "use strict"; +define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'emby-input', 'listViewStyle', 'paper-icon-button-light'], function ($, loading, globalize) { + 'use strict'; return function (page, providerId, options) { function getListingProvider(config, id) { @@ -15,26 +15,26 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa return getListingProvider(); } - return ApiClient.getJSON(ApiClient.getUrl("LiveTv/ListingProviders/Default")); + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/Default')); } function reload() { loading.show(); - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { getListingProvider(config, providerId).then(function (info) { - page.querySelector(".txtPath").value = info.Path || ""; - page.querySelector(".txtKids").value = (info.KidsCategories || []).join("|"); - page.querySelector(".txtNews").value = (info.NewsCategories || []).join("|"); - page.querySelector(".txtSports").value = (info.SportsCategories || []).join("|"); - page.querySelector(".txtMovies").value = (info.MovieCategories || []).join("|"); - page.querySelector(".txtMoviePrefix").value = info.MoviePrefix || ""; - page.querySelector(".txtUserAgent").value = info.UserAgent || ""; - page.querySelector(".chkAllTuners").checked = info.EnableAllTuners; - - if (page.querySelector(".chkAllTuners").checked) { - page.querySelector(".selectTunersSection").classList.add("hide"); + page.querySelector('.txtPath').value = info.Path || ''; + page.querySelector('.txtKids').value = (info.KidsCategories || []).join('|'); + page.querySelector('.txtNews').value = (info.NewsCategories || []).join('|'); + page.querySelector('.txtSports').value = (info.SportsCategories || []).join('|'); + page.querySelector('.txtMovies').value = (info.MovieCategories || []).join('|'); + page.querySelector('.txtMoviePrefix').value = info.MoviePrefix || ''; + page.querySelector('.txtUserAgent').value = info.UserAgent || ''; + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; + + if (page.querySelector('.chkAllTuners').checked) { + page.querySelector('.selectTunersSection').classList.add('hide'); } else { - page.querySelector(".selectTunersSection").classList.remove("hide"); + page.querySelector('.selectTunersSection').classList.remove('hide'); } refreshTunerDevices(page, info, config.TunerHosts); @@ -47,7 +47,7 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa var value = txtInput.value; if (value) { - return value.split("|"); + return value.split('|'); } return []; @@ -56,31 +56,31 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa function submitListingsForm() { loading.show(); var id = providerId; - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { var info = config.ListingProviders.filter(function (provider) { return provider.Id === id; })[0] || {}; - info.Type = "xmltv"; - info.Path = page.querySelector(".txtPath").value; - info.MoviePrefix = page.querySelector(".txtMoviePrefix").value || null; - info.UserAgent = page.querySelector(".txtUserAgent").value || null; - info.MovieCategories = getCategories(page.querySelector(".txtMovies")); - info.KidsCategories = getCategories(page.querySelector(".txtKids")); - info.NewsCategories = getCategories(page.querySelector(".txtNews")); - info.SportsCategories = getCategories(page.querySelector(".txtSports")); - info.EnableAllTuners = page.querySelector(".chkAllTuners").checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $(".chkTuner", page).get().filter(function (tuner) { + info.Type = 'xmltv'; + info.Path = page.querySelector('.txtPath').value; + info.MoviePrefix = page.querySelector('.txtMoviePrefix').value || null; + info.UserAgent = page.querySelector('.txtUserAgent').value || null; + info.MovieCategories = getCategories(page.querySelector('.txtMovies')); + info.KidsCategories = getCategories(page.querySelector('.txtKids')); + info.NewsCategories = getCategories(page.querySelector('.txtNews')); + info.SportsCategories = getCategories(page.querySelector('.txtSports')); + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (tuner) { return tuner.checked; }).map(function (tuner) { - return tuner.getAttribute("data-id"); + return tuner.getAttribute('data-id'); }); ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/ListingProviders", { + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { ValidateListings: true }), data: JSON.stringify(info), - contentType: "application/json" + contentType: 'application/json' }).then(function (result) { loading.hide(); @@ -88,11 +88,11 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa Dashboard.processServerConfigurationUpdateResult(); } - Events.trigger(self, "submitted"); + Events.trigger(self, 'submitted'); }, function () { loading.hide(); Dashboard.alert({ - message: Globalize.translate("ErrorAddingXmlTvFile") + message: globalize.translate('ErrorAddingXmlTvFile') }); }); }); @@ -100,51 +100,51 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa function getTunerName(providerId) { switch (providerId = providerId.toLowerCase()) { - case "m3u": - return "M3U Playlist"; - case "hdhomerun": - return "HDHomerun"; - case "satip": - return "DVB"; + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; default: - return "Unknown"; + return 'Unknown'; } } function refreshTunerDevices(page, providerInfo, devices) { - var html = ""; + var html = ''; for (var i = 0, length = devices.length; i < length; i++) { var device = devices[i]; html += '
'; var enabledTuners = providerInfo.EnabledTuners || []; var isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id); - var checkedAttribute = isChecked ? " checked" : ""; - html += '"; + var checkedAttribute = isChecked ? ' checked' : ''; + html += ''; html += '
'; html += '
'; html += device.FriendlyName || getTunerName(device.Type); - html += "
"; + html += '
'; html += '
'; html += device.Url; - html += "
"; - html += "
"; - html += "
"; + html += '
'; + html += '
'; + html += '
'; } - page.querySelector(".tunerList").innerHTML = html; + page.querySelector('.tunerList').innerHTML = html; } function onSelectPathClick(e) { - var page = $(e.target).parents(".xmltvForm")[0]; + var page = $(e.target).parents('.xmltvForm')[0]; - require(["directorybrowser"], function (directoryBrowser) { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ includeFiles: true, callback: function (path) { if (path) { - var txtPath = page.querySelector(".txtPath"); + var txtPath = page.querySelector('.txtPath'); txtPath.value = path; txtPath.focus(); } @@ -157,7 +157,7 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa var self = this; self.submit = function () { - page.querySelector(".btnSubmitListings").click(); + page.querySelector('.btnSubmitListings').click(); }; self.init = function () { @@ -166,21 +166,21 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa // Only hide the buttons if explicitly set to false; default to showing if undefined or null // FIXME: rename this option to clarify logic var hideCancelButton = options.showCancelButton === false; - page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton); + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); var hideSubmitButton = options.showSubmitButton === false; - page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton); + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - $("form", page).on("submit", function () { + $('form', page).on('submit', function () { submitListingsForm(); return false; }); - page.querySelector("#btnSelectPath").addEventListener("click", onSelectPathClick); - page.querySelector(".chkAllTuners").addEventListener("change", function (evt) { + page.querySelector('#btnSelectPath').addEventListener('click', onSelectPathClick); + page.querySelector('.chkAllTuners').addEventListener('change', function (evt) { if (evt.target.checked) { - page.querySelector(".selectTunersSection").classList.add("hide"); + page.querySelector('.selectTunersSection').classList.add('hide'); } else { - page.querySelector(".selectTunersSection").classList.remove("hide"); + page.querySelector('.selectTunersSection').classList.remove('hide'); } }); reload(); diff --git a/src/components/tvproviders/xmltv.template.html b/src/components/tvproviders/xmltv.template.html index 7f6490e6214..72c29904b32 100644 --- a/src/components/tvproviders/xmltv.template.html +++ b/src/components/tvproviders/xmltv.template.html @@ -1,7 +1,7 @@

Xml TV

- ${Help} + ${Help}
@@ -12,7 +12,7 @@

Xml TV

- +
${XmlTvPathHelp}
diff --git a/src/components/upnextdialog/upnextdialog.css b/src/components/upnextdialog/upnextdialog.css index 15f91b29d94..05e3b10f57a 100644 --- a/src/components/upnextdialog/upnextdialog.css +++ b/src/components/upnextdialog/upnextdialog.css @@ -63,8 +63,8 @@ height: auto; width: 100%; box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); - border: 0; user-drag: none; + border: 0; user-select: none; -moz-user-select: none; -webkit-user-drag: none; diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index 225859e0b34..3e9c9f9c5e6 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -10,7 +10,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (options.type === 'Primary') { @@ -44,7 +44,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l function imageUrl(item, options) { options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { diff --git a/src/components/userdatabuttons/userdatabuttons.js b/src/components/userdatabuttons/userdatabuttons.js index 2636be1e1dd..86b9902133b 100644 --- a/src/components/userdatabuttons/userdatabuttons.js +++ b/src/components/userdatabuttons/userdatabuttons.js @@ -30,7 +30,7 @@ define(['connectionManager', 'globalize', 'dom', 'itemHelper', 'paper-icon-butto iconCssClass += 'material-icons'; - return ''; + return ''; } function onContainerClick(e) { @@ -90,10 +90,10 @@ define(['connectionManager', 'globalize', 'dom', 'itemHelper', 'paper-icon-butto return html; } - var btnCssClass = "btnUserData"; + var btnCssClass = 'btnUserData'; if (cssClass) { - btnCssClass += " " + cssClass; + btnCssClass += ' ' + cssClass; } var iconCssClass = options.iconCssClass; diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index 607e7029d17..dfc9569f34e 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -1,16 +1,16 @@ -define(["browser", "dom", "layoutManager", "css!components/viewManager/viewContainer"], function (browser, dom, layoutManager) { - "use strict"; +define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewContainer'], function (browser, dom, layoutManager) { + 'use strict'; function setControllerClass(view, options) { if (options.controllerFactory) { return Promise.resolve(); } - var controllerUrl = view.getAttribute("data-controller"); + var controllerUrl = view.getAttribute('data-controller'); if (controllerUrl) { - if (0 === controllerUrl.indexOf("__plugin/")) { - controllerUrl = controllerUrl.substring("__plugin/".length); + if (0 === controllerUrl.indexOf('__plugin/')) { + controllerUrl = controllerUrl.substring('__plugin/'.length); } controllerUrl = Dashboard.getConfigurationResourceUrl(controllerUrl); @@ -38,21 +38,17 @@ define(["browser", "dom", "layoutManager", "css!components/viewManager/viewConta pageIndex = 0; } - var isPluginpage = -1 !== options.url.toLowerCase().indexOf("/configurationpage"); + var isPluginpage = -1 !== options.url.toLowerCase().indexOf('/configurationpage'); var newViewInfo = normalizeNewView(options, isPluginpage); var newView = newViewInfo.elem; var modulesToLoad = []; - if (isPluginpage) { - modulesToLoad.push("legacyDashboard"); - } - if (newViewInfo.hasjQuerySelect) { - modulesToLoad.push("legacySelectMenu"); + modulesToLoad.push('legacySelectMenu'); } if (newViewInfo.hasjQueryChecked) { - modulesToLoad.push("fnchecked"); + modulesToLoad.push('fnchecked'); } return new Promise(function (resolve) { @@ -65,12 +61,12 @@ define(["browser", "dom", "layoutManager", "css!components/viewManager/viewConta var view = newView; - if ("string" == typeof view) { - view = document.createElement("div"); + if ('string' == typeof view) { + view = document.createElement('div'); view.innerHTML = newView; } - view.classList.add("mainAnimatedPage"); + view.classList.add('mainAnimatedPage'); if (currentPage) { if (newViewInfo.hasScript && window.$) { @@ -88,17 +84,17 @@ define(["browser", "dom", "layoutManager", "css!components/viewManager/viewConta } if (options.type) { - view.setAttribute("data-type", options.type); + view.setAttribute('data-type', options.type); } var properties = []; if (options.fullscreen) { - properties.push("fullscreen"); + properties.push('fullscreen'); } if (properties.length) { - view.setAttribute("data-properties", properties.join(",")); + view.setAttribute('data-properties', properties.join(',')); } allPages[pageIndex] = view; @@ -133,11 +129,11 @@ define(["browser", "dom", "layoutManager", "css!components/viewManager/viewConta function parseHtml(html, hasScript) { if (hasScript) { - html = replaceAll(html, "\x3c!----\x3e", "<\/script>"); + html = replaceAll(html, '\x3c!----\x3e', '<\/script>'); } - var wrapper = document.createElement("div"); + var wrapper = document.createElement('div'); wrapper.innerHTML = html; return wrapper.querySelector('div[data-role="page"]'); } @@ -149,11 +145,11 @@ define(["browser", "dom", "layoutManager", "css!components/viewManager/viewConta return viewHtml; } - var hasScript = -1 !== viewHtml.indexOf("= 0 && rec.top < vpHeight + thresholdY; - var bViz = rec.bottom > 0 && rec.bottom <= vpHeight + thresholdY; - var lViz = rec.left >= 0 && rec.left < vpWidth + thresholdX; - var rViz = rec.right > 0 && rec.right <= vpWidth + thresholdX; - var vVisible = partial ? tViz || bViz : tViz && bViz; - var hVisible = partial ? lViz || rViz : lViz && rViz; - - return vVisible && hVisible; - } - - return visibleInViewport; -}); diff --git a/src/config.json b/src/config.json new file mode 120000 index 00000000000..f1bd2db71ed --- /dev/null +++ b/src/config.json @@ -0,0 +1 @@ +config.template.json \ No newline at end of file diff --git a/src/config.template.json b/src/config.template.json new file mode 100644 index 00000000000..4b22a699d6d --- /dev/null +++ b/src/config.template.json @@ -0,0 +1,3 @@ +{ + "multiserver": false +} diff --git a/src/controllers/apikeys.js b/src/controllers/apikeys.js deleted file mode 100644 index 448ffa29a7a..00000000000 --- a/src/controllers/apikeys.js +++ /dev/null @@ -1,83 +0,0 @@ -define(["datetime", "loading", "libraryMenu", "dom", "globalize", "emby-button"], function (datetime, loading, libraryMenu, dom, globalize) { - "use strict"; - - function revoke(page, key) { - require(["confirm"], function (confirm) { - 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 renderKeys(page, keys) { - var rows = keys.map(function (item) { - var html = ""; - html += '
'; - html += '"; - html += '"; - 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) { - require(["prompt"], function (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 () { - var page = this; - page.querySelector(".btnNewKey").addEventListener("click", function () { - showNewKeyPrompt(page); - }); - page.querySelector(".tblApiKeys").addEventListener("click", function (e) { - var btnRevoke = dom.parentWithClass(e.target, "btnRevoke"); - - if (btnRevoke) { - revoke(page, btnRevoke.getAttribute("data-token")); - } - }); - }); - pageIdOn("pagebeforeshow", "apiKeysPage", function () { - loadData(this); - }); -}); diff --git a/src/controllers/auth/addserver.js b/src/controllers/auth/addserver.js index a55ba3066c6..622d19082e0 100644 --- a/src/controllers/auth/addserver.js +++ b/src/controllers/auth/addserver.js @@ -1,52 +1,52 @@ -define(["appSettings", "loading", "browser", "emby-button"], function(appSettings, loading, browser) { - "use strict"; +define(['appSettings', 'loading', 'browser', 'globalize', 'emby-button'], function(appSettings, loading, browser, globalize) { + 'use strict'; function handleConnectionResult(page, result) { loading.hide(); switch (result.State) { - case "SignedIn": + case 'SignedIn': var apiClient = result.ApiClient; Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); - Dashboard.navigate("home.html"); + Dashboard.navigate('home.html'); break; - case "ServerSignIn": - Dashboard.navigate("login.html?serverid=" + result.Servers[0].Id, false, "none"); + case 'ServerSignIn': + Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id, false, 'none'); break; - case "ServerSelection": - Dashboard.navigate("selectserver.html", false, "none"); + case 'ServerSelection': + Dashboard.navigate('selectserver.html', false, 'none'); break; - case "ServerUpdateNeeded": + case 'ServerUpdateNeeded': Dashboard.alert({ - message: Globalize.translate("ServerUpdateNeeded", 'https://github.com/jellyfin/jellyfin') + message: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') }); break; - case "Unavailable": + case 'Unavailable': Dashboard.alert({ - message: Globalize.translate("MessageUnableToConnectToServer"), - title: Globalize.translate("HeaderConnectionFailure") + message: globalize.translate('MessageUnableToConnectToServer'), + title: globalize.translate('HeaderConnectionFailure') }); } } function submitServer(page) { loading.show(); - var host = page.querySelector("#txtServerHost").value; + var host = page.querySelector('#txtServerHost').value; ConnectionManager.connectToAddress(host, { enableAutoLogin: appSettings.enableAutoLogin() }).then(function(result) { handleConnectionResult(page, result); }, function() { handleConnectionResult(page, { - State: "Unavailable" + State: 'Unavailable' }); }); } return function(view, params) { - view.querySelector(".addServerForm").addEventListener("submit", onServerSubmit); - view.querySelector(".btnCancel").addEventListener("click", goBack); + view.querySelector('.addServerForm').addEventListener('submit', onServerSubmit); + view.querySelector('.btnCancel').addEventListener('click', goBack); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(view); }); @@ -57,7 +57,7 @@ define(["appSettings", "loading", "browser", "emby-button"], function(appSetting } function goBack() { - require(["appRouter"], function(appRouter) { + require(['appRouter'], function(appRouter) { appRouter.back(); }); } diff --git a/src/controllers/auth/forgotpassword.js b/src/controllers/auth/forgotpassword.js index e0f8ea4ef85..3756bf814da 100644 --- a/src/controllers/auth/forgotpassword.js +++ b/src/controllers/auth/forgotpassword.js @@ -1,34 +1,34 @@ -define([], function () { - "use strict"; +define(['globalize'], function (globalize) { + 'use strict'; function processForgotPasswordResult(result) { - if ("ContactAdmin" == result.Action) { + if ('ContactAdmin' == result.Action) { return void Dashboard.alert({ - message: Globalize.translate("MessageContactAdminToResetPassword"), - title: Globalize.translate("HeaderForgotPassword") + message: globalize.translate('MessageContactAdminToResetPassword'), + title: globalize.translate('HeaderForgotPassword') }); } - if ("InNetworkRequired" == result.Action) { + if ('InNetworkRequired' == result.Action) { return void Dashboard.alert({ - message: Globalize.translate("MessageForgotPasswordInNetworkRequired"), - title: Globalize.translate("HeaderForgotPassword") + message: globalize.translate('MessageForgotPasswordInNetworkRequired'), + title: globalize.translate('HeaderForgotPassword') }); } - if ("PinCode" == result.Action) { - var msg = Globalize.translate("MessageForgotPasswordFileCreated"); - msg += "
"; - msg += "
"; - msg += "Enter PIN here to finish Password Reset
"; - msg += "
"; + if ('PinCode' == result.Action) { + var msg = globalize.translate('MessageForgotPasswordFileCreated'); + msg += '
'; + msg += '
'; + msg += 'Enter PIN here to finish Password Reset
'; + msg += '
'; msg += result.PinFile; - msg += "
"; + msg += '
'; return void Dashboard.alert({ message: msg, - title: Globalize.translate("HeaderForgotPassword"), + title: globalize.translate('HeaderForgotPassword'), callback: function () { - Dashboard.navigate("forgotpasswordpin.html"); + Dashboard.navigate('forgotpasswordpin.html'); } }); } @@ -37,17 +37,17 @@ define([], function () { return function (view, params) { function onSubmit(e) { ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("Users/ForgotPassword"), - dataType: "json", + type: 'POST', + url: ApiClient.getUrl('Users/ForgotPassword'), + dataType: 'json', data: { - EnteredUsername: view.querySelector("#txtName").value + EnteredUsername: view.querySelector('#txtName').value } }).then(processForgotPasswordResult); e.preventDefault(); return false; } - view.querySelector("form").addEventListener("submit", onSubmit); + view.querySelector('form').addEventListener('submit', onSubmit); }; }); diff --git a/src/controllers/auth/forgotpasswordpin.js b/src/controllers/auth/forgotpasswordpin.js index 47b1c899b98..2a51890d2fe 100644 --- a/src/controllers/auth/forgotpasswordpin.js +++ b/src/controllers/auth/forgotpasswordpin.js @@ -1,41 +1,41 @@ -define([], function () { - "use strict"; +define(['globalize'], function (globalize) { + 'use strict'; function processForgotPasswordResult(result) { if (result.Success) { - var msg = Globalize.translate("MessagePasswordResetForUsers"); - msg += "
"; - msg += "
"; - msg += result.UsersReset.join("
"); + var msg = globalize.translate('MessagePasswordResetForUsers'); + msg += '
'; + msg += '
'; + msg += result.UsersReset.join('
'); return void Dashboard.alert({ message: msg, - title: Globalize.translate("HeaderPasswordReset"), + title: globalize.translate('HeaderPasswordReset'), callback: function () { - window.location.href = "index.html"; + window.location.href = 'index.html'; } }); } Dashboard.alert({ - message: Globalize.translate("MessageInvalidForgotPasswordPin"), - title: Globalize.translate("HeaderPasswordReset") + message: globalize.translate('MessageInvalidForgotPasswordPin'), + title: globalize.translate('HeaderPasswordReset') }); } return function (view, params) { function onSubmit(e) { ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("Users/ForgotPassword/Pin"), - dataType: "json", + type: 'POST', + url: ApiClient.getUrl('Users/ForgotPassword/Pin'), + dataType: 'json', data: { - Pin: view.querySelector("#txtPin").value + Pin: view.querySelector('#txtPin').value } }).then(processForgotPasswordResult); e.preventDefault(); return false; } - view.querySelector("form").addEventListener("submit", onSubmit); + view.querySelector('form').addEventListener('submit', onSubmit); }; }); diff --git a/src/controllers/auth/login.js b/src/controllers/auth/login.js index 4296b8bfb3f..640cb26c2e7 100644 --- a/src/controllers/auth/login.js +++ b/src/controllers/auth/login.js @@ -1,5 +1,5 @@ -define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layoutManager", "browser", "cardStyle", "emby-checkbox"], function (appHost, appSettings, dom, connectionManager, loading, layoutManager, browser) { - "use strict"; +define(['apphost', 'appSettings', 'dom', 'connectionManager', 'loading', 'layoutManager', 'browser', 'globalize', 'cardStyle', 'emby-checkbox'], function (appHost, appSettings, dom, connectionManager, loading, layoutManager, browser, globalize) { + 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; @@ -7,56 +7,50 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout loading.show(); apiClient.authenticateUserByName(username, password).then(function (result) { var user = result.User; - var serverId = getParameterByName("serverid"); - var newUrl; - - if (user.Policy.IsAdministrator && !serverId) { - newUrl = "dashboard.html"; - } else { - newUrl = "home.html"; - } - loading.hide(); + Dashboard.onServerChanged(user.Id, result.AccessToken, apiClient); - Dashboard.navigate(newUrl); + Dashboard.navigate('home.html'); }, function (response) { - page.querySelector("#txtManualName").value = ""; - page.querySelector("#txtManualPassword").value = ""; + page.querySelector('#txtManualName').value = ''; + page.querySelector('#txtManualPassword').value = ''; loading.hide(); - if (response.status === 401) { - require(["toast"], function (toast) { - toast(Globalize.translate("MessageInvalidUser")); + const UnauthorizedOrForbidden = [401, 403]; + if (UnauthorizedOrForbidden.includes(response.status)) { + require(['toast'], function (toast) { + const messageKey = response.status === 401 ? 'MessageInvalidUser' : 'MessageUnauthorizedUser'; + toast(globalize.translate(messageKey)); }); } else { Dashboard.alert({ - message: Globalize.translate("MessageUnableToConnectToServer"), - title: Globalize.translate("HeaderConnectionFailure") + message: globalize.translate('MessageUnableToConnectToServer'), + title: globalize.translate('HeaderConnectionFailure') }); } }); } 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"); + 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 (focusPassword) { - context.querySelector("#txtManualPassword").focus(); + context.querySelector('#txtManualPassword').focus(); } else { - context.querySelector("#txtManualName").focus(); + context.querySelector('#txtManualName').focus(); } if (showCancel) { - context.querySelector(".btnCancel").classList.remove("hide"); + context.querySelector('.btnCancel').classList.remove('hide'); } else { - context.querySelector(".btnCancel").classList.add("hide"); + context.querySelector('.btnCancel').classList.add('hide'); } } - var metroColors = ["#6FBD45", "#4BB3DD", "#4164A5", "#E12026", "#800080", "#E1B222", "#008040", "#0094FF", "#FF00C7", "#FF870F", "#7F0037"]; + var metroColors = ['#6FBD45', '#4BB3DD', '#4164A5', '#E12026', '#800080', '#E1B222', '#008040', '#0094FF', '#FF00C7', '#FF870F', '#7F0037']; function getRandomMetroColor() { var index = Math.floor(Math.random() * (metroColors.length - 1)); @@ -80,23 +74,23 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout } function loadUserList(context, apiClient, users) { - var html = ""; + var html = ''; for (var i = 0; i < users.length; i++) { var user = users[i]; // TODO move card creation code to Card component - var cssClass = "card squareCard scalableCard squareCard-scalable"; + var cssClass = 'card squareCard scalableCard squareCard-scalable'; if (layoutManager.tv) { - cssClass += " show-focus"; + cssClass += ' show-focus'; if (enableFocusTransform) { - cssClass += " show-animation"; + cssClass += ' show-animation'; } } - var cardBoxCssClass = "cardBox cardBox-bottompadded"; + var cardBoxCssClass = 'cardBox cardBox-bottompadded'; html += '"; + html += '
' + user.Name + '
'; + html += ''; + html += ''; + html += ''; } - context.querySelector("#divUsers").innerHTML = html; + context.querySelector('#divUsers').innerHTML = html; } return function (view, params) { @@ -141,60 +135,60 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout } function showVisualForm() { - view.querySelector(".visualLoginForm").classList.remove("hide"); - view.querySelector(".manualLoginForm").classList.add("hide"); - view.querySelector(".btnManual").classList.remove("hide"); + view.querySelector('.visualLoginForm').classList.remove('hide'); + view.querySelector('.manualLoginForm').classList.add('hide'); + view.querySelector('.btnManual').classList.remove('hide'); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(view); }); } - view.querySelector("#divUsers").addEventListener("click", function (e) { - var card = dom.parentWithClass(e.target, "card"); - var cardContent = card ? card.querySelector(".cardContent") : null; + view.querySelector('#divUsers').addEventListener('click', function (e) { + var card = dom.parentWithClass(e.target, 'card'); + var cardContent = card ? card.querySelector('.cardContent') : null; if (cardContent) { var context = view; - var id = cardContent.getAttribute("data-userid"); - var name = cardContent.getAttribute("data-username"); - var haspw = cardContent.getAttribute("data-haspw"); + var id = cardContent.getAttribute('data-userid'); + var name = cardContent.getAttribute('data-username'); + var haspw = cardContent.getAttribute('data-haspw'); if (id === 'manual') { - context.querySelector("#txtManualName").value = ""; + context.querySelector('#txtManualName').value = ''; showManualForm(context, true); } else if (haspw == 'false') { - authenticateUserByName(context, getApiClient(), name, ""); + authenticateUserByName(context, getApiClient(), name, ''); } else { - context.querySelector("#txtManualName").value = name; - context.querySelector("#txtManualPassword").value = ""; + context.querySelector('#txtManualName').value = name; + context.querySelector('#txtManualPassword').value = ''; showManualForm(context, true, true); } } }); - view.querySelector(".manualLoginForm").addEventListener("submit", function (e) { - appSettings.enableAutoLogin(view.querySelector(".chkRememberLogin").checked); + view.querySelector('.manualLoginForm').addEventListener('submit', function (e) { + appSettings.enableAutoLogin(view.querySelector('.chkRememberLogin').checked); var apiClient = getApiClient(); - authenticateUserByName(view, apiClient, view.querySelector("#txtManualName").value, view.querySelector("#txtManualPassword").value); + 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('.btnForgotPassword').addEventListener('click', function () { + Dashboard.navigate('forgotpassword.html'); }); - view.querySelector(".btnCancel").addEventListener("click", showVisualForm); - view.querySelector(".btnManual").addEventListener("click", function () { - view.querySelector("#txtManualName").value = ""; + view.querySelector('.btnCancel').addEventListener('click', showVisualForm); + view.querySelector('.btnManual').addEventListener('click', function () { + view.querySelector('#txtManualName').value = ''; showManualForm(view, true); }); - view.querySelector(".btnSelectServer").addEventListener("click", function () { + view.querySelector('.btnSelectServer').addEventListener('click', function () { Dashboard.selectServer(); }); - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { loading.show(); if (!appHost.supports('multiserver')) { - view.querySelector(".btnSelectServer").classList.add("hide"); + view.querySelector('.btnSelectServer').classList.add('hide'); } var apiClient = getApiClient(); @@ -203,14 +197,14 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout showVisualForm(); loadUserList(view, apiClient, users); } else { - view.querySelector("#txtManualName").value = ""; + view.querySelector('#txtManualName').value = ''; showManualForm(view, false, false); } }).catch().then(function () { loading.hide(); }); - apiClient.getJSON(apiClient.getUrl("Branding/Configuration")).then(function (options) { - view.querySelector(".disclaimer").textContent = options.LoginDisclaimer || ""; + apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { + view.querySelector('.disclaimer').textContent = options.LoginDisclaimer || ''; }); }); }; diff --git a/src/controllers/auth/selectserver.js b/src/controllers/auth/selectserver.js index e766dbdb5c0..ba883134840 100644 --- a/src/controllers/auth/selectserver.js +++ b/src/controllers/auth/selectserver.js @@ -1,5 +1,5 @@ -define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focusManager", "connectionManager", "globalize", "actionsheet", "dom", "browser", "material-icons", "flexStyles", "emby-scroller", "emby-itemscontainer", "cardStyle", "emby-button"], function (loading, appRouter, layoutManager, appSettings, appHost, focusManager, connectionManager, globalize, actionSheet, dom, browser) { - "use strict"; +define(['loading', 'appRouter', 'layoutManager', 'appSettings', 'apphost', 'focusManager', 'connectionManager', 'globalize', 'actionsheet', 'dom', 'browser', 'material-icons', 'flexStyles', 'emby-scroller', 'emby-itemscontainer', 'cardStyle', 'emby-button'], function (loading, appRouter, layoutManager, appSettings, appHost, focusManager, connectionManager, globalize, actionSheet, dom, browser) { + 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; @@ -8,8 +8,8 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu return { name: server.Name, showIcon: true, - icon: "cast", - cardType: "", + icon: 'cast', + cardType: '', id: server.Id, server: server }; @@ -18,28 +18,28 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu var cardImageContainer; if (item.showIcon) { - cardImageContainer = '' + item.icon + ""; + cardImageContainer = ''; } else { cardImageContainer = '
'; } // TODO move card creation code to Card component - var cssClass = "card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable"; + var cssClass = 'card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable'; if (layoutManager.tv) { - cssClass += " show-focus"; + cssClass += ' show-focus'; if (enableFocusTransform) { - cssClass += " show-animation"; + cssClass += ' show-animation'; } } - var cardBoxCssClass = "cardBox"; + var cardBoxCssClass = 'cardBox'; var innerOpening = '
'; var cardContainer = ''; - cardContainer += ''; return cardContainer; - }).join(""); - var itemsContainer = view.querySelector(".servers"); + }).join(''); + var itemsContainer = view.querySelector('.servers'); if (!items.length) { - html = '

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

"; + html = '

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

'; } itemsContainer.innerHTML = html; @@ -66,20 +66,20 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu } function updatePageStyle(view, params) { - if (params.showuser == "1") { - view.classList.add("libraryPage"); - view.classList.remove("standalonePage"); - view.classList.add("noSecondaryNavPage"); + 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"); + view.classList.add('standalonePage'); + view.classList.remove('libraryPage'); + view.classList.remove('noSecondaryNavPage'); } } function showGeneralError() { loading.hide(); - alertText(globalize.translate("DefaultErrorMessage")); + alertText(globalize.translate('DefaultErrorMessage')); } function alertText(text) { @@ -89,13 +89,13 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu } function alertTextWithOptions(options) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert(options); }); } function showServerConnectionFailure() { - alertText(globalize.translate("MessageUnableToConnectToServer"), globalize.translate("HeaderConnectionFailure")); + alertText(globalize.translate('MessageUnableToConnectToServer')); } return function (view, params) { @@ -108,20 +108,20 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu var apiClient = result.ApiClient; switch (result.State) { - case "SignedIn": + case 'SignedIn': Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); - Dashboard.navigate("home.html"); + Dashboard.navigate('home.html'); break; - case "ServerSignIn": + case 'ServerSignIn': Dashboard.onServerChanged(null, null, apiClient); - Dashboard.navigate("login.html?serverid=" + result.Servers[0].Id); + Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id); break; - case "ServerUpdateNeeded": + case 'ServerUpdateNeeded': alertTextWithOptions({ - text: globalize.translate("core#ServerUpdateNeeded", "https://github.com/jellyfin/jellyfin"), - html: globalize.translate("core#ServerUpdateNeeded", 'https://github.com/jellyfin/jellyfin') + text: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), + html: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') }); break; @@ -142,23 +142,23 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu function onServerClick(server) { var menuItems = []; menuItems.push({ - name: globalize.translate("Connect"), - id: "connect" + name: globalize.translate('Connect'), + id: 'connect' }); menuItems.push({ - name: globalize.translate("Delete"), - id: "delete" + name: globalize.translate('Delete'), + id: 'delete' }); actionSheet.show({ items: menuItems, title: server.Name }).then(function (id) { switch (id) { - case "connect": + case 'connect': connectToServer(server); break; - case "delete": + case 'delete': deleteServer(server); } }); @@ -180,7 +180,7 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu var servers; updatePageStyle(view, params); - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { var isRestored = e.detail.isRestored; appRouter.setTitle(null); @@ -188,16 +188,16 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu loadServers(); } }); - view.querySelector(".servers").addEventListener("click", function (e) { - var card = dom.parentWithClass(e.target, "card"); + view.querySelector('.servers').addEventListener('click', function (e) { + var card = dom.parentWithClass(e.target, 'card'); if (card) { - var url = card.getAttribute("data-url"); + var url = card.getAttribute('data-url'); if (url) { appRouter.show(url); } else { - var id = card.getAttribute("data-id"); + var id = card.getAttribute('data-id'); onServerClick(servers.filter(function (s) { return s.Id === id; })[0]); diff --git a/src/controllers/dashboard/apikeys.js b/src/controllers/dashboard/apikeys.js new file mode 100644 index 00000000000..f43bfd0329d --- /dev/null +++ b/src/controllers/dashboard/apikeys.js @@ -0,0 +1,83 @@ +define(['datetime', 'loading', 'libraryMenu', 'dom', 'globalize', 'emby-button'], function (datetime, loading, libraryMenu, dom, globalize) { + 'use strict'; + + function revoke(page, key) { + require(['confirm'], function (confirm) { + 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 renderKeys(page, keys) { + var rows = keys.map(function (item) { + var html = ''; + html += '
'; + html += ''; + html += ''; + 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) { + require(['prompt'], function (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 () { + var page = this; + page.querySelector('.btnNewKey').addEventListener('click', function () { + showNewKeyPrompt(page); + }); + page.querySelector('.tblApiKeys').addEventListener('click', function (e) { + var btnRevoke = dom.parentWithClass(e.target, 'btnRevoke'); + + if (btnRevoke) { + revoke(page, btnRevoke.getAttribute('data-token')); + } + }); + }); + pageIdOn('pagebeforeshow', 'apiKeysPage', function () { + loadData(this); + }); +}); diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 2057deaf6f7..e6e01338b0f 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -1,24 +1,24 @@ -define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "humanedate", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) { - "use strict"; +define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globalize', 'date-fns', 'dfnshelper', 'loading', 'connectionManager', 'playMethodHelper', 'cardBuilder', 'imageLoader', 'components/activitylog', 'scripts/imagehelper', 'indicators', 'listViewStyle', 'emby-button', 'flexStyles', 'emby-button', 'emby-itemscontainer'], function (datetime, events, itemHelper, serverNotifications, dom, globalize, datefns, dfnshelper, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) { + 'use strict'; function showPlaybackInfo(btn, session) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { var title; var text = []; var displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - if (displayPlayMethod === "DirectStream") { - title = globalize.translate("DirectStreaming"); - text.push(globalize.translate("DirectStreamHelp1")); - text.push("
"); - text.push(globalize.translate("DirectStreamHelp2")); - } else if (displayPlayMethod === "Transcode") { - title = globalize.translate("Transcoding"); - text.push(globalize.translate("MediaIsBeingConverted")); + if (displayPlayMethod === 'DirectStream') { + title = globalize.translate('DirectStreaming'); + text.push(globalize.translate('DirectStreamHelp1')); + text.push('
'); + text.push(globalize.translate('DirectStreamHelp2')); + } else if (displayPlayMethod === 'Transcode') { + title = globalize.translate('Transcoding'); + text.push(globalize.translate('MediaIsBeingConverted')); if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - text.push("
"); - text.push(globalize.translate("LabelReasonForTranscoding")); + text.push('
'); + text.push(globalize.translate('LabelReasonForTranscoding')); session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { text.push(globalize.translate(transcodeReason)); }); @@ -26,18 +26,18 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } alert({ - text: text.join("
"), + text: text.join('
'), title: title }); }); } function showSendMessageForm(btn, session) { - require(["prompt"], function (prompt) { + require(['prompt'], function (prompt) { prompt({ - title: globalize.translate("HeaderSendMessage"), - label: globalize.translate("LabelMessageText"), - confirmText: globalize.translate("ButtonSend") + title: globalize.translate('HeaderSendMessage'), + label: globalize.translate('LabelMessageText'), + confirmText: globalize.translate('ButtonSend') }).then(function (text) { if (text) { connectionManager.getApiClient(session.ServerId).sendMessageCommand(session.Id, { @@ -50,20 +50,20 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } function showOptionsMenu(btn, session) { - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { var menuItems = []; if (session.ServerId && session.DeviceId !== connectionManager.deviceId()) { menuItems.push({ - name: globalize.translate("SendMessage"), - id: "sendmessage" + name: globalize.translate('SendMessage'), + id: 'sendmessage' }); } if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { menuItems.push({ - name: globalize.translate("ViewPlaybackInfo"), - id: "transcodinginfo" + name: globalize.translate('ViewPlaybackInfo'), + id: 'transcodinginfo' }); } @@ -72,11 +72,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa positionTo: btn }).then(function (id) { switch (id) { - case "sendmessage": + case 'sendmessage': showSendMessageForm(btn, session); break; - case "transcodinginfo": + case 'transcodinginfo': showPlaybackInfo(btn, session); } }); @@ -84,28 +84,28 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } function onActiveDevicesClick(evt) { - var btn = dom.parentWithClass(evt.target, "sessionCardButton"); + var btn = dom.parentWithClass(evt.target, 'sessionCardButton'); if (btn) { - var card = dom.parentWithClass(btn, "card"); + var card = dom.parentWithClass(btn, 'card'); if (card) { var sessionId = card.id; var session = (DashboardPage.sessionsList || []).filter(function (dashboardSession) { - return "session" + dashboardSession.Id === sessionId; + return 'session' + dashboardSession.Id === sessionId; })[0]; if (session) { - if (btn.classList.contains("btnCardOptions")) { + if (btn.classList.contains('btnCardOptions')) { showOptionsMenu(btn, session); - } else if (btn.classList.contains("btnSessionInfo")) { + } else if (btn.classList.contains('btnSessionInfo')) { showPlaybackInfo(btn, session); - } else if (btn.classList.contains("btnSessionSendMessage")) { + } else if (btn.classList.contains('btnSessionSendMessage')) { showSendMessageForm(btn, session); - } else if (btn.classList.contains("btnSessionStop")) { - connectionManager.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, "Stop"); - } else if (btn.classList.contains("btnSessionPlayPause") && session.PlayState) { - connectionManager.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, "PlayPause"); + } else if (btn.classList.contains('btnSessionStop')) { + connectionManager.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'Stop'); + } else if (btn.classList.contains('btnSessionPlayPause') && session.PlayState) { + connectionManager.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'PlayPause'); } } } @@ -135,31 +135,31 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa apiClient.getLiveTvRecordings({ UserId: Dashboard.getCurrentUserId(), IsInProgress: true, - Fields: "CanDelete,PrimaryImageAspectRatio", + Fields: 'CanDelete,PrimaryImageAspectRatio', EnableTotalRecordCount: false, - EnableImageTypes: "Primary,Thumb,Backdrop" + EnableImageTypes: 'Primary,Thumb,Backdrop' }).then(function (result) { - var itemsContainer = view.querySelector(".activeRecordingItems"); + var itemsContainer = view.querySelector('.activeRecordingItems'); if (!result.Items.length) { - view.querySelector(".activeRecordingsSection").classList.add("hide"); - return void(itemsContainer.innerHTML = ""); + view.querySelector('.activeRecordingsSection').classList.add('hide'); + return void(itemsContainer.innerHTML = ''); } - view.querySelector(".activeRecordingsSection").classList.remove("hide"); + view.querySelector('.activeRecordingsSection').classList.remove('hide'); itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, - shape: "auto", - defaultShape: "backdrop", + shape: 'auto', + defaultShape: 'backdrop', showTitle: true, showParentTitle: true, coverImage: true, cardLayout: false, centerText: true, - preferThumb: "auto", + preferThumb: 'auto', overlayText: false, overlayMoreButton: true, - action: "none", + action: 'none', centerPlayButton: true }); imageLoader.lazyChildren(itemsContainer); @@ -168,28 +168,16 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa function reloadSystemInfo(view, apiClient) { apiClient.getSystemInfo().then(function (systemInfo) { - view.querySelector("#serverName").innerHTML = globalize.translate("DashboardServerName", systemInfo.ServerName); - var localizedVersion = globalize.translate("DashboardVersionNumber", systemInfo.Version); - - if (systemInfo.SystemUpdateLevel !== "Release") { - localizedVersion += " " + systemInfo.SystemUpdateLevel; - } - - view.querySelector("#versionNumber").innerHTML = localizedVersion; - view.querySelector("#operatingSystem").innerHTML = globalize.translate("DashboardOperatingSystem", systemInfo.OperatingSystem); - view.querySelector("#architecture").innerHTML = globalize.translate("DashboardArchitecture", systemInfo.SystemArchitecture); - - if (systemInfo.CanSelfRestart) { - view.querySelector("#btnRestartServer").classList.remove("hide"); - } else { - view.querySelector("#btnRestartServer").classList.add("hide"); - } - - view.querySelector("#cachePath").innerHTML = systemInfo.CachePath; - view.querySelector("#logPath").innerHTML = systemInfo.LogPath; - view.querySelector("#transcodePath").innerHTML = systemInfo.TranscodingTempPath; - view.querySelector("#metadataPath").innerHTML = systemInfo.InternalMetadataPath; - view.querySelector("#webPath").innerHTML = systemInfo.WebPath; + view.querySelector('#serverName').innerHTML = globalize.translate('DashboardServerName', systemInfo.ServerName); + view.querySelector('#versionNumber').innerHTML = globalize.translate('DashboardVersionNumber', systemInfo.Version); + view.querySelector('#operatingSystem').innerHTML = globalize.translate('DashboardOperatingSystem', systemInfo.OperatingSystem); + view.querySelector('#architecture').innerHTML = globalize.translate('DashboardArchitecture', systemInfo.SystemArchitecture); + + view.querySelector('#cachePath').innerHTML = systemInfo.CachePath; + view.querySelector('#logPath').innerHTML = systemInfo.LogPath; + view.querySelector('#transcodePath').innerHTML = systemInfo.TranscodingTempPath; + view.querySelector('#metadataPath').innerHTML = systemInfo.InternalMetadataPath; + view.querySelector('#webPath').innerHTML = systemInfo.WebPath; }); } @@ -211,25 +199,25 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } function renderActiveConnections(view, sessions) { - var html = ""; + var html = ''; DashboardPage.sessionsList = sessions; - var parentElement = view.querySelector(".activeDevices"); - var cardElem = parentElement.querySelector(".card"); + var parentElement = view.querySelector('.activeDevices'); + var cardElem = parentElement.querySelector('.card'); if (cardElem) { - cardElem.classList.add("deadSession"); + cardElem.classList.add('deadSession'); } for (var i = 0, length = sessions.length; i < length; i++) { var session = sessions[i]; - var rowId = "session" + session.Id; - var elem = view.querySelector("#" + rowId); + var rowId = 'session' + session.Id; + var elem = view.querySelector('#' + rowId); if (elem) { DashboardPage.updateSession(elem, session); } else { var nowPlayingItem = session.NowPlayingItem; - var className = "scalableCard card activeSession backdropCard backdropCard-scalable"; + var className = 'scalableCard card activeSession backdropCard backdropCard-scalable'; html += '
'; html += '
'; html += '
'; @@ -253,71 +241,65 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } html += '
'; - html += '
' + session.DeviceName + "
"; - html += '
' + DashboardPage.getAppSecondaryText(session) + "
"; - html += "
"; - html += "
"; - - if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { - html += '
' + session.TranscodingInfo.Framerate + " fps
"; - } else { - html += '
'; - } + html += '
' + session.DeviceName + '
'; + html += '
' + DashboardPage.getAppSecondaryText(session) + '
'; + html += '
'; + html += '
'; html += '
'; var nowPlayingName = DashboardPage.getNowPlayingName(session); html += '
'; html += nowPlayingName.html; - html += "
"; - html += '
' + DashboardPage.getSessionNowPlayingTime(session) + "
"; + html += '
'; + html += '
' + DashboardPage.getSessionNowPlayingTime(session) + '
'; html += ''; if (nowPlayingItem && nowPlayingItem.RunTimeTicks) { var percent = 100 * (session.PlayState.PositionTicks || 0) / nowPlayingItem.RunTimeTicks; html += indicators.getProgressHtml(percent, { - containerClass: "playbackProgress" + containerClass: 'playbackProgress' }); } else { // need to leave the element in just in case the device starts playback html += indicators.getProgressHtml(0, { - containerClass: "playbackProgress hide" + containerClass: 'playbackProgress hide' }); } if (session.TranscodingInfo && session.TranscodingInfo.CompletionPercentage) { var percent = session.TranscodingInfo.CompletionPercentage.toFixed(1); html += indicators.getProgressHtml(percent, { - containerClass: "transcodingProgress" + containerClass: 'transcodingProgress' }); } else { // same issue as playbackProgress element above html += indicators.getProgressHtml(0, { - containerClass: "transcodingProgress hide" + containerClass: 'transcodingProgress hide' }); } - html += ""; - html += ""; - html += ""; + html += ''; + html += ''; + html += ''; html += '
'; html += '
'; - var btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? "" : " hide"; - var playIcon = session.PlayState.IsPaused ? 'pause' : 'play'; + var btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? '' : ' hide'; + const playIcon = session.PlayState.IsPaused ? 'pause' : 'play_arrow'; - html += ''; - html += ''; + html += ''; + html += ''; - btnCssClass = session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length ? "" : " hide"; - html += ''; + btnCssClass = session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length ? '' : ' hide'; + html += ''; - btnCssClass = session.ServerId && -1 !== session.SupportedCommands.indexOf("DisplayMessage") && session.DeviceId !== connectionManager.deviceId() ? "" : " hide"; - html += ''; - html += "
"; + btnCssClass = session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId() ? '' : ' hide'; + html += ''; + html += '
'; html += '
'; html += DashboardPage.getSessionNowPlayingStreamInfo(session); - html += "
"; + html += ''; html += '
'; var userImage = DashboardPage.getUserImage(session); @@ -325,16 +307,16 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa html += '
'; html += DashboardPage.getUsersHtml(session); - html += "
"; - html += "
"; - html += ""; - html += ""; - html += ""; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; } } - parentElement.insertAdjacentHTML("beforeend", html); - var deadSessionElem = parentElement.querySelector(".deadSession"); + parentElement.insertAdjacentHTML('beforeend', html); + var deadSessionElem = parentElement.querySelector('.deadSession'); if (deadSessionElem) { deadSessionElem.parentNode.removeChild(deadSessionElem); @@ -342,9 +324,9 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } function renderRunningTasks(view, tasks) { - var html = ""; + var html = ''; tasks = tasks.filter(function (task) { - if ("Idle" != task.State) { + if ('Idle' != task.State) { return !task.IsHidden; } @@ -352,59 +334,59 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa }); if (tasks.length) { - view.querySelector(".runningTasksContainer").classList.remove("hide"); + view.querySelector('.runningTasksContainer').classList.remove('hide'); } else { - view.querySelector(".runningTasksContainer").classList.add("hide"); + view.querySelector('.runningTasksContainer').classList.add('hide'); } for (var i = 0, length = tasks.length; i < length; i++) { var task = tasks[i]; - html += "

"; - html += task.Name + "
"; + html += '

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

"; + html += '

'; } - view.querySelector("#divRunningTasks").innerHTML = html; + view.querySelector('#divRunningTasks').innerHTML = html; } window.DashboardPage = { startInterval: function (apiClient) { - apiClient.sendMessage("SessionsStart", "0,1500"); - apiClient.sendMessage("ScheduledTasksInfoStart", "0,1000"); + apiClient.sendMessage('SessionsStart', '0,1500'); + apiClient.sendMessage('ScheduledTasksInfoStart', '0,1000'); }, stopInterval: function (apiClient) { - apiClient.sendMessage("SessionsStop"); - apiClient.sendMessage("ScheduledTasksInfoStop"); + apiClient.sendMessage('SessionsStop'); + apiClient.sendMessage('ScheduledTasksInfoStop'); }, getSessionNowPlayingStreamInfo: function (session) { - var html = ""; + var html = ''; var showTranscodingInfo = false; var displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - if (displayPlayMethod === "DirectStream") { - html += globalize.translate("DirectStreaming"); - } else if (displayPlayMethod === "Transcode") { - html += globalize.translate("Transcoding"); + if (displayPlayMethod === 'DirectStream') { + html += globalize.translate('DirectStreaming'); + } else if (displayPlayMethod === 'Transcode') { + html += globalize.translate('Transcoding'); if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { - html += " (" + session.TranscodingInfo.Framerate + " fps)"; + html += ' (' + session.TranscodingInfo.Framerate + ' fps)'; } showTranscodingInfo = true; - } else if (displayPlayMethod === "DirectPlay") { - html += globalize.translate("DirectPlaying"); + } else if (displayPlayMethod === 'DirectPlay') { + html += globalize.translate('DirectPlaying'); } if (showTranscodingInfo) { @@ -413,9 +395,9 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (session.TranscodingInfo) { if (session.TranscodingInfo.Bitrate) { if (session.TranscodingInfo.Bitrate > 1e6) { - line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + " Mbps"); + line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + ' Mbps'); } else { - line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + " Kbps"); + line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + ' Kbps'); } } @@ -433,7 +415,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } if (line.length) { - html += " - " + line.join(" "); + html += ' - ' + line.join(' '); } } @@ -441,42 +423,43 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa }, getSessionNowPlayingTime: function (session) { var nowPlayingItem = session.NowPlayingItem; - var html = ""; + var html = ''; if (nowPlayingItem) { if (session.PlayState.PositionTicks) { html += datetime.getDisplayRunningTime(session.PlayState.PositionTicks); } else { - html += "0:00"; + html += '0:00'; } - html += " / "; + html += ' / '; if (nowPlayingItem && nowPlayingItem.RunTimeTicks) { html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); } else { - html += "0:00"; + html += '0:00'; } } return html; }, getAppSecondaryText: function (session) { - return session.Client + " " + session.ApplicationVersion; + return session.Client + ' ' + session.ApplicationVersion; }, getNowPlayingName: function (session) { - var imgUrl = ""; + var imgUrl = ''; var 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: "Last seen " + humaneDate(session.LastActivityDate), + html: globalize.translate('LastSeen', datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)), image: imgUrl }; } var topText = itemHelper.getDisplayName(nowPlayingItem); - var bottomText = ""; + var bottomText = ''; if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { bottomText = topText; @@ -495,14 +478,14 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa tag: nowPlayingItem.ImageTags.Logo, maxHeight: 24, maxWidth: 130, - type: "Logo" + type: 'Logo' }); } else if (nowPlayingItem.ParentLogoImageTag) { imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.ParentLogoItemId, { tag: nowPlayingItem.ParentLogoImageTag, maxHeight: 24, maxWidth: 130, - type: "Logo" + type: 'Logo' }); } @@ -511,7 +494,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } return { - html: bottomText ? topText + "
" + bottomText : topText, + html: bottomText ? topText + '
' + bottomText : topText, image: imgUrl }; }, @@ -526,106 +509,103 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa html.push(session.AdditionalUsers[i].UserName); } - return html.join(", "); + return html.join(', '); }, getUserImage: function (session) { if (session.UserId && session.UserPrimaryImageTag) { return ApiClient.getUserImageUrl(session.UserId, { tag: session.UserPrimaryImageTag, - type: "Primary" + type: 'Primary' }); } return null; }, updateSession: function (row, session) { - row.classList.remove("deadSession"); + row.classList.remove('deadSession'); var nowPlayingItem = session.NowPlayingItem; if (nowPlayingItem) { - row.classList.add("playingSession"); + row.classList.add('playingSession'); } else { - row.classList.remove("playingSession"); + row.classList.remove('playingSession'); } - if (session.ServerId && -1 !== session.SupportedCommands.indexOf("DisplayMessage") && session.DeviceId !== connectionManager.deviceId()) { - row.querySelector(".btnSessionSendMessage").classList.remove("hide"); + if (session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId()) { + row.querySelector('.btnSessionSendMessage').classList.remove('hide'); } else { - row.querySelector(".btnSessionSendMessage").classList.add("hide"); + row.querySelector('.btnSessionSendMessage').classList.add('hide'); } if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons.length) { - row.querySelector(".btnSessionInfo").classList.remove("hide"); + row.querySelector('.btnSessionInfo').classList.remove('hide'); } else { - row.querySelector(".btnSessionInfo").classList.add("hide"); + row.querySelector('.btnSessionInfo').classList.add('hide'); } - var btnSessionPlayPause = row.querySelector(".btnSessionPlayPause"); + var btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl && session.DeviceId !== connectionManager.deviceId()) { - btnSessionPlayPause.classList.remove("hide"); - row.querySelector(".btnSessionStop").classList.remove("hide"); + btnSessionPlayPause.classList.remove('hide'); + row.querySelector('.btnSessionStop').classList.remove('hide'); } else { - btnSessionPlayPause.classList.add("hide"); - row.querySelector(".btnSessionStop").classList.add("hide"); + btnSessionPlayPause.classList.add('hide'); + row.querySelector('.btnSessionStop').classList.add('hide'); } - if (session.PlayState && session.PlayState.IsPaused) { - btnSessionPlayPause.querySelector("i").innerHTML = ""; - } else { - btnSessionPlayPause.querySelector("i").innerHTML = "pause"; - } + 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(".sessionNowPlayingStreamInfo").innerHTML = DashboardPage.getSessionNowPlayingStreamInfo(session); - row.querySelector(".sessionNowPlayingTime").innerHTML = DashboardPage.getSessionNowPlayingTime(session); - row.querySelector(".sessionUserName").innerHTML = DashboardPage.getUsersHtml(session); - row.querySelector(".sessionAppSecondaryText").innerHTML = DashboardPage.getAppSecondaryText(session); - row.querySelector(".sessionTranscodingFramerate").innerHTML = session.TranscodingInfo && session.TranscodingInfo.Framerate ? session.TranscodingInfo.Framerate + " fps" : ""; + row.querySelector('.sessionNowPlayingStreamInfo').innerHTML = DashboardPage.getSessionNowPlayingStreamInfo(session); + row.querySelector('.sessionNowPlayingTime').innerHTML = DashboardPage.getSessionNowPlayingTime(session); + row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); + row.querySelector('.sessionAppSecondaryText').innerHTML = DashboardPage.getAppSecondaryText(session); var nowPlayingName = DashboardPage.getNowPlayingName(session); - var nowPlayingInfoElem = row.querySelector(".sessionNowPlayingInfo"); + var nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); - if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute("data-imgsrc"))) { + if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute('data-imgsrc'))) { nowPlayingInfoElem.innerHTML = nowPlayingName.html; - nowPlayingInfoElem.setAttribute("data-imgsrc", nowPlayingName.image || ""); + nowPlayingInfoElem.setAttribute('data-imgsrc', nowPlayingName.image || ''); } - var playbackProgressElem = row.querySelector(".playbackProgress"); + var playbackProgressElem = row.querySelector('.playbackProgress'); if (nowPlayingItem && nowPlayingItem.RunTimeTicks) { var percent = 100 * (session.PlayState.PositionTicks || 0) / nowPlayingItem.RunTimeTicks; playbackProgressElem.outerHTML = indicators.getProgressHtml(percent, { - containerClass: "playbackProgress" + containerClass: 'playbackProgress' }); } else { playbackProgressElem.outerHTML = indicators.getProgressHtml(0, { - containerClass: "playbackProgress hide" + containerClass: 'playbackProgress hide' }); } - var transcodingProgress = row.querySelector(".transcodingProgress"); + var transcodingProgress = row.querySelector('.transcodingProgress'); if (session.TranscodingInfo && session.TranscodingInfo.CompletionPercentage) { var percent = session.TranscodingInfo.CompletionPercentage.toFixed(1); transcodingProgress.outerHTML = indicators.getProgressHtml(percent, { - containerClass: "transcodingProgress" + containerClass: 'transcodingProgress' }); } else { transcodingProgress.outerHTML = indicators.getProgressHtml(0, { - containerClass: "transcodingProgress hide" + containerClass: 'transcodingProgress hide' }); } - var imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ""; - var imgElem = row.querySelector(".sessionNowPlayingContent"); + var imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ''; + var imgElem = row.querySelector('.sessionNowPlayingContent'); - if (imgUrl != imgElem.getAttribute("data-src")) { - imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ""; - imgElem.setAttribute("data-src", imgUrl); + if (imgUrl != imgElem.getAttribute('data-src')) { + imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ''; + imgElem.setAttribute('data-src', imgUrl); if (imgUrl) { - imgElem.classList.add("sessionNowPlayingContent-withbackground"); + imgElem.classList.add('sessionNowPlayingContent-withbackground'); } else { - imgElem.classList.remove("sessionNowPlayingContent-withbackground"); + imgElem.classList.remove('sessionNowPlayingContent-withbackground'); } } }, @@ -639,7 +619,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.BackdropImageTags && item.BackdropImageTags.length) { return ApiClient.getScaledImageUrl(item.Id, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Backdrop", + type: 'Backdrop', tag: item.BackdropImageTags[0] }); } @@ -647,7 +627,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Backdrop", + type: 'Backdrop', tag: item.ParentBackdropImageTags[0] }); } @@ -655,7 +635,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.BackdropImageTag) { return ApiClient.getScaledImageUrl(item.BackdropItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Backdrop", + type: 'Backdrop', tag: item.BackdropImageTag }); } @@ -665,7 +645,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && imageTags.Thumb) { return ApiClient.getScaledImageUrl(item.Id, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Thumb", + type: 'Thumb', tag: imageTags.Thumb }); } @@ -673,7 +653,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.ParentThumbImageTag) { return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Thumb", + type: 'Thumb', tag: item.ParentThumbImageTag }); } @@ -681,7 +661,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.ThumbImageTag) { return ApiClient.getScaledImageUrl(item.ThumbItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Thumb", + type: 'Thumb', tag: item.ThumbImageTag }); } @@ -689,7 +669,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && imageTags.Primary) { return ApiClient.getScaledImageUrl(item.Id, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Primary", + type: 'Primary', tag: imageTags.Primary }); } @@ -697,7 +677,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.PrimaryImageTag) { return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Primary", + type: 'Primary', tag: item.PrimaryImageTag }); } @@ -705,46 +685,46 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa if (item && item.AlbumPrimaryImageTag) { return ApiClient.getScaledImageUrl(item.AlbumId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: "Primary", + type: 'Primary', tag: item.AlbumPrimaryImageTag }); } return null; }, - systemUpdateTaskKey: "SystemUpdateTask", + systemUpdateTaskKey: 'SystemUpdateTask', stopTask: function (btn, id) { - var page = dom.parentWithClass(btn, "page"); + var page = dom.parentWithClass(btn, 'page'); ApiClient.stopScheduledTask(id).then(function () { pollForInfo(page, ApiClient); }); }, restart: function (btn) { - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ - title: globalize.translate("HeaderRestart"), - text: globalize.translate("MessageConfirmRestart"), - confirmText: globalize.translate("ButtonRestart"), - primary: "delete" + title: globalize.translate('HeaderRestart'), + text: globalize.translate('MessageConfirmRestart'), + confirmText: globalize.translate('ButtonRestart'), + primary: 'delete' }).then(function () { - var page = dom.parentWithClass(btn, "page"); - page.querySelector("#btnRestartServer").disabled = true; - page.querySelector("#btnShutdown").disabled = true; + var page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; ApiClient.restartServer(); }); }); }, shutdown: function (btn) { - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ - title: globalize.translate("HeaderShutdown"), - text: globalize.translate("MessageConfirmShutdown"), - confirmText: globalize.translate("ButtonShutdown"), - primary: "delete" + title: globalize.translate('HeaderShutdown'), + text: globalize.translate('MessageConfirmShutdown'), + confirmText: globalize.translate('ButtonShutdown'), + primary: 'delete' }).then(function () { - var page = dom.parentWithClass(btn, "page"); - page.querySelector("#btnRestartServer").disabled = true; - page.querySelector("#btnShutdown").disabled = true; + var page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; ApiClient.shutdownServer(); }); }); @@ -790,8 +770,8 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa } var serverId = ApiClient.serverId(); - view.querySelector(".activeDevices").addEventListener("click", onActiveDevicesClick); - view.addEventListener("viewshow", function () { + view.querySelector('.activeDevices').addEventListener('click', onActiveDevicesClick); + view.addEventListener('viewshow', function () { var page = this; var apiClient = ApiClient; @@ -799,28 +779,28 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa 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", onPackageInstalling); - events.on(serverNotifications, "PackageInstallationCompleted", onPackageInstallationCompleted); - events.on(serverNotifications, "Sessions", onSessionsUpdate); - events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + events.on(serverNotifications, 'RestartRequired', onRestartRequired); + events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + events.on(serverNotifications, 'ServerRestarting', onServerRestarting); + events.on(serverNotifications, 'PackageInstalling', onPackageInstalling); + events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstallationCompleted); + 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") + element: page.querySelector('.userActivityItems') }); } - if (ApiClient.isMinServerVersion("3.4.1.25")) { + if (ApiClient.isMinServerVersion('3.4.1.25')) { if (!page.serverActivityLog) { page.serverActivityLog = new ActivityLog({ serverId: ApiClient.serverId(), - element: page.querySelector(".serverActivityItems") + element: page.querySelector('.serverActivityItems') }); } } @@ -829,21 +809,21 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa loading.hide(); } }); - view.addEventListener("viewbeforehide", function () { + view.addEventListener('viewbeforehide', function () { var apiClient = ApiClient; - events.off(serverNotifications, "RestartRequired", onRestartRequired); - events.off(serverNotifications, "ServerShuttingDown", onServerShuttingDown); - events.off(serverNotifications, "ServerRestarting", onServerRestarting); - events.off(serverNotifications, "PackageInstalling", onPackageInstalling); - events.off(serverNotifications, "PackageInstallationCompleted", onPackageInstallationCompleted); - events.off(serverNotifications, "Sessions", onSessionsUpdate); - events.off(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + events.off(serverNotifications, 'RestartRequired', onRestartRequired); + events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + events.off(serverNotifications, 'ServerRestarting', onServerRestarting); + events.off(serverNotifications, 'PackageInstalling', onPackageInstalling); + events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstallationCompleted); + events.off(serverNotifications, 'Sessions', onSessionsUpdate); + events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); if (apiClient) { DashboardPage.stopInterval(apiClient); } }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { var page = this; var userActivityLog = page.userActivityLog; diff --git a/src/controllers/dashboard/devices/device.js b/src/controllers/dashboard/devices/device.js new file mode 100644 index 00000000000..9ff3f5902e2 --- /dev/null +++ b/src/controllers/dashboard/devices/device.js @@ -0,0 +1,50 @@ +define(['loading', 'libraryMenu', 'dom', 'emby-input', 'emby-button'], function (loading, libraryMenu, dom) { + 'use strict'; + + function load(page, device, deviceOptions) { + page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; + page.querySelector('.reportedName', page).innerHTML = device.Name || ''; + } + + function loadData() { + var page = this; + loading.show(); + var id = getParameterByName('id'); + var promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', { + Id: id + })); + var 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 save(page) { + var 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) { + var form = this; + save(dom.parentWithClass(form, 'page')); + e.preventDefault(); + return false; + } + + return function (view, params) { + view.querySelector('form').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', loadData); + }; +}); diff --git a/src/controllers/devices.js b/src/controllers/dashboard/devices/devices.js similarity index 54% rename from src/controllers/devices.js rename to src/controllers/dashboard/devices/devices.js index 3fd2be983e5..0dea81c7566 100644 --- a/src/controllers/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -1,24 +1,24 @@ -define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "humanedate", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper) { - "use strict"; +define(['loading', 'dom', 'libraryMenu', 'globalize', 'scripts/imagehelper', 'date-fns', 'dfnshelper', 'emby-button', 'emby-itemscontainer', 'cardStyle'], function (loading, dom, libraryMenu, globalize, imageHelper, datefns, dfnshelper) { + 'use strict'; function canDelete(deviceId) { return deviceId !== ApiClient.deviceId(); } function deleteDevice(page, id) { - var msg = globalize.translate("DeleteDeviceConfirmation"); + var msg = globalize.translate('DeleteDeviceConfirmation'); - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ text: msg, - title: globalize.translate("HeaderDeleteDevice"), - confirmText: globalize.translate("ButtonDelete"), - primary: "delete" + title: globalize.translate('HeaderDeleteDevice'), + confirmText: globalize.translate('ButtonDelete'), + primary: 'delete' }).then(function () { loading.show(); ApiClient.ajax({ - type: "DELETE", - url: ApiClient.getUrl("Devices", { + type: 'DELETE', + url: ApiClient.getUrl('Devices', { Id: id }) }).then(function () { @@ -33,31 +33,31 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu if (canEdit) { menuItems.push({ - name: globalize.translate("Edit"), - id: "open", - icon: "mode_edit" + name: globalize.translate('Edit'), + id: 'open', + icon: 'mode_edit' }); } if (canDelete(deviceId)) { menuItems.push({ - name: globalize.translate("Delete"), - id: "delete", - icon: "delete" + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' }); } - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: btn, callback: function (id) { switch (id) { - case "open": - Dashboard.navigate("device.html?id=" + deviceId); + case 'open': + Dashboard.navigate('device.html?id=' + deviceId); break; - case "delete": + case 'delete': deleteDevice(view, deviceId); } } @@ -66,74 +66,74 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu } function load(page, devices) { - var html = ""; + var html = ''; html += devices.map(function (device) { - var deviceHtml = ""; + var deviceHtml = ''; deviceHtml += "'; deviceHtml += '
'; if (canEdit || canDelete(device.Id)) { deviceHtml += '
'; - deviceHtml += ''; - deviceHtml += "
"; + deviceHtml += ''; + deviceHtml += '
'; } deviceHtml += "
"; deviceHtml += device.Name; - deviceHtml += "
"; + deviceHtml += ''; deviceHtml += "
"; - deviceHtml += device.AppName + " " + device.AppVersion; - deviceHtml += "
"; + deviceHtml += device.AppName + ' ' + device.AppVersion; + deviceHtml += ''; deviceHtml += "
"; if (device.LastUserName) { deviceHtml += device.LastUserName; - deviceHtml += ", " + humaneDate(device.DateLastActivity); + deviceHtml += ', ' + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix); } - deviceHtml += " "; - deviceHtml += "
"; - deviceHtml += ""; - deviceHtml += ""; - deviceHtml += ""; + deviceHtml += ' '; + deviceHtml += ''; + deviceHtml += ''; + deviceHtml += ''; + deviceHtml += ''; return deviceHtml; - }).join(""); - page.querySelector(".devicesList").innerHTML = html; + }).join(''); + page.querySelector('.devicesList').innerHTML = html; } function loadData(page) { loading.show(); - ApiClient.getJSON(ApiClient.getUrl("Devices")).then(function (result) { + ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { load(page, result.Items); loading.hide(); }); } - var canEdit = ApiClient.isMinServerVersion("3.4.1.31"); + var canEdit = ApiClient.isMinServerVersion('3.4.1.31'); return function (view, params) { - view.querySelector(".devicesList").addEventListener("click", function (e) { - var btnDeviceMenu = dom.parentWithClass(e.target, "btnDeviceMenu"); + view.querySelector('.devicesList').addEventListener('click', function (e) { + var btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); if (btnDeviceMenu) { - showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute("data-id")); + showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); } }); - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { loadData(this); }); }; diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js new file mode 100644 index 00000000000..54ef75a6d1f --- /dev/null +++ b/src/controllers/dashboard/dlna/profile.js @@ -0,0 +1,829 @@ +define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-input', 'emby-checkbox', 'listViewStyle', 'emby-button'], function ($, loading, globalize) { + 'use strict'; + + function loadProfile(page) { + loading.show(); + var promise1 = getProfile(); + var promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + currentProfile = responses[0]; + renderProfile(page, currentProfile, responses[1]); + loading.hide(); + }); + } + + function getProfile() { + var id = getParameterByName('id'); + var 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 = -1 != (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')); + }); + $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); + $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); + renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); + var 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 || []; + var usersHtml = '' + users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); + renderSubProfiles(page, profile); + } + + function renderIdentificationHeaders(page, headers) { + var index = 0; + var html = '
' + headers.map(function (h) { + var li = '
'; + li += ''; + li += '
'; + li += '

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

'; + li += '
' + (h.Match || '') + '
'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + var elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); + $('.btnDeleteIdentificationHeader', elem).on('click', function () { + var itemIndex = parseInt(this.getAttribute('data-index')); + 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 = null == header; + header = header || {}; + currentSubProfile = header; + var 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) { + var html = '
' + attribute.map(function (h) { + var li = '
'; + li += ''; + li += '
'; + li += '

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

'; + li += '
'; + li += ''; + return li += '
'; + }).join('') + '
'; + var elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); + $('.btnDeleteXmlAttribute', elem).on('click', function () { + var itemIndex = parseInt(this.getAttribute('data-index')); + currentProfile.XmlRootAttributes.splice(itemIndex, 1); + renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); + }); + } + + function editXmlDocumentAttribute(page, attribute) { + isSubProfileNew = null == attribute; + attribute = attribute || {}; + currentSubProfile = attribute; + var 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) { + var index = 0; + var html = '
' + profiles.map(function (h) { + var li = '
'; + li += ''; + li += '
'; + li += '

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

'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + var elem = $('.subtitleProfileList', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var itemIndex = parseInt(this.getAttribute('data-index')); + currentProfile.SubtitleProfiles.splice(itemIndex, 1); + renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var itemIndex = parseInt(this.getAttribute('data-index')); + editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); + }); + } + + function editSubtitleProfile(page, profile) { + isSubProfileNew = null == profile; + profile = profile || {}; + currentSubProfile = profile; + var 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) { + var html = ''; + html += ''; + var elem = $('.directPlayProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var index = this.getAttribute('data-profileindex'); + deleteDirectPlayProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var index = parseInt(this.getAttribute('data-profileindex')); + editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); + }); + } + + function deleteDirectPlayProfile(page, index) { + currentProfile.DirectPlayProfiles.splice(index, 1); + renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); + } + + function editDirectPlayProfile(page, directPlayProfile) { + isSubProfileNew = null == directPlayProfile; + directPlayProfile = directPlayProfile || {}; + currentSubProfile = directPlayProfile; + var 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) { + var html = ''; + html += ''; + var elem = $('.transcodingProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var index = this.getAttribute('data-profileindex'); + deleteTranscodingProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var index = parseInt(this.getAttribute('data-profileindex')); + editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); + }); + } + + function editTranscodingProfile(page, transcodingProfile) { + isSubProfileNew = null == transcodingProfile; + transcodingProfile = transcodingProfile || {}; + currentSubProfile = transcodingProfile; + var 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', 'Bytes' == transcodingProfile.TranscodeSeekInfo); + $('.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) { + var html = ''; + html += ''; + var elem = $('.containerProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var index = this.getAttribute('data-profileindex'); + deleteContainerProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var index = parseInt(this.getAttribute('data-profileindex')); + editContainerProfile(page, currentProfile.ContainerProfiles[index]); + }); + } + + function deleteContainerProfile(page, index) { + currentProfile.ContainerProfiles.splice(index, 1); + renderContainerProfiles(page, currentProfile.ContainerProfiles); + } + + function editContainerProfile(page, containerProfile) { + isSubProfileNew = null == containerProfile; + containerProfile = containerProfile || {}; + currentSubProfile = containerProfile; + var 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) { + var html = ''; + html += ''; + var elem = $('.codecProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var index = this.getAttribute('data-profileindex'); + deleteCodecProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var index = parseInt(this.getAttribute('data-profileindex')); + editCodecProfile(page, currentProfile.CodecProfiles[index]); + }); + } + + function deleteCodecProfile(page, index) { + currentProfile.CodecProfiles.splice(index, 1); + renderCodecProfiles(page, currentProfile.CodecProfiles); + } + + function editCodecProfile(page, codecProfile) { + isSubProfileNew = null == codecProfile; + codecProfile = codecProfile || {}; + currentSubProfile = codecProfile; + var 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) { + var html = ''; + html += ''; + var elem = $('.mediaProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + var index = this.getAttribute('data-profileindex'); + deleteResponseProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + var index = parseInt(this.getAttribute('data-profileindex')); + editResponseProfile(page, currentProfile.ResponseProfiles[index]); + }); + } + + function deleteResponseProfile(page, index) { + currentProfile.ResponseProfiles.splice(index, 1); + renderResponseProfiles(page, currentProfile.ResponseProfiles); + } + + function editResponseProfile(page, responseProfile) { + isSubProfileNew = null == responseProfile; + responseProfile = responseProfile || {}; + currentSubProfile = responseProfile; + var 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); + var id = getParameterByName('id'); + + if (id) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Dlna/Profiles/' + id), + data: JSON.stringify(profile), + contentType: 'application/json' + }).then(function () { + require(['toast'], function (toast) { + toast('Settings saved.'); + }); + }, 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(); + } + + var currentProfile; + var currentSubProfile; + var isSubProfileNew; + var allText = globalize.translate('LabelAll'); + + $(document).on('pageinit', '#dlnaProfilePage', function () { + var page = this; + $('.radioTabButton', page).on('click', function () { + $(this).siblings().removeClass('ui-btn-active'); + $(this).addClass('ui-btn-active'); + var value = 'A' == this.tagName ? this.getAttribute('data-value') : this.value; + var elem = $('.' + value, page); + elem.siblings('.tabContent').hide(); + elem.show(); + }); + $('#selectDirectPlayProfileType', page).on('change', function () { + if ('Video' == this.value) { + $('#fldDirectPlayVideoCodec', page).show(); + } else { + $('#fldDirectPlayVideoCodec', page).hide(); + } + + if ('Photo' == this.value) { + $('#fldDirectPlayAudioCodec', page).hide(); + } else { + $('#fldDirectPlayAudioCodec', page).show(); + } + }); + $('#selectTranscodingProfileType', page).on('change', function () { + if ('Video' == this.value) { + $('#fldTranscodingVideoCodec', page).show(); + $('#fldTranscodingProtocol', page).show(); + $('#fldEnableMpegtsM2TsMode', page).show(); + } else { + $('#fldTranscodingVideoCodec', page).hide(); + $('#fldTranscodingProtocol', page).hide(); + $('#fldEnableMpegtsM2TsMode', page).hide(); + } + + if ('Photo' == this.value) { + $('#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 ('Video' == this.value) { + $('#fldResponseProfileVideoCodec', page).show(); + } else { + $('#fldResponseProfileVideoCodec', page).hide(); + } + + if ('Photo' == this.value) { + $('#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 () { + var 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; + } + }; +}); diff --git a/src/controllers/dashboard/dlna/profiles.js b/src/controllers/dashboard/dlna/profiles.js new file mode 100644 index 00000000000..fb4caadeb90 --- /dev/null +++ b/src/controllers/dashboard/dlna/profiles.js @@ -0,0 +1,89 @@ +define(['jQuery', 'globalize', 'loading', 'libraryMenu', 'listViewStyle', 'emby-button'], function ($, globalize, loading, libraryMenu) { + 'use strict'; + + 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 'User' == p.Type; + })); + } + + function renderSystemProfiles(page, profiles) { + renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { + return 'System' == p.Type; + })); + } + + function renderProfiles(page, element, profiles) { + var html = ''; + + if (profiles.length) { + html += '
'; + } + + for (var i = 0, length = profiles.length; i < length; i++) { + var profile = profiles[i]; + html += '
'; + html += ''; + html += ''; + + if ('User' == profile.Type) { + html += ''; + } + + html += '
'; + } + + if (profiles.length) { + html += '
'; + } + + element.innerHTML = html; + $('.btnDeleteProfile', element).on('click', function () { + var id = this.getAttribute('data-profileid'); + deleteProfile(page, id); + }); + } + + function deleteProfile(page, id) { + require(['confirm'], function (confirm) { + 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('TabSettings') + }, { + href: 'dlnaprofiles.html', + name: globalize.translate('TabProfiles') + }]; + } + + $(document).on('pageshow', '#dlnaProfilesPage', function () { + libraryMenu.setTabs('dlna', 1, getTabs); + loadProfiles(this); + }); +}); diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js new file mode 100644 index 00000000000..5bbfea5d4b8 --- /dev/null +++ b/src/controllers/dashboard/dlna/settings.js @@ -0,0 +1,56 @@ +define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu, globalize) { + 'use strict'; + + 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); + var usersHtml = users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); + loading.hide(); + } + + function onSubmit() { + loading.show(); + var 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('TabSettings') + }, { + 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(); + var page = this; + var promise1 = ApiClient.getNamedConfiguration('dlna'); + var promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[0], responses[1]); + }); + }); +}); diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js new file mode 100644 index 00000000000..0f54f9d70ff --- /dev/null +++ b/src/controllers/dashboard/encodingsettings.js @@ -0,0 +1,195 @@ +define(['jQuery', 'loading', 'globalize', 'dom', 'libraryMenu'], function ($, loading, globalize, dom, libraryMenu) { + 'use strict'; + + function loadPage(page, config, systemInfo) { + Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { + c.checked = -1 !== (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')); + }); + page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; + page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; + page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; + $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); + $('#selectThreadCount', page).val(config.EncodingThreadCount); + $('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost); + page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || ''; + $('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || ''); + $('#txtVaapiDevice', page).val(config.VaapiDevice || ''); + page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || ''; + page.querySelector('#txtH264Crf').value = config.H264Crf || ''; + page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || ''; + 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(response) { + loading.hide(); + var msg = ''; + msg = globalize.translate('FFmpegSavePathNotFound'); + + require(['alert'], function (alert) { + alert(msg); + }); + } + + function updateEncoder(form) { + return ApiClient.getSystemInfo().then(function (systemInfo) { + return ApiClient.ajax({ + url: ApiClient.getUrl('System/MediaEncoder/Path'), + type: 'POST', + data: { + Path: form.querySelector('.txtEncoderPath').value, + PathType: 'Custom' + } + }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); + }); + } + + function onSubmit() { + var form = this; + + var onDecoderConfirmed = function () { + loading.show(); + ApiClient.getNamedConfiguration('encoding').then(function (config) { + config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val(); + config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val(); + config.EncodingThreadCount = $('#selectThreadCount', form).val(); + config.HardwareAccelerationType = $('#selectVideoDecoder', form).val(); + config.VaapiDevice = $('#txtVaapiDevice', form).val(); + config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; + config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0'); + config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; + 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.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; + ApiClient.updateNamedConfiguration('encoding', config).then(function () { + updateEncoder(form); + }, function () { + require(['alert'], function (alert) { + alert(globalize.translate('DefaultErrorMessage')); + }); + + Dashboard.processServerConfigurationUpdateResult(); + }); + }); + }; + + if ($('#selectVideoDecoder', form).val()) { + require(['alert'], function (alert) { + alert({ + title: globalize.translate('TitleHardwareAcceleration'), + text: globalize.translate('HardwareAccelerationWarning') + }).then(onDecoderConfirmed); + }); + } else { + onDecoderConfirmed(); + } + + return false; + } + + function setDecodingCodecsVisible(context, value) { + value = value || ''; + var any; + Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) { + if (-1 === c.getAttribute('data-types').split(',').indexOf(value)) { + 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('TabResumeSettings') + }, { + href: 'streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; + } + + $(document).on('pageinit', '#encodingSettingsPage', function () { + var page = this; + page.querySelector('#selectVideoDecoder').addEventListener('change', function () { + if ('vaapi' == this.value) { + 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) { + page.querySelector('.hardwareAccelerationOptions').classList.remove('hide'); + } else { + page.querySelector('.hardwareAccelerationOptions').classList.add('hide'); + } + + setDecodingCodecsVisible(page, this.value); + }); + $('#btnSelectEncoderPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { + var picker = new directoryBrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + $('.txtEncoderPath', page).val(path); + } + + picker.close(); + } + }); + }); + }); + $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { + var 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') + }); + }); + }); + $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); + }).on('pageshow', '#encodingSettingsPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 0, getTabs); + var page = this; + ApiClient.getNamedConfiguration('encoding').then(function (config) { + ApiClient.getSystemInfo().then(function (systemInfo) { + loadPage(page, config, systemInfo); + }); + }); + }); +}); diff --git a/src/controllers/dashboard/general.js b/src/controllers/dashboard/general.js index a434e462411..f215ace28b4 100644 --- a/src/controllers/dashboard/general.js +++ b/src/controllers/dashboard/general.js @@ -1,28 +1,15 @@ -define(["jQuery", "loading", "fnchecked", "emby-checkbox", "emby-textarea", "emby-input", "emby-select", "emby-button"], function ($, loading) { - "use strict"; +define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'emby-textarea', 'emby-input', 'emby-select', 'emby-button'], function ($, loading, globalize) { + 'use strict'; function loadPage(page, config, languageOptions, systemInfo) { - page.querySelector("#txtServerName").value = systemInfo.ServerName; - $("#chkAutoRunWebApp", page).checked(config.AutoRunWebApp); - - if (systemInfo.CanLaunchWebBrowser) { - page.querySelector("#fldAutoRunWebApp").classList.remove("hide"); - } else { - page.querySelector("#fldAutoRunWebApp").classList.add("hide"); - } - - page.querySelector("#txtCachePath").value = systemInfo.CachePath || ""; - $("#txtMetadataPath", page).val(systemInfo.InternalMetadataPath || ""); - $("#txtMetadataNetworkPath", page).val(systemInfo.MetadataNetworkPath || ""); - $("#selectLocalizationLanguage", page).html(languageOptions.map(function (language) { - return '"; + page.querySelector('#txtServerName').value = systemInfo.ServerName; + page.querySelector('#txtCachePath').value = systemInfo.CachePath || ''; + $('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || ''); + $('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || ''); + $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { + return ''; })).val(config.UICulture); currentLanguage = config.UICulture; - if (systemInfo.CanSelfRestart || systemInfo.CanSelfUpdate) { - $(".autoUpdatesContainer", page).removeClass("hide"); - } else { - $(".autoUpdatesContainer", page).addClass("hide"); - } loading.hide(); } @@ -30,19 +17,18 @@ define(["jQuery", "loading", "fnchecked", "emby-checkbox", "emby-textarea", "emb function onSubmit() { loading.show(); var form = this; - $(form).parents(".page"); + $(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.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(); var requiresReload = config.UICulture !== currentLanguage; - config.AutoRunWebApp = $("#chkAutoRunWebApp", form).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.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value; + brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value; if (currentBrandingOptions && brandingConfig.CustomCss !== currentBrandingOptions.CustomCss) { requiresReload = true; @@ -57,8 +43,8 @@ define(["jQuery", "loading", "fnchecked", "emby-checkbox", "emby-textarea", "emb }); }); }, function () { - require(["alert"], function (alert) { - alert(Globalize.translate("DefaultErrorMessage")); + require(['alert'], function (alert) { + alert(globalize.translate('DefaultErrorMessage')); }); Dashboard.processServerConfigurationUpdateResult(); @@ -69,61 +55,61 @@ define(["jQuery", "loading", "fnchecked", "emby-checkbox", "emby-textarea", "emb var currentBrandingOptions; var currentLanguage; - var brandingConfigKey = "branding"; + var brandingConfigKey = 'branding'; return function (view, params) { - $("#btnSelectCachePath", view).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('#btnSelectCachePath', view).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ callback: function (path) { if (path) { - view.querySelector("#txtCachePath").value = path; + view.querySelector('#txtCachePath').value = path; } picker.close(); }, validateWriteable: true, - header: Globalize.translate("HeaderSelectServerCachePath"), - instruction: Globalize.translate("HeaderSelectServerCachePathHelp") + header: globalize.translate('HeaderSelectServerCachePath'), + instruction: globalize.translate('HeaderSelectServerCachePathHelp') }); }); }); - $("#btnSelectMetadataPath", view).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ - path: $("#txtMetadataPath", view).val(), - networkSharePath: $("#txtMetadataNetworkPath", view).val(), + path: $('#txtMetadataPath', view).val(), + networkSharePath: $('#txtMetadataNetworkPath', view).val(), callback: function (path, networkPath) { if (path) { - $("#txtMetadataPath", view).val(path); + $('#txtMetadataPath', view).val(path); } if (networkPath) { - $("#txtMetadataNetworkPath", view).val(networkPath); + $('#txtMetadataNetworkPath', view).val(networkPath); } picker.close(); }, validateWriteable: true, - header: Globalize.translate("HeaderSelectMetadataPath"), - instruction: Globalize.translate("HeaderSelectMetadataPathHelp"), + header: globalize.translate('HeaderSelectMetadataPath'), + instruction: globalize.translate('HeaderSelectMetadataPathHelp'), enableNetworkSharePath: true }); }); }); - $(".dashboardGeneralForm", view).off("submit", onSubmit).on("submit", onSubmit); - view.addEventListener("viewshow", function () { + $('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); + view.addEventListener('viewshow', function () { var promiseConfig = ApiClient.getServerConfiguration(); - var promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl("Localization/Options")); + var promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); var 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) { currentBrandingOptions = config; - view.querySelector("#txtLoginDisclaimer").value = config.LoginDisclaimer || ""; - view.querySelector("#txtCustomCss").value = config.CustomCss || ""; + view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; + view.querySelector('#txtCustomCss').value = config.CustomCss || ''; }); }); }; diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js new file mode 100644 index 00000000000..a820c375289 --- /dev/null +++ b/src/controllers/dashboard/librarydisplay.js @@ -0,0 +1,67 @@ +define(['globalize', 'loading', 'libraryMenu', 'emby-checkbox', 'emby-button', 'emby-button'], function(globalize, loading, libraryMenu) { + 'use strict'; + + function getTabs() { + return [{ + href: 'library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: 'librarydisplay.html', + name: globalize.translate('TabDisplay') + }, { + href: 'metadataimages.html', + name: globalize.translate('TabMetadata') + }, { + href: 'metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; + } + + return function(view, params) { + 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(); + var 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 = '1' === $('#selectDateAdded', form).val(); + 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 ('Windows' === info.OperatingSystem) { + 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 144e21fc92e..7bf2785d50b 100644 --- a/src/controllers/dashboard/logs.js +++ b/src/controllers/dashboard/logs.js @@ -1,33 +1,40 @@ -define(["datetime", "loading", "apphost", "listViewStyle", "emby-button", "flexStyles"], function(datetime, loading, appHost) { - "use strict"; - return function(view, params) { - view.addEventListener("viewbeforeshow", function() { +import datetime from 'datetime'; +import loading from 'loading'; +import 'emby-button'; +import 'listViewStyle'; +import 'flexStyles'; + +/* eslint-disable indent */ + + export default function(view, params) { + view.addEventListener('viewbeforeshow', function() { loading.show(); var apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl("System/Logs")).then(function(logs) { - var html = ""; + apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { + var html = ''; html += '
'; html += logs.map(function(log) { - var logUrl = apiClient.getUrl("System/Logs/Log", { + var logUrl = apiClient.getUrl('System/Logs/Log', { name: log.Name }); - logUrl += "&api_key=" + apiClient.accessToken(); - var logHtml = ""; + logUrl += '&api_key=' + apiClient.accessToken(); + var logHtml = ''; logHtml += ''; logHtml += '
'; - logHtml += "

" + log.Name + "

"; + logHtml += "

" + log.Name + '

'; var date = datetime.parseISO8601Date(log.DateModified, true); var text = datetime.toLocaleDateString(date); - text += " " + datetime.getDisplayTime(date); - logHtml += '
' + text + "
"; - logHtml += "
"; - logHtml += "
"; + text += ' ' + datetime.getDisplayTime(date); + logHtml += '
' + text + '
'; + logHtml += '
'; + logHtml += ''; return logHtml; - }).join(""); - html += ""; - view.querySelector(".serverLogs").innerHTML = html; + }).join(''); + html += ''; + view.querySelector('.serverLogs').innerHTML = html; loading.hide(); }); }); } -}); + +/* eslint-enable indent */ diff --git a/src/controllers/medialibrarypage.js b/src/controllers/dashboard/mediaLibrary.js similarity index 55% rename from src/controllers/medialibrarypage.js rename to src/controllers/dashboard/mediaLibrary.js index d838c2145ef..da345859d90 100644 --- a/src/controllers/medialibrarypage.js +++ b/src/controllers/dashboard/mediaLibrary.js @@ -1,8 +1,8 @@ -define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "globalize", "dom", "indicators", "scripts/imagehelper", "cardStyle", "emby-itemrefreshindicator"], function ($, appHost, taskButton, loading, libraryMenu, globalize, dom, indicators, imageHelper) { - "use strict"; +define(['jQuery', 'apphost', 'scripts/taskbutton', 'loading', 'libraryMenu', 'globalize', 'dom', 'indicators', 'scripts/imagehelper', 'cardStyle', 'emby-itemrefreshindicator'], function ($, appHost, taskButton, loading, libraryMenu, globalize, dom, indicators, imageHelper) { + 'use strict'; function addVirtualFolder(page) { - require(["medialibrarycreator"], function (medialibrarycreator) { + require(['medialibrarycreator'], function (medialibrarycreator) { new medialibrarycreator().show({ collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { return !f.hidden; @@ -17,7 +17,7 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function editVirtualFolder(page, virtualFolder) { - require(["medialibraryeditor"], function (medialibraryeditor) { + require(['medialibraryeditor'], function (medialibraryeditor) { new medialibraryeditor().show({ refresh: shouldRefreshLibraryAfterChanges(page), library: virtualFolder @@ -30,14 +30,14 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function deleteVirtualFolder(page, virtualFolder) { - var msg = globalize.translate("MessageAreYouSureYouWishToRemoveMediaFolder"); + var msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder'); if (virtualFolder.Locations.length) { - msg += "

" + globalize.translate("MessageTheFollowingLocationWillBeRemovedFromLibrary") + "

"; - msg += virtualFolder.Locations.join("
"); + msg += '

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

'; + msg += virtualFolder.Locations.join('
'); } - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ text: msg, @@ -55,20 +55,20 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function refreshVirtualFolder(page, virtualFolder) { - require(["refreshDialog"], function (refreshDialog) { + require(['refreshDialog'], function (refreshDialog) { new refreshDialog({ itemIds: [virtualFolder.ItemId], serverId: ApiClient.serverId(), - mode: "scan" + mode: 'scan' }).show(); }); } function renameVirtualFolder(page, virtualFolder) { - require(["prompt"], function (prompt) { + require(['prompt'], function (prompt) { prompt({ - label: globalize.translate("LabelNewName"), - confirmText: globalize.translate("ButtonRename") + label: globalize.translate('LabelNewName'), + confirmText: globalize.translate('ButtonRename') }).then(function (newName) { if (newName && newName != virtualFolder.Name) { var refreshAfterChange = shouldRefreshLibraryAfterChanges(page); @@ -81,59 +81,59 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function showCardMenu(page, elem, virtualFolders) { - var card = dom.parentWithClass(elem, "card"); - var index = parseInt(card.getAttribute("data-index")); + var card = dom.parentWithClass(elem, 'card'); + var index = parseInt(card.getAttribute('data-index')); var virtualFolder = virtualFolders[index]; var menuItems = []; menuItems.push({ - name: globalize.translate("ButtonEditImages"), - id: "editimages", - icon: "photo" + name: globalize.translate('ButtonEditImages'), + id: 'editimages', + icon: 'photo' }); menuItems.push({ - name: globalize.translate("ManageLibrary"), - id: "edit", - icon: "folder_open" + name: globalize.translate('ManageLibrary'), + id: 'edit', + icon: 'folder_open' }); menuItems.push({ - name: globalize.translate("ButtonRemove"), - id: "delete", - icon: "delete" + name: globalize.translate('ButtonRemove'), + id: 'delete', + icon: 'delete' }); menuItems.push({ - name: globalize.translate("ButtonRename"), - id: "rename", - icon: "mode_edit" + name: globalize.translate('ButtonRename'), + id: 'rename', + icon: 'mode_edit' }); menuItems.push({ - name: globalize.translate("ScanLibrary"), - id: "refresh", - icon: "refresh" + name: globalize.translate('ScanLibrary'), + id: 'refresh', + icon: 'refresh' }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: elem, callback: function (resultId) { switch (resultId) { - case "edit": + case 'edit': editVirtualFolder(page, virtualFolder); break; - case "editimages": + case 'editimages': editImages(page, virtualFolder); break; - case "rename": + case 'rename': renameVirtualFolder(page, virtualFolder); break; - case "delete": + case 'delete': deleteVirtualFolder(page, virtualFolder); break; - case "refresh": + case 'refresh': refreshVirtualFolder(page, virtualFolder); } } @@ -149,19 +149,19 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function shouldRefreshLibraryAfterChanges(page) { - return "mediaLibraryPage" === page.id; + return 'mediaLibraryPage' === page.id; } function reloadVirtualFolders(page, virtualFolders) { - var html = ""; + var html = ''; virtualFolders.push({ - Name: globalize.translate("ButtonAddMediaLibrary"), - icon: "add_circle", + Name: globalize.translate('ButtonAddMediaLibrary'), + icon: 'add_circle', Locations: [], showType: false, showLocations: false, showMenu: false, - showNameWithIcon: true + showNameWithIcon: false }); for (var i = 0; i < virtualFolders.length; i++) { @@ -169,19 +169,19 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl html += getVirtualFolderHtml(page, virtualFolder, i); } - var divVirtualFolders = page.querySelector("#divVirtualFolders"); + var divVirtualFolders = page.querySelector('#divVirtualFolders'); divVirtualFolders.innerHTML = html; - divVirtualFolders.classList.add("itemsContainer"); - divVirtualFolders.classList.add("vertical-wrap"); - $(".btnCardMenu", divVirtualFolders).on("click", function () { + divVirtualFolders.classList.add('itemsContainer'); + divVirtualFolders.classList.add('vertical-wrap'); + $('.btnCardMenu', divVirtualFolders).on('click', function () { showCardMenu(page, this, virtualFolders); }); - divVirtualFolders.querySelector(".addLibrary").addEventListener("click", function () { + divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { addVirtualFolder(page); }); - $(".editLibrary", divVirtualFolders).on("click", function () { - var card = $(this).parents(".card")[0]; - var index = parseInt(card.getAttribute("data-index")); + $('.editLibrary', divVirtualFolders).on('click', function () { + var card = $(this).parents('.card')[0]; + var index = parseInt(card.getAttribute('data-index')); var virtualFolder = virtualFolders[index]; if (virtualFolder.ItemId) { @@ -192,7 +192,7 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function editImages(page, virtualFolder) { - require(["imageEditor"], function (imageEditor) { + require(['imageEditor'], function (imageEditor) { imageEditor.show({ itemId: virtualFolder.ItemId, serverId: ApiClient.serverId() @@ -203,61 +203,66 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl } function getLink(text, url) { - return globalize.translate(text, '', ""); + return globalize.translate(text, '', ''); } function getCollectionTypeOptions() { return [{ - name: "", - value: "" + name: '', + value: '' }, { - name: globalize.translate("FolderTypeMovies"), - value: "movies", - message: getLink("MovieLibraryHelp", "https://docs.jellyfin.org/general/server/media/movies.html") + name: globalize.translate('FolderTypeMovies'), + value: 'movies', + message: getLink('MovieLibraryHelp', 'https://docs.jellyfin.org/general/server/media/movies.html') }, { - name: globalize.translate("FolderTypeMusic"), - value: "music", - message: getLink("MusicLibraryHelp", "https://docs.jellyfin.org/general/server/media/music.html") + name: globalize.translate('FolderTypeMusic'), + value: 'music', + message: getLink('MusicLibraryHelp', 'https://docs.jellyfin.org/general/server/media/music.html') }, { - name: globalize.translate("FolderTypeTvShows"), - value: "tvshows", - message: getLink("TvLibraryHelp", "https://docs.jellyfin.org/general/server/media/shows.html") + name: globalize.translate('FolderTypeTvShows'), + value: 'tvshows', + message: getLink('TvLibraryHelp', 'https://docs.jellyfin.org/general/server/media/shows.html') }, { - name: globalize.translate("FolderTypeBooks"), - value: "books", - message: getLink("BookLibraryHelp", "https://docs.jellyfin.org/general/server/media/books.html") + name: globalize.translate('FolderTypeBooks'), + value: 'books', + message: getLink('BookLibraryHelp', 'https://docs.jellyfin.org/general/server/media/books.html') }, { - name: globalize.translate("OptionHomeVideos"), - value: "homevideos" + name: globalize.translate('OptionHomeVideos'), + value: 'homevideos' }, { - name: globalize.translate("FolderTypeMusicVideos"), - value: "musicvideos" + name: globalize.translate('FolderTypeMusicVideos'), + value: 'musicvideos' }, { - name: globalize.translate("FolderTypeUnset"), - value: "mixed", - message: globalize.translate("MessageUnsetContentHelp") + name: globalize.translate('FolderTypeUnset'), + value: 'mixed', + message: globalize.translate('MessageUnsetContentHelp') }]; } function getVirtualFolderHtml(page, virtualFolder, index) { - var html = ""; - var style = ""; + var html = ''; + var style = ''; - if (page.classList.contains("wizardPage")) { - style += "min-width:33.3%;"; + if (page.classList.contains('wizardPage')) { + style += 'min-width:33.3%;'; + } + + if (virtualFolder.Locations.length == 0) { + html += '
'; + } else { + html += '
'; } - html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; - var imgUrl = ""; + var imgUrl = ''; if (virtualFolder.PrimaryImageItemId) { imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.40), - type: "Primary" + type: 'Primary' }); } @@ -268,124 +273,124 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl hasCardImageContainer = true; } else if (!virtualFolder.showNameWithIcon) { html += '
'; - html += '' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + ""; + html += ''; hasCardImageContainer = true; } if (hasCardImageContainer) { html += '
'; - html += '
'; - html += "
"; - html += "
"; + html += '
'; + html += '
'; + html += '
'; } if (!imgUrl && virtualFolder.showNameWithIcon) { html += '

'; - html += '' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + ""; + html += ''; if (virtualFolder.showNameWithIcon) { html += '
'; html += virtualFolder.Name; - html += "
"; + html += '

'; } - html += ""; + html += ''; } - html += "
"; - html += "
"; + html += '
'; + html += ''; html += '
'; // always show menu unless explicitly hidden if (virtualFolder.showMenu !== false) { html += '
'; - html += ''; - html += "
"; + html += ''; + html += '
'; } html += "
"; if (virtualFolder.showNameWithIcon) { - html += " "; + html += ' '; } else { html += virtualFolder.Name; } - html += "
"; + html += ''; var typeName = getCollectionTypeOptions().filter(function (t) { return t.value == virtualFolder.CollectionType; })[0]; - typeName = typeName ? typeName.name : globalize.translate("FolderTypeUnset"); + typeName = typeName ? typeName.name : globalize.translate('FolderTypeUnset'); html += "
"; if (virtualFolder.showType === false) { - html += " "; + html += ' '; } else { html += typeName; } - html += "
"; + html += ''; if (virtualFolder.showLocations === false) { html += "
"; - html += " "; - html += "
"; + html += ' '; + html += ''; } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { html += "
"; html += virtualFolder.Locations[0]; - html += "
"; + html += ''; } else { html += "
"; - html += globalize.translate("NumLocationsValue", virtualFolder.Locations.length); - html += "
"; + html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length); + html += ''; } - html += ""; - html += ""; - html += ""; + html += ''; + html += ''; + html += ''; return html; } function getTabs() { return [{ - href: "library.html", - name: globalize.translate("HeaderLibraries") + href: 'library.html', + name: globalize.translate('HeaderLibraries') }, { - href: "librarydisplay.html", - name: globalize.translate("TabDisplay") + href: 'librarydisplay.html', + name: globalize.translate('TabDisplay') }, { - href: "metadataimages.html", - name: globalize.translate("TabMetadata") + href: 'metadataimages.html', + name: globalize.translate('TabMetadata') }, { - href: "metadatanfo.html", - name: globalize.translate("TabNfoSettings") + href: 'metadatanfo.html', + name: globalize.translate('TabNfoSettings') }]; } window.WizardLibraryPage = { next: function () { - Dashboard.navigate("wizardsettings.html"); + Dashboard.navigate('wizardsettings.html'); } }; - pageClassOn("pageshow", "mediaLibraryPage", function () { + pageClassOn('pageshow', 'mediaLibraryPage', function () { reloadLibrary(this); }); - pageIdOn("pageshow", "mediaLibraryPage", function () { - libraryMenu.setTabs("librarysetup", 0, getTabs); + pageIdOn('pageshow', 'mediaLibraryPage', function () { + libraryMenu.setTabs('librarysetup', 0, getTabs); var page = this; taskButton({ - mode: "on", - progressElem: page.querySelector(".refreshProgress"), - taskKey: "RefreshLibrary", - button: page.querySelector(".btnRefresh") + mode: 'on', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') }); }); - pageIdOn("pagebeforehide", "mediaLibraryPage", function () { + pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { var page = this; taskButton({ - mode: "off", - progressElem: page.querySelector(".refreshProgress"), - taskKey: "RefreshLibrary", - button: page.querySelector(".btnRefresh") + mode: 'off', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') }); }); }); diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js new file mode 100644 index 00000000000..3047736a681 --- /dev/null +++ b/src/controllers/dashboard/metadataImages.js @@ -0,0 +1,70 @@ +define(['jQuery', 'dom', 'loading', 'libraryMenu', 'globalize', 'listViewStyle'], function($, dom, loading, libraryMenu, globalize) { + 'use strict'; + + function populateLanguages(select) { + return ApiClient.getCultures().then(function(languages) { + var html = ''; + html += ""; + for (var i = 0, length = languages.length; i < length; i++) { + var culture = languages[i]; + html += "'; + } + select.innerHTML = html; + }); + } + + function populateCountries(select) { + return ApiClient.getCountries().then(function(allCountries) { + var html = ''; + html += ""; + for (var i = 0, length = allCountries.length; i < length; i++) { + var culture = allCountries[i]; + html += "'; + } + select.innerHTML = html; + }); + } + + function loadPage(page) { + var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector('#selectLanguage')), populateCountries(page.querySelector('#selectCountry'))]; + Promise.all(promises).then(function(responses) { + var config = responses[0]; + page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; + page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; + loading.hide(); + }); + } + + function onSubmit() { + var form = this; + return loading.show(), ApiClient.getServerConfiguration().then(function(config) { + config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + config.MetadataCountryCode = form.querySelector('#selectCountry').value; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }), !1; + } + + function getTabs() { + return [{ + href: 'library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: 'librarydisplay.html', + name: globalize.translate('TabDisplay') + }, { + href: 'metadataimages.html', + name: globalize.translate('TabMetadata') + }, { + 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); + }); +}); diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js new file mode 100644 index 00000000000..a9361926185 --- /dev/null +++ b/src/controllers/dashboard/metadatanfo.js @@ -0,0 +1,74 @@ +define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu, globalize) { + 'use strict'; + + function loadPage(page, config, users) { + var 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(); + var 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(config); + }); + }); + return false; + } + + function showConfirmMessage(config) { + var msg = []; + msg.push(globalize.translate('MetadataSettingChangeHelp')); + + require(['alert'], function (alert) { + alert({ + text: msg.join('

') + }); + }); + } + + function getTabs() { + return [{ + href: 'library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: 'librarydisplay.html', + name: globalize.translate('TabDisplay') + }, { + href: 'metadataimages.html', + name: globalize.translate('TabMetadata') + }, { + href: 'metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; + } + + var 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(); + var page = this; + var promise1 = ApiClient.getUsers(); + var promise2 = ApiClient.getNamedConfiguration(metadataKey); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[1], responses[0]); + }); + }); +}); diff --git a/src/controllers/dashboard/networking.js b/src/controllers/dashboard/networking.js index c0e4d76d0d6..4ddde7f24ce 100644 --- a/src/controllers/dashboard/networking.js +++ b/src/controllers/dashboard/networking.js @@ -1,68 +1,43 @@ -define(["loading", "libraryMenu", "globalize", "emby-checkbox", "emby-select"], function (loading, libraryMenu, globalize) { - "use strict"; +define(['loading', 'libraryMenu', 'globalize', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { + 'use strict'; function onSubmit(e) { var form = this; - var localAddress = form.querySelector("#txtLocalAddress").value; - var enableUpnp = form.querySelector("#chkEnableUpnp").checked; + var localAddress = form.querySelector('#txtLocalAddress').value; + var enableUpnp = form.querySelector('#chkEnableUpnp').checked; confirmSelections(localAddress, enableUpnp, function () { var validationResult = getValidationAlert(form); if (validationResult) { - alertText(validationResult); + showAlertText(validationResult); return; } validateHttps(form).then(function () { loading.show(); ApiClient.getServerConfiguration().then(function (config) { - config.LocalNetworkSubnets = form.querySelector("#txtLanNetworks").value.split(",").map(function (s) { + 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) { + config.RemoteIPFilter = form.querySelector('#txtExternalAddressFilter').value.split(',').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; }); - config.IsRemoteIPFilterBlacklist = "blacklist" === form.querySelector("#selectExternalAddressFilterMode").value; - config.PublicPort = form.querySelector("#txtPublicPort").value; - config.PublicHttpsPort = form.querySelector("#txtPublicHttpsPort").value; - var httpsMode = form.querySelector("#selectHttpsMode").value; - - switch (httpsMode) { - case "proxy": - config.EnableHttps = true; - config.RequireHttps = false; - config.IsBehindProxy = true; - break; - - case "required": - config.EnableHttps = true; - config.RequireHttps = true; - config.IsBehindProxy = false; - break; - - case "enabled": - config.EnableHttps = true; - config.RequireHttps = false; - config.IsBehindProxy = false; - break; - - default: - config.EnableHttps = false; - config.RequireHttps = false; - config.IsBehindProxy = false; - } - - config.HttpsPortNumber = form.querySelector("#txtHttpsPort").value; - config.HttpServerPortNumber = form.querySelector("#txtPortNumber").value; + config.IsRemoteIPFilterBlacklist = 'blacklist' === form.querySelector('#selectExternalAddressFilterMode').value; + 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.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.LocalNetworkAddresses = localAddress ? [localAddress] : []; ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); }); @@ -72,43 +47,40 @@ define(["loading", "libraryMenu", "globalize", "emby-checkbox", "emby-select"], } function triggerChange(select) { - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", false, true); + var 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 (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { + return 'The public http and https ports must be different.'; } - if (form.querySelector("#txtPortNumber").value === form.querySelector("#txtHttpsPort").value) { - return "The http and https ports must be different."; + if (form.querySelector('#txtPortNumber').value === form.querySelector('#txtHttpsPort').value) { + return 'The http and https ports must be different.'; } return null; } function validateHttps(form) { - var remoteAccess = form.querySelector("#chkRemoteAccess").checked; - var certPath = form.querySelector("#txtCertificatePath").value || null; - var httpsMode = form.querySelector("#selectHttpsMode").value; - - if (!remoteAccess || ("enabled" !== httpsMode && "required" !== httpsMode || certPath)) { - return Promise.resolve(); + var certPath = form.querySelector('#txtCertificatePath').value || null; + var httpsEnabled = form.querySelector('#chkEnableHttps').checked; + + if (httpsEnabled && !certPath) { + return showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('HttpsRequiresCert') + }).then(Promise.reject); } - return new Promise(function (resolve, reject) { - return alertText({ - title: globalize.translate("TitleHostingSettings"), - text: globalize.translate("HttpsRequiresCert") - }).then(reject, reject); - }); + return Promise.resolve(); } - function alertText(options) { + function showAlertText(options) { return new Promise(function (resolve, reject) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert(options).then(resolve, reject); }); }); @@ -116,9 +88,9 @@ define(["loading", "libraryMenu", "globalize", "emby-checkbox", "emby-select"], function confirmSelections(localAddress, enableUpnp, callback) { if (localAddress || !enableUpnp) { - alertText({ - title: globalize.translate("TitleHostingSettings"), - text: globalize.translate("SettingsWarning") + showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('SettingsWarning') }).then(callback); } else { callback(); @@ -127,76 +99,60 @@ define(["loading", "libraryMenu", "globalize", "emby-checkbox", "emby-select"], return function (view, params) { 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[0] || ""; - page.querySelector("#txtLanNetworks").value = (config.LocalNetworkSubnets || []).join(", "); - page.querySelector("#txtExternalAddressFilter").value = (config.RemoteIPFilter || []).join(", "); - page.querySelector("#selectExternalAddressFilterMode").value = config.IsRemoteIPFilterBlacklist ? "blacklist" : "whitelist"; - page.querySelector("#chkRemoteAccess").checked = null == config.EnableRemoteAccess || config.EnableRemoteAccess; - var selectHttpsMode = page.querySelector("#selectHttpsMode"); - - if (config.IsBehindProxy) { - selectHttpsMode.value = "proxy"; - } else if (config.RequireHttps) { - selectHttpsMode.value = "required"; - } else if (config.EnableHttps) { - selectHttpsMode.value = "enabled"; - } else { - selectHttpsMode.value = "disabled"; - } - - page.querySelector("#txtHttpsPort").value = config.HttpsPortNumber; - page.querySelector("#txtBaseUrl").value = config.BaseUrl || ""; - var 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('#txtPortNumber').value = config.HttpServerPortNumber; + page.querySelector('#txtPublicPort').value = config.PublicPort; + page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; + page.querySelector('#txtLocalAddress').value = config.LocalNetworkAddresses[0] || ''; + page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); + page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); + page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; + page.querySelector('#chkRemoteAccess').checked = null == config.EnableRemoteAccess || 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 || ''; + var txtCertificatePath = page.querySelector('#txtCertificatePath'); + txtCertificatePath.value = config.CertificatePath || ''; + page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; + page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; + triggerChange(page.querySelector('#chkRemoteAccess')); loading.hide(); } - view.querySelector("#chkRemoteAccess").addEventListener("change", function () { + 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(".fldCertificatePath").classList.remove("hide"); - view.querySelector(".fldCertPassword").classList.remove("hide"); - view.querySelector(".fldHttpsMode").classList.remove("hide"); - view.querySelector(".fldEnableUpnp").classList.remove("hide"); + 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(".fldCertificatePath").classList.add("hide"); - view.querySelector(".fldCertPassword").classList.add("hide"); - view.querySelector(".fldHttpsMode").classList.add("hide"); - view.querySelector(".fldEnableUpnp").classList.add("hide"); + 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 () { - require(["directorybrowser"], function (directoryBrowser) { + view.querySelector('#btnSelectCertPath').addEventListener('click', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ includeFiles: true, includeDirectories: true, callback: function (path) { if (path) { - view.querySelector("#txtCertificatePath").value = path; + view.querySelector('#txtCertificatePath').value = path; } picker.close(); }, - header: globalize.translate("HeaderSelectCertificatePath") + header: globalize.translate('HeaderSelectCertificatePath') }); }); }); - view.querySelector(".dashboardHostingForm").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", function (e) { + view.querySelector('.dashboardHostingForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function (e) { loading.show(); ApiClient.getServerConfiguration().then(function (config) { loadPage(view, config); diff --git a/src/controllers/dashboard/notifications/notification.js b/src/controllers/dashboard/notifications/notification.js index 70e6adaf61e..af9301c13fe 100644 --- a/src/controllers/dashboard/notifications/notification.js +++ b/src/controllers/dashboard/notifications/notification.js @@ -1,23 +1,23 @@ -define(["jQuery", "emby-checkbox", "fnchecked"], function ($) { - "use strict"; +define(['jQuery', 'emby-checkbox'], function ($) { + 'use strict'; function fillItems(elem, items, cssClass, idPrefix, currentList, isEnabledList) { var html = '
'; html += items.map(function (u) { var isChecked = isEnabledList ? currentList.indexOf(u.Id) != -1 : currentList.indexOf(u.Id) == -1; - var checkedHtml = isChecked ? ' checked="checked"' : ""; - return '"; - }).join(""); - html += "
"; - elem.html(html).trigger("create"); + var checkedHtml = isChecked ? ' checked="checked"' : ''; + return ''; + }).join(''); + html += ''; + elem.html(html).trigger('create'); } function reload(page) { - var type = getParameterByName("type"); + var type = getParameterByName('type'); var promise1 = ApiClient.getUsers(); var promise2 = ApiClient.getNamedConfiguration(notificationsConfigurationKey); - var promise3 = ApiClient.getJSON(ApiClient.getUrl("Notifications/Types")); - var promise4 = ApiClient.getJSON(ApiClient.getUrl("Notifications/Services")); + var promise3 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')); + var promise4 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Services')); Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) { var users = responses[0]; var notificationOptions = responses[1]; @@ -31,37 +31,37 @@ define(["jQuery", "emby-checkbox", "fnchecked"], function ($) { })[0] || {}; if (typeInfo.IsBasedOnUserEvent) { - $(".monitorUsers", page).show(); + $('.monitorUsers', page).show(); } else { - $(".monitorUsers", page).hide(); + $('.monitorUsers', page).hide(); } - $(".notificationType", page).html(typeInfo.Name || "Unknown Notification"); + $('.notificationType', page).html(typeInfo.Name || 'Unknown Notification'); if (!notificationConfig) { notificationConfig = { DisabledMonitorUsers: [], SendToUsers: [], DisabledServices: [], - SendToUserMode: "Admins" + SendToUserMode: 'Admins' }; } - fillItems($(".monitorUsersList", page), users, "chkMonitor", "chkMonitor", notificationConfig.DisabledMonitorUsers); - fillItems($(".sendToUsersList", page), users, "chkSendTo", "chkSendTo", notificationConfig.SendToUsers, true); - fillItems($(".servicesList", page), services, "chkService", "chkService", notificationConfig.DisabledServices); - $("#chkEnabled", page).checked(notificationConfig.Enabled || false); - $("#selectUsers", page).val(notificationConfig.SendToUserMode).trigger("change"); + fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers); + fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true); + fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices); + $('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false); + $('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change'); }); } function save(page) { - var type = getParameterByName("type"); + var type = getParameterByName('type'); var promise1 = ApiClient.getNamedConfiguration(notificationsConfigurationKey); - var promise2 = ApiClient.getJSON(ApiClient.getUrl("Notifications/Types")); + // TODO: Check if this promise is really needed, as it's unused. + var promise2 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')); Promise.all([promise1, promise2]).then(function (responses) { var notificationOptions = responses[0]; - var types = responses[1]; var notificationConfig = notificationOptions.Options.filter(function (n) { return n.Type == type; })[0]; @@ -73,50 +73,47 @@ define(["jQuery", "emby-checkbox", "fnchecked"], function ($) { notificationOptions.Options.push(notificationConfig); } - types.filter(function (n) { - return n.Type == type; - })[0]; - notificationConfig.Enabled = $("#chkEnabled", page).checked(); - notificationConfig.SendToUserMode = $("#selectUsers", page).val(); - notificationConfig.DisabledMonitorUsers = $(".chkMonitor", page).get().filter(function (c) { + notificationConfig.Enabled = $('#chkEnabled', page).is(':checked'); + notificationConfig.SendToUserMode = $('#selectUsers', page).val(); + notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) { return !c.checked; }).map(function (c) { - return c.getAttribute("data-itemid"); + return c.getAttribute('data-itemid'); }); - notificationConfig.SendToUsers = $(".chkSendTo", page).get().filter(function (c) { + notificationConfig.SendToUsers = $('.chkSendTo', page).get().filter(function (c) { return c.checked; }).map(function (c) { - return c.getAttribute("data-itemid"); + return c.getAttribute('data-itemid'); }); - notificationConfig.DisabledServices = $(".chkService", page).get().filter(function (c) { + notificationConfig.DisabledServices = $('.chkService', page).get().filter(function (c) { return !c.checked; }).map(function (c) { - return c.getAttribute("data-itemid"); + return c.getAttribute('data-itemid'); }); ApiClient.updateNamedConfiguration(notificationsConfigurationKey, notificationOptions).then(function (r) { Dashboard.processServerConfigurationUpdateResult(); - Dashboard.navigate("notificationsettings.html"); + Dashboard.navigate('notificationsettings.html'); }); }); } function onSubmit() { - save($(this).parents(".page")); + save($(this).parents('.page')); return false; } - var notificationsConfigurationKey = "notifications"; - $(document).on("pageinit", "#notificationSettingPage", function () { + var notificationsConfigurationKey = 'notifications'; + $(document).on('pageinit', '#notificationSettingPage', function () { var page = this; - $("#selectUsers", page).on("change", function () { - if ("Custom" == this.value) { - $(".selectCustomUsers", page).show(); + $('#selectUsers', page).on('change', function () { + if ('Custom' == this.value) { + $('.selectCustomUsers', page).show(); } else { - $(".selectCustomUsers", page).hide(); + $('.selectCustomUsers', page).hide(); } }); - $(".notificationSettingForm").off("submit", onSubmit).on("submit", onSubmit); - }).on("pageshow", "#notificationSettingPage", function () { + $('.notificationSettingForm').off('submit', onSubmit).on('submit', onSubmit); + }).on('pageshow', '#notificationSettingPage', function () { reload(this); }); }); diff --git a/src/controllers/dashboard/notifications/notifications.js b/src/controllers/dashboard/notifications/notifications.js index 8f63753bf38..4e049bc1065 100644 --- a/src/controllers/dashboard/notifications/notifications.js +++ b/src/controllers/dashboard/notifications/notifications.js @@ -1,60 +1,60 @@ -define(["loading", "libraryMenu", "globalize", "listViewStyle", "emby-button"], function(loading, libraryMenu, globalize) { - "use strict"; +define(['loading', 'libraryMenu', 'globalize', 'listViewStyle', 'emby-button'], function(loading, libraryMenu, globalize) { + 'use strict'; function reload(page) { loading.show(); - ApiClient.getJSON(ApiClient.getUrl("Notifications/Types")).then(function(list) { - var html = ""; - var lastCategory = ""; + ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')).then(function(list) { + var html = ''; + var lastCategory = ''; var showHelp = true; html += list.map(function(notification) { - var itemHtml = ""; + var itemHtml = ''; if (notification.Category !== lastCategory) { lastCategory = notification.Category; if (lastCategory) { - itemHtml += ""; - itemHtml += ""; + itemHtml += ''; + itemHtml += ''; } itemHtml += '
'; itemHtml += '
'; itemHtml += '

'; itemHtml += notification.Category; - itemHtml += "

"; + itemHtml += ''; if (showHelp) { showHelp = false; itemHtml += ''; - itemHtml += globalize.translate("Help"); - itemHtml += ""; + itemHtml += globalize.translate('Help'); + itemHtml += ''; } - itemHtml += "
"; + itemHtml += '
'; itemHtml += ''; + itemHtml += ''; + itemHtml += ''; return itemHtml; - }).join(""); + }).join(''); if (list.length) { - html += ""; - html += ""; + html += ''; + html += ''; } - page.querySelector(".notificationList").innerHTML = html; + page.querySelector('.notificationList').innerHTML = html; loading.hide(); - }) + }); } return function(view, params) { - view.addEventListener("viewshow", function() { + view.addEventListener('viewshow', function() { reload(view); }); - } + }; }); diff --git a/src/controllers/playbackconfiguration.js b/src/controllers/dashboard/playback.js similarity index 50% rename from src/controllers/playbackconfiguration.js rename to src/controllers/dashboard/playback.js index 76c704f7eb3..d5b67a29616 100644 --- a/src/controllers/playbackconfiguration.js +++ b/src/controllers/dashboard/playback.js @@ -1,10 +1,10 @@ -define(["jQuery", "loading", "libraryMenu"], function ($, loading, libraryMenu) { - "use strict"; +define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu, globalize) { + 'use strict'; function loadPage(page, config) { - $("#txtMinResumePct", page).val(config.MinResumePct); - $("#txtMaxResumePct", page).val(config.MaxResumePct); - $("#txtMinResumeDuration", page).val(config.MinResumeDurationSeconds); + $('#txtMinResumePct', page).val(config.MinResumePct); + $('#txtMaxResumePct', page).val(config.MaxResumePct); + $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds); loading.hide(); } @@ -24,22 +24,22 @@ define(["jQuery", "loading", "libraryMenu"], function ($, loading, libraryMenu) function getTabs() { return [{ - href: "encodingsettings.html", - name: Globalize.translate("Transcoding") + href: 'encodingsettings.html', + name: globalize.translate('Transcoding') }, { - href: "playbackconfiguration.html", - name: Globalize.translate("TabResumeSettings") + href: 'playbackconfiguration.html', + name: globalize.translate('TabResumeSettings') }, { - href: "streamingsettings.html", - name: Globalize.translate("TabStreaming") + href: 'streamingsettings.html', + name: globalize.translate('TabStreaming') }]; } - $(document).on("pageinit", "#playbackConfigurationPage", function () { - $(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit) - }).on("pageshow", "#playbackConfigurationPage", function () { + $(document).on('pageinit', '#playbackConfigurationPage', function () { + $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); + }).on('pageshow', '#playbackConfigurationPage', function () { loading.show(); - libraryMenu.setTabs("playback", 1, getTabs); + libraryMenu.setTabs('playback', 1, getTabs); var page = this; ApiClient.getServerConfiguration().then(function (config) { loadPage(page, config); diff --git a/src/controllers/dashboard/plugins/add.js b/src/controllers/dashboard/plugins/add.js index 72a7134fac5..8b1deb804d2 100644 --- a/src/controllers/dashboard/plugins/add.js +++ b/src/controllers/dashboard/plugins/add.js @@ -1,43 +1,36 @@ -define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "emby-button"], function ($, loading, libraryMenu, globalize, connectionManager) { - "use strict"; +define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'emby-button'], function ($, loading, libraryMenu, globalize, connectionManager) { + 'use strict'; function populateHistory(packageInfo, page) { - var html = ""; + var html = ''; var length = Math.min(packageInfo.versions.length, 10); for (var i = 0; i < length; i++) { var version = packageInfo.versions[i]; - html += '

' + version.versionStr + " (" + version.classification + ")

"; - html += '
' + version.description + "
"; + html += '

' + version.version + '

'; + html += '
' + version.changelog + '
'; } - $("#revisionHistory", page).html(html); + $('#revisionHistory', page).html(html); } function populateVersions(packageInfo, page, installedPlugin) { - var html = ""; + var html = ''; for (var i = 0; i < packageInfo.versions.length; i++) { var version = packageInfo.versions[i]; - html += '"; + html += ''; } - var selectmenu = $("#selectVersion", page).html(html); + var selectmenu = $('#selectVersion', page).html(html); if (!installedPlugin) { - $("#pCurrentVersion", page).hide().html(""); + $('#pCurrentVersion', page).hide().html(''); } - var packageVersion = packageInfo.versions.filter(function (current) { - return "Release" == current.classification; - })[0]; - packageVersion = packageVersion || packageInfo.versions.filter(function (current) { - return "Beta" == current.classification; - })[0]; - + var packageVersion = packageInfo.versions[0]; if (packageVersion) { - var val = packageVersion.versionStr + "|" + packageVersion.classification; - selectmenu.val(val); + selectmenu.val(packageVersion.version); } } @@ -45,81 +38,60 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e var installedPlugin = installedPlugins.filter(function (ip) { return ip.Name == pkg.name; })[0]; + populateVersions(pkg, page, installedPlugin); populateHistory(pkg, page); - $(".pluginName", page).html(pkg.name); - - if ("Server" == pkg.targetSystem) { - $("#btnInstallDiv", page).removeClass("hide"); - $("#nonServerMsg", page).hide(); - $("#pSelectVersion", page).removeClass("hide"); - } else { - $("#btnInstallDiv", page).addClass("hide"); - $("#pSelectVersion", page).addClass("hide"); - var msg = globalize.translate("MessageInstallPluginFromApp"); - $("#nonServerMsg", page).html(msg).show(); - } - - if (pkg.shortDescription) { - $("#tagline", page).show().html(pkg.shortDescription); - } else { - $("#tagline", page).hide(); - } - $("#overview", page).html(pkg.overview || ""); - $("#developer", page).html(pkg.owner); + $('.pluginName', page).html(pkg.name); + $('#btnInstallDiv', page).removeClass('hide'); + $('#pSelectVersion', page).removeClass('hide'); - if (pkg.richDescUrl) { - $("#pViewWebsite", page).show(); - $("#pViewWebsite a", page).attr("href", pkg.richDescUrl); + if (pkg.overview) { + $('#overview', page).show().html(pkg.overview); } else { - $("#pViewWebsite", page).hide(); + $('#overview', page).hide(); } - if (pkg.previewImage || pkg.thumbImage) { - var img = pkg.previewImage ? pkg.previewImage : pkg.thumbImage; - $("#pPreviewImage", page).show().html(""); - } else { - $("#pPreviewImage", page).hide().html(""); - } + $('#description', page).html(pkg.description); + $('#developer', page).html(pkg.owner); if (installedPlugin) { - var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "" + installedPlugin.Version + ""); - $("#pCurrentVersion", page).show().html(currentVersionText); + var currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); + $('#pCurrentVersion', page).show().html(currentVersionText); } else { - $("#pCurrentVersion", page).hide().html(""); + $('#pCurrentVersion', page).hide().html(''); } loading.hide(); } function alertText(options) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert(options); }); } - function performInstallation(page, packageName, guid, updateClass, version) { - var developer = $("#developer", page).html().toLowerCase(); + function performInstallation(page, name, guid, version) { + var developer = $('#developer', page).html().toLowerCase(); var alertCallback = function () { loading.show(); - page.querySelector("#btnInstall").disabled = true; - ApiClient.installPlugin(packageName, guid, updateClass, version).then(function () { + page.querySelector('#btnInstall').disabled = true; + ApiClient.installPlugin(name, guid, version).then(function () { loading.hide(); - alertText(globalize.translate("PluginInstalledMessage")); + alertText(globalize.translate('PluginInstalledMessage')); }); }; if (developer !== 'jellyfin') { loading.hide(); - var msg = globalize.translate("MessagePluginInstallDisclaimer"); - msg += "
"; - msg += "
"; - msg += globalize.translate("PleaseConfirmPluginInstallation"); + var msg = globalize.translate('MessagePluginInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmPluginInstallation'); - require(["confirm"], function (confirm) { - confirm(msg, globalize.translate("HeaderConfirmPluginInstallation")).then(function () { + require(['confirm'], function (confirm) { + confirm(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { alertCallback(); }, function () { console.debug('plugin not installed'); @@ -131,33 +103,32 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e } return function (view, params) { - $(".addPluginForm", view).on("submit", function () { + $('.addPluginForm', view).on('submit', function () { loading.show(); - var page = $(this).parents("#addPluginPage")[0]; + var page = $(this).parents('#addPluginPage')[0]; var name = params.name; var guid = params.guid; ApiClient.getInstalledPlugins().then(function (plugins) { var installedPlugin = plugins.filter(function (plugin) { return plugin.Name == name; })[0]; - var vals = $("#selectVersion", page).val().split("|"); - var version = vals[0]; + var version = $('#selectVersion', page).val(); if (installedPlugin) { if (installedPlugin.Version === version) { loading.hide(); Dashboard.alert({ - message: globalize.translate("MessageAlreadyInstalled"), - title: globalize.translate("HeaderPluginInstallation") + message: globalize.translate('MessageAlreadyInstalled'), + title: globalize.translate('HeaderPluginInstallation') }); } } else { - performInstallation(page, name, guid, vals[1], version); + performInstallation(page, name, guid, version); } }); return false; }); - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { var page = this; loading.show(); var name = params.name; diff --git a/src/controllers/dashboard/plugins/available.js b/src/controllers/dashboard/plugins/available.js index 5526bd9ade5..37df8801b5b 100644 --- a/src/controllers/dashboard/plugins/available.js +++ b/src/controllers/dashboard/plugins/available.js @@ -1,5 +1,5 @@ -define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby-checkbox", "emby-select"], function (loading, libraryMenu, globalize) { - "use strict"; +define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { + 'use strict'; function reloadList(page) { loading.show(); @@ -7,8 +7,8 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby var promise2 = ApiClient.getInstalledPlugins(); Promise.all([promise1, promise2]).then(function (responses) { populateList({ - catalogElement: page.querySelector("#pluginTiles"), - noItemsElement: page.querySelector("#noPlugins"), + catalogElement: page.querySelector('#pluginTiles'), + noItemsElement: page.querySelector('#noPlugins'), availablePlugins: responses[0], installedPlugins: responses[1] }); @@ -16,15 +16,15 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby } function getHeaderText(category) { - category = category.replace(" ", ""); - if ("Channel" === category) { - category = "Channels"; - } else if ("Theme" === category) { - category = "Themes"; - } else if ("LiveTV" === category) { - category = "HeaderLiveTV"; - } else if ("ScreenSaver" === category) { - category = "HeaderScreenSavers"; + category = category.replace(' ', ''); + if ('Channel' === category) { + category = 'Channels'; + } else if ('Theme' === category) { + category = 'Themes'; + } else if ('LiveTV' === category) { + category = 'HeaderLiveTV'; + } else if ('ScreenSaver' === category) { + category = 'HeaderScreenSavers'; } return globalize.translate(category); @@ -56,28 +56,28 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby }); var currentCategory = null; - var html = ""; + var html = ''; for (var i = 0; i < availablePlugins.length; i++) { var plugin = availablePlugins[i]; var category = plugin.categoryDisplayName; if (category != currentCategory) { if (currentCategory) { - html += ""; - html += ""; + html += ''; + html += ''; } html += '
'; - html += '

' + category + "

"; + html += '

' + category + '

'; html += '
'; currentCategory = category; } html += getPluginHtml(plugin, options, installedPlugins); } - html += "
"; - html += "
"; + html += ''; + html += ''; if (!availablePlugins.length && options.noItemsElement) { - options.noItemsElement.classList.remove("hide"); + options.noItemsElement.classList.remove('hide'); } options.catalogElement.innerHTML = html; @@ -85,51 +85,47 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby } function getPluginHtml(plugin, options, installedPlugins) { - var html = ""; - var href = plugin.externalUrl ? plugin.externalUrl : "addplugin.html?name=" + encodeURIComponent(plugin.name) + "&guid=" + plugin.guid; + var html = ''; + var href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; if (options.context) { - href += "&context=" + options.context; + href += '&context=' + options.context; } - var target = plugin.externalUrl ? ' target="_blank"' : ""; + var target = plugin.externalUrl ? ' target="_blank"' : ''; html += "
"; html += ''; html += '
'; html += "
"; html += plugin.name; - html += "
"; - var installedPlugin = plugin.isApp ? null : installedPlugins.filter(function (ip) { + html += '
'; + var installedPlugin = installedPlugins.filter(function (ip) { return ip.Id == plugin.guid; })[0]; html += "
"; - html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : " "; - html += "
"; - html += "
"; - html += ""; - return html += ""; + html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; + html += ''; + html += ''; + html += ''; + return html += ''; } function getTabs() { return [{ - href: "installedplugins.html", - name: globalize.translate("TabMyPlugins") + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') }, { - href: "availableplugins.html", - name: globalize.translate("TabCatalog") + href: 'repositories.html', + name: globalize.translate('TabRepositories') }]; } @@ -138,8 +134,8 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby }; return function (view, params) { - view.addEventListener("viewshow", function () { - libraryMenu.setTabs("plugins", 1, getTabs); + view.addEventListener('viewshow', function () { + libraryMenu.setTabs('plugins', 1, getTabs); reloadList(this); }); }; diff --git a/src/controllers/dashboard/plugins/installed.js b/src/controllers/dashboard/plugins/installed.js index 026b58ce67e..5ac9737d69b 100644 --- a/src/controllers/dashboard/plugins/installed.js +++ b/src/controllers/dashboard/plugins/installed.js @@ -1,15 +1,15 @@ -define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button"], function (loading, libraryMenu, dom, globalize) { - "use strict"; +define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { + 'use strict'; function deletePlugin(page, uniqueid, name) { - var msg = globalize.translate("UninstallPluginConfirmation").replace("{0}", name); + var msg = globalize.translate('UninstallPluginConfirmation', name); - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ - title: globalize.translate("UninstallPluginHeader"), + title: globalize.translate('UninstallPluginHeader'), text: msg, - primary: "delete", - confirmText: globalize.translate("UninstallPluginHeader") + primary: 'delete', + confirmText: globalize.translate('UninstallPluginHeader') }).then(function () { loading.show(); ApiClient.uninstallPlugin(uniqueid).then(function () { @@ -21,13 +21,13 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" function showNoConfigurationMessage() { Dashboard.alert({ - message: globalize.translate("NoPluginConfigurationMessage") + message: globalize.translate('NoPluginConfigurationMessage') }); } function showConnectMessage() { Dashboard.alert({ - message: globalize.translate("MessagePluginConfigurationRequiresLocalAccess") + message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') }); } @@ -36,40 +36,37 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" return pluginConfigurationPage.PluginId == plugin.Id; })[0]; var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; - var html = ""; - html += "
"; + var html = ''; + html += "
"; html += '
'; html += '
'; html += '
'; - html += configPageUrl ? '' : ''; + html += '
'; - if (plugin.ImageUrl) { - html += '
"; - html += "
"; - } else { - html += 'folder'; + if (configPage || plugin.CanUninstall) { + html += '
'; + html += ''; + html += '
'; } - html += configPageUrl ? "" : "
"; - html += "
"; - html += '
'; - html += '
'; - html += ''; - html += "
"; html += "
"; - html += configPage ? configPage.DisplayName || plugin.Name : plugin.Name; - html += "
"; + html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; + html += '
'; html += "
"; html += plugin.Version; - html += "
"; - html += "
"; - html += "
"; - html += "
"; + html += ''; + html += ''; + html += ''; + html += ''; return html; } function renderPlugins(page, plugins) { - ApiClient.getJSON(ApiClient.getUrl("web/configurationpages") + "?pageType=PluginConfiguration").then(function (configPages) { + ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { populateList(page, plugins, configPages); }); } @@ -85,22 +82,22 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" var html = plugins.map(function (p) { return getPluginCardHtml(p, pluginConfigurationPages); - }).join(""); + }).join(''); - var installedPluginsElement = page.querySelector(".installedPlugins"); - installedPluginsElement.removeEventListener("click", onInstalledPluginsClick); - installedPluginsElement.addEventListener("click", onInstalledPluginsClick); + var installedPluginsElement = page.querySelector('.installedPlugins'); + installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); + installedPluginsElement.addEventListener('click', onInstalledPluginsClick); if (plugins.length) { - installedPluginsElement.classList.add("itemsContainer"); - installedPluginsElement.classList.add("vertical-wrap"); + installedPluginsElement.classList.add('itemsContainer'); + installedPluginsElement.classList.add('vertical-wrap'); } else { html += '
'; - html += "

" + globalize.translate("MessageNoPluginsInstalled") + "

"; + html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; html += '

'; - html += globalize.translate("BrowsePluginCatalogMessage"); - html += "

"; - html += "
"; + html += globalize.translate('BrowsePluginCatalogMessage'); + html += '

'; + html += ''; } installedPluginsElement.innerHTML = html; @@ -108,36 +105,39 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" } function showPluginMenu(page, elem) { - var card = dom.parentWithClass(elem, "card"); - var id = card.getAttribute("data-id"); - var name = card.getAttribute("data-name"); - var configHref = card.querySelector(".cardContent").getAttribute("href"); + var card = dom.parentWithClass(elem, 'card'); + var id = card.getAttribute('data-id'); + var name = card.getAttribute('data-name'); + var removable = card.getAttribute('data-removable'); + var configHref = card.querySelector('.cardContent').getAttribute('href'); var menuItems = []; if (configHref) { menuItems.push({ - name: globalize.translate("ButtonSettings"), - id: "open", - icon: "mode_edit" + name: globalize.translate('ButtonSettings'), + id: 'open', + icon: 'mode_edit' }); } - menuItems.push({ - name: globalize.translate("ButtonUninstall"), - id: "delete", - icon: "delete" - }); + if (removable === 'true') { + menuItems.push({ + name: globalize.translate('ButtonUninstall'), + id: 'delete', + icon: 'delete' + }); + } - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: elem, callback: function (resultId) { switch (resultId) { - case "open": + case 'open': Dashboard.navigate(configHref); break; - case "delete": + case 'delete': deletePlugin(page, id, name); break; } @@ -155,29 +155,32 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" function getTabs() { return [{ - href: "installedplugins.html", - name: globalize.translate("TabMyPlugins") + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') }, { - href: "availableplugins.html", - name: globalize.translate("TabCatalog") + href: 'repositories.html', + name: globalize.translate('TabRepositories') }]; } function onInstalledPluginsClick(e) { - if (dom.parentWithClass(e.target, "noConfigPluginCard")) { + if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { showNoConfigurationMessage(); - } else if (dom.parentWithClass(e.target, "connectModePluginCard")) { + } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { showConnectMessage(); } else { - var btnCardMenu = dom.parentWithClass(e.target, "btnCardMenu"); + var btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); if (btnCardMenu) { - showPluginMenu(dom.parentWithClass(btnCardMenu, "page"), btnCardMenu); + showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); } } } - pageIdOn("pageshow", "pluginsPage", function () { - libraryMenu.setTabs("plugins", 0, getTabs); + pageIdOn('pageshow', 'pluginsPage', function () { + libraryMenu.setTabs('plugins', 0, getTabs); reloadList(this); }); diff --git a/src/controllers/dashboard/plugins/repositories.js b/src/controllers/dashboard/plugins/repositories.js new file mode 100644 index 00000000000..3087cdd9279 --- /dev/null +++ b/src/controllers/dashboard/plugins/repositories.js @@ -0,0 +1,153 @@ +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import dialogHelper from 'dialogHelper'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; +import 'formDialogStyle'; +import 'listViewStyle'; + +let repositories = []; + +function reloadList(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Repositories')).then(list => { + repositories = list; + populateList({ + listElement: page.querySelector('#repositories'), + noneElement: page.querySelector('#none'), + repositories: repositories + }); + }).catch(error => { + console.error('error loading repositories'); + page.querySelector('#none').classList.remove('hide'); + loading.hide(); + }); +} + +function saveList(page) { + loading.show(); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Repositories'), + data: JSON.stringify(repositories), + contentType: 'application/json' + }).then(response => { + reloadList(page); + }).catch(error => { + console.error('error saving repositories'); + loading.hide(); + }); +} + +function populateList(options) { + var html = ''; + + html += '
'; + for (var i = 0; i < options.repositories.length; i++) { + html += getRepositoryHtml(options.repositories[i]); + } + + html += '
'; + if (!options.repositories.length) { + options.noneElement.classList.remove('hide'); + } + + options.listElement.innerHTML = html; + loading.hide(); +} + +function getRepositoryHtml(repository) { + var html = ''; + + html += '
'; + html += ``; + html += ''; + html += ''; + html += '
'; + html += `

${repository.Name}

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

${globalize.translate('HeaderNewRepository')}

`; + html += '
'; + html += ''; + html += '
'; + html += ``; + html += `
${globalize.translate('LabelRepositoryNameHelp')}
`; + html += '
'; + html += '
'; + html += ``; + html += `
${globalize.translate('LabelRepositoryUrlHelp')}
`; + html += '
'; + html += ``; + html += ''; + html += ''; + + dialog.innerHTML = html; + dialog.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dialog); + }); + + dialog.querySelector('.newPluginForm').addEventListener('submit', () => { + repositories.push({ + Name: dialog.querySelector('#txtRepositoryName').value, + Url: dialog.querySelector('#txtRepositoryUrl').value, + Enabled: true + }); + + saveList(view); + dialogHelper.close(dialog); + return false; + }); + + dialogHelper.open(dialog); + }); +} diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 03eeeeb8704..52050d1f802 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -1,5 +1,5 @@ -define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby-button", "emby-select"], function ($, loading, datetime, dom, globalize) { - "use strict"; +define(['jQuery', 'loading', 'datetime', 'dom', 'globalize', 'emby-input', 'emby-button', 'emby-select'], function ($, loading, datetime, dom, globalize) { + 'use strict'; function fillTimeOfDay(select) { @@ -14,7 +14,7 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby select.innerHTML = options.map(function (o) { return ''; - }).join(""); + }).join(''); } Array.prototype.remove = function (from, to) { @@ -26,90 +26,92 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby var ScheduledTaskPage = { refreshScheduledTask: function (view) { loading.show(); - var id = getParameterByName("id"); + var 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); + $('.taskName', view).html(task.Name); + $('#pTaskDescription', view).html(task.Description); - require(["listViewStyle"], function () { + require(['listViewStyle'], function () { ScheduledTaskPage.loadTaskTriggers(view, task); }); loading.hide(); }, loadTaskTriggers: function (context, task) { - var html = ""; + var html = ''; html += '
'; for (var i = 0, length = task.Triggers.length; i < length; i++) { var trigger = task.Triggers[i]; html += '
'; - html += 'schedule'; + html += ''; if (trigger.MaxRuntimeMs) { html += '
'; } else { html += '
'; } - html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + "
"; + html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; if (trigger.MaxRuntimeMs) { html += '
'; var hours = trigger.MaxRuntimeTicks / 36e9; if (hours == 1) { - html += globalize.translate("ValueTimeLimitSingleHour"); + html += globalize.translate('ValueTimeLimitSingleHour'); } else { - html += globalize.translate("ValueTimeLimitMultiHour", hours); + html += globalize.translate('ValueTimeLimitMultiHour', hours); } - html += "
"; + html += '
'; } - html += "
"; - html += ''; - html += "
"; + html += '
'; + html += ''; + html += ''; } - html += ""; - context.querySelector(".taskTriggers").innerHTML = html; + html += ''; + context.querySelector('.taskTriggers').innerHTML = html; }, + // TODO: Replace this mess with date-fns and remove datetime completely getTriggerFriendlyName: function (trigger) { - if ("DailyTrigger" == trigger.Type) { - return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); + if ('DailyTrigger' == trigger.Type) { + return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } - if ("WeeklyTrigger" == trigger.Type) { - return trigger.DayOfWeek + "s at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); + if ('WeeklyTrigger' == trigger.Type) { + // TODO: The day of week isn't localised as well + return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } - if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) { - return "On wake from sleep"; + if ('SystemEventTrigger' == trigger.Type && 'WakeFromSleep' == trigger.SystemEvent) { + return globalize.translate('OnWakeFromSleep'); } - if (trigger.Type == "IntervalTrigger") { + if (trigger.Type == 'IntervalTrigger') { var hours = trigger.IntervalTicks / 36e9; if (hours == 0.25) { - return "Every 15 minutes"; + return globalize.translate('EveryXMinutes', '15'); } if (hours == 0.5) { - return "Every 30 minutes"; + return globalize.translate('EveryXMinutes', '30'); } if (hours == 0.75) { - return "Every 45 minutes"; + return globalize.translate('EveryXMinutes', '45'); } if (hours == 1) { - return "Every hour"; + return globalize.translate('EveryHour'); } - return "Every " + hours + " hours"; + return globalize.translate('EveryXHours', hours); } - if (trigger.Type == "StartupTrigger") { - return "On application startup"; + if (trigger.Type == 'StartupTrigger') { + return globalize.translate('OnApplicationStartup'); } return trigger.Type; @@ -122,20 +124,20 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby return datetime.getDisplayTime(now); }, showAddTriggerPopup: function (view) { - $("#selectTriggerType", view).val("DailyTrigger"); - view.querySelector("#selectTriggerType").dispatchEvent(new CustomEvent("change", {})); - $("#popupAddTrigger", view).removeClass("hide"); + $('#selectTriggerType', view).val('DailyTrigger'); + view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); + $('#popupAddTrigger', view).removeClass('hide'); }, confirmDeleteTrigger: function (view, index) { - require(["confirm"], function (confirm) { - confirm(globalize.translate("MessageDeleteTaskTrigger"), globalize.translate("HeaderDeleteTaskTrigger")).then(function () { + require(['confirm'], function (confirm) { + confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { ScheduledTaskPage.deleteTrigger(view, index); }); }); }, deleteTrigger: function (view, index) { loading.show(); - var id = getParameterByName("id"); + var id = getParameterByName('id'); ApiClient.getScheduledTask(id).then(function (task) { task.Triggers.remove(index); ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { @@ -144,55 +146,55 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby }); }, 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"); + 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) { var trigger = { - Type: $("#selectTriggerType", page).val() + 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(); + 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(); } - var timeLimit = $("#txtTimeLimit", page).val() || "0"; + var timeLimit = $('#txtTimeLimit', page).val() || '0'; timeLimit = parseFloat(timeLimit) * 3600000; trigger.MaxRuntimeMs = timeLimit || null; @@ -203,34 +205,34 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby return function (view, params) { function onSubmit(e) { loading.show(); - var id = getParameterByName("id"); + var 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"); + $('#popupAddTrigger').addClass('hide'); ScheduledTaskPage.refreshScheduledTask(view); }); }); e.preventDefault(); } - view.querySelector(".addTriggerForm").addEventListener("submit", onSubmit); - fillTimeOfDay(view.querySelector("#selectTimeOfDay")); - $(view.querySelector("#popupAddTrigger").parentNode).trigger("create"); - view.querySelector(".selectTriggerType").addEventListener("change", function () { + 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 () { + view.querySelector('.btnAddTrigger').addEventListener('click', function () { ScheduledTaskPage.showAddTriggerPopup(view); }); - view.addEventListener("click", function (e) { - var btnDeleteTrigger = dom.parentWithClass(e.target, "btnDeleteTrigger"); + view.addEventListener('click', function (e) { + var btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); if (btnDeleteTrigger) { - ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute("data-index"))); + ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'))); } }); - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { ScheduledTaskPage.refreshScheduledTask(view); }); }; diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 390fd17353f..5ce53cf6fe7 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -1,5 +1,5 @@ -define(["jQuery", "loading", "events", "globalize", "serverNotifications", "humanedate", "listViewStyle", "emby-button"], function($, loading, events, globalize, serverNotifications) { - "use strict"; +define(['jQuery', 'loading', 'events', 'globalize', 'serverNotifications', 'date-fns', 'dfnshelper', 'listViewStyle', 'emby-button'], function ($, loading, events, globalize, serverNotifications, datefns, dfnshelper) { + 'use strict'; function reloadList(page) { ApiClient.getScheduledTasks({ @@ -7,116 +7,119 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma }).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; + a = a.Category + ' ' + a.Name; + b = b.Category + ' ' + b.Name; return a == b ? 0 : a < b ? -1 : 1; }); var currentCategory; - var html = ""; + var html = ''; for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; if (task.Category != currentCategory) { currentCategory = task.Category; if (currentCategory) { - html += ""; - html += ""; + html += ''; + html += ''; } html += '
'; html += '
'; html += '

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

"; + html += ''; if (i === 0) { - html += '' + globalize.translate("Help") + ""; + html += '' + globalize.translate('Help') + ''; } - html += "
"; + html += '
'; html += '
'; } html += '
'; html += ""; - html += 'schedule'; - html += ""; + html += ''; + html += ''; html += '"; - if (task.State === "Running") { - html += ''; - } else if (task.State === "Idle") { - html += ''; + html += "

" + task.Name + '

'; + html += "
" + getTaskProgressHtml(task) + '
'; + html += ''; + html += '
'; + if (task.State === 'Running') { + html += ''; + } else if (task.State === 'Idle') { + html += ''; } - html += "
"; + html += ''; } if (tasks.length) { - html += ""; - html += ""; + html += ''; + html += ''; } - page.querySelector(".divScheduledTasks").innerHTML = html; + page.querySelector('.divScheduledTasks').innerHTML = html; } function getTaskProgressHtml(task) { - var html = ""; - if (task.State === "Idle") { + var html = ''; + if (task.State === 'Idle') { if (task.LastExecutionResult) { - html += globalize.translate("LabelScheduledTaskLastRan").replace("{0}", humaneDate(task.LastExecutionResult.EndTimeUtc)).replace("{1}", humaneElapsed(task.LastExecutionResult.StartTimeUtc, task.LastExecutionResult.EndTimeUtc)); - 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") + ""; + var endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); + var starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); + html += globalize.translate('LabelScheduledTaskLastRan', datefns.formatDistanceToNow(endtime, dfnshelper.localeWithSuffix), + datefns.formatDistance(starttime, endtime, { locale: dfnshelper.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") { + } else if (task.State === 'Running') { var progress = (task.CurrentProgressPercentage || 0).toFixed(1); html += '
'; html += '
'; html += '
'; - html += "
"; - html += "
"; - html += "" + progress + "%"; - html += "
"; + html += ''; + html += ''; + html += "" + progress + '%'; + html += ''; } else { - html += "" + globalize.translate("LabelStopping") + ""; + html += "" + globalize.translate('LabelStopping') + ''; } return html; } function setTaskButtonIcon(button, icon) { - var inner = button.querySelector("i"); - inner.classList.remove("stop", "play_arrow"); + var 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"); + 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); + $(elem).parents('.listItem')[0].setAttribute('data-status', state); } return function(view, params) { function updateTasks(tasks) { for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; - view.querySelector("#taskProgress" + task.Id).innerHTML = getTaskProgressHtml(task); - updateTaskButton(view.querySelector("#btnTask" + task.Id), task.State); + view.querySelector('#taskProgress' + task.Id).innerHTML = getTaskProgressHtml(task); + updateTaskButton(view.querySelector('#btnTask' + task.Id), task.State); } } @@ -133,47 +136,47 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma } function startInterval() { - ApiClient.sendMessage("ScheduledTasksInfoStart", "1000,1000"); + ApiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); pollInterval && clearInterval(pollInterval); pollInterval = setInterval(onPollIntervalFired, 1e4); } function stopInterval() { - ApiClient.sendMessage("ScheduledTasksInfoStop"); + ApiClient.sendMessage('ScheduledTasksInfoStop'); pollInterval && clearInterval(pollInterval); } var pollInterval; var serverId = ApiClient.serverId(); - $(".divScheduledTasks", view).on("click", ".btnStartTask", function() { + $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { var button = this; - var id = button.getAttribute("data-taskid"); + var id = button.getAttribute('data-taskid'); ApiClient.startScheduledTask(id).then(function() { - updateTaskButton(button, "Running"); + updateTaskButton(button, 'Running'); reloadList(view); - }) + }); }); - $(".divScheduledTasks", view).on("click", ".btnStopTask", function() { + $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { var button = this; - var id = button.getAttribute("data-taskid"); + var id = button.getAttribute('data-taskid'); ApiClient.stopScheduledTask(id).then(function() { - updateTaskButton(button, ""); + updateTaskButton(button, ''); reloadList(view); - }) + }); }); - view.addEventListener("viewbeforehide", function() { - events.off(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + view.addEventListener('viewbeforehide', function() { + events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); stopInterval(); }); - view.addEventListener("viewshow", function() { + view.addEventListener('viewshow', function() { loading.show(); startInterval(); reloadList(view); - events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); }); - } + }; }); diff --git a/src/controllers/dashboard/serveractivity.js b/src/controllers/dashboard/serveractivity.js new file mode 100644 index 00000000000..c48a2903ae5 --- /dev/null +++ b/src/controllers/dashboard/serveractivity.js @@ -0,0 +1,31 @@ +define(['components/activitylog', 'globalize'], function (ActivityLog, globalize) { + 'use strict'; + + return function (view, params) { + var activityLog; + + if (params.useractivity !== 'false') { + 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; + }); + }; +}); diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js new file mode 100644 index 00000000000..37afe5a0547 --- /dev/null +++ b/src/controllers/dashboard/streaming.js @@ -0,0 +1,43 @@ +define(['jQuery', 'libraryMenu', 'loading', 'globalize'], function ($, libraryMenu, loading, globalize) { + 'use strict'; + + function loadPage(page, config) { + $('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || ''); + loading.hide(); + } + + function onSubmit() { + loading.show(); + var form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0')); + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + + return false; + } + + function getTabs() { + return [{ + href: 'encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: 'playbackconfiguration.html', + name: globalize.translate('TabResumeSettings') + }, { + 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); + var page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); + }); +}); diff --git a/src/controllers/dashboard/users/useredit.js b/src/controllers/dashboard/users/useredit.js new file mode 100644 index 00000000000..af187412d04 --- /dev/null +++ b/src/controllers/dashboard/users/useredit.js @@ -0,0 +1,200 @@ +define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu, globalize) { + 'use strict'; + + function loadDeleteFolders(page, user, mediaFolders) { + ApiClient.getJSON(ApiClient.getUrl('Channels', { + SupportsMediaDeletion: true + })).then(function (channelsResult) { + var i; + var length; + var folder; + var isChecked; + var checkedAttribute; + var html = ''; + + for (i = 0, length = mediaFolders.length; i < length; i++) { + folder = mediaFolders[i]; + isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id); + checkedAttribute = isChecked ? ' checked="checked"' : ''; + html += ''; + } + + for (i = 0, length = channelsResult.Items.length; i < length; i++) { + folder = channelsResult.Items[i]; + isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id); + 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'); + } + + var currentProviderId = user.Policy.AuthenticationProviderId; + page.querySelector('.selectLoginProvider').innerHTML = providers.map(function (provider) { + var 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'); + } + + var currentProviderId = user.Policy.PasswordResetProviderId; + page.querySelector('.selectPasswordResetProvider').innerHTML = providers.map(function (provider) { + var selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : ''; + return ''; + }); + } + + function loadUser(page, user) { + currentUser = 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); + $('#chkRemoteControlSharedDevices', page).prop('checked', user.Policy.EnableSharedDeviceControl); + $('#chkEnableRemoteControlOtherUsers', page).prop('checked', user.Policy.EnableRemoteControlOfOtherUsers); + $('#chkEnableDownloading', page).prop('checked', user.Policy.EnableContentDownloading); + $('#chkManageLiveTv', page).prop('checked', user.Policy.EnableLiveTvManagement); + $('#chkEnableLiveTvAccess', page).prop('checked', user.Policy.EnableLiveTvAccess); + $('#chkEnableMediaPlayback', page).prop('checked', user.Policy.EnableMediaPlayback); + $('#chkEnableAudioPlaybackTranscoding', page).prop('checked', user.Policy.EnableAudioPlaybackTranscoding); + $('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding); + $('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing); + $('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding); + $('#chkRemoteAccess', page).prop('checked', null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess); + $('#chkEnableSyncTranscoding', page).prop('checked', user.Policy.EnableSyncTranscoding); + $('#chkEnableConversion', page).prop('checked', user.Policy.EnableMediaConversion || false); + $('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing); + $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); + $('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); + $('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess); + loading.hide(); + } + + function onSaveComplete(page, user) { + Dashboard.navigate('userprofiles.html'); + loading.hide(); + + require(['toast'], function (toast) { + 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.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked'); + user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked'); + user.Policy.EnableSyncTranscoding = $('#chkEnableSyncTranscoding', page).is(':checked'); + user.Policy.EnableMediaConversion = $('#chkEnableConversion', page).is(':checked'); + user.Policy.EnablePublicSharing = $('#chkEnableSharing', page).is(':checked'); + user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked'); + user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0')); + user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0'); + user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value; + user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value; + user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).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'); + }); + user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value; + ApiClient.updateUser(user).then(function () { + ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + onSaveComplete(page, user); + }); + }); + } + + function onSubmit() { + var page = $(this).parents('.page')[0]; + loading.show(); + getUser().then(function (result) { + saveUser(result, page); + }); + return false; + } + + function getUser() { + var userId = getParameterByName('userId'); + return ApiClient.getUser(userId); + } + + function loadData(page) { + loading.show(); + getUser().then(function (user) { + loadUser(page, user); + }); + } + + var currentUser; + $(document).on('pageinit', '#editUserPage', function () { + $('.editUserProfileForm').off('submit', onSubmit).on('submit', onSubmit); + this.querySelector('.sharingHelp').innerHTML = globalize.translate('OptionAllowLinkSharingHelp', 30); + var 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/userlibraryaccess.js b/src/controllers/dashboard/users/userlibraryaccess.js similarity index 53% rename from src/controllers/userlibraryaccess.js rename to src/controllers/dashboard/users/userlibraryaccess.js index 38418f5190a..5ea24e3da3c 100644 --- a/src/controllers/userlibraryaccess.js +++ b/src/controllers/dashboard/users/userlibraryaccess.js @@ -1,79 +1,79 @@ -define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, libraryMenu) { - "use strict"; +define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu, globalize) { + 'use strict'; function triggerChange(select) { - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", false, true); + var evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); select.dispatchEvent(evt); } function loadMediaFolders(page, user, mediaFolders) { - var html = ""; - html += '

' + Globalize.translate("HeaderLibraries") + "

"; + var html = ''; + html += '

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

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

' + Globalize.translate("HeaderChannels") + "

"; + var html = ''; + html += '

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

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

' + Globalize.translate("HeaderDevices") + "

"; + var html = ''; + html += '

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

'; html += '
'; for (var i = 0, length = devices.length; i < length; i++) { var device = devices[i]; - var checkedAttribute = user.Policy.EnableAllDevices || -1 != user.Policy.EnabledDevices.indexOf(device.Id) ? ' checked="checked"' : ""; - html += '"; + var checkedAttribute = user.Policy.EnableAllDevices || -1 != user.Policy.EnabledDevices.indexOf(device.Id) ? ' checked="checked"' : ''; + html += ''; } - html += "
"; - $(".deviceAccess", page).show().html(html); - $("#chkEnableAllDevices", page).checked(user.Policy.EnableAllDevices).trigger("change"); + html += ''; + $('.deviceAccess', page).show().html(html); + $('#chkEnableAllDevices', page).prop('checked', user.Policy.EnableAllDevices); if (user.Policy.IsAdministrator) { - page.querySelector(".deviceAccessContainer").classList.add("hide"); + page.querySelector('.deviceAccessContainer').classList.add('hide'); } else { - page.querySelector(".deviceAccessContainer").classList.remove("hide"); + page.querySelector('.deviceAccessContainer').classList.remove('hide'); } } function loadUser(page, user, loggedInUser, mediaFolders, channels, devices) { - page.querySelector(".username").innerHTML = user.Name; + page.querySelector('.username').innerHTML = user.Name; libraryMenu.setTitle(user.Name); loadChannels(page, user, channels); loadMediaFolders(page, user, mediaFolders); @@ -84,29 +84,29 @@ define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, function onSaveComplete(page) { loading.hide(); - require(["toast"], function (toast) { - toast(Globalize.translate("SettingsSaved")); + require(['toast'], function (toast) { + toast(globalize.translate('SettingsSaved')); }); } function saveUser(user, page) { - user.Policy.EnableAllFolders = $("#chkEnableAllFolders", page).checked(); - user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : $(".chkFolder", page).get().filter(function (c) { + 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"); + return c.getAttribute('data-id'); }); - user.Policy.EnableAllChannels = $("#chkEnableAllChannels", page).checked(); - user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : $(".chkChannel", page).get().filter(function (c) { + 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"); + return c.getAttribute('data-id'); }); - user.Policy.EnableAllDevices = $("#chkEnableAllDevices", page).checked(); - user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $(".chkDevice", page).get().filter(function (c) { + 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"); + return c.getAttribute('data-id'); }); user.Policy.BlockedChannels = null; user.Policy.BlockedMediaFolders = null; @@ -116,44 +116,44 @@ define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, } function onSubmit() { - var page = $(this).parents(".page"); + var page = $(this).parents('.page'); loading.show(); - var userId = getParameterByName("userId"); + var userId = getParameterByName('userId'); ApiClient.getUser(userId).then(function (result) { saveUser(result, page); }); return false; } - $(document).on("pageinit", "#userLibraryAccessPage", function () { + $(document).on('pageinit', '#userLibraryAccessPage', function () { var page = this; - $("#chkEnableAllDevices", page).on("change", function () { + $('#chkEnableAllDevices', page).on('change', function () { if (this.checked) { - $(".deviceAccessListContainer", page).hide(); + $('.deviceAccessListContainer', page).hide(); } else { - $(".deviceAccessListContainer", page).show(); + $('.deviceAccessListContainer', page).show(); } }); - $("#chkEnableAllChannels", page).on("change", function () { + $('#chkEnableAllChannels', page).on('change', function () { if (this.checked) { - $(".channelAccessListContainer", page).hide(); + $('.channelAccessListContainer', page).hide(); } else { - $(".channelAccessListContainer", page).show(); + $('.channelAccessListContainer', page).show(); } }); - page.querySelector("#chkEnableAllFolders").addEventListener("change", function () { + page.querySelector('#chkEnableAllFolders').addEventListener('change', function () { if (this.checked) { - page.querySelector(".folderAccessListContainer").classList.add("hide"); + page.querySelector('.folderAccessListContainer').classList.add('hide'); } else { - page.querySelector(".folderAccessListContainer").classList.remove("hide"); + page.querySelector('.folderAccessListContainer').classList.remove('hide'); } }); - $(".userLibraryAccessForm").off("submit", onSubmit).on("submit", onSubmit); - }).on("pageshow", "#userLibraryAccessPage", function () { + $('.userLibraryAccessForm').off('submit', onSubmit).on('submit', onSubmit); + }).on('pageshow', '#userLibraryAccessPage', function () { var page = this; loading.show(); var promise1; - var userId = getParameterByName("userId"); + var userId = getParameterByName('userId'); if (userId) { promise1 = ApiClient.getUser(userId); @@ -166,11 +166,11 @@ define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, } var promise2 = Dashboard.getCurrentUser(); - var promise4 = ApiClient.getJSON(ApiClient.getUrl("Library/MediaFolders", { + var promise4 = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', { IsHidden: false })); - var promise5 = ApiClient.getJSON(ApiClient.getUrl("Channels")); - var promise6 = ApiClient.getJSON(ApiClient.getUrl("Devices")); + var promise5 = ApiClient.getJSON(ApiClient.getUrl('Channels')); + var 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.js b/src/controllers/dashboard/users/usernew.js new file mode 100644 index 00000000000..ef4cd74f86c --- /dev/null +++ b/src/controllers/dashboard/users/usernew.js @@ -0,0 +1,126 @@ +define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading, globalize) { + 'use strict'; + + function loadMediaFolders(page, mediaFolders) { + var html = ''; + html += '

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

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

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

'; + html += '
'; + + for (var i = 0; i < channels.length; i++) { + var 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(); + var promiseFolders = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', { + IsHidden: false + })); + var 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) { + var user = {}; + user.Name = $('#txtUsername', page).val(); + user.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 (response) { + require(['toast'], function (toast) { + toast(globalize.translate('DefaultErrorMessage')); + }); + + loading.hide(); + }); + } + + function onSubmit() { + var page = $(this).parents('.page')[0]; + loading.show(); + saveUser(page); + return false; + } + + function loadData(page) { + loadUser(page); + } + + $(document).on('pageinit', '#newUserPage', function () { + var 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/userparentalcontrol.js b/src/controllers/dashboard/users/userparentalcontrol.js similarity index 60% rename from src/controllers/userparentalcontrol.js rename to src/controllers/dashboard/users/userparentalcontrol.js index 333c09f266a..e8255512d61 100644 --- a/src/controllers/userparentalcontrol.js +++ b/src/controllers/dashboard/users/userparentalcontrol.js @@ -1,8 +1,8 @@ -define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper-icon-button-light"], function ($, datetime, loading, libraryMenu) { - "use strict"; +define(['jQuery', 'datetime', 'loading', 'libraryMenu', 'globalize', 'listViewStyle', 'paper-icon-button-light'], function ($, datetime, loading, libraryMenu, globalize) { + 'use strict'; function populateRatings(allParentalRatings, page) { - var html = ""; + var html = ''; html += ""; var i; var length; @@ -14,7 +14,7 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- var lastRating = ratings[ratings.length - 1]; if (lastRating.Value === rating.Value) { - lastRating.Name += "/" + rating.Name; + lastRating.Name += '/' + rating.Name; continue; } } @@ -27,56 +27,56 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- for (i = 0, length = ratings.length; i < length; i++) { rating = ratings[i]; - html += ""; + html += "'; } - $("#selectMaxParentalRating", page).html(html); + $('#selectMaxParentalRating', page).html(html); } function loadUnratedItems(page, user) { var items = [{ - name: Globalize.translate("OptionBlockBooks"), - value: "Book" + name: globalize.translate('OptionBlockBooks'), + value: 'Book' }, { - name: Globalize.translate("OptionBlockChannelContent"), - value: "ChannelContent" + name: globalize.translate('OptionBlockChannelContent'), + value: 'ChannelContent' }, { - name: Globalize.translate("OptionBlockLiveTvChannels"), - value: "LiveTvChannel" + name: globalize.translate('OptionBlockLiveTvChannels'), + value: 'LiveTvChannel' }, { - name: Globalize.translate("OptionBlockMovies"), - value: "Movie" + name: globalize.translate('OptionBlockMovies'), + value: 'Movie' }, { - name: Globalize.translate("OptionBlockMusic"), - value: "Music" + name: globalize.translate('OptionBlockMusic'), + value: 'Music' }, { - name: Globalize.translate("OptionBlockTrailers"), - value: "Trailer" + name: globalize.translate('OptionBlockTrailers'), + value: 'Trailer' }, { - name: Globalize.translate("OptionBlockTvShows"), - value: "Series" + name: globalize.translate('OptionBlockTvShows'), + value: 'Series' }]; - var html = ""; - html += '

' + Globalize.translate("HeaderBlockItemsWithNoRating") + "

"; + var html = ''; + html += '

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

'; html += '
'; for (var i = 0, length = items.length; i < length; i++) { var item = items[i]; - var checkedAttribute = -1 != user.Policy.BlockUnratedItems.indexOf(item.value) ? ' checked="checked"' : ""; - html += '"; + var checkedAttribute = -1 != user.Policy.BlockUnratedItems.indexOf(item.value) ? ' checked="checked"' : ''; + html += ''; } - html += "
"; - $(".blockUnratedItems", page).html(html).trigger("create"); + html += ''; + $('.blockUnratedItems', page).html(html).trigger('create'); } function loadUser(page, user, allParentalRatings) { - page.querySelector(".username").innerHTML = user.Name; + page.querySelector('.username').innerHTML = user.Name; libraryMenu.setTitle(user.Name); loadUnratedItems(page, user); loadBlockedTags(page, user.Policy.BlockedTags); populateRatings(allParentalRatings, page); - var ratingValue = ""; + var ratingValue = ''; if (user.Policy.MaxParentalRating) { for (var i = 0, length = allParentalRatings.length; i < length; i++) { @@ -88,12 +88,12 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- } } - $("#selectMaxParentalRating", page).val(ratingValue); + $('#selectMaxParentalRating', page).val(ratingValue); if (user.Policy.IsAdministrator) { - $(".accessScheduleSection", page).hide(); + $('.accessScheduleSection', page).hide(); } else { - $(".accessScheduleSection", page).show(); + $('.accessScheduleSection', page).show(); } renderAccessSchedule(page, user.Policy.AccessSchedules || []); @@ -106,19 +106,19 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- li += '
'; li += '

'; li += h; - li += "

"; - li += "
"; - li += ''; - return li += ""; - }).join(""); + li += ''; + li += ''; + li += ''; + return li += ''; + }).join(''); if (html) { - html = '
' + html + "
"; + html = '
' + html + '
'; } - var elem = $(".blockedTags", page).html(html).trigger("create"); - $(".btnDeleteTag", elem).on("click", function () { - var tag = this.getAttribute("data-tag"); + var elem = $('.blockedTags', page).html(html).trigger('create'); + $('.btnDeleteTag', elem).on('click', function () { + var tag = this.getAttribute('data-tag'); var newTags = tags.filter(function (t) { return t != tag; }); @@ -132,43 +132,43 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- } function renderAccessSchedule(page, schedules) { - var html = ""; + var html = ''; var index = 0; html += schedules.map(function (a) { - var itemHtml = ""; + var itemHtml = ''; itemHtml += '
'; itemHtml += '
'; itemHtml += '

'; - itemHtml += Globalize.translate("Option" + a.DayOfWeek); - itemHtml += "

"; - itemHtml += '
' + getDisplayTime(a.StartHour) + " - " + getDisplayTime(a.EndHour) + "
"; - itemHtml += "
"; - itemHtml += ''; - itemHtml += "
"; + itemHtml += globalize.translate('Option' + a.DayOfWeek); + itemHtml += ''; + itemHtml += '
' + getDisplayTime(a.StartHour) + ' - ' + getDisplayTime(a.EndHour) + '
'; + itemHtml += ''; + itemHtml += ''; + itemHtml += ''; index++; return itemHtml; - }).join(""); - var accessScheduleList = page.querySelector(".accessScheduleList"); + }).join(''); + var accessScheduleList = page.querySelector('.accessScheduleList'); accessScheduleList.innerHTML = html; - $(".btnDelete", accessScheduleList).on("click", function () { - deleteAccessSchedule(page, schedules, parseInt(this.getAttribute("data-index"))); + $('.btnDelete', accessScheduleList).on('click', function () { + deleteAccessSchedule(page, schedules, parseInt(this.getAttribute('data-index'))); }); } function onSaveComplete(page) { loading.hide(); - require(["toast"], function (toast) { - toast(Globalize.translate("SettingsSaved")); + require(['toast'], function (toast) { + toast(globalize.translate('SettingsSaved')); }); } function saveUser(user, page) { - user.Policy.MaxParentalRating = $("#selectMaxParentalRating", page).val() || null; - user.Policy.BlockUnratedItems = $(".chkUnratedItem", page).get().filter(function (i) { + 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"); + return i.getAttribute('data-itemtype'); }); user.Policy.AccessSchedules = getSchedulesFromPage(page); user.Policy.BlockedTags = getBlockedTagsFromPage(page); @@ -191,7 +191,7 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- function showSchedulePopup(page, schedule, index) { schedule = schedule || {}; - require(["components/accessschedule/accessschedule"], function (accessschedule) { + require(['components/accessSchedule/accessSchedule'], function (accessschedule) { accessschedule.show({ schedule: schedule }).then(function (updatedSchedule) { @@ -208,25 +208,25 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- } function getSchedulesFromPage(page) { - return $(".liSchedule", page).map(function () { + return $('.liSchedule', page).map(function () { return { - DayOfWeek: this.getAttribute("data-day"), - StartHour: this.getAttribute("data-start"), - EndHour: this.getAttribute("data-end") + 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"); + return $('.blockedTag', page).map(function () { + return this.getAttribute('data-tag'); }).get(); } function showBlockedTagPopup(page) { - require(["prompt"], function (prompt) { + require(['prompt'], function (prompt) { prompt({ - label: Globalize.translate("LabelTag") + label: globalize.translate('LabelTag') }).then(function (value) { var tags = getBlockedTagsFromPage(page); @@ -240,28 +240,28 @@ define(["jQuery", "datetime", "loading", "libraryMenu", "listViewStyle", "paper- window.UserParentalControlPage = { onSubmit: function () { - var page = $(this).parents(".page"); + var page = $(this).parents('.page'); loading.show(); - var userId = getParameterByName("userId"); + var userId = getParameterByName('userId'); ApiClient.getUser(userId).then(function (result) { saveUser(result, page); }); return false; } }; - $(document).on("pageinit", "#userParentalControlPage", function () { + $(document).on('pageinit', '#userParentalControlPage', function () { var page = this; - $(".btnAddSchedule", page).on("click", function () { + $('.btnAddSchedule', page).on('click', function () { showSchedulePopup(page, {}, -1); }); - $(".btnAddBlockedTag", page).on("click", function () { + $('.btnAddBlockedTag', page).on('click', function () { showBlockedTagPopup(page); }); - $(".userParentalControlForm").off("submit", UserParentalControlPage.onSubmit).on("submit", UserParentalControlPage.onSubmit); - }).on("pageshow", "#userParentalControlPage", function () { + $('.userParentalControlForm').off('submit', UserParentalControlPage.onSubmit).on('submit', UserParentalControlPage.onSubmit); + }).on('pageshow', '#userParentalControlPage', function () { var page = this; loading.show(); - var userId = getParameterByName("userId"); + var userId = getParameterByName('userId'); var promise1 = ApiClient.getUser(userId); var promise2 = ApiClient.getParentalRatings(); Promise.all([promise1, promise2]).then(function (responses) { diff --git a/src/controllers/userpasswordpage.js b/src/controllers/dashboard/users/userpasswordpage.js similarity index 50% rename from src/controllers/userpasswordpage.js rename to src/controllers/dashboard/users/userpasswordpage.js index eeb9b25e3ee..186e39b1514 100644 --- a/src/controllers/userpasswordpage.js +++ b/src/controllers/dashboard/users/userpasswordpage.js @@ -1,67 +1,67 @@ -define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu) { - "use strict"; +define(['loading', 'libraryMenu', 'globalize', 'emby-button'], function (loading, libraryMenu, globalize) { + 'use strict'; function loadUser(page, params) { var userid = params.userId; ApiClient.getUser(userid).then(function (user) { Dashboard.getCurrentUser().then(function (loggedInUser) { libraryMenu.setTitle(user.Name); - page.querySelector(".username").innerHTML = user.Name; + page.querySelector('.username').innerHTML = user.Name; var showPasswordSection = true; var showLocalAccessSection = false; - if ("Guest" == user.ConnectLinkType) { - page.querySelector(".localAccessSection").classList.add("hide"); + if ('Guest' == user.ConnectLinkType) { + page.querySelector('.localAccessSection').classList.add('hide'); showPasswordSection = false; } else if (user.HasConfiguredPassword) { - page.querySelector("#btnResetPassword").classList.remove("hide"); - page.querySelector("#fldCurrentPassword").classList.remove("hide"); + 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"); + 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"); + page.querySelector('.passwordSection').classList.remove('hide'); } else { - page.querySelector(".passwordSection").classList.add("hide"); + page.querySelector('.passwordSection').classList.add('hide'); } if (showLocalAccessSection && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { - page.querySelector(".localAccessSection").classList.remove("hide"); + page.querySelector('.localAccessSection').classList.remove('hide'); } else { - page.querySelector(".localAccessSection").classList.add("hide"); + page.querySelector('.localAccessSection').classList.add('hide'); } - var txtEasyPassword = page.querySelector("#txtEasyPassword"); - txtEasyPassword.value = ""; + var txtEasyPassword = page.querySelector('#txtEasyPassword'); + txtEasyPassword.value = ''; if (user.HasConfiguredEasyPassword) { - txtEasyPassword.placeholder = "******"; - page.querySelector("#btnResetEasyPassword").classList.remove("hide"); + txtEasyPassword.placeholder = '******'; + page.querySelector('#btnResetEasyPassword').classList.remove('hide'); } else { - txtEasyPassword.removeAttribute("placeholder"); - txtEasyPassword.placeholder = ""; - page.querySelector("#btnResetEasyPassword").classList.add("hide"); + txtEasyPassword.removeAttribute('placeholder'); + txtEasyPassword.placeholder = ''; + page.querySelector('#btnResetEasyPassword').classList.add('hide'); } - page.querySelector(".chkEnableLocalEasyPassword").checked = user.Configuration.EnableLocalPassword; + page.querySelector('.chkEnableLocalEasyPassword').checked = user.Configuration.EnableLocalPassword; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); }); - page.querySelector("#txtCurrentPassword").value = ""; - page.querySelector("#txtNewPassword").value = ""; - page.querySelector("#txtNewPasswordConfirm").value = ""; + page.querySelector('#txtCurrentPassword').value = ''; + page.querySelector('#txtNewPassword').value = ''; + page.querySelector('#txtNewPasswordConfirm').value = ''; } return function (view, params) { function saveEasyPassword() { var userId = params.userId; - var easyPassword = view.querySelector("#txtEasyPassword").value; + var easyPassword = view.querySelector('#txtEasyPassword').value; if (easyPassword) { ApiClient.updateEasyPassword(userId, easyPassword).then(function () { @@ -74,12 +74,12 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu function onEasyPasswordSaved(userId) { ApiClient.getUser(userId).then(function (user) { - user.Configuration.EnableLocalPassword = view.querySelector(".chkEnableLocalEasyPassword").checked; + user.Configuration.EnableLocalPassword = view.querySelector('.chkEnableLocalEasyPassword').checked; ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () { loading.hide(); - require(["toast"], function (toast) { - toast(Globalize.translate("MessageSettingsSaved")); + require(['toast'], function (toast) { + toast(globalize.translate('MessageSettingsSaved')); }); loadUser(view, params); @@ -89,28 +89,28 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu function savePassword() { var userId = params.userId; - var currentPassword = view.querySelector("#txtCurrentPassword").value; - var newPassword = view.querySelector("#txtNewPassword").value; + var currentPassword = view.querySelector('#txtCurrentPassword').value; + var newPassword = view.querySelector('#txtNewPassword').value; - if (view.querySelector("#fldCurrentPassword").classList.contains("hide")) { + 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 = ""; + currentPassword = ''; } ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () { loading.hide(); - require(["toast"], function (toast) { - toast(Globalize.translate("PasswordSaved")); + require(['toast'], function (toast) { + toast(globalize.translate('PasswordSaved')); }); loadUser(view, params); }, function () { loading.hide(); Dashboard.alert({ - title: Globalize.translate("HeaderLoginFailure"), - message: Globalize.translate("MessageInvalidUser") + title: globalize.translate('HeaderLoginFailure'), + message: globalize.translate('MessageInvalidUser') }); }); } @@ -118,9 +118,9 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu function onSubmit(e) { var form = this; - if (form.querySelector("#txtNewPassword").value != form.querySelector("#txtNewPasswordConfirm").value) { - require(["toast"], function (toast) { - toast(Globalize.translate("PasswordMatchError")); + if (form.querySelector('#txtNewPassword').value != form.querySelector('#txtNewPasswordConfirm').value) { + require(['toast'], function (toast) { + toast(globalize.translate('PasswordMatchError')); }); } else { loading.show(); @@ -139,17 +139,17 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu } function resetPassword() { - var msg = Globalize.translate("PasswordResetConfirmation"); + var msg = globalize.translate('PasswordResetConfirmation'); - require(["confirm"], function (confirm) { - confirm(msg, Globalize.translate("PasswordResetHeader")).then(function () { + require(['confirm'], function (confirm) { + confirm(msg, globalize.translate('PasswordResetHeader')).then(function () { var userId = params.userId; loading.show(); ApiClient.resetUserPassword(userId).then(function () { loading.hide(); Dashboard.alert({ - message: Globalize.translate("PasswordResetComplete"), - title: Globalize.translate("PasswordResetHeader") + message: globalize.translate('PasswordResetComplete'), + title: globalize.translate('PasswordResetHeader') }); loadUser(view, params); }); @@ -158,17 +158,17 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu } function resetEasyPassword() { - var msg = Globalize.translate("PinCodeResetConfirmation"); + var msg = globalize.translate('PinCodeResetConfirmation'); - require(["confirm"], function (confirm) { - confirm(msg, Globalize.translate("HeaderPinCodeReset")).then(function () { + require(['confirm'], function (confirm) { + confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () { var userId = params.userId; loading.show(); ApiClient.resetEasyPassword(userId).then(function () { loading.hide(); Dashboard.alert({ - message: Globalize.translate("PinCodeResetComplete"), - title: Globalize.translate("HeaderPinCodeReset") + message: globalize.translate('PinCodeResetComplete'), + title: globalize.translate('HeaderPinCodeReset') }); loadUser(view, params); }); @@ -176,11 +176,11 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu }); } - 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 () { + 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/userprofilespage.js b/src/controllers/dashboard/users/userprofilespage.js similarity index 53% rename from src/controllers/userprofilespage.js rename to src/controllers/dashboard/users/userprofilespage.js index 2a2387ab607..c691c665f6d 100644 --- a/src/controllers/userprofilespage.js +++ b/src/controllers/dashboard/users/userprofilespage.js @@ -1,15 +1,15 @@ -define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize) { - "use strict"; +define(['loading', 'dom', 'globalize', 'date-fns', 'dfnshelper', 'paper-icon-button-light', 'cardStyle', 'emby-button', 'indicators', 'flexStyles'], function (loading, dom, globalize, datefns, dfnshelper) { + 'use strict'; function deleteUser(page, id) { - var msg = globalize.translate("DeleteUserConfirmation"); + var msg = globalize.translate('DeleteUserConfirmation'); - require(["confirm"], function (confirm) { + require(['confirm'], function (confirm) { confirm({ - title: globalize.translate("DeleteUser"), + title: globalize.translate('DeleteUser'), text: msg, - confirmText: globalize.translate("ButtonDelete"), - primary: "delete" + confirmText: globalize.translate('ButtonDelete'), + primary: 'delete' }).then(function () { loading.show(); ApiClient.deleteUser(id).then(function () { @@ -20,50 +20,50 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", } function showUserMenu(elem) { - var card = dom.parentWithClass(elem, "card"); - var page = dom.parentWithClass(card, "page"); - var userId = card.getAttribute("data-userid"); + var card = dom.parentWithClass(elem, 'card'); + var page = dom.parentWithClass(card, 'page'); + var userId = card.getAttribute('data-userid'); var menuItems = []; menuItems.push({ - name: globalize.translate("ButtonOpen"), - id: "open", - icon: "mode_edit" + name: globalize.translate('ButtonOpen'), + id: 'open', + icon: 'mode_edit' }); menuItems.push({ - name: globalize.translate("ButtonLibraryAccess"), - id: "access", - icon: "lock" + name: globalize.translate('ButtonLibraryAccess'), + id: 'access', + icon: 'lock' }); menuItems.push({ - name: globalize.translate("ButtonParentalControl"), - id: "parentalcontrol", - icon: "person" + name: globalize.translate('ButtonParentalControl'), + id: 'parentalcontrol', + icon: 'person' }); menuItems.push({ - name: globalize.translate("ButtonDelete"), - id: "delete", - icon: "delete" + name: globalize.translate('ButtonDelete'), + id: 'delete', + icon: 'delete' }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: card, callback: function (id) { switch (id) { - case "open": - Dashboard.navigate("useredit.html?userId=" + userId); + case 'open': + Dashboard.navigate('useredit.html?userId=' + userId); break; - case "access": - Dashboard.navigate("userlibraryaccess.html?userId=" + userId); + case 'access': + Dashboard.navigate('userlibraryaccess.html?userId=' + userId); break; - case "parentalcontrol": - Dashboard.navigate("userparentalcontrol.html?userId=" + userId); + case 'parentalcontrol': + Dashboard.navigate('userparentalcontrol.html?userId=' + userId); break; - case "delete": + case 'delete': deleteUser(page, userId); } } @@ -72,11 +72,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", } function getUserHtml(user, addConnectIndicator) { - var html = ""; - var cssClass = "card squareCard scalableCard squareCard-scalable"; + var html = ''; + var cssClass = 'card squareCard scalableCard squareCard-scalable'; if (user.Policy.IsDisabled) { - cssClass += " grayscale"; + cssClass += ' grayscale'; } html += "
"; @@ -90,78 +90,79 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", imgUrl = ApiClient.getUserImageUrl(user.Id, { width: 300, tag: user.PrimaryImageTag, - type: "Primary" + type: 'Primary' }); } - var imageClass = "cardImage"; + var imageClass = 'cardImage'; if (user.Policy.IsDisabled) { - imageClass += " disabledUser"; + imageClass += ' disabledUser'; } if (imgUrl) { html += '
"; } else { html += '
'; - html += 'person'; + html += ''; } - html += "
"; - html += ""; - html += "
"; + html += '
'; + html += ''; + html += ''; html += '
'; html += '
'; html += '
'; html += user.Name; - html += "
"; - html += ''; - html += "
"; + html += '
'; + html += ''; + html += ''; html += '
'; var lastSeen = getLastSeenText(user.LastActivityDate); - html += "" != lastSeen ? lastSeen : " "; - html += "
"; - html += ""; - html += ""; - return html + ""; + 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) { if (lastActivityDate) { - return "Last seen " + humaneDate(lastActivityDate); + return globalize.translate('LastSeen', datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix)); } - return ""; + return ''; } function getUserSectionHtml(users, addConnectIndicator) { return users.map(function (u__q) { return getUserHtml(u__q, addConnectIndicator); - }).join(""); + }).join(''); } function renderUsers(page, users) { - page.querySelector(".localUsers").innerHTML = getUserSectionHtml(users, true); + page.querySelector('.localUsers').innerHTML = getUserSectionHtml(users, true); } function showPendingUserMenu(elem) { var menuItems = []; menuItems.push({ - name: globalize.translate("ButtonCancel"), - id: "delete", - icon: "delete" + name: globalize.translate('ButtonCancel'), + id: 'delete', + icon: 'delete' }); - require(["actionsheet"], function (actionsheet) { - var card = dom.parentWithClass(elem, "card"); - var page = dom.parentWithClass(card, "page"); - var id = card.getAttribute("data-id"); + require(['actionsheet'], function (actionsheet) { + var card = dom.parentWithClass(elem, 'card'); + var page = dom.parentWithClass(card, 'page'); + var id = card.getAttribute('data-id'); actionsheet.show({ items: menuItems, positionTo: card, callback: function (menuItemId) { switch (menuItemId) { - case "delete": + case 'delete': cancelAuthorization(page, id); } } @@ -170,7 +171,7 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", } function getPendingUserHtml(user) { - var html = ""; + var html = ''; html += "
"; html += '
'; html += '
'; @@ -179,41 +180,41 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", if (user.ImageUrl) { html += '
"; - html += "
"; + html += '
'; } else { - html += 'person'; + html += ''; } - html += ""; - html += "
"; + html += ''; + html += '
'; html += '
'; html += '
'; - html += ''; - html += "
"; + html += ''; + html += '
'; html += '
'; html += user.UserName; - html += "
"; - html += ""; - html += ""; - return html + ""; + html += ''; + html += ''; + html += ''; + return html + ''; } function renderPendingGuests(page, users) { if (users.length) { - page.querySelector(".sectionPendingGuests").classList.remove("hide"); + page.querySelector('.sectionPendingGuests').classList.remove('hide'); } else { - page.querySelector(".sectionPendingGuests").classList.add("hide"); + page.querySelector('.sectionPendingGuests').classList.add('hide'); } - page.querySelector(".pending").innerHTML = users.map(getPendingUserHtml).join(""); + page.querySelector('.pending').innerHTML = users.map(getPendingUserHtml).join(''); } // TODO cvium: maybe reuse for invitation system function cancelAuthorization(page, id) { loading.show(); ApiClient.ajax({ - type: "DELETE", - url: ApiClient.getUrl("Connect/Pending", { + type: 'DELETE', + url: ApiClient.getUrl('Connect/Pending', { Id: id }) }).then(function () { @@ -235,34 +236,34 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", } function showInvitePopup(page) { - require(["components/guestinviter/guestinviter"], function (guestinviter) { + require(['components/guestinviter/guestinviter'], function (guestinviter) { guestinviter.show().then(function () { loadData(page); }); }); } - pageIdOn("pageinit", "userProfilesPage", function () { + pageIdOn('pageinit', 'userProfilesPage', function () { var page = this; - page.querySelector(".btnAddUser").addEventListener("click", function() { - Dashboard.navigate("usernew.html"); + page.querySelector('.btnAddUser').addEventListener('click', function() { + Dashboard.navigate('usernew.html'); }); - page.querySelector(".localUsers").addEventListener("click", function (e__e) { - var btnUserMenu = dom.parentWithClass(e__e.target, "btnUserMenu"); + page.querySelector('.localUsers').addEventListener('click', function (e__e) { + var btnUserMenu = dom.parentWithClass(e__e.target, 'btnUserMenu'); if (btnUserMenu) { showUserMenu(btnUserMenu); } }); - page.querySelector(".pending").addEventListener("click", function (e__r) { - var btnUserMenu = dom.parentWithClass(e__r.target, "btnUserMenu"); + page.querySelector('.pending').addEventListener('click', function (e__r) { + var btnUserMenu = dom.parentWithClass(e__r.target, 'btnUserMenu'); if (btnUserMenu) { showPendingUserMenu(btnUserMenu); } }); }); - pageIdOn("pagebeforeshow", "userProfilesPage", function () { + pageIdOn('pagebeforeshow', 'userProfilesPage', function () { loadData(this); }); }); diff --git a/src/controllers/device.js b/src/controllers/device.js deleted file mode 100644 index cfe7efbe73e..00000000000 --- a/src/controllers/device.js +++ /dev/null @@ -1,50 +0,0 @@ -define(["loading", "libraryMenu", "dom", "emby-input", "emby-button"], function (loading, libraryMenu, dom) { - "use strict"; - - function load(page, device, deviceOptions) { - page.querySelector("#txtCustomName", page).value = deviceOptions.CustomName || ""; - page.querySelector(".reportedName", page).innerHTML = device.Name || ""; - } - - function loadData() { - var page = this; - loading.show(); - var id = getParameterByName("id"); - var promise1 = ApiClient.getJSON(ApiClient.getUrl("Devices/Info", { - Id: id - })); - var 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 save(page) { - var 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) { - var form = this; - save(dom.parentWithClass(form, "page")); - e.preventDefault(); - return false; - } - - return function (view, params) { - view.querySelector("form").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", loadData); - }; -}); diff --git a/src/controllers/dlnaprofile.js b/src/controllers/dlnaprofile.js deleted file mode 100644 index fb4cdb425eb..00000000000 --- a/src/controllers/dlnaprofile.js +++ /dev/null @@ -1,829 +0,0 @@ -define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-input", "emby-checkbox", "listViewStyle", "emby-button"], function ($, loading) { - "use strict"; - - function loadProfile(page) { - loading.show(); - var promise1 = getProfile(); - var promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - currentProfile = responses[0]; - renderProfile(page, currentProfile, responses[1]); - loading.hide(); - }); - } - - function getProfile() { - var id = getParameterByName("id"); - var 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 = -1 != (profile.SupportedMediaTypes || "").split(",").indexOf(this.getAttribute("data-value")); - }); - $("#chkEnableAlbumArtInDidl", page).checked(profile.EnableAlbumArtInDidl); - $("#chkEnableSingleImageLimit", page).checked(profile.EnableSingleAlbumArtLimit); - renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); - var 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).checked(profile.IgnoreTranscodeByteRangeRequests); - $("#txtMaxAllowedBitrate", page).val(profile.MaxStreamingBitrate || ""); - $("#txtMusicStreamingTranscodingBitrate", page).val(profile.MusicStreamingTranscodingBitrate || ""); - $("#chkRequiresPlainFolders", page).checked(profile.RequiresPlainFolders); - $("#chkRequiresPlainVideoItems", page).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 || []; - var usersHtml = "" + users.map(function (u) { - return '"; - }).join(""); - $("#selectUser", page).html(usersHtml).val(profile.UserId || ""); - renderSubProfiles(page, profile); - } - - function renderIdentificationHeaders(page, headers) { - var index = 0; - var html = '
' + headers.map(function (h) { - var li = '
'; - li += 'info'; - li += '
'; - li += '

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

"; - li += '
' + (h.Match || "") + "
"; - li += "
"; - li += ''; - li += "
"; - index++; - return li; - }).join("") + "
"; - var elem = $(".httpHeaderIdentificationList", page).html(html).trigger("create"); - $(".btnDeleteIdentificationHeader", elem).on("click", function () { - var itemIndex = parseInt(this.getAttribute("data-index")); - 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 = null == header; - header = header || {}; - currentSubProfile = header; - var 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) { - var html = '
' + attribute.map(function (h) { - var li = '
'; - li += 'info'; - li += '
'; - li += '

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

"; - li += "
"; - li += ''; - return li += "
"; - }).join("") + "
"; - var elem = $(".xmlDocumentAttributeList", page).html(html).trigger("create"); - $(".btnDeleteXmlAttribute", elem).on("click", function () { - var itemIndex = parseInt(this.getAttribute("data-index")); - currentProfile.XmlRootAttributes.splice(itemIndex, 1); - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - }); - } - - function editXmlDocumentAttribute(page, attribute) { - isSubProfileNew = null == attribute; - attribute = attribute || {}; - currentSubProfile = attribute; - var 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) { - var index = 0; - var html = '
' + profiles.map(function (h) { - var li = '
'; - li += 'info'; - li += '
'; - li += '

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

"; - li += "
"; - li += ''; - li += "
"; - index++; - return li; - }).join("") + "
"; - var elem = $(".subtitleProfileList", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var itemIndex = parseInt(this.getAttribute("data-index")); - currentProfile.SubtitleProfiles.splice(itemIndex, 1); - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var itemIndex = parseInt(this.getAttribute("data-index")); - editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); - }); - } - - function editSubtitleProfile(page, profile) { - isSubProfileNew = null == profile; - profile = profile || {}; - currentSubProfile = profile; - var 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) { - var html = ""; - html += '"; - var elem = $(".directPlayProfiles", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var index = this.getAttribute("data-profileindex"); - deleteDirectPlayProfile(page, index); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var index = parseInt(this.getAttribute("data-profileindex")); - editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); - }); - } - - function deleteDirectPlayProfile(page, index) { - currentProfile.DirectPlayProfiles.splice(index, 1); - renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); - } - - function editDirectPlayProfile(page, directPlayProfile) { - isSubProfileNew = null == directPlayProfile; - directPlayProfile = directPlayProfile || {}; - currentSubProfile = directPlayProfile; - var 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) { - var html = ""; - html += '"; - var elem = $(".transcodingProfiles", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var index = this.getAttribute("data-profileindex"); - deleteTranscodingProfile(page, index); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var index = parseInt(this.getAttribute("data-profileindex")); - editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); - }); - } - - function editTranscodingProfile(page, transcodingProfile) { - isSubProfileNew = null == transcodingProfile; - transcodingProfile = transcodingProfile || {}; - currentSubProfile = transcodingProfile; - var 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).checked(transcodingProfile.EnableMpegtsM2TsMode || false); - $("#chkEstimateContentLength", popup).checked(transcodingProfile.EstimateContentLength || false); - $("#chkReportByteRangeRequests", popup).checked("Bytes" == transcodingProfile.TranscodeSeekInfo); - $(".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).checked(); - currentSubProfile.EstimateContentLength = $("#chkEstimateContentLength", page).checked(); - currentSubProfile.TranscodeSeekInfo = $("#chkReportByteRangeRequests", page).checked() ? "Bytes" : "Auto"; - - if (isSubProfileNew) { - currentProfile.TranscodingProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($("#transcodingProfilePopup", page)[0]); - } - - function renderContainerProfiles(page, profiles) { - var html = ""; - html += '"; - var elem = $(".containerProfiles", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var index = this.getAttribute("data-profileindex"); - deleteContainerProfile(page, index); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var index = parseInt(this.getAttribute("data-profileindex")); - editContainerProfile(page, currentProfile.ContainerProfiles[index]); - }); - } - - function deleteContainerProfile(page, index) { - currentProfile.ContainerProfiles.splice(index, 1); - renderContainerProfiles(page, currentProfile.ContainerProfiles); - } - - function editContainerProfile(page, containerProfile) { - isSubProfileNew = null == containerProfile; - containerProfile = containerProfile || {}; - currentSubProfile = containerProfile; - var 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) { - var html = ""; - html += '"; - var elem = $(".codecProfiles", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var index = this.getAttribute("data-profileindex"); - deleteCodecProfile(page, index); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var index = parseInt(this.getAttribute("data-profileindex")); - editCodecProfile(page, currentProfile.CodecProfiles[index]); - }); - } - - function deleteCodecProfile(page, index) { - currentProfile.CodecProfiles.splice(index, 1); - renderCodecProfiles(page, currentProfile.CodecProfiles); - } - - function editCodecProfile(page, codecProfile) { - isSubProfileNew = null == codecProfile; - codecProfile = codecProfile || {}; - currentSubProfile = codecProfile; - var 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) { - var html = ""; - html += '"; - var elem = $(".mediaProfiles", page).html(html).trigger("create"); - $(".btnDeleteProfile", elem).on("click", function () { - var index = this.getAttribute("data-profileindex"); - deleteResponseProfile(page, index); - }); - $(".lnkEditSubProfile", elem).on("click", function () { - var index = parseInt(this.getAttribute("data-profileindex")); - editResponseProfile(page, currentProfile.ResponseProfiles[index]); - }); - } - - function deleteResponseProfile(page, index) { - currentProfile.ResponseProfiles.splice(index, 1); - renderResponseProfiles(page, currentProfile.ResponseProfiles); - } - - function editResponseProfile(page, responseProfile) { - isSubProfileNew = null == responseProfile; - responseProfile = responseProfile || {}; - currentSubProfile = responseProfile; - var 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); - var id = getParameterByName("id"); - - if (id) { - ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("Dlna/Profiles/" + id), - data: JSON.stringify(profile), - contentType: "application/json" - }).then(function () { - require(["toast"], function (toast) { - toast("Settings saved."); - }); - }, 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).checked(); - profile.EnableSingleAlbumArtLimit = $("#chkEnableSingleImageLimit", page).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).checked(); - profile.RequiresPlainVideoItems = $("#chkRequiresPlainVideoItems", page).checked(); - profile.IgnoreTranscodeByteRangeRequests = $("#chkIgnoreTranscodeByteRangeRequests", page).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(); - } - - var currentProfile; - var currentSubProfile; - var isSubProfileNew; - var allText = Globalize.translate("LabelAll"); - - $(document).on("pageinit", "#dlnaProfilePage", function () { - var page = this; - $(".radioTabButton", page).on("click", function () { - $(this).siblings().removeClass("ui-btn-active"); - $(this).addClass("ui-btn-active"); - var value = "A" == this.tagName ? this.getAttribute("data-value") : this.value; - var elem = $("." + value, page); - elem.siblings(".tabContent").hide(); - elem.show(); - }); - $("#selectDirectPlayProfileType", page).on("change", function () { - if ("Video" == this.value) { - $("#fldDirectPlayVideoCodec", page).show(); - } else { - $("#fldDirectPlayVideoCodec", page).hide(); - } - - if ("Photo" == this.value) { - $("#fldDirectPlayAudioCodec", page).hide(); - } else { - $("#fldDirectPlayAudioCodec", page).show(); - } - }); - $("#selectTranscodingProfileType", page).on("change", function () { - if ("Video" == this.value) { - $("#fldTranscodingVideoCodec", page).show(); - $("#fldTranscodingProtocol", page).show(); - $("#fldEnableMpegtsM2TsMode", page).show(); - } else { - $("#fldTranscodingVideoCodec", page).hide(); - $("#fldTranscodingProtocol", page).hide(); - $("#fldEnableMpegtsM2TsMode", page).hide(); - } - - if ("Photo" == this.value) { - $("#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 ("Video" == this.value) { - $("#fldResponseProfileVideoCodec", page).show(); - } else { - $("#fldResponseProfileVideoCodec", page).hide(); - } - - if ("Photo" == this.value) { - $("#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 () { - var 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; - } - }; -}); diff --git a/src/controllers/dlnaprofiles.js b/src/controllers/dlnaprofiles.js deleted file mode 100644 index e1719ea5978..00000000000 --- a/src/controllers/dlnaprofiles.js +++ /dev/null @@ -1,89 +0,0 @@ -define(["jQuery", "globalize", "loading", "libraryMenu", "listViewStyle", "emby-button"], function ($, globalize, loading, libraryMenu) { - "use strict"; - - 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 "User" == p.Type; - })); - } - - function renderSystemProfiles(page, profiles) { - renderProfiles(page, page.querySelector(".systemProfiles"), profiles.filter(function (p) { - return "System" == p.Type; - })); - } - - function renderProfiles(page, element, profiles) { - var html = ""; - - if (profiles.length) { - html += '
'; - } - - for (var i = 0, length = profiles.length; i < length; i++) { - var profile = profiles[i]; - html += '
'; - html += ''; - html += '"; - - if ("User" == profile.Type) { - html += ''; - } - - html += "
"; - } - - if (profiles.length) { - html += "
"; - } - - element.innerHTML = html; - $(".btnDeleteProfile", element).on("click", function () { - var id = this.getAttribute("data-profileid"); - deleteProfile(page, id); - }); - } - - function deleteProfile(page, id) { - require(["confirm"], function (confirm) { - 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("TabSettings") - }, { - href: "dlnaprofiles.html", - name: globalize.translate("TabProfiles") - }]; - } - - $(document).on("pageshow", "#dlnaProfilesPage", function () { - libraryMenu.setTabs("dlna", 1, getTabs); - loadProfiles(this); - }); -}); diff --git a/src/controllers/dlnasettings.js b/src/controllers/dlnasettings.js deleted file mode 100644 index fbb3af12053..00000000000 --- a/src/controllers/dlnasettings.js +++ /dev/null @@ -1,56 +0,0 @@ -define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, libraryMenu) { - "use strict"; - - function loadPage(page, config, users) { - page.querySelector("#chkEnablePlayTo").checked = config.EnablePlayTo; - page.querySelector("#chkEnableDlnaDebugLogging").checked = config.EnableDebugLog; - $("#txtClientDiscoveryInterval", page).val(config.ClientDiscoveryIntervalSeconds); - $("#chkEnableServer", page).checked(config.EnableServer); - $("#chkBlastAliveMessages", page).checked(config.BlastAliveMessages); - $("#txtBlastInterval", page).val(config.BlastAliveMessageIntervalSeconds); - var usersHtml = users.map(function (u) { - return '"; - }).join(""); - $("#selectUser", page).html(usersHtml).val(config.DefaultUserId || ""); - loading.hide(); - } - - function onSubmit() { - loading.show(); - var 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).checked(); - config.BlastAliveMessages = $("#chkBlastAliveMessages", form).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("TabSettings") - }, { - 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(); - var page = this; - var promise1 = ApiClient.getNamedConfiguration("dlna"); - var promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[0], responses[1]); - }); - }); -}); diff --git a/src/controllers/edititemmetadata.js b/src/controllers/edititemmetadata.js index aba741d64ce..2bfc5e560d8 100644 --- a/src/controllers/edititemmetadata.js +++ b/src/controllers/edititemmetadata.js @@ -1,25 +1,25 @@ -define(["loading", "scripts/editorsidebar"], function (loading) { - "use strict"; +define(['loading', 'scripts/editorsidebar'], function (loading) { + 'use strict'; function reload(context, itemId) { loading.show(); if (itemId) { - require(["metadataEditor"], function (metadataEditor) { - metadataEditor.embed(context.querySelector(".editPageInnerContent"), itemId, ApiClient.serverInfo().Id); + require(['metadataEditor'], function (metadataEditor) { + metadataEditor.embed(context.querySelector('.editPageInnerContent'), itemId, ApiClient.serverInfo().Id); }); } else { - context.querySelector(".editPageInnerContent").innerHTML = ""; + context.querySelector('.editPageInnerContent').innerHTML = ''; loading.hide(); } } return function (view, params) { - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { reload(this, MetadataEditor.getCurrentItemId()); }); MetadataEditor.setCurrentItemId(null); - view.querySelector(".libraryTree").addEventListener("itemclicked", function (event) { + view.querySelector('.libraryTree').addEventListener('itemclicked', function (event) { var data = event.detail; if (data.id != MetadataEditor.getCurrentItemId()) { diff --git a/src/controllers/encodingsettings.js b/src/controllers/encodingsettings.js deleted file mode 100644 index ff140ed539e..00000000000 --- a/src/controllers/encodingsettings.js +++ /dev/null @@ -1,189 +0,0 @@ -define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, loading, globalize, dom, libraryMenu) { - "use strict"; - - function loadPage(page, config, systemInfo) { - Array.prototype.forEach.call(page.querySelectorAll(".chkDecodeCodec"), function (c) { - c.checked = -1 !== (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute("data-codec")); - }); - page.querySelector("#chkHardwareEncoding").checked = config.EnableHardwareEncoding; - $("#selectVideoDecoder", page).val(config.HardwareAccelerationType); - $("#selectThreadCount", page).val(config.EncodingThreadCount); - $("#txtDownMixAudioBoost", page).val(config.DownMixAudioBoost); - page.querySelector(".txtEncoderPath").value = config.EncoderAppPathDisplay || ""; - $("#txtTranscodingTempPath", page).val(systemInfo.TranscodingTempPath || ""); - $("#txtVaapiDevice", page).val(config.VaapiDevice || ""); - page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || ""; - page.querySelector("#txtH264Crf").value = config.H264Crf || ""; - 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(response) { - loading.hide(); - var msg = ""; - msg = globalize.translate("FFmpegSavePathNotFound"); - - require(["alert"], function (alert) { - alert(msg); - }); - } - - function updateEncoder(form) { - return ApiClient.getSystemInfo().then(function (systemInfo) { - return ApiClient.ajax({ - url: ApiClient.getUrl("System/MediaEncoder/Path"), - type: "POST", - data: { - Path: form.querySelector(".txtEncoderPath").value, - PathType: "Custom" - } - }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); - }); - } - - function onSubmit() { - var form = this; - - var onDecoderConfirmed = function () { - loading.show(); - ApiClient.getNamedConfiguration("encoding").then(function (config) { - config.DownMixAudioBoost = $("#txtDownMixAudioBoost", form).val(); - config.TranscodingTempPath = $("#txtTranscodingTempPath", form).val(); - config.EncodingThreadCount = $("#selectThreadCount", form).val(); - config.HardwareAccelerationType = $("#selectVideoDecoder", form).val(); - config.VaapiDevice = $("#txtVaapiDevice", form).val(); - config.EncoderPreset = form.querySelector("#selectEncoderPreset").value; - config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0"); - 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.EnableHardwareEncoding = form.querySelector("#chkHardwareEncoding").checked; - ApiClient.updateNamedConfiguration("encoding", config).then(function () { - updateEncoder(form); - }, function () { - require(["alert"], function (alert) { - alert(globalize.translate("DefaultErrorMessage")); - }); - - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - }; - - if ($("#selectVideoDecoder", form).val()) { - require(["alert"], function (alert) { - alert({ - title: globalize.translate("TitleHardwareAcceleration"), - text: globalize.translate("HardwareAccelerationWarning") - }).then(onDecoderConfirmed); - }); - } else { - onDecoderConfirmed(); - } - - return false; - } - - function setDecodingCodecsVisible(context, value) { - value = value || ""; - var any; - Array.prototype.forEach.call(context.querySelectorAll(".chkDecodeCodec"), function (c) { - if (-1 === c.getAttribute("data-types").split(",").indexOf(value)) { - 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("TabResumeSettings") - }, { - href: "streamingsettings.html", - name: Globalize.translate("TabStreaming") - }]; - } - - $(document).on("pageinit", "#encodingSettingsPage", function () { - var page = this; - page.querySelector("#selectVideoDecoder").addEventListener("change", function () { - if ("vaapi" == this.value) { - 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) { - page.querySelector(".hardwareAccelerationOptions").classList.remove("hide"); - } else { - page.querySelector(".hardwareAccelerationOptions").classList.add("hide"); - } - - setDecodingCodecsVisible(page, this.value); - }); - $("#btnSelectEncoderPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { - var picker = new directoryBrowser(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - $(".txtEncoderPath", page).val(path); - } - - picker.close(); - } - }); - }); - }); - $("#btnSelectTranscodingTempPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { - var 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") - }); - }); - }); - $(".encodingSettingsForm").off("submit", onSubmit).on("submit", onSubmit); - }).on("pageshow", "#encodingSettingsPage", function () { - loading.show(); - libraryMenu.setTabs("playback", 0, getTabs); - var page = this; - ApiClient.getNamedConfiguration("encoding").then(function (config) { - ApiClient.getSystemInfo().then(function (systemInfo) { - loadPage(page, config, systemInfo); - }); - }); - }); -}); diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js index c06b0bd8fbd..b4c7936239f 100644 --- a/src/controllers/favorites.js +++ b/src/controllers/favorites.js @@ -1,26 +1,26 @@ -define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "apphost", "layoutManager", "focusManager", "emby-itemscontainer", "emby-scroller"], function (appRouter, cardBuilder, dom, globalize, connectionManager, appHost, layoutManager, focusManager) { - "use strict"; +define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'emby-itemscontainer', 'emby-scroller'], function (appRouter, cardBuilder, dom, globalize, connectionManager, appHost, layoutManager, focusManager) { + 'use strict'; function enableScrollX() { return true; } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function getPosterShape() { - return enableScrollX() ? "overflowPortrait" : "portrait"; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } function getSquareShape() { - return enableScrollX() ? "overflowSquare" : "square"; + return enableScrollX() ? 'overflowSquare' : 'square'; } function getSections() { return [{ - name: "HeaderFavoriteMovies", - types: "Movie", + name: 'HeaderFavoriteMovies', + types: 'Movie', shape: getPosterShape(), showTitle: true, showYear: true, @@ -28,8 +28,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayText: false, centerText: true }, { - name: "HeaderFavoriteShows", - types: "Series", + name: 'HeaderFavoriteShows', + types: 'Series', shape: getPosterShape(), showTitle: true, showYear: true, @@ -37,8 +37,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayText: false, centerText: true }, { - name: "HeaderFavoriteEpisodes", - types: "Episode", + name: 'HeaderFavoriteEpisodes', + types: 'Episode', shape: getThumbShape(), preferThumb: false, showTitle: true, @@ -47,8 +47,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayText: false, centerText: true }, { - name: "HeaderFavoriteVideos", - types: "Video", + name: 'HeaderFavoriteVideos', + types: 'Video', shape: getThumbShape(), preferThumb: true, showTitle: true, @@ -56,16 +56,16 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayText: false, centerText: true }, { - name: "HeaderFavoriteCollections", - types: "BoxSet", + name: 'HeaderFavoriteCollections', + types: 'BoxSet', shape: getPosterShape(), showTitle: true, overlayPlayButton: true, overlayText: false, centerText: true }, { - name: "HeaderFavoritePlaylists", - types: "Playlist", + name: 'HeaderFavoritePlaylists', + types: 'Playlist', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -75,8 +75,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoritePeople", - types: "Person", + name: 'HeaderFavoritePeople', + types: 'Person', shape: getPosterShape(), preferThumb: false, showTitle: true, @@ -86,8 +86,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoriteArtists", - types: "MusicArtist", + name: 'HeaderFavoriteArtists', + types: 'MusicArtist', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -97,8 +97,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoriteAlbums", - types: "MusicAlbum", + name: 'HeaderFavoriteAlbums', + types: 'MusicAlbum', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -108,8 +108,8 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap overlayPlayButton: true, coverImage: true }, { - name: "HeaderFavoriteSongs", - types: "Audio", + name: 'HeaderFavoriteSongs', + types: 'Audio', shape: getSquareShape(), preferThumb: false, showTitle: true, @@ -117,11 +117,11 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap showParentTitle: true, centerText: true, overlayMoreButton: true, - action: "instantmix", + action: 'instantmix', coverImage: true }, { - name: "HeaderFavoriteBooks", - types: "Book", + name: 'HeaderFavoriteBooks', + types: 'Book', shape: getPosterShape(), showTitle: true, showYear: true, @@ -135,23 +135,23 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap return function () { var apiClient = this.apiClient; var options = { - SortBy: (section.types, "SeriesName,SortName"), - SortOrder: "Ascending", - Filters: "IsFavorite", + SortBy: (section.types, 'SeriesName,SortName'), + SortOrder: 'Ascending', + Filters: 'IsFavorite', Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', CollapseBoxSetItems: false, - ExcludeLocationTypes: "Virtual", + ExcludeLocationTypes: 'Virtual', EnableTotalRecordCount: false }; options.Limit = 20; var userId = apiClient.getCurrentUserId(); - if ("MusicArtist" === section.types) { + if ('MusicArtist' === section.types) { return apiClient.getArtists(userId, options); } - if ("Person" === section.types) { + if ('Person' === section.types) { return apiClient.getPeople(userId, options); } @@ -161,7 +161,7 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap } function getRouteUrl(section, serverId) { - return appRouter.getRouteUrl("list", { + return appRouter.getRouteUrl('list', { serverId: serverId, itemTypes: section.types, isFavorite: true @@ -170,14 +170,14 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap function getItemsHtmlFn(section) { return function (items) { - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle; cardLayout = false; var serverId = this.apiClient.serverId(); var leadingButtons = layoutManager.tv ? [{ - name: globalize.translate("All"), - id: "more", - icon: "favorite", + name: globalize.translate('All'), + id: 'more', + icon: 'favorite', routeUrl: getRouteUrl(section, serverId) }] : null; var lines = 0; @@ -220,7 +220,7 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap this.view = view; this.params = params; this.apiClient = connectionManager.currentApiClient(); - this.sectionsContainer = view.querySelector(".sections"); + this.sectionsContainer = view.querySelector('.sections'); createSections(this, this.sectionsContainer, this.apiClient); } @@ -228,50 +228,50 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap var i; var length; var sections = getSections(); - var html = ""; + var html = ''; for (i = 0, length = sections.length; i < length; i++) { var section = sections[i]; - var sectionClass = "verticalSection"; + var sectionClass = 'verticalSection'; if (!section.showTitle) { - sectionClass += " verticalSection-extrabottompadding"; + sectionClass += ' verticalSection-extrabottompadding'; } html += '
'; html += '
'; if (layoutManager.tv) { - html += '

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

"; + html += '

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

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

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

"; - html += ''; - html += "
"; + html += ''; + html += ''; + html += ''; } - html += "
"; + html += '
'; html += '
'; - html += ""; + html += ''; } elem.innerHTML = html; - var elems = elem.querySelectorAll(".itemsContainer"); + var elems = elem.querySelectorAll('.itemsContainer'); for (i = 0, length = elems.length; i < length; i++) { var itemsContainer = elems[i]; itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); - itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, "verticalSection"); + itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); } } FavoritesTab.prototype.onResume = function (options) { var promises = (this.apiClient, []); var view = this.view; - var elems = this.sectionsContainer.querySelectorAll(".itemsContainer"); + var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); for (var i = 0, length = elems.length; i < length; i++) { promises.push(elems[i].resume(options)); @@ -285,7 +285,7 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap }; FavoritesTab.prototype.onPause = function () { - var elems = this.sectionsContainer.querySelectorAll(".itemsContainer"); + var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); for (var i = 0, length = elems.length; i < length; i++) { elems[i].pause(); @@ -296,7 +296,7 @@ define(["appRouter", "cardBuilder", "dom", "globalize", "connectionManager", "ap this.view = null; this.params = null; this.apiClient = null; - var elems = this.sectionsContainer.querySelectorAll(".itemsContainer"); + var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); for (var i = 0, length = elems.length; i < length; i++) { elems[i].fetchData = null; diff --git a/src/controllers/home.js b/src/controllers/home.js index b1dd70ebd69..9a4cea22277 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,11 +1,11 @@ -define(["tabbedView", "globalize", "require", "emby-tabs", "emby-button", "emby-scroller"], function (TabbedView, globalize, require) { - "use strict"; +define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-scroller'], function (TabbedView, globalize, require) { + 'use strict'; function getTabs() { return [{ - name: globalize.translate("Home") + name: globalize.translate('Home') }, { - name: globalize.translate("Favorites") + name: globalize.translate('Favorites') }]; } @@ -21,18 +21,18 @@ define(["tabbedView", "globalize", "require", "emby-tabs", "emby-button", "emby- function getTabController(index) { if (null == index) { - throw new Error("index cannot be null"); + throw new Error('index cannot be null'); } var depends = []; switch (index) { case 0: - depends.push("controllers/hometab"); + depends.push('controllers/hometab'); break; case 1: - depends.push("controllers/favorites"); + depends.push('controllers/favorites'); } var instance = this; @@ -63,12 +63,12 @@ define(["tabbedView", "globalize", "require", "emby-tabs", "emby-button", "emby- HomeView.prototype.onPause = function () { TabbedView.prototype.onPause.call(this); - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); }; HomeView.prototype.onResume = function (options) { TabbedView.prototype.onResume.call(this, options); - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); }; return HomeView; diff --git a/src/controllers/hometab.js b/src/controllers/hometab.js index d2adcb2da24..8e2a1f92e7c 100644 --- a/src/controllers/hometab.js +++ b/src/controllers/hometab.js @@ -1,12 +1,12 @@ -define(["userSettings", "loading", "connectionManager", "apphost", "layoutManager", "focusManager", "homeSections", "emby-itemscontainer"], function (userSettings, loading, connectionManager, appHost, layoutManager, focusManager, homeSections) { - "use strict"; +define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'homeSections', 'emby-itemscontainer'], function (userSettings, loading, connectionManager, appHost, layoutManager, focusManager, homeSections) { + 'use strict'; function HomeTab(view, params) { this.view = view; this.params = params; this.apiClient = connectionManager.currentApiClient(); - this.sectionsContainer = view.querySelector(".sections"); - view.querySelector(".sections").addEventListener("settingschange", onHomeScreenSettingsChanged.bind(this)); + this.sectionsContainer = view.querySelector('.sections'); + view.querySelector('.sections').addEventListener('settingschange', onHomeScreenSettingsChanged.bind(this)); } function onHomeScreenSettingsChanged() { @@ -36,7 +36,7 @@ define(["userSettings", "loading", "connectionManager", "apphost", "layoutManage this.destroyHomeSections(); this.sectionsRendered = true; return apiClient.getCurrentUser().then(function (user) { - return homeSections.loadSections(view.querySelector(".sections"), apiClient, user, userSettings).then(function () { + return homeSections.loadSections(view.querySelector('.sections'), apiClient, user, userSettings).then(function () { if (options.autoFocus) { focusManager.autoFocus(view); } diff --git a/src/itemdetails.html b/src/controllers/itemDetails/index.html similarity index 66% rename from src/itemdetails.html rename to src/controllers/itemDetails/index.html index 3a1c2748a67..ddb7be3aec9 100644 --- a/src/itemdetails.html +++ b/src/controllers/itemDetails/index.html @@ -1,8 +1,5 @@ -
+
-
@@ -10,99 +7,80 @@
-
-
+
+
- - - - - - - - - - - - - -
@@ -110,7 +88,7 @@
-
+
@@ -124,6 +102,11 @@
+ +
+
+
+
@@ -136,7 +119,7 @@
-
+
@@ -148,6 +131,9 @@

+

@@ -161,15 +147,15 @@

-

${HeaderSchedule}

-
+

${HeaderSchedule}

+
-
+
-

${HeaderNextUp}

-
+

${HeaderNextUp}

+
@@ -177,64 +163,64 @@

${HeaderNextUp}

-

+

-
+
-

${HeaderAdditionalParts}

-
+

${HeaderAdditionalParts}

+
-
-

+
+

-
-

+
+

-

${HeaderCastCrew}

+

${HeaderCastCrew}

-

${HeaderUpcomingOnTV}

-
+

${HeaderUpcomingOnTV}

+
-

${HeaderSpecialFeatures}

-
+

${HeaderSpecialFeatures}

+
-

${HeaderMusicVideos}

-
+

${HeaderMusicVideos}

+
-
-

${HeaderScenes}

+
+

${HeaderScenes}

-
-

${HeaderMoreLikeThis}

+
+

${HeaderMoreLikeThis}

diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemDetails/index.js similarity index 50% rename from src/controllers/itemdetailpage.js rename to src/controllers/itemDetails/index.js index 23a672751cd..cf5549f7092 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemDetails/index.js @@ -1,5 +1,5 @@ -define(["loading", "appRouter", "layoutManager", "connectionManager", "userSettings", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "imageLoader", "libraryMenu", "globalize", "browser", "events", "playbackManager", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-button", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { - "use strict"; +define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSettings', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'dom', 'indicators', 'imageLoader', 'libraryMenu', 'globalize', 'browser', 'events', 'playbackManager', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox', 'emby-button', 'emby-playstatebutton', 'emby-ratingbutton', 'emby-scroller', 'emby-select'], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { + 'use strict'; function getPromise(apiClient, params) { var id = params.id; @@ -24,19 +24,15 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti return apiClient.getArtist(params.musicartist, apiClient.getCurrentUserId()); } - throw new Error("Invalid request"); + throw new Error('Invalid request'); } function hideAll(page, className, show) { - var i; - var length; - var elems = page.querySelectorAll("." + className); - - for (i = 0, length = elems.length; i < length; i++) { + for (const elem of page.querySelectorAll('.' + className)) { if (show) { - elems[i].classList.remove("hide"); + elem.classList.remove('hide'); } else { - elems[i].classList.add("hide"); + elem.classList.add('hide'); } } } @@ -51,134 +47,143 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti positionTo: button, cancelTimer: false, record: false, - deleteItem: true === item.IsFolder, + deleteItem: item.CanDelete === true, shuffle: false, instantMix: false, user: user, share: true }; + return options; } function getProgramScheduleHtml(items) { - var html = ""; + var html = ''; + html += '
'; html += listView.getListViewHtml({ items: items, enableUserDataButtons: false, image: true, - imageSource: "channel", + imageSource: 'channel', showProgramDateTime: true, showChannel: false, mediaInfo: false, - action: "none", + action: 'none', moreButton: false, recordButton: false }); - return html += "
"; + + html += '
'; + + return html; } function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) { apiClient.getLiveTvTimers({ UserId: apiClient.getCurrentUserId(), ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", - SortBy: "StartDate", + EnableImageTypes: 'Primary,Backdrop,Thumb', + SortBy: 'StartDate', EnableTotalRecordCount: false, EnableUserData: false, SeriesTimerId: seriesTimerId, - Fields: "ChannelInfo,ChannelImage" + Fields: 'ChannelInfo,ChannelImage' }).then(function (result) { if (result.Items.length && result.Items[0].SeriesTimerId != seriesTimerId) { result.Items = []; } var html = getProgramScheduleHtml(result.Items); - var scheduleTab = page.querySelector(".seriesTimerSchedule"); + var scheduleTab = page.querySelector('.seriesTimerSchedule'); scheduleTab.innerHTML = html; imageLoader.lazyChildren(scheduleTab); }); } function renderTimerEditor(page, item, apiClient, user) { - if ("Recording" !== item.Type || !user.Policy.EnableLiveTvManagement || !item.TimerId || "InProgress" !== item.Status) { - return void hideAll(page, "btnCancelTimer"); + if ('Recording' !== item.Type || !user.Policy.EnableLiveTvManagement || !item.TimerId || 'InProgress' !== item.Status) { + return void hideAll(page, 'btnCancelTimer'); } - hideAll(page, "btnCancelTimer", true); + hideAll(page, 'btnCancelTimer', true); } function renderSeriesTimerEditor(page, item, apiClient, user) { - if ("SeriesTimer" !== item.Type) { - return void hideAll(page, "btnCancelSeriesTimer"); + if ('SeriesTimer' !== item.Type) { + return void hideAll(page, 'btnCancelSeriesTimer'); } if (user.Policy.EnableLiveTvManagement) { - require(["seriesRecordingEditor"], function (seriesRecordingEditor) { + require(['seriesRecordingEditor'], function (seriesRecordingEditor) { seriesRecordingEditor.embed(item, apiClient.serverId(), { - context: page.querySelector(".seriesRecordingEditor") + context: page.querySelector('.seriesRecordingEditor') }); }); - page.querySelector(".seriesTimerScheduleSection").classList.remove("hide"); - hideAll(page, "btnCancelSeriesTimer", true); + page.querySelector('.seriesTimerScheduleSection').classList.remove('hide'); + hideAll(page, 'btnCancelSeriesTimer', true); return void renderSeriesTimerSchedule(page, apiClient, item.Id); } - page.querySelector(".seriesTimerScheduleSection").classList.add("hide"); - return void hideAll(page, "btnCancelSeriesTimer"); + page.querySelector('.seriesTimerScheduleSection').classList.add('hide'); + return void hideAll(page, 'btnCancelSeriesTimer'); } function renderTrackSelections(page, instance, item, forceReload) { - var select = page.querySelector(".selectSource"); - - if (!item.MediaSources || !itemHelper.supportsMediaSourceSelection(item) || -1 === playbackManager.getSupportedCommands().indexOf("PlayMediaSource") || !playbackManager.canPlay(item)) { - page.querySelector(".trackSelections").classList.add("hide"); - select.innerHTML = ""; - page.querySelector(".selectVideo").innerHTML = ""; - page.querySelector(".selectAudio").innerHTML = ""; - page.querySelector(".selectSubtitles").innerHTML = ""; + var select = page.querySelector('.selectSource'); + + if (!item.MediaSources || !itemHelper.supportsMediaSourceSelection(item) || -1 === playbackManager.getSupportedCommands().indexOf('PlayMediaSource') || !playbackManager.canPlay(item)) { + page.querySelector('.trackSelections').classList.add('hide'); + select.innerHTML = ''; + page.querySelector('.selectVideo').innerHTML = ''; + page.querySelector('.selectAudio').innerHTML = ''; + page.querySelector('.selectSubtitles').innerHTML = ''; return; } - playbackManager.getPlaybackMediaSources(item).then(function (mediaSources) { - instance._currentPlaybackMediaSources = mediaSources; - page.querySelector(".trackSelections").classList.remove("hide"); - select.setLabel(globalize.translate("LabelVersion")); - var currentValue = select.value; - var selectedId = mediaSources[0].Id; - select.innerHTML = mediaSources.map(function (v) { - var selected = v.Id === selectedId ? " selected" : ""; - return '"; - }).join(""); + var mediaSources = item.MediaSources; + instance._currentPlaybackMediaSources = mediaSources; - if (mediaSources.length > 1) { - page.querySelector(".selectSourceContainer").classList.remove("hide"); - } else { - page.querySelector(".selectSourceContainer").classList.add("hide"); - } + page.querySelector('.trackSelections').classList.remove('hide'); + select.setLabel(globalize.translate('LabelVersion')); - if (select.value !== currentValue || forceReload) { - renderVideoSelections(page, mediaSources); - renderAudioSelections(page, mediaSources); - renderSubtitleSelections(page, mediaSources); - } - }); + var currentValue = select.value; + + var selectedId = mediaSources[0].Id; + select.innerHTML = mediaSources.map(function (v) { + var selected = v.Id === selectedId ? ' selected' : ''; + return ''; + }).join(''); + + if (mediaSources.length > 1) { + page.querySelector('.selectSourceContainer').classList.remove('hide'); + } else { + page.querySelector('.selectSourceContainer').classList.add('hide'); + } + + if (select.value !== currentValue || forceReload) { + renderVideoSelections(page, mediaSources); + renderAudioSelections(page, mediaSources); + renderSubtitleSelections(page, mediaSources); + } } function renderVideoSelections(page, mediaSources) { - var mediaSourceId = page.querySelector(".selectSource").value; + var mediaSourceId = page.querySelector('.selectSource').value; var mediaSource = mediaSources.filter(function (m) { return m.Id === mediaSourceId; })[0]; + var tracks = mediaSource.MediaStreams.filter(function (m) { - return "Video" === m.Type; + return m.Type === 'Video'; }); - var select = page.querySelector(".selectVideo"); - select.setLabel(globalize.translate("LabelVideo")); + + var select = page.querySelector('.selectVideo'); + select.setLabel(globalize.translate('LabelVideo')); var selectedId = tracks.length ? tracks[0].Index : -1; select.innerHTML = tracks.map(function (v) { - var selected = v.Index === selectedId ? " selected" : ""; + var selected = v.Index === selectedId ? ' selected' : ''; var titleParts = []; var resolutionText = mediaInfo.getResolutionText(v); @@ -190,100 +195,120 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti titleParts.push(v.Codec.toUpperCase()); } - return '"; - }).join(""); - select.setAttribute("disabled", "disabled"); + return ''; + }).join(''); + select.setAttribute('disabled', 'disabled'); if (tracks.length) { - page.querySelector(".selectVideoContainer").classList.remove("hide"); + page.querySelector('.selectVideoContainer').classList.remove('hide'); } else { - page.querySelector(".selectVideoContainer").classList.add("hide"); + page.querySelector('.selectVideoContainer').classList.add('hide'); } } function renderAudioSelections(page, mediaSources) { - var mediaSourceId = page.querySelector(".selectSource").value; + var mediaSourceId = page.querySelector('.selectSource').value; var mediaSource = mediaSources.filter(function (m) { return m.Id === mediaSourceId; })[0]; var tracks = mediaSource.MediaStreams.filter(function (m) { - return "Audio" === m.Type; + return 'Audio' === m.Type; }); - var select = page.querySelector(".selectAudio"); - select.setLabel(globalize.translate("LabelAudio")); + var select = page.querySelector('.selectAudio'); + select.setLabel(globalize.translate('LabelAudio')); var selectedId = mediaSource.DefaultAudioStreamIndex; select.innerHTML = tracks.map(function (v) { - var selected = v.Index === selectedId ? " selected" : ""; - return '"; - }).join(""); + var selected = v.Index === selectedId ? ' selected' : ''; + return ''; + }).join(''); if (tracks.length > 1) { - select.removeAttribute("disabled"); + select.removeAttribute('disabled'); } else { - select.setAttribute("disabled", "disabled"); + select.setAttribute('disabled', 'disabled'); } if (tracks.length) { - page.querySelector(".selectAudioContainer").classList.remove("hide"); + page.querySelector('.selectAudioContainer').classList.remove('hide'); } else { - page.querySelector(".selectAudioContainer").classList.add("hide"); + page.querySelector('.selectAudioContainer').classList.add('hide'); } } function renderSubtitleSelections(page, mediaSources) { - var mediaSourceId = page.querySelector(".selectSource").value; + var mediaSourceId = page.querySelector('.selectSource').value; var mediaSource = mediaSources.filter(function (m) { return m.Id === mediaSourceId; })[0]; var tracks = mediaSource.MediaStreams.filter(function (m) { - return "Subtitle" === m.Type; + return 'Subtitle' === m.Type; }); - var select = page.querySelector(".selectSubtitles"); - select.setLabel(globalize.translate("LabelSubtitles")); + var select = page.querySelector('.selectSubtitles'); + select.setLabel(globalize.translate('LabelSubtitles')); var selectedId = null == mediaSource.DefaultSubtitleStreamIndex ? -1 : mediaSource.DefaultSubtitleStreamIndex; - if (tracks.length) { - var selected = -1 === selectedId ? " selected" : ""; - select.innerHTML = '" + tracks.map(function (v) { - selected = v.Index === selectedId ? " selected" : ""; - return '"; - }).join(""); - page.querySelector(".selectSubtitlesContainer").classList.remove("hide"); + var videoTracks = mediaSource.MediaStreams.filter(function (m) { + return 'Video' === m.Type; + }); + + // This only makes sence on Video items + if (videoTracks.length) { + var selected = -1 === selectedId ? ' selected' : ''; + select.innerHTML = '' + tracks.map(function (v) { + selected = v.Index === selectedId ? ' selected' : ''; + return ''; + }).join(''); + + if (tracks.length > 1) { + select.removeAttribute('disabled'); + } else { + select.setAttribute('disabled', 'disabled'); + } + + page.querySelector('.selectSubtitlesContainer').classList.remove('hide'); } else { - select.innerHTML = ""; - page.querySelector(".selectSubtitlesContainer").classList.add("hide"); + select.innerHTML = ''; + page.querySelector('.selectSubtitlesContainer').classList.add('hide'); } } function reloadPlayButtons(page, item) { var canPlay = false; - if ("Program" == item.Type) { + if ('Program' == item.Type) { var now = new Date(); if (now >= datetime.parseISO8601Date(item.StartDate, true) && now < datetime.parseISO8601Date(item.EndDate, true)) { - hideAll(page, "btnPlay", true); + hideAll(page, 'btnPlay', true); canPlay = true; } else { - hideAll(page, "btnPlay"); + hideAll(page, 'btnPlay'); } - hideAll(page, "btnResume"); - hideAll(page, "btnInstantMix"); - hideAll(page, "btnShuffle"); + hideAll(page, 'btnResume'); + hideAll(page, 'btnInstantMix'); + hideAll(page, 'btnShuffle'); } else if (playbackManager.canPlay(item)) { - hideAll(page, "btnPlay", true); - var enableInstantMix = -1 !== ["Audio", "MusicAlbum", "MusicGenre", "MusicArtist"].indexOf(item.Type); - hideAll(page, "btnInstantMix", enableInstantMix); - var enableShuffle = item.IsFolder || -1 !== ["MusicAlbum", "MusicGenre", "MusicArtist"].indexOf(item.Type); - hideAll(page, "btnShuffle", enableShuffle); + hideAll(page, 'btnPlay', true); + var enableInstantMix = -1 !== ['Audio', 'MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type); + hideAll(page, 'btnInstantMix', enableInstantMix); + var enableShuffle = item.IsFolder || -1 !== ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type); + hideAll(page, 'btnShuffle', enableShuffle); canPlay = true; - hideAll(page, "btnResume", item.UserData && item.UserData.PlaybackPositionTicks > 0); + + const isResumable = item.UserData && item.UserData.PlaybackPositionTicks > 0; + hideAll(page, 'btnResume', isResumable); + + if (isResumable) { + for (const elem of page.querySelectorAll('.btnPlay')) { + elem.querySelector('.detailButton-icon').classList.replace('play_arrow', 'replay'); + } + } } else { - hideAll(page, "btnPlay"); - hideAll(page, "btnResume"); - hideAll(page, "btnInstantMix"); - hideAll(page, "btnShuffle"); + hideAll(page, 'btnPlay'); + hideAll(page, 'btnResume'); + hideAll(page, 'btnInstantMix'); + hideAll(page, 'btnShuffle'); } return canPlay; @@ -292,30 +317,30 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function reloadUserDataButtons(page, item) { var i; var length; - var btnPlaystates = page.querySelectorAll(".btnPlaystate"); + var btnPlaystates = page.querySelectorAll('.btnPlaystate'); for (i = 0, length = btnPlaystates.length; i < length; i++) { var btnPlaystate = btnPlaystates[i]; if (itemHelper.canMarkPlayed(item)) { - btnPlaystate.classList.remove("hide"); + btnPlaystate.classList.remove('hide'); btnPlaystate.setItem(item); } else { - btnPlaystate.classList.add("hide"); + btnPlaystate.classList.add('hide'); btnPlaystate.setItem(null); } } - var btnUserRatings = page.querySelectorAll(".btnUserRating"); + var btnUserRatings = page.querySelectorAll('.btnUserRating'); for (i = 0, length = btnUserRatings.length; i < length; i++) { var btnUserRating = btnUserRatings[i]; if (itemHelper.canRate(item)) { - btnUserRating.classList.remove("hide"); + btnUserRating.classList.remove('hide'); btnUserRating.setItem(item); } else { - btnUserRating.classList.add("hide"); + btnUserRating.classList.add('hide'); btnUserRating.setItem(null); } } @@ -324,20 +349,26 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function getArtistLinksHtml(artists, serverId, context) { var html = []; - for (var i = 0, length = artists.length; i < length; i++) { - var artist = artists[i]; + for (const artist of artists) { var href = appRouter.getRouteUrl(artist, { context: context, - itemType: "MusicArtist", + itemType: 'MusicArtist', serverId: serverId }); - html.push('' + artist.Name + ""); + html.push('' + artist.Name + ''); } + html = html.join(' / '); - return html = html.join(" / "); + return html; } - function renderName(item, container, isStatic, context) { + /** + * Renders the item's name block + * @param {Object} item - Item used to render the name. + * @param {HTMLDivElement} container - Container to render the information into. + * @param {Object} context - Application context. + */ + function renderName(item, container, context) { var parentRoute; var parentNameHtml = []; var parentNameLast = false; @@ -345,102 +376,115 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if (item.AlbumArtists) { parentNameHtml.push(getArtistLinksHtml(item.AlbumArtists, item.ServerId, context)); parentNameLast = true; - } else if (item.ArtistItems && item.ArtistItems.length && "MusicVideo" === item.Type) { + } else if (item.ArtistItems && item.ArtistItems.length && 'MusicVideo' === item.Type) { parentNameHtml.push(getArtistLinksHtml(item.ArtistItems, item.ServerId, context)); parentNameLast = true; - } else if (item.SeriesName && "Episode" === item.Type) { + } else if (item.SeriesName && 'Episode' === item.Type) { parentRoute = appRouter.getRouteUrl({ Id: item.SeriesId, Name: item.SeriesName, - Type: "Series", + Type: 'Series', IsFolder: true, ServerId: item.ServerId }, { context: context }); - parentNameHtml.push('' + item.SeriesName + ""); + parentNameHtml.push('' + item.SeriesName + ''); } else if (item.IsSeries || item.EpisodeTitle) { parentNameHtml.push(item.Name); } - if (item.SeriesName && "Season" === item.Type) { + if (item.SeriesName && 'Season' === item.Type) { parentRoute = appRouter.getRouteUrl({ Id: item.SeriesId, Name: item.SeriesName, - Type: "Series", + Type: 'Series', IsFolder: true, ServerId: item.ServerId }, { context: context }); - parentNameHtml.push('' + item.SeriesName + ""); - } else if (null != item.ParentIndexNumber && "Episode" === item.Type) { + parentNameHtml.push('' + item.SeriesName + ''); + } else if (null != item.ParentIndexNumber && 'Episode' === item.Type) { parentRoute = appRouter.getRouteUrl({ Id: item.SeasonId, Name: item.SeasonName, - Type: "Season", + Type: 'Season', IsFolder: true, ServerId: item.ServerId }, { context: context }); - parentNameHtml.push('' + item.SeasonName + ""); + parentNameHtml.push('' + item.SeasonName + ''); } else if (null != item.ParentIndexNumber && item.IsSeries) { - parentNameHtml.push(item.SeasonName || "S" + item.ParentIndexNumber); - } else if (item.Album && item.AlbumId && ("MusicVideo" === item.Type || "Audio" === item.Type)) { + parentNameHtml.push(item.SeasonName || 'S' + item.ParentIndexNumber); + } else if (item.Album && item.AlbumId && ('MusicVideo' === item.Type || 'Audio' === item.Type)) { parentRoute = appRouter.getRouteUrl({ Id: item.AlbumId, Name: item.Album, - Type: "MusicAlbum", + Type: 'MusicAlbum', IsFolder: true, ServerId: item.ServerId }, { context: context }); - parentNameHtml.push('' + item.Album + ""); + parentNameHtml.push('' + item.Album + ''); } else if (item.Album) { parentNameHtml.push(item.Album); } - var html = ""; + // FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022 + var html = ''; + var tvShowHtml = parentNameHtml[0]; + var tvSeasonHtml = parentNameHtml[1]; if (parentNameHtml.length) { if (parentNameLast) { - html = '

' + parentNameHtml.join(" - ") + "

"; + // Music + if (layoutManager.mobile) { + html = '

' + parentNameHtml.join('
') + '

'; + } else { + html = '

' + parentNameHtml.join(' - ') + '

'; + } } else { - html = '

' + parentNameHtml.join(" - ") + "

"; + html = '

' + tvShowHtml + '

'; } } var name = itemHelper.getDisplayName(item, { includeParentInfo: false }); - var offset = parentNameLast ? ".25em" : ".5em"; if (html && !parentNameLast) { - html += '

' + name + '

'; + if (tvSeasonHtml) { + html += '

' + tvSeasonHtml + ' - ' + name + '

'; + } else { + html += '

' + name + '

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

' + name + '

' + html; } else { - html = '

' + name + "

" + html; + html = '

' + name + '

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

' + item.OriginalTitle + '

'; + html += '

' + item.OriginalTitle + '

'; } container.innerHTML = html; if (html.length) { - container.classList.remove("hide"); + container.classList.remove('hide'); } else { - container.classList.add("hide"); + container.classList.add('hide'); } } function setTrailerButtonVisibility(page, item) { - if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf("PlayTrailers")) { - hideAll(page, "btnPlayTrailer", true); + if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) { + hideAll(page, 'btnPlayTrailer', true); } else { - hideAll(page, "btnPlayTrailer"); + hideAll(page, 'btnPlayTrailer'); } } @@ -455,189 +499,143 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function renderDetailPageBackdrop(page, item, apiClient) { var imgUrl; var hasbackdrop = false; - var itemBackdropElement = page.querySelector("#itemBackdrop"); - var usePrimaryImage = item.MediaType === "Video" && item.Type !== "Movie" && item.Type !== "Trailer" || - item.MediaType && item.MediaType !== "Video" || - item.Type === "MusicAlbum" || - item.Type === "Person"; + var itemBackdropElement = page.querySelector('#itemBackdrop'); - if (!layoutManager.mobile && !userSettings.enableBackdrops()) { + if (!layoutManager.mobile && !userSettings.detailsBanner()) { return false; } - if ("Program" === item.Type && item.ImageTags && item.ImageTags.Thumb) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.ImageTags.Thumb - }); - page.classList.remove("noBackdrop"); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); - hasbackdrop = true; - } else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Primary", - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.ImageTags.Primary - }); - page.classList.remove("noBackdrop"); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); - hasbackdrop = true; - } else if (item.BackdropImageTags && item.BackdropImageTags.length) { + if (item.BackdropImageTags && item.BackdropImageTags.length) { imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Backdrop", + type: 'Backdrop', maxWidth: dom.getScreenWidth(), index: 0, tag: item.BackdropImageTags[0] }); - page.classList.remove("noBackdrop"); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: "Backdrop", + type: 'Backdrop', maxWidth: dom.getScreenWidth(), index: 0, tag: item.ParentBackdropImageTags[0] }); - page.classList.remove("noBackdrop"); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; - } else if (item.ImageTags && item.ImageTags.Thumb) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.ImageTags.Thumb - }); - page.classList.remove("noBackdrop"); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); - hasbackdrop = true; - } else { - itemBackdropElement.style.backgroundImage = ""; - } - - if ("Person" === item.Type) { - // FIXME: This hides the backdrop on all persons to fix a margin issue. Ideally, a proper fix should be made. - page.classList.add('noBackdrop'); - itemBackdropElement.classList.add("personBackdrop"); } else { - itemBackdropElement.classList.remove("personBackdrop"); + itemBackdropElement.style.backgroundImage = ''; } return hasbackdrop; } function reloadFromItem(instance, page, params, item, user) { - var context = params.context; - page.querySelector(".detailPagePrimaryContainer").classList.add("detailSticky"); + const apiClient = connectionManager.getApiClient(item.ServerId); - renderName(item, page.querySelector(".nameContainer"), false, context); - var apiClient = connectionManager.getApiClient(item.ServerId); - renderSeriesTimerEditor(page, item, apiClient, user); - renderTimerEditor(page, item, apiClient, user); - renderImage(page, item, apiClient, user); + Emby.Page.setTitle(''); + + // Start rendering the artwork first + renderImage(page, item); renderLogo(page, item, apiClient); - setTitle(item, apiClient); - setInitialCollapsibleState(page, item, apiClient, context, user); - renderDetails(page, item, apiClient, context); - renderTrackSelections(page, instance, item); renderBackdrop(item); renderDetailPageBackdrop(page, item, apiClient); + + // Render the main information for the item + page.querySelector('.detailPagePrimaryContainer').classList.add('detailRibbon'); + renderName(item, page.querySelector('.nameContainer'), params.context); + renderDetails(page, item, apiClient, params.context); + renderTrackSelections(page, instance, item); + + renderSeriesTimerEditor(page, item, apiClient, user); + renderTimerEditor(page, item, apiClient, user); + setInitialCollapsibleState(page, item, apiClient, params.context, user); var canPlay = reloadPlayButtons(page, item); - if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf("PlayTrailers")) { - hideAll(page, "btnPlayTrailer", true); + if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) { + hideAll(page, 'btnPlayTrailer', true); } else { - hideAll(page, "btnPlayTrailer"); + hideAll(page, 'btnPlayTrailer'); } setTrailerButtonVisibility(page, item); - if (item.CanDelete && !item.IsFolder) { - hideAll(page, "btnDeleteItem", true); + if ('Program' !== item.Type || canPlay) { + hideAll(page, 'mainDetailButtons', true); } else { - hideAll(page, "btnDeleteItem"); - } - - if ("Program" !== item.Type || canPlay) { - hideAll(page, "mainDetailButtons", true); - } else { - hideAll(page, "mainDetailButtons"); + hideAll(page, 'mainDetailButtons'); } showRecordingFields(instance, page, item, user); var groupedVersions = (item.MediaSources || []).filter(function (g) { - return "Grouping" == g.Type; + return 'Grouping' == g.Type; }); if (user.Policy.IsAdministrator && groupedVersions.length) { - page.querySelector(".btnSplitVersions").classList.remove("hide"); + page.querySelector('.btnSplitVersions').classList.remove('hide'); } else { - page.querySelector(".btnSplitVersions").classList.add("hide"); + page.querySelector('.btnSplitVersions').classList.add('hide'); } if (itemContextMenu.getCommands(getContextMenuOptions(item, user)).length) { - hideAll(page, "btnMoreCommands", true); + hideAll(page, 'btnMoreCommands', true); } else { - hideAll(page, "btnMoreCommands"); + hideAll(page, 'btnMoreCommands'); } - var itemBirthday = page.querySelector("#itemBirthday"); + var itemBirthday = page.querySelector('#itemBirthday'); - if ("Person" == item.Type && item.PremiereDate) { + if ('Person' == item.Type && item.PremiereDate) { try { var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); - itemBirthday.classList.remove("hide"); - itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday); + itemBirthday.classList.remove('hide'); + itemBirthday.innerHTML = globalize.translate('BirthDateValue', birthday); } catch (err) { - itemBirthday.classList.add("hide"); + itemBirthday.classList.add('hide'); } } else { - itemBirthday.classList.add("hide"); + itemBirthday.classList.add('hide'); } - var itemDeathDate = page.querySelector("#itemDeathDate"); + var itemDeathDate = page.querySelector('#itemDeathDate'); - if ("Person" == item.Type && item.EndDate) { + if ('Person' == item.Type && item.EndDate) { try { var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); - itemDeathDate.classList.remove("hide"); - itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday); + itemDeathDate.classList.remove('hide'); + itemDeathDate.innerHTML = globalize.translate('DeathDateValue', deathday); } catch (err) { - itemDeathDate.classList.add("hide"); + itemDeathDate.classList.add('hide'); } } else { - itemDeathDate.classList.add("hide"); + itemDeathDate.classList.add('hide'); } - var itemBirthLocation = page.querySelector("#itemBirthLocation"); + var itemBirthLocation = page.querySelector('#itemBirthLocation'); - if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) { - var gmap = '' + item.ProductionLocations[0] + ""; - itemBirthLocation.classList.remove("hide"); - itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap); + if ('Person' == item.Type && item.ProductionLocations && item.ProductionLocations.length) { + var gmap = '' + item.ProductionLocations[0] + ''; + itemBirthLocation.classList.remove('hide'); + itemBirthLocation.innerHTML = globalize.translate('BirthPlaceValue', gmap); } else { - itemBirthLocation.classList.add("hide"); + itemBirthLocation.classList.add('hide'); } setPeopleHeader(page, item); loading.hide(); - if (item.Type === "Book") { - hideAll(page, "btnDownload", true); + if (item.Type === 'Book') { + hideAll(page, 'btnDownload', true); } - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); } function logoImageUrl(item, apiClient, options) { options = options || {}; - options.type = "Logo"; + options.type = 'Logo'; if (item.ImageTags && item.ImageTags.Logo) { options.tag = item.ImageTags.Logo; @@ -652,247 +650,118 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti return null; } - function setTitle(item, apiClient) { - var url = logoImageUrl(item, apiClient, {}); - - if (url != null) { - var pageTitle = document.querySelector(".pageTitle"); - pageTitle.style.backgroundImage = "url('" + url + "')"; - pageTitle.classList.add("pageTitleWithLogo"); - pageTitle.innerHTML = ""; - } else { - Emby.Page.setTitle(""); - } - } - function renderLogo(page, item, apiClient) { - var url = logoImageUrl(item, apiClient, { - maxWidth: 400 - }); - var detailLogo = page.querySelector(".detailLogo"); + var detailLogo = page.querySelector('.detailLogo'); + + var url = logoImageUrl(item, apiClient, {}); if (!layoutManager.mobile && !userSettings.enableBackdrops()) { - detailLogo.classList.add("hide"); + detailLogo.classList.add('hide'); } else if (url) { - detailLogo.classList.remove("hide"); - detailLogo.classList.add("lazy"); - detailLogo.setAttribute("data-src", url); - imageLoader.lazyImage(detailLogo); + detailLogo.classList.remove('hide'); + imageLoader.setLazyImage(detailLogo, url); } else { - detailLogo.classList.add("hide"); + detailLogo.classList.add('hide'); } } function showRecordingFields(instance, page, item, user) { if (!instance.currentRecordingFields) { - var recordingFieldsElement = page.querySelector(".recordingFields"); + var recordingFieldsElement = page.querySelector('.recordingFields'); - if ("Program" == item.Type && user.Policy.EnableLiveTvManagement) { - require(["recordingFields"], function (recordingFields) { + if ('Program' == item.Type && user.Policy.EnableLiveTvManagement) { + require(['recordingFields'], function (recordingFields) { instance.currentRecordingFields = new recordingFields({ parent: recordingFieldsElement, programId: item.Id, serverId: item.ServerId }); - recordingFieldsElement.classList.remove("hide"); + recordingFieldsElement.classList.remove('hide'); }); } else { - recordingFieldsElement.classList.add("hide"); - recordingFieldsElement.innerHTML = ""; + recordingFieldsElement.classList.add('hide'); + recordingFieldsElement.innerHTML = ''; } } } - function renderLinks(linksElem, item) { - var html = []; + function renderLinks(page, item) { + var externalLinksElem = page.querySelector('.itemExternalLinks'); var links = []; if (!layoutManager.tv && item.HomePageUrl) { - links.push('' + globalize.translate("ButtonWebsite") + ""); + links.push(`${globalize.translate('ButtonWebsite')}`); } + if (item.ExternalUrls) { - for (var i = 0, length = item.ExternalUrls.length; i < length; i++) { - var url = item.ExternalUrls[i]; - links.push('' + url.Name + ""); + for (const url of item.ExternalUrls) { + links.push(`${url.Name}`); } } + var html = []; if (links.length) { - html.push(links.join(", ")); + html.push(links.join(', ')); } - linksElem.innerHTML = html.join(", "); + externalLinksElem.innerHTML = html.join(', '); if (html.length) { - linksElem.classList.remove("hide"); + externalLinksElem.classList.remove('hide'); } else { - linksElem.classList.add("hide"); + externalLinksElem.classList.add('hide'); } } - function renderDetailImage(page, elem, item, apiClient, editable, imageLoader, indicators) { - if ("SeriesTimer" === item.Type || "Program" === item.Type) { - editable = false; - } - - elem.classList.add("detailimg-hidemobile"); - - var imageTags = item.ImageTags || {}; - - if (item.PrimaryImageTag) { - imageTags.Primary = item.PrimaryImageTag; - } - - var url; - var html = ""; - var shape = "portrait"; - var detectRatio = false; - - /* In the following section, getScreenWidth() is multiplied by 0.5 as the posters - are 25vw and we need double the resolution to counter Skia's scaling. */ - // TODO: Find a reliable way to get the poster width - if (imageTags.Primary) { - url = apiClient.getScaledImageUrl(item.Id, { - type: "Primary", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.ImageTags.Primary - }); - detectRatio = true; - } else if (item.BackdropImageTags && item.BackdropImageTags.length) { - url = apiClient.getScaledImageUrl(item.Id, { - type: "Backdrop", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.BackdropImageTags[0] - }); - shape = "thumb"; - } else if (imageTags.Thumb) { - url = apiClient.getScaledImageUrl(item.Id, { - type: "Thumb", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.ImageTags.Thumb - }); - shape = "thumb"; - } else if (imageTags.Disc) { - url = apiClient.getScaledImageUrl(item.Id, { - type: "Disc", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.ImageTags.Disc - }); - shape = "square"; - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - url = apiClient.getScaledImageUrl(item.AlbumId, { - type: "Primary", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.AlbumPrimaryImageTag - }); - shape = "square"; - } else if (item.SeriesId && item.SeriesPrimaryImageTag) { - url = apiClient.getScaledImageUrl(item.SeriesId, { - type: "Primary", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.SeriesPrimaryImageTag - }); - } else if (item.ParentPrimaryImageItemId && item.ParentPrimaryImageTag) { - url = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, { - type: "Primary", - maxWidth: Math.round(dom.getScreenWidth() * 0.5), - tag: item.ParentPrimaryImageTag - }); - } - - if (editable && url === undefined) { - html += ""; - } else if (!editable && url === undefined) { - html += ""; - } - - var progressHtml = item.IsFolder || !item.UserData ? "" : indicators.getProgressBarHtml(item); - html += '
'; - - if (progressHtml) { - html += progressHtml; - } - - html += "
"; - elem.innerHTML = html; - - if (detectRatio && item.PrimaryImageAspectRatio) { - if (item.PrimaryImageAspectRatio >= 1.48) { - shape = "thumb"; - } else if (item.PrimaryImageAspectRatio >= 0.85 && item.PrimaryImageAspectRatio <= 1.34) { - shape = "square"; - } - } - - if ("thumb" == shape) { - elem.classList.add("thumbDetailImageContainer"); - elem.classList.remove("portraitDetailImageContainer"); - elem.classList.remove("squareDetailImageContainer"); - } else if ("square" == shape) { - elem.classList.remove("thumbDetailImageContainer"); - elem.classList.remove("portraitDetailImageContainer"); - elem.classList.add("squareDetailImageContainer"); - } else { - elem.classList.remove("thumbDetailImageContainer"); - elem.classList.add("portraitDetailImageContainer"); - elem.classList.remove("squareDetailImageContainer"); - } + function renderDetailImage(elem, item, imageLoader) { + const itemArray = []; + itemArray.push(item); + const cardHtml = cardBuilder.getCardsHtml(itemArray, { + shape: 'auto', + showTitle: false, + centerText: true, + overlayText: false, + transition: false, + disableIndicators: true, + disableHoverMenu: true, + overlayPlayButton: true, + width: dom.getWindowSize().innerWidth * 0.5 + }); - if (url) { - imageLoader.lazyImage(elem.querySelector("img"), url); - } + elem.innerHTML = cardHtml; + imageLoader.lazyChildren(elem); } - function renderImage(page, item, apiClient, user) { + function renderImage(page, item) { renderDetailImage( - page, - page.querySelector(".detailImageContainer"), + page.querySelector('.detailImageContainer'), item, - apiClient, - user.Policy.IsAdministrator && "Photo" != item.MediaType, - imageLoader, - indicators + imageLoader ); } function refreshDetailImageUserData(elem, item) { - elem.querySelector(".detailImageProgressContainer").innerHTML = indicators.getProgressBarHtml(item); + elem.querySelector('.detailImageProgressContainer').innerHTML = indicators.getProgressBarHtml(item); } function refreshImage(page, item) { - refreshDetailImageUserData(page.querySelector(".detailImageContainer"), item); + refreshDetailImageUserData(page.querySelector('.detailImageContainer'), item); } function setPeopleHeader(page, item) { - if ("Audio" == item.MediaType || "MusicAlbum" == item.Type || "Book" == item.MediaType || "Photo" == item.MediaType) { - page.querySelector("#peopleHeader").innerHTML = globalize.translate("HeaderPeople"); + if ('Audio' == item.MediaType || 'MusicAlbum' == item.Type || 'Book' == item.MediaType || 'Photo' == item.MediaType) { + page.querySelector('#peopleHeader').innerHTML = globalize.translate('HeaderPeople'); } else { - page.querySelector("#peopleHeader").innerHTML = globalize.translate("HeaderCastAndCrew"); + page.querySelector('#peopleHeader').innerHTML = globalize.translate('HeaderCastAndCrew'); } } function renderNextUp(page, item, user) { - var section = page.querySelector(".nextUpSection"); + var section = page.querySelector('.nextUpSection'); - if ("Series" != item.Type) { - return void section.classList.add("hide"); + if ('Series' != item.Type) { + return void section.classList.add('hide'); } connectionManager.getApiClient(item.ServerId).getNextUpEpisodes({ @@ -900,110 +769,125 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti UserId: user.Id }).then(function (result) { if (result.Items.length) { - section.classList.remove("hide"); + section.classList.remove('hide'); } else { - section.classList.add("hide"); + section.classList.add('hide'); } var html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "overflowBackdrop", + shape: 'overflowBackdrop', showTitle: true, - displayAsSpecial: "Season" == item.Type && item.IndexNumber, + displayAsSpecial: 'Season' == item.Type && item.IndexNumber, overlayText: false, centerText: true, overlayPlayButton: true }); - var itemsContainer = section.querySelector(".nextUpItems"); + var itemsContainer = section.querySelector('.nextUpItems'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); }); } function setInitialCollapsibleState(page, item, apiClient, context, user) { - page.querySelector(".collectionItems").innerHTML = ""; + page.querySelector('.collectionItems').innerHTML = ''; - if ("Playlist" == item.Type) { - page.querySelector("#childrenCollapsible").classList.remove("hide"); + if ('Playlist' == item.Type) { + page.querySelector('#childrenCollapsible').classList.remove('hide'); renderPlaylistItems(page, item); - } else if ("Studio" == item.Type || "Person" == item.Type || "Genre" == item.Type || "MusicGenre" == item.Type || "MusicArtist" == item.Type) { - page.querySelector("#childrenCollapsible").classList.remove("hide"); + } else if ('Studio' == item.Type || 'Person' == item.Type || 'Genre' == item.Type || 'MusicGenre' == item.Type || 'MusicArtist' == item.Type) { + page.querySelector('#childrenCollapsible').classList.remove('hide'); renderItemsByName(page, item); } else if (item.IsFolder) { - if ("BoxSet" == item.Type) { - page.querySelector("#childrenCollapsible").classList.add("hide"); + if ('BoxSet' == item.Type) { + page.querySelector('#childrenCollapsible').classList.add('hide'); } renderChildren(page, item); } else { - page.querySelector("#childrenCollapsible").classList.add("hide"); + page.querySelector('#childrenCollapsible').classList.add('hide'); } - if ("Series" == item.Type) { + if ('Series' == item.Type) { renderSeriesSchedule(page, item); renderNextUp(page, item, user); } else { - page.querySelector(".nextUpSection").classList.add("hide"); + page.querySelector('.nextUpSection').classList.add('hide'); } renderScenes(page, item); - if (item.SpecialFeatureCount && 0 != item.SpecialFeatureCount && "Series" != item.Type) { - page.querySelector("#specialsCollapsible").classList.remove("hide"); + if (item.SpecialFeatureCount && 0 != item.SpecialFeatureCount && 'Series' != item.Type) { + page.querySelector('#specialsCollapsible').classList.remove('hide'); renderSpecials(page, item, user, 6); } else { - page.querySelector("#specialsCollapsible").classList.add("hide"); + page.querySelector('#specialsCollapsible').classList.add('hide'); } renderCast(page, item); if (item.PartCount && item.PartCount > 1) { - page.querySelector("#additionalPartsCollapsible").classList.remove("hide"); + page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); renderAdditionalParts(page, item, user); } else { - page.querySelector("#additionalPartsCollapsible").classList.add("hide"); + page.querySelector('#additionalPartsCollapsible').classList.add('hide'); } - if ("MusicAlbum" == item.Type) { + if ('MusicAlbum' == item.Type) { renderMusicVideos(page, item, user); } else { - page.querySelector("#musicVideosCollapsible").classList.add("hide"); + page.querySelector('#musicVideosCollapsible').classList.add('hide'); } } - function renderOverview(elems, item) { - for (var i = 0, length = elems.length; i < length; i++) { - var elem = elems[i]; - var overview = item.Overview || ""; + function toggleLineClamp(clampTarget, e) { + var expandButton = e.target; + var clampClassName = 'detail-clamp-text'; + + if (clampTarget.classList.contains(clampClassName)) { + clampTarget.classList.remove(clampClassName); + expandButton.innerHTML = globalize.translate('ShowLess'); + } else { + clampTarget.classList.add(clampClassName); + expandButton.innerHTML = globalize.translate('ShowMore'); + } + } + + function renderOverview(page, item) { + for (const overviewElemnt of page.querySelectorAll('.overview')) { + var overview = item.Overview || ''; if (overview) { - elem.innerHTML = overview; - elem.classList.remove("hide"); - var anchors = elem.querySelectorAll("a"); + overviewElemnt.innerHTML = overview; + overviewElemnt.classList.remove('hide'); + overviewElemnt.classList.add('detail-clamp-text'); + + // Grab the sibling element to control the expand state + var expandButton = overviewElemnt.parentElement.querySelector('.overview-expand'); - for (var j = 0, length2 = anchors.length; j < length2; j++) { - anchors[j].setAttribute("target", "_blank"); + // Detect if we have overflow of text. Based on this StackOverflow answer + // https://stackoverflow.com/a/35157976 + if (Math.abs(overviewElemnt.scrollHeight - overviewElemnt.offsetHeight) > 2) { + expandButton.classList.remove('hide'); + } else { + expandButton.classList.add('hide'); + } + + expandButton.addEventListener('click', toggleLineClamp.bind(null, overviewElemnt)); + + for (const anchor of overviewElemnt.querySelectorAll('a')) { + anchor.setAttribute('target', '_blank'); } } else { - elem.innerHTML = ""; - elem.classList.add("hide"); + overviewElemnt.innerHTML = ''; + overviewElemnt.classList.add('hide'); } } } - function renderGenres(page, item, context) { - context = context || inferContext(item); - var type; + function renderGenres(page, item, context = inferContext(item)) { var genres = item.GenreItems || []; - - switch (context) { - case "music": - type = "MusicGenre"; - break; - - default: - type = "Genre"; - } + var type = context === 'music' ? 'MusicGenre' : 'Genre'; var html = genres.map(function (p) { return '' + p.Name + ""; - }).join(", "); + }) + '">' + p.Name + ''; + }).join(', '); - var genresLabel = page.querySelector(".genresLabel"); - genresLabel.innerHTML = globalize.translate(genres.length > 1 ? "Genres" : "Genre"); - var genresValue = page.querySelector(".genres"); + var genresLabel = page.querySelector('.genresLabel'); + genresLabel.innerHTML = globalize.translate(genres.length > 1 ? 'Genres' : 'Genre'); + var genresValue = page.querySelector('.genres'); genresValue.innerHTML = html; - var genresGroup = page.querySelector(".genresGroup"); + var genresGroup = page.querySelector('.genresGroup'); if (genres.length) { - genresGroup.classList.remove("hide"); + genresGroup.classList.remove('hide'); } else { - genresGroup.classList.add("hide"); + genresGroup.classList.add('hide'); } } - function renderDirector(page, item, context) { - var directors = (item.People || []).filter(function (p) { - return "Director" === p.Type; + function renderWriter(page, item, context) { + var writers = (item.People || []).filter(function (person) { + return person.Type === 'Writer'; }); - var html = directors.map(function (p) { + + var html = writers.map(function (person) { return '' + p.Name + ""; - }).join(", "); + }) + '">' + person.Name + ''; + }).join(', '); - var directorsLabel = page.querySelector(".directorsLabel"); - directorsLabel.innerHTML = globalize.translate(directors.length > 1 ? "Directors" : "Director"); - var directorsValue = page.querySelector(".directors"); - directorsValue.innerHTML = html; + var writersLabel = page.querySelector('.writersLabel'); + writersLabel.innerHTML = globalize.translate(writers.length > 1 ? 'Writers' : 'Writer'); + var writersValue = page.querySelector('.writers'); + writersValue.innerHTML = html; - var directorsGroup = page.querySelector(".directorsGroup"); - if (directors.length) { - directorsGroup.classList.remove("hide"); + var writersGroup = page.querySelector('.writersGroup'); + if (writers.length) { + writersGroup.classList.remove('hide'); } else { - directorsGroup.classList.add("hide"); + writersGroup.classList.add('hide'); } } - function renderDetails(page, item, apiClient, context, isStatic) { - renderSimilarItems(page, item, context); - renderMoreFromSeason(page, item, apiClient); - renderMoreFromArtist(page, item, apiClient); - renderDirector(page, item, context); - renderGenres(page, item, context); - renderChannelGuide(page, apiClient, item); - var taglineElement = page.querySelector(".tagline"); + function renderDirector(page, item, context) { + var directors = (item.People || []).filter(function (person) { + return person.Type === 'Director'; + }); - if (item.Taglines && item.Taglines.length) { - taglineElement.classList.remove("hide"); - taglineElement.innerHTML = item.Taglines[0]; + var html = directors.map(function (person) { + return '' + person.Name + ''; + }).join(', '); + + var directorsLabel = page.querySelector('.directorsLabel'); + directorsLabel.innerHTML = globalize.translate(directors.length > 1 ? 'Directors' : 'Director'); + var directorsValue = page.querySelector('.directors'); + directorsValue.innerHTML = html; + + var directorsGroup = page.querySelector('.directorsGroup'); + if (directors.length) { + directorsGroup.classList.remove('hide'); } else { - taglineElement.classList.add("hide"); + directorsGroup.classList.add('hide'); } + } - var overview = page.querySelector(".overview"); - var externalLinksElem = page.querySelector(".itemExternalLinks"); - - renderOverview([overview], item); - var i; - var itemMiscInfo; - itemMiscInfo = page.querySelectorAll(".itemMiscInfo-primary"); + function renderMiscInfo(page, item) { + const primaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary'); - for (i = 0; i < itemMiscInfo.length; i++) { - mediaInfo.fillPrimaryMediaInfo(itemMiscInfo[i], item, { + for (const miscInfo of primaryItemMiscInfo) { + mediaInfo.fillPrimaryMediaInfo(miscInfo, item, { interactive: true, episodeTitle: false, subtitles: false }); - if (itemMiscInfo[i].innerHTML && "SeriesTimer" !== item.Type) { - itemMiscInfo[i].classList.remove("hide"); + if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) { + miscInfo.classList.remove('hide'); } else { - itemMiscInfo[i].classList.add("hide"); + miscInfo.classList.add('hide'); } } - itemMiscInfo = page.querySelectorAll(".itemMiscInfo-secondary"); + const secondaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary'); - for (i = 0; i < itemMiscInfo.length; i++) { - mediaInfo.fillSecondaryMediaInfo(itemMiscInfo[i], item, { + for (const miscInfo of secondaryItemMiscInfo) { + mediaInfo.fillSecondaryMediaInfo(miscInfo, item, { interactive: true }); - if (itemMiscInfo[i].innerHTML && "SeriesTimer" !== item.Type) { - itemMiscInfo[i].classList.remove("hide"); + if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) { + miscInfo.classList.remove('hide'); } else { - itemMiscInfo[i].classList.add("hide"); + miscInfo.classList.add('hide'); } } + } + + function renderTagline(page, item) { + var taglineElement = page.querySelector('.tagline'); + if (item.Taglines && item.Taglines.length) { + taglineElement.classList.remove('hide'); + taglineElement.innerHTML = item.Taglines[0]; + } else { + taglineElement.classList.add('hide'); + } + } + + function renderDetails(page, item, apiClient, context, isStatic) { + renderSimilarItems(page, item, context); + renderMoreFromSeason(page, item, apiClient); + renderMoreFromArtist(page, item, apiClient); + renderDirector(page, item, context); + renderWriter(page, item, context); + renderGenres(page, item, context); + renderChannelGuide(page, apiClient, item); + renderTagline(page, item); + renderOverview(page, item); + renderMiscInfo(page, item); reloadUserDataButtons(page, item); - renderLinks(externalLinksElem, item); + renderLinks(page, item); renderTags(page, item); renderSeriesAirTime(page, item, isStatic); } @@ -1124,7 +1040,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti scrollX = enableScrollX(); } - return scrollX ? "overflowPortrait" : "portrait"; + return scrollX ? 'overflowPortrait' : 'portrait'; } function getSquareShape(scrollX) { @@ -1132,35 +1048,35 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti scrollX = enableScrollX(); } - return scrollX ? "overflowSquare" : "square"; + return scrollX ? 'overflowSquare' : 'square'; } function renderMoreFromSeason(view, item, apiClient) { - var section = view.querySelector(".moreFromSeasonSection"); + var section = view.querySelector('.moreFromSeasonSection'); if (section) { - if ("Episode" !== item.Type || !item.SeasonId || !item.SeriesId) { - return void section.classList.add("hide"); + if ('Episode' !== item.Type || !item.SeasonId || !item.SeriesId) { + return void section.classList.add('hide'); } var userId = apiClient.getCurrentUserId(); apiClient.getEpisodes(item.SeriesId, { SeasonId: item.SeasonId, UserId: userId, - Fields: "ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount" + Fields: 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount' }).then(function (result) { if (result.Items.length < 2) { - return void section.classList.add("hide"); + return void section.classList.add('hide'); } - section.classList.remove("hide"); - section.querySelector("h2").innerHTML = globalize.translate("MoreFromValue", item.SeasonName); - var itemsContainer = section.querySelector(".itemsContainer"); + section.classList.remove('hide'); + section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.SeasonName); + var itemsContainer = section.querySelector('.itemsContainer'); cardBuilder.buildCards(result.Items, { parentContainer: section, itemsContainer: itemsContainer, - shape: "autooverflow", - sectionTitleTagName: "h2", + shape: 'autooverflow', + sectionTitleTagName: 'h2', scalable: true, showTitle: true, overlayText: false, @@ -1172,7 +1088,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if (card) { setTimeout(function () { - section.querySelector(".emby-scroller").toStart(card.previousSibling || card, true); + section.querySelector('.emby-scroller').toStart(card.previousSibling || card, true); }, 100); } }); @@ -1180,28 +1096,28 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function renderMoreFromArtist(view, item, apiClient) { - var section = view.querySelector(".moreFromArtistSection"); + var section = view.querySelector('.moreFromArtistSection'); if (section) { - if ("MusicArtist" === item.Type) { - if (!apiClient.isMinServerVersion("3.4.1.19")) { - return void section.classList.add("hide"); + if ('MusicArtist' === item.Type) { + if (!apiClient.isMinServerVersion('3.4.1.19')) { + return void section.classList.add('hide'); } - } else if ("MusicAlbum" !== item.Type || !item.AlbumArtists || !item.AlbumArtists.length) { - return void section.classList.add("hide"); + } else if ('MusicAlbum' !== item.Type || !item.AlbumArtists || !item.AlbumArtists.length) { + return void section.classList.add('hide'); } var query = { - IncludeItemTypes: "MusicAlbum", + IncludeItemTypes: 'MusicAlbum', Recursive: true, ExcludeItemIds: item.Id, - SortBy: "ProductionYear,SortName", - SortOrder: "Descending" + SortBy: 'ProductionYear,SortName', + SortOrder: 'Descending' }; - if ("MusicArtist" === item.Type) { + if ('MusicArtist' === item.Type) { query.ContributingArtistIds = item.Id; - } else if (apiClient.isMinServerVersion("3.4.1.18")) { + } else if (apiClient.isMinServerVersion('3.4.1.18')) { query.AlbumArtistIds = item.AlbumArtists[0].Id; } else { query.ArtistIds = item.AlbumArtists[0].Id; @@ -1209,24 +1125,24 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) { if (!result.Items.length) { - return void section.classList.add("hide"); + return void section.classList.add('hide'); } - section.classList.remove("hide"); + section.classList.remove('hide'); - if ("MusicArtist" === item.Type) { - section.querySelector("h2").innerHTML = globalize.translate("HeaderAppearsOn"); + if ('MusicArtist' === item.Type) { + section.querySelector('h2').innerHTML = globalize.translate('HeaderAppearsOn'); } else { - section.querySelector("h2").innerHTML = globalize.translate("MoreFromValue", item.AlbumArtists[0].Name); + section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.AlbumArtists[0].Name); } cardBuilder.buildCards(result.Items, { parentContainer: section, - itemsContainer: section.querySelector(".itemsContainer"), - shape: "autooverflow", - sectionTitleTagName: "h2", + itemsContainer: section.querySelector('.itemsContainer'), + shape: 'autooverflow', + sectionTitleTagName: 'h2', scalable: true, - coverImage: "MusicArtist" === item.Type || "MusicAlbum" === item.Type, + coverImage: 'MusicArtist' === item.Type || 'MusicAlbum' === item.Type, showTitle: true, showParentTitle: false, centerText: true, @@ -1239,47 +1155,47 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function renderSimilarItems(page, item, context) { - var similarCollapsible = page.querySelector("#similarCollapsible"); + var similarCollapsible = page.querySelector('#similarCollapsible'); if (similarCollapsible) { - if ("Movie" != item.Type && "Trailer" != item.Type && "Series" != item.Type && "Program" != item.Type && "Recording" != item.Type && "MusicAlbum" != item.Type && "MusicArtist" != item.Type && "Playlist" != item.Type) { - return void similarCollapsible.classList.add("hide"); + if ('Movie' != item.Type && 'Trailer' != item.Type && 'Series' != item.Type && 'Program' != item.Type && 'Recording' != item.Type && 'MusicAlbum' != item.Type && 'MusicArtist' != item.Type && 'Playlist' != item.Type) { + return void similarCollapsible.classList.add('hide'); } - similarCollapsible.classList.remove("hide"); + similarCollapsible.classList.remove('hide'); var apiClient = connectionManager.getApiClient(item.ServerId); var options = { userId: apiClient.getCurrentUserId(), limit: 12, - fields: "PrimaryImageAspectRatio,UserData,CanDelete" + fields: 'PrimaryImageAspectRatio,UserData,CanDelete' }; - if ("MusicAlbum" == item.Type && item.AlbumArtists && item.AlbumArtists.length) { + if ('MusicAlbum' == item.Type && item.AlbumArtists && item.AlbumArtists.length) { options.ExcludeArtistIds = item.AlbumArtists[0].Id; } apiClient.getSimilarItems(item.Id, options).then(function (result) { if (!result.Items.length) { - return void similarCollapsible.classList.add("hide"); + return void similarCollapsible.classList.add('hide'); } - similarCollapsible.classList.remove("hide"); - var html = ""; + similarCollapsible.classList.remove('hide'); + var html = ''; html += cardBuilder.getCardsHtml({ items: result.Items, - shape: "autooverflow", - showParentTitle: "MusicAlbum" == item.Type, + shape: 'autooverflow', + showParentTitle: 'MusicAlbum' == item.Type, centerText: true, showTitle: true, context: context, lazy: true, showDetailsMenu: true, - coverImage: "MusicAlbum" == item.Type || "MusicArtist" == item.Type, + coverImage: 'MusicAlbum' == item.Type || 'MusicArtist' == item.Type, overlayPlayButton: true, overlayText: false, - showYear: "Movie" === item.Type || "Trailer" === item.Type || "Series" === item.Type + showYear: 'Movie' === item.Type || 'Trailer' === item.Type || 'Series' === item.Type }); - var similarContent = similarCollapsible.querySelector(".similarContent"); + var similarContent = similarCollapsible.querySelector('.similarContent'); similarContent.innerHTML = html; imageLoader.lazyChildren(similarContent); }); @@ -1287,52 +1203,52 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function renderSeriesAirTime(page, item, isStatic) { - var seriesAirTime = page.querySelector("#seriesAirTime"); - if ("Series" != item.Type) { - seriesAirTime.classList.add("hide"); + var seriesAirTime = page.querySelector('#seriesAirTime'); + if ('Series' != item.Type) { + seriesAirTime.classList.add('hide'); return; } - var html = ""; + var html = ''; if (item.AirDays && item.AirDays.length) { if (7 == item.AirDays.length) { - html += "daily"; + html += 'daily'; } else { html += item.AirDays.map(function (a) { - return a + "s"; - }).join(","); + return a + 's'; + }).join(','); } } if (item.AirTime) { - html += " at " + item.AirTime; + html += ' at ' + item.AirTime; } if (item.Studios.length) { if (isStatic) { - html += " on " + item.Studios[0].Name; + html += ' on ' + item.Studios[0].Name; } else { var context = inferContext(item); var href = appRouter.getRouteUrl(item.Studios[0], { context: context, - itemType: "Studio", + itemType: 'Studio', serverId: item.ServerId }); - html += ' on ' + item.Studios[0].Name + ""; + html += ' on ' + item.Studios[0].Name + ''; } } if (html) { - html = ("Ended" == item.Status ? "Aired " : "Airs ") + html; + html = ('Ended' == item.Status ? 'Aired ' : 'Airs ') + html; seriesAirTime.innerHTML = html; - seriesAirTime.classList.remove("hide"); + seriesAirTime.classList.remove('hide'); } else { - seriesAirTime.classList.add("hide"); + seriesAirTime.classList.add('hide'); } } function renderTags(page, item) { - var itemTags = page.querySelector(".itemTags"); + var itemTags = page.querySelector('.itemTags'); var tagElements = []; var tags = item.Tags || []; - if ("Program" === item.Type) { + if ('Program' === item.Type) { tags = []; } @@ -1341,93 +1257,92 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } if (tagElements.length) { - itemTags.innerHTML = globalize.translate("TagsValue", tagElements.join(", ")); - itemTags.classList.remove("hide"); + itemTags.innerHTML = globalize.translate('TagsValue', tagElements.join(', ')); + itemTags.classList.remove('hide'); } else { - itemTags.innerHTML = ""; - itemTags.classList.add("hide"); + itemTags.innerHTML = ''; + itemTags.classList.add('hide'); } } function renderChildren(page, item) { - var fields = "ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount"; + var fields = 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount'; var query = { ParentId: item.Id, Fields: fields }; - if ("BoxSet" !== item.Type) { - query.SortBy = "SortName"; + if ('BoxSet' !== item.Type) { + query.SortBy = 'SortName'; } var promise; var apiClient = connectionManager.getApiClient(item.ServerId); var userId = apiClient.getCurrentUserId(); - if ("Series" == item.Type) { + if ('Series' == item.Type) { promise = apiClient.getSeasons(item.Id, { userId: userId, Fields: fields }); - } else if ("Season" == item.Type) { - fields += ",Overview"; + } else if ('Season' == item.Type) { + fields += ',Overview'; promise = apiClient.getEpisodes(item.SeriesId, { seasonId: item.Id, userId: userId, Fields: fields }); - } else if ("MusicArtist" == item.Type) { - query.SortBy = "ProductionYear,SortName"; + } else if ('MusicArtist' == item.Type) { + query.SortBy = 'ProductionYear,SortName'; } promise = promise || apiClient.getItems(apiClient.getCurrentUserId(), query); promise.then(function (result) { - var html = ""; + var html = ''; var scrollX = false; var isList = false; - var childrenItemsContainer = page.querySelector(".childrenItemsContainer"); + var childrenItemsContainer = page.querySelector('.childrenItemsContainer'); - if ("MusicAlbum" == item.Type) { + if ('MusicAlbum' == item.Type) { html = listView.getListViewHtml({ items: result.Items, smallIcon: true, showIndex: true, - index: "disc", + index: 'disc', showIndexNumberLeft: true, playFromHere: true, - action: "playallfromhere", + action: 'playallfromhere', image: false, - artist: "auto", - containerAlbumArtists: item.AlbumArtists, - addToListButton: true + artist: 'auto', + containerAlbumArtists: item.AlbumArtists }); isList = true; - } else if ("Series" == item.Type) { + } else if ('Series' == item.Type) { scrollX = enableScrollX(); html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "overflowPortrait", + shape: 'overflowPortrait', showTitle: true, centerText: true, lazy: true, overlayPlayButton: true, allowBottomPadding: !scrollX }); - } else if ("Season" == item.Type || "Episode" == item.Type) { - if ("Episode" !== item.Type) { + } else if ('Season' == item.Type || 'Episode' == item.Type) { + if ('Episode' !== item.Type) { isList = true; } - scrollX = "Episode" == item.Type; - if (result.Items.length < 2 && "Episode" === item.Type) { + scrollX = 'Episode' == item.Type; + if (result.Items.length < 2 && 'Episode' === item.Type) { return; } - if ("Episode" === item.Type) { + if ('Episode' === item.Type) { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "overflowBackdrop", + shape: 'overflowBackdrop', showTitle: true, - displayAsSpecial: "Season" == item.Type && item.IndexNumber, + displayAsSpecial: 'Season' == item.Type && item.IndexNumber, playFromHere: true, overlayText: true, lazy: true, @@ -1436,94 +1351,98 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti allowBottomPadding: !scrollX, includeParentInfoInTitle: false }); - } else if ("Season" === item.Type) { + } else if ('Season' === item.Type) { html = listView.getListViewHtml({ items: result.Items, showIndexNumber: false, enableOverview: true, - imageSize: "large", + enablePlayedButton: layoutManager.mobile ? false : true, + infoButton: layoutManager.mobile ? false : true, + imageSize: 'large', enableSideMediaInfo: false, highlight: false, - action: layoutManager.tv ? "resume" : "none", - infoButton: true, + action: !layoutManager.desktop ? 'link' : 'none', imagePlayButton: true, includeParentInfoInTitle: false }); } } - if ("BoxSet" !== item.Type) { - page.querySelector("#childrenCollapsible").classList.remove("hide"); + if ('BoxSet' !== item.Type) { + page.querySelector('#childrenCollapsible').classList.remove('hide'); } if (scrollX) { - childrenItemsContainer.classList.add("scrollX"); - childrenItemsContainer.classList.add("hiddenScrollX"); - childrenItemsContainer.classList.remove("vertical-wrap"); - childrenItemsContainer.classList.remove("vertical-list"); + childrenItemsContainer.classList.add('scrollX'); + childrenItemsContainer.classList.add('hiddenScrollX'); + childrenItemsContainer.classList.remove('vertical-wrap'); + childrenItemsContainer.classList.remove('vertical-list'); } else { - childrenItemsContainer.classList.remove("scrollX"); - childrenItemsContainer.classList.remove("hiddenScrollX"); - childrenItemsContainer.classList.remove("smoothScrollX"); + childrenItemsContainer.classList.remove('scrollX'); + childrenItemsContainer.classList.remove('hiddenScrollX'); + childrenItemsContainer.classList.remove('smoothScrollX'); if (isList) { - childrenItemsContainer.classList.add("vertical-list"); - childrenItemsContainer.classList.remove("vertical-wrap"); + childrenItemsContainer.classList.add('vertical-list'); + childrenItemsContainer.classList.remove('vertical-wrap'); } else { - childrenItemsContainer.classList.add("vertical-wrap"); - childrenItemsContainer.classList.remove("vertical-list"); + childrenItemsContainer.classList.add('vertical-wrap'); + childrenItemsContainer.classList.remove('vertical-list'); } } + if (layoutManager.mobile) { + childrenItemsContainer.classList.remove('padded-right'); + } childrenItemsContainer.innerHTML = html; imageLoader.lazyChildren(childrenItemsContainer); - if ("BoxSet" == item.Type) { + if ('BoxSet' == item.Type) { var collectionItemTypes = [{ - name: globalize.translate("HeaderVideos"), - mediaType: "Video" + name: globalize.translate('HeaderVideos'), + mediaType: 'Video' }, { - name: globalize.translate("HeaderSeries"), - type: "Series" + name: globalize.translate('HeaderSeries'), + type: 'Series' }, { - name: globalize.translate("HeaderAlbums"), - type: "MusicAlbum" + name: globalize.translate('HeaderAlbums'), + type: 'MusicAlbum' }, { - name: globalize.translate("HeaderBooks"), - type: "Book" + name: globalize.translate('HeaderBooks'), + type: 'Book' }]; renderCollectionItems(page, item, collectionItemTypes, result.Items); } }); - if ("Season" == item.Type) { - page.querySelector("#childrenTitle").innerHTML = globalize.translate("HeaderEpisodes"); - } else if ("Series" == item.Type) { - page.querySelector("#childrenTitle").innerHTML = globalize.translate("HeaderSeasons"); - } else if ("MusicAlbum" == item.Type) { - page.querySelector("#childrenTitle").innerHTML = globalize.translate("HeaderTracks"); + if ('Season' == item.Type) { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderEpisodes'); + } else if ('Series' == item.Type) { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderSeasons'); + } else if ('MusicAlbum' == item.Type) { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderTracks'); } else { - page.querySelector("#childrenTitle").innerHTML = globalize.translate("HeaderItems"); + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderItems'); } - if ("MusicAlbum" == item.Type || "Season" == item.Type) { - page.querySelector(".childrenSectionHeader").classList.add("hide"); - page.querySelector("#childrenCollapsible").classList.add("verticalSection-extrabottompadding"); + if ('MusicAlbum' == item.Type || 'Season' == item.Type) { + page.querySelector('.childrenSectionHeader').classList.add('hide'); + page.querySelector('#childrenCollapsible').classList.add('verticalSection-extrabottompadding'); } else { - page.querySelector(".childrenSectionHeader").classList.remove("hide"); + page.querySelector('.childrenSectionHeader').classList.remove('hide'); } } function renderItemsByName(page, item) { - require("scripts/itembynamedetailpage".split(","), function () { + require('scripts/itembynamedetailpage'.split(','), function () { window.ItemsByName.renderItems(page, item); }); } function renderPlaylistItems(page, item) { - require("scripts/playlistedit".split(","), function () { + require('scripts/playlistedit'.split(','), function () { PlaylistViewer.render(page, item); }); } function renderProgramsForChannel(page, result) { - var html = ""; + var html = ''; var currentItems = []; var currentStartDate = null; @@ -1535,10 +1454,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if (currentItems.length) { html += '
'; html += '

' + datetime.toLocaleDateString(currentStartDate, { - weekday: "long", - month: "long", - day: "numeric" - }) + "

"; + weekday: 'long', + month: 'long', + day: 'numeric' + }) + ''; html += '
' + listView.getListViewHtml({ items: currentItems, enableUserDataButtons: false, @@ -1547,7 +1466,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti showProgramTime: true, mediaInfo: false, parentTitleWithTitle: true - }) + "
"; + }) + '
'; } currentStartDate = itemStartDate; @@ -1560,10 +1479,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if (currentItems.length) { html += '
'; html += '

' + datetime.toLocaleDateString(currentStartDate, { - weekday: "long", - month: "long", - day: "numeric" - }) + "

"; + weekday: 'long', + month: 'long', + day: 'numeric' + }) + ''; html += '
' + listView.getListViewHtml({ items: currentItems, enableUserDataButtons: false, @@ -1572,20 +1491,20 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti showProgramTime: true, mediaInfo: false, parentTitleWithTitle: true - }) + "
"; + }) + '
'; } - page.querySelector(".programGuide").innerHTML = html; + page.querySelector('.programGuide').innerHTML = html; } function renderChannelGuide(page, apiClient, item) { - if ("TvChannel" === item.Type) { - page.querySelector(".programGuideSection").classList.remove("hide"); + if ('TvChannel' === item.Type) { + page.querySelector('.programGuideSection').classList.remove('hide'); apiClient.getLiveTvPrograms({ ChannelIds: item.Id, UserId: apiClient.getCurrentUserId(), HasAired: false, - SortBy: "StartDate", + SortBy: 'StartDate', EnableTotalRecordCount: false, EnableImages: false, ImageTypeLimit: 0, @@ -1601,7 +1520,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti apiClient.getLiveTvPrograms({ UserId: apiClient.getCurrentUserId(), HasAired: false, - SortBy: "StartDate", + SortBy: 'StartDate', EnableTotalRecordCount: false, EnableImages: false, ImageTypeLimit: 0, @@ -1610,12 +1529,12 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti LibrarySeriesId: item.Id }).then(function (result) { if (result.Items.length) { - page.querySelector("#seriesScheduleSection").classList.remove("hide"); + page.querySelector('#seriesScheduleSection').classList.remove('hide'); } else { - page.querySelector("#seriesScheduleSection").classList.add("hide"); + page.querySelector('#seriesScheduleSection').classList.add('hide'); } - page.querySelector("#seriesScheduleList").innerHTML = listView.getListViewHtml({ + page.querySelector('#seriesScheduleList').innerHTML = listView.getListViewHtml({ items: result.Items, enableUserDataButtons: false, showParentTitle: false, @@ -1624,27 +1543,27 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti mediaInfo: false, showTitle: true, moreButton: false, - action: "programdialog" + action: 'programdialog' }); loading.hide(); }); } function inferContext(item) { - if ("Movie" === item.Type || "BoxSet" === item.Type) { - return "movies"; + if ('Movie' === item.Type || 'BoxSet' === item.Type) { + return 'movies'; } - if ("Series" === item.Type || "Season" === item.Type || "Episode" === item.Type) { - return "tvshows"; + if ('Series' === item.Type || 'Season' === item.Type || 'Episode' === item.Type) { + return 'tvshows'; } - if ("MusicArtist" === item.Type || "MusicAlbum" === item.Type || "Audio" === item.Type || "AudioBook" === item.Type) { - return "music"; + if ('MusicArtist' === item.Type || 'MusicAlbum' === item.Type || 'Audio' === item.Type || 'AudioBook' === item.Type) { + return 'music'; } - if ("Program" === item.Type) { - return "livetv"; + if ('Program' === item.Type) { + return 'livetv'; } return null; @@ -1673,12 +1592,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function renderCollectionItems(page, parentItem, types, items) { - page.querySelector(".collectionItems").innerHTML = ""; - var i; - var length; + page.querySelector('.collectionItems').classList.remove('hide'); + page.querySelector('.collectionItems').innerHTML = ''; - for (i = 0, length = types.length; i < length; i++) { - var type = types[i]; + for (const type of types) { var typeItems = filterItemsByCollectionItemType(items, type); if (typeItems.length) { @@ -1687,7 +1604,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } var otherType = { - name: globalize.translate("HeaderOtherItems") + name: globalize.translate('HeaderOtherItems') }; var otherTypeItems = items.filter(function (curr) { return !types.filter(function (t) { @@ -1701,49 +1618,49 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if (!items.length) { renderCollectionItemType(page, parentItem, { - name: globalize.translate("HeaderItems") + name: globalize.translate('HeaderItems') }, items); } - var containers = page.querySelectorAll(".collectionItemsContainer"); + var containers = page.querySelectorAll('.collectionItemsContainer'); var notifyRefreshNeeded = function () { renderChildren(page, parentItem); }; - for (i = 0, length = containers.length; i < length; i++) { - containers[i].notifyRefreshNeeded = notifyRefreshNeeded; + for (const container of containers) { + container.notifyRefreshNeeded = notifyRefreshNeeded; } // if nothing in the collection can be played hide play and shuffle buttons if (!canPlaySomeItemInCollection(items)) { - hideAll(page, "btnPlay", false); - hideAll(page, "btnShuffle", false); + hideAll(page, 'btnPlay', false); + hideAll(page, 'btnShuffle', false); } // HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem // FIXME: Sometimes focus does not move until all (?) sections are loaded - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); } function renderCollectionItemType(page, parentItem, type, items) { - var html = ""; + var html = ''; html += '
'; html += '
'; html += '

'; - html += "" + type.name + ""; - html += "

"; - html += ''; - html += "
"; + html += '' + type.name + ''; + html += ''; + html += ''; + html += '
'; html += '
'; - var shape = "MusicAlbum" == type.type ? getSquareShape(false) : getPortraitShape(false); + var shape = 'MusicAlbum' == type.type ? getSquareShape(false) : getPortraitShape(false); html += cardBuilder.getCardsHtml({ items: items, shape: shape, showTitle: true, - showYear: "Video" === type.mediaType || "Series" === type.type, + showYear: 'Video' === type.mediaType || 'Series' === type.type, centerText: true, lazy: true, showDetailsMenu: true, @@ -1752,16 +1669,16 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti showRemoveFromCollection: true, collectionId: parentItem.Id }); - html += "
"; - html += "
"; - var collectionItems = page.querySelector(".collectionItems"); - collectionItems.insertAdjacentHTML("beforeend", html); + html += '
'; + html += '
'; + var collectionItems = page.querySelector('.collectionItems'); + collectionItems.insertAdjacentHTML('beforeend', html); imageLoader.lazyChildren(collectionItems); - collectionItems.querySelector(".btnAddToCollection").addEventListener("click", function () { - require(["alert"], function (alert) { + collectionItems.querySelector('.btnAddToCollection').addEventListener('click', function () { + require(['alert'], function (alert) { alert({ - text: globalize.translate("AddItemToCollectionHelp"), - html: globalize.translate("AddItemToCollectionHelp") + '

' + globalize.translate("ButtonLearnMore") + "" + text: globalize.translate('AddItemToCollectionHelp'), + html: globalize.translate('AddItemToCollectionHelp') + '

' + globalize.translate('ButtonLearnMore') + '' }); }); }); @@ -1769,20 +1686,20 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function renderMusicVideos(page, item, user) { connectionManager.getApiClient(item.ServerId).getItems(user.Id, { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "MusicVideo", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'MusicVideo', Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount', AlbumIds: item.Id }).then(function (result) { if (result.Items.length) { - page.querySelector("#musicVideosCollapsible").classList.remove("hide"); - var musicVideosContent = page.querySelector(".musicVideosContent"); + page.querySelector('#musicVideosCollapsible').classList.remove('hide'); + var musicVideosContent = page.querySelector('.musicVideosContent'); musicVideosContent.innerHTML = getVideosHtml(result.Items, user); imageLoader.lazyChildren(musicVideosContent); } else { - page.querySelector("#musicVideosCollapsible").classList.add("hide"); + page.querySelector('#musicVideosCollapsible').classList.add('hide'); } }); } @@ -1790,12 +1707,12 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function renderAdditionalParts(page, item, user) { connectionManager.getApiClient(item.ServerId).getAdditionalVideoParts(user.Id, item.Id).then(function (result) { if (result.Items.length) { - page.querySelector("#additionalPartsCollapsible").classList.remove("hide"); - var additionalPartsContent = page.querySelector("#additionalPartsContent"); + page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); + var additionalPartsContent = page.querySelector('#additionalPartsContent'); additionalPartsContent.innerHTML = getVideosHtml(result.Items, user); imageLoader.lazyChildren(additionalPartsContent); } else { - page.querySelector("#additionalPartsCollapsible").classList.add("hide"); + page.querySelector('#additionalPartsCollapsible').classList.add('hide'); } }); } @@ -1804,34 +1721,35 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti var chapters = item.Chapters || []; if (chapters.length && !chapters[0].ImageTag && (chapters = []), chapters.length) { - page.querySelector("#scenesCollapsible").classList.remove("hide"); - var scenesContent = page.querySelector("#scenesContent"); + page.querySelector('#scenesCollapsible').classList.remove('hide'); + var scenesContent = page.querySelector('#scenesContent'); - require(["chaptercardbuilder"], function (chaptercardbuilder) { + require(['chaptercardbuilder'], function (chaptercardbuilder) { chaptercardbuilder.buildChapterCards(item, chapters, { itemsContainer: scenesContent, - backdropShape: "overflowBackdrop", - squareShape: "overflowSquare" + backdropShape: 'overflowBackdrop', + squareShape: 'overflowSquare', + imageBlurhashes: item.ImageBlurHashes }); }); } else { - page.querySelector("#scenesCollapsible").classList.add("hide"); + page.querySelector('#scenesCollapsible').classList.add('hide'); } } function getVideosHtml(items, user, limit, moreButtonClass) { var html = cardBuilder.getCardsHtml({ items: items, - shape: "auto", + shape: 'auto', showTitle: true, - action: "play", + action: 'play', overlayText: false, centerText: true, showRuntime: true }); if (limit && items.length > limit) { - html += '

"; + html += '

'; } return html; @@ -1839,30 +1757,31 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function renderSpecials(page, item, user, limit) { connectionManager.getApiClient(item.ServerId).getSpecialFeatures(user.Id, item.Id).then(function (specials) { - var specialsContent = page.querySelector("#specialsContent"); - specialsContent.innerHTML = getVideosHtml(specials, user, limit, "moreSpecials"); + var specialsContent = page.querySelector('#specialsContent'); + specialsContent.innerHTML = getVideosHtml(specials, user, limit, 'moreSpecials'); imageLoader.lazyChildren(specialsContent); }); } function renderCast(page, item) { var people = (item.People || []).filter(function (p) { - return "Director" !== p.Type; + return p.Type === 'Actor'; }); if (!people.length) { - return void page.querySelector("#castCollapsible").classList.add("hide"); + return void page.querySelector('#castCollapsible').classList.add('hide'); } - page.querySelector("#castCollapsible").classList.remove("hide"); - var castContent = page.querySelector("#castContent"); + page.querySelector('#castCollapsible').classList.remove('hide'); + var castContent = page.querySelector('#castContent'); - require(["peoplecardbuilder"], function (peoplecardbuilder) { + require(['peoplecardbuilder'], function (peoplecardbuilder) { peoplecardbuilder.buildPeopleCards(people, { itemsContainer: castContent, coverImage: true, serverId: item.ServerId, - shape: "overflowPortrait" + shape: 'overflowPortrait', + imageBlurhashes: item.ImageBlurHashes }); }); } @@ -1875,12 +1794,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function bindAll(view, selector, eventName, fn) { - var i; - var length; var elems = view.querySelectorAll(selector); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); + for (const elem of elems) { + elem.addEventListener(eventName, fn); } } @@ -1893,23 +1810,24 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti return function (view, params) { function reload(instance, page, params) { loading.show(); + var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient; - var promises = [getPromise(apiClient, params), apiClient.getCurrentUser()]; - Promise.all(promises).then(function (responses) { - var item = responses[0]; - var user = responses[1]; + + Promise.all([getPromise(apiClient, params), apiClient.getCurrentUser()]).then(([item, user]) => { currentItem = item; reloadFromItem(instance, page, params, item, user); + }).catch((error) => { + console.error('failed to get item or current user: ', error); }); } function splitVersions(instance, page, apiClient, params) { - require(["confirm"], function (confirm) { - confirm("Are you sure you wish to split the media sources into separate items?", "Split Media Apart").then(function () { + require(['confirm'], function (confirm) { + confirm('Are you sure you wish to split the media sources into separate items?', 'Split Media Apart').then(function () { loading.show(); apiClient.ajax({ - type: "DELETE", - url: apiClient.getUrl("Videos/" + params.id + "/AlternateSources") + type: 'DELETE', + url: apiClient.getUrl('Videos/' + params.id + '/AlternateSources') }).then(function () { loading.hide(); reload(instance, page, params); @@ -1919,12 +1837,12 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } function getPlayOptions(startPosition) { - var audioStreamIndex = view.querySelector(".selectAudio").value || null; + var audioStreamIndex = view.querySelector('.selectAudio').value || null; return { startPositionTicks: startPosition, - mediaSourceId: view.querySelector(".selectSource").value, + mediaSourceId: view.querySelector('.selectSource').value, audioStreamIndex: audioStreamIndex, - subtitleStreamIndex: view.querySelector(".selectSubtitles").value + subtitleStreamIndex: view.querySelector('.selectSubtitles').value }; } @@ -1941,7 +1859,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function playCurrentItem(button, mode) { var item = currentItem; - if ("Program" === item.Type) { + if ('Program' === item.Type) { var apiClient = connectionManager.getApiClient(item.ServerId); return void apiClient.getLiveTvChannel(item.ChannelId, apiClient.getCurrentUserId()).then(function (channel) { playbackManager.play({ @@ -1950,11 +1868,11 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti }); } - playItem(item, item.UserData && "resume" === mode ? item.UserData.PlaybackPositionTicks : 0); + playItem(item, item.UserData && mode === 'resume' ? item.UserData.PlaybackPositionTicks : 0); } function onPlayClick() { - playCurrentItem(this, this.getAttribute("data-mode")); + playCurrentItem(this, this.getAttribute('data-mode')); } function onInstantMixClick() { @@ -1965,25 +1883,16 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti playbackManager.shuffle(currentItem); } - function onDeleteClick() { - require(["deleteHelper"], function (deleteHelper) { - deleteHelper.deleteItem({ - item: currentItem, - navigate: true - }); - }); - } - function onCancelSeriesTimerClick() { - require(["recordingHelper"], function (recordingHelper) { + require(['recordingHelper'], function (recordingHelper) { recordingHelper.cancelSeriesTimerWithConfirmation(currentItem.Id, currentItem.ServerId).then(function () { - Dashboard.navigate("livetv.html"); + Dashboard.navigate('livetv.html'); }); }); } function onCancelTimerClick() { - require(["recordingHelper"], function (recordingHelper) { + require(['recordingHelper'], function (recordingHelper) { recordingHelper.cancelTimer(connectionManager.getApiClient(currentItem.ServerId), currentItem.TimerId).then(function () { reload(self, view, params); }); @@ -2025,7 +1934,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function editImages() { return new Promise(function (resolve, reject) { - require(["imageEditor"], function (imageEditor) { + require(['imageEditor'], function (imageEditor) { imageEditor.show({ itemId: currentItem.Id, serverId: currentItem.ServerId @@ -2037,7 +1946,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti function onWebSocketMessage(e, data) { var msg = data; - if ("UserDataChanged" === msg.MessageType && currentItem && msg.Data.UserId == apiClient.getCurrentUserId()) { + if ('UserDataChanged' === msg.MessageType && currentItem && msg.Data.UserId == apiClient.getCurrentUserId()) { var key = currentItem.UserData.Key; var userData = msg.Data.UserDataList.filter(function (u) { return u.Key == key; @@ -2054,70 +1963,66 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti var currentItem; var self = this; var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient; - view.querySelectorAll(".btnPlay"); - bindAll(view, ".btnPlay", "click", onPlayClick); - bindAll(view, ".btnResume", "click", onPlayClick); - bindAll(view, ".btnInstantMix", "click", onInstantMixClick); - bindAll(view, ".btnShuffle", "click", onShuffleClick); - bindAll(view, ".btnPlayTrailer", "click", onPlayTrailerClick); - bindAll(view, ".btnCancelSeriesTimer", "click", onCancelSeriesTimerClick); - bindAll(view, ".btnCancelTimer", "click", onCancelTimerClick); - bindAll(view, ".btnDeleteItem", "click", onDeleteClick); - bindAll(view, ".btnDownload", "click", onDownloadClick); - view.querySelector(".btnMoreCommands i").innerHTML = ""; - view.querySelector(".trackSelections").addEventListener("submit", onTrackSelectionsSubmit); - view.querySelector(".btnSplitVersions").addEventListener("click", function () { + view.querySelectorAll('.btnPlay'); + bindAll(view, '.btnPlay', 'click', onPlayClick); + bindAll(view, '.btnResume', 'click', onPlayClick); + bindAll(view, '.btnInstantMix', 'click', onInstantMixClick); + bindAll(view, '.btnShuffle', 'click', onShuffleClick); + bindAll(view, '.btnPlayTrailer', 'click', onPlayTrailerClick); + bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick); + bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick); + bindAll(view, '.btnDownload', 'click', onDownloadClick); + view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit); + view.querySelector('.btnSplitVersions').addEventListener('click', function () { splitVersions(self, view, apiClient, params); }); - bindAll(view, ".btnMoreCommands", "click", onMoreCommandsClick); - view.querySelector(".selectSource").addEventListener("change", function () { + bindAll(view, '.btnMoreCommands', 'click', onMoreCommandsClick); + view.querySelector('.selectSource').addEventListener('change', function () { renderVideoSelections(view, self._currentPlaybackMediaSources); renderAudioSelections(view, self._currentPlaybackMediaSources); renderSubtitleSelections(view, self._currentPlaybackMediaSources); }); - view.addEventListener("click", function (e) { - if (dom.parentWithClass(e.target, "moreScenes")) { + view.addEventListener('click', function (e) { + if (dom.parentWithClass(e.target, 'moreScenes')) { renderScenes(view, currentItem); - } else if (dom.parentWithClass(e.target, "morePeople")) { + } else if (dom.parentWithClass(e.target, 'morePeople')) { renderCast(view, currentItem); - } else if (dom.parentWithClass(e.target, "moreSpecials")) { + } else if (dom.parentWithClass(e.target, 'moreSpecials')) { apiClient.getCurrentUser().then(function (user) { renderSpecials(view, currentItem, user); }); } }); - view.querySelector(".detailImageContainer").addEventListener("click", function (e) { - if (dom.parentWithClass(e.target, "itemDetailGalleryLink")) { + view.querySelector('.detailImageContainer').addEventListener('click', function (e) { + if (dom.parentWithClass(e.target, 'itemDetailGalleryLink')) { editImages().then(function () { reload(self, view, params); }); } }); - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { var page = this; - if (layoutManager.mobile) { - libraryMenu.setTransparentMenu(true); - } + libraryMenu.setTransparentMenu(true); if (e.detail.isRestored) { if (currentItem) { - setTitle(currentItem, connectionManager.getApiClient(currentItem.ServerId)); + Emby.Page.setTitle(''); renderTrackSelections(page, self, currentItem, true); } } else { reload(self, page, params); } - events.on(apiClient, "message", onWebSocketMessage); - events.on(playbackManager, "playerchange", onPlayerChange); + events.on(apiClient, 'message', onWebSocketMessage); + events.on(playbackManager, 'playerchange', onPlayerChange); }); - view.addEventListener("viewbeforehide", function () { - events.off(apiClient, "message", onWebSocketMessage); - events.off(playbackManager, "playerchange", onPlayerChange); + view.addEventListener('viewbeforehide', function () { + events.off(apiClient, 'message', onWebSocketMessage); + events.off(playbackManager, 'playerchange', onPlayerChange); libraryMenu.setTransparentMenu(false); }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { currentItem = null; self._currentPlaybackMediaSources = null; self.currentRecordingFields = null; diff --git a/src/controllers/librarydisplay.js b/src/controllers/librarydisplay.js deleted file mode 100644 index 55986a4b7d2..00000000000 --- a/src/controllers/librarydisplay.js +++ /dev/null @@ -1,71 +0,0 @@ -define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "emby-button"], function(globalize, loading, libraryMenu) { - "use strict"; - - function getTabs() { - return [{ - href: "library.html", - name: Globalize.translate("HeaderLibraries") - }, { - href: "librarydisplay.html", - name: Globalize.translate("TabDisplay") - }, { - href: "metadataimages.html", - name: Globalize.translate("TabMetadata") - }, { - href: "metadatanfo.html", - name: Globalize.translate("TabNfoSettings") - }] - } - - return function(view, params) { - 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) { - loadMetadataConfig(this, metadata) - }); - } - - function loadMetadataConfig(page, config) { - $("#selectDateAdded", page).val(config.UseFileCreationTimeForDateAdded ? "1" : "0"); - } - - view.querySelector("form").addEventListener("submit", function(e) { - loading.show(); - var 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 = "1" === $("#selectDateAdded", form).val(); - 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 ("Windows" === info.OperatingSystem) { - view.querySelector(".fldSaveMetadataHidden").classList.remove("hide"); - } else { - view.querySelector(".fldSaveMetadataHidden").classList.add("hide"); - } - }); - }); - } -}); diff --git a/src/controllers/list.js b/src/controllers/list.js index bcc38f27cf7..d05616ec9db 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1,15 +1,15 @@ -define(["globalize", "listView", "layoutManager", "userSettings", "focusManager", "cardBuilder", "loading", "connectionManager", "alphaNumericShortcuts", "scroller", "playbackManager", "alphaPicker", "emby-itemscontainer", "emby-scroller"], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, alphaPicker) { - "use strict"; +define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, alphaPicker) { + 'use strict'; function getInitialLiveTvQuery(instance, params) { var query = { UserId: connectionManager.getApiClient(params.serverId).getCurrentUserId(), StartIndex: 0, - Fields: "ChannelInfo,PrimaryImageAspectRatio", + Fields: 'ChannelInfo,PrimaryImageAspectRatio', Limit: 300 }; - if ("Recordings" === params.type) { + if ('Recordings' === params.type) { query.IsInProgress = false; } else { query.HasAired = false; @@ -19,39 +19,39 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" query.GenreIds = params.genreId; } - if ("true" === params.IsMovie) { + if ('true' === params.IsMovie) { query.IsMovie = true; - } else if ("false" === params.IsMovie) { + } else if ('false' === params.IsMovie) { query.IsMovie = false; } - if ("true" === params.IsSeries) { + if ('true' === params.IsSeries) { query.IsSeries = true; - } else if ("false" === params.IsSeries) { + } else if ('false' === params.IsSeries) { query.IsSeries = false; } - if ("true" === params.IsNews) { + if ('true' === params.IsNews) { query.IsNews = true; - } else if ("false" === params.IsNews) { + } else if ('false' === params.IsNews) { query.IsNews = false; } - if ("true" === params.IsSports) { + if ('true' === params.IsSports) { query.IsSports = true; - } else if ("false" === params.IsSports) { + } else if ('false' === params.IsSports) { query.IsSports = false; } - if ("true" === params.IsKids) { + if ('true' === params.IsKids) { query.IsKids = true; - } else if ("false" === params.IsKids) { + } else if ('false' === params.IsKids) { query.IsKids = false; } - if ("true" === params.IsAiring) { + if ('true' === params.IsAiring) { query.IsAiring = true; - } else if ("false" === params.IsAiring) { + } else if ('false' === params.IsAiring) { query.IsAiring = false; } @@ -66,29 +66,29 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" query.SortOrder = sortValues.sortOrder; } - query.Fields = query.Fields ? query.Fields + ",PrimaryImageAspectRatio" : "PrimaryImageAspectRatio"; + query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; query.ImageTypeLimit = 1; var hasFilters; var queryFilters = []; var filters = instance.getFilters(); if (filters.IsPlayed) { - queryFilters.push("IsPlayed"); + queryFilters.push('IsPlayed'); hasFilters = true; } if (filters.IsUnplayed) { - queryFilters.push("IsUnplayed"); + queryFilters.push('IsUnplayed'); hasFilters = true; } if (filters.IsFavorite) { - queryFilters.push("IsFavorite"); + queryFilters.push('IsFavorite'); hasFilters = true; } if (filters.IsResumable) { - queryFilters.push("IsResumable"); + queryFilters.push('IsResumable'); hasFilters = true; } @@ -147,7 +147,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" hasFilters = true; } - query.Filters = queryFilters.length ? queryFilters.join(",") : null; + query.Filters = queryFilters.length ? queryFilters.join(',') : null; instance.setFilterStatus(hasFilters); if (instance.alphaPicker) { @@ -158,8 +158,8 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } function setSortButtonIcon(btnSortIcon, icon) { - btnSortIcon.classList.remove("arrow_downward"); - btnSortIcon.classList.remove("arrow_upward"); + btnSortIcon.classList.remove('arrow_downward'); + btnSortIcon.classList.remove('arrow_upward'); btnSortIcon.classList.add(icon); } @@ -173,7 +173,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" for (var i = 0, length = options.length; i < length; i++) { if (sortBy === options[i].value) { - btnSortText.innerHTML = globalize.translate("SortByValue", options[i].name); + btnSortText.innerHTML = globalize.translate('SortByValue', options[i].name); break; } } @@ -181,18 +181,18 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var btnSortIcon = instance.btnSortIcon; if (btnSortIcon) { - setSortButtonIcon(btnSortIcon, "Descending" === values.sortOrder ? "arrow_downward" : "arrow_upward"); + setSortButtonIcon(btnSortIcon, 'Descending' === values.sortOrder ? 'arrow_downward' : 'arrow_upward'); } } } function updateItemsContainerForViewType(instance) { - if ("list" === instance.getViewSettings().imageType) { - instance.itemsContainer.classList.remove("vertical-wrap"); - instance.itemsContainer.classList.add("vertical-list"); + if ('list' === instance.getViewSettings().imageType) { + 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"); + instance.itemsContainer.classList.add('vertical-wrap'); + instance.itemsContainer.classList.remove('vertical-list'); } } @@ -207,12 +207,12 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" numItems = 100; } - if ("SortName" === values.sortBy && "Ascending" === values.sortOrder && numItems > 40) { - alphaPicker.classList.remove("hide"); - instance.itemsContainer.parentNode.classList.add("padded-right-withalphapicker"); + if ('SortName' === values.sortBy && 'Ascending' === values.sortOrder && numItems > 40) { + 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"); + alphaPicker.classList.add('hide'); + instance.itemsContainer.parentNode.classList.remove('padded-right-withalphapicker'); } } } @@ -222,25 +222,25 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var apiClient = connectionManager.getApiClient(params.serverId); instance.queryRecursive = false; - if ("Recordings" === params.type) { + if ('Recordings' === params.type) { return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params)); } - if ("Programs" === params.type) { - if ("true" === params.IsAiring) { + if ('Programs' === params.type) { + if ('true' === params.IsAiring) { return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params)); } return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params)); } - if ("nextup" === params.type) { + if ('nextup' === params.type) { return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { Limit: limit, - Fields: "PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo', UserId: apiClient.getCurrentUserId(), ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + EnableImageTypes: 'Primary,Backdrop,Thumb', EnableTotalRecordCount: false, SortBy: sortBy })); @@ -248,57 +248,57 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" if (!item) { instance.queryRecursive = true; - var method = "getItems"; + var method = 'getItems'; - if ("MusicArtist" === params.type) { - method = "getArtists"; - } else if ("Person" === params.type) { - method = "getPeople"; + if ('MusicArtist' === params.type) { + method = 'getArtists'; + } else if ('Person' === params.type) { + method = 'getPeople'; } return apiClient[method](apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { StartIndex: startIndex, Limit: limit, - Fields: "PrimaryImageAspectRatio,SortName", + Fields: 'PrimaryImageAspectRatio,SortName', ImageTypeLimit: 1, - IncludeItemTypes: "MusicArtist" === params.type || "Person" === params.type ? null : params.type, + IncludeItemTypes: 'MusicArtist' === params.type || 'Person' === params.type ? null : params.type, Recursive: true, - IsFavorite: "true" === params.IsFavorite || null, + IsFavorite: 'true' === params.IsFavorite || null, ArtistIds: params.artistId || null, SortBy: sortBy })); } - if ("Genre" === item.Type || "MusicGenre" === item.Type || "Studio" === item.Type || "Person" === item.Type) { + if ('Genre' === item.Type || 'MusicGenre' === item.Type || 'Studio' === item.Type || 'Person' === item.Type) { instance.queryRecursive = true; var query = { StartIndex: startIndex, Limit: limit, - Fields: "PrimaryImageAspectRatio,SortName", + Fields: 'PrimaryImageAspectRatio,SortName', Recursive: true, parentId: params.parentId, SortBy: sortBy }; - if ("Studio" === item.Type) { + if ('Studio' === item.Type) { query.StudioIds = item.Id; - } else if ("Genre" === item.Type || "MusicGenre" === item.Type) { + } else if ('Genre' === item.Type || 'MusicGenre' === item.Type) { query.GenreIds = item.Id; - } else if ("Person" === item.Type) { + } else if ('Person' === item.Type) { query.PersonIds = item.Id; } - if ("MusicGenre" === item.Type) { - query.IncludeItemTypes = "MusicAlbum"; - } else if ("GameGenre" === item.Type) { - query.IncludeItemTypes = "Game"; - } else if ("movies" === item.CollectionType) { - query.IncludeItemTypes = "Movie"; - } else if ("tvshows" === item.CollectionType) { - query.IncludeItemTypes = "Series"; - } else if ("Genre" === item.Type) { - query.IncludeItemTypes = "Movie,Series,Video"; - } else if ("Person" === item.Type) { + if ('MusicGenre' === item.Type) { + query.IncludeItemTypes = 'MusicAlbum'; + } else if ('GameGenre' === item.Type) { + query.IncludeItemTypes = 'Game'; + } else if ('movies' === item.CollectionType) { + query.IncludeItemTypes = 'Movie'; + } else if ('tvshows' === item.CollectionType) { + query.IncludeItemTypes = 'Series'; + } else if ('Genre' === item.Type) { + query.IncludeItemTypes = 'Movie,Series,Video'; + } else if ('Person' === item.Type) { query.IncludeItemTypes = params.type; } @@ -308,7 +308,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { StartIndex: startIndex, Limit: limit, - Fields: "PrimaryImageAspectRatio,SortName", + Fields: 'PrimaryImageAspectRatio,SortName,Path', ImageTypeLimit: 1, ParentId: item.Id, SortBy: sortBy @@ -316,7 +316,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } function getItem(params) { - if ("Recordings" === params.type || "Programs" === params.type || "nextup" === params.type) { + if ('Recordings' === params.type || 'Programs' === params.type || 'nextup' === params.type) { return Promise.resolve(null); } @@ -333,7 +333,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function showViewSettingsMenu() { var instance = this; - require(["viewSettings"], function (ViewSettings) { + require(['viewSettings'], function (ViewSettings) { new ViewSettings().show({ settingsKey: instance.getSettingsKey(), settings: instance.getViewSettings(), @@ -348,7 +348,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function showFilterMenu() { var instance = this; - require(["filterMenu"], function (FilterMenu) { + require(['filterMenu'], function (FilterMenu) { new FilterMenu().show({ settingsKey: instance.getSettingsKey(), settings: instance.getFilters(), @@ -367,7 +367,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function showSortMenu() { var instance = this; - require(["sortMenu"], function (SortMenu) { + require(['sortMenu'], function (SortMenu) { new SortMenu().show({ settingsKey: instance.getSettingsKey(), settings: instance.getSortValues(), @@ -385,7 +385,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function onNewItemClick() { var instance = this; - require(["playlistEditor"], function (playlistEditor) { + require(['playlistEditor'], function (playlistEditor) { new playlistEditor().show({ items: [], serverId: instance.params.serverId @@ -396,9 +396,9 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function hideOrShowAll(elems, hide) { for (var i = 0, length = elems.length; i < length; i++) { if (hide) { - elems[i].classList.add("hide"); + elems[i].classList.add('hide'); } else { - elems[i].classList.remove("hide"); + elems[i].classList.remove('hide'); } } } @@ -424,7 +424,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" function getItemsHtml(items) { var settings = self.getViewSettings(); - if ("list" === settings.imageType) { + if ('list' === settings.imageType) { return listView.getListViewHtml({ items: items }); @@ -438,26 +438,26 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var item = self.currentItem; var lines = settings.showTitle ? 2 : 0; - if ("banner" === settings.imageType) { - shape = "banner"; - } else if ("disc" === settings.imageType) { - shape = "square"; + if ('banner' === settings.imageType) { + shape = 'banner'; + } else if ('disc' === settings.imageType) { + shape = 'square'; preferDisc = true; - } else if ("logo" === settings.imageType) { - shape = "backdrop"; + } else if ('logo' === settings.imageType) { + shape = 'backdrop'; preferLogo = true; - } else if ("thumb" === settings.imageType) { - shape = "backdrop"; + } else if ('thumb' === settings.imageType) { + shape = 'backdrop'; preferThumb = true; - } else if ("nextup" === params.type) { - shape = "backdrop"; - preferThumb = "thumb" === settings.imageType; - } else if ("Programs" === params.type || "Recordings" === params.type) { - shape = "true" === params.IsMovie ? "portrait" : "autoVertical"; - preferThumb = "true" !== params.IsMovie ? "auto" : false; - defaultShape = "true" === params.IsMovie ? "portrait" : "backdrop"; + } else if ('nextup' === params.type) { + shape = 'backdrop'; + preferThumb = 'thumb' === settings.imageType; + } else if ('Programs' === params.type || 'Recordings' === params.type) { + shape = 'true' === params.IsMovie ? 'portrait' : 'autoVertical'; + preferThumb = 'true' !== params.IsMovie ? 'auto' : false; + defaultShape = 'true' === params.IsMovie ? 'portrait' : 'backdrop'; } else { - shape = "autoVertical"; + shape = 'autoVertical'; } var posterOptions = { @@ -473,47 +473,47 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" overlayMoreButton: true, overlayText: !settings.showTitle, defaultShape: defaultShape, - action: "Audio" === params.type ? "playallfromhere" : null + action: 'Audio' === params.type ? 'playallfromhere' : null }; - if ("nextup" === params.type) { + if ('nextup' === params.type) { posterOptions.showParentTitle = settings.showTitle; - } else if ("Person" === params.type) { + } else if ('Person' === params.type) { posterOptions.showYear = false; posterOptions.showParentTitle = false; lines = 1; - } else if ("Audio" === params.type) { + } else if ('Audio' === params.type) { posterOptions.showParentTitle = settings.showTitle; - } else if ("MusicAlbum" === params.type) { + } else if ('MusicAlbum' === params.type) { posterOptions.showParentTitle = settings.showTitle; - } else if ("Episode" === params.type) { + } else if ('Episode' === params.type) { posterOptions.showParentTitle = settings.showTitle; - } else if ("MusicArtist" === params.type) { + } else if ('MusicArtist' === params.type) { posterOptions.showYear = false; lines = 1; - } else if ("Programs" === params.type) { + } else if ('Programs' === params.type) { lines = settings.showTitle ? 1 : 0; - var showParentTitle = settings.showTitle && "true" !== params.IsMovie; + var showParentTitle = settings.showTitle && 'true' !== params.IsMovie; if (showParentTitle) { lines++; } - var showAirTime = settings.showTitle && "Recordings" !== params.type; + var showAirTime = settings.showTitle && 'Recordings' !== params.type; if (showAirTime) { lines++; } - var showYear = settings.showTitle && "true" === params.IsMovie && "Recordings" === params.type; + var showYear = settings.showTitle && 'true' === params.IsMovie && 'Recordings' === params.type; if (showYear) { lines++; } posterOptions = Object.assign(posterOptions, { - inheritThumb: "Recordings" === params.type, - context: "livetv", + inheritThumb: 'Recordings' === params.type, + context: 'livetv', showParentTitle: showParentTitle, showAirTime: showAirTime, showAirDateTime: showAirTime, @@ -529,28 +529,28 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" posterOptions.lines = lines; posterOptions.items = items; - if (item && "folders" === item.CollectionType) { - posterOptions.context = "folders"; + if (item && 'folders' === item.CollectionType) { + posterOptions.context = 'folders'; } return cardBuilder.getCardsHtml(posterOptions); } function initAlphaPicker() { - self.scroller = view.querySelector(".scrollFrameY"); + self.scroller = view.querySelector('.scrollFrameY'); var alphaPickerElement = self.alphaPickerElement; - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - alphaPickerElement.classList.add("focuscontainer-right"); - self.itemsContainer.parentNode.classList.add("padded-right-withalphapicker"); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + alphaPickerElement.classList.add('focuscontainer-right'); + self.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, itemsContainer: layoutManager.tv ? self.itemsContainer : null, - itemClass: "card", - valueChangeEvent: layoutManager.tv ? null : "click" + itemClass: 'card', + valueChangeEvent: layoutManager.tv ? null : 'click' }); - self.alphaPicker.on("alphavaluechanged", onAlphaPickerValueChanged); + self.alphaPicker.on('alphavaluechanged', onAlphaPickerValueChanged); } function onAlphaPickerValueChanged() { @@ -559,90 +559,90 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } function setTitle(item) { - Emby.Page.setTitle(getTitle(item) || ""); + Emby.Page.setTitle(getTitle(item) || ''); - if (item && "playlists" === item.CollectionType) { - hideOrShowAll(view.querySelectorAll(".btnNewItem"), false); + if (item && 'playlists' === item.CollectionType) { + hideOrShowAll(view.querySelectorAll('.btnNewItem'), false); } else { - hideOrShowAll(view.querySelectorAll(".btnNewItem"), true); + hideOrShowAll(view.querySelectorAll('.btnNewItem'), true); } } function getTitle(item) { - if ("Recordings" === params.type) { - return globalize.translate("Recordings"); + if ('Recordings' === params.type) { + return globalize.translate('Recordings'); } - if ("Programs" === params.type) { - if ("true" === params.IsMovie) { - return globalize.translate("Movies"); + if ('Programs' === params.type) { + if ('true' === params.IsMovie) { + return globalize.translate('Movies'); } - if ("true" === params.IsSports) { - return globalize.translate("Sports"); + if ('true' === params.IsSports) { + return globalize.translate('Sports'); } - if ("true" === params.IsKids) { - return globalize.translate("HeaderForKids"); + if ('true' === params.IsKids) { + return globalize.translate('HeaderForKids'); } - if ("true" === params.IsAiring) { - return globalize.translate("HeaderOnNow"); + if ('true' === params.IsAiring) { + return globalize.translate('HeaderOnNow'); } - if ("true" === params.IsSeries) { - return globalize.translate("Shows"); + if ('true' === params.IsSeries) { + return globalize.translate('Shows'); } - if ("true" === params.IsNews) { - return globalize.translate("News"); + if ('true' === params.IsNews) { + return globalize.translate('News'); } - return globalize.translate("Programs"); + return globalize.translate('Programs'); } - if ("nextup" === params.type) { - return globalize.translate("NextUp"); + if ('nextup' === params.type) { + return globalize.translate('NextUp'); } - if ("favoritemovies" === params.type) { - return globalize.translate("FavoriteMovies"); + if ('favoritemovies' === params.type) { + return globalize.translate('FavoriteMovies'); } if (item) { return item.Name; } - if ("Movie" === params.type) { - return globalize.translate("Movies"); + if ('Movie' === params.type) { + return globalize.translate('Movies'); } - if ("Series" === params.type) { - return globalize.translate("Shows"); + if ('Series' === params.type) { + return globalize.translate('Shows'); } - if ("Season" === params.type) { - return globalize.translate("Seasons"); + if ('Season' === params.type) { + return globalize.translate('Seasons'); } - if ("Episode" === params.type) { - return globalize.translate("Episodes"); + if ('Episode' === params.type) { + return globalize.translate('Episodes'); } - if ("MusicArtist" === params.type) { - return globalize.translate("Artists"); + if ('MusicArtist' === params.type) { + return globalize.translate('Artists'); } - if ("MusicAlbum" === params.type) { - return globalize.translate("Albums"); + if ('MusicAlbum' === params.type) { + return globalize.translate('Albums'); } - if ("Audio" === params.type) { - return globalize.translate("Songs"); + if ('Audio' === params.type) { + return globalize.translate('Songs'); } - if ("Video" === params.type) { - return globalize.translate("Videos"); + if ('Video' === params.type) { + return globalize.translate('Videos'); } return void 0; @@ -686,7 +686,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" if (currentItem && !self.hasFilters) { playbackManager.shuffle(currentItem); } else { - getItems(self, self.params, currentItem, "Random", null, 300).then(function (result) { + getItems(self, self.params, currentItem, 'Random', null, 300).then(function (result) { playbackManager.play({ items: result.Items }); @@ -696,59 +696,59 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var self = this; self.params = params; - this.itemsContainer = view.querySelector(".itemsContainer"); + this.itemsContainer = view.querySelector('.itemsContainer'); if (params.parentId) { - this.itemsContainer.setAttribute("data-parentid", params.parentId); - } else if ("nextup" === params.type) { - this.itemsContainer.setAttribute("data-monitor", "videoplayback"); - } else if ("favoritemovies" === params.type) { - this.itemsContainer.setAttribute("data-monitor", "markfavorite"); - } else if ("Programs" === params.type) { - this.itemsContainer.setAttribute("data-refreshinterval", "300000"); + this.itemsContainer.setAttribute('data-parentid', params.parentId); + } else if ('nextup' === params.type) { + this.itemsContainer.setAttribute('data-monitor', 'videoplayback'); + } else if ('favoritemovies' === params.type) { + this.itemsContainer.setAttribute('data-monitor', 'markfavorite'); + } else if ('Programs' === params.type) { + this.itemsContainer.setAttribute('data-refreshinterval', '300000'); } var i; var length; - var btnViewSettings = view.querySelectorAll(".btnViewSettings"); + var btnViewSettings = view.querySelectorAll('.btnViewSettings'); for (i = 0, length = btnViewSettings.length; i < length; i++) { - btnViewSettings[i].addEventListener("click", showViewSettingsMenu.bind(this)); + btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this)); } - var filterButtons = view.querySelectorAll(".btnFilter"); + var filterButtons = view.querySelectorAll('.btnFilter'); this.filterButtons = filterButtons; var hasVisibleFilters = this.getVisibleFilters().length; for (i = 0, length = filterButtons.length; i < length; i++) { var btnFilter = filterButtons[i]; - btnFilter.addEventListener("click", showFilterMenu.bind(this)); + btnFilter.addEventListener('click', showFilterMenu.bind(this)); if (hasVisibleFilters) { - btnFilter.classList.remove("hide"); + btnFilter.classList.remove('hide'); } else { - btnFilter.classList.add("hide"); + btnFilter.classList.add('hide'); } } - var sortButtons = view.querySelectorAll(".btnSort"); + var sortButtons = view.querySelectorAll('.btnSort'); for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) { var sortButton = sortButtons[i]; - sortButton.addEventListener("click", showSortMenu.bind(this)); + sortButton.addEventListener('click', showSortMenu.bind(this)); - if ("nextup" !== params.type) { - sortButton.classList.remove("hide"); + if ('nextup' !== params.type) { + sortButton.classList.remove('hide'); } } - this.btnSortText = view.querySelector(".btnSortText"); - this.btnSortIcon = view.querySelector(".btnSortIcon"); - bindAll(view.querySelectorAll(".btnNewItem"), "click", onNewItemClick.bind(this)); - this.alphaPickerElement = view.querySelector(".alphaPicker"); + this.btnSortText = view.querySelector('.btnSortText'); + this.btnSortIcon = view.querySelector('.btnSortIcon'); + bindAll(view.querySelectorAll('.btnNewItem'), 'click', onNewItemClick.bind(this)); + this.alphaPickerElement = view.querySelector('.alphaPicker'); self.itemsContainer.fetchData = fetchData; self.itemsContainer.getItemsHtml = getItemsHtml; - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { var isRestored = e.detail.isRestored; if (!isRestored) { @@ -772,42 +772,42 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } }); - if (!isRestored && item && "PhotoAlbum" !== item.Type) { + if (!isRestored && item && 'PhotoAlbum' !== item.Type) { initAlphaPicker(); } var itemType = item ? item.Type : null; - if ("MusicGenre" === itemType || "Programs" !== params.type && "Channel" !== itemType) { - hideOrShowAll(view.querySelectorAll(".btnPlay"), false); + if ('MusicGenre' === itemType || 'Programs' !== params.type && 'Channel' !== itemType) { + hideOrShowAll(view.querySelectorAll('.btnPlay'), false); } else { - hideOrShowAll(view.querySelectorAll(".btnPlay"), true); + hideOrShowAll(view.querySelectorAll('.btnPlay'), true); } - if ("MusicGenre" === itemType || "Programs" !== params.type && "nextup" !== params.type && "Channel" !== itemType) { - hideOrShowAll(view.querySelectorAll(".btnShuffle"), false); + if ('MusicGenre' === itemType || 'Programs' !== params.type && 'nextup' !== params.type && 'Channel' !== itemType) { + hideOrShowAll(view.querySelectorAll('.btnShuffle'), false); } else { - hideOrShowAll(view.querySelectorAll(".btnShuffle"), true); + hideOrShowAll(view.querySelectorAll('.btnShuffle'), true); } if (item && playbackManager.canQueue(item)) { - hideOrShowAll(view.querySelectorAll(".btnQueue"), false); + hideOrShowAll(view.querySelectorAll('.btnQueue'), false); } else { - hideOrShowAll(view.querySelectorAll(".btnQueue"), true); + hideOrShowAll(view.querySelectorAll('.btnQueue'), true); } }); if (!isRestored) { - bindAll(view.querySelectorAll(".btnPlay"), "click", play); - bindAll(view.querySelectorAll(".btnQueue"), "click", queue); - bindAll(view.querySelectorAll(".btnShuffle"), "click", shuffle); + bindAll(view.querySelectorAll('.btnPlay'), 'click', play); + bindAll(view.querySelectorAll('.btnQueue'), 'click', queue); + bindAll(view.querySelectorAll('.btnShuffle'), 'click', shuffle); } this.alphaNumericShortcuts = new AlphaNumericShortcuts({ itemsContainer: self.itemsContainer }); }); - view.addEventListener("viewhide", function (e) { + view.addEventListener('viewhide', function (e) { var itemsContainer = self.itemsContainer; if (itemsContainer) { @@ -821,13 +821,13 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" self.alphaNumericShortcuts = null; } }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (self.listController) { self.listController.destroy(); } if (self.alphaPicker) { - self.alphaPicker.off("alphavaluechanged", onAlphaPickerValueChanged); + self.alphaPicker.off('alphavaluechanged', onAlphaPickerValueChanged); self.alphaPicker.destroy(); } @@ -845,30 +845,30 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" ItemsView.prototype.getFilters = function () { var basekey = this.getSettingsKey(); return { - IsPlayed: "true" === userSettings.getFilter(basekey + "-filter-IsPlayed"), - IsUnplayed: "true" === userSettings.getFilter(basekey + "-filter-IsUnplayed"), - IsFavorite: "true" === userSettings.getFilter(basekey + "-filter-IsFavorite"), - IsResumable: "true" === userSettings.getFilter(basekey + "-filter-IsResumable"), - Is4K: "true" === userSettings.getFilter(basekey + "-filter-Is4K"), - IsHD: "true" === userSettings.getFilter(basekey + "-filter-IsHD"), - IsSD: "true" === userSettings.getFilter(basekey + "-filter-IsSD"), - Is3D: "true" === userSettings.getFilter(basekey + "-filter-Is3D"), - VideoTypes: userSettings.getFilter(basekey + "-filter-VideoTypes"), - SeriesStatus: userSettings.getFilter(basekey + "-filter-SeriesStatus"), - HasSubtitles: userSettings.getFilter(basekey + "-filter-HasSubtitles"), - HasTrailer: userSettings.getFilter(basekey + "-filter-HasTrailer"), - HasSpecialFeature: userSettings.getFilter(basekey + "-filter-HasSpecialFeature"), - HasThemeSong: userSettings.getFilter(basekey + "-filter-HasThemeSong"), - HasThemeVideo: userSettings.getFilter(basekey + "-filter-HasThemeVideo"), - GenreIds: userSettings.getFilter(basekey + "-filter-GenreIds") + IsPlayed: 'true' === userSettings.getFilter(basekey + '-filter-IsPlayed'), + IsUnplayed: 'true' === userSettings.getFilter(basekey + '-filter-IsUnplayed'), + IsFavorite: 'true' === userSettings.getFilter(basekey + '-filter-IsFavorite'), + IsResumable: 'true' === userSettings.getFilter(basekey + '-filter-IsResumable'), + Is4K: 'true' === userSettings.getFilter(basekey + '-filter-Is4K'), + IsHD: 'true' === userSettings.getFilter(basekey + '-filter-IsHD'), + IsSD: 'true' === userSettings.getFilter(basekey + '-filter-IsSD'), + Is3D: 'true' === userSettings.getFilter(basekey + '-filter-Is3D'), + VideoTypes: userSettings.getFilter(basekey + '-filter-VideoTypes'), + SeriesStatus: userSettings.getFilter(basekey + '-filter-SeriesStatus'), + HasSubtitles: userSettings.getFilter(basekey + '-filter-HasSubtitles'), + HasTrailer: userSettings.getFilter(basekey + '-filter-HasTrailer'), + HasSpecialFeature: userSettings.getFilter(basekey + '-filter-HasSpecialFeature'), + HasThemeSong: userSettings.getFilter(basekey + '-filter-HasThemeSong'), + HasThemeVideo: userSettings.getFilter(basekey + '-filter-HasThemeVideo'), + GenreIds: userSettings.getFilter(basekey + '-filter-GenreIds') }; }; ItemsView.prototype.getSortValues = function () { var basekey = this.getSettingsKey(); return { - sortBy: userSettings.getFilter(basekey + "-sortby") || this.getDefaultSortBy(), - sortOrder: "Descending" === userSettings.getFilter(basekey + "-sortorder") ? "Descending" : "Ascending" + sortBy: userSettings.getFilter(basekey + '-sortby') || this.getDefaultSortBy(), + sortOrder: 'Descending' === userSettings.getFilter(basekey + '-sortorder') ? 'Descending' : 'Ascending' }; }; @@ -880,17 +880,17 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" return sortNameOption.value; } - return "IsFolder," + sortNameOption.value; + return 'IsFolder,' + sortNameOption.value; }; ItemsView.prototype.getSortMenuOptions = function () { var sortBy = []; var params = this.params; - if ("Programs" === params.type) { + if ('Programs' === params.type) { sortBy.push({ - name: globalize.translate("AirDate"), - value: "StartDate,SortName" + name: globalize.translate('AirDate'), + value: 'StartDate,SortName' }); } @@ -912,10 +912,10 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" sortBy.push(option); } - if ("Programs" !== params.type) { + if ('Programs' !== params.type) { sortBy.push({ - name: globalize.translate("DateAdded"), - value: "DateCreated,SortName" + name: globalize.translate('DateAdded'), + value: 'DateCreated,SortName' }); } @@ -928,14 +928,14 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" if (!params.type) { option = this.getNameSortOption(params); sortBy.push({ - name: globalize.translate("Folders"), - value: "IsFolder," + option.value + name: globalize.translate('Folders'), + value: 'IsFolder,' + option.value }); } sortBy.push({ - name: globalize.translate("ParentalRating"), - value: "OfficialRating,SortName" + name: globalize.translate('ParentalRating'), + value: 'OfficialRating,SortName' }); option = this.getPlayCountSortOption(); @@ -944,67 +944,67 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } sortBy.push({ - name: globalize.translate("ReleaseDate"), - value: "ProductionYear,PremiereDate,SortName" + name: globalize.translate('ReleaseDate'), + value: 'ProductionYear,PremiereDate,SortName' }); sortBy.push({ - name: globalize.translate("Runtime"), - value: "Runtime,SortName" + name: globalize.translate('Runtime'), + value: 'Runtime,SortName' }); return sortBy; }; ItemsView.prototype.getNameSortOption = function (params) { - if ("Episode" === params.type) { + if ('Episode' === params.type) { return { - name: globalize.translate("Name"), - value: "SeriesName,SortName" + name: globalize.translate('Name'), + value: 'SeriesName,SortName' }; } return { - name: globalize.translate("Name"), - value: "SortName" + name: globalize.translate('Name'), + value: 'SortName' }; }; ItemsView.prototype.getPlayCountSortOption = function () { - if ("Programs" === this.params.type) { + if ('Programs' === this.params.type) { return null; } return { - name: globalize.translate("PlayCount"), - value: "PlayCount,SortName" + name: globalize.translate('PlayCount'), + value: 'PlayCount,SortName' }; }; ItemsView.prototype.getDatePlayedSortOption = function () { - if ("Programs" === this.params.type) { + if ('Programs' === this.params.type) { return null; } return { - name: globalize.translate("DatePlayed"), - value: "DatePlayed,SortName" + name: globalize.translate('DatePlayed'), + value: 'DatePlayed,SortName' }; }; ItemsView.prototype.getCriticRatingSortOption = function () { - if ("Programs" === this.params.type) { + if ('Programs' === this.params.type) { return null; } return { - name: globalize.translate("CriticRating"), - value: "CriticRating,SortName" + name: globalize.translate('CriticRating'), + value: 'CriticRating,SortName' }; }; ItemsView.prototype.getCommunityRatingSortOption = function () { return { - name: globalize.translate("CommunityRating"), - value: "CommunityRating,SortName" + name: globalize.translate('CommunityRating'), + value: 'CommunityRating,SortName' }; }; @@ -1012,25 +1012,24 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var filters = []; var params = this.params; - if (!("nextup" === params.type)) { - if ("Programs" === params.type) { - filters.push("Genres"); + if (!('nextup' === params.type)) { + if ('Programs' === params.type) { + filters.push('Genres'); } else { - params.type; - filters.push("IsUnplayed"); - filters.push("IsPlayed"); + filters.push('IsUnplayed'); + filters.push('IsPlayed'); if (!params.IsFavorite) { - filters.push("IsFavorite"); + filters.push('IsFavorite'); } - filters.push("IsResumable"); - filters.push("VideoType"); - filters.push("HasSubtitles"); - filters.push("HasTrailer"); - filters.push("HasSpecialFeature"); - filters.push("HasThemeSong"); - filters.push("HasThemeVideo"); + filters.push('IsResumable'); + filters.push('VideoType'); + filters.push('HasSubtitles'); + filters.push('HasTrailer'); + filters.push('HasSpecialFeature'); + filters.push('HasThemeSong'); + filters.push('HasThemeVideo'); } } @@ -1044,22 +1043,22 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" if (filterButtons.length) { for (var i = 0, length = filterButtons.length; i < length; i++) { var btnFilter = filterButtons[i]; - var bubble = btnFilter.querySelector(".filterButtonBubble"); + var bubble = btnFilter.querySelector('.filterButtonBubble'); if (!bubble) { if (!hasFilters) { continue; } - btnFilter.insertAdjacentHTML("afterbegin", '
!
'); - btnFilter.classList.add("btnFilterWithBubble"); - bubble = btnFilter.querySelector(".filterButtonBubble"); + btnFilter.insertAdjacentHTML('afterbegin', '
!
'); + btnFilter.classList.add('btnFilterWithBubble'); + bubble = btnFilter.querySelector('.filterButtonBubble'); } if (hasFilters) { - bubble.classList.remove("hide"); + bubble.classList.remove('hide'); } else { - bubble.classList.add("hide"); + bubble.classList.add('hide'); } } } @@ -1080,13 +1079,13 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" ItemsView.prototype.getVisibleViewSettings = function () { var item = (this.params, this.currentItem); - var fields = ["showTitle"]; + var fields = ['showTitle']; - if (!item || "PhotoAlbum" !== item.Type && "ChannelFolderItem" !== item.Type) { - fields.push("imageType"); + if (!item || 'PhotoAlbum' !== item.Type && 'ChannelFolderItem' !== item.Type) { + fields.push('imageType'); } - fields.push("viewType"); + fields.push('viewType'); return fields; }; @@ -1094,41 +1093,41 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" var basekey = this.getSettingsKey(); var params = this.params; var item = this.currentItem; - var showTitle = userSettings.get(basekey + "-showTitle"); + var showTitle = userSettings.get(basekey + '-showTitle'); - if ("true" === showTitle) { + if ('true' === showTitle) { showTitle = true; - } else if ("false" === showTitle) { + } else if ('false' === showTitle) { showTitle = false; - } else if ("Programs" === params.type || "Recordings" === params.type || "Person" === params.type || "nextup" === params.type || "Audio" === params.type || "MusicAlbum" === params.type || "MusicArtist" === params.type) { + } else if ('Programs' === params.type || 'Recordings' === params.type || 'Person' === params.type || 'nextup' === params.type || 'Audio' === params.type || 'MusicAlbum' === params.type || 'MusicArtist' === params.type) { showTitle = true; - } else if (item && "PhotoAlbum" !== item.Type) { + } else if (item && 'PhotoAlbum' !== item.Type) { showTitle = true; } - var imageType = userSettings.get(basekey + "-imageType"); + var imageType = userSettings.get(basekey + '-imageType'); - if (!imageType && "nextup" === params.type) { - imageType = "thumb"; + if (!imageType && 'nextup' === params.type) { + imageType = 'thumb'; } return { showTitle: showTitle, - showYear: "false" !== userSettings.get(basekey + "-showYear"), - imageType: imageType || "primary", - viewType: userSettings.get(basekey + "-viewType") || "images" + showYear: 'false' !== userSettings.get(basekey + '-showYear'), + imageType: imageType || 'primary', + viewType: userSettings.get(basekey + '-viewType') || 'images' }; }; ItemsView.prototype.getItemTypes = function () { var params = this.params; - if ("nextup" === params.type) { - return ["Episode"]; + if ('nextup' === params.type) { + return ['Episode']; } - if ("Programs" === params.type) { - return ["Program"]; + if ('Programs' === params.type) { + return ['Program']; } return []; @@ -1136,7 +1135,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" ItemsView.prototype.getSettingsKey = function () { var values = []; - values.push("items"); + values.push('items'); var params = this.params; if (params.type) { @@ -1146,54 +1145,54 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager" } if (params.IsAiring) { - values.push("IsAiring"); + values.push('IsAiring'); } if (params.IsMovie) { - values.push("IsMovie"); + values.push('IsMovie'); } if (params.IsKids) { - values.push("IsKids"); + values.push('IsKids'); } if (params.IsSports) { - values.push("IsSports"); + values.push('IsSports'); } if (params.IsNews) { - values.push("IsNews"); + values.push('IsNews'); } if (params.IsSeries) { - values.push("IsSeries"); + values.push('IsSeries'); } if (params.IsFavorite) { - values.push("IsFavorite"); + values.push('IsFavorite'); } if (params.genreId) { - values.push("Genre"); + values.push('Genre'); } if (params.musicGenreId) { - values.push("MusicGenre"); + values.push('MusicGenre'); } if (params.studioId) { - values.push("Studio"); + values.push('Studio'); } if (params.personId) { - values.push("Person"); + values.push('Person'); } if (params.parentId) { - values.push("Folder"); + values.push('Folder'); } - return values.join("-"); + return values.join('-'); }; return ItemsView; diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 2de7e818439..62906d9d211 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -1,5 +1,5 @@ -define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "emby-itemscontainer"], function (cardBuilder, imageLoader, libraryBrowser, loading, events) { - "use strict"; +define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'userSettings', 'emby-itemscontainer'], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) { + 'use strict'; return function (view, params, tabContent) { function getPageData() { @@ -7,12 +7,15 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em pageData = { query: { StartIndex: 0, - Limit: 100, - Fields: "PrimaryImageAspectRatio" + Fields: 'PrimaryImageAspectRatio' } }; } + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + return pageData; } @@ -23,7 +26,7 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em function getChannelsHtml(channels) { return cardBuilder.getCardsHtml({ items: channels, - shape: "square", + shape: 'square', showTitle: true, lazy: true, cardLayout: true, @@ -39,7 +42,9 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(context); } @@ -48,12 +53,14 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(context); } var query = getQuery(); - context.querySelector(".paging").innerHTML = libraryBrowser.getQueryPagingHtml({ + context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -62,30 +69,30 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em filterButton: false }); var html = getChannelsHtml(result.Items); - var elem = context.querySelector("#items"); + var elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); var i; var length; var elems; - for (elems = context.querySelectorAll(".btnNextPage"), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); } - for (elems = context.querySelectorAll(".btnPreviousPage"), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); } } function showFilterMenu(context) { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(), - mode: "livetvchannels", + mode: 'livetvchannels', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { reloadItems(context); }); filterDialog.show(); @@ -103,7 +110,7 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(view); }); }); @@ -112,7 +119,7 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em var pageData; var self = this; var isLoading = false; - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { showFilterMenu(tabContent); }); diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index f7c2f1baaab..ec7a7a3f816 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -1,5 +1,5 @@ -define(["tvguide"], function (tvguide) { - "use strict"; +define(['tvguide'], function (tvguide) { + 'use strict'; return function (view, params, tabContent) { var guideInstance; diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index f8e49f25902..d5cfe666728 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "scripts/livetvcomponents", "listViewStyle", "emby-itemscontainer"], function (layoutManager, loading, cardBuilder, appHost, imageLoader) { - "use strict"; +define(['layoutManager', 'loading', 'cardBuilder', 'apphost', 'imageLoader', 'scripts/livetvcomponents', 'listViewStyle', 'emby-itemscontainer'], function (layoutManager, loading, cardBuilder, appHost, imageLoader) { + 'use strict'; function renderRecordings(elem, recordings, cardOptions, scrollX) { if (!elem) { @@ -7,35 +7,35 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc } if (recordings.length) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - var recordingItems = elem.querySelector(".recordingItems"); + var recordingItems = elem.querySelector('.recordingItems'); if (scrollX) { - recordingItems.classList.add("scrollX"); - recordingItems.classList.add("hiddenScrollX"); - recordingItems.classList.remove("vertical-wrap"); + recordingItems.classList.add('scrollX'); + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); } else { - recordingItems.classList.remove("scrollX"); - recordingItems.classList.remove("hiddenScrollX"); - recordingItems.classList.add("vertical-wrap"); + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); } - appHost.supports("imageanalysis"); + appHost.supports('imageanalysis'); recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ items: recordings, - shape: scrollX ? "autooverflow" : "auto", - defaultShape: scrollX ? "overflowBackdrop" : "backdrop", + shape: scrollX ? 'autooverflow' : 'auto', + defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', showTitle: true, showParentTitle: true, coverImage: true, cardLayout: false, centerText: true, allowBottomPadding: !scrollX, - preferThumb: "auto", + preferThumb: 'auto', overlayText: false }, cardOptions || {})); imageLoader.lazyChildren(recordingItems); @@ -43,7 +43,7 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc function renderLatestRecordings(context, promise) { promise.then(function (result) { - renderRecordings(context.querySelector("#latestRecordings"), result.Items, { + renderRecordings(context.querySelector('#latestRecordings'), result.Items, { showYear: true, lines: 2 }, false); @@ -53,7 +53,7 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc function renderRecordingFolders(context, promise) { promise.then(function (result) { - renderRecordings(context.querySelector("#recordingFolders"), result.Items, { + renderRecordings(context.querySelector('#recordingFolders'), result.Items, { showYear: false, showParentTitle: false }, false); @@ -61,12 +61,12 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc } function onMoreClick(e) { - var type = this.getAttribute("data-type"); + var type = this.getAttribute('data-type'); var serverId = ApiClient.serverId(); switch (type) { - case "latest": - Dashboard.navigate("list.html?type=Recordings&serverId=" + serverId); + case 'latest': + Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); } } @@ -79,10 +79,10 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc var latestPromise; var self = this; var lastFullRender = 0; - var moreButtons = tabContent.querySelectorAll(".more"); + var moreButtons = tabContent.querySelectorAll('.more'); for (var i = 0, length = moreButtons.length; i < length; i++) { - moreButtons[i].addEventListener("click", onMoreClick); + moreButtons[i].addEventListener('click', onMoreClick); } self.preRender = function () { @@ -90,9 +90,9 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc latestPromise = ApiClient.getLiveTvRecordings({ UserId: Dashboard.getCurrentUserId(), Limit: 12, - Fields: "CanDelete,PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', EnableTotalRecordCount: false, - EnableImageTypes: "Primary,Thumb,Backdrop" + EnableImageTypes: 'Primary,Thumb,Backdrop' }); foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId()); } diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index 3ee56a2a959..a6f509c6f1f 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -1,5 +1,5 @@ -define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "scripts/livetvcomponents", "emby-button", "emby-itemscontainer"], function (layoutManager, cardBuilder, appHost, imageLoader, loading) { - "use strict"; +define(['layoutManager', 'cardBuilder', 'apphost', 'imageLoader', 'loading', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (layoutManager, cardBuilder, appHost, imageLoader, loading) { + 'use strict'; function enableScrollX() { return !layoutManager.desktop; @@ -7,54 +7,54 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc function renderRecordings(elem, recordings, cardOptions) { if (recordings.length) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - var recordingItems = elem.querySelector(".recordingItems"); + var recordingItems = elem.querySelector('.recordingItems'); if (enableScrollX()) { - recordingItems.classList.add("scrollX"); + recordingItems.classList.add('scrollX'); if (layoutManager.tv) { - recordingItems.classList.add("smoothScrollX"); + recordingItems.classList.add('smoothScrollX'); } - recordingItems.classList.add("hiddenScrollX"); - recordingItems.classList.remove("vertical-wrap"); + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); } else { - recordingItems.classList.remove("scrollX"); - recordingItems.classList.remove("smoothScrollX"); - recordingItems.classList.remove("hiddenScrollX"); - recordingItems.classList.add("vertical-wrap"); + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('smoothScrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); } - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; cardLayout = false; recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ items: recordings, - shape: enableScrollX() ? "autooverflow" : "auto", + shape: enableScrollX() ? 'autooverflow' : 'auto', showTitle: true, showParentTitle: true, coverImage: true, cardLayout: cardLayout, centerText: !cardLayout, allowBottomPadding: !enableScrollX(), - preferThumb: "auto" + preferThumb: 'auto' }, cardOptions || {})); imageLoader.lazyChildren(recordingItems); } function getBackdropShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop" + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function renderActiveRecordings(context, promise) { promise.then(function (result) { - renderRecordings(context.querySelector("#activeRecordings"), result.Items, { - shape: enableScrollX() ? "autooverflow" : "auto", + renderRecordings(context.querySelector('#activeRecordings'), result.Items, { + shape: enableScrollX() ? 'autooverflow' : 'auto', defaultShape: getBackdropShape(), showParentTitle: false, showParentTitleOrTitle: true, @@ -74,19 +74,19 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc var elem = context; if (html) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - elem.querySelector(".recordingItems").innerHTML = html; + elem.querySelector('.recordingItems').innerHTML = html; imageLoader.lazyChildren(elem); }); } function renderUpcomingRecordings(context, promise) { promise.then(function (result) { - renderTimers(context.querySelector("#upcomingRecordings"), result.Items); + renderTimers(context.querySelector('#upcomingRecordings'), result.Items); loading.hide(); }); } @@ -95,7 +95,7 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc var activeRecordingsPromise; var upcomingRecordingsPromise; var self = this; - tabContent.querySelector("#upcomingRecordings .recordingItems").addEventListener("timercancelled", function () { + tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () { self.preRender(); self.renderTab(); }); @@ -104,9 +104,9 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc activeRecordingsPromise = ApiClient.getLiveTvRecordings({ UserId: Dashboard.getCurrentUserId(), IsInProgress: true, - Fields: "CanDelete,PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', EnableTotalRecordCount: false, - EnableImageTypes: "Primary,Thumb,Backdrop" + EnableImageTypes: 'Primary,Thumb,Backdrop' }); upcomingRecordingsPromise = ApiClient.getLiveTvTimers({ IsActive: false, diff --git a/src/controllers/livetv/livetvseriestimers.js b/src/controllers/livetv/livetvseriestimers.js index 9c95cfa91f5..27daca19837 100644 --- a/src/controllers/livetv/livetvseriestimers.js +++ b/src/controllers/livetv/livetvseriestimers.js @@ -1,16 +1,16 @@ -define(["datetime", "cardBuilder", "imageLoader", "apphost", "loading", "paper-icon-button-light", "emby-button"], function (datetime, cardBuilder, imageLoader, appHost, loading) { - "use strict"; +define(['datetime', 'cardBuilder', 'imageLoader', 'apphost', 'loading', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader, appHost, loading) { + 'use strict'; function renderTimers(context, timers) { - var html = ""; - appHost.supports("imageanalysis"); + var html = ''; + appHost.supports('imageanalysis'); html += cardBuilder.getCardsHtml({ items: timers, - shape: "auto", - defaultShape: "portrait", + shape: 'auto', + defaultShape: 'portrait', showTitle: true, cardLayout: false, - preferThumb: "auto", + preferThumb: 'auto', coverImage: true, overlayText: false, showSeriesTimerTime: true, @@ -19,7 +19,7 @@ define(["datetime", "cardBuilder", "imageLoader", "apphost", "loading", "paper-i overlayMoreButton: true, lines: 3 }); - var elem = context.querySelector("#items"); + var elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); loading.hide(); @@ -33,8 +33,8 @@ define(["datetime", "cardBuilder", "imageLoader", "apphost", "loading", "paper-i } var query = { - SortBy: "SortName", - SortOrder: "Ascending" + SortBy: 'SortName', + SortOrder: 'Ascending' }; return function (view, params, tabContent) { var timersPromise; diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 8f65df7e6f2..036eee9fc68 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -1,5 +1,5 @@ -define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", "libraryBrowser", "mainTabsManager", "cardBuilder", "apphost", "imageLoader", "scrollStyles", "emby-itemscontainer", "emby-tabs", "emby-button"], function (layoutManager, userSettings, inputManager, loading, globalize, libraryBrowser, mainTabsManager, cardBuilder, appHost, imageLoader) { - "use strict"; +define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize', 'libraryBrowser', 'mainTabsManager', 'cardBuilder', 'apphost', 'imageLoader', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (layoutManager, userSettings, inputManager, loading, globalize, libraryBrowser, mainTabsManager, cardBuilder, appHost, imageLoader) { + 'use strict'; function enableScrollX() { return !layoutManager.desktop; @@ -7,16 +7,16 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", function getBackdropShape() { if (enableScrollX()) { - return "overflowBackdrop"; + return 'overflowBackdrop'; } - return "backdrop"; + return 'backdrop'; } function getPortraitShape() { if (enableScrollX()) { - return "overflowPortrait"; + return 'overflowPortrait'; } - return "portrait"; + return 'portrait'; } function getLimit() { @@ -40,17 +40,17 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", IsAiring: true, limit: limit, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Thumb,Backdrop", + EnableImageTypes: 'Primary,Thumb,Backdrop', EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio" + Fields: 'ChannelInfo,PrimaryImageAspectRatio' }).then(function (result) { - renderItems(page, result.Items, "activeProgramItems", "play", { + renderItems(page, result.Items, 'activeProgramItems', 'play', { showAirDateTime: false, showAirEndTime: true }); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); @@ -69,10 +69,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", IsNews: false, IsSeries: true, EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio", - EnableImageTypes: "Primary,Thumb" + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, "upcomingEpisodeItems"); + renderItems(page, result.Items, 'upcomingEpisodeItems'); }); ApiClient.getLiveTvPrograms({ userId: Dashboard.getCurrentUserId(), @@ -80,10 +80,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", limit: getLimit(), IsMovie: true, EnableTotalRecordCount: false, - Fields: "ChannelInfo", - EnableImageTypes: "Primary,Thumb" + Fields: 'ChannelInfo', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, "upcomingTvMovieItems", null, { + renderItems(page, result.Items, 'upcomingTvMovieItems', null, { shape: getPortraitShape(), preferThumb: null, showParentTitle: false @@ -95,10 +95,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", limit: getLimit(), IsSports: true, EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio", - EnableImageTypes: "Primary,Thumb" + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, "upcomingSportsItems"); + renderItems(page, result.Items, 'upcomingSportsItems'); }); ApiClient.getLiveTvPrograms({ userId: Dashboard.getCurrentUserId(), @@ -106,10 +106,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", limit: getLimit(), IsKids: true, EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio", - EnableImageTypes: "Primary,Thumb" + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, "upcomingKidsItems"); + renderItems(page, result.Items, 'upcomingKidsItems'); }); ApiClient.getLiveTvPrograms({ userId: Dashboard.getCurrentUserId(), @@ -117,10 +117,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", limit: getLimit(), IsNews: true, EnableTotalRecordCount: false, - Fields: "ChannelInfo,PrimaryImageAspectRatio", - EnableImageTypes: "Primary,Thumb" + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, "upcomingNewsItems", null, { + renderItems(page, result.Items, 'upcomingNewsItems', null, { showParentTitleOrTitle: true, showTitle: false, showParentTitle: false @@ -132,9 +132,9 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", function renderItems(page, items, sectionClass, overlayButton, cardOptions) { var html = cardBuilder.getCardsHtml(Object.assign({ items: items, - preferThumb: "auto", + preferThumb: 'auto', inheritThumb: false, - shape: enableScrollX() ? "autooverflow" : "auto", + shape: enableScrollX() ? 'autooverflow' : 'auto', defaultShape: getBackdropShape(), showParentTitle: true, showTitle: true, @@ -142,57 +142,57 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", coverImage: true, overlayText: false, lazy: true, - overlayPlayButton: "play" === overlayButton, - overlayMoreButton: "more" === overlayButton, - overlayInfoButton: "info" === overlayButton, + overlayPlayButton: 'play' === overlayButton, + overlayMoreButton: 'more' === overlayButton, + overlayInfoButton: 'info' === overlayButton, allowBottomPadding: !enableScrollX(), showAirTime: true, showAirDateTime: true }, cardOptions || {})); - var elem = page.querySelector("." + sectionClass); + var elem = page.querySelector('.' + sectionClass); elem.innerHTML = html; imageLoader.lazyChildren(elem); } function getTabs() { return [{ - name: globalize.translate("Programs") + name: globalize.translate('Programs') }, { - name: globalize.translate("TabGuide") + name: globalize.translate('TabGuide') }, { - name: globalize.translate("TabChannels") + name: globalize.translate('TabChannels') }, { - name: globalize.translate("TabRecordings") + name: globalize.translate('TabRecordings') }, { - name: globalize.translate("HeaderSchedule") + name: globalize.translate('HeaderSchedule') }, { - name: globalize.translate("TabSeries") + name: globalize.translate('TabSeries') }, { - name: globalize.translate("ButtonSearch"), - cssClass: "searchTabButton" + name: globalize.translate('ButtonSearch'), + cssClass: 'searchTabButton' }]; } function setScrollClasses(elem, scrollX) { if (scrollX) { - elem.classList.add("hiddenScrollX"); + elem.classList.add('hiddenScrollX'); if (layoutManager.tv) { - elem.classList.add("smoothScrollX"); + elem.classList.add('smoothScrollX'); } - elem.classList.add("scrollX"); - elem.classList.remove("vertical-wrap"); + 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"); + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } function getDefaultTabIndex(folderId) { - if (userSettings.get("landing-" + folderId) === "guide") { + if (userSettings.get('landing-' + folderId) === 'guide') { return 1; } @@ -219,7 +219,7 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", } function getTabContainers() { - return view.querySelectorAll(".pageTabContent"); + return view.querySelectorAll('.pageTabContent'); } function initTabs() { @@ -235,27 +235,27 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", break; case 1: - depends.push("controllers/livetv/livetvguide"); + depends.push('controllers/livetv/livetvguide'); break; case 2: - depends.push("controllers/livetv/livetvchannels"); + depends.push('controllers/livetv/livetvchannels'); break; case 3: - depends.push("controllers/livetv/livetvrecordings"); + depends.push('controllers/livetv/livetvrecordings'); break; case 4: - depends.push("controllers/livetv/livetvschedule"); + depends.push('controllers/livetv/livetvschedule'); break; case 5: - depends.push("controllers/livetv/livetvseriestimers"); + depends.push('controllers/livetv/livetvseriestimers'); break; case 6: - depends.push("scripts/searchtab"); + depends.push('scripts/searchtab'); } require(depends, function (controllerFactory) { @@ -275,7 +275,7 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", controller = self; } else if (6 === index) { controller = new controllerFactory(view, tabContent, { - collectionType: "livetv" + collectionType: 'livetv' }); } else { controller = new controllerFactory(view, params, tabContent); @@ -320,28 +320,28 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", } function onInputCommand(evt) { - if (evt.detail.command === "search") { + if (evt.detail.command === 'search') { evt.preventDefault(); - Dashboard.navigate("search.html?collectionType=livetv"); + Dashboard.navigate('search.html?collectionType=livetv'); } } var isViewRestored; var self = this; - var currentTabIndex = parseInt(params.tab || getDefaultTabIndex("livetv")); + var currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv')); var initialTabIndex = currentTabIndex; var lastFullRender = 0; - [].forEach.call(view.querySelectorAll(".sectionTitleTextButton-programs"), function (link) { + [].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) { var href = link.href; if (href) { - link.href = href + "&serverId=" + ApiClient.serverId(); + link.href = href + '&serverId=' + ApiClient.serverId(); } }); self.initTab = function () { var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - var containers = tabContent.querySelectorAll(".itemsContainer"); + var containers = tabContent.querySelectorAll('.itemsContainer'); for (var i = 0, length = containers.length; i < length; i++) { setScrollClasses(containers[i], enableScrollX()); @@ -362,11 +362,11 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", var currentTabController; var tabControllers = []; var renderedTabs = []; - view.addEventListener("viewbeforeshow", function (evt) { + view.addEventListener('viewbeforeshow', function (evt) { isViewRestored = evt.detail.isRestored; initTabs(); }); - view.addEventListener("viewshow", function (evt) { + view.addEventListener('viewshow', function (evt) { isViewRestored = evt.detail.isRestored; if (!isViewRestored) { @@ -375,14 +375,14 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", inputManager.on(window, onInputCommand); }); - view.addEventListener("viewbeforehide", function (e) { + view.addEventListener('viewbeforehide', function (e) { if (currentTabController && currentTabController.onHide) { currentTabController.onHide(); } inputManager.off(window, onInputCommand); }); - view.addEventListener("viewdestroy", function (evt) { + view.addEventListener('viewdestroy', function (evt) { tabControllers.forEach(function (tabController) { if (tabController.destroy) { tabController.destroy(); diff --git a/src/controllers/livetvguideprovider.js b/src/controllers/livetvguideprovider.js index a58917f22d1..e83036992bd 100644 --- a/src/controllers/livetvguideprovider.js +++ b/src/controllers/livetvguideprovider.js @@ -1,30 +1,30 @@ -define(["events", "loading"], function (events, loading) { - "use strict"; +define(['events', 'loading', 'globalize'], function (events, loading, globalize) { + 'use strict'; function onListingsSubmitted() { - Dashboard.navigate("livetvstatus.html"); + Dashboard.navigate('livetvstatus.html'); } function init(page, type, providerId) { - var url = "components/tvproviders/" + type + ".js"; + var url = 'components/tvproviders/' + type + '.js'; require([url], function (factory) { var instance = new factory(page, providerId, {}); - events.on(instance, "submitted", onListingsSubmitted); + events.on(instance, 'submitted', onListingsSubmitted); instance.init(); }); } function loadTemplate(page, type, providerId) { - require(["text!./components/tvproviders/" + type + ".template.html"], function (html) { - page.querySelector(".providerTemplate").innerHTML = Globalize.translateDocument(html); + require(['text!./components/tvproviders/' + type + '.template.html'], function (html) { + page.querySelector('.providerTemplate').innerHTML = globalize.translateDocument(html); init(page, type, providerId); }); } - pageIdOn("pageshow", "liveTvGuideProviderPage", function () { + pageIdOn('pageshow', 'liveTvGuideProviderPage', function () { loading.show(); - var providerId = getParameterByName("id"); - loadTemplate(this, getParameterByName("type"), providerId); + var providerId = getParameterByName('id'); + loadTemplate(this, getParameterByName('type'), providerId); }); }); diff --git a/src/controllers/livetvsettings.js b/src/controllers/livetvsettings.js index 2b11071c756..4c5fdc60de6 100644 --- a/src/controllers/livetvsettings.js +++ b/src/controllers/livetvsettings.js @@ -1,38 +1,38 @@ -define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) { - "use strict"; +define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading, globalize) { + 'use strict'; function loadPage(page, config) { - $(".liveTvSettingsForm", page).show(); - $(".noLiveTvServices", page).hide(); - $("#selectGuideDays", page).val(config.GuideDays || ""); - $("#txtPrePaddingMinutes", page).val(config.PrePaddingSeconds / 60); - $("#txtPostPaddingMinutes", page).val(config.PostPaddingSeconds / 60); - page.querySelector("#txtRecordingPath").value = config.RecordingPath || ""; - page.querySelector("#txtMovieRecordingPath").value = config.MovieRecordingPath || ""; - page.querySelector("#txtSeriesRecordingPath").value = config.SeriesRecordingPath || ""; - page.querySelector("#txtPostProcessor").value = config.RecordingPostProcessor || ""; - page.querySelector("#txtPostProcessorArguments").value = config.RecordingPostProcessorArguments || ""; + $('.liveTvSettingsForm', page).show(); + $('.noLiveTvServices', page).hide(); + $('#selectGuideDays', page).val(config.GuideDays || ''); + $('#txtPrePaddingMinutes', page).val(config.PrePaddingSeconds / 60); + $('#txtPostPaddingMinutes', page).val(config.PostPaddingSeconds / 60); + page.querySelector('#txtRecordingPath').value = config.RecordingPath || ''; + page.querySelector('#txtMovieRecordingPath').value = config.MovieRecordingPath || ''; + page.querySelector('#txtSeriesRecordingPath').value = config.SeriesRecordingPath || ''; + page.querySelector('#txtPostProcessor').value = config.RecordingPostProcessor || ''; + page.querySelector('#txtPostProcessorArguments').value = config.RecordingPostProcessorArguments || ''; loading.hide(); } function onSubmit() { loading.show(); var form = this; - ApiClient.getNamedConfiguration("livetv").then(function (config) { - config.GuideDays = $("#selectGuideDays", form).val() || null; - var recordingPath = form.querySelector("#txtRecordingPath").value || null; - var movieRecordingPath = form.querySelector("#txtMovieRecordingPath").value || null; - var seriesRecordingPath = form.querySelector("#txtSeriesRecordingPath").value || null; + ApiClient.getNamedConfiguration('livetv').then(function (config) { + config.GuideDays = $('#selectGuideDays', form).val() || null; + var recordingPath = form.querySelector('#txtRecordingPath').value || null; + var movieRecordingPath = form.querySelector('#txtMovieRecordingPath').value || null; + var seriesRecordingPath = form.querySelector('#txtSeriesRecordingPath').value || null; var recordingPathChanged = recordingPath != config.RecordingPath || movieRecordingPath != config.MovieRecordingPath || seriesRecordingPath != config.SeriesRecordingPath; config.RecordingPath = recordingPath; config.MovieRecordingPath = movieRecordingPath; config.SeriesRecordingPath = seriesRecordingPath; - config.RecordingEncodingFormat = "mkv"; - config.PrePaddingSeconds = 60 * $("#txtPrePaddingMinutes", form).val(); - config.PostPaddingSeconds = 60 * $("#txtPostPaddingMinutes", form).val(); - config.RecordingPostProcessor = $("#txtPostProcessor", form).val(); - config.RecordingPostProcessorArguments = $("#txtPostProcessorArguments", form).val(); - ApiClient.updateNamedConfiguration("livetv", config).then(function () { + config.RecordingEncodingFormat = 'mkv'; + config.PrePaddingSeconds = 60 * $('#txtPrePaddingMinutes', form).val(); + config.PostPaddingSeconds = 60 * $('#txtPostPaddingMinutes', form).val(); + config.RecordingPostProcessor = $('#txtPostProcessor', form).val(); + config.RecordingPostProcessorArguments = $('#txtPostProcessorArguments', form).val(); + ApiClient.updateNamedConfiguration('livetv', config).then(function () { Dashboard.processServerConfigurationUpdateResult(); showSaveMessage(recordingPathChanged); }); @@ -41,29 +41,29 @@ define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) } function showSaveMessage(recordingPathChanged) { - var msg = ""; + var msg = ''; if (recordingPathChanged) { - msg += Globalize.translate("RecordingPathChangeMessage"); + msg += globalize.translate('RecordingPathChangeMessage'); } if (msg) { - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert(msg); }); } } - $(document).on("pageinit", "#liveTvSettingsPage", function () { + $(document).on('pageinit', '#liveTvSettingsPage', function () { var page = this; - $(".liveTvSettingsForm").off("submit", onSubmit).on("submit", onSubmit); - $("#btnSelectRecordingPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('.liveTvSettingsForm').off('submit', onSubmit).on('submit', onSubmit); + $('#btnSelectRecordingPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ callback: function (path) { if (path) { - $("#txtRecordingPath", page).val(path); + $('#txtRecordingPath', page).val(path); } picker.close(); @@ -72,13 +72,13 @@ define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) }); }); }); - $("#btnSelectMovieRecordingPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('#btnSelectMovieRecordingPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ callback: function (path) { if (path) { - $("#txtMovieRecordingPath", page).val(path); + $('#txtMovieRecordingPath', page).val(path); } picker.close(); @@ -87,13 +87,13 @@ define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) }); }); }); - $("#btnSelectSeriesRecordingPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('#btnSelectSeriesRecordingPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ callback: function (path) { if (path) { - $("#txtSeriesRecordingPath", page).val(path); + $('#txtSeriesRecordingPath', page).val(path); } picker.close(); @@ -102,14 +102,14 @@ define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) }); }); }); - $("#btnSelectPostProcessorPath", page).on("click.selectDirectory", function () { - require(["directorybrowser"], function (directoryBrowser) { + $('#btnSelectPostProcessorPath', page).on('click.selectDirectory', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ includeFiles: true, callback: function (path) { if (path) { - $("#txtPostProcessor", page).val(path); + $('#txtPostProcessor', page).val(path); } picker.close(); @@ -117,10 +117,10 @@ define(["jQuery", "loading", "fnchecked", "emby-button"], function ($, loading) }); }); }); - }).on("pageshow", "#liveTvSettingsPage", function () { + }).on('pageshow', '#liveTvSettingsPage', function () { loading.show(); var page = this; - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { loadPage(page, config); }); }); diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index aee5876a45a..82d0b697db8 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -1,23 +1,23 @@ -define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layoutManager", "loading", "browser", "listViewStyle", "flexStyles", "emby-itemscontainer", "cardStyle", "material-icons", "emby-button"], function ($, globalize, taskButton, dom, libraryMenu, layoutManager, loading, browser) { - "use strict"; +define(['jQuery', 'globalize', 'scripts/taskbutton', 'dom', 'libraryMenu', 'layoutManager', 'loading', 'browser', 'listViewStyle', 'flexStyles', 'emby-itemscontainer', 'cardStyle', 'material-icons', 'emby-button'], function ($, globalize, taskButton, dom, libraryMenu, layoutManager, loading, browser) { + 'use strict'; var enableFocusTransform = !browser.slow && !browser.edge; function getDeviceHtml(device) { var padderClass; - var html = ""; - var cssClass = "card scalableCard"; - var cardBoxCssClass = "cardBox visualCardBox"; - cssClass += " backdropCard backdropCard-scalable"; - padderClass = "cardPadder-backdrop"; + var html = ''; + var cssClass = 'card scalableCard'; + var cardBoxCssClass = 'cardBox visualCardBox'; + cssClass += ' backdropCard backdropCard-scalable'; + padderClass = 'cardPadder-backdrop'; // TODO move card creation code to Card component if (layoutManager.tv) { - cssClass += " show-focus"; + cssClass += ' show-focus'; if (enableFocusTransform) { - cssClass += " show-animation"; + cssClass += ' show-animation'; } } @@ -26,34 +26,34 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo html += '
'; html += '
'; html += '
'; - html += '
dvr
'; - html += "
"; - html += "
"; + html += '
'; + html += '
'; + html += '
'; html += '
'; - html += ''; - html += '
' + (device.FriendlyName || getTunerName(device.Type)) + "
"; + html += ''; + html += '
' + (device.FriendlyName || getTunerName(device.Type)) + '
'; html += '
'; - html += device.Url || " "; - html += "
"; - html += "
"; - html += "
"; - return html += ""; + html += device.Url || ' '; + html += ''; + html += ''; + html += ''; + return html += ''; } function renderDevices(page, devices) { - var html = devices.map(getDeviceHtml).join(""); - page.querySelector(".devicesList").innerHTML = html; + var html = devices.map(getDeviceHtml).join(''); + page.querySelector('.devicesList').innerHTML = html; } function deleteDevice(page, id) { - var message = globalize.translate("MessageConfirmDeleteTunerDevice"); + var message = globalize.translate('MessageConfirmDeleteTunerDevice'); - require(["confirm"], function (confirm) { - confirm(message, globalize.translate("HeaderDeleteDevice")).then(function () { + require(['confirm'], function (confirm) { + confirm(message, globalize.translate('HeaderDeleteDevice')).then(function () { loading.show(); ApiClient.ajax({ - type: "DELETE", - url: ApiClient.getUrl("LiveTv/TunerHosts", { + type: 'DELETE', + url: ApiClient.getUrl('LiveTv/TunerHosts', { Id: id }) }).then(function () { @@ -65,7 +65,7 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo function reload(page) { loading.show(); - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { renderDevices(page, config.TunerHosts); renderProviders(page, config.ListingProviders); }); @@ -73,27 +73,27 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo } function submitAddDeviceForm(page) { - page.querySelector(".dlgAddDevice").close(); + page.querySelector('.dlgAddDevice').close(); loading.show(); ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/TunerHosts"), + type: 'POST', + url: ApiClient.getUrl('LiveTv/TunerHosts'), data: JSON.stringify({ - Type: $("#selectTunerDeviceType", page).val(), - Url: $("#txtDevicePath", page).val() + Type: $('#selectTunerDeviceType', page).val(), + Url: $('#txtDevicePath', page).val() }), - contentType: "application/json" + contentType: 'application/json' }).then(function () { reload(page); }, function () { Dashboard.alert({ - message: globalize.translate("ErrorAddingTunerDevice") + message: globalize.translate('ErrorAddingTunerDevice') }); }); } function renderProviders(page, providers) { - var html = ""; + var html = ''; if (providers.length) { html += '
'; @@ -101,27 +101,27 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo for (var i = 0, length = providers.length; i < length; i++) { var provider = providers[i]; html += '"; + html += provider.Path || provider.ListingsId || ''; + html += '
'; + html += ''; + html += ''; + html += ''; + html += ''; } - html += ""; + html += ''; } - var elem = $(".providerList", page).html(html); - $(".btnOptions", elem).on("click", function () { - var id = this.getAttribute("data-id"); + var elem = $('.providerList', page).html(html); + $('.btnOptions', elem).on('click', function () { + var id = this.getAttribute('data-id'); showProviderOptions(page, id, this); }); } @@ -129,25 +129,25 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo function showProviderOptions(page, providerId, button) { var items = []; items.push({ - name: globalize.translate("ButtonDelete"), - id: "delete" + name: globalize.translate('ButtonDelete'), + id: 'delete' }); items.push({ - name: globalize.translate("MapChannels"), - id: "map" + name: globalize.translate('MapChannels'), + id: 'map' }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: items, positionTo: button }).then(function (id) { switch (id) { - case "delete": + case 'delete': deleteProvider(page, providerId); break; - case "map": + case 'map': mapChannels(page, providerId); } }); @@ -155,7 +155,7 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo } function mapChannels(page, providerId) { - require(["components/channelmapper/channelmapper"], function (channelmapper) { + require(['components/channelMapper/channelMapper'], function (channelmapper) { new channelmapper({ serverId: ApiClient.serverInfo().Id, providerId: providerId @@ -164,14 +164,14 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo } function deleteProvider(page, id) { - var message = globalize.translate("MessageConfirmDeleteGuideProvider"); + var message = globalize.translate('MessageConfirmDeleteGuideProvider'); - require(["confirm"], function (confirm) { - confirm(message, globalize.translate("HeaderDeleteProvider")).then(function () { + require(['confirm'], function (confirm) { + confirm(message, globalize.translate('HeaderDeleteProvider')).then(function () { loading.show(); ApiClient.ajax({ - type: "DELETE", - url: ApiClient.getUrl("LiveTv/ListingProviders", { + type: 'DELETE', + url: ApiClient.getUrl('LiveTv/ListingProviders', { Id: id }) }).then(function () { @@ -185,51 +185,51 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo function getTunerName(providerId) { switch (providerId = providerId.toLowerCase()) { - case "m3u": - return "M3U"; - case "hdhomerun": - return "HDHomeRun"; - case "hauppauge": - return "Hauppauge"; - case "satip": - return "DVB"; + case 'm3u': + return 'M3U'; + case 'hdhomerun': + return 'HDHomeRun'; + case 'hauppauge': + return 'Hauppauge'; + case 'satip': + return 'DVB'; default: - return "Unknown"; + return 'Unknown'; } } function getProviderName(providerId) { switch (providerId = providerId.toLowerCase()) { - case "schedulesdirect": - return "Schedules Direct"; - case "xmltv": - return "XMLTV"; + case 'schedulesdirect': + return 'Schedules Direct'; + case 'xmltv': + return 'XMLTV'; default: - return "Unknown"; + return 'Unknown'; } } function getProviderConfigurationUrl(providerId) { switch (providerId = providerId.toLowerCase()) { - case "xmltv": - return "livetvguideprovider.html?type=xmltv"; - case "schedulesdirect": - return "livetvguideprovider.html?type=schedulesdirect"; + case 'xmltv': + return 'livetvguideprovider.html?type=xmltv'; + case 'schedulesdirect': + return 'livetvguideprovider.html?type=schedulesdirect'; } } function addProvider(button) { var menuItems = []; menuItems.push({ - name: "Schedules Direct", - id: "SchedulesDirect" + name: 'Schedules Direct', + id: 'SchedulesDirect' }); menuItems.push({ - name: "XMLTV", - id: "xmltv" + name: 'XMLTV', + id: 'xmltv' }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: button, @@ -241,81 +241,81 @@ define(["jQuery", "globalize", "scripts/taskbutton", "dom", "libraryMenu", "layo } function addDevice(button) { - Dashboard.navigate("livetvtuner.html"); + Dashboard.navigate('livetvtuner.html'); } function showDeviceMenu(button, tunerDeviceId) { var items = []; items.push({ - name: globalize.translate("ButtonDelete"), - id: "delete" + name: globalize.translate('ButtonDelete'), + id: 'delete' }); items.push({ - name: globalize.translate("ButtonEdit"), - id: "edit" + name: globalize.translate('ButtonEdit'), + id: 'edit' }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: items, positionTo: button }).then(function (id) { switch (id) { - case "delete": - deleteDevice(dom.parentWithClass(button, "page"), tunerDeviceId); + case 'delete': + deleteDevice(dom.parentWithClass(button, 'page'), tunerDeviceId); break; - case "edit": - Dashboard.navigate("livetvtuner.html?id=" + tunerDeviceId); + case 'edit': + Dashboard.navigate('livetvtuner.html?id=' + tunerDeviceId); } }); }); } function onDevicesListClick(e) { - var card = dom.parentWithClass(e.target, "card"); + var card = dom.parentWithClass(e.target, 'card'); if (card) { - var id = card.getAttribute("data-id"); - var btnCardOptions = dom.parentWithClass(e.target, "btnCardOptions"); + var id = card.getAttribute('data-id'); + var btnCardOptions = dom.parentWithClass(e.target, 'btnCardOptions'); if (btnCardOptions) { showDeviceMenu(btnCardOptions, id); } else { - Dashboard.navigate("livetvtuner.html?id=" + id); + Dashboard.navigate('livetvtuner.html?id=' + id); } } } - $(document).on("pageinit", "#liveTvStatusPage", function () { + $(document).on('pageinit', '#liveTvStatusPage', function () { var page = this; - $(".btnAddDevice", page).on("click", function () { + $('.btnAddDevice', page).on('click', function () { addDevice(this); }); - $(".formAddDevice", page).on("submit", function () { + $('.formAddDevice', page).on('submit', function () { submitAddDeviceForm(page); return false; }); - $(".btnAddProvider", page).on("click", function () { + $('.btnAddProvider', page).on('click', function () { addProvider(this); }); - page.querySelector(".devicesList").addEventListener("click", onDevicesListClick); - }).on("pageshow", "#liveTvStatusPage", function () { + page.querySelector('.devicesList').addEventListener('click', onDevicesListClick); + }).on('pageshow', '#liveTvStatusPage', function () { var page = this; reload(page); taskButton({ - mode: "on", - progressElem: page.querySelector(".refreshGuideProgress"), - taskKey: "RefreshGuide", - button: page.querySelector(".btnRefresh") + mode: 'on', + progressElem: page.querySelector('.refreshGuideProgress'), + taskKey: 'RefreshGuide', + button: page.querySelector('.btnRefresh') }); - }).on("pagehide", "#liveTvStatusPage", function () { + }).on('pagehide', '#liveTvStatusPage', function () { var page = this; taskButton({ - mode: "off", - progressElem: page.querySelector(".refreshGuideProgress"), - taskKey: "RefreshGuide", - button: page.querySelector(".btnRefresh") + mode: 'off', + progressElem: page.querySelector('.refreshGuideProgress'), + taskKey: 'RefreshGuide', + button: page.querySelector('.btnRefresh') }); }); }); diff --git a/src/controllers/livetvtuner.js b/src/controllers/livetvtuner.js index 55a86d4be79..d7a4d92db21 100644 --- a/src/controllers/livetvtuner.js +++ b/src/controllers/livetvtuner.js @@ -1,34 +1,34 @@ -define(["globalize", "loading", "libraryMenu", "dom", "emby-input", "emby-button", "emby-checkbox", "emby-select"], function (globalize, loading, libraryMenu, dom) { - "use strict"; +define(['globalize', 'loading', 'libraryMenu', 'dom', 'emby-input', 'emby-button', 'emby-checkbox', 'emby-select'], function (globalize, loading, libraryMenu, dom) { + 'use strict'; function isM3uVariant(type) { - return ["nextpvr"].indexOf(type || "") !== -1; + return ['nextpvr'].indexOf(type || '') !== -1; } function fillTypes(view, currentId) { - return ApiClient.getJSON(ApiClient.getUrl("LiveTv/TunerHosts/Types")).then(function (types) { - var selectType = view.querySelector(".selectType"); - var html = ""; + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/TunerHosts/Types')).then(function (types) { + var selectType = view.querySelector('.selectType'); + var html = ''; html += types.map(function (tuner) { - return '"; - }).join(""); + return ''; + }).join(''); html += '"; + html += globalize.translate('TabOther'); + html += ''; selectType.innerHTML = html; selectType.disabled = null != currentId; - selectType.value = ""; + selectType.value = ''; onTypeChange.call(selectType); }); } function reload(view, providerId) { - view.querySelector(".txtDevicePath").value = ""; - view.querySelector(".chkFavorite").checked = false; - view.querySelector(".txtDevicePath").value = ""; + view.querySelector('.txtDevicePath').value = ''; + view.querySelector('.chkFavorite').checked = false; + view.querySelector('.txtDevicePath').value = ''; if (providerId) { - ApiClient.getNamedConfiguration("livetv").then(function (config) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { var info = config.TunerHosts.filter(function (i) { return i.Id === providerId; })[0]; @@ -38,8 +38,8 @@ define(["globalize", "loading", "libraryMenu", "dom", "emby-input", "emby-button } function fillTunerHostInfo(view, info) { - var selectType = view.querySelector(".selectType"); - var type = info.Type || ""; + var selectType = view.querySelector('.selectType'); + var type = info.Type || ''; if (info.Source && isM3uVariant(info.Source)) { type = info.Source; @@ -47,54 +47,53 @@ define(["globalize", "loading", "libraryMenu", "dom", "emby-input", "emby-button selectType.value = type; onTypeChange.call(selectType); - view.querySelector(".txtDevicePath").value = info.Url || ""; - view.querySelector(".txtFriendlyName").value = info.FriendlyName || ""; - view.querySelector(".txtUserAgent").value = info.UserAgent || ""; - view.querySelector(".fldDeviceId").value = info.DeviceId || ""; - view.querySelector(".chkFavorite").checked = info.ImportFavoritesOnly; - view.querySelector(".chkTranscode").checked = info.AllowHWTranscoding; - view.querySelector(".chkStreamLoop").checked = info.EnableStreamLooping; - view.querySelector(".txtTunerCount").value = info.TunerCount || "0"; + view.querySelector('.txtDevicePath').value = info.Url || ''; + view.querySelector('.txtFriendlyName').value = info.FriendlyName || ''; + view.querySelector('.txtUserAgent').value = info.UserAgent || ''; + view.querySelector('.fldDeviceId').value = info.DeviceId || ''; + view.querySelector('.chkFavorite').checked = info.ImportFavoritesOnly; + view.querySelector('.chkTranscode').checked = info.AllowHWTranscoding; + view.querySelector('.chkStreamLoop').checked = info.EnableStreamLooping; + view.querySelector('.txtTunerCount').value = info.TunerCount || '0'; } function submitForm(page) { loading.show(); var info = { - Type: page.querySelector(".selectType").value, - Url: page.querySelector(".txtDevicePath").value || null, - UserAgent: page.querySelector(".txtUserAgent").value || null, - FriendlyName: page.querySelector(".txtFriendlyName").value || null, - DeviceId: page.querySelector(".fldDeviceId").value || null, - TunerCount: page.querySelector(".txtTunerCount").value || 0, - ImportFavoritesOnly: page.querySelector(".chkFavorite").checked, - AllowHWTranscoding: page.querySelector(".chkTranscode").checked, - EnableStreamLooping: page.querySelector(".chkStreamLoop").checked + Type: page.querySelector('.selectType').value, + Url: page.querySelector('.txtDevicePath').value || null, + UserAgent: page.querySelector('.txtUserAgent').value || null, + FriendlyName: page.querySelector('.txtFriendlyName').value || null, + DeviceId: page.querySelector('.fldDeviceId').value || null, + TunerCount: page.querySelector('.txtTunerCount').value || 0, + ImportFavoritesOnly: page.querySelector('.chkFavorite').checked, + AllowHWTranscoding: page.querySelector('.chkTranscode').checked, + EnableStreamLooping: page.querySelector('.chkStreamLoop').checked }; if (isM3uVariant(info.Type)) { info.Source = info.Type; - info.Type = "m3u"; + info.Type = 'm3u'; } - var id = getParameterByName("id"); + var id = getParameterByName('id'); if (id) { info.Id = id; } - info.Id; ApiClient.ajax({ - type: "POST", - url: ApiClient.getUrl("LiveTv/TunerHosts"), + type: 'POST', + url: ApiClient.getUrl('LiveTv/TunerHosts'), data: JSON.stringify(info), - contentType: "application/json" + contentType: 'application/json' }).then(function (result) { Dashboard.processServerConfigurationUpdateResult(); - Dashboard.navigate("livetvstatus.html"); + Dashboard.navigate('livetvstatus.html'); }, function () { loading.hide(); Dashboard.alert({ - message: globalize.translate("ErrorSavingTvProvider") + message: globalize.translate('ErrorSavingTvProvider') }); }); } @@ -106,7 +105,7 @@ define(["globalize", "loading", "libraryMenu", "dom", "emby-input", "emby-button } function getDetectedDevice() { - return getRequirePromise(["tunerPicker"]).then(function (tunerPicker) { + return getRequirePromise(['tunerPicker']).then(function (tunerPicker) { return new tunerPicker().show({ serverId: ApiClient.serverId() }); @@ -115,113 +114,113 @@ define(["globalize", "loading", "libraryMenu", "dom", "emby-input", "emby-button function onTypeChange() { var value = this.value; - var view = dom.parentWithClass(this, "page"); - var mayIncludeUnsupportedDrmChannels = "hdhomerun" === value; - var supportsTranscoding = "hdhomerun" === value; - var supportsFavorites = "hdhomerun" === value; - var supportsTunerIpAddress = "hdhomerun" === value; - var supportsTunerFileOrUrl = "m3u" === value; - var supportsStreamLooping = "m3u" === value; - var supportsTunerCount = "m3u" === value; - var supportsUserAgent = "m3u" === value; - var suppportsSubmit = "other" !== value; + var view = dom.parentWithClass(this, 'page'); + var mayIncludeUnsupportedDrmChannels = 'hdhomerun' === value; + var supportsTranscoding = 'hdhomerun' === value; + var supportsFavorites = 'hdhomerun' === value; + var supportsTunerIpAddress = 'hdhomerun' === value; + var supportsTunerFileOrUrl = 'm3u' === value; + var supportsStreamLooping = 'm3u' === value; + var supportsTunerCount = 'm3u' === value; + var supportsUserAgent = 'm3u' === value; + var suppportsSubmit = 'other' !== value; var supportsSelectablePath = supportsTunerFileOrUrl; - var txtDevicePath = view.querySelector(".txtDevicePath"); + var txtDevicePath = view.querySelector('.txtDevicePath'); if (supportsTunerIpAddress) { - txtDevicePath.label(globalize.translate("LabelTunerIpAddress")); - view.querySelector(".fldPath").classList.remove("hide"); + txtDevicePath.label(globalize.translate('LabelTunerIpAddress')); + view.querySelector('.fldPath').classList.remove('hide'); } else if (supportsTunerFileOrUrl) { - txtDevicePath.label(globalize.translate("LabelFileOrUrl")); - view.querySelector(".fldPath").classList.remove("hide"); + txtDevicePath.label(globalize.translate('LabelFileOrUrl')); + view.querySelector('.fldPath').classList.remove('hide'); } else { - view.querySelector(".fldPath").classList.add("hide"); + view.querySelector('.fldPath').classList.add('hide'); } if (supportsSelectablePath) { - view.querySelector(".btnSelectPath").classList.remove("hide"); - view.querySelector(".txtDevicePath").setAttribute("required", "required"); + view.querySelector('.btnSelectPath').classList.remove('hide'); + view.querySelector('.txtDevicePath').setAttribute('required', 'required'); } else { - view.querySelector(".btnSelectPath").classList.add("hide"); - view.querySelector(".txtDevicePath").removeAttribute("required"); + view.querySelector('.btnSelectPath').classList.add('hide'); + view.querySelector('.txtDevicePath').removeAttribute('required'); } if (supportsUserAgent) { - view.querySelector(".fldUserAgent").classList.remove("hide"); + view.querySelector('.fldUserAgent').classList.remove('hide'); } else { - view.querySelector(".fldUserAgent").classList.add("hide"); + view.querySelector('.fldUserAgent').classList.add('hide'); } if (supportsFavorites) { - view.querySelector(".fldFavorites").classList.remove("hide"); + view.querySelector('.fldFavorites').classList.remove('hide'); } else { - view.querySelector(".fldFavorites").classList.add("hide"); + view.querySelector('.fldFavorites').classList.add('hide'); } if (supportsTranscoding) { - view.querySelector(".fldTranscode").classList.remove("hide"); + view.querySelector('.fldTranscode').classList.remove('hide'); } else { - view.querySelector(".fldTranscode").classList.add("hide"); + view.querySelector('.fldTranscode').classList.add('hide'); } if (supportsStreamLooping) { - view.querySelector(".fldStreamLoop").classList.remove("hide"); + view.querySelector('.fldStreamLoop').classList.remove('hide'); } else { - view.querySelector(".fldStreamLoop").classList.add("hide"); + view.querySelector('.fldStreamLoop').classList.add('hide'); } if (supportsTunerCount) { - view.querySelector(".fldTunerCount").classList.remove("hide"); - view.querySelector(".txtTunerCount").setAttribute("required", "required"); + view.querySelector('.fldTunerCount').classList.remove('hide'); + view.querySelector('.txtTunerCount').setAttribute('required', 'required'); } else { - view.querySelector(".fldTunerCount").classList.add("hide"); - view.querySelector(".txtTunerCount").removeAttribute("required"); + view.querySelector('.fldTunerCount').classList.add('hide'); + view.querySelector('.txtTunerCount').removeAttribute('required'); } if (mayIncludeUnsupportedDrmChannels) { - view.querySelector(".drmMessage").classList.remove("hide"); + view.querySelector('.drmMessage').classList.remove('hide'); } else { - view.querySelector(".drmMessage").classList.add("hide"); + view.querySelector('.drmMessage').classList.add('hide'); } if (suppportsSubmit) { - view.querySelector(".button-submit").classList.remove("hide"); + view.querySelector('.button-submit').classList.remove('hide'); } else { - view.querySelector(".button-submit").classList.add("hide"); + view.querySelector('.button-submit').classList.add('hide'); } } return function (view, params) { if (!params.id) { - view.querySelector(".btnDetect").classList.remove("hide"); + view.querySelector('.btnDetect').classList.remove('hide'); } - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { var currentId = params.id; fillTypes(view, currentId).then(function () { reload(view, currentId); }); }); - view.querySelector("form").addEventListener("submit", function (e) { + view.querySelector('form').addEventListener('submit', function (e) { submitForm(view); e.preventDefault(); e.stopPropagation(); return false; }); - view.querySelector(".selectType").addEventListener("change", onTypeChange); - view.querySelector(".btnDetect").addEventListener("click", function () { + view.querySelector('.selectType').addEventListener('change', onTypeChange); + view.querySelector('.btnDetect').addEventListener('click', function () { getDetectedDevice().then(function (info) { fillTunerHostInfo(view, info); }); }); - view.querySelector(".btnSelectPath").addEventListener("click", function () { - require(["directorybrowser"], function (directoryBrowser) { + view.querySelector('.btnSelectPath').addEventListener('click', function () { + require(['directorybrowser'], function (directoryBrowser) { var picker = new directoryBrowser(); picker.show({ includeFiles: true, callback: function (path) { if (path) { - view.querySelector(".txtDevicePath").value = path; + view.querySelector('.txtDevicePath').value = path; } picker.close(); diff --git a/src/controllers/metadataimagespage.js b/src/controllers/metadataimagespage.js deleted file mode 100644 index a4932e9bb0d..00000000000 --- a/src/controllers/metadataimagespage.js +++ /dev/null @@ -1,64 +0,0 @@ -define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($, dom, loading, libraryMenu) { - "use strict"; - - function populateLanguages(select) { - return ApiClient.getCultures().then(function(languages) { - var html = ""; - html += ""; - for (var i = 0, length = languages.length; i < length; i++) { - var culture = languages[i]; - html += "" - } - select.innerHTML = html - }) - } - - function populateCountries(select) { - return ApiClient.getCountries().then(function(allCountries) { - var html = ""; - html += ""; - for (var i = 0, length = allCountries.length; i < length; i++) { - var culture = allCountries[i]; - html += "" - } - select.innerHTML = html - }) - } - - function loadPage(page) { - var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector("#selectLanguage")), populateCountries(page.querySelector("#selectCountry"))]; - Promise.all(promises).then(function(responses) { - var config = responses[0]; - page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage || "", page.querySelector("#selectCountry").value = config.MetadataCountryCode || "", loading.hide() - }) - } - - function onSubmit() { - var form = this; - return loading.show(), ApiClient.getServerConfiguration().then(function(config) { - config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult) - }), !1 - } - - function getTabs() { - return [{ - href: "library.html", - name: Globalize.translate("HeaderLibraries") - }, { - href: "librarydisplay.html", - name: Globalize.translate("TabDisplay") - }, { - href: "metadataimages.html", - name: Globalize.translate("TabMetadata") - }, { - 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) - }) -}); diff --git a/src/controllers/metadatanfo.js b/src/controllers/metadatanfo.js deleted file mode 100644 index 20049837ddb..00000000000 --- a/src/controllers/metadatanfo.js +++ /dev/null @@ -1,74 +0,0 @@ -define(["jQuery", "loading", "libraryMenu"], function ($, loading, libraryMenu) { - "use strict"; - - function loadPage(page, config, users) { - var 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(); - var 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(config); - }); - }); - return false; - } - - function showConfirmMessage(config) { - var msg = []; - msg.push(Globalize.translate("MetadataSettingChangeHelp")); - - require(["alert"], function (alert) { - alert({ - text: msg.join("

") - }); - }); - } - - function getTabs() { - return [{ - href: "library.html", - name: Globalize.translate("HeaderLibraries") - }, { - href: "librarydisplay.html", - name: Globalize.translate("TabDisplay") - }, { - href: "metadataimages.html", - name: Globalize.translate("TabMetadata") - }, { - href: "metadatanfo.html", - name: Globalize.translate("TabNfoSettings") - }]; - } - - var 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(); - var page = this; - var promise1 = ApiClient.getUsers(); - var promise2 = ApiClient.getNamedConfiguration(metadataKey); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[1], responses[0]); - }); - }); -}); diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index f17f64b7c87..54d30c0c373 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -1,5 +1,5 @@ -define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, appHost) { - "use strict"; +define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -9,18 +9,22 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "BoxSet", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'BoxSet', Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName", + Fields: 'PrimaryImageAspectRatio,SortName', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -34,7 +38,7 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB function getSavedQueryKey(context) { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey("moviecollections"); + context.savedQueryKey = libraryBrowser.getSavedQueryKey('moviecollections'); } return context.savedQueryKey; @@ -42,17 +46,17 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB function onViewStyleChange() { var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems(page) { @@ -65,7 +69,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -74,7 +80,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -91,45 +99,45 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB filterButton: false }); var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', overlayPlayButton: true, centerText: true, showTitle: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', lazy: true, cardLayout: true, showTitle: true }); - } else if (viewStyle == "Banner") { + } else if (viewStyle == 'Banner') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "banner", + shape: 'banner', preferBanner: true, - context: "movies", + context: 'movies', lazy: true }); - } else if (viewStyle == "List") { + } else if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, - context: "movies", + context: 'movies', sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "auto", - context: "movies", + shape: 'auto', + context: 'movies', showTitle: true, centerText: false, cardLayout: true @@ -137,8 +145,8 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "auto", - context: "movies", + shape: 'auto', + context: 'movies', centerText: true, overlayPlayButton: true, showTitle: true @@ -146,41 +154,45 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB } var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } if (!result.Items.length) { - html = '

' + Globalize.translate("MessageNoCollectionsAvailable") + "

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

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

'; + html += '

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

'; + html += '
'; } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; @@ -189,23 +201,23 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB }; function initPage(tabContent) { - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SortName" + name: globalize.translate('OptionNameSort'), + id: 'SortName' }, { - name: Globalize.translate("OptionImdbRating"), - id: "CommunityRating,SortName" + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' }, { - name: Globalize.translate("OptionParentalRating"), - id: "OfficialRating,SortName" + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "PremiereDate,SortName" + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' }], callback: function () { getQuery(tabContent).StartIndex = 0; @@ -215,11 +227,11 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB button: e.target }); }); - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "List,Poster,PosterCard,Thumb,ThumbCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); @@ -227,8 +239,8 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB onViewStyleChange(); reloadItems(tabContent); }); - tabContent.querySelector(".btnNewCollection").addEventListener("click", function () { - require(["collectionEditor"], function (collectionEditor) { + tabContent.querySelector('.btnNewCollection').addEventListener('click', function () { + require(['collectionEditor'], function (collectionEditor) { var serverId = ApiClient.serverInfo().Id; new collectionEditor().show({ items: [], diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index d51a2e4789d..ab410c1bd45 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader", "apphost", "globalize", "appRouter", "dom", "emby-button"], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { - "use strict"; +define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader', 'apphost', 'globalize', 'appRouter', 'dom', 'emby-button'], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { + 'use strict'; return function (view, params, tabContent) { function getPageData() { @@ -9,13 +9,13 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Movie", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', Recursive: true, EnableTotalRecordCount: false }, - view: "Poster" + view: 'Poster' }; pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); @@ -29,7 +29,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey("moviegenres"); + return libraryBrowser.getSavedQueryKey('moviegenres'); } function getPromise() { @@ -43,29 +43,30 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function getPortraitShape() { - return enableScrollX() ? "overflowPortrait" : "portrait"; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(elem) { - var id = elem.getAttribute("data-id"); + function fillItemsContainer(entry) { + var elem = entry.target; + var id = elem.getAttribute('data-id'); var viewStyle = self.getCurrentViewStyle(); - var limit = "Thumb" == viewStyle || "ThumbCard" == viewStyle ? 5 : 9; + var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; if (enableScrollX()) { limit = 10; } - var enableImageTypes = "Thumb" == viewStyle || "ThumbCard" == viewStyle ? "Primary,Backdrop,Thumb" : "Primary"; + var enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary'; var query = { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Movie", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', Recursive: true, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', ImageTypeLimit: 1, EnableImageTypes: enableImageTypes, Limit: limit, @@ -74,9 +75,9 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader ParentId: params.topParentId }; ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getThumbShape(), @@ -87,7 +88,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader overlayMoreButton: true, allowBottomPadding: false }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getThumbShape(), @@ -98,7 +99,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader cardLayout: true, showYear: true }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getPortraitShape(), @@ -108,7 +109,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader cardLayout: true, showYear: true }); - } else if (viewStyle == "Poster") { + } else if (viewStyle == 'Poster') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getPortraitShape(), @@ -121,7 +122,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader }); } if (result.Items.length >= query.Limit) { - tabContent.querySelector(".btnMoreFromGenre" + id + " i").classList.remove("hide"); + tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); } }); } @@ -129,8 +130,8 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader function reloadItems(context, promise) { var query = getQuery(); promise.then(function (result) { - var elem = context.querySelector("#items"); - var html = ""; + var elem = context.querySelector('#items'); + var html = ''; var items = result.Items; for (var i = 0, length = items.length; i < length; i++) { @@ -139,20 +140,20 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader html += '
'; html += '"; + html += ''; + html += ''; + html += ''; + html += '
'; if (enableScrollX()) { - var scrollXClass = "scrollX hiddenScrollX"; + var scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { - scrollXClass += "smoothScrollX padded-top-focusscale padded-bottom-focusscale"; + scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; } html += '
'; @@ -160,8 +161,17 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader html += '
'; } - html += "
"; - html += "
"; + html += ''; + html += ''; + } + + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

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

'; + html += '

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

'; + html += '
'; } elem.innerHTML = html; @@ -180,16 +190,16 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader var data = {}; self.getViewStyles = function () { - return "Poster,PosterCard,Thumb,ThumbCard".split(","); + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index ce077bd1796..c22b52c47e4 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -1,17 +1,17 @@ -define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", "alphaPicker", "listView", "cardBuilder", "emby-itemscontainer"], function (loading, layoutManager, userSettings, events, libraryBrowser, alphaPicker, listView, cardBuilder) { - "use strict"; +define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', 'alphaPicker', 'listView', 'cardBuilder', 'globalize', 'emby-itemscontainer'], function (loading, layoutManager, userSettings, events, libraryBrowser, alphaPicker, listView, cardBuilder, globalize) { + 'use strict'; return function (view, params, tabContent, options) { function onViewStyleChange() { - if (self.getCurrentViewStyle() == "List") { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if (self.getCurrentViewStyle() == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function updateFilterControls() { @@ -32,7 +32,9 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } itemsContainer.refreshItems(); } @@ -41,7 +43,9 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } itemsContainer.refreshItems(); } @@ -59,26 +63,26 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", }); var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } isLoading = false; loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(tabContent); }); } @@ -87,49 +91,49 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", var html; var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', lazy: true, overlayPlayButton: true, showTitle: true, showYear: true, centerText: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', lazy: true, cardLayout: true, showTitle: true, showYear: true, centerText: true }); - } else if (viewStyle == "Banner") { + } else if (viewStyle == 'Banner') { html = cardBuilder.getCardsHtml({ items: items, - shape: "banner", + shape: 'banner', preferBanner: true, - context: "movies", + context: 'movies', lazy: true }); - } else if (viewStyle == "List") { + } else if (viewStyle == 'List') { html = listView.getListViewHtml({ items: items, - context: "movies", + context: 'movies', sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: items, - shape: "portrait", - context: "movies", + shape: 'portrait', + context: 'movies', showTitle: true, showYear: true, centerText: true, @@ -139,8 +143,8 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", } else { html = cardBuilder.getCardsHtml({ items: items, - shape: "portrait", - context: "movies", + shape: 'portrait', + context: 'movies', overlayPlayButton: true, showTitle: true, showYear: true, @@ -155,10 +159,10 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", itemsContainer.fetchData = fetchData; itemsContainer.getItemsHtml = getItemsHtml; itemsContainer.afterRefresh = afterRefresh; - var alphaPickerElement = tabContent.querySelector(".alphaPicker"); + var alphaPickerElement = tabContent.querySelector('.alphaPicker'); if (alphaPickerElement) { - alphaPickerElement.addEventListener("alphavaluechanged", function (e) { + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { var newValue = e.detail.value; query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; @@ -166,53 +170,53 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", }); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, - valueChangeEvent: "click" + valueChangeEvent: 'click' }); - tabContent.querySelector(".alphaPicker").classList.add("alphabetPicker-right"); - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - itemsContainer.classList.add("padded-right-withalphapicker"); + tabContent.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); } - var btnFilter = tabContent.querySelector(".btnFilter"); + var btnFilter = tabContent.querySelector('.btnFilter'); if (btnFilter) { - btnFilter.addEventListener("click", function () { + btnFilter.addEventListener('click', function () { self.showFilterMenu(); }); } - var btnSort = tabContent.querySelector(".btnSort"); + var btnSort = tabContent.querySelector('.btnSort'); if (btnSort) { - btnSort.addEventListener("click", function (e) { + btnSort.addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SortName,ProductionYear" + name: globalize.translate('OptionNameSort'), + id: 'SortName,ProductionYear' }, { - name: Globalize.translate("OptionImdbRating"), - id: "CommunityRating,SortName,ProductionYear" + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName,ProductionYear' }, { - name: Globalize.translate("OptionCriticRating"), - id: "CriticRating,SortName,ProductionYear" + name: globalize.translate('OptionCriticRating'), + id: 'CriticRating,SortName,ProductionYear' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName,ProductionYear" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName,ProductionYear' }, { - name: Globalize.translate("OptionDatePlayed"), - id: "DatePlayed,SortName,ProductionYear" + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName,ProductionYear' }, { - name: Globalize.translate("OptionParentalRating"), - id: "OfficialRating,SortName,ProductionYear" + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName,ProductionYear' }, { - name: Globalize.translate("OptionPlayCount"), - id: "PlayCount,SortName,ProductionYear" + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SortName,ProductionYear' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "PremiereDate,SortName,ProductionYear" + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName,ProductionYear' }, { - name: Globalize.translate("OptionRuntime"), - id: "Runtime,SortName,ProductionYear" + name: globalize.translate('OptionRuntime'), + id: 'Runtime,SortName,ProductionYear' }], callback: function () { query.StartIndex = 0; @@ -224,11 +228,11 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", }); }); } - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "Banner,List,Poster,PosterCard,Thumb,ThumbCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; userSettings.set(savedViewKey, viewStyle); query.StartIndex = 0; @@ -238,37 +242,41 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", } var self = this; - var itemsContainer = tabContent.querySelector(".itemsContainer"); - var savedQueryKey = params.topParentId + "-" + options.mode; - var savedViewKey = savedQueryKey + "-view"; + var itemsContainer = tabContent.querySelector('.itemsContainer'); + var savedQueryKey = params.topParentId + '-' + options.mode; + var savedViewKey = savedQueryKey + '-view'; var query = { - SortBy: "SortName,ProductionYear", - SortOrder: "Ascending", - IncludeItemTypes: "Movie", + SortBy: 'SortName,ProductionYear', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', Recursive: true, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0, - Limit: 100, ParentId: params.topParentId }; + + if (userSettings.libraryPageSize() > 0) { + query['Limit'] = userSettings.libraryPageSize(); + } + var isLoading = false; - if (options.mode === "favorites") { + if (options.mode === 'favorites') { query.IsFavorite = true; } query = userSettings.loadQuerySettings(savedQueryKey, query); self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: query, - mode: "movies", + mode: 'movies', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { query.StartIndex = 0; itemsContainer.refreshItems(); }); @@ -277,7 +285,7 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", }; self.getCurrentViewStyle = function () { - return userSettings.get(savedViewKey) || "Poster"; + return userSettings.get(savedViewKey) || 'Poster'; }; self.initTab = function () { diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 7e19af4b9fc..d948c1cef77 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,31 +1,31 @@ -define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu", "mainTabsManager", "cardBuilder", "dom", "imageLoader", "playbackManager", "emby-scroller", "emby-itemscontainer", "emby-tabs", "emby-button"], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager) { - "use strict"; +define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) { + 'use strict'; function enableScrollX() { return !layoutManager.desktop; } function getPortraitShape() { - return enableScrollX() ? "overflowPortrait" : "portrait"; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function loadLatest(page, userId, parentId) { var options = { - IncludeItemTypes: "Movie", + IncludeItemTypes: 'Movie', Limit: 18, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; - ApiClient.getJSON(ApiClient.getUrl("Users/" + userId + "/Items/Latest", options)).then(function (items) { + ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { var allowBottomPadding = !enableScrollX(); - var container = page.querySelector("#recentlyAddedItems"); + var container = page.querySelector('#recentlyAddedItems'); cardBuilder.buildCards(items, { itemsContainer: container, shape: getPortraitShape(), @@ -45,28 +45,28 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" function loadResume(page, userId, parentId) { var screenWidth = dom.getWindowSize().innerWidth; var options = { - SortBy: "DatePlayed", - SortOrder: "Descending", - IncludeItemTypes: "Movie", - Filters: "IsResumable", + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Movie', + Filters: 'IsResumable', Limit: screenWidth >= 1920 ? 5 : screenWidth >= 1600 ? 5 : 3, Recursive: true, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', CollapseBoxSetItems: false, ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; ApiClient.getItems(userId, options).then(function (result) { if (result.Items.length) { - page.querySelector("#resumableSection").classList.remove("hide"); + page.querySelector('#resumableSection').classList.remove('hide'); } else { - page.querySelector("#resumableSection").classList.add("hide"); + page.querySelector('#resumableSection').classList.add('hide'); } var allowBottomPadding = !enableScrollX(); - var container = page.querySelector("#resumableItems"); + var container = page.querySelector('#resumableItems'); cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, @@ -86,31 +86,31 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" } function getRecommendationHtml(recommendation) { - var html = ""; - var title = ""; + var html = ''; + var title = ''; switch (recommendation.RecommendationType) { - case "SimilarToRecentlyPlayed": - title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName); + case 'SimilarToRecentlyPlayed': + title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName); break; - case "SimilarToLikedItem": - title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName); + case 'SimilarToLikedItem': + title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName); break; - case "HasDirectorFromRecentlyPlayed": - case "HasLikedDirector": - title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName); + case 'HasDirectorFromRecentlyPlayed': + case 'HasLikedDirector': + title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName); break; - case "HasActorFromRecentlyPlayed": - case "HasLikedActor": - title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName); + case 'HasActorFromRecentlyPlayed': + case 'HasLikedActor': + title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName); break; } html += '
'; - html += '

' + title + "

"; + html += '

' + title + '

'; var allowBottomPadding = true; if (enableScrollX()) { @@ -133,31 +133,31 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" if (enableScrollX()) { html += '
'; } - html += ""; - html += ""; + html += ''; + html += ''; return html; } function loadSuggestions(page, userId, parentId) { var screenWidth = dom.getWindowSize().innerWidth; - var url = ApiClient.getUrl("Movies/Recommendations", { + var url = ApiClient.getUrl('Movies/Recommendations', { userId: userId, categoryLimit: 6, ItemLimit: screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 6 : 5, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb" + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' }); ApiClient.getJSON(url).then(function (recommendations) { if (!recommendations.length) { - page.querySelector(".noItemsMessage").classList.remove("hide"); - page.querySelector(".recommendations").innerHTML = ""; + page.querySelector('.noItemsMessage').classList.remove('hide'); + page.querySelector('.recommendations').innerHTML = ''; return; } - var html = recommendations.map(getRecommendationHtml).join(""); - page.querySelector(".noItemsMessage").classList.add("hide"); - var recs = page.querySelector(".recommendations"); + var html = recommendations.map(getRecommendationHtml).join(''); + page.querySelector('.noItemsMessage').classList.add('hide'); + var recs = page.querySelector('.recommendations'); recs.innerHTML = html; imageLoader.lazyChildren(recs); @@ -167,33 +167,33 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" } function autoFocus(page) { - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); } function setScrollClasses(elem, scrollX) { if (scrollX) { - elem.classList.add("hiddenScrollX"); + 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('smoothScrollX'); + elem.classList.add('padded-top-focusscale'); + elem.classList.add('padded-bottom-focusscale'); } - elem.classList.add("scrollX"); - elem.classList.remove("vertical-wrap"); + 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"); + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } function initSuggestedTab(page, tabContent) { - var containers = tabContent.querySelectorAll(".itemsContainer"); + var containers = tabContent.querySelectorAll('.itemsContainer'); for (var i = 0, length = containers.length; i < length; i++) { setScrollClasses(containers[i], enableScrollX()); @@ -203,7 +203,7 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" function loadSuggestionsTab(view, params, tabContent) { var parentId = params.topParentId; var userId = ApiClient.getCurrentUserId(); - console.debug("loadSuggestionsTab"); + console.debug('loadSuggestionsTab'); loadResume(tabContent, userId, parentId); loadLatest(tabContent, userId, parentId); loadSuggestions(tabContent, userId, parentId); @@ -211,35 +211,35 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" function getTabs() { return [{ - name: Globalize.translate("Movies") + name: globalize.translate('Movies') }, { - name: Globalize.translate("TabSuggestions") + name: globalize.translate('TabSuggestions') }, { - name: Globalize.translate("TabTrailers") + name: globalize.translate('TabTrailers') }, { - name: Globalize.translate("TabFavorites") + name: globalize.translate('TabFavorites') }, { - name: Globalize.translate("TabCollections") + name: globalize.translate('TabCollections') }, { - name: Globalize.translate("TabGenres") + name: globalize.translate('TabGenres') }, { - name: Globalize.translate("ButtonSearch"), - cssClass: "searchTabButton" + name: globalize.translate('ButtonSearch'), + cssClass: 'searchTabButton' }]; } function getDefaultTabIndex(folderId) { - switch (userSettings.get("landing-" + folderId)) { - case "suggestions": + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': return 1; - case "favorites": + case 'favorites': return 3; - case "collections": + case 'collections': return 4; - case "genres": + case 'genres': return 5; default: @@ -258,7 +258,7 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" } function getTabContainers() { - return view.querySelectorAll(".pageTabContent"); + return view.querySelectorAll('.pageTabContent'); } function initTabs() { @@ -270,30 +270,30 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" switch (index) { case 0: - depends.push("controllers/movies/movies"); + depends.push('controllers/movies/movies'); break; case 1: break; case 2: - depends.push("controllers/movies/movietrailers"); + depends.push('controllers/movies/movietrailers'); break; case 3: - depends.push("controllers/movies/movies"); + depends.push('controllers/movies/movies'); break; case 4: - depends.push("controllers/movies/moviecollections"); + depends.push('controllers/movies/moviecollections'); break; case 5: - depends.push("controllers/movies/moviegenres"); + depends.push('controllers/movies/moviegenres'); break; case 6: - depends.push("scripts/searchtab"); + depends.push('scripts/searchtab'); } require(depends, function (controllerFactory) { @@ -313,12 +313,12 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" controller = self; } else if (index === 6) { controller = new controllerFactory(view, tabContent, { - collectionType: "movies", + collectionType: 'movies', parentId: params.topParentId }); } else if (index == 0 || index == 3) { controller = new controllerFactory(view, params, tabContent, { - mode: index ? "favorites" : "movies" + mode: index ? 'favorites' : 'movies' }); } else { controller = new controllerFactory(view, params, tabContent); @@ -356,7 +356,7 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" } function onPlaybackStop(e, state) { - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { renderedTabs = []; mainTabsManager.getTabsElement().triggerTabChange(); } @@ -364,9 +364,9 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" function onInputCommand(e) { switch (e.detail.command) { - case "search": + case 'search': e.preventDefault(); - Dashboard.navigate("search.html?collectionType=movies&parentId=" + params.topParentId); + Dashboard.navigate('search.html?collectionType=movies&parentId=' + params.topParentId); } } @@ -388,28 +388,28 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" var tabControllers = []; var renderedTabs = []; - view.addEventListener("viewshow", function (e) { - if (isViewRestored = e.detail.isRestored, initTabs(), !view.getAttribute("data-title")) { + view.addEventListener('viewshow', function (e) { + if (isViewRestored = e.detail.isRestored, initTabs(), !view.getAttribute('data-title')) { var parentId = params.topParentId; if (parentId) { ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute("data-title", item.Name); + view.setAttribute('data-title', item.Name); libraryMenu.setTitle(item.Name); }); } else { - view.setAttribute("data-title", Globalize.translate("TabMovies")); - libraryMenu.setTitle(Globalize.translate("TabMovies")); + view.setAttribute('data-title', globalize.translate('TabMovies')); + libraryMenu.setTitle(globalize.translate('TabMovies')); } } - events.on(playbackManager, "playbackstop", onPlaybackStop); + events.on(playbackManager, 'playbackstop', onPlaybackStop); inputManager.on(window, onInputCommand); }); - view.addEventListener("viewbeforehide", function (e) { + view.addEventListener('viewbeforehide', function (e) { inputManager.off(window, onInputCommand); }); - view.addEventListener("viewdestroy", function (e) { + view.addEventListener('viewdestroy', function (e) { tabControllers.forEach(function (t) { if (t.destroy) { t.destroy(); diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index c764190ee00..5daad8d7c32 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) { - "use strict"; +define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -9,18 +9,22 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Trailer", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Trailer', Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -33,7 +37,7 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " function getSavedQueryKey(context) { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey("trailers"); + context.savedQueryKey = libraryBrowser.getSavedQueryKey('trailers'); } return context.savedQueryKey; @@ -49,7 +53,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(); } @@ -58,7 +64,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(); } @@ -77,43 +85,43 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " var html; var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', overlayPlayButton: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "movies", + context: 'movies', cardLayout: true, showTitle: true, showYear: true, centerText: true }); - } else if (viewStyle == "Banner") { + } else if (viewStyle == 'Banner') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "banner", + shape: 'banner', preferBanner: true, - context: "movies" + context: 'movies' }); - } else if (viewStyle == "List") { + } else if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, - context: "movies", + context: 'movies', sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "portrait", - context: "movies", + shape: 'portrait', + context: 'movies', showTitle: true, showYear: true, cardLayout: true, @@ -122,8 +130,8 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "portrait", - context: "movies", + shape: 'portrait', + context: 'movies', centerText: true, overlayPlayButton: true, showTitle: true, @@ -133,27 +141,32 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } if (!result.Items.length) { - html = '

' + Globalize.translate("MessageNoTrailersFound") + "

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

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

'; + html += '

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

'; + html += '
'; } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(tabContent), query); @@ -168,18 +181,17 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), - mode: "movies", + mode: 'movies', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { getQuery(tabContent).StartIndex = 0; reloadItems(); }); @@ -192,9 +204,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector(".alphaPicker"); - var itemsContainer = tabContent.querySelector(".itemsContainer"); - alphaPickerElement.addEventListener("alphavaluechanged", function (e) { + var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + var itemsContainer = tabContent.querySelector('.itemsContainer'); + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { var newValue = e.detail.value; var query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; @@ -203,39 +215,39 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, - valueChangeEvent: "click" + valueChangeEvent: 'click' }); - tabContent.querySelector(".alphaPicker").classList.add("alphabetPicker-right"); - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - itemsContainer.classList.add("padded-right-withalphapicker"); + tabContent.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SortName" + name: globalize.translate('OptionNameSort'), + id: 'SortName' }, { - name: Globalize.translate("OptionImdbRating"), - id: "CommunityRating,SortName" + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' }, { - name: Globalize.translate("OptionDatePlayed"), - id: "DatePlayed,SortName" + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName' }, { - name: Globalize.translate("OptionParentalRating"), - id: "OfficialRating,SortName" + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' }, { - name: Globalize.translate("OptionPlayCount"), - id: "PlayCount,SortName" + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SortName' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "PremiereDate,SortName" + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' }], callback: function () { getQuery(tabContent).StartIndex = 0; diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 58d30e71da2..645daf4ad9a 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -1,5 +1,5 @@ -define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) { - "use strict"; +define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function playAll() { @@ -23,18 +23,22 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser if (!pageData) { pageData = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "MusicAlbum", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'MusicAlbum', Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -48,7 +52,7 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser function getSavedQueryKey() { if (!savedQueryKey) { - savedQueryKey = libraryBrowser.getSavedQueryKey("musicalbums"); + savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); } return savedQueryKey; @@ -56,17 +60,17 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser function onViewStyleChange() { var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems(page) { @@ -79,7 +83,9 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -88,7 +94,9 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -106,18 +114,18 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser filterButton: false }); var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "List") { + if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, - context: "music", + context: 'music', sortBy: query.SortBy, addToListButton: true }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", - context: "music", + shape: 'square', + context: 'music', showTitle: true, coverImage: true, showParentTitle: true, @@ -127,8 +135,8 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", - context: "music", + shape: 'square', + context: 'music', showTitle: true, showParentTitle: true, lazy: true, @@ -138,30 +146,30 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser } var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(tabContent); }); }); @@ -175,17 +183,16 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser var savedQueryKey; var pageData; var self = this; - var pageSize = 100; var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(), - mode: "albums", + mode: 'albums', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { getQuery().StartIndex = 0; reloadItems(tabContent); }); @@ -198,10 +205,10 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector(".alphaPicker"); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - alphaPickerElement.addEventListener("alphavaluechanged", function (e) { + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { var newValue = e.detail.value; var query = getQuery(); query.NameStartsWithOrGreater = newValue; @@ -210,39 +217,39 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser }); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, - valueChangeEvent: "click" + valueChangeEvent: 'click' }); - tabContent.querySelector(".alphaPicker").classList.add("alphabetPicker-right"); - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - itemsContainer.classList.add("padded-right-withalphapicker"); + tabContent.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SortName" + name: globalize.translate('OptionNameSort'), + id: 'SortName' }, { - name: Globalize.translate("OptionAlbumArtist"), - id: "AlbumArtist,SortName" + name: globalize.translate('OptionAlbumArtist'), + id: 'AlbumArtist,SortName' }, { - name: Globalize.translate("OptionCommunityRating"), - id: "CommunityRating,SortName" + name: globalize.translate('OptionCommunityRating'), + id: 'CommunityRating,SortName' }, { - name: Globalize.translate("OptionCriticRating"), - id: "CriticRating,SortName" + name: globalize.translate('OptionCriticRating'), + id: 'CriticRating,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "ProductionYear,PremiereDate,SortName" + name: globalize.translate('OptionReleaseDate'), + id: 'ProductionYear,PremiereDate,SortName' }, { - name: Globalize.translate("OptionRandom"), - id: "Random,SortName" + name: globalize.translate('OptionRandom'), + id: 'Random,SortName' }], callback: function () { getQuery().StartIndex = 0; @@ -252,11 +259,11 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser button: e.target }); }); - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "List,Poster,PosterCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); @@ -264,8 +271,8 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser onViewStyleChange(); reloadItems(tabContent); }); - tabContent.querySelector(".btnPlayAll").addEventListener("click", playAll); - tabContent.querySelector(".btnShuffle").addEventListener("click", shuffle); + tabContent.querySelector('.btnPlayAll').addEventListener('click', playAll); + tabContent.querySelector('.btnShuffle').addEventListener('click', shuffle); } initPage(tabContent); diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index ceed448a062..7a889ff8b98 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) { - "use strict"; +define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'apphost', 'userSettings', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost, userSettings) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -7,18 +7,23 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " var pageData = data[key]; if (!pageData) { + var 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: { - SortBy: "SortName", - SortOrder: "Ascending", - Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", - StartIndex: 0, - ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - Limit: 100 - }, - view: libraryBrowser.getSavedView(key) || "Poster" + query: queryValues, + view: libraryBrowser.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); @@ -41,17 +46,17 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " function onViewStyleChange() { var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems(page) { @@ -67,7 +72,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -76,7 +83,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -94,16 +103,16 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " filterButton: false }); var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "List") { + if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", - context: "music", + shape: 'square', + context: 'music', showTitle: true, coverImage: true, cardLayout: true @@ -111,8 +120,8 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", - context: "music", + shape: 'square', + context: 'music', showTitle: true, coverImage: true, lazy: true, @@ -122,30 +131,30 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(tabContent); }); }); @@ -161,13 +170,13 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: self.mode, serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { getQuery(tabContent).StartIndex = 0; reloadItems(tabContent); }); @@ -180,10 +189,10 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector(".alphaPicker"); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - alphaPickerElement.addEventListener("alphavaluechanged", function (e) { + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { var newValue = e.detail.value; var query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; @@ -192,21 +201,21 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, - valueChangeEvent: "click" + valueChangeEvent: 'click' }); - tabContent.querySelector(".alphaPicker").classList.add("alphabetPicker-right"); - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - itemsContainer.classList.add("padded-right-withalphapicker"); + tabContent.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "List,Poster,PosterCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index b465a4d3508..82f2eba574d 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -1,5 +1,5 @@ -define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - "use strict"; +define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { + 'use strict'; return function (view, params, tabContent) { function getPageData() { @@ -9,13 +9,13 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", + SortBy: 'SortName', + SortOrder: 'Ascending', Recursive: true, - Fields: "PrimaryImageAspectRatio,ItemCounts", + Fields: 'PrimaryImageAspectRatio,ItemCounts', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); @@ -29,7 +29,7 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey("genres"); + return libraryBrowser.getSavedQueryKey('genres'); } function getPromise() { @@ -41,40 +41,40 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f function reloadItems(context, promise) { var query = getQuery(); promise.then(function (result) { - var html = ""; + var html = ''; var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, context: 'music', centerText: true, overlayMoreButton: true, showTitle: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, context: 'music', cardLayout: true, showTitle: true }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "auto", + shape: 'auto', context: 'music', cardLayout: true, showTitle: true }); - } else if (viewStyle == "Poster") { + } else if (viewStyle == 'Poster') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "auto", + shape: 'auto', context: 'music', centerText: true, overlayMoreButton: true, @@ -82,13 +82,13 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f }); } - var elem = context.querySelector("#items"); + var elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(context); }); }); @@ -103,16 +103,16 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f var data = {}; self.getViewStyles = function () { - return "Poster,PosterCard,Thumb,ThumbCard".split(","); + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index fd458c88aca..f508489216b 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -1,5 +1,5 @@ -define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - "use strict"; +define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { + 'use strict'; return function (view, params, tabContent) { function getPageData() { @@ -9,14 +9,14 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Playlist", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Playlist', Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,CanDelete", + Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); @@ -30,7 +30,7 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey("genres"); + return libraryBrowser.getSavedQueryKey('genres'); } function getPromise() { @@ -42,10 +42,10 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f function reloadItems(context, promise) { var query = getQuery(); promise.then(function (result) { - var html = ""; + var html = ''; html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", + shape: 'square', showTitle: true, coverImage: true, centerText: true, @@ -53,13 +53,13 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f allowBottomPadding: true, cardLayout: false }); - var elem = context.querySelector("#items"); + var elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(context); }); }); @@ -69,7 +69,7 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f var data = {}; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; var promise; diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index 8b87dff265f..3f025799f6f 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -1,5 +1,5 @@ -define(["browser", "layoutManager", "userSettings", "inputManager", "loading", "cardBuilder", "dom", "apphost", "imageLoader", "libraryMenu", "playbackManager", "mainTabsManager", "scrollStyles", "emby-itemscontainer", "emby-tabs", "emby-button", "flexStyles"], function (browser, layoutManager, userSettings, inputManager, loading, cardBuilder, dom, appHost, imageLoader, libraryMenu, playbackManager, mainTabsManager) { - "use strict"; +define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'libraryMenu', 'playbackManager', 'mainTabsManager', 'globalize', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button', 'flexStyles'], function (browser, layoutManager, userSettings, inputManager, loading, cardBuilder, dom, appHost, imageLoader, libraryMenu, playbackManager, mainTabsManager, globalize) { + 'use strict'; function itemsPerRow() { var screenWidth = dom.getWindowSize().innerWidth; @@ -24,24 +24,24 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " } function getSquareShape() { - return enableScrollX() ? "overflowSquare" : "square"; + return enableScrollX() ? 'overflowSquare' : 'square'; } function loadLatest(page, parentId) { loading.show(); var userId = ApiClient.getCurrentUserId(); var options = { - IncludeItemTypes: "Audio", + IncludeItemTypes: 'Audio', Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; - ApiClient.getJSON(ApiClient.getUrl("Users/" + userId + "/Items/Latest", options)).then(function (items) { - var elem = page.querySelector("#recentlyAddedSongs"); - var supportsImageAnalysis = appHost.supports("imageanalysis"); + ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + var elem = page.querySelector('#recentlyAddedSongs'); + var supportsImageAnalysis = appHost.supports('imageanalysis'); supportsImageAnalysis = false; elem.innerHTML = cardBuilder.getCardsHtml({ items: items, @@ -60,7 +60,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " imageLoader.lazyChildren(elem); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); @@ -68,29 +68,29 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " function loadRecentlyPlayed(page, parentId) { var options = { - SortBy: "DatePlayed", - SortOrder: "Descending", - IncludeItemTypes: "Audio", + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', Limit: itemsPerRow(), Recursive: true, - Fields: "PrimaryImageAspectRatio,AudioInfo", - Filters: "IsPlayed", + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector("#recentlyPlayed"); + var elem = page.querySelector('#recentlyPlayed'); if (result.Items.length) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - var itemsContainer = elem.querySelector(".itemsContainer"); - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var itemsContainer = elem.querySelector('.itemsContainer'); + var supportsImageAnalysis = appHost.supports('imageanalysis'); supportsImageAnalysis = false; itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, @@ -98,7 +98,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " shape: getSquareShape(), showTitle: true, showParentTitle: true, - action: "instantmix", + action: 'instantmix', lazy: true, centerText: !supportsImageAnalysis, overlayMoreButton: !supportsImageAnalysis, @@ -112,29 +112,29 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " function loadFrequentlyPlayed(page, parentId) { var options = { - SortBy: "PlayCount", - SortOrder: "Descending", - IncludeItemTypes: "Audio", + SortBy: 'PlayCount', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', Limit: itemsPerRow(), Recursive: true, - Fields: "PrimaryImageAspectRatio,AudioInfo", - Filters: "IsPlayed", + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector("#topPlayed"); + var elem = page.querySelector('#topPlayed'); if (result.Items.length) { - elem.classList.remove("hide"); + elem.classList.remove('hide'); } else { - elem.classList.add("hide"); + elem.classList.add('hide'); } - var itemsContainer = elem.querySelector(".itemsContainer"); - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var itemsContainer = elem.querySelector('.itemsContainer'); + var supportsImageAnalysis = appHost.supports('imageanalysis'); supportsImageAnalysis = false; itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, @@ -142,7 +142,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " shape: getSquareShape(), showTitle: true, showParentTitle: true, - action: "instantmix", + action: 'instantmix', lazy: true, centerText: !supportsImageAnalysis, overlayMoreButton: !supportsImageAnalysis, @@ -155,55 +155,55 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " } function loadSuggestionsTab(page, tabContent, parentId) { - console.debug("loadSuggestionsTab"); + console.debug('loadSuggestionsTab'); loadLatest(tabContent, parentId); loadRecentlyPlayed(tabContent, parentId); loadFrequentlyPlayed(tabContent, parentId); - require(["components/favoriteitems"], function (favoriteItems) { - favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ["favoriteArtists", "favoriteAlbums", "favoriteSongs"]); + require(['components/favoriteitems'], function (favoriteItems) { + favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); }); } function getTabs() { return [{ - name: Globalize.translate("TabSuggestions") + name: globalize.translate('TabSuggestions') }, { - name: Globalize.translate("TabAlbums") + name: globalize.translate('TabAlbums') }, { - name: Globalize.translate("TabAlbumArtists") + name: globalize.translate('TabAlbumArtists') }, { - name: Globalize.translate("TabArtists") + name: globalize.translate('TabArtists') }, { - name: Globalize.translate("TabPlaylists") + name: globalize.translate('TabPlaylists') }, { - name: Globalize.translate("TabSongs") + name: globalize.translate('TabSongs') }, { - name: Globalize.translate("TabGenres") + name: globalize.translate('TabGenres') }, { - name: Globalize.translate("ButtonSearch"), - cssClass: "searchTabButton" + name: globalize.translate('ButtonSearch'), + cssClass: 'searchTabButton' }]; } function getDefaultTabIndex(folderId) { - switch (userSettings.get("landing-" + folderId)) { - case "albums": + switch (userSettings.get('landing-' + folderId)) { + case 'albums': return 1; - case "albumartists": + case 'albumartists': return 2; - case "artists": + case 'artists': return 3; - case "playlists": + case 'playlists': return 4; - case "songs": + case 'songs': return 5; - case "genres": + case 'genres': return 6; default: @@ -224,19 +224,19 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " function setScrollClasses(elem, scrollX) { if (scrollX) { - elem.classList.add("hiddenScrollX"); + elem.classList.add('hiddenScrollX'); if (layoutManager.tv) { - elem.classList.add("smoothScrollX"); + elem.classList.add('smoothScrollX'); } - elem.classList.add("scrollX"); - elem.classList.remove("vertical-wrap"); + 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"); + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } @@ -249,7 +249,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " } function getTabContainers() { - return view.querySelectorAll(".pageTabContent"); + return view.querySelectorAll('.pageTabContent'); } function initTabs() { @@ -264,28 +264,28 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " break; case 1: - depends.push("controllers/music/musicalbums"); + depends.push('controllers/music/musicalbums'); break; case 2: case 3: - depends.push("controllers/music/musicartists"); + depends.push('controllers/music/musicartists'); break; case 4: - depends.push("controllers/music/musicplaylists"); + depends.push('controllers/music/musicplaylists'); break; case 5: - depends.push("controllers/music/songs"); + depends.push('controllers/music/songs'); break; case 6: - depends.push("controllers/music/musicgenres"); + depends.push('controllers/music/musicgenres'); break; case 7: - depends.push("scripts/searchtab"); + depends.push('scripts/searchtab'); } require(depends, function (controllerFactory) { @@ -305,7 +305,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " controller = self; } else if (index === 7) { controller = new controllerFactory(view, tabContent, { - collectionType: "music", + collectionType: 'music', parentId: params.topParentId }); } else { @@ -313,9 +313,9 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " } if (index == 2) { - controller.mode = "albumartists"; + controller.mode = 'albumartists'; } else if (index == 3) { - controller.mode = "artists"; + controller.mode = 'artists'; } tabControllers[index] = controller; @@ -350,9 +350,9 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " function onInputCommand(e) { switch (e.detail.command) { - case "search": + case 'search': e.preventDefault(); - Dashboard.navigate("search.html?collectionType=music&parentId=" + params.topParentId); + Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); } } @@ -363,7 +363,7 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " self.initTab = function () { var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - var containers = tabContent.querySelectorAll(".itemsContainer"); + var containers = tabContent.querySelectorAll('.itemsContainer'); for (var i = 0, length = containers.length; i < length; i++) { setScrollClasses(containers[i], enableScrollX()); @@ -376,29 +376,29 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " var tabControllers = []; var renderedTabs = []; - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { isViewRestored = e.detail.isRestored; initTabs(); - if (!view.getAttribute("data-title")) { + if (!view.getAttribute('data-title')) { var parentId = params.topParentId; if (parentId) { ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute("data-title", item.Name); + view.setAttribute('data-title', item.Name); libraryMenu.setTitle(item.Name); }); } else { - view.setAttribute("data-title", Globalize.translate("TabMusic")); - libraryMenu.setTitle(Globalize.translate("TabMusic")); + view.setAttribute('data-title', globalize.translate('TabMusic')); + libraryMenu.setTitle(globalize.translate('TabMusic')); } } inputManager.on(window, onInputCommand); }); - view.addEventListener("viewbeforehide", function (e) { + view.addEventListener('viewbeforehide', function (e) { inputManager.off(window, onInputCommand); }); - view.addEventListener("viewdestroy", function (e) { + view.addEventListener('viewdestroy', function (e) { tabControllers.forEach(function (t) { if (t.destroy) { t.destroy(); diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 47263be0d2b..aa63ec51fe1 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -1,5 +1,5 @@ -define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby-itemscontainer"], function (events, libraryBrowser, imageLoader, listView, loading) { - "use strict"; +define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userSettings', 'globalize', 'emby-itemscontainer'], function (events, libraryBrowser, imageLoader, listView, loading, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -9,17 +9,21 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- if (!pageData) { pageData = data[key] = { query: { - SortBy: "Album,SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Audio", + SortBy: 'Album,SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Audio', Recursive: true, - Fields: "AudioInfo,ParentId", - Limit: 100, + Fields: 'AudioInfo,ParentId', StartIndex: 0, ImageTypeLimit: 1, - EnableImageTypes: "Primary" + EnableImageTypes: 'Primary' } }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -33,7 +37,7 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- function getSavedQueryKey(context) { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey("songs"); + context.savedQueryKey = libraryBrowser.getSavedQueryKey('songs'); } return context.savedQueryKey; @@ -49,7 +53,9 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -58,7 +64,9 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -77,35 +85,35 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- }); var html = listView.getListViewHtml({ items: result.Items, - action: "playallfromhere", + action: 'playallfromhere', smallIcon: true, artist: true, addToListButton: true }); - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); @@ -116,13 +124,13 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), - mode: "songs", + mode: 'songs', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { getQuery(tabContent).StartIndex = 0; reloadItems(tabContent); }); @@ -135,38 +143,38 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- }; function initPage(tabContent) { - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionTrackName"), - id: "Name" + name: globalize.translate('OptionTrackName'), + id: 'Name' }, { - name: Globalize.translate("OptionAlbum"), - id: "Album,SortName" + name: globalize.translate('OptionAlbum'), + id: 'Album,SortName' }, { - name: Globalize.translate("OptionAlbumArtist"), - id: "AlbumArtist,Album,SortName" + name: globalize.translate('OptionAlbumArtist'), + id: 'AlbumArtist,Album,SortName' }, { - name: Globalize.translate("OptionArtist"), - id: "Artist,Album,SortName" + name: globalize.translate('OptionArtist'), + id: 'Artist,Album,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' }, { - name: Globalize.translate("OptionDatePlayed"), - id: "DatePlayed,SortName" + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName' }, { - name: Globalize.translate("OptionPlayCount"), - id: "PlayCount,SortName" + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SortName' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "PremiereDate,AlbumArtist,Album,SortName" + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,AlbumArtist,Album,SortName' }, { - name: Globalize.translate("OptionRuntime"), - id: "Runtime,AlbumArtist,Album,SortName" + name: globalize.translate('OptionRuntime'), + id: 'Runtime,AlbumArtist,Album,SortName' }], callback: function () { getQuery(tabContent).StartIndex = 0; diff --git a/src/controllers/playback/nowplaying.js b/src/controllers/playback/nowplaying.js index 6fcdb2a79ca..98c9945e844 100644 --- a/src/controllers/playback/nowplaying.js +++ b/src/controllers/playback/nowplaying.js @@ -1,17 +1,17 @@ -define(["components/remotecontrol/remotecontrol", "libraryMenu", "emby-button"], function (remotecontrolFactory, libraryMenu) { - "use strict"; +define(['components/remotecontrol/remotecontrol', 'libraryMenu', 'emby-button'], function (remotecontrolFactory, libraryMenu) { + 'use strict'; return function (view, params) { var remoteControl = new remotecontrolFactory(); - remoteControl.init(view, view.querySelector(".remoteControlContent")); - view.addEventListener("viewshow", function (e) { + remoteControl.init(view, view.querySelector('.remoteControlContent')); + view.addEventListener('viewshow', function (e) { libraryMenu.setTransparentMenu(true); if (remoteControl) { remoteControl.onShow(); } }); - view.addEventListener("viewbeforehide", function (e) { + view.addEventListener('viewbeforehide', function (e) { libraryMenu.setTransparentMenu(false); if (remoteControl) { diff --git a/src/controllers/playback/videoosd.js b/src/controllers/playback/videoosd.js index c61fd14a7af..634e4d30935 100644 --- a/src/controllers/playback/videoosd.js +++ b/src/controllers/playback/videoosd.js @@ -1,19 +1,19 @@ -define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "mediaInfo", "focusManager", "imageLoader", "scrollHelper", "events", "connectionManager", "browser", "globalize", "apphost", "layoutManager", "userSettings", "keyboardnavigation", "scrollStyles", "emby-slider", "paper-icon-button-light", "css!assets/css/videoosd"], function (playbackManager, dom, inputManager, datetime, itemHelper, mediaInfo, focusManager, imageLoader, scrollHelper, events, connectionManager, browser, globalize, appHost, layoutManager, userSettings, keyboardnavigation) { - "use strict"; +define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'mediaInfo', 'focusManager', 'imageLoader', 'scrollHelper', 'events', 'connectionManager', 'browser', 'globalize', 'apphost', 'layoutManager', 'userSettings', 'keyboardnavigation', 'scrollStyles', 'emby-slider', 'paper-icon-button-light', 'css!assets/css/videoosd'], function (playbackManager, dom, inputManager, datetime, itemHelper, mediaInfo, focusManager, imageLoader, scrollHelper, events, connectionManager, browser, globalize, appHost, layoutManager, userSettings, keyboardnavigation) { + 'use strict'; function seriesImageUrl(item, options) { - if ("Episode" !== item.Type) { + if ('Episode' !== item.Type) { return null; } options = options || {}; - options.type = options.type || "Primary"; - if ("Primary" === options.type && item.SeriesPrimaryImageTag) { + options.type = options.type || 'Primary'; + if ('Primary' === options.type && item.SeriesPrimaryImageTag) { options.tag = item.SeriesPrimaryImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - if ("Thumb" === options.type) { + if ('Thumb' === options.type) { if (item.SeriesThumbImageTag) { options.tag = item.SeriesThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); @@ -30,14 +30,14 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med function imageUrl(item, options) { options = options || {}; - options.type = options.type || "Primary"; + options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { options.tag = item.ImageTags[options.type]; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } - if ("Primary" === options.type && item.AlbumId && item.AlbumPrimaryImageTag) { + if ('Primary' === options.type && item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } @@ -45,23 +45,6 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med return null; } - function logoImageUrl(item, apiClient, options) { - options = options || {}; - options.type = "Logo"; - - if (item.ImageTags && item.ImageTags.Logo) { - options.tag = item.ImageTags.Logo; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.ParentLogoImageTag) { - options.tag = item.ParentLogoImageTag; - return apiClient.getScaledImageUrl(item.ParentLogoItemId, options); - } - - return null; - } - return function (view, params) { function onVerticalSwipe(e, elem, data) { var player = currentPlayer; @@ -110,7 +93,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function getDisplayItem(item) { - if ("TvChannel" === item.Type) { + if ('TvChannel' === item.Type) { var apiClient = connectionManager.getApiClient(item.ServerId); return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) { return { @@ -126,27 +109,27 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function updateRecordingButton(item) { - if (!item || "Program" !== item.Type) { + if (!item || 'Program' !== item.Type) { if (recordingButtonManager) { recordingButtonManager.destroy(); recordingButtonManager = null; } - return void view.querySelector(".btnRecord").classList.add("hide"); + return void view.querySelector('.btnRecord').classList.add('hide'); } connectionManager.getApiClient(item.ServerId).getCurrentUser().then(function (user) { if (user.Policy.EnableLiveTvManagement) { - require(["recordingButton"], function (RecordingButton) { + require(['recordingButton'], function (RecordingButton) { if (recordingButtonManager) { return void recordingButtonManager.refreshItem(item); } recordingButtonManager = new RecordingButton({ item: item, - button: view.querySelector(".btnRecord") + button: view.querySelector('.btnRecord') }); - view.querySelector(".btnRecord").classList.remove("hide"); + view.querySelector('.btnRecord').classList.remove('hide'); }); } }); @@ -166,23 +149,23 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med setTitle(displayItem, parentName); var titleElement; - var osdTitle = view.querySelector(".osdTitle"); + var osdTitle = view.querySelector('.osdTitle'); titleElement = osdTitle; var displayName = itemHelper.getDisplayName(displayItem, { - includeParentInfo: "Program" !== displayItem.Type, - includeIndexNumber: "Program" !== displayItem.Type + includeParentInfo: 'Program' !== displayItem.Type, + includeIndexNumber: 'Program' !== displayItem.Type }); if (!displayName) { - displayItem.Type; + displayName = displayItem.Type; } titleElement.innerHTML = displayName; if (displayName) { - titleElement.classList.remove("hide"); + titleElement.classList.remove('hide'); } else { - titleElement.classList.add("hide"); + titleElement.classList.add('hide'); } var mediaInfoHtml = mediaInfo.getPrimaryMediaInfoHtml(displayItem, { @@ -191,20 +174,20 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med tomatoes: false, endsAt: false, episodeTitle: false, - originalAirDate: "Program" !== displayItem.Type, - episodeTitleIndexNumber: "Program" !== displayItem.Type, + originalAirDate: 'Program' !== displayItem.Type, + episodeTitleIndexNumber: 'Program' !== displayItem.Type, programIndicator: false }); - var osdMediaInfo = view.querySelector(".osdMediaInfo"); + var osdMediaInfo = view.querySelector('.osdMediaInfo'); osdMediaInfo.innerHTML = mediaInfoHtml; if (mediaInfoHtml) { - osdMediaInfo.classList.remove("hide"); + osdMediaInfo.classList.remove('hide'); } else { - osdMediaInfo.classList.add("hide"); + osdMediaInfo.classList.add('hide'); } - var secondaryMediaInfo = view.querySelector(".osdSecondaryMediaInfo"); + var secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo'); var secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, { startDate: false, programTime: false @@ -212,29 +195,29 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med secondaryMediaInfo.innerHTML = secondaryMediaInfoHtml; if (secondaryMediaInfoHtml) { - secondaryMediaInfo.classList.remove("hide"); + secondaryMediaInfo.classList.remove('hide'); } else { - secondaryMediaInfo.classList.add("hide"); + secondaryMediaInfo.classList.add('hide'); } if (displayName) { - view.querySelector(".osdMainTextContainer").classList.remove("hide"); + view.querySelector('.osdMainTextContainer').classList.remove('hide'); } else { - view.querySelector(".osdMainTextContainer").classList.add("hide"); + view.querySelector('.osdMainTextContainer').classList.add('hide'); } if (enableProgressByTimeOfDay) { setDisplayTime(startTimeText, displayItem.StartDate); setDisplayTime(endTimeText, displayItem.EndDate); - startTimeText.classList.remove("hide"); - endTimeText.classList.remove("hide"); + startTimeText.classList.remove('hide'); + endTimeText.classList.remove('hide'); programStartDateMs = displayItem.StartDate ? datetime.parseISO8601Date(displayItem.StartDate).getTime() : 0; programEndDateMs = displayItem.EndDate ? datetime.parseISO8601Date(displayItem.EndDate).getTime() : 0; } else { - startTimeText.classList.add("hide"); - endTimeText.classList.add("hide"); - startTimeText.innerHTML = ""; - endTimeText.innerHTML = ""; + startTimeText.classList.add('hide'); + endTimeText.classList.add('hide'); + startTimeText.innerHTML = ''; + endTimeText.innerHTML = ''; programStartDateMs = 0; programEndDateMs = 0; } @@ -243,13 +226,13 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med function getDisplayTimeWithoutAmPm(date, showSeconds) { if (showSeconds) { return datetime.toLocaleTimeString(date, { - hour: "numeric", - minute: "2-digit", - second: "2-digit" - }).toLowerCase().replace("am", "").replace("pm", "").trim(); + hour: 'numeric', + minute: '2-digit', + second: '2-digit' + }).toLowerCase().replace('am', '').replace('pm', '').trim(); } - return datetime.getDisplayTime(date).toLowerCase().replace("am", "").replace("pm", "").trim(); + return datetime.getDisplayTime(date).toLowerCase().replace('am', '').replace('pm', '').trim(); } function setDisplayTime(elem, date) { @@ -260,11 +243,11 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med html = getDisplayTimeWithoutAmPm(date); } - elem.innerHTML = html || ""; + elem.innerHTML = html || ''; } function shouldEnableProgressByTimeOfDay(item) { - return !("TvChannel" !== item.Type || !item.CurrentProgram); + return !('TvChannel' !== item.Type || !item.CurrentProgram); } function updateNowPlayingInfo(player, state) { @@ -274,15 +257,15 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med if (!item) { setPoster(null); updateRecordingButton(null); - Emby.Page.setTitle(""); + Emby.Page.setTitle(''); nowPlayingVolumeSlider.disabled = true; nowPlayingPositionSlider.disabled = true; btnFastForward.disabled = true; btnRewind.disabled = true; - view.querySelector(".btnSubtitles").classList.add("hide"); - view.querySelector(".btnAudio").classList.add("hide"); - view.querySelector(".osdTitle").innerHTML = ""; - view.querySelector(".osdMediaInfo").innerHTML = ""; + view.querySelector('.btnSubtitles').classList.add('hide'); + view.querySelector('.btnAudio').classList.add('hide'); + view.querySelector('.osdTitle').innerHTML = ''; + view.querySelector('.osdMediaInfo').innerHTML = ''; return; } @@ -294,33 +277,22 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med btnRewind.disabled = false; if (playbackManager.subtitleTracks(player).length) { - view.querySelector(".btnSubtitles").classList.remove("hide"); + view.querySelector('.btnSubtitles').classList.remove('hide'); toggleSubtitleSync(); } else { - view.querySelector(".btnSubtitles").classList.add("hide"); - toggleSubtitleSync("forceToHide"); + view.querySelector('.btnSubtitles').classList.add('hide'); + toggleSubtitleSync('forceToHide'); } if (playbackManager.audioTracks(player).length > 1) { - view.querySelector(".btnAudio").classList.remove("hide"); + view.querySelector('.btnAudio').classList.remove('hide'); } else { - view.querySelector(".btnAudio").classList.add("hide"); + view.querySelector('.btnAudio').classList.add('hide'); } } function setTitle(item, parentName) { - var url = logoImageUrl(item, connectionManager.getApiClient(item.ServerId), {}); - - if (url) { - Emby.Page.setTitle(""); - var pageTitle = document.querySelector(".pageTitle"); - pageTitle.style.backgroundImage = "url('" + url + "')"; - pageTitle.classList.add("pageTitleWithLogo"); - pageTitle.classList.remove("pageTitleWithDefaultLogo"); - pageTitle.innerHTML = ""; - } else { - Emby.Page.setTitle(parentName || ""); - } + Emby.Page.setTitle(parentName || ''); var documentTitle = parentName || (item ? item.Name : null); @@ -330,56 +302,77 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function setPoster(item, secondaryItem) { - var osdPoster = view.querySelector(".osdPoster"); + var osdPoster = view.querySelector('.osdPoster'); if (item) { var imgUrl = seriesImageUrl(item, { maxWidth: osdPoster.clientWidth * 2, - type: "Primary" + type: 'Primary' }) || seriesImageUrl(item, { maxWidth: osdPoster.clientWidth * 2, - type: "Thumb" + type: 'Thumb' }) || imageUrl(item, { maxWidth: osdPoster.clientWidth * 2, - type: "Primary" + type: 'Primary' }); if (!imgUrl && secondaryItem && (imgUrl = seriesImageUrl(secondaryItem, { maxWidth: osdPoster.clientWidth * 2, - type: "Primary" + type: 'Primary' }) || seriesImageUrl(secondaryItem, { maxWidth: osdPoster.clientWidth * 2, - type: "Thumb" + type: 'Thumb' }) || imageUrl(secondaryItem, { maxWidth: osdPoster.clientWidth * 2, - type: "Primary" + type: 'Primary' })), imgUrl) { return void (osdPoster.innerHTML = ''); } } - osdPoster.innerHTML = ""; + osdPoster.innerHTML = ''; } + let osdLockCount = 0; + function showOsd() { slideDownToShow(headerElement); showMainOsdControls(); - startOsdHideTimer(); + if (!osdLockCount) { + startOsdHideTimer(); + } } function hideOsd() { + if (osdLockCount) { + return; + } + slideUpToHide(headerElement); hideMainOsdControls(); } function toggleOsd() { - if ("osd" === currentVisibleMenu) { + if ('osd' === currentVisibleMenu) { hideOsd(); } else if (!currentVisibleMenu) { showOsd(); } } + function lockOsd() { + osdLockCount++; + stopOsdHideTimer(); + } + + function unlockOsd() { + osdLockCount--; + // Restart hide timer if OSD is currently visible + if (currentVisibleMenu && !osdLockCount) { + startOsdHideTimer(); + } + } + function startOsdHideTimer() { stopOsdHideTimer(); osdHideTimeout = setTimeout(hideOsd, 3e3); @@ -393,11 +386,11 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function slideDownToShow(elem) { - elem.classList.remove("osdHeader-hidden"); + elem.classList.remove('osdHeader-hidden'); } function slideUpToHide(elem) { - elem.classList.add("osdHeader-hidden"); + elem.classList.add('osdHeader-hidden'); } function clearHideAnimationEventListeners(elem) { @@ -410,7 +403,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var elem = e.target; if (elem != osdBottomElement) return; - elem.classList.add("hide"); + elem.classList.add('hide'); dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { once: true }); @@ -419,14 +412,14 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med function showMainOsdControls() { if (!currentVisibleMenu) { var elem = osdBottomElement; - currentVisibleMenu = "osd"; + currentVisibleMenu = 'osd'; clearHideAnimationEventListeners(elem); - elem.classList.remove("hide"); - elem.classList.remove("videoOsdBottom-hidden"); + elem.classList.remove('hide'); + elem.classList.remove('videoOsdBottom-hidden'); if (!layoutManager.mobile) { setTimeout(function () { - focusManager.focus(elem.querySelector(".btnPause")); + focusManager.focus(elem.querySelector('.btnPause')); }, 50); } toggleSubtitleSync(); @@ -434,15 +427,15 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function hideMainOsdControls() { - if ("osd" === currentVisibleMenu) { + if ('osd' === currentVisibleMenu) { var elem = osdBottomElement; clearHideAnimationEventListeners(elem); - elem.classList.add("videoOsdBottom-hidden"); + elem.classList.add('videoOsdBottom-hidden'); dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, { once: true }); currentVisibleMenu = null; - toggleSubtitleSync("hide"); + toggleSubtitleSync('hide'); // Firefox does not blur by itself if (document.activeElement) { @@ -452,7 +445,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function onPointerMove(e) { - if ("mouse" === (e.pointerType || (layoutManager.mobile ? "touch" : "mouse"))) { + if ('mouse' === (e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'))) { var eventX = e.screenX || 0; var eventY = e.screenY || 0; var obj = lastPointerMoveData; @@ -479,8 +472,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var player = currentPlayer; switch (e.detail.command) { - case "left": - if ("osd" === currentVisibleMenu) { + case 'left': + if ('osd' === currentVisibleMenu) { showOsd(); } else { if (!currentVisibleMenu) { @@ -491,8 +484,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med break; - case "right": - if ("osd" === currentVisibleMenu) { + case 'right': + if ('osd' === currentVisibleMenu) { showOsd(); } else if (!currentVisibleMenu) { e.preventDefault(); @@ -501,54 +494,59 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med break; - case "pageup": + case 'pageup': playbackManager.nextChapter(player); break; - case "pagedown": + case 'pagedown': playbackManager.previousChapter(player); break; - case "up": - case "down": - case "select": - case "menu": - case "info": - case "play": - case "playpause": - case "pause": - case "fastforward": - case "rewind": - case "next": - case "previous": + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + case 'play': + case 'playpause': + case 'pause': + case 'fastforward': + case 'rewind': + case 'next': + case 'previous': showOsd(); break; - case "record": + case 'record': onRecordingCommand(); showOsd(); break; - case "togglestats": + case 'togglestats': toggleStats(); } } function onRecordingCommand() { - var btnRecord = view.querySelector(".btnRecord"); + var btnRecord = view.querySelector('.btnRecord'); - if (!btnRecord.classList.contains("hide")) { + if (!btnRecord.classList.contains('hide')) { btnRecord.click(); } } function updateFullscreenIcon() { + const button = view.querySelector('.btnFullscreen'); + const icon = button.querySelector('.material-icons'); + + icon.classList.remove('fullscreen_exit', 'fullscreen'); + if (playbackManager.isFullscreen(currentPlayer)) { - view.querySelector(".btnFullscreen").setAttribute("title", globalize.translate("ExitFullscreen")); - view.querySelector(".btnFullscreen i").innerHTML = ""; + button.setAttribute('title', globalize.translate('ExitFullscreen') + ' (f)'); + icon.classList.add('fullscreen_exit'); } else { - view.querySelector(".btnFullscreen").setAttribute("title", globalize.translate("Fullscreen") + " (f)"); - view.querySelector(".btnFullscreen i").innerHTML = "fullscreen"; + button.setAttribute('title', globalize.translate('Fullscreen') + ' (f)'); + icon.classList.add('fullscreen'); } } @@ -581,7 +579,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function onPlaybackStart(e, state) { - console.debug("nowplaying event: " + e.type); + console.debug('nowplaying event: ' + e.type); var player = this; onStateChanged.call(player, e, state); resetUpNextDialog(); @@ -600,10 +598,10 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med function onPlaybackStopped(e, state) { currentRuntimeTicks = null; resetUpNextDialog(); - console.debug("nowplaying event: " + e.type); + console.debug('nowplaying event: ' + e.type); - if ("Video" !== state.NextMediaType) { - view.removeEventListener("viewbeforehide", onViewHideStopPlayback); + if ('Video' !== state.NextMediaType) { + view.removeEventListener('viewbeforehide', onViewHideStopPlayback); Emby.Page.back(); } } @@ -612,16 +610,16 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var player = this; var state = playbackManager.getPlayerState(player); onStateChanged.call(player, { - type: "init" + type: 'init' }, state); } function onBeginFetch() { - document.querySelector(".osdMediaStatus").classList.remove("hide"); + document.querySelector('.osdMediaStatus').classList.remove('hide'); } function onEndFetch() { - document.querySelector(".osdMediaStatus").classList.add("hide"); + document.querySelector('.osdMediaStatus').classList.add('hide'); } function bindToPlayer(player) { @@ -632,18 +630,18 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } var state = playbackManager.getPlayerState(player); onStateChanged.call(player, { - type: "init" + type: 'init' }, state); - events.on(player, "playbackstart", onPlaybackStart); - 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(player, "fullscreenchange", updateFullscreenIcon); - events.on(player, "mediastreamschange", onMediaStreamsChanged); - events.on(player, "beginFetch", onBeginFetch); - events.on(player, "endFetch", onEndFetch); + events.on(player, 'playbackstart', onPlaybackStart); + 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(player, 'fullscreenchange', updateFullscreenIcon); + events.on(player, 'mediastreamschange', onMediaStreamsChanged); + events.on(player, 'beginFetch', onBeginFetch); + events.on(player, 'endFetch', onEndFetch); resetUpNextDialog(); if (player.isFetching) { @@ -658,14 +656,14 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var player = currentPlayer; if (player) { - events.off(player, "playbackstart", onPlaybackStart); - 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); - events.off(player, "fullscreenchange", updateFullscreenIcon); - events.off(player, "mediastreamschange", onMediaStreamsChanged); + events.off(player, 'playbackstart', onPlaybackStart); + 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); + events.off(player, 'fullscreenchange', updateFullscreenIcon); + events.off(player, 'mediastreamschange', onMediaStreamsChanged); currentPlayer = null; } } @@ -689,7 +687,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) { - if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && "Episode" === currentItem.Type && userSettings.enableNextVideoInfoOverlay()) { + if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && 'Episode' === currentItem.Type && userSettings.enableNextVideoInfoOverlay()) { var showAtSecondsLeft = runtimeTicks >= 3e10 ? 40 : runtimeTicks >= 24e9 ? 35 : 30; var showAtTicks = runtimeTicks - 1e3 * showAtSecondsLeft * 1e4; var timeRemainingTicks = runtimeTicks - currentTimeTicks; @@ -701,30 +699,30 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function onUpNextHidden() { - if ("upnext" === currentVisibleMenu) { + if ('upnext' === currentVisibleMenu) { currentVisibleMenu = null; } } function showComingUpNext(player) { - require(["upNextDialog"], function (UpNextDialog) { + require(['upNextDialog'], function (UpNextDialog) { if (!(currentVisibleMenu || currentUpNextDialog)) { - currentVisibleMenu = "upnext"; + currentVisibleMenu = 'upnext'; comingUpNextDisplayed = true; playbackManager.nextItem(player).then(function (nextItem) { currentUpNextDialog = new UpNextDialog({ - parent: view.querySelector(".upNextContainer"), + parent: view.querySelector('.upNextContainer'), player: player, nextItem: nextItem }); - events.on(currentUpNextDialog, "hide", onUpNextHidden); + events.on(currentUpNextDialog, 'hide', onUpNextHidden); }, onUpNextHidden); } }); } function refreshProgramInfoIfNeeded(player, item) { - if ("TvChannel" === item.Type) { + if ('TvChannel' === item.Type) { var program = item.CurrentProgram; if (program && program.EndDate) { @@ -732,27 +730,31 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var endDate = datetime.parseISO8601Date(program.EndDate); if (new Date().getTime() >= endDate.getTime()) { - console.debug("program info needs to be refreshed"); + console.debug('program info needs to be refreshed'); var state = playbackManager.getPlayerState(player); onStateChanged.call(player, { - type: "init" + type: 'init' }, state); } } catch (e) { - console.error("error parsing date: " + program.EndDate); + console.error('error parsing date: ' + program.EndDate); } } } } function updatePlayPauseState(isPaused) { - var button = view.querySelector(".btnPause i"); + const btnPlayPause = view.querySelector('.btnPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); + + btnPlayPauseIcon.classList.remove('play_arrow', 'pause'); + if (isPaused) { - button.innerHTML = ""; - button.setAttribute("title", globalize.translate("ButtonPlay") + " (k)"); + btnPlayPauseIcon.classList.add('play_arrow'); + btnPlayPause.setAttribute('title', globalize.translate('ButtonPlay') + ' (k)'); } else { - button.innerHTML = "pause"; - button.setAttribute("title", globalize.translate("ButtonPause") + " (k)"); + btnPlayPauseIcon.classList.add('pause'); + btnPlayPause.setAttribute('title', globalize.translate('ButtonPause') + ' (k)'); } } @@ -761,7 +763,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med updatePlayPauseState(playState.IsPaused); var supportedCommands = playbackManager.getSupportedCommands(player); currentPlayerSupportedCommands = supportedCommands; - supportsBrightnessChange = -1 !== supportedCommands.indexOf("SetBrightness"); + supportsBrightnessChange = -1 !== supportedCommands.indexOf('SetBrightness'); updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel); if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { @@ -775,10 +777,10 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.BufferedRanges || []); updateNowPlayingInfo(player, state); - if (state.MediaSource && state.MediaSource.SupportsTranscoding && -1 !== supportedCommands.indexOf("SetMaxStreamingBitrate")) { - view.querySelector(".btnVideoOsdSettings").classList.remove("hide"); + if (state.MediaSource && state.MediaSource.SupportsTranscoding && -1 !== supportedCommands.indexOf('SetMaxStreamingBitrate')) { + view.querySelector('.btnVideoOsdSettings').classList.remove('hide'); } else { - view.querySelector(".btnVideoOsdSettings").classList.add("hide"); + view.querySelector('.btnVideoOsdSettings').classList.add('hide'); } var isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks; @@ -789,22 +791,22 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks); } - if (-1 === supportedCommands.indexOf("ToggleFullscreen") || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { - view.querySelector(".btnFullscreen").classList.add("hide"); + if (-1 === supportedCommands.indexOf('ToggleFullscreen') || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { + view.querySelector('.btnFullscreen').classList.add('hide'); } else { - view.querySelector(".btnFullscreen").classList.remove("hide"); + view.querySelector('.btnFullscreen').classList.remove('hide'); } - if (-1 === supportedCommands.indexOf("PictureInPicture")) { - view.querySelector(".btnPip").classList.add("hide"); + if (-1 === supportedCommands.indexOf('PictureInPicture')) { + view.querySelector('.btnPip').classList.add('hide'); } else { - view.querySelector(".btnPip").classList.remove("hide"); + view.querySelector('.btnPip').classList.remove('hide'); } - if (-1 === supportedCommands.indexOf("AirPlay")) { - view.querySelector(".btnAirPlay").classList.add("hide"); + if (-1 === supportedCommands.indexOf('AirPlay')) { + view.querySelector('.btnAirPlay').classList.add('hide'); } else { - view.querySelector(".btnAirPlay").classList.remove("hide"); + view.querySelector('.btnAirPlay').classList.remove('hide'); } updateFullscreenIcon(); @@ -837,8 +839,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } } - nowPlayingPositionText.innerHTML = ""; - nowPlayingDurationText.innerHTML = ""; + nowPlayingPositionText.innerHTML = ''; + nowPlayingDurationText.innerHTML = ''; } else { if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { if (runtimeTicks) { @@ -849,10 +851,10 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med nowPlayingPositionSlider.value = 0; } - if (runtimeTicks && null != positionTicks && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && "Recording" !== currentItem.Type) { - endsAtText.innerHTML = "  -  " + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, true); + if (runtimeTicks && null != positionTicks && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && 'Recording' !== currentItem.Type) { + endsAtText.innerHTML = '  -  ' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, true); } else { - endsAtText.innerHTML = ""; + endsAtText.innerHTML = ''; } } @@ -870,38 +872,43 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var showMuteButton = true; var showVolumeSlider = true; - if (-1 === supportedCommands.indexOf("Mute")) { + if (-1 === supportedCommands.indexOf('Mute')) { showMuteButton = false; } - if (-1 === supportedCommands.indexOf("SetVolume")) { + if (-1 === supportedCommands.indexOf('SetVolume')) { showVolumeSlider = false; } - if (player.isLocalPlayer && appHost.supports("physicalvolumecontrol")) { + if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { showMuteButton = false; showVolumeSlider = false; } + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + if (isMuted) { - view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Unmute") + " (m)"); - view.querySelector(".buttonMute i").innerHTML = ""; + buttonMute.setAttribute('title', globalize.translate('Unmute') + ' (m)'); + buttonMuteIcon.classList.add('volume_off'); } else { - view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Mute") + " (m)"); - view.querySelector(".buttonMute i").innerHTML = ""; + buttonMute.setAttribute('title', globalize.translate('Mute') + ' (m)'); + buttonMuteIcon.classList.add('volume_up'); } if (showMuteButton) { - view.querySelector(".buttonMute").classList.remove("hide"); + buttonMute.classList.remove('hide'); } else { - view.querySelector(".buttonMute").classList.add("hide"); + buttonMute.classList.add('hide'); } if (nowPlayingVolumeSlider) { if (showVolumeSlider) { - nowPlayingVolumeSliderContainer.classList.remove("hide"); + nowPlayingVolumeSliderContainer.classList.remove('hide'); } else { - nowPlayingVolumeSliderContainer.classList.add("hide"); + nowPlayingVolumeSliderContainer.classList.add('hide'); } if (!nowPlayingVolumeSlider.dragging) { @@ -911,24 +918,24 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function updatePlaylist(player) { - var btnPreviousTrack = view.querySelector(".btnPreviousTrack"); - var btnNextTrack = view.querySelector(".btnNextTrack"); - btnPreviousTrack.classList.remove("hide"); - btnNextTrack.classList.remove("hide"); + var btnPreviousTrack = view.querySelector('.btnPreviousTrack'); + var btnNextTrack = view.querySelector('.btnNextTrack'); + btnPreviousTrack.classList.remove('hide'); + btnNextTrack.classList.remove('hide'); btnNextTrack.disabled = false; btnPreviousTrack.disabled = false; } function updateTimeText(elem, ticks, divider) { if (null == ticks) { - elem.innerHTML = ""; + elem.innerHTML = ''; return; } var html = datetime.getDisplayRunningTime(ticks); if (divider) { - html = " / " + html; + html = ' / ' + html; } elem.innerHTML = html; @@ -937,7 +944,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med function onSettingsButtonClick(e) { var btn = this; - require(["playerSettingsMenu"], function (playerSettingsMenu) { + require(['playerSettingsMenu'], function (playerSettingsMenu) { var player = currentPlayer; if (player) { @@ -947,7 +954,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med playbackManager.canHandleOffsetOnCurrentSubtitle(player); playerSettingsMenu.show({ - mediaType: "Video", + mediaType: 'Video', player: player, positionTo: btn, stats: true, @@ -959,9 +966,9 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function onSettingsOption(selectedOption) { - if ("stats" === selectedOption) { + if ('stats' === selectedOption) { toggleStats(); - } else if ("suboffset" === selectedOption) { + } else if ('suboffset' === selectedOption) { var player = currentPlayer; if (player) { playbackManager.enableShowingSubtitleOffset(player); @@ -971,7 +978,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function toggleStats() { - require(["playerStats"], function (PlayerStats) { + require(['playerStats'], function (PlayerStats) { var player = currentPlayer; if (player) { @@ -1011,10 +1018,10 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med }); var positionTo = this; - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, - title: globalize.translate("Audio"), + title: globalize.translate('Audio'), positionTo: positionTo }).then(function (id) { var index = parseInt(id); @@ -1037,7 +1044,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med streams.unshift({ Index: -1, - DisplayTitle: globalize.translate("Off") + DisplayTitle: globalize.translate('Off') }); var menuItems = streams.map(function (stream) { var opt = { @@ -1053,9 +1060,9 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med }); var positionTo = this; - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ - title: globalize.translate("Subtitles"), + title: globalize.translate('Subtitles'), items: menuItems, positionTo: positionTo }).then(function (id) { @@ -1071,7 +1078,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function toggleSubtitleSync(action) { - require(["subtitleSync"], function (SubtitleSync) { + require(['subtitleSync'], function (SubtitleSync) { var player = currentPlayer; if (subtitleSyncOverlay) { subtitleSyncOverlay.toggle(action); @@ -1094,7 +1101,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med */ var clickedElement; - function onWindowKeyDown(e) { + function onKeyDown(e) { clickedElement = e.srcElement; var key = keyboardnavigation.getKeyName(e); @@ -1111,69 +1118,126 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } switch (key) { - case "Enter": + case 'Enter': showOsd(); break; - case "Escape": - case "Back": + case 'Escape': + case 'Back': // Ignore key when some dialog is opened - if (currentVisibleMenu === "osd" && !document.querySelector(".dialogContainer")) { + if (currentVisibleMenu === 'osd' && !document.querySelector('.dialogContainer')) { hideOsd(); e.stopPropagation(); } break; - case "k": + case 'k': playbackManager.playPause(currentPlayer); showOsd(); break; - case "l": - case "ArrowRight": - case "Right": + case 'ArrowUp': + case 'Up': + playbackManager.volumeUp(currentPlayer); + break; + case 'ArrowDown': + case 'Down': + playbackManager.volumeDown(currentPlayer); + break; + case 'l': + case 'ArrowRight': + case 'Right': playbackManager.fastForward(currentPlayer); showOsd(); break; - case "j": - case "ArrowLeft": - case "Left": + case 'j': + case 'ArrowLeft': + case 'Left': playbackManager.rewind(currentPlayer); showOsd(); break; - case "f": + case 'f': if (!e.ctrlKey && !e.metaKey) { playbackManager.toggleFullscreen(currentPlayer); showOsd(); } break; - case "m": + case 'm': playbackManager.toggleMute(currentPlayer); showOsd(); break; - case "NavigationLeft": - case "GamepadDPadLeft": - case "GamepadLeftThumbstickLeft": + case 'p': + case 'P': + if (e.shiftKey) { + playbackManager.previousTrack(currentPlayer); + } + break; + case 'n': + case 'N': + if (e.shiftKey) { + playbackManager.nextTrack(currentPlayer); + } + break; + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbstickLeft': // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.rewind(currentPlayer); showOsd(); } break; - case "NavigationRight": - case "GamepadDPadRight": - case "GamepadLeftThumbstickRight": + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbstickRight': // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.fastForward(currentPlayer); showOsd(); } + break; + case 'Home': + playbackManager.seekPercent(0, currentPlayer); + break; + case 'End': + playbackManager.seekPercent(100, currentPlayer); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + var percent = parseInt(key, 10) * 10; + playbackManager.seekPercent(percent, currentPlayer); + break; + } + } + + function onKeyDownCapture() { + // Restart hide timer if OSD is currently visible + if (currentVisibleMenu) { + showOsd(); } } function onWindowMouseDown(e) { clickedElement = e.srcElement; + lockOsd(); + } + + function onWindowMouseUp() { + unlockOsd(); } function onWindowTouchStart(e) { clickedElement = e.srcElement; + lockOsd(); + } + + function onWindowTouchEnd() { + unlockOsd(); } function getImgUrl(item, chapter, index, maxWidth, apiClient) { @@ -1181,7 +1245,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med return apiClient.getScaledImageUrl(item.Id, { maxWidth: maxWidth, tag: chapter.ImageTag, - type: "Chapter", + type: 'Chapter', index: index }); } @@ -1214,12 +1278,12 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med html += '
'; html += '
'; html += chapter.Name; - html += "
"; + html += '
'; html += '

'; html += datetime.getDisplayRunningTime(positionTicks); - html += "

"; - html += ""; - return html + ""; + html += ''; + html += ''; + return html + ''; } return null; @@ -1234,17 +1298,17 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med clearTimeout(playPauseClickTimeout); var player = currentPlayer; - view.removeEventListener("viewbeforehide", onViewHideStopPlayback); + view.removeEventListener('viewbeforehide', onViewHideStopPlayback); releaseCurrentPlayer(); playbackManager.stop(player); } } function enableStopOnBack(enabled) { - view.removeEventListener("viewbeforehide", onViewHideStopPlayback); + view.removeEventListener('viewbeforehide', onViewHideStopPlayback); if (enabled && playbackManager.isPlayingVideo(currentPlayer)) { - view.addEventListener("viewbeforehide", onViewHideStopPlayback); + view.addEventListener('viewbeforehide', onViewHideStopPlayback); } } @@ -1272,92 +1336,117 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var programEndDateMs = 0; var playbackStartTimeTicks = 0; var subtitleSyncOverlay; - var volumeSliderTimer; - var nowPlayingVolumeSlider = view.querySelector(".osdVolumeSlider"); - var nowPlayingVolumeSliderContainer = view.querySelector(".osdVolumeSliderContainer"); - var nowPlayingPositionSlider = view.querySelector(".osdPositionSlider"); - var nowPlayingPositionText = view.querySelector(".osdPositionText"); - var nowPlayingDurationText = view.querySelector(".osdDurationText"); - var startTimeText = view.querySelector(".startTimeText"); - var endTimeText = view.querySelector(".endTimeText"); - var endsAtText = view.querySelector(".endsAtText"); - var btnRewind = view.querySelector(".btnRewind"); - var btnFastForward = view.querySelector(".btnFastForward"); + var nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); + var nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); + var nowPlayingPositionSlider = view.querySelector('.osdPositionSlider'); + var nowPlayingPositionText = view.querySelector('.osdPositionText'); + var nowPlayingDurationText = view.querySelector('.osdDurationText'); + var startTimeText = view.querySelector('.startTimeText'); + var endTimeText = view.querySelector('.endTimeText'); + var endsAtText = view.querySelector('.endsAtText'); + var btnRewind = view.querySelector('.btnRewind'); + var btnFastForward = view.querySelector('.btnFastForward'); var transitionEndEventName = dom.whichTransitionEvent(); - var headerElement = document.querySelector(".skinHeader"); - var osdBottomElement = document.querySelector(".videoOsdBottom-maincontrols"); + var headerElement = document.querySelector('.skinHeader'); + var osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols'); + + nowPlayingPositionSlider.enableKeyboardDragging(); + nowPlayingVolumeSlider.enableKeyboardDragging(); if (layoutManager.tv) { - nowPlayingPositionSlider.classList.add("focusable"); - nowPlayingPositionSlider.enableKeyboardDragging(); + nowPlayingPositionSlider.classList.add('focusable'); } - view.addEventListener("viewbeforeshow", function (e) { - headerElement.classList.add("osdHeader"); - Emby.Page.setTransparency("full"); + view.addEventListener('viewbeforeshow', function (e) { + headerElement.classList.add('osdHeader'); + Emby.Page.setTransparency('full'); }); - view.addEventListener("viewshow", function (e) { + view.addEventListener('viewshow', function (e) { try { - events.on(playbackManager, "playerchange", onPlayerChange); + events.on(playbackManager, 'playerchange', onPlayerChange); bindToPlayer(playbackManager.getCurrentPlayer()); - dom.addEventListener(document, window.PointerEvent ? "pointermove" : "mousemove", onPointerMove, { + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { passive: true }); showOsd(); inputManager.on(window, onInputCommand); - dom.addEventListener(window, "keydown", onWindowKeyDown, { + document.addEventListener('keydown', onKeyDown); + dom.addEventListener(document, 'keydown', onKeyDownCapture, { capture: true }); - dom.addEventListener(window, window.PointerEvent ? "pointerdown" : "mousedown", onWindowMouseDown, { + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { + passive: true + }); + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { passive: true }); - dom.addEventListener(window, "touchstart", onWindowTouchStart, { + dom.addEventListener(window, 'touchstart', onWindowTouchStart, { passive: true }); + ['touchend', 'touchcancel'].forEach((event) => { + dom.addEventListener(window, event, onWindowTouchEnd, { + passive: true + }); + }); } catch (e) { require(['appRouter'], function(appRouter) { - appRouter.showDirect('/'); + appRouter.goHome(); }); } }); - view.addEventListener("viewbeforehide", function () { + view.addEventListener('viewbeforehide', function () { if (statsOverlay) { statsOverlay.enabled(false); } - dom.removeEventListener(window, "keydown", onWindowKeyDown, { + document.removeEventListener('keydown', onKeyDown); + dom.removeEventListener(document, 'keydown', onKeyDownCapture, { capture: true }); - dom.removeEventListener(window, window.PointerEvent ? "pointerdown" : "mousedown", onWindowMouseDown, { + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { + passive: true + }); + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { passive: true }); - dom.removeEventListener(window, "touchstart", onWindowTouchStart, { + dom.removeEventListener(window, 'touchstart', onWindowTouchStart, { passive: true }); + ['touchend', 'touchcancel'].forEach((event) => { + dom.removeEventListener(window, event, onWindowTouchEnd, { + passive: true + }); + }); stopOsdHideTimer(); - headerElement.classList.remove("osdHeader"); - headerElement.classList.remove("osdHeader-hidden"); - dom.removeEventListener(document, window.PointerEvent ? "pointermove" : "mousemove", onPointerMove, { + headerElement.classList.remove('osdHeader'); + headerElement.classList.remove('osdHeader-hidden'); + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { passive: true }); inputManager.off(window, onInputCommand); - events.off(playbackManager, "playerchange", onPlayerChange); + events.off(playbackManager, 'playerchange', onPlayerChange); releaseCurrentPlayer(); }); - view.querySelector(".btnFullscreen").addEventListener("click", function () { + view.querySelector('.btnFullscreen').addEventListener('click', function () { playbackManager.toggleFullscreen(currentPlayer); }); - view.querySelector(".btnPip").addEventListener("click", function () { + view.querySelector('.btnPip').addEventListener('click', function () { playbackManager.togglePictureInPicture(currentPlayer); }); - view.querySelector(".btnAirPlay").addEventListener("click", function () { + view.querySelector('.btnAirPlay').addEventListener('click', function () { playbackManager.toggleAirPlay(currentPlayer); }); - view.querySelector(".btnVideoOsdSettings").addEventListener("click", onSettingsButtonClick); - view.addEventListener("viewhide", function () { - headerElement.classList.remove("hide"); + view.querySelector('.btnVideoOsdSettings').addEventListener('click', onSettingsButtonClick); + view.addEventListener('viewhide', function () { + headerElement.classList.remove('hide'); }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (self.touchHelper) { self.touchHelper.destroy(); self.touchHelper = null; @@ -1372,16 +1461,17 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med destroySubtitleSync(); }); var lastPointerDown = 0; - dom.addEventListener(view, window.PointerEvent ? "pointerdown" : "click", function (e) { - if (dom.parentWithClass(e.target, ["videoOsdBottom", "upNextContainer"])) { + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) { + if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) { return void showOsd(); } - var pointerType = e.pointerType || (layoutManager.mobile ? "touch" : "mouse"); + var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); var now = new Date().getTime(); switch (pointerType) { - case "touch": + case 'touch': if (now - lastPointerDown > 300) { lastPointerDown = now; toggleOsd(); @@ -1389,7 +1479,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med break; - case "mouse": + case 'mouse': if (!e.button) { if (playPauseClickTimeout) { clearTimeout(playPauseClickTimeout); @@ -1414,38 +1504,23 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med }); if (browser.touch) { - dom.addEventListener(view, "dblclick", onDoubleClick, {}); + dom.addEventListener(view, 'dblclick', onDoubleClick, {}); } else { var options = { passive: true }; - dom.addEventListener(view, "dblclick", function () { + dom.addEventListener(view, 'dblclick', function () { playbackManager.toggleFullscreen(currentPlayer); }, options); } - function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; - - playbackManager.setVolume(this.value, currentPlayer); - } - - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); - } - } - - view.querySelector(".buttonMute").addEventListener("click", function () { + view.querySelector('.buttonMute').addEventListener('click', function () { playbackManager.toggleMute(currentPlayer); }); - nowPlayingVolumeSlider.addEventListener("change", setVolume); - nowPlayingVolumeSlider.addEventListener("mousemove", setVolumeDelayed); - nowPlayingVolumeSlider.addEventListener("touchmove", setVolumeDelayed); - nowPlayingPositionSlider.addEventListener("change", function () { + nowPlayingVolumeSlider.addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); + + nowPlayingPositionSlider.addEventListener('change', function () { var player = currentPlayer; if (player) { @@ -1470,14 +1545,14 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med ms /= 100; ms *= value; ms += programStartDateMs; - return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms)), true) + "

"; + return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms)), true) + '

'; } - return "--:--"; + return '--:--'; } if (!currentRuntimeTicks) { - return "--:--"; + return '--:--'; } var ticks = currentRuntimeTicks; @@ -1493,41 +1568,41 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } } - return '

' + datetime.getDisplayRunningTime(ticks) + "

"; + return '

' + datetime.getDisplayRunningTime(ticks) + '

'; }; - view.querySelector(".btnPreviousTrack").addEventListener("click", function () { + view.querySelector('.btnPreviousTrack').addEventListener('click', function () { playbackManager.previousTrack(currentPlayer); }); - view.querySelector(".btnPause").addEventListener("click", function () { + view.querySelector('.btnPause').addEventListener('click', function () { // Ignore 'click' if another element was originally clicked (Firefox/Edge issue) if (this.contains(clickedElement)) { playbackManager.playPause(currentPlayer); } }); - view.querySelector(".btnNextTrack").addEventListener("click", function () { + view.querySelector('.btnNextTrack').addEventListener('click', function () { playbackManager.nextTrack(currentPlayer); }); - btnRewind.addEventListener("click", function () { + btnRewind.addEventListener('click', function () { playbackManager.rewind(currentPlayer); }); - btnFastForward.addEventListener("click", function () { + btnFastForward.addEventListener('click', function () { playbackManager.fastForward(currentPlayer); }); - view.querySelector(".btnAudio").addEventListener("click", showAudioTrackSelection); - view.querySelector(".btnSubtitles").addEventListener("click", showSubtitleTrackSelection); + view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection); + view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection); if (browser.touch) { (function () { - require(["touchHelper"], function (TouchHelper) { + require(['touchHelper'], function (TouchHelper) { self.touchHelper = new TouchHelper(view, { swipeYThreshold: 30, triggerOnMove: true, preventDefaultOnMove: true, - ignoreTagNames: ["BUTTON", "INPUT", "TEXTAREA"] + ignoreTagNames: ['BUTTON', 'INPUT', 'TEXTAREA'] }); - events.on(self.touchHelper, "swipeup", onVerticalSwipe); - events.on(self.touchHelper, "swipedown", onVerticalSwipe); + events.on(self.touchHelper, 'swipeup', onVerticalSwipe); + events.on(self.touchHelper, 'swipedown', onVerticalSwipe); }); })(); } diff --git a/src/controllers/searchpage.js b/src/controllers/searchpage.js index b260ef5751f..8a138b7516a 100644 --- a/src/controllers/searchpage.js +++ b/src/controllers/searchpage.js @@ -1,5 +1,5 @@ -define(["focusManager", "searchFields", "searchResults", "events"], function (focusManager, SearchFields, SearchResults, events) { - "use strict"; +define(['focusManager', 'searchFields', 'searchResults', 'events'], function (focusManager, SearchFields, SearchResults, events) { + 'use strict'; return function (view, params) { function onSearch(e, value) { @@ -7,21 +7,21 @@ define(["focusManager", "searchFields", "searchResults", "events"], function (fo } var self = this; - view.addEventListener("viewshow", function () { + view.addEventListener('viewshow', function () { if (!self.searchFields) { self.searchFields = new SearchFields({ - element: view.querySelector(".searchFields") + element: view.querySelector('.searchFields') }); self.searchResults = new SearchResults({ - element: view.querySelector(".searchResults"), + element: view.querySelector('.searchResults'), serverId: params.serverId || ApiClient.serverId(), parentId: params.parentId, collectionType: params.collectionType }); - events.on(self.searchFields, "search", onSearch); + events.on(self.searchFields, 'search', onSearch); } }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (self.searchFields) { self.searchFields.destroy(); self.searchFields = null; diff --git a/src/controllers/serveractivity.js b/src/controllers/serveractivity.js deleted file mode 100644 index fb3b8112dcc..00000000000 --- a/src/controllers/serveractivity.js +++ /dev/null @@ -1,31 +0,0 @@ -define(["components/activitylog", "globalize"], function (ActivityLog, globalize) { - "use strict"; - - return function (view, params) { - var activityLog; - - if (params.useractivity !== "false") { - 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; - }); - }; -}); diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 9c61ac80d83..eeede20661c 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -1,5 +1,5 @@ -define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder) { - "use strict"; +define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -9,19 +9,23 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB if (!pageData) { pageData = data[key] = { query: { - SortBy: "SeriesSortName,SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Episode", + SortBy: 'SeriesSortName,SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Episode', Recursive: true, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,UserData", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,UserData', IsMissing: false, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", - StartIndex: 0, - Limit: pageSize + EnableImageTypes: 'Primary,Backdrop,Thumb', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -35,7 +39,7 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB function getSavedQueryKey(context) { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey("episodes"); + context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); } return context.savedQueryKey; @@ -43,17 +47,17 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB function onViewStyleChange() { var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems(page) { @@ -66,7 +70,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -75,7 +81,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -92,17 +100,17 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB filterButton: false }); var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); - if (viewStyle == "List") { + var itemsContainer = tabContent.querySelector('.itemsContainer'); + if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, sortBy: query.SortBy, showParentTitle: true }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', showTitle: true, showParentTitle: true, scalable: true, @@ -111,7 +119,7 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', showTitle: true, showParentTitle: true, overlayText: false, @@ -124,19 +132,19 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB var length; var elems; - elems = tabContent.querySelectorAll(".paging"); + elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } itemsContainer.innerHTML = html; @@ -145,25 +153,24 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), - mode: "episodes", + mode: 'episodes', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { reloadItems(tabContent); }); filterDialog.show(); @@ -175,35 +182,35 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB }; function initPage(tabContent) { - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SeriesSortName,SortName" + name: globalize.translate('OptionNameSort'), + id: 'SeriesSortName,SortName' }, { - name: Globalize.translate("OptionTvdbRating"), - id: "CommunityRating,SeriesSortName,SortName" + name: globalize.translate('OptionTvdbRating'), + id: 'CommunityRating,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SeriesSortName,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionPremiereDate"), - id: "PremiereDate,SeriesSortName,SortName" + name: globalize.translate('OptionPremiereDate'), + id: 'PremiereDate,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionDatePlayed"), - id: "DatePlayed,SeriesSortName,SortName" + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionParentalRating"), - id: "OfficialRating,SeriesSortName,SortName" + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionPlayCount"), - id: "PlayCount,SeriesSortName,SortName" + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SeriesSortName,SortName' }, { - name: Globalize.translate("OptionRuntime"), - id: "Runtime,SeriesSortName,SortName" + name: globalize.translate('OptionRuntime'), + id: 'Runtime,SeriesSortName,SortName' }], callback: function () { reloadItems(tabContent); @@ -212,11 +219,11 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB button: e.target }); }); - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "List,Poster,PosterCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 9c37e04e7c8..7d09307fc20 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader", "apphost", "globalize", "appRouter", "dom", "emby-button"], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { - "use strict"; +define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader', 'apphost', 'globalize', 'appRouter', 'dom', 'emby-button'], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { + 'use strict'; return function (view, params, tabContent) { function getPageData() { @@ -9,13 +9,13 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Series", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', Recursive: true, EnableTotalRecordCount: false }, - view: "Poster" + view: 'Poster' }; pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); @@ -29,7 +29,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey("seriesgenres"); + return libraryBrowser.getSavedQueryKey('seriesgenres'); } function getPromise() { @@ -43,29 +43,30 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function getPortraitShape() { - return enableScrollX() ? "overflowPortrait" : "portrait"; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(elem) { - var id = elem.getAttribute("data-id"); + function fillItemsContainer(entry) { + var elem = entry.target; + var id = elem.getAttribute('data-id'); var viewStyle = self.getCurrentViewStyle(); - var limit = "Thumb" == viewStyle || "ThumbCard" == viewStyle ? 5 : 9; + var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; if (enableScrollX()) { limit = 10; } - var enableImageTypes = "Thumb" == viewStyle || "ThumbCard" == viewStyle ? "Primary,Backdrop,Thumb" : "Primary"; + var enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary'; var query = { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Series", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', Recursive: true, - Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', ImageTypeLimit: 1, EnableImageTypes: enableImageTypes, Limit: limit, @@ -74,9 +75,9 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader ParentId: params.topParentId }; ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getThumbShape(), @@ -87,7 +88,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader overlayMoreButton: true, allowBottomPadding: false }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getThumbShape(), @@ -98,7 +99,7 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader cardLayout: true, showYear: true }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: getPortraitShape(), @@ -108,17 +109,20 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader cardLayout: true, showYear: true }); - } else if (viewStyle == "Poster") { + } 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 + " i").classList.remove("hide"); + tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); } }); } @@ -126,8 +130,8 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader function reloadItems(context, promise) { var query = getQuery(); promise.then(function (result) { - var elem = context.querySelector("#items"); - var html = ""; + var elem = context.querySelector('#items'); + var html = ''; var items = result.Items; for (var i = 0, length = items.length; i < length; i++) { @@ -135,26 +139,35 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader html += '
'; html += '"; + html += ''; + html += ''; + html += ''; + html += '
'; if (enableScrollX()) { - var scrollXClass = "scrollX hiddenScrollX"; + var scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { - scrollXClass += "smoothScrollX padded-top-focusscale padded-bottom-focusscale"; + scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; } html += '
'; } else { html += '
'; } - html += "
"; - html += "
"; + html += ''; + html += ''; + } + + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

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

'; + html += '

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

'; + html += '
'; } elem.innerHTML = html; @@ -173,16 +186,16 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader var data = {}; self.getViewStyles = function () { - return "Poster,PosterCard,Thumb,ThumbCard".split(","); + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/shows/tvlatest.js b/src/controllers/shows/tvlatest.js index 5862fce45dd..08e420a5956 100644 --- a/src/controllers/shows/tvlatest.js +++ b/src/controllers/shows/tvlatest.js @@ -1,28 +1,28 @@ -define(["loading", "components/groupedcards", "cardBuilder", "apphost", "imageLoader"], function (loading, groupedcards, cardBuilder, appHost, imageLoader) { - "use strict"; +define(['loading', 'components/groupedcards', 'cardBuilder', 'apphost', 'imageLoader'], function (loading, groupedcards, cardBuilder, appHost, imageLoader) { + 'use strict'; function getLatestPromise(context, params) { loading.show(); var userId = ApiClient.getCurrentUserId(); var parentId = params.topParentId; var options = { - IncludeItemTypes: "Episode", + IncludeItemTypes: 'Episode', Limit: 30, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb" + EnableImageTypes: 'Primary,Backdrop,Thumb' }; - return ApiClient.getJSON(ApiClient.getUrl("Users/" + userId + "/Items/Latest", options)); + return ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)); } function loadLatest(context, params, promise) { promise.then(function (items) { - var html = ""; - appHost.supports("imageanalysis"); + var html = ''; + appHost.supports('imageanalysis'); html += cardBuilder.getCardsHtml({ items: items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, showTitle: true, showSeriesYear: true, @@ -36,12 +36,12 @@ define(["loading", "components/groupedcards", "cardBuilder", "apphost", "imageLo overlayPlayButton: true, lines: 2 }); - var elem = context.querySelector("#latestEpisodes"); + var elem = context.querySelector('#latestEpisodes'); elem.innerHTML = html; imageLoader.lazyChildren(elem); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(context); }); }); @@ -59,6 +59,6 @@ define(["loading", "components/groupedcards", "cardBuilder", "apphost", "imageLo loadLatest(tabContent, params, latestPromise); }; - tabContent.querySelector("#latestEpisodes").addEventListener("click", groupedcards.onItemsContainerClick); + tabContent.querySelector('#latestEpisodes').addEventListener('click', groupedcards.onItemsContainerClick); }; }); diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index d1adb04342c..8087a030969 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -1,39 +1,39 @@ -define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "dom", "userSettings", "cardBuilder", "playbackManager", "mainTabsManager", "scrollStyles", "emby-itemscontainer", "emby-button"], function (events, inputManager, libraryMenu, layoutManager, loading, dom, userSettings, cardBuilder, playbackManager, mainTabsManager) { - "use strict"; +define(['events', 'inputManager', 'libraryMenu', 'layoutManager', 'loading', 'dom', 'userSettings', 'cardBuilder', 'playbackManager', 'mainTabsManager', 'globalize', 'scrollStyles', 'emby-itemscontainer', 'emby-button'], function (events, inputManager, libraryMenu, layoutManager, loading, dom, userSettings, cardBuilder, playbackManager, mainTabsManager, globalize) { + 'use strict'; function getTabs() { return [{ - name: Globalize.translate("TabShows") + name: globalize.translate('TabShows') }, { - name: Globalize.translate("TabSuggestions") + name: globalize.translate('TabSuggestions') }, { - name: Globalize.translate("TabLatest") + name: globalize.translate('TabLatest') }, { - name: Globalize.translate("TabUpcoming") + name: globalize.translate('TabUpcoming') }, { - name: Globalize.translate("TabGenres") + name: globalize.translate('TabGenres') }, { - name: Globalize.translate("TabNetworks") + name: globalize.translate('TabNetworks') }, { - name: Globalize.translate("TabEpisodes") + name: globalize.translate('TabEpisodes') }, { - name: Globalize.translate("ButtonSearch"), - cssClass: "searchTabButton" + name: globalize.translate('ButtonSearch'), + cssClass: 'searchTabButton' }]; } function getDefaultTabIndex(folderId) { - switch (userSettings.get("landing-" + folderId)) { - case "suggestions": + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': return 1; - case "latest": + case 'latest': return 2; - case "favorites": + case 'favorites': return 1; - case "genres": + case 'genres': return 4; default: @@ -43,19 +43,19 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do function setScrollClasses(elem, scrollX) { if (scrollX) { - elem.classList.add("hiddenScrollX"); + elem.classList.add('hiddenScrollX'); if (layoutManager.tv) { - elem.classList.add("smoothScrollX"); + elem.classList.add('smoothScrollX'); } - elem.classList.add("scrollX"); - elem.classList.remove("vertical-wrap"); + 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"); + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } @@ -69,25 +69,25 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do function loadNextUp() { var query = { Limit: 24, - Fields: "PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo', UserId: ApiClient.getCurrentUserId(), ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + 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"); + view.querySelector('.noNextUpItems').classList.add('hide'); } else { - view.querySelector(".noNextUpItems").classList.remove("hide"); + view.querySelector('.noNextUpItems').classList.remove('hide'); } - var container = view.querySelector("#nextUpItems"); + var container = view.querySelector('#nextUpItems'); cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, - shape: "backdrop", + shape: 'backdrop', scalable: true, showTitle: true, showParentTitle: true, @@ -98,7 +98,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do }); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(view); }); }); @@ -109,7 +109,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function loadResume() { @@ -117,28 +117,28 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do var screenWidth = dom.getWindowSize().innerWidth; var limit = screenWidth >= 1600 ? 5 : 6; var options = { - SortBy: "DatePlayed", - SortOrder: "Descending", - IncludeItemTypes: "Episode", - Filters: "IsResumable", + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Episode', + Filters: 'IsResumable', Limit: limit, Recursive: true, - Fields: "PrimaryImageAspectRatio,SeriesInfo,UserData,BasicSyncInfo", - ExcludeLocationTypes: "Virtual", + Fields: 'PrimaryImageAspectRatio,SeriesInfo,UserData,BasicSyncInfo', + ExcludeLocationTypes: 'Virtual', ParentId: parentId, ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Thumb", + EnableImageTypes: 'Primary,Backdrop,Thumb', EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { if (result.Items.length) { - view.querySelector("#resumableSection").classList.remove("hide"); + view.querySelector('#resumableSection').classList.remove('hide'); } else { - view.querySelector("#resumableSection").classList.add("hide"); + view.querySelector('#resumableSection').classList.add('hide'); } var allowBottomPadding = !enableScrollX(); - var container = view.querySelector("#resumableItems"); + var container = view.querySelector('#resumableItems'); cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, @@ -165,7 +165,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do } function getTabContainers() { - return view.querySelectorAll(".pageTabContent"); + return view.querySelectorAll('.pageTabContent'); } function initTabs() { @@ -177,34 +177,34 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do switch (index) { case 0: - depends.push("controllers/shows/tvshows"); + depends.push('controllers/shows/tvshows'); break; case 1: break; case 2: - depends.push("controllers/shows/tvlatest"); + depends.push('controllers/shows/tvlatest'); break; case 3: - depends.push("controllers/shows/tvupcoming"); + depends.push('controllers/shows/tvupcoming'); break; case 4: - depends.push("controllers/shows/tvgenres"); + depends.push('controllers/shows/tvgenres'); break; case 5: - depends.push("controllers/shows/tvstudios"); + depends.push('controllers/shows/tvstudios'); break; case 6: - depends.push("controllers/shows/episodes"); + depends.push('controllers/shows/episodes'); break; case 7: - depends.push("scripts/searchtab"); + depends.push('scripts/searchtab'); } require(depends, function (controllerFactory) { @@ -224,7 +224,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do controller = self; } else if (index === 7) { controller = new controllerFactory(view, tabContent, { - collectionType: "tvshows", + collectionType: 'tvshows', parentId: params.topParentId }); } else { @@ -263,7 +263,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do } function onPlaybackStop(e, state) { - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { renderedTabs = []; mainTabsManager.getTabsElement().triggerTabChange(); } @@ -272,16 +272,16 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do function onWebSocketMessage(e, data) { var msg = data; - if (msg.MessageType === "UserDataChanged" && msg.Data.UserId == ApiClient.getCurrentUserId()) { + if (msg.MessageType === 'UserDataChanged' && msg.Data.UserId == ApiClient.getCurrentUserId()) { renderedTabs = []; } } function onInputCommand(e) { switch (e.detail.command) { - case "search": + case 'search': e.preventDefault(); - Dashboard.navigate("search.html?collectionType=tv&parentId=" + params.topParentId); + Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); } } @@ -292,7 +292,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do self.initTab = function () { var tabContent = self.tabContent; - setScrollClasses(tabContent.querySelector("#resumableItems"), enableScrollX()); + setScrollClasses(tabContent.querySelector('#resumableItems'), enableScrollX()); }; self.renderTab = function () { @@ -301,34 +301,34 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do var tabControllers = []; var renderedTabs = []; - setScrollClasses(view.querySelector("#resumableItems"), enableScrollX()); - view.addEventListener("viewshow", function (e) { + setScrollClasses(view.querySelector('#resumableItems'), enableScrollX()); + view.addEventListener('viewshow', function (e) { isViewRestored = e.detail.isRestored; initTabs(); - if (!view.getAttribute("data-title")) { + if (!view.getAttribute('data-title')) { var parentId = params.topParentId; if (parentId) { ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute("data-title", item.Name); + view.setAttribute('data-title', item.Name); libraryMenu.setTitle(item.Name); }); } else { - view.setAttribute("data-title", Globalize.translate("TabShows")); - libraryMenu.setTitle(Globalize.translate("TabShows")); + view.setAttribute('data-title', globalize.translate('TabShows')); + libraryMenu.setTitle(globalize.translate('TabShows')); } } - events.on(playbackManager, "playbackstop", onPlaybackStop); - events.on(ApiClient, "message", onWebSocketMessage); + events.on(playbackManager, 'playbackstop', onPlaybackStop); + events.on(ApiClient, 'message', onWebSocketMessage); inputManager.on(window, onInputCommand); }); - view.addEventListener("viewbeforehide", function (e) { + view.addEventListener('viewbeforehide', function (e) { inputManager.off(window, onInputCommand); - events.off(playbackManager, "playbackstop", onPlaybackStop); - events.off(ApiClient, "message", onWebSocketMessage); + events.off(playbackManager, 'playbackstop', onPlaybackStop); + events.off(ApiClient, 'message', onWebSocketMessage); }); - view.addEventListener("viewdestroy", function (e) { + view.addEventListener('viewdestroy', function (e) { tabControllers.forEach(function (t) { if (t.destroy) { t.destroy(); diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 24f6cb1a03a..0bd5e4b52e9 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -1,5 +1,5 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "alphaPicker", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, listView, cardBuilder, alphaPicker) { - "use strict"; +define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardBuilder', 'alphaPicker', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, listView, cardBuilder, alphaPicker, userSettings, globalize) { + 'use strict'; return function (view, params, tabContent) { function getPageData(context) { @@ -9,18 +9,22 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Series", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', Recursive: true, - Fields: "PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -34,7 +38,7 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " function getSavedQueryKey(context) { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey("series"); + context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); } return context.savedQueryKey; @@ -42,17 +46,17 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " function onViewStyleChange() { var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems(page) { @@ -65,7 +69,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -74,7 +80,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -92,45 +100,45 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " filterButton: false }); var viewStyle = self.getCurrentViewStyle(); - if (viewStyle == "Thumb") { + if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "tvshows", + context: 'tvshows', overlayMoreButton: true, showTitle: true, centerText: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, - context: "tvshows", + context: 'tvshows', cardLayout: true, showTitle: true, showYear: true, centerText: true }); - } else if (viewStyle == "Banner") { + } else if (viewStyle == 'Banner') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "banner", + shape: 'banner', preferBanner: true, - context: "tvshows" + context: 'tvshows' }); - } else if (viewStyle == "List") { + } else if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, - context: "tvshows", + context: 'tvshows', sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "portrait", - context: "tvshows", + shape: 'portrait', + context: 'tvshows', showTitle: true, showYear: true, centerText: true, @@ -139,8 +147,8 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "portrait", - context: "tvshows", + shape: 'portrait', + context: 'tvshows', centerText: true, lazy: true, overlayMoreButton: true, @@ -150,30 +158,30 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var i; var length; - var elems = tabContent.querySelectorAll(".paging"); + var elems = tabContent.querySelectorAll('.paging'); for (i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll(".btnNextPage"); + elems = tabContent.querySelectorAll('.btnNextPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onNextPageClick); + elems[i].addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll(".btnPreviousPage"); + elems = tabContent.querySelectorAll('.btnPreviousPage'); for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener("click", onPreviousPageClick); + elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(page); }); }); @@ -185,18 +193,17 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; self.showFilterMenu = function () { - require(["components/filterdialog/filterdialog"], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), - mode: "series", + mode: 'series', serverId: ApiClient.serverId() }); - events.on(filterDialog, "filterchange", function () { + events.on(filterDialog, 'filterchange', function () { getQuery(tabContent).StartIndex = 0; reloadItems(tabContent); }); @@ -209,10 +216,10 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector(".alphaPicker"); - var itemsContainer = tabContent.querySelector(".itemsContainer"); + var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + var itemsContainer = tabContent.querySelector('.itemsContainer'); - alphaPickerElement.addEventListener("alphavaluechanged", function (e) { + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { var newValue = e.detail.value; var query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; @@ -221,36 +228,36 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " }); self.alphaPicker = new alphaPicker({ element: alphaPickerElement, - valueChangeEvent: "click" + valueChangeEvent: 'click' }); - tabContent.querySelector(".alphaPicker").classList.add("alphabetPicker-right"); - alphaPickerElement.classList.add("alphaPicker-fixed-right"); - itemsContainer.classList.add("padded-right-withalphapicker"); + tabContent.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector(".btnFilter").addEventListener("click", function () { + tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - tabContent.querySelector(".btnSort").addEventListener("click", function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: Globalize.translate("OptionNameSort"), - id: "SortName" + name: globalize.translate('OptionNameSort'), + id: 'SortName' }, { - name: Globalize.translate("OptionImdbRating"), - id: "CommunityRating,SortName" + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' }, { - name: Globalize.translate("OptionDateAdded"), - id: "DateCreated,SortName" + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' }, { - name: Globalize.translate("OptionDatePlayed"), - id: "DatePlayed,SortName" + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName' }, { - name: Globalize.translate("OptionParentalRating"), - id: "OfficialRating,SortName" + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' }, { - name: Globalize.translate("OptionReleaseDate"), - id: "PremiereDate,SortName" + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' }], callback: function () { getQuery(tabContent).StartIndex = 0; @@ -260,11 +267,11 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " button: e.target }); }); - var btnSelectView = tabContent.querySelector(".btnSelectView"); - btnSelectView.addEventListener("click", function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), "Banner,List,Poster,PosterCard,Thumb,ThumbCard".split(",")); + var btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); - btnSelectView.addEventListener("layoutchange", function (e) { + btnSelectView.addEventListener('layoutchange', function (e) { var viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index 4e715e1fd73..1051bfa10b2 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -1,5 +1,5 @@ -define(["loading", "libraryBrowser", "cardBuilder", "apphost"], function (loading, libraryBrowser, cardBuilder, appHost) { - "use strict"; +define(['loading', 'libraryBrowser', 'cardBuilder', 'apphost'], function (loading, libraryBrowser, cardBuilder, appHost) { + 'use strict'; function getQuery(params) { var key = getSavedQueryKey(); @@ -8,11 +8,11 @@ define(["loading", "libraryBrowser", "cardBuilder", "apphost"], function (loadin if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Series", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', Recursive: true, - Fields: "DateCreated,PrimaryImageAspectRatio", + Fields: 'DateCreated,PrimaryImageAspectRatio', StartIndex: 0 } }; @@ -23,7 +23,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "apphost"], function (loadin } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey("studios"); + return libraryBrowser.getSavedQueryKey('studios'); } function getPromise(context, params) { @@ -34,20 +34,20 @@ define(["loading", "libraryBrowser", "cardBuilder", "apphost"], function (loadin function reloadItems(context, params, promise) { promise.then(function (result) { - var elem = context.querySelector("#items"); + var elem = context.querySelector('#items'); cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: "backdrop", + shape: 'backdrop', preferThumb: true, showTitle: true, scalable: true, centerText: true, overlayMoreButton: true, - context: "tvshows" + context: 'tvshows' }); loading.hide(); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(context); }); }); diff --git a/src/controllers/shows/tvupcoming.js b/src/controllers/shows/tvupcoming.js index 249d932d395..d57a5ae3cd6 100644 --- a/src/controllers/shows/tvupcoming.js +++ b/src/controllers/shows/tvupcoming.js @@ -1,18 +1,18 @@ -define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", "apphost", "imageLoader", "scrollStyles", "emby-itemscontainer"], function (layoutManager, loading, datetime, libraryBrowser, cardBuilder, appHost, imageLoader) { - "use strict"; +define(['layoutManager', 'loading', 'datetime', 'libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'globalize', 'scrollStyles', 'emby-itemscontainer'], function (layoutManager, loading, datetime, libraryBrowser, cardBuilder, appHost, imageLoader, globalize) { + 'use strict'; function getUpcomingPromise(context, params) { loading.show(); var query = { Limit: 48, - Fields: "AirTime,UserData", + Fields: 'AirTime,UserData', UserId: ApiClient.getCurrentUserId(), ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', EnableTotalRecordCount: false }; query.ParentId = params.topParentId; - return ApiClient.getJSON(ApiClient.getUrl("Shows/Upcoming", query)); + return ApiClient.getJSON(ApiClient.getUrl('Shows/Upcoming', query)); } function loadUpcoming(context, params, promise) { @@ -20,12 +20,12 @@ define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", var items = result.Items; if (items.length) { - context.querySelector(".noItemsMessage").style.display = "none"; + context.querySelector('.noItemsMessage').style.display = 'none'; } else { - context.querySelector(".noItemsMessage").style.display = "block"; + context.querySelector('.noItemsMessage').style.display = 'block'; } - renderUpcoming(context.querySelector("#upcomingItems"), items); + renderUpcoming(context.querySelector('#upcomingItems'), items); loading.hide(); }); } @@ -35,27 +35,27 @@ define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", } function getThumbShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function renderUpcoming(elem, items) { var i; var length; var groups = []; - var currentGroupName = ""; + var currentGroupName = ''; var currentGroup = []; for (i = 0, length = items.length; i < length; i++) { var item = items[i]; - var dateText = ""; + var dateText = ''; if (item.PremiereDate) { try { var premiereDate = datetime.parseISO8601Date(item.PremiereDate, true); - dateText = datetime.isRelativeDay(premiereDate, -1) ? Globalize.translate("Yesterday") : datetime.toLocaleDateString(premiereDate, { - weekday: "long", - month: "short", - day: "numeric" + 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'); @@ -77,20 +77,20 @@ define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", } } - var html = ""; + var html = ''; for (i = 0, length = groups.length; i < length; i++) { var group = groups[i]; html += '
'; - html += '

' + group.name + "

"; + html += '

' + group.name + '

'; var allowBottomPadding = true; if (enableScrollX()) { allowBottomPadding = false; - var scrollXClass = "scrollX hiddenScrollX"; + var scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { - scrollXClass += " smoothScrollX"; + scrollXClass += ' smoothScrollX'; } html += '
'; @@ -98,7 +98,7 @@ define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", html += '
'; } - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); supportsImageAnalysis = false; html += cardBuilder.getCardsHtml({ items: group.items, @@ -116,8 +116,8 @@ define(["layoutManager", "loading", "datetime", "libraryBrowser", "cardBuilder", overlayMoreButton: true, missingIndicator: false }); - html += "
"; - html += "
"; + html += '
'; + html += ''; } elem.innerHTML = html; diff --git a/src/controllers/streamingsettings.js b/src/controllers/streamingsettings.js deleted file mode 100644 index 14e5e028a8b..00000000000 --- a/src/controllers/streamingsettings.js +++ /dev/null @@ -1,43 +0,0 @@ -define(["jQuery", "libraryMenu", "loading"], function ($, libraryMenu, loading) { - "use strict"; - - function loadPage(page, config) { - $("#txtRemoteClientBitrateLimit", page).val(config.RemoteClientBitrateLimit / 1e6 || ""); - loading.hide(); - } - - function onSubmit() { - loading.show(); - var form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($("#txtRemoteClientBitrateLimit", form).val() || "0")); - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; - } - - function getTabs() { - return [{ - href: "encodingsettings.html", - name: Globalize.translate("Transcoding") - }, { - href: "playbackconfiguration.html", - name: Globalize.translate("TabResumeSettings") - }, { - 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); - var page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); - }); -}); diff --git a/src/controllers/user/display.js b/src/controllers/user/display.js index f348f287501..a400c50ccf3 100644 --- a/src/controllers/user/display.js +++ b/src/controllers/user/display.js @@ -1,19 +1,25 @@ -define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) { - "use strict"; +import DisplaySettings from 'displaySettings'; +import * as userSettings from 'userSettings'; +import autoFocuser from 'autoFocuser'; - return function (view, params) { +/* eslint-disable indent */ + + // Shortcuts + const UserSettings = userSettings.UserSettings; + + export default function (view, params) { function onBeforeUnload(e) { if (hasChanges) { - e.returnValue = "You currently have unsaved changes. Are you sure you wish to leave?"; + e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; } } - var settingsInstance; - var hasChanges; - var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); - view.addEventListener("viewshow", function () { - window.addEventListener("beforeunload", onBeforeUnload); + let settingsInstance; + let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + window.addEventListener('beforeunload', onBeforeUnload); if (settingsInstance) { settingsInstance.loadData(); @@ -21,30 +27,25 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"] settingsInstance = new DisplaySettings({ serverId: ApiClient.serverId(), userId: userId, - element: view.querySelector(".settingsContainer"), - userSettings: userSettings, - enableSaveButton: false, - enableSaveConfirmation: false, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, autoFocus: autoFocuser.isEnabled() }); } }); - view.addEventListener("change", function () { + + view.addEventListener('change', function () { hasChanges = true; }); - view.addEventListener("viewbeforehide", function () { - window.removeEventListener("beforeunload", onBeforeUnload); - hasChanges = false; - if (settingsInstance) { - settingsInstance.submit(); - } - }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (settingsInstance) { settingsInstance.destroy(); settingsInstance = null; } }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/user/home.js b/src/controllers/user/home.js index 7f12efc7fb4..e7058fd3ac7 100644 --- a/src/controllers/user/home.js +++ b/src/controllers/user/home.js @@ -1,19 +1,29 @@ -define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { - "use strict"; +import HomescreenSettings from 'homescreenSettings'; +import dom from 'dom'; +import globalize from 'globalize'; +import loading from 'loading'; +import * as userSettings from 'userSettings'; +import autoFocuser from 'autoFocuser'; +import 'listViewStyle'; - return function (view, params) { +/* eslint-disable indent */ + + // Shortcuts + const UserSettings = userSettings.UserSettings; + + export default function (view, params) { function onBeforeUnload(e) { if (hasChanges) { - e.returnValue = "You currently have unsaved changes. Are you sure you wish to leave?"; + e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; } } - var homescreenSettingsInstance; - var hasChanges; - var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); - view.addEventListener("viewshow", function () { - window.addEventListener("beforeunload", onBeforeUnload); + let homescreenSettingsInstance; + let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + window.addEventListener('beforeunload', onBeforeUnload); if (homescreenSettingsInstance) { homescreenSettingsInstance.loadData(); @@ -21,29 +31,25 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin homescreenSettingsInstance = new HomescreenSettings({ serverId: ApiClient.serverId(), userId: userId, - element: view.querySelector(".homeScreenSettingsContainer"), - userSettings: userSettings, - enableSaveButton: false, - enableSaveConfirmation: false, + element: view.querySelector('.homeScreenSettingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, autoFocus: autoFocuser.isEnabled() }); } }); - view.addEventListener("change", function () { + + view.addEventListener('change', function () { hasChanges = true; }); - view.addEventListener("viewbeforehide", function () { - hasChanges = false; - if (homescreenSettingsInstance) { - homescreenSettingsInstance.submit(); - } - }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (homescreenSettingsInstance) { homescreenSettingsInstance.destroy(); homescreenSettingsInstance = null; } }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/user/menu.js b/src/controllers/user/menu.js index e7d816fb6f8..586cd6744ce 100644 --- a/src/controllers/user/menu.js +++ b/src/controllers/user/menu.js @@ -1,62 +1,56 @@ -define(["apphost", "connectionManager", "layoutManager", "listViewStyle", "emby-button"], function(appHost, connectionManager, layoutManager) { - "use strict"; +define(['apphost', 'connectionManager', 'layoutManager', 'listViewStyle', 'emby-button'], function(appHost, connectionManager, layoutManager) { + 'use strict'; return function(view, params) { - view.querySelector(".btnLogout").addEventListener("click", function() { + view.querySelector('.btnLogout').addEventListener('click', function() { Dashboard.logout(); }); - view.querySelector(".selectServer").addEventListener("click", function () { + view.querySelector('.selectServer').addEventListener('click', function () { Dashboard.selectServer(); }); - view.querySelector(".clientSettings").addEventListener("click", function () { + view.querySelector('.clientSettings').addEventListener('click', function () { window.NativeShell.openClientSettings(); }); - view.addEventListener("viewshow", function() { + view.addEventListener('viewshow', function() { // this page can also be used by admins to change user preferences from the user edit page var userId = params.userId || Dashboard.getCurrentUserId(); var page = this; - page.querySelector(".lnkMyProfile").setAttribute("href", "myprofile.html?userId=" + userId); - page.querySelector(".lnkDisplayPreferences").setAttribute("href", "mypreferencesdisplay.html?userId=" + userId); - page.querySelector(".lnkHomePreferences").setAttribute("href", "mypreferenceshome.html?userId=" + userId); - page.querySelector(".lnkPlaybackPreferences").setAttribute("href", "mypreferencesplayback.html?userId=" + userId); - page.querySelector(".lnkSubtitlePreferences").setAttribute("href", "mypreferencessubtitles.html?userId=" + userId); + page.querySelector('.lnkMyProfile').setAttribute('href', 'myprofile.html?userId=' + userId); + page.querySelector('.lnkDisplayPreferences').setAttribute('href', 'mypreferencesdisplay.html?userId=' + userId); + page.querySelector('.lnkHomePreferences').setAttribute('href', 'mypreferenceshome.html?userId=' + userId); + page.querySelector('.lnkPlaybackPreferences').setAttribute('href', 'mypreferencesplayback.html?userId=' + userId); + page.querySelector('.lnkSubtitlePreferences').setAttribute('href', 'mypreferencessubtitles.html?userId=' + userId); - if (window.NativeShell && window.NativeShell.AppHost.supports("clientsettings")) { - page.querySelector(".clientSettings").classList.remove("hide"); + if (window.NativeShell && window.NativeShell.AppHost.supports('clientsettings')) { + page.querySelector('.clientSettings').classList.remove('hide'); } else { - page.querySelector(".clientSettings").classList.add("hide"); + page.querySelector('.clientSettings').classList.add('hide'); } - if (appHost.supports("multiserver")) { - page.querySelector(".selectServer").classList.remove("hide"); + if (appHost.supports('multiserver')) { + page.querySelector('.selectServer').classList.remove('hide'); } else { - page.querySelector(".selectServer").classList.add("hide"); + page.querySelector('.selectServer').classList.add('hide'); } // hide the actions if user preferences are being edited for a different user if (params.userId && params.userId !== Dashboard.getCurrentUserId) { - page.querySelector(".userSection").classList.add("hide"); - page.querySelector(".adminSection").classList.add("hide"); - } - - if (layoutManager.mobile) { - page.querySelector(".headerUsername").classList.add("hide"); - page.querySelector(".adminSection").classList.add("hide"); - page.querySelector(".userSection").classList.add("hide"); + page.querySelector('.userSection').classList.add('hide'); + page.querySelector('.adminSection').classList.add('hide'); } ApiClient.getUser(userId).then(function(user) { - page.querySelector(".headerUsername").innerHTML = user.Name; + page.querySelector('.headerUsername').innerHTML = user.Name; if (!user.Policy.IsAdministrator) { - page.querySelector(".adminSection").classList.add("hide"); + page.querySelector('.adminSection').classList.add('hide'); } }); - require(["autoFocuser"], function (autoFocuser) { + require(['autoFocuser'], function (autoFocuser) { autoFocuser.autoFocus(view); }); }); diff --git a/src/controllers/user/playback.js b/src/controllers/user/playback.js index 3def9d19313..5bcf055cd3c 100644 --- a/src/controllers/user/playback.js +++ b/src/controllers/user/playback.js @@ -1,19 +1,29 @@ -define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { - "use strict"; +import PlaybackSettings from 'playbackSettings'; +import dom from 'dom'; +import globalize from 'globalize'; +import loading from 'loading'; +import * as userSettings from 'userSettings'; +import autoFocuser from 'autoFocuser'; +import 'listViewStyle'; - return function (view, params) { +/* eslint-disable indent */ + + // Shortcuts + const UserSettings = userSettings.UserSettings; + + export default function (view, params) { function onBeforeUnload(e) { if (hasChanges) { - e.returnValue = "You currently have unsaved changes. Are you sure you wish to leave?"; + e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; } } - var settingsInstance; - var hasChanges; - var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); - view.addEventListener("viewshow", function () { - window.addEventListener("beforeunload", onBeforeUnload); + let settingsInstance; + let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + window.addEventListener('beforeunload', onBeforeUnload); if (settingsInstance) { settingsInstance.loadData(); @@ -21,29 +31,25 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading" settingsInstance = new PlaybackSettings({ serverId: ApiClient.serverId(), userId: userId, - element: view.querySelector(".settingsContainer"), - userSettings: userSettings, - enableSaveButton: false, - enableSaveConfirmation: false, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, autoFocus: autoFocuser.isEnabled() }); } }); - view.addEventListener("change", function () { + + view.addEventListener('change', function () { hasChanges = true; }); - view.addEventListener("viewbeforehide", function () { - hasChanges = false; - if (settingsInstance) { - settingsInstance.submit(); - } - }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (settingsInstance) { settingsInstance.destroy(); settingsInstance = null; } }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/user/profile.js b/src/controllers/user/profile.js index 3b85cb1d8c0..fd7d1e32ccb 100644 --- a/src/controllers/user/profile.js +++ b/src/controllers/user/profile.js @@ -1,31 +1,31 @@ -define(["controllers/userpasswordpage", "loading", "libraryMenu", "apphost", "emby-button"], function (UserPasswordPage, loading, libraryMenu, appHost) { - "use strict"; +define(['controllers/dashboard/users/userpasswordpage', 'loading', 'libraryMenu', 'apphost', 'globalize', 'emby-button'], function (UserPasswordPage, loading, libraryMenu, appHost, globalize) { + 'use strict'; function reloadUser(page) { - var userId = getParameterByName("userId"); + var userId = getParameterByName('userId'); loading.show(); ApiClient.getUser(userId).then(function (user) { - page.querySelector(".username").innerHTML = user.Name; + page.querySelector('.username').innerHTML = user.Name; libraryMenu.setTitle(user.Name); - var imageUrl = "assets/img/avatar.png"; + var imageUrl = 'assets/img/avatar.png'; if (user.PrimaryImageTag) { imageUrl = ApiClient.getUserImageUrl(user.Id, { tag: user.PrimaryImageTag, - type: "Primary" + type: 'Primary' }); } - var userImage = page.querySelector("#image"); - userImage.style.backgroundImage = "url(" + imageUrl + ")"; + var 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"); + 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(); @@ -36,8 +36,8 @@ define(["controllers/userpasswordpage", "loading", "libraryMenu", "apphost", "em loading.hide(); switch (evt.target.error.code) { case evt.target.error.NOT_FOUND_ERR: - require(["toast"], function (toast) { - toast(Globalize.translate("FileNotFound")); + require(['toast'], function (toast) { + toast(globalize.translate('FileNotFound')); }); break; case evt.target.error.ABORT_ERR: @@ -45,24 +45,24 @@ define(["controllers/userpasswordpage", "loading", "libraryMenu", "apphost", "em break; case evt.target.error.NOT_READABLE_ERR: default: - require(["toast"], function (toast) { - toast(Globalize.translate("FileReadError")); + require(['toast'], function (toast) { + toast(globalize.translate('FileReadError')); }); } } function onFileReaderAbort(evt) { loading.hide(); - require(["toast"], function (toast) { - toast(Globalize.translate("FileReadCancelled")); + require(['toast'], function (toast) { + toast(globalize.translate('FileReadCancelled')); }); } function setFiles(page, files) { - var userImage = page.querySelector("#image"); + var userImage = page.querySelector('#image'); var file = files[0]; - if (!file || !file.type.match("image.*")) { + if (!file || !file.type.match('image.*')) { return false; } @@ -70,9 +70,9 @@ define(["controllers/userpasswordpage", "loading", "libraryMenu", "apphost", "em reader.onerror = onFileReaderError; reader.onabort = onFileReaderAbort; reader.onload = function (evt) { - userImage.style.backgroundImage = "url(" + evt.target.result + ")"; - var userId = getParameterByName("userId"); - ApiClient.uploadUserImage(userId, "Primary", file).then(function () { + userImage.style.backgroundImage = 'url(' + evt.target.result + ')'; + var userId = getParameterByName('userId'); + ApiClient.uploadUserImage(userId, 'Primary', file).then(function () { loading.hide(); reloadUser(page); }); @@ -84,22 +84,22 @@ define(["controllers/userpasswordpage", "loading", "libraryMenu", "apphost", "em return function (view, params) { reloadUser(view); new UserPasswordPage(view, params); - view.querySelector("#btnDeleteImage").addEventListener("click", function () { - require(["confirm"], function (confirm) { - confirm(Globalize.translate("DeleteImageConfirmation"), Globalize.translate("DeleteImage")).then(function () { + view.querySelector('#btnDeleteImage').addEventListener('click', function () { + require(['confirm'], function (confirm) { + confirm(globalize.translate('DeleteImageConfirmation'), globalize.translate('DeleteImage')).then(function () { loading.show(); - var userId = getParameterByName("userId"); - ApiClient.deleteUserImage(userId, "primary").then(function () { + var userId = getParameterByName('userId'); + ApiClient.deleteUserImage(userId, 'primary').then(function () { loading.hide(); reloadUser(view); }); }); }); }); - view.querySelector("#btnAddImage").addEventListener("click", function (evt) { - view.querySelector("#uploadImage").click(); + view.querySelector('#btnAddImage').addEventListener('click', function (evt) { + view.querySelector('#uploadImage').click(); }); - view.querySelector("#uploadImage").addEventListener("change", function (evt) { + view.querySelector('#uploadImage').addEventListener('change', function (evt) { setFiles(view, evt.target.files); }); }; diff --git a/src/controllers/user/subtitles.js b/src/controllers/user/subtitles.js index 1f1102194e7..82f5270a4e8 100644 --- a/src/controllers/user/subtitles.js +++ b/src/controllers/user/subtitles.js @@ -1,19 +1,25 @@ -define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) { - "use strict"; +import SubtitleSettings from 'subtitleSettings'; +import * as userSettings from 'userSettings'; +import autoFocuser from 'autoFocuser'; - return function (view, params) { +/* eslint-disable indent */ + + // Shortcuts + const UserSettings = userSettings.UserSettings; + + export default function (view, params) { function onBeforeUnload(e) { if (hasChanges) { - e.returnValue = "You currently have unsaved changes. Are you sure you wish to leave?"; + e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; } } - var subtitleSettingsInstance; - var hasChanges; - var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); - view.addEventListener("viewshow", function () { - window.addEventListener("beforeunload", onBeforeUnload); + let subtitleSettingsInstance; + let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + window.addEventListener('beforeunload', onBeforeUnload); if (subtitleSettingsInstance) { subtitleSettingsInstance.loadData(); @@ -21,29 +27,25 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser" subtitleSettingsInstance = new SubtitleSettings({ serverId: ApiClient.serverId(), userId: userId, - element: view.querySelector(".settingsContainer"), - userSettings: userSettings, - enableSaveButton: false, - enableSaveConfirmation: false, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, autoFocus: autoFocuser.isEnabled() }); } }); - view.addEventListener("change", function () { + + view.addEventListener('change', function () { hasChanges = true; }); - view.addEventListener("viewbeforehide", function () { - hasChanges = false; - if (subtitleSettingsInstance) { - subtitleSettingsInstance.submit(); - } - }); - view.addEventListener("viewdestroy", function () { + view.addEventListener('viewdestroy', function () { if (subtitleSettingsInstance) { subtitleSettingsInstance.destroy(); subtitleSettingsInstance = null; } }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/useredit.js b/src/controllers/useredit.js deleted file mode 100644 index f6a5aaf0002..00000000000 --- a/src/controllers/useredit.js +++ /dev/null @@ -1,198 +0,0 @@ -define(["jQuery", "loading", "libraryMenu", "fnchecked"], function ($, loading, libraryMenu) { - "use strict"; - - function loadDeleteFolders(page, user, mediaFolders) { - ApiClient.getJSON(ApiClient.getUrl("Channels", { - SupportsMediaDeletion: true - })).then(function (channelsResult) { - var i; - var length; - var folder; - var isChecked; - var checkedAttribute; - var html = ""; - - for (i = 0, length = mediaFolders.length; i < length; i++) { - folder = mediaFolders[i]; - isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id); - checkedAttribute = isChecked ? ' checked="checked"' : ""; - html += '"; - } - - for (i = 0, length = channelsResult.Items.length; i < length; i++) { - folder = channelsResult.Items[i]; - isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id); - checkedAttribute = isChecked ? ' checked="checked"' : ""; - html += '"; - } - - $(".deleteAccess", page).html(html).trigger("create"); - $("#chkEnableDeleteAllFolders", page).checked(user.Policy.EnableContentDeletion).trigger("change"); - }); - } - - function loadAuthProviders(page, user, providers) { - if (providers.length > 1) { - page.querySelector(".fldSelectLoginProvider").classList.remove("hide"); - } else { - page.querySelector(".fldSelectLoginProvider").classList.add("hide"); - } - - var currentProviderId = user.Policy.AuthenticationProviderId; - page.querySelector(".selectLoginProvider").innerHTML = providers.map(function (provider) { - var 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"); - } - - var currentProviderId = user.Policy.PasswordResetProviderId; - page.querySelector(".selectPasswordResetProvider").innerHTML = providers.map(function (provider) { - var selected = provider.Id === currentProviderId || providers.length < 2 ? " selected" : ""; - return '"; - }); - } - - function loadUser(page, user) { - currentUser = 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).checked(user.Policy.IsAdministrator); - $("#chkDisabled", page).checked(user.Policy.IsDisabled); - $("#chkIsHidden", page).checked(user.Policy.IsHidden); - $("#chkRemoteControlSharedDevices", page).checked(user.Policy.EnableSharedDeviceControl); - $("#chkEnableRemoteControlOtherUsers", page).checked(user.Policy.EnableRemoteControlOfOtherUsers); - $("#chkEnableDownloading", page).checked(user.Policy.EnableContentDownloading); - $("#chkManageLiveTv", page).checked(user.Policy.EnableLiveTvManagement); - $("#chkEnableLiveTvAccess", page).checked(user.Policy.EnableLiveTvAccess); - $("#chkEnableMediaPlayback", page).checked(user.Policy.EnableMediaPlayback); - $("#chkEnableAudioPlaybackTranscoding", page).checked(user.Policy.EnableAudioPlaybackTranscoding); - $("#chkEnableVideoPlaybackTranscoding", page).checked(user.Policy.EnableVideoPlaybackTranscoding); - $("#chkEnableVideoPlaybackRemuxing", page).checked(user.Policy.EnablePlaybackRemuxing); - $("#chkForceRemoteSourceTranscoding", page).checked(user.Policy.ForceRemoteSourceTranscoding); - $("#chkRemoteAccess", page).checked(null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess); - $("#chkEnableSyncTranscoding", page).checked(user.Policy.EnableSyncTranscoding); - $("#chkEnableConversion", page).checked(user.Policy.EnableMediaConversion || false); - $("#chkEnableSharing", page).checked(user.Policy.EnablePublicSharing); - $("#txtRemoteClientBitrateLimit", page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ""); - $("#txtLoginAttemptsBeforeLockout", page).val(user.Policy.LoginAttemptsBeforeLockout || "0"); - loading.hide(); - } - - function onSaveComplete(page, user) { - Dashboard.navigate("userprofiles.html"); - loading.hide(); - - require(["toast"], function (toast) { - toast(Globalize.translate("SettingsSaved")); - }); - } - - function saveUser(user, page) { - user.Name = $("#txtUserName", page).val(); - user.Policy.IsAdministrator = $("#chkIsAdmin", page).checked(); - user.Policy.IsHidden = $("#chkIsHidden", page).checked(); - user.Policy.IsDisabled = $("#chkDisabled", page).checked(); - user.Policy.EnableRemoteControlOfOtherUsers = $("#chkEnableRemoteControlOtherUsers", page).checked(); - user.Policy.EnableLiveTvManagement = $("#chkManageLiveTv", page).checked(); - user.Policy.EnableLiveTvAccess = $("#chkEnableLiveTvAccess", page).checked(); - user.Policy.EnableSharedDeviceControl = $("#chkRemoteControlSharedDevices", page).checked(); - user.Policy.EnableMediaPlayback = $("#chkEnableMediaPlayback", page).checked(); - user.Policy.EnableAudioPlaybackTranscoding = $("#chkEnableAudioPlaybackTranscoding", page).checked(); - user.Policy.EnableVideoPlaybackTranscoding = $("#chkEnableVideoPlaybackTranscoding", page).checked(); - user.Policy.EnablePlaybackRemuxing = $("#chkEnableVideoPlaybackRemuxing", page).checked(); - user.Policy.ForceRemoteSourceTranscoding = $("#chkForceRemoteSourceTranscoding", page).checked(); - user.Policy.EnableContentDownloading = $("#chkEnableDownloading", page).checked(); - user.Policy.EnableSyncTranscoding = $("#chkEnableSyncTranscoding", page).checked(); - user.Policy.EnableMediaConversion = $("#chkEnableConversion", page).checked(); - user.Policy.EnablePublicSharing = $("#chkEnableSharing", page).checked(); - user.Policy.EnableRemoteAccess = $("#chkRemoteAccess", page).checked(); - user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($("#txtRemoteClientBitrateLimit", page).val() || "0")); - user.Policy.LoginAttemptsBeforeLockout = parseInt($("#txtLoginAttemptsBeforeLockout", page).val() || "0"); - user.Policy.AuthenticationProviderId = page.querySelector(".selectLoginProvider").value; - user.Policy.PasswordResetProviderId = page.querySelector(".selectPasswordResetProvider").value; - user.Policy.EnableContentDeletion = $("#chkEnableDeleteAllFolders", page).checked(); - user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : $(".chkFolder", page).get().filter(function (c) { - return c.checked; - }).map(function (c) { - return c.getAttribute("data-id"); - }); - ApiClient.updateUser(user).then(function () { - ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { - onSaveComplete(page, user); - }); - }); - } - - function onSubmit() { - var page = $(this).parents(".page")[0]; - loading.show(); - getUser().then(function (result) { - saveUser(result, page); - }); - return false; - } - - function getUser() { - var userId = getParameterByName("userId"); - return ApiClient.getUser(userId); - } - - function loadData(page) { - loading.show(); - getUser().then(function (user) { - loadUser(page, user); - }); - } - - var currentUser; - $(document).on("pageinit", "#editUserPage", function () { - $(".editUserProfileForm").off("submit", onSubmit).on("submit", onSubmit); - this.querySelector(".sharingHelp").innerHTML = Globalize.translate("OptionAllowLinkSharingHelp", 30); - var 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/usernew.js b/src/controllers/usernew.js deleted file mode 100644 index ec80679f8c1..00000000000 --- a/src/controllers/usernew.js +++ /dev/null @@ -1,126 +0,0 @@ -define(["jQuery", "loading", "fnchecked", "emby-checkbox"], function ($, loading) { - "use strict"; - - function loadMediaFolders(page, mediaFolders) { - var html = ""; - html += '

' + Globalize.translate("HeaderLibraries") + "

"; - html += '
'; - - for (var i = 0; i < mediaFolders.length; i++) { - var folder = mediaFolders[i]; - html += '"; - } - - html += "
"; - $(".folderAccess", page).html(html).trigger("create"); - $("#chkEnableAllFolders", page).checked(true).trigger("change"); - } - - function loadChannels(page, channels) { - var html = ""; - html += '

' + Globalize.translate("HeaderChannels") + "

"; - html += '
'; - - for (var i = 0; i < channels.length; i++) { - var folder = channels[i]; - html += '"; - } - - html += "
"; - $(".channelAccess", page).show().html(html).trigger("create"); - - if (channels.length) { - $(".channelAccessContainer", page).show(); - } else { - $(".channelAccessContainer", page).hide(); - } - - $("#chkEnableAllChannels", page).checked(true).trigger("change"); - } - - function loadUser(page) { - $("#txtUsername", page).val(""); - $("#txtPassword", page).val(""); - loading.show(); - var promiseFolders = ApiClient.getJSON(ApiClient.getUrl("Library/MediaFolders", { - IsHidden: false - })); - var 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) { - var user = {}; - user.Name = $("#txtUsername", page).val(); - user.Password = $("#txtPassword", page).val(); - ApiClient.createUser(user).then(function (user) { - user.Policy.EnableAllFolders = $("#chkEnableAllFolders", page).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).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 (response) { - require(["toast"], function (toast) { - toast(Globalize.translate("DefaultErrorMessage")); - }); - - loading.hide(); - }); - } - - function onSubmit() { - var page = $(this).parents(".page")[0]; - loading.show(); - saveUser(page); - return false; - } - - function loadData(page) { - loadUser(page); - } - - $(document).on("pageinit", "#newUserPage", function () { - var 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/wizard/finish.js b/src/controllers/wizard/finish.js index 8242a16cb49..c27d9a5f4b8 100644 --- a/src/controllers/wizard/finish.js +++ b/src/controllers/wizard/finish.js @@ -1,18 +1,18 @@ -define(["loading"], function (loading) { - "use strict"; +define(['loading'], function (loading) { + 'use strict'; function onFinish() { loading.show(); ApiClient.ajax({ - url: ApiClient.getUrl("Startup/Complete"), - type: "POST" + url: ApiClient.getUrl('Startup/Complete'), + type: 'POST' }).then(function () { loading.hide(); - window.location.href = "index.html"; + window.location.href = 'index.html'; }); } return function (view, params) { - view.querySelector(".btnWizardNext").addEventListener("click", onFinish); + view.querySelector('.btnWizardNext').addEventListener('click', onFinish); }; }); diff --git a/src/controllers/wizard/remoteaccess.js b/src/controllers/wizard/remoteaccess.js index 554a417e57a..400cd357f46 100644 --- a/src/controllers/wizard/remoteaccess.js +++ b/src/controllers/wizard/remoteaccess.js @@ -1,16 +1,16 @@ -define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loading) { - "use strict"; +define(['loading', 'emby-checkbox', 'emby-button', 'emby-select'], function (loading) { + 'use strict'; function save(page) { loading.show(); var apiClient = ApiClient; var config = {}; - config.EnableRemoteAccess = page.querySelector("#chkRemoteAccess").checked; - config.EnableAutomaticPortMapping = page.querySelector("#chkEnableUpnp").checked; + config.EnableRemoteAccess = page.querySelector('#chkRemoteAccess').checked; + config.EnableAutomaticPortMapping = page.querySelector('#chkEnableUpnp').checked; apiClient.ajax({ - type: "POST", + type: 'POST', data: config, - url: apiClient.getUrl("Startup/RemoteAccess") + url: apiClient.getUrl('Startup/RemoteAccess') }).then(function () { loading.hide(); navigateToNextPage(); @@ -18,7 +18,7 @@ define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loa } function navigateToNextPage() { - Dashboard.navigate("wizardfinish.html"); + Dashboard.navigate('wizardfinish.html'); } function onSubmit(e) { @@ -28,12 +28,12 @@ define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loa } return function (view, params) { - view.querySelector(".wizardSettingsForm").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", function () { - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); + view.querySelector('.wizardSettingsForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function () { + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); }); - view.addEventListener("viewhide", function () { - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); + view.addEventListener('viewhide', function () { + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); }); }; }); diff --git a/src/controllers/wizard/settings.js b/src/controllers/wizard/settings.js index 487f068a405..2062e795a33 100644 --- a/src/controllers/wizard/settings.js +++ b/src/controllers/wizard/settings.js @@ -1,16 +1,16 @@ -define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loading) { - "use strict"; +define(['loading', 'emby-checkbox', 'emby-button', 'emby-select'], function (loading) { + 'use strict'; function save(page) { loading.show(); var apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl("Startup/Configuration")).then(function (config) { - config.PreferredMetadataLanguage = page.querySelector("#selectLanguage").value; - config.MetadataCountryCode = page.querySelector("#selectCountry").value; + apiClient.getJSON(apiClient.getUrl('Startup/Configuration')).then(function (config) { + config.PreferredMetadataLanguage = page.querySelector('#selectLanguage').value; + config.MetadataCountryCode = page.querySelector('#selectCountry').value; apiClient.ajax({ - type: "POST", + type: 'POST', data: config, - url: apiClient.getUrl("Startup/Configuration") + url: apiClient.getUrl('Startup/Configuration') }).then(function () { loading.hide(); navigateToNextPage(); @@ -19,41 +19,41 @@ define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loa } function populateLanguages(select, languages) { - var html = ""; + var html = ''; html += ""; for (var i = 0, length = languages.length; i < length; i++) { var culture = languages[i]; - html += ""; + html += "'; } select.innerHTML = html; } function populateCountries(select, allCountries) { - var html = ""; + var html = ''; html += ""; for (var i = 0, length = allCountries.length; i < length; i++) { var culture = allCountries[i]; - html += ""; + html += "'; } select.innerHTML = html; } function reloadData(page, config, cultures, countries) { - populateLanguages(page.querySelector("#selectLanguage"), cultures); - populateCountries(page.querySelector("#selectCountry"), countries); - page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage; - page.querySelector("#selectCountry").value = config.MetadataCountryCode; + populateLanguages(page.querySelector('#selectLanguage'), cultures); + populateCountries(page.querySelector('#selectCountry'), countries); + page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage; + page.querySelector('#selectCountry').value = config.MetadataCountryCode; loading.hide(); } function reload(page) { loading.show(); var apiClient = ApiClient; - var promise1 = apiClient.getJSON(apiClient.getUrl("Startup/Configuration")); + var promise1 = apiClient.getJSON(apiClient.getUrl('Startup/Configuration')); var promise2 = apiClient.getCultures(); var promise3 = apiClient.getCountries(); Promise.all([promise1, promise2, promise3]).then(function (responses) { @@ -62,7 +62,7 @@ define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loa } function navigateToNextPage() { - Dashboard.navigate("wizardremoteaccess.html"); + Dashboard.navigate('wizardremoteaccess.html'); } function onSubmit(e) { @@ -72,13 +72,13 @@ define(["loading", "emby-checkbox", "emby-button", "emby-select"], function (loa } return function (view, params) { - view.querySelector(".wizardSettingsForm").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", function () { - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); + view.querySelector('.wizardSettingsForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function () { + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); reload(this); }); - view.addEventListener("viewhide", function () { - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); + view.addEventListener('viewhide', function () { + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); }); }; }); diff --git a/src/controllers/wizard/start.js b/src/controllers/wizard/start.js index 1c2917b9ec4..b7fb920d45b 100644 --- a/src/controllers/wizard/start.js +++ b/src/controllers/wizard/start.js @@ -1,9 +1,9 @@ -define(["jQuery", "loading", "emby-button", "emby-select"], function ($, loading) { - "use strict"; +define(['jQuery', 'loading', 'emby-button', 'emby-select'], function ($, loading) { + 'use strict'; function loadPage(page, config, languageOptions) { - $("#selectLocalizationLanguage", page).html(languageOptions.map(function (l) { - return '"; + $('#selectLocalizationLanguage', page).html(languageOptions.map(function (l) { + return ''; })).val(config.UICulture); loading.hide(); } @@ -11,38 +11,38 @@ define(["jQuery", "loading", "emby-button", "emby-select"], function ($, loading function save(page) { loading.show(); var apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl("Startup/Configuration")).then(function (config) { - config.UICulture = $("#selectLocalizationLanguage", page).val(); + apiClient.getJSON(apiClient.getUrl('Startup/Configuration')).then(function (config) { + config.UICulture = $('#selectLocalizationLanguage', page).val(); apiClient.ajax({ - type: "POST", + type: 'POST', data: config, - url: apiClient.getUrl("Startup/Configuration") + url: apiClient.getUrl('Startup/Configuration') }).then(function () { - Dashboard.navigate("wizarduser.html"); + Dashboard.navigate('wizarduser.html'); }); }); } function onSubmit() { - save($(this).parents(".page")); + save($(this).parents('.page')); return false; } return function (view, params) { - $(".wizardStartForm", view).on("submit", onSubmit); - view.addEventListener("viewshow", function () { - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); + $('.wizardStartForm', view).on('submit', onSubmit); + view.addEventListener('viewshow', function () { + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); loading.show(); var page = this; var apiClient = ApiClient; - var promise1 = apiClient.getJSON(apiClient.getUrl("Startup/Configuration")); - var promise2 = apiClient.getJSON(apiClient.getUrl("Localization/Options")); + var promise1 = apiClient.getJSON(apiClient.getUrl('Startup/Configuration')); + var promise2 = apiClient.getJSON(apiClient.getUrl('Localization/Options')); Promise.all([promise1, promise2]).then(function (responses) { loadPage(page, responses[0], responses[1]); }); }); - view.addEventListener("viewhide", function () { - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); + view.addEventListener('viewhide', function () { + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); }); }; }); diff --git a/src/controllers/wizard/user.js b/src/controllers/wizard/user.js index 270953b24e1..e62edef9fe8 100644 --- a/src/controllers/wizard/user.js +++ b/src/controllers/wizard/user.js @@ -1,16 +1,16 @@ -define(["loading", "globalize", "dashboardcss", "emby-input", "emby-button", "emby-button"], function (loading, globalize) { - "use strict"; +define(['loading', 'globalize', 'dashboardcss', 'emby-input', 'emby-button', 'emby-button'], function (loading, globalize) { + 'use strict'; function getApiClient() { return ApiClient; } function nextWizardPage() { - Dashboard.navigate("wizardlibrary.html"); + Dashboard.navigate('wizardlibrary.html'); } function onUpdateUserComplete(result) { - console.debug("user update complete: " + result); + console.debug('user update complete: ' + result); loading.hide(); nextWizardPage(); } @@ -19,21 +19,21 @@ define(["loading", "globalize", "dashboardcss", "emby-input", "emby-button", "em loading.show(); var apiClient = getApiClient(); apiClient.ajax({ - type: "POST", + type: 'POST', data: { - Name: form.querySelector("#txtUsername").value, - Password: form.querySelector("#txtManualPassword").value + Name: form.querySelector('#txtUsername').value, + Password: form.querySelector('#txtManualPassword').value }, - url: apiClient.getUrl("Startup/User") + url: apiClient.getUrl('Startup/User') }).then(onUpdateUserComplete); } function onSubmit(e) { var form = this; - if (form.querySelector("#txtManualPassword").value != form.querySelector("#txtPasswordConfirm").value) { - require(["toast"], function (toast) { - toast(Globalize.translate("PasswordMatchError")); + if (form.querySelector('#txtManualPassword').value != form.querySelector('#txtPasswordConfirm').value) { + require(['toast'], function (toast) { + toast(globalize.translate('PasswordMatchError')); }); } else { submit(form); @@ -47,21 +47,21 @@ define(["loading", "globalize", "dashboardcss", "emby-input", "emby-button", "em loading.show(); var page = this; var apiClient = getApiClient(); - apiClient.getJSON(apiClient.getUrl("Startup/User")).then(function (user) { - page.querySelector("#txtUsername").value = user.Name || ""; - page.querySelector("#txtManualPassword").value = user.Password || ""; + apiClient.getJSON(apiClient.getUrl('Startup/User')).then(function (user) { + page.querySelector('#txtUsername').value = user.Name || ''; + page.querySelector('#txtManualPassword').value = user.Password || ''; loading.hide(); }); } return function (view, params) { - view.querySelector(".wizardUserForm").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", function () { - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); + view.querySelector('.wizardUserForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function () { + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); }); - view.addEventListener("viewhide", function () { - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); + view.addEventListener('viewhide', function () { + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); }); - view.addEventListener("viewshow", onViewShow); + view.addEventListener('viewshow', onViewShow); }; }); diff --git a/src/dashboard.html b/src/dashboard.html index adb38a9ad36..b7af3c8c032 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -5,7 +5,7 @@

${TabServer}

- +
@@ -16,7 +16,7 @@

${TabServer}

- +
${LabelCachePathHelp}
@@ -49,7 +42,7 @@

${HeaderPaths}

- +
${LabelMetadataPathHelp}
@@ -62,7 +55,7 @@

${HeaderBranding}

${LabelLoginDisclaimerHelp}
-
+
${LabelCustomCssHelp}
diff --git a/src/device.html b/src/device.html index 11fc9671b1e..093b3112088 100644 --- a/src/device.html +++ b/src/device.html @@ -5,7 +5,7 @@
diff --git a/src/devices.html b/src/devices.html index 4e6552f05e5..55f51d7e23d 100644 --- a/src/devices.html +++ b/src/devices.html @@ -4,7 +4,7 @@

${TabDevices}

- ${Help} + ${Help}
diff --git a/src/dlnaprofile.html b/src/dlnaprofile.html index 2420ab4c35d..e960d97a0f0 100644 --- a/src/dlnaprofile.html +++ b/src/dlnaprofile.html @@ -5,7 +5,7 @@

${HeaderProfileInformation}

- ${Help} + ${Help}
@@ -98,7 +98,7 @@

${HeaderIdentificationCriteriaHelp}

${HeaderHttpHeaders}

@@ -221,7 +221,7 @@

${HeaderHttpHeaders}

${HeaderXmlDocumentAttributes}

@@ -272,7 +272,7 @@

${HeaderXmlDocumentAttri

-
+

${HeaderDirectPlayProfile}

@@ -312,7 +312,7 @@

${HeaderDirectPlayProfile}

-
+

${HeaderTranscodingProfile}

@@ -394,7 +394,7 @@

${HeaderTranscodingProfile}

-
+

${HeaderContainerProfile}

@@ -426,7 +426,7 @@

${HeaderContainerProfile}

-
+

${HeaderCodecProfile}

@@ -455,7 +455,7 @@

${HeaderCodecProfile}

-
+

${HeaderResponseProfile}

@@ -495,7 +495,7 @@

${HeaderResponseProfile}

-
+

${HeaderIdentificationHeader}

@@ -525,7 +525,7 @@

${HeaderIdentificationHeader}

-
+

${HeaderXmlDocumentAttribute}

@@ -548,7 +548,7 @@

${HeaderXmlDocumentAttribute}

-
+

${HeaderSubtitleProfile}

diff --git a/src/dlnaprofiles.html b/src/dlnaprofiles.html index c1ae38609c5..b47b2fd6bf1 100644 --- a/src/dlnaprofiles.html +++ b/src/dlnaprofiles.html @@ -9,9 +9,9 @@

${HeaderCustomDlnaProfiles}

- add + - ${Help} + ${Help}

${CustomDlnaProfilesHelp}

diff --git a/src/dlnasettings.html b/src/dlnasettings.html index 8068acf5ab3..703dd66a9ff 100644 --- a/src/dlnasettings.html +++ b/src/dlnasettings.html @@ -8,7 +8,7 @@

${TabSettings}

- ${Help} + ${Help}
diff --git a/src/elements/emby-button/emby-button.css b/src/elements/emby-button/emby-button.css index a19ce571e21..09631256528 100644 --- a/src/elements/emby-button/emby-button.css +++ b/src/elements/emby-button/emby-button.css @@ -49,6 +49,7 @@ .button-link { background: transparent; + cursor: pointer; margin: 0; padding: 0; vertical-align: initial; @@ -58,12 +59,12 @@ text-decoration: underline; } -.emby-button > i { +.emby-button > .material-icons { /* For non-fab buttons that have icons */ font-size: 1.36em; } -.button-link > i { +.button-link > .material-icons { font-size: 1em; } @@ -136,7 +137,7 @@ cursor: default; } -.paper-icon-button-light > i { +.paper-icon-button-light > .material-icons { font-size: 1.66956521739130434em; /* Make sure its on top of the ripple */ diff --git a/src/elements/emby-button/emby-button.js b/src/elements/emby-button/emby-button.js index be52b1d512e..0178dde5902 100644 --- a/src/elements/emby-button/emby-button.js +++ b/src/elements/emby-button/emby-button.js @@ -1,4 +1,4 @@ -define(['browser', 'dom', 'layoutManager', 'shell', 'appRouter', 'apphost', 'css!./emby-button', 'registerElement'], function (browser, dom, layoutManager, shell, appRouter, appHost) { +define(['browser', 'dom', 'layoutManager', 'shell', 'appRouter', 'apphost', 'css!./emby-button', 'webcomponents'], function (browser, dom, layoutManager, shell, appRouter, appHost) { 'use strict'; var EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype); diff --git a/src/elements/emby-button/paper-icon-button-light.js b/src/elements/emby-button/paper-icon-button-light.js index 7eda76baec9..7b56f4afbca 100644 --- a/src/elements/emby-button/paper-icon-button-light.js +++ b/src/elements/emby-button/paper-icon-button-light.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'css!./emby-button', 'registerElement'], function (layoutManager) { +define(['layoutManager', 'css!./emby-button', 'webcomponents'], function (layoutManager) { 'use strict'; var EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype); diff --git a/src/elements/emby-checkbox/emby-checkbox.js b/src/elements/emby-checkbox/emby-checkbox.js index b5e587d5a6f..a8a076cb83d 100644 --- a/src/elements/emby-checkbox/emby-checkbox.js +++ b/src/elements/emby-checkbox/emby-checkbox.js @@ -1,4 +1,4 @@ -define(['browser', 'dom', 'css!./emby-checkbox', 'registerElement'], function (browser, dom) { +define(['browser', 'dom', 'css!./emby-checkbox', 'webcomponents'], function (browser, dom) { 'use strict'; var EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); @@ -57,8 +57,8 @@ define(['browser', 'dom', 'css!./emby-checkbox', 'registerElement'], function (b var checkedIcon = this.getAttribute('data-checkedicon') || 'check'; var uncheckedIcon = this.getAttribute('data-uncheckedicon') || ''; - var checkHtml = '' + checkedIcon + ''; - var uncheckedHtml = '' + uncheckedIcon + ''; + var checkHtml = ''; + var uncheckedHtml = ''; labelElement.insertAdjacentHTML('beforeend', '' + checkHtml + uncheckedHtml + ''); labelTextElement.classList.add('checkboxLabel'); diff --git a/src/elements/emby-collapse/emby-collapse.js b/src/elements/emby-collapse/emby-collapse.js index fdd77adf0b5..06351711680 100644 --- a/src/elements/emby-collapse/emby-collapse.js +++ b/src/elements/emby-collapse/emby-collapse.js @@ -1,4 +1,4 @@ -define(['browser', 'css!./emby-collapse', 'registerElement', 'emby-button'], function (browser) { +define(['browser', 'css!./emby-collapse', 'webcomponents', 'emby-button'], function (browser) { 'use strict'; var EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); @@ -24,7 +24,7 @@ define(['browser', 'css!./emby-collapse', 'registerElement', 'emby-button'], fun elem.style.height = 'auto'; }, 300); - var icon = button.querySelector('i'); + var icon = button.querySelector('.material-icons'); //icon.innerHTML = 'expand_less'; icon.classList.add('emby-collapse-expandIconExpanded'); } @@ -46,7 +46,7 @@ define(['browser', 'css!./emby-collapse', 'registerElement', 'emby-button'], fun } }, 300); - var icon = button.querySelector('i'); + var icon = button.querySelector('.material-icons'); //icon.innerHTML = 'expand_more'; icon.classList.remove('emby-collapse-expandIconExpanded'); } @@ -80,7 +80,7 @@ define(['browser', 'css!./emby-collapse', 'registerElement', 'emby-button'], fun var title = this.getAttribute('title'); - var html = ''; + var html = ''; this.insertAdjacentHTML('afterbegin', html); diff --git a/src/elements/emby-input/emby-input.css b/src/elements/emby-input/emby-input.css index 18ad37a87a6..195e6802793 100644 --- a/src/elements/emby-input/emby-input.css +++ b/src/elements/emby-input/emby-input.css @@ -29,6 +29,27 @@ margin-bottom: 1.8em; } +.inlineForm { + display: flex; +} + +.inlineForm .inputContainer, +.inlineForm .selectContainer { + flex-basis: 0; + flex-grow: 1; + margin: 0 0.5em 1.8em; +} + +.inlineForm .inputContainer:first-child, +.inlineForm .selectContainer:first-child { + margin-left: 0; +} + +.inlineForm .inputContainer:last-child, +.inlineForm .selectContainer:last-child { + margin-right: 0; +} + .inputLabel { display: inline-block; margin-bottom: 0.25em; diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index 03ba2b93aad..1618a16088c 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'registerElement'], function (layoutManager, browser, dom) { +define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'webcomponents'], function (layoutManager, browser, dom) { 'use strict'; var EmbyInputPrototype = Object.create(HTMLInputElement.prototype); @@ -109,9 +109,7 @@ define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'registerElement' } EmbyInputPrototype.attachedCallback = function () { - this.labelElement.htmlFor = this.id; - onChange.call(this); }; diff --git a/src/components/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js similarity index 96% rename from src/components/emby-itemrefreshindicator/emby-itemrefreshindicator.js rename to src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js index 9864dbbb67d..780339e3c19 100644 --- a/src/components/emby-itemrefreshindicator/emby-itemrefreshindicator.js +++ b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js @@ -1,4 +1,4 @@ -define(['emby-progressring', 'dom', 'serverNotifications', 'events', 'registerElement'], function (EmbyProgressRing, dom, serverNotifications, events) { +define(['emby-progressring', 'dom', 'serverNotifications', 'events', 'webcomponents'], function (EmbyProgressRing, dom, serverNotifications, events) { 'use strict'; function addNotificationEvent(instance, name, handler) { diff --git a/src/components/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js similarity index 98% rename from src/components/emby-itemscontainer/emby-itemscontainer.js rename to src/elements/emby-itemscontainer/emby-itemscontainer.js index c1aba9b6a0f..231e681047b 100644 --- a/src/components/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -1,4 +1,4 @@ -define(['itemShortcuts', 'inputManager', 'connectionManager', 'playbackManager', 'imageLoader', 'layoutManager', 'browser', 'dom', 'loading', 'focusManager', 'serverNotifications', 'events', 'registerElement'], function (itemShortcuts, inputManager, connectionManager, playbackManager, imageLoader, layoutManager, browser, dom, loading, focusManager, serverNotifications, events) { +define(['itemShortcuts', 'inputManager', 'connectionManager', 'playbackManager', 'imageLoader', 'layoutManager', 'browser', 'dom', 'loading', 'focusManager', 'serverNotifications', 'events', 'webcomponents'], function (itemShortcuts, inputManager, connectionManager, playbackManager, imageLoader, layoutManager, browser, dom, loading, focusManager, serverNotifications, events) { 'use strict'; var ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); @@ -124,7 +124,7 @@ define(['itemShortcuts', 'inputManager', 'connectionManager', 'playbackManager', var self = this; require(['sortable'], function (Sortable) { self.sortable = new Sortable(self, { - draggable: ".listItem", + draggable: '.listItem', handle: '.listViewDragHandle', // dragging ended diff --git a/src/components/userdatabuttons/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js similarity index 98% rename from src/components/userdatabuttons/emby-playstatebutton.js rename to src/elements/emby-playstatebutton/emby-playstatebutton.js index 4c3e5b6b62f..57f7eb76eb9 100644 --- a/src/components/userdatabuttons/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -41,7 +41,7 @@ define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby function setState(button, played, updateAttribute) { var icon = button.iconElement; if (!icon) { - button.iconElement = button.querySelector('i'); + button.iconElement = button.querySelector('.material-icons'); icon = button.iconElement; } diff --git a/src/elements/emby-programcell/emby-programcell.js b/src/elements/emby-programcell/emby-programcell.js new file mode 100644 index 00000000000..a959033186b --- /dev/null +++ b/src/elements/emby-programcell/emby-programcell.js @@ -0,0 +1,16 @@ +define([], function() { + 'use strict'; + + var ProgramCellPrototype = Object.create(HTMLButtonElement.prototype); + + ProgramCellPrototype.detachedCallback = function () { + this.posLeft = null; + this.posWidth = null; + this.guideProgramName = null; + }; + + document.registerElement('emby-programcell', { + prototype: ProgramCellPrototype, + extends: 'button' + }); +}); diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js new file mode 100644 index 00000000000..a799f82bdd0 --- /dev/null +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -0,0 +1,42 @@ +define([], function() { + 'use strict'; + + var ProgressBarPrototype = Object.create(HTMLDivElement.prototype); + + function onAutoTimeProgress() { + var start = parseInt(this.getAttribute('data-starttime')); + var end = parseInt(this.getAttribute('data-endtime')); + + var now = new Date().getTime(); + var total = end - start; + var pct = 100 * ((now - start) / total); + + pct = Math.min(100, pct); + pct = Math.max(0, pct); + + var itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); + itemProgressBarForeground.style.width = pct + '%'; + } + + ProgressBarPrototype.attachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); + } + + 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; + } + }; + + document.registerElement('emby-progressbar', { + prototype: ProgressBarPrototype, + extends: 'div' + }); +}); diff --git a/src/elements/emby-progressring/emby-progressring.js b/src/elements/emby-progressring/emby-progressring.js index edc635947c9..962ae746633 100644 --- a/src/elements/emby-progressring/emby-progressring.js +++ b/src/elements/emby-progressring/emby-progressring.js @@ -1,4 +1,4 @@ -define(['require', 'css!./emby-progressring', 'registerElement'], function (require) { +define(['require', 'css!./emby-progressring', 'webcomponents'], function (require) { 'use strict'; var EmbyProgressRing = Object.create(HTMLDivElement.prototype); diff --git a/src/elements/emby-radio/emby-radio.js b/src/elements/emby-radio/emby-radio.js index 46a3e3826c0..b83461d7b8c 100644 --- a/src/elements/emby-radio/emby-radio.js +++ b/src/elements/emby-radio/emby-radio.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'css!./emby-radio', 'registerElement'], function (layoutManager) { +define(['layoutManager', 'css!./emby-radio', 'webcomponents'], function (layoutManager) { 'use strict'; var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); diff --git a/src/components/userdatabuttons/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js similarity index 95% rename from src/components/userdatabuttons/emby-ratingbutton.js rename to src/elements/emby-ratingbutton/emby-ratingbutton.js index 84ae780de50..142920ca15f 100644 --- a/src/components/userdatabuttons/emby-ratingbutton.js +++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js @@ -57,12 +57,12 @@ define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby function setState(button, likes, isFavorite, updateAttribute) { - var icon = button.querySelector('i'); + var icon = button.querySelector('.material-icons'); if (isFavorite) { if (icon) { - icon.innerHTML = 'favorite'; + icon.classList.add('favorite'); icon.classList.add('ratingbutton-icon-withrating'); } @@ -71,7 +71,7 @@ define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby } else if (likes) { if (icon) { - icon.innerHTML = 'favorite'; + icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); //icon.innerHTML = 'thumb_up'; } @@ -80,7 +80,7 @@ define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby } else if (likes === false) { if (icon) { - icon.innerHTML = 'favorite'; + icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); //icon.innerHTML = 'thumb_down'; } @@ -89,7 +89,7 @@ define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby } else { if (icon) { - icon.innerHTML = 'favorite'; + icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); //icon.innerHTML = 'thumbs_up_down'; } diff --git a/src/components/emby-scrollbuttons/emby-scrollbuttons.css b/src/elements/emby-scrollbuttons/emby-scrollbuttons.css similarity index 80% rename from src/components/emby-scrollbuttons/emby-scrollbuttons.css rename to src/elements/emby-scrollbuttons/emby-scrollbuttons.css index 32e60292add..5af739bac1c 100644 --- a/src/components/emby-scrollbuttons/emby-scrollbuttons.css +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.css @@ -6,13 +6,13 @@ justify-content: center; min-width: 104px; min-height: 24px; - padding-top: 1.25em; + padding-top: 0.85em; z-index: 1; color: #fff; display: flex; } -.emby-scrollbuttons-button > i { +.emby-scrollbuttons-button > .material-icons { min-width: 24px; min-height: 24px; display: block; diff --git a/src/components/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js similarity index 94% rename from src/components/emby-scrollbuttons/emby-scrollbuttons.js rename to src/elements/emby-scrollbuttons/emby-scrollbuttons.js index af8261a632d..ae86e2b24fe 100644 --- a/src/components/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'dom', 'css!./emby-scrollbuttons', 'registerElement', 'paper-icon-button-light'], function (layoutManager, dom) { +define(['layoutManager', 'dom', 'css!./emby-scrollbuttons', 'webcomponents', 'paper-icon-button-light'], function (layoutManager, dom) { 'use strict'; var EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); @@ -7,10 +7,10 @@ define(['layoutManager', 'dom', 'css!./emby-scrollbuttons', 'registerElement', ' function getScrollButtonHtml(direction) { var html = ''; - var icon = direction === 'left' ? '' : ''; + var icon = direction === 'left' ? 'chevron_left' : 'chevron_right'; html += ''; return html; diff --git a/src/components/emby-scroller/emby-scroller.css b/src/elements/emby-scroller/emby-scroller.css similarity index 100% rename from src/components/emby-scroller/emby-scroller.css rename to src/elements/emby-scroller/emby-scroller.css diff --git a/src/components/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js similarity index 89% rename from src/components/emby-scroller/emby-scroller.js rename to src/elements/emby-scroller/emby-scroller.js index cb5bae818f1..35c44a36f06 100644 --- a/src/components/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -1,4 +1,4 @@ -define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'browser', 'registerElement', 'css!./emby-scroller'], function (scroller, dom, layoutManager, inputManager, focusManager, browser) { +define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'browser', 'webcomponents', 'css!./emby-scroller'], function (scroller, dom, layoutManager, inputManager, focusManager, browser) { 'use strict'; var ScrollerPrototype = Object.create(HTMLDivElement.prototype); @@ -96,17 +96,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro } } - function initHeadroom(elem) { - require(['headroom'], function (Headroom) { - var headroom = new Headroom([], { - scroller: elem - }); - - headroom.add(document.querySelector('.skinHeader')); - elem.headroom = headroom; - }); - } - ScrollerPrototype.attachedCallback = function () { if (this.getAttribute('data-navcommands')) { inputManager.on(this, onInputCommand); @@ -120,8 +109,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro slider.style['white-space'] = 'nowrap'; } - var bindHeader = this.getAttribute('data-bindheader') === 'true'; - var scrollFrame = this; var enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; @@ -137,7 +124,7 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro dragHandle: 1, autoImmediate: true, skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', - dispatchScrollEvent: enableScrollButtons || bindHeader || this.getAttribute('data-scrollevent') === 'true', + dispatchScrollEvent: enableScrollButtons || this.getAttribute('data-scrollevent') === 'true', hideScrollbar: enableScrollButtons || this.getAttribute('data-hidescrollbar') === 'true', allowNativeSmoothScroll: this.getAttribute('data-allownativesmoothscroll') === 'true' && !enableScrollButtons, allowNativeScroll: !enableScrollButtons, @@ -155,10 +142,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro initCenterFocus(this, this.scroller); } - if (bindHeader && layoutManager.mobile) { - initHeadroom(this); - } - if (enableScrollButtons) { loadScrollButtons(this); } diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index c772c0f9604..6088445208f 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registerElement'], function (layoutManager, browser, actionsheet) { +define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'webcomponents'], function (layoutManager, browser, actionsheet) { 'use strict'; var EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); @@ -27,8 +27,8 @@ define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registe } function triggerChange(select) { - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", false, true); + var evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); select.dispatchEvent(evt); } @@ -144,7 +144,7 @@ define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registe this.parentNode.insertBefore(label, this); if (this.classList.contains('emby-select-withcolor')) { - this.parentNode.insertAdjacentHTML('beforeend', '
0
'); + this.parentNode.insertAdjacentHTML('beforeend', '
0
'); } }; diff --git a/src/elements/emby-slider/emby-slider.css b/src/elements/emby-slider/emby-slider.css index f59c2a3cd07..7661895f15c 100644 --- a/src/elements/emby-slider/emby-slider.css +++ b/src/elements/emby-slider/emby-slider.css @@ -87,10 +87,6 @@ transform: scale(1.3); } -.slider-no-webkit-thumb::-webkit-slider-thumb { - opacity: 0 !important; -} - .mdl-slider::-moz-range-thumb { -moz-appearance: none; width: 1.08em; diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 12177fb60bd..34da55af97d 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -1,4 +1,4 @@ -define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-slider', 'registerElement', 'emby-input'], function (browser, dom, layoutManager, keyboardnavigation) { +define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-slider', 'webcomponents', 'emby-input'], function (browser, dom, layoutManager, keyboardnavigation) { 'use strict'; var EmbySliderPrototype = Object.create(HTMLInputElement.prototype); @@ -148,10 +148,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli this.classList.add('mdl-slider'); this.classList.add('mdl-js-slider'); - if (browser.noFlex) { - this.classList.add('slider-no-webkit-thumb'); - } - if (browser.edge || browser.msie) { + if (browser.edge) { this.classList.add('slider-browser-edge'); } if (!layoutManager.mobile) { @@ -226,6 +223,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli passive: true }); + /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { if (!this.dragging) { @@ -243,6 +241,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli passive: true }); + /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { sliderBubble.classList.add('hide'); hasHideClass = true; @@ -402,7 +401,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli this.addEventListener('keydown', onKeyDown); this.keyboardDraggingEnabled = true; } - } + }; /** * Set steps for keyboard input. @@ -413,7 +412,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { this.keyboardStepDown = stepDown || stepUp || 1; this.keyboardStepUp = stepUp || stepDown || 1; - } + }; function setRange(elem, startPercent, endPercent) { diff --git a/src/components/emby-tabs/emby-tabs.css b/src/elements/emby-tabs/emby-tabs.css similarity index 91% rename from src/components/emby-tabs/emby-tabs.css rename to src/elements/emby-tabs/emby-tabs.css index 355f904cb8e..fe29f461439 100644 --- a/src/components/emby-tabs/emby-tabs.css +++ b/src/elements/emby-tabs/emby-tabs.css @@ -31,11 +31,6 @@ .emby-tabs-slider { position: relative; - overflow: hidden; -} - -.layout-mobile .emby-tabs-slider { - overflow: auto; } .tabContent:not(.is-active) { diff --git a/src/components/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js similarity index 95% rename from src/components/emby-tabs/emby-tabs.js rename to src/elements/emby-tabs/emby-tabs.js index fa5979fa4c0..dfe53de029d 100644 --- a/src/components/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -1,4 +1,4 @@ -define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'registerElement', 'css!./emby-tabs', 'scrollStyles'], function (dom, scroller, browser, layoutManager, focusManager) { +define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'webcomponents', 'css!./emby-tabs', 'scrollStyles'], function (dom, scroller, browser, layoutManager, focusManager) { 'use strict'; var EmbyTabs = Object.create(HTMLDivElement.prototype); @@ -46,7 +46,7 @@ define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'register function triggerBeforeTabChange(tabs, index, previousIndex) { - tabs.dispatchEvent(new CustomEvent("beforetabchange", { + tabs.dispatchEvent(new CustomEvent('beforetabchange', { detail: { selectedTabIndex: index, previousIndex: previousIndex @@ -94,7 +94,7 @@ define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'register tabs.selectedTabIndex = index; - tabs.dispatchEvent(new CustomEvent("tabchange", { + tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { selectedTabIndex: index, previousIndex: previousIndex @@ -198,7 +198,7 @@ define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'register if (!this.readyFired) { this.readyFired = true; - this.dispatchEvent(new CustomEvent("ready", {})); + this.dispatchEvent(new CustomEvent('ready', {})); } }; @@ -238,7 +238,7 @@ define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'register triggerBeforeTabChange(tabs, selected, current); - tabs.dispatchEvent(new CustomEvent("tabchange", { + tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { selectedTabIndex: selected } @@ -315,7 +315,7 @@ define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'register var tabs = this; - tabs.dispatchEvent(new CustomEvent("tabchange", { + tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { selectedTabIndex: tabs.selectedIndex() } diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index e0ce77aa515..e02f2f49fba 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -1,4 +1,4 @@ -define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'emby-input'], function (layoutManager, browser) { +define(['layoutManager', 'browser', 'css!./emby-textarea', 'webcomponents', 'emby-input'], function (layoutManager, browser) { 'use strict'; function autoGrow(textarea, maxLines) { @@ -55,6 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } + $('.customCssContainer').css('height', newHeight + 'px'); textarea.style.height = newHeight + 'px'; } diff --git a/src/elements/emby-toggle/emby-toggle.js b/src/elements/emby-toggle/emby-toggle.js index bd7eba30784..2869f78d8f0 100644 --- a/src/elements/emby-toggle/emby-toggle.js +++ b/src/elements/emby-toggle/emby-toggle.js @@ -1,4 +1,4 @@ -define(['css!./emby-toggle', 'registerElement'], function () { +define(['css!./emby-toggle', 'webcomponents'], function () { 'use strict'; var EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); diff --git a/src/encodingsettings.html b/src/encodingsettings.html index bcdd86544ef..858375b1453 100644 --- a/src/encodingsettings.html +++ b/src/encodingsettings.html @@ -5,7 +5,7 @@

${TabTranscoding}

- ${Help} + ${Help}
@@ -19,9 +19,10 @@

${TabTranscoding}

+
@@ -35,36 +36,49 @@

${TabTranscoding}

${LabelEnableHardwareDecodingFor}

+
+ +
+
+ +
+
${LabelffmpegPathHelp}
@@ -107,7 +121,7 @@

${LabelEnableHardwareDecodingFor}

- +
${LabelTranscodingTempPathHelp}
@@ -136,6 +150,14 @@

${LabelEnableHardwareDecodingFor}

${H264CrfHelp}
+
+ +
${DeinterlaceMethodHelp}
+
+
-
+
+ +
diff --git a/src/legacy/dashboard.js b/src/legacy/dashboard.js deleted file mode 100644 index 0268806f211..00000000000 --- a/src/legacy/dashboard.js +++ /dev/null @@ -1,20 +0,0 @@ -Dashboard.confirm = function(message, title, callback) { - "use strict"; - require(["confirm"], function(confirm) { - confirm(message, title).then(function() { - callback(!0) - }, function() { - callback(!1) - }) - }) -}, Dashboard.showLoadingMsg = function() { - "use strict"; - require(["loading"], function(loading) { - loading.show() - }) -}, Dashboard.hideLoadingMsg = function() { - "use strict"; - require(["loading"], function(loading) { - loading.hide() - }) -}; diff --git a/src/legacy/fnchecked.js b/src/legacy/fnchecked.js index 4f24990f468..c3eff813e0d 100644 --- a/src/legacy/fnchecked.js +++ b/src/legacy/fnchecked.js @@ -1,10 +1,12 @@ -define(["jQuery"], function($) { - "use strict"; +define(['jQuery'], function($) { + 'use strict'; $.fn.checked = function(value) { return !0 === value || !1 === value ? $(this).each(function() { - this.checked = value - }) : this.length && this[0].checked - }, $.fn.checkboxradio = function() { - return this - } + this.checked = value; + }) : this.length && this[0].checked; + }; + + $.fn.checkboxradio = function() { + return this; + }; }); diff --git a/src/components/polyfills/focusPreventScroll.js b/src/legacy/focusPreventScroll.js similarity index 81% rename from src/components/polyfills/focusPreventScroll.js rename to src/legacy/focusPreventScroll.js index 6df9e9928c5..93f53dca29b 100644 --- a/src/components/polyfills/focusPreventScroll.js +++ b/src/legacy/focusPreventScroll.js @@ -4,14 +4,14 @@ if (HTMLElement.prototype.nativeFocus === undefined) { (function () { var supportsPreventScrollOption = false; try { - var focusElem = document.createElement("div"); + var focusElem = document.createElement('div'); - focusElem.addEventListener("focus", function(event) { + focusElem.addEventListener('focus', function(event) { event.preventDefault(); event.stopPropagation(); }, true); - var opts = Object.defineProperty({}, "preventScroll", { + var opts = Object.defineProperty({}, 'preventScroll', { // eslint-disable-next-line getter-return get: function () { supportsPreventScrollOption = true; @@ -20,7 +20,7 @@ if (HTMLElement.prototype.nativeFocus === undefined) { focusElem.focus(opts); } catch (e) { - console.error("error checking preventScroll support"); + console.error('error checking preventScroll support'); } if (!supportsPreventScrollOption) { diff --git a/src/legacy/selectmenu.js b/src/legacy/selectmenu.js index ba8c688511e..f4cc5466b1a 100644 --- a/src/legacy/selectmenu.js +++ b/src/legacy/selectmenu.js @@ -1,6 +1,6 @@ -define(["jQuery"], function($) { - "use strict"; +define(['jQuery'], function($) { + 'use strict'; $.fn.selectmenu = function() { - return this - } + return this; + }; }); diff --git a/src/libraries/apiclient/apiclient.js b/src/libraries/apiclient/apiclient.js deleted file mode 100644 index 58cfa300b9e..00000000000 --- a/src/libraries/apiclient/apiclient.js +++ /dev/null @@ -1,236 +0,0 @@ -//TODO: (vitorsemeano) modify this lines for webpack -define(["libraries/apiclient/apiclientcore", "localassetmanager"], function(ApiClient, localassetmanager) { - "use strict"; - - if ("cordova" !== window.appMode && "android" !== window.appMode) { - return ApiClient; - } - - function isLocalId(str) { - return startsWith(str, localPrefix) - } - - function isLocalViewId(str) { - return startsWith(str, localViewPrefix) - } - - function isTopLevelLocalViewId(str) { - return "localview" === str - } - - function stripLocalPrefix(str) { - var res = stripStart(str, localPrefix); - return res = stripStart(res, localViewPrefix) - } - - function startsWith(str, find) { - return !!(str && find && str.length > find.length && 0 === str.indexOf(find)) - } - - function stripStart(str, find) { - return startsWith(str, find) ? str.substr(find.length) : str - } - - function createEmptyList() { - return { - Items: [], - TotalRecordCount: 0 - } - } - - function convertGuidToLocal(guid) { - return guid ? isLocalId(guid) ? guid : "local:" + guid : null - } - - function adjustGuidProperties(downloadedItem) { - downloadedItem.Id = convertGuidToLocal(downloadedItem.Id), downloadedItem.SeriesId = convertGuidToLocal(downloadedItem.SeriesId), downloadedItem.SeasonId = convertGuidToLocal(downloadedItem.SeasonId), downloadedItem.AlbumId = convertGuidToLocal(downloadedItem.AlbumId), downloadedItem.ParentId = convertGuidToLocal(downloadedItem.ParentId), downloadedItem.ParentThumbItemId = convertGuidToLocal(downloadedItem.ParentThumbItemId), downloadedItem.ParentPrimaryImageItemId = convertGuidToLocal(downloadedItem.ParentPrimaryImageItemId), downloadedItem.PrimaryImageItemId = convertGuidToLocal(downloadedItem.PrimaryImageItemId), downloadedItem.ParentLogoItemId = convertGuidToLocal(downloadedItem.ParentLogoItemId), downloadedItem.ParentBackdropItemId = convertGuidToLocal(downloadedItem.ParentBackdropItemId), downloadedItem.ParentBackdropImageTags = null - } - - function getLocalView(instance, serverId, userId) { - return instance.getLocalFolders(serverId, userId).then(function(views) { - var localView = null; - return views.length > 0 && (localView = { - Name: instance.downloadsTitleText || "Downloads", - ServerId: serverId, - Id: "localview", - Type: "localview", - IsFolder: !0 - }), Promise.resolve(localView) - }) - } - - function ApiClientEx(serverAddress, clientName, applicationVersion, deviceName, deviceId) { - ApiClient.call(this, serverAddress, clientName, applicationVersion, deviceName, deviceId) - } - var localPrefix = "local:", - localViewPrefix = "localview:"; - return Object.assign(ApiClientEx.prototype, ApiClient.prototype), ApiClientEx.prototype.getPlaybackInfo = function(itemId, options, deviceProfile) { - var onFailure = function() { - return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile) - }; - if (isLocalId(itemId)) return localassetmanager.getLocalItem(this.serverId(), stripLocalPrefix(itemId)).then(function(item) { - return { - MediaSources: item.Item.MediaSources.map(function(m) { - return m.SupportsDirectPlay = !0, m.SupportsDirectStream = !1, m.SupportsTranscoding = !1, m.IsLocal = !0, m - }) - } - }, onFailure); - var instance = this; - return localassetmanager.getLocalItem(this.serverId(), itemId).then(function(item) { - if (item) { - var mediaSources = item.Item.MediaSources.map(function(m) { - return m.SupportsDirectPlay = !0, m.SupportsDirectStream = !1, m.SupportsTranscoding = !1, m.IsLocal = !0, m - }); - return localassetmanager.fileExists(item.LocalPath).then(function(exists) { - if (exists) { - var res = { - MediaSources: mediaSources - }; - return Promise.resolve(res) - } - return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile) - }, onFailure) - } - return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile) - }, onFailure) - }, ApiClientEx.prototype.getItems = function(userId, options) { - var i, serverInfo = this.serverInfo(); - if (serverInfo && "localview" === options.ParentId) return this.getLocalFolders(serverInfo.Id, userId).then(function(items) { - var result = { - Items: items, - TotalRecordCount: items.length - }; - return Promise.resolve(result) - }); - if (serverInfo && options && (isLocalId(options.ParentId) || isLocalId(options.SeriesId) || isLocalId(options.SeasonId) || isLocalViewId(options.ParentId) || isLocalId(options.AlbumIds))) return localassetmanager.getViewItems(serverInfo.Id, userId, options).then(function(items) { - items.forEach(function(item) { - adjustGuidProperties(item) - }); - var result = { - Items: items, - TotalRecordCount: items.length - }; - return Promise.resolve(result) - }); - if (options && options.ExcludeItemIds && options.ExcludeItemIds.length) { - var exItems = options.ExcludeItemIds.split(","); - for (i = 0; i < exItems.length; i++) - if (isLocalId(exItems[i])) return Promise.resolve(createEmptyList()) - } else if (options && options.Ids && options.Ids.length) { - var ids = options.Ids.split(","), - hasLocal = !1; - for (i = 0; i < ids.length; i++) isLocalId(ids[i]) && (hasLocal = !0); - if (hasLocal) return localassetmanager.getItemsFromIds(serverInfo.Id, ids).then(function(items) { - items.forEach(function(item) { - adjustGuidProperties(item) - }); - var result = { - Items: items, - TotalRecordCount: items.length - }; - return Promise.resolve(result) - }) - } - return ApiClient.prototype.getItems.call(this, userId, options) - }, ApiClientEx.prototype.getUserViews = function(options, userId) { - var instance = this; - options = options || {}; - var basePromise = ApiClient.prototype.getUserViews.call(instance, options, userId); - return options.enableLocalView ? basePromise.then(function(result) { - var serverInfo = instance.serverInfo(); - return serverInfo ? getLocalView(instance, serverInfo.Id, userId).then(function(localView) { - return localView && (result.Items.push(localView), result.TotalRecordCount++), Promise.resolve(result) - }) : Promise.resolve(result) - }) : basePromise - }, ApiClientEx.prototype.getItem = function(userId, itemId) { - if (!itemId) throw new Error("null itemId"); - itemId && (itemId = itemId.toString()); - var serverInfo; - return isTopLevelLocalViewId(itemId) && (serverInfo = this.serverInfo()) ? getLocalView(this, serverInfo.Id, userId) : isLocalViewId(itemId) && (serverInfo = this.serverInfo()) ? this.getLocalFolders(serverInfo.Id, userId).then(function(items) { - var views = items.filter(function(item) { - return item.Id === itemId - }); - return views.length > 0 ? Promise.resolve(views[0]) : Promise.reject() - }) : isLocalId(itemId) && (serverInfo = this.serverInfo()) ? localassetmanager.getLocalItem(serverInfo.Id, stripLocalPrefix(itemId)).then(function(item) { - return adjustGuidProperties(item.Item), Promise.resolve(item.Item) - }) : ApiClient.prototype.getItem.call(this, userId, itemId) - }, ApiClientEx.prototype.getLocalFolders = function(userId) { - var serverInfo = this.serverInfo(); - return userId = userId || serverInfo.UserId, localassetmanager.getViews(serverInfo.Id, userId) - }, ApiClientEx.prototype.getNextUpEpisodes = function(options) { - return options.SeriesId && isLocalId(options.SeriesId) ? Promise.resolve(createEmptyList()) : ApiClient.prototype.getNextUpEpisodes.call(this, options) - }, ApiClientEx.prototype.getSeasons = function(itemId, options) { - return isLocalId(itemId) ? (options.SeriesId = itemId, options.IncludeItemTypes = "Season", options.SortBy = "SortName", this.getItems(this.getCurrentUserId(), options)) : ApiClient.prototype.getSeasons.call(this, itemId, options) - }, ApiClientEx.prototype.getEpisodes = function(itemId, options) { - return isLocalId(options.SeasonId) || isLocalId(options.seasonId) ? (options.SeriesId = itemId, options.IncludeItemTypes = "Episode", options.SortBy = "SortName", this.getItems(this.getCurrentUserId(), options)) : isLocalId(itemId) ? (options.SeriesId = itemId, options.IncludeItemTypes = "Episode", options.SortBy = "SortName", this.getItems(this.getCurrentUserId(), options)) : ApiClient.prototype.getEpisodes.call(this, itemId, options) - }, ApiClientEx.prototype.getLatestOfflineItems = function(options) { - options.SortBy = "DateCreated", options.SortOrder = "Descending"; - var serverInfo = this.serverInfo(); - return serverInfo ? localassetmanager.getViewItems(serverInfo.Id, null, options).then(function(items) { - return items.forEach(function(item) { - adjustGuidProperties(item) - }), Promise.resolve(items) - }) : Promise.resolve([]) - }, ApiClientEx.prototype.getThemeMedia = function(userId, itemId, inherit) { - return isLocalViewId(itemId) || isLocalId(itemId) || isTopLevelLocalViewId(itemId) ? Promise.reject() : ApiClient.prototype.getThemeMedia.call(this, userId, itemId, inherit) - }, ApiClientEx.prototype.getSpecialFeatures = function(userId, itemId) { - return isLocalId(itemId) ? Promise.resolve([]) : ApiClient.prototype.getSpecialFeatures.call(this, userId, itemId) - }, ApiClientEx.prototype.getSimilarItems = function(itemId, options) { - return isLocalId(itemId) ? Promise.resolve(createEmptyList()) : ApiClient.prototype.getSimilarItems.call(this, itemId, options) - }, ApiClientEx.prototype.updateFavoriteStatus = function(userId, itemId, isFavorite) { - return isLocalId(itemId) ? Promise.resolve() : ApiClient.prototype.updateFavoriteStatus.call(this, userId, itemId, isFavorite) - }, ApiClientEx.prototype.getScaledImageUrl = function(itemId, options) { - if (isLocalId(itemId) || options && options.itemid && isLocalId(options.itemid)) { - var serverInfo = this.serverInfo(), - id = stripLocalPrefix(itemId); - return localassetmanager.getImageUrl(serverInfo.Id, id, options) - } - return ApiClient.prototype.getScaledImageUrl.call(this, itemId, options) - }, ApiClientEx.prototype.reportPlaybackStart = function(options) { - if (!options) throw new Error("null options"); - return isLocalId(options.ItemId) ? Promise.resolve() : ApiClient.prototype.reportPlaybackStart.call(this, options) - }, ApiClientEx.prototype.reportPlaybackProgress = function(options) { - if (!options) throw new Error("null options"); - if (isLocalId(options.ItemId)) { - var serverInfo = this.serverInfo(); - return serverInfo ? localassetmanager.getLocalItem(serverInfo.Id, stripLocalPrefix(options.ItemId)).then(function(item) { - var libraryItem = item.Item; - return "Video" === libraryItem.MediaType || "AudioBook" === libraryItem.Type ? (libraryItem.UserData = libraryItem.UserData || {}, libraryItem.UserData.PlaybackPositionTicks = options.PositionTicks, libraryItem.UserData.PlayedPercentage = Math.min(libraryItem.RunTimeTicks ? (options.PositionTicks || 0) / libraryItem.RunTimeTicks * 100 : 0, 100), localassetmanager.addOrUpdateLocalItem(item)) : Promise.resolve() - }) : Promise.resolve() - } - return ApiClient.prototype.reportPlaybackProgress.call(this, options) - }, ApiClientEx.prototype.reportPlaybackStopped = function(options) { - if (!options) throw new Error("null options"); - if (isLocalId(options.ItemId)) { - var serverInfo = this.serverInfo(), - action = { - Date: (new Date).getTime(), - ItemId: stripLocalPrefix(options.ItemId), - PositionTicks: options.PositionTicks, - ServerId: serverInfo.Id, - Type: 0, - UserId: this.getCurrentUserId() - }; - return localassetmanager.recordUserAction(action) - } - return ApiClient.prototype.reportPlaybackStopped.call(this, options) - }, ApiClientEx.prototype.getIntros = function(itemId) { - return isLocalId(itemId) ? Promise.resolve({ - Items: [], - TotalRecordCount: 0 - }) : ApiClient.prototype.getIntros.call(this, itemId) - }, ApiClientEx.prototype.getInstantMixFromItem = function(itemId, options) { - return isLocalId(itemId) ? Promise.resolve({ - Items: [], - TotalRecordCount: 0 - }) : ApiClient.prototype.getInstantMixFromItem.call(this, itemId, options) - }, ApiClientEx.prototype.getItemDownloadUrl = function(itemId) { - if (isLocalId(itemId)) { - var serverInfo = this.serverInfo(); - if (serverInfo) return localassetmanager.getLocalItem(serverInfo.Id, stripLocalPrefix(itemId)).then(function(item) { - return Promise.resolve(item.LocalPath) - }) - } - return ApiClient.prototype.getItemDownloadUrl.call(this, itemId) - }, ApiClientEx -}); diff --git a/src/libraries/apiclient/apiclientcore.js b/src/libraries/apiclient/apiclientcore.js deleted file mode 100644 index 15409398e73..00000000000 --- a/src/libraries/apiclient/apiclientcore.js +++ /dev/null @@ -1,1611 +0,0 @@ -define(["events", "appStorage"], function(events, appStorage) { - "use strict"; - - function redetectBitrate(instance) { - stopBitrateDetection(instance), instance.accessToken() && !1 !== instance.enableAutomaticBitrateDetection && setTimeout(redetectBitrateInternal.bind(instance), 6e3) - } - - function redetectBitrateInternal() { - this.accessToken() && this.detectBitrate() - } - - function stopBitrateDetection(instance) { - instance.detectTimeout && clearTimeout(instance.detectTimeout) - } - - function replaceAll(originalString, strReplace, strWith) { - var reg = new RegExp(strReplace, "ig"); - return originalString.replace(reg, strWith) - } - - function onFetchFail(instance, url, response) { - events.trigger(instance, "requestfail", [{ - url: url, - status: response.status, - errorCode: response.headers ? response.headers.get("X-Application-Error-Code") : null - }]) - } - - function paramsToString(params) { - var values = []; - for (var key in params) { - var value = params[key]; - null !== value && void 0 !== value && "" !== value && values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)) - } - return values.join("&") - } - - function fetchWithTimeout(url, options, timeoutMs) { - return new Promise(function(resolve, reject) { - var timeout = setTimeout(reject, timeoutMs); - options = options || {}, options.credentials = "same-origin", fetch(url, options).then(function(response) { - clearTimeout(timeout), resolve(response) - }, function(error) { - clearTimeout(timeout), reject(error) - }) - }) - } - - function getFetchPromise(request) { - var headers = request.headers || {}; - "json" === request.dataType && (headers.accept = "application/json"); - var fetchRequest = { - headers: headers, - method: request.type, - credentials: "same-origin" - }, - contentType = request.contentType; - return request.data && ("string" == typeof request.data ? fetchRequest.body = request.data : (fetchRequest.body = paramsToString(request.data), contentType = contentType || "application/x-www-form-urlencoded; charset=UTF-8")), contentType && (headers["Content-Type"] = contentType), request.timeout ? fetchWithTimeout(request.url, fetchRequest, request.timeout) : fetch(request.url, fetchRequest) - } - - function ApiClient(serverAddress, appName, appVersion, deviceName, deviceId) { - if (!serverAddress) { - throw new Error("Must supply a serverAddress"); - } - - console.debug("ApiClient serverAddress: " + serverAddress); - console.debug("ApiClient appName: " + appName); - console.debug("ApiClient appVersion: " + appVersion); - console.debug("ApiClient deviceName: " + deviceName); - console.debug("ApiClient deviceId: " + deviceId); - - this._serverInfo = {}; - this._serverAddress = serverAddress; - this._deviceId = deviceId; - this._deviceName = deviceName; - this._appName = appName; - this._appVersion = appVersion; - } - - function setSavedEndpointInfo(instance, info) { - instance._endPointInfo = info - } - - function getTryConnectPromise(instance, url, state, resolve, reject) { - console.debug("getTryConnectPromise " + url); - fetchWithTimeout(instance.getUrl("system/info/public", null, url), { - method: "GET", - accept: "application/json" - }, 15e3).then(function() { - state.resolved || (state.resolved = !0, console.debug("Reconnect succeeded to " + url), instance.serverAddress(url), resolve()) - }, function() { - state.resolved || (console.error("Reconnect failed to " + url), ++state.rejects >= state.numAddresses && reject()) - }) - } - - function tryReconnectInternal(instance) { - var addresses = [], - addressesStrings = [], - serverInfo = instance.serverInfo(); - return serverInfo.LocalAddress && -1 === addressesStrings.indexOf(serverInfo.LocalAddress) && (addresses.push({ - url: serverInfo.LocalAddress, - timeout: 0 - }), addressesStrings.push(addresses[addresses.length - 1].url)), serverInfo.ManualAddress && -1 === addressesStrings.indexOf(serverInfo.ManualAddress) && (addresses.push({ - url: serverInfo.ManualAddress, - timeout: 100 - }), addressesStrings.push(addresses[addresses.length - 1].url)), serverInfo.RemoteAddress && -1 === addressesStrings.indexOf(serverInfo.RemoteAddress) && (addresses.push({ - url: serverInfo.RemoteAddress, - timeout: 200 - }), addressesStrings.push(addresses[addresses.length - 1].url)), console.debug("tryReconnect: " + addressesStrings.join("|")), new Promise(function(resolve, reject) { - var state = {}; - state.numAddresses = addresses.length, state.rejects = 0, addresses.map(function(url) { - setTimeout(function() { - state.resolved || getTryConnectPromise(instance, url.url, state, resolve, reject) - }, url.timeout) - }) - }) - } - - function tryReconnect(instance, retryCount) { - return retryCount = retryCount || 0, retryCount >= 20 ? Promise.reject() : tryReconnectInternal(instance).catch(function(err) { - return console.error("error in tryReconnectInternal: " + (err || "")), new Promise(function(resolve, reject) { - setTimeout(function() { - tryReconnect(instance, retryCount + 1).then(resolve, reject) - }, 500) - }) - }) - } - - function getCachedUser(instance, userId) { - var serverId = instance.serverId(); - if (!serverId) return null; - var json = appStorage.getItem("user-" + userId + "-" + serverId); - return json ? JSON.parse(json) : null - } - - function onWebSocketMessage(msg) { - var instance = this; - msg = JSON.parse(msg.data), onMessageReceivedInternal(instance, msg) - } - - function onMessageReceivedInternal(instance, msg) { - var messageId = msg.MessageId; - if (messageId) { - if (messageIdsReceived[messageId]) return; - messageIdsReceived[messageId] = !0 - } - if ("UserDeleted" === msg.MessageType) instance._currentUser = null; - else if ("UserUpdated" === msg.MessageType || "UserConfigurationUpdated" === msg.MessageType) { - var user = msg.Data; - user.Id === instance.getCurrentUserId() && (instance._currentUser = null) - } - events.trigger(instance, "message", [msg]) - } - - function onWebSocketOpen() { - var instance = this; - console.debug("web socket connection opened"), events.trigger(instance, "websocketopen") - - // MPV Shim Auto-Connect - // There is almost certainly a better place to do this. - require(["playbackManager"], function(playbackManager) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', "/mpv_shim_id", true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.onloadend = function (result) { - var res = JSON.parse(result.target.response); - playbackManager.getTargets().then(function (targets) { - for (var i = 0; i < targets.length; i++) { - if (targets[i].appName == res.appName && - targets[i].deviceName == res.deviceName) - playbackManager.trySetActivePlayer(targets[i].playerName, targets[i]); - } - }); - }; - xhr.send("{}"); - }); - } - - function onWebSocketError() { - var instance = this; - events.trigger(instance, "websocketerror") - } - - function setSocketOnClose(apiClient, socket) { - socket.onclose = function() { - console.debug("web socket closed"); - if (apiClient._webSocket === socket) { - console.debug("nulling out web socket"); - apiClient._webSocket = null; - } - setTimeout(function() { - events.trigger(apiClient, "websocketclose") - }, 0) - } - } - - function normalizeReturnBitrate(instance, bitrate) { - if (!bitrate) return instance.lastDetectedBitrate ? instance.lastDetectedBitrate : Promise.reject(); - var result = Math.round(.7 * bitrate); - if (instance.getMaxBandwidth) { - var maxRate = instance.getMaxBandwidth(); - maxRate && (result = Math.min(result, maxRate)) - } - return instance.lastDetectedBitrate = result, instance.lastDetectedBitrateTime = (new Date).getTime(), result - } - - function detectBitrateInternal(instance, tests, index, currentBitrate) { - if (index >= tests.length) return normalizeReturnBitrate(instance, currentBitrate); - var test = tests[index]; - return instance.getDownloadSpeed(test.bytes).then(function(bitrate) { - return bitrate < test.threshold ? normalizeReturnBitrate(instance, bitrate) : detectBitrateInternal(instance, tests, index + 1, bitrate) - }, function() { - return normalizeReturnBitrate(instance, currentBitrate) - }) - } - - function detectBitrateWithEndpointInfo(instance, endpointInfo) { - if (endpointInfo.IsInNetwork) { - return instance.lastDetectedBitrate = 14e7, instance.lastDetectedBitrateTime = (new Date).getTime(), 14e7 - } - return detectBitrateInternal(instance, [{ - bytes: 5e5, - threshold: 5e5 - }, { - bytes: 1e6, - threshold: 2e7 - }, { - bytes: 3e6, - threshold: 5e7 - }], 0) - } - - function getRemoteImagePrefix(instance, options) { - var urlPrefix; - return options.artist ? (urlPrefix = "Artists/" + instance.encodeName(options.artist), delete options.artist) : options.person ? (urlPrefix = "Persons/" + instance.encodeName(options.person), delete options.person) : options.genre ? (urlPrefix = "Genres/" + instance.encodeName(options.genre), delete options.genre) : options.musicGenre ? (urlPrefix = "MusicGenres/" + instance.encodeName(options.musicGenre), delete options.musicGenre) : options.studio ? (urlPrefix = "Studios/" + instance.encodeName(options.studio), delete options.studio) : (urlPrefix = "Items/" + options.itemId, delete options.itemId), urlPrefix - } - - function normalizeImageOptions(instance, options) { - var ratio = window.devicePixelRatio; - ratio && (options.minScale && (ratio = Math.max(options.minScale, ratio)), options.width && (options.width = Math.round(options.width * ratio)), options.height && (options.height = Math.round(options.height * ratio)), options.maxWidth && (options.maxWidth = Math.round(options.maxWidth * ratio)), options.maxHeight && (options.maxHeight = Math.round(options.maxHeight * ratio))), options.quality = options.quality || instance.getDefaultImageQuality(options.type), instance.normalizeImageOptions && instance.normalizeImageOptions(options) - } - - function compareVersions(a, b) { - a = a.split("."), b = b.split("."); - for (var i = 0, length = Math.max(a.length, b.length); i < length; i++) { - var aVal = parseInt(a[i] || "0"), - bVal = parseInt(b[i] || "0"); - if (aVal < bVal) return -1; - if (aVal > bVal) return 1 - } - return 0 - } - ApiClient.prototype.appName = function() { - return this._appName - }, ApiClient.prototype.setRequestHeaders = function(headers) { - var currentServerInfo = this.serverInfo(), - appName = this._appName, - accessToken = currentServerInfo.AccessToken, - values = []; - if (appName && values.push('Client="' + appName + '"'), this._deviceName && values.push('Device="' + this._deviceName + '"'), this._deviceId && values.push('DeviceId="' + this._deviceId + '"'), this._appVersion && values.push('Version="' + this._appVersion + '"'), accessToken && values.push('Token="' + accessToken + '"'), values.length) { - var auth = "MediaBrowser " + values.join(", "); - headers["X-Emby-Authorization"] = auth - } - }, ApiClient.prototype.appVersion = function() { - return this._appVersion - }, ApiClient.prototype.deviceName = function() { - return this._deviceName - }, ApiClient.prototype.deviceId = function() { - return this._deviceId - }, ApiClient.prototype.serverAddress = function(val) { - if (null != val) { - if (0 !== val.toLowerCase().indexOf("http")) throw new Error("Invalid url: " + val); - var changed = val !== this._serverAddress; - this._serverAddress = val, this.onNetworkChange(), changed && events.trigger(this, "serveraddresschanged") - } - return this._serverAddress - }, ApiClient.prototype.onNetworkChange = function() { - this.lastDetectedBitrate = 0, this.lastDetectedBitrateTime = 0, setSavedEndpointInfo(this, null), redetectBitrate(this) - }, ApiClient.prototype.getUrl = function(name, params, serverAddress) { - if (!name) throw new Error("Url name cannot be empty"); - var url = serverAddress || this._serverAddress; - if (!url) throw new Error("serverAddress is yet not set"); - var lowered = url.toLowerCase(); - return "/" !== name.charAt(0) && (url += "/"), url += name, params && (params = paramsToString(params)) && (url += "?" + params), url - }, ApiClient.prototype.fetchWithFailover = function(request, enableReconnection) { - console.debug("Requesting " + request.url), request.timeout = 3e4; - var instance = this; - return getFetchPromise(request).then(function(response) { - return instance.lastFetch = (new Date).getTime(), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : "text" === request.dataType || 0 === (response.headers.get("Content-Type") || "").toLowerCase().indexOf("text/") ? response.text() : response : (onFetchFail(instance, request.url, response), Promise.reject(response)) - }, function(error) { - if (error ? console.error("Request failed to " + request.url + " " + (error.status || "") + " " + error.toString()) : console.error("Request timed out to " + request.url), error && error.status || !enableReconnection) throw console.error("Reporting request failure"), onFetchFail(instance, request.url, {}), error; - console.debug("Attempting reconnection"); - var previousServerAddress = instance.serverAddress(); - return tryReconnect(instance).then(function() { - return console.debug("Reconnect succeesed"), request.url = request.url.replace(previousServerAddress, instance.serverAddress()), instance.fetchWithFailover(request, !1) - }, function(innerError) { - throw console.error("Reconnect failed"), onFetchFail(instance, request.url, {}), innerError - }) - }) - }, ApiClient.prototype.fetch = function(request, includeAuthorization) { - if (!request) throw new Error("Request cannot be null"); - if (request.headers = request.headers || {}, !1 !== includeAuthorization && this.setRequestHeaders(request.headers), !1 === this.enableAutomaticNetworking || "GET" !== request.type) { - console.debug("Requesting url without automatic networking: " + request.url); - var instance = this; - return getFetchPromise(request).then(function(response) { - return instance.lastFetch = (new Date).getTime(), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : "text" === request.dataType || 0 === (response.headers.get("Content-Type") || "").toLowerCase().indexOf("text/") ? response.text() : response : (onFetchFail(instance, request.url, response), Promise.reject(response)) - }, function(error) { - throw onFetchFail(instance, request.url, {}), error - }) - } - return this.fetchWithFailover(request, !0) - }, ApiClient.prototype.setAuthenticationInfo = function(accessKey, userId) { - this._currentUser = null, this._serverInfo.AccessToken = accessKey, this._serverInfo.UserId = userId, redetectBitrate(this) - }, ApiClient.prototype.serverInfo = function(info) { - return info && (this._serverInfo = info), this._serverInfo - }, ApiClient.prototype.getCurrentUserId = function() { - return this._serverInfo.UserId - }, ApiClient.prototype.accessToken = function() { - return this._serverInfo.AccessToken - }, ApiClient.prototype.serverId = function() { - return this.serverInfo().Id - }, ApiClient.prototype.serverName = function() { - return this.serverInfo().Name - }, ApiClient.prototype.ajax = function(request, includeAuthorization) { - if (!request) throw new Error("Request cannot be null"); - return this.fetch(request, includeAuthorization) - }, ApiClient.prototype.getCurrentUser = function(enableCache) { - if (this._currentUser) return Promise.resolve(this._currentUser); - var userId = this.getCurrentUserId(); - if (!userId) return Promise.reject(); - var user, instance = this, - serverPromise = this.getUser(userId).then(function(user) { - return appStorage.setItem("user-" + user.Id + "-" + user.ServerId, JSON.stringify(user)), instance._currentUser = user, user - }, function(response) { - if (!response.status && userId && instance.accessToken() && (user = getCachedUser(instance, userId))) return Promise.resolve(user); - throw response - }); - return !this.lastFetch && !1 !== enableCache && (user = getCachedUser(instance, userId)) ? Promise.resolve(user) : serverPromise - }, ApiClient.prototype.isLoggedIn = function() { - var info = this.serverInfo(); - return !!(info && info.UserId && info.AccessToken) - }, ApiClient.prototype.logout = function() { - stopBitrateDetection(this), this.closeWebSocket(); - var done = function() { - appStorage.removeItem("user-" + this._currentUser.Id + "-" + this._currentUser.ServerId) - this.setAuthenticationInfo(null, null) - }.bind(this); - - // MPV Shim Logout Callback - // There is almost certainly a better place to do this. - var xhr = new XMLHttpRequest(); - xhr.open('POST', "/destroy_session", true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send("{}"); - - if (this.accessToken()) { - var url = this.getUrl("Sessions/Logout"); - return this.ajax({ - type: "POST", - url: url - }).then(done, done) - } - return done(), Promise.resolve() - }, ApiClient.prototype.authenticateUserByName = function(name, password) { - if (!name) return Promise.reject(); - var url = this.getUrl("Users/authenticatebyname"), - instance = this; - return new Promise(function(resolve, reject) { - var postData = { - Username: name, - Pw: password || "" - }; - - // MPV Shim Password Provider - // There is almost certainly a better place to do this. - var xhr = new XMLHttpRequest(); - xhr.open('POST', "/mpv_shim_password", true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.onloadend = function (result) { - var res = JSON.parse(result.target.response); - if (!res.success) alert("MPV Shim Login Failed"); - }; - xhr.send(JSON.stringify({ - server: instance.serverAddress(), - username: name, - password: password - })); - - instance.ajax({ - type: "POST", - url: url, - data: JSON.stringify(postData), - dataType: "json", - contentType: "application/json" - }).then(function(result) { - var afterOnAuthenticated = function() { - redetectBitrate(instance), resolve(result) - }; - instance.onAuthenticated ? instance.onAuthenticated(instance, result).then(afterOnAuthenticated) : afterOnAuthenticated() - }, reject) - }) - }, ApiClient.prototype.ensureWebSocket = function() { - if (!this.isWebSocketOpenOrConnecting() && this.isWebSocketSupported()) try { - this.openWebSocket() - } catch (err) { - console.error("error opening web socket: " + err) - } - }; - var messageIdsReceived = {}; - return ApiClient.prototype.openWebSocket = function() { - var accessToken = this.accessToken(); - if (!accessToken) throw new Error("Cannot open web socket without access token."); - var url = this.getUrl("socket"); - url = replaceAll(url, "emby/socket", "embywebsocket"), url = replaceAll(url, "https:", "wss:"), url = replaceAll(url, "http:", "ws:"), url += "?api_key=" + accessToken, url += "&deviceId=" + this.deviceId(), console.debug("opening web socket with url: " + url); - var webSocket = new WebSocket(url); - webSocket.onmessage = onWebSocketMessage.bind(this), webSocket.onopen = onWebSocketOpen.bind(this), webSocket.onerror = onWebSocketError.bind(this), setSocketOnClose(this, webSocket), this._webSocket = webSocket - }, ApiClient.prototype.closeWebSocket = function() { - var socket = this._webSocket; - socket && socket.readyState === WebSocket.OPEN && socket.close() - }, ApiClient.prototype.sendWebSocketMessage = function(name, data) { - console.debug("Sending web socket message: " + name); - var msg = { - MessageType: name - }; - data && (msg.Data = data), msg = JSON.stringify(msg), this._webSocket.send(msg) - }, ApiClient.prototype.sendMessage = function(name, data) { - this.isWebSocketOpen() && this.sendWebSocketMessage(name, data) - }, ApiClient.prototype.isMessageChannelOpen = function() { - return this.isWebSocketOpen() - }, ApiClient.prototype.isWebSocketOpen = function() { - var socket = this._webSocket; - return !!socket && socket.readyState === WebSocket.OPEN - }, ApiClient.prototype.isWebSocketOpenOrConnecting = function() { - var socket = this._webSocket; - return !!socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) - }, ApiClient.prototype.get = function(url) { - return this.ajax({ - type: "GET", - url: url - }) - }, ApiClient.prototype.getJSON = function(url, includeAuthorization) { - return this.fetch({ - url: url, - type: "GET", - dataType: "json", - headers: { - accept: "application/json" - } - }, includeAuthorization) - }, ApiClient.prototype.updateServerInfo = function(server, serverUrl) { - if (null == server) throw new Error("server cannot be null"); - if (this.serverInfo(server), !serverUrl) throw new Error("serverUrl cannot be null. serverInfo: " + JSON.stringify(server)); - console.debug("Setting server address to " + serverUrl), this.serverAddress(serverUrl) - }, ApiClient.prototype.isWebSocketSupported = function() { - try { - return null != WebSocket - } catch (err) { - return !1 - } - }, ApiClient.prototype.clearAuthenticationInfo = function() { - this.setAuthenticationInfo(null, null) - }, ApiClient.prototype.encodeName = function(name) { - name = name.split("/").join("-"), name = name.split("&").join("-"), name = name.split("?").join("-"); - var val = paramsToString({ - name: name - }); - return val.substring(val.indexOf("=") + 1).replace("'", "%27") - }, ApiClient.prototype.getDownloadSpeed = function(byteSize) { - var url = this.getUrl("Playback/BitrateTest", { - Size: byteSize - }), - now = (new Date).getTime(); - return this.ajax({ - type: "GET", - url: url, - timeout: 5e3 - }).then(function() { - var responseTimeSeconds = ((new Date).getTime() - now) / 1e3, - bytesPerSecond = byteSize / responseTimeSeconds; - return Math.round(8 * bytesPerSecond) - }) - }, ApiClient.prototype.detectBitrate = function(force) { - if (!force && this.lastDetectedBitrate && (new Date).getTime() - (this.lastDetectedBitrateTime || 0) <= 36e5) return Promise.resolve(this.lastDetectedBitrate); - var instance = this; - return this.getEndpointInfo().then(function(info) { - return detectBitrateWithEndpointInfo(instance, info) - }, function(info) { - return detectBitrateWithEndpointInfo(instance, {}) - }) - }, ApiClient.prototype.getItem = function(userId, itemId) { - if (!itemId) throw new Error("null itemId"); - var url = userId ? this.getUrl("Users/" + userId + "/Items/" + itemId) : this.getUrl("Items/" + itemId); - return this.getJSON(url) - }, ApiClient.prototype.getRootFolder = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/Items/Root"); - return this.getJSON(url) - }, ApiClient.prototype.getNotificationSummary = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Notifications/" + userId + "/Summary"); - return this.getJSON(url) - }, ApiClient.prototype.getNotifications = function(userId, options) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Notifications/" + userId, options || {}); - return this.getJSON(url) - }, ApiClient.prototype.markNotificationsRead = function(userId, idList, isRead) { - if (!userId) throw new Error("null userId"); - if (!idList) throw new Error("null idList"); - var suffix = isRead ? "Read" : "Unread", - params = { - UserId: userId, - Ids: idList.join(",") - }, - url = this.getUrl("Notifications/" + userId + "/" + suffix, params); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getRemoteImageProviders = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages/Providers", options); - return this.getJSON(url) - }, ApiClient.prototype.getAvailableRemoteImages = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages", options); - return this.getJSON(url) - }, ApiClient.prototype.downloadRemoteImage = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages/Download", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getRecordingFolders = function(userId) { - var url = this.getUrl("LiveTv/Recordings/Folders", { - userId: userId - }); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvInfo = function(options) { - var url = this.getUrl("LiveTv/Info", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvGuideInfo = function(options) { - var url = this.getUrl("LiveTv/GuideInfo", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvChannel = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Channels/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvChannels = function(options) { - var url = this.getUrl("LiveTv/Channels", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvPrograms = function(options) { - return options = options || {}, options.channelIds && options.channelIds.length > 1800 ? this.ajax({ - type: "POST", - url: this.getUrl("LiveTv/Programs"), - data: JSON.stringify(options), - contentType: "application/json", - dataType: "json" - }) : this.ajax({ - type: "GET", - url: this.getUrl("LiveTv/Programs", options), - dataType: "json" - }) - }, ApiClient.prototype.getLiveTvRecommendedPrograms = function(options) { - return options = options || {}, this.ajax({ - type: "GET", - url: this.getUrl("LiveTv/Programs/Recommended", options), - dataType: "json" - }) - }, ApiClient.prototype.getLiveTvRecordings = function(options) { - var url = this.getUrl("LiveTv/Recordings", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingSeries = function(options) { - var url = this.getUrl("LiveTv/Recordings/Series", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingGroups = function(options) { - var url = this.getUrl("LiveTv/Recordings/Groups", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingGroup = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Recordings/Groups/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecording = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Recordings/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvProgram = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Programs/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.deleteLiveTvRecording = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Recordings/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.cancelLiveTvTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Timers/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.getLiveTvTimers = function(options) { - var url = this.getUrl("LiveTv/Timers", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Timers/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getNewLiveTvTimerDefaults = function(options) { - options = options || {}; - var url = this.getUrl("LiveTv/Timers/Defaults", options); - return this.getJSON(url) - }, ApiClient.prototype.createLiveTvTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/Timers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updateLiveTvTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/Timers/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.resetLiveTvTuner = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Tuners/" + id + "/Reset"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getLiveTvSeriesTimers = function(options) { - var url = this.getUrl("LiveTv/SeriesTimers", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvSeriesTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/SeriesTimers/" + id); - return this.getJSON(url) - }, ApiClient.prototype.cancelLiveTvSeriesTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/SeriesTimers/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.createLiveTvSeriesTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/SeriesTimers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updateLiveTvSeriesTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/SeriesTimers/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.getRegistrationInfo = function(feature) { - var url = this.getUrl("Registrations/" + feature); - return this.getJSON(url) - }, ApiClient.prototype.getSystemInfo = function() { - var url = this.getUrl("System/Info"), - instance = this; - return this.getJSON(url).then(function(info) { - return instance.setSystemInfo(info), Promise.resolve(info) - }) - }, ApiClient.prototype.getSyncStatus = function(itemId) { - var url = this.getUrl("Sync/" + itemId + "/Status"); - return this.ajax({ - url: url, - type: "POST", - dataType: "json", - contentType: "application/json", - data: JSON.stringify({ - TargetId: this.deviceId() - }) - }) - }, ApiClient.prototype.getPublicSystemInfo = function() { - var url = this.getUrl("System/Info/Public"), - instance = this; - return this.getJSON(url).then(function(info) { - return instance.setSystemInfo(info), Promise.resolve(info) - }) - }, ApiClient.prototype.getInstantMixFromItem = function(itemId, options) { - var url = this.getUrl("Items/" + itemId + "/InstantMix", options); - return this.getJSON(url) - }, ApiClient.prototype.getEpisodes = function(itemId, options) { - var url = this.getUrl("Shows/" + itemId + "/Episodes", options); - return this.getJSON(url) - }, ApiClient.prototype.getDisplayPreferences = function(id, userId, app) { - var url = this.getUrl("DisplayPreferences/" + id, { - userId: userId, - client: app - }); - return this.getJSON(url) - }, ApiClient.prototype.updateDisplayPreferences = function(id, obj, userId, app) { - var url = this.getUrl("DisplayPreferences/" + id, { - userId: userId, - client: app - }); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(obj), - contentType: "application/json" - }) - }, ApiClient.prototype.getSeasons = function(itemId, options) { - var url = this.getUrl("Shows/" + itemId + "/Seasons", options); - return this.getJSON(url) - }, ApiClient.prototype.getSimilarItems = function(itemId, options) { - var url = this.getUrl("Items/" + itemId + "/Similar", options); - return this.getJSON(url) - }, ApiClient.prototype.getCultures = function() { - var url = this.getUrl("Localization/cultures"); - return this.getJSON(url) - }, ApiClient.prototype.getCountries = function() { - var url = this.getUrl("Localization/countries"); - return this.getJSON(url) - }, ApiClient.prototype.getPlaybackInfo = function(itemId, options, deviceProfile) { - var postData = { - DeviceProfile: deviceProfile - }; - return this.ajax({ - url: this.getUrl("Items/" + itemId + "/PlaybackInfo", options), - type: "POST", - data: JSON.stringify(postData), - contentType: "application/json", - dataType: "json" - }) - }, ApiClient.prototype.getLiveStreamMediaInfo = function(liveStreamId) { - var postData = { - LiveStreamId: liveStreamId - }; - return this.ajax({ - url: this.getUrl("LiveStreams/MediaInfo"), - type: "POST", - data: JSON.stringify(postData), - contentType: "application/json", - dataType: "json" - }) - }, ApiClient.prototype.getIntros = function(itemId) { - return this.getJSON(this.getUrl("Users/" + this.getCurrentUserId() + "/Items/" + itemId + "/Intros")) - }, ApiClient.prototype.getDirectoryContents = function(path, options) { - if (!path) throw new Error("null path"); - if ("string" != typeof path) throw new Error("invalid path"); - options = options || {}, options.path = path; - var url = this.getUrl("Environment/DirectoryContents", options); - return this.getJSON(url) - }, ApiClient.prototype.getNetworkShares = function(path) { - if (!path) throw new Error("null path"); - var options = {}; - options.path = path; - var url = this.getUrl("Environment/NetworkShares", options); - return this.getJSON(url) - }, ApiClient.prototype.getParentPath = function(path) { - if (!path) throw new Error("null path"); - var options = {}; - options.path = path; - var url = this.getUrl("Environment/ParentPath", options); - return this.ajax({ - type: "GET", - url: url, - dataType: "text" - }) - }, ApiClient.prototype.getDrives = function() { - var url = this.getUrl("Environment/Drives"); - return this.getJSON(url) - }, ApiClient.prototype.getNetworkDevices = function() { - var url = this.getUrl("Environment/NetworkDevices"); - return this.getJSON(url) - }, ApiClient.prototype.cancelPackageInstallation = function(installationId) { - if (!installationId) throw new Error("null installationId"); - var url = this.getUrl("Packages/Installing/" + installationId); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.refreshItem = function(itemId, options) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId + "/Refresh", options || {}); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.installPlugin = function(name, guid, updateClass, version) { - if (!name) throw new Error("null name"); - if (!updateClass) throw new Error("null updateClass"); - var options = { - updateClass: updateClass, - AssemblyGuid: guid - }; - version && (options.version = version); - var url = this.getUrl("Packages/Installed/" + name, options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.restartServer = function() { - var url = this.getUrl("System/Restart"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.shutdownServer = function() { - var url = this.getUrl("System/Shutdown"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getPackageInfo = function(name, guid) { - if (!name) throw new Error("null name"); - var options = { - AssemblyGuid: guid - }, - url = this.getUrl("Packages/" + name, options); - return this.getJSON(url) - }, ApiClient.prototype.getVirtualFolders = function() { - var url = "Library/VirtualFolders"; - return url = this.getUrl(url), this.getJSON(url) - }, ApiClient.prototype.getPhysicalPaths = function() { - var url = this.getUrl("Library/PhysicalPaths"); - return this.getJSON(url) - }, ApiClient.prototype.getServerConfiguration = function() { - var url = this.getUrl("System/Configuration"); - return this.getJSON(url) - }, ApiClient.prototype.getDevicesOptions = function() { - var url = this.getUrl("System/Configuration/devices"); - return this.getJSON(url) - }, ApiClient.prototype.getContentUploadHistory = function() { - var url = this.getUrl("Devices/CameraUploads", { - DeviceId: this.deviceId() - }); - return this.getJSON(url) - }, ApiClient.prototype.getNamedConfiguration = function(name) { - var url = this.getUrl("System/Configuration/" + name); - return this.getJSON(url) - }, ApiClient.prototype.getScheduledTasks = function(options) { - options = options || {}; - var url = this.getUrl("ScheduledTasks", options); - return this.getJSON(url) - }, ApiClient.prototype.startScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/Running/" + id); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getNextUpEpisodes = function(options) { - var url = this.getUrl("Shows/NextUp", options); - return this.getJSON(url) - }, ApiClient.prototype.stopScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/Running/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.getPluginConfiguration = function(id) { - if (!id) throw new Error("null Id"); - var url = this.getUrl("Plugins/" + id + "/Configuration"); - return this.getJSON(url) - }, ApiClient.prototype.getAvailablePlugins = function(options) { - options = options || {}, options.PackageType = "UserInstalled"; - var url = this.getUrl("Packages", options); - return this.getJSON(url) - }, ApiClient.prototype.uninstallPlugin = function(id) { - if (!id) throw new Error("null Id"); - var url = this.getUrl("Plugins/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.removeVirtualFolder = function(name, refreshLibrary) { - if (!name) throw new Error("null name"); - var url = "Library/VirtualFolders"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - name: name - }), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.addVirtualFolder = function(name, type, refreshLibrary, libraryOptions) { - if (!name) throw new Error("null name"); - var options = {}; - type && (options.collectionType = type), options.refreshLibrary = !!refreshLibrary, options.name = name; - var url = "Library/VirtualFolders"; - return url = this.getUrl(url, options), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - LibraryOptions: libraryOptions - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateVirtualFolderOptions = function(id, libraryOptions) { - if (!id) throw new Error("null name"); - var url = "Library/VirtualFolders/LibraryOptions"; - return url = this.getUrl(url), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Id: id, - LibraryOptions: libraryOptions - }), - contentType: "application/json" - }) - }, ApiClient.prototype.renameVirtualFolder = function(name, newName, refreshLibrary) { - if (!name) throw new Error("null name"); - var url = "Library/VirtualFolders/Name"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - newName: newName, - name: name - }), this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.addMediaPath = function(virtualFolderName, mediaPath, networkSharePath, refreshLibrary) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!mediaPath) throw new Error("null mediaPath"); - var url = "Library/VirtualFolders/Paths", - pathInfo = { - Path: mediaPath - }; - return networkSharePath && (pathInfo.NetworkPath = networkSharePath), url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary - }), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Name: virtualFolderName, - PathInfo: pathInfo - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateMediaPath = function(virtualFolderName, pathInfo) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!pathInfo) throw new Error("null pathInfo"); - var url = "Library/VirtualFolders/Paths/Update"; - return url = this.getUrl(url), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Name: virtualFolderName, - PathInfo: pathInfo - }), - contentType: "application/json" - }) - }, ApiClient.prototype.removeMediaPath = function(virtualFolderName, mediaPath, refreshLibrary) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!mediaPath) throw new Error("null mediaPath"); - var url = "Library/VirtualFolders/Paths"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - path: mediaPath, - name: virtualFolderName - }), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteUser = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("Users/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteUserImage = function(userId, imageType, imageIndex) { - if (!userId) throw new Error("null userId"); - if (!imageType) throw new Error("null imageType"); - var url = this.getUrl("Users/" + userId + "/Images/" + imageType); - return null != imageIndex && (url += "/" + imageIndex), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteItemImage = function(itemId, imageType, imageIndex) { - if (!imageType) throw new Error("null imageType"); - var url = this.getUrl("Items/" + itemId + "/Images"); - return url += "/" + imageType, null != imageIndex && (url += "/" + imageIndex), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteItem = function(itemId) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.stopActiveEncodings = function(playSessionId) { - var options = { - deviceId: this.deviceId() - }; - playSessionId && (options.PlaySessionId = playSessionId); - var url = this.getUrl("Videos/ActiveEncodings", options); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.reportCapabilities = function(options) { - var url = this.getUrl("Sessions/Capabilities/Full"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(options), - contentType: "application/json" - }) - }, ApiClient.prototype.updateItemImageIndex = function(itemId, imageType, imageIndex, newIndex) { - if (!imageType) throw new Error("null imageType"); - var options = { - newIndex: newIndex - }, - url = this.getUrl("Items/" + itemId + "/Images/" + imageType + "/" + imageIndex + "/Index", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getItemImageInfos = function(itemId) { - var url = this.getUrl("Items/" + itemId + "/Images"); - return this.getJSON(url) - }, ApiClient.prototype.getCriticReviews = function(itemId, options) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId + "/CriticReviews", options); - return this.getJSON(url) - }, ApiClient.prototype.getItemDownloadUrl = function(itemId) { - if (!itemId) throw new Error("itemId cannot be empty"); - var url = "Items/" + itemId + "/Download"; - return this.getUrl(url, { - api_key: this.accessToken() - }) - }, ApiClient.prototype.getSessions = function(options) { - var url = this.getUrl("Sessions", options); - return this.getJSON(url) - }, ApiClient.prototype.uploadUserImage = function(userId, imageType, file) { - if (!userId) throw new Error("null userId"); - if (!imageType) throw new Error("null imageType"); - if (!file) throw new Error("File must be an image."); - if ("image/png" !== file.type && "image/jpeg" !== file.type && "image/jpeg" !== file.type) throw new Error("File must be an image."); - var instance = this; - return new Promise(function(resolve, reject) { - var reader = new FileReader; - reader.onerror = function() { - reject() - }, reader.onabort = function() { - reject() - }, reader.onload = function(e) { - var data = e.target.result.split(",")[1], - url = instance.getUrl("Users/" + userId + "/Images/" + imageType); - instance.ajax({ - type: "POST", - url: url, - data: data, - contentType: "image/" + file.name.substring(file.name.lastIndexOf(".") + 1) - }).then(resolve, reject) - }, reader.readAsDataURL(file) - }) - }, ApiClient.prototype.uploadItemImage = function(itemId, imageType, file) { - if (!itemId) throw new Error("null itemId"); - if (!imageType) throw new Error("null imageType"); - if (!file) throw new Error("File must be an image."); - if ("image/png" !== file.type && "image/jpeg" !== file.type && "image/jpeg" !== file.type) throw new Error("File must be an image."); - var url = this.getUrl("Items/" + itemId + "/Images"); - url += "/" + imageType; - var instance = this; - return new Promise(function(resolve, reject) { - var reader = new FileReader; - reader.onerror = function() { - reject() - }, reader.onabort = function() { - reject() - }, reader.onload = function(e) { - var data = e.target.result.split(",")[1]; - instance.ajax({ - type: "POST", - url: url, - data: data, - contentType: "image/" + file.name.substring(file.name.lastIndexOf(".") + 1) - }).then(resolve, reject) - }, reader.readAsDataURL(file) - }) - }, ApiClient.prototype.getInstalledPlugins = function() { - var options = {}, - url = this.getUrl("Plugins", options); - return this.getJSON(url) - }, ApiClient.prototype.getUser = function(id) { - if (!id) throw new Error("Must supply a userId"); - var url = this.getUrl("Users/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getStudio = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Studios/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getGenre = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Genres/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getMusicGenre = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("MusicGenres/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getArtist = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Artists/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getPerson = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Persons/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getPublicUsers = function() { - var url = this.getUrl("users/public"); - return this.ajax({ - type: "GET", - url: url, - dataType: "json" - }, !1) - }, ApiClient.prototype.getUsers = function(options) { - var url = this.getUrl("users", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getParentalRatings = function() { - var url = this.getUrl("Localization/ParentalRatings"); - return this.getJSON(url) - }, ApiClient.prototype.getDefaultImageQuality = function(imageType) { - return "backdrop" === imageType.toLowerCase() ? 80 : 90 - }, ApiClient.prototype.getUserImageUrl = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}; - var url = "Users/" + userId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), normalizeImageOptions(this, options), delete options.type, delete options.index, this.getUrl(url, options) - }, ApiClient.prototype.getImageUrl = function(itemId, options) { - if (!itemId) throw new Error("itemId cannot be empty"); - options = options || {}; - var url = "Items/" + itemId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), options.quality = options.quality || this.getDefaultImageQuality(options.type), this.normalizeImageOptions && this.normalizeImageOptions(options), delete options.type, delete options.index, this.getUrl(url, options) - }, ApiClient.prototype.getScaledImageUrl = function(itemId, options) { - if (!itemId) throw new Error("itemId cannot be empty"); - options = options || {}; - var url = "Items/" + itemId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), normalizeImageOptions(this, options), delete options.type, delete options.index, delete options.minScale, this.getUrl(url, options) - }, ApiClient.prototype.getThumbImageUrl = function(item, options) { - if (!item) throw new Error("null item"); - return options = options || {}, options.imageType = "thumb", item.ImageTags && item.ImageTags.Thumb ? (options.tag = item.ImageTags.Thumb, this.getImageUrl(item.Id, options)) : item.ParentThumbItemId ? (options.tag = item.ImageTags.ParentThumbImageTag, this.getImageUrl(item.ParentThumbItemId, options)) : null - }, ApiClient.prototype.updateUserPassword = function(userId, currentPassword, newPassword) { - if (!userId) return Promise.reject(); - var url = this.getUrl("Users/" + userId + "/Password"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - CurrentPw: currentPassword || "", - NewPw: newPassword - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateEasyPassword = function(userId, newPassword) { - if (!userId) return void Promise.reject(); - var url = this.getUrl("Users/" + userId + "/EasyPassword"); - return this.ajax({ - type: "POST", - url: url, - data: { - NewPw: newPassword - } - }) - }, ApiClient.prototype.resetUserPassword = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/Password"), - postData = {}; - return postData.resetPassword = !0, this.ajax({ - type: "POST", - url: url, - data: postData - }) - }, ApiClient.prototype.resetEasyPassword = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/EasyPassword"), - postData = {}; - return postData.resetPassword = !0, this.ajax({ - type: "POST", - url: url, - data: postData - }) - }, ApiClient.prototype.updateServerConfiguration = function(configuration) { - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("System/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateNamedConfiguration = function(name, configuration) { - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("System/Configuration/" + name); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateItem = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("Items/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updatePluginSecurityInfo = function(info) { - var url = this.getUrl("Plugins/SecurityInfo"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(info), - contentType: "application/json" - }) - }, ApiClient.prototype.createUser = function(user) { - var url = this.getUrl("Users/New"); - return this.ajax({ - type: "POST", - url: url, - data: user, - dataType: "json" - }) - }, ApiClient.prototype.updateUser = function(user) { - if (!user) throw new Error("null user"); - var url = this.getUrl("Users/" + user.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(user), - contentType: "application/json" - }) - }, ApiClient.prototype.updateUserPolicy = function(userId, policy) { - if (!userId) throw new Error("null userId"); - if (!policy) throw new Error("null policy"); - var url = this.getUrl("Users/" + userId + "/Policy"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(policy), - contentType: "application/json" - }) - }, ApiClient.prototype.updateUserConfiguration = function(userId, configuration) { - if (!userId) throw new Error("null userId"); - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("Users/" + userId + "/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateScheduledTaskTriggers = function(id, triggers) { - if (!id) throw new Error("null id"); - if (!triggers) throw new Error("null triggers"); - var url = this.getUrl("ScheduledTasks/" + id + "/Triggers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(triggers), - contentType: "application/json" - }) - }, ApiClient.prototype.updatePluginConfiguration = function(id, configuration) { - if (!id) throw new Error("null Id"); - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("Plugins/" + id + "/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.getAncestorItems = function(itemId, userId) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Items/" + itemId + "/Ancestors", options); - return this.getJSON(url) - }, ApiClient.prototype.getItems = function(userId, options) { - var url; - return url = "string" === (typeof userId).toString().toLowerCase() ? this.getUrl("Users/" + userId + "/Items", options) : this.getUrl("Items", options), this.getJSON(url) - }, ApiClient.prototype.getResumableItems = function(userId, options) { - return this.isMinServerVersion("3.2.33") ? this.getJSON(this.getUrl("Users/" + userId + "/Items/Resume", options)) : this.getItems(userId, Object.assign({ - SortBy: "DatePlayed", - SortOrder: "Descending", - Filters: "IsResumable", - Recursive: !0, - CollapseBoxSetItems: !1, - ExcludeLocationTypes: "Virtual" - }, options)) - }, ApiClient.prototype.getMovieRecommendations = function(options) { - return this.getJSON(this.getUrl("Movies/Recommendations", options)) - }, ApiClient.prototype.getUpcomingEpisodes = function(options) { - return this.getJSON(this.getUrl("Shows/Upcoming", options)) - }, ApiClient.prototype.getUserViews = function(options, userId) { - options = options || {}; - var url = this.getUrl("Users/" + (userId || this.getCurrentUserId()) + "/Views", options); - return this.getJSON(url) - }, ApiClient.prototype.getArtists = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Artists", options); - return this.getJSON(url) - }, ApiClient.prototype.getAlbumArtists = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Artists/AlbumArtists", options); - return this.getJSON(url) - }, ApiClient.prototype.getGenres = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Genres", options); - return this.getJSON(url) - }, ApiClient.prototype.getMusicGenres = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("MusicGenres", options); - return this.getJSON(url) - }, ApiClient.prototype.getPeople = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Persons", options); - return this.getJSON(url) - }, ApiClient.prototype.getStudios = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Studios", options); - return this.getJSON(url) - }, ApiClient.prototype.getLocalTrailers = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/LocalTrailers"); - return this.getJSON(url) - }, ApiClient.prototype.getAdditionalVideoParts = function(userId, itemId) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Videos/" + itemId + "/AdditionalParts", options); - return this.getJSON(url) - }, ApiClient.prototype.getThemeMedia = function(userId, itemId, inherit) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId), options.InheritFromParent = inherit || !1; - var url = this.getUrl("Items/" + itemId + "/ThemeMedia", options); - return this.getJSON(url) - }, ApiClient.prototype.getSearchHints = function(options) { - var url = this.getUrl("Search/Hints", options), - serverId = this.serverId(); - return this.getJSON(url).then(function(result) { - return result.SearchHints.forEach(function(i) { - i.ServerId = serverId - }), result - }) - }, ApiClient.prototype.getSpecialFeatures = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/SpecialFeatures"); - return this.getJSON(url) - }, ApiClient.prototype.getDateParamValue = function(date) { - function formatDigit(i) { - return i < 10 ? "0" + i : i - } - var d = date; - return "" + d.getFullYear() + formatDigit(d.getMonth() + 1) + formatDigit(d.getDate()) + formatDigit(d.getHours()) + formatDigit(d.getMinutes()) + formatDigit(d.getSeconds()) - }, ApiClient.prototype.markPlayed = function(userId, itemId, date) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var options = {}; - date && (options.DatePlayed = this.getDateParamValue(date)); - var url = this.getUrl("Users/" + userId + "/PlayedItems/" + itemId, options); - return this.ajax({ - type: "POST", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.markUnplayed = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/PlayedItems/" + itemId); - return this.ajax({ - type: "DELETE", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.updateFavoriteStatus = function(userId, itemId, isFavorite) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/FavoriteItems/" + itemId), - method = isFavorite ? "POST" : "DELETE"; - return this.ajax({ - type: method, - url: url, - dataType: "json" - }) - }, ApiClient.prototype.updateUserItemRating = function(userId, itemId, likes) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/Rating", { - likes: likes - }); - return this.ajax({ - type: "POST", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.getItemCounts = function(userId) { - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Items/Counts", options); - return this.getJSON(url) - }, ApiClient.prototype.clearUserItemRating = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/Rating"); - return this.ajax({ - type: "DELETE", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.reportPlaybackStart = function(options) { - if (!options) throw new Error("null options"); - this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, stopBitrateDetection(this); - var url = this.getUrl("Sessions/Playing"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.reportPlaybackProgress = function(options) { - if (!options) throw new Error("null options"); - var newPositionTicks = options.PositionTicks; - if ("timeupdate" === (options.EventName || "timeupdate")) { - var now = (new Date).getTime(), - msSinceLastReport = now - (this.lastPlaybackProgressReport || 0); - if (msSinceLastReport <= 1e4) { - if (!newPositionTicks) return Promise.resolve(); - var expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0); - if (Math.abs((newPositionTicks || 0) - expectedReportTicks) < 5e7) return Promise.resolve() - } - this.lastPlaybackProgressReport = now - } else this.lastPlaybackProgressReport = 0; - this.lastPlaybackProgressReportTicks = newPositionTicks; - var url = this.getUrl("Sessions/Playing/Progress"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.reportOfflineActions = function(actions) { - if (!actions) throw new Error("null actions"); - var url = this.getUrl("Sync/OfflineActions"); - return this.ajax({ - type: "POST", - data: JSON.stringify(actions), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.syncData = function(data) { - if (!data) throw new Error("null data"); - var url = this.getUrl("Sync/Data"); - return this.ajax({ - type: "POST", - data: JSON.stringify(data), - contentType: "application/json", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.getReadySyncItems = function(deviceId) { - if (!deviceId) throw new Error("null deviceId"); - var url = this.getUrl("Sync/Items/Ready", { - TargetId: deviceId - }); - return this.getJSON(url) - }, ApiClient.prototype.reportSyncJobItemTransferred = function(syncJobItemId) { - if (!syncJobItemId) throw new Error("null syncJobItemId"); - var url = this.getUrl("Sync/JobItems/" + syncJobItemId + "/Transferred"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.cancelSyncItems = function(itemIds, targetId) { - if (!itemIds) throw new Error("null itemIds"); - var url = this.getUrl("Sync/" + (targetId || this.deviceId()) + "/Items", { - ItemIds: itemIds.join(",") - }); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.reportPlaybackStopped = function(options) { - if (!options) throw new Error("null options"); - this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, redetectBitrate(this); - var url = this.getUrl("Sessions/Playing/Stopped"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.sendPlayCommand = function(sessionId, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!options) throw new Error("null options"); - var url = this.getUrl("Sessions/" + sessionId + "/Playing", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.sendCommand = function(sessionId, command) { - if (!sessionId) throw new Error("null sessionId"); - if (!command) throw new Error("null command"); - var url = this.getUrl("Sessions/" + sessionId + "/Command"), - ajaxOptions = { - type: "POST", - url: url - }; - return ajaxOptions.data = JSON.stringify(command), ajaxOptions.contentType = "application/json", this.ajax(ajaxOptions) - }, ApiClient.prototype.sendMessageCommand = function(sessionId, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!options) throw new Error("null options"); - var url = this.getUrl("Sessions/" + sessionId + "/Message"), - ajaxOptions = { - type: "POST", - url: url - }; - return ajaxOptions.data = JSON.stringify(options), ajaxOptions.contentType = "application/json", this.ajax(ajaxOptions) - }, ApiClient.prototype.sendPlayStateCommand = function(sessionId, command, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!command) throw new Error("null command"); - var url = this.getUrl("Sessions/" + sessionId + "/Playing/" + command, options || {}); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.createPackageReview = function(review) { - var url = this.getUrl("Packages/Reviews/" + review.id, review); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getPackageReviews = function(packageId, minRating, maxRating, limit) { - if (!packageId) throw new Error("null packageId"); - var options = {}; - minRating && (options.MinRating = minRating), maxRating && (options.MaxRating = maxRating), limit && (options.Limit = limit); - var url = this.getUrl("Packages/" + packageId + "/Reviews", options); - return this.getJSON(url) - }, ApiClient.prototype.getSavedEndpointInfo = function() { - return this._endPointInfo - }, ApiClient.prototype.getEndpointInfo = function() { - var savedValue = this._endPointInfo; - if (savedValue) return Promise.resolve(savedValue); - var instance = this; - return this.getJSON(this.getUrl("System/Endpoint")).then(function(endPointInfo) { - return setSavedEndpointInfo(instance, endPointInfo), endPointInfo - }) - }, ApiClient.prototype.getLatestItems = function(options) { - return options = options || {}, this.getJSON(this.getUrl("Users/" + this.getCurrentUserId() + "/Items/Latest", options)) - }, ApiClient.prototype.getFilters = function(options) { - return this.getJSON(this.getUrl("Items/Filters2", options)) - }, ApiClient.prototype.setSystemInfo = function(info) { - this._serverVersion = info.Version - }, ApiClient.prototype.serverVersion = function() { - return this._serverVersion - }, ApiClient.prototype.isMinServerVersion = function(version) { - var serverVersion = this.serverVersion(); - return !!serverVersion && compareVersions(serverVersion, version) >= 0 - }, ApiClient.prototype.handleMessageReceived = function(msg) { - onMessageReceivedInternal(this, msg) - }, ApiClient -}); diff --git a/src/libraries/apiclient/appStorage.js b/src/libraries/apiclient/appStorage.js deleted file mode 100644 index dacf38822f0..00000000000 --- a/src/libraries/apiclient/appStorage.js +++ /dev/null @@ -1,53 +0,0 @@ -define([], function() { - "use strict"; - - function onCachePutFail(e) { - console.error("cannot put to a cache: " + e); - } - - function updateCache(instance) { - if (instance.cache) { - instance.cache.put("data", new Response(JSON.stringify(instance.localData))).catch(onCachePutFail); - } - } - - function onCacheOpened(result) { - this.cache = result; - this.localData = {}; - } - - function MyStore() { - - this.setItem = function(name, value) { - localStorage.setItem(name, value); - - if (this.localData && this.localData[name] !== value) { - this.localData[name] = value; - updateCache(this); - } - }; - - this.getItem = function(name) { - return localStorage.getItem(name); - }; - - this.removeItem = function(name) { - localStorage.removeItem(name); - - if (this.localData) { - delete this.localData[name]; - updateCache(this); - } - }; - - try { - if (self.caches) { - self.caches.open("embydata").then(onCacheOpened.bind(this)); - } - } catch (err) { - console.error("error opening cache: " + err); - } - } - - return new MyStore; -}); diff --git a/src/libraries/apiclient/connectionmanager.js b/src/libraries/apiclient/connectionmanager.js deleted file mode 100644 index dc5ef406e5f..00000000000 --- a/src/libraries/apiclient/connectionmanager.js +++ /dev/null @@ -1,807 +0,0 @@ -define(["events", "apiclient", "appStorage"], function (events, apiClientFactory, appStorage) { - "use strict"; - - function getServerAddress(server, mode) { - switch (mode) { - case ConnectionMode.Local: - return server.LocalAddress; - - case ConnectionMode.Manual: - return server.ManualAddress; - - case ConnectionMode.Remote: - return server.RemoteAddress; - - default: - return server.ManualAddress || server.LocalAddress || server.RemoteAddress; - } - } - - function paramsToString(params) { - var values = []; - - for (var key in params) { - var value = params[key]; - - if (null !== value && void 0 !== value && "" !== value) { - values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); - } - } - - return values.join("&"); - } - - function resolveFailure(instance, resolve) { - resolve({ - State: "Unavailable", - }); - } - - function mergeServers(credentialProvider, list1, list2) { - for (var i = 0, length = list2.length; i < length; i++) { - credentialProvider.addOrUpdateServer(list1, list2[i]); - } - - return list1; - } - - function updateServerInfo(server, systemInfo) { - server.Name = systemInfo.ServerName; - if (systemInfo.Id) { - server.Id = systemInfo.Id; - } - - if (systemInfo.LocalAddress) { - server.LocalAddress = systemInfo.LocalAddress; - } - } - - function getEmbyServerUrl(baseUrl, handler) { - return baseUrl + "/" + handler; - } - - function getFetchPromise(request) { - var headers = request.headers || {}; - - if ("json" === request.dataType) { - headers.accept = "application/json"; - } - - var fetchRequest = { - headers: headers, - method: request.type, - credentials: "same-origin" - }; - var contentType = request.contentType; - - if (request.data) { - if ("string" == typeof request.data) { - 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; - } - - if (request.timeout) { - return fetchWithTimeout(request.url, fetchRequest, request.timeout); - } - - return fetch(request.url, fetchRequest); - } - - function fetchWithTimeout(url, options, timeoutMs) { - console.debug("fetchWithTimeout: timeoutMs: " + timeoutMs + ", url: " + url); - return new Promise(function (resolve, reject) { - var 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.error("fetchWithTimeout: timed out connecting to url: " + url); - reject(); - }); - }); - } - - function ajax(request) { - if (!request) { - throw new Error("Request cannot be null"); - } - - request.headers = request.headers || {}; - console.debug("ConnectionManager requesting url: " + request.url); - return getFetchPromise(request).then(function (response) { - console.debug("ConnectionManager response status: " + response.status + ", url: " + request.url); - - if (response.status < 400) { - if ("json" === request.dataType || "application/json" === request.headers.accept) { - return response.json(); - } - - return response; - } - - return Promise.reject(response); - }, function (err) { - console.error("ConnectionManager request failed to url: " + request.url); - throw err; - }); - } - - function replaceAll(originalString, strReplace, strWith) { - var reg = new RegExp(strReplace, "ig"); - return originalString.replace(reg, strWith); - } - - function normalizeAddress(address) { - address = address.trim(); - - if (0 !== address.toLowerCase().indexOf("http")) { - address = "http://" + address; - } - - address = replaceAll(address, "Http:", "http:"); - address = replaceAll(address, "Https:", "https:"); - - return address; - } - - function stringEqualsIgnoreCase(str1, str2) { - return (str1 || "").toLowerCase() === (str2 || "").toLowerCase(); - } - - function compareVersions(a, b) { - a = a.split("."); - b = b.split("."); - - for (var i = 0, length = Math.max(a.length, b.length); i < length; i++) { - var aVal = parseInt(a[i] || "0"); - var bVal = parseInt(b[i] || "0"); - - if (aVal < bVal) { - return -1; - } - - if (aVal > bVal) { - return 1; - } - } - - return 0; - } - - var defaultTimeout = 20000; - var ConnectionMode = { - Local: 0, - Remote: 1, - Manual: 2 - }; - - var ConnectionManager = function (credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) { - - function onAuthenticated(apiClient, result, options, saveCredentials) { - var credentials = credentialProvider.credentials(); - var servers = credentials.Servers.filter(function (s) { - return s.Id === result.ServerId; - }); - var server = servers.length ? servers[0] : apiClient.serverInfo(); - - if (false !== options.updateDateLastAccessed) { - server.DateLastAccessed = new Date().getTime(); - } - - server.Id = result.ServerId; - - if (saveCredentials) { - server.UserId = result.User.Id; - server.AccessToken = result.AccessToken; - } else { - server.UserId = null; - server.AccessToken = null; - } - - credentialProvider.addOrUpdateServer(credentials.Servers, server); - credentialProvider.credentials(credentials); - apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection; - apiClient.serverInfo(server); - afterConnected(apiClient, options); - return onLocalUserSignIn(server, apiClient.serverAddress(), result.User); - } - - function afterConnected(apiClient, options) { - options = options || {}; - - if (false !== options.reportCapabilities) { - apiClient.reportCapabilities(capabilities); - } - - apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection; - - if (false !== options.enableWebSocket) { - console.debug("calling apiClient.ensureWebSocket"); - apiClient.ensureWebSocket(); - } - } - - function onLocalUserSignIn(server, serverUrl, user) { - self._getOrAddApiClient(server, serverUrl); - - var promise = self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve(); - return promise.then(function () { - events.trigger(self, "localusersignedin", [user]) - }) - } - - function validateAuthentication(server, serverUrl) { - return ajax({ - type: "GET", - url: getEmbyServerUrl(serverUrl, "System/Info"), - dataType: "json", - headers: { - "X-MediaBrowser-Token": server.AccessToken - } - }).then(function (systemInfo) { - updateServerInfo(server, systemInfo); - return Promise.resolve(); - }, function () { - server.UserId = null; - server.AccessToken = null; - return Promise.resolve(); - }); - } - - function getImageUrl(localUser) { - if (localUser && localUser.PrimaryImageTag) { - return { - url: self.getApiClient(localUser).getUserImageUrl(localUser.Id, { - tag: localUser.PrimaryImageTag, - type: "Primary" - }), - supportsParams: true - }; - } - - return { - url: null, - supportsParams: false - }; - } - - function logoutOfServer(apiClient) { - var serverInfo = apiClient.serverInfo() || {}; - var logoutInfo = { - serverId: serverInfo.Id - }; - return apiClient.logout().then(function () { - events.trigger(self, "localusersignedout", [logoutInfo]); - }, function () { - events.trigger(self, "localusersignedout", [logoutInfo]); - }); - } - - function findServers() { - return new Promise(function (resolve, reject) { - var onFinish = function (foundServers) { - var servers = foundServers.map(function (foundServer) { - var info = { - Id: foundServer.Id, - LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address, - Name: foundServer.Name - }; - info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local; - return info; - }); - resolve(servers); - }; - - if (window.NativeShell && typeof window.NativeShell.findServers === 'function') { - window.NativeShell.findServers(1e3).then(onFinish, function () { - onFinish([]); - }); - } else { - resolve([]); - } - }); - } - - function convertEndpointAddressToManualAddress(info) { - if (info.Address && info.EndpointAddress) { - var address = info.EndpointAddress.split(":")[0]; - var parts = info.Address.split(":"); - - if (parts.length > 1) { - var portString = parts[parts.length - 1]; - - if (!isNaN(parseInt(portString))) { - address += ":" + portString; - } - } - - return normalizeAddress(address); - } - - return null; - } - - function getTryConnectPromise(url, connectionMode, state, resolve, reject) { - console.debug("getTryConnectPromise " + url); - ajax({ - url: getEmbyServerUrl(url, "system/info/public"), - timeout: defaultTimeout, - type: "GET", - dataType: "json" - }).then(function (result) { - if (!state.resolved) { - state.resolved = true; - console.debug("Reconnect succeeded to " + url); - resolve({ - url: url, - connectionMode: connectionMode, - data: result - }); - } - }, function () { - if (!state.resolved) { - console.error("Reconnect failed to " + url); - - if (++state.rejects >= state.numAddresses) { - reject(); - } - } - }); - } - - function tryReconnect(serverInfo) { - var addresses = []; - var addressesStrings = []; - - if (!serverInfo.manualAddressOnly && serverInfo.LocalAddress && -1 === addressesStrings.indexOf(serverInfo.LocalAddress)) { - addresses.push({ - url: serverInfo.LocalAddress, - mode: ConnectionMode.Local, - timeout: 0 - }); - addressesStrings.push(addresses[addresses.length - 1].url); - } - - if (serverInfo.ManualAddress && -1 === addressesStrings.indexOf(serverInfo.ManualAddress)) { - addresses.push({ - url: serverInfo.ManualAddress, - mode: ConnectionMode.Manual, - timeout: 100 - }); - addressesStrings.push(addresses[addresses.length - 1].url); - } - - if (!serverInfo.manualAddressOnly && serverInfo.RemoteAddress && -1 === addressesStrings.indexOf(serverInfo.RemoteAddress)) { - addresses.push({ - url: serverInfo.RemoteAddress, - mode: ConnectionMode.Remote, - timeout: 200 - }); - addressesStrings.push(addresses[addresses.length - 1].url); - } - - console.debug("tryReconnect: " + addressesStrings.join("|")); - return new Promise(function (resolve, reject) { - var state = {}; - state.numAddresses = addresses.length; - state.rejects = 0; - addresses.map(function (url) { - setTimeout(function () { - if (!state.resolved) { - getTryConnectPromise(url.url, url.mode, state, resolve, reject); - } - }, url.timeout); - }); - }); - } - - function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, options, resolve) { - var credentials = credentialProvider.credentials(); - options = options || {}; - - afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, true, options, resolve); - } - - function afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, options, resolve) { - options = options || {}; - if (false === options.enableAutoLogin) { - server.UserId = null; - server.AccessToken = null; - } else if (verifyLocalAuthentication && server.AccessToken && false !== options.enableAutoLogin) { - return void validateAuthentication(server, serverUrl).then(function () { - afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, false, options, resolve); - }); - } - - updateServerInfo(server, systemInfo); - server.LastConnectionMode = connectionMode; - - if (false !== options.updateDateLastAccessed) { - server.DateLastAccessed = new Date().getTime(); - } - - credentialProvider.addOrUpdateServer(credentials.Servers, server); - credentialProvider.credentials(credentials); - var result = { - Servers: [] - }; - result.ApiClient = self._getOrAddApiClient(server, serverUrl); - result.ApiClient.setSystemInfo(systemInfo); - result.State = server.AccessToken && false !== options.enableAutoLogin ? "SignedIn" : "ServerSignIn"; - result.Servers.push(server); - result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection; - result.ApiClient.updateServerInfo(server, serverUrl); - - var resolveActions = function () { - resolve(result); - events.trigger(self, "connected", [result]); - }; - - if ("SignedIn" === result.State) { - afterConnected(result.ApiClient, options); - result.ApiClient.getCurrentUser().then(function (user) { - onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions); - }, resolveActions); - } else { - resolveActions(); - } - } - - console.debug("Begin ConnectionManager constructor"); - var self = this; - this._apiClients = []; - self._minServerVersion = "3.2.33"; - - self.appVersion = function () { - return appVersion; - }; - - self.appName = function () { - return appName; - }; - - self.capabilities = function () { - return capabilities; - }; - - self.deviceId = function () { - return deviceId; - }; - - self.credentialProvider = function () { - return credentialProvider; - }; - - self.getServerInfo = function (id) { - return credentialProvider.credentials().Servers.filter(function (s) { - return s.Id === id; - })[0]; - }; - - self.getLastUsedServer = function () { - var servers = credentialProvider.credentials().Servers; - servers.sort(function (a, b) { - return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0); - }); - - if (servers.length) { - return servers[0]; - } - - return null; - }; - - self.addApiClient = function (apiClient) { - self._apiClients.push(apiClient); - - var existingServers = credentialProvider.credentials().Servers.filter(function (s) { - return stringEqualsIgnoreCase(s.ManualAddress, apiClient.serverAddress()) || stringEqualsIgnoreCase(s.LocalAddress, apiClient.serverAddress()) || stringEqualsIgnoreCase(s.RemoteAddress, apiClient.serverAddress()); - }); - var existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo(); - - existingServer.DateLastAccessed = new Date().getTime(); - existingServer.LastConnectionMode = ConnectionMode.Manual; - existingServer.ManualAddress = apiClient.serverAddress(); - if (apiClient.manualAddressOnly) { - existingServer.manualAddressOnly = true; - } - apiClient.serverInfo(existingServer); - apiClient.onAuthenticated = function (instance, result) { - return onAuthenticated(instance, result, {}, true); - }; - if (!existingServers.length) { - var credentials = credentialProvider.credentials(); - credentials.Servers = [existingServer]; - credentialProvider.credentials(credentials); - } - - events.trigger(self, "apiclientcreated", [apiClient]); - }; - - self.clearData = function () { - console.debug("connection manager clearing data"); - var credentials = credentialProvider.credentials(); - credentials.Servers = []; - credentialProvider.credentials(credentials); - }; - - self._getOrAddApiClient = function (server, serverUrl) { - var apiClient = self.getApiClient(server.Id); - - if (!apiClient) { - apiClient = new apiClientFactory(serverUrl, appName, appVersion, deviceName, deviceId); - self._apiClients.push(apiClient); - apiClient.serverInfo(server); - apiClient.onAuthenticated = function (instance, result) { - return onAuthenticated(instance, result, {}, true); - }; - - events.trigger(self, "apiclientcreated", [apiClient]); - } - - console.debug("returning instance from getOrAddApiClient"); - return apiClient; - }; - - self.getOrCreateApiClient = function (serverId) { - var credentials = credentialProvider.credentials(); - var servers = credentials.Servers.filter(function (s) { - return stringEqualsIgnoreCase(s.Id, serverId); - }); - - if (!servers.length) { - throw new Error("Server not found: " + serverId); - } - - var server = servers[0]; - return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode)); - }; - - self.user = function (apiClient) { - return new Promise(function (resolve, reject) { - function onLocalUserDone(e) { - if (apiClient && apiClient.getCurrentUserId()) { - apiClient.getCurrentUser().then(function (u) { - localUser = u; - var image = getImageUrl(localUser); - resolve({ - localUser: localUser, - name: localUser ? localUser.Name : null, - imageUrl: image.url, - supportsImageParams: image.supportsParams, - }); - }, onLocalUserDone); - } - } - var localUser; - if (apiClient && apiClient.getCurrentUserId()) { - onLocalUserDone(); - } - }); - }; - - self.logout = function () { - console.debug("begin connectionManager loguot"); - var promises = []; - - for (var i = 0, length = self._apiClients.length; i < length; i++) { - var apiClient = self._apiClients[i]; - - if (apiClient.accessToken()) { - promises.push(logoutOfServer(apiClient)); - } - } - - return Promise.all(promises).then(function () { - var credentials = credentialProvider.credentials(); - var servers = credentials.Servers.filter(function (u) { - return "Guest" !== u.UserLinkType; - }); - - for (var j = 0, numServers = servers.length; j < numServers; j++) { - var server = servers[j]; - server.UserId = null; - server.AccessToken = null; - server.ExchangeToken = null; - } - }); - }; - - self.getSavedServers = function () { - var credentials = credentialProvider.credentials(); - var servers = credentials.Servers.slice(0); - servers.sort(function (a, b) { - return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0); - }); - return servers; - }; - - self.getAvailableServers = function () { - console.debug("begin getAvailableServers"); - var credentials = credentialProvider.credentials(); - return Promise.all([findServers()]).then(function (responses) { - var foundServers = responses[0]; - var servers = credentials.Servers.slice(0); - mergeServers(credentialProvider, servers, foundServers); - servers.sort(function (a, b) { - return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0); - }); - credentials.Servers = servers; - credentialProvider.credentials(credentials); - return servers; - }); - }; - - self.connectToServers = function (servers, options) { - console.debug("begin connectToServers, with " + servers.length + " servers"); - var firstServer = servers.length ? servers[0] : null; - - if (firstServer) { - return self.connectToServer(firstServer, options).then(function (result) { - if ("Unavailable" === result.State) { - result.State = "ServerSelection"; - } - - console.debug("resolving connectToServers with result.State: " + result.State); - return result; - }); - } - - return Promise.resolve({ - Servers: servers, - State: "ServerSelection" - }); - }; - - self.connectToServer = function (server, options) { - console.debug("begin connectToServer"); - return new Promise(function (resolve, reject) { - options = options || {}; - tryReconnect(server).then(function (result) { - var serverUrl = result.url; - var connectionMode = result.connectionMode; - result = result.data; - - if (1 === compareVersions(self.minServerVersion(), result.Version)) { - console.debug("minServerVersion requirement not met. Server version: " + result.Version); - resolve({ - State: "ServerUpdateNeeded", - Servers: [server] - }); - } else { - if (server.Id && result.Id !== server.Id) { - console.debug("http request succeeded, but found a different server Id than what was expected"); - resolveFailure(self, resolve); - } else { - onSuccessfulConnection(server, result, connectionMode, serverUrl, options, resolve); - } - } - }, function () { - resolveFailure(self, resolve); - }); - }); - }; - - self.connectToAddress = function (address, options) { - function onFail() { - console.error("connectToAddress " + address + " failed"); - return Promise.resolve({ - State: "Unavailable", - }); - } - - if (!address) { - return Promise.reject(); - } - - address = normalizeAddress(address); - var instance = this; - var server = { - ManualAddress: address, - LastConnectionMode: ConnectionMode.Manual - }; - return self.connectToServer(server, options).catch(onFail); - }; - - self.deleteServer = function (serverId) { - if (!serverId) { - throw new Error("null serverId"); - } - - var server = credentialProvider.credentials().Servers.filter(function (s) { - return s.Id === serverId; - }); - server = server.length ? server[0] : null; - return new Promise(function (resolve, reject) { - function onDone() { - var credentials = credentialProvider.credentials(); - credentials.Servers = credentials.Servers.filter(function (s) { - return s.Id !== serverId; - }); - credentialProvider.credentials(credentials); - resolve(); - } - - if (!server.ConnectServerId) { - return void onDone(); - } - }); - }; - }; - - ConnectionManager.prototype.connect = function (options) { - console.debug("begin connect"); - var instance = this; - return instance.getAvailableServers().then(function (servers) { - return instance.connectToServers(servers, options); - }); - }; - - ConnectionManager.prototype.getApiClients = function () { - var servers = this.getSavedServers(); - - for (var i = 0, length = servers.length; i < length; i++) { - var server = servers[i]; - - if (server.Id) { - this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode)); - } - } - - return this._apiClients; - }; - - ConnectionManager.prototype.getApiClient = function (item) { - if (!item) { - throw new Error("item or serverId cannot be null"); - } - - if (item.ServerId) { - item = item.ServerId; - } - - return this._apiClients.filter(function (a) { - var serverInfo = a.serverInfo(); - return !serverInfo || serverInfo.Id === item; - })[0]; - }; - - ConnectionManager.prototype.minServerVersion = function (val) { - if (val) { - this._minServerVersion = val; - } - - return this._minServerVersion; - }; - - ConnectionManager.prototype.handleMessageReceived = function (msg) { - var serverId = msg.ServerId; - - if (serverId) { - var apiClient = this.getApiClient(serverId); - - if (apiClient) { - if ("string" == typeof msg.Data) { - try { - msg.Data = JSON.parse(msg.Data); - } catch (err) {} - } - - apiClient.handleMessageReceived(msg); - } - } - }; - - return ConnectionManager; -}); diff --git a/src/libraries/apiclient/credentialprovider.js b/src/libraries/apiclient/credentialprovider.js deleted file mode 100644 index 4ae809cace2..00000000000 --- a/src/libraries/apiclient/credentialprovider.js +++ /dev/null @@ -1,29 +0,0 @@ -define(["events", "appStorage"], function(events, appStorage) { - "use strict"; - - function ensure(instance, data) { - if (!instance._credentials) { - var json = appStorage.getItem(instance.key) || "{}"; - console.debug("credentials initialized with: " + json), instance._credentials = JSON.parse(json), instance._credentials.Servers = instance._credentials.Servers || [] - } - } - - function set(instance, data) { - data ? (instance._credentials = data, appStorage.setItem(instance.key, JSON.stringify(data))) : instance.clear(), events.trigger(instance, "credentialsupdated") - } - - function Credentials(key) { - this.key = key || "jellyfin_credentials" - } - return Credentials.prototype.clear = function() { - this._credentials = null, appStorage.removeItem(this.key) - }, Credentials.prototype.credentials = function(data) { - return data && set(this, data), ensure(this), this._credentials - }, Credentials.prototype.addOrUpdateServer = function(list, server) { - if (!server.Id) throw new Error("Server.Id cannot be null or empty"); - var existing = list.filter(function(s) { - return s.Id === server.Id - })[0]; - return existing ? (existing.DateLastAccessed = Math.max(existing.DateLastAccessed || 0, server.DateLastAccessed || 0), existing.UserLinkType = server.UserLinkType, server.AccessToken && (existing.AccessToken = server.AccessToken, existing.UserId = server.UserId), server.ExchangeToken && (existing.ExchangeToken = server.ExchangeToken), server.RemoteAddress && (existing.RemoteAddress = server.RemoteAddress), server.ManualAddress && (existing.ManualAddress = server.ManualAddress), server.LocalAddress && (existing.LocalAddress = server.LocalAddress), server.Name && (existing.Name = server.Name), null != server.LastConnectionMode && (existing.LastConnectionMode = server.LastConnectionMode), server.ConnectServerId && (existing.ConnectServerId = server.ConnectServerId), existing) : (list.push(server), server) - }, Credentials -}); diff --git a/src/libraries/apiclient/events.js b/src/libraries/apiclient/events.js deleted file mode 100644 index c109ee9c48e..00000000000 --- a/src/libraries/apiclient/events.js +++ /dev/null @@ -1,30 +0,0 @@ -define([], function() { - "use strict"; - - function getCallbacks(obj, name) { - if (!obj) throw new Error("obj cannot be null!"); - obj._callbacks = obj._callbacks || {}; - var list = obj._callbacks[name]; - return list || (obj._callbacks[name] = [], list = obj._callbacks[name]), list - } - return { - on: function(obj, eventName, fn) { - getCallbacks(obj, eventName).push(fn) - }, - off: function(obj, eventName, fn) { - var list = getCallbacks(obj, eventName), - i = list.indexOf(fn); - 1 !== i && list.splice(i, 1) - }, - trigger: function(obj, eventName) { - var eventObject = { - type: eventName - }, - eventArgs = []; - eventArgs.push(eventObject); - for (var additionalArgs = arguments[2] || [], i = 0, length = additionalArgs.length; i < length; i++) eventArgs.push(additionalArgs[i]); - getCallbacks(obj, eventName).slice(0).forEach(function(c) { - c.apply(obj, eventArgs) - }) - } - } -}); diff --git a/src/libraries/apiclient/localassetmanager.js b/src/libraries/apiclient/localassetmanager.js deleted file mode 100644 index 6fb1d4722d2..00000000000 --- a/src/libraries/apiclient/localassetmanager.js +++ /dev/null @@ -1,406 +0,0 @@ -define(["filerepository", "itemrepository", "useractionrepository", "transfermanager"], function(filerepository, itemrepository, useractionrepository, transfermanager) { - "use strict"; - - function getLocalItem(serverId, itemId) { - return console.debug("localassetmanager: begin getLocalItem"), itemrepository.get(serverId, itemId) - } - - function recordUserAction(action) { - return action.Id = createGuid(), useractionrepository.set(action.Id, action) - } - - function getUserActions(serverId) { - return useractionrepository.getByServerId(serverId) - } - - function deleteUserAction(action) { - return useractionrepository.remove(action.Id) - } - - function deleteUserActions(actions) { - var results = []; - return actions.forEach(function(action) { - results.push(deleteUserAction(action)) - }), Promise.all(results) - } - - function getServerItems(serverId) { - return console.debug("localassetmanager: begin getServerItems"), itemrepository.getAll(serverId) - } - - function getItemsFromIds(serverId, ids) { - var actions = ids.map(function(id) { - var strippedId = stripStart(id, "local:"); - return getLocalItem(serverId, strippedId) - }); - return Promise.all(actions).then(function(items) { - var libItems = items.map(function(locItem) { - return locItem.Item - }); - return Promise.resolve(libItems) - }) - } - - function getViews(serverId, userId) { - return itemrepository.getServerItemTypes(serverId, userId).then(function(types) { - var item, list = []; - return types.indexOf("Audio") > -1 && (item = { - Name: "Music", - ServerId: serverId, - Id: "localview:MusicView", - Type: "MusicView", - CollectionType: "music", - IsFolder: !0 - }, list.push(item)), types.indexOf("Photo") > -1 && (item = { - Name: "Photos", - ServerId: serverId, - Id: "localview:PhotosView", - Type: "PhotosView", - CollectionType: "photos", - IsFolder: !0 - }, list.push(item)), types.indexOf("Episode") > -1 && (item = { - Name: "TV", - ServerId: serverId, - Id: "localview:TVView", - Type: "TVView", - CollectionType: "tvshows", - IsFolder: !0 - }, list.push(item)), types.indexOf("Movie") > -1 && (item = { - Name: "Movies", - ServerId: serverId, - Id: "localview:MoviesView", - Type: "MoviesView", - CollectionType: "movies", - IsFolder: !0 - }, list.push(item)), types.indexOf("Video") > -1 && (item = { - Name: "Videos", - ServerId: serverId, - Id: "localview:VideosView", - Type: "VideosView", - CollectionType: "videos", - IsFolder: !0 - }, list.push(item)), types.indexOf("MusicVideo") > -1 && (item = { - Name: "Music Videos", - ServerId: serverId, - Id: "localview:MusicVideosView", - Type: "MusicVideosView", - CollectionType: "videos", - IsFolder: !0 - }, list.push(item)), Promise.resolve(list) - }) - } - - function updateFiltersForTopLevelView(parentId, mediaTypes, includeItemTypes, query) { - switch (parentId) { - case "MusicView": - return query.Recursive ? includeItemTypes.push("Audio") : includeItemTypes.push("MusicAlbum"), !0; - case "PhotosView": - return query.Recursive ? includeItemTypes.push("Photo") : includeItemTypes.push("PhotoAlbum"), !0; - case "TVView": - return query.Recursive ? includeItemTypes.push("Episode") : includeItemTypes.push("Series"), !0; - case "VideosView": - return query.Recursive, includeItemTypes.push("Video"), !0; - case "MoviesView": - return query.Recursive, includeItemTypes.push("Movie"), !0; - case "MusicVideosView": - return query.Recursive, includeItemTypes.push("MusicVideo"), !0 - } - return !1 - } - - function normalizeId(id) { - return id ? (id = stripStart(id, "localview:"), id = stripStart(id, "local:")) : null - } - - function normalizeIdList(val) { - return val ? val.split(",").map(normalizeId) : [] - } - - function shuffle(array) { - for (var temporaryValue, randomIndex, currentIndex = array.length; 0 !== currentIndex;) randomIndex = Math.floor(Math.random() * currentIndex), currentIndex -= 1, temporaryValue = array[currentIndex], array[currentIndex] = array[randomIndex], array[randomIndex] = temporaryValue; - return array - } - - function sortItems(items, query) { - if (!query.SortBy || 0 === query.SortBy.length) return items; - if ("Random" === query.SortBy) return shuffle(items); - var sortSpec = getSortSpec(query); - return items.sort(function(a, b) { - for (var i = 0; i < sortSpec.length; i++) { - var result = compareValues(a, b, sortSpec[i].Field, sortSpec[i].OrderDescending); - if (0 !== result) return result - } - return 0 - }), items - } - - function compareValues(a, b, field, orderDesc) { - if (!a.hasOwnProperty(field) || !b.hasOwnProperty(field)) return 0; - var valA = a[field], - valB = b[field], - result = 0; - return "string" == typeof valA || "string" == typeof valB ? (valA = valA || "", valB = valB || "", result = valA.toLowerCase().localeCompare(valB.toLowerCase())) : valA > valB ? result = 1 : valA < valB && (result = -1), orderDesc && (result *= -1), result - } - - function getSortSpec(query) { - for (var sortFields = (query.SortBy || "").split(","), sortOrders = (query.SortOrder || "").split(","), sortSpec = [], i = 0; i < sortFields.length; i++) { - var orderDesc = !1; - i < sortOrders.length && -1 !== sortOrders[i].toLowerCase().indexOf("desc") && (orderDesc = !0), sortSpec.push({ - Field: sortFields[i], - OrderDescending: orderDesc - }) - } - return sortSpec - } - - function getViewItems(serverId, userId, options) { - var searchParentId = options.ParentId; - searchParentId = normalizeId(searchParentId); - var seasonId = normalizeId(options.SeasonId || options.seasonId), - seriesId = normalizeId(options.SeriesId || options.seriesId), - albumIds = normalizeIdList(options.AlbumIds || options.albumIds), - includeItemTypes = options.IncludeItemTypes ? options.IncludeItemTypes.split(",") : [], - filters = options.Filters ? options.Filters.split(",") : [], - mediaTypes = options.MediaTypes ? options.MediaTypes.split(",") : []; - return updateFiltersForTopLevelView(searchParentId, mediaTypes, includeItemTypes, options) && (searchParentId = null), getServerItems(serverId).then(function(items) { - var itemsMap = new Map, - subtreeIdSet = new Set; - if (items.forEach(function(item) { - item.Item.LocalChildren = [], itemsMap.set(item.Item.Id, item.Item) - }), itemsMap.forEach(function(item, ignored, ignored2) { - if (item.ParentId && itemsMap.has(item.ParentId)) { - itemsMap.get(item.ParentId).LocalChildren.push(item) - } - }), options.Recursive && searchParentId && itemsMap.has(searchParentId)) { - var addSubtreeIds = function(recurseItem) { - subtreeIdSet.has(recurseItem.Id) || subtreeIdSet.add(recurseItem.Id), recurseItem.LocalChildren.forEach(function(childItem) { - addSubtreeIds(childItem) - }) - }, - searchParentItem = itemsMap.get(searchParentId); - addSubtreeIds(searchParentItem) - } - var resultItems = items.filter(function(item) { - return (!item.SyncStatus || "synced" === item.SyncStatus) && ((!mediaTypes.length || -1 !== mediaTypes.indexOf(item.Item.MediaType || "")) && ((!seriesId || item.Item.SeriesId === seriesId) && ((!seasonId || item.Item.SeasonId === seasonId) && ((!albumIds.length || -1 !== albumIds.indexOf(item.Item.AlbumId || "")) && ((!item.Item.IsFolder || -1 === filters.indexOf("IsNotFolder")) && (!(!item.Item.IsFolder && -1 !== filters.indexOf("IsFolder")) && ((!includeItemTypes.length || -1 !== includeItemTypes.indexOf(item.Item.Type || "")) && (!searchParentId || (options.Recursive ? subtreeIdSet.has(item.Item.Id) : item.Item.ParentId === searchParentId))))))))) - }).map(function(item2) { - return item2.Item - }); - return resultItems = sortItems(resultItems, options), options.Limit && (resultItems = resultItems.slice(0, options.Limit)), Promise.resolve(resultItems) - }) - } - - function removeObsoleteContainerItems(serverId) { - return getServerItems(serverId).then(function(items) { - var seriesItems = items.filter(function(item) { - return "series" === (item.Item.Type || "").toLowerCase() - }), - seasonItems = items.filter(function(item) { - return "season" === (item.Item.Type || "").toLowerCase() - }), - albumItems = items.filter(function(item) { - var type = (item.Item.Type || "").toLowerCase(); - return "musicalbum" === type || "photoalbum" === type - }), - requiredSeriesIds = items.filter(function(item) { - return "episode" === (item.Item.Type || "").toLowerCase() - }).map(function(item2) { - return item2.Item.SeriesId - }).filter(filterDistinct), - requiredSeasonIds = items.filter(function(item) { - return "episode" === (item.Item.Type || "").toLowerCase() - }).map(function(item2) { - return item2.Item.SeasonId - }).filter(filterDistinct), - requiredAlbumIds = items.filter(function(item) { - var type = (item.Item.Type || "").toLowerCase(); - return "audio" === type || "photo" === type - }).map(function(item2) { - return item2.Item.AlbumId - }).filter(filterDistinct), - obsoleteItems = []; - seriesItems.forEach(function(item) { - requiredSeriesIds.indexOf(item.Item.Id) < 0 && obsoleteItems.push(item) - }), seasonItems.forEach(function(item) { - requiredSeasonIds.indexOf(item.Item.Id) < 0 && obsoleteItems.push(item) - }), albumItems.forEach(function(item) { - requiredAlbumIds.indexOf(item.Item.Id) < 0 && obsoleteItems.push(item) - }); - var p = Promise.resolve(); - return obsoleteItems.forEach(function(item) { - p = p.then(function() { - return itemrepository.remove(item.ServerId, item.Id) - }) - }), p - }) - } - - function removeLocalItem(localItem) { - return itemrepository.get(localItem.ServerId, localItem.Id).then(function(item) { - var onFileDeletedSuccessOrFail = function() { - return itemrepository.remove(localItem.ServerId, localItem.Id) - }, - p = Promise.resolve(); - return item.LocalPath && (p = p.then(function() { - return filerepository.deleteFile(item.LocalPath) - })), item && item.Item && item.Item.MediaSources && item.Item.MediaSources.forEach(function(mediaSource) { - mediaSource.MediaStreams && mediaSource.MediaStreams.length > 0 && mediaSource.MediaStreams.forEach(function(mediaStream) { - mediaStream.Path && (p = p.then(function() { - return filerepository.deleteFile(mediaStream.Path) - })) - }) - }), p.then(onFileDeletedSuccessOrFail, onFileDeletedSuccessOrFail) - }, function(item) { - return Promise.resolve() - }) - } - - function addOrUpdateLocalItem(localItem) { - return itemrepository.set(localItem.ServerId, localItem.Id, localItem) - } - - function getSubtitleSaveFileName(localItem, mediaPath, language, isForced, format) { - var name = getNameWithoutExtension(mediaPath); - name = filerepository.getValidFileName(name), language && (name += "." + language.toLowerCase()), isForced && (name += ".foreign"), name = name + "." + format.toLowerCase(); - var mediaFolder = filerepository.getParentPath(localItem.LocalPath); - return filerepository.combinePath(mediaFolder, name) - } - - function getItemFileSize(path) { - return filerepository.getItemFileSize(path) - } - - function getNameWithoutExtension(path) { - var fileName = path, - pos = fileName.lastIndexOf("."); - return pos > 0 && (fileName = fileName.substring(0, pos)), fileName - } - - function downloadFile(url, localItem) { - var imageUrl = getImageUrl(localItem.Item.ServerId, localItem.Item.Id, { - type: "Primary", - index: 0 - }); - return transfermanager.downloadFile(url, localItem, imageUrl) - } - - function downloadSubtitles(url, fileName) { - return transfermanager.downloadSubtitles(url, fileName) - } - - function getImageUrl(serverId, itemId, imageOptions) { - var imageType = imageOptions.type, - index = imageOptions.index, - pathArray = getImagePath(serverId, itemId, imageType, index); - return filerepository.getImageUrl(pathArray) - } - - function hasImage(serverId, itemId, imageType, index) { - var pathArray = getImagePath(serverId, itemId, imageType, index), - localFilePath = filerepository.getFullMetadataPath(pathArray); - return filerepository.fileExists(localFilePath).then(function(exists) { - return Promise.resolve(exists) - }, function(err) { - return Promise.resolve(!1) - }) - } - - function fileExists(localFilePath) { - return filerepository.fileExists(localFilePath) - } - - function downloadImage(localItem, url, serverId, itemId, imageType, index) { - var localPathParts = getImagePath(serverId, itemId, imageType, index); - return transfermanager.downloadImage(url, localPathParts) - } - - function isDownloadFileInQueue(path) { - return transfermanager.isDownloadFileInQueue(path) - } - - function getDownloadItemCount() { - return transfermanager.getDownloadItemCount() - } - - function getDirectoryPath(item) { - var parts = [], - itemtype = item.Type.toLowerCase(), - mediaType = (item.MediaType || "").toLowerCase(); - "episode" === itemtype || "series" === itemtype || "season" === itemtype ? parts.push("TV") : "video" === mediaType ? parts.push("Videos") : "audio" === itemtype || "musicalbum" === itemtype || "musicartist" === itemtype ? parts.push("Music") : "photo" === itemtype || "photoalbum" === itemtype ? parts.push("Photos") : null; - var albumArtist = item.AlbumArtist; - albumArtist && parts.push(albumArtist); - var seriesName = item.SeriesName; - seriesName && parts.push(seriesName); - var seasonName = item.SeasonName; - seasonName && parts.push(seasonName), item.Album && parts.push(item.Album), ("video" === mediaType && "episode" !== itemtype || item.IsFolder) && parts.push(item.Name); - for (var finalParts = [], i = 0; i < parts.length; i++) finalParts.push(filerepository.getValidFileName(parts[i])); - return finalParts - } - - function getImagePath(serverId, itemId, imageType, index) { - var parts = []; - parts.push("images"), index = index || 0, parts.push(itemId + "_" + imageType + "_" + index.toString()); - for (var finalParts = [], i = 0; i < parts.length; i++) finalParts.push(parts[i]); - return finalParts - } - - function getLocalFileName(item, originalFileName) { - var filename = originalFileName || item.Name; - return filerepository.getValidFileName(filename) - } - - function resyncTransfers() { - return transfermanager.resyncTransfers() - } - - function createGuid() { - var d = (new Date).getTime(); - return window.performance && "function" == typeof window.performance.now && (d += performance.now()), "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - var r = (d + 16 * Math.random()) % 16 | 0; - return d = Math.floor(d / 16), ("x" === c ? r : 3 & r | 8).toString(16) - }) - } - - function startsWith(str, find) { - return !!(str && find && str.length > find.length && 0 === str.indexOf(find)) - } - - function stripStart(str, find) { - return startsWith(str, find) ? str.substr(find.length) : str - } - - function filterDistinct(value, index, self) { - return self.indexOf(value) === index - } - - function enableBackgroundCompletion() { - return transfermanager.enableBackgroundCompletion - } - return { - getLocalItem: getLocalItem, - getDirectoryPath: getDirectoryPath, - getLocalFileName: getLocalFileName, - recordUserAction: recordUserAction, - getUserActions: getUserActions, - deleteUserAction: deleteUserAction, - deleteUserActions: deleteUserActions, - removeLocalItem: removeLocalItem, - addOrUpdateLocalItem: addOrUpdateLocalItem, - downloadFile: downloadFile, - downloadSubtitles: downloadSubtitles, - hasImage: hasImage, - downloadImage: downloadImage, - getImageUrl: getImageUrl, - getSubtitleSaveFileName: getSubtitleSaveFileName, - getServerItems: getServerItems, - getItemFileSize: getItemFileSize, - isDownloadFileInQueue: isDownloadFileInQueue, - getDownloadItemCount: getDownloadItemCount, - getViews: getViews, - getViewItems: getViewItems, - resyncTransfers: resyncTransfers, - getItemsFromIds: getItemsFromIds, - removeObsoleteContainerItems: removeObsoleteContainerItems, - fileExists: fileExists, - enableBackgroundCompletion: enableBackgroundCompletion - } -}); diff --git a/src/libraries/apiclient/package.json b/src/libraries/apiclient/package.json deleted file mode 100644 index 67f9d497561..00000000000 --- a/src/libraries/apiclient/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "main": "apiclient.js" -} diff --git a/src/libraries/apiclient/sync/filerepository.js b/src/libraries/apiclient/sync/filerepository.js deleted file mode 100644 index 5b7088af31d..00000000000 --- a/src/libraries/apiclient/sync/filerepository.js +++ /dev/null @@ -1,45 +0,0 @@ -define([], function() { - "use strict"; - - function getValidFileName(path) { - return path - } - - function getFullLocalPath(pathArray) { - return pathArray.join("/") - } - - function getPathFromArray(pathArray) { - return pathArray.join("/") - } - - function deleteFile(path) { - return Promise.resolve() - } - - function deleteDirectory(path) { - return Promise.resolve() - } - - function fileExists(path) { - return Promise.resolve() - } - - function getItemFileSize(path) { - return Promise.resolve(0) - } - - function getImageUrl(pathParts) { - return pathParts.join("/") - } - return { - getValidFileName: getValidFileName, - getFullLocalPath: getFullLocalPath, - getPathFromArray: getPathFromArray, - deleteFile: deleteFile, - deleteDirectory: deleteDirectory, - fileExists: fileExists, - getItemFileSize: getItemFileSize, - getImageUrl: getImageUrl - } -}); diff --git a/src/libraries/apiclient/sync/itemrepository.js b/src/libraries/apiclient/sync/itemrepository.js deleted file mode 100644 index db1f9628763..00000000000 --- a/src/libraries/apiclient/sync/itemrepository.js +++ /dev/null @@ -1,123 +0,0 @@ -define([], function() { - "use strict"; - - function ServerDatabase(dbName, readyCallback) { - var request = indexedDB.open(dbName, dbVersion); - request.onerror = function(event) {}, request.onupgradeneeded = function(event) { - var db = event.target.result; - db.createObjectStore(dbName).transaction.oncomplete = function(event) { - readyCallback(db) - } - }, request.onsuccess = function(event) { - var db = event.target.result; - readyCallback(db) - } - } - - function getDbName(serverId) { - return "items_" + serverId - } - - function getDb(serverId, callback) { - var dbName = getDbName(serverId), - db = databases[dbName]; - if (db) return void callback(db); - new ServerDatabase(dbName, function(db) { - databases[dbName] = db, callback(db) - }) - } - - function getServerItemTypes(serverId, userId) { - return getAll(serverId, userId).then(function(all) { - return all.map(function(item2) { - return item2.Item.Type || "" - }).filter(filterDistinct) - }) - } - - function getAll(serverId, userId) { - return new Promise(function(resolve, reject) { - getDb(serverId, function(db) { - var request, storeName = getDbName(serverId), - transaction = db.transaction([storeName], "readonly"), - objectStore = transaction.objectStore(storeName); - if ("getAll" in objectStore) request = objectStore.getAll(null, 1e4), request.onsuccess = function(event) { - resolve(event.target.result) - }; - else { - var results = []; - request = objectStore.openCursor(), request.onsuccess = function(event) { - var cursor = event.target.result; - cursor ? (results.push(cursor.value), cursor.continue()) : resolve(results) - } - } - request.onerror = reject - }) - }) - } - - function get(serverId, key) { - return new Promise(function(resolve, reject) { - getDb(serverId, function(db) { - var storeName = getDbName(serverId), - transaction = db.transaction([storeName], "readonly"), - objectStore = transaction.objectStore(storeName), - request = objectStore.get(key); - request.onerror = reject, request.onsuccess = function(event) { - resolve(request.result) - } - }) - }) - } - - function set(serverId, key, val) { - return new Promise(function(resolve, reject) { - getDb(serverId, function(db) { - var storeName = getDbName(serverId), - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.put(val, key); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - - function remove(serverId, key) { - return new Promise(function(resolve, reject) { - getDb(serverId, function(db) { - var storeName = getDbName(serverId), - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.delete(key); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - - function clear(serverId) { - return new Promise(function(resolve, reject) { - getDb(serverId, function(db) { - var storeName = getDbName(serverId), - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.clear(); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - - function filterDistinct(value, index, self) { - return self.indexOf(value) === index - } - var indexedDB = self.indexedDB || self.mozIndexedDB || self.webkitIndexedDB || self.msIndexedDB, - dbVersion = (self.IDBTransaction || self.webkitIDBTransaction || self.msIDBTransaction, self.IDBKeyRange || self.webkitIDBKeyRange || self.msIDBKeyRange, 1), - databases = {}; - return { - get: get, - set: set, - remove: remove, - clear: clear, - getAll: getAll, - getServerItemTypes: getServerItemTypes - } -}); diff --git a/src/libraries/apiclient/sync/localsync.js b/src/libraries/apiclient/sync/localsync.js deleted file mode 100644 index 14e9ff9c0f2..00000000000 --- a/src/libraries/apiclient/sync/localsync.js +++ /dev/null @@ -1,17 +0,0 @@ -define(["connectionManager"], function(connectionManager) { - "use strict"; - var isSyncing; - return { - sync: function(options) { - return console.debug("localSync.sync starting..."), isSyncing ? Promise.resolve() : (isSyncing = !0, new Promise(function(resolve, reject) { - require(["multiserversync", "appSettings"], function(MultiServerSync, appSettings) { - options = options || {}, options.cameraUploadServers = appSettings.cameraUploadServers(), (new MultiServerSync).sync(connectionManager, options).then(function() { - isSyncing = null, resolve() - }, function(err) { - isSyncing = null, reject(err) - }) - }) - })) - } - } -}); diff --git a/src/libraries/apiclient/sync/mediasync.js b/src/libraries/apiclient/sync/mediasync.js deleted file mode 100644 index e6d9e781f36..00000000000 --- a/src/libraries/apiclient/sync/mediasync.js +++ /dev/null @@ -1,370 +0,0 @@ -define(["localassetmanager"], function(localassetmanager) { - "use strict"; - - function processDownloadStatus(apiClient, serverInfo, options) { - return console.debug("mediasync: begin processDownloadStatus"), localassetmanager.resyncTransfers().then(function() { - return localassetmanager.getServerItems(serverInfo.Id).then(function(items) { - console.debug("mediasync: begin processDownloadStatus getServerItems completed"); - var p = Promise.resolve(), - cnt = 0; - return items.filter(function(item) { - return "transferring" === item.SyncStatus || "queued" === item.SyncStatus - }).forEach(function(item) { - p = p.then(function() { - return reportTransfer(apiClient, item) - }), cnt++ - }), p.then(function() { - console.debug("mediasync: exit processDownloadStatus"); - console.debug("items reported: " + cnt.toString()); - return Promise.resolve(); - }) - }) - }) - } - - function reportTransfer(apiClient, item) { - return localassetmanager.getItemFileSize(item.LocalPath).then(function(size) { - return size > 0 ? apiClient.reportSyncJobItemTransferred(item.SyncJobItemId).then(function() { - return item.SyncStatus = "synced", console.debug("mediasync: reportSyncJobItemTransferred called for " + item.LocalPath), localassetmanager.addOrUpdateLocalItem(item) - }, function(error) { - return console.error("mediasync: mediasync error on reportSyncJobItemTransferred", error), item.SyncStatus = "error", localassetmanager.addOrUpdateLocalItem(item) - }) : localassetmanager.isDownloadFileInQueue(item.LocalPath).then(function(result) { - return result ? Promise.resolve() : (console.debug("mediasync: reportTransfer: Size is 0 and download no longer in queue. Deleting item."), localassetmanager.removeLocalItem(item).then(function() { - return console.debug("mediasync: reportTransfer: Item deleted."), Promise.resolve() - }, function(err2) { - return console.debug("mediasync: reportTransfer: Failed to delete item.", err2), Promise.resolve() - })) - }) - }, function(error) { - return console.error("mediasync: reportTransfer: error on getItemFileSize. Deleting item.", error), localassetmanager.removeLocalItem(item).then(function() { - return console.debug("mediasync: reportTransfer: Item deleted."), Promise.resolve() - }, function(err2) { - return console.error("mediasync: reportTransfer: Failed to delete item.", err2), Promise.resolve() - }) - }) - } - - function reportOfflineActions(apiClient, serverInfo) { - return console.debug("mediasync: begin reportOfflineActions"), localassetmanager.getUserActions(serverInfo.Id).then(function(actions) { - return actions.length ? apiClient.reportOfflineActions(actions).then(function() { - return localassetmanager.deleteUserActions(actions).then(function() { - return console.debug("mediasync: exit reportOfflineActions (actions reported and deleted.)"), Promise.resolve() - }) - }, function(err) { - return console.error("mediasync: error on apiClient.reportOfflineActions: " + err.toString()), localassetmanager.deleteUserActions(actions) - }) : (console.debug("mediasync: exit reportOfflineActions (no actions)"), Promise.resolve()) - }) - } - - function syncData(apiClient, serverInfo) { - return console.debug("mediasync: begin syncData"), localassetmanager.getServerItems(serverInfo.Id).then(function(items) { - var completedItems = items.filter(function(item) { - return item && ("synced" === item.SyncStatus || "error" === item.SyncStatus) - }), - request = { - TargetId: apiClient.deviceId(), - LocalItemIds: completedItems.map(function(xitem) { - return xitem.ItemId - }) - }; - return apiClient.syncData(request).then(function(result) { - return afterSyncData(apiClient, serverInfo, result).then(function() { - return console.debug("mediasync: exit syncData"), Promise.resolve() - }, function(err) { - return console.error("mediasync: error in syncData: " + err.toString()), Promise.resolve() - }) - }) - }) - } - - function afterSyncData(apiClient, serverInfo, syncDataResult) { - console.debug("mediasync: begin afterSyncData"); - var p = Promise.resolve(); - return syncDataResult.ItemIdsToRemove && syncDataResult.ItemIdsToRemove.length > 0 && syncDataResult.ItemIdsToRemove.forEach(function(itemId) { - p = p.then(function() { - return removeLocalItem(itemId, serverInfo.Id) - }) - }), p = p.then(function() { - return removeObsoleteContainerItems(serverInfo.Id) - }), p.then(function() { - return console.debug("mediasync: exit afterSyncData"), Promise.resolve() - }) - } - - function removeObsoleteContainerItems(serverId) { - return console.debug("mediasync: begin removeObsoleteContainerItems"), localassetmanager.removeObsoleteContainerItems(serverId) - } - - function removeLocalItem(itemId, serverId) { - return console.debug("mediasync: begin removeLocalItem"), localassetmanager.getLocalItem(serverId, itemId).then(function(item) { - return item ? localassetmanager.removeLocalItem(item) : Promise.resolve() - }, function(err2) { - return console.error("mediasync: removeLocalItem: Failed: ", err2), Promise.resolve() - }) - } - - function getNewMedia(apiClient, downloadCount) { - return console.debug("mediasync: begin getNewMedia"), apiClient.getReadySyncItems(apiClient.deviceId()).then(function(jobItems) { - console.debug("mediasync: getReadySyncItems returned " + jobItems.length + " items"); - var p = Promise.resolve(), - currentCount = downloadCount; - return jobItems.forEach(function(jobItem) { - currentCount++ <= 10 && (p = p.then(function() { - return getNewItem(jobItem, apiClient) - })) - }), p.then(function() { - return console.debug("mediasync: exit getNewMedia"), Promise.resolve() - }) - }, function(err) { - return console.error("mediasync: getReadySyncItems: Failed: ", err), Promise.resolve() - }) - } - - function afterMediaDownloaded(apiClient, jobItem, localItem) { - return console.debug("mediasync: begin afterMediaDownloaded"), getImages(apiClient, jobItem, localItem).then(function() { - var libraryItem = jobItem.Item; - return downloadParentItems(apiClient, jobItem, libraryItem).then(function() { - return getSubtitles(apiClient, jobItem, localItem) - }) - }) - } - - function createLocalItem(libraryItem, jobItem) { - console.debug("localassetmanager: begin createLocalItem"); - var item = { - Item: libraryItem, - ItemId: libraryItem.Id, - ServerId: libraryItem.ServerId, - Id: libraryItem.Id - }; - return jobItem && (item.SyncJobItemId = jobItem.SyncJobItemId), console.debug("localassetmanager: end createLocalItem"), item - } - - function getNewItem(jobItem, apiClient) { - console.debug("mediasync: begin getNewItem"); - var libraryItem = jobItem.Item; - return localassetmanager.getLocalItem(libraryItem.ServerId, libraryItem.Id).then(function(existingItem) { - if (existingItem && ("queued" === existingItem.SyncStatus || "transferring" === existingItem.SyncStatus || "synced" === existingItem.SyncStatus) && (console.debug("mediasync: getNewItem: getLocalItem found existing item"), localassetmanager.enableBackgroundCompletion())) return Promise.resolve(); - libraryItem.CanDelete = !1, libraryItem.CanDownload = !1, libraryItem.SupportsSync = !1, libraryItem.People = [], libraryItem.Chapters = [], libraryItem.Studios = [], libraryItem.SpecialFeatureCount = null, libraryItem.LocalTrailerCount = null, libraryItem.RemoteTrailers = []; - var localItem = createLocalItem(libraryItem, jobItem); - return localItem.SyncStatus = "queued", downloadMedia(apiClient, jobItem, localItem) - }) - } - - function downloadParentItems(apiClient, jobItem, libraryItem) { - var p = Promise.resolve(); - return libraryItem.SeriesId && (p = p.then(function() { - return downloadItem(apiClient, libraryItem.SeriesId) - })), libraryItem.SeasonId && (p = p.then(function() { - return downloadItem(apiClient, libraryItem.SeasonId).then(function(seasonItem) { - return libraryItem.SeasonPrimaryImageTag = (seasonItem.Item.ImageTags || {}).Primary, Promise.resolve() - }) - })), libraryItem.AlbumId && (p = p.then(function() { - return downloadItem(apiClient, libraryItem.AlbumId) - })), p - } - - function downloadItem(apiClient, itemId) { - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function(downloadedItem) { - downloadedItem.CanDelete = !1, downloadedItem.CanDownload = !1, downloadedItem.SupportsSync = !1, downloadedItem.People = [], downloadedItem.SpecialFeatureCount = null, downloadedItem.BackdropImageTags = null, downloadedItem.ParentBackdropImageTags = null, downloadedItem.ParentArtImageTag = null, downloadedItem.ParentLogoImageTag = null; - var localItem = createLocalItem(downloadedItem, null); - return localassetmanager.addOrUpdateLocalItem(localItem).then(function() { - return Promise.resolve(localItem) - }, function(err) { - return console.error("mediasync: downloadItem failed: " + err.toString()), Promise.resolve(null) - }) - }) - } - - function ensureLocalPathParts(localItem, jobItem) { - if (!localItem.LocalPathParts) { - var libraryItem = localItem.Item, - parts = localassetmanager.getDirectoryPath(libraryItem); - parts.push(localassetmanager.getLocalFileName(libraryItem, jobItem.OriginalFileName)), localItem.LocalPathParts = parts - } - } - - function downloadMedia(apiClient, jobItem, localItem) { - console.debug("mediasync: downloadMedia: start."); - var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/File", { - api_key: apiClient.accessToken() - }); - return ensureLocalPathParts(localItem, jobItem), localassetmanager.downloadFile(url, localItem).then(function(result) { - console.debug("mediasync: downloadMedia-downloadFile returned path: " + result.path); - var localPath = result.path, - libraryItem = localItem.Item; - if (localPath && libraryItem.MediaSources) - for (var i = 0; i < libraryItem.MediaSources.length; i++) { - var mediaSource = libraryItem.MediaSources[i]; - mediaSource.Path = localPath, mediaSource.Protocol = "File" - } - return localItem.LocalPath = localPath, localItem.SyncStatus = "transferring", localassetmanager.addOrUpdateLocalItem(localItem).then(function() { - return afterMediaDownloaded(apiClient, jobItem, localItem).then(function() { - return result.isComplete ? (localItem.SyncStatus = "synced", reportTransfer(apiClient, localItem)) : Promise.resolve() - }, function(err) { - return console.debug("mediasync: downloadMedia: afterMediaDownloaded failed: " + err), Promise.reject(err) - }) - }, function(err) { - return console.debug("mediasync: downloadMedia: addOrUpdateLocalItem failed: " + err), Promise.reject(err) - }) - }, function(err) { - return console.debug("mediasync: downloadMedia: localassetmanager.downloadFile failed: " + err), Promise.reject(err) - }) - } - - function getImages(apiClient, jobItem, localItem) { - console.debug("mediasync: begin getImages"); - var p = Promise.resolve(), - libraryItem = localItem.Item, - serverId = libraryItem.ServerId, - mainImageTag = (libraryItem.ImageTags || {}).Primary; - libraryItem.Id && mainImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.Id, mainImageTag, "Primary") - })); - var logoImageTag = (libraryItem.ImageTags || {}).Logo; - libraryItem.Id && logoImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.Id, logoImageTag, "Logo") - })); - var artImageTag = (libraryItem.ImageTags || {}).Art; - libraryItem.Id && artImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.Id, artImageTag, "Art") - })); - var bannerImageTag = (libraryItem.ImageTags || {}).Banner; - libraryItem.Id && bannerImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.Id, bannerImageTag, "Banner") - })); - var thumbImageTag = (libraryItem.ImageTags || {}).Thumb; - if (libraryItem.Id && thumbImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.Id, thumbImageTag, "Thumb") - })), libraryItem.Id && libraryItem.BackdropImageTags) - for (var i = 0; i < libraryItem.BackdropImageTags.length; i++); - return libraryItem.SeriesId && libraryItem.SeriesPrimaryImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesPrimaryImageTag, "Primary") - })), libraryItem.SeriesId && libraryItem.SeriesThumbImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesThumbImageTag, "Thumb") - })), libraryItem.SeasonId && libraryItem.SeasonPrimaryImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.SeasonId, libraryItem.SeasonPrimaryImageTag, "Primary") - })), libraryItem.AlbumId && libraryItem.AlbumPrimaryImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.AlbumId, libraryItem.AlbumPrimaryImageTag, "Primary") - })), libraryItem.ParentThumbItemId && libraryItem.ParentThumbImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.ParentThumbItemId, libraryItem.ParentThumbImageTag, "Thumb") - })), libraryItem.ParentPrimaryImageItemId && libraryItem.ParentPrimaryImageTag && (p = p.then(function() { - return downloadImage(localItem, apiClient, serverId, libraryItem.ParentPrimaryImageItemId, libraryItem.ParentPrimaryImageTag, "Primary") - })), p.then(function() { - return console.debug("mediasync: finished getImages"), localassetmanager.addOrUpdateLocalItem(localItem) - }, function(err) { - return console.error("mediasync: error getImages: " + err.toString()), Promise.resolve() - }) - } - - function downloadImage(localItem, apiClient, serverId, itemId, imageTag, imageType, index) { - return index = index || 0, localassetmanager.hasImage(serverId, itemId, imageType, index).then(function(hasImage) { - if (hasImage) return console.debug("mediasync: downloadImage - skip existing: " + itemId + " " + imageType + "_" + index.toString()), Promise.resolve(); - var maxWidth = 400; - "backdrop" === imageType && (maxWidth = null); - var imageUrl = apiClient.getScaledImageUrl(itemId, { - tag: imageTag, - type: imageType, - maxWidth: maxWidth, - api_key: apiClient.accessToken() - }); - return console.debug("mediasync: downloadImage " + itemId + " " + imageType + "_" + index.toString()), localassetmanager.downloadImage(localItem, imageUrl, serverId, itemId, imageType, index).then(function(result) { - return Promise.resolve(result) - }, function(err) { - return console.error("mediasync: error downloadImage: " + err.toString()), Promise.resolve() - }) - }, function(err) { - return console.error("mediasync: error downloadImage: " + err.toString()), Promise.resolve() - }) - } - - function getSubtitles(apiClient, jobItem, localItem) { - if (console.debug("mediasync: begin getSubtitles"), !jobItem.Item.MediaSources.length) return console.debug("mediasync: cannot download subtitles because video has no media source info."), Promise.resolve(); - var files = jobItem.AdditionalFiles.filter(function(f) { - return "Subtitles" === f.Type - }), - mediaSource = jobItem.Item.MediaSources[0], - p = Promise.resolve(); - return files.forEach(function(file) { - p = p.then(function() { - return getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource) - }) - }), p.then(function() { - return console.debug("mediasync: exit getSubtitles"), Promise.resolve() - }) - } - - function getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource) { - console.debug("mediasync: begin getItemSubtitle"); - var subtitleStream = mediaSource.MediaStreams.filter(function(m) { - return "Subtitle" === m.Type && m.Index === file.Index - })[0]; - if (!subtitleStream) return console.debug("mediasync: cannot download subtitles because matching stream info was not found."), Promise.resolve(); - var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/AdditionalFiles", { - Name: file.Name, - api_key: apiClient.accessToken() - }), - fileName = localassetmanager.getSubtitleSaveFileName(localItem, jobItem.OriginalFileName, subtitleStream.Language, subtitleStream.IsForced, subtitleStream.Codec); - return localassetmanager.downloadSubtitles(url, fileName).then(function(subtitleResult) { - return localItem.AdditionalFiles && localItem.AdditionalFiles.forEach(function(item) { - item.Name === file.Name && (item.Path = subtitleResult.path) - }), subtitleStream.Path = subtitleResult.path, subtitleStream.DeliveryMethod = "External", localassetmanager.addOrUpdateLocalItem(localItem) - }) - } - - function checkLocalFileExistence(apiClient, serverInfo, options) { - return options.checkFileExistence ? (console.debug("mediasync: begin checkLocalFileExistence"), localassetmanager.getServerItems(serverInfo.Id).then(function(items) { - var completedItems = items.filter(function(item) { - return item && ("synced" === item.SyncStatus || "error" === item.SyncStatus) - }), - p = Promise.resolve(); - return completedItems.forEach(function(completedItem) { - p = p.then(function() { - return localassetmanager.fileExists(completedItem.LocalPath).then(function(exists) { - return exists ? Promise.resolve() : localassetmanager.removeLocalItem(completedItem).then(function() { - return Promise.resolve() - }, function() { - return Promise.resolve() - }) - }) - }) - }), p - })) : Promise.resolve() - } - return function() { - var self = this; - "string" == typeof webWorkerBaseUrl && -1 !== webWorkerBaseUrl.indexOf("ms-appx://") ? self.sync = function(apiClient, serverInfo, options) { - return console.debug("mediasync: start sync"), checkLocalFileExistence(apiClient, serverInfo, options).then(function() { - return processDownloadStatus(apiClient, serverInfo, options).then(function() { - return localassetmanager.getDownloadItemCount().then(function(downloadCount) { - return !0 === options.syncCheckProgressOnly && downloadCount > 2 ? Promise.resolve() : reportOfflineActions(apiClient, serverInfo).then(function() { - return getNewMedia(apiClient, downloadCount).then(function() { - return syncData(apiClient, serverInfo).then(function() { - return console.debug("mediasync: Exit sync"), Promise.resolve() - }) - }) - }) - }) - }) - }, function(err) { - console.error(err.toString()) - }) - } : self.sync = function(apiClient, serverInfo, options) { - return console.debug("mediasync: Start sync"), checkLocalFileExistence(apiClient, serverInfo, options).then(function() { - return syncData(apiClient, serverInfo).then(function() { - return processDownloadStatus(apiClient, serverInfo, options).then(function() { - return localassetmanager.getDownloadItemCount().then(function(downloadCount) { - return !0 === options.syncCheckProgressOnly && downloadCount > 2 ? Promise.resolve() : reportOfflineActions(apiClient, serverInfo).then(function() { - return getNewMedia(apiClient, downloadCount).then(function() { - return syncData(apiClient, serverInfo) - }) - }) - }) - }) - }) - }, function(err) { - console.error(err.toString()) - }) - } - } -}); diff --git a/src/libraries/apiclient/sync/multiserversync.js b/src/libraries/apiclient/sync/multiserversync.js deleted file mode 100644 index ff01a5ad1d7..00000000000 --- a/src/libraries/apiclient/sync/multiserversync.js +++ /dev/null @@ -1,22 +0,0 @@ -define(["serversync"], function(ServerSync) { - "use strict"; - - function syncNext(connectionManager, servers, index, options, resolve, reject) { - var length = servers.length; - if (index >= length) return console.debug("MultiServerSync.sync complete"), void resolve(); - var server = servers[index]; - console.debug("Creating ServerSync to server: " + server.Id), (new ServerSync).sync(connectionManager, server, options).then(function() { - console.debug("ServerSync succeeded to server: " + server.Id), syncNext(connectionManager, servers, index + 1, options, resolve, reject) - }, function(err) { - console.error("ServerSync failed to server: " + server.Id + ". " + err), syncNext(connectionManager, servers, index + 1, options, resolve, reject) - }) - } - - function MultiServerSync() {} - return MultiServerSync.prototype.sync = function(connectionManager, options) { - return console.debug("MultiServerSync.sync starting..."), new Promise(function(resolve, reject) { - var servers = connectionManager.getSavedServers(); - syncNext(connectionManager, servers, 0, options, resolve, reject) - }) - }, MultiServerSync -}); diff --git a/src/libraries/apiclient/sync/serversync.js b/src/libraries/apiclient/sync/serversync.js deleted file mode 100644 index 887b829cee6..00000000000 --- a/src/libraries/apiclient/sync/serversync.js +++ /dev/null @@ -1,42 +0,0 @@ -define([], function() { - "use strict"; - - function performSync(connectionManager, server, options) { - console.debug("ServerSync.performSync to server: " + server.Id), options = options || {}; - var cameraUploadServers = options.cameraUploadServers || []; - console.debug("ServerSync cameraUploadServers: " + JSON.stringify(cameraUploadServers)); - var uploadPhotos = -1 !== cameraUploadServers.indexOf(server.Id); - return console.debug("ServerSync uploadPhotos: " + uploadPhotos), (uploadPhotos ? uploadContent(connectionManager, server, options) : Promise.resolve()).then(function() { - return syncMedia(connectionManager, server, options) - }) - } - - function uploadContent(connectionManager, server, options) { - return new Promise().reject(); - } - - function syncMedia(connectionManager, server, options) { - return new Promise(function(resolve, reject) { - require(["mediasync"], function(MediaSync) { - var apiClient = connectionManager.getApiClient(server.Id); - (new MediaSync).sync(apiClient, server, options).then(resolve, reject) - }) - }) - } - - function ServerSync() {} - return ServerSync.prototype.sync = function(connectionManager, server, options) { - if (!server.AccessToken && !server.ExchangeToken) return console.debug("Skipping sync to server " + server.Id + " because there is no saved authentication information."), Promise.resolve(); - var connectionOptions = { - updateDateLastAccessed: !1, - enableWebSocket: !1, - reportCapabilities: !1, - enableAutomaticBitrateDetection: !1 - }; - return connectionManager.connectToServer(server, connectionOptions).then(function(result) { - return "SignedIn" === result.State ? performSync(connectionManager, server, options) : (console.error("Unable to connect to server id: " + server.Id), Promise.reject()) - }, function(err) { - throw console.error("Unable to connect to server id: " + server.Id), err - }) - }, ServerSync -}); diff --git a/src/libraries/apiclient/sync/transfermanager.js b/src/libraries/apiclient/sync/transfermanager.js deleted file mode 100644 index d9889fffbd1..00000000000 --- a/src/libraries/apiclient/sync/transfermanager.js +++ /dev/null @@ -1,30 +0,0 @@ -define([], function() { - "use strict"; - - function downloadFile(url, folder, localItem, imageUrl) { - return Promise.reject() - } - - function downloadSubtitles(url, folder, fileName) { - return Promise.reject() - } - - function downloadImage(url, folder, fileName) { - return Promise.reject() - } - - function resyncTransfers() { - return Promise.resolve() - } - - function getDownloadItemCount() { - return Promise.resolve(0) - } - return { - downloadFile: downloadFile, - downloadSubtitles: downloadSubtitles, - downloadImage: downloadImage, - resyncTransfers: resyncTransfers, - getDownloadItemCount: getDownloadItemCount - } -}); diff --git a/src/libraries/apiclient/sync/useractionrepository.js b/src/libraries/apiclient/sync/useractionrepository.js deleted file mode 100644 index 7b33ba1c357..00000000000 --- a/src/libraries/apiclient/sync/useractionrepository.js +++ /dev/null @@ -1,108 +0,0 @@ -define([], function() { - "use strict"; - - function getDb(callback) { - var db = databaseInstance; - if (db) return void callback(db); - var request = indexedDB.open(dbName, dbVersion); - request.onerror = function(event) {}, request.onupgradeneeded = function(event) { - var db = event.target.result; - db.createObjectStore(dbName).transaction.oncomplete = function(event) { - callback(db) - } - }, request.onsuccess = function(event) { - var db = event.target.result; - callback(db) - } - } - - function getByServerId(serverId) { - return getAll().then(function(items) { - return items.filter(function(item) { - return item.ServerId === serverId - }) - }) - } - - function getAll() { - return new Promise(function(resolve, reject) { - getDb(function(db) { - var request, storeName = dbName, - transaction = db.transaction([storeName], "readonly"), - objectStore = transaction.objectStore(storeName); - if ("getAll" in objectStore) request = objectStore.getAll(null, 1e4), request.onsuccess = function(event) { - resolve(event.target.result) - }; - else { - var results = []; - request = objectStore.openCursor(), request.onsuccess = function(event) { - var cursor = event.target.result; - cursor ? (results.push(cursor.value), cursor.continue()) : resolve(results) - } - } - request.onerror = reject - }) - }) - } - - function get(key) { - return new Promise(function(resolve, reject) { - getDb(function(db) { - var storeName = dbName, - transaction = db.transaction([storeName], "readonly"), - objectStore = transaction.objectStore(storeName), - request = objectStore.get(key); - request.onerror = reject, request.onsuccess = function(event) { - resolve(request.result) - } - }) - }) - } - - function set(key, val) { - return new Promise(function(resolve, reject) { - getDb(function(db) { - var storeName = dbName, - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.put(val, key); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - - function remove(key) { - return new Promise(function(resolve, reject) { - getDb(function(db) { - var storeName = dbName, - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.delete(key); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - - function clear() { - return new Promise(function(resolve, reject) { - getDb(function(db) { - var storeName = dbName, - transaction = db.transaction([storeName], "readwrite"), - objectStore = transaction.objectStore(storeName), - request = objectStore.clear(); - request.onerror = reject, request.onsuccess = resolve - }) - }) - } - var databaseInstance, indexedDB = self.indexedDB || self.mozIndexedDB || self.webkitIndexedDB || self.msIndexedDB, - dbName = (self.IDBTransaction || self.webkitIDBTransaction || self.msIDBTransaction, self.IDBKeyRange || self.webkitIDBKeyRange || self.msIDBKeyRange, "useractions"), - dbVersion = 1; - return { - get: get, - set: set, - remove: remove, - clear: clear, - getAll: getAll, - getByServerId: getByServerId - } -}); diff --git a/src/components/navdrawer/navdrawer.css b/src/libraries/navdrawer/navdrawer.css similarity index 100% rename from src/components/navdrawer/navdrawer.css rename to src/libraries/navdrawer/navdrawer.css diff --git a/src/components/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js similarity index 99% rename from src/components/navdrawer/navdrawer.js rename to src/libraries/navdrawer/navdrawer.js index 9c15fbc1848..d9c246b406b 100644 --- a/src/components/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -260,7 +260,6 @@ define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, TouchMenuLA.prototype.showMask = function () { mask.classList.remove("hide"); - mask.offsetWidth; mask.classList.add("backdrop"); }; diff --git a/src/components/screensavermanager.js b/src/libraries/screensavermanager.js similarity index 100% rename from src/components/screensavermanager.js rename to src/libraries/screensavermanager.js diff --git a/src/components/scroller.js b/src/libraries/scroller.js similarity index 100% rename from src/components/scroller.js rename to src/libraries/scroller.js diff --git a/src/library.html b/src/library.html index 398613f9e62..1bca6072496 100644 --- a/src/library.html +++ b/src/library.html @@ -6,7 +6,7 @@ ${ButtonScanAllLibraries} - ${Help} + ${Help}
diff --git a/src/list.html b/src/list.html index 375653c12d0..464cd15530c 100644 --- a/src/list.html +++ b/src/list.html @@ -7,45 +7,45 @@ ${HeaderPlayAll}
diff --git a/src/livetv.html b/src/livetv.html index 9986529b7f1..fdf19bd915e 100644 --- a/src/livetv.html +++ b/src/livetv.html @@ -7,7 +7,7 @@
@@ -16,7 +16,7 @@

${TabShows}

- +

@@ -25,7 +25,7 @@

${HeaderMovies}

- +

@@ -34,7 +34,7 @@

${Sports}

- +

@@ -43,7 +43,7 @@

${HeaderForKids}

- +

@@ -52,7 +52,7 @@

${News}

- +

@@ -63,7 +63,7 @@

- +

@@ -72,7 +72,7 @@

diff --git a/src/livetvsettings.html b/src/livetvsettings.html index 1b1557889e1..02b960f08d4 100644 --- a/src/livetvsettings.html +++ b/src/livetvsettings.html @@ -3,8 +3,8 @@
-

DVR

- ${Help} +

${HeaderDVR}

+ ${Help}
@@ -34,7 +34,7 @@

DVR

- +
${LabelRecordingPathHelp}
@@ -43,7 +43,7 @@

DVR

- +
@@ -51,7 +51,7 @@

DVR

- +
@@ -84,7 +84,7 @@

${HeaderRecordingPostProcessing}

- +
diff --git a/src/livetvstatus.html b/src/livetvstatus.html index fc855f32b0f..3aa27637d05 100644 --- a/src/livetvstatus.html +++ b/src/livetvstatus.html @@ -8,9 +8,9 @@

${HeaderTunerDevices}

- ${Help} + ${Help}
@@ -21,7 +21,7 @@

${HeaderGuideProviders}

diff --git a/src/livetvtuner.html b/src/livetvtuner.html index fecbda90aa8..f45a3a1d042 100644 --- a/src/livetvtuner.html +++ b/src/livetvtuner.html @@ -5,7 +5,7 @@

${HeaderLiveTvTunerSetup}

- ${Help} + ${Help}
@@ -24,7 +24,7 @@

${HeaderLiveTvTunerSetup}

- + diff --git a/src/login.html b/src/login.html index 955ce3879e3..8e48901c117 100644 --- a/src/login.html +++ b/src/login.html @@ -8,7 +8,7 @@

${HeaderPleaseSignIn}

- +
diff --git a/src/movies.html b/src/movies.html index e552ca392f4..a2221c510d9 100644 --- a/src/movies.html +++ b/src/movies.html @@ -3,9 +3,9 @@
- - - + + +
@@ -46,8 +46,8 @@

${HeaderLatestMovies}
- - + +
@@ -62,7 +62,7 @@

${HeaderLatestMovies}
- +
@@ -74,9 +74,9 @@

${HeaderLatestMovies}
- - - + + +
diff --git a/src/music.html b/src/music.html index 9a284a5f8fb..1e22ae9f3a0 100644 --- a/src/music.html +++ b/src/music.html @@ -37,11 +37,11 @@

${HeaderFrequentlyPlayed
- - - - - + + + + +
@@ -56,8 +56,8 @@

${HeaderFrequentlyPlayed
- - + +
@@ -72,8 +72,8 @@

${HeaderFrequentlyPlayed
- - + +
@@ -92,8 +92,8 @@

${HeaderFrequentlyPlayed
- - + +
diff --git a/src/mypreferencesmenu.html b/src/mypreferencesmenu.html index 4219059dd03..2c3ca0edd98 100644 --- a/src/mypreferencesmenu.html +++ b/src/mypreferencesmenu.html @@ -5,7 +5,7 @@

- person +
${ButtonProfile}
@@ -14,7 +14,7 @@

- tv +
${HeaderDisplay}
@@ -23,7 +23,7 @@

- home +
${HeaderHome}
@@ -32,7 +32,7 @@

- +
${TitlePlayback}
@@ -41,7 +41,7 @@

- +
${Subtitles}
@@ -50,7 +50,7 @@

- +
${ClientSettings}
@@ -61,7 +61,7 @@

${HeaderAdmin}

- dashboard +
${TabDashboard}
@@ -69,7 +69,7 @@

${HeaderAdmin}

- +
${Metadata}
@@ -80,7 +80,7 @@

${HeaderAdmin}

${HeaderUser}

- wifi +
${HeaderSelectServer}
@@ -88,7 +88,7 @@

${HeaderUser}

- +
${ButtonSignOut}
diff --git a/src/networking.html b/src/networking.html index 1352e0ef5f3..74ddefb87b4 100644 --- a/src/networking.html +++ b/src/networking.html @@ -5,88 +5,110 @@
-
- -
${LanNetworksHelp}
-
-
- -
${LabelBindToLocalNetworkAddressHelp}
-
-
- -
${LabelLocalHttpServerPortNumberHelp}
-
-
- -
${LabelHttpsPortHelp}
-
+
+

${HeaderServerAddressSettings}

-
- -
${AllowRemoteAccessHelp}
-
-
- -
${AllowedRemoteAddressesHelp}
-
-
- -
-
- -
${LabelPublicHttpPortHelp}
-
-
- -
${LabelPublicHttpsPortHelp}
-
+
+ +
${LabelLocalHttpServerPortNumberHelp}
+
-
- -
${LabelBaseUrlHelp}
-
+
+ +
${LabelEnableHttpsHelp}
+
+ +
+ +
${LabelHttpsPortHelp}
+
+ +
+ +
${LabelBaseUrlHelp}
+
+ +
+ +
${LabelBindToLocalNetworkAddressHelp}
+
-
-
-
- +
+ +
${LanNetworksHelp}
+
+
+ +
+

${HeaderHttpsSettings}

+ +
+ +
${LabelRequireHttpsHelp}
+
+ +
+
+
+ +
+
- +
${LabelCustomCertificatePathHelp}
-
${LabelCustomCertificatePathHelp}
-
-
- -
${LabelCertificatePasswordHelp}
-
+
+ +
${LabelCertificatePasswordHelp}
+
+ -
- -
+
+

${HeaderRemoteAccessSettings}

-
- -
${LabelEnableAutomaticPortMapHelp}
-
+
+ +
${AllowRemoteAccessHelp}
+
+
+ +
${AllowedRemoteAddressesHelp}
+
+
+ +
+ +
+ +
${LabelEnableAutomaticPortMapHelp}
+
+
+ +
${LabelPublicHttpPortHelp}
+
+
+ +
${LabelPublicHttpsPortHelp}
+
+
+
+
+
+ +
+
+
- +
+ +
+ + - + - + - + - + - + - + + + -
-
-
/
-
+ + + + +
- - -
- -
+
-
+
-
-
@@ -155,21 +175,19 @@

- -
-
-
-

${TabPlaylist}

- + +
-
-
+
-
-
-
-
diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js new file mode 100644 index 00000000000..88bfa1f4b76 --- /dev/null +++ b/src/plugins/backdropScreensaver/plugin.js @@ -0,0 +1,52 @@ +/* eslint-disable indent */ +import connectionManager from 'connectionManager'; + +class BackdropScreensaver { + constructor() { + this.name = 'Backdrop ScreenSaver'; + this.type = 'screensaver'; + 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 + }; + + const apiClient = connectionManager.currentApiClient(); + apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { + + if (result.Items.length) { + + import('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; + } + } + } +/* eslint-enable indent */ + +export default BackdropScreensaver; diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js new file mode 100644 index 00000000000..5e8732e97fe --- /dev/null +++ b/src/plugins/bookPlayer/plugin.js @@ -0,0 +1,285 @@ +import connectionManager from 'connectionManager'; +import loading from 'loading'; +import keyboardnavigation from 'keyboardnavigation'; +import dialogHelper from 'dialogHelper'; +import events from 'events'; +import 'css!./style'; +import 'material-icons'; +import 'paper-icon-button-light'; + +import TableOfContents from './tableOfContents'; + +export class BookPlayer { + constructor() { + this.name = 'Book Player'; + this.type = 'mediaplayer'; + this.id = 'bookplayer'; + this.priority = 1; + + this.onDialogClosed = this.onDialogClosed.bind(this); + this.openTableOfContents = this.openTableOfContents.bind(this); + this.onWindowKeyUp = this.onWindowKeyUp.bind(this); + } + + play(options) { + this._progress = 0; + this._loaded = false; + + loading.show(); + let elem = this.createMediaElement(); + return this.setCurrentSrc(elem, options); + } + + stop() { + this.unbindEvents(); + + let elem = this._mediaElement; + let tocElement = this._tocElement; + let rendition = this._rendition; + + if (elem) { + dialogHelper.close(elem); + this._mediaElement = null; + } + + if (tocElement) { + tocElement.destroy(); + this._tocElement = null; + } + + if (rendition) { + rendition.destroy(); + } + + // Hide loader in case player was not fully loaded yet + loading.hide(); + this._cancellationToken.shouldCancel = true; + } + + currentItem() { + return this._currentItem; + } + + currentTime() { + return this._progress * 1000; + } + + duration() { + return 1000; + } + + getBufferedRanges() { + return [{ + start: 0, + end: 10000000 + }]; + } + + volume() { + return 100; + } + + isMuted() { + return false; + } + + paused() { + return false; + } + + seekable() { + return true; + } + + onWindowKeyUp(e) { + let key = keyboardnavigation.getKeyName(e); + let rendition = this._rendition; + let book = rendition.book; + + switch (key) { + case 'l': + case 'ArrowRight': + case 'Right': + if (this._loaded) { + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + } + break; + case 'j': + case 'ArrowLeft': + case 'Left': + if (this._loaded) { + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + } + break; + case 'Escape': + if (this._tocElement) { + // Close table of contents on ESC if it is open + this._tocElement.destroy(); + } else { + // Otherwise stop the entire book player + this.stop(); + } + break; + } + } + + onDialogClosed() { + this.stop(); + } + + bindMediaElementEvents() { + let elem = this._mediaElement; + + elem.addEventListener('close', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerToc').addEventListener('click', this.openTableOfContents); + } + + bindEvents() { + this.bindMediaElementEvents(); + + document.addEventListener('keyup', this.onWindowKeyUp); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus + this._rendition.on('keyup', this.onWindowKeyUp); + } + + unbindMediaElementEvents() { + let elem = this._mediaElement; + + elem.removeEventListener('close', this.onDialogClosed); + elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed); + elem.querySelector('.btnBookplayerToc').removeEventListener('click', this.openTableOfContents); + } + + unbindEvents() { + if (this._mediaElement) { + this.unbindMediaElementEvents(); + } + document.removeEventListener('keyup', this.onWindowKeyUp); + if (this._rendition) { + this._rendition.off('keyup', this.onWindowKeyUp); + } + } + + openTableOfContents() { + if (this._loaded) { + this._tocElement = new TableOfContents(this); + } + } + + createMediaElement() { + let elem = this._mediaElement; + + if (elem) { + return elem; + } + + elem = document.getElementById('bookPlayer'); + + if (!elem) { + elem = dialogHelper.createDialog({ + exitAnimationDuration: 400, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + elem.id = 'bookPlayer'; + + let html = ''; + html += '
'; + html += ''; + html += '
'; + html += '
'; + html += ''; + html += '
'; + + elem.innerHTML = html; + + dialogHelper.open(elem); + } + + this._mediaElement = elem; + + return elem; + } + + setCurrentSrc(elem, options) { + let item = options.items[0]; + this._currentItem = item; + this.streamInfo = { + started: true, + ended: false, + mediaSource: { + Id: item.Id + } + }; + + let serverId = item.ServerId; + let apiClient = connectionManager.getApiClient(serverId); + + return new Promise((resolve, reject) => { + require(['epubjs'], (epubjs) => { + let downloadHref = apiClient.getItemDownloadUrl(item.Id); + let book = epubjs.default(downloadHref, {openAs: 'epub'}); + let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + + this._currentSrc = downloadHref; + this._rendition = rendition; + let cancellationToken = { + shouldCancel: false + }; + this._cancellationToken = cancellationToken; + + return rendition.display().then(() => { + let epubElem = document.querySelector('.epub-container'); + epubElem.style.display = 'none'; + + this.bindEvents(); + + return this._rendition.book.locations.generate(1024).then(async () => { + if (cancellationToken.shouldCancel) { + return reject(); + } + + const percentageTicks = options.startPositionTicks / 10000000; + if (percentageTicks !== 0.0) { + const resumeLocation = book.locations.cfiFromPercentage(percentageTicks); + await rendition.display(resumeLocation); + } + + this._loaded = true; + epubElem.style.display = 'block'; + rendition.on('relocated', (locations) => { + this._progress = book.locations.percentageFromCfi(locations.start.cfi); + + events.trigger(this, 'timeupdate'); + }); + + loading.hide(); + + return resolve(); + }); + }, () => { + console.error('Failed to display epub'); + return reject(); + }); + }); + }); + } + + canPlayMediaType(mediaType) { + return (mediaType || '').toLowerCase() === 'book'; + } + + canPlayItem(item) { + if (item.Path && (item.Path.endsWith('epub'))) { + return true; + } + + return false; + } +} + +export default BookPlayer; diff --git a/src/plugins/bookPlayer/style.css b/src/plugins/bookPlayer/style.css new file mode 100644 index 00000000000..e37b995f31d --- /dev/null +++ b/src/plugins/bookPlayer/style.css @@ -0,0 +1,39 @@ +#bookPlayer { + position: relative; + height: 100%; + width: 100%; + overflow: auto; + z-index: 100; + background: #fff; +} + +.topRightActionButtons { + right: 0.5vh; + top: 0.5vh; + z-index: 1002; + position: absolute; +} + +.topLeftActionButtons { + left: 0.5vh; + top: 0.5vh; + z-index: 1002; + position: absolute; +} + +.bookplayerButtonIcon { + color: black; + opacity: 0.7; +} + +#dialogToc { + background-color: white; +} + +.toc li { + margin-bottom: 5px; +} + +.bookplayerErrorMsg { + text-align: center; +} diff --git a/src/plugins/bookPlayer/tableOfContents.js b/src/plugins/bookPlayer/tableOfContents.js new file mode 100644 index 00000000000..23e288aff5b --- /dev/null +++ b/src/plugins/bookPlayer/tableOfContents.js @@ -0,0 +1,91 @@ +import dialogHelper from 'dialogHelper'; + +export default class TableOfContents { + constructor(bookPlayer) { + this._bookPlayer = bookPlayer; + this._rendition = bookPlayer._rendition; + + this.onDialogClosed = this.onDialogClosed.bind(this); + + this.createMediaElement(); + } + + destroy() { + let elem = this._elem; + if (elem) { + this.unbindEvents(); + dialogHelper.close(elem); + } + + this._bookPlayer._tocElement = null; + } + + bindEvents() { + let elem = this._elem; + + elem.addEventListener('close', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); + } + + unbindEvents() { + let elem = this._elem; + + elem.removeEventListener('close', this.onDialogClosed); + elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); + } + + onDialogClosed() { + this.destroy(); + } + + replaceLinks(contents, f) { + let links = contents.querySelectorAll('a[href]'); + + links.forEach((link) => { + let href = link.getAttribute('href'); + + link.onclick = () => { + f(href); + return false; + }; + }); + } + + createMediaElement() { + let rendition = this._rendition; + + let elem = dialogHelper.createDialog({ + size: 'small', + autoFocus: false, + removeOnClose: true + }); + + elem.id = 'dialogToc'; + + let tocHtml = '
'; + tocHtml += ''; + tocHtml += '
'; + tocHtml += '
    '; + rendition.book.navigation.forEach((chapter) => { + tocHtml += '
  • '; + // Remove '../' from href + let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + tocHtml += `${chapter.label}`; + tocHtml += '
  • '; + }); + + tocHtml += '
'; + elem.innerHTML = tocHtml; + + this.replaceLinks(elem, (href) => { + let relative = rendition.book.path.relative(href); + rendition.display(relative); + this.destroy(); + }); + + this._elem = elem; + + this.bindEvents(); + dialogHelper.open(elem); + } +} diff --git a/src/components/chromecast/chromecasthelpers.js b/src/plugins/chromecastPlayer/chromecastHelpers.js similarity index 80% rename from src/components/chromecast/chromecasthelpers.js rename to src/plugins/chromecastPlayer/chromecastHelpers.js index 2fef0c68b3a..ca2d27c977e 100644 --- a/src/components/chromecast/chromecasthelpers.js +++ b/src/plugins/chromecastPlayer/chromecastHelpers.js @@ -37,69 +37,69 @@ define(['events'], function (events) { // 5) It wasn't as smart as it could have been about what should be part of a // URL and what should be part of human language. - var protocols = "(?:(?:http|https|rtsp|ftp):\\/\\/)"; + var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)'; var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) - + "\\@)"; + + '\\@)'; // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License // http://intermapper.com/ - var ipv6 = "(" - + "(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))" - + "|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" - + "|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" - + "|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" - + "|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" - + "|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" - + "|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" - + "|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" - + ")(%.+)?"; - - var ipv4 = "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\." - + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." - + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." - + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])"; + var ipv6 = '(' + + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))' + + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + ')(%.+)?'; + + var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])'; // This would have been a lot cleaner if JS RegExp supported conditionals... var linkRegExpString = // begin match for protocol / username / password / host - "(?:" + '(?:' // ============================ // If we have a recognized protocol at the beginning of the URL, we're // more relaxed about what we accept, because we assume the user wants // this to be a URL, and we're not accidentally matching human language - + protocols + "?" + + protocols + '?' // optional username:password@ - + credentials + "?" + + credentials + '?' // IP address (both v4 and v6) - + "(?:" + + '(?:' // IPv6 + ipv6 // IPv4 - + "|" + ipv4 + + '|' + ipv4 - + ")" + + ')' // end match for protocol / username / password / host - + ")" + + ')' // optional port number - + "(?:\\:\\d{1,5})?" + + '(?:\\:\\d{1,5})?' // plus optional path and query params (no unicode allowed here?) - + "(?:" - + "\\/(?:" + + '(?:' + + '\\/(?:' // some characters we'll accept because it's unlikely human language // would use them after a URL unless they were part of the url - + "(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])" - + "|(?:\\%[a-f0-9]{2})" + + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])' + + '|(?:\\%[a-f0-9]{2})' // some characters are much more likely to be used AFTER a url and // were not intended to be included in the url itself. Mostly end // of sentence type things. It's also likely that the URL would @@ -108,9 +108,9 @@ define(['events'], function (events) { // they must be followed by another character that we're reasonably // sure is part of the url + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" - + ")*" - + "|\\b|\$" - + ")"; + + ')*' + + '|\\b|\$' + + ')'; // regex = XRegExp(regex,'gi'); var linkRegExp = RegExp(linkRegExpString, 'gi'); @@ -120,7 +120,7 @@ define(['events'], function (events) { // if url doesn't begin with a known protocol, add http by default function ensureProtocol(url) { if (!url.match(protocolRegExp)) { - url = "http://" + url; + url = 'http://' + url; } return url; } @@ -188,9 +188,9 @@ define(['events'], function (events) { return apiClient.getEndpointInfo().then(function (endpoint) { if (endpoint.IsInNetwork) { return apiClient.getPublicSystemInfo().then(function (info) { - var localAddress = info.LocalAddress + var localAddress = info.LocalAddress; if (!localAddress) { - console.debug("No valid local address returned, defaulting to external one") + console.debug('No valid local address returned, defaulting to external one'); localAddress = serverAddress; } addToCache(serverAddress, localAddress); diff --git a/src/components/chromecast/chromecastplayer.js b/src/plugins/chromecastPlayer/plugin.js similarity index 91% rename from src/components/chromecast/chromecastplayer.js rename to src/plugins/chromecastPlayer/plugin.js index 18103e433fb..6384ce7690b 100644 --- a/src/components/chromecast/chromecastplayer.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -5,7 +5,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' var currentResolve; var currentReject; - var PlayerName = 'Chromecast'; + var PlayerName = 'Google Cast'; function sendConnectionResult(isOk) { @@ -54,7 +54,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' // production version registered with google // replace this value if you want to test changes on another instance - var applicationID = "F007D354"; + var applicationStable = 'F007D354'; + var applicationNightly = '6F511C87'; var messageNamespace = 'urn:x-cast:com.connectsdk'; @@ -99,6 +100,11 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return; } + var applicationID = applicationStable; + if (userSettings.chromecastVersion() === 'nightly') { + applicationID = applicationNightly; + } + // request session var sessionRequest = new chrome.cast.SessionRequest(applicationID); var apiConfig = new chrome.cast.ApiConfig(sessionRequest, @@ -114,14 +120,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' */ CastPlayer.prototype.onInitSuccess = function () { this.isInitialized = true; - console.debug("chromecast init success"); + console.debug('chromecast init success'); }; /** * Generic error callback function */ CastPlayer.prototype.onError = function () { - console.debug("chromecast error"); + console.debug('chromecast error'); }; /** @@ -177,10 +183,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' */ CastPlayer.prototype.receiverListener = function (e) { if (e === 'available') { - console.debug("chromecast receiver found"); + console.debug('chromecast receiver found'); this.hasReceivers = true; } else { - console.debug("chromecast receiver list empty"); + console.debug('chromecast receiver list empty'); this.hasReceivers = false; } }; @@ -195,8 +201,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.session = null; this.deviceState = DEVICE_STATE.IDLE; this.castPlayerState = PLAYER_STATE.IDLE; - document.removeEventListener("volumeupbutton", onVolumeUpKeyDown, false); - document.removeEventListener("volumedownbutton", onVolumeDownKeyDown, false); + document.removeEventListener('volumeupbutton', onVolumeUpKeyDown, false); + document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false); console.debug('sessionUpdateListener: setting currentMediaSession to null'); this.currentMediaSession = null; @@ -211,7 +217,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * session request in opt_sessionRequest. */ CastPlayer.prototype.launchApp = function () { - console.debug("chromecast launching app..."); + console.debug('chromecast launching app...'); chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); }; @@ -220,7 +226,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * @param {Object} e A chrome.cast.Session object */ CastPlayer.prototype.onRequestSessionSuccess = function (e) { - console.debug("chromecast session success: " + e.sessionId); + console.debug('chromecast session success: ' + e.sessionId); this.onSessionConnected(e); }; @@ -232,8 +238,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.session.addMediaListener(this.sessionMediaListener.bind(this)); this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); - document.addEventListener("volumeupbutton", onVolumeUpKeyDown, false); - document.addEventListener("volumedownbutton", onVolumeDownKeyDown, false); + document.addEventListener('volumeupbutton', onVolumeUpKeyDown, false); + document.addEventListener('volumedownbutton', onVolumeDownKeyDown, false); events.trigger(this, 'connect'); this.sendMessage({ @@ -262,7 +268,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * Callback function for launch error */ CastPlayer.prototype.onLaunchError = function () { - console.debug("chromecast launch error"); + console.debug('chromecast launch error'); this.deviceState = DEVICE_STATE.ERROR; sendConnectionResult(false); }; @@ -284,8 +290,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.deviceState = DEVICE_STATE.IDLE; this.castPlayerState = PLAYER_STATE.IDLE; - document.removeEventListener("volumeupbutton", onVolumeUpKeyDown, false); - document.removeEventListener("volumedownbutton", onVolumeDownKeyDown, false); + document.removeEventListener('volumeupbutton', onVolumeUpKeyDown, false); + document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false); this.currentMediaSession = null; }; @@ -296,7 +302,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' */ CastPlayer.prototype.loadMedia = function (options, command) { if (!this.session) { - console.debug("no session"); + console.debug('no session'); return Promise.reject(); } @@ -386,7 +392,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' */ CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { - console.debug("chromecast new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')'); + console.debug('chromecast new media session ID:' + mediaSession.mediaSessionId + ' (' + how + ')'); this.currentMediaSession = mediaSession; if (how === 'loadMedia') { @@ -405,7 +411,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * @param {!Boolean} e true/false */ CastPlayer.prototype.onMediaStatusUpdate = function (e) { - console.debug("chromecast updating media: " + e); + console.debug('chromecast updating media: ' + e); if (e === false) { this.castPlayerState = PLAYER_STATE.IDLE; } @@ -482,7 +488,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } else { query.Limit = query.Limit || 100; - query.ExcludeLocationTypes = "Virtual"; + query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; return apiClient.getItems(userId, query); @@ -506,13 +512,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' instance._castPlayer = new CastPlayer(); // To allow the native android app to override - document.dispatchEvent(new CustomEvent("chromecastloaded", { + document.dispatchEvent(new CustomEvent('chromecastloaded', { detail: { player: instance } })); - events.on(instance._castPlayer, "connect", function (e) { + events.on(instance._castPlayer, 'connect', function (e) { if (currentResolve) { sendConnectionResult(true); @@ -525,24 +531,24 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' instance.lastPlayerData = null; }); - events.on(instance._castPlayer, "playbackstart", function (e, data) { + events.on(instance._castPlayer, 'playbackstart', function (e, data) { console.debug('cc: playbackstart'); instance._castPlayer.initializeCastPlayer(); var state = instance.getPlayerStateInternal(data); - events.trigger(instance, "playbackstart", [state]); + events.trigger(instance, 'playbackstart', [state]); }); - events.on(instance._castPlayer, "playbackstop", function (e, data) { + events.on(instance._castPlayer, 'playbackstop', function (e, data) { console.debug('cc: playbackstop'); var state = instance.getPlayerStateInternal(data); - events.trigger(instance, "playbackstop", [state]); + events.trigger(instance, 'playbackstop', [state]); - var state = instance.lastPlayerData.PlayState || {}; + state = instance.lastPlayerData.PlayState || {}; var volume = state.VolumeLevel || 0.5; var mute = state.IsMuted || false; @@ -553,12 +559,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' instance.lastPlayerData.PlayState.IsMuted = mute; }); - events.on(instance._castPlayer, "playbackprogress", function (e, data) { + events.on(instance._castPlayer, 'playbackprogress', function (e, data) { console.debug('cc: positionchange'); var state = instance.getPlayerStateInternal(data); - events.trigger(instance, "timeupdate", [state]); + events.trigger(instance, 'timeupdate', [state]); }); bindEventForRelay(instance, 'timeupdate'); @@ -566,13 +572,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' bindEventForRelay(instance, 'unpause'); bindEventForRelay(instance, 'volumechange'); bindEventForRelay(instance, 'repeatmodechange'); + bindEventForRelay(instance, 'shufflequeuemodechange'); - events.on(instance._castPlayer, "playstatechange", function (e, data) { + events.on(instance._castPlayer, 'playstatechange', function (e, data) { console.debug('cc: playstatechange'); var state = instance.getPlayerStateInternal(data); - events.trigger(instance, "pause", [state]); + events.trigger(instance, 'pause', [state]); }); } @@ -630,24 +637,25 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' name: PlayerName, id: PlayerName, playerName: PlayerName, - playableMediaTypes: ["Audio", "Video"], + playableMediaTypes: ['Audio', 'Video'], isLocalPlayer: false, appName: PlayerName, deviceName: appName, supportedCommands: [ - "VolumeUp", - "VolumeDown", - "Mute", - "Unmute", - "ToggleMute", - "SetVolume", - "SetAudioStreamIndex", - "SetSubtitleStreamIndex", - "DisplayContent", - "SetRepeatMode", - "EndSession", - "PlayMediaSource", - "PlayTrailers" + 'VolumeUp', + 'VolumeDown', + 'Mute', + 'Unmute', + 'ToggleMute', + 'SetVolume', + 'SetAudioStreamIndex', + 'SetSubtitleStreamIndex', + 'DisplayContent', + 'SetRepeatMode', + 'SetShuffleQueue', + 'EndSession', + 'PlayMediaSource', + 'PlayTrailers' ] }; }; @@ -667,7 +675,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' console.debug(JSON.stringify(data)); if (triggerStateChange) { - events.trigger(this, "statechange", [data]); + events.trigger(this, 'statechange', [data]); } return data; @@ -858,6 +866,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return state.RepeatMode; }; + ChromecastPlayer.prototype.getQueueShuffleMode = function () { + var state = this.lastPlayerData || {}; + state = state.PlayState || {}; + return state.ShuffleMode; + }; + ChromecastPlayer.prototype.playTrailers = function (item) { this._castPlayer.sendMessage({ @@ -878,6 +892,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }); }; + ChromecastPlayer.prototype.setQueueShuffleMode = function (value) { + this._castPlayer.sendMessage({ + options: { + ShuffleMode: value + }, + command: 'SetShuffleQueue' + }); + }; + ChromecastPlayer.prototype.toggleMute = function () { this._castPlayer.sendMessage({ diff --git a/src/components/playback/experimentalwarnings.js b/src/plugins/experimentalWarnings/plugin.js similarity index 69% rename from src/components/playback/experimentalwarnings.js rename to src/plugins/experimentalWarnings/plugin.js index 2d1ef53c19c..632e38208c8 100644 --- a/src/components/playback/experimentalwarnings.js +++ b/src/plugins/experimentalWarnings/plugin.js @@ -1,5 +1,5 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function (connectionManager, globalize, userSettings, appHost) { - "use strict"; + 'use strict'; function getRequirePromise(deps) { @@ -44,24 +44,15 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function ( } function showBlurayMessage() { - - var message = - 'Playback of Bluray folders in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native Bluray folder support.'; - return showMessage(message, 'blurayexpirementalinfo', 'nativeblurayplayback'); + return showMessage(globalize.translate('UnsupportedPlayback'), 'blurayexpirementalinfo', 'nativeblurayplayback'); } function showDvdMessage() { - - var message = - 'Playback of Dvd folders in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native Dvd folder support.'; - return showMessage(message, 'dvdexpirementalinfo', 'nativedvdplayback'); + return showMessage(globalize.translate('UnsupportedPlayback'), 'dvdexpirementalinfo', 'nativedvdplayback'); } function showIsoMessage() { - - var message = - 'Playback of ISO files in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native ISO support.'; - return showMessage(message, 'isoexpirementalinfo', 'nativeisoplayback'); + return showMessage(globalize.translate('UnsupportedPlayback'), 'isoexpirementalinfo', 'nativeisoplayback'); } function ExpirementalPlaybackWarnings() { diff --git a/src/components/htmlaudioplayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js similarity index 90% rename from src/components/htmlaudioplayer/plugin.js rename to src/plugins/htmlAudioPlayer/plugin.js index 8cae76bbee8..8265987e283 100644 --- a/src/components/htmlaudioplayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -1,5 +1,5 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelper'], function (events, browser, require, appHost, appSettings, htmlMediaHelper) { - "use strict"; + 'use strict'; function getDefaultProfile() { return new Promise(function (resolve, reject) { @@ -101,7 +101,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._timeUpdated = false; self._currentTime = null; - var elem = createMediaElement(options); + var elem = createMediaElement(); return setCurrentSrc(elem, options); }; @@ -136,7 +136,10 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp requireHlsPlayer(function () { var hls = new Hls({ - manifestLoadingTimeOut: 20000 + manifestLoadingTimeOut: 20000, + xhrSetup: function(xhr, url) { + xhr.withCredentials = true; + } //appendErrorMaxRetry: 6, //debug: true }); @@ -155,6 +158,9 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp elem.autoplay = true; + // Safari will not send cookies without this + elem.crossOrigin = 'use-credentials'; + return htmlMediaHelper.applySrc(elem, val, options).then(function () { self._currentSrc = val; @@ -171,6 +177,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp elem.addEventListener('pause', onPause); elem.addEventListener('playing', onPlaying); elem.addEventListener('play', onPlay); + elem.addEventListener('waiting', onWaiting); } function unBindEvents(elem) { @@ -180,6 +187,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp elem.removeEventListener('pause', onPause); elem.removeEventListener('playing', onPlaying); elem.removeEventListener('play', onPlay); + elem.removeEventListener('waiting', onWaiting); } self.stop = function (destroyPlayer) { @@ -294,6 +302,10 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp events.trigger(self, 'pause'); } + function onWaiting() { + events.trigger(self, 'waiting'); + } + function onError() { var errorCode = this.error ? (this.error.code || 0) : 0; @@ -450,6 +462,21 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return false; }; + HtmlAudioPlayer.prototype.setPlaybackRate = function (value) { + var mediaElement = this._mediaElement; + if (mediaElement) { + mediaElement.playbackRate = value; + } + }; + + HtmlAudioPlayer.prototype.getPlaybackRate = function () { + var mediaElement = this._mediaElement; + if (mediaElement) { + return mediaElement.playbackRate; + } + return null; + }; + HtmlAudioPlayer.prototype.setVolume = function (val) { var mediaElement = this._mediaElement; if (mediaElement) { @@ -493,5 +520,26 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; + var supportedFeatures; + + function getSupportedFeatures() { + var list = []; + var audio = document.createElement('audio'); + + if (typeof audio.playbackRate === 'number') { + list.push('PlaybackRate'); + } + + return list; + } + + HtmlAudioPlayer.prototype.supports = function (feature) { + if (!supportedFeatures) { + supportedFeatures = getSupportedFeatures(); + } + + return supportedFeatures.indexOf(feature) !== -1; + }; + return HtmlAudioPlayer; }); diff --git a/src/components/htmlvideoplayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js similarity index 91% rename from src/components/htmlvideoplayer/plugin.js rename to src/plugins/htmlVideoPlayer/plugin.js index 995df6607be..cc312bb9568 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,5 +1,5 @@ -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'fullscreenManager', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, fullscreenManager, globalize) { - "use strict"; +define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'screenfull', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, screenfull, globalize) { + 'use strict'; /* globals cast */ var mediaManager; @@ -106,18 +106,27 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa }); } + function hidePrePlaybackPage() { + let animatedPage = document.querySelector('.page:not(.hide)'); + animatedPage.classList.add('hide'); + // At this point, we must hide the scrollbar placeholder, so it's not being displayed while the item is being loaded + document.body.classList.remove('force-scroll'); + } + function zoomIn(elem) { return new Promise(function (resolve, reject) { var duration = 240; elem.style.animation = 'htmlvideoplayer-zoomin ' + duration + 'ms ease-in normal'; + hidePrePlaybackPage(); dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { once: true }); }); } - function normalizeTrackEventText(text) { - return text.replace(/\\N/gi, '\n'); + function normalizeTrackEventText(text, useHtml) { + var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + return useHtml ? result.replace(/\n/gi, '
') : result; } function setTracks(elem, tracks, item, mediaSource) { @@ -211,7 +220,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function incrementFetchQueue() { if (self._fetchQueue <= 0) { self.isFetching = true; - events.trigger(self, "beginFetch"); + events.trigger(self, 'beginFetch'); } self._fetchQueue++; @@ -222,7 +231,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (self._fetchQueue <= 0) { self.isFetching = false; - events.trigger(self, "endFetch"); + events.trigger(self, 'endFetch'); } } @@ -272,14 +281,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } self.play = function (options) { - - if (browser.msie) { - if (options.playMethod === 'Transcode' && !window.MediaSource) { - alert('Playback of this content is not supported in Internet Explorer. For a better experience, try a modern browser such as Microsoft Edge, Google Chrome, Firefox or Opera.'); - return Promise.reject(); - } - } - self._started = false; self._timeUpdated = false; @@ -289,7 +290,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return createMediaElement(options).then(function (elem) { - return updateVideoUrl(options, options.mediaSource).then(function () { + return updateVideoUrl(options).then(function () { return setCurrentSrc(elem, options); }); }); @@ -329,7 +330,10 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa requireHlsPlayer(function () { var hls = new Hls({ - manifestLoadingTimeOut: 20000 + manifestLoadingTimeOut: 20000, + xhrSetup: function(xhr, xhr_url) { + xhr.withCredentials = true; + } //appendErrorMaxRetry: 6, //debug: true }); @@ -465,7 +469,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa console.debug('content type: ' + contentType); host.onError = function (errorCode) { - console.error("fatal Error - " + errorCode); + console.error('fatal Error - ' + errorCode); }; mediaElement.autoplay = false; @@ -550,6 +554,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa elem.autoplay = true; + // Safari will not send cookies without this + elem.crossOrigin = 'use-credentials'; + return htmlMediaHelper.applySrc(elem, val, options).then(function () { self._currentSrc = val; @@ -567,19 +574,19 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa self.resetSubtitleOffset = function() { currentTrackOffset = 0; showTrackOffset = false; - } + }; self.enableShowingSubtitleOffset = function() { showTrackOffset = true; - } + }; self.disableShowingSubtitleOffset = function() { showTrackOffset = false; - } + }; self.isShowingSubtitleOffsetEnabled = function() { return showTrackOffset; - } + }; function getTextTrack() { var videoElement = self._mediaElement; @@ -599,8 +606,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var offsetValue = parseFloat(offset); // if .ass currently rendering - if (currentAssRenderer) { + if (currentSubtitlesOctopus) { updateCurrentTrackOffset(offsetValue); + currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; } else { var trackElement = getTextTrack(); // if .vtt currently rendering @@ -609,7 +617,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } else if (currentTrackEvents) { setTrackEventsSubtitleOffset(currentTrackEvents, offsetValue); } else { - console.debug("No available track, cannot apply offset: ", offsetValue); + console.debug('No available track, cannot apply offset: ', offsetValue); } } }; @@ -641,7 +649,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function setTrackEventsSubtitleOffset(trackEvents, offsetValue) { if (Array.isArray(trackEvents)) { - offsetValue = updateCurrentTrackOffset(offsetValue); + offsetValue = updateCurrentTrackOffset(offsetValue) * 1e7; // ticks trackEvents.forEach(function(trackEvent) { trackEvent.StartPositionTicks -= offsetValue; trackEvent.EndPositionTicks -= offsetValue; @@ -651,7 +659,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa self.getSubtitleOffset = function() { return currentTrackOffset; - } + }; function isAudioStreamSupported(stream, deviceProfile) { @@ -783,6 +791,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa videoElement.removeEventListener('play', onPlay); videoElement.removeEventListener('click', onClick); videoElement.removeEventListener('dblclick', onDblClick); + videoElement.removeEventListener('waiting', onWaiting); videoElement.parentNode.removeChild(videoElement); } @@ -793,7 +802,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa dlg.parentNode.removeChild(dlg); } - fullscreenManager.exitFullscreen(); + if (screenfull.isEnabled) { + screenfull.exit(); + } }; function onEnded() { @@ -832,7 +843,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function onNavigatedToOsd() { var dlg = videoDialog; if (dlg) { - dlg.classList.remove('videoPlayerContainer-withBackdrop'); dlg.classList.remove('videoPlayerContainer-onTop'); onStartedAndNavigatedToOsd(); @@ -855,7 +865,13 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa loading.hide(); - htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks); + htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks, function () { + if (currentSubtitlesOctopus) { + currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + currentTrackOffset; + currentSubtitlesOctopus.resize(); + currentSubtitlesOctopus.resetRenderAheadCache(false); + } + }); if (self._currentPlayOptions.fullscreen) { @@ -863,7 +879,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } else { appRouter.setTransparency('backdrop'); - videoDialog.classList.remove('videoPlayerContainer-withBackdrop'); videoDialog.classList.remove('videoPlayerContainer-onTop'); onStartedAndNavigatedToOsd(); @@ -905,6 +920,10 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa events.trigger(self, 'pause'); } + function onWaiting() { + events.trigger(self, 'waiting'); + } + function onError() { var errorCode = this.error ? (this.error.code || 0) : 0; var errorMessage = this.error ? (this.error.message || '') : ''; @@ -1019,7 +1038,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa xhr.onerror = function (e) { reject(e); decrementFetchQueue(); - } + }; xhr.send(); }); @@ -1048,17 +1067,31 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function renderSsaAss(videoElement, track, item) { var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || []; + var apiClient = connectionManager.getApiClient(item); var options = { video: videoElement, subUrl: getTextTrackUrl(track, item), fonts: attachments.map(function (i) { - return i.DeliveryUrl; + return apiClient.getUrl(i.DeliveryUrl); }), - workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js", - legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js", + workerUrl: appRouter.baseUrl() + '/libraries/subtitles-octopus-worker.js', + legacyWorkerUrl: appRouter.baseUrl() + '/libraries/subtitles-octopus-worker-legacy.js', onError: function() { htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror'); - } + }, + timeOffset: (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, + + // new octopus options; override all, even defaults + renderMode: 'blend', + dropAllAnimations: false, + libassMemoryLimit: 40, + libassGlyphLimit: 40, + targetFps: 24, + prescaleTradeoff: 0.8, + softHeightLimit: 1080, + hardHeightLimit: 2160, + resizeVariation: 0.2, + renderAhead: 90 }; require(['JavascriptSubtitlesOctopus'], function(SubtitlesOctopus) { currentSubtitlesOctopus = new SubtitlesOctopus(options); @@ -1196,7 +1229,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa data.TrackEvents.forEach(function (trackEvent) { var trackCueObject = window.VTTCue || window.TextTrackCue; - var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text)); + var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false)); trackElement.addCue(cue); }); @@ -1206,11 +1239,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function updateSubtitleText(timeMs) { - // handle offset for ass tracks - if (currentTrackOffset) { - timeMs += (currentTrackOffset * 1000); - } - var clock = currentClock; if (clock) { try { @@ -1237,8 +1265,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (selectedTrackEvent && selectedTrackEvent.Text) { - - subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text); + subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); subtitleTextElement.classList.remove('hide'); } else { @@ -1272,12 +1299,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function createMediaElement(options) { - if (browser.tv || browser.iOS || browser.mobile) { - // too slow - // also on iOS, the backdrop image doesn't look right - // on android mobile, it works, but can be slow to have the video surface fully cover the backdrop - options.backdropUrl = null; - } return new Promise(function (resolve, reject) { var dlg = document.querySelector('.videoPlayerContainer'); @@ -1292,11 +1313,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa dlg.classList.add('videoPlayerContainer'); - if (options.backdropUrl) { - dlg.classList.add('videoPlayerContainer-withBackdrop'); - dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"; - } - if (options.fullscreen) { dlg.classList.add('videoPlayerContainer-onTop'); } @@ -1330,6 +1346,10 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa videoElement.addEventListener('play', onPlay); videoElement.addEventListener('click', onClick); videoElement.addEventListener('dblclick', onDblClick); + videoElement.addEventListener('waiting', onWaiting); + if (options.backdropUrl) { + videoElement.poster = options.backdropUrl; + } document.body.insertBefore(dlg, document.body.firstChild); videoDialog = dlg; @@ -1349,15 +1369,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa resolve(videoElement); }); } else { + hidePrePlaybackPage(); resolve(videoElement); } }); } else { - if (options.backdropUrl) { - dlg.classList.add('videoPlayerContainer-withBackdrop'); - dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"; - } - resolve(dlg.querySelector('video')); } }); @@ -1399,15 +1415,8 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var list = []; var video = document.createElement('video'); - if (video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === "function" || document.pictureInPictureEnabled) { + if (video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function' || document.pictureInPictureEnabled) { list.push('PictureInPicture'); - } else if (browser.ipad) { - // Unfortunately this creates a false positive on devices where its' not actually supported - if (navigator.userAgent.toLowerCase().indexOf('os 9') === -1) { - if (video.webkitSupportsPresentationMode && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === "function") { - list.push('PictureInPicture'); - } - } } else if (window.Windows) { if (Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) { list.push('PictureInPicture'); @@ -1415,11 +1424,15 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (browser.safari || browser.iOS || browser.iPad) { - list.push('AirPlay') + list.push('AirPlay'); + } + + if (typeof video.playbackRate === 'number') { + list.push('PlaybackRate'); } list.push('SetBrightness'); - list.push("SetAspectRatio") + list.push('SetAspectRatio'); return list; } @@ -1504,8 +1517,8 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.default); } } else { - if (video && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === "function") { - video.webkitSetPresentationMode(isEnabled ? "picture-in-picture" : "inline"); + if (video && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { + video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline'); } } }; @@ -1519,7 +1532,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } else { var video = this._mediaElement; if (video) { - return video.webkitPresentationMode === "picture-in-picture"; + return video.webkitPresentationMode === 'picture-in-picture'; } } @@ -1542,11 +1555,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (video) { if (isEnabled) { video.requestAirPlay().catch(function(err) { - console.error("Error requesting AirPlay", err) + console.error('Error requesting AirPlay', err); }); } else { document.exitAirPLay().catch(function(err) { - console.error("Error exiting AirPlay", err) + console.error('Error exiting AirPlay', err); }); } } @@ -1638,6 +1651,21 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return false; }; + HtmlVideoPlayer.prototype.setPlaybackRate = function (value) { + var mediaElement = this._mediaElement; + if (mediaElement) { + mediaElement.playbackRate = value; + } + }; + + HtmlVideoPlayer.prototype.getPlaybackRate = function () { + var mediaElement = this._mediaElement; + if (mediaElement) { + return mediaElement.playbackRate; + } + return null; + }; + HtmlVideoPlayer.prototype.setVolume = function (val) { var mediaElement = this._mediaElement; if (mediaElement) { @@ -1678,30 +1706,30 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa HtmlVideoPlayer.prototype.setAspectRatio = function (val) { var mediaElement = this._mediaElement; if (mediaElement) { - if ("auto" === val) { - mediaElement.style.removeProperty("object-fit") + if ('auto' === val) { + mediaElement.style.removeProperty('object-fit'); } else { - mediaElement.style["object-fit"] = val + mediaElement.style['object-fit'] = val; } } - this._currentAspectRatio = val + this._currentAspectRatio = val; }; HtmlVideoPlayer.prototype.getAspectRatio = function () { - return this._currentAspectRatio || "auto"; + return this._currentAspectRatio || 'auto'; }; HtmlVideoPlayer.prototype.getSupportedAspectRatios = function () { return [{ - name: "Auto", - id: "auto" + name: 'Auto', + id: 'auto' }, { - name: "Cover", - id: "cover" + name: 'Cover', + id: 'cover' }, { - name: "Fill", - id: "fill" - }] + name: 'Fill', + id: 'fill' + }]; }; HtmlVideoPlayer.prototype.togglePictureInPicture = function () { @@ -1749,7 +1777,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (protocol) { mediaCategory.stats.push({ - label: globalize.translate("LabelProtocol"), + label: globalize.translate('LabelProtocol'), value: protocol }); } @@ -1759,12 +1787,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (this._hlsPlayer || this._shakaPlayer) { mediaCategory.stats.push({ - label: globalize.translate("LabelStreamType"), + label: globalize.translate('LabelStreamType'), value: 'HLS' }); } else { mediaCategory.stats.push({ - label: globalize.translate("LabelStreamType"), + label: globalize.translate('LabelStreamType'), value: 'Video' }); } @@ -1782,7 +1810,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa // Don't show player dimensions on smart TVs because the app UI could be lower resolution than the video and this causes users to think there is a problem if (width && height && !browser.tv) { videoCategory.stats.push({ - label: globalize.translate("LabelPlayerDimensions"), + label: globalize.translate('LabelPlayerDimensions'), value: width + 'x' + height }); } @@ -1792,7 +1820,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (width && height) { videoCategory.stats.push({ - label: globalize.translate("LabelVideoResolution"), + label: globalize.translate('LabelVideoResolution'), value: width + 'x' + height }); } @@ -1802,13 +1830,13 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var droppedVideoFrames = playbackQuality.droppedVideoFrames || 0; videoCategory.stats.push({ - label: globalize.translate("LabelDroppedFrames"), + label: globalize.translate('LabelDroppedFrames'), value: droppedVideoFrames }); var corruptedVideoFrames = playbackQuality.corruptedVideoFrames || 0; videoCategory.stats.push({ - label: globalize.translate("LabelCorruptedFrames"), + label: globalize.translate('LabelCorruptedFrames'), value: corruptedVideoFrames }); } diff --git a/src/components/htmlvideoplayer/style.css b/src/plugins/htmlVideoPlayer/style.css similarity index 82% rename from src/components/htmlvideoplayer/style.css rename to src/plugins/htmlVideoPlayer/style.css index 5ecf4af66a9..b83a7816f5d 100644 --- a/src/components/htmlvideoplayer/style.css +++ b/src/plugins/htmlVideoPlayer/style.css @@ -6,20 +6,9 @@ right: 0; display: flex; align-items: center; -} - -.videoPlayerContainer:not(.videoPlayerContainer-withBackdrop) { background: #000 !important; } -.videoPlayerContainer-withBackdrop { - background-repeat: no-repeat; - background-position: center center; - background-size: cover; - background-attachment: fixed; - background-color: #000; -} - .videoPlayerContainer-onTop { z-index: 1000; } diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js new file mode 100644 index 00000000000..f9dd9019827 --- /dev/null +++ b/src/plugins/logoScreensaver/plugin.js @@ -0,0 +1,192 @@ +define(['pluginManager'], function (pluginManager) { + + return function () { + + var self = this; + + self.name = 'Logo ScreenSaver'; + self.type = 'screensaver'; + self.id = 'logoscreensaver'; + self.supportsAnonymous = true; + + var interval; + + function animate() { + + var animations = [ + + bounceInLeft, + bounceInRight, + swing, + tada, + wobble, + rotateIn, + rotateOut + ]; + + var elem = document.querySelector('.logoScreenSaverImage'); + + if (elem && elem.animate) { + var random = getRandomInt(0, animations.length - 1); + + animations[random](elem, 1); + } + } + + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + function bounceInLeft(elem, iterations) { + var keyframes = [ + { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(-100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function bounceInRight(elem, iterations) { + var keyframes = [ + { transform: 'translate3d(3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(-25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(-5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function shake(elem, iterations) { + var keyframes = [ + { transform: 'translate3d(0, 0, 0)', offset: 0 }, + { transform: 'translate3d(-10px, 0, 0)', offset: 0.1 }, + { transform: 'translate3d(10px, 0, 0)', offset: 0.2 }, + { transform: 'translate3d(-10px, 0, 0)', offset: 0.3 }, + { transform: 'translate3d(10px, 0, 0)', offset: 0.4 }, + { transform: 'translate3d(-10px, 0, 0)', offset: 0.5 }, + { transform: 'translate3d(10px, 0, 0)', offset: 0.6 }, + { transform: 'translate3d(-10px, 0, 0)', offset: 0.7 }, + { transform: 'translate3d(10px, 0, 0)', offset: 0.8 }, + { transform: 'translate3d(-10px, 0, 0)', offset: 0.9 }, + { transform: 'translate3d(0, 0, 0)', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function swing(elem, iterations) { + var keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 15deg)', offset: 0.2 }, + { transform: 'rotate3d(0, 0, 1, -10deg)', offset: 0.4 }, + { transform: 'rotate3d(0, 0, 1, 5deg)', offset: 0.6 }, + { transform: 'rotate3d(0, 0, 1, -5deg)', offset: 0.8 }, + { transform: 'rotate3d(0, 0, 1, 0deg)', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function tada(elem, iterations) { + var keyframes = [ + { transform: 'scale3d(1, 1, 1)', offset: 0 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.1 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.2 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.3 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.4 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.5 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.6 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.7 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.8 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.9 }, + { transform: 'scale3d(1, 1, 1)', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function wobble(elem, iterations) { + var keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)', offset: 0.15 }, + { transform: 'translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)', offset: 0.45 }, + { transform: 'translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)', offset: 0.6 }, + { transform: 'translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)', offset: 0.75 }, + { transform: 'translateX(0%)', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateIn(elem, iterations) { + var transformOrigin = elem.style['transform-origin']; + var keyframes = [{ transform: 'rotate3d(0, 0, 1, -200deg)', opacity: '0', transformOrigin: 'center', offset: 0 }, + { transform: 'none', opacity: '1', transformOrigin: 'center', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateOut(elem, iterations) { + var transformOrigin = elem.style['transform-origin']; + var keyframes = [{ transform: 'none', opacity: '1', transformOrigin: 'center', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 200deg)', opacity: '0', transformOrigin: 'center', offset: 1 }]; + var timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + + } + + function fadeOut(elem, iterations) { + var keyframes = [ + { opacity: '1', offset: 0 }, + { opacity: '0', offset: 1 }]; + var timing = { duration: 400, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function stopInterval() { + if (interval) { + clearInterval(interval); + interval = null; + } + } + + self.show = function () { + + require(['css!' + pluginManager.mapPath(self, 'style.css')], function () { + + var elem = document.querySelector('.logoScreenSaver'); + + if (!elem) { + elem = document.createElement('div'); + elem.classList.add('logoScreenSaver'); + document.body.appendChild(elem); + + elem.innerHTML = ''; + } + + stopInterval(); + interval = setInterval(animate, 3000); + }); + }; + + self.hide = function () { + + stopInterval(); + + var elem = document.querySelector('.logoScreenSaver'); + + if (elem) { + + var onAnimationFinish = function () { + elem.parentNode.removeChild(elem); + }; + + if (elem.animate) { + var animation = fadeOut(elem, 1); + animation.onfinish = onAnimationFinish; + } else { + onAnimationFinish(); + } + } + }; + }; +}); diff --git a/src/components/logoscreensaver/style.css b/src/plugins/logoScreensaver/style.css similarity index 100% rename from src/components/logoscreensaver/style.css rename to src/plugins/logoScreensaver/style.css diff --git a/src/plugins/photoPlayer/plugin.js b/src/plugins/photoPlayer/plugin.js new file mode 100644 index 00000000000..d8e55fa67e3 --- /dev/null +++ b/src/plugins/photoPlayer/plugin.js @@ -0,0 +1,38 @@ +import connectionManager from 'connectionManager'; + +export default class PhotoPlayer { + constructor() { + this.name = 'Photo Player'; + this.type = 'mediaplayer'; + this.id = 'photoplayer'; + this.priority = 1; + } + + play(options) { + return new Promise(function (resolve, reject) { + import('slideshow').then(({default: slideshow}) => { + var index = options.startIndex || 0; + + var apiClient = connectionManager.currentApiClient(); + apiClient.getCurrentUser().then(function(result) { + var newSlideShow = new slideshow({ + showTitle: false, + cover: false, + items: options.items, + startIndex: index, + interval: 11000, + interactive: true, + user: result + }); + + newSlideShow.show(); + resolve(); + }); + }); + }); + } + + canPlayMediaType(mediaType) { + return (mediaType || '').toLowerCase() === 'photo'; + } +} diff --git a/src/components/playback/playaccessvalidation.js b/src/plugins/playAccessValidation/plugin.js similarity index 98% rename from src/components/playback/playaccessvalidation.js rename to src/plugins/playAccessValidation/plugin.js index 46a6f7546c2..5148d2b821c 100644 --- a/src/components/playback/playaccessvalidation.js +++ b/src/plugins/playAccessValidation/plugin.js @@ -1,5 +1,5 @@ define(['connectionManager', 'globalize'], function (connectionManager, globalize) { - "use strict"; + 'use strict'; function getRequirePromise(deps) { return new Promise(function (resolve, reject) { diff --git a/src/components/sessionplayer.js b/src/plugins/sessionPlayer/plugin.js similarity index 97% rename from src/components/sessionplayer.js rename to src/plugins/sessionPlayer/plugin.js index bbb22a96546..084aa027cf3 100644 --- a/src/components/sessionplayer.js +++ b/src/plugins/sessionPlayer/plugin.js @@ -79,7 +79,7 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'] instance.isUpdating = true; var apiClient = getCurrentApiClient(instance); - apiClient.sendMessage("SessionsStop"); + apiClient.sendMessage('SessionsStop'); if (instance.pollInterval) { clearInterval(instance.pollInterval); instance.pollInterval = null; @@ -158,7 +158,7 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'] instance.isUpdating = true; var apiClient = getCurrentApiClient(instance); - apiClient.sendMessage("SessionsStart", "100,800"); + apiClient.sendMessage('SessionsStart', '100,800'); if (instance.pollInterval) { clearInterval(instance.pollInterval); instance.pollInterval = null; @@ -263,7 +263,7 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'] appName: s.Client, playableMediaTypes: s.PlayableMediaTypes, isLocalPlayer: false, - supportedCommands: s.SupportedCommands, + supportedCommands: s.Capabilities.SupportedCommands, user: s.UserId ? { Id: s.UserId, @@ -506,6 +506,17 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'] }); }; + SessionPlayer.prototype.setQueueShuffleMode = function (mode) { + + sendCommandByName(this, 'SetShuffleQueue', { + ShuffleMode: mode + }); + }; + + SessionPlayer.prototype.getQueueShuffleMode = function () { + + }; + SessionPlayer.prototype.displayContent = function (options) { sendCommandByName(this, 'DisplayContent', options); diff --git a/src/components/youtubeplayer/plugin.js b/src/plugins/youtubePlayer/plugin.js similarity index 99% rename from src/components/youtubeplayer/plugin.js rename to src/plugins/youtubePlayer/plugin.js index fd9c05292e7..5b5e1b1a3ce 100644 --- a/src/components/youtubeplayer/plugin.js +++ b/src/plugins/youtubePlayer/plugin.js @@ -1,5 +1,5 @@ define(['require', 'events', 'browser', 'appRouter', 'loading'], function (require, events, browser, appRouter, loading) { - "use strict"; + 'use strict'; /* globals YT */ function zoomIn(elem, iterations) { @@ -182,7 +182,7 @@ define(['require', 'events', 'browser', 'appRouter', 'loading'], function (requi if (!window.YT) { var tag = document.createElement('script'); - tag.src = "https://www.youtube.com/iframe_api"; + tag.src = 'https://www.youtube.com/iframe_api'; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); } else { diff --git a/src/components/youtubeplayer/style.css b/src/plugins/youtubePlayer/style.css similarity index 100% rename from src/components/youtubeplayer/style.css rename to src/plugins/youtubePlayer/style.css diff --git a/src/repositories.html b/src/repositories.html new file mode 100644 index 00000000000..ff3406fb955 --- /dev/null +++ b/src/repositories.html @@ -0,0 +1,19 @@ +
+
+
+
+

${TabRepositories}

+ +
+ +
+ +
+

${MessageNoRepositories}

+

${MessageAddRepository}

+
+
+
+
diff --git a/src/scheduledtask.html b/src/scheduledtask.html index f5bd6550f27..c7e82594ce2 100644 --- a/src/scheduledtask.html +++ b/src/scheduledtask.html @@ -4,7 +4,7 @@ @@ -13,14 +13,14 @@

${HeaderTaskTriggers}

-
+

${HeaderAddScheduledTaskTrigger}

diff --git a/src/components/alphanumericshortcuts/alphanumericshortcuts.js b/src/scripts/alphanumericshortcuts.js similarity index 100% rename from src/components/alphanumericshortcuts/alphanumericshortcuts.js rename to src/scripts/alphanumericshortcuts.js diff --git a/src/scripts/apploader.js b/src/scripts/apploader.js index e8a63217a8e..183b765d160 100644 --- a/src/scripts/apploader.js +++ b/src/scripts/apploader.js @@ -1,16 +1,17 @@ (function() { - "use strict"; + 'use strict'; function injectScriptElement(src, onload) { if (!src) { return; } - var script = document.createElement("script"); + var script = document.createElement('script'); if (self.dashboardVersion) { - src += "?v=" + self.dashboardVersion; + src += `?v=${self.dashboardVersion}`; } script.src = src; + script.setAttribute('async', ''); if (onload) { script.onload = onload; @@ -21,18 +22,28 @@ function loadSite() { injectScriptElement( - "./libraries/alameda.js", + './libraries/alameda.js', function() { // onload of require library - injectScriptElement("./scripts/site.js"); + injectScriptElement('./scripts/site.js'); } ); } + try { + Promise.resolve(); + } catch (ex) { + // this checks for several cases actually, typical is + // Promise() being missing on some legacy browser, and a funky one + // is Promise() present but buggy on WebOS 2 + window.Promise = undefined; + self.Promise = undefined; + } + if (!self.Promise) { // Load Promise polyfill if they are not natively supported injectScriptElement( - "./libraries/npo.js", + './libraries/npo.js', loadSite ); } else { diff --git a/src/scripts/autobackdrops.js b/src/scripts/autoBackdrops.js similarity index 71% rename from src/scripts/autobackdrops.js rename to src/scripts/autoBackdrops.js index 09f89fad6f6..abd86c9dbf4 100644 --- a/src/scripts/autobackdrops.js +++ b/src/scripts/autoBackdrops.js @@ -1,26 +1,28 @@ -define(["backdrop", "userSettings", "libraryMenu"], function (backdrop, userSettings, libraryMenu) { - "use strict"; +define(['backdrop', 'userSettings', 'libraryMenu'], function (backdrop, userSettings, libraryMenu) { + 'use strict'; + + var cache = {}; function enabled() { return userSettings.enableBackdrops(); } function getBackdropItemIds(apiClient, userId, types, parentId) { - var key = "backdrops2_" + userId + (types || "") + (parentId || ""); + var key = `backdrops2_${userId + (types || '') + (parentId || '')}`; var data = cache[key]; if (data) { - console.debug("Found backdrop id list in cache. Key: " + key); + console.debug(`Found backdrop id list in cache. Key: ${key}`); data = JSON.parse(data); return Promise.resolve(data); } var options = { - SortBy: "IsFavoriteOrLiked,Random", + SortBy: 'IsFavoriteOrLiked,Random', Limit: 20, Recursive: true, IncludeItemTypes: types, - ImageTypes: "Backdrop", + ImageTypes: 'Backdrop', ParentId: parentId, EnableTotalRecordCount: false }; @@ -54,18 +56,17 @@ define(["backdrop", "userSettings", "libraryMenu"], function (backdrop, userSett } } - var cache = {}; - pageClassOn("pageshow", "page", function () { + pageClassOn('pageshow', 'page', function () { var page = this; - if (!page.classList.contains("selfBackdropPage")) { - if (page.classList.contains("backdropPage")) { + if (!page.classList.contains('selfBackdropPage')) { + if (page.classList.contains('backdropPage')) { if (enabled()) { - var type = page.getAttribute("data-backdroptype"); - var parentId = page.classList.contains("globalBackdropPage") ? "" : libraryMenu.getTopParentId(); + var type = page.getAttribute('data-backdroptype'); + var parentId = page.classList.contains('globalBackdropPage') ? '' : libraryMenu.getTopParentId(); showBackdrop(type, parentId); } else { - page.classList.remove("backdropPage"); + page.classList.remove('backdropPage'); backdrop.clear(); } } else { diff --git a/src/scripts/browser.js b/src/scripts/browser.js index f9e19423209..a377f08fdb5 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -14,15 +14,11 @@ define([], function () { return true; } - if (userAgent.indexOf('nintendo') !== -1) { - return true; - } - if (userAgent.indexOf('viera') !== -1) { return true; } - if (userAgent.indexOf('webos') !== -1) { + if (userAgent.indexOf('web0s') !== -1) { return true; } @@ -53,45 +49,6 @@ define([], function () { return false; } - function isStyleSupported(prop, value) { - - if (typeof window === 'undefined') { - return false; - } - - // If no value is supplied, use "inherit" - value = arguments.length === 2 ? value : 'inherit'; - // Try the native standard method first - if ('CSS' in window && 'supports' in window.CSS) { - return window.CSS.supports(prop, value); - } - // Check Opera's native method - if ('supportsCSS' in window) { - return window.supportsCSS(prop, value); - } - - // need try/catch because it's failing on tizen - - try { - // Convert to camel-case for DOM interactions - var camel = prop.replace(/-([a-z]|[0-9])/ig, function (all, letter) { - return (letter + '').toUpperCase(); - }); - // Check if the property is supported - var support = (camel in el.style); - // Create test element - var el = document.createElement('div'); - // Assign the property and value to invoke - // the CSS interpreter - el.style.cssText = prop + ':' + value; - // Ensure both the property and value are - // supported and return - return support && (el.style[camel] !== ''); - } catch (err) { - return false; - } - } - function hasKeyboard(browser) { if (browser.touch) { @@ -132,7 +89,7 @@ define([], function () { var _supportsCssAnimation; var _supportsCssAnimationWithPrefix; function supportsCssAnimation(allowPrefix) { - + // TODO: Assess if this is still needed, as all of our targets should natively support CSS animations. if (allowPrefix) { if (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false) { return _supportsCssAnimationWithPrefix; @@ -184,8 +141,7 @@ define([], function () { /(chrome)[ \/]([\w.]+)/.exec(ua) || /(safari)[ \/]([\w.]+)/.exec(ua) || /(firefox)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; var versionMatch = /(version)[ \/]([\w.]+)/.exec(ua); @@ -196,18 +152,10 @@ define([], function () { /(android)/.exec(ua) || []; - var browser = match[1] || ""; - - if (browser === "edge") { - platform_match = [""]; - } else { - if (ua.indexOf("windows phone") !== -1 || ua.indexOf("iemobile") !== -1) { + var browser = match[1] || ''; - // http://www.neowin.net/news/ie11-fakes-user-agent-to-fool-gmail-in-windows-phone-81-gdr1-update - browser = "msie"; - } else if (ua.indexOf("like gecko") !== -1 && ua.indexOf('webkit') === -1 && ua.indexOf('opera') === -1 && ua.indexOf('chrome') === -1 && ua.indexOf('safari') === -1) { - browser = "msie"; - } + if (browser === 'edge') { + platform_match = ['']; } if (browser === 'opr') { @@ -219,7 +167,7 @@ define([], function () { version = versionMatch[2]; } - version = version || match[2] || "0"; + version = version || match[2] || '0'; var versionMajor = parseInt(version.split('.')[0]); @@ -230,7 +178,7 @@ define([], function () { return { browser: browser, version: version, - platform: platform_match[0] || "", + platform: platform_match[0] || '', versionMajor: versionMajor }; }; @@ -250,11 +198,11 @@ define([], function () { browser[matched.platform] = true; } - if (!browser.chrome && !browser.msie && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf("webkit") !== -1) { + if (!browser.chrome && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf('webkit') !== -1) { browser.safari = true; } - if (userAgent.toLowerCase().indexOf("playstation 4") !== -1) { + if (userAgent.toLowerCase().indexOf('playstation 4') !== -1) { browser.ps4 = true; browser.tv = true; } @@ -263,7 +211,10 @@ define([], function () { browser.mobile = true; } - browser.xboxOne = userAgent.toLowerCase().indexOf('xbox') !== -1; + if (userAgent.toLowerCase().indexOf('xbox') !== -1) { + browser.xboxOne = true; + browser.tv = true; + } browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null; browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || self.tizen != null; browser.web0s = userAgent.toLowerCase().indexOf('Web0S'.toLowerCase()) !== -1; @@ -283,16 +234,13 @@ define([], function () { browser.tv = isTv(); browser.operaTv = browser.tv && userAgent.toLowerCase().indexOf('opr/') !== -1; - if (!isStyleSupported('display', 'flex')) { - browser.noFlex = true; - } - if (browser.mobile || browser.tv) { browser.slow = true; } if (typeof document !== 'undefined') { - if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + /* eslint-disable-next-line compat/compat */ + if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) { browser.touch = true; } } diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserDeviceProfile.js similarity index 77% rename from src/scripts/browserdeviceprofile.js rename to src/scripts/browserDeviceProfile.js index 337463987cc..fcbe0025497 100644 --- a/src/scripts/browserdeviceprofile.js +++ b/src/scripts/browserDeviceProfile.js @@ -6,18 +6,10 @@ define(['browser'], function (browser) { } function canPlayH265(videoTestElement, options) { - if (browser.tizen || browser.orsay || browser.xboxOne || browser.web0s || options.supportsHevc) { + if (browser.tizen || browser.xboxOne || browser.web0s || options.supportsHevc) { return true; } - var userAgent = navigator.userAgent.toLowerCase(); - if (browser.chromecast) { - var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; - if (isChromecastUltra) { - return true; - } - } - if (browser.ps4) { return false; } @@ -31,7 +23,7 @@ define(['browser'], function (browser) { var _supportsTextTracks; function supportsTextTracks() { - if (browser.tizen || browser.orsay) { + if (browser.tizen) { return true; } @@ -53,7 +45,7 @@ define(['browser'], function (browser) { } function canPlayNativeHls() { - if (browser.tizen || browser.orsay) { + if (browser.tizen) { return true; } @@ -68,11 +60,11 @@ define(['browser'], function (browser) { function canPlayHlsWithMSE() { // text tracks don’t work with this in firefox - return window.MediaSource != null; + return window.MediaSource != null; /* eslint-disable-line compat/compat */ } function supportsAc3(videoTestElement) { - if (browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s) { + if (browser.edgeUwp || browser.tizen || browser.web0s) { return true; } @@ -80,7 +72,7 @@ define(['browser'], function (browser) { } function supportsEac3(videoTestElement) { - if (browser.tizen || browser.orsay || browser.web0s) { + if (browser.tizen || browser.web0s) { return true; } @@ -88,7 +80,7 @@ define(['browser'], function (browser) { } function supportsAc3InHls(videoTestElement) { - if (browser.tizen || browser.orsay || browser.web0s) { + if (browser.tizen || browser.web0s) { return true; } @@ -104,11 +96,11 @@ define(['browser'], function (browser) { var typeString; if (format === 'flac') { - if (browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp) { + if (browser.tizen || browser.web0s || browser.edgeUwp) { return true; } } else if (format === 'wma') { - if (browser.tizen || browser.orsay || browser.edgeUwp) { + if (browser.tizen || browser.edgeUwp) { return true; } } else if (format === 'asf') { @@ -143,7 +135,7 @@ define(['browser'], function (browser) { } function testCanPlayMkv(videoTestElement) { - if (browser.tizen || browser.orsay || browser.web0s) { + if (browser.tizen || browser.web0s) { return true; } @@ -152,54 +144,33 @@ define(['browser'], function (browser) { return true; } - // Unfortunately there's no real way to detect mkv support - if (browser.chrome) { - // Not supported on opera tv - if (browser.operaTv) { - return false; - } - - var userAgent = navigator.userAgent.toLowerCase(); - - // Filter out browsers based on chromium that don't support mkv - if (userAgent.indexOf('vivaldi') !== -1 || userAgent.indexOf('opera') !== -1) { - return false; - } - + if (browser.edgeUwp) { return true; } - if (browser.edgeUwp) { + return false; + } + + function testCanPlayAv1(videoTestElement) { + if (browser.tizenVersion >= 5.5) { + return true; + } else if (browser.web0sVersion >= 5 && window.outerHeight >= 2160) { return true; } - return false; + return videoTestElement.canPlayType('video/webm; codecs="av01.0.15M.10"').replace(/no/, ''); } function testCanPlayTs() { - return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + return browser.tizen || browser.web0s || browser.edgeUwp; } function supportsMpeg2Video() { - return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + return browser.tizen || browser.web0s || browser.edgeUwp; } - function supportsVc1() { - return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; - } - - function getFlvMseDirectPlayProfile() { - var videoAudioCodecs = ['aac']; - if (!browser.edge && !browser.msie) { - videoAudioCodecs.push('mp3'); - } - - return { - Container: 'flv', - Type: 'Video', - VideoCodec: 'h264', - AudioCodec: videoAudioCodecs.join(',') - }; + function supportsVc1(videoTestElement) { + return browser.tizen || browser.web0s || browser.edgeUwp || videoTestElement.canPlayType('video/mp4; codecs="vc-1"').replace(/no/, ''); } function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) { @@ -209,11 +180,11 @@ define(['browser'], function (browser) { switch (container) { case 'asf': - supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.edgeUwp; videoAudioCodecs = []; break; case 'avi': - supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.edgeUwp; // New Samsung TV don't support XviD/DivX // Explicitly add supported codecs to make other codecs be transcoded if (browser.tizenVersion >= 4) { @@ -226,29 +197,26 @@ define(['browser'], function (browser) { break; case 'mpg': case 'mpeg': - supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.edgeUwp; break; case 'flv': - supported = browser.tizen || browser.orsay; - //if (!supported && window.MediaSource != null && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) { - // return getFlvMseDirectPlayProfile(); - //} + supported = browser.tizen; break; case '3gp': case 'mts': case 'trp': case 'vob': case 'vro': - supported = browser.tizen || browser.orsay; + supported = browser.tizen; break; case 'mov': - supported = browser.tizen || browser.orsay || browser.web0s || browser.chrome || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.chrome || browser.edgeUwp; videoCodecs.push('h264'); break; case 'm2ts': - supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.edgeUwp; videoCodecs.push('h264'); - if (supportsVc1()) { + if (supportsVc1(videoTestElement)) { videoCodecs.push('vc1'); } if (supportsMpeg2Video()) { @@ -256,7 +224,7 @@ define(['browser'], function (browser) { } break; case 'wmv': - supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + supported = browser.tizen || browser.web0s || browser.edgeUwp; videoAudioCodecs = []; break; case 'ts': @@ -266,7 +234,7 @@ define(['browser'], function (browser) { videoCodecs.push('h265'); videoCodecs.push('hevc'); } - if (supportsVc1()) { + if (supportsVc1(videoTestElement)) { videoCodecs.push('vc1'); } if (supportsMpeg2Video()) { @@ -291,29 +259,14 @@ define(['browser'], function (browser) { } function getGlobalMaxVideoBitrate() { - var userAgent = navigator.userAgent.toLowerCase(); - if (browser.chromecast) { - var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; - if (isChromecastUltra) { - return null; - } - - // This is a hack to try and detect chromecast on vizio - if (self.screen && self.screen.width >= 3800) { - return null; - } - - return 30000000; - } - var isTizenFhd = false; if (browser.tizen) { try { var isTizenUhd = webapis.productinfo.isUdPanelSupported(); isTizenFhd = !isTizenUhd; - console.debug("isTizenFhd = " + isTizenFhd); + console.debug('isTizenFhd = ' + isTizenFhd); } catch (error) { - console.error("isUdPanelSupported() error code = " + error.code); + console.error('isUdPanelSupported() error code = ' + error.code); } } @@ -349,11 +302,12 @@ define(['browser'], function (browser) { var videoAudioCodecs = []; var hlsVideoAudioCodecs = []; - var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '') || - videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, ''); + var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '') + || videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, '') + || videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp3"').replace(/no/, ''); // Not sure how to test for this - var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s; + var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.web0s; var maxVideoWidth = browser.xboxOne ? (self.screen ? self.screen.width : null) : @@ -365,11 +319,6 @@ define(['browser'], function (browser) { var canPlayAacVideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"').replace(/no/, ''); - if (canPlayAacVideoAudio && browser.chromecast && physicalAudioChannels <= 2) { - // prioritize this first - videoAudioCodecs.push('aac'); - } - // Only put mp3 first if mkv support is there // Otherwise with HLS and mp3 audio we're seeing some browsers // safari is lying @@ -392,11 +341,6 @@ define(['browser'], function (browser) { } } - if (canPlayAacVideoAudio && browser.chromecast && videoAudioCodecs.indexOf('aac') === -1) { - // prioritize this first - videoAudioCodecs.push('aac'); - } - if (supportsMp3VideoAudio) { videoAudioCodecs.push('mp3'); @@ -431,15 +375,11 @@ define(['browser'], function (browser) { videoAudioCodecs.push('mp2'); } - var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts; + var supportsDts = browser.tizen || browser.web0s || options.supportsDts || videoTestElement.canPlayType('video/mp4; codecs="dts-"').replace(/no/, '') || videoTestElement.canPlayType('video/mp4; codecs="dts+"').replace(/no/, ''); - if (self.tizen && self.tizen.systeminfo) { - var v = tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version'); - - // DTS audio not supported in 2018 models (Tizen 4.0) - if (v && parseFloat(v) >= parseFloat('4.0')) { - supportsDts = false; - } + // DTS audio not supported in 2018 models (Tizen 4.0) + if (browser.tizenVersion >= 4) { + supportsDts = false; } if (supportsDts) { @@ -447,7 +387,7 @@ define(['browser'], function (browser) { videoAudioCodecs.push('dts'); } - if (browser.tizen || browser.orsay || browser.web0s) { + if (browser.tizen || browser.web0s) { videoAudioCodecs.push('pcm_s16le'); videoAudioCodecs.push('pcm_s24le'); } @@ -456,7 +396,7 @@ define(['browser'], function (browser) { videoAudioCodecs.push('truehd'); } - if (browser.tizen || browser.orsay) { + if (browser.tizen) { videoAudioCodecs.push('aac_latm'); } @@ -479,6 +419,7 @@ define(['browser'], function (browser) { }); var mp4VideoCodecs = []; + var webmVideoCodecs = []; var hlsVideoCodecs = []; if (canPlayH264(videoTestElement)) { @@ -500,26 +441,42 @@ define(['browser'], function (browser) { mp4VideoCodecs.push('mpeg2video'); } - if (supportsVc1()) { + if (supportsVc1(videoTestElement)) { mp4VideoCodecs.push('vc1'); } - if (browser.tizen || browser.orsay) { + if (browser.tizen) { mp4VideoCodecs.push('msmpeg4v2'); } if (canPlayVp8) { mp4VideoCodecs.push('vp8'); + webmVideoCodecs.push('vp8'); } if (canPlayVp9) { mp4VideoCodecs.push('vp9'); + webmVideoCodecs.push('vp9'); + } + + if (testCanPlayAv1(videoTestElement)) { + mp4VideoCodecs.push('av1'); + webmVideoCodecs.push('av1'); } - if (canPlayVp8 || browser.tizen || browser.orsay) { + if (canPlayVp8 || browser.tizen) { videoAudioCodecs.push('vorbis'); } + if (webmVideoCodecs.length) { + profile.DirectPlayProfiles.push({ + Container: 'webm', + Type: 'Video', + VideoCodec: webmVideoCodecs.join(','), + AudioCodec: webmAudioCodecs.join(',') + }); + } + if (mp4VideoCodecs.length) { profile.DirectPlayProfiles.push({ Container: 'mp4,m4v', @@ -578,24 +535,6 @@ define(['browser'], function (browser) { } }); - if (canPlayVp8) { - profile.DirectPlayProfiles.push({ - Container: 'webm', - Type: 'Video', - AudioCodec: webmAudioCodecs.join(','), - VideoCodec: 'VP8' - }); - } - - if (canPlayVp9) { - profile.DirectPlayProfiles.push({ - Container: 'webm', - Type: 'Video', - AudioCodec: webmAudioCodecs.join(','), - VideoCodec: 'VP9' - }); - } - profile.TranscodingProfiles = []; var hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls() ? true : false; @@ -639,7 +578,7 @@ define(['browser'], function (browser) { }); }); - if (canPlayMkv && !browser.tizen && !browser.orsay && options.enableMkvProgressive !== false) { + if (canPlayMkv && !browser.tizen && options.enableMkvProgressive !== false) { profile.TranscodingProfiles.push({ Container: 'mkv', Type: 'Video', @@ -704,7 +643,7 @@ define(['browser'], function (browser) { profile.CodecProfiles = []; - var supportsSecondaryAudio = browser.tizen || browser.orsay || videoTestElement.audioTracks; + var supportsSecondaryAudio = browser.tizen || videoTestElement.audioTracks; var aacCodecProfileConditions = []; @@ -727,15 +666,6 @@ define(['browser'], function (browser) { }); } - if (browser.chromecast) { - aacCodecProfileConditions.push({ - Condition: 'LessThanEqual', - Property: 'AudioChannels', - Value: '2', - IsRequired: true - }); - } - if (aacCodecProfileConditions.length) { profile.CodecProfiles.push({ Type: 'VideoAudio', @@ -761,12 +691,17 @@ define(['browser'], function (browser) { var maxH264Level = 42; var h264Profiles = 'high|main|baseline|constrained baseline'; - if (browser.tizen || browser.orsay || browser.web0s || + if (browser.tizen || browser.web0s || videoTestElement.canPlayType('video/mp4; codecs="avc1.640833"').replace(/no/, '')) { maxH264Level = 51; } - if (browser.tizen || browser.orsay || + // Support H264 Level 52 (Tizen 5.0) - app only + if (browser.tizenVersion >= 5 && window.NativeShell) { + maxH264Level = 52; + } + + if (browser.tizen || videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { // These tests are passing in safari, but playback is failing @@ -800,20 +735,13 @@ define(['browser'], function (browser) { ] }); - if (!browser.edgeUwp && !browser.tizen && !browser.orsay && !browser.web0s) { - //profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ - // Condition: 'NotEquals', - // Property: 'IsAVC', - // Value: 'false', - // IsRequired: false - //}); - - //profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ - // Condition: 'NotEquals', - // Property: 'IsInterlaced', - // Value: 'true', - // IsRequired: false - //}); + if (!browser.edgeUwp && !browser.tizen && !browser.web0s) { + profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ + Condition: 'NotEquals', + Property: 'IsInterlaced', + Value: 'true', + IsRequired: false + }); } if (maxVideoWidth) { @@ -864,19 +792,6 @@ define(['browser'], function (browser) { }); } - if (browser.chromecast) { - profile.CodecProfiles.push({ - Type: 'Audio', - Codec: 'flac', - Conditions: [ - { - Condition: 'LessThanEqual', - Property: 'AudioSampleRate', - Value: '96000' - }] - }); - } - // Subtitle profiles // External vtt or burn in profile.SubtitleProfiles = []; @@ -886,6 +801,16 @@ define(['browser'], function (browser) { Method: 'External' }); } + if (options.enableSsaRender) { + profile.SubtitleProfiles.push({ + Format: 'ass', + Method: 'External' + }); + profile.SubtitleProfiles.push({ + Format: 'ssa', + Method: 'External' + }); + } profile.ResponseProfiles = []; profile.ResponseProfiles.push({ diff --git a/src/scripts/datetime.js b/src/scripts/datetime.js index 8a7666844c9..34ff23fe637 100644 --- a/src/scripts/datetime.js +++ b/src/scripts/datetime.js @@ -41,12 +41,12 @@ define(['globalize'], function (globalize) { } // if there's a timezone, calculate it - if (d[8] !== "Z" && d[10]) { + if (d[8] !== 'Z' && d[10]) { var offset = d[10] * 60 * 60 * 1000; if (d[11]) { offset += d[11] * 60 * 1000; } - if (d[9] === "-") { + if (d[9] === '-') { ms -= offset; } else { ms += offset; @@ -159,13 +159,13 @@ define(['globalize'], function (globalize) { var optionList = getOptionList(options); if (optionList.length === 1 && optionList[0].name === 'weekday') { var weekday = []; - weekday[0] = "Sun"; - weekday[1] = "Mon"; - weekday[2] = "Tue"; - weekday[3] = "Wed"; - weekday[4] = "Thu"; - weekday[5] = "Fri"; - weekday[6] = "Sat"; + weekday[0] = 'Sun'; + weekday[1] = 'Mon'; + weekday[2] = 'Tue'; + weekday[3] = 'Wed'; + weekday[4] = 'Thu'; + weekday[5] = 'Fri'; + weekday[6] = 'Sat'; return weekday[date.getDay()]; } diff --git a/src/scripts/deleteHelper.js b/src/scripts/deleteHelper.js new file mode 100644 index 00000000000..e13eb9c4aee --- /dev/null +++ b/src/scripts/deleteHelper.js @@ -0,0 +1,54 @@ +import connectionManager from 'connectionManager'; +import confirm from 'confirm'; +import appRouter from 'appRouter'; +import globalize from 'globalize'; + +function alertText(options) { + + return new Promise(function (resolve, reject) { + + require(['alert'], function (alert) { + alert(options).then(resolve, resolve); + }); + }); +} + +export function deleteItem(options) { + + const item = options.item; + const parentId = item.SeasonId || item.SeriesId || item.ParentId; + + let apiClient = connectionManager.getApiClient(item.ServerId); + + return confirm({ + + title: globalize.translate('HeaderDeleteItem'), + text: globalize.translate('ConfirmDeleteItem'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + + }).then(function () { + + return apiClient.deleteItem(item.Id).then(function () { + + if (options.navigate) { + if (parentId) { + appRouter.showItem(parentId, item.ServerId); + } else { + appRouter.goHome(); + } + } + }, function (err) { + + let result = function () { + return Promise.reject(err); + }; + + return alertText(globalize.translate('ErrorDeletingItem')).then(result, result); + }); + }); +} + +export default { + deleteItem: deleteItem +}; diff --git a/src/scripts/dfnshelper.js b/src/scripts/dfnshelper.js new file mode 100644 index 00000000000..6ad2ee97096 --- /dev/null +++ b/src/scripts/dfnshelper.js @@ -0,0 +1,62 @@ +import { ar, be, bg, ca, cs, da, de, el, enGB, enUS, es, faIR, fi, fr, frCA, he, hi, hr, hu, id, it, ja, kk, ko, lt, ms, nb, + nl, pl, ptBR, pt, ro, ru, sk, sl, sv, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale'; +import globalize from 'globalize'; + +const dateLocales = (locale) => ({ + 'ar': ar, + 'be-by': be, + 'bg-bg': bg, + 'ca': ca, + 'cs': cs, + 'da': da, + 'de': de, + 'el': el, + 'en-gb': enGB, + 'en-us': enUS, + 'es': es, + 'es-ar': es, + 'es-mx': es, + 'fa': faIR, + 'fi': fi, + 'fr': fr, + 'fr-ca': frCA, + 'gsw': de, + 'he': he, + 'hi-in': hi, + 'hr': hr, + 'hu': hu, + 'id': id, + 'it': it, + 'ja': ja, + 'kk': kk, + 'ko': ko, + 'lt-lt': lt, + 'ms': ms, + 'nb': nb, + 'nl': nl, + 'pl': pl, + 'pt-br': ptBR, + 'pt-pt': pt, + 'ro': ro, + 'ru': ru, + 'sk': sk, + 'sl-si': sl, + 'sv': sv, + 'tr': tr, + 'uk': uk, + 'vi': vi, + 'zh-cn': zhCN, + 'zh-hk': zhCN, + 'zh-tw': zhTW +})[locale]; + +export function getLocale() { + return dateLocales(globalize.getCurrentLocale()) || enUS; +} + +export const localeWithSuffix = { addSuffix: true, locale: getLocale() }; + +export default { + getLocale: getLocale, + localeWithSuffix: localeWithSuffix +}; diff --git a/src/scripts/dom.js b/src/scripts/dom.js new file mode 100644 index 00000000000..080bff66ae2 --- /dev/null +++ b/src/scripts/dom.js @@ -0,0 +1,278 @@ +/* eslint-disable indent */ + +/** + * Useful DOM utilities. + * @module components/dom + */ + + /** + * Returns parent of element with specified attribute value. + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {string} name - Attribute name. + * @param {mixed} value - Attribute value. + * @returns {HTMLElement} Parent with specified attribute value. + */ + export function parentWithAttribute(elem, name, value) { + while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) { + elem = elem.parentNode; + + if (!elem || !elem.getAttribute) { + return null; + } + } + + return elem; + } + + /** + * Returns parent of element with one of specified tag names. + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {(string|Array)} tagNames - Tag name or array of tag names. + * @returns {HTMLElement} Parent with one of specified tag names. + */ + export function parentWithTag(elem, tagNames) { + // accept both string and array passed in + if (!Array.isArray(tagNames)) { + tagNames = [tagNames]; + } + + while (tagNames.indexOf(elem.tagName || '') === -1) { + elem = elem.parentNode; + + if (!elem) { + return null; + } + } + + return elem; + } + + /** + * Returns _true_ if class list contains one of specified names. + * @param {DOMTokenList} classList - Class list. + * @param {Array} classNames - Array of class names. + * @returns {boolean} _true_ if class list contains one of specified names. + */ + function containsAnyClass(classList, classNames) { + for (let i = 0, length = classNames.length; i < length; i++) { + if (classList.contains(classNames[i])) { + return true; + } + } + return false; + } + + /** + * Returns parent of element with one of specified class names. + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {(string|Array)} classNames - Class name or array of class names. + * @returns {HTMLElement} Parent with one of specified class names. + */ + export function parentWithClass(elem, classNames) { + // accept both string and array passed in + if (!Array.isArray(classNames)) { + classNames = [classNames]; + } + + while (!elem.classList || !containsAnyClass(elem.classList, classNames)) { + elem = elem.parentNode; + + if (!elem) { + return null; + } + } + + return elem; + } + + let supportsCaptureOption = false; + try { + const opts = Object.defineProperty({}, 'capture', { + // eslint-disable-next-line getter-return + get: function () { + supportsCaptureOption = true; + } + }); + window.addEventListener('test', null, opts); + } catch (e) { + console.debug('error checking capture support'); + } + + /** + * Adds event listener to specified target. + * @param {EventTarget} target - Event target. + * @param {string} type - Event type. + * @param {function} handler - Event handler. + * @param {Object} [options] - Listener options. + */ + export function addEventListener(target, type, handler, options) { + let optionsOrCapture = options || {}; + if (!supportsCaptureOption) { + optionsOrCapture = optionsOrCapture.capture; + } + target.addEventListener(type, handler, optionsOrCapture); + } + + /** + * Removes event listener from specified target. + * @param {EventTarget} target - Event target. + * @param {string} type - Event type. + * @param {function} handler - Event handler. + * @param {Object} [options] - Listener options. + */ + export function removeEventListener(target, type, handler, options) { + let optionsOrCapture = options || {}; + if (!supportsCaptureOption) { + optionsOrCapture = optionsOrCapture.capture; + } + target.removeEventListener(type, handler, optionsOrCapture); + } + + /** + * Cached window size. + */ + let windowSize; + + /** + * Flag of event listener bound. + */ + let windowSizeEventsBound; + + /** + * Resets cached window size. + */ + function clearWindowSize() { + windowSize = null; + } + + /** + * Returns window size. + * @returns {Object} Window size. + */ + export function getWindowSize() { + if (!windowSize) { + windowSize = { + innerHeight: window.innerHeight, + innerWidth: window.innerWidth + }; + + if (!windowSizeEventsBound) { + windowSizeEventsBound = true; + addEventListener(window, 'orientationchange', clearWindowSize, { passive: true }); + addEventListener(window, 'resize', clearWindowSize, { passive: true }); + } + } + + return windowSize; + } + + /** + * Standard screen widths. + */ + const standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680]; + + /** + * Returns screen width. + * @returns {number} Screen width. + */ + export function getScreenWidth() { + let width = window.innerWidth; + const height = window.innerHeight; + + if (height > width) { + width = height * (16.0 / 9.0); + } + + const closest = standardWidths.sort(function (a, b) { + return Math.abs(width - a) - Math.abs(width - b); + })[0]; + + return closest; + } + + /** + * Name of animation end event. + */ + let _animationEvent; + + /** + * Returns name of animation end event. + * @returns {string} Name of animation end event. + */ + export function whichAnimationEvent() { + if (_animationEvent) { + return _animationEvent; + } + + const el = document.createElement('div'); + const animations = { + 'animation': 'animationend', + 'OAnimation': 'oAnimationEnd', + 'MozAnimation': 'animationend', + 'WebkitAnimation': 'webkitAnimationEnd' + }; + for (const t in animations) { + if (el.style[t] !== undefined) { + _animationEvent = animations[t]; + return animations[t]; + } + } + + _animationEvent = 'animationend'; + return _animationEvent; + } + + /** + * Returns name of animation cancel event. + * @returns {string} Name of animation cancel event. + */ + export function whichAnimationCancelEvent() { + return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel'); + } + + /** + * Name of transition end event. + */ + let _transitionEvent; + + /** + * Returns name of transition end event. + * @returns {string} Name of transition end event. + */ + export function whichTransitionEvent() { + if (_transitionEvent) { + return _transitionEvent; + } + + const el = document.createElement('div'); + const transitions = { + 'transition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'MozTransition': 'transitionend', + 'WebkitTransition': 'webkitTransitionEnd' + }; + for (const t in transitions) { + if (el.style[t] !== undefined) { + _transitionEvent = transitions[t]; + return transitions[t]; + } + } + + _transitionEvent = 'transitionend'; + return _transitionEvent; + } + +/* eslint-enable indent */ + +export default { + parentWithAttribute: parentWithAttribute, + parentWithClass: parentWithClass, + parentWithTag: parentWithTag, + addEventListener: addEventListener, + removeEventListener: removeEventListener, + getWindowSize: getWindowSize, + getScreenWidth: getScreenWidth, + whichTransitionEvent: whichTransitionEvent, + whichAnimationEvent: whichAnimationEvent, + whichAnimationCancelEvent: whichAnimationCancelEvent +}; diff --git a/src/scripts/editorsidebar.js b/src/scripts/editorsidebar.js index b7cb4b5c576..81683895667 100644 --- a/src/scripts/editorsidebar.js +++ b/src/scripts/editorsidebar.js @@ -1,5 +1,5 @@ -define(["datetime", "jQuery", "material-icons"], function (datetime, $) { - "use strict"; +define(['datetime', 'jQuery', 'globalize', 'material-icons'], function (datetime, $, globalize) { + 'use strict'; function getNode(item, folderState, selected) { var htmlName = getNodeInnerHtml(item); @@ -7,7 +7,7 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { id: item.Id, text: htmlName, state: { - opened: item.IsFolder && folderState == "open", + opened: item.IsFolder && folderState == 'open', selected: selected }, li_attr: { @@ -17,7 +17,7 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { }; if (item.IsFolder) { node.children = [{ - text: "Loading...", + text: 'Loading...', icon: false }]; node.icon = false; @@ -36,30 +36,30 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { function getNodeInnerHtml(item) { var name = item.Name; if (item.Number) { - name = item.Number + " - " + name; + name = item.Number + ' - ' + name; } - if (item.IndexNumber != null && item.Type != "Season") { - name = item.IndexNumber + " - " + name; + if (item.IndexNumber != null && item.Type != 'Season') { + name = item.IndexNumber + ' - ' + name; } var htmlName = "
"; if (item.IsFolder) { - htmlName += 'folder'; - } else if (item.MediaType === "Video") { - htmlName += 'movie'; - } else if (item.MediaType === "Audio") { - htmlName += 'audiotrack'; - } else if (item.Type === "TvChannel") { - htmlName += ''; - } else if (item.MediaType === "Photo") { - htmlName += 'photo'; - } else if (item.MediaType === "Book") { - htmlName += 'book'; + htmlName += ''; + } else if (item.MediaType === 'Video') { + htmlName += ''; + } else if (item.MediaType === 'Audio') { + htmlName += ''; + } else if (item.Type === 'TvChannel') { + htmlName += ''; + } else if (item.MediaType === 'Photo') { + htmlName += ''; + } else if (item.MediaType === 'Book') { + htmlName += ''; } if (item.LockData) { - htmlName += 'lock'; + htmlName += ''; } htmlName += name; - htmlName += "
"; + htmlName += '
'; return htmlName; } @@ -69,36 +69,36 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { }).then(function (result) { var nodes = []; nodes.push({ - id: "MediaFolders", - text: Globalize.translate("HeaderMediaFolders"), + id: 'MediaFolders', + text: globalize.translate('HeaderMediaFolders'), state: { opened: true }, li_attr: { - itemtype: "mediafolders", + itemtype: 'mediafolders', loadedFromServer: true }, icon: false }); if (result.TotalRecordCount) { nodes.push({ - id: "livetv", - text: Globalize.translate("HeaderLiveTV"), + id: 'livetv', + text: globalize.translate('HeaderLiveTV'), state: { opened: false }, li_attr: { - itemtype: "livetv" + itemtype: 'livetv' }, children: [{ - text: "Loading...", + text: 'Loading...', icon: false }], icon: false }); } callback.call(scope, nodes); - nodesToLoad.push("MediaFolders"); + nodesToLoad.push('MediaFolders'); }); } @@ -108,7 +108,7 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { AddCurrentProgram: false }).then(function (result) { var nodes = result.Items.map(function (i) { - var state = openItems.indexOf(i.Id) == -1 ? "closed" : "open"; + var state = openItems.indexOf(i.Id) == -1 ? 'closed' : 'open'; return getNode(i, state, false); }); callback(nodes); @@ -116,9 +116,9 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { } function loadMediaFolders(page, scope, openItems, callback) { - ApiClient.getJSON(ApiClient.getUrl("Library/MediaFolders")).then(function (result) { + ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders')).then(function (result) { var nodes = result.Items.map(function (n) { - var state = openItems.indexOf(n.Id) == -1 ? "closed" : "open"; + var state = openItems.indexOf(n.Id) == -1 ? 'closed' : 'open'; return getNode(n, state, false); }); callback.call(scope, nodes); @@ -132,21 +132,21 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { function loadNode(page, scope, node, openItems, selectedId, currentUser, callback) { var id = node.id; - if (id == "#") { + if (id == '#') { loadChildrenOfRootNode(page, scope, callback); return; } - if (id == "livetv") { + if (id == 'livetv') { loadLiveTvChannels(id, openItems, callback); return; } - if (id == "MediaFolders") { + if (id == 'MediaFolders') { loadMediaFolders(page, scope, openItems, callback); return; } var query = { ParentId: id, - Fields: "Settings", + Fields: 'Settings', IsVirtualUnaired: false, IsMissing: false, EnableTotalRecordCount: false, @@ -154,12 +154,12 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { EnableUserData: false }; var itemtype = node.li_attr.itemtype; - if (itemtype != "Season" && itemtype != "Series") { - query.SortBy = "SortName"; + if (itemtype != 'Season' && itemtype != 'Series') { + query.SortBy = 'SortName'; } ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { var nodes = result.Items.map(function (n) { - var state = openItems.indexOf(n.Id) == -1 ? "closed" : "open"; + var state = openItems.indexOf(n.Id) == -1 ? 'closed' : 'open'; return getNode(n, state, n.Id == selectedId); }); callback.call(scope, nodes); @@ -172,14 +172,14 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { } function scrollToNode(id) { - var elem = $("#" + id)[0]; + var elem = $('#' + id)[0]; if (elem) { elem.scrollIntoView(); } } function initializeTree(page, currentUser, openItems, selectedId) { - require(["jstree"], function () { + require(['jstree'], function () { initializeTreeInternal(page, currentUser, openItems, selectedId); }); } @@ -192,41 +192,41 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { serverItemType: node.li_attr.serveritemtype, collectionType: node.li_attr.collectiontype }; - if (eventData.itemType != "livetv" && eventData.itemType != "mediafolders") { + if (eventData.itemType != 'livetv' && eventData.itemType != 'mediafolders') { { - this.dispatchEvent(new CustomEvent("itemclicked", { + this.dispatchEvent(new CustomEvent('itemclicked', { detail: eventData, bubbles: true, cancelable: false })); } - document.querySelector(".editPageSidebar").classList.add("editPageSidebar-withcontent"); + document.querySelector('.editPageSidebar').classList.add('editPageSidebar-withcontent'); } else { - document.querySelector(".editPageSidebar").classList.remove("editPageSidebar-withcontent"); + document.querySelector('.editPageSidebar').classList.remove('editPageSidebar-withcontent'); } } function onNodeOpen(event, data) { - var page = $(this).parents(".page")[0]; + var page = $(this).parents('.page')[0]; var node = data.node; - if (node.children && node.children) { + if (node.children) { loadNodesToLoad(page, node); } - if (node.li_attr && node.id != "#" && !node.li_attr.loadedFromServer) { + if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) { node.li_attr.loadedFromServer = true; - $.jstree.reference(".libraryTree", page).load_node(node.id, loadNodeCallback); + $.jstree.reference('.libraryTree', page).load_node(node.id, loadNodeCallback); } } function onNodeLoad(event, data) { - var page = $(this).parents(".page")[0]; + var page = $(this).parents('.page')[0]; var node = data.node; - if (node.children && node.children) { + if (node.children) { loadNodesToLoad(page, node); } - if (node.li_attr && node.id != "#" && !node.li_attr.loadedFromServer) { + if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) { node.li_attr.loadedFromServer = true; - $.jstree.reference(".libraryTree", page).load_node(node.id, loadNodeCallback); + $.jstree.reference('.libraryTree', page).load_node(node.id, loadNodeCallback); } } @@ -234,18 +234,18 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { nodesToLoad = []; selectedNodeId = null; $.jstree.destroy(); - $(".libraryTree", page).jstree({ - "plugins": ["wholerow"], + $('.libraryTree', page).jstree({ + 'plugins': ['wholerow'], core: { check_callback: true, data: function (node, callback) { loadNode(page, this, node, openItems, selectedId, currentUser, callback); }, themes: { - variant: "large" + variant: 'large' } } - }).off("select_node.jstree", onNodeSelect).on("select_node.jstree", onNodeSelect).off("open_node.jstree", onNodeOpen).on("open_node.jstree", onNodeOpen).off("load_node.jstree", onNodeLoad).on("load_node.jstree", onNodeLoad); + }).off('select_node.jstree', onNodeSelect).on('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).on('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad).on('load_node.jstree', onNodeLoad); } function loadNodesToLoad(page, node) { @@ -256,7 +256,7 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { nodesToLoad = nodesToLoad.filter(function (n) { return n != child; }); - $.jstree.reference(".libraryTree", page).load_node(child, loadNodeCallback); + $.jstree.reference('.libraryTree', page).load_node(child, loadNodeCallback); } } } @@ -270,14 +270,14 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { } function updateEditorNode(page, item) { - var elem = $("#" + item.Id + ">a", page)[0]; + var elem = $('#' + item.Id + '>a', page)[0]; if (elem == null) { return; } - $(".editorNode", elem).remove(); + $('.editorNode', elem).remove(); $(elem).append(getNodeInnerHtml(item)); if (item.IsFolder) { - var tree = jQuery.jstree._reference(".libraryTree"); + var tree = jQuery.jstree._reference('.libraryTree'); var currentNode = tree._get_node(null, false); tree.refresh(currentNode); } @@ -292,15 +292,15 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { return itemId; } var url = window.location.hash || window.location.href; - return getParameterByName("id", url); + return getParameterByName('id', url); } var nodesToLoad = []; var selectedNodeId; - $(document).on("itemsaved", ".metadataEditorPage", function (e, item) { + $(document).on('itemsaved', '.metadataEditorPage', function (e, item) { updateEditorNode(this, item); - }).on("pagebeforeshow", ".metadataEditorPage", function () { - require(["css!assets/css/metadataeditor.css"]); - }).on("pagebeforeshow", ".metadataEditorPage", function () { + }).on('pagebeforeshow', '.metadataEditorPage', function () { + require(['css!assets/css/metadataeditor.css']); + }).on('pagebeforeshow', '.metadataEditorPage', function () { var page = this; Dashboard.getCurrentUser().then(function (user) { var id = getCurrentItemId(); @@ -315,9 +315,9 @@ define(["datetime", "jQuery", "material-icons"], function (datetime, $) { initializeTree(page, user, []); } }); - }).on("pagebeforehide", ".metadataEditorPage", function () { + }).on('pagebeforehide', '.metadataEditorPage', function () { var page = this; - $(".libraryTree", page).off("select_node.jstree", onNodeSelect).off("open_node.jstree", onNodeOpen).off("load_node.jstree", onNodeLoad); + $('.libraryTree', page).off('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad); }); var itemId; window.MetadataEditor = { diff --git a/src/components/filedownloader.js b/src/scripts/fileDownloader.js similarity index 69% rename from src/components/filedownloader.js rename to src/scripts/fileDownloader.js index d64e48967c2..f2ca42211f2 100644 --- a/src/components/filedownloader.js +++ b/src/scripts/fileDownloader.js @@ -1,10 +1,10 @@ -import multiDownload from "multi-download" +import multiDownload from 'multi-download'; export function download(items) { if (window.NativeShell) { items.map(function (item) { - window.NativeShell.downloadFile(item.url); + window.NativeShell.downloadFile(item); }); } else { multiDownload(items.map(function (item) { diff --git a/src/components/filesystem.js b/src/scripts/filesystem.js similarity index 100% rename from src/components/filesystem.js rename to src/scripts/filesystem.js diff --git a/src/components/input/gamepadtokey.js b/src/scripts/gamepadtokey.js similarity index 89% rename from src/components/input/gamepadtokey.js rename to src/scripts/gamepadtokey.js index 5356bcbb45a..089e5c81e94 100644 --- a/src/components/input/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -20,7 +20,7 @@ // # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // # THE SOFTWARE. require(['apphost'], function (appHost) { - "use strict"; + 'use strict'; var _GAMEPAD_A_BUTTON_INDEX = 0; var _GAMEPAD_B_BUTTON_INDEX = 1; @@ -28,16 +28,16 @@ require(['apphost'], function (appHost) { var _GAMEPAD_DPAD_DOWN_BUTTON_INDEX = 13; var _GAMEPAD_DPAD_LEFT_BUTTON_INDEX = 14; var _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX = 15; - var _GAMEPAD_A_KEY = "GamepadA"; - var _GAMEPAD_B_KEY = "GamepadB"; - var _GAMEPAD_DPAD_UP_KEY = "GamepadDPadUp"; - var _GAMEPAD_DPAD_DOWN_KEY = "GamepadDPadDown"; - var _GAMEPAD_DPAD_LEFT_KEY = "GamepadDPadLeft"; - var _GAMEPAD_DPAD_RIGHT_KEY = "GamepadDPadRight"; - var _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = "GamepadLeftThumbStickUp"; - var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = "GamepadLeftThumbStickDown"; - var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = "GamepadLeftThumbStickLeft"; - var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = "GamepadLeftThumbStickRight"; + var _GAMEPAD_A_KEY = 'GamepadA'; + var _GAMEPAD_B_KEY = 'GamepadB'; + var _GAMEPAD_DPAD_UP_KEY = 'GamepadDPadUp'; + var _GAMEPAD_DPAD_DOWN_KEY = 'GamepadDPadDown'; + var _GAMEPAD_DPAD_LEFT_KEY = 'GamepadDPadLeft'; + var _GAMEPAD_DPAD_RIGHT_KEY = 'GamepadDPadRight'; + var _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = 'GamepadLeftThumbStickUp'; + var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = 'GamepadLeftThumbStickDown'; + var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = 'GamepadLeftThumbStickLeft'; + var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = 'GamepadLeftThumbStickRight'; var _GAMEPAD_A_KEYCODE = 0; var _GAMEPAD_B_KEYCODE = 27; var _GAMEPAD_DPAD_UP_KEYCODE = 38; @@ -184,7 +184,7 @@ require(['apphost'], function (appHost) { function allowInput() { // This would be nice but always seems to return true with electron - if (!isElectron && document.hidden) { + if (!isElectron && document.hidden) { /* eslint-disable-line compat/compat */ return false; } @@ -234,7 +234,7 @@ require(['apphost'], function (appHost) { } if (fire && keyCode) { - raiseEvent("keydown", key, keyCode); + raiseEvent('keydown', key, keyCode); } } else if (newPressedState === false && oldPressedState === true) { @@ -243,7 +243,7 @@ require(['apphost'], function (appHost) { // button up if (keyCode) { - raiseEvent("keyup", key, keyCode); + raiseEvent('keyup', key, keyCode); } if (clickonKeyUp) { clickElement(document.activeElement || window); @@ -254,7 +254,7 @@ require(['apphost'], function (appHost) { var inputLoopTimer; function runInputLoop() { // Get the latest gamepad state. - var gamepads = navigator.getGamepads(); + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (!gamepad) { @@ -362,7 +362,7 @@ require(['apphost'], function (appHost) { } function isGamepadConnected() { - var gamepads = navigator.getGamepads(); + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (gamepad && gamepad.connected) { @@ -373,36 +373,38 @@ require(['apphost'], function (appHost) { } function onFocusOrGamepadAttach(e) { + /* eslint-disable-next-line compat/compat */ if (isGamepadConnected() && document.hasFocus()) { - console.log("Gamepad connected! Starting input loop"); + console.log('Gamepad connected! Starting input loop'); startInputLoop(); } } function onFocusOrGamepadDetach(e) { + /* eslint-disable-next-line compat/compat */ if (!isGamepadConnected() || !document.hasFocus()) { - console.log("Gamepad disconnected! No other gamepads are connected, stopping input loop"); + console.log('Gamepad disconnected! No other gamepads are connected, stopping input loop'); stopInputLoop(); } else { - console.log("Gamepad disconnected! There are gamepads still connected."); + console.log('Gamepad disconnected! There are gamepads still connected.'); } } // Event listeners for any change in gamepads' state. - window.addEventListener("gamepaddisconnected", onFocusOrGamepadDetach); - window.addEventListener("gamepadconnected", onFocusOrGamepadAttach); - window.addEventListener("blur", onFocusOrGamepadDetach); - window.addEventListener("focus", onFocusOrGamepadAttach); + window.addEventListener('gamepaddisconnected', onFocusOrGamepadDetach); + window.addEventListener('gamepadconnected', onFocusOrGamepadAttach); + window.addEventListener('blur', onFocusOrGamepadDetach); + window.addEventListener('focus', onFocusOrGamepadAttach); onFocusOrGamepadAttach(); // The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs. // It won't exist in Win8.1 style apps or browsers. - if (window.navigator && typeof window.navigator.gamepadInputEmulation === "string") { + if (window.navigator && typeof window.navigator.gamepadInputEmulation === 'string') { // We want the gamepad to provide gamepad VK keyboard events rather than moving a // mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events // and provide input to the DOM navigator.getGamepads API. - window.navigator.gamepadInputEmulation = "gamepad"; + window.navigator.gamepadInputEmulation = 'gamepad'; } }); diff --git a/src/scripts/globalize.js b/src/scripts/globalize.js index 69bb3aee21a..2d7d725b5dc 100644 --- a/src/scripts/globalize.js +++ b/src/scripts/globalize.js @@ -1,4 +1,4 @@ -define(['connectionManager', 'userSettings', 'events'], function (connectionManager, userSettings, events) { +define(['userSettings', 'events'], function (userSettings, events) { 'use strict'; var fallbackCulture = 'en-us'; @@ -253,7 +253,6 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana updateCurrentCulture(); - events.on(connectionManager, 'localusersignedin', updateCurrentCulture); events.on(userSettings, 'change', function (e, name) { if (name === 'language' || name === 'datetimelocale') { updateCurrentCulture(); @@ -269,6 +268,7 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana defaultModule: defaultModule, getCurrentLocale: getCurrentLocale, getCurrentDateTimeLocale: getCurrentDateTimeLocale, - register: register + register: register, + updateCurrentCulture: updateCurrentCulture }; }); diff --git a/src/scripts/imagehelper.js b/src/scripts/imagehelper.js index a8c4702698d..be3fd2834e9 100644 --- a/src/scripts/imagehelper.js +++ b/src/scripts/imagehelper.js @@ -1,78 +1,81 @@ -define(["browser"], function (browser) { - "use strict"; +/* eslint-disable indent */ - function getDeviceIcon(device) { - var baseUrl = "assets/img/devices/"; +import browser from 'browser'; + + export function getDeviceIcon(device) { + var baseUrl = 'assets/img/devices/'; switch (device.AppName || device.Client) { - case "Samsung Smart TV": - return baseUrl + "samsung.svg"; - case "Xbox One": - return baseUrl + "xbox.svg"; - case "Sony PS4": - return baseUrl + "playstation.svg"; - case "Kodi": - return baseUrl + "kodi.svg"; - case "Jellyfin Android": - return baseUrl + "android.svg"; - case "Jellyfin Web": + case 'Samsung Smart TV': + return baseUrl + 'samsung.svg'; + case 'Xbox One': + return baseUrl + 'xbox.svg'; + case 'Sony PS4': + return baseUrl + 'playstation.svg'; + case 'Kodi': + return baseUrl + 'kodi.svg'; + case 'Jellyfin Android': + case 'Android TV': + return baseUrl + 'android.svg'; + case 'Jellyfin Web': switch (device.Name || device.DeviceName) { - case "Opera": - case "Opera TV": - case "Opera Android": - return baseUrl + "opera.svg"; - case "Chrome": - case "Chrome Android": - return baseUrl + "chrome.svg"; - case "Firefox": - case "Firefox Android": - return baseUrl + "firefox.svg"; - case "Safari": - case "Safari iPad": - case "Safari iPhone": - return baseUrl + "safari.svg"; - case "Edge": - return baseUrl + "edge.svg"; - case "Internet Explorer": - return baseUrl + "msie.svg"; + case 'Opera': + case 'Opera TV': + case 'Opera Android': + return baseUrl + 'opera.svg'; + case 'Chrome': + case 'Chrome Android': + return baseUrl + 'chrome.svg'; + case 'Firefox': + case 'Firefox Android': + return baseUrl + 'firefox.svg'; + case 'Safari': + case 'Safari iPad': + case 'Safari iPhone': + return baseUrl + 'safari.svg'; + case 'Edge': + return baseUrl + 'edge.svg'; + case 'Internet Explorer': + return baseUrl + 'msie.svg'; default: - return baseUrl + "html5.svg"; + return baseUrl + 'html5.svg'; } default: - return baseUrl + "other.svg"; + return baseUrl + 'other.svg'; } } - function getLibraryIcon(library) { + export function getLibraryIcon(library) { switch (library) { - case "movies": - return "video_library"; - case "music": - return "library_music"; - case "photos": - return "photo_library"; - case "livetv": - return "live_tv"; - case "tvshows": - return "tv"; - case "trailers": - return "local_movies"; - case "homevideos": - return "photo_library"; - case "musicvideos": - return "music_video"; - case "books": - return "library_books"; - case "channels": - return "videocam"; - case "playlists": - return "view_list"; + case 'movies': + return 'video_library'; + case 'music': + return 'library_music'; + case 'photos': + return 'photo_library'; + case 'livetv': + return 'live_tv'; + case 'tvshows': + return 'tv'; + case 'trailers': + return 'local_movies'; + case 'homevideos': + return 'photo_library'; + case 'musicvideos': + return 'music_video'; + case 'books': + return 'library_books'; + case 'channels': + return 'videocam'; + case 'playlists': + return 'view_list'; default: - return "folder"; + return 'folder'; } } - return { - getDeviceIcon: getDeviceIcon, - getLibraryIcon: getLibraryIcon - }; -}); +/* eslint-enable indent */ + +export default { + getDeviceIcon: getDeviceIcon, + getLibraryIcon: getLibraryIcon +}; diff --git a/src/scripts/inputManager.js b/src/scripts/inputManager.js index 4d62ff9f209..8af52c77e80 100644 --- a/src/scripts/inputManager.js +++ b/src/scripts/inputManager.js @@ -1,44 +1,49 @@ -define(['playbackManager', 'focusManager', 'appRouter', 'dom', 'apphost'], function (playbackManager, focusManager, appRouter, dom, appHost) { - 'use strict'; +import playbackManager from 'playbackManager'; +import focusManager from 'focusManager'; +import appRouter from 'appRouter'; +import dom from 'dom'; +import appHost from 'apphost'; - var lastInputTime = new Date().getTime(); +/* eslint-disable indent */ - function notify() { + let lastInputTime = new Date().getTime(); + + export function notify() { lastInputTime = new Date().getTime(); handleCommand('unknown'); } - function notifyMouseMove() { + export function notifyMouseMove() { lastInputTime = new Date().getTime(); } - function idleTime() { + export function idleTime() { return new Date().getTime() - lastInputTime; } - function select(sourceElement) { + export function select(sourceElement) { sourceElement.click(); } - var eventListenerCount = 0; - function on(scope, fn) { + let eventListenerCount = 0; + export function on(scope, fn) { eventListenerCount++; dom.addEventListener(scope, 'command', fn, {}); } - function off(scope, fn) { + export function off(scope, fn) { if (eventListenerCount) { eventListenerCount--; } dom.removeEventListener(scope, 'command', fn, {}); } - var commandTimes = {}; + let commandTimes = {}; function checkCommandTime(command) { - var last = commandTimes[command] || 0; - var now = new Date().getTime(); + const last = commandTimes[command] || 0; + const now = new Date().getTime(); if ((now - last) < 1000) { return false; @@ -48,11 +53,11 @@ define(['playbackManager', 'focusManager', 'appRouter', 'dom', 'apphost'], funct return true; } - function handleCommand(name, options) { + export function handleCommand(commandName, options) { lastInputTime = new Date().getTime(); - var sourceElement = (options ? options.sourceElement : null); + let sourceElement = (options ? options.sourceElement : null); if (sourceElement) { sourceElement = focusManager.focusableParent(sourceElement); @@ -61,7 +66,7 @@ define(['playbackManager', 'focusManager', 'appRouter', 'dom', 'apphost'], funct if (!sourceElement) { sourceElement = document.activeElement || window; - var dlg = document.querySelector('.dialogContainer .dialog.opened'); + const dlg = document.querySelector('.dialogContainer .dialog.opened'); if (dlg && (!sourceElement || !dlg.contains(sourceElement))) { sourceElement = dlg; @@ -69,183 +74,182 @@ define(['playbackManager', 'focusManager', 'appRouter', 'dom', 'apphost'], funct } if (eventListenerCount) { - var customEvent = new CustomEvent("command", { + const customEvent = new CustomEvent('command', { detail: { - command: name + command: commandName }, bubbles: true, cancelable: true }); - var eventResult = sourceElement.dispatchEvent(customEvent); + const eventResult = sourceElement.dispatchEvent(customEvent); if (!eventResult) { // event cancelled return; } } - switch (name) { - case 'up': + const keyActions = (command) => ({ + 'up': () => { focusManager.moveUp(sourceElement); - break; - case 'down': + }, + 'down': () => { focusManager.moveDown(sourceElement); - break; - case 'left': + }, + 'left': () => { focusManager.moveLeft(sourceElement); - break; - case 'right': + }, + 'right': () => { focusManager.moveRight(sourceElement); - break; - case 'home': + }, + 'home': () => { appRouter.goHome(); - break; - case 'settings': + }, + 'settings': () => { appRouter.showSettings(); - break; - case 'back': + }, + 'back': () => { if (appRouter.canGoBack()) { appRouter.back(); } else if (appHost.supports('exit')) { appHost.exit(); } - break; - case 'forward': - break; - case 'select': + }, + 'select': () => { select(sourceElement); - break; - case 'pageup': - break; - case 'pagedown': - break; - case 'end': - break; - case 'menu': - break; - case 'info': - break; - case 'nextchapter': + }, + 'nextchapter': () => { playbackManager.nextChapter(); - break; - case 'next': - case 'nexttrack': + }, + 'next': () => { + playbackManager.nextTrack(); + }, + 'nexttrack': () => { playbackManager.nextTrack(); - break; - case 'previous': - case 'previoustrack': + }, + 'previous': () => { + playbackManager.previousTrack(); + }, + 'previoustrack': () => { playbackManager.previousTrack(); - break; - case 'previouschapter': + }, + 'previouschapter': () => { playbackManager.previousChapter(); - break; - case 'guide': + }, + 'guide': () => { appRouter.showGuide(); - break; - case 'recordedtv': + }, + 'recordedtv': () => { appRouter.showRecordedTV(); - break; - case 'record': - break; - case 'livetv': + }, + 'livetv': () => { appRouter.showLiveTV(); - break; - case 'mute': + }, + 'mute': () => { playbackManager.setMute(true); - break; - case 'unmute': + }, + 'unmute': () => { playbackManager.setMute(false); - break; - case 'togglemute': + }, + 'togglemute': () => { playbackManager.toggleMute(); - break; - case 'channelup': + }, + 'channelup': () => { playbackManager.channelUp(); - break; - case 'channeldown': + }, + 'channeldown': () => { playbackManager.channelDown(); - break; - case 'volumedown': + }, + 'volumedown': () => { playbackManager.volumeDown(); - break; - case 'volumeup': + }, + 'volumeup': () => { playbackManager.volumeUp(); - break; - case 'play': + }, + 'play': () => { playbackManager.unpause(); - break; - case 'pause': + }, + 'pause': () => { playbackManager.pause(); - break; - case 'playpause': + }, + 'playpause': () => { playbackManager.playPause(); - break; - case 'stop': + }, + 'stop': () => { if (checkCommandTime('stop')) { playbackManager.stop(); } - break; - case 'changezoom': + }, + 'changezoom': () => { playbackManager.toggleAspectRatio(); - break; - case 'changeaudiotrack': + }, + 'changeaudiotrack': () => { playbackManager.changeAudioStream(); - break; - case 'changesubtitletrack': + }, + 'changesubtitletrack': () => { playbackManager.changeSubtitleStream(); - break; - case 'search': + }, + 'search': () => { appRouter.showSearch(); - break; - case 'favorites': + }, + 'favorites': () => { appRouter.showFavorites(); - break; - case 'fastforward': + }, + 'fastforward': () => { playbackManager.fastForward(); - break; - case 'rewind': + }, + 'rewind': () => { playbackManager.rewind(); - break; - case 'togglefullscreen': + }, + 'togglefullscreen': () => { playbackManager.toggleFullscreen(); - break; - case 'disabledisplaymirror': + }, + 'disabledisplaymirror': () => { playbackManager.enableDisplayMirroring(false); - break; - case 'enabledisplaymirror': + }, + 'enabledisplaymirror': () => { playbackManager.enableDisplayMirroring(true); - break; - case 'toggledisplaymirror': + }, + 'toggledisplaymirror': () => { playbackManager.toggleDisplayMirroring(); - break; - case 'nowplaying': + }, + 'nowplaying': () => { appRouter.showNowPlaying(); - break; - case 'repeatnone': + }, + 'repeatnone': () => { playbackManager.setRepeatMode('RepeatNone'); - break; - case 'repeatall': + }, + 'repeatall': () => { playbackManager.setRepeatMode('RepeatAll'); - break; - case 'repeatone': + }, + 'repeatone': () => { playbackManager.setRepeatMode('RepeatOne'); - break; - default: - break; + } + })[command]; + + const action = keyActions(commandName); + if (action !== undefined) { + action.call(); + } else { + console.debug(`inputManager: tried to process command with no action assigned: ${commandName}`); } } + // Alias for backward compatibility + export const trigger = handleCommand; + dom.addEventListener(document, 'click', notify, { passive: true }); - return { - trigger: handleCommand, - handle: handleCommand, - notify: notify, - notifyMouseMove: notifyMouseMove, - idleTime: idleTime, - on: on, - off: off - }; -}); +/* eslint-enable indent */ + +export default { + trigger: handleCommand, + handle: handleCommand, + notify: notify, + notifyMouseMove: notifyMouseMove, + idleTime: idleTime, + on: on, + off: off +}; diff --git a/src/scripts/itembynamedetailpage.js b/src/scripts/itembynamedetailpage.js index ea760900ec3..fd29724225d 100644 --- a/src/scripts/itembynamedetailpage.js +++ b/src/scripts/itembynamedetailpage.js @@ -1,105 +1,105 @@ -define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryBrowser", "emby-itemscontainer", "emby-button"], function (connectionManager, listView, cardBuilder, imageLoader, libraryBrowser) { - "use strict"; +define(['connectionManager', 'listView', 'cardBuilder', 'imageLoader', 'libraryBrowser', 'globalize', 'emby-itemscontainer', 'emby-button'], function (connectionManager, listView, cardBuilder, imageLoader, libraryBrowser, globalize) { + 'use strict'; function renderItems(page, item) { var sections = []; if (item.ArtistCount) { sections.push({ - name: Globalize.translate("TabArtists"), - type: "MusicArtist" + name: globalize.translate('TabArtists'), + type: 'MusicArtist' }); } - if (item.ProgramCount && "Person" == item.Type) { + if (item.ProgramCount && 'Person' == item.Type) { sections.push({ - name: Globalize.translate("HeaderUpcomingOnTV"), - type: "Program" + name: globalize.translate('HeaderUpcomingOnTV'), + type: 'Program' }); } if (item.MovieCount) { sections.push({ - name: Globalize.translate("TabMovies"), - type: "Movie" + name: globalize.translate('TabMovies'), + type: 'Movie' }); } if (item.SeriesCount) { sections.push({ - name: Globalize.translate("TabShows"), - type: "Series" + name: globalize.translate('TabShows'), + type: 'Series' }); } if (item.EpisodeCount) { sections.push({ - name: Globalize.translate("TabEpisodes"), - type: "Episode" + name: globalize.translate('TabEpisodes'), + type: 'Episode' }); } if (item.TrailerCount) { sections.push({ - name: Globalize.translate("TabTrailers"), - type: "Trailer" + name: globalize.translate('TabTrailers'), + type: 'Trailer' }); } if (item.AlbumCount) { sections.push({ - name: Globalize.translate("TabAlbums"), - type: "MusicAlbum" + name: globalize.translate('TabAlbums'), + type: 'MusicAlbum' }); } if (item.MusicVideoCount) { sections.push({ - name: Globalize.translate("TabMusicVideos"), - type: "MusicVideo" + name: globalize.translate('TabMusicVideos'), + type: 'MusicVideo' }); } - var elem = page.querySelector("#childrenContent"); + var elem = page.querySelector('#childrenContent'); elem.innerHTML = sections.map(function (section) { - var html = ""; - var sectionClass = "verticalSection"; + var html = ''; + var sectionClass = 'verticalSection'; - if ("Audio" === section.type) { - sectionClass += " verticalSection-extrabottompadding"; + if ('Audio' === section.type) { + sectionClass += ' verticalSection-extrabottompadding'; } html += '
'; html += '
'; - html += '

'; + html += '

'; html += section.name; - html += "

"; - html += '"; - html += "
"; - html += '
'; - html += "
"; - return html += "
"; - }).join(""); - var sectionElems = elem.querySelectorAll(".verticalSection"); + html += '

'; + html += ''; + html += '
'; + html += '
'; + html += '
'; + return html += '
'; + }).join(''); + var sectionElems = elem.querySelectorAll('.verticalSection'); for (var i = 0, length = sectionElems.length; i < length; i++) { - renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute("data-type")); + renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute('data-type')); } } function renderSection(page, item, element, type) { switch (type) { - case "Program": + case 'Program': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Program", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'Program', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 10, - SortBy: "StartDate" + SortBy: 'StartDate' }, { - shape: "overflowBackdrop", + shape: 'overflowBackdrop', showTitle: true, centerText: true, overlayMoreButton: true, @@ -111,17 +111,17 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB }); break; - case "Movie": + case 'Movie': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Movie", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'Movie', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 10, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowPortrait", + shape: 'overflowPortrait', showTitle: true, centerText: true, overlayMoreButton: true, @@ -130,68 +130,68 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB }); break; - case "MusicVideo": + case 'MusicVideo': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "MusicVideo", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'MusicVideo', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 10, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowPortrait", + shape: 'overflowPortrait', showTitle: true, centerText: true, overlayPlayButton: true }); break; - case "Trailer": + case 'Trailer': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Trailer", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'Trailer', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 10, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowPortrait", + shape: 'overflowPortrait', showTitle: true, centerText: true, overlayPlayButton: true }); break; - case "Series": + case 'Series': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Series", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'Series', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 10, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowPortrait", + shape: 'overflowPortrait', showTitle: true, centerText: true, overlayMoreButton: true }); break; - case "MusicAlbum": + case 'MusicAlbum': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "MusicAlbum", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", - SortOrder: "Descending", - SortBy: "ProductionYear,Sortname" + MediaTypes: '', + IncludeItemTypes: 'MusicAlbum', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortOrder: 'Descending', + SortBy: 'ProductionYear,Sortname' }, { - shape: "overflowSquare", + shape: 'overflowSquare', playFromHere: true, showTitle: true, showYear: true, @@ -201,17 +201,17 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB }); break; - case "MusicArtist": + case 'MusicArtist': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "MusicArtist", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'MusicArtist', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 8, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowSquare", + shape: 'overflowSquare', playFromHere: true, showTitle: true, showParentTitle: true, @@ -221,17 +221,17 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB }); break; - case "Episode": + case 'Episode': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Episode", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", + MediaTypes: '', + IncludeItemTypes: 'Episode', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', Limit: 6, - SortBy: "SortName" + SortBy: 'SortName' }, { - shape: "overflowBackdrop", + shape: 'overflowBackdrop', showTitle: true, showParentTitle: true, centerText: true, @@ -239,17 +239,17 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB }); break; - case "Audio": + case 'Audio': loadItems(element, item, type, { - MediaTypes: "", - IncludeItemTypes: "Audio", - PersonTypes: "", - ArtistIds: "", - AlbumArtistIds: "", - SortBy: "AlbumArtist,Album,SortName" + MediaTypes: '', + IncludeItemTypes: 'Audio', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortBy: 'AlbumArtist,Album,SortName' }, { playFromHere: true, - action: "playallfromhere", + action: 'playallfromhere', smallIcon: true, artist: true }); @@ -259,27 +259,27 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB function loadItems(element, item, type, query, listOptions) { query = getQuery(query, item); getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) { - var html = ""; + var html = ''; if (query.Limit && result.TotalRecordCount > query.Limit) { - var link = element.querySelector("a"); - link.classList.remove("hide"); - link.setAttribute("href", getMoreItemsHref(item, type)); + var link = element.querySelector('a'); + link.classList.remove('hide'); + link.setAttribute('href', getMoreItemsHref(item, type)); } else { - element.querySelector("a").classList.add("hide"); + element.querySelector('a').classList.add('hide'); } listOptions.items = result.Items; - var itemsContainer = element.querySelector(".itemsContainer"); + var itemsContainer = element.querySelector('.itemsContainer'); - if ("Audio" == type) { + if ('Audio' == type) { html = listView.getListViewHtml(listOptions); - itemsContainer.classList.remove("vertical-wrap"); - itemsContainer.classList.add("vertical-list"); + itemsContainer.classList.remove('vertical-wrap'); + itemsContainer.classList.add('vertical-list'); } else { html = cardBuilder.getCardsHtml(listOptions); - itemsContainer.classList.add("vertical-wrap"); - itemsContainer.classList.remove("vertical-list"); + itemsContainer.classList.add('vertical-wrap'); + itemsContainer.classList.remove('vertical-list'); } itemsContainer.innerHTML = html; @@ -288,51 +288,51 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB } function getMoreItemsHref(item, type) { - if ("Genre" == item.Type) { - return "list.html?type=" + type + "&genreId=" + item.Id + "&serverId=" + item.ServerId; + if ('Genre' == item.Type) { + return 'list.html?type=' + type + '&genreId=' + item.Id + '&serverId=' + item.ServerId; } - if ("MusicGenre" == item.Type) { - return "list.html?type=" + type + "&musicGenreId=" + item.Id + "&serverId=" + item.ServerId; + if ('MusicGenre' == item.Type) { + return 'list.html?type=' + type + '&musicGenreId=' + item.Id + '&serverId=' + item.ServerId; } - if ("Studio" == item.Type) { - return "list.html?type=" + type + "&studioId=" + item.Id + "&serverId=" + item.ServerId; + if ('Studio' == item.Type) { + return 'list.html?type=' + type + '&studioId=' + item.Id + '&serverId=' + item.ServerId; } - if ("MusicArtist" == item.Type) { - return "list.html?type=" + type + "&artistId=" + item.Id + "&serverId=" + item.ServerId; + if ('MusicArtist' == item.Type) { + return 'list.html?type=' + type + '&artistId=' + item.Id + '&serverId=' + item.ServerId; } - if ("Person" == item.Type) { - return "list.html?type=" + type + "&personId=" + item.Id + "&serverId=" + item.ServerId; + if ('Person' == item.Type) { + return 'list.html?type=' + type + '&personId=' + item.Id + '&serverId=' + item.ServerId; } - return "list.html?type=" + type + "&parentId=" + item.Id + "&serverId=" + item.ServerId; + return 'list.html?type=' + type + '&parentId=' + item.Id + '&serverId=' + item.ServerId; } function addCurrentItemToQuery(query, item) { - if (item.Type == "Person") { + if (item.Type == 'Person') { query.PersonIds = item.Id; - } else if (item.Type == "Genre") { + } else if (item.Type == 'Genre') { query.Genres = item.Name; - } else if (item.Type == "MusicGenre") { + } else if (item.Type == 'MusicGenre') { query.Genres = item.Name; - } else if (item.Type == "GameGenre") { + } else if (item.Type == 'GameGenre') { query.Genres = item.Name; - } else if (item.Type == "Studio") { + } else if (item.Type == 'Studio') { query.StudioIds = item.Id; - } else if (item.Type == "MusicArtist") { + } else if (item.Type == 'MusicArtist') { query.AlbumArtistIds = item.Id; } } function getQuery(options, item) { var query = { - SortOrder: "Ascending", - IncludeItemTypes: "", + SortOrder: 'Ascending', + IncludeItemTypes: '', Recursive: true, - Fields: "AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo", + Fields: 'AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo', Limit: 100, StartIndex: 0, CollapseBoxSetItems: false @@ -349,12 +349,12 @@ define(["connectionManager", "listView", "cardBuilder", "imageLoader", "libraryB query.Limit = limit; if (fields) { - query.Fields += "," + fields; + query.Fields += ',' + fields; } var apiClient = connectionManager.getApiClient(item.ServerId); - if ("MusicArtist" === query.IncludeItemTypes) { + if ('MusicArtist' === query.IncludeItemTypes) { query.IncludeItemTypes = null; return apiClient.getAlbumArtists(apiClient.getCurrentUserId(), query); } diff --git a/src/scripts/keyboardNavigation.js b/src/scripts/keyboardNavigation.js new file mode 100644 index 00000000000..6543207feec --- /dev/null +++ b/src/scripts/keyboardNavigation.js @@ -0,0 +1,170 @@ +/** + * Module for performing keyboard navigation. + * @module components/input/keyboardnavigation + */ + +import inputManager from 'inputManager'; +import layoutManager from 'layoutManager'; + +/** + * Key name mapping. + */ +const KeyNames = { + 13: 'Enter', + 19: 'Pause', + 27: 'Escape', + 32: 'Space', + 37: 'ArrowLeft', + 38: 'ArrowUp', + 39: 'ArrowRight', + 40: 'ArrowDown', + // MediaRewind (Tizen/WebOS) + 412: 'MediaRewind', + // MediaStop (Tizen/WebOS) + 413: 'MediaStop', + // MediaPlay (Tizen/WebOS) + 415: 'MediaPlay', + // MediaFastForward (Tizen/WebOS) + 417: 'MediaFastForward', + // Back (WebOS) + 461: 'Back', + // Back (Tizen) + 10009: 'Back', + // MediaTrackPrevious (Tizen) + 10232: 'MediaTrackPrevious', + // MediaTrackNext (Tizen) + 10233: 'MediaTrackNext', + // MediaPlayPause (Tizen) + 10252: 'MediaPlayPause' +}; + +/** + * Keys used for keyboard navigation. + */ +const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; + +let hasFieldKey = false; +try { + hasFieldKey = 'key' in new KeyboardEvent('keydown'); +} catch (e) { + console.error("error checking 'key' field"); +} + +if (!hasFieldKey) { + // Add [a..z] + for (let i = 65; i <= 90; i++) { + KeyNames[i] = String.fromCharCode(i).toLowerCase(); + } +} + +/** + * Returns key name from event. + * + * @param {KeyboardEvent} event - Keyboard event. + * @return {string} Key name. + */ +export function getKeyName(event) { + return KeyNames[event.keyCode] || event.key; +} + +/** + * Returns _true_ if key is used for navigation. + * + * @param {string} key - Key name. + * @return {boolean} _true_ if key is used for navigation. + */ +export function isNavigationKey(key) { + return NavigationKeys.indexOf(key) != -1; +} + +export function enable() { + window.addEventListener('keydown', function (e) { + const key = getKeyName(e); + + // Ignore navigation keys for non-TV + if (!layoutManager.tv && isNavigationKey(key)) { + return; + } + + let capture = true; + + switch (key) { + case 'ArrowLeft': + inputManager.handle('left'); + break; + case 'ArrowUp': + inputManager.handle('up'); + break; + case 'ArrowRight': + inputManager.handle('right'); + break; + case 'ArrowDown': + inputManager.handle('down'); + break; + + case 'Back': + inputManager.handle('back'); + break; + + case 'Escape': + if (layoutManager.tv) { + inputManager.handle('back'); + } else { + capture = false; + } + break; + + case 'MediaPlay': + inputManager.handle('play'); + break; + case 'Pause': + inputManager.handle('pause'); + break; + case 'MediaPlayPause': + inputManager.handle('playpause'); + break; + case 'MediaRewind': + inputManager.handle('rewind'); + break; + case 'MediaFastForward': + inputManager.handle('fastforward'); + break; + case 'MediaStop': + inputManager.handle('stop'); + break; + case 'MediaTrackPrevious': + inputManager.handle('previoustrack'); + break; + case 'MediaTrackNext': + inputManager.handle('nexttrack'); + break; + + default: + capture = false; + } + + if (capture) { + console.debug('disabling default event handling'); + e.preventDefault(); + } + }); +} + +// Gamepad initialisation. No script is required if no gamepads are present at init time, saving a bit of resources. +// Whenever the gamepad is connected, we hand all the control of the gamepad to gamepadtokey.js by removing the event handler +function attachGamepadScript(e) { + console.log('Gamepad connected! Attaching gamepadtokey.js script'); + window.removeEventListener('gamepadconnected', attachGamepadScript); + require(['scripts/gamepadtokey']); +} + +// No need to check for gamepads manually at load time, the eventhandler will be fired for that +if (navigator.getGamepads) { /* eslint-disable-line compat/compat */ + window.addEventListener('gamepadconnected', attachGamepadScript); +} + +export default { + enable: enable, + getKeyName: getKeyName, + isNavigationKey: isNavigationKey +}; diff --git a/src/scripts/librarybrowser.js b/src/scripts/libraryBrowser.js similarity index 64% rename from src/scripts/librarybrowser.js rename to src/scripts/libraryBrowser.js index bd8980aed2e..f9e5e23596e 100644 --- a/src/scripts/librarybrowser.js +++ b/src/scripts/libraryBrowser.js @@ -1,9 +1,9 @@ -define(["userSettings"], function (userSettings) { - "use strict"; +define(['userSettings', 'globalize'], function (userSettings, globalize) { + 'use strict'; var libraryBrowser = { getSavedQueryKey: function (modifier) { - return window.location.href.split("#")[0] + (modifier || ""); + return window.location.href.split('#')[0] + (modifier || ''); }, loadSavedQueryValues: function (key, query) { var values = userSettings.get(key); @@ -29,34 +29,34 @@ define(["userSettings"], function (userSettings) { userSettings.set(key, JSON.stringify(values)); }, saveViewSetting: function (key, value) { - userSettings.set(key + "-_view", value); + userSettings.set(key + '-_view', value); }, getSavedView: function (key) { - return userSettings.get(key + "-_view"); + return userSettings.get(key + '-_view'); }, showLayoutMenu: function (button, currentLayout, views) { var dispatchEvent = true; if (!views) { dispatchEvent = false; - views = button.getAttribute("data-layouts"); - views = views ? views.split(",") : ["List", "Poster", "PosterCard", "Thumb", "ThumbCard"]; + views = button.getAttribute('data-layouts'); + views = views ? views.split(',') : ['List', 'Poster', 'PosterCard', 'Thumb', 'ThumbCard']; } var menuItems = views.map(function (v) { return { - name: Globalize.translate("Option" + v), + name: globalize.translate('Option' + v), id: v, selected: currentLayout == v }; }); - require(["actionsheet"], function (actionsheet) { + require(['actionsheet'], function (actionsheet) { actionsheet.show({ items: menuItems, positionTo: button, callback: function (id) { - button.dispatchEvent(new CustomEvent("layoutchange", { + button.dispatchEvent(new CustomEvent('layoutchange', { detail: { viewStyle: id }, @@ -66,7 +66,7 @@ define(["userSettings"], function (userSettings) { if (!dispatchEvent) { if (window.$) { - $(button).trigger("layoutchange", [id]); + $(button).trigger('layoutchange', [id]); } } } @@ -77,49 +77,49 @@ define(["userSettings"], function (userSettings) { var startIndex = options.startIndex; var limit = options.limit; var totalRecordCount = options.totalRecordCount; - var html = ""; + var html = ''; var recordsEnd = Math.min(startIndex + limit, totalRecordCount); var showControls = limit < totalRecordCount; if (html += '
', showControls) { html += ''; - html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount; - html += ""; + html += globalize.translate('ListPaging', (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount); + html += ''; } if (showControls || options.viewButton || options.filterButton || options.sortButton || options.addLayoutButton) { html += '
'; if (showControls) { - html += ''; - html += ''; + html += ''; + html += ''; } if (options.addLayoutButton) { - html += ''; + html += ''; } if (options.sortButton) { - html += ''; + html += ''; } if (options.filterButton) { - html += ''; + html += ''; } - html += "
"; + html += '
'; } - return html += "

"; + return html += '
'; }, showSortMenu: function (options) { - require(["dialogHelper", "emby-radio"], function (dialogHelper) { + require(['dialogHelper', 'emby-radio'], function (dialogHelper) { function onSortByChange() { var newValue = this.value; if (this.checked) { var changed = options.query.SortBy != newValue; - options.query.SortBy = newValue.replace("_", ","); + options.query.SortBy = newValue.replace('_', ','); options.query.StartIndex = 0; if (options.callback && changed) { @@ -148,48 +148,48 @@ define(["userSettings"], function (userSettings) { entryAnimationDuration: 160, exitAnimationDuration: 200 }); - dlg.classList.add("ui-body-a"); - dlg.classList.add("background-theme-a"); - dlg.classList.add("formDialog"); - var html = ""; + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('formDialog'); + var html = ''; html += '
'; html += '

'; - html += Globalize.translate("HeaderSortBy"); - html += "

"; + html += globalize.translate('HeaderSortBy'); + html += '

'; var i; var length; var isChecked; html += '
'; for (i = 0, length = options.items.length; i < length; i++) { var option = options.items[i]; - var radioValue = option.id.replace(",", "_"); - isChecked = (options.query.SortBy || "").replace(",", "_") == radioValue ? " checked" : ""; - html += '"; + var radioValue = option.id.replace(',', '_'); + isChecked = (options.query.SortBy || '').replace(',', '_') == radioValue ? ' checked' : ''; + html += ''; } - html += "
"; + html += '
'; html += '

'; - html += Globalize.translate("HeaderSortOrder"); - html += "

"; - html += "
"; - isChecked = "Ascending" == options.query.SortOrder ? " checked" : ""; - html += '"; - isChecked = "Descending" == options.query.SortOrder ? " checked" : ""; - html += '"; - html += "
"; - html += "
"; + html += globalize.translate('HeaderSortOrder'); + html += '

'; + html += '
'; + isChecked = 'Ascending' == options.query.SortOrder ? ' checked' : ''; + html += ''; + isChecked = 'Descending' == options.query.SortOrder ? ' checked' : ''; + html += ''; + html += '
'; + html += '
'; dlg.innerHTML = html; dialogHelper.open(dlg); - var sortBys = dlg.querySelectorAll(".menuSortBy"); + var sortBys = dlg.querySelectorAll('.menuSortBy'); for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].addEventListener("change", onSortByChange); + sortBys[i].addEventListener('change', onSortByChange); } - var sortOrders = dlg.querySelectorAll(".menuSortOrder"); + var sortOrders = dlg.querySelectorAll('.menuSortOrder'); for (i = 0, length = sortOrders.length; i < length; i++) { - sortOrders[i].addEventListener("change", onSortOrderChange); + sortOrders[i].addEventListener('change', onSortOrderChange); } }); } diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js new file mode 100644 index 00000000000..7f3a16e31e5 --- /dev/null +++ b/src/scripts/libraryMenu.js @@ -0,0 +1,974 @@ +define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncPlayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncPlayManager, groupSelectionMenu, browser, globalize, imageHelper) { + 'use strict'; + + function renderHeader() { + var html = ''; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '

'; + html += '
'; + html += '
'; + html += ''; + html += ``; + html += ``; + html += ``; + html += ``; + html += ''; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + + skinHeader.classList.add('skinHeader-withBackground'); + skinHeader.classList.add('skinHeader-blurred'); + skinHeader.innerHTML = html; + + headerHomeButton = skinHeader.querySelector('.headerHomeButton'); + headerUserButton = skinHeader.querySelector('.headerUserButton'); + headerCastButton = skinHeader.querySelector('.headerCastButton'); + headerAudioPlayerButton = skinHeader.querySelector('.headerAudioPlayerButton'); + headerSearchButton = skinHeader.querySelector('.headerSearchButton'); + headerSyncButton = skinHeader.querySelector('.headerSyncButton'); + + lazyLoadViewMenuBarImages(); + bindMenuEvents(); + } + + function getCurrentApiClient() { + if (currentUser && currentUser.localUser) { + return connectionManager.getApiClient(currentUser.localUser.ServerId); + } + + return connectionManager.currentApiClient(); + } + + function lazyLoadViewMenuBarImages() { + require(['imageLoader'], function (imageLoader) { + imageLoader.lazyChildren(skinHeader); + }); + } + + function onBackClick() { + appRouter.back(); + } + + function updateUserInHeader(user) { + var hasImage; + + if (user && user.name) { + if (user.imageUrl) { + var url = user.imageUrl; + updateHeaderUserButton(url); + hasImage = true; + } + headerUserButton.title = user.name; + headerUserButton.classList.remove('hide'); + } else { + headerUserButton.classList.add('hide'); + } + + if (!hasImage) { + updateHeaderUserButton(null); + } + + if (user && user.localUser) { + if (headerHomeButton) { + headerHomeButton.classList.remove('hide'); + } + + if (headerSearchButton) { + headerSearchButton.classList.remove('hide'); + } + + if (!layoutManager.tv) { + headerCastButton.classList.remove('hide'); + } + + var policy = user.Policy ? user.Policy : user.localUser.Policy; + + if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None') { + headerSyncButton.classList.remove('hide'); + } + } else { + headerHomeButton.classList.add('hide'); + headerCastButton.classList.add('hide'); + headerSyncButton.classList.add('hide'); + + if (headerSearchButton) { + headerSearchButton.classList.add('hide'); + } + } + + requiresUserRefresh = false; + } + + function updateHeaderUserButton(src) { + if (src) { + headerUserButton.classList.add('headerUserButtonRound'); + headerUserButton.innerHTML = '
"; + } else { + headerUserButton.classList.remove('headerUserButtonRound'); + headerUserButton.innerHTML = ''; + } + } + + function showSearch() { + inputManager.trigger('search'); + } + + function onHeaderUserButtonClick(e) { + Dashboard.navigate('mypreferencesmenu.html'); + } + + function onHeaderHomeButtonClick() { + Dashboard.navigate('home.html'); + } + + function showAudioPlayer() { + return appRouter.showNowPlaying(); + } + + function bindMenuEvents() { + mainDrawerButton = document.querySelector('.mainDrawerButton'); + + if (mainDrawerButton) { + mainDrawerButton.addEventListener('click', toggleMainDrawer); + } + + var headerBackButton = skinHeader.querySelector('.headerBackButton'); + + if (headerBackButton) { + headerBackButton.addEventListener('click', onBackClick); + } + + if (headerSearchButton) { + headerSearchButton.addEventListener('click', showSearch); + } + + headerUserButton.addEventListener('click', onHeaderUserButtonClick); + headerHomeButton.addEventListener('click', onHeaderHomeButtonClick); + + if (!layoutManager.tv) { + headerCastButton.addEventListener('click', onCastButtonClicked); + } + + headerAudioPlayerButton.addEventListener('click', showAudioPlayer); + headerSyncButton.addEventListener('click', onSyncButtonClicked); + + if (layoutManager.mobile) { + initHeadRoom(skinHeader); + } + events.on(playbackManager, 'playbackstart', onPlaybackStart); + events.on(playbackManager, 'playbackstop', onPlaybackStop); + } + + function onPlaybackStart(e) { + if (playbackManager.isPlayingAudio() && layoutManager.tv) { + headerAudioPlayerButton.classList.remove('hide'); + } else { + headerAudioPlayerButton.classList.add('hide'); + } + } + + function onPlaybackStop(e, stopInfo) { + if (stopInfo.nextMediaType != 'Audio') { + headerAudioPlayerButton.classList.add('hide'); + } + } + + function onCastButtonClicked() { + var btn = this; + + require(['playerSelectionMenu'], function (playerSelectionMenu) { + playerSelectionMenu.show(btn); + }); + } + + function onSyncButtonClicked() { + var btn = this; + groupSelectionMenu.show(btn); + } + + function onSyncPlayEnabled(event, enabled) { + var icon = headerSyncButton.querySelector('span'); + icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); + if (enabled) { + icon.classList.add('sync'); + } else { + icon.classList.add('sync_disabled'); + } + } + + function onSyncPlaySyncing(event, is_syncing, syncMethod) { + var icon = headerSyncButton.querySelector('span'); + icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); + if (is_syncing) { + icon.classList.add('sync_problem'); + } else { + icon.classList.add('sync'); + } + } + + function getItemHref(item, context) { + return appRouter.getRouteUrl(item, { + context: context + }); + } + + function toggleMainDrawer() { + if (navDrawerInstance.isVisible) { + closeMainDrawer(); + } else { + openMainDrawer(); + } + } + + function openMainDrawer() { + navDrawerInstance.open(); + lastOpenTime = new Date().getTime(); + } + + function onMainDrawerOpened() { + if (layoutManager.mobile) { + document.body.classList.add('bodyWithPopupOpen'); + } + } + + function closeMainDrawer() { + navDrawerInstance.close(); + } + + function onMainDrawerSelect(e) { + if (navDrawerInstance.isVisible) { + onMainDrawerOpened(); + } else { + document.body.classList.remove('bodyWithPopupOpen'); + } + } + + function refreshLibraryInfoInDrawer(user, drawer) { + var html = ''; + html += '
'; + html += '' + globalize.translate('ButtonHome') + ''; + + // libraries are added here + html += '
'; + html += '
'; + + if (user.localUser && user.localUser.Policy.IsAdministrator) { + html += '
'; + html += '

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

'; + html += '' + globalize.translate('TabDashboard') + ''; + html += '' + globalize.translate('Metadata') + ''; + html += '
'; + } + + if (user.localUser) { + html += '
'; + html += '

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

'; + + if (appHost.supports('multiserver')) { + html += '' + globalize.translate('ButtonSelectServer') + ''; + } + + html += '' + globalize.translate('ButtonSettings') + ''; + html += '' + globalize.translate('ButtonSignOut') + ''; + html += '
'; + } + + // add buttons to navigation drawer + navDrawerScrollContainer.innerHTML = html; + + var btnSettings = navDrawerScrollContainer.querySelector('.btnSettings'); + if (btnSettings) { + btnSettings.addEventListener('click', onSettingsClick); + } + + var btnLogout = navDrawerScrollContainer.querySelector('.btnLogout'); + if (btnLogout) { + btnLogout.addEventListener('click', onLogoutClick); + } + } + + function refreshDashboardInfoInDrawer(apiClient) { + currentDrawerType = 'admin'; + loadNavDrawer(); + + if (navDrawerScrollContainer.querySelector('.adminDrawerLogo')) { + updateDashboardMenuSelectedItem(); + } else { + createDashboardMenu(apiClient); + } + } + + function isUrlInCurrentView(url) { + return -1 !== window.location.href.toString().toLowerCase().indexOf(url.toLowerCase()); + } + + function updateDashboardMenuSelectedItem() { + var links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); + var currentViewId = viewManager.currentView().id; + + for (var i = 0, length = links.length; i < length; i++) { + var link = links[i]; + var selected = false; + var pageIds = link.getAttribute('data-pageids'); + + if (pageIds) { + pageIds = pageIds.split('|'); + selected = -1 != pageIds.indexOf(currentViewId); + } + + var pageUrls = link.getAttribute('data-pageurls'); + + if (pageUrls) { + pageUrls = pageUrls.split('|'); + selected = pageUrls.filter(isUrlInCurrentView).length > 0; + } + + if (selected) { + link.classList.add('navMenuOption-selected'); + var title = ''; + link = link.querySelector('.navMenuOptionText') || link; + title += (link.innerText || link.textContent).trim(); + LibraryMenu.setTitle(title); + } else { + link.classList.remove('navMenuOption-selected'); + } + } + } + + function createToolsMenuList(pluginItems) { + var links = [{ + name: globalize.translate('TabServer') + }, { + name: globalize.translate('TabDashboard'), + href: 'dashboard.html', + pageIds: ['dashboardPage'], + icon: 'dashboard' + }, { + name: globalize.translate('General'), + href: 'dashboardgeneral.html', + pageIds: ['dashboardGeneralPage'], + icon: 'settings' + }, { + name: globalize.translate('TabUsers'), + href: 'userprofiles.html', + pageIds: ['userProfilesPage', 'newUserPage', 'editUserPage', 'userLibraryAccessPage', 'userParentalControlPage', 'userPasswordPage'], + icon: 'people' + }, { + name: globalize.translate('HeaderLibraries'), + href: 'library.html', + pageIds: ['mediaLibraryPage', 'librarySettingsPage', 'libraryDisplayPage', 'metadataImagesConfigurationPage', 'metadataNfoPage'], + icon: 'folder' + }, { + name: globalize.translate('TabPlayback'), + icon: 'play_arrow', + href: 'encodingsettings.html', + pageIds: ['encodingSettingsPage', 'playbackConfigurationPage', 'streamingSettingsPage'] + }]; + addPluginPagesToMainMenu(links, pluginItems, 'server'); + links.push({ + divider: true, + name: globalize.translate('TabDevices') + }); + links.push({ + name: globalize.translate('TabDevices'), + href: 'devices.html', + pageIds: ['devicesPage', 'devicePage'], + icon: 'devices' + }); + links.push({ + name: globalize.translate('HeaderActivity'), + href: 'serveractivity.html', + pageIds: ['serverActivityPage'], + icon: 'assessment' + }); + links.push({ + name: globalize.translate('DLNA'), + href: 'dlnasettings.html', + pageIds: ['dlnaSettingsPage', 'dlnaProfilesPage', 'dlnaProfilePage'], + icon: 'input' + }); + links.push({ + divider: true, + name: globalize.translate('TabLiveTV') + }); + links.push({ + name: globalize.translate('TabLiveTV'), + href: 'livetvstatus.html', + pageIds: ['liveTvStatusPage', 'liveTvTunerPage'], + icon: 'live_tv' + }); + links.push({ + name: globalize.translate('TabDVR'), + href: 'livetvsettings.html', + pageIds: ['liveTvSettingsPage'], + icon: 'dvr' + }); + links.push({ + divider: true, + name: globalize.translate('TabAdvanced') + }); + links.push({ + name: globalize.translate('TabNetworking'), + icon: 'cloud', + href: 'networking.html', + pageIds: ['networkingPage'] + }); + links.push({ + name: globalize.translate('HeaderApiKeys'), + icon: 'vpn_key', + href: 'apikeys.html', + pageIds: ['apiKeysPage'] + }); + links.push({ + name: globalize.translate('TabLogs'), + href: 'log.html', + pageIds: ['logPage'], + icon: 'bug_report' + }); + links.push({ + name: globalize.translate('TabNotifications'), + icon: 'notifications', + href: 'notificationsettings.html', + pageIds: ['notificationSettingsPage', 'notificationSettingPage'] + }); + links.push({ + name: globalize.translate('TabPlugins'), + icon: 'shopping_cart', + href: 'installedplugins.html', + pageIds: ['pluginsPage', 'pluginCatalogPage'] + }); + links.push({ + name: globalize.translate('TabScheduledTasks'), + href: 'scheduledtasks.html', + pageIds: ['scheduledTasksPage', 'scheduledTaskPage'], + icon: 'schedule' + }); + addPluginPagesToMainMenu(links, pluginItems); + return links; + } + + function addPluginPagesToMainMenu(links, pluginItems, section) { + for (var i = 0, length = pluginItems.length; i < length; i++) { + var pluginItem = pluginItems[i]; + + if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) { + links.push({ + name: pluginItem.DisplayName, + icon: pluginItem.MenuIcon || 'folder', + href: Dashboard.getConfigurationPageUrl(pluginItem.Name), + pageUrls: [Dashboard.getConfigurationPageUrl(pluginItem.Name)] + }); + } + } + } + + function getToolsMenuLinks(apiClient) { + return apiClient.getJSON(apiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration&EnableInMainMenu=true').then(createToolsMenuList, function (err) { + return createToolsMenuList([]); + }); + } + + function getToolsLinkHtml(item) { + var menuHtml = ''; + var pageIds = item.pageIds ? item.pageIds.join('|') : ''; + pageIds = pageIds ? ' data-pageids="' + pageIds + '"' : ''; + var pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; + pageUrls = pageUrls ? ' data-pageurls="' + pageUrls + '"' : ''; + menuHtml += ''; + + if (item.icon) { + menuHtml += ''; + } + + menuHtml += ''; + menuHtml += item.name; + menuHtml += ''; + return menuHtml + ''; + } + + function getToolsMenuHtml(apiClient) { + return getToolsMenuLinks(apiClient).then(function (items) { + var item; + var menuHtml = ''; + menuHtml += '
'; + + for (var i = 0; i < items.length; i++) { + item = items[i]; + + if (item.href) { + menuHtml += getToolsLinkHtml(item); + } else if (item.name) { + menuHtml += '

'; + menuHtml += item.name; + menuHtml += '

'; + } + } + + return menuHtml + '
'; + }); + } + + function createDashboardMenu(apiClient) { + return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) { + var html = ''; + html += ''; + html += toolsMenuHtml; + navDrawerScrollContainer.innerHTML = html; + updateDashboardMenuSelectedItem(); + }); + } + + function onSidebarLinkClick() { + var section = this.getElementsByClassName('sectionName')[0]; + var text = section ? section.innerHTML : this.innerHTML; + LibraryMenu.setTitle(text); + } + + function getUserViews(apiClient, userId) { + return apiClient.getUserViews({}, userId).then(function (result) { + var items = result.Items; + var list = []; + + for (var i = 0, length = items.length; i < length; i++) { + var view = items[i]; + list.push(view); + + if ('livetv' == view.CollectionType) { + view.ImageTags = {}; + view.icon = 'live_tv'; + var guideView = Object.assign({}, view); + guideView.Name = globalize.translate('ButtonGuide'); + guideView.ImageTags = {}; + guideView.icon = 'dvr'; + guideView.url = 'livetv.html?tab=1'; + list.push(guideView); + } + } + + return list; + }); + } + + function showBySelector(selector, show) { + var elem = document.querySelector(selector); + + if (elem) { + if (show) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + } + } + + function updateLibraryMenu(user) { + // FIXME: Potential equivalent might be + // showBySelector(".lnkSyncToOtherDevices", !!user.Policy.EnableContentDownloading); + if (!user) { + showBySelector('.libraryMenuDownloads', false); + showBySelector('.lnkSyncToOtherDevices', false); + return void showBySelector('.userMenuOptions', false); + } + + // FIXME: Potentially the same as above + if (user.Policy.EnableContentDownloading) { + showBySelector('.lnkSyncToOtherDevices', true); + } else { + showBySelector('.lnkSyncToOtherDevices', false); + } + + if (user.Policy.EnableContentDownloading && appHost.supports('sync')) { + showBySelector('.libraryMenuDownloads', true); + } else { + showBySelector('.libraryMenuDownloads', false); + } + + var userId = Dashboard.getCurrentUserId(); + var apiClient = getCurrentApiClient(); + var libraryMenuOptions = document.querySelector('.libraryMenuOptions'); + + if (libraryMenuOptions) { + getUserViews(apiClient, userId).then(function (result) { + var items = result; + var html = `

${globalize.translate('HeaderMedia')}

`; + html += items.map(function (i) { + var icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); + var itemId = i.Id; + + const linkHtml = ` + + ${i.Name} + `; + + return linkHtml; + }).join(''); + libraryMenuOptions.innerHTML = html; + var elem = libraryMenuOptions; + var sidebarLinks = elem.querySelectorAll('.navMenuOption'); + + for (const sidebarLink of sidebarLinks) { + sidebarLink.removeEventListener('click', onSidebarLinkClick); + sidebarLink.addEventListener('click', onSidebarLinkClick); + } + }); + } + } + + function getTopParentId() { + return getParameterByName('topParentId') || null; + } + + function onMainDrawerClick(e) { + if (dom.parentWithTag(e.target, 'A')) { + setTimeout(closeMainDrawer, 30); + } + } + + function onSettingsClick() { + Dashboard.navigate('mypreferencesmenu.html'); + } + + function onLogoutClick() { + Dashboard.logout(); + } + + function updateCastIcon() { + var context = document; + var info = playbackManager.getPlayerInfo(); + var icon = headerCastButton.querySelector('.material-icons'); + + icon.classList.remove('cast_connected', 'cast'); + + if (info && !info.isLocalPlayer) { + icon.classList.add('cast_connected'); + headerCastButton.classList.add('castButton-active'); + context.querySelector('.headerSelectedPlayer').innerHTML = info.deviceName || info.name; + } else { + icon.classList.add('cast'); + headerCastButton.classList.remove('castButton-active'); + context.querySelector('.headerSelectedPlayer').innerHTML = ''; + } + } + + function updateLibraryNavLinks(page) { + var i; + var length; + var isLiveTvPage = page.classList.contains('liveTvPage'); + var isChannelsPage = page.classList.contains('channelsPage'); + var isEditorPage = page.classList.contains('metadataEditorPage'); + var isMySyncPage = page.classList.contains('mySyncPage'); + var id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains('allLibraryPage') ? '' : getTopParentId() || ''; + var elems = document.getElementsByClassName('lnkMediaFolder'); + + for (var i = 0, length = elems.length; i < length; i++) { + var lnkMediaFolder = elems[i]; + var itemId = lnkMediaFolder.getAttribute('data-itemid'); + + if (isChannelsPage && 'channels' === itemId) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else if (isLiveTvPage && 'livetv' === itemId) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else if (isEditorPage && 'editor' === itemId) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else if (isMySyncPage && 'manageoffline' === itemId && -1 != window.location.href.toString().indexOf('mode=download')) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else if (isMySyncPage && 'syncotherdevices' === itemId && -1 == window.location.href.toString().indexOf('mode=download')) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else if (id && itemId == id) { + lnkMediaFolder.classList.add('navMenuOption-selected'); + } else { + lnkMediaFolder.classList.remove('navMenuOption-selected'); + } + } + } + + function updateMenuForPageType(isDashboardPage, isLibraryPage) { + var newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; + + if (currentPageType !== newPageType) { + currentPageType = newPageType; + + if (isDashboardPage && !layoutManager.mobile) { + skinHeader.classList.add('headroomDisabled'); + } else { + skinHeader.classList.remove('headroomDisabled'); + } + + var bodyClassList = document.body.classList; + + if (isLibraryPage) { + bodyClassList.add('libraryDocument'); + bodyClassList.remove('dashboardDocument'); + bodyClassList.remove('hideMainDrawer'); + + if (navDrawerInstance) { + navDrawerInstance.setEdgeSwipeEnabled(true); + } + } else { + if (isDashboardPage) { + bodyClassList.remove('libraryDocument'); + bodyClassList.add('dashboardDocument'); + bodyClassList.remove('hideMainDrawer'); + + if (navDrawerInstance) { + navDrawerInstance.setEdgeSwipeEnabled(true); + } + } else { + bodyClassList.remove('libraryDocument'); + bodyClassList.remove('dashboardDocument'); + bodyClassList.add('hideMainDrawer'); + + if (navDrawerInstance) { + navDrawerInstance.setEdgeSwipeEnabled(false); + } + } + } + } + + if (requiresUserRefresh) { + connectionManager.user(getCurrentApiClient()).then(updateUserInHeader); + } + } + + function updateTitle(page) { + var title = page.getAttribute('data-title'); + + if (title) { + LibraryMenu.setTitle(title); + } else if (page.classList.contains('standalonePage')) { + LibraryMenu.setDefaultTitle(); + } + } + + function updateBackButton(page) { + if (!headerBackButton) { + headerBackButton = document.querySelector('.headerBackButton'); + } + + if (headerBackButton) { + if ('false' !== page.getAttribute('data-backbutton') && appRouter.canGoBack()) { + headerBackButton.classList.remove('hide'); + } else { + headerBackButton.classList.add('hide'); + } + } + } + + function initHeadRoom(elem) { + require(['headroom'], function (Headroom) { + var headroom = new Headroom(elem); + headroom.init(); + }); + } + + function refreshLibraryDrawer(user) { + loadNavDrawer(); + currentDrawerType = 'library'; + + if (user) { + Promise.resolve(user); + } else { + connectionManager.user(getCurrentApiClient()).then(function (user) { + refreshLibraryInfoInDrawer(user); + updateLibraryMenu(user.localUser); + }); + } + } + + function getNavDrawerOptions() { + var drawerWidth = screen.availWidth - 50; + drawerWidth = Math.max(drawerWidth, 240); + drawerWidth = Math.min(drawerWidth, 320); + return { + target: navDrawerElement, + onChange: onMainDrawerSelect, + width: drawerWidth + }; + } + + function loadNavDrawer() { + if (navDrawerInstance) { + return Promise.resolve(navDrawerInstance); + } + + navDrawerElement = document.querySelector('.mainDrawer'); + navDrawerScrollContainer = navDrawerElement.querySelector('.scrollContainer'); + navDrawerScrollContainer.addEventListener('click', onMainDrawerClick); + return new Promise(function (resolve, reject) { + require(['navdrawer'], function (navdrawer) { + navDrawerInstance = new navdrawer(getNavDrawerOptions()); + + if (!layoutManager.tv) { + navDrawerElement.classList.remove('hide'); + } + + resolve(navDrawerInstance); + }); + }); + } + + var navDrawerElement; + var navDrawerScrollContainer; + var navDrawerInstance; + var mainDrawerButton; + var headerHomeButton; + var currentDrawerType; + var pageTitleElement; + var headerBackButton; + var headerUserButton; + var currentUser; + var headerCastButton; + var headerSearchButton; + var headerAudioPlayerButton; + var headerSyncButton; + var enableLibraryNavDrawer = layoutManager.desktop; + var enableLibraryNavDrawerHome = !layoutManager.tv; + var skinHeader = document.querySelector('.skinHeader'); + var requiresUserRefresh = true; + var lastOpenTime = new Date().getTime(); + window.LibraryMenu = { + getTopParentId: getTopParentId, + onHardwareMenuButtonClick: function () { + toggleMainDrawer(); + }, + setTabs: function (type, selectedIndex, builder) { + require(['mainTabsManager'], function (mainTabsManager) { + if (type) { + mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { + return []; + }); + } else { + mainTabsManager.setTabs(null); + } + }); + }, + setDefaultTitle: function () { + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); + } + + if (pageTitleElement) { + pageTitleElement.classList.add('pageTitleWithLogo'); + pageTitleElement.classList.add('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = ''; + } + + document.title = 'Jellyfin'; + }, + setTitle: function (title) { + if (null == title) { + return void LibraryMenu.setDefaultTitle(); + } + + if ('-' === title) { + title = ''; + } + + var html = title; + + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); + } + + if (pageTitleElement) { + pageTitleElement.classList.remove('pageTitleWithLogo'); + pageTitleElement.classList.remove('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = html || ''; + } + + document.title = title || 'Jellyfin'; + }, + setTransparentMenu: function (transparent) { + if (transparent) { + skinHeader.classList.add('semiTransparent'); + } else { + skinHeader.classList.remove('semiTransparent'); + } + } + }; + var currentPageType; + pageClassOn('pagebeforeshow', 'page', function (e) { + if (!this.classList.contains('withTabs')) { + LibraryMenu.setTabs(null); + } + }); + pageClassOn('pageshow', 'page', function (e) { + var page = this; + var isDashboardPage = page.classList.contains('type-interior'); + var isHomePage = page.classList.contains('homePage'); + var isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); + var apiClient = getCurrentApiClient(); + + if (isDashboardPage) { + if (mainDrawerButton) { + mainDrawerButton.classList.remove('hide'); + } + + refreshDashboardInfoInDrawer(apiClient); + } else { + if (mainDrawerButton) { + if (enableLibraryNavDrawer || (isHomePage && enableLibraryNavDrawerHome)) { + mainDrawerButton.classList.remove('hide'); + } else { + mainDrawerButton.classList.add('hide'); + } + } + + if ('library' !== currentDrawerType) { + refreshLibraryDrawer(); + } + } + + updateMenuForPageType(isDashboardPage, isLibraryPage); + + // TODO: Seems to do nothing? Check if needed (also in other views). + if (!e.detail.isRestored) { + window.scrollTo(0, 0); + } + + updateTitle(page); + updateBackButton(page); + updateLibraryNavLinks(page); + }); + + renderHeader(); + + events.on(connectionManager, 'localusersignedin', function (e, user) { + var currentApiClient = connectionManager.getApiClient(user.ServerId); + + currentDrawerType = null; + currentUser = { + localUser: user + }; + + loadNavDrawer(); + + connectionManager.user(currentApiClient).then(function (user) { + currentUser = user; + updateUserInHeader(user); + }); + }); + events.on(connectionManager, 'localusersignedout', function () { + currentUser = {}; + updateUserInHeader(); + }); + events.on(playbackManager, 'playerchange', updateCastIcon); + events.on(syncPlayManager, 'enabled', onSyncPlayEnabled); + events.on(syncPlayManager, 'syncing', onSyncPlaySyncing); + loadNavDrawer(); + return LibraryMenu; +}); diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js deleted file mode 100644 index 11c89b01daa..00000000000 --- a/src/scripts/librarymenu.js +++ /dev/null @@ -1,930 +0,0 @@ -define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "viewManager", "libraryBrowser", "appRouter", "apphost", "playbackManager", "browser", "globalize", "scripts/imagehelper", "paper-icon-button-light", "material-icons", "scrollStyles", "flexStyles"], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, browser, globalize, imageHelper) { - "use strict"; - - function renderHeader() { - var html = ""; - html += '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += '

'; - html += "
"; - html += '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += "
"; - html += "
"; - html += '
'; - html += "
"; - - skinHeader.classList.add("skinHeader-withBackground"); - skinHeader.classList.add("skinHeader-blurred"); - skinHeader.innerHTML = html; - - headerHomeButton = skinHeader.querySelector(".headerHomeButton"); - headerUserButton = skinHeader.querySelector(".headerUserButton"); - headerCastButton = skinHeader.querySelector(".headerCastButton"); - headerAudioPlayerButton = skinHeader.querySelector(".headerAudioPlayerButton"); - headerSearchButton = skinHeader.querySelector(".headerSearchButton"); - - lazyLoadViewMenuBarImages(); - bindMenuEvents(); - } - - function getCurrentApiClient() { - if (currentUser && currentUser.localUser) { - return connectionManager.getApiClient(currentUser.localUser.ServerId); - } - - return connectionManager.currentApiClient(); - } - - function lazyLoadViewMenuBarImages() { - require(["imageLoader"], function (imageLoader) { - imageLoader.lazyChildren(skinHeader); - }); - } - - function onBackClick() { - appRouter.back(); - } - - function updateUserInHeader(user) { - var hasImage; - - if (user && user.name) { - if (user.imageUrl) { - var url = user.imageUrl; - updateHeaderUserButton(url); - hasImage = true; - } - - headerUserButton.classList.remove("hide"); - } else { - headerUserButton.classList.add("hide"); - } - - if (!hasImage) { - updateHeaderUserButton(null); - } - - if (user && user.localUser) { - if (headerHomeButton && !layoutManager.mobile) { - headerHomeButton.classList.remove("hide"); - } - - if (headerSearchButton) { - headerSearchButton.classList.remove("hide"); - } - - if (!layoutManager.tv) { - headerCastButton.classList.remove("hide"); - } - } else { - headerHomeButton.classList.add("hide"); - headerCastButton.classList.add("hide"); - - if (headerSearchButton) { - headerSearchButton.classList.add("hide"); - } - } - - requiresUserRefresh = false; - } - - function updateHeaderUserButton(src) { - if (src) { - headerUserButton.classList.add("headerUserButtonRound"); - headerUserButton.innerHTML = '
"; - } else { - headerUserButton.classList.remove("headerUserButtonRound"); - headerUserButton.innerHTML = 'person'; - } - } - - function showSearch() { - inputManager.trigger("search"); - } - - function onHeaderUserButtonClick(e) { - Dashboard.navigate("mypreferencesmenu.html"); - } - - function onHeaderHomeButtonClick() { - Dashboard.navigate("home.html"); - } - - function showAudioPlayer() { - return appRouter.showNowPlaying(); - } - - function bindMenuEvents() { - mainDrawerButton = document.querySelector(".mainDrawerButton"); - - if (mainDrawerButton) { - mainDrawerButton.addEventListener("click", toggleMainDrawer); - } - - var headerBackButton = skinHeader.querySelector(".headerBackButton"); - - if (headerBackButton) { - headerBackButton.addEventListener("click", onBackClick); - } - - if (headerSearchButton) { - headerSearchButton.addEventListener("click", showSearch); - } - - headerUserButton.addEventListener("click", onHeaderUserButtonClick); - headerHomeButton.addEventListener("click", onHeaderHomeButtonClick); - - if (!layoutManager.tv) { - headerCastButton.addEventListener("click", onCastButtonClicked); - } - - headerAudioPlayerButton.addEventListener("click", showAudioPlayer); - - if (layoutManager.mobile) { - initHeadRoom(skinHeader); - } - events.on(playbackManager, 'playbackstart', onPlaybackStart); - events.on(playbackManager, 'playbackstop', onPlaybackStop); - } - - function onPlaybackStart(e) { - if (playbackManager.isPlayingAudio() && layoutManager.tv) { - headerAudioPlayerButton.classList.remove("hide"); - } else { - headerAudioPlayerButton.classList.add("hide"); - } - } - - function onPlaybackStop(e, stopInfo) { - if (stopInfo.nextMediaType != 'Audio') { - headerAudioPlayerButton.classList.add("hide"); - } - } - - function onCastButtonClicked() { - var btn = this; - - require(["playerSelectionMenu"], function (playerSelectionMenu) { - playerSelectionMenu.show(btn); - }); - } - - function getItemHref(item, context) { - return appRouter.getRouteUrl(item, { - context: context - }); - } - - function toggleMainDrawer() { - if (navDrawerInstance.isVisible) { - closeMainDrawer(); - } else { - openMainDrawer(); - } - } - - function openMainDrawer() { - navDrawerInstance.open(); - lastOpenTime = new Date().getTime(); - } - - function onMainDrawerOpened() { - if (layoutManager.mobile) { - document.body.classList.add("bodyWithPopupOpen"); - } - } - - function closeMainDrawer() { - navDrawerInstance.close(); - } - - function onMainDrawerSelect(e) { - if (navDrawerInstance.isVisible) { - onMainDrawerOpened(); - } else { - document.body.classList.remove("bodyWithPopupOpen"); - } - } - - function refreshLibraryInfoInDrawer(user, drawer) { - var html = ""; - html += '
'; - html += 'home' + globalize.translate("ButtonHome") + ""; - - // libraries are added here - html += '
'; - html += "
"; - - if (user.localUser && user.localUser.Policy.IsAdministrator) { - html += '
'; - html += '

'; - html += globalize.translate("HeaderAdmin"); - html += "

"; - html += 'dashboard' + globalize.translate("TabDashboard") + ""; - html += '' + globalize.translate("Metadata") + ""; - html += "
"; - } - - if (user.localUser) { - html += '
'; - html += '

'; - html += globalize.translate("HeaderUser"); - html += "

"; - - if (appHost.supports("multiserver")) { - html += 'wifi' + globalize.translate("ButtonSelectServer") + ""; - } - - html += '' + globalize.translate("ButtonSettings") + ""; - html += '' + globalize.translate("ButtonSignOut") + ""; - html += "
"; - } - - // add buttons to navigation drawer - navDrawerScrollContainer.innerHTML = html; - - var btnSettings = navDrawerScrollContainer.querySelector(".btnSettings"); - if (btnSettings) { - btnSettings.addEventListener("click", onSettingsClick); - } - - var btnLogout = navDrawerScrollContainer.querySelector(".btnLogout"); - if (btnLogout) { - btnLogout.addEventListener("click", onLogoutClick); - } - } - - function refreshDashboardInfoInDrawer(apiClient) { - currentDrawerType = "admin"; - loadNavDrawer(); - - if (navDrawerScrollContainer.querySelector(".adminDrawerLogo")) { - updateDashboardMenuSelectedItem(); - } else { - createDashboardMenu(apiClient); - } - } - - function isUrlInCurrentView(url) { - return -1 !== window.location.href.toString().toLowerCase().indexOf(url.toLowerCase()); - } - - function updateDashboardMenuSelectedItem() { - var links = navDrawerScrollContainer.querySelectorAll(".navMenuOption"); - var currentViewId = viewManager.currentView().id; - - for (var i = 0, length = links.length; i < length; i++) { - var link = links[i]; - var selected = false; - var pageIds = link.getAttribute("data-pageids"); - - if (pageIds) { - pageIds = pageIds.split("|"); - selected = -1 != pageIds.indexOf(currentViewId); - } - - var pageUrls = link.getAttribute("data-pageurls"); - - if (pageUrls) { - pageUrls = pageUrls.split("|"); - selected = pageUrls.filter(isUrlInCurrentView).length > 0; - } - - if (selected) { - link.classList.add("navMenuOption-selected"); - var title = ""; - link = link.querySelector("span") || link; - title += (link.innerText || link.textContent).trim(); - LibraryMenu.setTitle(title); - } else { - link.classList.remove("navMenuOption-selected"); - } - } - } - - function createToolsMenuList(pluginItems) { - var links = [{ - name: globalize.translate("TabServer") - }, { - name: globalize.translate("TabDashboard"), - href: "dashboard.html", - pageIds: ["dashboardPage"], - icon: "dashboard" - }, { - name: globalize.translate("General"), - href: "dashboardgeneral.html", - pageIds: ["dashboardGeneralPage"], - icon: "settings" - }, { - name: globalize.translate("TabUsers"), - href: "userprofiles.html", - pageIds: ["userProfilesPage", "newUserPage", "editUserPage", "userLibraryAccessPage", "userParentalControlPage", "userPasswordPage"], - icon: "people" - }, { - name: globalize.translate("HeaderLibraries"), - href: "library.html", - pageIds: ["mediaLibraryPage", "librarySettingsPage", "libraryDisplayPage", "metadataImagesConfigurationPage", "metadataNfoPage"], - icon: "folder" - }, { - name: globalize.translate("TabPlayback"), - icon: "", - href: "encodingsettings.html", - pageIds: ["encodingSettingsPage", "playbackConfigurationPage", "streamingSettingsPage"] - }]; - addPluginPagesToMainMenu(links, pluginItems, "server"); - links.push({ - divider: true, - name: globalize.translate("TabDevices") - }); - links.push({ - name: globalize.translate("TabDevices"), - href: "devices.html", - pageIds: ["devicesPage", "devicePage"], - icon: "devices" - }); - links.push({ - name: globalize.translate("HeaderActivity"), - href: "serveractivity.html", - pageIds: ["serverActivityPage"], - icon: "assessment" - }); - links.push({ - name: globalize.translate("DLNA"), - href: "dlnasettings.html", - pageIds: ["dlnaSettingsPage", "dlnaProfilesPage", "dlnaProfilePage"], - icon: "input" - }); - links.push({ - divider: true, - name: globalize.translate("TabLiveTV") - }); - links.push({ - name: globalize.translate("TabLiveTV"), - href: "livetvstatus.html", - pageIds: ["liveTvStatusPage", "liveTvTunerPage"], - icon: "live_tv" - }); - links.push({ - name: globalize.translate("DVR"), - href: "livetvsettings.html", - pageIds: ["liveTvSettingsPage"], - icon: "dvr" - }); - links.push({ - divider: true, - name: globalize.translate("TabAdvanced") - }); - links.push({ - name: globalize.translate("TabNetworking"), - icon: "cloud", - href: "networking.html", - pageIds: ["networkingPage"] - }); - links.push({ - name: globalize.translate("HeaderApiKeys"), - icon: "vpn_key", - href: "apikeys.html", - pageIds: ["apiKeysPage"] - }); - links.push({ - name: globalize.translate("TabLogs"), - href: "log.html", - pageIds: ["logPage"], - icon: "bug_report" - }); - links.push({ - name: globalize.translate("TabNotifications"), - icon: "notifications", - href: "notificationsettings.html", - pageIds: ["notificationSettingsPage", "notificationSettingPage"] - }); - links.push({ - name: globalize.translate("TabPlugins"), - icon: "shopping_cart", - href: "installedplugins.html", - pageIds: ["pluginsPage", "pluginCatalogPage"] - }); - links.push({ - name: globalize.translate("TabScheduledTasks"), - href: "scheduledtasks.html", - pageIds: ["scheduledTasksPage", "scheduledTaskPage"], - icon: "schedule" - }); - addPluginPagesToMainMenu(links, pluginItems); - return links; - } - - function addPluginPagesToMainMenu(links, pluginItems, section) { - for (var i = 0, length = pluginItems.length; i < length; i++) { - var pluginItem = pluginItems[i]; - - if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) { - links.push({ - name: pluginItem.DisplayName, - icon: pluginItem.MenuIcon || "folder", - href: Dashboard.getConfigurationPageUrl(pluginItem.Name), - pageUrls: [Dashboard.getConfigurationPageUrl(pluginItem.Name)] - }); - } - } - } - - function getToolsMenuLinks(apiClient) { - return apiClient.getJSON(apiClient.getUrl("web/configurationpages") + "?pageType=PluginConfiguration&EnableInMainMenu=true").then(createToolsMenuList, function (err) { - return createToolsMenuList([]); - }); - } - - function getToolsLinkHtml(item) { - var menuHtml = ""; - var pageIds = item.pageIds ? item.pageIds.join("|") : ""; - pageIds = pageIds ? ' data-pageids="' + pageIds + '"' : ""; - var pageUrls = item.pageUrls ? item.pageUrls.join("|") : ""; - pageUrls = pageUrls ? ' data-pageurls="' + pageUrls + '"' : ""; - menuHtml += '"; - - if (item.icon) { - menuHtml += '' + item.icon + ""; - } - - menuHtml += ''; - menuHtml += item.name; - menuHtml += ""; - return menuHtml + ""; - } - - function getToolsMenuHtml(apiClient) { - return getToolsMenuLinks(apiClient).then(function (items) { - var item; - var menuHtml = ""; - menuHtml += '
'; - - for (var i = 0; i < items.length; i++) { - item = items[i]; - - if (item.href) { - menuHtml += getToolsLinkHtml(item); - } else if (item.name) { - menuHtml += '

'; - menuHtml += item.name; - menuHtml += "

"; - } - } - - return menuHtml + "
"; - }); - } - - function createDashboardMenu(apiClient) { - return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) { - var html = ""; - html += '"; - html += toolsMenuHtml; - navDrawerScrollContainer.innerHTML = html; - updateDashboardMenuSelectedItem(); - }); - } - - function onSidebarLinkClick() { - var section = this.getElementsByClassName("sectionName")[0]; - var text = section ? section.innerHTML : this.innerHTML; - LibraryMenu.setTitle(text); - } - - function getUserViews(apiClient, userId) { - return apiClient.getUserViews({}, userId).then(function (result) { - var items = result.Items; - var list = []; - - for (var i = 0, length = items.length; i < length; i++) { - var view = items[i]; - list.push(view); - - if ("livetv" == view.CollectionType) { - view.ImageTags = {}; - view.icon = "live_tv"; - var guideView = Object.assign({}, view); - guideView.Name = globalize.translate("ButtonGuide"); - guideView.ImageTags = {}; - guideView.icon = "dvr"; - guideView.url = "livetv.html?tab=1"; - list.push(guideView); - } - } - - return list; - }); - } - - function showBySelector(selector, show) { - var elem = document.querySelector(selector); - - if (elem) { - if (show) { - elem.classList.remove("hide"); - } else { - elem.classList.add("hide"); - } - } - } - - function updateLibraryMenu(user) { - // FIXME: Potential equivalent might be - // showBySelector(".lnkSyncToOtherDevices", !!user.Policy.EnableContentDownloading); - if (!user) { - showBySelector(".libraryMenuDownloads", false); - showBySelector(".lnkSyncToOtherDevices", false); - return void showBySelector(".userMenuOptions", false); - } - - // FIXME: Potentially the same as above - if (user.Policy.EnableContentDownloading) { - showBySelector(".lnkSyncToOtherDevices", true); - } else { - showBySelector(".lnkSyncToOtherDevices", false); - } - - if (user.Policy.EnableContentDownloading && appHost.supports("sync")) { - showBySelector(".libraryMenuDownloads", true); - } else { - showBySelector(".libraryMenuDownloads", false); - } - - var userId = Dashboard.getCurrentUserId(); - var apiClient = getCurrentApiClient(); - var libraryMenuOptions = document.querySelector(".libraryMenuOptions"); - - if (libraryMenuOptions) { - getUserViews(apiClient, userId).then(function (result) { - var items = result; - var html = ""; - html += '

'; - html += globalize.translate("HeaderMedia"); - html += "

"; - html += items.map(function (i) { - var icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); - var itemId = i.Id; - - if (i.onclick) { - i.onclick; - } - - return '' + icon + '' + i.Name + ""; - }).join(""); - libraryMenuOptions.innerHTML = html; - var elem = libraryMenuOptions; - var sidebarLinks = elem.querySelectorAll(".navMenuOption"); - - for (var i = 0, length = sidebarLinks.length; i < length; i++) { - sidebarLinks[i].removeEventListener("click", onSidebarLinkClick); - sidebarLinks[i].addEventListener("click", onSidebarLinkClick); - } - }); - } - } - - function getTopParentId() { - return getParameterByName("topParentId") || null; - } - - function onMainDrawerClick(e) { - if (dom.parentWithTag(e.target, "A")) { - setTimeout(closeMainDrawer, 30); - } - } - - function onSettingsClick() { - Dashboard.navigate("mypreferencesmenu.html"); - } - - function onLogoutClick() { - Dashboard.logout(); - } - - function updateCastIcon() { - var context = document; - var info = playbackManager.getPlayerInfo(); - var icon = headerCastButton.querySelector("i"); - - if (info && !info.isLocalPlayer) { - icon.innerHTML = ""; - headerCastButton.classList.add("castButton-active"); - context.querySelector(".headerSelectedPlayer").innerHTML = info.deviceName || info.name; - } else { - icon.innerHTML = "cast"; - headerCastButton.classList.remove("castButton-active"); - context.querySelector(".headerSelectedPlayer").innerHTML = ""; - } - } - - function updateLibraryNavLinks(page) { - var i; - var length; - var isLiveTvPage = page.classList.contains("liveTvPage"); - var isChannelsPage = page.classList.contains("channelsPage"); - var isEditorPage = page.classList.contains("metadataEditorPage"); - var isMySyncPage = page.classList.contains("mySyncPage"); - var id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains("allLibraryPage") ? "" : getTopParentId() || ""; - var elems = document.getElementsByClassName("lnkMediaFolder"); - - for (var i = 0, length = elems.length; i < length; i++) { - var lnkMediaFolder = elems[i]; - var itemId = lnkMediaFolder.getAttribute("data-itemid"); - - if (isChannelsPage && "channels" === itemId) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else if (isLiveTvPage && "livetv" === itemId) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else if (isEditorPage && "editor" === itemId) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else if (isMySyncPage && "manageoffline" === itemId && -1 != window.location.href.toString().indexOf("mode=download")) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else if (isMySyncPage && "syncotherdevices" === itemId && -1 == window.location.href.toString().indexOf("mode=download")) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else if (id && itemId == id) { - lnkMediaFolder.classList.add("navMenuOption-selected"); - } else { - lnkMediaFolder.classList.remove("navMenuOption-selected"); - } - } - } - - function updateMenuForPageType(isDashboardPage, isLibraryPage) { - var newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; - - if (currentPageType !== newPageType) { - currentPageType = newPageType; - - if (isDashboardPage && !layoutManager.mobile) { - skinHeader.classList.add("headroomDisabled"); - } else { - skinHeader.classList.remove("headroomDisabled"); - } - - var bodyClassList = document.body.classList; - - if (isLibraryPage) { - bodyClassList.add("libraryDocument"); - bodyClassList.remove("dashboardDocument"); - bodyClassList.remove("hideMainDrawer"); - - if (navDrawerInstance) { - navDrawerInstance.setEdgeSwipeEnabled(true); - } - } else { - if (isDashboardPage) { - bodyClassList.remove("libraryDocument"); - bodyClassList.add("dashboardDocument"); - bodyClassList.remove("hideMainDrawer"); - - if (navDrawerInstance) { - navDrawerInstance.setEdgeSwipeEnabled(true); - } - } else { - bodyClassList.remove("libraryDocument"); - bodyClassList.remove("dashboardDocument"); - bodyClassList.add("hideMainDrawer"); - - if (navDrawerInstance) { - navDrawerInstance.setEdgeSwipeEnabled(false); - } - } - } - } - - if (requiresUserRefresh) { - connectionManager.user(getCurrentApiClient()).then(updateUserInHeader); - } - } - - function updateTitle(page) { - var title = page.getAttribute("data-title"); - - if (title) { - LibraryMenu.setTitle(title); - } else if (page.classList.contains("standalonePage")) { - LibraryMenu.setDefaultTitle(); - } - } - - function updateBackButton(page) { - if (!headerBackButton) { - headerBackButton = document.querySelector(".headerBackButton"); - } - - if (headerBackButton) { - if ("false" !== page.getAttribute("data-backbutton") && appRouter.canGoBack()) { - headerBackButton.classList.remove("hide"); - } else { - headerBackButton.classList.add("hide"); - } - } - } - - function initHeadRoom(elem) { - require(["headroom"], function (Headroom) { - var headroom = new Headroom([], {}); - headroom.add(elem); - }); - } - - function refreshLibraryDrawer(user) { - loadNavDrawer(); - currentDrawerType = "library"; - - if (user) { - Promise.resolve(user); - } else { - connectionManager.user(getCurrentApiClient()).then(function (user) { - refreshLibraryInfoInDrawer(user); - updateLibraryMenu(user.localUser); - }); - } - } - - function getNavDrawerOptions() { - var drawerWidth = screen.availWidth - 50; - drawerWidth = Math.max(drawerWidth, 240); - drawerWidth = Math.min(drawerWidth, 320); - return { - target: navDrawerElement, - onChange: onMainDrawerSelect, - width: drawerWidth - }; - } - - function loadNavDrawer() { - if (navDrawerInstance) { - return Promise.resolve(navDrawerInstance); - } - - navDrawerElement = document.querySelector(".mainDrawer"); - navDrawerScrollContainer = navDrawerElement.querySelector(".scrollContainer"); - navDrawerScrollContainer.addEventListener("click", onMainDrawerClick); - return new Promise(function (resolve, reject) { - require(["navdrawer"], function (navdrawer) { - navDrawerInstance = new navdrawer(getNavDrawerOptions()); - - if (!layoutManager.tv) { - navDrawerElement.classList.remove("hide"); - } - - resolve(navDrawerInstance); - }); - }); - } - - var navDrawerElement; - var navDrawerScrollContainer; - var navDrawerInstance; - var mainDrawerButton; - var headerHomeButton; - var currentDrawerType; - var pageTitleElement; - var headerBackButton; - var headerUserButton; - var currentUser; - var headerCastButton; - var headerSearchButton; - var headerAudioPlayerButton; - var enableLibraryNavDrawer = layoutManager.desktop; - var skinHeader = document.querySelector(".skinHeader"); - var requiresUserRefresh = true; - var lastOpenTime = new Date().getTime(); - window.LibraryMenu = { - getTopParentId: getTopParentId, - onHardwareMenuButtonClick: function () { - toggleMainDrawer(); - }, - setTabs: function (type, selectedIndex, builder) { - require(["mainTabsManager"], function (mainTabsManager) { - if (type) { - mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { - return []; - }); - } else { - mainTabsManager.setTabs(null); - } - }); - }, - setDefaultTitle: function () { - if (!pageTitleElement) { - pageTitleElement = document.querySelector(".pageTitle"); - } - - if (pageTitleElement) { - pageTitleElement.classList.add("pageTitleWithLogo"); - pageTitleElement.classList.add("pageTitleWithDefaultLogo"); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = ""; - } - - document.title = "Jellyfin"; - }, - setTitle: function (title) { - if (null == title) { - return void LibraryMenu.setDefaultTitle(); - } - - if ("-" === title) { - title = ""; - } - - var html = title; - - if (!pageTitleElement) { - pageTitleElement = document.querySelector(".pageTitle"); - } - - if (pageTitleElement) { - pageTitleElement.classList.remove("pageTitleWithLogo"); - pageTitleElement.classList.remove("pageTitleWithDefaultLogo"); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = html || ""; - } - - document.title = title || "Jellyfin"; - }, - setTransparentMenu: function (transparent) { - if (transparent) { - skinHeader.classList.add("semiTransparent"); - } else { - skinHeader.classList.remove("semiTransparent"); - } - } - }; - var currentPageType; - pageClassOn("pagebeforeshow", "page", function (e) { - if (!this.classList.contains("withTabs")) { - LibraryMenu.setTabs(null); - } - }); - pageClassOn("pageshow", "page", function (e) { - var page = this; - var isDashboardPage = page.classList.contains("type-interior"); - var isHomePage = page.classList.contains("homePage"); - var isLibraryPage = !isDashboardPage && page.classList.contains("libraryPage"); - var apiClient = getCurrentApiClient(); - - if (isDashboardPage) { - if (mainDrawerButton) { - mainDrawerButton.classList.remove("hide"); - } - - refreshDashboardInfoInDrawer(apiClient); - } else { - if (mainDrawerButton) { - if (enableLibraryNavDrawer || isHomePage) { - mainDrawerButton.classList.remove("hide"); - } else { - mainDrawerButton.classList.add("hide"); - } - } - - if ("library" !== currentDrawerType) { - refreshLibraryDrawer(); - } - } - - updateMenuForPageType(isDashboardPage, isLibraryPage); - - if (!e.detail.isRestored) { - window.scrollTo(0, 0); - } - - updateTitle(page); - updateBackButton(page); - updateLibraryNavLinks(page); - }); - - renderHeader(); - - events.on(connectionManager, "localusersignedin", function (e, user) { - currentDrawerType = null; - currentUser = { - localUser: user - }; - loadNavDrawer(); - connectionManager.user(connectionManager.getApiClient(user.ServerId)).then(function (user) { - currentUser = user; - updateUserInHeader(user); - }); - }); - events.on(connectionManager, "localusersignedout", function () { - currentUser = {}; - updateUserInHeader(); - }); - events.on(playbackManager, "playerchange", updateCastIcon); - loadNavDrawer(); - return LibraryMenu; -}); diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index 61ffc66fb46..fd4a46c0f19 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -1,12 +1,12 @@ -define(["layoutManager", "datetime", "cardBuilder", "apphost"], function (layoutManager, datetime, cardBuilder, appHost) { - "use strict"; +define(['layoutManager', 'datetime', 'cardBuilder', 'apphost'], function (layoutManager, datetime, cardBuilder, appHost) { + 'use strict'; function enableScrollX() { return !layoutManager.desktop; } function getBackdropShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop"; + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; } function getTimersHtml(timers, options) { @@ -14,27 +14,27 @@ define(["layoutManager", "datetime", "cardBuilder", "apphost"], function (layout var i; var length; var items = timers.map(function (t) { - t.Type = "Timer"; + t.Type = 'Timer'; return t; }); var groups = []; - var currentGroupName = ""; + var currentGroupName = ''; var currentGroup = []; for (i = 0, length = items.length; i < length; i++) { var item = items[i]; - var dateText = ""; + var dateText = ''; if (options.indexByDate !== false && item.StartDate) { try { var premiereDate = datetime.parseISO8601Date(item.StartDate, true); dateText = datetime.toLocaleDateString(premiereDate, { - weekday: "long", - month: "short", - day: "numeric" + weekday: 'long', + month: 'short', + day: 'numeric' }); } catch (err) { - console.error("error parsing premiereDate:" + item.StartDate + "; error: " + err); + console.error('error parsing premiereDate:' + item.StartDate + '; error: ' + err); } } @@ -60,23 +60,23 @@ define(["layoutManager", "datetime", "cardBuilder", "apphost"], function (layout }); } - var html = ""; + var html = ''; for (i = 0, length = groups.length; i < length; i++) { var group = groups[i]; - var supportsImageAnalysis = appHost.supports("imageanalysis"); + var supportsImageAnalysis = appHost.supports('imageanalysis'); var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; cardLayout = true; if (group.name) { html += '
'; - html += '

' + group.name + "

"; + html += '

' + group.name + '

'; } if (enableScrollX()) { - var scrollXClass = "scrollX hiddenScrollX"; + var scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { - scrollXClass += " smoothScrollX"; + scrollXClass += ' smoothScrollX'; } html += '
'; @@ -86,26 +86,26 @@ define(["layoutManager", "datetime", "cardBuilder", "apphost"], function (layout html += cardBuilder.getCardsHtml({ items: group.items, - shape: cardLayout ? getBackdropShape() : enableScrollX() ? "autoOverflow" : "autoVertical", + shape: cardLayout ? getBackdropShape() : enableScrollX() ? 'autoOverflow' : 'autoVertical', showParentTitleOrTitle: true, showAirTime: true, showAirEndTime: true, showChannelName: !cardLayout, cardLayout: cardLayout, centerText: !cardLayout, - action: "edit", - cardFooterAside: "none", - preferThumb: !!cardLayout || "auto", - defaultShape: cardLayout ? null : "portrait", + action: 'edit', + cardFooterAside: 'none', + preferThumb: !!cardLayout || 'auto', + defaultShape: cardLayout ? null : 'portrait', coverImage: true, allowBottomPadding: false, overlayText: false, showChannelLogo: cardLayout }); - html += "
"; + html += '
'; if (group.name) { - html += "
"; + html += '

'; } } diff --git a/src/components/input/mouseManager.js b/src/scripts/mouseManager.js similarity index 95% rename from src/components/input/mouseManager.js rename to src/scripts/mouseManager.js index 78057c0bfe9..84fe58e7e55 100644 --- a/src/components/input/mouseManager.js +++ b/src/scripts/mouseManager.js @@ -41,7 +41,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd var eventY = e.screenY; // if coord don't exist how could it move - if (typeof eventX === "undefined" && typeof eventY === "undefined") { + if (typeof eventX === 'undefined' && typeof eventY === 'undefined') { return; } @@ -136,6 +136,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd stopMouseInterval(); + /* eslint-disable-next-line compat/compat */ dom.removeEventListener(document, (window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove, { passive: true }); @@ -148,6 +149,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd }); } + /* eslint-disable-next-line compat/compat */ dom.removeEventListener(document, (window.PointerEvent ? 'pointerenter' : 'mouseenter'), onPointerEnter, { capture: true, passive: true diff --git a/src/components/multidownload.js b/src/scripts/multiDownload.js similarity index 100% rename from src/components/multidownload.js rename to src/scripts/multiDownload.js diff --git a/src/scripts/playlistedit.js b/src/scripts/playlistedit.js index 32a3a960a43..636a7ef0568 100644 --- a/src/scripts/playlistedit.js +++ b/src/scripts/playlistedit.js @@ -1,14 +1,14 @@ -define(["listView"], function (listView) { - "use strict"; +define(['listView'], function (listView) { + 'use strict'; function getFetchPlaylistItemsFn(itemId) { return function () { var query = { - Fields: "PrimaryImageAspectRatio,UserData", - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + Fields: 'PrimaryImageAspectRatio,UserData', + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', UserId: ApiClient.getCurrentUserId() }; - return ApiClient.getJSON(ApiClient.getUrl("Playlists/" + itemId + "/Items", query)); + return ApiClient.getJSON(ApiClient.getUrl(`Playlists/${itemId}/Items`, query)); }; } @@ -19,7 +19,7 @@ define(["listView"], function (listView) { showIndex: false, showRemoveFromPlaylist: true, playFromHere: true, - action: "playallfromhere", + action: 'playallfromhere', smallIcon: true, dragHandle: true, playlistId: itemId @@ -28,9 +28,9 @@ define(["listView"], function (listView) { } function init(page, item) { - var elem = page.querySelector("#childrenContent .itemsContainer"); - elem.classList.add("vertical-list"); - elem.classList.remove("vertical-wrap"); + var elem = page.querySelector('#childrenContent .itemsContainer'); + elem.classList.add('vertical-list'); + elem.classList.remove('vertical-wrap'); elem.enableDragReordering(true); elem.fetchData = getFetchPlaylistItemsFn(item.Id); elem.getItemsHtml = getItemsHtmlFn(item.Id); @@ -43,8 +43,8 @@ define(["listView"], function (listView) { init(page, item); } - page.querySelector("#childrenContent").classList.add("verticalSection-extrabottompadding"); - page.querySelector("#childrenContent .itemsContainer").refreshItems(); + page.querySelector('#childrenContent').classList.add('verticalSection-extrabottompadding'); + page.querySelector('#childrenContent .itemsContainer').refreshItems(); } }; }); diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index 91c49017da8..974d00f8e64 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -1,5 +1,5 @@ -define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", "apphost", "imageLoader", "emby-itemscontainer"], function (loading, listView, cardBuilder, libraryMenu, libraryBrowser, appHost, imageLoader) { - "use strict"; +define(['loading', 'listView', 'cardBuilder', 'libraryMenu', 'libraryBrowser', 'apphost', 'imageLoader', 'userSettings', 'emby-itemscontainer'], function (loading, listView, cardBuilder, libraryMenu, libraryBrowser, appHost, imageLoader, userSettings) { + 'use strict'; return function (view, params) { function getPageData(context) { @@ -9,16 +9,20 @@ define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", " if (!pageData) { pageData = data[key] = { query: { - SortBy: "SortName", - SortOrder: "Ascending", - IncludeItemTypes: "Playlist", + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Playlist', Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete", - StartIndex: 0, - Limit: 100 + Fields: 'PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete', + StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || "Poster" + view: libraryBrowser.getSavedView(key) || 'Poster' }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = libraryMenu.getTopParentId(); libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -48,31 +52,32 @@ define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", " function onViewStyleChange() { var viewStyle = getPageData(view).view; - var itemsContainer = view.querySelector(".itemsContainer"); + var itemsContainer = view.querySelector('.itemsContainer'); - if ("List" == viewStyle) { - itemsContainer.classList.add("vertical-list"); - itemsContainer.classList.remove("vertical-wrap"); + if ('List' == viewStyle) { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); } else { - itemsContainer.classList.remove("vertical-list"); - itemsContainer.classList.add("vertical-wrap"); + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); } - itemsContainer.innerHTML = ""; + itemsContainer.innerHTML = ''; } function reloadItems() { showLoadingMessage(); var query = getQuery(view); var promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query); + // TODO: promise2 is unused, check if necessary. var promise2 = Dashboard.getCurrentUser(); Promise.all([promise1, promise2]).then(function (responses) { var result = responses[0]; - responses[1]; + // TODO: Is the scroll necessary? window.scrollTo(0, 0); - var html = ""; + var html = ''; var viewStyle = getPageData(view).view; - view.querySelector(".listTopPaging").innerHTML = libraryBrowser.getQueryPagingHtml({ + view.querySelector('.listTopPaging').innerHTML = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -80,37 +85,37 @@ define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", " showLimit: false, updatePageSizeSetting: false, addLayoutButton: true, - layouts: "List,Poster,PosterCard,Thumb,ThumbCard", + layouts: 'List,Poster,PosterCard,Thumb,ThumbCard', currentLayout: viewStyle }); if (result.TotalRecordCount) { - if (viewStyle == "List") { + if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, sortBy: query.SortBy }); - } else if (viewStyle == "PosterCard") { + } else if (viewStyle == 'PosterCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", + shape: 'square', coverImage: true, showTitle: true, cardLayout: true }); - } else if (viewStyle == "Thumb") { + } else if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', showTitle: true, centerText: true, preferThumb: true, overlayPlayButton: true }); - } else if (viewStyle == "ThumbCard") { + } else if (viewStyle == 'ThumbCard') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "backdrop", + shape: 'backdrop', showTitle: true, preferThumb: true, cardLayout: true @@ -118,43 +123,47 @@ define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", " } else { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: "square", + shape: 'square', showTitle: true, coverImage: true, centerText: true, overlayPlayButton: true }); } - view.querySelector(".noItemsMessage").classList.add("hide"); + view.querySelector('.noItemsMessage').classList.add('hide'); } else { - view.querySelector(".noItemsMessage").classList.remove("hide"); + view.querySelector('.noItemsMessage').classList.remove('hide'); } - var elem = view.querySelector(".itemsContainer"); + var elem = view.querySelector('.itemsContainer'); elem.innerHTML = html; imageLoader.lazyChildren(elem); - var btnNextPage = view.querySelector(".btnNextPage"); + var btnNextPage = view.querySelector('.btnNextPage'); if (btnNextPage) { - btnNextPage.addEventListener("click", function () { - query.StartIndex += query.Limit; + btnNextPage.addEventListener('click', function () { + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(); }); } - var btnPreviousPage = view.querySelector(".btnPreviousPage"); + var btnPreviousPage = view.querySelector('.btnPreviousPage'); if (btnPreviousPage) { - btnPreviousPage.addEventListener("click", function () { - query.StartIndex -= query.Limit; + btnPreviousPage.addEventListener('click', function () { + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(); }); } - var btnChangeLayout = view.querySelector(".btnChangeLayout"); + var btnChangeLayout = view.querySelector('.btnChangeLayout'); if (btnChangeLayout) { - btnChangeLayout.addEventListener("layoutchange", function (e) { + btnChangeLayout.addEventListener('layoutchange', function (e) { var layout = e.detail.viewStyle; getPageData(view).view = layout; libraryBrowser.saveViewSetting(getSavedQueryKey(view), layout); @@ -169,11 +178,11 @@ define(["loading", "listView", "cardBuilder", "libraryMenu", "libraryBrowser", " } var data = {}; - view.addEventListener("viewbeforeshow", function () { + view.addEventListener('viewbeforeshow', function () { reloadItems(); }); - view.querySelector(".btnNewPlaylist").addEventListener("click", function () { - require(["playlistEditor"], function (playlistEditor) { + view.querySelector('.btnNewPlaylist').addEventListener('click', function () { + require(['playlistEditor'], function (playlistEditor) { var serverId = ApiClient.serverInfo().Id; new playlistEditor().show({ items: [], diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 9c3db58a7f5..ab12b53abf5 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -1,439 +1,443 @@ define([ - "jQuery", - "emby-button", - "emby-input", - "scripts/livetvcomponents", - "paper-icon-button-light", - "emby-itemscontainer", - "emby-collapse", - "emby-select", - "livetvcss", - "emby-checkbox", - "emby-slider", - "listViewStyle", - "dashboardcss", - "detailtablecss"], function () { + 'jQuery', + 'emby-button', + 'emby-input', + 'scripts/livetvcomponents', + 'paper-icon-button-light', + 'emby-itemscontainer', + 'emby-collapse', + 'emby-select', + 'livetvcss', + 'emby-checkbox', + 'emby-slider', + 'listViewStyle', + 'dashboardcss', + 'detailtablecss'], function () { function defineRoute(newRoute) { - var path = newRoute.path; - console.debug("defining route: " + path); - newRoute.dictionary = "core"; + var path = newRoute.alias ? newRoute.alias : newRoute.path; + console.debug('defining route: ' + path); + newRoute.dictionary = 'core'; Emby.Page.addRoute(path, newRoute); } - console.debug("defining core routes"); + console.debug('defining core routes'); defineRoute({ - path: "/addplugin.html", + path: '/addserver.html', autoFocus: false, - roles: "admin", - controller: "dashboard/plugins/add" + anonymous: true, + startup: true, + controller: 'auth/addserver' }); defineRoute({ - path: "/mypreferencesmenu.html", + path: '/selectserver.html', autoFocus: false, - transition: "fade", - controller: "user/menu" + anonymous: true, + startup: true, + controller: 'auth/selectserver', + type: 'selectserver' }); defineRoute({ - path: "/myprofile.html", + path: '/login.html', autoFocus: false, - transition: "fade", - controller: "user/profile" + anonymous: true, + startup: true, + controller: 'auth/login', + type: 'login' }); defineRoute({ - path: "/addserver.html", - autoFocus: false, + path: '/forgotpassword.html', anonymous: true, startup: true, - controller: "auth/addserver" + controller: 'auth/forgotpassword' }); defineRoute({ - path: "/mypreferencesdisplay.html", + path: '/forgotpasswordpin.html', autoFocus: false, - transition: "fade", - controller: "user/display" + anonymous: true, + startup: true, + controller: 'auth/forgotpasswordpin' }); + defineRoute({ - path: "/mypreferenceshome.html", + path: '/mypreferencesmenu.html', autoFocus: false, - transition: "fade", - controller: "user/home" + transition: 'fade', + controller: 'user/menu' }); defineRoute({ - path: "/mypreferencesplayback.html", + path: '/myprofile.html', autoFocus: false, - transition: "fade", - controller: "user/playback" + transition: 'fade', + controller: 'user/profile' }); defineRoute({ - path: "/mypreferencessubtitles.html", + path: '/mypreferencesdisplay.html', autoFocus: false, - transition: "fade", - controller: "user/subtitles" + transition: 'fade', + controller: 'user/display' }); - defineRoute({ - path: "/dashboard.html", + path: '/mypreferenceshome.html', autoFocus: false, - roles: "admin", - controller: "dashboard/dashboard" + transition: 'fade', + controller: 'user/home' }); defineRoute({ - path: "/dashboardgeneral.html", - controller: "dashboard/general", + path: '/mypreferencesplayback.html', autoFocus: false, - roles: "admin" + transition: 'fade', + controller: 'user/playback' }); defineRoute({ - path: "/networking.html", + path: '/mypreferencessubtitles.html', autoFocus: false, - roles: "admin", - controller: "dashboard/networking" + transition: 'fade', + controller: 'user/subtitles' }); + defineRoute({ - path: "/devices.html", + path: '/dashboard.html', autoFocus: false, - roles: "admin", - controller: "devices" + roles: 'admin', + controller: 'dashboard/dashboard' }); defineRoute({ - path: "/device.html", + path: '/dashboardgeneral.html', + controller: 'dashboard/general', autoFocus: false, - roles: "admin", - controller: "device" + roles: 'admin' }); defineRoute({ - path: "/dlnaprofile.html", + path: '/networking.html', autoFocus: false, - roles: "admin", - controller: "dlnaprofile" + roles: 'admin', + controller: 'dashboard/networking' }); defineRoute({ - path: "/dlnaprofiles.html", + path: '/devices.html', autoFocus: false, - roles: "admin", - controller: "dlnaprofiles" + roles: 'admin', + controller: 'dashboard/devices/devices' }); defineRoute({ - path: "/dlnasettings.html", + path: '/device.html', autoFocus: false, - roles: "admin", - controller: "dlnasettings" + roles: 'admin', + controller: 'dashboard/devices/device' }); defineRoute({ - path: "/edititemmetadata.html", - controller: "edititemmetadata", - autoFocus: false - }); - defineRoute({ - path: "/encodingsettings.html", + path: '/dlnaprofile.html', autoFocus: false, - roles: "admin", - controller: "encodingsettings" - }); - defineRoute({ - path: "/forgotpassword.html", - anonymous: true, - startup: true, - controller: "auth/forgotpassword" + roles: 'admin', + controller: 'dashboard/dlna/profile' }); defineRoute({ - path: "/forgotpasswordpin.html", + path: '/dlnaprofiles.html', autoFocus: false, - anonymous: true, - startup: true, - controller: "auth/forgotpasswordpin" + roles: 'admin', + controller: 'dashboard/dlna/profiles' }); defineRoute({ - path: "/home.html", + path: '/addplugin.html', autoFocus: false, - controller: "home", - transition: "fade", - type: "home" + roles: 'admin', + controller: 'dashboard/plugins/add' }); defineRoute({ - path: "/list.html", + path: '/library.html', autoFocus: false, - controller: "list", - transition: "fade" + roles: 'admin', + controller: 'dashboard/mediaLibrary' }); defineRoute({ - path: "/index.html", + path: '/librarydisplay.html', autoFocus: false, - isDefaultRoute: true + roles: 'admin', + controller: 'dashboard/librarydisplay' }); defineRoute({ - path: "/itemdetails.html", - controller: "itemdetailpage", + path: '/dlnasettings.html', autoFocus: false, - transition: "fade" + roles: 'admin', + controller: 'dashboard/dlna/settings' }); defineRoute({ - path: "/library.html", - autoFocus: false, - roles: "admin", - controller: "medialibrarypage" + path: '/edititemmetadata.html', + controller: 'edititemmetadata', + autoFocus: false }); defineRoute({ - path: "/librarydisplay.html", + path: '/encodingsettings.html', autoFocus: false, - roles: "admin", - controller: "librarydisplay" + roles: 'admin', + controller: 'dashboard/encodingsettings' }); defineRoute({ - path: "/librarysettings.html", - autoFocus: false, - roles: "admin", - controller: "librarysettings" + path: '/log.html', + roles: 'admin', + controller: 'dashboard/logs' }); defineRoute({ - path: "/livetv.html", - controller: "livetv/livetvsuggested", + path: '/metadataimages.html', autoFocus: false, - transition: "fade" + roles: 'admin', + controller: 'dashboard/metadataImages' }); defineRoute({ - path: "/livetvguideprovider.html", + path: '/metadatanfo.html', autoFocus: false, - roles: "admin", - controller: "livetvguideprovider" + roles: 'admin', + controller: 'dashboard/metadatanfo' }); defineRoute({ - path: "/livetvsettings.html", + path: '/notificationsetting.html', autoFocus: false, - controller: "livetvsettings" + roles: 'admin', + controller: 'dashboard/notifications/notification' }); defineRoute({ - path: "/livetvstatus.html", + path: '/notificationsettings.html', + controller: 'dashboard/notifications/notifications', autoFocus: false, - roles: "admin", - controller: "livetvstatus" + roles: 'admin' }); defineRoute({ - path: "/livetvtuner.html", + path: '/playbackconfiguration.html', autoFocus: false, - roles: "admin", - controller: "livetvtuner" + roles: 'admin', + controller: 'dashboard/playback' }); defineRoute({ - path: "/log.html", - roles: "admin", - controller: "dashboard/logs" + path: '/availableplugins.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/plugins/available' }); defineRoute({ - path: "/login.html", + path: '/repositories.html', autoFocus: false, - anonymous: true, - startup: true, - controller: "auth/login", - type: "login" + roles: 'admin', + controller: 'dashboard/plugins/repositories' }); + defineRoute({ - path: "/metadataimages.html", + path: '/home.html', autoFocus: false, - roles: "admin", - controller: "metadataimagespage" + controller: 'home', + transition: 'fade', + type: 'home' }); defineRoute({ - path: "/metadatanfo.html", - autoFocus: false, - roles: "admin", - controller: "metadatanfo" + path: '/search.html', + controller: 'searchpage' }); defineRoute({ - path: "/movies.html", + path: '/list.html', autoFocus: false, - controller: "movies/moviesrecommended", - transition: "fade" + controller: 'list', + transition: 'fade' }); defineRoute({ - path: "/music.html", - controller: "music/musicrecommended", + alias: '/details', + path: '/controllers/itemDetails/index.html', + controller: 'itemDetails/index', autoFocus: false, - transition: "fade" + transition: 'fade' }); defineRoute({ - path: "/notificationsetting.html", + path: '/livetv.html', + controller: 'livetv/livetvsuggested', autoFocus: false, - roles: "admin", - controller: "dashboard/notifications/notification" + transition: 'fade' }); defineRoute({ - path: "/notificationsettings.html", - controller: "dashboard/notifications/notifications", + path: '/livetvguideprovider.html', autoFocus: false, - roles: "admin" + roles: 'admin', + controller: 'livetvguideprovider' }); defineRoute({ - path: "/nowplaying.html", - controller: "playback/nowplaying", + path: '/livetvsettings.html', autoFocus: false, - transition: "fade", - fullscreen: true, - supportsThemeMedia: true, - enableMediaControl: false + controller: 'livetvsettings' }); defineRoute({ - path: "/playbackconfiguration.html", + path: '/livetvstatus.html', autoFocus: false, - roles: "admin", - controller: "playbackconfiguration" + roles: 'admin', + controller: 'livetvstatus' }); defineRoute({ - path: "/availableplugins.html", + path: '/livetvtuner.html', autoFocus: false, - roles: "admin", - controller: "dashboard/plugins/available" + roles: 'admin', + controller: 'livetvtuner' }); defineRoute({ - path: "/installedplugins.html", + path: '/movies.html', autoFocus: false, - roles: "admin", - controller: "dashboard/plugins/installed" + controller: 'movies/moviesrecommended', + transition: 'fade' }); defineRoute({ - path: "/scheduledtask.html", + path: '/music.html', + controller: 'music/musicrecommended', autoFocus: false, - roles: "admin", - controller: "dashboard/scheduledtasks/scheduledtask" + transition: 'fade' }); defineRoute({ - path: "/scheduledtasks.html", + path: '/installedplugins.html', autoFocus: false, - roles: "admin", - controller: "dashboard/scheduledtasks/scheduledtasks" + roles: 'admin', + controller: 'dashboard/plugins/installed' }); defineRoute({ - path: "/search.html", - controller: "searchpage" + path: '/scheduledtask.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/scheduledtasks/scheduledtask' }); defineRoute({ - path: "/selectserver.html", + path: '/scheduledtasks.html', autoFocus: false, - anonymous: true, - startup: true, - controller: "auth/selectserver", - type: "selectserver" + roles: 'admin', + controller: 'dashboard/scheduledtasks/scheduledtasks' }); defineRoute({ - path: "/serveractivity.html", + path: '/serveractivity.html', autoFocus: false, - roles: "admin", - controller: "serveractivity" + roles: 'admin', + controller: 'dashboard/serveractivity' }); defineRoute({ - path: "/apikeys.html", + path: '/apikeys.html', autoFocus: false, - roles: "admin", - controller: "apikeys" + roles: 'admin', + controller: 'dashboard/apikeys' }); defineRoute({ - path: "/streamingsettings.html", + path: '/streamingsettings.html', autoFocus: false, - roles: "admin", - controller: "streamingsettings" + roles: 'admin', + controller: 'dashboard/streaming' }); defineRoute({ - path: "/tv.html", + path: '/tv.html', autoFocus: false, - controller: "shows/tvrecommended", - transition: "fade" + controller: 'shows/tvrecommended', + transition: 'fade' }); + defineRoute({ - path: "/useredit.html", + path: '/useredit.html', autoFocus: false, - roles: "admin", - controller: "useredit" + roles: 'admin', + controller: 'dashboard/users/useredit' }); defineRoute({ - path: "/userlibraryaccess.html", + path: '/userlibraryaccess.html', autoFocus: false, - roles: "admin", - controller: "userlibraryaccess" + roles: 'admin', + controller: 'dashboard/users/userlibraryaccess' }); defineRoute({ - path: "/usernew.html", + path: '/usernew.html', autoFocus: false, - roles: "admin", - controller: "usernew" + roles: 'admin', + controller: 'dashboard/users/usernew' }); defineRoute({ - path: "/userparentalcontrol.html", + path: '/userparentalcontrol.html', autoFocus: false, - roles: "admin", - controller: "userparentalcontrol" + roles: 'admin', + controller: 'dashboard/users/userparentalcontrol' }); defineRoute({ - path: "/userpassword.html", + path: '/userpassword.html', autoFocus: false, - controller: "userpasswordpage" + controller: 'dashboard/users/userpasswordpage' }); defineRoute({ - path: "/userprofiles.html", + path: '/userprofiles.html', autoFocus: false, - roles: "admin", - controller: "userprofilespage" + roles: 'admin', + controller: 'dashboard/users/userprofilespage' }); defineRoute({ - path: "/wizardremoteaccess.html", + path: '/wizardremoteaccess.html', autoFocus: false, anonymous: true, - controller: "wizard/remoteaccess" + controller: 'wizard/remoteaccess' }); defineRoute({ - path: "/wizardfinish.html", + path: '/wizardfinish.html', autoFocus: false, anonymous: true, - controller: "wizard/finish" + controller: 'wizard/finish' }); defineRoute({ - path: "/wizardlibrary.html", + path: '/wizardlibrary.html', autoFocus: false, anonymous: true, - controller: "medialibrarypage" + controller: 'dashboard/mediaLibrary' }); defineRoute({ - path: "/wizardsettings.html", + path: '/wizardsettings.html', autoFocus: false, anonymous: true, - controller: "wizard/settings" + controller: 'wizard/settings' }); defineRoute({ - path: "/wizardstart.html", + path: '/wizardstart.html', autoFocus: false, anonymous: true, - controller: "wizard/start" + controller: 'wizard/start' }); defineRoute({ - path: "/wizarduser.html", - controller: "wizard/user", + path: '/wizarduser.html', + controller: 'wizard/user', autoFocus: false, anonymous: true }); defineRoute({ - path: "/videoosd.html", - transition: "fade", - controller: "playback/videoosd", + path: '/videoosd.html', + transition: 'fade', + controller: 'playback/videoosd', autoFocus: false, - type: "video-osd", + type: 'video-osd', supportsThemeMedia: true, fullscreen: true, enableMediaControl: false }); defineRoute({ - path: "/configurationpage", + path: '/nowplaying.html', + controller: 'playback/nowplaying', + autoFocus: false, + transition: 'fade', + fullscreen: true, + supportsThemeMedia: true, + enableMediaControl: false + }); + defineRoute({ + path: '/configurationpage', autoFocus: false, enableCache: false, enableContentQueryString: true, - roles: "admin" + roles: 'admin' }); defineRoute({ - path: "/", + path: '/', isDefaultRoute: true, autoFocus: false }); + defineRoute({ + path: '/index.html', + autoFocus: false, + isDefaultRoute: true + }); }); diff --git a/src/components/scrollhelper.js b/src/scripts/scrollHelper.js similarity index 100% rename from src/components/scrollhelper.js rename to src/scripts/scrollHelper.js diff --git a/src/scripts/searchtab.js b/src/scripts/searchtab.js index c0852bfc771..95b1e5a7c34 100644 --- a/src/scripts/searchtab.js +++ b/src/scripts/searchtab.js @@ -1,18 +1,18 @@ -define(["searchFields", "searchResults", "events"], function (SearchFields, SearchResults, events) { - "use strict"; +define(['searchFields', 'searchResults', 'events'], function (SearchFields, SearchResults, events) { + 'use strict'; function init(instance, tabContent, options) { tabContent.innerHTML = '
'; instance.searchFields = new SearchFields({ - element: tabContent.querySelector(".searchFields") + element: tabContent.querySelector('.searchFields') }); instance.searchResults = new SearchResults({ - element: tabContent.querySelector(".searchResults"), + element: tabContent.querySelector('.searchResults'), serverId: ApiClient.serverId(), parentId: options.parentId, collectionType: options.collectionType }); - events.on(instance.searchFields, "search", function (e, value) { + events.on(instance.searchFields, 'search', function (e, value) { instance.searchResults.search(value); }); } diff --git a/src/components/serverNotifications.js b/src/scripts/serverNotifications.js similarity index 86% rename from src/components/serverNotifications.js rename to src/scripts/serverNotifications.js index 55549252723..cddd2cf7943 100644 --- a/src/components/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -1,4 +1,4 @@ -define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, events, inputManager, focusManager, appRouter) { +define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncPlayManager, events, inputManager, focusManager, appRouter) { 'use strict'; var serverNotifications = {}; @@ -65,6 +65,9 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus case 'SetRepeatMode': playbackManager.setRepeatMode(cmd.Arguments.RepeatMode); break; + case 'SetShuffleQueue': + playbackManager.setQueueShuffleMode(cmd.Arguments.ShuffleMode); + break; case 'VolumeUp': inputManager.trigger('volumeup'); return; @@ -142,12 +145,12 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus function onMessageReceived(e, msg) { var apiClient = this; - if (msg.MessageType === "Play") { + if (msg.MessageType === 'Play') { notifyApp(); var serverId = apiClient.serverInfo().Id; - if (msg.Data.PlayCommand === "PlayNext") { + if (msg.Data.PlayCommand === 'PlayNext') { playbackManager.queueNext({ ids: msg.Data.ItemIds, serverId: serverId }); - } else if (msg.Data.PlayCommand === "PlayLast") { + } else if (msg.Data.PlayCommand === 'PlayLast') { playbackManager.queue({ ids: msg.Data.ItemIds, serverId: serverId }); } else { playbackManager.play({ @@ -160,7 +163,7 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus serverId: serverId }); } - } else if (msg.MessageType === "Playstate") { + } else if (msg.MessageType === 'Playstate') { if (msg.Data.Command === 'Stop') { inputManager.trigger('stop'); } else if (msg.Data.Command === 'Pause') { @@ -178,22 +181,26 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus } else { notifyApp(); } - } else if (msg.MessageType === "GeneralCommand") { + } else if (msg.MessageType === 'GeneralCommand') { var cmd = msg.Data; processGeneralCommand(cmd, apiClient); - } else if (msg.MessageType === "UserDataChanged") { + } else if (msg.MessageType === 'UserDataChanged') { if (msg.Data.UserId === apiClient.getCurrentUserId()) { for (var i = 0, length = msg.Data.UserDataList.length; i < length; i++) { events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]); } } + } else if (msg.MessageType === 'SyncPlayCommand') { + syncPlayManager.processCommand(msg.Data, apiClient); + } else if (msg.MessageType === 'SyncPlayGroupUpdate') { + syncPlayManager.processGroupUpdate(msg.Data, apiClient); } else { events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]); } } function bindEvents(apiClient) { - events.off(apiClient, "message", onMessageReceived); - events.on(apiClient, "message", onMessageReceived); + events.off(apiClient, 'message', onMessageReceived); + events.on(apiClient, 'message', onMessageReceived); } connectionManager.getApiClients().forEach(bindEvents); diff --git a/src/scripts/settings/appSettings.js b/src/scripts/settings/appSettings.js index bd91786f870..6f0975e98c3 100644 --- a/src/scripts/settings/appSettings.js +++ b/src/scripts/settings/appSettings.js @@ -1,5 +1,7 @@ -define(['appStorage', 'events'], function (appStorage, events) { - 'use strict'; +/* eslint-disable indent */ + +import appStorage from 'appStorage'; +import events from 'events'; function getKey(name, userId) { if (userId) { @@ -9,20 +11,25 @@ define(['appStorage', 'events'], function (appStorage, events) { return name; } - function AppSettings() { - } - - AppSettings.prototype.enableAutoLogin = function (val) { - if (val != null) { + export function enableAutoLogin(val) { + if (val !== undefined) { this.set('enableAutoLogin', val.toString()); } return this.get('enableAutoLogin') !== 'false'; - }; + } + + export function enableSystemExternalPlayers(val) { + if (val !== undefined) { + this.set('enableSystemExternalPlayers', val.toString()); + } - AppSettings.prototype.enableAutomaticBitrateDetection = function (isInNetwork, mediaType, val) { + return this.get('enableSystemExternalPlayers') === 'true'; + } + + export function enableAutomaticBitrateDetection(isInNetwork, mediaType, val) { var key = 'enableautobitratebitrate-' + mediaType + '-' + isInNetwork; - if (val != null) { + if (val !== undefined) { if (isInNetwork && mediaType === 'Audio') { val = true; } @@ -35,11 +42,11 @@ define(['appStorage', 'events'], function (appStorage, events) { } else { return this.get(key) !== 'false'; } - }; + } - AppSettings.prototype.maxStreamingBitrate = function (isInNetwork, mediaType, val) { + export function maxStreamingBitrate(isInNetwork, mediaType, val) { var key = 'maxbitrate-' + mediaType + '-' + isInNetwork; - if (val != null) { + if (val !== undefined) { if (isInNetwork && mediaType === 'Audio') { // nothing to do, this is always a max value } else { @@ -53,44 +60,44 @@ define(['appStorage', 'events'], function (appStorage, events) { } else { return parseInt(this.get(key) || '0') || 1500000; } - }; + } - AppSettings.prototype.maxStaticMusicBitrate = function (val) { + export function maxStaticMusicBitrate(val) { if (val !== undefined) { this.set('maxStaticMusicBitrate', val); } var defaultValue = 320000; return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue; - }; + } - AppSettings.prototype.maxChromecastBitrate = function (val) { - if (val != null) { + export function maxChromecastBitrate(val) { + if (val !== undefined) { this.set('chromecastBitrate1', val); } val = this.get('chromecastBitrate1'); return val ? parseInt(val) : null; - }; + } - AppSettings.prototype.syncOnlyOnWifi = function (val) { - if (val != null) { + export function syncOnlyOnWifi(val) { + if (val !== undefined) { this.set('syncOnlyOnWifi', val.toString()); } return this.get('syncOnlyOnWifi') !== 'false'; - }; + } - AppSettings.prototype.syncPath = function (val) { - if (val != null) { + export function syncPath(val) { + if (val !== undefined) { this.set('syncPath', val); } return this.get('syncPath'); - }; + } - AppSettings.prototype.cameraUploadServers = function (val) { - if (val != null) { + export function cameraUploadServers(val) { + if (val !== undefined) { this.set('cameraUploadServers', val.join(',')); } @@ -100,36 +107,42 @@ define(['appStorage', 'events'], function (appStorage, events) { } return []; - }; + } - AppSettings.prototype.runAtStartup = function (val) { - if (val != null) { + export function runAtStartup(val) { + if (val !== undefined) { this.set('runatstartup', val.toString()); } return this.get('runatstartup') === 'true'; - }; + } - AppSettings.prototype.set = function (name, value, userId) { + export function set(name, value, userId) { var currentValue = this.get(name, userId); appStorage.setItem(getKey(name, userId), value); if (currentValue !== value) { events.trigger(this, 'change', [name]); } - }; + } - AppSettings.prototype.get = function (name, userId) { + export function get(name, userId) { return appStorage.getItem(getKey(name, userId)); - }; - - AppSettings.prototype.enableSystemExternalPlayers = function (val) { - if (val != null) { - this.set('enableSystemExternalPlayers', val.toString()); - } - - return this.get('enableSystemExternalPlayers') === 'true'; - }; + } - return new AppSettings(); -}); +/* eslint-enable indent */ + +export default { + enableAutoLogin: enableAutoLogin, + enableSystemExternalPlayers: enableSystemExternalPlayers, + enableAutomaticBitrateDetection: enableAutomaticBitrateDetection, + maxStreamingBitrate: maxStreamingBitrate, + maxStaticMusicBitrate: maxStaticMusicBitrate, + maxChromecastBitrate: maxChromecastBitrate, + syncOnlyOnWifi: syncOnlyOnWifi, + syncPath: syncPath, + cameraUploadServers: cameraUploadServers, + runAtStartup: runAtStartup, + set: set, + get: get +}; diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js new file mode 100644 index 00000000000..1b5283fa437 --- /dev/null +++ b/src/scripts/settings/userSettings.js @@ -0,0 +1,480 @@ +import appSettings from 'appSettings'; +import events from 'events'; + +function onSaveTimeout() { + var self = this; + self.saveTimeout = null; + self.currentApiClient.updateDisplayPreferences('usersettings', self.displayPrefs, self.currentUserId, 'emby'); +} + +function saveServerPreferences(instance) { + if (instance.saveTimeout) { + clearTimeout(instance.saveTimeout); + } + + instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50); +} + +export class UserSettings { + constructor() { + } + + /** + * Bind UserSettings instance to user. + * @param {string} - User identifier. + * @param {Object} - ApiClient instance. + */ + setUserInfo(userId, apiClient) { + if (this.saveTimeout) { + clearTimeout(this.saveTimeout); + } + + this.currentUserId = userId; + this.currentApiClient = apiClient; + + if (!userId) { + this.displayPrefs = null; + return Promise.resolve(); + } + + var self = this; + + return apiClient.getDisplayPreferences('usersettings', userId, 'emby').then(function (result) { + result.CustomPrefs = result.CustomPrefs || {}; + self.displayPrefs = result; + }); + } + + // FIXME: Seems unused + getData() { + return this.displayPrefs; + } + + // FIXME: Seems unused + importFrom(instance) { + this.displayPrefs = instance.getData(); + } + + // FIXME: 'appSettings.set' doesn't return any value + /** + * Set value of setting. + * @param {string} name - Name of setting. + * @param {mixed} value - Value of setting. + * @param {boolean} enableOnServer - Flag to save preferences on server. + */ + set(name, value, enableOnServer) { + var userId = this.currentUserId; + var currentValue = this.get(name, enableOnServer); + var result = appSettings.set(name, value, userId); + + if (enableOnServer !== false && this.displayPrefs) { + this.displayPrefs.CustomPrefs[name] = value == null ? value : value.toString(); + saveServerPreferences(this); + } + + if (currentValue !== value) { + events.trigger(this, 'change', [name]); + } + + return result; + } + + /** + * Get value of setting. + * @param {string} name - Name of setting. + * @param {boolean} enableOnServer - Flag to return preferences from server (cached). + * @return {string} Value of setting. + */ + get(name, enableOnServer) { + var userId = this.currentUserId; + if (enableOnServer !== false && this.displayPrefs) { + return this.displayPrefs.CustomPrefs[name]; + } + + return appSettings.get(name, userId); + } + + /** + * Get or set user config. + * @param {Object|undefined} config - Configuration or undefined. + * @return {Object|Promise} Configuration or Promise. + */ + serverConfig(config) { + var apiClient = this.currentApiClient; + if (config) { + return apiClient.updateUserConfiguration(this.currentUserId, config); + } + + return apiClient.getUser(this.currentUserId).then(function (user) { + return user.Configuration; + }); + } + + /** + * Get or set 'Cinema Mode' state. + * @param {boolean|undefined} val - Flag to enable 'Cinema Mode' or undefined. + * @return {boolean} 'Cinema Mode' state. + */ + enableCinemaMode(val) { + if (val !== undefined) { + return this.set('enableCinemaMode', val.toString(), false); + } + + val = this.get('enableCinemaMode', false); + return val !== 'false'; + } + + /** + * Get or set 'Next Video Info Overlay' state. + * @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined. + * @return {boolean} 'Next Video Info Overlay' state. + */ + enableNextVideoInfoOverlay(val) { + if (val !== undefined) { + return this.set('enableNextVideoInfoOverlay', val.toString()); + } + + val = this.get('enableNextVideoInfoOverlay', false); + return val !== 'false'; + } + + /** + * Get or set 'Theme Songs' state. + * @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined. + * @return {boolean} 'Theme Songs' state. + */ + enableThemeSongs(val) { + if (val !== undefined) { + return this.set('enableThemeSongs', val.toString(), false); + } + + val = this.get('enableThemeSongs', false); + return val === 'true'; + } + + /** + * Get or set 'Theme Videos' state. + * @param {boolean|undefined} val - Flag to enable 'Theme Videos' or undefined. + * @return {boolean} 'Theme Videos' state. + */ + enableThemeVideos(val) { + if (val !== undefined) { + return this.set('enableThemeVideos', val.toString(), false); + } + + val = this.get('enableThemeVideos', false); + return val === 'true'; + } + + /** + * Get or set 'Fast Fade-in' state. + * @param {boolean|undefined} val - Flag to enable 'Fast Fade-in' or undefined. + * @return {boolean} 'Fast Fade-in' state. + */ + enableFastFadein(val) { + if (val !== undefined) { + return this.set('fastFadein', val.toString(), false); + } + + val = this.get('fastFadein', false); + return val !== 'false'; + } + + /** + * Get or set 'Blurhash' state. + * @param {boolean|undefined} val - Flag to enable 'Blurhash' or undefined. + * @return {boolean} 'Blurhash' state. + */ + enableBlurhash(val) { + if (val !== undefined) { + return this.set('blurhash', val.toString(), false); + } + + val = this.get('blurhash', false); + return val !== 'false'; + } + + /** + * Get or set 'Backdrops' state. + * @param {boolean|undefined} val - Flag to enable 'Backdrops' or undefined. + * @return {boolean} 'Backdrops' state. + */ + enableBackdrops(val) { + if (val !== undefined) { + return this.set('enableBackdrops', val.toString(), false); + } + + val = this.get('enableBackdrops', false); + return val !== 'false'; + } + + /** + * Get or set 'Details Banner' state. + * @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined. + * @return {boolean} 'Details Banner' state. + */ + detailsBanner(val) { + if (val !== undefined) { + return this.set('detailsBanner', val.toString(), false); + } + + val = this.get('detailsBanner', false); + return val !== 'false'; + } + + /** + * Get or set language. + * @param {string|undefined} val - Language. + * @return {string} Language. + */ + language(val) { + if (val !== undefined) { + return this.set('language', val.toString(), false); + } + + return this.get('language', false); + } + + /** + * Get or set datetime locale. + * @param {string|undefined} val - Datetime locale. + * @return {string} Datetime locale. + */ + dateTimeLocale(val) { + if (val !== undefined) { + return this.set('datetimelocale', val.toString(), false); + } + + return this.get('datetimelocale', false); + } + + /** + * Get or set Chromecast version. + * @param {string|undefined} val - Chromecast version. + * @return {string} Chromecast version. + */ + chromecastVersion(val) { + if (val !== undefined) { + return this.set('chromecastVersion', val.toString()); + } + + return this.get('chromecastVersion') || 'stable'; + } + + /** + * Get or set amount of rewind. + * @param {number|undefined} val - Amount of rewind. + * @return {number} Amount of rewind. + */ + skipBackLength(val) { + if (val !== undefined) { + return this.set('skipBackLength', val.toString()); + } + + return parseInt(this.get('skipBackLength') || '10000'); + } + + /** + * Get or set amount of fast forward. + * @param {number|undefined} val - Amount of fast forward. + * @return {number} Amount of fast forward. + */ + skipForwardLength(val) { + if (val !== undefined) { + return this.set('skipForwardLength', val.toString()); + } + + return parseInt(this.get('skipForwardLength') || '30000'); + } + + /** + * Get or set theme for Dashboard. + * @param {string|undefined} val - Theme for Dashboard. + * @return {string} Theme for Dashboard. + */ + dashboardTheme(val) { + if (val !== undefined) { + return this.set('dashboardTheme', val); + } + + return this.get('dashboardTheme'); + } + + /** + * Get or set skin. + * @param {string|undefined} val - Skin. + * @return {string} Skin. + */ + skin(val) { + if (val !== undefined) { + return this.set('skin', val, false); + } + + return this.get('skin', false); + } + + /** + * Get or set main theme. + * @param {string|undefined} val - Main theme. + * @return {string} Main theme. + */ + theme(val) { + if (val !== undefined) { + return this.set('appTheme', val, false); + } + + return this.get('appTheme', false); + } + + /** + * Get or set screensaver. + * @param {string|undefined} val - Screensaver. + * @return {string} Screensaver. + */ + screensaver(val) { + if (val !== undefined) { + return this.set('screensaver', val, false); + } + + return this.get('screensaver', false); + } + + /** + * Get or set library page size. + * @param {number|undefined} val - Library page size. + * @return {number} Library page size. + */ + libraryPageSize(val) { + if (val !== undefined) { + return this.set('libraryPageSize', parseInt(val, 10), false); + } + + var libraryPageSize = parseInt(this.get('libraryPageSize', false), 10); + if (libraryPageSize === 0) { + // Explicitly return 0 to avoid returning 100 because 0 is falsy. + return 0; + } else { + return libraryPageSize || 100; + } + } + + /** + * Get or set sound effects. + * @param {string|undefined} val - Sound effects. + * @return {string} Sound effects. + */ + soundEffects(val) { + if (val !== undefined) { + return this.set('soundeffects', val, false); + } + + return this.get('soundeffects', false); + } + + /** + * Load query settings. + * @param {string} key - Query key. + * @param {Object} query - Query base. + * @return {Object} Query. + */ + loadQuerySettings(key, query) { + var values = this.get(key); + if (values) { + values = JSON.parse(values); + return Object.assign(query, values); + } + + return query; + } + + /** + * Save query settings. + * @param {string} key - Query key. + * @param {Object} query - Query. + */ + saveQuerySettings(key, query) { + var values = {}; + if (query.SortBy) { + values.SortBy = query.SortBy; + } + + if (query.SortOrder) { + values.SortOrder = query.SortOrder; + } + + return this.set(key, JSON.stringify(values)); + } + + /** + * Get subtitle appearance settings. + * @param {string|undefined} key - Settings key. + * @return {Object} Subtitle appearance settings. + */ + getSubtitleAppearanceSettings(key) { + key = key || 'localplayersubtitleappearance3'; + return JSON.parse(this.get(key, false) || '{}'); + } + + /** + * Set subtitle appearance settings. + * @param {Object} value - Subtitle appearance settings. + * @param {string|undefined} key - Settings key. + */ + setSubtitleAppearanceSettings(value, key) { + key = key || 'localplayersubtitleappearance3'; + return this.set(key, JSON.stringify(value), false); + } + + /** + * Set filter. + * @param {string} key - Filter key. + * @param {string} value - Filter value. + */ + setFilter(key, value) { + return this.set(key, value, true); + } + + /** + * Get filter. + * @param {string} key - Filter key. + * @return {string} Filter value. + */ + getFilter(key) { + return this.get(key, true); + } +} + +export const currentSettings = new UserSettings; + +// Wrappers for non-ES6 modules and backward compatibility +export const setUserInfo = currentSettings.setUserInfo.bind(currentSettings); +export const getData = currentSettings.getData.bind(currentSettings); +export const importFrom = currentSettings.importFrom.bind(currentSettings); +export const set = currentSettings.set.bind(currentSettings); +export const get = currentSettings.get.bind(currentSettings); +export const serverConfig = currentSettings.serverConfig.bind(currentSettings); +export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings); +export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings); +export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings); +export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings); +export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings); +export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings); +export const enableBackdrops = currentSettings.enableBackdrops.bind(currentSettings); +export const detailsBanner = currentSettings.detailsBanner.bind(currentSettings); +export const language = currentSettings.language.bind(currentSettings); +export const dateTimeLocale = currentSettings.dateTimeLocale.bind(currentSettings); +export const chromecastVersion = currentSettings.chromecastVersion.bind(currentSettings); +export const skipBackLength = currentSettings.skipBackLength.bind(currentSettings); +export const skipForwardLength = currentSettings.skipForwardLength.bind(currentSettings); +export const dashboardTheme = currentSettings.dashboardTheme.bind(currentSettings); +export const skin = currentSettings.skin.bind(currentSettings); +export const theme = currentSettings.theme.bind(currentSettings); +export const screensaver = currentSettings.screensaver.bind(currentSettings); +export const libraryPageSize = currentSettings.libraryPageSize.bind(currentSettings); +export const soundEffects = currentSettings.soundEffects.bind(currentSettings); +export const loadQuerySettings = currentSettings.loadQuerySettings.bind(currentSettings); +export const saveQuerySettings = currentSettings.saveQuerySettings.bind(currentSettings); +export const getSubtitleAppearanceSettings = currentSettings.getSubtitleAppearanceSettings.bind(currentSettings); +export const setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings); +export const setFilter = currentSettings.setFilter.bind(currentSettings); +export const getFilter = currentSettings.getFilter.bind(currentSettings); diff --git a/src/scripts/settings/userSettingsBuilder.js b/src/scripts/settings/userSettingsBuilder.js deleted file mode 100644 index 1a35434c6d3..00000000000 --- a/src/scripts/settings/userSettingsBuilder.js +++ /dev/null @@ -1,255 +0,0 @@ -define(['appSettings', 'events'], function (appSettings, events) { - 'use strict'; - - function onSaveTimeout() { - var self = this; - self.saveTimeout = null; - self.currentApiClient.updateDisplayPreferences('usersettings', self.displayPrefs, self.currentUserId, 'emby'); - } - - function saveServerPreferences(instance) { - if (instance.saveTimeout) { - clearTimeout(instance.saveTimeout); - } - - instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50); - } - - function UserSettings() { - } - - UserSettings.prototype.setUserInfo = function (userId, apiClient) { - if (this.saveTimeout) { - clearTimeout(this.saveTimeout); - } - - this.currentUserId = userId; - this.currentApiClient = apiClient; - - if (!userId) { - this.displayPrefs = null; - return Promise.resolve(); - } - - var self = this; - - return apiClient.getDisplayPreferences('usersettings', userId, 'emby').then(function (result) { - result.CustomPrefs = result.CustomPrefs || {}; - self.displayPrefs = result; - }); - }; - - UserSettings.prototype.getData = function () { - return this.displayPrefs; - }; - - UserSettings.prototype.importFrom = function (instance) { - this.displayPrefs = instance.getData(); - }; - - UserSettings.prototype.set = function (name, value, enableOnServer) { - var userId = this.currentUserId; - var currentValue = this.get(name, enableOnServer); - var result = appSettings.set(name, value, userId); - - if (enableOnServer !== false && this.displayPrefs) { - this.displayPrefs.CustomPrefs[name] = value == null ? value : value.toString(); - saveServerPreferences(this); - } - - if (currentValue !== value) { - events.trigger(this, 'change', [name]); - } - - return result; - }; - - UserSettings.prototype.get = function (name, enableOnServer) { - var userId = this.currentUserId; - if (enableOnServer !== false && this.displayPrefs) { - return this.displayPrefs.CustomPrefs[name]; - } - - return appSettings.get(name, userId); - }; - - UserSettings.prototype.serverConfig = function (config) { - var apiClient = this.currentApiClient; - if (config) { - return apiClient.updateUserConfiguration(this.currentUserId, config); - } - - return apiClient.getUser(this.currentUserId).then(function (user) { - return user.Configuration; - }); - }; - - UserSettings.prototype.enableCinemaMode = function (val) { - if (val != null) { - return this.set('enableCinemaMode', val.toString(), false); - } - - val = this.get('enableCinemaMode', false); - return val !== 'false'; - }; - - UserSettings.prototype.enableNextVideoInfoOverlay = function (val) { - if (val != null) { - return this.set('enableNextVideoInfoOverlay', val.toString()); - } - - val = this.get('enableNextVideoInfoOverlay', false); - return val !== 'false'; - }; - - UserSettings.prototype.enableThemeSongs = function (val) { - if (val != null) { - return this.set('enableThemeSongs', val.toString(), false); - } - - val = this.get('enableThemeSongs', false); - return val !== 'false'; - }; - - UserSettings.prototype.enableThemeVideos = function (val) { - if (val != null) { - return this.set('enableThemeVideos', val.toString(), false); - } - - val = this.get('enableThemeVideos', false); - return val !== 'false'; - }; - - UserSettings.prototype.enableFastFadein = function (val) { - if (val != null) { - return this.set('fastFadein', val.toString(), false); - } - - val = this.get('fastFadein', false); - return val !== 'false'; - }; - - UserSettings.prototype.enableBackdrops = function (val) { - if (val != null) { - return this.set('enableBackdrops', val.toString(), false); - } - - val = this.get('enableBackdrops', false); - return val !== 'false'; - }; - - UserSettings.prototype.language = function (val) { - if (val != null) { - return this.set('language', val.toString(), false); - } - - return this.get('language', false); - }; - - UserSettings.prototype.dateTimeLocale = function (val) { - if (val != null) { - return this.set('datetimelocale', val.toString(), false); - } - - return this.get('datetimelocale', false); - }; - - UserSettings.prototype.skipBackLength = function (val) { - if (val != null) { - return this.set('skipBackLength', val.toString()); - } - - return parseInt(this.get('skipBackLength') || '10000'); - }; - - UserSettings.prototype.skipForwardLength = function (val) { - if (val != null) { - return this.set('skipForwardLength', val.toString()); - } - - return parseInt(this.get('skipForwardLength') || '30000'); - }; - - UserSettings.prototype.dashboardTheme = function (val) { - if (val != null) { - return this.set('dashboardTheme', val); - } - - return this.get('dashboardTheme'); - }; - - UserSettings.prototype.skin = function (val) { - if (val != null) { - return this.set('skin', val, false); - } - - return this.get('skin', false); - }; - - UserSettings.prototype.theme = function (val) { - if (val != null) { - return this.set('appTheme', val, false); - } - - return this.get('appTheme', false); - }; - - UserSettings.prototype.screensaver = function (val) { - if (val != null) { - return this.set('screensaver', val, false); - } - - return this.get('screensaver', false); - }; - - UserSettings.prototype.soundEffects = function (val) { - if (val != null) { - return this.set('soundeffects', val, false); - } - - return this.get('soundeffects', false); - }; - - UserSettings.prototype.loadQuerySettings = function (key, query) { - var values = this.get(key); - if (values) { - values = JSON.parse(values); - return Object.assign(query, values); - } - - return query; - }; - - UserSettings.prototype.saveQuerySettings = function (key, query) { - var values = {}; - if (query.SortBy) { - values.SortBy = query.SortBy; - } - - if (query.SortOrder) { - values.SortOrder = query.SortOrder; - } - - return this.set(key, JSON.stringify(values)); - }; - - UserSettings.prototype.getSubtitleAppearanceSettings = function (key) { - key = key || 'localplayersubtitleappearance3'; - return JSON.parse(this.get(key, false) || '{}'); - }; - - UserSettings.prototype.setSubtitleAppearanceSettings = function (value, key) { - key = key || 'localplayersubtitleappearance3'; - return this.set(key, JSON.stringify(value), false); - }; - - UserSettings.prototype.setFilter = function (key, value) { - return this.set(key, value, true); - }; - - UserSettings.prototype.getFilter = function (key) { - return this.get(key, true); - }; - - return UserSettings; -}); diff --git a/src/scripts/settings/webSettings.js b/src/scripts/settings/webSettings.js new file mode 100644 index 00000000000..64989b4fc2a --- /dev/null +++ b/src/scripts/settings/webSettings.js @@ -0,0 +1,28 @@ +let data; + +function getConfig() { + if (data) return Promise.resolve(data); + return fetch('config.json?nocache=' + new Date().getUTCMilliseconds()).then(response => { + data = response.json(); + return data; + }).catch(error => { + console.warn('web config file is missing so the template will be used'); + return getDefaultConfig(); + }); +} + +function getDefaultConfig() { + return fetch('config.template.json').then(function (response) { + data = response.json(); + return data; + }); +} + +export function enableMultiServer() { + return getConfig().then(config => { + return config.multiserver; + }).catch(error => { + console.log('cannot get web config:', error); + return false; + }); +} diff --git a/src/components/shell.js b/src/scripts/shell.js similarity index 100% rename from src/components/shell.js rename to src/scripts/shell.js diff --git a/src/scripts/site.js b/src/scripts/site.js index 4e208dbe0f1..abec68e024c 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -1,36 +1,36 @@ function getWindowLocationSearch(win) { - "use strict"; + 'use strict'; var search = (win || window).location.search; if (!search) { - var index = window.location.href.indexOf("?"); + var index = window.location.href.indexOf('?'); if (-1 != index) { search = window.location.href.substring(index); } } - return search || ""; + return search || ''; } function getParameterByName(name, url) { - "use strict"; + 'use strict'; - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regexS = "[\\?&]" + name + "=([^&#]*)"; - var regex = new RegExp(regexS, "i"); + name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS, 'i'); var results = regex.exec(url || getWindowLocationSearch()); if (null == results) { - return ""; + return ''; } - return decodeURIComponent(results[1].replace(/\+/g, " ")); + return decodeURIComponent(results[1].replace(/\+/g, ' ')); } function pageClassOn(eventName, className, fn) { - "use strict"; + 'use strict'; document.addEventListener(eventName, function (event) { var target = event.target; @@ -42,7 +42,7 @@ function pageClassOn(eventName, className, fn) { } function pageIdOn(eventName, id, fn) { - "use strict"; + 'use strict'; document.addEventListener(eventName, function (event) { var target = event.target; @@ -71,17 +71,17 @@ var Dashboard = { } var urlLower = window.location.href.toLowerCase(); - var index = urlLower.lastIndexOf("/web"); + var index = urlLower.lastIndexOf('/web'); if (-1 != index) { return urlLower.substring(0, index); } var loc = window.location; - var address = loc.protocol + "//" + loc.hostname; + var address = loc.protocol + '//' + loc.hostname; if (loc.port) { - address += ":" + loc.port; + address += ':' + loc.port; } return address; @@ -104,21 +104,21 @@ var Dashboard = { var loginPage; if (AppInfo.isNativeApp) { - loginPage = "selectserver.html"; + loginPage = 'selectserver.html'; window.ApiClient = null; } else { - loginPage = "login.html"; + loginPage = 'login.html'; } Dashboard.navigate(loginPage); }); }, getConfigurationPageUrl: function (name) { - return "configurationpage?name=" + encodeURIComponent(name); + return 'configurationpage?name=' + encodeURIComponent(name); }, getConfigurationResourceUrl: function (name) { if (AppInfo.isNativeApp) { - return ApiClient.getUrl("web/ConfigurationPage", { + return ApiClient.getUrl('web/ConfigurationPage', { name: name }); } @@ -127,7 +127,7 @@ var Dashboard = { }, navigate: function (url, preserveQueryString) { if (!url) { - throw new Error("url cannot be null or empty"); + throw new Error('url cannot be null or empty'); } var queryString = getWindowLocationSearch(); @@ -137,36 +137,36 @@ var Dashboard = { } return new Promise(function (resolve, reject) { - require(["appRouter"], function (appRouter) { + require(['appRouter'], function (appRouter) { return appRouter.show(url).then(resolve, reject); }); }); }, navigate_direct: function (path) { return new Promise(function (resolve, reject) { - require(["appRouter"], function (appRouter) { + require(['appRouter'], function (appRouter) { return appRouter.showDirect(path).then(resolve, reject); }); }); }, processPluginConfigurationUpdateResult: function () { - require(["loading", "toast"], function (loading, toast) { + require(['loading', 'toast'], function (loading, toast) { loading.hide(); - toast(Globalize.translate("MessageSettingsSaved")); + toast(Globalize.translate('MessageSettingsSaved')); }); }, processServerConfigurationUpdateResult: function (result) { - require(["loading", "toast"], function (loading, toast) { + require(['loading', 'toast'], function (loading, toast) { loading.hide(); - toast(Globalize.translate("MessageSettingsSaved")); + toast(Globalize.translate('MessageSettingsSaved')); }); }, processErrorResponse: function (response) { - require(["loading"], function (loading) { + require(['loading'], function (loading) { loading.hide(); }); - var status = "" + response.status; + var status = '' + response.status; if (response.statusText) { status = response.statusText; @@ -174,71 +174,74 @@ var Dashboard = { Dashboard.alert({ title: status, - message: response.headers ? response.headers.get("X-Application-Error-Code") : null + message: response.headers ? response.headers.get('X-Application-Error-Code') : null }); }, alert: function (options) { - if ("string" == typeof options) { - return void require(["toast"], function (toast) { + if ('string' == typeof options) { + return void require(['toast'], function (toast) { toast({ text: options }); }); } - require(["alert"], function (alert) { + require(['alert'], function (alert) { alert({ - title: options.title || Globalize.translate("HeaderAlert"), + title: options.title || Globalize.translate('HeaderAlert'), text: options.message }).then(options.callback || function () {}); }); }, - restartServer: function () { - var apiClient = window.ApiClient; - - if (apiClient) { - require(["serverRestartDialog", "events"], function (ServerRestartDialog, events) { - var dialog = new ServerRestartDialog({ - apiClient: apiClient - }); - events.on(dialog, "restarted", function () { - if (AppInfo.isNativeApp) { - apiClient.ensureWebSocket(); - } else { - window.location.reload(true); - } - }); - dialog.show(); - }); - } - }, capabilities: function (appHost) { var capabilities = { - PlayableMediaTypes: ["Audio", "Video"], - SupportedCommands: ["MoveUp", "MoveDown", "MoveLeft", "MoveRight", "PageUp", "PageDown", "PreviousLetter", "NextLetter", "ToggleOsd", "ToggleContextMenu", "Select", "Back", "SendKey", "SendString", "GoHome", "GoToSettings", "VolumeUp", "VolumeDown", "Mute", "Unmute", "ToggleMute", "SetVolume", "SetAudioStreamIndex", "SetSubtitleStreamIndex", "DisplayContent", "GoToSearch", "DisplayMessage", "SetRepeatMode", "ChannelUp", "ChannelDown", "PlayMediaSource", "PlayTrailers"], - SupportsPersistentIdentifier: "cordova" === self.appMode || "android" === self.appMode, + PlayableMediaTypes: ['Audio', 'Video'], + SupportedCommands: ['MoveUp', 'MoveDown', 'MoveLeft', 'MoveRight', 'PageUp', 'PageDown', 'PreviousLetter', 'NextLetter', 'ToggleOsd', 'ToggleContextMenu', 'Select', 'Back', 'SendKey', 'SendString', 'GoHome', 'GoToSettings', 'VolumeUp', 'VolumeDown', 'Mute', 'Unmute', 'ToggleMute', 'SetVolume', 'SetAudioStreamIndex', 'SetSubtitleStreamIndex', 'DisplayContent', 'GoToSearch', 'DisplayMessage', 'SetRepeatMode', 'SetShuffleQueue', 'ChannelUp', 'ChannelDown', 'PlayMediaSource', 'PlayTrailers'], + SupportsPersistentIdentifier: 'cordova' === self.appMode || 'android' === self.appMode, SupportsMediaControl: true }; appHost.getPushTokenInfo(); return capabilities = Object.assign(capabilities, appHost.getPushTokenInfo()); }, selectServer: function () { - if (window.NativeShell && typeof window.NativeShell.selectServer === "function") { + if (window.NativeShell && typeof window.NativeShell.selectServer === 'function') { window.NativeShell.selectServer(); } else { - Dashboard.navigate("selectserver.html"); + Dashboard.navigate('selectserver.html'); } + }, + hideLoadingMsg: function() { + 'use strict'; + require(['loading'], function(loading) { + loading.hide(); + }); + }, + showLoadingMsg: function() { + 'use strict'; + require(['loading'], function(loading) { + loading.show(); + }); + }, + confirm: function(message, title, callback) { + 'use strict'; + require(['confirm'], function(confirm) { + confirm(message, title).then(function() { + callback(!0); + }).catch(function() { + callback(!1); + }); + }); } }; var AppInfo = {}; !function () { - "use strict"; + 'use strict'; function defineConnectionManager(connectionManager) { window.ConnectionManager = connectionManager; - define("connectionManager", [], function () { + define('connectionManager', [], function () { return connectionManager; }); } @@ -264,13 +267,13 @@ var AppInfo = {}; return userSettings.setUserInfo(user.Id, localApiClient); }; - events.on(connectionManager, "localusersignedout", function () { + events.on(connectionManager, 'localusersignedout', function () { userSettings.setUserInfo(null, null); }); } function createConnectionManager() { - return require(["connectionManagerFactory", "apphost", "credentialprovider", "events", "userSettings"], function (ConnectionManager, apphost, credentialProvider, events, userSettings) { + return require(['connectionManagerFactory', 'apphost', 'credentialprovider', 'events', 'userSettings'], function (ConnectionManager, apphost, credentialProvider, events, userSettings) { var credentialProviderInstance = new credentialProvider(); var promises = [apphost.getSyncProfile(), apphost.init()]; @@ -286,10 +289,10 @@ var AppInfo = {}; bindConnectionManagerEvents(connectionManager, events, userSettings); if (!AppInfo.isNativeApp) { - console.debug("loading ApiClient singleton"); + console.debug('loading ApiClient singleton'); - return require(["apiclient"], function (apiClientFactory) { - console.debug("creating ApiClient singleton"); + return require(['apiclient'], function (apiClientFactory) { + console.debug('creating ApiClient singleton'); var apiClient = new apiClientFactory(Dashboard.serverAddress(), apphost.appName(), apphost.appVersion(), apphost.deviceName(), apphost.deviceId()); @@ -301,7 +304,7 @@ var AppInfo = {}; window.ApiClient = apiClient; localApiClient = apiClient; - console.debug("loaded ApiClient singleton"); + console.debug('loaded ApiClient singleton'); }); } @@ -314,28 +317,35 @@ var AppInfo = {}; return obj; } + function returnDefault(obj) { + if (obj.default === null) { + throw new Error('Object has no default!'); + } + return obj.default; + } + function getBowerPath() { - return "libraries"; + return 'libraries'; } function getComponentsPath() { - return "components"; + return 'components'; } function getElementsPath() { - return "elements" + return 'elements'; } function getScriptsPath() { - return "scripts" + return 'scripts'; } function getPlaybackManager(playbackManager) { - window.addEventListener("beforeunload", function () { + window.addEventListener('beforeunload', function () { try { playbackManager.onAppClose(); } catch (err) { - console.error("error in onAppClose: " + err); + console.error('error in onAppClose: ' + err); } }); return playbackManager; @@ -350,145 +360,117 @@ var AppInfo = {}; return layoutManager; } - function createWindowHeadroom(Headroom) { - var headroom = new Headroom([], {}); - return headroom; - } - function createSharedAppFooter(appFooter) { return new appFooter({}); } function onRequireJsError(requireType, requireModules) { - console.error("RequireJS error: " + (requireType || "unknown") + ". Failed modules: " + (requireModules || []).join(",")); + console.error('RequireJS error: ' + (requireType || 'unknown') + '. Failed modules: ' + (requireModules || []).join(',')); } function defineResizeObserver() { if (self.ResizeObserver) { - define("ResizeObserver", [], function () { + define('ResizeObserver', [], function () { return self.ResizeObserver; }); } else { - define("ResizeObserver", ["resize-observer-polyfill"], returnFirstDependency); + define('ResizeObserver', ['resize-observer-polyfill'], returnFirstDependency); } } - function initRequireWithBrowser(browser) { - var bowerPath = getBowerPath(); + function initRequireWithBrowser() { var componentsPath = getComponentsPath(); + var scriptsPath = getScriptsPath(); - define("filesystem", [componentsPath + "/filesystem"], returnFirstDependency); - - if (window.IntersectionObserver && !browser.edge) { - define("lazyLoader", [componentsPath + "/lazyloader/lazyloader-intersectionobserver"], returnFirstDependency); - } else { - define("lazyLoader", [componentsPath + "/lazyloader/lazyloader-scroll"], returnFirstDependency); - } - - define("shell", [componentsPath + "/shell"], returnFirstDependency); - - define("apiclient", [bowerPath + "/apiclient/apiclient"], returnFirstDependency); - - if ("registerElement" in document) { - define("registerElement", []); - } else if (browser.msie) { - define("registerElement", ["webcomponents"], returnFirstDependency); - } else { - define("registerElement", ["document-register-element"], returnFirstDependency); - } - - define("imageFetcher", [componentsPath + "/images/imageFetcher"], returnFirstDependency); + define('filesystem', [scriptsPath + '/filesystem'], returnFirstDependency); - var preferNativeAlerts = browser.tv; + define('lazyLoader', [componentsPath + '/lazyLoader/lazyLoaderIntersectionObserver'], returnFirstDependency); + define('shell', [scriptsPath + '/shell'], returnFirstDependency); - define("alert", [componentsPath + "/alert"], returnFirstDependency); + define('alert', [componentsPath + '/alert'], returnFirstDependency); defineResizeObserver(); - define("dialog", [componentsPath + "/dialog/dialog"], returnFirstDependency); - define("confirm", [componentsPath + "/confirm/confirm"], returnFirstDependency); + define('dialog', [componentsPath + '/dialog/dialog'], returnFirstDependency); - define("prompt", [componentsPath + "/prompt/prompt"], returnFirstDependency); + define('confirm', [componentsPath + '/confirm/confirm'], returnFirstDependency); - define("loading", [componentsPath + "/loading/loading"], returnFirstDependency); - define("multi-download", [componentsPath + "/multidownload"], returnFirstDependency); - define("fileDownloader", [componentsPath + "/filedownloader"], returnFirstDependency); - define("localassetmanager", [bowerPath + "/apiclient/localassetmanager"], returnFirstDependency); + define('prompt', [componentsPath + '/prompt/prompt'], returnFirstDependency); - define("castSenderApiLoader", [componentsPath + "/castSenderApi"], returnFirstDependency); + define('loading', [componentsPath + '/loading/loading'], returnFirstDependency); + define('multi-download', [scriptsPath + '/multiDownload'], returnFirstDependency); + define('fileDownloader', [scriptsPath + '/fileDownloader'], returnFirstDependency); - define("transfermanager", [bowerPath + "/apiclient/sync/transfermanager"], returnFirstDependency); - define("filerepository", [bowerPath + "/apiclient/sync/filerepository"], returnFirstDependency); - define("localsync", [bowerPath + "/apiclient/sync/localsync"], returnFirstDependency); + define('castSenderApiLoader', [componentsPath + '/castSenderApi'], returnFirstDependency); } function init() { - define("livetvcss", ["css!assets/css/livetv.css"], returnFirstDependency); - define("detailtablecss", ["css!assets/css/detailtable.css"], returnFirstDependency); + define('livetvcss', ['css!assets/css/livetv.css'], returnFirstDependency); + define('detailtablecss', ['css!assets/css/detailtable.css'], returnFirstDependency); var promises = []; if (!window.fetch) { - promises.push(require(["fetch"])); - } - if ("function" != typeof Object.assign) { - promises.push(require(["objectassign"])); + promises.push(require(['fetch'])); } Promise.all(promises).then(function () { createConnectionManager().then(function () { - console.debug("initAfterDependencies promises resolved"); + console.debug('initAfterDependencies promises resolved'); - require(["globalize", "browser"], function (globalize, browser) { + require(['globalize', 'browser'], function (globalize, browser) { window.Globalize = globalize; loadCoreDictionary(globalize).then(function () { onGlobalizeInit(browser); }); }); - require(["keyboardnavigation"], function(keyboardnavigation) { + require(['keyboardnavigation'], function(keyboardnavigation) { keyboardnavigation.enable(); }); - require(["mouseManager"]); - require(["focusPreventScroll"]); - require(["autoFocuser"], function(autoFocuser) { + require(['mouseManager']); + require(['focusPreventScroll']); + require(['autoFocuser'], function(autoFocuser) { autoFocuser.enable(); }); + require(['globalize', 'connectionManager', 'events'], function (globalize, connectionManager, events) { + events.on(connectionManager, 'localusersignedin', globalize.updateCurrentCulture); + }); }); }); } function loadCoreDictionary(globalize) { - var languages = ["ar", "be-by", "bg-bg", "ca", "cs", "da", "de", "el", "en-gb", "en-us", "es", "es-ar", "es-mx", "fa", "fi", "fr", "fr-ca", "gsw", "he", "hi-in", "hr", "hu", "id", "it", "kk", "ko", "lt-lt", "ms", "nb", "nl", "pl", "pt-br", "pt-pt", "ro", "ru", "sk", "sl-si", "sv", "tr", "uk", "vi", "zh-cn", "zh-hk", "zh-tw"]; + var languages = ['ar', 'be-by', 'bg-bg', 'ca', 'cs', 'da', 'de', 'el', 'en-gb', 'en-us', 'es', 'es-ar', 'es-mx', 'fa', 'fi', 'fr', 'fr-ca', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'kk', 'ko', 'lt-lt', 'ms', 'nb', 'nl', 'pl', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sv', 'tr', 'uk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw']; var translations = languages.map(function (language) { return { lang: language, - path: "strings/" + language + ".json" + path: 'strings/' + language + '.json' }; }); - globalize.defaultModule("core"); + globalize.defaultModule('core'); return globalize.loadStrings({ - name: "core", + name: 'core', translations: translations }); } function onGlobalizeInit(browser) { - if ("android" === self.appMode) { - if (-1 !== self.location.href.toString().toLowerCase().indexOf("start=backgroundsync")) { + if ('android' === self.appMode) { + if (-1 !== self.location.href.toString().toLowerCase().indexOf('start=backgroundsync')) { return onAppReady(browser); } } - document.title = Globalize.translateDocument(document.title, "core"); + document.title = Globalize.translateDocument(document.title, 'core'); if (browser.tv && !browser.android) { - console.debug("using system fonts with explicit sizes"); - require(["systemFontsSizedCss"]); + console.debug('using system fonts with explicit sizes'); + require(['systemFontsSizedCss']); } else { - console.debug("using default fonts"); - require(["systemFontsCss"]); + console.debug('using default fonts'); + require(['systemFontsCss']); } - require(["apphost", "css!assets/css/librarybrowser"], function (appHost) { + require(['apphost', 'css!assets/css/librarybrowser'], function (appHost) { loadPlugins(appHost, browser).then(function () { onAppReady(browser); }); @@ -496,23 +478,24 @@ var AppInfo = {}; } function loadPlugins(appHost, browser, shell) { - console.debug("loading installed plugins"); + console.debug('loading installed plugins'); var list = [ - "components/playback/playaccessvalidation", - "components/playback/experimentalwarnings", - "components/htmlaudioplayer/plugin", - "components/htmlvideoplayer/plugin", - "components/photoplayer/plugin", - "components/youtubeplayer/plugin", - "components/backdropscreensaver/plugin", - "components/logoscreensaver/plugin" + 'plugins/playAccessValidation/plugin', + 'plugins/experimentalWarnings/plugin', + 'plugins/htmlAudioPlayer/plugin', + 'plugins/htmlVideoPlayer/plugin', + 'plugins/photoPlayer/plugin', + 'plugins/bookPlayer/plugin', + 'plugins/youtubePlayer/plugin', + 'plugins/backdropScreensaver/plugin', + 'plugins/logoScreensaver/plugin' ]; - if (appHost.supports("remotecontrol")) { - list.push("components/sessionplayer"); + if (appHost.supports('remotecontrol')) { + list.push('plugins/sessionPlayer/plugin'); if (browser.chrome || browser.opera) { - list.push("components/chromecast/chromecastplayer"); + list.push('plugins/chromecastPlayer/plugin'); } } @@ -522,7 +505,7 @@ var AppInfo = {}; return new Promise(function (resolve, reject) { Promise.all(list.map(loadPlugin)).then(function () { - require(["packageManager"], function (packageManager) { + require(['packageManager'], function (packageManager) { packageManager.init().then(resolve, reject); }); }, reject); @@ -531,64 +514,69 @@ var AppInfo = {}; function loadPlugin(url) { return new Promise(function (resolve, reject) { - require(["pluginManager"], function (pluginManager) { + require(['pluginManager'], function (pluginManager) { pluginManager.loadPlugin(url).then(resolve, reject); }); }); } function onAppReady(browser) { - console.debug("begin onAppReady"); + console.debug('begin onAppReady'); // ensure that appHost is loaded in this point require(['apphost', 'appRouter'], function (appHost, appRouter) { window.Emby = {}; - console.debug("onAppReady: loading dependencies"); + console.debug('onAppReady: loading dependencies'); if (browser.iOS) { require(['css!assets/css/ios.css']); } window.Emby.Page = appRouter; - require(['emby-button', 'scripts/themeloader', 'libraryMenu', 'scripts/routes'], function () { + require(['emby-button', 'scripts/themeLoader', 'libraryMenu', 'scripts/routes'], function () { Emby.Page.start({ click: false, hashbang: true }); - require(["components/thememediaplayer", "scripts/autobackdrops"]); + require(['components/themeMediaPlayer', 'scripts/autoBackdrops']); if (!browser.tv && !browser.xboxOne && !browser.ps4) { - require(["components/nowplayingbar/nowplayingbar"]); + require(['components/nowPlayingBar/nowPlayingBar']); } - if (appHost.supports("remotecontrol")) { - require(["playerSelectionMenu", "components/playback/remotecontrolautoplay"]); + if (appHost.supports('remotecontrol')) { + require(['playerSelectionMenu', 'components/playback/remotecontrolautoplay']); } - require(["components/screensavermanager"]); + require(['libraries/screensavermanager']); - if (!appHost.supports("physicalvolumecontrol") || browser.touch) { - require(["components/playback/volumeosd"]); + if (!appHost.supports('physicalvolumecontrol') || browser.touch) { + require(['components/playback/volumeosd']); } - require(["mediaSession", "serverNotifications"]); + /* eslint-disable-next-line compat/compat */ + if (navigator.mediaSession || window.NativeShell) { + require(['mediaSession']); + } + require(['serverNotifications']); + require(['date-fns', 'date-fns/locale']); if (!browser.tv && !browser.xboxOne) { - require(["components/playback/playbackorientation"]); + require(['components/playback/playbackorientation']); registerServiceWorker(); if (window.Notification) { - require(["components/notifications/notifications"]); + require(['components/notifications/notifications']); } } - require(["playerSelectionMenu", "fullscreenManager"]); + require(['playerSelectionMenu']); var apiClient = window.ConnectionManager && window.ConnectionManager.currentApiClient(); if (apiClient) { - fetch(apiClient.getUrl("Branding/Css")) + fetch(apiClient.getUrl('Branding/Css')) .then(function(response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); @@ -611,17 +599,21 @@ var AppInfo = {}; } function registerServiceWorker() { - if (navigator.serviceWorker && "cordova" !== self.appMode && "android" !== self.appMode) { + /* eslint-disable compat/compat */ + if (navigator.serviceWorker && self.appMode !== 'cordova' && self.appMode !== 'android') { try { - navigator.serviceWorker.register("serviceworker.js"); + navigator.serviceWorker.register('serviceworker.js'); } catch (err) { - console.error("error registering serviceWorker: " + err); + console.error('error registering serviceWorker: ' + err); } + } else { + console.warn('serviceWorker unsupported'); } + /* eslint-enable compat/compat */ } - function onWebComponentsReady(browser) { - initRequireWithBrowser(browser); + function onWebComponentsReady() { + initRequireWithBrowser(); if (self.appMode === 'cordova' || self.appMode === 'android' || self.appMode === 'standalone') { AppInfo.isNativeApp = true; @@ -633,7 +625,7 @@ var AppInfo = {}; var localApiClient; (function () { - var urlArgs = "v=" + (window.dashboardVersion || new Date().getDate()); + var urlArgs = 'v=' + (window.dashboardVersion || new Date().getDate()); var bowerPath = getBowerPath(); var componentsPath = getComponentsPath(); @@ -641,60 +633,72 @@ var AppInfo = {}; var scriptsPath = getScriptsPath(); var paths = { - browserdeviceprofile: "scripts/browserdeviceprofile", - browser: "scripts/browser", - libraryBrowser: "scripts/librarybrowser", - inputManager: "scripts/inputManager", - datetime: "scripts/datetime", - globalize: "scripts/globalize", - libraryMenu: "scripts/librarymenu", - playlisteditor: componentsPath + "/playlisteditor/playlisteditor", - medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator", - medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor", - imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor", - humanedate: componentsPath + "/humanedate", - apphost: componentsPath + "/apphost", - visibleinviewport: componentsPath + "/visibleinviewport", - qualityoptions: componentsPath + "/qualityoptions", - focusManager: componentsPath + "/focusManager", - itemHelper: componentsPath + "/itemhelper", - itemShortcuts: componentsPath + "/shortcuts", - playQueueManager: componentsPath + "/playback/playqueuemanager", - nowPlayingHelper: componentsPath + "/playback/nowplayinghelper", - pluginManager: componentsPath + "/pluginManager", - packageManager: componentsPath + "/packagemanager", - screensaverManager: componentsPath + "/screensavermanager" + browserdeviceprofile: 'scripts/browserDeviceProfile', + browser: 'scripts/browser', + libraryBrowser: 'scripts/libraryBrowser', + inputManager: 'scripts/inputManager', + datetime: 'scripts/datetime', + globalize: 'scripts/globalize', + dfnshelper: 'scripts/dfnshelper', + libraryMenu: 'scripts/libraryMenu', + playlisteditor: componentsPath + '/playlisteditor/playlisteditor', + medialibrarycreator: componentsPath + '/mediaLibraryCreator/mediaLibraryCreator', + medialibraryeditor: componentsPath + '/mediaLibraryEditor/mediaLibraryEditor', + imageoptionseditor: componentsPath + '/imageOptionsEditor/imageOptionsEditor', + apphost: componentsPath + '/apphost', + visibleinviewport: bowerPath + '/visibleinviewport', + qualityoptions: componentsPath + '/qualityOptions', + focusManager: componentsPath + '/focusManager', + itemHelper: componentsPath + '/itemHelper', + itemShortcuts: componentsPath + '/shortcuts', + playQueueManager: componentsPath + '/playback/playqueuemanager', + nowPlayingHelper: componentsPath + '/playback/nowplayinghelper', + pluginManager: componentsPath + '/pluginManager', + packageManager: componentsPath + '/packageManager', + screensaverManager: componentsPath + '/screensavermanager', + chromecastHelper: 'plugins/chromecastPlayer/chromecastHelpers' }; requirejs.onError = onRequireJsError; requirejs.config({ waitSeconds: 0, map: { - "*": { - css: "components/require/requirecss", - text: "components/require/requiretext" + '*': { + css: 'components/require/requirecss', + text: 'components/require/requiretext' } }, bundles: { bundle: [ - "document-register-element", - "fetch", - "flvjs", - "jstree", - "jQuery", - "hlsjs", - "howler", - "native-promise-only", - "resize-observer-polyfill", - "shaka", - "swiper", - "queryString", - "sortable", - "webcomponents", - "material-icons", - "jellyfin-noto", - "page", - "polyfill" + 'fetch', + 'flvjs', + 'jstree', + 'epubjs', + 'jQuery', + 'hlsjs', + 'howler', + 'native-promise-only', + 'resize-observer-polyfill', + 'shaka', + 'swiper', + 'queryString', + 'sortable', + 'webcomponents', + 'material-icons', + 'jellyfin-noto', + 'date-fns', + 'page', + 'polyfill', + 'fast-text-encoding', + 'intersection-observer', + 'classlist-polyfill', + 'screenfull', + 'headroom', + 'apiclient', + 'events', + 'credentialprovider', + 'connectionManagerFactory', + 'appStorage' ] }, urlArgs: urlArgs, @@ -702,185 +706,173 @@ var AppInfo = {}; onError: onRequireJsError }); - require(["polyfill"]); + require(['fetch']); + require(['polyfill']); + require(['fast-text-encoding']); + require(['intersection-observer']); + require(['classlist-polyfill']); // Expose jQuery globally - require(["jQuery"], function(jQuery) { + require(['jQuery'], function(jQuery) { window.$ = jQuery; window.jQuery = jQuery; }); - require(["css!assets/css/site"]); - require(["jellyfin-noto"]); + require(['css!assets/css/site']); + require(['jellyfin-noto']); // define styles // TODO determine which of these files can be moved to the components themselves - define("systemFontsCss", ["css!assets/css/fonts"], returnFirstDependency); - define("systemFontsSizedCss", ["css!assets/css/fonts.sized"], returnFirstDependency); - define("scrollStyles", ["css!assets/css/scrollstyles"], returnFirstDependency); - define("dashboardcss", ["css!assets/css/dashboard"], returnFirstDependency); - define("programStyles", ["css!" + componentsPath + "/guide/programs"], returnFirstDependency); - define("listViewStyle", ["css!" + componentsPath + "/listview/listview"], returnFirstDependency); - define("formDialogStyle", ["css!" + componentsPath + "/formdialog"], returnFirstDependency); - define("clearButtonStyle", ["css!assets/css/clearbutton"], returnFirstDependency); - define("cardStyle", ["css!" + componentsPath + "/cardbuilder/card"], returnFirstDependency); - define("flexStyles", ["css!assets/css/flexstyles"], returnFirstDependency); + define('systemFontsCss', ['css!assets/css/fonts'], returnFirstDependency); + define('systemFontsSizedCss', ['css!assets/css/fonts.sized'], returnFirstDependency); + define('scrollStyles', ['css!assets/css/scrollstyles'], returnFirstDependency); + define('dashboardcss', ['css!assets/css/dashboard'], returnFirstDependency); + define('programStyles', ['css!' + componentsPath + '/guide/programs'], returnFirstDependency); + define('listViewStyle', ['css!' + componentsPath + '/listview/listview'], returnFirstDependency); + define('formDialogStyle', ['css!' + componentsPath + '/formdialog'], returnFirstDependency); + define('clearButtonStyle', ['css!assets/css/clearbutton'], returnFirstDependency); + define('cardStyle', ['css!' + componentsPath + '/cardbuilder/card'], returnFirstDependency); + define('flexStyles', ['css!assets/css/flexstyles'], returnFirstDependency); // define legacy features // TODO delete the rest of these - define("fnchecked", ["legacy/fnchecked"], returnFirstDependency); - define("legacyDashboard", ["legacy/dashboard"], returnFirstDependency); - define("legacySelectMenu", ["legacy/selectmenu"], returnFirstDependency); + define('fnchecked', ['legacy/fnchecked'], returnFirstDependency); + define('legacySelectMenu', ['legacy/selectmenu'], returnFirstDependency); // there are several objects that need to be instantiated // TODO find a better way to do this - define("appFooter", [componentsPath + "/appfooter/appfooter"], returnFirstDependency); - define("appFooter-shared", ["appFooter"], createSharedAppFooter); - - // TODO pull apiclient out of this repository - define('events', [bowerPath + "/apiclient/events"], returnFirstDependency); - define('credentialprovider', [bowerPath + "/apiclient/credentialprovider"], returnFirstDependency); - define('connectionManagerFactory', [bowerPath + "/apiclient/connectionmanager"], returnFirstDependency); - define('appStorage', [bowerPath + "/apiclient/appStorage"], returnFirstDependency); - define("serversync", [bowerPath + "/apiclient/sync/serversync"], returnFirstDependency); - define("multiserversync", [bowerPath + "/apiclient/sync/multiserversync"], returnFirstDependency); - define("mediasync", [bowerPath + "/apiclient/sync/mediasync"], returnFirstDependency); - define("itemrepository", [bowerPath + "/apiclient/sync/itemrepository"], returnFirstDependency); - define("useractionrepository", [bowerPath + "/apiclient/sync/useractionrepository"], returnFirstDependency); + define('appFooter', [componentsPath + '/appFooter/appFooter'], returnFirstDependency); + define('appFooter-shared', ['appFooter'], createSharedAppFooter); // TODO remove these libraries // all of these have been modified so we need to fix that first - define("headroom", [componentsPath + "/headroom/headroom"], returnFirstDependency); - define("scroller", [componentsPath + "/scroller"], returnFirstDependency); - define("navdrawer", [componentsPath + "/navdrawer/navdrawer"], returnFirstDependency); - - define("emby-button", [elementsPath + "/emby-button/emby-button"], returnFirstDependency); - define("paper-icon-button-light", [elementsPath + "/emby-button/paper-icon-button-light"], returnFirstDependency); - define("emby-checkbox", [elementsPath + "/emby-checkbox/emby-checkbox"], returnFirstDependency); - define("emby-collapse", [elementsPath + "/emby-collapse/emby-collapse"], returnFirstDependency); - define("emby-input", [elementsPath + "/emby-input/emby-input"], returnFirstDependency); - define("emby-progressring", [elementsPath + "/emby-progressring/emby-progressring"], returnFirstDependency); - define("emby-radio", [elementsPath + "/emby-radio/emby-radio"], returnFirstDependency); - define("emby-select", [elementsPath + "/emby-select/emby-select"], returnFirstDependency); - define("emby-slider", [elementsPath + "/emby-slider/emby-slider"], returnFirstDependency); - define("emby-textarea", [elementsPath + "/emby-textarea/emby-textarea"], returnFirstDependency); - define("emby-toggle", [elementsPath + "/emby-toggle/emby-toggle"], returnFirstDependency); - - define("appSettings", [scriptsPath + "/settings/appSettings"], returnFirstDependency); - define("userSettingsBuilder", [scriptsPath + "/settings/userSettingsBuilder"], returnFirstDependency); - define("userSettings", ["userSettingsBuilder"], function(userSettingsBuilder) { - return new userSettingsBuilder(); - }); - - define("chromecastHelper", [componentsPath + "/chromecast/chromecasthelpers"], returnFirstDependency); - define("mediaSession", [componentsPath + "/playback/mediasession"], returnFirstDependency); - define("actionsheet", [componentsPath + "/actionsheet/actionsheet"], returnFirstDependency); - define("tunerPicker", [componentsPath + "/tunerpicker"], returnFirstDependency); - define("mainTabsManager", [componentsPath + "/maintabsmanager"], returnFirstDependency); - define("imageLoader", [componentsPath + "/images/imageLoader"], returnFirstDependency); - define("directorybrowser", [componentsPath + "/directorybrowser/directorybrowser"], returnFirstDependency); - define("metadataEditor", [componentsPath + "/metadataeditor/metadataeditor"], returnFirstDependency); - define("personEditor", [componentsPath + "/metadataeditor/personeditor"], returnFirstDependency); - define("playerSelectionMenu", [componentsPath + "/playback/playerSelectionMenu"], returnFirstDependency); - define("playerSettingsMenu", [componentsPath + "/playback/playersettingsmenu"], returnFirstDependency); - define("playMethodHelper", [componentsPath + "/playback/playmethodhelper"], returnFirstDependency); - define("brightnessOsd", [componentsPath + "/playback/brightnessosd"], returnFirstDependency); - define("emby-itemscontainer", [componentsPath + "/emby-itemscontainer/emby-itemscontainer"], returnFirstDependency); - define("alphaNumericShortcuts", [componentsPath + "/alphanumericshortcuts/alphanumericshortcuts"], returnFirstDependency); - define("emby-scroller", [componentsPath + "/emby-scroller/emby-scroller"], returnFirstDependency); - define("emby-tabs", [componentsPath + "/emby-tabs/emby-tabs"], returnFirstDependency); - define("emby-scrollbuttons", [componentsPath + "/emby-scrollbuttons/emby-scrollbuttons"], returnFirstDependency); - define("emby-itemrefreshindicator", [componentsPath + "/emby-itemrefreshindicator/emby-itemrefreshindicator"], returnFirstDependency); - define("multiSelect", [componentsPath + "/multiselect/multiselect"], returnFirstDependency); - define("alphaPicker", [componentsPath + "/alphapicker/alphapicker"], returnFirstDependency); - define("tabbedView", [componentsPath + "/tabbedview/tabbedview"], returnFirstDependency); - define("itemsTab", [componentsPath + "/tabbedview/itemstab"], returnFirstDependency); - define("collectionEditor", [componentsPath + "/collectioneditor/collectioneditor"], returnFirstDependency); - define("serverRestartDialog", [componentsPath + "/serverRestartDialog"], returnFirstDependency); - define("playlistEditor", [componentsPath + "/playlisteditor/playlisteditor"], returnFirstDependency); - define("recordingCreator", [componentsPath + "/recordingcreator/recordingcreator"], returnFirstDependency); - define("recordingEditor", [componentsPath + "/recordingcreator/recordingeditor"], returnFirstDependency); - define("seriesRecordingEditor", [componentsPath + "/recordingcreator/seriesrecordingeditor"], returnFirstDependency); - define("recordingFields", [componentsPath + "/recordingcreator/recordingfields"], returnFirstDependency); - define("recordingButton", [componentsPath + "/recordingcreator/recordingbutton"], returnFirstDependency); - define("recordingHelper", [componentsPath + "/recordingcreator/recordinghelper"], returnFirstDependency); - define("subtitleEditor", [componentsPath + "/subtitleeditor/subtitleeditor"], returnFirstDependency); - define("subtitleSync", [componentsPath + "/subtitlesync/subtitlesync"], returnFirstDependency); - define("itemIdentifier", [componentsPath + "/itemidentifier/itemidentifier"], returnFirstDependency); - define("itemMediaInfo", [componentsPath + "/itemMediaInfo/itemMediaInfo"], returnFirstDependency); - define("mediaInfo", [componentsPath + "/mediainfo/mediainfo"], returnFirstDependency); - define("itemContextMenu", [componentsPath + "/itemcontextmenu"], returnFirstDependency); - define("imageEditor", [componentsPath + "/imageeditor/imageeditor"], returnFirstDependency); - define("imageDownloader", [componentsPath + "/imagedownloader/imagedownloader"], returnFirstDependency); - define("dom", [componentsPath + "/dom"], returnFirstDependency); - define("playerStats", [componentsPath + "/playerstats/playerstats"], returnFirstDependency); - define("searchFields", [componentsPath + "/search/searchfields"], returnFirstDependency); - define("searchResults", [componentsPath + "/search/searchresults"], returnFirstDependency); - define("upNextDialog", [componentsPath + "/upnextdialog/upnextdialog"], returnFirstDependency); - define("fullscreen-doubleclick", [componentsPath + "/fullscreen/fullscreen-dc"], returnFirstDependency); - define("fullscreenManager", [componentsPath + "/fullscreenManager", "events"], returnFirstDependency); - define("subtitleAppearanceHelper", [componentsPath + "/subtitlesettings/subtitleappearancehelper"], returnFirstDependency); - define("subtitleSettings", [componentsPath + "/subtitlesettings/subtitlesettings"], returnFirstDependency); - define("displaySettings", [componentsPath + "/displaysettings/displaysettings"], returnFirstDependency); - define("playbackSettings", [componentsPath + "/playbacksettings/playbacksettings"], returnFirstDependency); - define("homescreenSettings", [componentsPath + "/homescreensettings/homescreensettings"], returnFirstDependency); - define("playbackManager", [componentsPath + "/playback/playbackmanager"], getPlaybackManager); - define("layoutManager", [componentsPath + "/layoutManager", "apphost"], getLayoutManager); - define("homeSections", [componentsPath + "/homesections/homesections"], returnFirstDependency); - define("playMenu", [componentsPath + "/playmenu"], returnFirstDependency); - define("refreshDialog", [componentsPath + "/refreshdialog/refreshdialog"], returnFirstDependency); - define("backdrop", [componentsPath + "/backdrop/backdrop"], returnFirstDependency); - define("fetchHelper", [componentsPath + "/fetchhelper"], returnFirstDependency); - define("cardBuilder", [componentsPath + "/cardbuilder/cardBuilder"], returnFirstDependency); - define("peoplecardbuilder", [componentsPath + "/cardbuilder/peoplecardbuilder"], returnFirstDependency); - define("chaptercardbuilder", [componentsPath + "/cardbuilder/chaptercardbuilder"], returnFirstDependency); - define("deleteHelper", [componentsPath + "/deletehelper"], returnFirstDependency); - define("tvguide", [componentsPath + "/guide/guide"], returnFirstDependency); - define("guide-settings-dialog", [componentsPath + "/guide/guide-settings"], returnFirstDependency); - define("loadingDialog", [componentsPath + "/loadingdialog/loadingdialog"], returnFirstDependency); - define("viewManager", [componentsPath + "/viewManager/viewManager"], function (viewManager) { + define('scroller', [bowerPath + '/scroller'], returnFirstDependency); + define('navdrawer', [bowerPath + '/navdrawer/navdrawer'], returnFirstDependency); + + define('emby-button', [elementsPath + '/emby-button/emby-button'], returnFirstDependency); + define('paper-icon-button-light', [elementsPath + '/emby-button/paper-icon-button-light'], returnFirstDependency); + define('emby-checkbox', [elementsPath + '/emby-checkbox/emby-checkbox'], returnFirstDependency); + define('emby-collapse', [elementsPath + '/emby-collapse/emby-collapse'], returnFirstDependency); + define('emby-input', [elementsPath + '/emby-input/emby-input'], returnFirstDependency); + define('emby-progressring', [elementsPath + '/emby-progressring/emby-progressring'], returnFirstDependency); + define('emby-radio', [elementsPath + '/emby-radio/emby-radio'], returnFirstDependency); + define('emby-select', [elementsPath + '/emby-select/emby-select'], returnFirstDependency); + define('emby-slider', [elementsPath + '/emby-slider/emby-slider'], returnFirstDependency); + define('emby-textarea', [elementsPath + '/emby-textarea/emby-textarea'], returnFirstDependency); + define('emby-toggle', [elementsPath + '/emby-toggle/emby-toggle'], returnFirstDependency); + define('emby-scroller', [elementsPath + '/emby-scroller/emby-scroller'], returnFirstDependency); + define('emby-tabs', [elementsPath + '/emby-tabs/emby-tabs'], returnFirstDependency); + define('emby-scrollbuttons', [elementsPath + '/emby-scrollbuttons/emby-scrollbuttons'], returnFirstDependency); + define('emby-itemrefreshindicator', [elementsPath + '/emby-itemrefreshindicator/emby-itemrefreshindicator'], returnFirstDependency); + define('emby-itemscontainer', [elementsPath + '/emby-itemscontainer/emby-itemscontainer'], returnFirstDependency); + define('emby-playstatebutton', [elementsPath + '/emby-playstatebutton/emby-playstatebutton'], returnFirstDependency); + define('emby-ratingbutton', [elementsPath + '/emby-ratingbutton/emby-ratingbutton'], returnFirstDependency); + define('emby-progressbar', [elementsPath + '/emby-progressbar/emby-progressbar'], returnFirstDependency); + define('emby-programcell', [elementsPath + '/emby-programcell/emby-programcell'], returnFirstDependency); + + define('webSettings', [scriptsPath + '/settings/webSettings'], returnFirstDependency); + define('appSettings', [scriptsPath + '/settings/appSettings'], returnFirstDependency); + define('userSettings', [scriptsPath + '/settings/userSettings'], returnFirstDependency); + + define('mediaSession', [componentsPath + '/playback/mediasession'], returnFirstDependency); + define('actionsheet', [componentsPath + '/actionSheet/actionSheet'], returnFirstDependency); + define('tunerPicker', [componentsPath + '/tunerPicker'], returnFirstDependency); + define('mainTabsManager', [componentsPath + '/maintabsmanager'], returnFirstDependency); + define('imageLoader', [componentsPath + '/images/imageLoader'], returnFirstDependency); + define('directorybrowser', [componentsPath + '/directorybrowser/directorybrowser'], returnFirstDependency); + define('metadataEditor', [componentsPath + '/metadataEditor/metadataEditor'], returnFirstDependency); + define('personEditor', [componentsPath + '/metadataEditor/personEditor'], returnFirstDependency); + define('playerSelectionMenu', [componentsPath + '/playback/playerSelectionMenu'], returnFirstDependency); + define('playerSettingsMenu', [componentsPath + '/playback/playersettingsmenu'], returnFirstDependency); + define('playMethodHelper', [componentsPath + '/playback/playmethodhelper'], returnFirstDependency); + define('brightnessOsd', [componentsPath + '/playback/brightnessosd'], returnFirstDependency); + define('alphaNumericShortcuts', [scriptsPath + '/alphanumericshortcuts'], returnFirstDependency); + define('multiSelect', [componentsPath + '/multiSelect/multiSelect'], returnFirstDependency); + define('alphaPicker', [componentsPath + '/alphaPicker/alphaPicker'], returnFirstDependency); + define('tabbedView', [componentsPath + '/tabbedview/tabbedview'], returnFirstDependency); + define('itemsTab', [componentsPath + '/tabbedview/itemstab'], returnFirstDependency); + define('collectionEditor', [componentsPath + '/collectionEditor/collectionEditor'], returnFirstDependency); + define('playlistEditor', [componentsPath + '/playlisteditor/playlisteditor'], returnFirstDependency); + define('recordingCreator', [componentsPath + '/recordingcreator/recordingcreator'], returnFirstDependency); + define('recordingEditor', [componentsPath + '/recordingcreator/recordingeditor'], returnFirstDependency); + define('seriesRecordingEditor', [componentsPath + '/recordingcreator/seriesrecordingeditor'], returnFirstDependency); + define('recordingFields', [componentsPath + '/recordingcreator/recordingfields'], returnFirstDependency); + define('recordingButton', [componentsPath + '/recordingcreator/recordingbutton'], returnFirstDependency); + define('recordingHelper', [componentsPath + '/recordingcreator/recordinghelper'], returnFirstDependency); + define('subtitleEditor', [componentsPath + '/subtitleeditor/subtitleeditor'], returnFirstDependency); + define('subtitleSync', [componentsPath + '/subtitlesync/subtitlesync'], returnFirstDependency); + define('itemIdentifier', [componentsPath + '/itemidentifier/itemidentifier'], returnFirstDependency); + define('itemMediaInfo', [componentsPath + '/itemMediaInfo/itemMediaInfo'], returnFirstDependency); + define('mediaInfo', [componentsPath + '/mediainfo/mediainfo'], returnFirstDependency); + define('itemContextMenu', [componentsPath + '/itemContextMenu'], returnFirstDependency); + define('imageEditor', [componentsPath + '/imageeditor/imageeditor'], returnFirstDependency); + define('imageDownloader', [componentsPath + '/imageDownloader/imageDownloader'], returnFirstDependency); + define('dom', [scriptsPath + '/dom'], returnFirstDependency); + define('playerStats', [componentsPath + '/playerstats/playerstats'], returnFirstDependency); + define('searchFields', [componentsPath + '/search/searchfields'], returnFirstDependency); + define('searchResults', [componentsPath + '/search/searchresults'], returnFirstDependency); + define('upNextDialog', [componentsPath + '/upnextdialog/upnextdialog'], returnFirstDependency); + define('subtitleAppearanceHelper', [componentsPath + '/subtitlesettings/subtitleappearancehelper'], returnFirstDependency); + define('subtitleSettings', [componentsPath + '/subtitlesettings/subtitlesettings'], returnFirstDependency); + define('displaySettings', [componentsPath + '/displaySettings/displaySettings'], returnFirstDependency); + define('playbackSettings', [componentsPath + '/playbackSettings/playbackSettings'], returnFirstDependency); + define('homescreenSettings', [componentsPath + '/homeScreenSettings/homeScreenSettings'], returnFirstDependency); + define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); + define('timeSyncManager', [componentsPath + '/syncPlay/timeSyncManager'], returnDefault); + define('groupSelectionMenu', [componentsPath + '/syncPlay/groupSelectionMenu'], returnFirstDependency); + define('syncPlayManager', [componentsPath + '/syncPlay/syncPlayManager'], returnDefault); + define('playbackPermissionManager', [componentsPath + '/syncPlay/playbackPermissionManager'], returnDefault); + define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); + define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); + define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency); + define('refreshDialog', [componentsPath + '/refreshdialog/refreshdialog'], returnFirstDependency); + define('backdrop', [componentsPath + '/backdrop/backdrop'], returnFirstDependency); + define('fetchHelper', [componentsPath + '/fetchhelper'], returnFirstDependency); + define('cardBuilder', [componentsPath + '/cardbuilder/cardBuilder'], returnFirstDependency); + define('peoplecardbuilder', [componentsPath + '/cardbuilder/peoplecardbuilder'], returnFirstDependency); + define('chaptercardbuilder', [componentsPath + '/cardbuilder/chaptercardbuilder'], returnFirstDependency); + define('deleteHelper', [scriptsPath + '/deleteHelper'], returnFirstDependency); + define('tvguide', [componentsPath + '/guide/guide'], returnFirstDependency); + define('guide-settings-dialog', [componentsPath + '/guide/guide-settings'], returnFirstDependency); + define('viewManager', [componentsPath + '/viewManager/viewManager'], function (viewManager) { window.ViewManager = viewManager; viewManager.dispatchPageEvents(true); return viewManager; }); - define("slideshow", [componentsPath + "/slideshow/slideshow"], returnFirstDependency); - define("objectassign", [componentsPath + "/polyfills/objectassign"], returnFirstDependency); - define("focusPreventScroll", [componentsPath + "/polyfills/focusPreventScroll"], returnFirstDependency); - define("userdataButtons", [componentsPath + "/userdatabuttons/userdatabuttons"], returnFirstDependency); - define("emby-playstatebutton", [componentsPath + "/userdatabuttons/emby-playstatebutton"], returnFirstDependency); - define("emby-ratingbutton", [componentsPath + "/userdatabuttons/emby-ratingbutton"], returnFirstDependency); - define("listView", [componentsPath + "/listview/listview"], returnFirstDependency); - define("indicators", [componentsPath + "/indicators/indicators"], returnFirstDependency); - define("viewSettings", [componentsPath + "/viewsettings/viewsettings"], returnFirstDependency); - define("filterMenu", [componentsPath + "/filtermenu/filtermenu"], returnFirstDependency); - define("sortMenu", [componentsPath + "/sortmenu/sortmenu"], returnFirstDependency); - define("idb", [componentsPath + "/idb"], returnFirstDependency); - define("sanitizefilename", [componentsPath + "/sanitizefilename"], returnFirstDependency); - define("toast", [componentsPath + "/toast/toast"], returnFirstDependency); - define("scrollHelper", [componentsPath + "/scrollhelper"], returnFirstDependency); - define("touchHelper", [componentsPath + "/touchhelper"], returnFirstDependency); - define("imageUploader", [componentsPath + "/imageuploader/imageuploader"], returnFirstDependency); - define("htmlMediaHelper", [componentsPath + "/htmlMediaHelper"], returnFirstDependency); - define("viewContainer", [componentsPath + "/viewContainer"], returnFirstDependency); - define("dialogHelper", [componentsPath + "/dialogHelper/dialogHelper"], returnFirstDependency); - define("serverNotifications", [componentsPath + "/serverNotifications"], returnFirstDependency); - define("skinManager", [componentsPath + "/skinManager"], returnFirstDependency); - define("keyboardnavigation", [componentsPath + "/input/keyboardnavigation"], returnFirstDependency); - define("mouseManager", [componentsPath + "/input/mouseManager"], returnFirstDependency); - define("scrollManager", [componentsPath + "/scrollManager"], returnFirstDependency); - define("autoFocuser", [componentsPath + "/autoFocuser"], returnFirstDependency); - define("connectionManager", [], function () { + define('slideshow', [componentsPath + '/slideshow/slideshow'], returnFirstDependency); + define('focusPreventScroll', ['legacy/focusPreventScroll'], returnFirstDependency); + define('userdataButtons', [componentsPath + '/userdatabuttons/userdatabuttons'], returnFirstDependency); + define('listView', [componentsPath + '/listview/listview'], returnFirstDependency); + define('indicators', [componentsPath + '/indicators/indicators'], returnFirstDependency); + define('viewSettings', [componentsPath + '/viewSettings/viewSettings'], returnFirstDependency); + define('filterMenu', [componentsPath + '/filtermenu/filtermenu'], returnFirstDependency); + define('sortMenu', [componentsPath + '/sortmenu/sortmenu'], returnFirstDependency); + define('sanitizefilename', [componentsPath + '/sanitizeFilename'], returnFirstDependency); + define('toast', [componentsPath + '/toast/toast'], returnFirstDependency); + define('scrollHelper', [scriptsPath + '/scrollHelper'], returnFirstDependency); + define('touchHelper', [scriptsPath + '/touchHelper'], returnFirstDependency); + define('imageUploader', [componentsPath + '/imageUploader/imageUploader'], returnFirstDependency); + define('htmlMediaHelper', [componentsPath + '/htmlMediaHelper'], returnFirstDependency); + define('viewContainer', [componentsPath + '/viewContainer'], returnFirstDependency); + define('dialogHelper', [componentsPath + '/dialogHelper/dialogHelper'], returnFirstDependency); + define('serverNotifications', [scriptsPath + '/serverNotifications'], returnFirstDependency); + define('skinManager', [componentsPath + '/skinManager'], returnFirstDependency); + define('keyboardnavigation', [scriptsPath + '/keyboardNavigation'], returnFirstDependency); + define('mouseManager', [scriptsPath + '/mouseManager'], returnFirstDependency); + define('scrollManager', [componentsPath + '/scrollManager'], returnFirstDependency); + define('autoFocuser', [componentsPath + '/autoFocuser'], returnFirstDependency); + define('connectionManager', [], function () { return ConnectionManager; }); - define("apiClientResolver", [], function () { + define('apiClientResolver', [], function () { return function () { return window.ApiClient; }; }); - define("appRouter", [componentsPath + "/appRouter", "itemHelper"], function (appRouter, itemHelper) { + define('appRouter', [componentsPath + '/appRouter', 'itemHelper'], function (appRouter, itemHelper) { function showItem(item, serverId, options) { - if ("string" == typeof item) { - require(["connectionManager"], function (connectionManager) { + if ('string' == typeof item) { + require(['connectionManager'], function (connectionManager) { var apiClient = connectionManager.currentApiClient(); apiClient.getItem(apiClient.getCurrentUserId(), item).then(function (item) { appRouter.showItem(item, options); @@ -891,58 +883,58 @@ var AppInfo = {}; options = arguments[1]; } - appRouter.show("/" + appRouter.getRouteUrl(item, options), { + appRouter.show('/' + appRouter.getRouteUrl(item, options), { item: item }); } } appRouter.showLocalLogin = function (serverId, manualLogin) { - Dashboard.navigate("login.html?serverid=" + serverId); + Dashboard.navigate('login.html?serverid=' + serverId); }; appRouter.showVideoOsd = function () { - return Dashboard.navigate("videoosd.html"); + return Dashboard.navigate('videoosd.html'); }; appRouter.showSelectServer = function () { - Dashboard.navigate(AppInfo.isNativeApp ? "selectserver.html" : "login.html"); + Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html'); }; appRouter.showWelcome = function () { - Dashboard.navigate(AppInfo.isNativeApp ? "selectserver.html" : "login.html"); + Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html'); }; appRouter.showSettings = function () { - Dashboard.navigate("mypreferencesmenu.html"); + Dashboard.navigate('mypreferencesmenu.html'); }; appRouter.showGuide = function () { - Dashboard.navigate("livetv.html?tab=1"); + Dashboard.navigate('livetv.html?tab=1'); }; appRouter.goHome = function () { - Dashboard.navigate("home.html"); + Dashboard.navigate('home.html'); }; appRouter.showSearch = function () { - Dashboard.navigate("search.html"); + Dashboard.navigate('search.html'); }; appRouter.showLiveTV = function () { - Dashboard.navigate("livetv.html"); + Dashboard.navigate('livetv.html'); }; appRouter.showRecordedTV = function () { - Dashboard.navigate("livetv.html?tab=3"); + Dashboard.navigate('livetv.html?tab=3'); }; appRouter.showFavorites = function () { - Dashboard.navigate("home.html?tab=1"); + Dashboard.navigate('home.html?tab=1'); }; appRouter.showSettings = function () { - Dashboard.navigate("mypreferencesmenu.html"); + Dashboard.navigate('mypreferencesmenu.html'); }; appRouter.setTitle = function (title) { @@ -951,7 +943,7 @@ var AppInfo = {}; appRouter.getRouteUrl = function (item, options) { if (!item) { - throw new Error("item cannot be null"); + throw new Error('item cannot be null'); } if (item.url) { @@ -969,168 +961,168 @@ var AppInfo = {}; var itemType = item.Type || (options ? options.itemType : null); var serverId = item.ServerId || options.serverId; - if ("settings" === item) { - return "mypreferencesmenu.html"; + if ('settings' === item) { + return 'mypreferencesmenu.html'; } - if ("wizard" === item) { - return "wizardstart.html"; + if ('wizard' === item) { + return 'wizardstart.html'; } - if ("manageserver" === item) { - return "dashboard.html"; + if ('manageserver' === item) { + return 'dashboard.html'; } - if ("recordedtv" === item) { - return "livetv.html?tab=3&serverId=" + options.serverId; + if ('recordedtv' === item) { + return 'livetv.html?tab=3&serverId=' + options.serverId; } - if ("nextup" === item) { - return "list.html?type=nextup&serverId=" + options.serverId; + if ('nextup' === item) { + return 'list.html?type=nextup&serverId=' + options.serverId; } - if ("list" === item) { - var url = "list.html?serverId=" + options.serverId + "&type=" + options.itemTypes; + if ('list' === item) { + var url = 'list.html?serverId=' + options.serverId + '&type=' + options.itemTypes; if (options.isFavorite) { - url += "&IsFavorite=true"; + url += '&IsFavorite=true'; } return url; } - if ("livetv" === item) { - if ("programs" === options.section) { - return "livetv.html?tab=0&serverId=" + options.serverId; + if ('livetv' === item) { + if ('programs' === options.section) { + return 'livetv.html?tab=0&serverId=' + options.serverId; } - if ("guide" === options.section) { - return "livetv.html?tab=1&serverId=" + options.serverId; + if ('guide' === options.section) { + return 'livetv.html?tab=1&serverId=' + options.serverId; } - if ("movies" === options.section) { - return "list.html?type=Programs&IsMovie=true&serverId=" + options.serverId; + if ('movies' === options.section) { + return 'list.html?type=Programs&IsMovie=true&serverId=' + options.serverId; } - if ("shows" === options.section) { - return "list.html?type=Programs&IsSeries=true&IsMovie=false&IsNews=false&serverId=" + options.serverId; + if ('shows' === options.section) { + return 'list.html?type=Programs&IsSeries=true&IsMovie=false&IsNews=false&serverId=' + options.serverId; } - if ("sports" === options.section) { - return "list.html?type=Programs&IsSports=true&serverId=" + options.serverId; + if ('sports' === options.section) { + return 'list.html?type=Programs&IsSports=true&serverId=' + options.serverId; } - if ("kids" === options.section) { - return "list.html?type=Programs&IsKids=true&serverId=" + options.serverId; + if ('kids' === options.section) { + return 'list.html?type=Programs&IsKids=true&serverId=' + options.serverId; } - if ("news" === options.section) { - return "list.html?type=Programs&IsNews=true&serverId=" + options.serverId; + if ('news' === options.section) { + return 'list.html?type=Programs&IsNews=true&serverId=' + options.serverId; } - if ("onnow" === options.section) { - return "list.html?type=Programs&IsAiring=true&serverId=" + options.serverId; + if ('onnow' === options.section) { + return 'list.html?type=Programs&IsAiring=true&serverId=' + options.serverId; } - if ("dvrschedule" === options.section) { - return "livetv.html?tab=4&serverId=" + options.serverId; + if ('dvrschedule' === options.section) { + return 'livetv.html?tab=4&serverId=' + options.serverId; } - if ("seriesrecording" === options.section) { - return "livetv.html?tab=5&serverId=" + options.serverId; + if ('seriesrecording' === options.section) { + return 'livetv.html?tab=5&serverId=' + options.serverId; } - return "livetv.html?serverId=" + options.serverId; + return 'livetv.html?serverId=' + options.serverId; } - if ("SeriesTimer" == itemType) { - return "itemdetails.html?seriesTimerId=" + id + "&serverId=" + serverId; + if ('SeriesTimer' == itemType) { + return 'details?seriesTimerId=' + id + '&serverId=' + serverId; } - if ("livetv" == item.CollectionType) { - return "livetv.html"; + if ('livetv' == item.CollectionType) { + return 'livetv.html'; } - if ("Genre" === item.Type) { - url = "list.html?genreId=" + item.Id + "&serverId=" + serverId; + if ('Genre' === item.Type) { + url = 'list.html?genreId=' + item.Id + '&serverId=' + serverId; - if ("livetv" === context) { - url += "&type=Programs"; + if ('livetv' === context) { + url += '&type=Programs'; } if (options.parentId) { - url += "&parentId=" + options.parentId; + url += '&parentId=' + options.parentId; } return url; } - if ("MusicGenre" === item.Type) { - url = "list.html?musicGenreId=" + item.Id + "&serverId=" + serverId; + if ('MusicGenre' === item.Type) { + url = 'list.html?musicGenreId=' + item.Id + '&serverId=' + serverId; if (options.parentId) { - url += "&parentId=" + options.parentId; + url += '&parentId=' + options.parentId; } return url; } - if ("Studio" === item.Type) { - url = "list.html?studioId=" + item.Id + "&serverId=" + serverId; + if ('Studio' === item.Type) { + url = 'list.html?studioId=' + item.Id + '&serverId=' + serverId; if (options.parentId) { - url += "&parentId=" + options.parentId; + url += '&parentId=' + options.parentId; } return url; } - if ("folders" !== context && !itemHelper.isLocalItem(item)) { - if ("movies" == item.CollectionType) { - url = "movies.html?topParentId=" + item.Id; + if ('folders' !== context && !itemHelper.isLocalItem(item)) { + if ('movies' == item.CollectionType) { + url = 'movies.html?topParentId=' + item.Id; - if (options && "latest" === options.section) { - url += "&tab=1"; + if (options && 'latest' === options.section) { + url += '&tab=1'; } return url; } - if ("tvshows" == item.CollectionType) { - url = "tv.html?topParentId=" + item.Id; + if ('tvshows' == item.CollectionType) { + url = 'tv.html?topParentId=' + item.Id; - if (options && "latest" === options.section) { - url += "&tab=2"; + if (options && 'latest' === options.section) { + url += '&tab=2'; } return url; } - if ("music" == item.CollectionType) { - return "music.html?topParentId=" + item.Id; + if ('music' == item.CollectionType) { + return 'music.html?topParentId=' + item.Id; } } - var itemTypes = ["Playlist", "TvChannel", "Program", "BoxSet", "MusicAlbum", "MusicGenre", "Person", "Recording", "MusicArtist"]; + var itemTypes = ['Playlist', 'TvChannel', 'Program', 'BoxSet', 'MusicAlbum', 'MusicGenre', 'Person', 'Recording', 'MusicArtist']; if (itemTypes.indexOf(itemType) >= 0) { - return "itemdetails.html?id=" + id + "&serverId=" + serverId; + return 'details?id=' + id + '&serverId=' + serverId; } - var contextSuffix = context ? "&context=" + context : ""; + var contextSuffix = context ? '&context=' + context : ''; - if ("Series" == itemType || "Season" == itemType || "Episode" == itemType) { - return "itemdetails.html?id=" + id + contextSuffix + "&serverId=" + serverId; + if ('Series' == itemType || 'Season' == itemType || 'Episode' == itemType) { + return 'details?id=' + id + contextSuffix + '&serverId=' + serverId; } if (item.IsFolder) { if (id) { - return "list.html?parentId=" + id + "&serverId=" + serverId; + return 'list.html?parentId=' + id + '&serverId=' + serverId; } - return "#"; + return '#'; } - return "itemdetails.html?id=" + id + "&serverId=" + serverId; + return 'details?id=' + id + '&serverId=' + serverId; }; appRouter.showItem = showItem; @@ -1138,13 +1130,13 @@ var AppInfo = {}; }); })(); - return require(["browser"], onWebComponentsReady); + return onWebComponentsReady(); }(); -pageClassOn("viewshow", "standalonePage", function () { - document.querySelector(".skinHeader").classList.add("noHeaderRight"); +pageClassOn('viewshow', 'standalonePage', function () { + document.querySelector('.skinHeader').classList.add('noHeaderRight'); }); -pageClassOn("viewhide", "standalonePage", function () { - document.querySelector(".skinHeader").classList.remove("noHeaderRight"); +pageClassOn('viewhide', 'standalonePage', function () { + document.querySelector('.skinHeader').classList.remove('noHeaderRight'); }); diff --git a/src/scripts/taskbutton.js b/src/scripts/taskbutton.js index f9774167c09..8facaf89008 100644 --- a/src/scripts/taskbutton.js +++ b/src/scripts/taskbutton.js @@ -1,5 +1,5 @@ -define(["events", "userSettings", "serverNotifications", "connectionManager", "emby-button"], function (events, userSettings, serverNotifications, connectionManager) { - "use strict"; +define(['events', 'userSettings', 'serverNotifications', 'connectionManager', 'globalize', 'emby-button'], function (events, userSettings, serverNotifications, connectionManager, globalize) { + 'use strict'; return function (options) { function pollTasks() { @@ -26,12 +26,12 @@ define(["events", "userSettings", "serverNotifications", "connectionManager", "e } if (task.State == 'Idle') { - button.removeAttribute("disabled"); + button.removeAttribute('disabled'); } else { - button.setAttribute("disabled", "disabled"); + button.setAttribute('disabled', 'disabled'); } - button.setAttribute("data-taskid", task.Id); + button.setAttribute('data-taskid', task.Id); var progress = (task.CurrentProgressPercentage || 0).toFixed(1); if (options.progressElem) { @@ -47,12 +47,12 @@ define(["events", "userSettings", "serverNotifications", "connectionManager", "e if (options.lastResultElem) { var lastResult = task.LastExecutionResult ? task.LastExecutionResult.Status : ''; - if (lastResult == "Failed") { - options.lastResultElem.html('(' + Globalize.translate('LabelFailed') + ')'); - } else if (lastResult == "Cancelled") { - options.lastResultElem.html('(' + Globalize.translate('LabelCancelled') + ')'); - } else if (lastResult == "Aborted") { - options.lastResultElem.html('' + Globalize.translate('LabelAbortedByServerShutdown') + ''); + if (lastResult == 'Failed') { + options.lastResultElem.html('(' + globalize.translate('LabelFailed') + ')'); + } else if (lastResult == 'Cancelled') { + options.lastResultElem.html('(' + globalize.translate('LabelCancelled') + ')'); + } else if (lastResult == 'Aborted') { + options.lastResultElem.html('' + globalize.translate('LabelAbortedByServerShutdown') + ''); } else { options.lastResultElem.html(lastResult); } @@ -64,7 +64,7 @@ define(["events", "userSettings", "serverNotifications", "connectionManager", "e } function onButtonClick() { - onScheduledTaskMessageConfirmed(this.getAttribute("data-taskid")); + onScheduledTaskMessageConfirmed(this.getAttribute('data-taskid')); } function onScheduledTasksUpdate(e, apiClient, info) { @@ -89,12 +89,12 @@ define(["events", "userSettings", "serverNotifications", "connectionManager", "e if (pollInterval) { clearInterval(pollInterval); } - apiClient.sendMessage("ScheduledTasksInfoStart", "1000,1000"); + apiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); pollInterval = setInterval(onPollIntervalFired, 5000); } function stopInterval() { - connectionManager.getApiClient(serverId).sendMessage("ScheduledTasksInfoStop"); + connectionManager.getApiClient(serverId).sendMessage('ScheduledTasksInfoStop'); if (pollInterval) { clearInterval(pollInterval); @@ -102,18 +102,18 @@ define(["events", "userSettings", "serverNotifications", "connectionManager", "e } if (options.panel) { - options.panel.classList.add("hide"); + options.panel.classList.add('hide'); } if (options.mode == 'off') { - button.removeEventListener("click", onButtonClick); - events.off(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + button.removeEventListener('click', onButtonClick); + events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); stopInterval(); } else { - button.addEventListener("click", onButtonClick); + button.addEventListener('click', onButtonClick); pollTasks(); startInterval(); - events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); + events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); } }; }); diff --git a/src/scripts/themeloader.js b/src/scripts/themeLoader.js similarity index 50% rename from src/scripts/themeloader.js rename to src/scripts/themeLoader.js index ea7ff57f9ad..f75d6d0e294 100644 --- a/src/scripts/themeloader.js +++ b/src/scripts/themeLoader.js @@ -1,19 +1,19 @@ -define(["userSettings", "skinManager", "connectionManager", "events"], function (userSettings, skinManager, connectionManager, events) { - "use strict"; +define(['userSettings', 'skinManager', 'connectionManager', 'events'], function (userSettings, skinManager, connectionManager, events) { + 'use strict'; var currentViewType; - pageClassOn("viewbeforeshow", "page", function () { + pageClassOn('viewbeforeshow', 'page', function () { var classList = this.classList; - var viewType = classList.contains("type-interior") || classList.contains("wizardPage") ? "a" : "b"; + var viewType = classList.contains('type-interior') || classList.contains('wizardPage') ? 'a' : 'b'; if (viewType !== currentViewType) { currentViewType = viewType; var theme; var context; - if ("a" === viewType) { + if ('a' === viewType) { theme = userSettings.dashboardTheme(); - context = "serverdashboard"; + context = 'serverdashboard'; } else { theme = userSettings.theme(); } @@ -21,7 +21,7 @@ define(["userSettings", "skinManager", "connectionManager", "events"], function skinManager.setTheme(theme, context); } }); - events.on(connectionManager, "localusersignedin", function (e, user) { + events.on(connectionManager, 'localusersignedin', function (e, user) { currentViewType = null; }); }); diff --git a/src/components/touchhelper.js b/src/scripts/touchHelper.js similarity index 100% rename from src/components/touchhelper.js rename to src/scripts/touchHelper.js diff --git a/src/scripts/wizardagreement.js b/src/scripts/wizardagreement.js deleted file mode 100644 index 148b0ef39af..00000000000 --- a/src/scripts/wizardagreement.js +++ /dev/null @@ -1,27 +0,0 @@ -define(["dom", "emby-button"], function (dom) { - "use strict"; - - function onSubmit(e) { - if (dom.parentWithClass(this, "page").querySelector(".chkAccept").checked) { - Dashboard.navigate("wizardfinish.html"); - } else { - Dashboard.alert({ - message: Globalize.translate("MessagePleaseAcceptTermsOfServiceBeforeContinuing"), - title: "" - }); - } - - e.preventDefault(); - return false; - } - - return function (view, params) { - view.querySelector(".wizardAgreementForm").addEventListener("submit", onSubmit); - view.addEventListener("viewshow", function () { - document.querySelector(".skinHeader").classList.add("noHomeButtonHeader"); - }); - view.addEventListener("viewhide", function () { - document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader"); - }); - }; -}); diff --git a/src/serviceworker.js b/src/serviceworker.js index c43d1f4b4e2..22101831487 100644 --- a/src/serviceworker.js +++ b/src/serviceworker.js @@ -1,2 +1,2 @@ /* eslint-env serviceworker */ -importScripts("components/serviceworker/notifications.js"); +importScripts('components/serviceworker/notifications.js'); diff --git a/src/standalone.js b/src/standalone.js index 04d40d6b115..237872703ae 100644 --- a/src/standalone.js +++ b/src/standalone.js @@ -1,4 +1,4 @@ (function() { - "use strict"; + 'use strict'; window.appMode = 'standalone'; })(); diff --git a/src/strings/af.json b/src/strings/af.json index 6ac71790f64..5c4e13dc7c4 100644 --- a/src/strings/af.json +++ b/src/strings/af.json @@ -40,5 +40,129 @@ "Add": "Voeg by", "Actor": "Akteur", "AccessRestrictedTryAgainLater": "Toegang is beperk. Probeer weer later .", - "Absolute": "Absoluut" + "Absolute": "Absoluut", + "AlbumArtist": "Album Kunstenaar", + "TabLatest": "Nuutste", + "TabInfo": "Inligting", + "TabGuide": "Gids", + "TabGenres": "Genres", + "TabFavorites": "Gunstellinge", + "TabEpisodes": "Episodes", + "TabDisplay": "Vertoon", + "TabDirectPlay": "Speel Direk", + "TabDevices": "Toestelle", + "TabDashboard": "Paneelbord", + "TabContainers": "Houers", + "Collections": "Versamelings", + "TabCollections": "Versamelings", + "UnsupportedPlayback": "Jellyfin kan nie inhoud wat beskerm word deur DRM ontsuifer nie, maar daar sal 'n poging aangwend word inelkgeval, insluitend beskermde titels. Sommige leêrs mag geheel en al swart verksyn weens enkripsie of ander on-ondersteunde funksies, byvoorbeeld interaktiewe titels.", + "OnApplicationStartup": "Op applikasie begin", + "EveryXHours": "Elke {0} ure", + "EveryHour": "Elke uur", + "EveryXMinutes": "Elke {0} minute", + "OnWakeFromSleep": "Op wakker word van slaap", + "WeeklyAt": "{0}s teen {1}", + "DailyAt": "Daagliks teen {0}", + "LastSeen": "Laas gekyk {0}", + "PersonRole": "soos {0}", + "ListPaging": "{0}-{1} van {2}", + "WriteAccessRequired": "Jellyfin Bediener benodig skryf toegang tot die leêr. Maak asseblief seker dat dit skryf toegang het en probeer weer.", + "PathNotFound": "Die pad kon nie gevind word nie. Maak asseblief seker dat die pad geldig is en probeer weer.", + "Yesterday": "Gister", + "Yes": "Ja", + "XmlTvSportsCategoriesHelp": "Programme met die kategorieë sal vertoon word as sport programme. Verdeel met veelvuldige '|'.", + "XmlTvPathHelp": "'n Pad tot 'n XMLTV lêer. Jellyfin sal die lêer lees en van tyd tot tyd soek vir opdaterings. Jy is verantwoordelik vir die maak en opdatering van die lêer.", + "XmlTvNewsCategoriesHelp": "Programme met die kategorieë sal vertoon word as nuus programme. Verdeel met veelvuldige '|'.", + "XmlTvMovieCategoriesHelp": "Programme met die kategorieë sal vertoon word as replprente. Verdeel met veelvuldige '|'.", + "XmlTvKidsCategoriesHelp": "Programme met die kategorieë sal vertoon word as programme vir kinders. Verdeel met veelvuldige '|'.", + "XmlDocumentAttributeListHelp": "Hierdie kenmerke word toegepas tot die wortel element van elke XML reaksie.", + "Writer": "Skrywer", + "WizardCompleted": "Dit is al wat ons benodig vir nou. Jellyfin het begin om inligting van jou media biblioteek te versamel. Kyk na van ons apps, en dan klik Finaliseer om die Paneelbord.", + "Whitelist": "Witlys", + "WelcomeToProject": "Welkom tot Jellyfin!", + "Wednesday": "Woensdag", + "Watched": "Gekyk", + "ViewPlaybackInfo": "Beskou terugspeel inligting", + "ViewArtist": "Beskou kunstenaar", + "ViewAlbum": "Beskou album", + "VideoRange": "Video reekse", + "Vertical": "Vertikaal", + "ValueVideoCodec": "Video Kodek: {0}", + "ValueTimeLimitSingleHour": "Tyd limiet: 1 uur", + "ValueTimeLimitMultiHour": "Tyd limiet: {0} ure", + "ValueSpecialEpisodeName": "Spesiale - {0}", + "ValueSongCount": "{0} liedjies", + "ValueSeriesCount": "{0} reekse", + "ValueSeconds": "{0} sekondes", + "ValueOneSong": "1 liedjie", + "ValueOneSeries": "1 reeks", + "ValueOneMusicVideo": "1 musiek video", + "ValueOneMovie": "1 rolprent", + "ValueMusicVideoCount": "{0} musiek videos", + "ValueMovieCount": "{0} rolprente", + "ValueMinutes": "{0} minute", + "ValueDiscNumber": "Skyf {0}", + "ValueContainer": "Houers: {0}", + "ValueConditions": "Kondisies: {0}", + "ValueCodec": "Kodek: {0}", + "ValueAudioCodec": "Audio Kodec: {0}", + "ValueAlbumCount": "{0} albums", + "UserProfilesIntro": "Jellyfin verskaf ondersteuning vir gebruiker profiele met pas instellings vir vertoon, speel staat, en ouer-beheer.", + "UserAgentHelp": "Verskaf 'n pas gebruiker-agent HTTP opskrif.", + "Upload": "Oplaai", + "Up": "Op", + "Unplayed": "Ongespeel", + "Unmute": "Ontstom", + "UninstallPluginHeader": "Oninstalleer Plugin", + "UninstallPluginConfirmation": "Is jy seker jy wil voortgaan met die oninstallasie {0}?", + "Uniform": "Uniform", + "TvLibraryHelp": "Hersien die {0}TV benamings gids{1}.", + "Tuesday": "Dinsdag", + "Transcoding": "Trankodering", + "Trailers": "Voorprente", + "TrackCount": "{0} nommers", + "Track": "Nommer", + "TitlePlayback": "Terugspeel", + "TitleHostingSettings": "Hosting Instellings", + "TitleHardwareAcceleration": "Hardeware Versnelling", + "Thursday": "Donderdag", + "Thumb": "Duim", + "ThisWizardWillGuideYou": "Hierdie gids sal jou deur die opstel proses help. Om te begin, kies asseblief jou taal van voorkeur.", + "TheseSettingsAffectSubtitlesOnThisDevice": "Hierdie instellings affekteer die sub-titels vie hierdie toestel", + "ThemeVideos": "Tema Videos", + "ThemeSongs": "Tema Liedjies", + "TellUsAboutYourself": "Vertel ons van jouself", + "TabUsers": "Gebruikers", + "TabUpcoming": "Komende", + "TabTranscoding": "Transkodering", + "TabTrailers": "Voorprente", + "TabSuggestions": "Voorstelle", + "TabStreaming": "Stroom", + "TabSongs": "Liedjies", + "TabShows": "Programme", + "TabSettings": "Instellings", + "TabServer": "Bediener", + "TabSeries": "Reekse", + "TabScheduledTasks": "Geskeduleerde Take", + "TabResumeSettings": "Hervat", + "TabResponses": "Reaksies", + "TabRecordings": "Opnames", + "TabProfiles": "Profiele", + "TabProfile": "Profiel", + "TabPlaylists": "Speel lyste", + "TabPlaylist": "Speel lys", + "TabPlayback": "Terugspeel", + "TabPassword": "Wagwoord", + "TabParentalControl": "Ouer Beheer", + "TabOther": "Ander", + "TabNotifications": "Kennisgewings", + "TabNfoSettings": "NFO Instellings", + "TabNetworking": "Netwerking", + "TabNetworks": "Netwerke", + "TabMusicVideos": "Musiek Videos", + "TabMusic": "Musiek", + "TabMovies": "Rolprente", + "TabMetadata": "Meta Inligting", + "TabLogs": "Logs", + "TabLiveTV": "Lewendige TV" } diff --git a/src/strings/ar.json b/src/strings/ar.json index 18ceb61deae..e7217b425bd 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -17,7 +17,7 @@ "ButtonAddScheduledTaskTrigger": "إضافة زناد", "ButtonAddServer": "إضافة خادم", "ButtonAddUser": "اضافة مستخدم", - "ButtonArrowDown": "أدنى", + "ButtonArrowDown": "أسفل", "ButtonArrowLeft": "يسار", "ButtonArrowRight": "يمين", "ButtonArrowUp": "أعلى", @@ -146,8 +146,8 @@ "HeaderAlbums": "الألبومات", "HeaderAlert": "تنبيه", "HeaderAllowMediaDeletionFrom": "السماح بحذف الوسائط من قبل", - "HeaderApiKey": "مفتاح api", - "HeaderApiKeys": "مفاتيح api", + "HeaderApiKey": "مفتاح API", + "HeaderApiKeys": "مفاتيح API", "HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح api لكي تتصل بخادم أمبي. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بحساب أمبي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.", "HeaderApp": "التطبيق", "HeaderAudioSettings": "إعدادات الصوت", @@ -162,7 +162,7 @@ "HeaderCodecProfileHelp": "عرائض الكودك تشير إلى محدودية جهاز ما عند تشغيل وسيطة مشفر بكودك معيّن. إن كان هناك أي محدودية مذكورة فستحال الوسيطة إلى التشغير البيني، حتى لو كانت الصيغة مضبوطة للعمل بتلقائية.", "HeaderConfirmPluginInstallation": "أكد عملية تثبيت الملحق", "HeaderConfirmProfileDeletion": "أكّد حذف العريضة", - "HeaderConfirmRevokeApiKey": "أرفض مفتاح api", + "HeaderConfirmRevokeApiKey": "أرفض مفتاح API", "HeaderConnectToServer": "اتصل إلى الخادم", "HeaderConnectionFailure": "فشل في الاتصال", "HeaderContainerProfile": "عريضة الحاوية", @@ -193,7 +193,7 @@ "HeaderFrequentlyPlayed": "تم تشغيله مراراً", "HeaderGenres": "أنواع الأفلام", "HeaderGuideProviders": "مزودو الأدلة", - "HeaderHttpHeaders": "رؤوس http", + "HeaderHttpHeaders": "رؤوس HTTP", "HeaderIdentification": "التعريفة", "HeaderIdentificationCriteriaHelp": "أدخل على الأقل معيار واحد للتعريف.", "HeaderIdentificationHeader": "رأس التعريفة", @@ -234,7 +234,7 @@ "HeaderPlayback": "تشغيل الوسائط", "HeaderPleaseSignIn": "الرجاء تسجيل الدخول", "HeaderPluginInstallation": "تثبيت الملحفات", - "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات:", + "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات", "HeaderProfile": "الحساب", "HeaderProfileInformation": "معلومات العريضة", "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل خادم أمبي في الجهاز", @@ -918,11 +918,11 @@ "HeaderFavoriteEpisodes": "الحلقات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", "Shows": "الحلقات", - "Books": "كتب", - "ValueSpecialEpisodeName": "مميز - {0}", + "Books": "الكتب", + "ValueSpecialEpisodeName": "خاص - {0}", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderAlbumArtists": "فناني الألبومات", - "Genres": "الأنواع", + "Genres": "التضنيفات", "Folders": "المجلدات", "Favorites": "المفضلة", "Collections": "مجموعات", @@ -952,7 +952,7 @@ "Artists": "الفنانين", "Art": "فن", "Anytime": "اي وقت", - "AnyLanguage": "اي لغة", + "AnyLanguage": "أي لغة", "AlwaysPlaySubtitlesHelp": "الترجمة التي تطابق تفضيلات اللغة سيتم تحميلها بغض النظر عن لغة الصوت.", "AlwaysPlaySubtitles": "شغل الترجمة دائماً", "AllowedRemoteAddressesHelp": "قائمة لعناوين IP أو إدخالات IP / قناع الشبكة مفصولة بفاصلة للشبكات التي سيتم السماح لها بالاتصال عن بعد. إذا تركت فارغة ، فسيتم السماح بجميع العناوين البعيدة.", @@ -962,9 +962,9 @@ "AllowMediaConversion": "السماح بتحويل الوسائظ", "AllLanguages": "كل اللغات", "AllEpisodes": "كل الحلقات", - "AllComplexFormats": "جميع التنسيقات المعقدة (ASS ، SSA ، VOBSUB ، PGS ، SUB / IDX ، إلخ.)", + "AllComplexFormats": "جميع التنسيقات المعقدة (ASS ، SSA ، VOBSUB ، PGS ، SUB / IDX ، ...)", "AllChannels": "كل القنوات", - "Albums": "ألبومات", + "Albums": "البومات", "Aired": "عرضت", "AirDate": "تاريخ العرض", "AddedOnValue": "أضيفت", @@ -1018,7 +1018,6 @@ "HeaderSeriesStatus": "حالة المسلسل", "HeaderSeriesOptions": "اعدادات المسلسل", "HeaderSecondsValue": "{0} ثوانى", - "HeaderRestartingServer": "اعادة تشغيل الخادم", "HeaderRecordingOptions": "اعدادات التسجيل", "HeaderPlaybackError": "خطأ فى التشغيل", "HeaderPlayOn": "شغل على", @@ -1040,5 +1039,120 @@ "DatePlayed": "تاريخ التشغيل", "DateAdded": "تاريخ الاضافة", "CriticRating": "تقييم النقاد", - "ResumeAt": "اكمل من {0}" + "ResumeAt": "اكمل من {0}", + "AskAdminToCreateLibrary": "أطلب من الأدمن إنشاء مكتبة.", + "Artist": "الفنان", + "AllowFfmpegThrottling": "إبطاء الترميزات", + "AlbumArtist": "المؤدي", + "Album": "الألبوم", + "Disconnect": "قطع الاتصال", + "Disc": "القرص", + "Disabled": "تعطيل", + "Directors": "المخرجون", + "Director": "المخرج", + "DirectPlaying": "بث بدون تحويل الصيغة", + "DirectStreaming": "البث المباشر", + "DirectStreamHelp2": "البث المباشر للملف يستخدم طاقة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", + "DirectStreamHelp1": "الوسائط متوافقة مع الجهاز فيما يتعلق بالدقة ونوع الوسائط (H.264 ، AC3 ، إلخ) ، ولكنها في حاوية ملفات غير متوافقة (mkv ، avi ، wmv ، إلخ). سيتم إعادة حزم الفيديو في الوقت الحقيقي قبل بثه إلى الجهاز.", + "DetectingDevices": "يتم الكشف عن الأجهزة", + "Desktop": "سطح المكتب", + "Descending": "تنازلي", + "Depressed": "منخفض", + "DeinterlaceMethodHelp": "حدد طريقة فك التشابك لاستخدامها عند تحويل محتوى متشابك.", + "DefaultSubtitlesHelp": "يتم تحميل الترجمات استنادًا إلى العلامات الافتراضية والقسرية في البيانات الوصفية المضمنة. سيتم اعتبار تفضيلات اللغة عند توفر خيارات متعددة.", + "DefaultMetadataLangaugeDescription": "هذه هي إعداداتك الافتراضية ويمكن تخصيصها على أساس كل مكتبة.", + "Default": "افتراضي", + "CopyStreamURLError": "توجد مشكله في نسخ الرابط", + "CopyStreamURL": "نسخ عنوان الرابط", + "Continuing": "مستمر", + "CopyStreamURLSuccess": "URL copied successfully.", + "Connect": "اتصال", + "ConfirmEndPlayerSession": "هل تريد اطفاء النظام؟", + "ColorPrimaries": "الألوان", + "ClientSettings": "إعدادات التطبيق", + "ButtonTogglePlaylist": "قائمة التشغيل", + "BoxSet": "طقم", + "ButtonToggleContextMenu": "المزيد", + "ButtonSplit": "تقسيم", + "AllowFfmpegThrottlingHelp": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل.", + "InstallingPackage": "تثبيت {0} (الإصدار {1})", + "Images": "الصور", + "Identify": "التعرف على الوسائط", + "HttpsRequiresCert": "لتمكين الاتصالات الآمنة ، ستحتاج إلى توفير شهادة SSL موثوقة ، مثل Letsencrypt. يرجى إما تقديم شهادة أو تعطيل الاتصالات الآمنة.", + "HeaderServerAddressSettings": "إعدادات عنوان السيرفر", + "HeaderRemoteAccessSettings": "إعدادات الوصول عن بعد", + "HeaderKeepSeries": "حافظ على السلسلة", + "HeaderKeepRecording": "استمر في التسجيل", + "HeaderIdentifyItemHelp": "أدخل معيار بحث واحد أو أكثر. إزالة المعايير لزيادة نتائج البحث.", + "HeaderHttpsSettings": "إعدادات HTTPS", + "HeaderHome": "الصفحة الرئيسية", + "HeaderFetcherSettings": "إعدادات الجلب", + "HeaderFavoritePlaylists": "قوائم التشغيل المفضلة", + "HeaderFavoriteVideos": "مقاطع الفيديو المفضلة", + "HeaderFavoritePeople": "أناس مفضلين", + "HeaderFavoriteMovies": "الأفلام المفضلة", + "HeaderFavoriteBooks": "الكتب المفضلة", + "HeaderExternalIds": "المعرفات الخارجية:", + "HeaderEnabledFieldsHelp": "قم بإلغاء تحديد حقل لقفله ومنع تغيير بياناته.", + "HeaderEnabledFields": "الحقول الممكّنة", + "HeaderEditImages": "تحرير الصور", + "HeaderDVR": "DVR", + "HeaderDownloadSync": "تنزيل ومزامنة", + "HeaderDetectMyDevices": "كشف أجهزتي", + "HeaderDeleteItems": "حذف العناصر", + "HeaderContinueListening": "استمر في الاستماع", + "HeaderConfigureRemoteAccess": "إعدادات الوصول عن بعد", + "HeaderChapterImages": "صور الفصل", + "HeaderCancelSeries": "إلغاء السلسلة", + "HeaderCancelRecording": "إلغاء التسجيل", + "HeaderBlockItemsWithNoRating": "حظر العناصر التي لا تحتوي على معلومات تصنيف أو لم يتم التعرف عليها:", + "HeaderAudioBooks": "الكتب الصوتية", + "HeaderAppearsOn": "يظهر على", + "ApiKeysCaption": "قائمة مفاتيح API الممكّنة حاليًا", + "HeaderAddToPlaylist": "أضف إلى قائمة التشغيل", + "HeaderAddToCollection": "أضف إلى المجموعة", + "HDPrograms": "برامج HD", + "Guide": "الدليل", + "GuestStar": "النجم الضيف", + "GroupVersions": "إصدارات المجموعة", + "GroupBySeries": "تجميع حسب السلسلة", + "Genre": "نوع أدبي", + "General": "الاعدادات العامة", + "FormatValue": "صيغة الملف: {0}", + "Filters": "مرشحات", + "File": "ملف", + "FetchingData": "يتم تنزيل بيانات إضافية", + "Features": "الميزات", + "Favorite": "المفضلة", + "Extras": "مواسم إضافية", + "ErrorDeletingItem": "حدث خطأ في حذف العنصر من سيرفر Jellyfin. يرجى التحقق من أن سيرفر Jellyfin لديه حق الوصول للكتابة إلى مجلد الوسائط وحاول مرة أخرى.", + "Episode": "حلقة", + "EnableThemeVideosHelp": "قم بتشغيل الفيديوهات الرئيسية في الخلفية أثناء تصفح المكتبة.", + "EnableThemeVideos": "الفيديوهات الرئيسية", + "EnableThemeSongsHelp": "قم بتشغيل اللحن الرئيسي في الخلفية أثناء تصفح المكتبة.", + "EnableThemeSongs": "اللحن الرئيسي", + "EnableStreamLoopingHelp": "قم بتمكين هذا إذا كانت عمليات البث المباشر تحتوي فقط على بضع ثوان من البيانات وتحتاج إلى إعادة طلب مستمر. قد يؤدي تمكين هذا عندما لا تكون هناك حاجة إلى مشاكل.", + "EnableStreamLooping": "تكرار البث المباشر", + "EnableHardwareEncoding": "تمكين تشفير الأجهزة", + "EnableExternalVideoPlayersHelp": "سيتم عرض قائمة مشغل خارجي عند بدء تشغيل الفيديو.", + "EnableExternalVideoPlayers": "مشغلات الفيديو الخارجية", + "EnableDisplayMirroring": "اعرض شاشتك على شاشة أخرى", + "EnableColorCodedBackgrounds": "تصنيف الخلفيات حسب اللون", + "EnableCinemaMode": "وضع السينما", + "EnableBackdropsHelp": "اعرض الخلفيات في خلفية بعض الصفحات أثناء تصفح المكتبة.", + "EnableBackdrops": "الخلفيات", + "DownloadsValue": "عدد التنزيلات {0}", + "Download": "تحميل", + "Down": "أسفل", + "DoNotRecord": "لا تسجل", + "DisplayModeHelp": "حدد نمط العرض الذي تريده للواجهة.", + "DisplayMissingEpisodesWithinSeasonsHelp": "يجب تمكين هذا أيضًا لمكتبات التلفزيون في إعدادات السيرفر.", + "DisplayMissingEpisodesWithinSeasons": "عرض الحلقات المفقودة خلال المواسم", + "DisplayInOtherHomeScreenSections": "عرض في أقسام الشاشة الرئيسية مثل أحدث الوسائط واستمر في المشاهدة", + "DisplayInMyMedia": "عرض على الشاشة الرئيسية", + "Display": "عرض", + "Dislike": "لم يعجبنى", + "ButtonSyncPlay": "SyncPlay", + "ExtraLarge": "كبير جدا", + "EnableNextVideoInfoOverlayHelp": "في نهاية الفيديو, عرض معلومات عن الفيديو القادم في قائمة التشغيل." } diff --git a/src/strings/be-by.json b/src/strings/be-by.json index c3675b310e6..28cf51bcde8 100644 --- a/src/strings/be-by.json +++ b/src/strings/be-by.json @@ -19,5 +19,6 @@ "ThisWizardWillGuideYou": "Гэты памочнік правядзе вас праз усе фазы ўстаноўкі і налады. Спачатку абярыце упадабаную мову.", "UserProfilesIntro": "У Jellyfin існуе ўбудаваная падтрымка для карыстальніцкіх профіляў, дазваляючы кожнаму карыстальніку валодаць сваімі ўласнымі параметрамі адлюстравання, станам прайгравання і кіраваннем ўтрымання.", "WelcomeToProject": "Пачатак працы ў Jellyfin!", - "WizardCompleted": "Гэта ўсё, што нам трэба зараз. Jellyfin пачынае збіраць звесткі аб вашай медыятэцы. Азнаёмцеся пакуль з некаторымі нашымі праграмамі, а затым націсніце Гатова, каб праглядзець Инфопанель сервера." + "WizardCompleted": "Гэта ўсё, што нам трэба зараз. Jellyfin пачынае збіраць звесткі аб вашай медыятэцы. Азнаёмцеся пакуль з некаторымі нашымі праграмамі, а затым націсніце Гатова, каб праглядзець Инфопанель сервера.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index e6aaa30fb16..71fdf80e323 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -10,7 +10,7 @@ "All": "Всички", "AllLibraries": "Всички библиотеки", "Art": "Картина", - "Artists": "Изпълнители", + "Artists": "Артисти", "AttributeNew": "Нови", "Audio": "Звук", "Auto": "Автоматично", @@ -86,17 +86,17 @@ "ChannelAccessHelp": "Изберете каналите, които да споделите с потребителя. Администраторите ще могат да редактират всички канали, използвайки управлението на метаданни.", "Collections": "Колекции", "ColorSpace": "Цветово пространство", - "CommunityRating": "Обществена ощенка", + "CommunityRating": "Рейтинг на общността", "Composer": "Съчинител", "ConfirmDeleteImage": "Изтриване на изображението?", - "ContinueWatching": "Продължаване на гледането", + "ContinueWatching": "Продължи гледането", "Continuing": "Продължаващо", "CriticRating": "Оценка на критиците", "DateAdded": "Дата на добавяне", "DatePlayed": "Дата на пускане", "DeathDateValue": "Починал/а на: {0}", "Default": "По подразбиране", - "Delete": "Изтриване", + "Delete": "Премахване", "DeleteMedia": "Изтриване на медията", "Desktop": "Работен плот", "DeviceAccessHelp": "Това се отнася само за устройства, които могат да бъдат различени и няма да попречи на достъп от мрежов четец. Филтрирането на потребителски устройства ще предотврати използването им докато не бъдат одобрени тук.", @@ -277,7 +277,7 @@ "Identify": "Разпознаване", "Images": "Изображения", "ImportMissingEpisodesHelp": "Ако е активирано, информация за липсващи епизоди ще бъде добавена в базата данни на Jellyfin и ще бъде показвана заедно със сезони и серии. Това може да доведе до значително по-дълго сканиране на библиотеката.", - "InstallingPackage": "Инсталиране на {0}", + "InstallingPackage": "Инсталиране на {0} на версия {1})", "InstantMix": "Пускане на подобни", "Label3DFormat": "Триизмерен формат:", "LabelAirDays": "Дни на излъчване:", @@ -302,10 +302,10 @@ "LabelCountry": "Държава:", "LabelCriticRating": "Оценка на критиците:", "LabelCurrentPassword": "Текуща парола:", - "LabelCustomCertificatePath": "Път към потребителския сертификат:", - "LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12, съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.", + "LabelCustomCertificatePath": "Ръчно задаване на пътя към SSL сертификата:", + "LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12 (PKCS #12), съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.", "LabelCustomCss": "CSS по избор:", - "LabelCustomCssHelp": "Използвайте собствен CSS към уеб интерфейса.", + "LabelCustomCssHelp": "Добавете собствен стил към уеб-интерфейса.", "LabelCustomDeviceDisplayName": "Показвано име:", "LabelCustomRating": "Оценка по избор:", "LabelDashboardTheme": "Облик на сървърното табло:", @@ -333,7 +333,7 @@ "LabelEnableDlnaPlayTo": "Включване на функцията \"възпроизвеждане с ДЛНА\"", "LabelEnableDlnaPlayToHelp": "Засичане на устройства в мрежата ви и предлагане на възможност за дистанционно управление.", "LabelEnableDlnaServer": "Включване на ДЛНА-сървър", - "LabelEnableDlnaServerHelp": "Разрешава на UPnP устройства в мрежата да разглеждат и пускат Jellyfin съдържание.", + "LabelEnableDlnaServerHelp": "Разрешава на UPnP устройства в мрежата да разглеждат и пускат Джелифин съдържание.", "LabelEnableRealtimeMonitor": "Активиране на наблюдение в реално време", "LabelEnableRealtimeMonitorHelp": "Промените ще бъдат обработени веднага, на поддържани файлови системи.", "LabelEpisodeNumber": "Номер на епизода:", @@ -348,20 +348,20 @@ "LabelHomeNetworkQuality": "Качество на домашната мрежа:", "LabelHomeScreenSectionValue": "Раздел {0} на началния екран:", "LabelHttpsPort": "Локален HTTPS порт:", - "LabelHttpsPortHelp": "TCP портът на който HTTPS сървърът на Jellyfin трябва да се закачи.", + "LabelHttpsPortHelp": "TCP портът на който HTTPS сървърът на Джелифин трябва да се закачи.", "LabelImageType": "Вид изображение:", "LabelInternetQuality": "Качество на интернетната връзка:", "LabelKodiMetadataDateFormat": "Формат на датата на издаване:", "LabelKodiMetadataUserHelp": "Разрешете това, за да запазите данните за гледанията във файлове Nfo за употреба от други програми.", "LabelLanguage": "Език:", "LabelLocalHttpServerPortNumber": "Локален HTTP порт:", - "LabelLocalHttpServerPortNumberHelp": "TCP портът на който HTTP сървърът на Jellyfin трябва да се закачи.", + "LabelLocalHttpServerPortNumberHelp": "TCP портът на който HTTP сървърът на Джелифин трябва да се закачи.", "LabelLogs": "Журнали:", - "LabelManufacturer": "Производител", + "LabelManufacturer": "Производител:", "LabelManufacturerUrl": "Адрес на производителя", "LabelMaxBackdropsPerItem": "Максимален брой фонове на медия:", "LabelMaxParentalRating": "Максимално допустима родителска оценка:", - "LabelMaxResumePercentage": "Макс процент за продължение:", + "LabelMaxResumePercentage": "Максимален процент за продължение:", "LabelMaxScreenshotsPerItem": "Максимален брой снимки на екрана на медия:", "LabelMaxStreamingBitrate": "Максимално качество на излъчване:", "LabelMetadata": "Метаданни:", @@ -369,9 +369,9 @@ "LabelMetadataPath": "Път към метаданните:", "LabelMetadataPathHelp": "Задайте място по избор за свалени картини и метаданни.", "LabelMinBackdropDownloadWidth": "Минимална широчина на сваления фон:", - "LabelMinResumeDuration": "Мин време за продължение (секунди):", - "LabelMinResumePercentage": "Мин процент за продължение:", - "LabelMinResumePercentageHelp": "Заглавията се считат за непускани ако бъдат спрени преди това време.", + "LabelMinResumeDuration": "Минимално време за продължение:", + "LabelMinResumePercentage": "Минимален процент за продължение:", + "LabelMinResumePercentageHelp": "Заглавията се считат ,че не са възпроизведени ако бъдат спрени преди този момент.", "LabelMinScreenshotDownloadWidth": "Минимална широчина на свалената снимка на екрана:", "LabelModelDescription": "Описание на модела", "LabelModelName": "Модел", @@ -443,7 +443,7 @@ "LabelTimeLimitHours": "Времево ограничение (часове):", "LabelTitle": "Заглавие:", "LabelTranscodingAudioCodec": "Звуков кодек:", - "LabelTranscodingTempPathHelp": "Тази папка съдържа работни файлове използвани от транскодера. Задайте място по избор или оставете празно за мястото по подразбиране.", + "LabelTranscodingTempPathHelp": "Посочете персонализиран път за файлове,които е необходимо да бъдат транскодирани и доставени на клиентите. Оставете празно ,за да се използва мястото по подразбиране.", "LabelTranscodingVideoCodec": "Видеокодек:", "LabelTriggerType": "Тип на спусъка:", "LabelType": "Вид:", @@ -512,7 +512,7 @@ "News": "Новини", "NoNextUpItemsMessage": "Нищо не е намерено. Започнете да гледате вашите предавания!", "NoSubtitleSearchResultsFound": "Няма намерени резултати.", - "NoSubtitles": "Без субтитри", + "NoSubtitles": "Без", "None": "Нищо", "Normal": "Нормален", "NumLocationsValue": "{0} папки", @@ -583,7 +583,7 @@ "OptionMissingEpisode": "Липсващи епизоди", "OptionMonday": "Понеделник", "OptionNameSort": "Име", - "OptionNew": "Нов...", + "OptionNew": "Нов…", "OptionNone": "Нищо", "OptionOnAppStartup": "Като се стартира приложението", "OptionOnInterval": "През интервал", @@ -640,7 +640,7 @@ "Refresh": "Опресняване", "RefreshMetadata": "Опресняване на метаданните", "ReleaseDate": "Дата на издаване", - "RememberMe": "Запомняне на данните", + "RememberMe": "Запомни ме", "RemoveFromCollection": "Премахване от колекцията", "RemoveFromPlaylist": "Премахване от списъка", "Repeat": "Повтаряне", @@ -711,7 +711,7 @@ "TabMusicVideos": "Музикални клипове", "TabMyPlugins": "Моите приставки", "TabNetworks": "Мрежи", - "TabNfoSettings": "Формат за метаданни .nfo", + "TabNfoSettings": "Формат за метаданни NFO", "TabNotifications": "Известия", "TabOther": "Други", "TabParentalControl": "Родителски контрол", @@ -724,7 +724,7 @@ "TabProfiles": "Профили", "TabRecordings": "Записи", "TabResponses": "Отговори", - "TabResumeSettings": "Настройки за продължаване", + "TabResumeSettings": "Възобнови", "TabScheduledTasks": "Планирани задачи", "TabSeries": "Сериали", "TabServer": "Сървър", @@ -786,7 +786,7 @@ "AllowMediaConversion": "Разрешаване на медийни преобразувания", "AllLanguages": "Всички езици", "AllEpisodes": "Всички епизоди", - "AllComplexFormats": "Всички комплексни формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и т.н.)", + "AllComplexFormats": "Всички общи формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и др. )", "AllChannels": "Всички канали", "Alerts": "Известия", "AdditionalNotificationServices": "Разгледайте каталога с добавки за допълнителни услуги за известяване.", @@ -834,5 +834,737 @@ "AllowOnTheFlySubtitleExtraction": "Позволява моментално извличане на поднадписи", "AllowHWTranscodingHelp": "Позволява на тунера да прекодира моментално. Това може да помогне за редуциране на прекодирането от сървъра.", "AddItemToCollectionHelp": "Добавяне към колекция чрез търсенето им и използване на дясно-щракване с мишката или контекстното меню.", - "Absolute": "Aбсолютен" + "Absolute": "Aбсолютен", + "LabelLanNetworks": "Локални мрежи:", + "LabelKodiMetadataSaveImagePathsHelp": "Това е препоръчително, ако наименованието на изображенията не са съобразени с изискванията на Kodi.", + "LabelKodiMetadataSaveImagePaths": "Записване на пътеките към изображенията в nfo файловете", + "LabelChannels": "Канали:", + "DropShadow": "Сянка", + "Raised": "Повишено", + "OptionResElement": "рес. елемент", + "ButtonChangeServer": "Смяна на сървър", + "ButtonAddImage": "Добавяне на изображение", + "BrowsePluginCatalogMessage": "За да видите наличните добавки, прегледайте каталога с добавките.", + "Box": "Кутия", + "AlwaysPlaySubtitlesHelp": "Поднадписите, съвпадащи с езика от настройките, ще се зареждат, независимо от езика на аудио то.", + "BookLibraryHelp": "Поддържат се аудио книги такива съдържащи текст. Проверете ръководството за наименуване {1} на книги {0}.", + "Blacklist": "Списък с блокирани", + "BirthLocation": "Месторождение", + "Banner": "Банер", + "AspectRatio": "Съотношение", + "AskAdminToCreateLibrary": "Помолете администратора за създаване на библиотека.", + "Ascending": "Възходящо", + "AsManyAsPossible": "Колкото е възможно повече", + "Artist": "Артист", + "AroundTime": "Около {0}", + "Anytime": "По всяко време", + "AnyLanguage": "Който и да е език", + "AlwaysPlaySubtitles": "Постоянно изпълнение", + "AllowRemoteAccessHelp": "Ако не е маркирано, всеки отдалечен достъп ще бъде блокиран.", + "AllowRemoteAccess": "Позволяване на отдалечен достъп до този Jellyfin сървър.", + "AllowFfmpegThrottling": "Подтискане на прекодирането", + "AllowMediaConversionHelp": "Даване или отнемане на права за функциите за конвертиране на медия.", + "AlbumArtist": "Изпълнител", + "Album": "Албум", + "ClientSettings": "Клиентски настройки", + "ChannelNumber": "Номер на канала", + "ChannelNameOnly": "Само {0} канал", + "CancelSeries": "Откажи сериите", + "CancelRecording": "Откажи записа", + "ButtonSplit": "Раздели", + "ButtonResetEasyPassword": "Нулиране на бързия ПИН код", + "ButtonRevoke": "Отмени", + "ButtonEditOtherUserPreferences": "Редакция на потребителския профил, изображение и лични предпочитания.", + "BoxRear": "Комплект (стар)", + "BoxSet": "Комплект", + "AuthProviderHelp": "Избор на доставчик на услуга за Автентификация, която ще се използва за автентификация на потребителската парола.", + "AllowedRemoteAddressesHelp": "Списък с IP адреси или IP/маска записи, разделени със запетая, които ще имат отдалечен достъп. Ако полето не е попълнено всички адреси ще имат отдалечен достъп.", + "BurnSubtitlesHelp": "Определя дали сървърът трябва да записва субтитри във видеофайлове при прекодиране. Избягването на това значително ще подобри производителността. Изберете Auto, за да запишете формати, базирани на изображения (VOBSUB, PGS, SUB, IDX) и някои ASS или SSA субтитри.", + "AllowFfmpegThrottlingHelp": "Когато прекодирането или запазването на видео стигне достатъчно далеч напред от текущата позиция за възпроизвеждане, поставете на пауза процеса, така ще се изразходват по-малко ресурси. Това е най-полезно, когато се гледа, без да се търси често из видеото. Изключете това, ако имате проблеми с възпроизвеждането.", + "AllowOnTheFlySubtitleExtractionHelp": "Вградените субтитри могат да бъдат извлечени от видеофайловете и прехвърлени към клиентите като обикновен текст, за да се предотврати транскодирането на видеото. В някои системи това може да отнеме много време и да спре възпроизвеждането на видео по време на процеса на извличане. Деактивирайте това, за да има вградени субтитри, записани с видео кодиране, когато те не се поддържат от клиентското устройство.", + "CinemaModeConfigurationHelp": "Режимът на кино носи театрално изживяване направо във вашата всекидневна с възможност за пускане на трейлъри и персонализирани интродукции преди основния филм.", + "ChangingMetadataImageSettingsNewContent": "Промените в настройките за изтегляне на метаданни или изображения ще се прилагат само за ново съдържание, добавено към вашата библиотека. За да приложите промените към съществуващите заглавия, ще трябва да обновите метаданните им ръчно.", + "DefaultMetadataLangaugeDescription": "Това са настройки по подразбиране и могат да се променят на база библиотека.", + "DefaultErrorMessage": "Възникна грешка при изпълнение на заявката. Моля опитайте по-късно.", + "CustomDlnaProfilesHelp": "Създаване на персонализиран профил за целево устройство или заменяне на системния профил.", + "CopyStreamURL": "Копиране URL на стрийма", + "CopyStreamURLError": "Възникна грешка при копиране на URL.", + "CopyStreamURLSuccess": "URL се копира успешно.", + "Connect": "Свързване", + "ConfirmEndPlayerSession": "Искате ли да изключите Jellyfin на {0}?", + "ConfirmDeletion": "Потвърждаване на изтриването", + "ConfirmDeleteItem": "Изтриването на елемента ще го премахне едновременно от файловата система и библиотеката. Сигурни ли сте, че искате да продължите?", + "ConfigureDateAdded": "Конфигурацията на добавянето на датата се определя в панела на Jellyfin сървъра в секцията за настройка на библиотека", + "ConfirmDeleteItems": "Изтриването на елементите ще ги премахне едновременно от файловата система и библиотеката. Сигурни ли сте, че искате да продължите?", + "ColorTransfer": "Предаване на цвета", + "ColorPrimaries": "Основни цветове", + "DeleteUserConfirmation": "Сигурнили сте че искате да премахнете този потребител?", + "DeleteUser": "Премахване на потребител", + "DeleteImageConfirmation": "Сигурнили сте че искате да премахнете това Изображение?", + "DeleteImage": "Премахване на Исображение", + "ButtonTogglePlaylist": "Списък с изпълнения", + "ButtonToggleContextMenu": "Повече", + "ErrorSavingTvProvider": "Има проблем със запазването на ТВ доставчика.Убедете се ,че е достъпен и опитайте отново.", + "ErrorPleaseSelectLineup": "Моля изберете списък и опитайте отново.Ако няма налични списъци се убедете ,че името,паролата и пощенския код са точни.", + "ErrorMessageStartHourGreaterThanEnd": "Времето за край трябва да бъде по-голямо от началното време.", + "ErrorGettingTvLineups": "Има проблем при опита да бъдат свалени списъци с ТВ.Убедете се ,че информацията е правилна и опитайте отново.", + "ErrorDeletingItem": "Има проблем при опита да бъде изтрит файла от сървъра.Убедете се ,че сървъра има право да трие папки и опитайте отново.", + "ErrorAddingXmlTvFile": "Има проблем при достъпа на XMLTV файла.Уверете се ,че е наличен и пробвайте отново.", + "ErrorAddingTunerDevice": "Възникна проблем при добавянето на тунера. Уверете се ,че устройството е достъпно и пробвайте отново.", + "ErrorAddingMediaPathToVirtualFolder": "Има проблем при добавянето на пътя.Моля уверете се ,че е валиден и сървъра има достъп до папката.", + "Episode": "Епизод", + "EnableDetailsBannerHelp": "Покажи картинка с банер в горната част на страницата с детайли.", + "EnableDetailsBanner": "Банер с подробности", + "EnableThemeVideosHelp": "Пускай тематични видеа на заден план ,докато се разглежда библиотеката.", + "EnableThemeVideos": "Тематични видеа", + "EnableThemeSongsHelp": "Пускай тематична музика ,докато се разглежда библиотеката.", + "EnableStreamLoopingHelp": "Включи това ,ако поточното видео се предава на кратки интервали и е необходимо да се изпращат заявки постоянно.Включването на тази опция без нужда може да породи проблеми.", + "EnableStreamLooping": "Автоматично повторение на поточни видеа", + "EnablePhotosHelp": "Снимките ще бъдат разпознати и показани заедно с другите медийни файлове.", + "EnablePhotos": "Покажи снимки", + "EnableNextVideoInfoOverlayHelp": "В края на видеото покажи информация за следващия файл от текущия списък.", + "EnableNextVideoInfoOverlay": "Покажи информация за следващото видео по време на възпроизвеждане", + "EnableHardwareEncoding": "Включи хардуерно енкодиране", + "EnableExternalVideoPlayersHelp": "Меню от външен плеър ще бъде показано при стартиране на възпроизвеждането.", + "EnableDisplayMirroring": "Дублиране на дисплей", + "EnableColorCodedBackgrounds": "Цветни \"декори\"", + "EnableBackdropsHelp": "Показвай \"декори\" на заден план за някои страници ,докато се разглежда библиотеката.", + "DrmChannelsNotImported": "Канали със DRM няма да бъдат импортирани.", + "Down": "Долу", + "DoNotRecord": "Не записвай", + "DisplayModeHelp": "Изберете какво оформление желаете за интерфейса.", + "DisplayMissingEpisodesWithinSeasonsHelp": "Това трябва да бъде включено и за ТВ библиотеки в настройките на сървъра.", + "DisplayMissingEpisodesWithinSeasons": "Показвай липсващите епизоди от сезона", + "DisplayInOtherHomeScreenSections": "Покажи на главната страница (като \"последно добавени\" и \"продължи да гледаш\")", + "DisplayInMyMedia": "Покажи на главната страница", + "Disconnect": "Прекъсване", + "Disabled": "Изключено", + "DirectStreaming": "Директно възпроизвеждане", + "DirectStreamHelp2": "Директното възпроизвеждане на файла използва минимална процесорна мощност без загуба на качество.", + "DirectStreamHelp1": "Файлът е съвместим с устройството по отношение на резолюция и тип (H.264, AC3 и т.н.), но контейнера е несъвместим (mkv, avi, wmv, т.н.).Файлът ще бъде \"препакетиран\" преди да се възпроизведе от устройството.", + "DirectPlaying": "Директен метод на възпроизвеждане", + "DetectingDevices": "Откриване на устройства", + "Descending": "Намаляващ", + "Depressed": "Понижен", + "DeleteDeviceConfirmation": "Сигурни ли сте ,че искате да изтриете устройството? Ще се появи отново ,когато потребителят се впише с него.", + "DeinterlaceMethodHelp": "Избери типа деинтерлейсинг ,когато е необходимо транскодиране на подобно съдържание.", + "DefaultSubtitlesHelp": "Субтитрите са заредени според настройките зададени в метадатата на видеофайла.Когато има повече от едни субтитри се зарежда първо зададените в настройките.", + "HeaderFavoriteVideos": "Любими видеа", + "HeaderFavoritePeople": "Любими хора", + "HeaderFavoriteMovies": "Любими филми", + "HeaderFavoriteBooks": "Любими книги", + "HeaderExternalIds": "Външни идентификатори:", + "HeaderEpisodes": "Епизоди", + "HeaderEnabledFieldsHelp": "Махни отметката ,за да го заключиш и да предотвратиш неговата промяна.", + "HeaderDVR": "DVR (Цифрово записващо устройство)", + "HeaderDirectPlayProfileHelp": "Добави профили за директно възпроизвеждане ,за да се укаже кои формати може да възпроизвежда устройството.", + "HeaderDeleteTaskTrigger": "Изтрий \"пусковият механизъм\" за задачи", + "HeaderDeleteProvider": "Изтрий доставчик", + "HeaderDeleteItems": "Изтрий елемент", + "HeaderDeleteItem": "Изтрий елемент", + "HeaderDeleteDevice": "Изтрий устройство", + "HeaderDefaultRecordingSettings": "Стандартни настройки за запис", + "HeaderDateIssued": "Дата на издаване", + "HeaderContainerProfileHelp": "Профилите на различните контейнери показват ограниченията на устройството ,когато се възпроизвеждат определени формати.Ако ограничението е валидно - медията ще бъде транскодирана дори дадения формат да е конфигуриран за директно възпроизвеждане.", + "HeaderConnectionFailure": "Проблем при свързване", + "HeaderConnectToServer": "Свържи се със сървър", + "HeaderConfirmRevokeApiKey": "Отмяна на API ключа", + "HeaderConfirmProfileDeletion": "Потвърди изтриването на профила", + "HeaderConfirmPluginInstallation": "Потвърди инсталирането на плъгина", + "HeaderConfigureRemoteAccess": "Настройка на отдалечения достъп", + "HeaderCodecProfileHelp": "Профилите на различните кодеци показват ограниченията на устройството при тяхното възпроизвеждане.Ако ограничението е валидно - медията ще бъде транскодирана дори дадения кодек да е конфигуриран за директно възпроизвеждане.", + "HeaderChapterImages": "Картинки към раздела", + "HeaderChannelAccess": "Достъп до канали", + "HeaderCancelSeries": "Отмени сериал", + "HeaderCancelRecording": "Отмени запис", + "HeaderBranding": "Оформление", + "HeaderBlockItemsWithNoRating": "Забрани достъпа на елементи без информация за рейтинг:", + "HeaderAudioBooks": "Аудио книги", + "HeaderAppearsOn": "Фигурира в", + "ApiKeysCaption": "Списък с работещите в момента API ключове", + "HeaderApiKeysHelp": "Външните програми се налага да имат API ключ ,за да комуникират правилно със сървъра.Такива се издават при вписването в сървъра или чрез ръчно предоставяне.", + "HeaderAllowMediaDeletionFrom": "Позволи изтриването на медия от", + "HeaderAlert": "Предупреждение", + "HeaderAccessScheduleHelp": "Създай разписание за достъп ,за да го ограничиш до определени часове.", + "HeaderAccessSchedule": "Разписание за достъп", + "HardwareAccelerationWarning": "Включването на хардуерното ускорение може да предизвика нестабилност в някои случаи.Уверете се ,че операционната ви система и видео драйверите са актуални.Ако имате проблеми с възпроизвеждането при включена настройка е необходимо да я изключите.", + "EncoderPresetHelp": "Избери по-бърза стойност ,за да се подобри производителността или по-бавна стойност ,за да се подобри качеството.", + "H264CrfHelp": "Факторът \"постоянна скорост на предаване\" (CRF) е настройката по подразбиране на x264 енкодера.Може да определите стойност между 0 и 51, където най-ниската стойност ще даде по-добро качество (като се вземе предвид,че крайния размер на файла ще се увеличи).Препоръчват се стойности между 18 и 28.Стойността по подразбиране за x264 е 23 ,така че може да я имате за някакъв ориентир.", + "GuideProviderSelectListings": "Избери списъци", + "GroupBySeries": "Групирай по сериали", + "General": "Общи", + "FileReadError": "Има проблем при четенето на файла.", + "FileReadCancelled": "Четенето на файла е преустановено.", + "FileNotFound": "Файлът не е намерен.", + "FetchingData": "Извличане на допълнителни данни", + "Features": "Характеристики", + "FFmpegSavePathNotFound": "Не е намерен FFmpeg в зададения път.FFprobe е също задължителен и трябва да се намира в същата папка.Двете програми обикновено вървят заедно ,когато ги теглите.Уверете се ,че пътя е правилен и опитайте отново.", + "ExtractChapterImagesHelp": "Извличането на снимки от раздел ще позволи на устройствата да възпроизвеждат графични кадри при избора на меню.Този процес може бавен,ресурсно натоварващ и може да е необходимо да се заемат няколко гигабайта дисково пространство.Процесът се извършва при откриването на нови файлове или може да бъде зададен , като планирана задача при слаба активност на сървъра.Това може да бъде конфигурирано в раздела с планирани задачи.Не е препоръчително да се задава изпълнението на такава задача в натоварените часове на сървъра.", + "HttpsRequiresCert": "За да разрешите \"сигурни връзки\" е необходимо да притежавате доверен издател на сертификати ,като Let's Encrypt.Моля въведете ,ако притежавате такъв или изключете \"сигурни връзки\".", + "HeaderXmlSettings": "Настройки на XML", + "HeaderXmlDocumentAttributes": "Атрибути на XML документа", + "HeaderXmlDocumentAttribute": "Атрибут на XML документа", + "HeaderUpcomingOnTV": "Скоро по ТВ", + "HeaderTypeText": "Въведи текст", + "HeaderTypeImageFetchers": "{0} Извличане на картини", + "HeaderTuners": "Тунери", + "HeaderTranscodingProfileHelp": "Добави профили за транскодиране ,за да се види кои формати ще се използват ,когато е необходимо транскодиране.", + "HeaderThisUserIsCurrentlyDisabled": "Този потребител в момента е блокиран", + "HeaderSyncPlayEnabled": "Включване на \"синхронизирано възпроизвеждане\"", + "HeaderSyncPlaySelectGroup": "Присъединяване към група", + "HeaderSubtitleProfilesHelp": "Профилите за субтитри показват форматите поддържани от устройството.", + "HeaderSubtitleProfiles": "Профили за субтитри", + "HeaderSubtitleProfile": "Профил за субтитри", + "HeaderSubtitleDownloads": "Сваляне на субтитри", + "HeaderStopRecording": "Спри запис", + "HeaderSpecialEpisodeInfo": "Информация за специалните епизоди", + "HeaderShutdown": "Изключване", + "HeaderServerAddressSettings": "Настройки за адреса на сървъра", + "HeaderSeriesStatus": "Състояние на сериала", + "HeaderSeriesOptions": "Настройки на сериала", + "HeaderSelectTranscodingPathHelp": "Търси или въведи ръчно пътя ,където искаш да се съхраняват временните файлове при транскодиране.Папката трябва да има права за запис.", + "HeaderSelectTranscodingPath": "Избери папка за временното транскодиране на файлове", + "HeaderSelectServerCachePathHelp": "Търси или въведи ръчно пътя до временните файлове на сървъра.Папката трябва да има права за запис.", + "HeaderSelectServerCachePath": "Избери папка за временните файлове на сървъра", + "HeaderSelectServer": "Избери сървър", + "HeaderSelectMetadataPathHelp": "Търси или въведи ръчно пътя ,където искаш да се съхраняват метаданните.Папката трябва да има права за запис.", + "HeaderSelectMetadataPath": "Избери папка със метаданни", + "HeaderSelectCertificatePath": "Избери папка със сертификат", + "HeaderRestartingServer": "Рестартиране на сървъра", + "HeaderResponseProfileHelp": "Профилите за комуникация дават възможност да се настрои типът информация ,която се изпраща към устройството при възпроизвеждането на определени типове медия.", + "HeaderRemoveMediaLocation": "Премахни папката с медия", + "HeaderRemoteAccessSettings": "Настройки за отдалечен достъп", + "HeaderRecordingPostProcessing": "Последваща обработка на записи", + "HeaderRecordingOptions": "Настройки за запис", + "HeaderPluginInstallation": "Инсталиране на добавка", + "HeaderPlaybackError": "Грешка при възпроизвеждане", + "HeaderPlayback": "Възпроизвеждане на медия", + "HeaderPinCodeReset": "Зануляване на пин код", + "HeaderPhotoAlbums": "Фото албум", + "HeaderPasswordReset": "Зануляване на парола", + "HeaderOtherItems": "Други елементи", + "HeaderNextVideoPlayingInValue": "Следващото видео ще се възпроизведе след {0}", + "HeaderNavigation": "Навигация", + "HeaderLiveTvTunerSetup": "Настройка на тв тунера", + "HeaderKodiMetadataHelp": "За да включиш или изключиш използването на NFO метаданните влез в настройките за библиотеки на сървъра и намери секцията за съхранение.", + "HeaderKeepSeries": "Запази сериалите", + "HeaderKeepRecording": "Запази записите", + "HeaderItems": "Елементи", + "HeaderImageOptions": "Настройки на картини", + "HeaderIdentifyItemHelp": "Въведете един или повече критерии за търсене.Премахнете ,ако искате да увеличите резултатите при търсенето.", + "HeaderIdentificationHeader": "Идентификационен хедър", + "HeaderIdentificationCriteriaHelp": "Въведете пони един критерии.", + "HeaderHttpsSettings": "HTTPS настройки", + "HeaderHttpHeaders": "HTTP Хедъри", + "HeaderHome": "Главна", + "HeaderFetcherSettings": "Настройки на програмата за изтегляне", + "HeaderFavoritePlaylists": "Любими списъци", + "LabelDeathDate": "Дата на смърт:", + "LabelDateAddedBehaviorHelp": "Ако е взета стойност от метаданните, тя винаги ще бъде използвана преди някоя от тези опции.", + "LabelDateAddedBehavior": "за ново съдържание се приема дата на добавяне:", + "LabelCustomDeviceDisplayNameHelp": "Добави ръчно име ,с което ще се показва устройството или остави това ,което е по подразбиране.", + "LabelCorruptedFrames": "Повредени кадри:", + "LabelCancelled": "Отменено", + "LabelCache": "Временни файлове:", + "LabelBurnSubtitles": "Вграждане на субтитри:", + "LabelBlockContentWithTags": "Блокирай елементи с етикети:", + "LabelBlastMessageIntervalHelp": "Определя продължителността в секунди при \"бомбардирането\" с активни съобщения.", + "LabelBlastMessageInterval": "Интервал на активните съобщения (в секунди)", + "LabelBitrate": "Битрейт:", + "LabelBirthYear": "Година на раждане:", + "LabelBirthDate": "Дата на раждане:", + "LabelBindToLocalNetworkAddressHelp": "Не задължително.Замени локалния IP адрес за \"закачане\" към http сървъра.Ако полето е празно сървъра ще \"закачи\" всички налични адреси.Промяната на тази стойност изисква рестарт на сървъра.", + "LabelBindToLocalNetworkAddress": "\"Закачи\" към локален мрежов адрес:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Автоматично обновявай метаданните от Интернет:", + "LabelAuthProvider": "Доставчик за идентификация:", + "LabelAudioSampleRate": "Честотна дискретизация на аудиото:", + "LabelAudioCodec": "Аудио кодек:", + "LabelAudioChannels": "Аудио канали:", + "LabelAudioBitrate": "Скорост на предаване на аудиото:", + "LabelAudioBitDepth": "Битова дълбочина на аудиото:", + "LabelAudio": "Аудио", + "LabelAppNameExample": "Примерно: Sickbeard, Sonarr", + "LabelAllowedRemoteAddressesMode": "Режим на филтъра за външни ИП адреси:", + "LabelAllowedRemoteAddresses": "Филтър за външни ИП адреси:", + "LabelAllowHWTranscoding": "Разреши хардуерно транскодиране", + "LabelAll": "Всички", + "LabelAlbumArtMaxWidthHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.", + "LabelAlbumArtMaxHeightHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Максимална височина на обложките за албуми:", + "LabelAlbumArtHelp": "PN се използва за обложки на албуми dlna:profileID attribute on upnp:albumArtURI.Някои устройства се нуждаят от определена стойност ,независимо от размера на картината.", + "LabelAirsBeforeSeason": "Ще бъде излъчен преди сезон:", + "LabelAirsBeforeEpisode": "Ще бъде излъчен преди епизод:", + "LabelAirsAfterSeason": "Ще бъде излъчен след сезон:", + "LabelAccessStart": "Начално време:", + "LabelAccessEnd": "Време на приключване:", + "LabelAccessDay": "Ден от седмицата:", + "LabelAbortedByServerShutdown": "(Прекъснато поради изключването на сървъра)", + "Kids": "Деца", + "Items": "Елементи", + "ItemCount": "{0} Елементи", + "ImportFavoriteChannelsHelp": "Ако е включено само канали ,които са отбелязани ,като любими на устройството ще бъдат добавени.", + "LabelFriendlyName": "Лесно име:", + "LabelFormat": "Формат:", + "LabelFolder": "Папка:", + "LabelFileOrUrl": "Файл или URL:", + "LabelExtractChaptersDuringLibraryScanHelp": "Генерирай изображения от различните раздели при импортиране на видеофайлове (по време на сканиране на библиотеката).В противен случай те ще бъдат извлечени при планиране на такава задача,което ще позволи сканирането на библиотеката да се извърши по-бързо.", + "LabelExtractChaptersDuringLibraryScan": "Извлечи изображения от разделите при сканиране на библиотеката", + "LabelBaseUrlHelp": "Добавя потребителска папка към адреса на съръвра.Например: http://example.com/<baseurl>", + "LabelBaseUrl": "Основен URL:", + "LabelEndDate": "Крайна дата:", + "LabelEnableSingleImageInDidlLimitHelp": "Някои устройства няма да го покажат правилно ,ако множество изображения са вградени в Didl.", + "LabelEnableSingleImageInDidlLimit": "Ограничи до едно вградено изображение", + "LabelEnableHttpsHelp": "Позволява на сървъра да \"слуша\" на предварително зададен HTTPS порт.Необходимо е да има настроен валиден сертификат ,за да работи правилно настройката.", + "LabelEnableHttps": "Включи HTTPS", + "LabelEnableHardwareDecodingFor": "Включи хардуерно декодиране за:", + "LabelEnableDlnaDebugLoggingHelp": "Създава големи журнали файлове и е редно да се използва само с цел отстраняване на проблеми.", + "LabelEnableBlastAliveMessagesHelp": "Включи ,ако сървъра не се открива правилно от други UPnP устройства в мрежата.", + "LabelEnableBlastAliveMessages": "\"Бомбардира\" активните съобщения", + "LabelEnableAutomaticPortMapHelp": "Автоматично пренасочва външните портове на рутера към локалните портове на сървъра чрез UPnP.Тази опция може да на работи при някои модели рутери или мрежови конфигурации.Промените влизат в сила само след рестарт на сървъра.", + "LabelEmbedAlbumArtDidlHelp": "Някои устройства предпочитат този метод за извличане на обложки за албуми.При други може да има проблем при възпроизвеждането ,ако опцията е включена.", + "LabelEasyPinCode": "Лесен пин код:", + "LabelDynamicExternalId": "{0} Ид:", + "LabelDroppedFrames": "Пропуснати кадри:", + "LabelDiscNumber": "Диск номер:", + "LabelDidlMode": "DIDL режим:", + "LabelDeinterlaceMethod": "Метод на деинтерлейсинг:", + "LabelDefaultUserHelp": "Определя коя потребителска библиотека ще се показва на свързаните устройства.Това може да бъде отменено за всяко отделно устройство чрез използване на профилите.", + "LabelDefaultUser": "Потребител по подразбиране:", + "LabelDefaultScreen": "Дисплей по подразбиране:", + "LabelPostProcessor": "Приложение за последваща обработка:", + "LabelPleaseRestart": "Промените ще влязат в сила след ръчно презареждане на уеб клиента.", + "LabelPlayMethod": "Метод на възпроизвеждане:", + "LabelPlayerDimensions": "Размери на плеъра:", + "LabelPlayer": "Плеър:", + "LabelPersonRoleHelp": "Пример: Шофьор на камион за сладолед", + "LabelPasswordRecoveryPinCode": "Пин код:", + "LabelPasswordResetProvider": "Услуга за нулиране на парола:", + "LabelParentNumber": "Родителски номер:", + "LabelOptionalNetworkPathHelp": "Ако папката е споделена във вашата мрежа,предоставянето на споделения път ще позволи приложения инсталирани на други устройства да имат достъп до медията директно.Например, {0} or {1}.", + "LabelNumber": "Номер:", + "LabelNotificationEnabled": "Включване на известие", + "LabelNewsCategories": "Категории новини:", + "LabelNightly": "Тестов", + "LabelStable": "Стабилна", + "LabelChromecastVersion": "Версия на Chromecast", + "LabelMusicStreamingTranscodingBitrateHelp": "Посочете максимален битрейт при поточно предаване на музика.", + "LabelMusicStreamingTranscodingBitrate": "Битрейт при транскодиране на музика:", + "LabelMoviePrefixHelp": "Ако има добавен префикс към филмовите заглавия го въведете тук ,за да може сървъра да го обработи правилно.", + "LabelMoviePrefix": "Префикс на филма:", + "LabelMovieCategories": "Филмови категории:", + "LabelMonitorUsers": "Следете активността от:", + "LabelMinResumeDurationHelp": "Най-краткото време в секунди позволяващо ви да запазите текущата позиция на възпроизвеждане и впоследствие да я възобновите.", + "LabelMethod": "Метод:", + "LabelMetadataSaversHelp": "Изберете форматите на файловете, в които да запишете метаданните си.", + "LabelMetadataSavers": "Хранилища на метаданни:", + "LabelMetadataReadersHelp": "Подреди по важност любимите си източници на локални метаданни.Първият намерен файл ще бъде прочетен.", + "LabelMetadataReaders": "Четци на метаданни:", + "LabelMetadataDownloadersHelp": "Включи и подреди по важност любимите си програми за изтегляне на метаданни.Тези с по-нисък ранг ще се използват само за попълване на липсваща информация.", + "LabelMessageTitle": "Заглавие на съобщението:", + "LabelMessageText": "Текст на съобщението:", + "LabelMaxStreamingBitrateHelp": "Определете максималния битрейт при излъчване.", + "LabelMaxResumePercentageHelp": "Приема се ,че файловете се възпроизведени до края ,ако се спре след този момент.", + "LabelMaxChromecastBitrate": "Качество на излъчване чрез Chromecast:", + "LabelMatchType": "Тип съвпадение:", + "LabelLoginDisclaimerHelp": "Съобщението ще се показва в долната част на страницата за вход.", + "LabelLoginDisclaimer": "Предупреждение при вход:", + "LabelLockItemToPreventChanges": "Заключи дадения елемент ,за да забраниш бъдещи промени", + "LabelLineup": "Редица:", + "LabelLibraryPageSizeHelp": "Настройва броя елементи ,които ще се показват в страницата на библиотеката.Въведете 0 ,за да забраните номерирането.", + "LabelLibraryPageSize": "Размер на страницата на библиотеката:", + "LabelKodiMetadataUser": "Запази данните за активността на потребителя в файл тип NFO за:", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Активирай подмяната на пътя за изображения ,като се използват настройките зададени от сървъра.", + "LabelKodiMetadataEnablePathSubstitution": "Активирай подмяната на пътя", + "LabelKodiMetadataEnableExtraThumbsHelp": "Когато се свалят изображения те могат да бъдат запазени и в двете полета extrathumbs и extrafanart за по-добра съвместимост с облиците на Коди.", + "LabelKodiMetadataEnableExtraThumbs": "Копирай extrafanart в полето extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Всички дати в NFO файловете ще бъдат анализирани в този формат.", + "LabelKidsCategories": "Детски категории:", + "LabelKeepUpTo": "Пази до:", + "LabelInNetworkSignInWithEasyPasswordHelp": "Използвай лесен пин код за вписване от потребителите в домашната мрежа.Основната ви парола ще се използва ,само когато сте вписвате отдалечено.Ако полето за пин код е празно няма да имате нужда да се вписвате с парола в домашната мрежа.", + "LabelInNetworkSignInWithEasyPassword": "Включи вписване в мрежата с лесен пин код", + "LabelImportOnlyFavoriteChannels": "Ограничи до канали обозначени ,като любими", + "LabelImageFetchersHelp": "Включете и подредете любимите ви услуги за извличане на изображения по важност.", + "LabelIconMaxWidthHelp": "Максимална резолюция на иконите показани чрез upnp:icon.", + "LabelIconMaxWidth": "Максимална ширина на икона:", + "LabelIconMaxHeightHelp": "Максимална резолюция на иконите показани чрез upnp:icon.", + "LabelIconMaxHeight": "Максимална височина на икона:", + "LabelHardwareAccelerationTypeHelp": "Хардуерното ускорение се нуждае от допълнителни настройки.", + "LabelEncoderPreset": "Шаблон за енкодиране чрез H264 и H265 :", + "LabelH264Crf": "Значение на CRF H264-кодиране:", + "LabelGroupMoviesIntoCollectionsHelp": "Когато се показват списъци с филми, филмите, принадлежащи към колекция, ще бъдат показани като един общ елемент.", + "LabelServerNameHelp": "Това е името ,което ще се ползва ,за да се разпознава сървъра и по подразбиране съвпада с името на компютъра.", + "LabelRefreshMode": "Режим на опресняване:", + "LabelRecordingPathHelp": "Посочете местоположението по подразбиране, за запазване на записи.Ако полето е празно ще се използва папката на сървъра.", + "LabelRecord": "Запис:", + "LabelReasonForTranscoding": "Причина за транскодиране:", + "LabelProtocolInfoHelp": "Стойността, която ще бъде използвана при отговор към GetProtocolInfo заявките от устройството.", + "LabelProtocolInfo": "Информация за протокола:", + "LabelPostProcessorArgumentsHelp": "Използвай {path},като път за записване на файла.", + "LabelPostProcessorArguments": "Аргументи на командния ред след обработка:", + "EnableBlurhashHelp": "Изображенията, които все още се зареждат, ще се показват чрез функцията\"размито запълване\"", + "EnableBlurhash": "Активиране на функцията \"размито запълване\" за изображения", + "UnsupportedPlayback": "Джелифин не може да дешифрира съдържание, защитено с DRM, но въпреки това цялото съдържание ще бъде обработено, включително защитените заглавия. Някои файлове могат да изглеждат напълно черни поради криптиране или други неподдържани функции, например интерактивни заглавия.", + "OnApplicationStartup": "При стартиране на приложението", + "EveryXHours": "На всеки {0} часа", + "EveryHour": "Всеки час", + "EveryXMinutes": "Всеки {0} минути", + "OnWakeFromSleep": "При \"събуждане от сън\"", + "WeeklyAt": "{0}сек в {1}", + "DailyAt": "Ежедневно в {0}", + "LastSeen": "Последно видян {0}", + "PersonRole": "като {0}", + "ListPaging": "{0}-{1} от {2}", + "WriteAccessRequired": "Джелифин сървъра изисква достъп с права за запис до тази папка. Моля, осигурете достъп с права за запис и опитайте отново.", + "PathNotFound": "Пътят не можа да бъде намерен. Моля, уверете се, че пътят е валиден и опитайте отново.", + "Yesterday": "Вчера", + "YadifBob": "YADIF х2", + "Yadif": "YADIF (Yet Another DeInterlacing Filter)", + "XmlTvSportsCategoriesHelp": "Програмите от тези категории ще се показват като спортни програми.За разделяне на множество използвайте '|'.", + "XmlTvPathHelp": "Път до XMLTV файла.Джелифин ще чете от този файл и периодично ще го проверява за актуализации.Ваша грижа е да го създадете и редовно да го обновявате.", + "XmlTvKidsCategoriesHelp": "Програмите с тези категории ще се показват като програми за деца.За разделяне на множество използвайте '|'.", + "XmlTvNewsCategoriesHelp": "Програмите от тези категории ще се показват като новинарски програми.За разделяне на множество използвайте '|'.", + "XmlTvMovieCategoriesHelp": "Програмите от тези категории ще се показват като филми.За разделяне на множество използвайте '|'.", + "XmlDocumentAttributeListHelp": "Тези атрибути се прилагат към коренния елемент на всеки XML отговор.", + "Whitelist": "Бял списък", + "ViewPlaybackInfo": "Вижте информация за възпроизвеждането", + "VideoRange": "Диапазон на видео", + "ValueTimeLimitSingleHour": "Времеви лимит: 1 час", + "ValueTimeLimitMultiHour": "Времеви лимит {0} часове", + "ValueContainer": "Контейнер: {0}", + "UserAgentHelp": "Предоставяне на персонализиран ползавтел-агент HTTP хедър.", + "Up": "Нагоре", + "Unrated": "Без категории", + "UninstallPluginConfirmation": "Сигурни ли сте ,че искате да премахнете {0}?", + "Uniform": "Еднороден", + "TvLibraryHelp": "Прегледайте {0}ръководството за именуване на ТВ{1}.", + "Transcoding": "Транскодиране", + "Track": "Пътека", + "TitleHostingSettings": "Настройки за хостинг", + "TitleHardwareAcceleration": "Хардуерно ускорение", + "TabNetworking": "Работа в мрежа", + "TabDVR": "ДВР", + "SystemDlnaProfilesHelp": "Системните профили са с достъп \"само за четене\".При промяна в системния профил ще бъде създаден нов персонализиран профил.", + "SyncPlayAccessHelp": "Избери нивото на достъп ,който този потребител ще има за услугата \"Синхронизирано възпроизвеждане\".С нейна помощ може да синхронизирате възпроизвеждането с други устройства.", + "SubtitleOffset": "Изместване на субтитрите", + "SubtitleDownloadersHelp": "Включи и подреди по собствени предпочитания услугите за сваляне на субтитри.", + "SubtitleAppearanceSettingsDisclaimer": "Настройките не могат да се приложат за графични субтитри (PGS, DVD, т.н.) или ASS/SSA ,защото те зареждат свои стилове.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Настройките важат за всеки Chromecast поток стартиран от това устройство.", + "StopRecording": "Спри запис", + "SortName": "Подреди по име", + "SortChannelsBy": "Подреди канали по:", + "SmartSubtitlesHelp": "Когато аудиото е чуждоезично ще бъдат заредени субтитри според зададените настройки за език.", + "ErrorAddingListingsToSchedulesDirect": "Възникна грешка при добавянето на списъка към вашия \"Schedules Direct\" акаунт.Услугата позволява определен брой списъци към един акаунт.Ще е необходимо да влезете в акаунта си (през сайта на услугата) и да отстраните другите списъци преди да продължите с добавянето на нови.", + "LabelIdentificationFieldHelp": "Подниз или регулярен израз с различаване на главни букви.", + "EnableFastImageFadeInHelp": "Използвай бързи анимации преходи", + "EnableFastImageFadeIn": "Бързи анимации", + "LabelScreensaver": "Скрийнсейвър:", + "LabelScheduledTaskLastRan": "Последно пускан {0}, заел {1}.", + "LabelRuntimeMinutes": "Продължителност (в минути):", + "LabelRequireHttpsHelp": "Ако е отметнато сървъра ще пренасочва автоматично всички заявка от HTTP към HTTPS.Няма никакъв ефект ,ако сървъра не \"слуша\" по HTTPS.", + "LabelRequireHttps": "Изисква HTTPS", + "LabelRemoteClientBitrateLimitHelp": "Допълнително ограничаване битрейта на поточното предаване за всички мрежови устройства.Това е необходимо ,за да не допуснете устройствата да изискват по-висок битрейт, отколкото вашата интернет връзка позволява.Това може да доведе до повишено натоварване на процесора на вашия сървър, за щото видеата ще се прекодират \"в движение\" до по-нисък битрейт.", + "LabelTranscodingThreadCount": "Количество транскодирани потоци :", + "LabelTranscodingProgress": "Прогрес на транскодирането:", + "LabelTranscodingFramerate": "Честота на кадрите при транскодиране:", + "LabelTranscodes": "Транскодирания:", + "LabelTranscodePath": "Път за транскодиране:", + "LabelTranscodingContainer": "Контейнер:", + "LabelTrackNumber": "Номер на песен:", + "LabelTextBackgroundColor": "Цвят на фона на текста:", + "LabelTagline": "Ключова фраза:", + "LabelTVHomeScreen": "Главна страница в режим ТВ:", + "LabelSyncPlayAccess": "Достъп до \"синхронизирано възпроизвеждане\"", + "LabelSyncPlayAccessNone": "Изключено за този потребител", + "LabelSyncPlayAccessJoinGroups": "Разрешаване на потребителя да се присъединява към групи", + "LabelSyncPlayAccessCreateAndJoinGroups": "Разрешаване на потребителя да създава и да се присъединява към групи", + "LabelSyncPlayLeaveGroupDescription": "Деактивирай \"синхронизирано възпроизвеждане\"", + "LabelSyncPlayLeaveGroup": "Напусни групата", + "LabelSyncPlayNewGroupDescription": "Създай нова група", + "LabelSyncPlayNewGroup": "Нова група", + "LabelSyncPlaySyncMethod": "Метод за синхронизиране:", + "LabelSyncPlayPlaybackDiff": "Разлика във времето на възпроизвеждане:", + "MillisecondsUnit": "мс", + "LabelSyncPlayTimeOffset": "Компенсиране на разликата във времето със сървъра:", + "LabelSubtitleFormatHelp": "Пример: srt", + "LabelSubtitleDownloaders": "Услуги за сваляне на субтитри:", + "LabelStreamType": "Вид на потока:", + "LabelStopping": "Спиране", + "LabelSportsCategories": "Спортни категории:", + "LabelSoundEffects": "Звукови ефекти:", + "LabelSortTitle": "Подреди по заглавие:", + "LabelSonyAggregationFlags": "\"Флагове\" за статистическа обработка на Сони:", + "LabelSkipIfGraphicalSubsPresentHelp": "Наличието на текстови версии на субтитрите ще доведе до по-ефективна доставка и намаляване на вероятността от транскодиране на видеото.", + "LabelSkipIfAudioTrackPresentHelp": "Махнете отметката ,за да се гарантира ,че всички видеофайлове имат субтитри,независимо от езика на аудиото им.", + "LabelSkin": "Облик:", + "LabelSize": "Размер:", + "LabelSimultaneousConnectionLimit": "Ограничение на броя едновременни потоци:", + "LabelServerName": "Име на сървъра:", + "LabelServerHostHelp": "192.168.1.100:8096 или https://myserver.com", + "LabelServerHost": "Хост:", + "LabelSendNotificationToUsers": "Изпрати уведомление до:", + "LabelSelectFolderGroupsHelp": "Папките ,които не са отметнати ще се показват самостоятелно с техен изглед.", + "LabelSelectFolderGroups": "Автоматично групира съдържанието от следните папки в режим \"изглед\" като Филми, Музика и ТВ:", + "MessageConfirmRemoveMediaLocation": "Сигурни ли сте ,че искате да премахнете мястото?", + "MessageConfirmRecordingCancellation": "Отмяна на записа?", + "MessageConfirmProfileDeletion": "Сигурни ли сте ,че искате да изтриете този профил?", + "MessageConfirmDeleteTunerDevice": "Сигурни ли сте ,че искате да изтриете това устройство?", + "MessageConfirmDeleteGuideProvider": "Сигурни ли се ,че искате да изтриете доставчика на справочника?", + "MessageConfirmAppExit": "Искате ли да излезете?", + "MessageAreYouSureDeleteSubtitles": "Сигурни ли се ,че искате да изтриете файла със субтитри?", + "MediaIsBeingConverted": "Медията е конвертирана във формат ,който е съвместим с устройството ,което ще я възпроизведе.", + "MediaInfoSoftware": "Софтуер", + "MediaInfoTimestamp": "Времеви отпечатък", + "MediaInfoSampleRate": "Кадрова честота", + "MediaInfoRefFrames": "Ref кадри", + "MediaInfoPixelFormat": "Пикселен формат", + "MediaInfoCodecTag": "Етикет на кодека", + "MediaInfoBitDepth": "Дълбочина на цвета", + "MediaInfoAnamorphic": "Анаморфно", + "MapChannels": "Разпредели канали", + "ManageRecording": "Управление на записа", + "ManageLibrary": "Управление на библиотеката", + "LiveBroadcasts": "Предавания на живо", + "LeaveBlankToNotSetAPassword": "Можете да оставите това поле празно и да не задавате парола.", + "LearnHowYouCanContribute": "Научете как можете да допринесете.", + "LaunchWebAppOnStartupHelp": "Отвори уеб клиента във браузъра по подразбиране при първото стартиране на сървъра.Това няма да се случи при използване на функцията на сървъра за рестартиране.", + "LaunchWebAppOnStartup": "Стартирай уеб интерфейса ,когато се стартира сървъра", + "LanNetworksHelp": "Списък разделен със запетая съдържащ ИП адреси или записи за ИП/мрежова маски отнасящи се за мрежи ,които ще се считат за локални ,когато се налагат ограничения в честотната лента.Ако е зададено всички други ИП адреси ще се считат за принадлежащи към външни мрежи и за тях ще важат правилата за ограничения на външни ИП -та.Ако полето е празно ще се счита ,че само подмрежата на сървъра е част от локалната мрежа.", + "LabelffmpegPathHelp": "Пътят към файла на приложението ffmpeg или папката, съдържаща ffmpeg.", + "LabelffmpegPath": "Път към FFmpeg:", + "LabelZipCode": "Пощенски код:", + "LabelXDlnaDoc": "Схема X-DLNA:", + "LabelXDlnaCapHelp": "Определя съдържанието на елемента X_DLNACAP във urn:schemas-dlna-org:device-1-0 в пространството от имена.", + "LabelXDlnaCap": "Свойства на X-Dlna:", + "LabelWeb": "Уеб:", + "LabelVideoResolution": "Видео резолюция:", + "LabelVideoCodec": "Видео кодек:", + "LabelVideoBitrate": "Видео битрейт:", + "DashboardArchitecture": "Архитектура: {0}", + "DashboardOperatingSystem": "Операционна система: {0}", + "DashboardServerName": "Сървър: {0}", + "DashboardVersionNumber": "Версия: {0}", + "LabelVersionInstalled": "{0} Инсталирано", + "LabelValue": "Стойност:", + "LabelVaapiDeviceHelp": "Това е възелът на визуализация, който се използва при хардуерно ускорение.", + "LabelVaapiDevice": "Устройство VA-API:", + "LabelUserRemoteClientBitrateLimitHelp": "Замени глобалната стойност по подразбиране, зададена в настройките за възпроизвеждане на сървъра.", + "LabelUserLoginAttemptsBeforeLockout": "Неуспешни опити за влизане, преди потребителският акаунт да бъде заключен:", + "LabelUserLibraryHelp": "Изберете коя библиотека да се показва на устройството.Оставете полето празно ,за да работят настройките по подразбиране.", + "LabelUserAgent": "Потребителски агент:", + "LabelUseNotificationServices": "Използвайте следните услуги:", + "LabelTypeMetadataDownloaders": "{0} услуги за сваляне на метаданни:", + "LabelTunerType": "Тип на тунера:", + "LabelTunerIpAddress": "ИП адрес на тунера:", + "LabelTranscodingThreadCountHelp": "Изберете максималния брой процесорни нишки ,които ще се използват при транскодиране.Намаляването на броя на нишките ще намали използването на процесора, но може да не преобразува достатъчно бързо за да осигури гладко възпроизвеждане.", + "MessageForgotPasswordFileCreated": "Следния файл беше създаден на вашия сървър и съдържа инструкции как да постъпите:", + "MessageFileReadError": "Възникна грешка при четенето на файла.Моля опитайте отново.", + "MessageEnablingOptionLongerScans": "Включването на тази опция значително ще удължи сканирането на библиотеките.", + "MessageDownloadQueued": "Свалянето е на опашката.", + "MessageDirectoryPickerLinuxInstruction": "За потребителите на следните Линукс дистрибуции: Arch Linux, CentOS, Debian, Fedora, openSUSE, или Ubuntu е необходимо да се предостави на сервизния потребител поне достъп с права за четене на вашите хранилища.", + "MessageDirectoryPickerBSDInstruction": "За BSD ще е нужно да настроите хранилище във вашия FreeNAS Jail ,за да може сървъра да има достъп до него.", + "MessageDeleteTaskTrigger": "Сигурни ли сте ,че искате да изтриете \"пусковият механизъм\" за задачи?", + "MessageCreateAccountAt": "Създай акаунт в {0}", + "MessageContactAdminToResetPassword": "Моля, свържете се със системния си администратор, за да нулирате паролата си.", + "MessageConfirmRevokeApiKey": "Сигурни ли сте ,че искате да оттеглите api ключа?Връзката на приложението със сървъра ще бъде прекратена незабавно.", + "MessageSyncPlayGroupDoesNotExist": "Неуспешно присъединяване към групата, защото не съществува.", + "MessageSyncPlayPlaybackPermissionRequired": "Необходимо е разрешение за възпроизвеждане.", + "MessageSyncPlayNoGroupsAvailable": "Няма налични групи.Пуснете нещо да се възпроизвежда.", + "MessageSyncPlayGroupWait": "{0} буферира се...", + "MessageSyncPlayUserLeft": "{0} е напуснал групата.", + "MessageSyncPlayUserJoined": "{0} се присъедини към групата.", + "MessageSyncPlayDisabled": "Услугата \"синхронизирано възпроизвеждане\" е изключена.", + "MessageSyncPlayEnabled": "Услугата \"синхронизирано възпроизвеждане\" е включена.", + "MessageUnsetContentHelp": "Съдържанието ще се показва като обикновени папки. За най-добри резултати използвайте мениджъра на метаданни, за да зададете типовете съдържание на подпапките.", + "MessageUnableToConnectToServer": "В момента не можем да се свържем с избрания сървър. Моля, уверете се, че работи и опитайте отново.", + "MessageReenableUser": "Вижте по-долу, за да активирате отново", + "MessagePluginInstallDisclaimer": "Приставките, създадени от членове на общността, са чудесен начин да подобрите изживяването с Джелифин чрез допълнителните функции и предимства.Преди да инсталирате, имайте предвид ефектите, които те могат да имат върху вашия Джелифин сървър, като по-дълго време за сканиране на библиотеки, допълнителна обработка на заден фон и намалена стабилност на системата.", + "MessagePluginConfigurationRequiresLocalAccess": "За да конфигурирате тази приставка, моля, впишете се директно в локалния си сървър.", + "MessagePleaseWait": "Моля,изчакайте. Това може да отнеме минута.", + "MessagePlayAccessRestricted": "Възпроизвеждането на това съдържание в момента е ограничено.Моля, свържете се с администратора на вашия сървър за повече информация.", + "MessagePasswordResetForUsers": "Следните потребители са занулили паролите си.Те вече могат да влязат с пин кодовете, използвани за извършване на нулирането.", + "MessageNoTrailersFound": "За да подобрите филмовото изживяване инсталирайте канал за трейлъри,може да подредите няколко канала в библиотека.", + "MessageNoServersAvailable": "Не са намерени сървъри, използващи функцията за автоматично откриване на сървър.", + "MessageNoMovieSuggestionsAvailable": "Понастоящем няма предложени филми. Започнете да гледате и оценявате филмите си, а след това се върнете, за да видите препоръките си.", + "MessageNoCollectionsAvailable": "Колекциите ви позволяват да се наслаждавате на персонализирани групи от филми, сериали и албуми. Кликнете върху бутона +, за да започнете да създавате колекции.", + "MessageLeaveEmptyToInherit": "Оставете празни, за да наследите настройки от родителски елемент или глобалната стойност по подразбиране.", + "MessageItemsAdded": "Добавени са елементи.", + "MessageItemSaved": "Елементът е запазен.", + "MessageUnauthorizedUser": "Понастоящем нямате право да получите достъп до сървъра. Моля, свържете се с администратора на вашия сървър за повече информация.", + "MessageInvalidUser": "Невалидно потребителско име или парола. Моля, опитайте отново.", + "MessageInvalidForgotPasswordPin": "Въведен е невалиден или изтекъл пин код. Моля, опитайте отново.", + "MessageInstallPluginFromApp": "Този плъгин трябва да бъде инсталиран от приложението, в което възнамерявате да го използвате.", + "MessageImageTypeNotSelected": "Изберете типът изображение от падащото меню.", + "MessageImageFileTypeAllowed": "Поддържат се само файлове с разширение JPEG и PNG.", + "MessageForgotPasswordInNetworkRequired": "Опитайте пак в домашната мрежа да повторите процеса по нулиране на паролата.", + "Smaller": "По-малък", + "SmallCaps": "Малки букви", + "SkipEpisodesAlreadyInMyLibraryHelp": "Епизодите ще се сравняват, като се използват номера на сезона и епизода, когато са налични.", + "SkipEpisodesAlreadyInMyLibrary": "Не записвай епизоди, които вече са в моята библиотека", + "SimultaneousConnectionLimitHelp": "Максималният брой разрешени едновременни потоци. Въвеждането на 0 означава ,че няма ограничение.", + "Filter": "Филтър", + "New": "Нов", + "ShowMore": "Покажи повече", + "ShowLess": "Покажи по-малко", + "ShowIndicatorsFor": "Показване на индикатори за:", + "SettingsWarning": "Промяната на тези стойности може да доведе до нестабилност или проблеми в свързаността. Ако срещнете някакви проблеми, препоръчваме ви да ги върнете по подразбиране.", + "SeriesSettings": "Настройки на сериала", + "SeriesRecordingScheduled": "Назначено е запис на сериал.", + "SeriesDisplayOrderHelp": "Подредете епизодите по дата на излъчване, излизане на DVD или абсолютна номерация.", + "SeriesCancelled": "Сериала е отменен.", + "Series": "Сериал", + "SelectAdminUsername": "Моля, изберете потребителско име за администраторския акаунт.", + "Season": "Сезон", + "SearchResults": "Резултати от търсенето", + "ScanForNewAndUpdatedFiles": "Сканирай за нови и актуализирани файлове", + "SaveSubtitlesIntoMediaFoldersHelp": "Съхраняването на субтитрите при видео файлове ще позволи по-лесното им управление.", + "SaveSubtitlesIntoMediaFolders": "Запазване на субтитрите в папките с медията", + "SaveChanges": "Запазете промените", + "RunAtStartup": "Пускай при стартиране", + "RestartPleaseWaitMessage": "Моля, изчакайте, докато сървъра се изключи и рестартира. Това може да отнеме минута или две.", + "RepeatOne": "Повтори един път", + "RepeatMode": "Режим на повторение", + "RepeatEpisodes": "Повтори епизодите", + "RepeatAll": "Повтори всички", + "ReleaseGroup": "Издаден от група", + "RefreshQueued": "Назначено е обновяване.", + "RefreshDialogHelp": "Метаданните се обновяват въз основа на настройките и интернет услугите, които са активирани от таблото за управление на сървъра.", + "Recordings": "Записи", + "RecordingScheduled": "Записът е насрочен.", + "RecordingPathChangeMessage": "Промяната на вашата папка за запис няма да мигрира съществуващите записи от старото местоположение към новото.Необходимо е да направите това ръчно.", + "RecordSeries": "Запиши сериал", + "RecommendationStarring": "В главните роли {0}", + "RecommendationDirectedBy": "Режисьор {0}", + "Rate": "Оценка", + "QueueAllFromHere": "Поред всичко от тук", + "ProductionLocations": "Места на заснемане", + "Previous": "Предишен", + "PreferEmbeddedEpisodeInfosOverFileNames": "Предпочитай \"вградената\" информация за епизода вместо името на файла", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Това използва информацията за епизода от вградените метаданни, ако е налична.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Това определя заглавието по подразбиране, когато няма интернет метаданни или локални метаданни.", + "PluginInstalledMessage": "Добавката е успешно инсталирана. Джелифин ще трябва да бъде рестартиран, за да влязат в сила промените.", + "PleaseSelectTwoItems": "Моля, изберете поне два елемента.", + "PleaseEnterNameOrId": "Моля, въведете име или външен идентификатор.", + "PleaseConfirmPluginInstallation": "Моля, щракнете върху OK, за да потвърдите, че сте прочели горното и искате да продължите с инсталирането на добавката.", + "PleaseAddAtLeastOneFolder": "Моля, добавете поне една папка към тази библиотека, като щракнете върху бутона \"Добавяне\".", + "PlaybackErrorNoCompatibleStream": "Този клиент не е съвместим с медията и сървърът не изпраща съвместим формат за медията.", + "PlayNext": "Възпроизведи следващ", + "PlayFromBeginning": "Възпроизведи от началото", + "PlayCount": "Брой възпроизвеждания", + "PlaybackData": "Данни за възпроизвеждане", + "PlaceFavoriteChannelsAtBeginning": "Постави любимите канали в началото", + "PinCodeResetConfirmation": "Сигурни ли сте, че искате да нулирате пин кода?", + "PinCodeResetComplete": "Пин кодът е нулиран.", + "Person": "Личност", + "PerfectMatch": "Перфектно съвпадение", + "PasswordSaved": "Паролата е запазена.", + "PasswordResetProviderHelp": "Изберете доставчик за нулиране на пароли, който да се използва, когато този потребител поиска нулиране на паролата", + "PasswordResetConfirmation": "Сигурни ли сте, че искате да нулирате паролата?", + "PasswordResetComplete": "Паролата е нулирана.", + "PasswordMatchError": "Паролата и потвърждението на паролата трябва да съвпадат.", + "PackageInstallFailed": "Инсталирането на {0} версия {1}) е неуспешно.", + "PackageInstallCompleted": "Инсталирането на {0} версия {1}) е завършено.", + "PackageInstallCancelled": "Инсталирането на {0} версия {1}) е отменено.", + "OtherArtist": "Друг изпълнител", + "OptionWeekends": "Почивни дни", + "OptionWeekdays": "Делници", + "OptionTvdbRating": "Рейтинг според ТВДБ", + "OptionThumbCard": "Икона карта", + "OptionThumb": "Икона", + "OptionSubstring": "Подниз", + "OptionSaveMetadataAsHiddenHelp": "Промяната на това ще се прилага за нови метаданни, запазени занапред. Съществуващите файлове с метаданни ще бъдат актуализирани следващия път, когато бъдат запазени на сървъра.", + "OptionSaveMetadataAsHidden": "Запишете метаданните и изображенията като скрити файлове", + "OptionRequirePerfectSubtitleMatchHelp": "Изискването за перфектно съвпадение ще филтрира субтитрите, за да включва само тези, които са тествани и проверени с точния видео файл.Ако премахнете отметката, това ще увеличи броя изтеглени субтитри, но ще увеличи шансовете за изтегляне на субтитри с неправилна синхронизация или несъвпадащи с видеофайла.", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Това е необходимо за някои устройства, които не търсят \"по време\" правилно.", + "OptionReportByteRangeSeekingWhenTranscoding": "Подава информация, че сървърът поддържа търсене на байтове при прекодиране", + "OptionRegex": "Регуларен", + "OptionRandom": "Случаен", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "Директно предаване по HTTP", + "OptionPosterCard": "Плакат карта", + "OptionPoster": "Плакат", + "OptionPlainVideoItemsHelp": "Ако е активирано, всички видеофайлове са представени в DIDL като \"object.item.videoItem\" вместо по-конкретен тип, като например \"object.item.videoItem.movie\".", + "OptionPlainStorageFoldersHelp": "Ако е активирано, всички папки са представени в DIDL като \"object.container.storageFolder\" вместо по-конкретен тип, като например \"object.container.person.musicArtist\".", + "OptionMax": "Максимално", + "OptionLoginAttemptsBeforeLockoutHelp": "Стойност нула означава наследяване по подразбиране на три опита за нормални потребители и пет за администратори. Задаването на това на -1 ще деактивира функцията.", + "OptionLoginAttemptsBeforeLockout": "Определя колко неправилни опита за влизане могат да бъдат направени, преди да бъде блокиран.", + "OptionList": "Списък", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Ако са активирани, тези заявки ще бъдат удовлетворени, но ще се пренебрегне заглавната част от обхвата на байтовете.", + "OptionIgnoreTranscodeByteRangeRequests": "Игнорирайте заявките за обхват на байтове при прекодиране", + "OptionHlsSegmentedSubtitles": "HLS сегментирани субтитри", + "OptionExtractChapterImage": "Включи извличането на изображения от разделите", + "OptionEstimateContentLength": "Приблизителна дължина на съдържанието при транскодиране", + "OptionEquals": "Се равнява", + "OptionEnableM2tsModeHelp": "Включи режим m2ts ,когато се декодира до mpegts.", + "OptionEnableM2tsMode": "Включи режим M2ts", + "OptionEnableForAllTuners": "Активиране за всички тунер устройства", + "OptionEnableExternalContentInSuggestionsHelp": "Разрешете интернет трейлърите и телевизионните програми на живо да бъдат включени в предложеното съдържание.", + "OptionEnableExternalContentInSuggestions": "Активиране на външно съдържание в предложенията", + "OptionEmbedSubtitles": "Внедрено вътре в контейнера", + "OptionDownloadImagesInAdvanceHelp": "По подразбиране повечето изображения се изтеглят само когато са поискани от приложенията свързани с Джелифин.Активирайте тази опция, за да изтеглите всички изображения предварително, при добавянето на нова медия. Това може да доведе до значително по-дълго сканиране на библиотеката.", + "OptionDisplayFolderViewHelp": "Показвай папките заедно с тези от медийната библиотека. Това може да бъде полезно, ако искате да имате по разбираем изглед за папките.", + "OptionDateAddedImportTime": "Използвай датата на сканиране в библиотеката", + "OptionDateAddedFileTime": "Използвай датата на създаване на файла", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionBluray": "Блу-рей", + "OptionBlockTrailers": "Трейлъри", + "OptionBlockMusic": "Музика", + "OptionBlockLiveTvChannels": "Телевизионни канали на живо", + "OptionBlockChannelContent": "Съдържание на интернет канала", + "OptionBanner": "Банер", + "OptionAutomaticallyGroupSeriesHelp": "Ако е активирано, сезоните, които се намират в различни папки, ще бъдат автоматично обединени в един сериал.", + "OptionAutomaticallyGroupSeries": "Автоматично обединява сезони, които са разпределени в множество папки", + "OptionAllowVideoPlaybackTranscoding": "Разреши възпроизвеждане на видео, което изисква транскодиране", + "OptionAllowVideoPlaybackRemuxing": "Разреши възпроизвеждане на видео, което изисква преобразуване без повторно кодиране", + "OptionAllowSyncTranscoding": "Разреши изтегляне и синхронизиране на медия, която изисква транскодиране", + "OptionAllowMediaPlaybackTranscodingHelp": "Ограничаването на достъпа до транскодирането може да доведе до проблеми при възпроизвеждането в приложенията на Джелифин поради неподдържани медийни формати.", + "OptionAllowLinkSharingHelp": "Споделят се само уеб страници, съдържащи медийна информация.Медийните файлове никога не се споделят публично. Споделянията са ограничени във времето и изтичат след {0} дни.", + "OptionAllowContentDownloading": "Разрешаване на изтегляне и синхронизиране на медия", + "OptionForceRemoteSourceTranscoding": "Принудително транскодиране на отдалечени медийни източници (като поточна ТВ)", + "OptionAllowAudioPlaybackTranscoding": "Разреши възпроизвеждане на аудио, което изисква транскодиране", + "OnlyImageFormats": "Само графични формати (VOBSUB, PGS, SUB)", + "OnlyForcedSubtitlesHelp": "Ще бъдат показани само субтитри, маркирани за \"принудително\" зареждане.", + "OnlyForcedSubtitles": "Само принудително", + "OneChannel": "Един канал", + "NoSubtitlesHelp": "Субтитрите няма да бъдат заредени по подразбиране. Те все още могат да бъдат включени ръчно по време на възпроизвеждане.", + "NoPluginConfigurationMessage": "Тази добавка няма настройки за конфигуриране.", + "NoNewDevicesFound": "Не са намерени нови устройства. За да добавите нов тунер, затворете този диалогов прозорец и въведете ръчно информацията за устройството.", + "NoCreatedLibraries": "Изглежда, че все още не сте създали библиотеки. {0} Искате ли да ги създадете сега? {1}", + "NextUp": "Следващ по ред", + "Next": "Следващ", + "NewCollectionHelp": "Колекциите ви позволяват да създавате персонализирани групи от филми и друго библиотечно съдържание.", + "MusicLibraryHelp": "Прегледайте {0} ръководството за именуване на музика {1}.", + "MusicArtist": "Музикален изпълнител", + "MusicAlbum": "Музикален албум", + "Movie": "Филм", + "MovieLibraryHelp": "Прегледайте {0} ръководството за именуване на филми {1}.", + "MoveRight": "Премести надясно", + "MoveLeft": "Премести на ляво", + "MoreMediaInfo": "Информация за медията", + "MetadataSettingChangeHelp": "Промяната на настройките на метаданните ще повлияе на новото съдържание, което ще се добавя занапред.За да обновите съществуващото съдържание, отворете панела с подробности и щракнете върху бутона за опресняване или извършете групово обновяване с помощта на мениджъра на метаданни.", + "MessageSyncPlayErrorMedia": "Неуспешно активиране на услугата \"синхронизирано възпроизвеждане\"!Грешка в медията.", + "MessageSyncPlayErrorMissingSession": "Неуспешно активиране на услугата \"синхронизирано възпроизвеждане\"! Липсва сесия.", + "MessageSyncPlayErrorNoActivePlayer": "Не е намерен активен плеър.Функцията \"синхронизирано възпроизвеждане\" е изключена.", + "MessageSyncPlayErrorAccessingGroups": "Възникна грешка при достъп до списъка с групи.", + "MessageSyncPlayLibraryAccessDenied": "Достъпът до това съдържание е ограничен.", + "MessageSyncPlayJoinGroupDenied": "Необходимо е разрешени за използване на функцията \"синхронизирано възпроизвеждане\".", + "MessageSyncPlayCreateGroupDenied": "Необходимо е разрешени за създаване на група.", + "LabelSonyAggregationFlagsHelp": "Определя съдържанието на aggregationFlags елемента във urn:schemas-sonycom:av пространство от имена.", + "LabelXDlnaDocHelp": "Определя съдържанието на X_DLNADOC елемента в urn:schemas-dlna-org:device-1-0 пространство от имена.", + "LabelSkipForwardLength": "Време за придвижване напред:", + "LabelSkipBackLength": "Време за придвижване назад:", + "LabelRepositoryUrlHelp": "Местоположението на манифеста на хранилището, което искате да включите.", + "MessageNoGenresAvailable": "Доставчиците на метаданни ще могат да изтеглят жанрове от интернет.", + "MessageAddRepository": "Ако искате да добавите хранилище, щракнете върху бутона до заглавната част и попълнете исканата информация.", + "LabelRepositoryNameHelp": "Персонализирано име за разграничаване на това хранилище от всички останали добавени към вашия сървър.", + "LabelRepositoryName": "Име на хранилището", + "LabelRepositoryUrl": "Адрес на хранилището", + "HeaderNewRepository": "Ново хранилище", + "MessageNoRepositories": "Няма хранилища.", + "EnableFasterAnimationsHelp": "Използвайте по-бързи анимации и преходи", + "EnableFasterAnimations": "Бързи анимации", + "EnableDecodingColorDepth10Vp9": "Включи 10 битово хардуерно декодиране за VP9", + "EnableDecodingColorDepth10Hevc": "Включи 10 битово хардуерно декодиране за HEVC", + "ButtonCast": "Стриймване", + "ButtonSyncPlay": "SyncPlay", + "TabRepositories": "Хранилища" } diff --git a/src/strings/ca.json b/src/strings/ca.json index 80e4b900671..680be3ef77f 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -7,7 +7,7 @@ "All": "Tot", "AllChannels": "Tots els canals", "AllEpisodes": "Tots els episodis", - "AlwaysPlaySubtitles": "Reprodueix sempre amb subtítols", + "AlwaysPlaySubtitles": "Reprodueix sempre", "AroundTime": "Cap a les {0}", "Artists": "Artistes", "AsManyAsPossible": "Tants com sigui possible", @@ -732,7 +732,7 @@ "XmlTvNewsCategoriesHelp": "Els programes amb aquestes categories es mostraran com a programes de notícies. Separa'n varis emprant '|'.", "XmlTvSportsCategoriesHelp": "Els programes amb aquestes categories es mostraran com a programes esportius. Separa'n varis emprant '|'.", "Books": "Llibres", - "Folders": "Directoris", + "Folders": "Carpetes", "Photos": "Fotos", "Shows": "Programes", "Songs": "Cançons", @@ -741,7 +741,7 @@ "Channels": "Canals", "Collections": "Col·leccions", "Favorites": "Preferits", - "HeaderAlbumArtists": "Artistes dels Àlbums", + "HeaderAlbumArtists": "Artistes del Àlbum", "HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteArtists": "Artistes Preferits", "HeaderFavoriteEpisodes": "Episodis Preferits", @@ -795,5 +795,29 @@ "AddedOnValue": "Afegit {0}", "AddItemToCollectionHelp": "Afegiu els elements a les col·leccions buscant-los i fent clic amb el botó dret o toqueu els menús per afegir-los a una col·lecció.", "Actor": "Actor", - "Absolute": "Absolut" + "Absolute": "Absolut", + "ClientSettings": "Configuració del client", + "CinemaModeConfigurationHelp": "El mode Cinema aporta l'experiència del teatre directament a la sala d'estar amb la possibilitat de jugar a tràilers i presentacions personalitzades abans de la funció principal.", + "ChannelNameOnly": "Número de canal", + "ChangingMetadataImageSettingsNewContent": "Els canvis als paràmetres de descàrrega de metadades o d'obra d'art només s'apliquen al contingut nou afegit a la biblioteca. Per aplicar els canvis als títols existents, haureu de refrescar les metadades manualment.", + "ButtonTogglePlaylist": "Llista de reproducció", + "ButtonToggleContextMenu": "més", + "ButtonOff": "Apagar", + "BurnSubtitlesHelp": "Determina si el servidor hauria de gravar-se en els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VOBSUB, PGS, SUB, IDX) i certs subtítols ASS o SSA.", + "Browse": "Navega", + "BoxRear": "Caixa (posterior)", + "BoxSet": "conjunt de caixes", + "Box": "Caixa", + "BookLibraryHelp": "Els àudio i llibres de text són compatibles. Reviseu la {0} guia de denominació de llibres {1}.", + "Backdrops": "Fons", + "Backdrop": "Fons", + "AutoBasedOnLanguageSetting": "Auto (basada en la configuració de l’idioma)", + "Artist": "Artista", + "AllowedRemoteAddressesHelp": "Llista d’adreces IP o d’entrades IP / netmasca separades per comes per a xarxes que podran connectar-se de forma remota. Si es deixa en blanc, es permetran totes les adreces remotes.", + "AllowFfmpegThrottlingHelp": "Quan un transcòdi o un remux estigui prou lluny de la posició de reproducció actual, feu una pausa en el procés perquè consumirà menys recursos. Això és més útil per mirar sense buscar sovint. Desactiveu-la si teniu problemes de reproducció.", + "AllowFfmpegThrottling": "Transcodes de l’acceleració", + "AllowOnTheFlySubtitleExtractionHelp": "Els subtítols incrustats es poden extreure de vídeos i entregar-los a clients en text senzill per tal d'evitar la transcodificació de vídeo. En alguns sistemes, això pot trigar molt i fer que la reproducció de vídeo s’aturi durant el procés d’extracció. Desactiveu-ho per tenir subtítols incrustats incrustats amb la transcodificació de vídeo quan no són compatibles amb el dispositiu client de forma nativa.", + "AlbumArtist": "Album artista", + "Album": "Album", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/cs.json b/src/strings/cs.json index bf74c4ad2d1..52991654156 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -14,23 +14,23 @@ "AllEpisodes": "Všechny epizody", "AllLanguages": "Všechny jazyky", "AllowHWTranscodingHelp": "Povolit tuneru překódování v reálném čase. Může snížit zátěž překódovávání požadované Jellyfin serverem.", - "AlwaysPlaySubtitles": "Vždy zobrazit titulky", + "AlwaysPlaySubtitles": "Vždy zobrazovat", "AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.", "Anytime": "Kdykoliv", - "AroundTime": "Okolo {0}", + "AroundTime": "Okolo", "Art": "Umění", "Artists": "Umělci", "AsManyAsPossible": "Tolikrát jak je možné", "AspectRatio": "Poměr stran", "AttributeNew": "Nové", "Audio": "Zvuk", - "Auto": "Automatizovat", + "Auto": "Automaticky", "Backdrop": "Pozadí", "Backdrops": "Pozadí", "BirthDateValue": "Narozen: {0}", "BirthLocation": "Místo narození", "BirthPlaceValue": "Místo narození: {0}", - "BookLibraryHelp": "Audio a textové knihy jsou podporovány. Přečtěte si {0}pravidla pro názvy knih {1}.", + "BookLibraryHelp": "Audio a textové knihy jsou podporovány. Přečtěte si {0} pravidla pojmenování knih {1}.", "Books": "Knihy", "Box": "Pouzdro", "BoxRear": "Zadní část pouzdra", @@ -60,7 +60,7 @@ "ButtonForgotPassword": "Zapomenuté heslo", "ButtonFullscreen": "Celá obrazovka", "ButtonGotIt": "Mám to", - "ButtonGuide": "Průvodce", + "ButtonGuide": "Programový průvodce", "ButtonHelp": "Nápověda", "ButtonHome": "Domů", "ButtonLearnMore": "Zjistit více", @@ -79,7 +79,7 @@ "ButtonProfile": "Profil", "ButtonQuickStartGuide": "Rychlý průvodce", "ButtonRefresh": "Obnovit", - "ButtonRefreshGuideData": "Obnovit data průvodce", + "ButtonRefreshGuideData": "Obnovit data programového průvodce", "ButtonRemove": "Odstranit", "ButtonRename": "Přejmenovat", "ButtonRepeat": "Opakovat", @@ -102,7 +102,7 @@ "ButtonStop": "Zastavit", "ButtonSubmit": "Potvrdit", "ButtonSubtitles": "Titulky", - "ButtonTrailer": "Ukázka/trailer", + "ButtonTrailer": "Upoutávka", "ButtonUninstall": "Odinstalovat", "ButtonUp": "Zesílit", "ButtonViewWebsite": "Přejít na webové stránky", @@ -118,11 +118,11 @@ "ChannelAccessHelp": "Vyberte kanály, které chcete sdílet s tímto uživatelem. Administrátoři budou moci upravovat všechny kanály pomocí správce metadat.", "ChannelNameOnly": "Kanál {0} jen", "ChannelNumber": "Číslo kanálu", - "CinemaModeConfigurationHelp": "Režim Cinema přináší zážitky jako z kina přímo do vašeho obývacího pokoje s možností přehrát trailery a vlastní intra před hlavním programem.", + "CinemaModeConfigurationHelp": "Tento režim přibližuje domácí sledování filmů zážitku v kině díky možnosti přehrát upoutávky k filmům a vlastní úvodní video před hlavním pořadem.", "Collections": "Kolekce", "CommunityRating": "Hodnocení komunity", "Composer": "Skladatel", - "ConfigureDateAdded": "Konfigurace přidání data je definována v nastavení knihovny v ovládacím panelu", + "ConfigureDateAdded": "Konfigurace přidání data je definována v nastavení knihovny na nástěnce serveru Jellyfin", "ConfirmDeleteImage": "Odstranit obrázek?", "ConfirmDeleteItem": "Smazáním položky odstraníte soubor jak z knihovny médií tak ze souborového systému. Jste si jisti, že chcete pokračovat?", "ConfirmDeleteItems": "Odstraněním těchto položek odstraníte vaše média jak z knihovny médií, tak i ze souborového systému. Jste si jisti, že chcete pokračovat?", @@ -158,7 +158,7 @@ "Display": "Zobrazení", "DisplayMissingEpisodesWithinSeasons": "Zobrazit chybějící epizody", "DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí být zapnuto pro knihovny TV v nastavení serveru.", - "DisplayModeHelp": "Zvolte typ obrazovky, na které používáte Jellyfin.", + "DisplayModeHelp": "Vyberte styl rozvržení, který chcete pro rozhraní.", "DoNotRecord": "Nenahrávat", "Down": "Dolů", "Download": "Stáhnout", @@ -167,8 +167,8 @@ "DownloadingDots": "Stahování...", "Downloads": "Stahování", "DrmChannelsNotImported": "Kanál s DRM nebude importován.", - "DropShadow": "Vrhat stín", - "EasyPasswordHelp": "Váš PIN kód je snadné používat pro přístup v režimu offline s podporovanými Jellyfin aplikacemi, může být také použit pro snadné přihlášení v lokální síti.", + "DropShadow": "Stín", + "EasyPasswordHelp": "Váš jednoduchý kód PIN slouží k přístupu v režimu offline v podporovaných klientech Jellyfin a k snadnému přihlášení v místní síti.", "Edit": "Upravit", "EditImages": "Editace obrázků", "EditSubtitles": "Editovat titulky", @@ -189,16 +189,14 @@ "EndsAtValue": "Končí v {0}", "Episodes": "Epizody", "ErrorAddingListingsToSchedulesDirect": "Došlo k chybě při přidání sestavy do účtu vašeho Direct plánovače. Direct plánovač umožňuje pouze omezený počet sestav na účet. Možná se budete muset přihlásit do webových stránek Direct plánovače a před pokračováním odstranit ostatní výpisy ze svého účtu.", - "ErrorAddingMediaPathToVirtualFolder": "Nastala chyba při přidávání cesty k médiím. Zkontrolujte zda zadaná složka je validní a Jellyfin Server má k této složce přístup.", + "ErrorAddingMediaPathToVirtualFolder": "Při přidávání cesty k médiím došlo k chybě. Zkontrolujte zda je zadaná složka platná a zda má server Jellyfin k této složce přístup.", "ErrorAddingTunerDevice": "Došlo k chybě při přidání zařízení tuneru. Prosím, ujistěte se, že je přístupný a zkuste to znovu.", "ErrorAddingXmlTvFile": "Nastala chyba při přístupu k XMLTV souboru. Ujistěte se, že soubor existuje a zkuste jej znovu otevřít.", - "ErrorDeletingItem": "Nastala chyba při mazání položky z Jellyfin Serveru. Zkontrolujte prosím, že Jellyfin Server má oprávnění k zápisu do složky médií a zkuste to prosím znovu.", + "ErrorDeletingItem": "Při odstranění položky ze serveru Jellyfin došlo k chybě. Zkontrolujte prosím, zda má server Jellyfin oprávnění k zápisu do složky médií, a zkuste to prosím znovu.", "ErrorGettingTvLineups": "Došlo k chybě při stahování TV sestav. Ujistěte se prosím, že zadané informace jsou správné a zkuste to znovu.", "ErrorMessageStartHourGreaterThanEnd": "Čas ukončení musí být větší než čas startu.", "ErrorMessageUsernameInUse": "Uživatelské jméno se již používá. Prosím, vyberte nový název a zkuste to znovu.", "ErrorPleaseSelectLineup": "Vyberte prosím sestavu a zkuste to znovu. Pokud nejsou k dispozici žádné sestavy, zkontrolujte, zda je vaše uživatelské jméno, heslo a poštovní směrovací číslo správné.", - "ErrorReachingJellyfinConnect": "Došlo k chybě při navázání spojení k serveru Jellyfin Connect. Ujistěte se, zda je funkční připojení k internetu a zkuste to znovu.", - "ErrorRemovingJellyfinConnectAccount": "Nastala chyba při odebrání účtu Jellyfin Connect. Zkontrolujte zda máte aktivní internetové připojení a zkuste znovu.", "ErrorSavingTvProvider": "Při ukládání poskytovatele TV došlo k chybě. Prosím, ujistěte se, že je přístupný a zkuste to znovu.", "ExitFullscreen": "Opustit celou obrazovku", "ExtraLarge": "Extra velký", @@ -223,7 +221,7 @@ "Genres": "Žánry", "GroupVersions": "Skupinové verze", "GuestStar": "Hostující hvězda", - "Guide": "Průvodce", + "Guide": "Programový průvodce", "GuideProviderLogin": "Přihlášení", "GuideProviderSelectListings": "Výběr zobrazení", "H264CrfHelp": "Constant Rate faktor (CRF) je výchozím nastavení kvality pro kodér x264. Můžete nastavit hodnoty mezi 0 a 51, kde nižší hodnoty vedou lepší kvalitě (na úkor větší velikosti souborů). Rozumné hodnoty jsou mezi 18 a 28. Výchozí hodnota pro x264 je 23, který můžete použít jako výchozí bod.", @@ -247,7 +245,7 @@ "HeaderAlert": "Upozornění", "HeaderApiKey": "Klíč Api", "HeaderApiKeys": "Klíče Api", - "HeaderApiKeysHelp": "Externí aplikace musí mít API klíč, aby mohla komunikovat s Jellyfin serverem. Klíče jsou vydávány přihlášením pomocí účtu Jellyfin, nebo manuální žádostí o klíč.", + "HeaderApiKeysHelp": "Externí aplikace musí mít klíč k API, aby mohly komunikovat se serverem Jellyfin. Klíče jsou vydávány přihlášením k účtu Jellyfin nebo ruční žádostí o klíč.", "HeaderApp": "Aplikace", "HeaderAudioBooks": "Audio knihy", "HeaderAudioSettings": "Nastavení zvuku", @@ -299,7 +297,7 @@ "HeaderForgotPassword": "Zapomenuté heslo", "HeaderFrequentlyPlayed": "Nejčastěji přehráváno", "HeaderGenres": "Žánry", - "HeaderGuideProviders": "Průvodce poskytovatelů", + "HeaderGuideProviders": "Poskytovatelé programových průvodců", "HeaderHttpHeaders": "Http hlavičky", "HeaderIdentification": "Identifikace", "HeaderIdentificationCriteriaHelp": "Zadejte alespoň jedno identifikační kritérium.", @@ -354,7 +352,7 @@ "HeaderPreferredMetadataLanguage": "Preferovaný jazyk metadat", "HeaderProfile": "Profil", "HeaderProfileInformation": "Informace o profilu", - "HeaderProfileServerSettingsHelp": "Tyto hodnoty určují, jak se Jellyfin Server bude prezentovat v zařízení.", + "HeaderProfileServerSettingsHelp": "Tyto hodnoty určují, jak se server Jellyfin bude zobrazovat v zařízení.", "HeaderRecentlyPlayed": "Naposledy přehráváno", "HeaderRecordingOptions": "Nastavení nahrávání", "HeaderRecordingPostProcessing": "Následné zpracování nahrávek", @@ -425,8 +423,8 @@ "Identify": "Identifikuj", "Images": "Obrázky", "ImportFavoriteChannelsHelp": "Pokud je povoleno, jen kanály označené jako oblíbené budou importována na zařízení tuneru.", - "ImportMissingEpisodesHelp": "Pokud je povoleno, budou informace o chybějících epizodách importovány do databáze Jellyfin a zobrazí se v sezónách seriálu. To může způsobit podstatně delší skenování knihovny.", - "InstallingPackage": "Instalace {0}", + "ImportMissingEpisodesHelp": "Informace o chybějících epizodách budou importovány do databáze Jellyfin a zobrazí se u sezón a seriálů. Skenování knihovny se tím může značně prodloužit.", + "InstallingPackage": "Instalace {0} (Verze {1})", "InstantMix": "Okamžité míchání", "ItemCount": "{0} položek", "Items": "Položky", @@ -436,7 +434,6 @@ "LabelAccessDay": "Den týdne:", "LabelAccessEnd": "Konec:", "LabelAccessStart": "Začátek:", - "LabelAddConnectSupporterHelp": "Chcete-li přidat uživatele, který není uveden v seznamu, budete muset nejprve propojit svůj účet Jellyfin Connect ze strany profilu uživatele.", "LabelAddedOnDate": "Přidáno {0}", "LabelAirDate": "Dny vysílání:", "LabelAirDays": "Vysíláno:", @@ -464,7 +461,7 @@ "LabelAudio": "Zvuk", "LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:", "LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:", - "LabelBindToLocalNetworkAddressHelp": "Volitelné. Přepsat lokální IP adresu vazanou na http server. Pokud je ponecháno prázdné, server se sváže ke všem dostupným adresám (aplikace bude dostupná na všech síťových zařízení, které server nabízí). Změna této hodnoty vyžaduje restartování Jellyfin Serveru.", + "LabelBindToLocalNetworkAddressHelp": "Volitelné. Změní místní IP adresu, na kterou se váže server HTTP. Pokud je ponecháno prázdné, server bude svázán se všemi dostupnými adresami. Změna této hodnoty vyžaduje restartování serveru Jellyfin.", "LabelBirthDate": "Datum narození:", "LabelBirthYear": "Rok narození:", "LabelBlastMessageInterval": "Doba zobrazení zprávy (v sekundách)", @@ -484,7 +481,7 @@ "LabelCustomDeviceDisplayName": "Jméno pro zobrazení:", "LabelCustomDeviceDisplayNameHelp": "Nahradit vlastním názvem zobrazení nebo ponechte prázdné, aby název byl určen zařízením.", "LabelCustomRating": "Vlastní hodnocení:", - "LabelDashboardTheme": "Téma hlavní nabídky serveru:", + "LabelDashboardTheme": "Téma nástěnky serveru:", "LabelDateAdded": "Datum přidání:", "LabelDateAddedBehavior": "Nový obsah řadit dle data:", "LabelDateAddedBehaviorHelp": "Pokud je hodnota metadat přítomna, bude vždy použita před některou z těchto možností.", @@ -498,7 +495,7 @@ "LabelDidlMode": "DIDL režim:", "LabelDiscNumber": "Číslo disku:", "LabelDisplayLanguage": "Jazyk rozhraní:", - "LabelDisplayLanguageHelp": "Překlad Jellyfin je projekt ve fázi neustálého vývoje.", + "LabelDisplayLanguageHelp": "Překlad projektu Jellyfin se neustále vyvíjí.", "LabelDisplayMissingEpisodesWithinSeasons": "Zobrazit chybějící epizody", "LabelDisplayMissingEpisodesWithinSeasonsHelp": "Toto musí být zapnuto pro knihovny TV v nastavení Jellyfin serveru.", "LabelDisplayMode": "Režim zobrazení:", @@ -511,16 +508,16 @@ "LabelDownloadInternetMetadataHelp": "Jellyfin server může stahovat informace o vašich médiích, aby umožnil vylepšené prezentace.", "LabelDownloadLanguages": "Stahované jazyky:", "LabelDropImageHere": "Sem přetáhněte obrázek nebo klikněte pro procházení.", - "LabelDropShadow": "Vrhat stín:", + "LabelDropShadow": "Typ zvýraznění titulků:", "LabelEasyPinCode": "Kód Easy pin:", "LabelEmbedAlbumArtDidl": "Vložit alba do DIDL", "LabelEmbedAlbumArtDidlHelp": "Některá zařízení preferují tento způsob pro získání alba. Jiné mohou selhat pokud máte tuto volbu povolenu.", "LabelEnableAutomaticPortMap": "Povolit automatické mapování portů", - "LabelEnableAutomaticPortMapHelp": "Pokusí se automaticky namapovat veřejný port místního portu přes UPnP na vašem routeru. Nemusí fungovat u některých modelů routeru. Změny se projeví až po restartování serveru.", + "LabelEnableAutomaticPortMapHelp": "Automaticky zpřístupní port na vašem routeru pomocí technologie UPnP. Nemusí fungovat u některých routerů. Změny se projeví až po restartování serveru.", "LabelEnableBlastAliveMessages": "Vytroubit zprávu do světa", - "LabelEnableBlastAliveMessagesHelp": "Povolit v případě, že server není zjistitelný jinými UPnP zařízeními v síti.", + "LabelEnableBlastAliveMessagesHelp": "Tuto možnost povolte, pokud není server zjistitelný jinými UPnP zařízeními v síti.", "LabelEnableDlnaClientDiscoveryInterval": "Čas pro vyhledání klienta (sekund)", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje dobu trvání v sekundách mezi SSDP vyhledávání prováděných pomocí Jellyfin.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi vyhledáváním SSDP, které Jellyfin provádí.", "LabelEnableDlnaDebugLogging": "Povolit DLNA protokolování (pro ladění)", "LabelEnableDlnaDebugLoggingHelp": "Vytváří velké soubory se záznamy a doporučuje se používat pouze pro potřeby odstraňování problémů.", "LabelEnableDlnaPlayTo": "Povolit DLNA přehrávání", @@ -546,8 +543,6 @@ "LabelFormat": "Formát:", "LabelFree": "Zdarma", "LabelFriendlyName": "Přívětivý název:", - "LabelFriendlyServerName": "Název serveru:", - "LabelFriendlyServerNameHelp": "Toto jméno bude použito jako identifikace serveru, ponecháte-li prázdné bude použit název počítače.", "LabelFromHelp": "Například: {0} (na serveru)", "LabelGroupMoviesIntoCollections": "Seskupit filmy do kolekcí", "LabelGroupMoviesIntoCollectionsHelp": "Při zobrazení seznamů filmu, budou filmy patřící do kolekce, zobrazeny jako jedna položka.", @@ -557,7 +552,7 @@ "LabelHardwareAccelerationTypeHelp": "Hardwarová akcelerace vyžaduje další konfiguraci.", "LabelHomeScreenSectionValue": "Sekce domovské obrazovky {0}:", "LabelHttpsPort": "Lokální HTTPS port:", - "LabelHttpsPortHelp": "Číslo portu TCP, ke kterému by se měl připojit HTTPS server Jellyfin.", + "LabelHttpsPortHelp": "Číslo portu TCP, ke kterému by se měl HTTPS server Jellyfin připojit.", "LabelIconMaxHeight": "Maximální výška ikon:", "LabelIconMaxHeightHelp": "Maximální rozlišení ikon nabízené prostřednictvím upnp:icon.", "LabelIconMaxWidth": "Maximální šířka ikon:", @@ -580,10 +575,9 @@ "LabelKodiMetadataSaveImagePathsHelp": "Toto nastavení je doporučeno, pokud používáte názvy souborů (obrázků), které nejsou v souladu s pokyny Kodi.", "LabelLanguage": "Jazyk:", "LabelLastResult": "Poslední výsledky:", - "LabelLimitIntrosToUnwatchedContent": "Přehrávat trailery pouze u nezhlédnutého obsahu", - "LabelLineup": "Hlavní linie:", + "LabelLineup": "V pořadí:", "LabelLocalHttpServerPortNumber": "Lokální HTTP port:", - "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP, ke kterému by se měl připojit HTTP server Jellyfin.", + "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP, ke kterému by se měl HTTP server Jellyfin připojit.", "LabelLockItemToPreventChanges": "Uzamknout položku pro zabránění budoucích změn", "LabelLoginDisclaimer": "Zřeknutí se následujících práv při přihlášení:", "LabelLoginDisclaimerHelp": "Zpráva, která se zobrazí v dolní části přihlašovací stránky.", @@ -634,8 +628,8 @@ "LabelNext": "Další", "LabelNotificationEnabled": "Povolit toto oznámení", "LabelNumber": "Číslo:", - "LabelNumberOfGuideDays": "Počet dnů pro stažení dat průvodce:", - "LabelNumberOfGuideDaysHelp": "Stažením více dnů dat průvodce umožní v plánech nastavit budoucí nahrávání více do budoucna. Může však déle trvat stažení těchto dat. Auto vybere možnost podle počtu kanálů.", + "LabelNumberOfGuideDays": "Počet dnů programového průvodce ke stažení:", + "LabelNumberOfGuideDaysHelp": "Stažení více dnů televizního průvodce umožňuje naplánovat nahrávání na delší dobu dopředu, ale trvá déle. Automatické nastavení určí počet podle počtu kanálů.", "LabelOptionalNetworkPath": "(Nepovinné) Sdílená síťová složka:", "LabelOriginalAspectRatio": "Původní poměr stran:", "LabelOriginalTitle": "Originální název:", @@ -653,7 +647,7 @@ "LabelPostProcessorArguments": "Argumenty příkazové řádky pro následné zpracování:", "LabelPostProcessorArgumentsHelp": "Použij {path} jako cestu k nahrávanému souboru.", "LabelPreferredDisplayLanguage": "Preferovaný jazyk zobrazení:", - "LabelPreferredDisplayLanguageHelp": "Překlad Jellyfin je projekt ve fázi neustálého vývoje.", + "LabelPreferredDisplayLanguageHelp": "Překlad projektu Jellyfin se neustále vyvíjí.", "LabelPreferredSubtitleLanguage": "Preferovaný jazyk titulků:", "LabelPrevious": "Předchozí", "LabelProfileAudioCodecs": "Audio kodeky:", @@ -696,7 +690,7 @@ "LabelSkipIfAudioTrackPresent": "Přeskočit, pokud výchozí zvuková stopa odpovídá jazyku stahování", "LabelSkipIfAudioTrackPresentHelp": "Zrušte zaškrtnutí pro zobrazení titulků u všech videí, bez ohledu na jazyk zvuku.", "LabelSkipIfGraphicalSubsPresent": "Přeskočit, jestliže video obsahuje vložené titulky", - "LabelSkipIfGraphicalSubsPresentHelp": "Udržením textových verzí titulků bude mít za následek efektivnější dodávku a sníží tím pravděpodobnost překódování videa.", + "LabelSkipIfGraphicalSubsPresentHelp": "Ponecháním textových titulků je možné dosáhnout efektivnějšího přenosu videa a snížení pravděpodobnosti, že bude video nutné překódovat.", "LabelSonyAggregationFlags": "Agregační příznaky Sony:", "LabelSonyAggregationFlagsHelp": "Určuje obsah prvku aggregationFlags ve jmenném prostoru urn:schemas-sonycom:av.", "LabelSortTitle": "Třídit dle názvu:", @@ -796,13 +790,13 @@ "MessageConfirmProfileDeletion": "Jste si jisti, že chcete smazat tento profil?", "MessageConfirmRecordingCancellation": "Zrušit nahrávání?", "MessageConfirmRemoveMediaLocation": "Jste si jist, že chcete odstranit toto umístění?", - "MessageConfirmRestart": "Jste si jist, že chcete restartovat Jellyfin server?", - "MessageConfirmRevokeApiKey": "Jste si jisti, že chcete odvolat tento klíč API? Připojení k aplikaci k Jellyfin Server bude násilně ukončeno.", + "MessageConfirmRestart": "Opravdu chcete restartovat server Jellyfin?", + "MessageConfirmRevokeApiKey": "Opravdu chcete zrušit tento klíč k API? Připojení dané aplikace k serveru Jellyfin bude náhle ukončeno.", "MessageConfirmShutdown": "Jste si jisti, že chcete server vypnout?", "MessageContactAdminToResetPassword": "Kontaktujte, prosím, vašeho systémového administrátora k obnovení vašeho hesla.", "MessageCreateAccountAt": "Vytvořit účet v {0}", "MessageDeleteTaskTrigger": "Opravdu si přejete odebrat spouštění úlohy?", - "MessageDirectoryPickerBSDInstruction": "Pro BSD, budete možná muset nakonfigurovat úložiště přímo ve Vašem FreeNAS Jail aby k nim Jellyfin povolil přístup.", + "MessageDirectoryPickerBSDInstruction": "V operačním systému FreeBSD či FreeNAS může být nutné nakonfigurovat úložiště přímo pomocí izolační funkce jail, aby k němu měl Jellyfin přístup.", "MessageDirectoryPickerInstruction": "Síťové cesty lze zadat ručně v případě, že tlačítko 'Síť' nedokáže automaticky lokalizovat vaše zařízení. Například, {0} nebo {1}.", "MessageDirectoryPickerLinuxInstruction": "Pro systémy Linux jako Arch Linux, CentOS, Debian, Fedora, OpenSUSE nebo Ubuntu musíte udělit uživateli služby oprávnění alespoň pro čtení.", "MessageDownloadQueued": "Stažení zařazeno.", @@ -818,15 +812,13 @@ "MessageNoAvailablePlugins": "Nejsou dostupné žádné zásuvné moduly.", "MessageNoMovieSuggestionsAvailable": "Žádné návrhy nejsou v současnosti k dispozici. Začněte sledovat a hodnotit filmy, a pak se vám doporučení zobrazí.", "MessageNoPluginsInstalled": "Nemáte instalovány žádné zásuvné moduly.", - "MessageNoTrailersFound": "Nebyly nalezeny žádné ukázky z filmů (trailery). Nainstalujte trailer kanál pro lepší zážitek z filmu přidáním knihovny internetových trailerů.", + "MessageNoTrailersFound": "Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.", "MessageNothingHere": "Tady nic není.", "MessagePasswordResetForUsers": "Obnovení hesla bylo provedeno následujícími uživateli. Nyní se mohou přihlásit pomocí kódů PIN, které byly použity k provedení resetu.", "MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.", "MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.", - "MessagePleaseRestart": "Pro dokončení aktualizací, prosím, restartujte.", - "MessagePleaseRestartServerToFinishUpdating": "Restartujte, prosím, server pro aplikaci aktualizací.", "MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.", - "MessagePluginInstallDisclaimer": "Zasuvné moduly vytvořené členy Jellyfin komunity jsou skvělý způsob, jak zvýšit svůj Jellyfin prožitek pomocí doplňkových funkcí :-) Před instalací, se prosím seznamte se všemi dopady, které mohou mít na Jellyfin Server, jako je například delší prohledávání knihovny, další zpracování na pozadí, a snížení stability systému.", + "MessagePluginInstallDisclaimer": "Zásuvné moduly vytvořené členy komunity Jellyfin jsou skvělým způsobem, jak si zlepšit prožitek z používání projektu Jellyfin. Před instalací se prosím seznamte se všemi dopady, které mohou doplňky mít na server Jellyfin, např.: pomalejší skenování knihovny, další zpracování na pozadí nebo snížení stability systému.", "MessageReenableUser": "Viz níže pro znovuzapnutí", "MessageSettingsSaved": "Nastavení uloženo.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií:", @@ -844,7 +836,7 @@ "Mobile": "Mobilní", "Monday": "Pondělí", "MoreFromValue": "Více z {0}", - "MoreUsersCanBeAddedLater": "Další uživatele můžete přidat později na Hlavní nabídce.", + "MoreUsersCanBeAddedLater": "Další uživatele můžete přidat později na nástěnce serveru.", "MoveLeft": "Posunout vlevo", "MoveRight": "Posunout vpravo", "Movies": "Filmy", @@ -862,24 +854,24 @@ "NoNextUpItemsMessage": "Nic nenalezeno. Začněte sledovat Vaše oblíbené seriály!", "NoPluginConfigurationMessage": "Tento zásuvný modul nemá žádné nastavení.", "NoSubtitleSearchResultsFound": "Žádné výsledky.", - "NoSubtitles": "Žádné titulky", + "NoSubtitles": "Žádné", "NoSubtitlesHelp": "Ve výchozím nastavení nebudou titulky načteny. Během přehrávání však mohou být manuálně zapnuty.", "None": "Žádný", "Normal": "Normální", "NumLocationsValue": "{0} složky", "Off": "Vypnuto", "OneChannel": "Jeden kanál", - "OnlyForcedSubtitles": "Pouze vynucené titulky", + "OnlyForcedSubtitles": "Pouze vynucené", "OnlyForcedSubtitlesHelp": "Jen vynucené titulky budou nahrány.", "OptionAdminUsers": "Administrátoři", "OptionAlbumArtist": "Umělec Alba", "OptionAllUsers": "Všichni uživatelé", "OptionAllowAudioPlaybackTranscoding": "Povolit přehrávání audia, které vyžaduje překódování", - "OptionAllowBrowsingLiveTv": "Povolit přístup k Live TV", + "OptionAllowBrowsingLiveTv": "Povolit přístup k televiznímu vysílání", "OptionAllowContentDownloading": "Povolit stahování médií", "OptionAllowLinkSharing": "Povolit sdílení pomocí sociálních médií", "OptionAllowLinkSharingHelp": "Sdílené jsou pouze webové stránky obsahující informace o médiích. Obsah souboru není nikdy sdílen veřejně. Sdílené položky jsou časově omezené a jejich platnost vyprší za {0} dny.", - "OptionAllowManageLiveTv": "Povolit správu nahrávání z Live TV", + "OptionAllowManageLiveTv": "Povolit správu nahrávání z televize", "OptionAllowMediaPlayback": "Povolit přehrávání médií", "OptionAllowRemoteControlOthers": "Povolit vzdálené ovládání ostatních uživatelů", "OptionAllowRemoteSharedDevices": "Povolit vzdálené ovládání sdílených zařízení", @@ -897,7 +889,7 @@ "OptionAutomaticallyGroupSeriesHelp": "Pokud je povoleno, budou díly seriálu uložené ve více adresářích v této knihovně, automaticky sloučeny k jednomu seriálu.", "OptionBlockBooks": "Knihy", "OptionBlockChannelContent": "Obsah internetového kanálu", - "OptionBlockLiveTvChannels": "Kanály Live TV", + "OptionBlockLiveTvChannels": "Televizní kanály", "OptionBlockMovies": "Filmy", "OptionBlockMusic": "Hudba", "OptionBlockTrailers": "Upoutávky", @@ -921,7 +913,7 @@ "OptionDownloadBackImage": "Zadek", "OptionDownloadDiscImage": "Disk", "OptionDownloadImagesInAdvance": "Stáhnout obrázky pokročilejším způsobem", - "OptionDownloadImagesInAdvanceHelp": "Ve výchozím nastavení se většina obrázků stahuje pouze na žádost aplikace Jellyfin. Povolte tuto možnost pro stažení všech obrázků předem, když se importují nová média. To může způsobit výrazně delší skenování knihovny.", + "OptionDownloadImagesInAdvanceHelp": "Ve výchozím nastavení se většina obrázků stahuje pouze na žádost aplikace Jellyfin. Povolením této možnosti dojde ke stažení všech obrázků předem současně s importem nových médií. Může způsobit výrazně delší skenování knihoven.", "OptionDownloadMenuImage": "Nabídka", "OptionDownloadPrimaryImage": "Primární", "OptionDownloadThumbImage": "Miniatura", @@ -931,7 +923,7 @@ "OptionEnableAccessToAllChannels": "Povolit přístup ze všech kanálů", "OptionEnableAccessToAllLibraries": "Povolit přístup ke všem knihovnám", "OptionEnableExternalContentInSuggestions": "Aktivovat externí obsah v návrzích", - "OptionEnableExternalContentInSuggestionsHelp": "Povolit zahrnutí internetových upoutávek a živých televizních programů do navrhovaného obsahu.", + "OptionEnableExternalContentInSuggestionsHelp": "Povolit zahrnutí internetových upoutávek a televizního vysílání do navrhovaného obsahu.", "OptionEnableForAllTuners": "Povolit pro všechna zařízení tunerů", "OptionEnableM2tsMode": "Povolit M2ts mód", "OptionEnableM2tsModeHelp": "Povolit režim M2TS při kódování do MPEGTS.", @@ -947,7 +939,7 @@ "OptionHasSubtitles": "Titulky", "OptionHasThemeSong": "Tematická hudba", "OptionHasThemeVideo": "Tematické video", - "OptionHasTrailer": "Ukázka/trailer", + "OptionHasTrailer": "Upoutávka", "OptionHideUser": "Skrýt tohoto uživatele z přihlašovacích obrazovek", "OptionHideUserFromLoginHelp": "Vhodné pro soukromé a administrátorské účty. Pro přihlášení musí uživatel manuálně zadat uživatelské jméno a heslo.", "OptionHlsSegmentedSubtitles": "Segmentované titulky HLS", @@ -959,7 +951,7 @@ "OptionMissingEpisode": "Chybějící episody", "OptionMonday": "Pondělí", "OptionNameSort": "Název", - "OptionNew": "Nový...", + "OptionNew": "Nový…", "OptionNone": "Žádný", "OptionOnAppStartup": "Při spuštění aplikace", "OptionOnInterval": "V intervalu", @@ -977,12 +969,12 @@ "OptionReportByteRangeSeekingWhenTranscoding": "Hlásit, že server podporuje vyhledávání bajtů při překódování", "OptionReportByteRangeSeekingWhenTranscodingHelp": "Tento krok je nutný pro některá zařízení, které nemají moc dobrý time seek.", "OptionRequirePerfectSubtitleMatch": "Stahovat jen titulky, které perfektně sedí k mým video souborům", - "OptionResElement": "Zdrojový element", + "OptionResElement": "Prvek \"res\"", "OptionResumable": "Pozastavavitelný", "OptionRuntime": "Délka", "OptionSaturday": "Sobota", "OptionSaveMetadataAsHidden": "Ukládat metadata a obrázky jako skryté soubory", - "OptionSaveMetadataAsHiddenHelp": "Změna bude platit pro nově uložená metadata do budoucna. Existující soubory metadat budou aktualizovány příště, jakmile budou uloženy Jellyfin Serverem.", + "OptionSaveMetadataAsHiddenHelp": "Změna se projeví u všech nově uložených metadat. Existující soubory metadat se aktualizují při příštím uložení serverem Jellyfin.", "OptionSpecialEpisode": "Speciální", "OptionSubstring": "subřetězec", "OptionSunday": "Neděle", @@ -999,9 +991,9 @@ "OptionWeekly": "Týdenní", "OriginalAirDateValue": "Datum vysílání originálu: {0}", "Overview": "Přehled/Obsah", - "PackageInstallCancelled": "Instalace {0} zrušena.", - "PackageInstallCompleted": "Instalace {0} dokončena.", - "PackageInstallFailed": "Instalace {0} selhala.", + "PackageInstallCancelled": "Instalace {0} (verze {1}) zrušena.", + "PackageInstallCompleted": "Instalace {0} (verze {1}) dokončena.", + "PackageInstallFailed": "Instalace {0} (verze {1}) selhala.", "ParentalRating": "Rodičovské hodnocení", "PasswordMatchError": "Heslo a potvrzení hesla musí souhlasit.", "PasswordResetComplete": "Heslo bylo obnoveno.", @@ -1022,7 +1014,7 @@ "PlayNext": "Přehrát další", "PlayNextEpisodeAutomatically": "Automaticky přehrávat další epizodu", "PlayOnAnotherDevice": "Přehrát na jiném zařízení", - "PlaybackErrorNoCompatibleStream": "Žádné kompatibilní streamy nejsou v současné době k dispozici. Zkuste to prosím později, nebo pro více podrobností kontaktujte svého správce systému.", + "PlaybackErrorNoCompatibleStream": "Tento klient není kompatibilní s médiem a server neodesílá kompatibilní formát médií.", "PlaybackErrorNotAllowed": "V současné době nejste oprávněni přehrávat tento obsah. Pro více informací se obraťte se na správce systému.", "PlaybackErrorPlaceHolder": "Chcete-li toto video přehrát, vložte disk.", "Played": "Přehráno", @@ -1030,7 +1022,7 @@ "PleaseAddAtLeastOneFolder": "Přidejte prosím nejméně jednu složku do této knihovny pomocí tlačítka Přidat.", "PleaseConfirmPluginInstallation": "Pro potvrzení, že jste si přečetli text výše a chcete pokračovat v instalaci zásuvných modulů, klikněte na tlačítko OK.", "PleaseEnterNameOrId": "Prosím, zadejte název nebo externí Id.", - "PleaseRestartServerName": "Prosím, restartujte Jellyfin Server - {0}.", + "PleaseRestartServerName": "Prosím restartuje server Jellyfin - {0}.", "PleaseSelectTwoItems": "Vyberte nejméně dvě položky prosím.", "Premiere": "Premiéra", "Premieres": "Premiéry", @@ -1053,7 +1045,7 @@ "RecordingScheduled": "Plán nahrávání.", "Recordings": "Nahrávky", "Refresh": "Obnovit", - "RefreshDialogHelp": "Metadata se aktualizují na základě nastavení a internetových služeb, které jsou povoleny v nastavení Jellyfin Server.", + "RefreshDialogHelp": "Metadata se aktualizují na základě nastavení a internetových služeb, které jsou povoleny na nástěnce serveru Jellyfin.", "RefreshMetadata": "Obnovit metadata", "RefreshQueued": "Obnovení zařazeno.", "ReleaseDate": "Datum vydání", @@ -1067,7 +1059,6 @@ "RepeatOne": "Opakovat jeden", "ReplaceAllMetadata": "Přepsat všechna metadata", "ReplaceExistingImages": "Nahradit existující obrázky", - "RestartPleaseWaitMessage": "Počkejte prosím, než se Jellyfin Server vypne a restartuje. Může to trvat pár minut.", "ResumeAt": "Obnovit přehrávání od {0}", "Rewind": "Přetočit zpět", "RunAtStartup": "Spustit po startu", @@ -1089,9 +1080,9 @@ "SeriesRecordingScheduled": "Plán nahrávání seriálu.", "SeriesSettings": "Nastavení seriálu", "SeriesYearToPresent": "{0} - Současnost", - "ServerNameIsRestarting": "Jellyfin Server - {0} je restartován.", - "ServerNameIsShuttingDown": "Jellyfin Server - {0} je vypínán.", - "ServerUpdateNeeded": "Tento Jellyfin Server je třeba aktualizovat. Chcete-li stáhnout nejnovější verzi, navštivte prosím {0}", + "ServerNameIsRestarting": "Server Jellyfin - {0} se restartuje.", + "ServerNameIsShuttingDown": "Server Jellyfin - {0} se vypíná.", + "ServerUpdateNeeded": "Tento server Jellyfin je nutné aktualizovat. Chcete-li stáhnout nejnovější verzi, navštivte prosím {0}", "Settings": "Nastavení", "SettingsSaved": "Nastavení uloženo.", "SettingsWarning": "Změna těchto hodnot může způsobit nestabilitu nebo selhání připojení. Pokud narazíte na nějaké problémy, doporučujeme jej změnit zpět na výchozí hodnotu.", @@ -1126,13 +1117,13 @@ "TabCodecs": "Kodeky", "TabCollections": "Kolekce", "TabContainers": "Obaly", - "TabDashboard": "Hlavní nabídka", + "TabDashboard": "Nástěnka", "TabDevices": "Zařízení", "TabDisplay": "Zobrazení", "TabEpisodes": "Epizody", "TabFavorites": "Oblíbené", "TabGenres": "Žánry", - "TabGuide": "Průvodce", + "TabGuide": "Programový průvodce", "TabLatest": "Nejnovější", "TabLogs": "Záznamy", "TabMovies": "Filmy", @@ -1160,7 +1151,7 @@ "TabSongs": "Skladby", "TabStreaming": "Streamování", "TabSuggestions": "Návrhy", - "TabTrailers": "Ukázky/trailery", + "TabTrailers": "Upoutávky", "TabTranscoding": "Překódování", "TabUpcoming": "Nové", "TabUsers": "Uživatelé", @@ -1182,7 +1173,7 @@ "Unrated": "Nehodnoceno", "Up": "Nahoru", "Upload": "Nahrát", - "UserProfilesIntro": "Jellyfin zahrnuje podporu uživatelských profilů s podrobným nastavením zobrazení, stavem přehrávání a rodičovskou kontrolou.", + "UserProfilesIntro": "Jellyfin podporuje uživatelské profily s podrobným nastavením zobrazení, stavem přehrávání a rodičovskou kontrolou.", "ValueAlbumCount": "{0} alb", "ValueAudioCodec": "Audio kodeky: {0}", "ValueCodec": "Kodek: {0}", @@ -1208,47 +1199,42 @@ "Watched": "Zhlédnuto", "Wednesday": "Středa", "WelcomeToProject": "Vítejte v Jellyfin!", - "WizardCompleted": "To je vše, co nyní potřebujeme. Jellyfin začala shromažďovat informace o vaší knihovně médií. Podívejte se na některé z našich aplikací, a potom klepněte na tlačítko Dokončit pro zobrazení hlavního panelu.", - "Writer": "Napsal", + "WizardCompleted": "To je zatím vše, co potřebujeme. Server Jellyfin začal shromažďovat informace o vaší knihovně médií. Vyzkoušejte některé z našich aplikací a potom klikněte na tlačítko Dokončit pro zobrazení nástěnky.", + "Writer": "Scénárista", "XmlDocumentAttributeListHelp": "Tyto atributy jsou použity na kořenový prvek každé XML odpovědi.", "XmlTvKidsCategoriesHelp": "Programy s těmito kategoriemi budou zobrazeny jako programy pro děti. Více kategorií oddělte \"|\".", "XmlTvMovieCategoriesHelp": "Programy s těmito kategoriemi budou zobrazeny jako filmy. Více kategorií oddělte \"|\".", "XmlTvNewsCategoriesHelp": "Programy s těmito kategoriemi budou zobrazeny jako zpravodajské pořady. Více kategorií oddělte \"|\".", - "XmlTvPathHelp": "Cesta k souboru XMLTV. Jellyfin tento soubor načte a pravidelně jej kontroluje, zda neobsahuje aktualizace. Jste zodpovědní za vytvoření a aktualizaci souboru.", + "XmlTvPathHelp": "Cesta k souboru XMLTV. Jellyfin tento soubor načte a bude pravidelně kontrolovat, zda neobsahuje aktualizace. Vytvoření a aktualizace souboru je na uživateli.", "XmlTvSportsCategoriesHelp": "Programy s těmito kategoriemi budou zobrazeny jako sportovní pořady. Více kategorií oddělte \"|\".", "Yes": "Ano", "Yesterday": "Včera", "Absolute": "Absolutní", - "AddUserByManually": "Přidat místního uživatele ručním zadáním informací.", "AirDate": "Datum vysílání", "Aired": "Vysíláno", "Alerts": "Upozornění", - "AllComplexFormats": "Všechny komplexní formáty (ASS, SSA, VOBSUB, PGS, SUB/IDX, atd.)", + "AllComplexFormats": "Všechny komplexní formáty (ASS, SSA, VOBSUB, PGS, SUB/IDX, atd.)", "AllLibraries": "Všechny knihovny", "AllowDeletionFromAll": "Povolit smazání médií ze všech knihoven", "AllowMediaConversion": "Povolit konverzi médií", "AllowMediaConversionHelp": "Povolit nebo zakázat přístup k funkci konverze médií.", "AllowOnTheFlySubtitleExtraction": "Povolit extrahování titulků za běhu", - "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky mohou být extrahovány z videa a dodávány do aplikací ve formě prostého textu, aby se zabránilo překódování videa. V některých systémech to může trvat dlouho a způsobit zasekávání přehrávání videa. Při vypnutí funkce budou během překódování obsažené titulky vypáleny do obrazu, pokud je klientské zařízení nativně nepodporuje.", - "AllowRemoteAccess": "Povolit vzdálené připojení na tento Jellyfin server.", + "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky je možné vytáhnout z videa a dodat klientům v textové podobě, aby nebylo nutné video překódovat. Na některých systémech to může trvat dlouho a způsobit zasekávání videa. Pokud tuto funkci vypnete a klientské zařízení vložené titulky nepodporuje, při překódování budou vypáleny přímo do obrazu.", + "AllowRemoteAccess": "Povolit vzdálené připojení k tomuto serveru Jellyfin.", "AllowRemoteAccessHelp": "Pokud není zapnuto, všechna vzdálená připojení budou blokována.", - "AllowSeasonalThemesHelp": "Pokud je povoleno, sezónní motivy občas přepíšou nastavení vašeho motivu.", "AllowedRemoteAddressesHelp": "Seznam IP adres nebo síťových masek oddělených čárkou pro sítě, ze kterých se lze vzdáleně připojit. Pokud necháte prázdné, všechny adresy budou povoleny.", "AnamorphicVideoNotSupported": "Anamorfní video není podporováno", - "AndroidUnlockRestoreHelp": "Chcete-li obnovit předchozí nákup, ujistěte se, že jste přihlášeni do zařízení se stejným účtem Google (nebo Amazon), na kterém byl nákup původně proveden. Ujistěte se, že je povolen přístup k úložišti aplikací a není omezen žádnou rodičovskou kontrolou a ujistěte se, že máte aktivní připojení k internetu. Toto budete muset provést pouze jednou, abyste tento předchozí nákup obnovili.", "AnyLanguage": "Jakýkoli jazyk", "Ascending": "Vzestupně", "AudioBitDepthNotSupported": "Bitová hloubka zvuku není podporována", "AudioSampleRateNotSupported": "Frekvence vzorkování zvuku není podporována", "AutoBasedOnLanguageSetting": "Automaticky (na základě jazykového nastavení)", - "AutomaticallyConvertNewContent": "Automaticky zkonvertovat nový obsah", - "AutomaticallyConvertNewContentHelp": "Nový obsah přidaný do této složky bude automaticky zkonvertován.", - "Banner": "Banner", + "Banner": "Výřez plakátu", "BestFit": "Nejvhodnější", - "Blacklist": "Černá listina", + "Blacklist": "Zakázat vše kromě výjimek", "BobAndWeaveWithHelp": "Bob and weave (vyšší kvalita, ale pomalejší)", "Browse": "Procházet", - "BurnSubtitlesHelp": "Určuje, zda má server vypalovat titulky při převodu videa v závislosti na formátu titulků. Vynechání vypalování titulků zlepší výkon serveru. Chcete-li vypálit grafické formáty (VOBSUB, PGS, SUB / IDX atd.) a některé titulky ASS / SSA, vyberte možnost Auto.", + "BurnSubtitlesHelp": "Určuje, zda má server při překódování videa vypálit titulky do obrazu. Tato funkce má velký negativní vliv na výkon. Chcete-li vypálit grafické formáty titulků (VOBSUB, PGS, SUB, IDX, atd.) a některé titulky ASS nebo SSA, vyberte možnost Automaticky.", "ButtonInfo": "Info", "ButtonMenu": "Menu", "ButtonOk": "Ok", @@ -1271,7 +1257,7 @@ "ConvertingDots": "Konverze...", "CriticRating": "Kritické hodnocení", "DefaultCameraUploadPathHelp": "Vyberte vlastní cestu nahrávání. Pokud zůstane prázdné, bude použita výchozí složka. Pokud použijete vlastní cestu, bude třeba ji přidat jako knihovnu v nastavení knihovny Jellyfin.", - "Depressed": "Stlačený", + "Depressed": "Vytlačené", "Descending": "Klesající", "DetectingDevices": "Hledání zařízení", "DirectPlayError": "Chyba přímého přehrávání", @@ -1295,7 +1281,7 @@ "Features": "Funkce", "Filters": "Filtry", "Folders": "Složky", - "General": "Hlavní", + "General": "Obecné", "Genre": "Žánr", "GroupBySeries": "Seskupit podle série", "HandledByProxy": "Zpracováno reverzním proxy", @@ -1317,11 +1303,10 @@ "HeaderFetcherSettings": "Nastavení načítání", "HeaderImageLogo": "Logo", "HeaderImageOptions": "Volby obrázku", - "HeaderInviteWithJellyfinConnect": "Pozvat s Jellyfin Connect", "HeaderKodiMetadataHelp": "Chcete-li povolit nebo zakázat Nfo metadata, upravte nastavení knihovny v sekci ukládání metadat.", - "HeaderLiveTV": "Živá TV", - "HeaderLiveTv": "Živá TV", - "HeaderLiveTvTunerSetup": "Nastavení tuneru Live TV", + "HeaderLiveTV": "Televize", + "HeaderLiveTv": "Televize", + "HeaderLiveTvTunerSetup": "Nastavení televizního tuneru", "HeaderMenu": "Menu", "HeaderNewDevices": "Nové zařízení", "HeaderPhotoAlbums": "Fotoalba", @@ -1335,14 +1320,10 @@ "HeaderTV": "TV", "HeaderTypeImageFetchers": "{0} stahovačů obrázků", "HeaderUpcomingEpisodes": "Následující epizody", - "HeaderUpcomingNews": "Následující novinky", "HeaderVideo": "Video", "HeaderVideoType": "Formát videa", "Horizontal": "Vodorovně", - "HowWouldYouLikeToAddUser": "Jak chcete přidat uživatele?", "HttpsRequiresCert": "Chcete-li povolit zabezpečená připojení, budete muset zadat důvěryhodný certifikát SSL, například Let's Encrypt. Zadejte prosím certifikát nebo zakažte zabezpečená připojení.", - "Invitations": "Pozvánky", - "InviteAnJellyfinConnectUser": "Přidejte uživatele odesláním e-mailové pozvánky.", "KeepDownload": "Zachovat stahování", "LabelAlbum": "Album:", "LabelAllowedRemoteAddresses": "Filtr vzdálené IP adresy:", @@ -1355,7 +1336,6 @@ "LabelCameraUploadPathHelp": "Vyberte vlastní umístění nahraných souborů. Toto přepíše jakékoli výchozí nastavení v sekci nahrávání souborů z fotoaparátu. Pokud použijete vlastní umístění, bude potřeba jej také přidat jako knihovnu v nastavení Jellyfin.", "LabelCertificatePassword": "Heslo certifikátu:", "LabelCertificatePasswordHelp": "Pokud certifikát vyžaduje heslo, zadejte jej prosím zde.", - "LabelConvertTo": "Konvertovat na:", "LabelCustomCertificatePath": "Vlastní umístění SSL certifikátu:", "LabelCustomCertificatePathHelp": "Umístění souboru PKCS #12, který obsahuje certifikát a soukromý klíč k povolení podpory TLS na vlastní doméně.", "LabelDateTimeLocale": "Místní nastavení data:", @@ -1372,7 +1352,7 @@ "LabelLimit": "Limit:", "LabelMaxStreamingBitrate": "Maximální kvalita streamování:", "LabelMetadata": "Metadata:", - "LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, může poskytování cesty ke sdílené složce umožnit aplikacím Jellyfin v jiných zařízeních přímý přístup k mediálním souborům.", + "LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, zadání cesty ke sdílené složce umožní aplikacím Jellyfin na jiných zařízeních přímý přístup k souborům s médii. Například {0} nebo {1}.", "LabelPersonRole": "Úloha:", "LabelPlaylist": "Playlist:", "LabelReasonForTranscoding": "Důvod pro překódování:", @@ -1385,7 +1365,6 @@ "LabelSortOrder": "Pořadí řazení:", "LabelSpecialSeasonsDisplayName": "Zobrazovaný název pro zvláštní sezónu:", "LabelSubtitleDownloaders": "Stahovače titulků:", - "LabelSyncNoTargetsHelp": "Vypadá to, že v současné době nemáte žádné aplikace, které podporují stahování offline.", "LabelTVHomeScreen": "Hlavní obrazovka TV režimu:", "LabelTag": "Tag:", "LabelTypeMetadataDownloaders": "{0} stahovače metadat:", @@ -1399,7 +1378,7 @@ "LetterButtonAbbreviation": "A", "LinkApi": "API", "LinksValue": "Odkazy: {0}", - "LiveTV": "Živá TV", + "LiveTV": "Televize", "LiveTvFeatureDescription": "Streamujte televizní vysílání do libovolné aplikace Jellyfin s kompatibilním televizním tunerem instalovaným na serveru Jellyfin.", "Logo": "Logo", "ManageLibrary": "Spravovat knihovnu", @@ -1415,7 +1394,6 @@ "HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteSongs": "Oblíbená hudba", - "HeaderRestartingServer": "Restartování serveru", "LabelAuthProvider": "Poskytovatel ověření:", "LabelServerNameHelp": "Tento název bude použit k identifikaci serveru a bude výchozí pro název počítače serveru.", "LabelPasswordResetProvider": "Poskytovatel obnovy hesla:", @@ -1458,33 +1436,32 @@ "OptionProfileAudio": "Audio", "OptionProfileVideo": "Video", "OptionProfileVideoAudio": "Video Audio", - "OptionProtocolHls": "Živý HTTP stream", + "OptionProtocolHls": "Přímý přenos z internetu", "OptionProtocolHttp": "HTTP", "OptionRequirePerfectSubtitleMatchHelp": "Vyžadování dokonalé shody filtruje titulky tak, aby obsahovaly pouze ty, které byly testovány a ověřeny s vaším přesným videosouborem. Zrušení zaškrtnutí tohoto políčka zvýší pravděpodobnost stahování titulků, ale zvýší pravděpodobnost chybného nebo nesprávného textu titulků.", "PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit, když tento uživatel o něj požádá", "PlaybackSettings": "Nastavení přehrávání", "PlaybackSettingsIntro": "Chcete-li nastavit výchozí volby přehrávání, zastavte přehrávání videa a klepněte na ikonu uživatele v pravé horní části aplikace.", - "PluginInstalledMessage": "Plugin byl úspěšně nainstalován. Jellyfin server bude muset být restartován, aby se změny projevily.", + "PluginInstalledMessage": "Zásuvný modul byl úspěšně nainstalován. Server Jellyfin bude nutné restartovat, aby se změny projevily.", "PluginTabAppClassic": "Jellyfin pro Windows Media Center", "PreferEmbeddedTitlesOverFileNames": "Preferovat vložené názvy nad názvy souborů", "PreferEmbeddedTitlesOverFileNamesHelp": "Toto určuje výchozí název zobrazení, pokud nejsou k dispozici žádná metadata z internetu nebo místní metadata.", "PreferredNotRequired": "Preferováno, ale není vyžadováno", - "Raised": "Vyvolané", + "Raised": "Vystupující", "Rate": "Hodnocení", "RequiredForAllRemoteConnections": "Požadováno pro všechna vzdálená připojení", "SaveSubtitlesIntoMediaFolders": "Titulky ukládat do mediálních složek", "SaveSubtitlesIntoMediaFoldersHelp": "Ukládání titulků vedle video souborů umožní jejich snadnější správu.", "ScanLibrary": "Skenovat knihovnu", "SeriesDisplayOrderHelp": "Seřadit epizody podle data vysílání, pořadí DVD nebo absolutního číslování.", - "ServerRestartNeededAfterPluginInstall": "Jellyfin server bude nutné po instalaci pluginu restartovat.", + "ServerRestartNeededAfterPluginInstall": "Server Jellyfin bude nutné po instalaci zásuvného modulu restartovat.", "ShowAdvancedSettings": "Zobrazit rozšířená nastavení", - "ShowTitle": "Ukázat název", - "ShowYear": "Ukázat rok", + "ShowTitle": "Zobrazit název", + "ShowYear": "Zobrazit rok", "SimultaneousConnectionLimitHelp": "Maximální počet povolených současných streamů. Pro vypnutí omezení zadejte 0.", "SmallCaps": "Malá písmena", "Smaller": "Menší", "Sort": "Třídit", - "StatsForNerds": "Statistiky pro šprty", "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Tato nastavení platí také pro jakékoli přehrávání na Chromecastu spuštěné tímto zařízením.", "SubtitleAppearanceSettingsDisclaimer": "Tato nastavení se nevztahuje na grafické titulky (PGS, DVD atd.) nebo ASS/SSA titulky, které mají vlastní vložené styly.", "SubtitleDownloadersHelp": "Povolte a zařaďte preferované stahovače titulků v pořadí podle priority.", @@ -1493,16 +1470,16 @@ "TV": "TV", "TabDirectPlay": "Přímé přehrávání", "TabInfo": "Info", - "TabLiveTV": "Živá TV", + "TabLiveTV": "Televize", "TabMetadata": "Metadata", "TabPlaylist": "Playlist", "TabServer": "Server", "TagsValue": "Tagy: {0}", "ThemeSongs": "Tematická hudba", "ThemeVideos": "Tematická videa", - "Trailers": "Trailery", + "Trailers": "Upoutávky", "TvLibraryHelp": "Podívejte se na {0}průvodce pojmenováním TV pořadů{1}.", - "Uniform": "Uniformní", + "Uniform": "Obrys", "Unplayed": "Nepřehrané", "UserAgentHelp": "Zadejte vlastní HTTP hlavičku user agenta.", "ValueMinutes": "{0} min", @@ -1511,7 +1488,7 @@ "Vertical": "Svisle", "VideoRange": "Rozsah videa", "ViewPlaybackInfo": "Zobrazení informací o přehrávání", - "Whitelist": "Bílá listina", + "Whitelist": "Povolit vše kromě výjimek", "HeaderHome": "Domů", "DashboardOperatingSystem": "Operační systém: {0}", "DashboardArchitecture": "Architektura: {0}", @@ -1540,7 +1517,7 @@ "LabelPlayMethod": "Způsob přehrání:", "LabelPlayer": "Přehrávač:", "LabelFolder": "Složka:", - "LabelBaseUrlHelp": "Zde můžete přidat vlastní podsložku aby bylo možné přistupovat na server z unikátnější adresy.", + "LabelBaseUrlHelp": "Přidá vlastní řetězec na konec adresy serveru, např.: http://priklad.cz/<vlastni-retezec>", "LabelBaseUrl": "Výchozí URL:", "LabelBitrate": "Datový tok:", "LabelAudioSampleRate": "Vzorkovací frekvence zvuku:", @@ -1561,14 +1538,120 @@ "CopyStreamURLError": "Při kopírování URL došlo k chybě.", "LabelVideoResolution": "Rozlišení videa:", "LabelStreamType": "Typ streamu:", - "EnableFastImageFadeInHelp": "Povolte rychlejší animaci pro načtené obrázky", - "EnableFastImageFadeIn": "Rychlé zmizení obrazu", "LabelPlayerDimensions": "Zobrazené rozlišení:", "LabelDroppedFrames": "Vynechané snímky:", "LabelCorruptedFrames": "Poškozené snímky:", - "OptionForceRemoteSourceTranscoding": "Vynutit transkódování vzdálených zdrojů médií (jako např. živá TV)", + "OptionForceRemoteSourceTranscoding": "Vynutit překódování vzdálených zdrojů médií (např.: televizního vysílání)", "NoCreatedLibraries": "Zdá se, že jste dosud nevytvořili žádnou knihovnu. {0}Chtěli byste nějakou vytvořit nyní?{1}", "AskAdminToCreateLibrary": "Požádejte administrátora o vytvoření knihovny.", - "AllowFfmpegThrottlingHelp": "Když se překódování nebo remux dostane dostatečně daleko dopředu od aktuální pozice přehrávání, pozastaví se proces, aby spotřeboval méně zdrojů. To je nejužitečnější při sledování bez častého vyhledávání. Pokud máte problémy s přehráváním, vypněte tuto funkci.", - "AllowFfmpegThrottling": "Omezit překódování" + "AllowFfmpegThrottlingHelp": "Pozastaví proces překódování či remuxování, pokud je dostatečně napřed, aby se spotřebovalo méně zdrojů. Nejvhodnější, pokud příliš nepřeskakujete. Pokud máte problémy s přehráváním, tuto funkci vypněte.", + "AllowFfmpegThrottling": "Omezit překódování", + "BoxSet": "Sbírka", + "Track": "Stopa", + "Season": "Sezóna", + "ReleaseGroup": "Vydavatel", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferovat vloženou informaci o epizodě před názvem souboru", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Používat informaci o epizodě z vložených metadat, pokud jsou k dispozici.", + "Person": "Osoba", + "OtherArtist": "Ostatní interpreti", + "Movie": "Film", + "Episode": "Epizoda", + "ClientSettings": "Nastavení klienta", + "Artist": "Interpret", + "AlbumArtist": "Interpret alba", + "Album": "Album", + "OnApplicationStartup": "Při zapnutí aplikace", + "EveryXHours": "Každých {0} hodin", + "EveryHour": "Každou hodinu", + "EveryXMinutes": "Každých {0} minut", + "OnWakeFromSleep": "Při probuzení", + "DailyAt": "Denně v {0}", + "PersonRole": "jako {0}", + "ListPaging": "{0}-{1} ze {2}", + "WriteAccessRequired": "Server Jellyfin vyžaduje oprávnění pro zápis do této složky. Zkontrolujte oprávnění a zkuste to znovu.", + "PathNotFound": "Cesta nebyla nalezena. Zkontrolujte, zda je platná a zkuste to znovu.", + "WeeklyAt": "V {0} v {1}", + "LastSeen": "Naposledy zobrazené {0}", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "LabelLibraryPageSizeHelp": "Určuje počet položek k zobrazení na stránce knihovny. Nastavte na 0 pro vypnutí stránkování.", + "LabelLibraryPageSize": "Velikost stránky knihovny:", + "LabelDeinterlaceMethod": "Metoda odstranění prokládání:", + "DeinterlaceMethodHelp": "Vyberte metodu odstranění prokládání obrazu při překódování obsahu.", + "UnsupportedPlayback": "Jellyfin nedokáže dešifrovat obsah chráněný Správou digitálních práv (DRM), ale pokusí se zobrazit veškerý obsah, včetně toho chráněného. Některé soubory se nemusí vůbec zobrazit kvůli šifrování nebo jiným nepodporovaným funkcím, např.: interaktivním názvům.", + "MessageUnauthorizedUser": "Momentálně nemáte oprávnění k přístupu na server. Další informace získáte od správce serveru.", + "Filter": "Filtr", + "New": "Nové", + "ButtonTogglePlaylist": "Playlist", + "ButtonToggleContextMenu": "Více", + "LabelNightly": "Nightly", + "LabelStable": "Stabilní", + "LabelChromecastVersion": "Verze Chromecastu", + "ApiKeysCaption": "Seznam povolených API klíčů", + "LabelEnableHttpsHelp": "Umožní serveru naslouchat na určeném portu HTTPS. K fungování je nutné nakonfigurovat i platný certifikát.", + "LabelEnableHttps": "Povolit HTTPS", + "HeaderServerAddressSettings": "Nastavení adresy serveru", + "HeaderRemoteAccessSettings": "Nastavení vzdáleného přístupu", + "HeaderHttpsSettings": "Nastavení HTTPS", + "LabelRequireHttpsHelp": "Server automaticky přesměruje všechny požadavky z HTTP na HTTPS. Pokud server nenaslouchá na portu HTTPS, tato funkce nemá žádný účinek.", + "LabelRequireHttps": "Vyžadovat HTTPS", + "TabDVR": "Nahrávání", + "HeaderDVR": "Nahrávání", + "SaveChanges": "Uložit změny", + "LabelSyncPlayPlaybackDiff": "Rozdíl v době přehrávání:", + "SyncPlayAccessHelp": "Určuje úroveň přístupu k synchronizaci přehrávání, kterou tento uživatel bude mít. Tato funkce umožňuje synchronizovat přehrávání s dalšími zařízeními.", + "MessageSyncPlayErrorMedia": "Zapnutí synchronizace přehrávání se nezdařilo. Chyba média.", + "MessageSyncPlayErrorMissingSession": "Zapnutí synchronizace přehrávání se nezdařilo. Nebyla nalezena relace.", + "MessageSyncPlayErrorNoActivePlayer": "Nebyl nalezen žádný aktivní přehrávač. Synchronizace přehrávání byla vypnuta.", + "MessageSyncPlayErrorAccessingGroups": "Při načítání seznamu skupin došlo k chybě.", + "MessageSyncPlayLibraryAccessDenied": "Přístup k tomuto obsahu je omezen.", + "MessageSyncPlayJoinGroupDenied": "K použití synchronizace přehrávání je vyžadováno povolení.", + "MessageSyncPlayCreateGroupDenied": "K vytvoření skupiny je vyžadováno povolení.", + "MessageSyncPlayGroupDoesNotExist": "Připojení ke skupině se nezdařilo, protože skupina neexistuje.", + "MessageSyncPlayPlaybackPermissionRequired": "K přehrávání je vyžadováno povolení.", + "MessageSyncPlayNoGroupsAvailable": "Neexistují žádné skupiny. Začněte něco přehrávat.", + "MessageSyncPlayGroupWait": "Přehrávání uživatele {0} se načítá…", + "MessageSyncPlayUserLeft": "Uživatel {0} opustil skupinu.", + "MessageSyncPlayUserJoined": "Uživatel {0} se připojil do skupiny.", + "MessageSyncPlayDisabled": "Synchronizace přehrávání zakázána.", + "MessageSyncPlayEnabled": "Synchronizace přehrávání povolena.", + "LabelSyncPlayAccess": "Přístup k funkci synchronizace přehrávání", + "LabelSyncPlayAccessNone": "Zakázáno pro tohoto uživatele", + "LabelSyncPlayAccessJoinGroups": "Povolit uživateli připojovat se do skupin", + "LabelSyncPlayAccessCreateAndJoinGroups": "Povolit uživateli vytvářet a připojovat se do skupin", + "LabelSyncPlayLeaveGroupDescription": "Zakázat synchronizaci přehrávání", + "LabelSyncPlayLeaveGroup": "Opustit skupinu", + "LabelSyncPlayNewGroupDescription": "Vytvořit skupinu", + "LabelSyncPlayNewGroup": "Nová skupina", + "LabelSyncPlaySyncMethod": "Způsob synchronizace:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Časový rozdíl mezi serverem:", + "HeaderSyncPlayEnabled": "Synchronizace přehrávání povolena", + "HeaderSyncPlaySelectGroup": "Připojit ke skupině", + "EnableDetailsBannerHelp": "Zobrazí obrázek ve vrchní části detailu položky.", + "EnableDetailsBanner": "Obrázek detailu", + "ShowMore": "Zobrazit více", + "ShowLess": "Zobrazit méně", + "EnableBlurhashHelp": "Nenačtené obrázky budou zobrazeny pomocí neurčitých zástupných obrázků", + "EnableBlurhash": "Povolit zástupné obrázky", + "ButtonCast": "Přehrát v zařízení", + "ButtonSyncPlay": "SyncPlay", + "MessageNoGenresAvailable": "Povolit některým poskytovatelům metadat stahovat informace o žánrech z Internetu.", + "EnableFasterAnimationsHelp": "Použít rychlejší animace a přechody", + "EnableFasterAnimations": "Rychlejší animace", + "EnableDecodingColorDepth10Vp9": "Povolit 10bitové hardwarové dekódování formátu VP9", + "EnableDecodingColorDepth10Hevc": "Povolit 10bitové hardwarové dekódování formátu HEVC", + "TabRepositories": "Repozitáře", + "MessageAddRepository": "Pokud chcete přidat repozitář, klikněte na tlačítko vedle záhlaví a vyplňte požadované informace.", + "LabelRepositoryNameHelp": "Vlastní pojmenování, které slouží k odlišení tohoto repozitáře od ostatních repozitářů přidaných na vašem serveru.", + "LabelRepositoryName": "Název repozitáře", + "LabelRepositoryUrlHelp": "Umístění manifestu repozitáře, který chcete zahrnout.", + "LabelRepositoryUrl": "URL adresa repozitáře", + "HeaderNewRepository": "Nový repozitář", + "MessageNoRepositories": "Neexistují žádné repozitáře.", + "ButtonPlayer": "Přehrávač", + "Writers": "Scénáristé", + "ClearQueue": "Vymazat frontu", + "StopPlayback": "Zastavit přehrávání", + "ViewAlbumArtist": "Zobrazit interpreta alba" } diff --git a/src/strings/da.json b/src/strings/da.json index 22ddc52200d..5e29e1574c6 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -12,11 +12,11 @@ "AllChannels": "Alle kanaler", "AllEpisodes": "Alle episoder", "AllLibraries": "Alle biblioteker", - "AllowHWTranscodingHelp": "Hvis aktiveret, omkoder tuneren streams on-the-fly. Dette kan hjælpe med at reducere omkodning der kræves af Jellyfin Server.", + "AllowHWTranscodingHelp": "Tillader tuneren at transkode streams \"on the fly\". Dette kan hjælpe med at reducere transkodningen, der kræves af serveren.", "AllowMediaConversion": "Tillad media konvertering", "AllowMediaConversionHelp": "Giv eller nægt adgang til Konvertér Media featuren.", "AllowOnTheFlySubtitleExtraction": "Tillad udtræk af undertekster on-the-fly", - "AllowOnTheFlySubtitleExtractionHelp": "Indeholdte undertekster kan trækkes ud af videoer og leveres til Jellyfin apps i ren tekst for at afhjælpe video kodning. På nogle systemer kan dette tage lang tid og forårsage at afspilning kan hænge mens den udtrækker. Slå dette fra, for at have undertekster brændt ind i video kodningen når det er supporteret på klient enheden.", + "AllowOnTheFlySubtitleExtractionHelp": "Indeholdte undertekster kan trækkes ud af videoer og leveres til klienter i ren tekst for at afhjælpe video omkodning. På nogle systemer kan dette tage lang tid og forårsage at afspilning kan hænge mens den udtrækker. Slå dette fra, for at have undertekster brændt ind i vha. video omkodningen når der ikke er indbygget understøttelse for ren tekst undertekster i klient enheden.", "AllowRemoteAccess": "Tillad fjernadgang til denne Jellyfin Server.", "AllowRemoteAccessHelp": "Hvis ikke markeret, vil alle fjernforbindelser blive blokeret.", "AllowedRemoteAddressesHelp": "Komma seperareret liste over IP adresser og netmasker der har ret til fjernadgang. Hvis blank er alle adresser tilladte.", @@ -29,7 +29,7 @@ "BirthDateValue": "Født: {0}", "BirthLocation": "Fødselslokation", "BirthPlaceValue": "Fødselssted: {0}", - "BookLibraryHelp": "Lyd- og tekstbøger er understøttet. Se {0}Jellyfins guide til navngivning af bøger{1}.", + "BookLibraryHelp": "Lyd- og tekstbøger er understøttet. Se {0}guiden til navngivning af bøger{1}.", "Browse": "Gennemse", "BrowsePluginCatalogMessage": "Gennemse vores plugin-katalog for at se tilgængelige plugins.", "ButtonAdd": "Tilføj", @@ -53,7 +53,7 @@ "ButtonEdit": "Rediger", "ButtonEditImages": "Rediger billeder", "ButtonEditOtherUserPreferences": "Rediger denne brugers profil, billede og personlige indstillinger.", - "ButtonForgotPassword": "Glemt adgangskode", + "ButtonForgotPassword": "Glemt Adgangskode", "ButtonFullscreen": "Fuld skærm", "ButtonGotIt": "Forstået", "ButtonHelp": "Hjælp", @@ -136,30 +136,30 @@ "DoNotRecord": "Optag ikke", "Download": "Hent", "DrmChannelsNotImported": "Kanaler med DRM importeres ikke.", - "EasyPasswordHelp": "Din pinkode bruges til offline adgang til understøttede Jellyfin apps, og kan også bruges til nemt login inden for eget netværk.", + "EasyPasswordHelp": "Din nemme pin-kode bruges til offline adgang på understøttede klienter og kan også bruges til nem login på netværket.", "Edit": "Rediger", "EditImages": "Rediger billeder", "EditSubtitles": "Rediger undertekster", - "EnableCinemaMode": "Aktiver biograftilstand", - "EnableColorCodedBackgrounds": "Aktiver farvekodet baggrunde", + "EnableCinemaMode": "Aktiver Biograftilstand", + "EnableColorCodedBackgrounds": "Aktiver Farvekodet baggrunde", "EnableHardwareEncoding": "Aktiver hardware indkoding", - "EnablePhotos": "Aktiver fotos", - "EnablePhotosHelp": "Fotos bliver opdaget og vist sammen med andre mediefiler.", + "EnablePhotos": "Vis fotoer", + "EnablePhotosHelp": "Billeder registreres og vises sammen med andre mediefiler.", "EnableStreamLooping": "Auto-gentag live streams", "EnableStreamLoopingHelp": "Aktiver dette hvis live streams kun indeholder få sekunders data og skal efterspørgsel hele tiden. Aktivering af dette uden det er nødvendigt kan forårsage problemer.", "Ended": "Færdig", "EndsAtValue": "Slutter {0}", - "ErrorAddingListingsToSchedulesDirect": "Der opstod en fejl under tilføjelse af opstilling til din Schedules Direct-konto. Schedules Direct tillader kun et begrænset antal opstillinger pr. konto. Du bliver muligvis nød til at logge på Schedules Direct-hjemmesiden og fjerne andre lister fra din konto for at fortsætte.", + "ErrorAddingListingsToSchedulesDirect": "Der opstod en fejl ved tilføjelse af lineup til din Schedules Direct-konto. Schedules Direct tillader kun et begrænset antal opstillinger pr. Konto. Det kan være nødvendigt at du logger ind på Schedules Direct-webstedet og fjerner andre lister fra din konto, før du fortsætter.", "ErrorAddingMediaPathToVirtualFolder": "Der opstod en fejl under tilføjelse af mediesti. Kontroller venligst at stien er gyldig og at Jellyfin Server-processen har adgang til denne lokation.", "ErrorAddingTunerDevice": "Der opstod en fejl under tilføjelse af tuner-enhed. Kontroller venligst at den er tilgængelig og prøv igen.", - "ErrorAddingXmlTvFile": "Der opstod en fejl under tilgang til XmlTV-filen. Kontroller venligst at filen findes og prøv igen.", - "ErrorGettingTvLineups": "Der opstod en fejl under download af tv-opstillinger. Kontroller venligst at dine informationer er korrekte og prøv igen.", + "ErrorAddingXmlTvFile": "Der opstod en fejl under tilgang til XMLTV-filen. Kontroller venligst at filen findes og prøv igen.", + "ErrorGettingTvLineups": "Der opstod en fejl under download af TV-opstillinger. Kontroller venligst at dine informationer er korrekte og prøv igen.", "ErrorMessageStartHourGreaterThanEnd": "Slut tid skal være større end start tid.", "ErrorPleaseSelectLineup": "Vælg venligst en opstilling og prøv igen. Hvis ingen opstillinger er tilgængelige, så kontroller venligst at dit brugernavn, adgangskode og postnummer er korrekt.", "ErrorSavingTvProvider": "Der opstod en fejl i forsøget på at gemme udbyder. Kontroller venligst at den er tilgængelig og prøv igen.", "EveryNDays": "Hver {0} dage", "ExitFullscreen": "Afslut fuldskærm", - "ExtractChapterImagesHelp": "Udvinding af kapitelbilleder lader Jellyfin apps vise grafiske menuer for scenevalg. Processen kan være langsom, cpu-intensiv og kræver muligvis adskillige gigabytes af lagerplads. Den køres når videoer opdages og desuden som en planlagt opgave, natligt. Den planlagte opgave kan konfigureres i området for planlagte opgaver. Det anbefales ikke at køre denne opgave under timer med spidsbelastning.", + "ExtractChapterImagesHelp": "Udtrækning af kapitelbilleder giver klienter mulighed for at vise grafiske scenevalgmenuer. Processen kan være langsom, ressourcekrævende og kan kræve flere gigabyte plads. Det kører, når videoer opdages, og også som en planlagt nat opgave. Skemaet kan konfigureres i det planlagte opgaverområde. Det anbefales ikke at køre denne opgave i de mest brugte timer.", "FFmpegSavePathNotFound": "Vi er ikke i stand til at finde FFmpeg via stien du har angivet. FFprobe er også påkrævet og skal findes i samme mappe. Disse komponenter er som regel pakket i den samme download. Kontroller venligst stien og prøv igen.", "FastForward": "Spol fremad", "Favorite": "Favorit", @@ -170,9 +170,9 @@ "FolderTypeBooks": "Bøger", "FolderTypeMovies": "Film", "FolderTypeMusic": "Musik", - "FolderTypeMusicVideos": "Musikvideoer", + "FolderTypeMusicVideos": "Musik Videoer", "FolderTypeTvShows": "TV", - "FolderTypeUnset": "Ikke valgt (blandet indhold)", + "FolderTypeUnset": "Blandet Indhold", "Friday": "Fredag", "Fullscreen": "Fuldskærm", "General": "Generel", @@ -183,7 +183,7 @@ "H264CrfHelp": "Den Konstante Ratefaktor (CRF) er standardindstillingen for X264-koderen. Du kan sætte værdien i mellem 0 og 51, hvor de lavere værdier resulterer i bedre kvalitet (på bekostning af større filstørrelser). Fornuftige værdier er i mellem 18 og 28. Standarden for X264 er 23, så du kan bruge dette som udgangspunkt.", "EncoderPresetHelp": "Vælg en hurtigere værdi for at forbedre ydeevne, eller en langsommere værdi for at forbedre kvalitet.", "HDPrograms": "HD-programmer", - "HardwareAccelerationWarning": "Aktivering af hardwareacceleration kan forårsage ustabilitet i nogle miljøer. Kontroller at dit operativsystem og videodriver er ajourført. Hvis du har problemer med at afspille video efter aktivering af dette, bliver du nød til at skifte tilbage til Auto.", + "HardwareAccelerationWarning": "Aktivering af hardwareacceleration kan forårsage ustabilitet i nogle miljøer. Kontroller at dit operativsystem og videodriver er opdateret. Hvis du har problemer med at afspille video efter aktivering af dette, bliver du nød til at skifte tilbage til Ingen.", "HeaderAccessSchedule": "Adgangsskema", "HeaderAccessScheduleHelp": "Skab et adgangsskema for at begrænse adgangen til bestemte tidsrum.", "HeaderActiveDevices": "Aktive enheder", @@ -197,9 +197,9 @@ "HeaderAdditionalParts": "Andre stier", "HeaderAlert": "Advarsel", "HeaderAllowMediaDeletionFrom": "Tillad Media Sletning Fra", - "HeaderApiKey": "API nøgle", - "HeaderApiKeys": "API nøgler", - "HeaderApiKeysHelp": "Eksterne applikationer skal have en API nøgle for at kunne kommunikere med Jellyfin. Nøgler udstedes ved at logge ind med en Jellyfin konto, eller ved manuelt at tildele applikationen en nøgle.", + "HeaderApiKey": "API Nøgle", + "HeaderApiKeys": "API Nøgler", + "HeaderApiKeysHelp": "Eksterne applikationer skal have en API-nøgle for at kunne kommunikere med Jellyfin. Nøgler udstedes ved at logge ind med en Jellyfin konto, eller ved manuelt at tildele applikationen en nøgle.", "HeaderAudioSettings": "Lydindstillinger", "HeaderAutomaticUpdates": "Automatiske opdateringer", "HeaderBlockItemsWithNoRating": "Klokér titler uden eller med ukendt bedømmelses information:", @@ -266,7 +266,7 @@ "HeaderItems": "Element", "HeaderKeepRecording": "Bevar Optagelse", "HeaderKeepSeries": "Bevar Serie", - "HeaderKodiMetadataHelp": "For at aktivere NFO metadata, rediger et bibliotek i Jellyfin biblioteks redigering og find metadata gemmer sektionen.", + "HeaderKodiMetadataHelp": "For at aktivere eller deaktivere NFO-metadata skal du redigere et bibliotek i Jellyfin-biblioteksopsætningen og finde afsnittet om metadata.", "HeaderLatestEpisodes": "Sidste episoder", "HeaderLatestMedia": "Seneste medier", "HeaderLatestMovies": "Seneste film", @@ -287,7 +287,7 @@ "HeaderMusicVideos": "Musikvideoer", "HeaderMyDevice": "Min Enhed", "HeaderMyMedia": "Mine medier", - "HeaderNewApiKey": "Ny API nøgle", + "HeaderNewApiKey": "Ny API Nøgle", "HeaderNewDevices": "Nye Enheder", "HeaderNextUp": "Næste", "HeaderOnNow": "Vises Nu", @@ -373,7 +373,7 @@ "Images": "Billeder", "ImportFavoriteChannelsHelp": "Hvis aktiveret, importeres der udelukkende kanaler der er markeret som favoritter på tuner-enheden.", "ImportMissingEpisodesHelp": "hvis aktiveret, vil information omkring manglende episoder bliver importeret ind i din Jellyfin-database og blive vist i sæsoner og serier. Dette medfører muligvis længere biblioteksscanninger.", - "InstallingPackage": "Installerer {0}", + "InstallingPackage": "Installerer {0} (version {1})", "InstantMix": "Instant Mix", "ItemCount": "{0} elementer", "Items": "emner", @@ -397,13 +397,13 @@ "LabelAll": "Alle", "LabelAllowHWTranscoding": "Tillad hardware-omkodning", "LabelAllowServerAutoRestart": "Tillad serveren at genstarte automatisk for at påføre opdateringer", - "LabelAllowServerAutoRestartHelp": "Serveren vil kun genstarte i inaktive perioder, når ingen brugere er aktive", + "LabelAllowServerAutoRestartHelp": "Serveren vil kun genstarte i inaktive perioder, når ingen brugere er aktive.", "LabelAllowedRemoteAddresses": "Fjernadgang IP adresse filter:", "LabelAllowedRemoteAddressesMode": "Fjernadgang IP adresse filter mode:", "LabelAppName": "App navn", - "LabelAppNameExample": "F. eks: Sickbeard, NzbDrone", + "LabelAppNameExample": "F. eks: Sickbeard, Sonarr", "LabelArtists": "Artister:", - "LabelArtistsHelp": "Angiv flere ved at sætte mellem dem ;", + "LabelArtistsHelp": "Angiv flere ved at sætte ;", "LabelAudioLanguagePreference": "Foretrukket lydsprog:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Genopfrisk automatisk metadata fra internettet:", "LabelBindToLocalNetworkAddress": "Bind til lokal netværksadresse:", @@ -411,9 +411,9 @@ "LabelBirthDate": "Fødselsdato:", "LabelBirthYear": "Fødselsår:", "LabelBlastMessageInterval": "Interval mellem 'i live' beskeder (sekunder)", - "LabelBlastMessageIntervalHelp": "Angiver intervallet i sekunder mellem serverens 'i live' beskeder.", + "LabelBlastMessageIntervalHelp": "Bestemmer varigheden i sekunder mellem eksplosive levende meddelelser.", "LabelCachePath": "Cachesti:", - "LabelCachePathHelp": "Angiv en brugerdefineret lokation for server cachefiler, så som billeder. Efterlad blankt for at benytte serverens standard.", + "LabelCachePathHelp": "Angiv en brugerdefineret placering for servercache-filer, såsom billeder. Lad være tom for at bruge serverens standard.", "LabelCancelled": "Annulleret", "LabelChannels": "Kanaler:", "LabelCollection": "Samling:", @@ -425,8 +425,8 @@ "LabelCustomCertificatePath": "Brugerdefineret SSL certifikat sti:", "LabelCustomCertificatePathHelp": "Sti til PKCS #12 fil indeholdende et certifikat og privat nøgle for at aktivere TLS understøttelse på et brugerdefineret domæne.", "LabelCustomCss": "Brugerdefineret CSS:", - "LabelCustomCssHelp": "Anvend din egen css til webinterfacet.", - "LabelCustomDeviceDisplayName": "Vist navn:", + "LabelCustomCssHelp": "Anvend din egen stil til webinterfacet.", + "LabelCustomDeviceDisplayName": "Visningsnavn:", "LabelCustomDeviceDisplayNameHelp": "Angiv en brugerdefineret navn. hvis der ikke angives et navn, bruges det navn enheden sender.", "LabelCustomRating": "Brugerdefineret bedømmelse:", "LabelDateAdded": "Dato for tilføjelse:", @@ -444,24 +444,24 @@ "LabelDisplayOrder": "Visningsorden:", "LabelDisplaySpecialsWithinSeasons": "Vis specialepisoder sammen med den sæson de blev sent i", "LabelDownMixAudioScale": "Forøg lydstyrke ved nedmiksning:", - "LabelDownMixAudioScaleHelp": "Forøg lydstyrken når der nedmikses. Sæt værdien til 1 for at beholde den originale lydstyrke.", + "LabelDownMixAudioScaleHelp": "Forøg lydstyrken når der nedmikses. Sæt værdien til en, for at beholde den originale lydstyrke.", "LabelDownloadLanguages": "Hent sprog:", - "LabelDropImageHere": "Smid billede her.", + "LabelDropImageHere": "Drop billede her, eller Tryk for at vælge.", "LabelEasyPinCode": "Pinkode:", "LabelEmbedAlbumArtDidl": "Indsæt album billede i DIDL", "LabelEmbedAlbumArtDidlHelp": "Nogle enheder foretrækker denne metode til overførsel af album billede. Andre kan fejle når dette er aktiveret.", "LabelEnableAutomaticPortMap": "Aktiver automatisk port mapping", - "LabelEnableAutomaticPortMapHelp": "Forsøg at mappe den offentlige port til den lokale port med uPnP. Dette virker ikke med alle routere.", + "LabelEnableAutomaticPortMapHelp": "Forsøg at mappe den offentlige port til den lokale port med uPnP. Dette virker ikke med alle routere. Ændringerne vil først træde i kræft et en server genstart.", "LabelEnableBlastAliveMessages": "Masseudsend 'i live' beskeder", "LabelEnableBlastAliveMessagesHelp": "Aktiver dette hvis UPnP enheder har problemer med forbindelsen til serveren.", "LabelEnableDlnaClientDiscoveryInterval": "Interval for klientsøgning (sekunder)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "angiver intervallet i sekunder mellem Jellyfins SSDP søgninger.", "LabelEnableDlnaDebugLogging": "Aktiver debu logning af DLNA", - "LabelEnableDlnaDebugLoggingHelp": "Dette generer meget store logfiler, og er kun anbefalet at bruge til fejlfindingsformål.", + "LabelEnableDlnaDebugLoggingHelp": "Opret store logfiler og skal kun bruges efter behov til fejlfinding.", "LabelEnableDlnaPlayTo": "Aktiver DLNA \"Afspil Til\"", - "LabelEnableDlnaPlayToHelp": "Jellyfin kan finde enheder i dit netværk og tilbyde at kontrollere dem.", - "LabelEnableDlnaServer": "Aktiver DNLA server", - "LabelEnableDlnaServerHelp": "Tillader UPnP enheder i dit netværk at gennemse og afspille Jellyfins indhold.", + "LabelEnableDlnaPlayToHelp": "Find enheder i dit netværk og tilbyd at fjernstyre dem.", + "LabelEnableDlnaServer": "Aktiver DLNA server", + "LabelEnableDlnaServerHelp": "Tillader UPnP enheder i dit netværk at gennemse og afspille indhold.", "LabelEnableRealtimeMonitor": "Aktiver realtidsovervågning", "LabelEnableRealtimeMonitorHelp": "Ændringer vil blive behandlet øjeblikkeligt på understøttede filsystemer.", "LabelEnableSingleImageInDidlLimit": "Begræns til et enkelt indlejret billede", @@ -471,56 +471,56 @@ "LabelEvent": "Hændelse:", "LabelEveryXMinutes": "Hver:", "LabelExtractChaptersDuringLibraryScan": "Udtræk kapitelbilleder under biblioteksskanning", - "LabelExtractChaptersDuringLibraryScanHelp": "Aktiver dette for at udtrække kapitelbillleder mens videofiler bliver importeret under biblioteksskanningen. Hvi det ikke er aktiveret, bliver de udtrukket når den planlagte opgave kapitelbilleder kører, og lader den almindelige biblioteksskanning afslutte hurtigere.", + "LabelExtractChaptersDuringLibraryScanHelp": "Generere kapitelbillleder mens videofiler bliver importeret under biblioteksskanningen. Alternativt bliver de udtrukket når den planlagte opgave kapitelbilleder kører, hvilket tillader den almindelige biblioteksskanning at afslutte hurtigere.", "LabelFailed": "Fejlet", - "LabelFileOrUrl": "Fil eller url:", + "LabelFileOrUrl": "Fil eller URL:", "LabelFinish": "Afslut", "LabelForgotPasswordUsernameHelp": "Indtast dit brugernavn, hvis du kan huske det.", "LabelFriendlyName": "System venligt navn:", - "LabelServerNameHelp": "Dette navn bruges til at identificere serveren. Hvis det ikke udfyldes, bruges computerens navn.", + "LabelServerNameHelp": "Dette navn bruges til at identificere serveren. Som udgangspunkt anvendes computerens navn.", "LabelGroupMoviesIntoCollections": "Grupper film i samlinger", "LabelGroupMoviesIntoCollectionsHelp": "Film i samlinger vil blive vist som en samlet enhed i filmlister.", "LabelH264Crf": "H264-kodning CRF:", "LabelEncoderPreset": "Forudindstillet H264-kodning:", "LabelHardwareAccelerationType": "Hardwareacceleration:", - "LabelHardwareAccelerationTypeHelp": "Kun tilgængelig for understøttede systemer.", + "LabelHardwareAccelerationTypeHelp": "Hardwareacceleration kræver yderligere konfiguration.", "LabelHttpsPort": "Lokalt HTTPS portnummer:", - "LabelHttpsPortHelp": "Det portnummer Jellyfins https-server bruger.", - "LabelIconMaxHeight": "Max højde på ikoner:", - "LabelIconMaxHeightHelp": "Maksimumopløsningen på ikoner der bliver vist med upnp:icon", - "LabelIconMaxWidth": "Max bredde på ikoner:", - "LabelIconMaxWidthHelp": "Maksimumopløsningen på ikoner der bliver vist med upnp:icon", + "LabelHttpsPortHelp": "Det TCP-portnummer, som Jellyfins HTTPS-server skal benytte.", + "LabelIconMaxHeight": "Maximal højde af ikoner:", + "LabelIconMaxHeightHelp": "Maksimalopløsningen af ikoner, der bliver vist med upnp:icon.", + "LabelIconMaxWidth": "Maximal bredde på ikoner:", + "LabelIconMaxWidthHelp": "Maksimalopløsningen på ikoner der bliver vist med upnp:icon.", "LabelIdentificationFieldHelp": "En case-insensitive substring eller regex ekspression.", "LabelImageFetchersHelp": "Aktiver og ranger dine fortrukne billede-hentere i en prioriteret rækkefølge.", "LabelImageType": "Billedtype:", "LabelImportOnlyFavoriteChannels": "Begræns til kanaler der er markeret som favoritter", "LabelInNetworkSignInWithEasyPassword": "Tillad login inden for eget netværk med pinkode", - "LabelInNetworkSignInWithEasyPasswordHelp": "Aktiver dette for at loge ind i Jellyfin apps med din pinkode inden for dit eget netværk. Din almindelige adgangskode skal du så kun bruge når du ikke er hjemme. Hvis pinkoden er tom, kan du logge ind uden adgangskode inden for dit eget netværk.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Brug den lette pinkode til at logge ind på klienter i dit lokale netværk. Din almindelige adgangskode er kun nødvendig hjemmefra. Hvis pinkoden efterlades tom, behøver du ikke en adgangskode på dit hjemmenetværk.", "LabelKeepUpTo": "Bevar op til:", "LabelKidsCategories": "Børnekategorier:", "LabelKodiMetadataDateFormat": "Format for udgivelsesdato:", - "LabelKodiMetadataDateFormatHelp": "Alle datoer i NFO-filer vil blive læst og skrevet med dette format.", - "LabelKodiMetadataEnableExtraThumbs": "kopier extrafanart til extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Alle datoer i NFO-filer vil blive analyseret med dette format.", + "LabelKodiMetadataEnableExtraThumbs": "Kopier ekstra fanart til extrathumbs", "LabelKodiMetadataEnableExtraThumbsHelp": "Ved hentning af billeder, kan de gemmes i både extrafanart og extrathumbs. Dette giver maksimal Kodi skin kompatibilitet.", "LabelKodiMetadataEnablePathSubstitution": "Aktiver stisubstitution", "LabelKodiMetadataEnablePathSubstitutionHelp": "Aktiverer stisubstitution for billedstier med serverens stisubstitutionsindstillinger.", "LabelKodiMetadataSaveImagePaths": "Gem stier til billeder i Nfo-filer", "LabelKodiMetadataSaveImagePathsHelp": "Dette er anbefalet hvis du har billedfiler med navne der ikke lever op til Kodis retningslinjer.", - "LabelKodiMetadataUser": "Gem brugers set data til NFO'er for:", - "LabelKodiMetadataUserHelp": "Aktiver dette for at komme set data til NFO filer som andre programmer kan bruge.", + "LabelKodiMetadataUser": "Gem brugerdata til NFO-filer til:", + "LabelKodiMetadataUserHelp": "Gem overvågningsdata til NFO-filer til andre applikationer.", "LabelLanNetworks": "LAN netwærk:", "LabelLanguage": "Sprog:", "LabelLineup": "Opstilling:", - "LabelLocalHttpServerPortNumber": "Lokalt http portnummer:", - "LabelLocalHttpServerPortNumberHelp": "Det portnummer Jellyfins http-server bruger.", + "LabelLocalHttpServerPortNumber": "Lokalt HTTP-portnummer:", + "LabelLocalHttpServerPortNumberHelp": "Det TCP-portnummer, som Jellyfin's HTTP-server skal binde til.", "LabelLockItemToPreventChanges": "Lås for at undgå fremtidige ændringer", "LabelLoginDisclaimer": "Login ansvarsfraskrivelse:", - "LabelLoginDisclaimerHelp": "Dette bliver vist i bunden af loginsiden.", - "LabelManufacturer": "Producent", - "LabelManufacturerUrl": "Producent url", + "LabelLoginDisclaimerHelp": "En besked, som vises i bunden af loginsiden.", + "LabelManufacturer": "Producent:", + "LabelManufacturerUrl": "Producentens URL", "LabelMaxBackdropsPerItem": "Maksimum antal af bagtæpper per element:", "LabelMaxParentalRating": "Højst tilladte aldersgrænse:", - "LabelMaxResumePercentage": "Maks. fortsæt procentdel:", + "LabelMaxResumePercentage": "Maksimal fortsæt procentdel:", "LabelMaxResumePercentageHelp": "Medier anses som fuldt afspillet, hvis de stoppes efter denne tid.", "LabelMaxScreenshotsPerItem": "Maksimum antal af skærmbilleder per element:", "LabelMaxStreamingBitrate": "Maks. streaming kvalitet:", @@ -538,9 +538,9 @@ "LabelMethod": "Metode:", "LabelMinBackdropDownloadWidth": "Minimum baggrundsbillede bredde:", "LabelMinResumeDuration": "Min. fortsæt tidsrum (sekunder):", - "LabelMinResumeDurationHelp": "Medier med kortere afspilningstid en denne kan ikke fortsættes.", - "LabelMinResumePercentage": "Min. fortsæt procentdel:", - "LabelMinResumePercentageHelp": "Medier anses om ikke afspillet, hvis de stoppes inden denne tid.", + "LabelMinResumeDurationHelp": "Den korteste videolængde i sekunder, der gemmer afspilningsplacering og giver dig mulighed for at genoptage.", + "LabelMinResumePercentage": "Minimal fortsæt procentdel:", + "LabelMinResumePercentageHelp": "Medier anses som ikke afspillede, hvis de stoppes inden denne tid.", "LabelMinScreenshotDownloadWidth": "Minimum skærmbillede bredde:", "LabelModelDescription": "Modelbeskrivelse", "LabelModelName": "Modelnavn", @@ -548,10 +548,10 @@ "LabelMonitorUsers": "Overvåg aktivitet fra:", "LabelMovieCategories": "Filmkategorier:", "LabelMoviePrefix": "Film-præfiks:", - "LabelMoviePrefixHelp": "Angiv venligst her hvis der tilføjes et præfiks til filmtitler, så Jellyfin kan håndtere det korrekt.", + "LabelMoviePrefixHelp": "Angiv venligst her hvis der tilføjes et præfiks til filmtitler, så serveren kan håndtere det korrekt.", "LabelMovieRecordingPath": "Film afspilningssti (valgfri):", "LabelMusicStreamingTranscodingBitrate": "Bitrate for musiktranskodning:", - "LabelMusicStreamingTranscodingBitrateHelp": "Angiv en maksimal bitrate når der streames musik", + "LabelMusicStreamingTranscodingBitrateHelp": "Angiv en maksimal bitrate når der streames musik.", "LabelName": "Navn:", "LabelNewName": "Nyt navn:", "LabelNewPassword": "Ny kode:", @@ -590,10 +590,10 @@ "LabelProtocol": "Protokol:", "LabelProtocolInfo": "Protokolinformation:", "LabelProtocolInfoHelp": "Den værdi der bruges til svar på GetProtocolInfo-forespørgsler fra enheden.", - "LabelPublicHttpPort": "Offentligt http portnummer:", - "LabelPublicHttpPortHelp": "Det offentlige portnummer som bliver knyttet til det lokale http portnummer.", - "LabelPublicHttpsPort": "Offentligt https portnummer:", - "LabelPublicHttpsPortHelp": "Det offentlige portnummer som bliver knyttet til det lokale https portnummer.", + "LabelPublicHttpPort": "Offentligt HTTP-portnummer:", + "LabelPublicHttpPortHelp": "Det offentlige portnummer som bliver knyttet til det lokale HTTP-portnummer.", + "LabelPublicHttpsPort": "Offentligt HTTPS portnummer:", + "LabelPublicHttpsPortHelp": "Det offentlige portnummer som bliver knyttet til det lokale HTTPS portnummer.", "LabelReadHowYouCanContribute": "Lær hvordan du kan bidrage.", "LabelRecord": "Optag:", "LabelRecordingPath": "Standard afspilningssti:", @@ -614,7 +614,7 @@ "LabelSerialNumber": "Serienummer", "LabelSeriesRecordingPath": "Serier afspilningssti (valgfri):", "LabelServerHost": "Vært:", - "LabelServerHostHelp": "F. eks: 192.168.1.100 eller https://myserver.com", + "LabelServerHostHelp": "F. eks: 192.168.1.100:8096 eller https://myserver.com", "LabelSimultaneousConnectionLimit": "samtidige stream begrænsning:", "LabelSkipIfAudioTrackPresent": "Undlad hvis standardlydsporet er det samme sprog", "LabelSkipIfAudioTrackPresentHelp": "Angiv ikke dette for at sikre at alle videoer har undertekster, uanset hvilket sprog lydsporet anvender.", @@ -632,11 +632,6 @@ "LabelSubtitleFormatHelp": "F. eks: srt", "LabelSubtitlePlaybackMode": "Underteksttilstand:", "LabelSupportedMediaTypes": "Understøttede medieformater:", - "LabelSyncJobName": "Navn til synkroniserings job:", - "LabelSyncPath": "Synked indholdssti:", - "LabelSyncTempPath": "Sti for midlertidige filer:", - "LabelSyncTempPathHelp": "Specificér en brugerdefineret synkroniserings arbejds-mappe. Konverterede filer vil under synkroniseringsprocessen blive gemt her.", - "LabelSyncTo": "Synkroniser til:", "LabelTheme": "Tema:", "LabelTime": "Tid:", "LabelTimeLimitHours": "Tidsgrænse (timer):", @@ -647,7 +642,7 @@ "LabelTranscodingTempPathHelp": "Definér en bugerdefineret sti til transkodede filer til klienter. Lad den stå tom for at bruge standardmappen i serverens datamappe.", "LabelTranscodingTemporaryFiles": "Midlertidige filer til omkodning:", "LabelTranscodingThreadCount": "Antal af omkodningstråde:", - "LabelTranscodingThreadCountHelp": "Vælg det maksimale antal af tråde der bruges under omkodning. Reduktion af antallet af tråde sænker cpu-forbrug, men resulterer muligvis i at konverteringer ikke foregår hurtigt nok til en jævn afspilning.", + "LabelTranscodingThreadCountHelp": "Vælg det maksimale antal af tråde der bruges under transcoding. Reduktion af antallet af tråde sænker CPU-forbrug, men resulterer muligvis i at konverteringer ikke foregår hurtigt nok til en jævn afspilning.", "LabelTriggerType": "Udløsertype:", "LabelTunerIpAddress": "IP-adresse for Tuner:", "LabelTunerType": "Tunertype:", @@ -707,37 +702,37 @@ "MessageConfirmRemoveMediaLocation": "Er du sikker på du ønsker at fjerne denne lokalisation?", "MessageConfirmRestart": "Er du sikker på du ønsker at genstarte Jellyfin?", "MessageConfirmRevokeApiKey": "Er du sikker på du ønsker at invalidere denne api nøgle? Applikationens forbindelse til Jellyfin vil blive afbrudt øjeblikkeligt.", - "MessageConfirmShutdown": "Er du sikker på du ønsker at lukke Jellyfin?", + "MessageConfirmShutdown": "Er du sikker på du ønsker at slukke for serveren?", "MessageContactAdminToResetPassword": "Kontakt venligst din systemadministrator for at nulstille din adgangskode.", "MessageCreateAccountAt": "Opret en konto hos {0}", "MessageDeleteTaskTrigger": "Er du sikker på du ønsker at slette denne task trigger?", "MessageDirectoryPickerBSDInstruction": "For BSD skal du muligvis konfigurere lager i dit FreeNAS Jail, før Jellyfin kan tilgå det.", "MessageDirectoryPickerInstruction": "Netværksstier kan indtastes manuelt i tilfælde af at netværksknappen ikke kan lokalisere dine enheder. Foreksempel, {0} eller {1}.", - "MessageDirectoryPickerLinuxInstruction": "For Linux på Arch Linux, CentOS, Debian, Fedora, OpenSuse eller Ubuntu skal du give Jellyfin-systembrugeren minimum læseadgang til dine lagerlokationer.", + "MessageDirectoryPickerLinuxInstruction": "For Linux på Arch Linux, CentOS, Debian, Fedora, openSUSE eller Ubuntu, skal du give servicebrugeren mindst læseadgang til dine lagerpladser.", "MessageDownloadQueued": "Download sat i kø.", "MessageEnablingOptionLongerScans": "Aktivering af denne indstilling kan resultere i væsentlig længere biblioteks skan.", "MessageFileReadError": "Der opstod en fejl i forsøget på at læse filen.", "MessageForgotPasswordFileCreated": "Den følgende fil er blevet oprettet på din server og indeholder instruktioner vedrørende hvordan du skal fortsætte:", "MessageForgotPasswordInNetworkRequired": "Prøv igen inde i dit hjemmenetværk for at igangsætte nulstilling af din adgangskode.", - "MessageInstallPluginFromApp": "Dette plugin skal være installeret inde i den app du ønsker at benytte det fra.", + "MessageInstallPluginFromApp": "Dette plugin skal installeres fra den app, du har til hensigt at bruge det i.", "MessageInvalidForgotPasswordPin": "En ugyldig eller udløbet pinkode blev indtastet. Prøv igen.", "MessageInvalidUser": "Ukendt brugernavn eller adgangskode. Prøv igen.", "MessageItemSaved": "Element gemt.", "MessageItemsAdded": "Emne tilføjet.", - "MessageLeaveEmptyToInherit": "Efterlad tom for at arve indstillinger fra en overliggende post eller den globale standardværdi.", + "MessageLeaveEmptyToInherit": "Lad være tom for at arve indstillinger fra et overordnet element eller den globale standardværdi.", "MessageNoAvailablePlugins": "Ingen tilgængelige plugins.", "MessageNoMovieSuggestionsAvailable": "Ingen filmforslag er tilgængelige. Begynd at se og vurder dine film, og kom tilbage for at se dine anbefalinger.", "MessageNoPluginsInstalled": "Du har ingen plugins installeret.", "MessageNoTrailersFound": "Ingen trailere fundet. Installer Trailer kanalen for at tilføje et bibliotek med trailere fra internettet.", "MessageNothingHere": "Her er ingenting.", - "MessagePasswordResetForUsers": "Adgangskoder blev fjernet for følgende brugere. For at logge ind, skal der benyttes en blank adgangskode.", + "MessagePasswordResetForUsers": "Følgende brugere har fået nulstillet deres adgangskoder. De kan nu logge på med de pinkoder, der blev brugt til at udføre nulstillingen.", "MessagePleaseEnsureInternetMetadata": "Sørg venligst for at hentning af metadata fra internettet er aktiveret.", "MessagePleaseWait": "Vent venligst. Dette kan tage et minut.", "MessagePluginConfigurationRequiresLocalAccess": "For at konfigurerer dette plugin log da venligst direkte ind på din lokale server.", "MessagePluginInstallDisclaimer": "Plugins fremstillet af medlemmer fra Jellyfin-fællesskabet er en alle tiders måde at forbedre din oplevelse af Jellyfin med yderligere features og fordele. Før installation, bedes du venligst være opmærksom på de effekter de kan have på din Jellyfin Server; så som lange scantider på biblioteker, yderligere baggrundsbehandling og forringet systemstabilitet.", "MessageReenableUser": "Se nedenfor om genaktivering", "MessageSettingsSaved": "Indstillinger er gemt.", - "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Følgende medielokationer bliver fjernet fra dit Jellyfin-bibliotek:", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Følgende medieplaceringer fjernes fra dit bibliotek:", "MessageUnableToConnectToServer": "Vi kan ikke forbinde til den valgte server på nuværende tidspunkt. Sikrer dig venligst at serveren kører og prøv igen.", "MessageUnsetContentHelp": "Indhold vil blive vist som almindelige mapper. For det bedste resultat benyt metadata manageren til at vælge indholdstypen i undermapper.", "MessageYouHaveVersionInstalled": "Du har version {0} installeret.", @@ -747,10 +742,10 @@ "MinutesBefore": "minutter før", "Monday": "Mandag", "MoreFromValue": "Mere fra {0}", - "MoreUsersCanBeAddedLater": "Flere brugere kan tilføjes senere i betjeningspanelet.", + "MoreUsersCanBeAddedLater": "Flere brugere kan tilføjes senere fra dashboardet.", "MoveLeft": "Flyt mod venstre", "MoveRight": "Flyt mod højre", - "MovieLibraryHelp": "Gennemse {0}Jellyfin film navngivningsguiden{1}.", + "MovieLibraryHelp": "Gennemse {0}film navngivningsguiden{1}.", "Movies": "Film", "Mute": "Afbryd lyd", "MySubtitles": "Mine Undertekster", @@ -782,7 +777,7 @@ "OptionAllowMediaPlaybackTranscodingHelp": "At begrænse adgang til omkodning kan forårsage afspilningsfejl i Jellyfin apps på grund af usupporterede medie formater.", "OptionAllowRemoteControlOthers": "Tillad fjernstyring af andre brugere", "OptionAllowRemoteSharedDevices": "Tillad fjernstyring af delte enheder", - "OptionAllowRemoteSharedDevicesHelp": "DLNA-enheder er delte indtil en bruger begynder at bruge den.", + "OptionAllowRemoteSharedDevicesHelp": "DLNA-enheder betragtes som delt, indtil en bruger begynder at kontrollere dem.", "OptionAllowSyncTranscoding": "Tillad medie hentning og synkronisering der kræver omkodning", "OptionAllowUserToManageServer": "Tillad denne bruger at administrere serveren", "OptionAllowVideoPlaybackRemuxing": "Tillad videoafspilning som kræver konvertering uden omkodning", @@ -811,13 +806,13 @@ "OptionDisableUserHelp": "Hvis deaktiveret vil serveren ikke tillade forbindelser fra denne bruger. Eksisterende forbindelser vil blive afbrudt øjeblikkeligt.", "OptionDislikes": "Ikke-Lide", "OptionDisplayFolderView": "Få vist en mappevisning til at se enkle mediemapper", - "OptionDisplayFolderViewHelp": "Hvis aktiveret, vil Jellyfin apps vise en Mappekategori ved siden af dine mediebiblioteker. Dette er brugbart, hvis du vil have vist enkle mappevisninger.", + "OptionDisplayFolderViewHelp": "Vis mapper sammen med dine andre mediebiblioteker. Dette kan være nyttigt, hvis du gerne vil have en almindelig mappevisning.", "OptionDownloadArtImage": "Billede", "OptionDownloadBackImage": "Bagside", "OptionDownloadBoxImage": "Boks", "OptionDownloadDiscImage": "Disk", "OptionDownloadImagesInAdvance": "Download billeder på forhånd", - "OptionDownloadImagesInAdvanceHelp": "Som standard downloades billeder kun når de anmodes af en Jellyfin app. Aktiver denne indstilling for at downloade alle billeder på forhånd, i mens nyt medie importeres. Dette resulterer muligvis i længere scanninger af bibliotek.", + "OptionDownloadImagesInAdvanceHelp": "Som standard downloades de fleste billeder kun, når de anmodes fra en Jellyfin-app. Aktivér denne mulighed for at downloade alle billeder på forhånd, da nye medier importeres. Dette kan forårsage betydeligt længere biblioteksscanninger.", "OptionDownloadPrimaryImage": "Primær", "OptionDownloadThumbImage": "Miniature", "OptionDvd": "DVD", @@ -826,7 +821,7 @@ "OptionEnableAccessToAllChannels": "Tillad adgang til alle kanaler", "OptionEnableAccessToAllLibraries": "Tillad adgang til alle biblioteker", "OptionEnableExternalContentInSuggestions": "Aktiver eksternt indhold i anbefalinger", - "OptionEnableExternalContentInSuggestionsHelp": "Tillad at internet-trailers og live-tv-programmer bliver inkluderet i det anbefalede indhold.", + "OptionEnableExternalContentInSuggestionsHelp": "Tillad at internet-trailers og live TV programmer bliver inkluderet i det anbefalede indhold.", "OptionEnableForAllTuners": "Aktiver for alle tuner-enheder", "OptionEnableM2tsMode": "Aktiver M2ts tilstand", "OptionEnableM2tsModeHelp": "Aktiver M2ts tilstand når der omkodes til mpegts.", @@ -844,8 +839,8 @@ "OptionHasThemeVideo": "Temavideo", "OptionHideUser": "Vis ikke denne bruger på loginsiden", "OptionHideUserFromLoginHelp": "Nyttigt for private kontoer eller skjulte administratorkontoer. Brugeren skal logge ind ved at skive sit brugernavn og adgangskode.", - "OptionHlsSegmentedSubtitles": "Hls segmented undertekster", - "OptionHomeVideos": "Hjemmevideoer og billeder", + "OptionHlsSegmentedSubtitles": "HLS segmenterede undertekster", + "OptionHomeVideos": "Billeder", "OptionIgnoreTranscodeByteRangeRequests": "Ignorer forespørgsler vedrørende transcode byte interval", "OptionIgnoreTranscodeByteRangeRequestsHelp": "Hvis aktiveret vil disse forespørgsler blive efterkommet, men byte range headeren ignoreret.", "OptionImdbRating": "IMDB bedømmelse", @@ -883,7 +878,7 @@ "OptionThursday": "Torsdag", "OptionTrackName": "Nummerets navn", "OptionTuesday": "Tirsdag", - "OptionTvdbRating": "Tvdb bedømmelse", + "OptionTvdbRating": "TVDB bedømmelse", "OptionUnairedEpisode": "Ikke sendte episoder", "OptionUnplayed": "Ikke afspillet", "OptionWakeFromSleep": "Vågner fra dvale", @@ -893,9 +888,9 @@ "OptionWeekly": "Ugentlig", "OriginalAirDateValue": "Originalt sendt: {0}", "Overview": "Overblik", - "PackageInstallCancelled": "{0} installation afbrudt.", - "PackageInstallCompleted": "{0} installation udført.", - "PackageInstallFailed": "{0} installationen mislykkedes.", + "PackageInstallCancelled": "{0} (version {1}) installation annulleret.", + "PackageInstallCompleted": "{0} (version {1}) installation udført.", + "PackageInstallFailed": "{0} (version {1}) installationen mislykkedes.", "ParentalRating": "Parental Rating", "PasswordMatchError": "Adgangskode og bekræft adgangskode skal være ens.", "PasswordResetComplete": "Adgangskoden er blevet nulstillet.", @@ -913,7 +908,7 @@ "Played": "Afspillet", "PleaseAddAtLeastOneFolder": "Tilføj venligst som minimum en enkelt mappe til dette bibliotek ved at klikke på Tilføj-knappen.", "PleaseConfirmPluginInstallation": "Klik venligst OK for at bekræfte at du har læst ovenstående og ønsker at fortsætte med installationen af plugin.", - "PleaseEnterNameOrId": "Indtast venligst et navn eller eksternt Id.", + "PleaseEnterNameOrId": "Indtast venligst et navn eller eksternt ID.", "PleaseRestartServerName": "Genstart venligst Jellyfin Server - {0}.", "PleaseSelectTwoItems": "Vælg venligst mindst to elementer.", "PluginInstalledMessage": "Plugin blev installeret med success. Jellyfin serveren skal genstartes for at aktivere det.", @@ -1025,7 +1020,7 @@ "TabProfiles": "Profiler", "TabRecordings": "Optagelser", "TabResponses": "Svar", - "TabResumeSettings": "Indstillinger for Genoptag", + "TabResumeSettings": "Genoptag", "TabScheduledTasks": "Planlagte opgaver", "TabSeries": "Serier", "TabSettings": "Indstillinger", @@ -1044,13 +1039,13 @@ "TitlePlayback": "Afspilning", "TrackCount": "{0} numre", "Tuesday": "Tirsdag", - "TvLibraryHelp": "Gennemse {0}Jellyfin TV navngivningsguiden{1}.", + "TvLibraryHelp": "Gennemgå {0} TV-navneguiden {1}.", "UninstallPluginConfirmation": "Er du sikker på du vil afinstallere {0}?", "UninstallPluginHeader": "Afinstaller plugin", "Unmute": "Genoptag lyd", "Unrated": "Ingen bedømmelse", - "UserAgentHelp": "Angiv en brugerdefineret user-agent http header, hvis nødvendigt.", - "UserProfilesIntro": "Jellyfin har indbygget understøttelse af brugerprofiler. Dette giver hver bruger sine egne indstillinger for visning, afspilningsstatus og forældrekontrol.", + "UserAgentHelp": "Angiv en brugerdefineret bruger-agent HTTP-header.", + "UserProfilesIntro": "Jellyfin inkluderer support til brugerprofiler med granuleret displayindstillinger, afspilningstilstand og forældrekontrol.", "ValueAlbumCount": "{0} album", "ValueAudioCodec": "Lyd codec: {0}", "ValueConditions": "Forhold: {0}", @@ -1072,17 +1067,17 @@ "ViewPlaybackInfo": "Vis afspilnings information", "Wednesday": "Onsdag", "WelcomeToProject": "Velkommen til Jellyfin!", - "WizardCompleted": "Det er alt vi behøver for nu. Jellyfin er begyndt at indsamle information omkring dit mediebibliotek. Tjek nogle af vores apps og klik derefter på Færdig for at se Server betjeningspanelet.", + "WizardCompleted": "Det er alt, hvad vi har brug for nu. Jellyfin er begyndt at indsamle information om dit mediebibliotek. Se nogle af vores apps, og klik derefter på Udfør for at se Dashboard.", "Writer": "Forfatter", - "XmlDocumentAttributeListHelp": "Disse attributter bliver tilføjet til rodelementet i alle XML svar.", + "XmlDocumentAttributeListHelp": "Disse attributter anvendes til rodelementet i hvert XML-svar.", "XmlTvKidsCategoriesHelp": "Programmer med disse kategorier bliver vist som programmer for børn. Adskil flere med '|'.", "XmlTvMovieCategoriesHelp": "Programmer med disse kategorier bliver vist som film. Adskil flere med '|'.", "XmlTvNewsCategoriesHelp": "Programmer med disse kategorier bliver vist som nyhedsprogrammer. Adskil flere med '|'.", - "XmlTvPathHelp": "En sti til en xml tv-fil. Jellyfin læser denne fil og kontrollerer periodisk for opdateringer. Du er ansvarlig for at oprette og opdatere filen.", + "XmlTvPathHelp": "En sti til en XMLTV fil. Jellyfin læser denne fil og kontrollerer periodisk for opdateringer. Du er ansvarlig for at oprette og opdatere filen.", "XmlTvSportsCategoriesHelp": "Programmer med disse kategorier bliver vist som sportsprogrammer. Adskil flere med '|'.", "Yesterday": "I går", "AirDate": "Luftdata", - "Albums": "Album", + "Albums": "Albums", "Artists": "Kunstnere", "Books": "Bøger", "Collections": "Samlinger", @@ -1092,15 +1087,14 @@ "Absolute": "Absolut", "AccessRestrictedTryAgainLater": "Adgang er begrænset. Prøv igen senere.", "Aired": "Blev sendt", - "AllComplexFormats": "Alle komplekse formater (ASS, SSA, VOBSUB, PGS, SUB/IDX osv.)", + "AllComplexFormats": "Alle Komplekse Formater (ASS, SSA, VOBSUB, PGS, SUB,IDX osv.)", "AllLanguages": "Alle sprog", - "AlwaysPlaySubtitles": "Afspil altid undertekster", + "AlwaysPlaySubtitles": "Afspil Altid", "AlwaysPlaySubtitlesHelp": "Undertekster, der matcher dine sprogindstillinger, vil altid blive indlæst uanset lydsprog.", "HeaderLiveTV": "Live-TV", "Shows": "Serier", "Songs": "Sange", - "AndroidUnlockRestoreHelp": "For at gendanne dit tidligere køb skal du sørge for, at du er logget ind på enheden med den samme Google- eller Amazon-konto, som oprindeligt gjorde købet. Sørg for, at app store er aktiveret og ikke begrænset af forældrekontrol, og sørg for, at du har en aktiv internetforbindelse. Du skal kun gøre dette én gang for at gendanne dit tidligere køb.", - "AnyLanguage": "Ethvert sprog", + "AnyLanguage": "Hvilken som helst sprog", "Art": "Kunst", "Ascending": "Stigende", "AudioBitDepthNotSupported": "Lyd bit dybde ikke understøttet", @@ -1111,17 +1105,13 @@ "AudioSampleRateNotSupported": "Lydens samplerate ikke understøttet", "Auto": "Auto", "AutoBasedOnLanguageSetting": "Automatisk (baseret på sprogindstilling)", - "AutomaticallyConvertNewContent": "Konverter automatisk nyt indhold", - "AutomaticallyConvertNewContentHelp": "Nyt indhold tilføjet til denne mappe vil blive konverteret automatisk.", - "AutomaticallySyncNewContent": "Download nyt indhold automatisk", - "AutomaticallySyncNewContentHelp": "Nyt indhold tilføjet til denne mappe vil automatisk blive downloadet til enheden.", "Backdrop": "Baggrund", "Backdrops": "Baggrunde", - "Banner": "Bannere", + "Banner": "Banner", "Blacklist": "Blackliste", "Box": "Boks", "BoxRear": "Boks (bagside)", - "BurnSubtitlesHelp": "Bestemmer om serveren skal brænde underteksterne ind i videoen når den konverterer baseret på undertekstformatet. Det vil øge serverens ydeevne ikke at brænde underteksterne i filen. Vælg Automatisk for at brænde billedbaserede formater (VOBSUB, PGS, SUB/IDX, osv) og nogle ASS/SSA undertekster.", + "BurnSubtitlesHelp": "Bestemmer om serveren skal brænde undertekster, når der afspilles transcoding videoer. Undgå dette vil forbedre ydelsen meget. Vælg Auto for at brænde billedbaserede formater (VOBSUB, PGS, SUB, IDX) og bestemte ASS- eller SSA-undertekster.", "ButtonFilter": "Filter", "ButtonGuide": "Vejledning", "ButtonInfo": "Information", @@ -1174,14 +1164,14 @@ "DisplayInOtherHomeScreenSections": "Visning på hjemmeskærm sektioner som seneste medier og se videre", "DisplayMissingEpisodesWithinSeasons": "Vis manglende afsnit inde i sæsoner", "DisplayMissingEpisodesWithinSeasonsHelp": "Dette skal også være aktiveret for TV biblioteker i serverens indstillinger.", - "DisplayModeHelp": "Vælg skærmtypen du kører Jellyfin på.", + "DisplayModeHelp": "Vælg det ønskede tema for grænsefladen.", "Down": "Ned", "DownloadItemLimitHelp": "Valgfri. Sæt en begrænsning på antallet af ting der vil blive hentet.", "Downloaded": "Hentet", "DownloadingDots": "Henter...", "Downloads": "Hentninger", "DownloadsValue": "{0} hentninger", - "DropShadow": "Drop skygge", + "DropShadow": "Drop Skygge", "DvrFeatureDescription": "Tidsindstil individuelle TV optagelser, serie optagelser, og mere med Jellyfin DVR.", "EditMetadata": "Redigér metadata", "EnableBackdrops": "Baggrundsbilleder", @@ -1198,10 +1188,8 @@ "Episodes": "Afsnit", "ErrorAddingGuestAccount1": "Der skete en fejl ved tilføjelsen af Jellyfin Connect kontoen. Har din gæst lavet en Jellyfin konto? De kan regsistrere sig på {0}.", "ErrorAddingGuestAccount2": "Hvis du stadig har problemer, så send venligst en email til {0}, og inkludér din email adresse såvel som deres.", - "ErrorAddingJellyfinConnectAccount1": "Der skete en fejl ved tilføjelsen af Jellyfin Connect kontoen. Har du lavet en Jellyfin konto? Registrer dig på {0}.", - "ErrorAddingJellyfinConnectAccount2": "Hvis du stadig har et problem, så send venligst en email til {0} fra den email adresse tilknyttet Jellyfin kontoen.", "ErrorDeletingItem": "Der skete en fejl ved sletningen af mediet fra Jellyfin Server. Tjek venligst at Jellyfin Server har skrive adgang til mediemappen og prøv igen.", - "ExtraLarge": "Ekstra stor", + "ExtraLarge": "Ekstra Stor", "Extras": "Bonusmateriale", "Features": "Funktioner", "Filters": "Filtre", @@ -1236,7 +1224,6 @@ "HeaderNavigation": "Navigation", "HeaderNextEpisodePlayingInValue": "Næste afsnit afspilles om {0}", "HeaderNextVideoPlayingInValue": "Næste video afspilles om {0}", - "HeaderOffline": "Offline", "HeaderPhotoAlbums": "Foto Albummer", "HeaderPlayOn": "Afspil På", "HeaderProgram": "Program", @@ -1252,36 +1239,32 @@ "HeaderTags": "Mærker", "HeaderTopPlugins": "Bedste Plugins", "HeaderType": "Type", - "HeaderUpcomingNews": "Kommende Nyheder", "HeaderVideo": "Video", "HeaderVideoQuality": "Video Kvalitet", "HeaderVideoType": "Video Type", - "HeaderWaitingForWifi": "Venter på Wifi", "Hide": "Skjul", "HideWatchedContentFromLatestMedia": "Skjul set indhold fra seneste medier", "Home": "Hjem", "Horizontal": "Horisontalt", "ImdbRating": "IMDb bedømmelse", "InterlacedVideoNotSupported": "Interlaced video ikke understøttet", - "InviteAnJellyfinConnectUser": "Tilføj en bruger ved at sende en email invitation.", "KeepDownload": "Behold hentning", "Label3DFormat": "3D format:", "LabelAlbum": "Album:", "LabelArtist": "Kunstner", "LabelAudio": "Lyd", "LabelBitrateMbps": "Bitrate (Mbps):", - "LabelBlockContentWithTags": "Blokér filer med mærkerne:", + "LabelBlockContentWithTags": "Blokér filer med etiketter:", "LabelBurnSubtitles": "Brænd undertekster:", "LabelCache": "Cache:", "LabelCertificatePassword": "Adgangskode til certifikat:", - "LabelCertificatePasswordHelp": "Hvis dit certifikat kræver en adgangskode, skriv det benligst her.", - "LabelConvertTo": "Konvertér til:", + "LabelCertificatePasswordHelp": "Hvis dit certifikat kræver en adgangskode, skriv det venligst her.", "LabelDashboardTheme": "Server dashboard tema:", "LabelDateTimeLocale": "Dato og tid område:", "LabelDefaultScreen": "Standard skærm:", "LabelDisplayLanguage": "Visningssprog:", "LabelDisplayLanguageHelp": "Oversættelse af Jellyfin er et vedvarende projekt.", - "LabelDisplayMode": "Visningsmodus:", + "LabelDisplayMode": "Visningstilstand:", "LabelDropShadow": "Drop skygge:", "LabelDynamicExternalId": "{0} ID:", "LabelEmail": "Email:", @@ -1297,7 +1280,7 @@ "LabelMaxBitrate": "Maks bitrate:", "LabelMaxChromecastBitrate": "Chromecast streaming kvalitet:", "LabelMetadata": "Metadata:", - "LabelModelUrl": "Model link", + "LabelModelUrl": "Model URL", "LabelPreferredSubtitleLanguage": "Foretrukket undertekst sprog:", "LabelProfileCodecs": "Codecs:", "LabelProfileContainer": "Beholder:", @@ -1314,7 +1297,6 @@ "LabelSoundEffects": "Lydeffekter:", "LabelStatus": "Status:", "LabelSubtitles": "Undertekster", - "LabelSyncNoTargetsHelp": "Det ser ud til at du ikke har nogen apps der understøtter offline hentning.", "LabelTVHomeScreen": "TV modus hjemmeskærm:", "LabelTag": "Mærke:", "LabelTagline": "Taglinje:", @@ -1333,7 +1315,6 @@ "LabelXDlnaCap": "X-DLNA begrænsning:", "LabelXDlnaDoc": "X-DLNA dokumentation:", "LabelYear": "År:", - "LabelffmpegVersion": "FFmpeg version:", "Large": "Stor", "LearnHowYouCanContribute": "Lær hvordan du kan bidrage.", "LeaveBlankToNotSetAPassword": "Du kan lade dette felt være tomt hvis du ikke ønsker adgangskode.", @@ -1365,7 +1346,6 @@ "MessageImageFileTypeAllowed": "Kun JPEG og PNG filer er understøttet.", "MessageImageTypeNotSelected": "Vælg venligst en type af billede i drop-down menuen.", "MessageNoDownloadsFound": "Ingen offline hentninger. Hent dine medier til offline brug ved at klikke Hent igennem app'en.", - "MessageNoSyncJobsFound": "Ingen hentninger fundet. Opret hent job ved at bruge Hent knapperne igennem app'en.", "MessagePlayAccessRestricted": "Afspilning af dette indhold er begrænset. Kontakt venligst server administratoren for mere information.", "Metadata": "Metadata", "Mobile": "Mobil", @@ -1378,11 +1358,8 @@ "Off": "Fra", "OnlyForcedSubtitles": "Kun tvungne undertekster", "OnlyForcedSubtitlesHelp": "Kun undertekster markeret som tvungne vil blive indlæst.", - "OnlyImageFormats": "Kun billedformater (VOBSUB, PGS, SUB, etc)", - "Option2Player": "2+", + "OnlyImageFormats": "Kun billedformater (VOBSUB, PGS, SUB)", "Option3D": "3D", - "Option3Player": "3+", - "Option4Player": "4+", "OptionAlbum": "Album", "OptionArtist": "Kunstner", "OptionAuto": "Automatisk", @@ -1420,13 +1397,12 @@ "PlayCount": "Afspilninger", "PlayNext": "Afspil næste", "PlayNextEpisodeAutomatically": "Afspil næste afsnit automatisk", - "PlaybackErrorNoCompatibleStream": "Ingen kompatible streams er tilgængelige. Prøv venligst igen senere eller kontakt din system administrator for detaljer.", + "PlaybackErrorNoCompatibleStream": "Denne klient er ikke kompatibel med medierne, og serveren sender ikke et kompatibelt medieformat.", "PlaybackErrorNotAllowed": "Du har ikke adgang til at afspille dette indhold. Kontakt venligst system administratoren for detaljer.", "PlaybackErrorPlaceHolder": "Indlæs venligst disken for at afspille denne video.", "PlaybackSettings": "Afspilningsindstillinger", "PlaybackSettingsIntro": "For at indstille standard afspilningsindstillingerne, stop video afspilning, herefter klik på dit bruger ikon i øverste højre sektion af denne app.", "Playlists": "Afspilningslister", - "PleaseSelectDeviceToSyncTo": "Vælg venligst en enhed at hente til.", "Previous": "Forrige", "Primary": "Primær", "PrivacyPolicy": "Privatlivs politik", @@ -1436,15 +1412,14 @@ "RefFramesNotSupported": "Antal af video reference billeder ikke understøttet", "RefreshMetadata": "Genopfrisk metadata", "RepeatAll": "Gentag alle", - "RepeatMode": "Gentagelsesmode", + "RepeatMode": "Gentagelses tilstand", "RepeatOne": "Gentag én", - "RestartPleaseWaitMessage": "Vent venligst mens Jellyfin Server lukker og genstarter. Dette kan tage et minut eller to.", "RunAtStartup": "Kør ved opstart", "ScanForNewAndUpdatedFiles": "Skan for nye og opdaterede filer", "Schedule": "Tidsplan", "Screenshot": "Skærmbillede", "SecondaryAudioNotSupported": "Lydspor skift ikke understøttet", - "SeriesDisplayOrderHelp": "Sortér afsnit efter sende dato, dvd rækkefølge eller obsolut nummering.", + "SeriesDisplayOrderHelp": "Sortér episoder efter luftdato, DVD-orden eller absolut nummerering.", "ShowTitle": "Vis titel", "ShowYear": "Vis år", "Small": "Lille", @@ -1455,16 +1430,12 @@ "Sort": "Sortér", "SortByValue": "Sortér efter {0}", "Standard": "Standard", - "StatsForNerds": "Stats for nørder", "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Disse indstilinger bliver aktiveret på enhver Chromecast afspilning på denne enhed.", "SubtitleAppearanceSettingsDisclaimer": "Disse indstillinger bliver ikke aktiveret på grafiske undertekster (PGS, DVD, etc) eller ASS/SSA undertekster der har deres egen indbyggede stil.", "SubtitleCodecNotSupported": "Undertekst format ikke understøttet", "SubtitleSettings": "Undertekst indstillinger", "SubtitleSettingsIntro": "For at konfigurere standard undertekst udseende og sprog indstillinger, stop video afspilning, herefter klik på dit bruger ikon i øverste højre sektion af denne app.", "Suggestions": "Forslag", - "SyncUnwatchedVideosOnly": "Hent kun usete videoer", - "SyncUnwatchedVideosOnlyHelp": "Kun usete videoer vil blive hentet, og videoer vil bliver slettet fra enheden når de er set.", - "SyncingDots": "Synkroniserer...", "TV": "TV", "TabAlbums": "Albummer", "TabCodecs": "Codeks", @@ -1478,7 +1449,6 @@ "TabPlaylist": "Afspilningsliste", "TabServer": "Server", "TabStreaming": "Streamer", - "TabSync": "Sync", "TabTV": "TV", "Tags": "Mærker", "TagsValue": "Mærker: {0}", @@ -1489,7 +1459,6 @@ "TitleLiveTV": "Live TV", "TitleServer": "Server", "TitleSupport": "Support", - "TitleSync": "Sync", "Trailer": "Forfilm", "Trailers": "Forfilm", "Transcoding": "Omkodning", @@ -1519,7 +1488,6 @@ "ViewTypeTvShows": "TV", "Watched": "Set", "Whitelist": "Hvidliste", - "WifiRequiredToDownload": "En Wifi forbindelse er påkrævet for at forsætte hentning.", "Yes": "Ja", "HeaderFavoriteMovies": "Favoritfilm", "HeaderFavoriteShows": "Favoritserier", @@ -1531,6 +1499,115 @@ "HeaderHome": "Hjem", "LabelServerName": "Server navn:", "LabelUserLoginAttemptsBeforeLockout": "Fejlede loginforsøg før bruger lukkes ude:", - "HeaderRestartingServer": "Genstarter Server", - "ButtonAddImage": "Tilføj billede" + "ButtonAddImage": "Tilføj billede", + "AllowFfmpegThrottlingHelp": "Når en omkodning eller remux kommer langt nok foran den nuværende afspildings position, pauses processen så der bruges færre resurser. Dette er mest brugbart når man ikke springer i filmen. Slå dette fra hvis du har problemer med playback.", + "AllowFfmpegThrottling": "Begræns Omkodning", + "AlbumArtist": "Album Artist", + "Album": "Album", + "EveryHour": "Hver time", + "EveryXMinutes": "Hvert {0} minut", + "OnWakeFromSleep": "Ved vækning fra dvale", + "WeeklyAt": "{0}s ved {1}", + "DailyAt": "Dagligt kl. {0}", + "LastSeen": "Sidst set {0}", + "PersonRole": "som {0}", + "ListPaging": "{0}-{1} af {2}", + "WriteAccessRequired": "Jellyfin Server kræver skriveadgang til denne mappe. Sørg for skriveadgang, og prøv igen.", + "PathNotFound": "Stien blev ikke fundet. Sørg for, at stien er gyldig, og prøv igen.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Spor", + "TabNetworking": "Netværk", + "SubtitleOffset": "Undertekst Offset", + "SelectAdminUsername": "Vælg et brugernavn til administrator kontoen.", + "Season": "Sæson", + "ReleaseGroup": "Release Group", + "Premiere": "Premiere", + "PreferEmbeddedEpisodeInfosOverFileNames": "Foretrækker integreret episode information frem for filnavne", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Dette bruger episode informationen fra de integrerede metadata, hvis den er tilgængelig.", + "PlaybackData": "Afspilningsdata", + "Person": "Person", + "PasswordResetProviderHelp": "Vælg en leverandør af nulstil adgangskode, der skal bruges, når denne bruger anmoder om en nulstilling af adgangskode", + "OtherArtist": "Anden kunstner", + "OptionThumbCard": "Thumb card", + "OptionThumb": "Thumb", + "OptionRandom": "Tilfældig", + "OptionPosterCard": "Plakatkort", + "OptionPoster": "Plakat", + "OptionLoginAttemptsBeforeLockoutHelp": "En værdi på nul betyder at arve standard for tre forsøg for normale brugere og fem for administratorer. Indstilling af dette til -1 vil deaktivere funktionen.", + "OptionLoginAttemptsBeforeLockout": "Bestemmer, hvor mange forkerte loginforsøg, der kan gøres, før lockout finder sted.", + "OptionList": "Liste", + "OptionForceRemoteSourceTranscoding": "Tving transcoding af eksterne mediekilder (som LiveTV)", + "NoCreatedLibraries": "Det ser ud til, at du ikke har oprettet nogen biblioteker endnu. {0} Vil du oprette en nu? {1}", + "MusicVideo": "Musik Video", + "MusicLibraryHelp": "Gennemgå {0} guide til navngivning af musik {1}.", + "MusicArtist": "Musik Artist", + "MusicAlbum": "Musik Album", + "Movie": "Film", + "MoreMediaInfo": "Medieinfo", + "MessageNoServersAvailable": "Der er ikke fundet nogen servere ved hjælp af den automatiske serveropdagelse.", + "MessageNoCollectionsAvailable": "Samlinger tillader dig at nyde personlige grupperinger af Film, Serier og Albums. Klik på + knappen for at skabe en samling.", + "MessageConfirmAppExit": "Vil du afslutte?", + "MediaInfoStreamTypeSubtitle": "Undertekst", + "MediaInfoStreamTypeEmbeddedImage": "Indlejret billede", + "MediaInfoStreamTypeAudio": "Lyd", + "LaunchWebAppOnStartupHelp": "Åben web klienten i den standard web browser når serveren starter første gang. Dette vil ikke ske når restart server funktionen benyttes.", + "LaunchWebAppOnStartup": "Åben webinterfacet når serveren startes", + "LabelWeb": "Web:", + "LabelVideoResolution": "Videoopløsning:", + "LabelVideoBitrate": "Video bitrate:", + "DashboardArchitecture": "Arkitektur: {0}", + "DashboardOperatingSystem": "Styresystem: {0}", + "DashboardServerName": "Server: {0}", + "DashboardVersionNumber": "Version: {0}", + "LabelTranscodingProgress": "Transcoding fremskridt:", + "LabelTranscodingFramerate": "Transcoding framerate:", + "LabelTranscodes": "Transcodes:", + "LabelTranscodePath": "Transcode sti:", + "LabelStreamType": "Stream type:", + "LabelSonyAggregationFlags": "Sony aggregering flag:", + "LabelSize": "Størrelse:", + "LabelPleaseRestart": "Ændringer vil træde i kraft efter web klienten er blevet genindlæst manuelt.", + "LabelPlayMethod": "Afspilnings metode:", + "LabelPlayerDimensions": "Afspillerdimensioner:", + "LabelPlayer": "Afspiller:", + "LabelPasswordResetProvider": "Udbyder til nulstilling as kodeord:", + "LabelLibraryPageSizeHelp": "Indstiller mængden af genstande, der skal vises på en bibliotekside. Indstil til 0 for at deaktivere.", + "LabelLibraryPageSize": "Biblioteks størrelse:", + "LabelFolder": "Mappe:", + "LabelBaseUrl": "Base URL:", + "LabelBaseUrlHelp": "Du kan tilføje en speciel undermappe her for, at have adgang til serveren fra en mere unik URL.", + "LabelDroppedFrames": "Tabte frames:", + "LabelDeinterlaceMethod": "Konventerings metode:", + "LabelCorruptedFrames": "Korrupte frames:", + "LabelBitrate": "Bitrate:", + "LabelAuthProvider": "Autentificeringsudbyder:", + "LabelAudioSampleRate": "Lydeksempelfrekvens:", + "LabelAudioCodec": "Lyd codec:", + "LabelAudioChannels": "Lyd kanaler:", + "LabelAudioBitrate": "Lyd bitrate:", + "LabelAudioBitDepth": "Lyd bitdybde:", + "HeaderFavoritePeople": "Foretrukne Personer", + "HeaderFavoriteBooks": "Foretrukne Bøger", + "FetchingData": "Henter yderligere data", + "Episode": "Afsnit", + "DeinterlaceMethodHelp": "Vælg hvilken konverteringsmulighed der skal bruges til transkodning af indhold.", + "CopyStreamURLError": "Der skete en fejl med at kopiere URL'en.", + "CopyStreamURLSuccess": "URL blev kopieret.", + "CopyStreamURL": "Kopiér stream URL", + "ClientSettings": "Klient Indstillinger", + "ButtonSplit": "Opdel", + "BoxSet": "Box Set", + "AuthProviderHelp": "Vælg en godkendelsesudbyder til at godkende denne brugers adgangskode.", + "AskAdminToCreateLibrary": "Spørg en administrator om at oprette et bibliotek.", + "Artist": "Artist", + "EveryXHours": "Hver {0} time", + "OnApplicationStartup": "Ved programstart", + "UnsupportedPlayback": "Jellyfin kan ikke dekryptere indhold, der er beskyttet af DRM, men alt indhold vil blive forsøgt afspillet uanset, inklusive beskyttede titler. Nogle filer kan eventuelt vises med sort skærm på grund af kryptering eller andre funktioner, der ikke understøttes, såsom interaktive titler.", + "MessageUnauthorizedUser": "Du har ikke tilladelse til at tilgå serveren på dette tidspunkt. Kontakt din serveradministrator for mere information.", + "Filter": "Filtrer", + "New": "Nye", + "ButtonTogglePlaylist": "Spilleliste", + "ButtonToggleContextMenu": "Mere", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/de.json b/src/strings/de.json index 97cb1f959e8..121e5853704 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -9,15 +9,14 @@ "AddToPlayQueue": "Zur Warteschlange hinzufügen", "AddToPlaylist": "Zur Wiedergabeliste hinzufügen", "AddUser": "Benutzer anlegen", - "AddUserByManually": "Lege einen lokalen User durch manuelle Eingabe der User-Informationen an.", "AddedOnValue": "{0} hinzugefügt", - "AdditionalNotificationServices": "Schau im Pluginkatalog, um weitere Benachrichtigungsdienste zu installieren.", + "AdditionalNotificationServices": "Durchsuchen sie den Pluginkatalog um weitere Benachrichtigungsdienste zu installieren.", "AirDate": "Erstausstrahlung", "Aired": "Ausgestrahlt", "Albums": "Alben", "All": "Alle", "AllChannels": "Alle Kanäle", - "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX, ...)", "AllEpisodes": "Alle Folgen", "AllLanguages": "Alle Sprachen", "AllLibraries": "Alle Bibliotheken", @@ -26,17 +25,15 @@ "AllowMediaConversion": "Erlaube Medienkonvertierung", "AllowMediaConversionHelp": "Erlaube oder unterbinde Zugriff auf die Medienkonvertierung.", "AllowOnTheFlySubtitleExtraction": "Erlaube Untertitelextraktion \"on-the-fly\"", - "AllowOnTheFlySubtitleExtractionHelp": "Eingebettete Untertitel können aus Videos extrahiert und im Textformat an Clients gesendet werden um die Videotranskodierung zu vermeiden. Auf manchen Systemen kann dieser Vorgang eine lange Zeit in Anspruch nehmen und die Videowiedergabe während der Extraktion unterbrochen werden. Deaktiviere diese Option um eingebettete Untertitel während der Videotranskodierung einbrennen zu lassen, wenn sie nicht nativ vom Client unterstützt werden.", + "AllowOnTheFlySubtitleExtractionHelp": "Eingebettete Untertitel können aus Videos extrahiert und in Reintext an Clients gesendet werden, um eine Videotranskodierung zu vermeiden. Auf manchen Systemen kann dieser Vorgang eine lange Zeit in Anspruch nehmen und deswegen währenddessen die Videowiedergabe stoppen. Deaktiviere diese Option, um eingebettete Untertitel während des Videotranskodierens einbrennen zu lassen, wenn sie nicht nativ vom Client unterstützt werden.", "AllowRemoteAccess": "Erlaube externe Verbindungen zu diesem Jellyfin Server.", "AllowRemoteAccessHelp": "Wenn deaktiviert werden alle externen Verbindungen blockiert.", - "AllowSeasonalThemes": "Erlaube automatische Jahreszeitenmotive", - "AllowSeasonalThemesHelp": "Wenn aktiviert, werden Jahreszeitenmotive von Zeit zu Zeit deine Motiveinstellungen überschreiben.", "AllowedRemoteAddressesHelp": "Kommagetrennte Liste von IP Adressen oder IP/Netzmasken für Netzwerke, für die externe Verbindungen erlaubt sind. Wenn leer, sind alle Adressen erlaubt.", - "AlwaysPlaySubtitles": "Untertitel immer einblenden", + "AlwaysPlaySubtitles": "Immer anzeigen", "AlwaysPlaySubtitlesHelp": "Untertitel die den Spracheinstellungen entsprechen werden unabhängig von der Tonspursprache geladen.", "AnyLanguage": "Jede Sprache", "Anytime": "Jederzeit", - "AroundTime": "Um {0}", + "AroundTime": "Um", "Artists": "Interpreten", "AsManyAsPossible": "So viele wie möglich", "Ascending": "Aufsteigend", @@ -55,12 +52,12 @@ "BirthLocation": "Geburtsort", "BirthPlaceValue": "Geburtsort: {0}", "BobAndWeaveWithHelp": "Bob & Weave (höhere Qualität, aber langsamer)", - "BookLibraryHelp": "Hörbücher und E-Books werden unterstützt. Schaue in den {0}Book Naming Guide {1}.", + "BookLibraryHelp": "Hörbücher und E-Books werden unterstützt. Schaue in den {0} Book Naming Guide {1}.", "Books": "Bücher", "BoxRear": "Box (Rückseite)", "Browse": "Blättern", "BrowsePluginCatalogMessage": "Durchsuche unsere Bibliothek, um alle verfügbaren Plugins anzuzeigen.", - "BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel basierend auf deren Format während der Videokonvertierung einbrennen soll. Die Vermeidung des Einbrennen von Untertiteln verbessert die Serverperformance. Wähle Auto, um Bildfomate (z.B. VOBSUB, PGS, SUB/IDX, etc.) sowie bestimmte ASS/SSA-Untertitel einbrennen zu lassen.", + "BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel während der Videotranskodierung einbrennen soll. Deaktivieren verbessert die Serverperformance immens. Wähle Auto, um bildbasierte Formate (z.B. VOBSUB, PGS, SUB, IDX, ...) sowie bestimmte ASS- oder SSA-Untertitel einbrennen zu lassen.", "ButtonAdd": "Hinzufügen", "ButtonAddMediaLibrary": "Füge Medienbibliothek hinzu", "ButtonAddScheduledTaskTrigger": "Auslöser hinzufügen", @@ -148,7 +145,7 @@ "ConfirmDeletion": "Bestätige Löschung", "ConfirmEndPlayerSession": "Möchtest du Jellyfin auf {0} beenden?", "Connect": "Verbinde", - "ContinueWatching": "Weiterschauen", + "ContinueWatching": "Fortsetzen", "Continuing": "Fortlaufend", "CriticRating": "Kritiker Bewertung", "CustomDlnaProfilesHelp": "Erstelle ein benutzerdefiniertes Profil für ein neues Zielgerät, oder um ein vorhandenes Systemprofil zu überschreiben.", @@ -185,7 +182,7 @@ "DisplayInOtherHomeScreenSections": "Zeige auf dem Homescreen Bereiche wie 'Neueste Medien' oder 'Weiterschauen'", "DisplayMissingEpisodesWithinSeasons": "Zeige fehlende Episoden innerhalb von Staffeln", "DisplayMissingEpisodesWithinSeasonsHelp": "Dies muss auch für Serienbibliotheken in den Servereinstellungen aktiviert sein.", - "DisplayModeHelp": "Bitte wähle den Typ des Bildschirms auf dem Du Jellyfin verwendest.", + "DisplayModeHelp": "Wähle das Layout welches du für die Oberfläche verwenden möchtest.", "DoNotRecord": "Nicht aufnehmen", "Down": "Runter", "DownloadsValue": "{0} Downloads", @@ -230,7 +227,7 @@ "ExitFullscreen": "Vollbild verlassen", "ExtraLarge": "Extragroß", "ExtractChapterImagesHelp": "Das Extrahieren von Kapitel-Bildern ermöglicht es Jellyfin-Apps eine grafische Szenenauswahl anzubieten. Das Erstellen ist recht langsam, rechenintensiv und erfordert ggf. einige Gigabyte an freien Speicherplatz. Diese Aufgabe startet wenn neue Videos erkannt werden und ebenso als eine nächtliche Aufgabe. Es wird nicht empfohlen diese Aufgabe in Zeiten hoher Server-Auslastung zu starten.", - "FFmpegSavePathNotFound": "Wir konnten kein FFmpeg in dem von Dir erfassten Verzeichnis finden. FFprobe wird ebenso benötigt und muss sich im gleichen Verzeichnis befinden. Diese Komponenten sind normalerweise in einem Paket vorhanden um kommen zusammen mit einem Download. Bitte prüfe das Verzeichnis und probiere es erneut.", + "FFmpegSavePathNotFound": "Wir konnten kein FFmpeg in dem von Dir erfassten Verzeichnis finden. FFprobe wird ebenso benötigt und muss sich im gleichen Verzeichnis befinden. Diese Komponenten sind normalerweise in einem Paket vorhanden und kommen zusammen in einem Download. Bitte prüfe das Verzeichnis und probiere es erneut.", "FastForward": "Vorwärts spulen", "Favorite": "Favorit", "Favorites": "Favoriten", @@ -449,7 +446,7 @@ "HeaderTunerDevices": "Tuner", "HeaderTuners": "Tuner", "HeaderTypeText": "Texteingabe", - "HeaderUpcomingOnTV": "Bald im TV", + "HeaderUpcomingOnTV": "Demnächst im Fernsehen", "HeaderUploadImage": "Bild hochladen", "HeaderUser": "Benutzer", "HeaderUsers": "Benutzer", @@ -464,12 +461,12 @@ "Help": "Hilfe", "Hide": "Verstecke", "HideWatchedContentFromLatestMedia": "Verberge gesehene Inhalte von neuesten Medien", - "HttpsRequiresCert": "Um sichere Verbindungen zu ermöglichen, musst du ein vertrauenswürdiges SSL-Zertifikat, wie beispielsweise eines von Let's Encrypt, bereitstellen. Stelle bitte entweder ein Zertifikat zur Verfügung oder deaktiviere sichere Verbindungen.", + "HttpsRequiresCert": "Um https für externe Verbindungen zu erzwingen, benötigst du ein vertrauenswürdiges SSL-Zertifikat, z.B. von Let's Encrypt. Bitte stelle entweder ein Zertifikat bereit, oder deaktiviere sichere Verbindungen.", "Identify": "Identifizieren", "Images": "Bilder", "ImportFavoriteChannelsHelp": "Wenn aktiviert, werden nur auf dem Tuner favorisierte Kanäle importiert.", "ImportMissingEpisodesHelp": "Wenn aktiviert, werden Informationen über fehlende Episoden in Deine Jellyfin Datenbank importiert und innerhalb von Staffeln angezeigt. Dies kann zu deutlich längeren Bibliothek Scans führen.", - "InstallingPackage": "Installiere {0}", + "InstallingPackage": "Installiere {0} (Version {1})", "InstantMix": "Schnellmix", "ItemCount": "{0} Einträge", "Items": "Einträge", @@ -512,7 +509,7 @@ "LabelBlockContentWithTags": "Blockiere Inhalte mit Tags:", "LabelBurnSubtitles": "Untertitel einbrennen:", "LabelCachePath": "Cache Pfad:", - "LabelCachePathHelp": "Legen Sie ein eigenes Verzeichnis für den Server Zwischenspeicher fest. (z.B. für Bilder) Lassen Sie dieses Feld leer um die Standardeinstellung zu verwenden.", + "LabelCachePathHelp": "Gib einen benutzerdefinierten Speicherort für Server-Cache-Dateien wie beispielsweise Bilder an. Lasse das Feld leer, um den Server-Standard zu verwenden.", "LabelCancelled": "Abgebrochen", "LabelCertificatePassword": "Zertifikat Passwort:", "LabelCertificatePasswordHelp": "Wenn Dein Zertifikat ein Passwort benötigt, gib es hier ein.", @@ -551,7 +548,7 @@ "LabelDisplayOrder": "Anzeigereihenfolge:", "LabelDisplaySpecialsWithinSeasons": "Zeige Sonderinhalt innerhalb der Staffel in der er ausgestrahlt wurde", "LabelDownMixAudioScale": "Audio Verstärkung bei Downmixing:", - "LabelDownMixAudioScaleHelp": "Erhöhe die Audiolautstärke beim Heruntermischen. Setzte auf 1 um die original Lautstärke zu erhalten.", + "LabelDownMixAudioScaleHelp": "Erhöhe die Audiolautstärke beim Zusammenmischen zu Stereo. Setze den Wert auf 1 um die Originallautstärke zu erhalten.", "LabelDownloadLanguages": "Herunterzuladende Sprachen:", "LabelDropImageHere": "Fotos hierher ziehen oder klicken im zu browsen.", "LabelDropShadow": "Schlagschatten:", @@ -559,17 +556,17 @@ "LabelEmbedAlbumArtDidl": "Integrierte Alben-Cover in Didl", "LabelEmbedAlbumArtDidlHelp": "Einige Geräte bevorzugen diese Methode um Album Art darstellen zu können. Andere wiederum können evtl. nichts abspielen, wenn diese Funktion aktiviert ist.", "LabelEnableAutomaticPortMap": "Aktiviere das automatische Port-Mapping", - "LabelEnableAutomaticPortMapHelp": "Versuche automatisch den öffentlichen Port dem lokalen Port mit Hilfe von UPnP zuzuordnen. Dies kann mit einigen Router-Modellen nicht funktionieren. Die Änderungen werden erst nach einem Neustart des Server aktiv.", + "LabelEnableAutomaticPortMapHelp": "Leitet automatisch die öffentlichen Ports des Routers an die lokalen Ports des Servers mit Hilfe von UPnP weiter. Dies kann mit einigen Router-Modellen nicht funktionieren. Die Änderungen werden erst nach einem Neustart des Server aktiv.", "LabelEnableBlastAliveMessages": "Erzeuge Alive Meldungen", "LabelEnableBlastAliveMessagesHelp": "Aktiviere dies, wenn der Server nicht zuverlässig von anderen UPnP Geräten in ihrem Netzwerk erkannt wird.", "LabelEnableDlnaClientDiscoveryInterval": "Client-Entdeckungs Intervall (Sekunden)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Ermittelt die Zeit in Sekunden zwischen SSDP Suchanfragen die durch Jellyfin ausgeführt wurden.", "LabelEnableDlnaDebugLogging": "Aktiviere DLNA Debug Logging", - "LabelEnableDlnaDebugLoggingHelp": "Erstellt große Logdateien und sollte nur bei Bedarf zur Fehlersuche verwendet werden.", + "LabelEnableDlnaDebugLoggingHelp": "Erzeugt große Logdateien und sollte nur zur Fehlerbehebung benutzt werden.", "LabelEnableDlnaPlayTo": "Aktiviere DLNA Play To", - "LabelEnableDlnaPlayToHelp": "Geräte in deinem Netzwerk erkennen und deren Fernsteuerung ermöglichen.", + "LabelEnableDlnaPlayToHelp": "Jellyfin kann Geräte in Ihrem Netzwerk erkennen und bietet die Möglichkeit, diese fernzusteuern.", "LabelEnableDlnaServer": "DLNA-Server aktivieren", - "LabelEnableDlnaServerHelp": "Erlaubt UPnP Geräten in Ihrem Netzwerk Zugriff und Wiedergabe von Jellyfin Inhalten.", + "LabelEnableDlnaServerHelp": "Erlaubt UPnP Geräten in Ihrem Netzwerk den Zugriff und die Wiedergabe von Inhalten.", "LabelEnableHardwareDecodingFor": "Aktiviere Hardware-Decoding für:", "LabelEnableRealtimeMonitor": "Erlaube Echtzeitüberwachung", "LabelEnableRealtimeMonitorHelp": "Änderungen werden auf unterstützten Dateisystemen sofort übernommen.", @@ -580,14 +577,14 @@ "LabelEvent": "Ereignis:", "LabelEveryXMinutes": "Alle:", "LabelExtractChaptersDuringLibraryScan": "Erzeuge Kapitelbilder während des Bibliothekscans", - "LabelExtractChaptersDuringLibraryScanHelp": "Generiert Kapitelbilder während des Imports von Videos beim Bibliothekenscan erzeugt. Anderenfalls werden die Kapitelbilder während einer eigens dafür geplanten Aufgabe erstellt, was den regelmäßig Bibliothekenscan beschleunigt.", + "LabelExtractChaptersDuringLibraryScanHelp": "Falls aktiviert, werden die Kapitelbilder während des Imports von Videos beim Bibliothekenscan erzeugt. Falls deaktiviert, werden die Kapitelbilder während einer eigens dafür geplanten Aufgabe erstellt, was den regelmäßig Bibliothekenscan beschleunigt.", "LabelFailed": "Fehlgeschlagen", "LabelFileOrUrl": "Datei oder URL:", "LabelFinish": "Fertig", "LabelFont": "Schriftart:", "LabelForgotPasswordUsernameHelp": "Bitte gib deinen Benutzernamen ein, falls du dich daran erinnerst.", "LabelFriendlyName": "Benutzerfreundlicher Name:", - "LabelServerNameHelp": "Dieser Name wird benutzt um den Server zu identifizieren, normalerweise wird der Server-/Computername verwendet.", + "LabelServerNameHelp": "Dieser Name wird benutzt um den Server zu identifizieren, standardmäßig wird der Server-/Computername verwendet.", "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections", "LabelGroupMoviesIntoCollectionsHelp": "Wenn Filmlisten angezeigt werden, dann werden Filme, die zu einer Collection gehören, als ein gruppiertes Element angezeigt.", "LabelEncoderPreset": "H264 Encoding Voreinstellung:", @@ -600,42 +597,42 @@ "LabelIconMaxHeight": "Maximale Iconhöhe:", "LabelIconMaxHeightHelp": "Maximale Auflösung für durch UPnP übermittelte Icons:icon.", "LabelIconMaxWidth": "Maximale Iconbreite:", - "LabelIconMaxWidthHelp": "Maximale Auflösung für durch UPnP übermittelte Icons:icon.", + "LabelIconMaxWidthHelp": "Maximale Auflösung der Icons, die über upnp:icon angezeigt werden.", "LabelIdentificationFieldHelp": "Ein Teilstring oder Regex Ausdruck, der keine Groß- und Kleinschreibung berücksichtigt.", "LabelImageFetchersHelp": "Aktiviere und ordne deine bevorzugten Bildquellen nach Präferenzen.", "LabelImageType": "Bildtyp:", "LabelImportOnlyFavoriteChannels": "Beschränke auf favorisierte Kanäle", "LabelInNetworkSignInWithEasyPassword": "Schalte Login mit einfachen Passwort für das eigene Netzwerk ein", - "LabelInNetworkSignInWithEasyPasswordHelp": "Wenn aktiviert, können Sie sich in ihrem eigenen Netzwerk mit dem vereinfachten PIN bei Jellyfin Apps anmelden. Ihr reguläres Kennwort wird nur benötigt, wenn Sie unterwegs sind. Wenn Sie den PIN frei lassen, so benötigen Sie in Ihrem Netzwerk keinen PIN.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Einfachen Pin verwenden, um sich bei Jellyfin Apps im lokalen Netzwerk anzumelden. Ihr reguläres Kennwort wird nur benötigt, wenn Sie unterwegs sind. Wenn Sie den PIN frei lassen, so benötigen Sie in Ihrem Netzwerk keinen PIN.", "LabelInternetQuality": "Internetqualität:", "LabelKeepUpTo": "Fortführen:", "LabelKidsCategories": "Kinderkategorien:", "LabelKodiMetadataDateFormat": "Veröffentlichungsdatum Format:", - "LabelKodiMetadataDateFormatHelp": "Alle Daten innerhalb von NFO-Dateien werden in diesem Format analysiert.", - "LabelKodiMetadataEnableExtraThumbs": "Kopiere Extrafanart in Extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Alle Daten innerhalb von NFO-Dateien werden in diesem Format gelesen.", + "LabelKodiMetadataEnableExtraThumbs": "Kopiere Extra-Fanart- nach Extra-Vorschau-Feld", "LabelKodiMetadataEnableExtraThumbsHelp": "Beim downloaden von Bildern können diese sowohl als Extrafanart als auch als Extrathumb gespeichert werden, um maximale Kodi Kompatibilität zu erzielen.", "LabelKodiMetadataEnablePathSubstitution": "Aktiviere Pfadersetzung", "LabelKodiMetadataEnablePathSubstitutionHelp": "Aktiviert die Pfadersetzung für Bildpfade durch Benutzung der Server Pfadersetzung Einstellungen.", "LabelKodiMetadataSaveImagePaths": "Speicher Bildpfade innerhalb der NFO Dateien", "LabelKodiMetadataSaveImagePathsHelp": "Dies ist empfehlenswert wenn du Dateinamen hast, die nicht den Kodi Richtlinien entsprechen.", - "LabelKodiMetadataUser": "Speichere den \"Gesehen\" Status von Benutzern in NFO's für:", - "LabelKodiMetadataUserHelp": "Aktivieren, um den \"Gesehen\" Status in NFO-Dateien zu speichern, damit diese von anderen Anwendungen verwendet werden können.", + "LabelKodiMetadataUser": "Speichere den \"Gesehen\" Status von Benutzern in NFO-Dateien für:", + "LabelKodiMetadataUserHelp": "\"Gesehen\" Status in NFO-Dateien speichern, damit diese von anderen Anwendungen verwendet werden können.", "LabelLanNetworks": "Lokale Netzwerke:", "LabelLanguage": "Sprache:", "LabelLineup": "TV Programm:", "LabelLocalHttpServerPortNumber": "Lokale HTTP Portnummer:", - "LabelLocalHttpServerPortNumberHelp": "Die TCP Port Nummer, auf die der Jellyfin http Server hört.", + "LabelLocalHttpServerPortNumberHelp": "Die TCP-Portnummer, die der HTTP-Server von Jellyfin verwenden soll.", "LabelLockItemToPreventChanges": "Sperre diesen Eintrag um zukünftige Änderungen zu verhindern", "LabelLoginDisclaimer": "Anmeldung Haftungsausschluss:", - "LabelLoginDisclaimerHelp": "Dies wird am Boden des Anmeldebildschirms angezeigt.", + "LabelLoginDisclaimerHelp": "Diese Nachricht wird am unteren Ende des Anmeldebildschirms angezeigt.", "LabelManufacturer": "Hersteller:", - "LabelManufacturerUrl": "Hersteller URL", + "LabelManufacturerUrl": "Hersteller-URL", "LabelMatchType": "Übereinstimmungstyp:", "LabelMaxBackdropsPerItem": "Maximale Anzahl von Hintergründen pro Element:", "LabelMaxChromecastBitrate": "Max Chromcast Datenrate:", "LabelMaxParentalRating": "Höchste erlaubte elterlich Bewertung:", "LabelMaxResumePercentage": "Maximale Prozent für Wiederaufnahme:", - "LabelMaxResumePercentageHelp": "Titel werden als \"vollständig gesehen\" markiert, wenn sie nach dieser Zeitmarke gestoppt werden.", + "LabelMaxResumePercentageHelp": "Titel werden als vollständig gesehen markiert, wenn sie nach dieser Zeit gestoppt werden", "LabelMaxScreenshotsPerItem": "Maximale Anzahl von Screenshots pro Element:", "LabelMaxStreamingBitrate": "Maximale Streaming-Qualität:", "LabelMaxStreamingBitrateHelp": "Wähle die maximale Bitrate während des streamens.", @@ -648,12 +645,12 @@ "LabelMetadataPathHelp": "Wähle ein Verzeichnis, für die heruntergeladenen Artworks und Metadaten.", "LabelMetadataReaders": "Metadatenleser:", "LabelMetadataReadersHelp": "Lege deine bevorzugte lokale Metadatenquelle fest und ordne sie nach Prioritäten. Die erste Datei die gefunden wird, wird verwendet.", - "LabelMetadataSavers": "Metadatenspeicherer:", - "LabelMetadataSaversHelp": "Wähle das Dateiformat in dem deine Metadaten gespeichert werden sollen.", + "LabelMetadataSavers": "Metadaten-Speicherer:", + "LabelMetadataSaversHelp": "Wähle das Dateiformat, in dem deine Metadaten gespeichert werden sollen.", "LabelMethod": "Methode:", "LabelMinBackdropDownloadWidth": "Minimale Breite für zu herunterladende Hintergründe:", "LabelMinResumeDuration": "Minimale Dauer für Wiederaufnahme:", - "LabelMinResumeDurationHelp": "Die Videolänge in Sekunden, ab der die Wiedergabeposition gespeichert wird und dich fortsetzen lässt.", + "LabelMinResumeDurationHelp": "Die kürzeste Videolänge in Sekunden, die den Wiedergabeposition speichert und dich fortsetzen lässt.", "LabelMinResumePercentage": "Minimale Prozent für Wiederaufnahme:", "LabelMinResumePercentageHelp": "Titel werden als \"Ungesehen\" eingetragen, wenn sie vor dieser Zeit gestoppt werden.", "LabelMinScreenshotDownloadWidth": "Minimale Breite für zu herunterladende Screenshot:", @@ -664,9 +661,9 @@ "LabelMonitorUsers": "Überwache Aktivität von:", "LabelMovieCategories": "Filmkategorien:", "LabelMoviePrefix": "Filmpräfix:", - "LabelMoviePrefixHelp": "Wenn ein Präfix in Filmtiteln angewendet wird, gib es hier ein damit Jellyfin es korrekt behandeln kann.", + "LabelMoviePrefixHelp": "Wenn ein Präfix in Filmtiteln angewendet wird, gib es hier ein damit der Server es korrekt behandeln kann.", "LabelMovieRecordingPath": "Film Aufnahmepfad (Optional):", - "LabelMusicStreamingTranscodingBitrate": "Musik Transkodier Bitrate:", + "LabelMusicStreamingTranscodingBitrate": "Musik-Transkodierung Bitrate:", "LabelMusicStreamingTranscodingBitrateHelp": "Wähle die maximale Bitrate für das streamen von Musik.", "LabelNewName": "Neuer Name:", "LabelNewPassword": "Neues Passwort:", @@ -678,7 +675,7 @@ "LabelNumberOfGuideDays": "Anzahl von Tagen für die Programminformationen geladen werden sollen:", "LabelNumberOfGuideDaysHelp": "Das laden von zusätzlichen Programmdaten bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Aber es wird länger dauern alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", "LabelOptionalNetworkPath": "(Optionaler) Gemeinsamer Netzwerkordner:", - "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Jellyfin Apps auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen.", + "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Jellyfin Apps auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen. Beispielsweise {0} oder {1}.", "LabelOriginalAspectRatio": "Original Seitenverhältnis:", "LabelOriginalTitle": "Original Titel:", "LabelOverview": "Übersicht:", @@ -770,7 +767,7 @@ "LabelTitle": "Titel:", "LabelTrackNumber": "Stück Nummer:", "LabelTranscodingAudioCodec": "Audio Codec:", - "LabelTranscodingTempPathHelp": "Dieses Verzeichnis beinhaltet Dateien die für den Betrieb des Transcoders benutzt werden. Wähle einen eigenen Pfad oder lasse das Feld frei, um den Standardspeicherort im Server Datenverzeichnis zu nutzen.", + "LabelTranscodingTempPathHelp": "Wähle einen eigenen Pfad für transkodierte Dateien. Lasse das Feld frei, um den Standardspeicherort zu nutzen.", "LabelTranscodingThreadCount": "Anzahl Transkodierungs-Threads:", "LabelTranscodingThreadCountHelp": "Legen Sie die maximale Anzahl von Transkodierungs-Threads fest. Das Reduzieren der Thread-Anzahl verringert die CPU Auslastung, wird aber möglicherweise die Transkodierung nicht schnell genug für eine störungsfrei Wiedergabe ermöglichen.", "LabelTranscodingVideoCodec": "Video Codec:", @@ -804,7 +801,7 @@ "Large": "Groß", "LatestFromLibrary": "Neueste {0}", "LearnHowYouCanContribute": "Erfahre, wie du unterstützen kannst.", - "LibraryAccessHelp": "Wähle die Medienverzeichnisse die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden um alle Ordner zu bearbeiten.", + "LibraryAccessHelp": "Wähle die Bibliotheken aus, die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden um alle Ordner zu bearbeiten.", "Like": "Mag ich", "List": "Liste", "LiveBroadcasts": "Liveübertragungen", @@ -840,11 +837,11 @@ "MessageConfirmDeleteGuideProvider": "Möchten Sie diese Quelle wirklich löschen?", "MessageConfirmDeleteTunerDevice": "Möchten Sie dieses Gerät wirklich löschen?", "MessageConfirmProfileDeletion": "Bist du dir sicher, dass du dieses Profil löschen möchtest?", - "MessageConfirmRecordingCancellation": "Aufzeichnung abbrechen?", + "MessageConfirmRecordingCancellation": "Aufnahme abbrechen?", "MessageConfirmRemoveMediaLocation": "Bist du dir sicher diese Medienquelle entfernen zu wollen?", "MessageConfirmRestart": "Möchten Sie Jellyfin Server wirklich neu starten?", "MessageConfirmRevokeApiKey": "Möchten Sie diesen API Schlüssel wirklich löschen? Die Verbindung der Anwendung zum Jellyfin Server wird sofort unterbrochen.", - "MessageConfirmShutdown": "Möchsten Sie Jellyfin Server wirklich beenden?", + "MessageConfirmShutdown": "Möchten Sie den Server wirklich herunterfahren?", "MessageContactAdminToResetPassword": "Bitte kontaktiere deinen Systemadministrator, um dein Passwort zurücksetzen zu lassen.", "MessageCreateAccountAt": "Erstellen Sie ein Konto bei {0}", "MessageDeleteTaskTrigger": "Bist du dir sicher, dass du diesen Aufgabenauslöser entfernen möchtest?", @@ -865,7 +862,7 @@ "MessageNoAvailablePlugins": "Keine verfügbaren Erweiterungen.", "MessageNoMovieSuggestionsAvailable": "Momentan sind keine Filmvorschläge verfügbar. Schaue und bewerte zuerst deine Filme. Komme danach zurück, um deine Filmvorschläge anzuschauen.", "MessageNoPluginsInstalled": "Du hast keine Plugins installiert.", - "MessageNoTrailersFound": "Keine Trailer gefunden. Installieren Sie den Trailer-Channel um Ihre Film-Bibliothek mit Trailer aus dem Internet zu erweitern.", + "MessageNoTrailersFound": "Installiere den Filmvorschau-Kanal um die Film-Bibliothek mit Filmvorschauen aus dem Internet zu erweitern.", "MessageNothingHere": "Nichts hier.", "MessagePasswordResetForUsers": "Die Passwörter der folgenden Benutzer wurden zurückgesetzt. Diese können sich nun mit den PIN-Codes anmelden, mit denen der Reset durchgeführt wurde.", "MessagePlayAccessRestricted": "Das Abspielen dieses Inhaltes ist derzeit eingeschränkt. Bitte kontaktiere deinen Server-Administrator für weitere Informationen.", @@ -890,7 +887,7 @@ "MoreUsersCanBeAddedLater": "Weitere Benutzer können später über das Dashboard hinzugefügt werden.", "MoveLeft": "Nach links bewegen", "MoveRight": "Nach rechts bewegen", - "MovieLibraryHelp": "Überprüfe den {0}Filmbenennungsguide{1}.", + "MovieLibraryHelp": "Überprüfe den {0}Filmbenennungsleitfaden{1}.", "Movies": "Filme", "Mute": "Stumm", "MySubtitles": "Meine Untertitel", @@ -908,15 +905,15 @@ "NoNextUpItemsMessage": "Es wurde nichts gefunden. Schau dir deine Shows an!", "NoPluginConfigurationMessage": "Dieses Plugin hat keine konfigurierbaren Einstellungen.", "NoSubtitleSearchResultsFound": "Keine Ergebnisse gefunden.", - "NoSubtitles": "Keine Untertitel", + "NoSubtitles": "Keine", "NoSubtitlesHelp": "Untertitel werden standardmäßig nicht geladen. Sie können aber während der Wiedergabe manuell aktiviert werden.", "None": "Keines", "NumLocationsValue": "{0} Verzeichnisse", "Off": "Aus", "OneChannel": "Ein Kanal", - "OnlyForcedSubtitles": "Nur erzwungene Untertitel", + "OnlyForcedSubtitles": "Nur Erzwungene", "OnlyForcedSubtitlesHelp": "Nur Untertitel, die als erzwungen markiert wurden, werden geladen.", - "OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB, etc.)", + "OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administratoren", "OptionAlbumArtist": "Album-Interpret", "OptionAllUsers": "Alle Benutzer", @@ -1000,7 +997,7 @@ "OptionLikes": "Mag ich", "OptionMissingEpisode": "Fehlende Episoden", "OptionMonday": "Montag", - "OptionNew": "Neu...", + "OptionNew": "Neu…", "OptionNone": "Keines", "OptionOnAppStartup": "Bei Anwendungsstart", "OptionOnInterval": "Nach einem Intervall", @@ -1036,9 +1033,9 @@ "OptionWeekly": "Wöchentlich", "OriginalAirDateValue": "Erstausstrahlung: {0}", "Overview": "Übersicht", - "PackageInstallCancelled": "{0} Installation abgebrochen.", - "PackageInstallCompleted": "{0} Installation abgeschlossen.", - "PackageInstallFailed": "{0} Installation fehlgeschlagen.", + "PackageInstallCancelled": "{0} (Version {1}) Installation abgebrochen.", + "PackageInstallCompleted": "{0} (Version {1}) Installation abgeschlossen.", + "PackageInstallFailed": "{0} (Version {1}) Installation fehlgeschlagen.", "ParentalRating": "Altersfreigabe", "PasswordMatchError": "Die Passwörter müssen übereinstimmen.", "PasswordResetComplete": "Das Passwort wurde zurückgesetzt.", @@ -1088,7 +1085,7 @@ "RecommendationStarring": "In der Hauptrolle {0}", "Record": "Aufnehmen", "RecordSeries": "Serie aufnehmen", - "RecordingCancelled": "Aufzeichnung abgebrochen.", + "RecordingCancelled": "Aufnahme abgebrochen.", "RecordingPathChangeMessage": "Das Ändern des Aufnahmeverzeichnisses wird alte Aufnahmen nicht automatisch verschieben. Wenn Du das möchtest, musst Du das selber machen.", "RecordingScheduled": "Aufnahme geplant.", "Recordings": "Aufnahmen", @@ -1097,7 +1094,7 @@ "RefreshMetadata": "Aktualisiere Metadaten", "RefreshQueued": "Aktualisierung eingereiht.", "ReleaseDate": "Veröffentlichungsdatum", - "RememberMe": "Erinnere mich", + "RememberMe": "Angemeldet bleiben", "RemoveFromCollection": "Aus Sammlung entfernen", "RemoveFromPlaylist": "Von Wiedergabeliste entfernen", "Repeat": "Wiederholen", @@ -1108,7 +1105,6 @@ "ReplaceAllMetadata": "Ersetze alle Metadaten", "ReplaceExistingImages": "Ersetze vorhandene Bilder", "RequiredForAllRemoteConnections": "Benötigt für alle Remote Verbindungen", - "RestartPleaseWaitMessage": "Warte bitte bis der Jellyfin Server heruntergefahren und neu gestartet wurde. Dieser Vorgang dauert 1 bis 2 Minuten.", "ResumeAt": "Fortsetzen bei {0}", "Rewind": "Zurückspulen", "RunAtStartup": "Nach Hochfahren automatisch starten", @@ -1126,7 +1122,7 @@ "SearchForMissingMetadata": "Suche nach fehlenden Metadaten", "SearchForSubtitles": "Suche nach Untertiteln", "SearchResults": "Suchergebnisse", - "SendMessage": "Sende Nachricht", + "SendMessage": "Nachricht senden", "Series": "Serien", "SeriesCancelled": "Serie abgebrochen.", "SeriesDisplayOrderHelp": "Sortiere Episoden nach Ausstrahlungsdatum, DVD Reihenfolge oder absoluter Nummerierung.", @@ -1226,7 +1222,7 @@ "TrackCount": "{0} Titel", "Trailers": "Trailer", "Tuesday": "Dienstag", - "TvLibraryHelp": "Überprüfe den {0}Serienbenennungsguide{1}.", + "TvLibraryHelp": "Überprüfe den {0}Serienbenennungsleitfaden{1}.", "Uniform": "Einheitlich", "UninstallPluginConfirmation": "Möchtest du {0} wirklich deinstallieren?", "UninstallPluginHeader": "Plugin deinstallieren", @@ -1236,7 +1232,7 @@ "Up": "Hoch", "Upload": "Hochladen", "UserAgentHelp": "Stelle einen benutzerdefinierten User-Agent HTTP header zur Verfügung.", - "UserProfilesIntro": "Jellyfin bietet die Unterstützung von Benutzerprofilen, mit eigenen Ansichten, Altersfreigaben und Spielstände von Medien.", + "UserProfilesIntro": "Jellyfin bietet Unterstützung für Benutzerprofile mit eigenen Ansichtseinstellungen, Wiedergabepositionen und Altersfreigaben.", "ValueAlbumCount": "{0} Alben", "ValueConditions": "Bedingungen: {0}", "ValueEpisodeCount": "{0} Episoden", @@ -1314,7 +1310,7 @@ "LabelProfileCodecs": "Codecs:", "LabelProfileContainer": "Container:", "LabelSkin": "Textur:", - "Art": "Kunst", + "Art": "Coverkunst", "Name": "Name", "Songs": "Songs", "ValueSpecialEpisodeName": "Extra - {0}", @@ -1325,7 +1321,7 @@ "LabelVersion": "Version:", "LabelVersionNumber": "Version {0}", "LabelVideo": "Video", - "LeaveBlankToNotSetAPassword": "Du kannst dieses Feld frei lassen um kein Passwort zu setzen.", + "LeaveBlankToNotSetAPassword": "Dieses Feld frei lassen, um kein Passwort zu setzen.", "LinksValue": "Links: {0}", "MessageImageFileTypeAllowed": "Nur JPEG- und PNG-Dateien werden unterstützt.", "MessageImageTypeNotSelected": "Bitte wähle einen Bildtyp aus dem Drop-Down Menü aus.", @@ -1400,8 +1396,8 @@ "Thumb": "Miniaturansicht", "TitleSupport": "Hilfe", "Whitelist": "Erlaubt", - "AuthProviderHelp": "Auswählen eines Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzes verwendet werden soll.", - "Features": "Features", + "AuthProviderHelp": "Wähle einen Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzes verwendet werden soll.", + "Features": "Funktionen", "HeaderFavoriteBooks": "Lieblingsbücher", "HeaderFavoriteMovies": "Lieblingsfilme", "HeaderFavoriteShows": "Lieblingsserien", @@ -1410,7 +1406,6 @@ "HeaderFavoriteArtists": "Lieblings-Interpreten", "HeaderFavoriteSongs": "Lieblingslieder", "HeaderFavoriteVideos": "Lieblingsvideos", - "HeaderRestartingServer": "Server startet neu", "LabelAuthProvider": "Authentifizierungsanbieter:", "LabelServerName": "Servername:", "LabelTranscodePath": "Transkodierungspfad:", @@ -1430,7 +1425,7 @@ "OptionLoginAttemptsBeforeLockoutHelp": "Null (0) bedeutet den Standardwert von drei Versuchen für normale, sowie fünf für Administrator-Benutzer zu übernehmen. Ein Wert von -1 deaktiviert die Funktion.", "PasswordResetProviderHelp": "Wählen Sie einen Password Reset Provider, der verwendet werden soll, wenn dieser Benutzer ein Passwort zurücksetzen möchte", "Box": "Box", - "HeaderHome": "Home", + "HeaderHome": "Startseite", "LabelAudioCodec": "Audiocodec:", "LabelAudioChannels": "Audiokanäle:", "HeaderTypeImageFetchers": "{0} Bildquellen", @@ -1438,7 +1433,7 @@ "LabelAudioBitrate": "Tonbitrate:", "ButtonAddImage": "Bild hinzufügen", "LabelSize": "Größe:", - "LabelTranscodes": "Transkoder:", + "LabelTranscodes": "Transcodiert:", "LabelTranscodingProgress": "Transcodierungsfortschritt:", "LabelAudioBitDepth": "Audio-Bittiefe:", "LabelPleaseRestart": "Die Änderungen werden nach dem manuellen Neuladen des Webclients wirksam.", @@ -1446,7 +1441,7 @@ "LabelTranscodingFramerate": "Transcodierrate:", "LabelAudioSampleRate": "Audio-Abtastrate:", "LabelBaseUrl": "Basis URL:", - "LabelBaseUrlHelp": "Du kannst hier ein benutzerdefiniertes Unterverzeichnis hinzufügen, um über eine eindeutige URL auf den Server zuzugreifen.", + "LabelBaseUrlHelp": "Fügt ein benutzerdefiniertes Unterverzeichnis zur Server-URL hinzu, zum Beispiel: http://example.com/<baseurl>", "LabelFolder": "Ordner:", "LabelPasswordResetProvider": "Anbieter zum Zurücksetzen des Passwortes:", "LabelPlayMethod": "Spielmethode:", @@ -1461,7 +1456,7 @@ "MessageNoServersAvailable": "Die automatische Serversuche konnte keinen Server finden.", "LabelPlayer": "Schauspieler:", "MediaInfoCodecTag": "Codec Tag", - "SubtitleOffset": "Untertitelvorlauf", + "SubtitleOffset": "Untertitel-Synchronisierung", "PlaybackData": "Wiedergabeinformationen", "OptionThumbCard": "Vorschaukarte", "OptionPosterCard": "Posterkarte", @@ -1474,15 +1469,13 @@ "OptionRandom": "Zufällig", "TabNetworking": "Netzwerk", "VideoRange": "Videobereich", - "ButtonSplit": "Teilen", - "SelectAdminUsername": "Bitte einen Benutzernamen für das Administrator-Konto auswählen.", + "ButtonSplit": "Trennen", + "SelectAdminUsername": "Bitte wählen Sie einen Benutzernamen für den Administrator-Account.", "HeaderNavigation": "Navigation", "CopyStreamURLError": "Beim Kopieren der URL ist ein Fehler aufgetreten.", "MessageConfirmAppExit": "Wirklich verlassen?", "LabelVideoResolution": "Videoauflösung:", "LabelStreamType": "Streamtyp:", - "EnableFastImageFadeInHelp": "Aktiviere schnellere Einblendeanimation für geladene Bilder", - "EnableFastImageFadeIn": "Schnelle Bildeinblendung", "LabelPlayerDimensions": "Playerabmessungen:", "LabelDroppedFrames": "Verlorene Frames:", "LabelCorruptedFrames": "Fehlerhafte Frames:", @@ -1490,7 +1483,113 @@ "AskAdminToCreateLibrary": "Bitten Sie einen Administrator, eine Bibliothek zu erstellen.", "NoCreatedLibraries": "Sieht so aus als hättest du bis jetzt keine Bibliothek erstellt. {0}Möchtest du jetzt eine Bibliothek erstellen?{1}", "AllowFfmpegThrottling": "Transkodierung drosseln", - "PlaybackErrorNoCompatibleStream": "Es gab ein Problem bei der Erkennung des Wiedergabeprofils des Clients und der Server sendet kein kompatibles Format.", + "PlaybackErrorNoCompatibleStream": "Dieser Client ist nicht mit den Medien kompatibel und der Server sendet kein kompatibles Medienformat.", "AllowFfmpegThrottlingHelp": "Wenn eine Transkodierung oder ein Remux weit genug über die aktuelle Abspielposition fortgeschritten ist, pausiere sie sodass weniger Ressourcen verbraucht werden. Dies ist am nützlichsten, wenn wenig geskippt wird. Bei Wiedergabeproblemen sollte diese Option deaktiviert werden.", - "ClientSettings": "Client Einstellungen" + "ClientSettings": "Client Einstellungen", + "OnApplicationStartup": "Beim Starten der Applikation", + "EveryXHours": "Alle {0} Stunden", + "EveryHour": "Jede Stunde", + "EveryXMinutes": "Alle {0} Minuten", + "OnWakeFromSleep": "Beim Aufwachen aus \"Energie sparen\"", + "WeeklyAt": "{0} um {1}", + "DailyAt": "Täglich um {0}", + "LastSeen": "Zuletzt gesehen {0}", + "PersonRole": "als {0}", + "ListPaging": "{0}-{1} von {2}", + "WriteAccessRequired": "Jellyfin Server benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.", + "PathNotFound": "Der Pfad konnte nicht gefunden werden. Bitte versichere dich dass der Pfad korrekt ist und versuche es erneut.", + "Track": "Track", + "Season": "Staffel", + "ReleaseGroup": "Veröffentlichungs-Gruppe", + "Person": "Person", + "OtherArtist": "Andere Künstler", + "Movie": "Film", + "Episode": "Episode", + "Artist": "Künstler", + "AlbumArtist": "Album Künstler", + "Album": "Album", + "BoxSet": "Box Set", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "LabelLibraryPageSizeHelp": "Setzt die Anzahl der auf einer Seite angezeigten Objekte. Setze auf 0, um alle Elemente auf einer Seite anzuzeigen.", + "LabelLibraryPageSize": "Bibliothek Seiten Größe:", + "DeinterlaceMethodHelp": "Wähle die Deinterlacing-Methode zum Transkodieren von Inhalten im Zeilensprungverfahren (Interlace).", + "LabelDeinterlaceMethod": "Deinterlacing-Methode:", + "UnsupportedPlayback": "Jellyfin kann keine DRM-geschützten Inhalte entschlüsseln, aber es wird versucht, alle Inhalte unabhängig davon zu entschlüsseln, einschließlich geschützter Titel. Einige Dateien können aufgrund der Verschlüsselung oder anderer nicht unterstützter Funktionen, wie z.B. interaktive Titel, komplett schwarz erscheinen.", + "Filter": "Filter", + "New": "Neu", + "MessageUnauthorizedUser": "Sie sind im Moment nicht berechtigt, auf den Server zuzugreifen. Bitte kontaktieren Sie Ihren Server-Administrator für weitere Informationen.", + "HeaderFavoritePlaylists": "Lieblings-Wiedergabeliste", + "ButtonTogglePlaylist": "Wiedergabeliste", + "ButtonToggleContextMenu": "Mehr", + "ApiKeysCaption": "Liste der aktuell aktivierten API-Schlüssel", + "LabelNightly": "Nightly", + "LabelStable": "Stable", + "LabelChromecastVersion": "Chromecast Version", + "HeaderDVR": "DVR", + "TabDVR": "DVR", + "SaveChanges": "Änderungen speichern", + "LabelRequireHttpsHelp": "Wenn dies ausgewählt ist, leitet der Server alle Anfragen über HTTP an HTTPS weiter. Dies hat keinen Effekt, falls der Server nicht auf HTTPS hört.", + "LabelRequireHttps": "Erfordere HTTPS", + "LabelEnableHttpsHelp": "Erlaubt es dem Server, den konfigurierten HTTPS-Port zu beobachten. Damit dies geschehen kann, muss ein gültiges Zertifikat konfiguriert sein.", + "LabelEnableHttps": "Aktiviere HTTPS", + "HeaderServerAddressSettings": "Server-Adresseinstellungen", + "HeaderRemoteAccessSettings": "Fernzugriffs-Einstellungen", + "HeaderHttpsSettings": "HTTPS-Einstellungen", + "SyncPlayAccessHelp": "Wähle die Berechtigungsstufe, die dieser Benutzer auf das SyncPlay-Feature hat. SyncPlay ermöglicht die Synchronisierung der Wiedergabe mit anderen Geräten.", + "MessageSyncPlayErrorMedia": "SyncPlay konnte nicht aktiviert werden! Medienfehler.", + "MessageSyncPlayErrorMissingSession": "SyncPlay konnte nicht aktiviert werden! Fehlende Sitzung.", + "MessageSyncPlayErrorNoActivePlayer": "Keine aktive Wiedergabe gefunden. SyncPlay wurde deaktiviert.", + "MessageSyncPlayErrorAccessingGroups": "Beim Zugriff auf die Gruppen ist ein Fehler aufgetreten.", + "MessageSyncPlayLibraryAccessDenied": "Der Zugang zu diesem Inhalt ist beschränkt.", + "MessageSyncPlayJoinGroupDenied": "Eine Berechtigung ist erforderlich um SyncPlay zu benutzen.", + "MessageSyncPlayCreateGroupDenied": "Zum Erstellen einer Gruppe ist eine Genehmigung erforderlich.", + "MessageSyncPlayGroupDoesNotExist": "Konnte der Gruppe nicht beitreten, da sie nicht existiert.", + "MessageSyncPlayPlaybackPermissionRequired": "Wiedergabegenehmigung erforderlich.", + "MessageSyncPlayNoGroupsAvailable": "Keine Gruppen verfügbar. Fange an, etwas abzuspielen.", + "MessageSyncPlayGroupWait": "{0} ist am laden...", + "MessageSyncPlayUserLeft": "{0} hat die Gruppe verlassen.", + "MessageSyncPlayUserJoined": "{0} ist der Gruppe beigetreten.", + "MessageSyncPlayDisabled": "SyncPlay deaktiviert.", + "MessageSyncPlayEnabled": "SyncPlay aktiviert.", + "LabelSyncPlayAccess": "SyncPlay-Zugriff", + "LabelSyncPlayAccessNone": "Deaktiviert für diesen Benutzer", + "LabelSyncPlayAccessJoinGroups": "Erlaube dem Benutzer, Gruppen beizutreten", + "LabelSyncPlayAccessCreateAndJoinGroups": "Erlaube dem Benutzer, Gruppen zu erstellen und beizutreten", + "LabelSyncPlayLeaveGroupDescription": "Deaktiviere SyncPlay", + "LabelSyncPlayLeaveGroup": "Gruppe verlassen", + "LabelSyncPlayNewGroupDescription": "Erstelle eine neue Gruppe", + "LabelSyncPlayNewGroup": "Neue Gruppe", + "LabelSyncPlaySyncMethod": "Sync-Methode:", + "LabelSyncPlayPlaybackDiff": "Zeitversatz bei der Wiedergabe:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Zeitversatz mit dem Server:", + "HeaderSyncPlayEnabled": "SyncPlay aktiviert", + "HeaderSyncPlaySelectGroup": "Tritt einer Gruppe bei", + "EnableDetailsBannerHelp": "Zeigt ein Bannerbild im oberen Bereich der Seite Item-Details.", + "EnableDetailsBanner": "Detailbanner", + "ShowMore": "Mehr anzeigen", + "ShowLess": "Weniger anzeigen", + "EnableBlurhashHelp": "Bilder, die noch nicht fertig geladen wurden, werden mit einem verschwommenen Platzhalter dargestellt", + "EnableBlurhash": "Verschwommene Platzhalter für Bilder erlauben", + "EnableFasterAnimations": "Schnellere Animationen", + "EnableDecodingColorDepth10Vp9": "Aktiviere 10-Bit-Hardware-Dekodierung für VP9", + "EnableDecodingColorDepth10Hevc": "Aktiviere 10-Bit-Hardware-Dekodierung für HEVC", + "MessageNoGenresAvailable": "Aktiviere einige Metadaten-Anbieter um Genres aus dem Internet zu holen.", + "EnableFasterAnimationsHelp": "Benutze schnellere Animationen und Übergänge", + "ButtonCast": "Besetzung", + "ButtonSyncPlay": "SyncPlay", + "TabRepositories": "Repositories", + "MessageAddRepository": "Wenn Sie ein Repository hinzufügen möchten, klicken Sie auf die Schaltfläche neben der Kopfzeile und füllen Sie die angeforderten Informationen aus.", + "LabelRepositoryUrlHelp": "Der Speicherort des Repository-Manifests, das Sie aufnehmen möchten.", + "LabelRepositoryNameHelp": "Ein benutzerdefinierter Name zur Unterscheidung dieses Repositorys von den anderen, die zu Ihrem Server hinzugefügt wurden.", + "LabelRepositoryName": "Name des Repository", + "LabelRepositoryUrl": "URL des Repository", + "HeaderNewRepository": "Neues Repository", + "MessageNoRepositories": "Keine Repositories.", + "ButtonPlayer": "Player", + "Writers": "Autoren", + "ClearQueue": "Wiedergabeliste leeren", + "StopPlayback": "Wiedergabe anhalten", + "ViewAlbumArtist": "Zeige Albumkünstler" } diff --git a/src/strings/el.json b/src/strings/el.json index 3856480fd01..24fd7c09345 100644 --- a/src/strings/el.json +++ b/src/strings/el.json @@ -10,25 +10,25 @@ "AdditionalNotificationServices": "Περιηγηθείτε στον κατάλογο plugin για να εγκαταστήσετε πρόσθετες υπηρεσίες ειδοποίησης.", "AirDate": "Ημερομηνία προβολής", "Aired": "Προβλήθηκε", - "Albums": "Άλμπουμ", + "Albums": "Άλμπουμς", "All": "Όλα", "AllChannels": "Όλα τα κανάλια", - "AllComplexFormats": "Όλες οι σύνθετες μορφές (ASS, SSA, VOBSUB, PGS, SUB / IDX κ.λπ.)", + "AllComplexFormats": "Όλες οι σύνθετες μορφές (ASS, SSA, VOBSUB, PGS, SUB / IDX, ...)", "AllEpisodes": "Ολα τα επεισόδια", "AllLanguages": "Όλες οι γλώσσες", "AllLibraries": "Όλες οι βιβλιοθήκες", "AllowRemoteAccess": "Να επιτρέπονται οι απομακρυσμένες συνδέσεις σε αυτόν το διακομιστή Jellyfin.", "AllowRemoteAccessHelp": "Εάν δεν επιλεχθεί, όλες οι απομακρυσμένες συνδέσεις θα αποκλειστούν.", - "AlwaysPlaySubtitles": "Πάντα αναπαραγωγή Υποτίτλων", + "AlwaysPlaySubtitles": "Παίξτε πάντα", "AlwaysPlaySubtitlesHelp": "Οι υπότιτλοι που ταιριάζουν με τις προτιμήσεις γλώσσας θα φορτωθούν ανεξάρτητα από τη γλώσσα του ήχου.", "AnyLanguage": "Οποιαδήποτε γλώσσα", "Anytime": "Οποτεδήποτε", - "AroundTime": "Περίπου {0}", + "AroundTime": "Περίπου", "Art": "Τέχνη", "Artists": "Καλλιτέχνες", "AsManyAsPossible": "Οσο το δυνατον περισσοτερα", "Ascending": "Αύξουσα", - "AspectRatio": "Αρχικός λόγος διαστάσεων", + "AspectRatio": "Αναλογία απεικόνισης", "AttributeNew": "Νέο", "Audio": "Ήχος", "Auto": "Αυτόματο", @@ -985,7 +985,6 @@ "RepeatOne": "Επαναλάβετε ένα", "ReplaceAllMetadata": "Αντικατάσταση όλων των μεταδεδομένων", "ReplaceExistingImages": "Αντικατάσταση υπαρχουσών εικόνων", - "RestartPleaseWaitMessage": "Περιμένετε μέχρι ο τερματικός σταθμός Jellyfin να τερματιστεί και να επανεκκινήσει. Αυτό μπορεί να διαρκέσει ένα λεπτό ή δύο.", "ResumeAt": "Συνέχιση από {0}", "Rewind": "Αναπαραγωγή προς τα πίσω", "RunAtStartup": "Εκτέλεση κατά την εκκίνηση", @@ -1165,7 +1164,6 @@ "HeaderSelectMetadataPathHelp": "Περιηγηθείτε ή επιλέξτε την διαδρομή αποθήκευσης των μεταδεδομένων. Ο φάκελος πρέπει να είναι εγγράψιμος.", "HeaderSelectMetadataPath": "Επιλέξτε Διαδρομή Μεταδεδομένων", "HeaderSelectCertificatePath": "Επιλέξτε Διαδρομή Πιστοποιητικού", - "HeaderRestartingServer": "Επανεκκίνηση Διακομιστή", "HeaderRemoveMediaFolder": "Αφαίρεση Φακέλου Μέσων", "HeaderPeople": "Πρόσωπα", "HeaderIdentification": "Ταυτοποίηση", @@ -1224,6 +1222,18 @@ "LabelServerName": "Όνομα Διακομιστή:", "ButtonAddImage": "Προσθήκη Εικόνας", "BoxRear": "Κουτί(πίσω)", - "BookLibraryHelp": "Ήχος και βιβλία υποστηρίζονται.Ελέγξτε τον {0}ονομαστικό οδηγό βιβλίων{1}.", - "AuthProviderHelp": "Επιλέξτε ένα Πάροχο Επαλήθευσης για να επαληθεύσετε το κωδικό αυτού του χρήστη." + "BookLibraryHelp": "Υποστήριξη ήχου και βιβλίων κειμένου. Εξετάστε τον {0}οδηγό ονομάτων βιβλίου{1}.", + "AuthProviderHelp": "Επιλέξτε ένα Πάροχο Επαλήθευσης για να επαληθεύσετε το κωδικό αυτού του χρήστη.", + "AllowFfmpegThrottling": "Επιτάχυνση Διακωδικοποιησής", + "AlbumArtist": "Άλμπουμ Καλλιτέχνη", + "Album": "Άλμπουμ", + "BoxSet": "Σετ Κουτιού", + "AskAdminToCreateLibrary": "Ζητήστε από έναν διαχειριστή να δημιουργήσει μια βιβλιοθήκη.", + "Artist": "Καλλιτέχνης", + "AllowedRemoteAddressesHelp": "Λίστα διαχωρισμένων διευθύνσεων IP ή καταχωρίσεων IP / netmask για δίκτυα που θα επιτρέπεται η σύνδεση εξ αποστάσεως. Εάν αφεθεί κενό, όλες οι απομακρυσμένες διευθύνσεις θα επιτρέπονται.", + "AllowFfmpegThrottlingHelp": "Όταν ένας διακωδικοποιητής ή remux φτάσει αρκετά μπροστά από την τρέχουσα θέση αναπαραγωγής, διακόψτε τη διαδικασία ώστε να καταναλώσει λιγότερους πόρους. Αυτό είναι πιο χρήσιμο όταν παρακολουθείτε χωρίς να αναζητάτε συχνά. Απενεργοποιήστε το εάν αντιμετωπίζετε προβλήματα αναπαραγωγής.", + "ButtonTogglePlaylist": "Λίστα αναπαραγωγής", + "ButtonToggleContextMenu": "Περισσότερα", + "ButtonSplit": "Διαχωρισμός", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 757ea552665..8b63def2a23 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -63,7 +63,7 @@ "Alerts": "Alerts", "All": "All", "AllChannels": "All channels", - "AllComplexFormats": "All complex formats (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "All Complex Formats (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllEpisodes": "All episodes", "AllLanguages": "All languages", "AllLibraries": "All libraries", @@ -71,15 +71,15 @@ "AllowMediaConversion": "Allow media conversion", "AllowMediaConversionHelp": "Grant or deny access to the convert media feature.", "AllowOnTheFlySubtitleExtraction": "Allow subtitle extraction on the fly", - "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", + "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", "AllowRemoteAccess": "Allow remote connections to this Jellyfin Server.", "AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.", "AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.", - "AlwaysPlaySubtitles": "Always play subtitles", + "AlwaysPlaySubtitles": "Always Play", "AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", - "AnyLanguage": "Any language", + "AnyLanguage": "Any Language", "Anytime": "Anytime", - "AroundTime": "Around {0}", + "AroundTime": "Around", "Art": "Art", "AsManyAsPossible": "As many as possible", "Ascending": "Ascending", @@ -96,11 +96,11 @@ "BirthLocation": "Birth location", "BirthPlaceValue": "Birth place: {0}", "Blacklist": "Blacklist", - "BookLibraryHelp": "Audio and text books are supported. Review the {0}book naming guide{1}.", + "BookLibraryHelp": "Audio and text books are supported. Review the {0} book naming guide {1}.", "Box": "Box", "BoxRear": "Box (rear)", "Browse": "Browse", - "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when converting video depending on the subtitle format. Avoiding burning in subtitles will improve server performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB/IDX, etc) and certain ASS/SSA subtitles.", + "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.", "ButtonAdd": "Add", "ButtonAddMediaLibrary": "Add Media Library", "ButtonAddScheduledTaskTrigger": "Add Trigger", @@ -233,7 +233,7 @@ "DisplayInOtherHomeScreenSections": "Display in home screen sections such as latest media and continue watching", "DisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", "DisplayMissingEpisodesWithinSeasonsHelp": "This must also be enabled for TV libraries in the server configuration.", - "DisplayModeHelp": "Select the type of screen you're running Jellyfin on.", + "DisplayModeHelp": "Select the layout style you want for the interface.", "DoNotRecord": "Do not record", "Down": "Down", "Download": "Download", @@ -385,6 +385,7 @@ "HeaderFavoriteArtists": "Favourite Artists", "HeaderFavoriteSongs": "Favourite Songs", "HeaderFavoriteVideos": "Favourite Videos", + "HeaderFavoritePlaylists": "Favourite Playlists", "HeaderFeatureAccess": "Feature Access", "HeaderFeatures": "Features", "HeaderFetchImages": "Fetch Images:", @@ -527,6 +528,8 @@ "Smart": "Smart", "SimultaneousConnectionLimitHelp": "The maximum number of allowed simultaneous streams. Enter 0 for no limit.", "Shuffle": "Shuffle", + "New": "New", + "Filter": "Filter", "ShowYear": "Show year", "ShowIndicatorsFor": "Show indicators for:", "ShowAdvancedSettings": "Show advanced settings", @@ -558,7 +561,6 @@ "RunAtStartup": "Run at startup", "Rewind": "Rewind", "ResumeAt": "Resume from {0}", - "RestartPleaseWaitMessage": "Please wait while Jellyfin Server shuts down and restarts. This may take a minute or two.", "RequiredForAllRemoteConnections": "Required for all remote connections", "ReplaceExistingImages": "Replace existing images", "ReplaceAllMetadata": "Replace all metadata", @@ -569,7 +571,7 @@ "Repeat": "Repeat", "RemoveFromPlaylist": "Remove from playlist", "RemoveFromCollection": "Remove from collection", - "RememberMe": "Remember me", + "RememberMe": "Remember Me", "ReleaseDate": "Release date", "RefreshMetadata": "Refresh metadata", "RefreshDialogHelp": "Metadata is refreshed based on settings and internet services that are enabled in the Jellyfin Server dashboard.", @@ -617,9 +619,9 @@ "PasswordResetComplete": "The password has been reset.", "PasswordMatchError": "Password and password confirmation must match.", "ParentalRating": "Parental rating", - "PackageInstallFailed": "{0} installation failed.", - "PackageInstallCompleted": "{0} installation completed.", - "PackageInstallCancelled": "{0} installation cancelled.", + "PackageInstallFailed": "{0} (version {1}) installation failed.", + "PackageInstallCompleted": "{0} (version {1}) installation completed.", + "PackageInstallCancelled": "{0} (version {1}) installation cancelled.", "OriginalAirDateValue": "Original air date: {0}", "OptionWeekly": "Weekly", "OptionWeekends": "Weekends", @@ -657,7 +659,7 @@ "OptionOnInterval": "On an interval", "OptionOnAppStartup": "On application startup", "OptionNone": "None", - "OptionNew": "New...", + "OptionNew": "New…", "OptionMissingEpisode": "Missing Episodes", "OptionMax": "Max", "OptionLoginAttemptsBeforeLockoutHelp": "A value of zero means inheriting the default of three attempts for normal users and five for administrators. Setting this to -1 will disable the feature.", @@ -690,12 +692,12 @@ "OptionAlbumArtist": "Album Artist", "OptionAlbum": "Album", "Option3D": "3D", - "OnlyImageFormats": "Only image formats (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Only Image Formats (VOBSUB, PGS, SUB)", "OnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.", "Normal": "Normal", "None": "None", "NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.", - "NoSubtitles": "No subtitles", + "NoSubtitles": "None", "NoPluginConfigurationMessage": "This plugin has no settings to configure.", "NoNextUpItemsMessage": "None found. Start watching your shows!", "No": "No", @@ -717,7 +719,7 @@ "MessagePlayAccessRestricted": "Playback of this content is currently restricted. Please contact your server administrator for more information.", "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the pin codes that were used to perform the reset.", "MessageNothingHere": "Nothing here.", - "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.", + "MessageNoTrailersFound": "Install the trailers channel to enhance your movie experience by adding a library of internet trailers.", "MessageNoServersAvailable": "No servers have been found using the automatic server discovery.", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoAvailablePlugins": "No available plugins.", @@ -834,8 +836,8 @@ "LabelSecureConnectionsMode": "Secure connection mode:", "LabelSeasonNumber": "Season number:", "LabelScreensaver": "Screensaver:", - "EnableFastImageFadeIn": "Fast image fade-in", - "EnableFastImageFadeInHelp": "Enable faster fade-in animation for loaded images", + "EnableFasterAnimations": "Faster animations", + "EnableFasterAnimationsHelp": "Use faster animations and transitions", "LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.", "LabelSaveLocalMetadataHelp": "Saving artwork into media folders will put them in a place where they can be easily edited.", "LabelRuntimeMinutes": "Run time (minutes):", @@ -871,7 +873,7 @@ "MessageConfirmProfileDeletion": "Are you sure you wish to delete this profile?", "LaunchWebAppOnStartup": "Launch the web interface when starting the server", "LabelYourFirstName": "Your first name:", - "OnlyForcedSubtitles": "Only forced subtitles", + "OnlyForcedSubtitles": "Only Forced", "Off": "Off", "NumLocationsValue": "{0} folders", "Name": "Name", @@ -1029,8 +1031,8 @@ "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play content.", "LabelEnableDlnaDebugLoggingHelp": "Create large log files and should only be used as needed for troubleshooting purposes.", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Jellyfin.", - "LabelEnableAutomaticPortMapHelp": "Attempt to automatically map the public port to the local port via UPnP. This may not work with some router models. Changes will not apply until after a server restart.", - "InstallingPackage": "Installing {0}", + "LabelEnableAutomaticPortMapHelp": "Automatically forward public ports on your router to local ports on your server via UPnP. This may not work with some router models or network configurations. Changes will not apply until after a server restart.", + "InstallingPackage": "Installing {0} (version {1})", "ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.", "HeaderSubtitleAppearance": "Subtitle Appearance", "LabelProtocol": "Protocol:", @@ -1115,7 +1117,7 @@ "LabelForgotPasswordUsernameHelp": "Enter your username, if you remember it.", "LabelFont": "Font:", "LabelExtractChaptersDuringLibraryScanHelp": "Generate chapter images when videos are imported during the library scan. Otherwise, they will be extracted during the chapter images scheduled task, allowing the regular library scan to complete faster.", - "LabelBaseUrlHelp": "You can add a custom subdirectory here to access the server from a more unique URL.", + "LabelBaseUrlHelp": "Adds a custom subdirectory to the server URL. For example: http://example.com/<baseurl>", "LabelEveryXMinutes": "Every:", "LabelEvent": "Event:", "LabelEpisodeNumber": "Episode number:", @@ -1243,7 +1245,7 @@ "LabelPasswordResetProvider": "Password Reset Provider:", "LabelPasswordConfirm": "Password (confirm):", "LabelOriginalTitle": "Original title:", - "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly.", + "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly. For example, {0} or {1}.", "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", "LabelNumberOfGuideDays": "Number of days of guide data to download:", "LabelNumber": "Number:", @@ -1432,7 +1434,6 @@ "HeaderScenes": "Scenes", "HeaderRunningTasks": "Running Tasks", "HeaderRevisionHistory": "Revision History", - "HeaderRestartingServer": "Restarting Server", "HeaderRestart": "Restart", "HeaderResponseProfile": "Response Profile", "HeaderRemoveMediaLocation": "Remove Media Location", @@ -1472,7 +1473,111 @@ "CopyStreamURLError": "There was an error copying the URL.", "NoCreatedLibraries": "Seems like you haven't created any libraries yet. {0}Would you like to create one now?{1}", "AskAdminToCreateLibrary": "Ask an administrator to create a library.", - "PlaybackErrorNoCompatibleStream": "There was an issue with the client profiling and the server isn't sending a compatible media format.", + "PlaybackErrorNoCompatibleStream": "This client isn't compatible with the media and the server isn't sending a compatible media format.", "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", - "AllowFfmpegThrottling": "Throttle Transcodes" + "AllowFfmpegThrottling": "Throttle Transcodes", + "OnApplicationStartup": "On application startup", + "EveryXHours": "Every {0} hours", + "EveryHour": "Every hour", + "EveryXMinutes": "Every {0} minutes", + "OnWakeFromSleep": "On wake from sleep", + "WeeklyAt": "{0}s at {1}", + "DailyAt": "Daily at {0}", + "LastSeen": "Last seen {0}", + "PersonRole": "as {0}", + "ListPaging": "{0}-{1} of {2}", + "WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.", + "PathNotFound": "The path could not be found. Please ensure the path is valid and try again.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Track", + "Season": "Season", + "ReleaseGroup": "Release Group", + "PreferEmbeddedEpisodeInfosOverFileNames": "Prefer embedded episode information over filenames", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode information from the embedded metadata if available.", + "Person": "Person", + "OtherArtist": "Other Artist", + "Movie": "Movie", + "LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.", + "LabelLibraryPageSize": "Library page size:", + "LabelDeinterlaceMethod": "Deinterlacing method:", + "Episode": "Episode", + "DeinterlaceMethodHelp": "Select the deinterlacing method to use when transcoding interlaced content.", + "ClientSettings": "Client Settings", + "BoxSet": "Box Set", + "Artist": "Artist", + "AlbumArtist": "Album Artist", + "Album": "Album", + "UnsupportedPlayback": "Jellyfin cannot decrypt content protected by DRM but all content will be attempted regardless, including protected titles. Some files may appear completely black due to encryption or other unsupported features, such as interactive titles.", + "MessageUnauthorizedUser": "You are not authorized to access the server at this time. Please contact your server administrator for more information.", + "ButtonTogglePlaylist": "Playlist", + "ButtonToggleContextMenu": "More", + "HeaderDVR": "DVR", + "ApiKeysCaption": "List of the currently enabled API keys", + "ButtonCast": "Cast", + "ButtonSyncPlay": "SyncPlay", + "EnableBlurhashHelp": "Images that are still being loaded will be displayed with a blurred placeholder", + "EnableBlurhash": "Enable blurred placeholders for images", + "TabDVR": "DVR", + "TabRepositories": "Repositories", + "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other devices.", + "ShowMore": "Show more", + "ShowLess": "Show less", + "SaveChanges": "Save changes", + "MessageSyncPlayErrorMedia": "Failed to enable SyncPlay! Media error.", + "MessageSyncPlayErrorMissingSession": "Failed to enable SyncPlay! Missing session.", + "MessageSyncPlayErrorNoActivePlayer": "No active player found. SyncPlay has been disabled.", + "MessageSyncPlayErrorAccessingGroups": "An error occurred while accessing groups list.", + "MessageSyncPlayLibraryAccessDenied": "Access to this content is restricted.", + "MessageSyncPlayJoinGroupDenied": "Permission required to use SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", + "MessageSyncPlayGroupDoesNotExist": "Failed to join group because it does not exist.", + "MessageSyncPlayPlaybackPermissionRequired": "Playback permission required.", + "MessageSyncPlayNoGroupsAvailable": "No groups available. Start playing something first.", + "MessageSyncPlayGroupWait": "{0} is buffering...", + "MessageSyncPlayUserLeft": "{0} has left the group.", + "MessageSyncPlayUserJoined": "{0} has joined the group.", + "MessageSyncPlayDisabled": "SyncPlay disabled.", + "MessageSyncPlayEnabled": "SyncPlay enabled.", + "MessageNoGenresAvailable": "Enable some metadata providers to pull genres from the internet.", + "MessageAddRepository": "If you wish to add a repository, click the button next to the header and fill out the requested information.", + "LabelRepositoryNameHelp": "A custom name to distinguish this repository from any others added to your server.", + "LabelRepositoryName": "Repository Name", + "LabelRepositoryUrlHelp": "The location of the repository manifest you want to include.", + "LabelRepositoryUrl": "Repository URL", + "HeaderNewRepository": "New Repository", + "MessageNoRepositories": "No repositories.", + "LabelSyncPlayAccess": "SyncPlay access", + "LabelSyncPlayAccessNone": "Disabled for this user", + "LabelSyncPlayAccessJoinGroups": "Allow user to join groups", + "LabelSyncPlayAccessCreateAndJoinGroups": "Allow user to create and join groups", + "LabelSyncPlayLeaveGroupDescription": "Disable SyncPlay", + "LabelSyncPlayLeaveGroup": "Leave group", + "LabelSyncPlayNewGroupDescription": "Create a new group", + "LabelSyncPlayNewGroup": "New group", + "LabelSyncPlaySyncMethod": "Sync method:", + "LabelSyncPlayPlaybackDiff": "Playback time difference:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Time offset with the server:", + "LabelRequireHttpsHelp": "If checked, the server will automatically redirect all requests over HTTP to HTTPS. This has no effect if the server is not listening on HTTPS.", + "LabelRequireHttps": "Require HTTPS", + "LabelNightly": "Nightly", + "LabelStable": "Stable", + "LabelChromecastVersion": "Chromecast Version", + "LabelEnableHttpsHelp": "Enables the server to listen on the configured HTTPS port. A valid certificate must also be configured in order for this to take effect.", + "LabelEnableHttps": "Enable HTTPS", + "HeaderSyncPlayEnabled": "SyncPlay enabled", + "HeaderSyncPlaySelectGroup": "Join a group", + "HeaderServerAddressSettings": "Server Address Settings", + "HeaderRemoteAccessSettings": "Remote Access Settings", + "HeaderHttpsSettings": "HTTPS Settings", + "EnableDetailsBannerHelp": "Display a banner image at the top of the item details page.", + "EnableDetailsBanner": "Details Banner", + "EnableDecodingColorDepth10Vp9": "Enable 10-Bit hardware decoding for VP9", + "EnableDecodingColorDepth10Hevc": "Enable 10-Bit hardware decoding for HEVC", + "ViewAlbumArtist": "View album artist", + "ClearQueue": "Clear queue", + "StopPlayback": "Stop playback", + "ButtonPlayer": "Player", + "Writers": "Writers" } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 924f1a188b9..c1ea50431d2 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -11,11 +11,13 @@ "AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.", "AirDate": "Air date", "Aired": "Aired", + "Album": "Album", + "AlbumArtist": "Album Artist", "Albums": "Albums", "Alerts": "Alerts", "All": "All", "AllChannels": "All channels", - "AllComplexFormats": "All complex formats (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "All Complex Formats (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllEpisodes": "All episodes", "AllLanguages": "All languages", "AllLibraries": "All libraries", @@ -23,18 +25,19 @@ "AllowMediaConversion": "Allow media conversion", "AllowMediaConversionHelp": "Grant or deny access to the convert media feature.", "AllowOnTheFlySubtitleExtraction": "Allow subtitle extraction on the fly", - "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", + "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", "AllowFfmpegThrottling": "Throttle Transcodes", "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", "AllowRemoteAccess": "Allow remote connections to this Jellyfin Server.", "AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.", "AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.", - "AlwaysPlaySubtitles": "Always play subtitles", + "AlwaysPlaySubtitles": "Always Play", "AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", - "AnyLanguage": "Any language", + "AnyLanguage": "Any Language", "Anytime": "Anytime", - "AroundTime": "Around {0}", + "AroundTime": "Around", "Art": "Art", + "Artist": "Artist", "Artists": "Artists", "AsManyAsPossible": "As many as possible", "Ascending": "Ascending", @@ -52,13 +55,14 @@ "BirthLocation": "Birth location", "BirthPlaceValue": "Birth place: {0}", "Blacklist": "Blacklist", - "BookLibraryHelp": "Audio and text books are supported. Review the {0}book naming guide{1}.", + "BookLibraryHelp": "Audio and text books are supported. Review the {0} book naming guide {1}.", "Books": "Books", "Box": "Box", + "BoxSet": "Box Set", "BoxRear": "Box (rear)", "Browse": "Browse", "BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins.", - "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when converting video depending on the subtitle format. Avoiding burning in subtitles will improve server performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB/IDX, etc) and certain ASS/SSA subtitles.", + "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.", "ButtonAdd": "Add", "ButtonAddImage": "Add Image", "ButtonAddMediaLibrary": "Add Media Library", @@ -133,6 +137,8 @@ "ButtonSplit": "Split", "ButtonSubmit": "Submit", "ButtonSubtitles": "Subtitles", + "ButtonToggleContextMenu": "More", + "ButtonTogglePlaylist": "Playlist", "ButtonTrailer": "Trailer", "ButtonUninstall": "Uninstall", "ButtonUp": "Up", @@ -175,6 +181,7 @@ "DefaultErrorMessage": "There was an error processing the request. Please try again later.", "DefaultMetadataLangaugeDescription": "These are your defaults and can be customized on a per-library basis.", "DefaultSubtitlesHelp": "Subtitles are loaded based on the default and forced flags in the embedded metadata. Language preferences are considered when multiple options are available.", + "DeinterlaceMethodHelp": "Select the deinterlacing method to use when transcoding interlaced content.", "Delete": "Delete", "DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.", "DeleteImage": "Delete Image", @@ -202,7 +209,7 @@ "DisplayInOtherHomeScreenSections": "Display in home screen sections such as latest media and continue watching", "DisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", "DisplayMissingEpisodesWithinSeasonsHelp": "This must also be enabled for TV libraries in the server configuration.", - "DisplayModeHelp": "Select the type of screen you're running Jellyfin on.", + "DisplayModeHelp": "Select the layout style you want for the interface.", "DoNotRecord": "Do not record", "Down": "Down", "Download": "Download", @@ -218,6 +225,8 @@ "EnableBackdropsHelp": "Display backdrops in the background of some pages while browsing the library.", "EnableCinemaMode": "Cinema mode", "EnableColorCodedBackgrounds": "Color coded backgrounds", + "EnableDecodingColorDepth10Hevc": "Enable 10-Bit hardware decoding for HEVC", + "EnableDecodingColorDepth10Vp9": "Enable 10-Bit hardware decoding for VP9", "EnableDisplayMirroring": "Display mirroring", "EnableExternalVideoPlayers": "External video players", "EnableExternalVideoPlayersHelp": "An external player menu will be shown when starting video playback.", @@ -232,8 +241,11 @@ "EnableThemeSongsHelp": "Play theme songs in the background while browsing the library.", "EnableThemeVideos": "Theme videos", "EnableThemeVideosHelp": "Play theme videos in the background while browsing the library.", + "EnableDetailsBanner": "Details Banner", + "EnableDetailsBannerHelp": "Display a banner image at the top of the item details page.", "Ended": "Ended", "EndsAtValue": "Ends at {0}", + "Episode": "Episode", "Episodes": "Episodes", "ErrorAddingListingsToSchedulesDirect": "There was an error adding the lineup to your Schedules Direct account. Schedules Direct only allows a limited number of lineups per account. You may need to log into the Schedules Direct website and remove others listings from your account before proceeding.", "ErrorAddingMediaPathToVirtualFolder": "There was an error adding the media path. Please ensure the path is valid and the Jellyfin Server process has access to that location.", @@ -282,7 +294,6 @@ "H264CrfHelp": "The Constant Rate Factor (CRF) is the default quality setting for the x264 encoder. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, so you can use this as a starting point.", "EncoderPresetHelp": "Choose a faster value to improve performance, or a slower value to improve quality.", "HDPrograms": "HD programs", - "HandledByProxy": "Handled by reverse proxy", "HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.", "HeaderAccessSchedule": "Access Schedule", "HeaderAccessScheduleHelp": "Create an access schedule to limit access to certain hours.", @@ -303,6 +314,7 @@ "HeaderApiKey": "API Key", "HeaderApiKeys": "API Keys", "HeaderApiKeysHelp": "External applications are required to have an API key in order to communicate with Jellyfin Server. Keys are issued by logging in with a Jellyfin account, or by manually granting the application a key.", + "ApiKeysCaption": "List of the currently enabled API keys", "HeaderApp": "App", "HeaderAppearsOn": "Appears On", "HeaderAudioBooks": "Audio Books", @@ -346,6 +358,7 @@ "HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.", "HeaderDisplay": "Display", "HeaderDownloadSync": "Download & Sync", + "HeaderDVR": "DVR", "HeaderEasyPinCode": "Easy Pin Code", "HeaderEditImages": "Edit Images", "HeaderEnabledFields": "Enabled Fields", @@ -362,6 +375,7 @@ "HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteSongs": "Favorite Songs", "HeaderFavoriteVideos": "Favorite Videos", + "HeaderFavoritePlaylists": "Favorite Playlists", "HeaderFeatureAccess": "Feature Access", "HeaderFeatures": "Features", "HeaderFetchImages": "Fetch Images:", @@ -374,6 +388,7 @@ "HeaderGuideProviders": "TV Guide Data Providers", "HeaderHome": "Home", "HeaderHttpHeaders": "HTTP Headers", + "HeaderHttpsSettings": "HTTPS Settings", "HeaderIdentification": "Identification", "HeaderIdentificationCriteriaHelp": "Enter at least one identification criteria.", "HeaderIdentificationHeader": "Identification Header", @@ -440,13 +455,13 @@ "HeaderRecentlyPlayed": "Recently Played", "HeaderRecordingOptions": "Recording Options", "HeaderRecordingPostProcessing": "Recording Post Processing", + "HeaderRemoteAccessSettings": "Remote Access Settings", "HeaderRemoteControl": "Remote Control", "HeaderRemoveMediaFolder": "Remove Media Folder", "HeaderRemoveMediaLocation": "Remove Media Location", "HeaderResponseProfile": "Response Profile", "HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.", "HeaderRestart": "Restart", - "HeaderRestartingServer": "Restarting Server", "HeaderRevisionHistory": "Revision History", "HeaderRunningTasks": "Running Tasks", "HeaderScenes": "Scenes", @@ -466,6 +481,7 @@ "HeaderSeries": "Series", "HeaderSeriesOptions": "Series Options", "HeaderSeriesStatus": "Series Status", + "HeaderServerAddressSettings": "Server Address Settings", "HeaderServerSettings": "Server Settings", "HeaderSettings": "Settings", "HeaderSetupLibrary": "Setup your media libraries", @@ -482,6 +498,8 @@ "HeaderSubtitleProfile": "Subtitle Profile", "HeaderSubtitleProfiles": "Subtitle Profiles", "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", + "HeaderSyncPlaySelectGroup": "Join a group", + "HeaderSyncPlayEnabled": "SyncPlay enabled", "HeaderSystemDlnaProfiles": "System Profiles", "HeaderTags": "Tags", "HeaderTaskTriggers": "Task Triggers", @@ -516,7 +534,7 @@ "Images": "Images", "ImportFavoriteChannelsHelp": "If enabled, only channels that are marked as favorite on the tuner device will be imported.", "ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.", - "InstallingPackage": "Installing {0}", + "InstallingPackage": "Installing {0} (version {1})", "InstantMix": "Instant mix", "ItemCount": "{0} items", "Items": "Items", @@ -598,6 +616,7 @@ "LabelDefaultScreen": "Default screen:", "LabelDefaultUser": "Default user:", "LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.", + "LabelDeinterlaceMethod": "Deinterlacing method:", "LabelDeviceDescription": "Device description", "LabelDidlMode": "DIDL mode:", "LabelDiscNumber": "Disc number:", @@ -619,7 +638,7 @@ "LabelEmbedAlbumArtDidl": "Embed album art in Didl", "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.", "LabelEnableAutomaticPortMap": "Enable automatic port mapping", - "LabelEnableAutomaticPortMapHelp": "Attempt to automatically map the public port to the local port via UPnP. This may not work with some router models. Changes will not apply until after a server restart.", + "LabelEnableAutomaticPortMapHelp": "Automatically forward public ports on your router to local ports on your server via UPnP. This may not work with some router models or network configurations. Changes will not apply until after a server restart.", "LabelEnableBlastAliveMessages": "Blast alive messages", "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval (seconds)", @@ -631,6 +650,8 @@ "LabelEnableDlnaServer": "Enable DLNA server", "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play content.", "LabelEnableHardwareDecodingFor": "Enable hardware decoding for:", + "LabelEnableHttps": "Enable HTTPS", + "LabelEnableHttpsHelp": "Enables the server to listen on the configured HTTPS port. A valid certificate must also be configured in order for this to take effect.", "LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately, on supported file systems.", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", @@ -640,7 +661,7 @@ "LabelEvent": "Event:", "LabelEveryXMinutes": "Every:", "LabelBaseUrl": "Base URL:", - "LabelBaseUrlHelp": "You can add a custom subdirectory here to access the server from a more unique URL.", + "LabelBaseUrlHelp": "Adds a custom subdirectory to the server URL. For example: http://example.com/<baseurl>", "LabelExtractChaptersDuringLibraryScan": "Extract chapter images during the library scan", "LabelExtractChaptersDuringLibraryScanHelp": "Generate chapter images when videos are imported during the library scan. Otherwise, they will be extracted during the chapter images scheduled task, allowing the regular library scan to complete faster.", "LabelFailed": "Failed", @@ -687,6 +708,8 @@ "LabelKodiMetadataUserHelp": "Save watch data to NFO files for other applications to utilize.", "LabelLanNetworks": "LAN networks:", "LabelLanguage": "Language:", + "LabelLibraryPageSize": "Library page size:", + "LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.", "LabelLineup": "Lineup:", "LabelLocalHttpServerPortNumber": "Local HTTP port number:", "LabelLocalHttpServerPortNumberHelp": "The TCP port number that Jellyfin's HTTP server should bind to.", @@ -735,6 +758,9 @@ "LabelMusicStreamingTranscodingBitrate": "Music transcoding bitrate:", "LabelMusicStreamingTranscodingBitrateHelp": "Specify a max bitrate when streaming music.", "LabelName": "Name:", + "LabelChromecastVersion": "Chromecast Version", + "LabelStable": "Stable", + "LabelNightly": "Nightly", "LabelNewName": "New name:", "LabelNewPassword": "New password:", "LabelNewPasswordConfirm": "New password confirm:", @@ -745,7 +771,7 @@ "LabelNumberOfGuideDays": "Number of days of guide data to download:", "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", "LabelOptionalNetworkPath": "(Optional) Shared network folder:", - "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly.", + "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly. For example, {0} or {1}.", "LabelOriginalAspectRatio": "Original aspect ratio:", "LabelOriginalTitle": "Original title:", "LabelOverview": "Overview:", @@ -794,15 +820,16 @@ "LabelReleaseDate": "Release date:", "LabelRemoteClientBitrateLimit": "Internet streaming bitrate limit (Mbps):", "LabelRemoteClientBitrateLimitHelp": "An optional per-stream bitrate limit for all out of network devices. This is useful to prevent devices from requesting a higher bitrate than your internet connection can handle. This may result in increased CPU load on your server in order to transcode videos on the fly to a lower bitrate.", + "LabelRequireHttps": "Require HTTPS", + "LabelRequireHttpsHelp": "If checked, the server will automatically redirect all requests over HTTP to HTTPS. This has no effect if the server is not listening on HTTPS.", "LabelRuntimeMinutes": "Run time (minutes):", "LabelSaveLocalMetadata": "Save artwork into media folders", "LabelSaveLocalMetadataHelp": "Saving artwork into media folders will put them in a place where they can be easily edited.", "LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.", "LabelScreensaver": "Screensaver:", - "EnableFastImageFadeIn": "Fast image fade-in", - "EnableFastImageFadeInHelp": "Enable faster fade-in animation for loaded images", + "EnableFasterAnimations": "Faster animations", + "EnableFasterAnimationsHelp": "Use faster animations and transitions", "LabelSeasonNumber": "Season number:", - "LabelSecureConnectionsMode": "Secure connection mode:", "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:", "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", "LabelSelectUsers": "Select users:", @@ -841,6 +868,18 @@ "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelSubtitles": "Subtitles", "LabelSupportedMediaTypes": "Supported Media Types:", + "LabelSyncPlayTimeOffset": "Time offset with the server:", + "MillisecondsUnit": "ms", + "LabelSyncPlayPlaybackDiff": "Playback time difference:", + "LabelSyncPlaySyncMethod": "Sync method:", + "LabelSyncPlayNewGroup": "New group", + "LabelSyncPlayNewGroupDescription": "Create a new group", + "LabelSyncPlayLeaveGroup": "Leave group", + "LabelSyncPlayLeaveGroupDescription": "Disable SyncPlay", + "LabelSyncPlayAccessCreateAndJoinGroups": "Allow user to create and join groups", + "LabelSyncPlayAccessJoinGroups": "Allow user to join groups", + "LabelSyncPlayAccessNone": "Disabled for this user", + "LabelSyncPlayAccess": "SyncPlay access", "LabelTVHomeScreen": "TV mode home screen:", "LabelTag": "Tag:", "LabelTagline": "Tagline:", @@ -969,7 +1008,6 @@ "MessageCreateAccountAt": "Create an account at {0}", "MessageDeleteTaskTrigger": "Are you sure you wish to delete this task trigger?", "MessageDirectoryPickerBSDInstruction": "For BSD, you may need to configure storage within your FreeNAS Jail in order to allow Jellyfin to access it.", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", "MessageDirectoryPickerLinuxInstruction": "For Linux on Arch Linux, CentOS, Debian, Fedora, openSUSE, or Ubuntu, you must grant the service user at least read access to your storage locations.", "MessageDownloadQueued": "Download queued.", "MessageEnablingOptionLongerScans": "Enabling this option may result in significantly longer library scans.", @@ -981,15 +1019,24 @@ "MessageInstallPluginFromApp": "This plugin must be installed from within the app you intend to use it in.", "MessageInvalidForgotPasswordPin": "An invalid or expired pin code was entered. Please try again.", "MessageInvalidUser": "Invalid username or password. Please try again.", + "MessageUnauthorizedUser": "You are not authorized to access the server at this time. Please contact your server administrator for more information.", "MessageItemSaved": "Item saved.", "MessageItemsAdded": "Items added.", "MessageLeaveEmptyToInherit": "Leave empty to inherit settings from a parent item or the global default value.", "MessageNoAvailablePlugins": "No available plugins.", + "MessageNoRepositories": "No repositories.", + "HeaderNewRepository": "New Repository", + "LabelRepositoryUrl": "Repository URL", + "LabelRepositoryUrlHelp": "The location of the repository manifest you want to include.", + "LabelRepositoryName": "Repository Name", + "LabelRepositoryNameHelp": "A custom name to distinguish this repository from any others added to your server.", + "MessageAddRepository": "If you wish to add a repository, click the button next to the header and fill out the requested information.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, and Albums. Click the + button to start creating collections.", + "MessageNoGenresAvailable": "Enable some metadata providers to pull genres from the internet.", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoPluginsInstalled": "You have no plugins installed.", "MessageNoServersAvailable": "No servers have been found using the automatic server discovery.", - "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.", + "MessageNoTrailersFound": "Install the trailers channel to enhance your movie experience by adding a library of internet trailers.", "MessageNothingHere": "Nothing here.", "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the pin codes that were used to perform the reset.", "MessagePlayAccessRestricted": "Playback of this content is currently restricted. Please contact your server administrator for more information.", @@ -1003,6 +1050,21 @@ "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please ensure it is running and try again.", "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.", "MessageYouHaveVersionInstalled": "You currently have version {0} installed.", + "MessageSyncPlayEnabled": "SyncPlay enabled.", + "MessageSyncPlayDisabled": "SyncPlay disabled.", + "MessageSyncPlayUserJoined": "{0} has joined the group.", + "MessageSyncPlayUserLeft": "{0} has left the group.", + "MessageSyncPlayGroupWait": "{0} is buffering…", + "MessageSyncPlayNoGroupsAvailable": "No groups available. Start playing something first.", + "MessageSyncPlayPlaybackPermissionRequired": "Playback permission required.", + "MessageSyncPlayGroupDoesNotExist": "Failed to join group because it does not exist.", + "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", + "MessageSyncPlayJoinGroupDenied": "Permission required to use SyncPlay.", + "MessageSyncPlayLibraryAccessDenied": "Access to this content is restricted.", + "MessageSyncPlayErrorAccessingGroups": "An error occurred while accessing groups list.", + "MessageSyncPlayErrorNoActivePlayer": "No active player found. SyncPlay has been disabled.", + "MessageSyncPlayErrorMissingSession": "Failed to enable SyncPlay! Missing session.", + "MessageSyncPlayErrorMedia": "Failed to enable SyncPlay! Media error.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", @@ -1016,6 +1078,7 @@ "MoveLeft": "Move left", "MoveRight": "Move right", "MovieLibraryHelp": "Review the {0}movie naming guide{1}.", + "Movie": "Movie", "Movies": "Movies", "MusicAlbum": "Music Album", "MusicArtist": "Music Artist", @@ -1039,16 +1102,16 @@ "NoNextUpItemsMessage": "None found. Start watching your shows!", "NoPluginConfigurationMessage": "This plugin has no settings to configure.", "NoSubtitleSearchResultsFound": "No results found.", - "NoSubtitles": "No subtitles", + "NoSubtitles": "None", "NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.", "None": "None", "Normal": "Normal", "NumLocationsValue": "{0} folders", "Off": "Off", "OneChannel": "One channel", - "OnlyForcedSubtitles": "Only forced subtitles", + "OnlyForcedSubtitles": "Only Forced", "OnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.", - "OnlyImageFormats": "Only image formats (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Only Image Formats (VOBSUB, PGS, SUB)", "Option3D": "3D", "OptionAdminUsers": "Administrators", "OptionAlbum": "Album", @@ -1152,7 +1215,7 @@ "OptionMissingEpisode": "Missing Episodes", "OptionMonday": "Monday", "OptionNameSort": "Name", - "OptionNew": "New...", + "OptionNew": "New…", "OptionNone": "None", "OptionOnAppStartup": "On application startup", "OptionOnInterval": "On an interval", @@ -1202,10 +1265,11 @@ "OptionWeekends": "Weekends", "OptionWeekly": "Weekly", "OriginalAirDateValue": "Original air date: {0}", + "OtherArtist": "Other Artist", "Overview": "Overview", - "PackageInstallCancelled": "{0} installation cancelled.", - "PackageInstallCompleted": "{0} installation completed.", - "PackageInstallFailed": "{0} installation failed.", + "PackageInstallCancelled": "{0} (version {1}) installation cancelled.", + "PackageInstallCompleted": "{0} (version {1}) installation completed.", + "PackageInstallFailed": "{0} (version {1}) installation failed.", "ParentalRating": "Parental rating", "PasswordMatchError": "Password and password confirmation must match.", "PasswordResetComplete": "The password has been reset.", @@ -1215,6 +1279,7 @@ "PasswordSaved": "Password saved.", "People": "People", "PerfectMatch": "Perfect match", + "Person": "Person", "Photos": "Photos", "PictureInPicture": "Picture in picture", "PinCodeResetComplete": "The pin code has been reset.", @@ -1227,7 +1292,7 @@ "PlayFromBeginning": "Play from beginning", "PlayNext": "Play next", "PlayNextEpisodeAutomatically": "Play next episode automatically", - "PlaybackErrorNoCompatibleStream": "There was an issue with the client profiling and the server isn't sending a compatible media format.", + "PlaybackErrorNoCompatibleStream": "This client isn't compatible with the media and the server isn't sending a compatible media format.", "Played": "Played", "Playlists": "Playlists", "PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the Add button.", @@ -1238,9 +1303,8 @@ "PluginInstalledMessage": "The plugin has been successfully installed. Jellyfin Server will need to be restarted for changes to take effect.", "PreferEmbeddedTitlesOverFileNames": "Prefer embedded titles over filenames", "PreferEmbeddedTitlesOverFileNamesHelp": "This determines the default display title when no internet metadata or local metadata is available.", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode informations from the embedded metadata if available.", - "PreferEmbeddedEpisodeInfosOverFileNames": "Prefer embedded episode informations over filenames", - "PreferredNotRequired": "Preferred, but not required", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode information from the embedded metadata if available.", + "PreferEmbeddedEpisodeInfosOverFileNames": "Prefer embedded episode information over filenames", "Premiere": "Premiere", "Premieres": "Premieres", "Previous": "Previous", @@ -1268,7 +1332,8 @@ "RefreshMetadata": "Refresh metadata", "RefreshQueued": "Refresh queued.", "ReleaseDate": "Release date", - "RememberMe": "Remember me", + "ReleaseGroup": "Release Group", + "RememberMe": "Remember Me", "RemoveFromCollection": "Remove from collection", "RemoveFromPlaylist": "Remove from playlist", "Repeat": "Repeat", @@ -1278,14 +1343,13 @@ "RepeatOne": "Repeat one", "ReplaceAllMetadata": "Replace all metadata", "ReplaceExistingImages": "Replace existing images", - "RequiredForAllRemoteConnections": "Required for all remote connections", - "RestartPleaseWaitMessage": "Please wait while Jellyfin Server shuts down and restarts. This may take a minute or two.", "ResumeAt": "Resume from {0}", "Rewind": "Rewind", "RunAtStartup": "Run at startup", "Runtime": "Runtime", "Saturday": "Saturday", "Save": "Save", + "SaveChanges": "Save changes", "SaveSubtitlesIntoMediaFolders": "Save subtitles into media folders", "SaveSubtitlesIntoMediaFoldersHelp": "Storing subtitles next to video files will allow them to be more easily managed.", "ScanForNewAndUpdatedFiles": "Scan for new and updated files", @@ -1298,6 +1362,7 @@ "SearchForMissingMetadata": "Search for missing metadata", "SearchForSubtitles": "Search for Subtitles", "SearchResults": "Search Results", + "Season": "Season", "SelectAdminUsername": "Please select a username for the admin account.", "SendMessage": "Send message", "Series": "Series", @@ -1316,10 +1381,14 @@ "Share": "Share", "ShowAdvancedSettings": "Show advanced settings", "ShowIndicatorsFor": "Show indicators for:", + "ShowLess": "Show less", + "ShowMore": "Show more", "ShowTitle": "Show title", "ShowYear": "Show year", "Shows": "Shows", "Shuffle": "Shuffle", + "New": "New", + "Filter": "Filter", "SimultaneousConnectionLimitHelp": "The maximum number of allowed simultaneous streams. Enter 0 for no limit.", "SkipEpisodesAlreadyInMyLibrary": "Don't record episodes that are already in my library", "SkipEpisodesAlreadyInMyLibraryHelp": "Episodes will be compared using season and episode numbers, when available.", @@ -1344,6 +1413,7 @@ "Suggestions": "Suggestions", "Sunday": "Sunday", "Sync": "Sync", + "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other devices.", "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", "TV": "TV", "TabAccess": "Access", @@ -1352,6 +1422,7 @@ "TabAlbums": "Albums", "TabArtists": "Artists", "TabCatalog": "Catalog", + "TabRepositories": "Repositories", "TabChannels": "Channels", "TabCodecs": "Codecs", "TabCollections": "Collections", @@ -1360,6 +1431,7 @@ "TabDevices": "Devices", "TabDirectPlay": "Direct Play", "TabDisplay": "Display", + "TabDVR": "DVR", "TabEpisodes": "Episodes", "TabFavorites": "Favorites", "TabGenres": "Genres", @@ -1413,6 +1485,7 @@ "TitleHardwareAcceleration": "Hardware Acceleration", "TitleHostingSettings": "Hosting Settings", "TitlePlayback": "Playback", + "Track": "Track", "TrackCount": "{0} tracks", "Trailers": "Trailers", "Transcoding": "Transcoding", @@ -1454,7 +1527,7 @@ "Vertical": "Vertical", "VideoRange": "Video range", "ViewAlbum": "View album", - "ViewArtist": "View artist", + "ViewAlbumArtist": "View album artist", "ViewPlaybackInfo": "View playback info", "Watched": "Watched", "Wednesday": "Wednesday", @@ -1462,12 +1535,35 @@ "Whitelist": "Whitelist", "WizardCompleted": "That's all we need for now. Jellyfin has begun collecting information about your media library. Check out some of our apps, and then click Finish to view the Dashboard.", "Writer": "Writer", + "Writers": "Writers", "XmlDocumentAttributeListHelp": "These attributes are applied to the root element of every XML response.", "XmlTvKidsCategoriesHelp": "Programs with these categories will be displayed as programs for children. Separate multiple with '|'.", "XmlTvMovieCategoriesHelp": "Programs with these categories will be displayed as movies. Separate multiple with '|'.", "XmlTvNewsCategoriesHelp": "Programs with these categories will be displayed as news programs. Separate multiple with '|'.", "XmlTvPathHelp": "A path to a XMLTV file. Jellyfin will read this file and periodically check it for updates. You are responsible for creating and updating the file.", "XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.", + "Yadif": "YADIF", + "YadifBob": "YADIF Bob", "Yes": "Yes", - "Yesterday": "Yesterday" + "Yesterday": "Yesterday", + "PathNotFound": "The path could not be found. Please ensure the path is valid and try again.", + "WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.", + "ListPaging": "{0}-{1} of {2}", + "PersonRole": "as {0}", + "LastSeen": "Last seen {0}", + "DailyAt": "Daily at {0}", + "WeeklyAt": "{0}s at {1}", + "OnWakeFromSleep": "On wake from sleep", + "EveryXMinutes": "Every {0} minutes", + "EveryHour": "Every hour", + "EveryXHours": "Every {0} hours", + "OnApplicationStartup": "On application startup", + "UnsupportedPlayback": "Jellyfin cannot decrypt content protected by DRM but all content will be attempted regardless, including protected titles. Some files may appear completely black due to encryption or other unsupported features, such as interactive titles.", + "EnableBlurhash": "Enable blurred placeholders for images", + "EnableBlurhashHelp": "Images that are still being loaded will be displayed with a blurred placeholder", + "ButtonSyncPlay": "SyncPlay", + "ButtonCast": "Cast", + "ButtonPlayer": "Player", + "StopPlayback": "Stop playback", + "ClearQueue": "Clear queue" } diff --git a/src/strings/eo.json b/src/strings/eo.json new file mode 100644 index 00000000000..8c7fe70c076 --- /dev/null +++ b/src/strings/eo.json @@ -0,0 +1,6 @@ +{ + "AddToCollection": "Aldoni al kolekto", + "Actor": "Aktoro", + "Absolute": "Absoluta", + "ButtonSyncPlay": "SyncPlay" +} diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index 6c7ed6f2d4a..d4030f13ff7 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -1,6 +1,6 @@ { "ButtonQuickStartGuide": "Guía de inicio rápido", - "ButtonSignOut": "Sign out", + "ButtonSignOut": "Cerrar sesión", "EnableHardwareEncoding": "Habilitar la codificación de hardware", "FolderTypeTvShows": "TV", "HeaderAddUser": "Agregar Usuario", @@ -8,13 +8,13 @@ "HeaderLiveTV": "TV en vivo", "HeaderSeries": "Series", "LabelAll": "Todo", - "LabelDisplayMissingEpisodesWithinSeasons": "Mostar capítulos no disponibles en temporadas", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar capítulos no disponibles en temporadas", "LabelFinish": "Terminar", "LabelNext": "Siguiente", "LabelPrevious": "Anterior", "LabelYourFirstName": "Su nombre:", "LabelYoureDone": "Ha terminado!", - "MoreUsersCanBeAddedLater": "Más usuarios se pueden agregar más tarde dentro del panel.", + "MoreUsersCanBeAddedLater": "Se pueden agregar más usuarios más tarde desde el tablero.", "NewCollectionNameExample": "Ejemplo: Colección de Star Wars", "OptionMissingEpisode": "Capítulos faltantes", "OptionUnairedEpisode": "Capítulos no emitidos", @@ -22,13 +22,13 @@ "TabEpisodes": "Capítulos", "TellUsAboutYourself": "Contanos acerca de vos", "ThisWizardWillGuideYou": "Este asistente le ayudará a guiarlo durante el proceso de configuración. Para comenzar, seleccione su idioma preferido.", - "UserProfilesIntro": "Jellyfin incluye soporte integrado para los perfiles de usuario, lo que permite a cada usuario tener su propia configuración de pantalla, estado de reproducción y controles parentales.", + "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con configuraciones de visualización granulares, estado de reproducción y controles parentales.", "ValueAudioCodec": "Códec de audio: {0}", "ValueCodec": "Códec: {0}", "ValueConditions": "Condiciones: {0}", "ValueVideoCodec": "Códec de video: {0}", "WelcomeToProject": "Bienvenidos a Jellyfin!", - "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre su biblioteca de medios. Dale un vistazo a algunas de nuestras aplicaciones y, a continuación, hacé clic en Finalizar para ver el Panel de control del servidor.", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recopilar información sobre su biblioteca de medios. Consulte algunas de nuestras aplicaciones y luego haga clic en Finalizar para ver el Tablero.", "Albums": "Álbumes", "Artists": "Artistas", "Books": "Libros", @@ -37,9 +37,9 @@ "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", - "HeaderAlbumArtists": "Artistas de álbumes", - "HeaderContinueWatching": "Continuar viendo", - "HeaderNextUp": "Continuar Viendo", + "HeaderAlbumArtists": "Artistas de álbum", + "HeaderContinueWatching": "Seguir viendo", + "HeaderNextUp": "Siguiente", "Movies": "Películas", "Photos": "Fotos", "Playlists": "Listas de reproducción", @@ -47,31 +47,31 @@ "Sync": "Sincronizar", "ValueSpecialEpisodeName": "Especial - {0}", "Absolute": "Absoluto", - "AccessRestrictedTryAgainLater": "El acceso está actualmente restringido. Por favor intente nuevamente mas tarde.", + "AccessRestrictedTryAgainLater": "El acceso está actualmente restringido. Por favor intente nuevamente más tarde.", "Actor": "Actor", "Add": "Agregar", - "AddItemToCollectionHelp": "Agregue items a las colecciones buscándolas y usando el click derecho o tocando el menu para añadirlas a una colección.", + "AddItemToCollectionHelp": "Agregue elementos a las colecciones buscándolos y usando el click derecho o tocando el menú para añadirlos a una colección.", "AddToCollection": "Añadir a la colección", - "AddToPlayQueue": "Añadir a la lista de reproducción", + "AddToPlayQueue": "Añadir a la cola de reproducción", "AddToPlaylist": "Añadir a la lista de reproducción", "AddedOnValue": "Añadidos {0}", - "AdditionalNotificationServices": "Navegue el catálogo de plugins para instalar servicios de notificación adicionales.", + "AdditionalNotificationServices": "Navegue el catálogo de complementos para instalar servicios de notificación adicionales.", "AirDate": "Fecha de emisión", "Aired": "Emitido", "Alerts": "Alertas", - "All": "Todo", + "All": "Todos", "AllChannels": "Todos los canales", - "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllEpisodes": "Todos los capítulos", "AllLanguages": "Todos los idiomas", "AllLibraries": "Todas las bibliotecas", "AllowMediaConversion": "Permitir conversión de medios", "AllowMediaConversionHelp": "Permitir o denegar acceso a la opción de convertir medios.", "AllowOnTheFlySubtitleExtraction": "Permitir extracción de subtítulos al vuelo", - "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los reproductores en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", - "AllowRemoteAccess": "Permitir conexiones remotas a este Servidor Jellyfin.", + "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados se pueden extraer de los videos y entregar a los clientes en texto plano, para ayudar a evitar la transcodificación de videos. En algunos sistemas, esto puede llevar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esto para que los subtítulos incrustados se graben con la transcodificación de video cuando el dispositivo cliente no los admite de forma nativa.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", "AllowRemoteAccessHelp": "Si no está tildado, todas las conexiones remotas serán bloqueadas.", - "AlwaysPlaySubtitles": "Siempre reproducir subtítulos", + "AlwaysPlaySubtitles": "Reproducir siempre", "AnyLanguage": "Cualquier idioma", "Anytime": "Cualquier fecha", "Ascending": "Ascendente", @@ -79,21 +79,16 @@ "Audio": "Audio", "Auto": "Auto", "AutoBasedOnLanguageSetting": "Auto (basado en configuración de idioma)", - "AutomaticallyConvertNewContent": "Convertir contenido nuevo automáticamente", "Backdrop": "Fondo", - "AllowHWTranscodingHelp": "Permite que el sintonizador transcodifique las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", - "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP o IP/máscara-de-red para redes a las que se les permitirá conectarse de forma remota. Si se deja vacía, todas las direcciones remotas serán permitidas.", + "AllowHWTranscodingHelp": "Permita que el sintonizador transcodifique transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP o IP/máscara de red para redes a las que se les permitirá conectarse de forma remota. Si se deja vacía, todas las direcciones remotas serán permitidas.", "AlwaysPlaySubtitlesHelp": "Los subtítulos que concuerden con la preferencia de idioma se cargarán independientemente del idioma del audio.", - "AndroidUnlockRestoreHelp": "Para recuperar tu compra anterior, por favor asegurate que iniciaste sesión en el dispositivo con la misma cuenta de Google (o Amazon) que hizo la compra originalmente. Asegurate de que la tienda de aplicaciones esté habilitada y no posea control parental alguno, y que tiene una conexión a Internet activa. Solo tendrás que hacer esto una sola vez para recuperar tu compra anterior.", - "AroundTime": "Alrededor de {0}", + "AroundTime": "Alrededor", "Art": "Arte", "AsManyAsPossible": "Tantos como sea posible", "AspectRatio": "Relación de aspecto", "AudioBitrateNotSupported": "Tasa de bits de audio no soportado", "AudioSampleRateNotSupported": "Frecuencia de muestreo de audio no soportada", - "AutomaticallyConvertNewContentHelp": "El contenido nuevo que se agregue a esta carpeta va a convertirse automáticamente.", - "AutomaticallySyncNewContent": "Descargar contenido nuevo automáticamente", - "AutomaticallySyncNewContentHelp": "El contenido nuevo agregado a esta carpeta va a ser descargado automáticamente al dispositivo.", "Backdrops": "Imágenes de fondo", "Banner": "Cartel", "BestFit": "Mejor ajuste", @@ -102,12 +97,12 @@ "BirthPlaceValue": "Lugar de nacimiento: {0}", "Blacklist": "Lista negra", "BobAndWeaveWithHelp": "Bob and weave (mayor calidad, pero más lento)", - "BookLibraryHelp": "Los libros de texto y audio libros son soportados. Revise la {0}Guía para Nomenclatura de Libros{1}.", + "BookLibraryHelp": "Se admiten libros de audio y texto. Revise la {0}guía de nombres de libros {1}.", "Box": "Caja", "BoxRear": "Caja (lado opuesto)", "Browse": "Explorar", "BrowsePluginCatalogMessage": "Explore nuestro catálogo de complementos para ver los complementos disponibles.", - "BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al convertir videos dependiendo del formato de los subtítulos. Evitar la grabación de subtítulos mejorará el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX, etc.) y ciertos subtítulos ASS/SSA.", + "BurnSubtitlesHelp": "Determina si el servidor debe grabarse en subtítulos al transcodificar videos. Evitar esto mejorará en gran medida el rendimiento. Seleccione Automático para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX, ...) y ciertos subtítulos ASS o SSA.", "ButtonAccept": "Aceptar", "ButtonAdd": "Agregar", "ButtonAddMediaLibrary": "Agregar biblioteca de medios", @@ -128,7 +123,6 @@ "ButtonConfigurePassword": "Configurar contraseña", "ButtonConfigurePinCode": "Configurar clave PIN", "ButtonConnect": "Conectar", - "ButtonConvertMedia": "Convertir medios", "ButtonCreate": "Crear", "ButtonDelete": "Eliminar", "ButtonDeleteImage": "Eliminar imagen", @@ -169,7 +163,6 @@ "ButtonParentalControl": "Control parental", "ButtonPause": "Pausar", "ButtonPlay": "Reproducir", - "ButtonPlayOneMinute": "Reproducir un minuto", "ButtonPlayTrailer": "Tráiler", "ButtonPlaylist": "Lista de reproducción", "ButtonPreferences": "Preferencias", @@ -178,10 +171,8 @@ "ButtonPrivacyPolicy": "Política de privacidad", "ButtonProfile": "Perfil", "ButtonProfileHelp": "Definir imagen de perfil y contraseña", - "ButtonPurchase": "Comprar", "ButtonQuality": "Calidad", "ButtonRecord": "Grabar", - "ButtonReenable": "Reactivar", "ButtonRefresh": "Actualizar", "ButtonRefreshGuideData": "Actualizar datos de la guía", "ButtonReject": "Rechazar", @@ -192,12 +183,11 @@ "ButtonRepeat": "Repetir", "ButtonReports": "Reportes", "ButtonReset": "Restablecer", - "ButtonResetEasyPassword": "Restablecer codigo PIN", + "ButtonResetEasyPassword": "Restablecer código PIN", "ButtonResetPassword": "Restablecer contraseña", "ButtonResetTuner": "Restablecer sintonizador", "ButtonRestart": "Reiniciar", "ButtonRestartNow": "Reiniciar ahora", - "ButtonRestorePreviousPurchase": "Recuperar compra", "ButtonResume": "Resumir", "ButtonRevoke": "Revocar", "ButtonSave": "Guardar", @@ -213,12 +203,11 @@ "ButtonSendInvitation": "Enviar invitación", "ButtonServer": "Servidor", "ButtonServerDashboard": "Panel del servidor", - "ButtonSettings": "Ajustes", + "ButtonSettings": "Configuraciones", "ButtonShare": "Compartir", "ButtonShuffle": "Aleatorio", "ButtonShutdown": "Apagar", "ButtonSignIn": "Iniciar sesión", - "ButtonSignUp": "Registrarse", "ButtonSkip": "Saltear", "ButtonSort": "Ordenar", "ButtonStart": "Iniciar", @@ -227,11 +216,9 @@ "ButtonSubmit": "Enviar", "ButtonSubtitles": "Subtítulos", "ButtonSync": "Sincronizar", - "ButtonTrailer": "Tráiler", + "ButtonTrailer": "Avance", "ButtonTryAgain": "Intentar de nuevo", "ButtonUninstall": "Desinstalar", - "ButtonUnlockPrice": "Desbloquear {0}", - "ButtonUnlockWithPurchase": "Desbloquear con compra", "ButtonUnmute": "Desilenciar", "ButtonUp": "Arriba", "ButtonUpdateNow": "Actualizar ahora", @@ -251,11 +238,10 @@ "CategorySystem": "Sistema", "CategoryUser": "Usuario", "ChangingMetadataImageSettingsNewContent": "Cambiar la configuración de descarga de metadatos o arte solo aplicará al contenido nuevo que se agregue a tu biblioteca. Para aplicar los cambios a los títulos existentes, tendrás que actualizar sus metadatos manualmente.", - "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales con el administrador de metadatos", + "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales con el administrador de metadatos.", "ChannelNameOnly": "Sólo canal {0}", "ChannelNumber": "Número del canal", - "CinemaModeConfigurationHelp": "El modo cine trae la experiencia del cine directamente a tu living, con la posibilidad de reproducir tráilers e introducciones personalizadas antes de la función principal.", - "CinemaModeConfigurationHelp2": "Las aplicaciones de Jellyfin tienen la opcion de habilitar o deshabilitar el modo cine. Las aplicaciones de TV activan el modo cine de forma predeterminada.", + "CinemaModeConfigurationHelp": "El modo cine trae la experiencia del cine directamente a tu living, con la posibilidad de reproducir avances e introducciones personalizadas antes de la función principal.", "CinemaModeFeatureDescription": "El modo cine te da la verdadera experiencia del cine con tráilers e introducciones personalizadas antes de la función principal.", "CloudSyncFeatureDescription": "Sincroniza tus medios a la nube para respaldos, archivados y conversiones más fáciles.", "ColorPrimaries": "Colores primarios", @@ -265,8 +251,8 @@ "Composer": "Compositor", "ConfigureDateAdded": "Configura cómo se va a determinar las fechas de adición en la pestaña Bibliotecas del panel del servidor Jellyfin", "ConfirmDeleteImage": "¿Eliminar imagen?", - "ConfirmDeleteItem": "Eliminar este elemento lo eliminara tanto del sistema de archivos como de la biblioteca de medios. ¿Está seguro que desea continuar?", - "ConfirmDeleteItems": "Eliminar estos elementos los eliminara tanto del sistema de archivos como de la biblioteca de medios. ¿Está seguro que desea continuar?", + "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema de archivos como de la biblioteca de medios. ¿Está seguro que desea continuar?", + "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema de archivos como de la biblioteca de medios. ¿Está seguro que desea continuar?", "ConfirmDeletion": "Confirmar eliminación", "ConfirmEndPlayerSession": "¿Desea apagar Jellyfin en {0}?", "ConfirmRemoveDownload": "¿Quitar descarga?", @@ -286,20 +272,20 @@ "CoverArt": "Arte de portada", "CustomDlnaProfilesHelp": "Crear un perfil personalizado para apuntar un nuevo dispositivo o sobre-escribir el perfil del sistema.", "DateAdded": "Fecha agregada", - "DatePlayed": "Fecha reproducido", + "DatePlayed": "Fecha de reproducción", "Days": "Dias", "DeathDateValue": "Muerte: {0}", "Default": "Predeterminado", "DefaultCameraUploadPathHelp": "Selecciona un directorio personalizado. Si queda en blanco, una carpeta predeterminada va a ser usada. Si es una carpeta personalizada, esta va a necesitar ser agregada en la biblioteca, en la configuración de bibliotecas de Jellyfin.", "DefaultErrorMessage": "Hubo un error procesando la solicitud. Por favor intentalo nuevamente mas tarde.", - "DefaultMetadataLangaugeDescription": "Estos son tus predeterminados y pueden ser personalizados por librería unicamente.", + "DefaultMetadataLangaugeDescription": "Estos son tus predeterminados y pueden ser personalizados por librería únicamente.", "Delete": "Borrar", "DeleteDeviceConfirmation": "Estas seguro que quieres borrar este dispositivo? Este va a volver a aparecer cuando una persona se registre.", "DeleteImage": "Borrar Imagen", - "DeleteImageConfirmation": "Estas seguro que quieres borrar esta imagen?", + "DeleteImageConfirmation": "Estás seguro que quieres borrar esta imagen?", "DeleteMedia": "Borrar medio", - "DeleteUser": "Borrar Usuario", - "DeleteUserConfirmation": "Estas seguro que quieres borrar este usuario?", + "DeleteUser": "Borrar usuario", + "DeleteUserConfirmation": "Estás seguro que quieres borrar este usuario?", "Depressed": "Deprimido", "Descending": "Descendiente", "Desktop": "Escritorio", @@ -308,20 +294,20 @@ "DeviceLastUsedByUserName": "Usado ultima vez por {0}", "DirectPlayError": "Error en la reproducción directa", "DirectPlaying": "Reproducción directa", - "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video sera reempaquetado en el acto antes de transmitirlo al dispositivo.", - "DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco procesamiento, esto sin perdida en la calidad de video.", - "DirectStreaming": "Transmisión en directo", + "DirectStreamHelp1": "El medio es compatible con el dispositivo con respecto a la resolución y el tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivos incompatible (mkv, avi, wmv, etc.). El video se volverá a empaquetar sobre la marcha antes de transmitirlo al dispositivo.", + "DirectStreamHelp2": "La transmisión directa de un archivo utiliza muy poca potencia de procesamiento sin pérdida de calidad de video.", + "DirectStreaming": "Transmisión directa", "Director": "Director", "Directors": "Directores", "Disabled": "Deshabilitado", "Disc": "Disco", "Disconnect": "Desconectar", "Display": "Pantalla", - "DisplayInMyMedia": "Mostrar en pantalla principal", + "DisplayInMyMedia": "Mostrar en la pantalla de inicio", "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla principal, como últimos medios y continuar viendo", - "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes entre temporadas", + "DisplayMissingEpisodesWithinSeasons": "Mostrar capítulos faltantes entre temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", - "DisplayModeHelp": "Selecciona el tipo de pantalla en el cual estas usando Jellyfin.", + "DisplayModeHelp": "Seleccione el estilo de diseño que desea en la Interfaz.", "DoNotRecord": "No grabar", "Down": "Abajo", "Download": "Descargar", @@ -332,52 +318,52 @@ "Downloads": "Descargas", "DownloadsValue": "{0} descargas", "DrmChannelsNotImported": "Canales con DRM no van a ser importados.", - "DropShadow": "Sombra paralela", + "DropShadow": "Dejar sombra", "DvrFeatureDescription": "Agendar grabaciones individuales de TV en vivo, grabaciones de series y mucho mas con Jellyfin DVR.", "DvrSubscriptionRequired": "Jellyfin DVR requiere una suscripción a Jellyfin Premiere.", - "EasyPasswordHelp": "Tu numero de PIN fácil puede ser usado por las aplicaciones de Jellyfin soportadas, también pueden ser utilizadas para acceder fácilmente al ingresar dentro de tu red.", + "EasyPasswordHelp": "Su código PIN fácil se usa para acceder sin conexión a clientes compatibles y también se puede usar para iniciar sesión fácilmente en la red.", "Edit": "Editar", "EditImages": "Editar imagenes", "EditMetadata": "Editar metadata", "EditSubtitles": "Editar subtitulos", - "EnableBackdrops": "Habilitar fondo", - "EnableBackdropsHelp": "Si esta habilitado, los fondos van a ser mostrados en segundo plano de algunas paginas mientras estas en la biblioteca.", - "EnableCinemaMode": "Habilitar modo Cine", - "EnableColorCodedBackgrounds": "Habilitar colores en el fondo del codigo", + "EnableBackdrops": "Fondos", + "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", + "EnableCinemaMode": "Modo cine", + "EnableColorCodedBackgrounds": "Habilitar colores en el fondo del código", "AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", - "CriticRating": "Calificación de la crítica", - "DefaultSubtitlesHelp": "Los subtítulos se cargan en base a los indicadores por defecto y los indicadores forzados en los metadatos embebidos. Las preferencias de idioma son consideradas cuando existe más de una opción.", + "CriticRating": "Valoración crítica", + "DefaultSubtitlesHelp": "Los subtítulos se cargan según los indicadores predeterminados y forzados en los metadatos incrustados. Las preferencias de idioma se consideran cuando hay varias opciones disponibles.", "Dislike": "No me gusta", "EnableDebugLoggingHelp": "El registro de depuración debería activarse solo a fin de solucionar problemas. El incremento en el acceso al sistema de archivos podría prevenir que el servidor entre en modo de suspensión en algunos entornos.", "EnableDisplayMirroring": "Habilitar duplicación de la pantalla", - "EnableExternalVideoPlayers": "Habilitar reproductores de vídeo externos", - "EnableExternalVideoPlayersHelp": "Se mostrará un menú de reproductor externo al iniciar la reproducción de video", - "EnableNextVideoInfoOverlay": "Habilitar vista de información del siguiente vídeo durante la reproducción", - "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente vídeo en la lista de reproducción", - "EnablePhotos": "Habilitar fotos", - "EnablePhotosHelp": "Las fotos serán detectadas y se mostrarán junto a otros archivos de medios", + "EnableExternalVideoPlayers": "Habilitar reproductores de video externos", + "EnableExternalVideoPlayersHelp": "Se mostrará un menú de reproductor externo al iniciar la reproducción de video.", + "EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción", + "EnableNextVideoInfoOverlayHelp": "Al final de un video, muestra información sobre el próximo video que aparece en la lista de reproducción actual.", + "EnablePhotos": "Mostrar fotografías", + "EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.", "EnableStreamLooping": "Repetir automáticamente transmisiones en vivo", "EnableStreamLoopingHelp": "Habilita esto sí las transmisiones en vivo sólo contienen unos cuantos segundos y es necesario solicitarlos continuamente. Habilitar esto cuando no es necesario puede causar problemas.", - "EnableThemeSongs": "Habilitar canciones tema", - "EnableThemeSongsHelp": "Al habilitarse, las canciones tema se reproducirán de fondo cuando navegas por la biblioteca.", - "EnableThemeVideos": "Habilitar videos de tema", - "EnableThemeVideosHelp": "Al habilitarse, los videos de tema se reproducirán de fondo mientras navegues por la biblioteca.", + "EnableThemeSongs": "Canciones temáticas", + "EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.", + "EnableThemeVideos": "Videos temáticos", + "EnableThemeVideosHelp": "Al habilitarse, los vídeos de tema se reproducirán de fondo mientras navegues por la biblioteca.", "Ended": "Finalizado", "EndsAtValue": "Termina en {0}", - "Episodes": "Episodios", + "Episodes": "Capítulos", "ErrorAddingListingsToSchedulesDirect": "Ocurrió un error al añadir el alineamiento a tu cuenta de Schedules Direct. Schedules Direct solo permite una cantidad limitada de alineamientos por cuenta. Quizás necesites ingresar al sitio de Schedules Direct y eliminar otros alineamientos de tu cuenta antes de continuar.", "ErrorAddingMediaPathToVirtualFolder": "Ocurrió un error al agregar la ruta de medios. Por favor, asegurate que la ruta es válida y que el proceso que sirve Jellyfin tiene acceso a esa ubicación.", "ErrorAddingTunerDevice": "Ocurrió un error al añadir el dispositivo sintonizador. Por favor asegurate que está disponible e intenta de nuevo.", - "ErrorAddingXmlTvFile": "Ocurrió un error al acceder al archivo de XmlTV. Por favor asegurate de que el archivo existe e intenta de nuevo.", + "ErrorAddingXmlTvFile": "Ocurrió un error al acceder al archivo de XmlTV. Por favor asegúrate de que el archivo existe e intenta de nuevo.", "ErrorDeletingItem": "Ocurrió un error al eliminar el ítem del servidor Jellyfin. Por favor verifica que el servidor Jellyfin tiene permiso de escritura a la carpeta de medios e intenta de nuevo.", - "ErrorGettingTvLineups": "Ocurrió un error al descargar los alineamientos de TV. Por favor asegúrate que tu información es correcta e intenta de nuevo.", + "ErrorGettingTvLineups": "Ocurrió un error al descargar la guía de programación de TV. Por favor asegúrate que tu información es correcta e intenta de nuevo.", "ErrorMessageStartHourGreaterThanEnd": "La hora de fin tiene que ser mayor que la de inicio.", "ErrorPleaseSelectLineup": "Por favor selecciona un alineamiento e intenta de nuevo. Si no existen alineamientos disponibles, asegúrate de que tu nombre de usuario, contraseña y código postal son correctos.", "ErrorSavingTvProvider": "Ocurrió un error al guardar el proveedor de TV. Por favor asegúrate de que está disponible e intenta de nuevo.", "EveryNDays": "Cada {0} días", "ExitFullscreen": "Salir de pantalla completa", "ExtraLarge": "Extra grande", - "ExtractChapterImagesHelp": "Extraer imágenes de los capítulos permitirá a las aplicaciones de Jellyfin mostrar menúes gráficos de selección de escena. El proceso puede ser lento, ocupar mucho tiempo de cpu y puede ocupar varios gigabytes de almacenamiento. El proceso corre cuando los videos son descubiertos, y también como un proceso periódico por la noche. El horario del proceso periódico se puede configurar en el área de procesos periódicos. No se recomienda correr este proceso durante horas de alto uso.", + "ExtractChapterImagesHelp": "Extraer imágenes de los capítulos permitirá a las aplicaciones de Jellyfin mostrar menús gráficos de selección de escena. El proceso puede ser lento, ocupar mucho tiempo de cpu y puede ocupar varios gigabytes de almacenamiento. El proceso corre cuando los vídeos son descubiertos, y también como un proceso periódico por la noche. El horario del proceso periódico se puede configurar en el área de procesos periódicos. No se recomienda correr este proceso durante horas de alto uso.", "Extras": "Extras", "FFmpegSavePathNotFound": "No pudimos localizar FFmpeg usando la ruta que has ingresado. FFprobe también es necesario y debe existir en la misma carpeta. Éstos componentes normalmente están incluidos en la misma descarga. Por favor, revisa la ruta e intenta de nuevo.", "FastForward": "Avanzar Rápido", @@ -391,8 +377,8 @@ "FolderTypeBooks": "Libros", "FolderTypeMovies": "Películas", "FolderTypeMusic": "Música", - "FolderTypeMusicVideos": "Videos Musicales", - "FolderTypeUnset": "Sin especificar (Contenido Mixto)", + "FolderTypeMusicVideos": "Vídeos Musicales", + "FolderTypeUnset": "Contenido Mixto", "FormatValue": "Formato: {0}", "Friday": "Viernes", "Fullscreen": "Pantalla Completa", @@ -406,16 +392,1266 @@ "GuideProviderSelectListings": "Seleccionar Listados", "HeaderFavoriteSongs": "Canciones favoritas", "HeaderFavoriteShows": "Programas favoritos", - "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteEpisodes": "Capítulos favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos", - "Shows": "Series", + "Shows": "Programas", "CopyStreamURLError": "Hubo un error copiando la URL.", "CopyStreamURLSuccess": "URL copiada con éxito.", - "CopyStreamURL": "Copiar la URL de la transmisión", + "CopyStreamURL": "Copiar URL de transmisión", "ButtonSplit": "Dividir", "ButtonAddImage": "Agregar imagen", - "AskAdminToCreateLibrary": "Pregunte al administrador para crear una biblioteca.", + "AskAdminToCreateLibrary": "Preguntar al administrador para crear una biblioteca.", "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o conversión avanza demasiado con respecto a la posición actual de la reproducción, se pausara el proceso para consumir menos recursos. esto es mas útil cuando no se hacen búsquedas de tiempo a menudo. Desactive esta opción si experimenta problemas en la reproducción.", - "AllowFfmpegThrottling": "Transcodificación Throttle" + "AllowFfmpegThrottling": "Transcodificación Throttle", + "HeaderCancelRecording": "Cancelar Grabación", + "HeaderBranding": "Marca", + "HeaderBooks": "Libros", + "HeaderBlockItemsWithNoRating": "Bloquear elementos con rating de información vacía o no reconocible:", + "HeaderAutomaticUpdates": "Actualizaciones Automáticas", + "HeaderAudioSettings": "Configuraciones de audio", + "HeaderAudioBooks": "Audiolibros", + "HeaderAppearsOn": "Aparece en", + "HeaderApp": "Aplicación", + "HeaderApiKeysHelp": "Se requiere que las aplicaciones externas tengan una clave de API para comunicarse con el servidor Jellyfin. Las claves se emiten iniciando sesión con una cuenta Jellyfin, u otorgando manualmente una clave a la aplicación.", + "HeaderApiKeys": "Claves de API", + "HeaderApiKey": "Contraseña API", + "HeaderAllowMediaDeletionFrom": "Permitir el borrado de medios desde", + "HeaderAlert": "Alerta", + "HeaderAlbums": "Albumes", + "HeaderAdmin": "Admin", + "HeaderAdditionalParts": "Partes adicionales", + "HeaderAddUpdateImage": "Agregar/Actualizar imagen", + "HeaderAddToPlaylist": "Agregar a la lista de reproducción", + "HeaderAddToCollection": "Agregar a la Colección", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderActivity": "Actividad", + "HeaderActiveRecordings": "Grabaciones activas", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderAccessScheduleHelp": "Cree un horario de acceso para limitar el acceso a ciertas horas.", + "HeaderAccessSchedule": "Horario de acceso", + "HardwareAccelerationWarning": "Habilitar la aceleración de hardware puede causar inestabilidad en algunos entornos. Asegúrese de que su sistema operativo y los controladores de vídeo estén completamente actualizados. Si tiene dificultades para reproducir el vídeo después de habilitarlo, deberá volver a cambiar la configuración a \"Nada\".", + "HandledByProxy": "Manejado por un proxy reverso", + "HDPrograms": "Programas en HD", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar el desempeño, o elige un valor más lento para mejorar la calidad.", + "FetchingData": "Obteniendo información adicional", + "Episode": "Capítulo", + "Yesterday": "Ayer", + "ClientSettings": "Configuración de cliente", + "BoxSet": "Colección", + "Artist": "Artista", + "AlbumArtist": "Artista del álbum", + "Album": "Álbum", + "HeaderDateIssued": "Fecha de Emisión", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderContinueListening": "Seguir escuchando", + "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo cuando se reproducen formatos específicos. Si se aplica una limitación, los medios se transcodificarán, incluso si el formato está configurado para reproducción directa.", + "HeaderContainerProfile": "Perfil de contenedor", + "HeaderConnectionFailure": "Conexión fallida", + "HeaderConnectToServer": "Conectar al servidor", + "HeaderConfirmRevokeApiKey": "Revocar llave de API", + "HeaderConfirmProfileDeletion": "Confirmar borrado de perfil", + "HeaderConfirmPluginInstallation": "Confirmar instalación del complemento", + "HeaderConfigureRemoteAccess": "Configurar acceso remoto", + "HeaderCodecProfileHelp": "Los perfiles de códec indican las limitaciones de un dispositivo cuando se reproducen códecs específicos. Si se aplica una limitación, los medios se transcodificarán, incluso si el códec está configurado para reproducción directa.", + "HeaderCodecProfile": "Perfil del códec", + "HeaderChapterImages": "Imágenes del capitulo", + "HeaderChannels": "Canales", + "HeaderChannelAccess": "Acceso al canal", + "HeaderCastCrew": "Reparto", + "HeaderCastAndCrew": "Reparto", + "HeaderCancelSeries": "Cancelar serie", + "H264CrfHelp": "El Factor de velocidad constante (CRF) es la configuración de calidad predeterminada para el codificador x264. Puede establecer los valores entre 0 y 51, donde valores más bajos resultarían en una mejor calidad (a expensas de tamaños de archivo más altos). Los valores correctos están entre 18 y 28. El valor predeterminado para x264 es 23, por lo que puede usar esto como punto de partida.", + "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", + "HeaderFavoriteVideos": "Videos favoritos", + "HeaderFavoritePeople": "Gente favorita", + "HeaderFavoriteMovies": "Películas Favoritas", + "HeaderFavoriteBooks": "Libros favoritos", + "HeaderExternalIds": "IDs externos:", + "HeaderError": "Error", + "HeaderEpisodes": "Capítulos", + "HeaderEnabledFields": "Campos habilitados", + "HeaderEditImages": "Editar imágenes", + "HeaderEasyPinCode": "Código PIN fácil", + "HeaderDownloadSync": "Descargar y sincronizar", + "HeaderDisplay": "Pantalla", + "HeaderDirectPlayProfile": "Perfil Direct Play", + "HeaderDevices": "Dispositivos", + "HeaderDeviceAccess": "Acceso al dispositivo", + "HeaderDeveloperInfo": "Información para desarrolladores", + "HeaderDetectMyDevices": "Detectar mis dispositivos", + "HeaderDeleteTaskTrigger": "Elminar disparador de acciones", + "HeaderDeleteProvider": "Eliminar proveedor", + "HeaderDeleteItems": "Eliminar ítems", + "HeaderDeleteItem": "Eliminar ítem", + "HeaderDeleteDevice": "Eliminar dispositivo", + "HeaderDefaultRecordingSettings": "Ajustes de grabación por defecto", + "UnsupportedPlayback": "Jellyfin no puede descifrar contenido protegido por DRM pero a pesar de esto lo intentará con todo el contenido, incluyendo títulos protegidos. Algunos archivos pueden aparecer completamente en negro por estar cifrados o por otras características no soportadas, como títulos interactivos.", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderMedia": "Medios", + "HeaderLoginFailure": "Error al iniciar sesión", + "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV en vivo", + "HeaderLiveTv": "TV en vivo", + "HeaderLibrarySettings": "Configuraciones de biblioteca", + "HeaderLibraryOrder": "Orden de biblioteca", + "HeaderLibraryFolders": "Carpetas de biblioteca", + "HeaderLibraryAccess": "Acceso a la biblioteca", + "HeaderLibraries": "Bibliotecas", + "HeaderLatestRecordings": "Grabaciones recientes", + "HeaderLatestMusic": "Música reciente", + "HeaderLatestMovies": "Películas recientes", + "HeaderLatestMedia": "Medios recientes", + "HeaderKeepSeries": "Mantener serie", + "HeaderKeepRecording": "Mantener grabación", + "HeaderItems": "Artículos", + "HeaderInstantMix": "Mezcla instantánea", + "HeaderInstall": "Instalar", + "HeaderImageSettings": "Configuraciones de imagen", + "HeaderImageOptions": "Opciones de imagen", + "HeaderIdentifyItemHelp": "Ingrese uno o más criterios de búsqueda. Eliminar criterios para aumentar los resultados de búsqueda.", + "HeaderIdentificationHeader": "Encabezado de identificación", + "HeaderIdentificationCriteriaHelp": "Ingrese al menos un criterio de identificación.", + "HeaderIdentification": "Identificación", + "HeaderHttpHeaders": "Encabezados HTTP", + "HeaderHome": "Inicio", + "HeaderGuideProviders": "Proveedores de datos de guías de TV", + "HeaderGenres": "Géneros", + "HeaderFrequentlyPlayed": "Reproducido con frecuencia", + "HeaderForgotPassword": "Olvidé la contraseña", + "HeaderForKids": "Para niños", + "HeaderFilters": "Filtros", + "HeaderFetcherSettings": "Configuración del recuperador", + "HeaderFetchImages": "Obtener imágenes:", + "HeaderFeatures": "Caracteristicas", + "HeaderFeatureAccess": "Acceso a características", + "HeaderFavoritePlaylists": "Listas de reproducción favoritas", + "ButtonTogglePlaylist": "Lista de reproducción", + "ButtonToggleContextMenu": "Más", + "HeaderPlaybackError": "Error de reproducción", + "HeaderPlayback": "Reproducción de medios", + "HeaderPlayOn": "Reproducir en", + "HeaderPlayAll": "Reproducir todo", + "HeaderPinCodeReset": "Restablecer código PIN", + "HeaderPhotoAlbums": "Álbumes de fotos", + "HeaderPeople": "Personas", + "HeaderPendingInvitations": "Invitaciones pendientes", + "HeaderPaths": "Rutas", + "HeaderPasswordReset": "Restablecer contraseña", + "HeaderPassword": "Contraseña", + "HeaderParentalRatings": "Calificaciones parentales", + "HeaderOtherItems": "Otros elementos", + "HeaderOnNow": "Ahora", + "HeaderNewDevices": "Nuevos dispositivos", + "HeaderNewApiKey": "Nueva clave de API", + "HeaderNavigation": "Navegación", + "HeaderMyMediaSmall": "Mis medios (pequeño)", + "HeaderMyMedia": "Mis medios", + "HeaderMyDevice": "Mi dispositivo", + "HeaderMusicVideos": "Videos musicales", + "HeaderMusicQuality": "Calidad de música", + "HeaderMovies": "Películas", + "LabelAccessDay": "Día de la semana:", + "LabelAbortedByServerShutdown": "(Abortado por el apagado del servidor)", + "Label3DFormat": "Formato 3D:", + "Kids": "Niños", + "Items": "Artículos", + "ItemCount": "{0} artículos", + "InstantMix": "Mezcla instantánea", + "InstallingPackage": "Instalando {0} (versión {1})", + "ImportFavoriteChannelsHelp": "Si está habilitado, solo se importarán los canales que estén marcados como favoritos en el dispositivo sintonizador.", + "Images": "Imágenes", + "Identify": "Identificar", + "HttpsRequiresCert": "Para habilitar conexiones seguras, deberá proporcionar un certificado SSL confiable, como Let's Encrypt. Proporcione un certificado o desactive las conexiones seguras.", + "Horizontal": "Horizontal", + "Home": "Inicio", + "HideWatchedContentFromLatestMedia": "Ocultar contenido visto de los últimos medios", + "Hide": "Ocultar", + "Help": "Ayuda", + "HeadersFolders": "Carpetas", + "HeaderYears": "Años", + "HeaderXmlSettings": "Configuraciones Xml", + "HeaderXmlDocumentAttributes": "Atributos del documento Xml", + "HeaderXmlDocumentAttribute": "Atributo de documento Xml", + "HeaderVideos": "Videos", + "HeaderVideoTypes": "Tipos de video", + "HeaderVideoType": "Tipo de video", + "HeaderVideoQuality": "Calidad de video", + "HeaderUsers": "Usuarios", + "HeaderUser": "Usuario", + "HeaderUploadImage": "Subir imagen", + "HeaderUpcomingOnTV": "Próximamente en TV", + "HeaderTypeText": "Ingrese texto", + "HeaderTuners": "Sintonizadores", + "HeaderTunerDevices": "Dispositivos sintonizadores", + "HeaderTranscodingProfileHelp": "Agregue perfiles de transcodificación para indicar qué formatos deben usarse cuando se requiere transcodificación.", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTracks": "Pistas", + "HeaderThisUserIsCurrentlyDisabled": "Este usuario está actualmente deshabilitado", + "HeaderTaskTriggers": "Desencadenantes de tareas", + "HeaderTags": "Etiquetas", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", + "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulos describen los formatos de subtítulos compatibles con el dispositivo.", + "HeaderSubtitleProfiles": "Perfiles de subtítulos", + "HeaderSubtitleProfile": "Perfil de subtítulos", + "HeaderSubtitleDownloads": "Descargas de subtítulos", + "HeaderSubtitleAppearance": "Apariencia de subtítulos", + "HeaderStopRecording": "Detener grabación", + "HeaderStatus": "Estado", + "HeaderStartNow": "Empezar ahora", + "HeaderSpecialFeatures": "Características especiales", + "HeaderSpecialEpisodeInfo": "Información especial del capítulo", + "HeaderSortOrder": "Orden de clasificación", + "HeaderSortBy": "Ordenar por", + "HeaderShutdown": "Apagar", + "HeaderSetupLibrary": "Configura tus bibliotecas de medios", + "HeaderSettings": "Configuraciones", + "HeaderServerSettings": "Configuración del servidor", + "HeaderServerAddressSettings": "Configuración de la dirección del servidor", + "HeaderSeriesStatus": "Estado de la serie", + "HeaderSeriesOptions": "Opciones de serie", + "HeaderSendMessage": "Enviar mensaje", + "HeaderSelectTranscodingPathHelp": "Examine o ingrese la ruta a utilizar para transcodificar archivos temporales. La carpeta debe ser grabable.", + "HeaderSelectTranscodingPath": "Seleccionar ruta temporal de transcodificación", + "HeaderSelectServerCachePathHelp": "Examine o ingrese la ruta a utilizar para los archivos de caché del servidor. La carpeta debe ser grabable.", + "HeaderSelectServerCachePath": "Seleccionar ruta de caché del servidor", + "HeaderSelectServer": "Seleccionar servidor", + "HeaderSelectPath": "Seleccionar ruta", + "HeaderSelectMetadataPathHelp": "Examine o ingrese la ruta en la que desea almacenar metadatos. La carpeta debe ser grabable.", + "HeaderSelectMetadataPath": "Seleccionar ruta de metadatos", + "HeaderSelectCertificatePath": "Seleccionar ruta del certificado", + "HeaderSecondsValue": "{0} segundos", + "HeaderSeasons": "Temporadas", + "HeaderSchedule": "Programación", + "HeaderScenes": "Escenas", + "HeaderRunningTasks": "Ejecución de tareas", + "HeaderRevisionHistory": "Revisión histórica", + "HeaderRestart": "Reiniciar", + "HeaderResponseProfile": "Perfil de respuesta", + "HeaderRemoveMediaLocation": "Eliminar ubicación de medios", + "HeaderRemoveMediaFolder": "Eliminar carpeta de medios", + "HeaderRemoteControl": "Control remoto", + "HeaderRemoteAccessSettings": "Configuración de acceso remoto", + "HeaderRecordingPostProcessing": "Grabación posterior al procesamiento", + "HeaderRecordingOptions": "Opciones de grabación", + "HeaderRecentlyPlayed": "Recientemente reproducido", + "HeaderProfileServerSettingsHelp": "Estos valores controlan cómo el servidor Jellyfin se presentará al dispositivo.", + "HeaderProfileInformation": "Información del perfil", + "HeaderProfile": "Perfil", + "HeaderPreferredMetadataLanguage": "Lenguaje de metadatos preferido", + "HeaderPluginInstallation": "Instalación de complementos", + "HeaderPleaseSignIn": "Por favor, inicie sesión", + "HeaderNextVideoPlayingInValue": "Reproducción del siguiente video en {0}", + "HeaderNextEpisodePlayingInValue": "Reproducción del siguiente capítulo en {0}", + "HeaderMoreLikeThis": "Más como esto", + "HeaderMetadataSettings": "Configuraciones de metadatos", + "HeaderMediaInfo": "Información de medios", + "HeaderHttpsSettings": "Configuraciones HTTPS", + "HeaderEnabledFieldsHelp": "Desmarque un campo para bloquearlo y evitar que se modifiquen sus datos.", + "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", + "ApiKeysCaption": "Lista de las claves de API habilitadas actualmente", + "LabelArtists": "Artistas:", + "LabelAppName": "Nombre de la aplicación", + "LabelAllowedRemoteAddressesMode": "Modo de filtro de dirección IP remota:", + "LabelAlbum": "Álbum:", + "LabelAirTime": "Tiempo al aire:", + "LabelAirDays": "Días al aire:", + "LabelAccessStart": "Hora de inicio:", + "LabelAccessEnd": "Hora de finalización:", + "HeaderDVR": "DVR", + "LabelBlockContentWithTags": "Bloquear elementos con etiquetas:", + "LabelBlastMessageIntervalHelp": "Determina la duración en segundos entre los mensajes explosivos vivos.", + "LabelBlastMessageInterval": "Intervalo de mensaje vivo (segundos)", + "LabelBitrate": "Tasa de bits:", + "LabelBirthYear": "Año de nacimiento:", + "LabelBirthDate": "Fecha de nacimiento:", + "LabelBindToLocalNetworkAddressHelp": "Opcional. Anule la dirección IP local para vincular el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddress": "Enlace a la dirección de red local:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde internet:", + "LabelAuthProvider": "Proveedor de autenticación:", + "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", + "LabelAudioLanguagePreference": "Idioma de audio preferido:", + "LabelAudioCodec": "Códec de audio:", + "LabelAudioChannels": "Canales de audio:", + "LabelAudioBitrate": "Velocidad de bits de audio:", + "LabelAudio": "Audio", + "LabelAllowedRemoteAddresses": "Filtro de dirección IP remota:", + "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante los períodos de inactividad cuando no haya usuarios activos.", + "LabelAllowServerAutoRestart": "Permita que el servidor se reinicie automáticamente para aplicar actualizaciones", + "LabelAllowHWTranscoding": "Permitir transcodificación con hardware", + "LabelAlbumArtists": "Artistas del álbum:", + "LabelContentType": "Tipo de contenido:", + "LabelCommunityRating": "Valoración de la comunidad:", + "LabelCollection": "Colección:", + "LabelChannels": "Canales:", + "LabelCertificatePasswordHelp": "Si su certificado requiere una contraseña, ingrésela aquí.", + "LabelCertificatePassword": "Contraseña del certificado:", + "LabelCancelled": "Cancelado", + "LabelCachePath": "Ruta a la caché:", + "LabelCache": "Caché:", + "LabelBurnSubtitles": "Grabar subtítulos:", + "LabelAudioBitDepth": "Profundidad de bits de audio:", + "LabelArtistsHelp": "Separar usando múltiples ;", + "LabelInNetworkSignInWithEasyPassword": "Habilitar el inicio de sesión en la red con mi código PIN fácil", + "LabelImportOnlyFavoriteChannels": "Restringir a canales marcados como favoritos", + "LabelImageType": "Tipo de imagen:", + "LabelImageFetchersHelp": "Habilite y clasifique sus buscadores de imágenes preferidos en orden de prioridad.", + "LabelIdentificationFieldHelp": "Una subcadena que no distingue entre mayúsculas y minúsculas o expresión regular.", + "LabelIconMaxWidthHelp": "Resolución máxima de íconos expuestos a través de comentarios UPnP.", + "LabelIconMaxWidth": "Ancho máximo del ícono:", + "LabelIconMaxHeightHelp": "Resolución máxima de íconos expuestos a través de comentarios UPnP.", + "LabelIconMaxHeight": "Altura máxima del ícono:", + "LabelHttpsPortHelp": "El número de puerto TCP al que debe unirse el servidor HTTPS de Jellyfin.", + "LabelHttpsPort": "Número de puerto HTTPS local:", + "LabelHomeScreenSectionValue": "Sección de la pantalla de inicio {0}:", + "LabelHomeNetworkQuality": "Calidad de la red doméstica:", + "LabelHardwareAccelerationTypeHelp": "La aceleración por hardware requiere una configuración adicional.", + "LabelHardwareAccelerationType": "Aceleración por hardware:", + "LabelEncoderPreset": "Preajuste de codificación H264 y H265:", + "LabelH264Crf": "H264 que codifica CRF:", + "LabelGroupMoviesIntoCollectionsHelp": "Al mostrar listas de películas, las películas que pertenecen a una colección se mostrarán como un elemento agrupado.", + "LabelGroupMoviesIntoCollections": "Agrupar películas en colecciones", + "LabelServerNameHelp": "Este nombre se usará para identificar el servidor y será el nombre predeterminado de la computadora del servidor.", + "LabelFriendlyName": "Nombre amigable:", + "LabelFormat": "Formato:", + "LabelForgotPasswordUsernameHelp": "Ingrese su nombre de usuario, si lo recuerda.", + "LabelFont": "Fuente:", + "LabelFolder": "Carpeta:", + "LabelFileOrUrl": "Archivo o URL:", + "LabelFailed": "Fallido", + "LabelExtractChaptersDuringLibraryScanHelp": "Genere imágenes de capítulos cuando se importen videos durante el escaneo de la biblioteca. De lo contrario, se extraerán durante la tarea programada de las imágenes de los capítulos, lo que permitirá que el análisis de la biblioteca regular se complete más rápido.", + "LabelExtractChaptersDuringLibraryScan": "Extraer imágenes de capítulos durante el escaneo de la biblioteca", + "LabelBaseUrlHelp": "Agrega un subdirectorio personalizado a la URL del servidor. Por ejemplo: http://ejemplo.com/<baseurl>", + "LabelBaseUrl": "URL base:", + "LabelEveryXMinutes": "Cada:", + "LabelEvent": "Evento:", + "LabelEpisodeNumber": "Número de capítulo:", + "LabelEndDate": "Fecha final:", + "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no se procesarán correctamente si se incrustan varias imágenes en Didl.", + "LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada", + "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos se procesarán de inmediato, en los sistemas de archivos compatibles.", + "LabelEnableRealtimeMonitor": "Habilitar monitoreo en tiempo real", + "LabelEnableHttpsHelp": "Permite que el servidor escuche en el puerto HTTPS configurado. También se debe configurar un certificado válido para que esto surta efecto.", + "LabelEnableHttps": "Habilitar HTTPS", + "LabelEnableHardwareDecodingFor": "Habilite la decodificación por hardware para:", + "LabelEnableDlnaServerHelp": "Permite a los dispositivos UPnP en su red navegar y reproducir contenido.", + "LabelEnableDlnaServer": "Habilitar servidor DLNA", + "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de tu red y ofrece la posibilidad de controlarlos de forma remota.", + "LabelEnableDlnaPlayTo": "Habilitar DLNA para reproducir", + "LabelEnableDlnaDebugLoggingHelp": "Cree archivos de registro grandes y solo se deben usar según sea necesario para fines de resolución de problemas.", + "LabelEnableDlnaDebugLogging": "Habilitar el registro de depuración DLNA", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duración en segundos entre las búsquedas SSDP realizadas por Jellyfin.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de descubrimiento del cliente (segundos)", + "LabelEnableBlastAliveMessagesHelp": "Habilítelo si el servidor no es detectado confiablemente por otros dispositivos UPnP en su red.", + "LabelEnableBlastAliveMessages": "Explosión de mensajes vivos", + "LabelEnableAutomaticPortMapHelp": "Reenvíe automáticamente los puertos públicos de su enrutador a los puertos locales de su servidor a través de UPnP. Es posible que esto no funcione con algunos modelos de enrutadores o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", + "LabelEnableAutomaticPortMap": "Habilitar asignación automática de puertos", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener la carátula del álbum. Otros pueden no reproducir con esta opción habilitada.", + "LabelEmbedAlbumArtDidl": "Incruste la carátula del álbum en Didl", + "LabelEasyPinCode": "Código pin fácil:", + "LabelDynamicExternalId": "{0} Id:", + "LabelDropShadow": "Disminuir sombra:", + "LabelDroppedFrames": "Marcos caídos:", + "LabelDropImageHere": "Suelta la imagen aquí, o haz clic para buscar.", + "LabelDownloadLanguages": "Descargar idiomas:", + "LabelDownMixAudioScaleHelp": "Aumenta el audio al mezclar. Un valor de uno preservará el volumen original.", + "LabelDownMixAudioScale": "Aumento de audio al mezclar:", + "LabelDisplaySpecialsWithinSeasons": "Muestra especiales en las temporadas en las que se transmitieron", + "LabelDisplayOrder": "Orden de visualización:", + "LabelDisplayName": "Nombre para mostrar:", + "LabelDisplayMode": "Modo de visualización:", + "LabelDisplayLanguageHelp": "Traducir Jellyfin es un proyecto en curso.", + "LabelDisplayLanguage": "Idioma de la pantalla:", + "LabelDiscNumber": "Número de disco:", + "LabelDidlMode": "Modo DIDL:", + "LabelDeviceDescription": "Descripción del dispositivo", + "LabelDeinterlaceMethod": "Método de desentrelazado:", + "LabelDefaultUserHelp": "Determina qué biblioteca de usuario debe mostrarse en los dispositivos conectados. Esto se puede anular para cada dispositivo usando perfiles.", + "LabelDefaultUser": "Usuario predeterminado:", + "LabelDefaultScreen": "Pantalla predeterminada:", + "LabelDay": "Día:", + "LabelDateTimeLocale": "Fecha hora local:", + "LabelDateAddedBehaviorHelp": "Si un valor de metadatos está presente, siempre se usará antes de cualquiera de estas opciones.", + "LabelDateAddedBehavior": "Comportamiento de fecha agregada para contenido nuevo:", + "LabelDateAdded": "Fecha agregada:", + "LabelDashboardTheme": "Tema del panel del servidor:", + "LabelCustomRating": "Calificación personalizada:", + "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjelo vacío para usar el nombre informado por el dispositivo.", + "LabelCustomDeviceDisplayName": "Nombre para mostrar:", + "LabelCustomCssHelp": "Aplique su propio estilo personalizado a la interfaz web.", + "LabelCustomCss": "CSS personalizado:", + "LabelCustomCertificatePathHelp": "Ruta a un archivo PKCS #12 que contiene un certificado y una clave privada para habilitar el soporte TLS en un dominio personalizado.", + "LabelCustomCertificatePath": "Ruta del certificado SSL personalizado:", + "LabelCurrentPassword": "Contraseña actual:", + "LabelCriticRating": "Valoración crítica:", + "LabelCountry": "País:", + "LabelCorruptedFrames": "Marcos corruptos:", + "LabelCachePathHelp": "Especifique una ubicación personalizada para los archivos de caché del servidor, como las imágenes. Déjelo en blanco para usar el servidor predeterminado.", + "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", + "LabelAlbumArtMaxWidthHelp": "Resolución máxima de la carátula del álbum expuesta a través upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Ancho máximo de la carátula del álbum:", + "LabelAlbumArtMaxHeightHelp": "Resolución máxima de la carátula del álbum expuesta a través de upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Altura máxima de la carátula del álbum:", + "ImportMissingEpisodesHelp": "Si está habilitado, la información sobre capítulos faltantes se importará a su base de datos Jellyfin y se mostrará dentro de temporadas y series. Esto puede causar escaneos de bibliotecas significativamente más largos.", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan una forma de personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar metadatos NFO, edite una biblioteca en la configuración de la biblioteca Jellyfin y localice la sección de ahorradores de metadatos.", + "EnableDetailsBannerHelp": "Muestra una imagen de la pancarta en la parte superior de la página de detalles del elemento.", + "EnableDetailsBanner": "Detalles de la pancarta", + "EnableDecodingColorDepth10Vp9": "Habilitar la decodificación por hardware de VP9 de 10-Bit", + "EnableDecodingColorDepth10Hevc": "Habilitar la decodificación por hardware de HEVC de 10-Bit", + "LabelKodiMetadataEnableExtraThumbsHelp": "Al descargar imágenes, se pueden guardar en extrafanart y extrathumbs para obtener la máxima compatibilidad con Kodi.", + "LabelKodiMetadataEnableExtraThumbs": "Copiar extrafanart al campo extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Todas las fechas dentro de los archivos NFO se analizarán usando este formato.", + "LabelKodiMetadataDateFormat": "Formato de fecha de lanzamiento:", + "LabelKidsCategories": "Categorías de niños:", + "LabelKeepUpTo": "Mantente al día con:", + "LabelInternetQuality": "Calidad de internet:", + "LabelDeathDate": "Fecha de muerte:", + "HeaderTypeImageFetchers": "{0} Buscadores de imágenes", + "LabelSyncPlayNewGroupDescription": "Crea un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia horaria de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Compensación horaria con el servidor:", + "LabelSupportedMediaTypes": "Tipos de medios soportados:", + "LabelSubtitles": "Subtítulos", + "LabelSubtitlePlaybackMode": "Modo de subtítulos:", + "LabelSubtitleFormatHelp": "Ejemplo: srt", + "LabelSubtitleDownloaders": "Descargadores de subtítulos:", + "LabelStreamType": "Tipo de transmisión:", + "LabelStopping": "Deteniendo", + "LabelStatus": "Estado:", + "LabelSportsCategories": "Categorías de deportes:", + "LabelSpecialSeasonsDisplayName": "Nombre de la temporada especial:", + "LabelSource": "Fuente:", + "LabelSoundEffects": "Efectos de sonido:", + "LabelSortTitle": "Ordenar título:", + "LabelSortOrder": "Orden de clasificación:", + "LabelSortBy": "Ordenar por:", + "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el espacio de nombres urn:schemas-sonycom:av.", + "LabelSonyAggregationFlags": "Banderas de agregación de Sony:", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos dará como resultado una entrega más eficiente y disminuirá la probabilidad de transcodificación de video.", + "LabelSkipIfGraphicalSubsPresent": "Omita si el video ya contiene subtítulos incrustados", + "LabelSkipIfAudioTrackPresentHelp": "Desmarque esto para asegurarse de que todos los videos tengan subtítulos, independientemente del idioma de audio.", + "LabelSkipIfAudioTrackPresent": "Omita si la pista de audio predeterminada coincide con el idioma de descarga", + "LabelSkipBackLength": "Saltar de nuevo la longitud:", + "LabelSkin": "Piel:", + "LabelSize": "Tamaño:", + "LabelSimultaneousConnectionLimit": "Límite de transmisiones simultáneas:", + "LabelServerName": "Nombre del servidor:", + "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", + "LabelServerHost": "Host:", + "LabelSeriesRecordingPath": "Ruta de grabación de la serie (opcional):", + "LabelSerialNumber": "Número de serie", + "LabelSendNotificationToUsers": "Enviar la notificación a:", + "LabelSelectVersionToInstall": "Seleccione la versión para instalar:", + "LabelSelectUsers": "Seleccionar usuarios:", + "LabelSelectFolderGroupsHelp": "Las carpetas que no están marcadas se mostrarán por sí mismas en su propia vista.", + "LabelSelectFolderGroups": "Agrupe automáticamente el contenido de las siguientes carpetas en vistas como Películas, Música y TV:", + "LabelSeasonNumber": "Número de temporada:", + "EnableFasterAnimationsHelp": "Usa animaciones y transiciones más rápidas", + "EnableFasterAnimations": "Animaciones más rápidas", + "LabelScreensaver": "Salvapantallas:", + "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", + "LabelSaveLocalMetadataHelp": "Guardar las ilustraciones en carpetas de medios las colocará en un lugar donde puedan editarse fácilmente.", + "LabelSaveLocalMetadata": "Guardar ilustraciones en carpetas de medios", + "LabelRuntimeMinutes": "Tiempo de ejecución (minutos):", + "LabelRequireHttps": "Requerir HTTPS", + "LabelRemoteClientBitrateLimit": "Límite de velocidad de transmisión en Internet (Mbps):", + "LabelReleaseDate": "Fecha de lanzamiento:", + "LabelRefreshMode": "Modo de actualización:", + "LabelRecordingPathHelp": "Especifique la ubicación predeterminada para guardar grabaciones. Si se deja vacío, se utilizará la carpeta de datos del programa del servidor.", + "LabelRecordingPath": "Ruta de grabación predeterminada:", + "LabelRecord": "Grabar:", + "LabelReasonForTranscoding": "Motivo de la transcodificación:", + "LabelReadHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LabelPublicHttpsPortHelp": "El número de puerto público que debe asignarse al puerto HTTPS local.", + "LabelPublicHttpsPort": "Número de puerto HTTPS público:", + "LabelPublicHttpPortHelp": "El número de puerto público que debe asignarse al puerto HTTP local.", + "LabelPublicHttpPort": "Número de puerto HTTP público:", + "LabelProtocolInfoHelp": "El valor que se utilizará al responder a las solicitudes GetProtocolInfo del dispositivo.", + "LabelProtocolInfo": "Información del protocolo:", + "LabelProtocol": "Protocolo:", + "LabelProfileVideoCodecs": "Códecs de video:", + "LabelProfileContainersHelp": "Separado por coma. Esto se puede dejar vacío para aplicar a todos los contenedores.", + "LabelProfileContainer": "Contenedor:", + "LabelProfileCodecsHelp": "Separado por coma. Esto puede dejarse vacío para aplicar a todos los códecs.", + "LabelProfileCodecs": "Códecs:", + "LabelProfileAudioCodecs": "Códecs de audio:", + "LabelPreferredSubtitleLanguage": "Idioma de subtítulos preferido:", + "LabelPreferredDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", + "LabelPreferredDisplayLanguage": "Idioma de visualización preferido:", + "LabelPostProcessorArgumentsHelp": "Use {ruta} como la ruta al archivo de grabación.", + "LabelPostProcessorArguments": "Argumentos de la línea de comando del post-procesador:", + "LabelPostProcessor": "Aplicación de postprocesamiento:", + "LabelPleaseRestart": "Los cambios surtirán efecto después de volver a cargar manualmente el cliente web.", + "LabelPlayMethod": "Método de reproducción:", + "LabelPlaylist": "Lista de reproducción:", + "LabelPlayerDimensions": "Dimensiones del reproductor:", + "LabelPlayer": "Reproductor:", + "LabelPlayDefaultAudioTrack": "Reproduce la pista de audio predeterminada independientemente del idioma", + "LabelPlaceOfBirth": "Lugar de nacimiento:", + "LabelPersonRoleHelp": "Ejemplo: conductor de camión de helados", + "LabelPersonRole": "Rol:", + "LabelPath": "Ruta:", + "LabelPasswordRecoveryPinCode": "Código PIN:", + "LabelPasswordResetProvider": "Proveedor de restablecimiento de contraseña:", + "LabelPasswordConfirm": "Contraseña (confirmación):", + "LabelPassword": "Contraseña:", + "LabelParentalRating": "Calificación parental:", + "LabelParentNumber": "Número parental:", + "LabelOverview": "Visión general:", + "LabelOriginalTitle": "Título original:", + "LabelOriginalAspectRatio": "Relación de aspecto original:", + "LabelOptionalNetworkPathHelp": "Si esta carpeta se comparte en su red, el suministro de la ruta compartida de la red puede permitir que las aplicaciones Jellyfin en otros dispositivos accedan directamente a los archivos multimedia. Por ejemplo, {0} o {1}.", + "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", + "LabelNumberOfGuideDaysHelp": "La descarga de datos de la guía por más días proporciona la capacidad de programar con mayor anticipación y ver más listados, pero también tomará más tiempo descargarlos. Auto elegirá en función de la cantidad de canales.", + "LabelNumberOfGuideDays": "Número de días de datos de guía para descargar:", + "LabelNumber": "Número:", + "LabelNotificationEnabled": "Habilitar esta notificación", + "LabelNewsCategories": "Categorías de noticias:", + "LabelNewPasswordConfirm": "Nueva contraseña confirmada:", + "LabelNewPassword": "Nueva contraseña:", + "LabelNewName": "Nuevo nombre:", + "LabelNightly": "Nocturna", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "LabelName": "Nombre:", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifique una tasa de bits máxima cuando transmita música.", + "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", + "LabelMovieRecordingPath": "Ruta de grabación de películas (opcional):", + "LabelMoviePrefixHelp": "Si se aplica un prefijo a los títulos de las películas, ingréselo aquí para que el servidor pueda manejarlo correctamente.", + "LabelMoviePrefix": "Prefijo de película:", + "LabelMovieCategories": "Categorías de películas:", + "LabelMonitorUsers": "Monitorear la actividad de:", + "LabelModelUrl": "URL del modelo", + "LabelModelNumber": "Número de modelo", + "LabelModelName": "Nombre del modelo", + "LabelModelDescription": "Descripción del modelo", + "LabelMinScreenshotDownloadWidth": "Ancho mínimo de descarga de captura de pantalla:", + "LabelMinResumePercentageHelp": "Se supone que los títulos no se reproducen si se detienen antes de este momento.", + "LabelMinResumePercentage": "Porcentaje mínimo de reanudación:", + "LabelMinResumeDurationHelp": "La duración de video más corta en segundos que guardará la ubicación de reproducción y le permitirá reanudar.", + "LabelMinResumeDuration": "Duración mínima de la reanudación:", + "LabelMinBackdropDownloadWidth": "Ancho mínimo de descarga de fondo:", + "LabelMethod": "Método:", + "LabelMetadataSaversHelp": "Elija los formatos de archivo para guardar sus metadatos.", + "LabelMetadataSavers": "Ahorradores de metadatos:", + "LabelMetadataReadersHelp": "Clasifique sus fuentes de metadatos locales preferidas en orden de prioridad. Se leerá el primer archivo encontrado.", + "LabelMetadataReaders": "Lectores de metadatos:", + "LabelMetadataPathHelp": "Especifique una ubicación personalizada para las ilustraciones y los metadatos descargados.", + "LabelMetadataPath": "Ruta a los metadatos:", + "LabelMetadataDownloadersHelp": "Habilite y clasifique sus descargadores de metadatos preferidos en orden de prioridad. Los descargadores de menor prioridad solo se utilizarán para completar la información que falta.", + "LabelMetadataDownloadLanguage": "Idioma de descarga preferido:", + "LabelMetadata": "Metadatos:", + "LabelMessageTitle": "Título del mensaje:", + "LabelMessageText": "Mensaje de texto:", + "LabelMaxStreamingBitrateHelp": "Especifique una tasa de bits máxima al transmitir.", + "LabelMaxStreamingBitrate": "Máxima calidad de transmisión:", + "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por elemento:", + "LabelMaxResumePercentageHelp": "Se asume que los títulos fueron vistos si se detienen después de este tiempo.", + "LabelMaxResumePercentage": "Porcentaje máximo de reanudación:", + "LabelMaxParentalRating": "Calificación parental máxima permitida:", + "LabelMaxChromecastBitrate": "Calidad de transmisión del Chromecast:", + "LabelMaxBackdropsPerItem": "Número máximo de fondos por artículo:", + "LabelManufacturerUrl": "URL del fabricante", + "LabelManufacturer": "Fabricante:", + "LabelLogs": "Registros:", + "LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.", + "LabelLockItemToPreventChanges": "Bloquee este elemento para evitar futuros cambios", + "LabelLocalHttpServerPortNumberHelp": "El número de puerto TCP al que debe unirse al servidor Jellyfin mediante HTTP.", + "LabelLocalHttpServerPortNumber": "Número de puerto HTTP local:", + "LabelLibraryPageSize": "Tamaño de página de la biblioteca:", + "LabelLanguage": "Idioma:", + "LabelLanNetworks": "Redes LAN:", + "OptionBlockBooks": "Libros", + "OptionBanner": "Pancarta", + "OptionAutomaticallyGroupSeriesHelp": "Si está habilitado, las series que se distribuyen en varias carpetas dentro de esta biblioteca se fusionarán automáticamente en una sola serie.", + "OptionAutomaticallyGroupSeries": "Combinar automáticamente series que se extienden a través de múltiples carpetas", + "OptionAutomatic": "Auto", + "OptionAuto": "Auto", + "OptionAscending": "Ascendente", + "OptionArtist": "Artista", + "OptionAllowVideoPlaybackTranscoding": "Permitir reproducción de video que requiere transcodificación", + "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiere conversión sin volver a codificar", + "OptionAllowUserToManageServer": "Permitir a este usuario administrar el servidor", + "OptionAllowSyncTranscoding": "Permitir la descarga y sincronización de medios que requieren transcodificación", + "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se consideran compartidos hasta que un usuario comienza a controlarlos.", + "OptionAllowRemoteSharedDevices": "Permitir el control remoto de dispositivos compartidos", + "OptionAllowRemoteControlOthers": "Permitir el control remoto de otros usuarios", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación puede causar fallas de reproducción en las aplicaciones Jellyfin debido a formatos de medios no compatibles.", + "OptionAllowMediaPlayback": "Permitir reproducción de medios", + "OptionAllowManageLiveTv": "Permitir la administración de grabaciones de TV en vivo", + "OptionAllowLinkSharingHelp": "Solo se comparten páginas web que contienen información de medios. Los archivos multimedia nunca se comparten públicamente. Las acciones tienen un límite de tiempo y caducarán después de {0} días.", + "OptionAllowLinkSharing": "Permitir compartir en redes sociales", + "OptionAllowContentDownloading": "Permitir la descarga y sincronización de medios", + "OptionAllowBrowsingLiveTv": "Permitir el acceso a TV en vivo", + "OptionForceRemoteSourceTranscoding": "Forzar la transcodificación de fuentes de medios remotos (como LiveTV)", + "OptionAllowAudioPlaybackTranscoding": "Permitir reproducción de audio que requiere transcodificación", + "OptionAllUsers": "Todos los usuarios", + "OptionAlbumArtist": "Artista del álbum", + "OptionAlbum": "Álbum", + "OptionAdminUsers": "Administradores", + "Option3D": "3D", + "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", + "OnlyForcedSubtitlesHelp": "Solo se cargarán los subtítulos marcados como forzados.", + "OnlyForcedSubtitles": "Solo forzado", + "OneChannel": "Un canal", + "Off": "Apagado", + "NumLocationsValue": "{0} carpetas", + "Normal": "Normal", + "None": "Nunguno", + "NoSubtitlesHelp": "Los subtítulos no se cargarán de manera predeterminada. Todavía se pueden activar manualmente durante la reproducción.", + "NoSubtitles": "Ninguno", + "NoSubtitleSearchResultsFound": "No se han encontrado resultados.", + "NoPluginConfigurationMessage": "Este complemento no tiene opciones para configurar.", + "NoNextUpItemsMessage": "Nada encontrado. ¡Comienza a ver tus shows!", + "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un nuevo sintonizador, cierre este cuadro de diálogo e ingrese la información del dispositivo manualmente.", + "NoCreatedLibraries": "Parece que todavía no has creado ninguna biblioteca. {0}¿Te gustaría crear una ahora?{1}", + "No": "No", + "NextUp": "Siguiente", + "Next": "Siguiente", + "News": "Noticias", + "NewEpisodesOnly": "Solo capítulos nuevos", + "NewEpisodes": "Nuevos capítulos", + "NewCollectionHelp": "Las colecciones le permiten crear agrupaciones personalizadas de películas y otro contenido de la biblioteca.", + "NewCollection": "Nueva colección", + "Never": "Nunca", + "Name": "Nombre", + "MySubtitles": "Mis subtítulos", + "Mute": "Silenciar", + "MusicVideo": "Video musical", + "MusicArtist": "Artista musical", + "MusicAlbum": "Álbum de música", + "Movie": "Película", + "MoveRight": "Mover a la derecha", + "MoveLeft": "Mover a la izquierda", + "MoreMediaInfo": "Información de medios", + "MoreFromValue": "Más de {0}", + "Monday": "Lunes", + "Mobile": "Móvil", + "MinutesBefore": "minutos antes", + "MinutesAfter": "minutos después", + "MetadataSettingChangeHelp": "Cambiar la configuración de metadatos afectará el contenido nuevo que se agrega en el futuro. Para actualizar el contenido existente, abra la pantalla de detalles y haga clic en el botón actualizar, o realice actualizaciones masivas utilizando el administrador de metadatos.", + "MetadataManager": "Administrador de metadatos", + "Metadata": "Metadatos", + "MessageSyncPlayErrorMedia": "¡Error al habilitar SyncPlay! Error de medios.", + "MessageSyncPlayErrorMissingSession": "¡Error al habilitar SyncPlay! Falta sesión.", + "MessageSyncPlayErrorNoActivePlayer": "No se ha encontrado ningún reproductor activo. SyncPlay ha sido deshabilitado.", + "MessageSyncPlayErrorAccessingGroups": "Se produjo un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "El acceso a este contenido está restringido.", + "MessageSyncPlayJoinGroupDenied": "Se requiere permiso para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Se requiere permiso para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "No se pudo unir al grupo porque no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Se requiere permiso de reproducción.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Comienza a reproducir algo primero.", + "MessageSyncPlayGroupWait": "{0} está almacenando...", + "MessageSyncPlayUserLeft": "{0} ha abandonado el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "MessageYouHaveVersionInstalled": "Actualmente tiene instalada la versión {0}.", + "MessageUnsetContentHelp": "El contenido se mostrará como carpetas simples. Para obtener mejores resultados, use el administrador de metadatos para configurar los tipos de contenido de las subcarpetas.", + "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Asegúrese de que se esté ejecutando e intente nuevamente.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se eliminarán de su biblioteca:", + "MessageSettingsSaved": "Configuraciones guardadas.", + "MessageReenableUser": "Ver abajo para volver a habilitar", + "MessagePluginInstallDisclaimer": "Los complementos creados por miembros de la comunidad Jellyfin son una excelente manera de mejorar su experiencia Jellyfin con características y beneficios adicionales. Antes de la instalación, tenga en cuenta los efectos que pueden tener en su servidor Jellyfin, como escaneos de bibliotecas más largos, procesamiento en segundo plano adicional y disminución de la estabilidad del sistema.", + "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento, inicie sesión directamente en su servidor local.", + "MessagePleaseWait": "Por favor espere. Esto puede tardar un minuto.", + "MessagePleaseEnsureInternetMetadata": "Asegúrese de que la descarga de metadatos de Internet esté habilitada.", + "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Póngase en contacto con el administrador del servidor para obtener más información.", + "MessagePasswordResetForUsers": "Los siguientes usuarios han restablecido sus contraseñas. Ahora pueden iniciar sesión con los códigos PIN que se usaron para realizar el restablecimiento.", + "MessageNothingHere": "Nada aquí.", + "MessageNoTrailersFound": "Instale el canal de avances para mejorar su experiencia cinematográfica agregando una biblioteca de avances de Internet.", + "MessageNoServersAvailable": "No se han encontrado servidores con el descubrimiento automático de servidores.", + "MessageNoPluginsInstalled": "No tienes complementos instalados.", + "MessageNoMovieSuggestionsAvailable": "Actualmente no hay sugerencias de películas disponibles. Comienza a ver y calificar tus películas y luego vuelve a ver tus recomendaciones.", + "MessageNoGenresAvailable": "Permitir a algunos proveedores de metadatos extraer géneros de Internet.", + "MessageNoCollectionsAvailable": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haga clic en el botón + para comenzar a crear colecciones.", + "MessageAddRepository": "Si desea agregar un repositorio, haga clic en el botón al lado del encabezado y complete la información solicitada.", + "LabelRepositoryNameHelp": "Un nombre personalizado para distinguir este repositorio de cualquier otro agregado a su servidor.", + "LabelRepositoryName": "Nombre del repositorio", + "LabelRepositoryUrlHelp": "La ubicación del manifiesto del repositorio que desea incluir.", + "LabelRepositoryUrl": "URL del repositorio", + "HeaderNewRepository": "Nuevo repositorio", + "MessageNoRepositories": "Sin repositorios.", + "MessageNoAvailablePlugins": "No hay complementos disponibles.", + "MessageLeaveEmptyToInherit": "Deje en blanco para heredar la configuración de un elemento primario o el valor predeterminado global.", + "MessageItemsAdded": "Artículos añadidos.", + "MessageItemSaved": "Artículo guardado.", + "MessageUnauthorizedUser": "No tiene autorización para acceder al servidor en este momento. Póngase en contacto con el administrador del servidor para obtener más información.", + "MessageInvalidUser": "Usuario o contraseña inválidos. Inténtalo de nuevo.", + "MessageInvalidForgotPasswordPin": "Se ingresó un código PIN no válido o caducado. Inténtalo de nuevo.", + "MessageInstallPluginFromApp": "Este complemento debe instalarse desde la aplicación en la que desea utilizarlo.", + "MessageImageTypeNotSelected": "Seleccione un tipo de imagen del menú desplegable.", + "MessageImageFileTypeAllowed": "Solo se admiten archivos JPEG y PNG.", + "MessageForgotPasswordInNetworkRequired": "Intente nuevamente dentro de su red doméstica para iniciar el proceso de restablecimiento de contraseña.", + "MessageForgotPasswordFileCreated": "El siguiente archivo se ha creado en su servidor y contiene instrucciones sobre cómo proceder:", + "MessageFileReadError": "Se produjo un error al leer el archivo. Inténtalo de nuevo.", + "MessageEnablingOptionLongerScans": "Habilitar esta opción puede resultar en escaneos de biblioteca significativamente más largos.", + "MessageDownloadQueued": "Descarga en cola.", + "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE o Ubuntu, debe otorgar al usuario del servicio al menos acceso de lectura a sus ubicaciones de almacenamiento.", + "MessageDirectoryPickerBSDInstruction": "Para BSD, es posible que deba configurar el almacenamiento dentro de su jaula (Jail) FreeNAS para permitir que Jellyfin acceda a él.", + "MessageDeleteTaskTrigger": "¿Estás seguro de que desea eliminar este activador de tarea?", + "MessageCreateAccountAt": "Cree una cuenta en {0}", + "MessageContactAdminToResetPassword": "Póngase en contacto con el administrador del sistema para restablecer su contraseña.", + "MessageConfirmShutdown": "¿Está seguro de que desea apagar el servidor?", + "MessageConfirmRevokeApiKey": "¿Está seguro de que desea revocar esta clave de API? La conexión de la aplicación al servidor Jellyfin se cerrará abruptamente.", + "MessageConfirmRestart": "¿Está seguro de que desea reiniciar el servidor Jellyfin?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de que deseas eliminar esta ubicación?", + "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", + "MessageConfirmProfileDeletion": "¿Estás seguro de que deseas eliminar este perfil?", + "MessageConfirmDeleteTunerDevice": "¿Estás seguro de que deseas eliminar este dispositivo?", + "MessageConfirmDeleteGuideProvider": "¿Está seguro de que desea eliminar este proveedor de guías?", + "MessageConfirmAppExit": "¿Quieres salir?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Estás seguro de que deseas eliminar esta carpeta multimedia?", + "MessageAreYouSureDeleteSubtitles": "¿Estás seguro de que deseas eliminar este archivo de subtítulos?", + "MessageAlreadyInstalled": "Esta versión ya está instalada.", + "Menu": "Menú", + "MediaIsBeingConverted": "Los medios se están convirtiendo a un formato compatible con el dispositivo que los reproduce.", + "MediaInfoStreamTypeVideo": "Video", + "MediaInfoStreamTypeSubtitle": "Subtítulo", + "MediaInfoStreamTypeEmbeddedImage": "Imagen incrustada", + "MediaInfoStreamTypeData": "Data", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoSoftware": "Software", + "MediaInfoTimestamp": "Marca de tiempo", + "MediaInfoSize": "Tamaño", + "MediaInfoSampleRate": "Frecuencia de muestreo", + "MediaInfoResolution": "Resolución", + "MediaInfoRefFrames": "Marcos de referencia", + "MediaInfoProfile": "Perfil", + "MediaInfoPixelFormat": "Formato de píxeles", + "MediaInfoPath": "Ruta", + "MediaInfoLevel": "Nivel", + "MediaInfoLayout": "Diseño", + "MediaInfoLanguage": "Idioma", + "MediaInfoInterlaced": "Entrelazado", + "MediaInfoFramerate": "Cuadros por segundo", + "MediaInfoForced": "Forzado", + "MediaInfoExternal": "Externo", + "MediaInfoDefault": "Predeterminado", + "MediaInfoContainer": "Contenedor", + "MediaInfoCodecTag": "Etiqueta de códec", + "MediaInfoCodec": "Códec", + "MediaInfoChannels": "Canales", + "MediaInfoBitrate": "Tasa de bits", + "MediaInfoBitDepth": "Profundidad de bits", + "MediaInfoAspectRatio": "Relación de aspecto", + "MediaInfoAnamorphic": "Anamórfico", + "MaxParentalRatingHelp": "El contenido con una calificación más alta estará oculto para este usuario.", + "MapChannels": "Canales de mapas", + "ManageRecording": "Administrar grabación", + "ManageLibrary": "Administrar biblioteca", + "Logo": "Logo", + "LiveTV": "TV en vivo", + "LiveBroadcasts": "Transmisiones en vivo", + "Live": "En vivo", + "List": "Lista", + "LinksValue": "Enlaces: {0}", + "Like": "Me gusta", + "LeaveBlankToNotSetAPassword": "Puede dejar este campo en blanco para no establecer una contraseña.", + "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor", + "LatestFromLibrary": "Últimos {0}", + "Large": "Grande", + "LabelffmpegPathHelp": "La ruta al archivo de la aplicación ffmpeg o la carpeta que contiene ffmpeg.", + "LabelffmpegPath": "Ruta de FFmpeg:", + "LabelZipCode": "Código postal:", + "LabelYear": "Año:", + "LabelWeb": "Web:", + "LabelVideoResolution": "Resolución de video:", + "LabelVideoCodec": "Códec de vídeo:", + "LabelVideoBitrate": "Tasa de bits de video:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arquitectura: {0}", + "DashboardOperatingSystem": "Sistema operativo: {0}", + "DashboardServerName": "Servidor: {0}", + "DashboardVersionNumber": "Versión: {0}", + "LabelVersionInstalled": "{0} instalado", + "LabelVersion": "Versión:", + "LabelValue": "Valor:", + "LabelVaapiDeviceHelp": "Este es el nodo de representación que se utiliza para la aceleración por hardware.", + "LabelUsername": "Nombre de usuario:", + "LabelUserRemoteClientBitrateLimitHelp": "Anule el valor global predeterminado establecido en la configuración de reproducción del servidor.", + "LabelUserLoginAttemptsBeforeLockout": "Intentos de inicio de sesión fallidos antes de que el usuario quede bloqueado:", + "LabelUserLibraryHelp": "Seleccione qué biblioteca de usuario mostrar al dispositivo. Deje en blanco para heredar la configuración predeterminada.", + "LabelUserLibrary": "Biblioteca de usuario:", + "LabelUserAgent": "Agente de usuario:", + "LabelUser": "Usuario:", + "LabelUseNotificationServices": "Utiliza los siguientes servicios:", + "LabelTypeText": "Texto", + "LabelTypeMetadataDownloaders": "{0} descargadores de metadatos:", + "LabelType": "Tipo:", + "LabelTunerType": "Tipo de sintonizador:", + "LabelTunerIpAddress": "Dirección IP del sintonizador:", + "LabelTriggerType": "Tipo de disparador:", + "LabelTranscodingVideoCodec": "Códec de vídeo:", + "LabelTranscodingThreadCountHelp": "Elija el número máximo de hilos para usar al transcodificar. Reducir el recuento de hilos disminuirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido para una experiencia de reproducción fluida.", + "LabelTranscodingThreadCount": "Conteo de hilos de transcodificación:", + "LabelTranscodingProgress": "Progreso de transcodificación:", + "LabelTranscodingFramerate": "Velocidad de fotogramas de transcodificación:", + "LabelTranscodes": "Transcodificaciones:", + "LabelTranscodingTempPathHelp": "Especifique una ruta personalizada para los archivos de transcodificación servidos a los clientes. Déjelo en blanco para usar el servidor predeterminado.", + "LabelTranscodePath": "Ruta para las transcodificaciones:", + "LabelTranscodingContainer": "Contenedor:", + "LabelTranscodingAudioCodec": "Códec de audio:", + "LabelTrackNumber": "Número de pista:", + "LabelTitle": "Título:", + "LabelTimeLimitHours": "Límite de tiempo (horas):", + "LabelTime": "Hora:", + "LabelTheme": "Tema:", + "LabelTextSize": "Tamaño del texto:", + "LabelTextColor": "Color de texto:", + "LabelTextBackgroundColor": "Color de fondo del texto:", + "LabelTagline": "Lema:", + "LabelTag": "Etiqueta:", + "LabelTVHomeScreen": "Pantalla de inicio del modo TV:", + "LabelSyncPlayAccess": "Acceso SyncPlay", + "LabelSyncPlayAccessNone": "Deshabilitado para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir al usuario unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir al usuario crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Dejar grupo", + "OptionEnableForAllTuners": "Habilitar para todos los dispositivos sintonizadores", + "OptionEnableExternalContentInSuggestionsHelp": "Permita que se incluyan avances de Internet y programas de TV en vivo dentro del contenido sugerido.", + "OptionEnableExternalContentInSuggestions": "Habilitar contenido externo en sugerencias", + "OptionEnableAccessToAllLibraries": "Habilite el acceso a todas las bibliotecas", + "OptionEnableAccessToAllChannels": "Habilita el acceso a todos los canales", + "OptionEnableAccessFromAllDevices": "Habilite el acceso desde todos los dispositivos", + "OptionEmbedSubtitles": "Incrustar dentro del contenedor", + "OptionDvd": "DVD", + "OptionDownloadThumbImage": "Pulgar", + "OptionDownloadPrimaryImage": "Primario", + "OptionDownloadMenuImage": "Menú", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo se descargan cuando lo solicita una aplicación Jellyfin. Active esta opción para descargar todas las imágenes de antemano, ya que se importan nuevos medios. Esto puede causar escaneos de biblioteca significativamente más largos.", + "OptionDownloadImagesInAdvance": "Descargar imágenes por adelantado", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadBoxImage": "Caja", + "OptionDownloadBannerImage": "Pancarta", + "OptionDownloadBackImage": "Volver", + "OptionDownloadArtImage": "Arte", + "OptionDisplayFolderViewHelp": "Mostrar carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si desea tener una vista de carpeta simple.", + "OptionDisplayFolderView": "Mostrar una vista de carpeta para mostrar carpetas de medios simples", + "OptionDislikes": "No me gustas", + "OptionDisableUserHelp": "Si está deshabilitado, el servidor no permitirá ninguna conexión de este usuario. Las conexiones existentes se terminarán abruptamente.", + "OptionDisableUser": "Deshabilitar este usuario", + "OptionDescending": "Descendente", + "OptionDatePlayed": "Fecha de reproducción", + "OptionDateAddedImportTime": "Use la fecha escaneada en la biblioteca", + "OptionDateAddedFileTime": "Usar fecha de creación de archivo", + "OptionDateAdded": "Fecha agregada", + "OptionDaily": "Diario", + "OptionCustomUsers": "Personalizado", + "OptionCriticRating": "Valoración crítica", + "OptionContinuing": "Continuo", + "OptionCommunityRating": "Calificación de la comunidad", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionBluray": "Blu-ray", + "OptionBlockTvShows": "Programas de televisión", + "OptionBlockTrailers": "Avances", + "OptionBlockMusic": "Música", + "OptionBlockMovies": "Películas", + "OptionBlockLiveTvChannels": "Canales de TV en vivo", + "OptionBlockChannelContent": "Contenido del canal de internet", + "MusicLibraryHelp": "Revise la {0}guía de nomenclatura musical{1}.", + "MovieLibraryHelp": "Revise la {0}guía de nombres de películas{1}.", + "LibraryAccessHelp": "Seleccione las bibliotecas para compartir con este usuario. Los administradores podrán editar todas las carpetas con el administrador de metadatos.", + "LaunchWebAppOnStartupHelp": "Abra el cliente web en su navegador web predeterminado cuando el servidor arranque inicialmente. Esto no ocurrirá cuando use la función de reinicio del servidor.", + "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para redes que se considerarán en la red local cuando se impongan restricciones de ancho de banda. Si se establece, todas las demás direcciones IP se considerarán en la red externa y estarán sujetas a las restricciones de ancho de banda externo. Si se deja en blanco, solo se considera que la subred del servidor está en la red local.", + "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el espacio de nombres urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaDoc": "Doc. X-DLNA:", + "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el espacio de nombres urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaCap": "Tapa X-DLNA:", + "LabelVaapiDevice": "Dispositivo VA API:", + "LabelStopWhenPossible": "Detener cuando sea posible:", + "LabelStartWhenPossible": "Iniciar cuando sea posible:", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por flujo para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits superior a la que puede manejar su conexión a Internet. Esto puede aumentar la carga de la CPU en su servidor para transcodificar videos sobre la marcha a una tasa de bits más baja.", + "LabelMatchType": "Tipo de concordancia:", + "LabelLoginDisclaimer": "Descargo de responsabilidad de inicio de sesión:", + "LabelLineup": "Alineación:", + "LabelLibraryPageSizeHelp": "Establece la cantidad de elementos para mostrar en una página de biblioteca. Establezca a 0 para deshabilitar la paginación.", + "LabelKodiMetadataUserHelp": "Guarde los datos de observación en archivos NFO para que otras aplicaciones los utilicen.", + "LabelKodiMetadataUser": "Guarde los datos de observación del usuario en archivos NFO para:", + "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tiene nombres de archivos de imágenes que no cumplen con las pautas de Kodi.", + "LabelKodiMetadataSaveImagePaths": "Guardar rutas de imagen dentro de archivos nfo", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Permite la sustitución de rutas de imágenes utilizando la configuración de sustitución de rutas del servidor.", + "LabelKodiMetadataEnablePathSubstitution": "Habilitar sustitución de ruta", + "LabelInNetworkSignInWithEasyPasswordHelp": "Use el código PIN fácil para iniciar sesión en clientes dentro de su red local. Su contraseña habitual sólo será necesaria fuera de casa. Si el código PIN se deja en blanco, no necesitará una contraseña dentro de su red doméstica.", + "LabelAlbumArtPN": "Art PN de álbum:", + "LabelAlbumArtHelp": "PN utilizado para la carátula del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren un valor específico, independientemente del tamaño de la imagen.", + "LabelAirsBeforeSeason": "Aires antes de la temporada:", + "LabelAirsBeforeEpisode": "Aires antes del capítulo:", + "LabelAirsAfterSeason": "Aires después de la temporada:", + "OptionEnableM2tsMode": "Habilitar el modo M2ts", + "ReleaseDate": "Fecha de lanzamiento", + "RefreshQueued": "Actualizar cola.", + "RefreshMetadata": "Actualizar metadatos", + "RefreshDialogHelp": "Los metadatos se actualizan en función de la configuración y los servicios de Internet que están habilitados en el panel del servidor Jellyfin.", + "Refresh": "Actualizar", + "Recordings": "Grabaciones", + "RecordingScheduled": "Grabación programada.", + "RecordingPathChangeMessage": "Cambiar su carpeta de grabación no migrará las grabaciones existentes de la ubicación anterior a la nueva. Tendrá que moverlos manualmente si lo desea.", + "RecordingCancelled": "Grabación cancelada.", + "RecordSeries": "Grabar series", + "Record": "Grabar", + "RecommendationStarring": "Protagonizada por {0}", + "RecommendationDirectedBy": "Dirigido por {0}", + "RecommendationBecauseYouWatched": "Porque viste {0}", + "RecommendationBecauseYouLike": "Porque te gusta {0}", + "RecentlyWatched": "Recientemente visto", + "Rate": "Velocidad", + "Raised": "Elevado", + "QueueAllFromHere": "Hacer cola todo desde aquí", + "Quality": "Calidad", + "Programs": "Programas", + "ProductionLocations": "Ubicaciones de producción", + "Producer": "Productor", + "Primary": "Primario", + "Previous": "Anterior", + "Premieres": "Estrenos", + "Premiere": "Estreno", + "PreferEmbeddedEpisodeInfosOverFileNames": "Prefiere la información incrustada del capítulo sobre los nombres de archivo", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto usa la información de los metadatos incrustados del capítulo si está disponible.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el título de visualización predeterminado cuando no hay metadatos de Internet o metadatos locales disponibles.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incrustados sobre nombres de archivo", + "PluginInstalledMessage": "El complemento se ha instalado correctamente. El servidor Jellyfin deberá reiniciarse para que los cambios surtan efecto.", + "PleaseSelectTwoItems": "Seleccione al menos dos elementos.", + "PleaseRestartServerName": "Reinicie el servidor Jellyfin - {0}.", + "PleaseEnterNameOrId": "Por favor, introduzca un nombre o una ID externa.", + "PleaseConfirmPluginInstallation": "Haga clic en Aceptar para confirmar que ha leído lo anterior y desea continuar con la instalación del complemento.", + "PleaseAddAtLeastOneFolder": "Agregue al menos una carpeta a esta biblioteca haciendo clic en el botón Agregar.", + "Played": "Reproducido", + "PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no envía un formato de medios compatible.", + "PlayNextEpisodeAutomatically": "Reproducir el próximo capítulo automáticamente", + "PlayNext": "Reproducir siguiente", + "PlayFromBeginning": "Reproducir desde el principio", + "PlayCount": "Cuenta de reproducciones", + "PlaybackData": "Datos de reproducción", + "PlayAllFromHere": "Reproducir todo desde aquí", + "Play": "Reproducir", + "PlaceFavoriteChannelsAtBeginning": "Colocar los canales favoritos al principio", + "PinCodeResetConfirmation": "¿Estás seguro de que deseas restablecer el código PIN?", + "PinCodeResetComplete": "El código pin ha sido restablecido.", + "PictureInPicture": "Imagen en imagen", + "Person": "Persona", + "PerfectMatch": "Combinación perfecta", + "People": "Personas", + "PasswordSaved": "Contraseña guardada.", + "PasswordResetProviderHelp": "Elija un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", + "PasswordResetHeader": "Restablecer contraseña", + "PasswordResetConfirmation": "¿Estás seguro de que deseas restablecer la contraseña?", + "PasswordResetComplete": "La contraseña ha sido restablecida.", + "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben coincidir.", + "PackageInstallFailed": "La instalación de {0} (versión {1}) falló.", + "PackageInstallCompleted": "Instalación {0} (versión {1}) completada.", + "PackageInstallCancelled": "Instalación de {0} (versión {1}) cancelada.", + "Overview": "Visión general", + "OtherArtist": "Otro artista", + "OriginalAirDateValue": "Fecha de emisión original: {0}", + "OptionWeekly": "Semanal", + "OptionWeekends": "Fines de semana", + "OptionWeekdays": "Días de la semana", + "OptionWednesday": "Miércoles", + "OptionWakeFromSleep": "Despertarse del sueño", + "OptionUnplayed": "No reproducido", + "OptionTvdbRating": "Calificación de TVDB", + "OptionTuesday": "Martes", + "OptionTrackName": "Nombre de la pista", + "OptionThumbCard": "Tarjeta del pulgar", + "OptionThumb": "Pulgar", + "OptionThursday": "Jueves", + "OptionSunday": "Domingo", + "OptionSubstring": "Subcadena", + "OptionSpecialEpisode": "Especiales", + "OptionSaveMetadataAsHiddenHelp": "Cambiar esto se aplicará a los nuevos metadatos guardados en el futuro. Los archivos de metadatos existentes se actualizarán la próxima vez que el servidor Jellyfin los guarde.", + "OptionSaveMetadataAsHidden": "Guardar metadatos e imágenes como archivos ocultos", + "OptionSaturday": "Sábado", + "OptionRuntime": "Tiempo de ejecución", + "OptionResumable": "Reanudable", + "OptionResElement": "elemento res", + "OptionRequirePerfectSubtitleMatchHelp": "Requerir una combinación perfecta filtrará los subtítulos para incluir solo aquellos que han sido probados y verificados con su archivo de video exacto. Desmarcar esto aumentará la probabilidad de que se descarguen los subtítulos, pero aumentará las posibilidades de texto de subtítulos incorrecto o incorrecto.", + "OptionRequirePerfectSubtitleMatch": "Descargar solo subtítulos que coincidan perfectamente con mis archivos de video", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es necesario para algunos dispositivos que no buscan el tiempo muy bien.", + "OptionReportByteRangeSeekingWhenTranscoding": "Informe que el servidor admite la búsqueda de bytes al transcodificar", + "OptionReleaseDate": "Fecha de lanzamiento", + "OptionRegex": "Regex", + "OptionRandom": "Aleatorio", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "Transmisión en vivo HTTP", + "OptionProfileVideoAudio": "Video Audio", + "OptionProfileVideo": "Video", + "OptionProfilePhoto": "Foto", + "OptionProfileAudio": "Audio", + "OptionPremiereDate": "Fecha de estreno", + "OptionPosterCard": "Tarjeta de póster", + "OptionPoster": "Póster", + "OptionPlayed": "Reproducido", + "OptionPlayCount": "Cuento de reproducciones", + "OptionPlainVideoItemsHelp": "Si está habilitado, todos los videos se representan en DIDL como \"object.item.videoItem\" en lugar de un tipo más específico, como \"object.item.videoItem.movie\".", + "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", + "OptionPlainStorageFoldersHelp": "Si está habilitado, todas las carpetas se representan en DIDL como \"object.container.storageFolder\" en lugar de un tipo más específico, como \"object.container.person.musicArtist\".", + "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento sin formato", + "OptionParentalRating": "Calificación parental", + "OptionOnInterval": "En un intervalo", + "OptionOnAppStartup": "En el inicio de la aplicación", + "OptionNone": "Ninguno", + "OptionNew": "Nuevo…", + "OptionNameSort": "Nombre", + "OptionMonday": "Lunes", + "OptionMax": "Máx.", + "OptionLoginAttemptsBeforeLockoutHelp": "Un valor de cero significa heredar el valor predeterminado de tres intentos para usuarios normales y cinco para administradores. Establecer esto en -1 deshabilitará la función.", + "OptionLoginAttemptsBeforeLockout": "Determina cuántos intentos de inicio de sesión incorrectos se pueden realizar antes de que ocurra el bloqueo.", + "OptionList": "Lista", + "OptionLikes": "Me gustas", + "OptionIsSD": "SD", + "OptionIsHD": "HD", + "OptionImdbRating": "Calificación de IMDb", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si está habilitado, estas solicitudes serán atendidas pero ignorarán el encabezado del rango de bytes.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar las solicitudes de rango de bytes de transcodificación", + "OptionHomeVideos": "Fotos", + "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", + "OptionHideUserFromLoginHelp": "Útil para cuentas de administrador privadas u ocultas. El usuario deberá iniciar sesión manualmente ingresando su nombre de usuario y contraseña.", + "OptionHideUser": "Ocultar a este usuario de las pantallas de inicio de sesión", + "OptionHasTrailer": "Avance", + "OptionHasThemeVideo": "Video temático", + "OptionHasThemeSong": "Tema musical", + "OptionHasSubtitles": "Subtítulos", + "OptionHasSpecialFeatures": "Características especiales", + "OptionFriday": "Viernes", + "OptionFavorite": "Favoritos", + "OptionExtractChapterImage": "Habilitar la extracción de imágenes de capítulos", + "OptionExternallyDownloaded": "Descarga externa", + "OptionEveryday": "Cada día", + "OptionEstimateContentLength": "Estimar la longitud del contenido al transcodificar", + "OptionEquals": "Igual a", + "OptionEnded": "Terminó", + "OptionEnableM2tsModeHelp": "Habilite el modo m2ts al codificar en mpegts.", + "ReplaceExistingImages": "Reemplazar imágenes existentes", + "ReplaceAllMetadata": "Reemplazar todos los metadatos", + "RepeatOne": "Repetir uno", + "RepeatMode": "Modo de repetición", + "RepeatEpisodes": "Repetir capítulos", + "RepeatAll": "Repetir todo", + "Repeat": "Repetir", + "RemoveFromPlaylist": "Eliminar de la lista de reproducción", + "RemoveFromCollection": "Eliminar de la colección", + "RememberMe": "Recuérdame", + "ReleaseGroup": "Grupo de lanzamiento", + "ButtonCast": "Transmitir", + "ButtonSyncPlay": "SyncPlay", + "EnableBlurhashHelp": "Las imágenes que aún se están cargando se mostrarán con un marcador de posición borroso", + "EnableBlurhash": "Habilitar marcadores de posición borrosos para imágenes", + "OnApplicationStartup": "En el inicio de la aplicación", + "EveryXHours": "Cada {0} horas", + "EveryHour": "Cada hora", + "EveryXMinutes": "Cada {0} minutos", + "OnWakeFromSleep": "Al despertar del sueño", + "WeeklyAt": "{0}s en {1}", + "DailyAt": "Diariamente en {0}", + "LastSeen": "Visto por última vez {0}", + "PersonRole": "como {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "El servidor Jellyfin requiere acceso de escritura a esta carpeta. Asegúrese del acceso de escritura e intente nuevamente.", + "PathNotFound": "La ruta no se pudo encontrar. Asegúrese de que la ruta sea válida e intente nuevamente.", + "Yes": "Sí", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "XmlTvSportsCategoriesHelp": "Los programas con estas categorías se mostrarán como programas deportivos. Separe múltiples con '|'.", + "XmlTvPathHelp": "Una ruta a un archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Usted es responsable de crear y actualizar el archivo.", + "XmlTvNewsCategoriesHelp": "Los programas con estas categorías se mostrarán como programas de noticias. Separe múltiples con '|'.", + "XmlTvMovieCategoriesHelp": "Los programas con estas categorías se mostrarán como películas. Separe múltiples con '|'.", + "XmlTvKidsCategoriesHelp": "Los programas con estas categorías se mostrarán como programas para niños. Separe múltiples con '|'.", + "XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.", + "Writer": "Escritor", + "Whitelist": "Lista blanca", + "Wednesday": "Miércoles", + "Watched": "Visto", + "ViewPlaybackInfo": "Ver información de reproducción", + "ViewArtist": "Ver artista", + "ViewAlbum": "Ver álbum", + "VideoRange": "Rango de video", + "Vertical": "Vertical", + "ValueTimeLimitSingleHour": "Límite de tiempo: 1 hora", + "ValueTimeLimitMultiHour": "Límite de tiempo: {0} horas", + "ValueSongCount": "{0} canciones", + "ValueSeriesCount": "{0} series", + "ValueSeconds": "{0} segundos", + "ValueOneSong": "1 canción", + "ValueOneSeries": "1 serie", + "ValueOneMusicVideo": "1 video musical", + "ValueOneMovie": "1 película", + "ValueOneAlbum": "1 álbum", + "ValueMusicVideoCount": "{0} videos musicales", + "ValueMovieCount": "{0} películas", + "ValueMinutes": "{0} min", + "ValueOneEpisode": "1 capítulo", + "ValueEpisodeCount": "{0} capítulos", + "ValueDiscNumber": "Disco {0}", + "ValueContainer": "Contenedor: {0}", + "ValueAlbumCount": "{0} álbumes", + "UserAgentHelp": "Proporcione un encabezado HTTP de agente de usuario personalizado.", + "Upload": "Subir", + "Up": "Subir arriba", + "Unrated": "Sin calificación", + "Unplayed": "No reproducido", + "Unmute": "Dejar de silenciar", + "UninstallPluginHeader": "Desinstalar complemento", + "UninstallPluginConfirmation": "¿Estás seguro de que deseas desinstalar {0}?", + "Uniform": "Uniforme", + "TvLibraryHelp": "Revise la {0}guía de nombres de TV{1}.", + "Tuesday": "Martes", + "Transcoding": "Transcodificación", + "TrackCount": "{0} pistas", + "Track": "Pista", + "TitlePlayback": "Reproducción", + "TitleHostingSettings": "Configuraciones de alojamiento", + "TitleHardwareAcceleration": "Aceleración por hardware", + "Thursday": "Jueves", + "Thumb": "Pulgar", + "TheseSettingsAffectSubtitlesOnThisDevice": "Esta configuración afecta los subtítulos en este dispositivo", + "ThemeVideos": "Videos temáticos", + "ThemeSongs": "Canciones temáticas", + "TagsValue": "Etiquetas: {0}", + "Tags": "Etiquetas", + "TabUsers": "Usuarios", + "TabUpcoming": "Próximamente", + "TabTranscoding": "Transcodificación", + "MarkUnplayed": "Marcar no reproducido", + "MarkPlayed": "Marcar reproducido", + "LabelSkipForwardLength": "Saltar hacia adelante longitud:", + "Trailers": "Avances", + "TabTrailers": "Avances", + "TabSuggestions": "Sugerencias", + "TabStreaming": "Transmisión", + "TabSongs": "Canciones", + "TabShows": "Programas", + "TabSettings": "Configuraciones", + "TabServer": "Servidor", + "TabSeries": "Series", + "TabScheduledTasks": "Tareas programadas", + "TabResumeSettings": "Continuar", + "TabResponses": "Respuestas", + "TabRecordings": "Grabaciones", + "TabProfiles": "Perfiles", + "TabProfile": "Perfil", + "TabPlugins": "Complementos", + "TabPlaylists": "Listas de reproducción", + "TabPlaylist": "Lista de reproducción", + "TabPlayback": "Reproducción", + "TabPassword": "Contraseña", + "TabParentalControl": "Control parental", + "TabOther": "Otro", + "TabNotifications": "Notificaciones", + "TabNfoSettings": "Configuraciones de NFO", + "TabNetworking": "Redes", + "TabNetworks": "Redes", + "TabMyPlugins": "Mis complementos", + "TabMusicVideos": "Videos musicales", + "TabMusic": "Música", + "TabMovies": "Películas", + "TabMetadata": "Metadatos", + "TabLogs": "Registros", + "TabLiveTV": "TV en vivo", + "TabLatest": "Último", + "TabInfo": "Información", + "TabGuide": "Guía", + "TabGenres": "Géneros", + "TabFavorites": "Favoritos", + "TabDVR": "DVR", + "TabDisplay": "Pantalla", + "TabDirectPlay": "Reproducción directa", + "TabDevices": "Dispositivos", + "TabDashboard": "Tablero", + "TabContainers": "Contenedores", + "TabCollections": "Colecciones", + "TabCodecs": "Códecs", + "TabChannels": "Canales", + "TabRepositories": "Repositorios", + "TabCatalog": "Catálogo", + "TabArtists": "Artistas", + "TabAlbums": "Álbumes", + "TabAlbumArtists": "Artistas del álbum", + "TabAdvanced": "Avanzado", + "TabAccess": "Acceso", + "TV": "TV", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de solo lectura. Los cambios en un perfil del sistema se guardarán en un nuevo perfil personalizado.", + "SyncPlayAccessHelp": "Seleccione el nivel de acceso que este usuario tiene a la función SyncPlay. SyncPlay permite sincronizar la reproducción con otros dispositivos.", + "Sunday": "Domingo", + "Suggestions": "Sugerencias", + "Subtitles": "Subtítulos", + "SubtitleOffset": "Desplazamiento de subtítulos", + "SubtitleDownloadersHelp": "Habilite y clasifique sus descargadores de subtítulos preferidos en orden de prioridad.", + "SubtitleAppearanceSettingsDisclaimer": "Esta configuración no se aplicará a los subtítulos gráficos (PGS, DVD, etc.) ni a los subtítulos ASS/SSA que incorporen sus propios estilos.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Esta configuración también se aplica a cualquier reproducción de Chromecast iniciada por este dispositivo.", + "Studios": "Estudios", + "StopRecording": "Detener grabación", + "Sports": "Deportes", + "SortName": "Ordenar por nombre", + "SortChannelsBy": "Ordenar canales por:", + "SortByValue": "Ordenar por {0}", + "Sort": "Ordenar", + "SmartSubtitlesHelp": "Los subtítulos que coincidan con la preferencia de idioma se cargarán cuando el audio esté en un idioma extranjero.", + "Smart": "Inteligente", + "Smaller": "Más pequeño", + "SmallCaps": "Letras minúsculas", + "Small": "Pequeño", + "SkipEpisodesAlreadyInMyLibraryHelp": "Los capítulos se compararán usando los números de temporada y episodio, cuando estén disponibles.", + "SkipEpisodesAlreadyInMyLibrary": "No grabar capítulos que ya están en mi biblioteca", + "SimultaneousConnectionLimitHelp": "El número máximo de transmisiones simultáneas permitidas. Ingrese 0 para ilimitado.", + "Filter": "Filtrar", + "New": "Nuevo", + "Shuffle": "Mezclar", + "ShowYear": "Mostrar año", + "ShowTitle": "Mostrar título", + "ShowMore": "Mostrar más", + "ShowLess": "Mostrar menos", + "ShowIndicatorsFor": "Mostrar indicadores para:", + "ShowAdvancedSettings": "Mostrar configuración avanzada", + "Share": "Compartir", + "SettingsWarning": "Cambiar estos valores puede causar inestabilidad o fallas de conectividad. Si tiene algún problema, le recomendamos cambiarlos a los valores predeterminados.", + "SettingsSaved": "Configuraciones guardadas.", + "Settings": "Configuraciones", + "ServerUpdateNeeded": "Este servidor Jellyfin necesita ser actualizado. Para descargar la última versión, visite {0}", + "ServerRestartNeededAfterPluginInstall": "El servidor Jellyfin deberá reiniciarse después de instalar un complemento.", + "ServerNameIsShuttingDown": "Servidor Jellyfin: {0} se está cerrando.", + "ServerNameIsRestarting": "Servidor Jellyfin: {0} se está reiniciando.", + "SeriesYearToPresent": "{0} - Presente", + "SeriesSettings": "Configuraciones de la serie", + "SeriesRecordingScheduled": "Grabación de la serie programada.", + "SeriesDisplayOrderHelp": "Ordenar capítulos por fecha de emisión, orden de DVD o numeración absoluta.", + "SeriesCancelled": "Series canceladas.", + "Series": "Series", + "SendMessage": "Enviar mensaje", + "SelectAdminUsername": "Seleccione un nombre de usuario para la cuenta de administrador.", + "Season": "Temporada", + "SearchResults": "Resultados de la búsqueda", + "SearchForSubtitles": "Buscar subtítulos", + "SearchForMissingMetadata": "Buscar metadatos faltantes", + "SearchForCollectionInternetMetadata": "Busque en Internet obras de arte y metadatos", + "Search": "Buscar", + "Screenshots": "Capturas de pantalla", + "Screenshot": "Captura de pantalla", + "Schedule": "Programación", + "ScanLibrary": "Escanear biblioteca", + "ScanForNewAndUpdatedFiles": "Escanear en busca de archivos nuevos y actualizados", + "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar subtítulos junto a los archivos de video les permitirá administrarlos más fácilmente.", + "SaveSubtitlesIntoMediaFolders": "Guardar subtítulos en carpetas de medios", + "SaveChanges": "Guardar cambios", + "Save": "Guardar", + "Saturday": "Sábado", + "Runtime": "Tiempo de ejecución", + "RunAtStartup": "Ejecutar en el arranque", + "Rewind": "Rebobinar", + "ResumeAt": "Reanudar desde {0}", + "ButtonPlayer": "Reproductor", + "Writers": "Escritores", + "ClearQueue": "Eliminar cola", + "StopPlayback": "Detener reproducción", + "ViewAlbumArtist": "Ver artista del álbum" } diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 1aff47675c4..ca4973bd712 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -1,82 +1,81 @@ { "Absolute": "Absoluto", "Accept": "Aceptar", - "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtelo más tarde.", + "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", "Add": "Agregar", - "AddItemToCollectionHelp": "Agregue elementos a las colecciones buscándolos y utilizando sus menús al hacer clic derecho o al tocarlos para agregarlos a una colección.", + "AddItemToCollectionHelp": "Agrega elementos a las colecciones buscándolos y utilizando sus menúes al hacer clic derecho o al tocarlos para agregarlos a una colección.", "AddToCollection": "Agregar a colección", "AddToPlayQueue": "Agregar a la cola de reproducción", "AddToPlaylist": "Agregar a lista de reproducción", "AddedOnValue": "Agregado {0}", - "AdditionalNotificationServices": "Explore el catálogo de complementos para instalar servicios de notificaciones adicionales.", + "AdditionalNotificationServices": "Explora el catálogo de complementos para instalar servicios de notificaciones adicionales.", "AirDate": "Fecha de emisión", "Aired": "Transmitido", "Albums": "Álbumes", "Alerts": "Alertas", "All": "Todo", "AllChannels": "Todos los canales", - "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX...)", "AllEpisodes": "Todos los episodios", "AllLanguages": "Todos los idiomas", "AllLibraries": "Todas las bibliotecas", - "AllowHWTranscodingHelp": "Permite que el sintonizador transcodifique las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllowHWTranscodingHelp": "Permite al sintonizador transcodificar las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", "AllowMediaConversion": "Permitir conversión de medios", "AllowMediaConversionHelp": "Permitir o denegar acceso a la función de convertir medios.", "AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha", "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", - "AllowRemoteAccess": "Permitir conexiones remotas a este Servidor Jellyfin.", - "AllowRemoteAccessHelp": "Si se deshabilita, todas las conexiones remotas serán bloqueadas.", - "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/mascaras de subred para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, todas las IP remotas serán permitidas.", - "AlwaysPlaySubtitles": "Siempre mostrar subtítulos", - "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el lenguaje preferido serán cargados independientemente del lenguaje del audio.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", + "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", + "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", + "AlwaysPlaySubtitles": "Siempre reproducir", + "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados independientemente del idioma del audio.", "AnamorphicVideoNotSupported": "Video anamorfico no soportado", - "AndroidUnlockRestoreHelp": "Para restaurar su compra previa, por favor asegúrese de que se encuentra registrado en el dispositivo con la misma cuenta de Google (o Amazon) con que hizo la compra. Asegúrese que la tienda de aplicaciones esta habilitada, no esta restringida por cualquier control parental y que tiene una conexión de internet activa. Esto se tiene que hacer solo una vez para restaurar su compra previa.", "AnyLanguage": "Cualquier idioma", "Anytime": "En cualquier momento", - "AroundTime": "Alrededor de {0}", + "AroundTime": "Alrededor de", "Art": "Arte", "Artists": "Artistas", "AsManyAsPossible": "Tantos como sea posible", "Ascending": "Ascendente", "AspectRatio": "Relación de aspecto", "AttributeNew": "Nuevo", - "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del lenguaje)", - "Backdrop": "Imagen de Fondo", + "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del idioma)", + "Backdrop": "Imagen de fondo", "Backdrops": "Imágenes de fondo", - "Banner": "Cartel", + "Banner": "Banner", "BestFit": "Mejor ajuste", "BirthDateValue": "Nacimiento: {0}", "BirthLocation": "Lugar de nacimiento", "BirthPlaceValue": "Lugar de nacimiento: {0}", - "Blacklist": "Bloqueados", - "BookLibraryHelp": "Los libros de texto y audio libros son soportados. Revise la {0}Guía para Nomenclatura de Libros{1}.", + "Blacklist": "Lista negra", + "BookLibraryHelp": "Los libros de texto y audiolibros están soportados. Revisa la {0} guía de nombrado de libros {1}.", "Books": "Libros", "Box": "Caja", - "BoxRear": "Reverso de caja", - "Browse": "Navegar", - "BrowsePluginCatalogMessage": "Explorar el catalogo de complementos para ver los complementos disponibles.", - "BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al convertir videos dependiendo del formato de los subtítulos. Evitar la grabación de subtítulos mejorará el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX, etc.) y ciertos subtítulos ASS/SSA.", + "BoxRear": "Caja (parte trasera)", + "Browse": "Explorar", + "BrowsePluginCatalogMessage": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", + "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", "ButtonAdd": "Agregar", - "ButtonAddMediaLibrary": "Agregar Biblioteca de Medios", - "ButtonAddScheduledTaskTrigger": "Agregar Disparador", - "ButtonAddServer": "Agregar Servidor", - "ButtonAddUser": "Agregar Usuario", + "ButtonAddMediaLibrary": "Agregar biblioteca de medios", + "ButtonAddScheduledTaskTrigger": "Agregar disparador", + "ButtonAddServer": "Agregar servidor", + "ButtonAddUser": "Agregar usuario", "ButtonArrowDown": "Abajo", "ButtonArrowLeft": "Izquierda", "ButtonArrowRight": "Derecha", "ButtonArrowUp": "Arriba", - "ButtonAudioTracks": "Pistas de Audio", + "ButtonAudioTracks": "Pistas de audio", "ButtonBack": "Atrás", "ButtonCancel": "Cancelar", - "ButtonChangeServer": "Cambiar Servidor", + "ButtonChangeServer": "Cambiar servidor", "ButtonConnect": "Conectar", "ButtonDelete": "Eliminar", - "ButtonDeleteImage": "Eliminar Imagen", + "ButtonDeleteImage": "Eliminar imagen", "ButtonDown": "Abajo", "ButtonDownload": "Descargar", "ButtonEdit": "Editar", "ButtonEditImages": "Editar imágenes", - "ButtonEditOtherUserPreferences": "Editar el perfíl de este usuario. imágen y preferencias personales.", + "ButtonEditOtherUserPreferences": "Editar el perfil, la imagen y las preferencias personales de este usuario.", "ButtonFilter": "Filtro", "ButtonForgotPassword": "Olvidé mi contraseña", "ButtonFullscreen": "Pantalla completa", @@ -84,9 +83,9 @@ "ButtonGuide": "Guía", "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", - "ButtonLearnMore": "Aprenda más", - "ButtonLibraryAccess": "Acceso a biblioteca", - "ButtonManualLogin": "Inicio de Sesión Manual", + "ButtonLearnMore": "Aprender más", + "ButtonLibraryAccess": "Acceso a biblioteca(s)", + "ButtonManualLogin": "Inicio de sesión manual", "ButtonMore": "Más", "ButtonNetwork": "Red", "ButtonNew": "Nuevo", @@ -97,29 +96,29 @@ "ButtonPause": "Pausar", "ButtonPlay": "Reproducir", "ButtonPreviousTrack": "Pista anterior", - "ButtonProfile": "Perfíl", - "ButtonQuickStartGuide": "Guía de Inicio Rápido", + "ButtonProfile": "Perfil", + "ButtonQuickStartGuide": "Guía de inicio rápido", "ButtonRefresh": "Actualizar", - "ButtonRefreshGuideData": "Actualizar Datos de la Guía", - "ButtonRemove": "Eliminar", + "ButtonRefreshGuideData": "Actualizar datos de la guía", + "ButtonRemove": "Remover", "ButtonRename": "Renombrar", "ButtonRepeat": "Repetir", - "ButtonResetEasyPassword": "Restaurar código pin sencillo", - "ButtonResetPassword": "Restablecer Contraseña", + "ButtonResetEasyPassword": "Restablecer código PIN sencillo", + "ButtonResetPassword": "Restablecer contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Continuar", "ButtonRevoke": "Revocar", "ButtonSave": "Guardar", - "ButtonScanAllLibraries": "Escanear Todas las Bibliotecas", + "ButtonScanAllLibraries": "Escanear todas las bibliotecas", "ButtonSearch": "Búsqueda", - "ButtonSelectDirectory": "Seleccionar Carpeta", - "ButtonSelectServer": "Seleccionar Servidor", + "ButtonSelectDirectory": "Seleccionar directorio", + "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectView": "Seleccionar vista", "ButtonSend": "Enviar", "ButtonSettings": "Configuración", "ButtonShuffle": "Aleatorio", "ButtonShutdown": "Apagar", - "ButtonSignIn": "Iniciar Sesión", + "ButtonSignIn": "Iniciar sesión", "ButtonSignOut": "Cerrar sesión", "ButtonSort": "Ordenar", "ButtonStart": "Iniciar", @@ -133,70 +132,70 @@ "CancelRecording": "Cancelar grabación", "CancelSeries": "Cancelar serie", "Categories": "Categorías", - "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicaran a contenido nuevo agregado a su biblioteca. Para aplicar los cambios a los títulos existentes, necesita actualizar los metadatos manualmente.", - "ChannelAccessHelp": "Seleccione los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", + "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicarán a contenido nuevo agregado a tu biblioteca. Para aplicar los cambios a los títulos existentes, necesitarás actualizar sus metadatos manualmente.", + "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", "ChannelNameOnly": "Canal {0} solamente", - "ChannelNumber": "Numero de canal", + "ChannelNumber": "Número de canal", "Channels": "Canales", - "CinemaModeConfigurationHelp": "El modo cine trae la experiencia del cine directo al la sala de TV con la habilidad de reproducir tráilers e intros personalizados antes de la presentación estelar.", + "CinemaModeConfigurationHelp": "El modo cine lleva la experiencia del cine directamente a tu sala de estar con la capacidad de reproducir trailers e introducciones personalizadas antes de la función principal.", "Collections": "Colecciones", "ColorPrimaries": "Colores primarios", "ColorSpace": "Espacio de color", "ColorTransfer": "Transferencia de color", "CommunityRating": "Calificación de la comunidad", "Composer": "Compositor", - "ConfigureDateAdded": "Configure como la fecha de adición es determinada en el Panel de Control del Servidor Jellyfin bajo la configuración de Bibliotecas", + "ConfigureDateAdded": "Configura cómo se determina la fecha de adición en el panel de control del servidor Jellyfin en la configuración de la biblioteca", "ConfirmDeleteImage": "¿Eliminar imagen?", - "ConfirmDeleteItem": "Al eliminar este ítem se eliminará tanto del sistema de archivos como de su biblioteca de medios. ¿Esta seguro de querer continuar?", - "ConfirmDeleteItems": "Al borrar estos items serán eliminados tanto del sistema de archivos como de la biblioteca de medios. ¿Esta seguro que desea continuar?", + "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", "ConfirmDeletion": "Confirmar eliminación", - "ConfirmEndPlayerSession": "¿Desea cerrar Jellyfin en {0}?", + "ConfirmEndPlayerSession": "¿Deseas apagar Jellyfin en {0}?", "Connect": "Conectar", "ContinueWatching": "Continuar viendo", "Continuing": "Continuando", - "CriticRating": "Calificación de la crítica", + "CriticRating": "Calificación de los críticos", "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", "DateAdded": "Fecha de adición", "DatePlayed": "Fecha de reproducción", - "DeathDateValue": "Fallcimiento: {0}", + "DeathDateValue": "Falleció: {0}", "Default": "Por defecto", - "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor inténtelo de nuevo mas tarde.", - "DefaultMetadataLangaugeDescription": "Estas son sus configuraciones por defecto y puedes ser personalizadas independientemente en cada biblioteca.", - "DefaultSubtitlesHelp": "Los subtitulos son cargados basados en los indicadores \"por defecto\" y \"forzado\" incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", + "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor, inténtalo de nuevo más tarde.", + "DefaultMetadataLangaugeDescription": "Estos son sus valores por defecto y pueden ser personalizados en cada biblioteca.", + "DefaultSubtitlesHelp": "Los subtítulos se cargan basados en los indicadores «por defecto» y «forzado» incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", "Delete": "Eliminar", - "DeleteDeviceConfirmation": "¿Está seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión en él.", + "DeleteDeviceConfirmation": "¿Estás seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión con él.", "DeleteImage": "Eliminar imagen", - "DeleteImageConfirmation": "¿Está seguro de querer eliminar esta imagen?", + "DeleteImageConfirmation": "¿Estás seguro de querer eliminar esta imagen?", "DeleteMedia": "Eliminar medios", - "DeleteUser": "Eliminar Usuario", - "DeleteUserConfirmation": "¿Está seguro de querer eliminar este usuario?", - "Depressed": "Depresión", + "DeleteUser": "Eliminar usuario", + "DeleteUserConfirmation": "¿Estás seguro de querer eliminar este usuario?", + "Depressed": "Presionado", "Descending": "Descendente", "Desktop": "Escritorio", "DetectingDevices": "Detectando dispositivos", - "DeviceAccessHelp": "Esto solo aplica a dispositivos que pueden ser identificados de manera individual y no evitará acceso al navegador. Al filtrar el acceso de usuarios a dispositivos se impedirá que utilicen nuevos dispositivos hasta que hayan sido aprobados aquí.", + "DeviceAccessHelp": "Esto solo se aplica a los dispositivos que pueden ser identificados de manera única y no impedirá el acceso desde navegadores. Filtrar el acceso a los dispositivos de los usuarios les impedirá usar nuevos dispositivos hasta que hayan sido aprobados aquí.", "DirectPlaying": "Reproducción directa", - "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video sera reempaquetado en el acto antes de transmitirlo al dispositivo.", - "DirectStreamHelp2": "La Transmisión Directa de un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", - "DirectStreaming": "Transmisión Directa", + "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video será reempaquetado sobre la marcha antes de transmitirlo al dispositivo.", + "DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", + "DirectStreaming": "Transmisión directa", "Directors": "Directores", "Disabled": "Desactivado", "Disc": "DIsco", "Disconnect": "Desconectar", "Dislike": "No me gusta", "Display": "Pantalla", - "DisplayInMyMedia": "Mostrar en pantalla de inicio", - "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como Recientes o Continua Viendo", + "DisplayInMyMedia": "Mostrar en la pantalla de inicio", + "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como recientes o continuar viendo", "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", - "DisplayModeHelp": "Seleccione el tipo de pantalla en la que se encuentra ejecutando Jellyfin.", + "DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.", "DoNotRecord": "No grabar", "Down": "Abajo", "Download": "Descargar", "DownloadsValue": "{0} descargas", "DrmChannelsNotImported": "Los canales con DRM no serán importados.", - "DropShadow": "Sombra Paralela", - "EasyPasswordHelp": "Su código PIN fácil se utiliza para el acceso sin conexión en los clientes compatibles y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", + "DropShadow": "Sombra paralela", + "EasyPasswordHelp": "Tu código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", "Edit": "Editar", "EditImages": "Editar imágenes", "EditMetadata": "Editar metadatos", @@ -204,17 +203,17 @@ "EnableBackdrops": "Imágenes de fondo", "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", "EnableCinemaMode": "Modo cine", - "EnableColorCodedBackgrounds": "Fondos con código de color", + "EnableColorCodedBackgrounds": "Fondos de colores codificados", "EnableDisplayMirroring": "Duplicado de pantalla", "EnableExternalVideoPlayers": "Reproductores de video externos", "EnableExternalVideoPlayersHelp": "Un menú de reproductor externo se mostrara cuando inicie la reproducción de un video.", "EnableHardwareEncoding": "Habilitar codificación por hardware", "EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción", - "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción.", + "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción actual.", "EnablePhotos": "Mostrar fotografías", "EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.", - "EnableStreamLooping": "Repetir automáticamente transmisiones en vivo", - "EnableStreamLoopingHelp": "Habilite esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitarlo cuando no es requerido puede causar problemas.", + "EnableStreamLooping": "Repetir automáticamente las transmisiones en vivo", + "EnableStreamLoopingHelp": "Habilita esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitar esto cuando no es requerido puede causar problemas.", "EnableThemeSongs": "Canciones temáticas", "EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.", "EnableThemeVideos": "Videos temáticos", @@ -222,21 +221,21 @@ "Ended": "Finalizado", "EndsAtValue": "Termina a las {0}", "Episodes": "Episodios", - "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de su cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesite acceder al sitio web de Schedules Direct y eliminar otras programaciones de su cuenta antes de continuar.", - "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor asegúrese de que la ruta es valida y que el proceso del Servidor Jellyfin tenga acceso a ese destino.", - "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor asegúrese de que este disponible e intente de nuevo.", - "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XmlTV. Por favor asegúrese de que el archivo existe e intente de nuevo.", - "ErrorDeletingItem": "Hubo un error eliminando el ítem del Servidor Jellyfin. Por favor verifique tenga permisos de escritura en la carpeta de medios e intente de nuevo.", - "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrese de que su información este correcta e inténtelo de nuevo.", - "ErrorMessageStartHourGreaterThanEnd": "El horario de fin debe ser mayor al de comienzo.", - "ErrorPleaseSelectLineup": "Por favor seleccione una programación e intente de nuevo. Si no hay disponible ninguna, entonces por favor verifique que su nombre de usuario, contraseña, y código postal sean correctos.", - "ErrorSavingTvProvider": "Hubo un error al salvar el proveedor de TV. Por favor asegúrese de que este disponible e intente de nuevo.", + "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de tu cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesites acceder al sitio web de Schedules Direct y eliminar otras programaciones de tu cuenta antes de continuar.", + "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a ese destino.", + "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor, asegúrate de que esté disponible e inténtalo de nuevo.", + "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor, asegúrate de que el archivo existe e inténtalo de nuevo.", + "ErrorDeletingItem": "Hubo un error eliminando el elemento del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", + "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrate de que tu información sea correcta e inténtalo de nuevo.", + "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización debe ser mayor que la hora de inicio.", + "ErrorPleaseSelectLineup": "Por favor, selecciona una programación e inténtalo de nuevo. Si no hay disponible ninguna, entonces, por favor, verifica que tu nombre de usuario, contraseña, y código postal sean correctos.", + "ErrorSavingTvProvider": "Hubo un error al guardar el proveedor de TV. Por favor, asegúrate de que sea accesible e inténtalo de nuevo.", "EveryNDays": "Cada {0} días", - "ExitFullscreen": "Salir de Pantalla Completa", + "ExitFullscreen": "Salir de la pantalla completa", "ExtraLarge": "Extra grande", "ExtractChapterImagesHelp": "La extracción de imágenes de capítulos permitirá a los clientes mostrar menús gráficos de selección de escenas. El proceso puede ser lento, intensivo en recursos y puede requerir varios gigabytes de espacio. Se ejecuta cuando se descubren los videos y también como una tarea programada cada noche. La programación es configurable en el área de tareas programadas. No se recomienda ejecutar esta tarea durante las horas de mayor uso.", - "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que introdujo. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor verifique la ruta e intente de nuevo.", - "FastForward": "Avance Rápido", + "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que has introducido. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor, verifica la ruta e inténtalo de nuevo.", + "FastForward": "Avance rápido", "Favorite": "Favorito", "Favorites": "Favoritos", "Features": "Características", @@ -254,349 +253,349 @@ "Folders": "Carpetas", "FormatValue": "Formato: {0}", "Friday": "Viernes", - "Fullscreen": "Pantalla Completa", - "Genre": "Genero", + "Fullscreen": "Pantalla completa", + "Genre": "Género", "Genres": "Géneros", "GroupBySeries": "Agrupar por series", "GroupVersions": "Agrupar versiones", "GuestStar": "Estrella invitada", "Guide": "Guía", - "GuideProviderLogin": "Iniciar Sesión", - "GuideProviderSelectListings": "Elegir Listados", - "H264CrfHelp": "El \"Factor de Transferencia Constante\" o \"Constant Rate Factor\" (CFR) es la configuración por defecto para el codificador x264. Puede poner valores entre 0 y 51, donde los valores mas bajos dan como resultado mejor calidad (a expensas de archivos mas grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, puede usar este valor como punto de referencia.", - "EncoderPresetHelp": "Elija un valor mas rápido para mejorar el rendimiento, o uno mas lento para mejorar la calidad.", + "GuideProviderLogin": "Iniciar sesión", + "GuideProviderSelectListings": "Elegir listados", + "H264CrfHelp": "El «Factor de transferencia constante» (CRF) es la configuración de calidad por defecto para el codificador x264. Puedes establecer valores entre 0 y 51, donde los valores más bajos dan como resultado mejor calidad (a expensas de archivos más grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, así que puedes usar este valor como punto de referencia.", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar el rendimiento, o uno más lento para mejorar la calidad.", "HDPrograms": "Programas en HD", "HandledByProxy": "Manejado por un proxy inverso", - "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos, Asegúrese de que su sistema operativo y controladores de video están actualizados. Si tiene dificultades reproduciendo vides después de habilitar esto, necesita cambiar las configuraciones de nuevo a Auto.", - "HeaderAccessSchedule": "Acceder Programación", - "HeaderAccessScheduleHelp": "Crear programación de acceso para limitar el acceso a ciertos horarios.", - "HeaderActiveDevices": "Dispositivos Activos", - "HeaderActiveRecordings": "Grabaciones Activas", + "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrate de que tu sistema operativo y controladores de video están actualizados. Si tienes dificultades reproduciendo videos después de habilitar esto, necesitarás volver a cambiar la configuración a Ninguno.", + "HeaderAccessSchedule": "Programación de acceso", + "HeaderAccessScheduleHelp": "Crea una programación de acceso para limitar el acceso a ciertos horarios.", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderActiveRecordings": "Grabaciones activas", "HeaderActivity": "Actividad", - "HeaderAddScheduledTaskTrigger": "Agregar Disparador", - "HeaderAddToCollection": "Agregar a Colección", - "HeaderAddToPlaylist": "Agregar a Lista de Reproducción", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderAddToCollection": "Agregar a colección", + "HeaderAddToPlaylist": "Agregar a lista de reproducción", "HeaderAddUpdateImage": "Agregar/Actualizar Imagen", - "HeaderAddUser": "Agregar Usuario", - "HeaderAdditionalParts": "Partes Adicionales", + "HeaderAddUser": "Agregar usuario", + "HeaderAdditionalParts": "Partes adicionales", "HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbums": "Álbumes", "HeaderAlert": "Alerta", - "HeaderAllowMediaDeletionFrom": "Permitir Eliminacion de Medios De", + "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", "HeaderApiKey": "Clave API", - "HeaderApiKeys": "Claves de API", - "HeaderApiKeysHelp": "Son necesarias aplicaciones externas para obtener una clave API para comunicarse con el Servidor Jellyfin. Las clave son emitidas accediendo con una cuenta Jellyfin, o bien concediéndole manualmente una clave a la aplicación.", - "HeaderAppearsOn": "Aparece En", - "HeaderAudioBooks": "Audio Libros", - "HeaderAudioSettings": "Configuración de Audio", - "HeaderAutomaticUpdates": "Actualizaciones Automáticas", - "HeaderBlockItemsWithNoRating": "Bloquear ítems sin clasificación o con información de clasificación desconocida:", + "HeaderApiKeys": "Claves API", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderAppearsOn": "Aparece en", + "HeaderAudioBooks": "Audiolibros", + "HeaderAudioSettings": "Configuración de audio", + "HeaderAutomaticUpdates": "Actualizaciones automáticas", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin clasificación o con información de clasificación desconocida:", "HeaderBooks": "Libros", - "HeaderBranding": "Establecer Marca", - "HeaderCancelRecording": "Cancelar Grabación", - "HeaderCancelSeries": "Cancelar Serie", - "HeaderCastAndCrew": "Reparto & Personal", - "HeaderCastCrew": "Reparto y Personal", - "HeaderChannelAccess": "Acceso a los Canales", + "HeaderBranding": "Establecer marca", + "HeaderCancelRecording": "Cancelar grabación", + "HeaderCancelSeries": "Cancelar serie", + "HeaderCastAndCrew": "Reparto y equipo", + "HeaderCastCrew": "Reparto y equipo", + "HeaderChannelAccess": "Acceso a los canales", "HeaderChannels": "Canales", - "HeaderChapterImages": "Imagenes de Capitulo", - "HeaderCodecProfile": "Perfil de Codecs", - "HeaderCodecProfileHelp": "Los perfiles de codificación indican las limitaciones de un dispositivo al reproducir con codecs específicos. Si aplica una limitación el medio será transcodificado, aún si el codec ha sido configurado para reprodución directa.", - "HeaderConfigureRemoteAccess": "Configurar Acceso Remoto", - "HeaderConfirmPluginInstallation": "Confirmar Instalación de Complemento", - "HeaderConfirmProfileDeletion": "Confirmar Eliminación del Perfil", - "HeaderConfirmRevokeApiKey": "Revocar llave de API", + "HeaderChapterImages": "Imágenes de los capítulos", + "HeaderCodecProfile": "Perfil de códec", + "HeaderCodecProfileHelp": "Los perfiles de códecs indican las limitaciones de un dispositivo al reproducir códecs específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el códec ha sido configurado para reproducción directa.", + "HeaderConfigureRemoteAccess": "Configurar acceso remoto", + "HeaderConfirmPluginInstallation": "Confirmar instalación de complemento", + "HeaderConfirmProfileDeletion": "Confirmar eliminación de perfil", + "HeaderConfirmRevokeApiKey": "Revocar clave API", "HeaderConnectToServer": "Conectarse al servidor", - "HeaderConnectionFailure": "Falla de Conexión", - "HeaderContainerProfile": "Perfil del Contenedor", - "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si aplica una limitación el medio será transcodificado, aún si el formato ha sifo configurado para reproducción directa.", - "HeaderContinueListening": "Continuar Escuchando", - "HeaderContinueWatching": "Continuar Viendo", - "HeaderCustomDlnaProfiles": "Perfiles Personalizados", - "HeaderDateIssued": "Fecha de Emisión", - "HeaderDefaultRecordingSettings": "Configuración Predeterminada de Grabaciones", - "HeaderDeleteDevice": "Eliminar Dispositivo", - "HeaderDeleteItem": "Eliminar Ítem", - "HeaderDeleteItems": "Borrar items", - "HeaderDeleteProvider": "Eliminar Proveedor", - "HeaderDeleteTaskTrigger": "Borrar Disparador de Tarea", - "HeaderDetectMyDevices": "Detectar Mis Dispositivos", + "HeaderConnectionFailure": "Falla de conexión", + "HeaderContainerProfile": "Perfil del contenedor", + "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el formato ha sido configurado para reproducción directa.", + "HeaderContinueListening": "Continuar escuchando", + "HeaderContinueWatching": "Continuar viendo", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderDateIssued": "Fecha de emisión", + "HeaderDefaultRecordingSettings": "Configuración predeterminada de grabaciones", + "HeaderDeleteDevice": "Eliminar dispositivo", + "HeaderDeleteItem": "Eliminar elemento", + "HeaderDeleteItems": "Eliminar elementos", + "HeaderDeleteProvider": "Eliminar proveedor", + "HeaderDeleteTaskTrigger": "Borrar disparador de tarea", + "HeaderDetectMyDevices": "Detectar mis dispositivos", "HeaderDeveloperInfo": "Info del desarrollador", - "HeaderDeviceAccess": "Acceso a Dispositivos", + "HeaderDeviceAccess": "Acceso a dispositivos", "HeaderDevices": "Dispositivos", - "HeaderDirectPlayProfile": "Perfil de Reproducción Directa", - "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducción directa para indicar que formatos puede manejar el dispositivo de manera nativa.", + "HeaderDirectPlayProfile": "Perfil de reproducción directa", + "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", "HeaderDisplay": "Pantalla", - "HeaderDownloadSync": "Descargar y Sincronizar", - "HeaderEasyPinCode": "Código Pin Sencillo", + "HeaderDownloadSync": "Descargar y sincronizar", + "HeaderEasyPinCode": "Código PIN sencillo", "HeaderEditImages": "Editar imágenes", - "HeaderEnabledFields": "Campos Habilitados", - "HeaderEnabledFieldsHelp": "Desmarcar un campo para bloquearlo y prevenir que sus datos cambien.", + "HeaderEnabledFields": "Campos habilitados", + "HeaderEnabledFieldsHelp": "Desmarca un campo para bloquearlo y prevenir que sus datos sean cambiados.", "HeaderEpisodes": "Episodios", - "HeaderExternalIds": "IDs Externos:", - "HeaderFeatureAccess": "Permisos de acceso", + "HeaderExternalIds": "IDs externos:", + "HeaderFeatureAccess": "Acceso a características", "HeaderFeatures": "Características", - "HeaderFetchImages": "Buscar imágenes:", - "HeaderFetcherSettings": "Configuración de Recolectores", + "HeaderFetchImages": "Obtener imágenes:", + "HeaderFetcherSettings": "Configuración del recolector", "HeaderFilters": "Filtros", - "HeaderForKids": "Para Niños", - "HeaderForgotPassword": "Contraseña Olvidada", - "HeaderFrequentlyPlayed": "Reproducido Frecuentemente", + "HeaderForKids": "Para niños", + "HeaderForgotPassword": "Olvidé mi contraseña", + "HeaderFrequentlyPlayed": "Reproducido frecuentemente", "HeaderGenres": "Géneros", "HeaderGuideProviders": "Proveedores de Guías de TV", "HeaderHttpHeaders": "Encabezados HTTP", "HeaderIdentification": "Identificación", "HeaderIdentificationCriteriaHelp": "Introduzca, al menos, un criterio de identificación.", - "HeaderIdentificationHeader": "Encabezado de Identificación", - "HeaderIdentifyItemHelp": "Introduzca uno o más criterios de búsqueda. Elimine criterios para expandir los resultados.", - "HeaderImageOptions": "Opciones de Imagen", - "HeaderImageSettings": "Configuración de Imágenes", + "HeaderIdentificationHeader": "Encabezado de identificación", + "HeaderIdentifyItemHelp": "Introduce uno o más criterios de búsqueda. Elimina criterios para expandir los resultados.", + "HeaderImageOptions": "Opciones de imagen", + "HeaderImageSettings": "Configuración de imagen", "HeaderInstall": "Instalar", - "HeaderInstantMix": "Mezcla Instantánea", - "HeaderItems": "Ítems", - "HeaderKeepRecording": "Conservar Grabaciones", - "HeaderKeepSeries": "Conservar Serie", - "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos Nfo, edite una biblioteca en la configuracion de bibliotecas de Jellyfin y busque la sección de grabadores de metadatos.", - "HeaderLatestEpisodes": "Episodios Recientes", - "HeaderLatestMedia": "Agregadas Recientemente", - "HeaderLatestMovies": "Películas Recientes", - "HeaderLatestMusic": "Música Reciente", - "HeaderLatestRecordings": "Grabaciones Recientes", + "HeaderInstantMix": "Mix instantáneo", + "HeaderItems": "Elementos", + "HeaderKeepRecording": "Conservar grabación", + "HeaderKeepSeries": "Conservar serie", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderLatestEpisodes": "Últimos episodios", + "HeaderLatestMedia": "Últimos medios", + "HeaderLatestMovies": "Últimas películas", + "HeaderLatestMusic": "Última música", + "HeaderLatestRecordings": "Últimas grabaciones", "HeaderLibraries": "Bibliotecas", - "HeaderLibraryAccess": "Acceso a la Biblioteca", - "HeaderLibraryFolders": "Carpetas de Biblioteca", - "HeaderLibraryOrder": "Orden de Bibliotecas", - "HeaderLibrarySettings": "Configuraciones de Biblioteca", - "HeaderLiveTV": "TV en Vivo", - "HeaderLiveTv": "TV en Vivo", + "HeaderLibraryAccess": "Acceso a bibliotecas", + "HeaderLibraryFolders": "Carpetas de bibliotecas", + "HeaderLibraryOrder": "Orden de las bibliotecas", + "HeaderLibrarySettings": "Configuraciones de biblioteca", + "HeaderLiveTV": "TV en vivo", + "HeaderLiveTv": "TV en vivo", "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV", - "HeaderLoginFailure": "Falló el Inicio de Sesión", + "HeaderLoginFailure": "Falló el inicio de sesión", "HeaderMedia": "Medios", - "HeaderMediaFolders": "Carpetas de Medios", - "HeaderMediaInfo": "Info del Medio", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderMediaInfo": "Info del medio", "HeaderMetadataSettings": "Configuración de metadatos", - "HeaderMoreLikeThis": "Mas Como Esto", + "HeaderMoreLikeThis": "Más como esto", "HeaderMovies": "Películas", - "HeaderMusicQuality": "Calidad de Musica", - "HeaderMusicVideos": "Videos Musicales", - "HeaderMyDevice": "Mi Dispositivo", - "HeaderMyMedia": "Mis Medios", + "HeaderMusicQuality": "Calidad de la música", + "HeaderMusicVideos": "Videos musicales", + "HeaderMyDevice": "Mi dispositivo", + "HeaderMyMedia": "Mis medios", "HeaderMyMediaSmall": "Mis medios (pequeño)", "HeaderNewApiKey": "Nueva clave API", - "HeaderNewDevices": "Nuevos Dispositivos", - "HeaderNextEpisodePlayingInValue": "El Siguiente Episodio se Reproducirá en {0}", - "HeaderNextUp": "A Continuación", - "HeaderNextVideoPlayingInValue": "El Siguiente Video se Reproducirá en {0}", - "HeaderOnNow": "Transmitiéndo Ahora", - "HeaderOtherItems": "Otros Ítems", - "HeaderParentalRatings": "Clasificación Parental", + "HeaderNewDevices": "Nuevos dispositivos", + "HeaderNextEpisodePlayingInValue": "El siguiente episodio se reproducirá en {0}", + "HeaderNextUp": "A continuación", + "HeaderNextVideoPlayingInValue": "El siguiente video se reproducirá en {0}", + "HeaderOnNow": "Transmitiendo ahora", + "HeaderOtherItems": "Otros elementos", + "HeaderParentalRatings": "Clasificación parental", "HeaderPassword": "Contraseña", - "HeaderPasswordReset": "Restablecer Contraseña", + "HeaderPasswordReset": "Restablecer contraseña", "HeaderPaths": "Rutas", - "HeaderPendingInvitations": "Invitaciones Pendientes", + "HeaderPendingInvitations": "Invitaciones pendientes", "HeaderPeople": "Personas", - "HeaderPhotoAlbums": "Álbumes de Fotos", - "HeaderPinCodeReset": "Restablecer Código Pin", - "HeaderPlayAll": "Reproducir Todo", - "HeaderPlayOn": "Reproducir En", - "HeaderPlayback": "Reproducción de Medios", - "HeaderPlaybackError": "Error de Reproducción", - "HeaderPleaseSignIn": "Por favor inicie sesión", + "HeaderPhotoAlbums": "Álbumes de fotos", + "HeaderPinCodeReset": "Restablecer código PIN", + "HeaderPlayAll": "Reproducir todo", + "HeaderPlayOn": "Reproducir en", + "HeaderPlayback": "Reproducción de medios", + "HeaderPlaybackError": "Error de reproducción", + "HeaderPleaseSignIn": "Por favor, inicia sesión", "HeaderPluginInstallation": "Instalación de complemento", - "HeaderPreferredMetadataLanguage": "Idioma Preferido para Metadatos", - "HeaderProfile": "Perfíl", - "HeaderProfileInformation": "Información de Perfil", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el Servidor Jellyfin se presentara al dispositivo.", - "HeaderRecentlyPlayed": "Reproducido Recientemente", - "HeaderRecordingOptions": "Opciones de Grabación", - "HeaderRecordingPostProcessing": "Post Procesado de las Grabaciones", - "HeaderRemoteControl": "Control Remoto", - "HeaderRemoveMediaFolder": "Eliminar Carpteta de Medios", - "HeaderRemoveMediaLocation": "Eliminar Ubicación de Medios", - "HeaderResponseProfile": "Perfil de Respuesta", - "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada a un dispositivo cuando se reproducen ciertos tipos de medios.", + "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", + "HeaderProfile": "Perfil", + "HeaderProfileInformation": "Información del perfil", + "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderRecentlyPlayed": "Reproducido recientemente", + "HeaderRecordingOptions": "Opciones de grabación", + "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", + "HeaderRemoteControl": "Control remoto", + "HeaderRemoveMediaFolder": "Remover carpeta de medios", + "HeaderRemoveMediaLocation": "Remover ubicación de medios", + "HeaderResponseProfile": "Perfil de respuesta", + "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", "HeaderRestart": "Reiniciar", - "HeaderRevisionHistory": "Historial de Versiones", - "HeaderRunningTasks": "Tareas en Ejecución", + "HeaderRevisionHistory": "Historial de versiones", + "HeaderRunningTasks": "Tareas en ejecución", "HeaderScenes": "Escenas", "HeaderSchedule": "Programación", "HeaderSeasons": "Temporadas", - "HeaderSecondsValue": "{0} Segundos", - "HeaderSelectCertificatePath": "Seleccione la Ruta del Certificado", - "HeaderSelectMetadataPath": "Seleccionar Ruta para Metadatos", - "HeaderSelectMetadataPathHelp": "Explore o introduzca la ruta donde desea almacenar los metadatos. La carpeta debe tener permisos de escritura.", - "HeaderSelectPath": "Seleccionar Ruta", - "HeaderSelectServer": "Seleccionar Servidor", - "HeaderSelectServerCachePath": "Seleccionar ruta para Caché del Servidor", - "HeaderSelectServerCachePathHelp": "Explore o introduzca la ruta a utilizar para los archivos del caché del servidor. La carpeta debe tener permisos de escritura.", - "HeaderSelectTranscodingPath": "Seleccionar Ruta para Transcodificación Temporal", - "HeaderSelectTranscodingPathHelp": "Explore o introduzca la ruta a utilizar para los archivos temporales de transcodificación. La carpeta debe tener permisos de escritura.", - "HeaderSendMessage": "Enviar Mensaje", + "HeaderSecondsValue": "{0} segundos", + "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", + "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", + "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectPath": "Seleccionar ruta", + "HeaderSelectServer": "Seleccionar servidor", + "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", + "HeaderSelectServerCachePathHelp": "Explora o introduce la ruta a utilizar para los archivos caché del servidor. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", + "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSendMessage": "Enviar mensaje", "HeaderSeries": "Series", - "HeaderSeriesOptions": "Opciones de Serie", - "HeaderSeriesStatus": "Estado de la Serie", - "HeaderServerSettings": "Configuración del Servidor", + "HeaderSeriesOptions": "Opciones de serie", + "HeaderSeriesStatus": "Estado de la serie", + "HeaderServerSettings": "Configuración del servidor", "HeaderSettings": "Configuración", - "HeaderSetupLibrary": "Configurar sus bibliotecas de medios", + "HeaderSetupLibrary": "Configura tus bibliotecas de medios", "HeaderShutdown": "Apagar", - "HeaderSortBy": "Ordenar Por", - "HeaderSortOrder": "Ordenado Por", - "HeaderSpecialEpisodeInfo": "Información del Episodio Especial", - "HeaderSpecialFeatures": "Características Especiales", - "HeaderStartNow": "Iniciar Ahora", + "HeaderSortBy": "Ordenar por", + "HeaderSortOrder": "Clasificar ordenado", + "HeaderSpecialEpisodeInfo": "Información del episodio especial", + "HeaderSpecialFeatures": "Características especiales", + "HeaderStartNow": "Iniciar ahora", "HeaderStatus": "Estado", - "HeaderStopRecording": "Detener Grabación", - "HeaderSubtitleAppearance": "Apariencia de Subtitulos", - "HeaderSubtitleDownloads": "Descarga de Subtitulos", - "HeaderSubtitleProfile": "Perfíl de Subtítulo", - "HeaderSubtitleProfiles": "Perfiles de Subtítulos", - "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulos describen el formato del subtítulo soportado por el dispositivo.", - "HeaderSystemDlnaProfiles": "Perfiles del Sistema", + "HeaderStopRecording": "Detener grabación", + "HeaderSubtitleAppearance": "Apariencia de subtítulos", + "HeaderSubtitleDownloads": "Descarga de subtítulos", + "HeaderSubtitleProfile": "Perfil de subtítulo", + "HeaderSubtitleProfiles": "Perfiles de subtítulo", + "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulo describen los formatos de subtítulo soportados por el dispositivo.", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", "HeaderTags": "Etiquetas", - "HeaderTaskTriggers": "Disparadores de Tarea", + "HeaderTaskTriggers": "Disparadores de tarea", "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", "HeaderTracks": "Pistas", - "HeaderTranscodingProfile": "Perfil de Transcodificación", - "HeaderTranscodingProfileHelp": "Agruegue perfiles de transcodificación para indicar que formatos deben ser usados cuando se requiera transcodificar.", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTranscodingProfileHelp": "Agrega perfiles de transcodificación para indicar qué formatos deben ser usados cuando se requiere transcodificar.", "HeaderTunerDevices": "Sintonizadores", "HeaderTuners": "Sintonizador", - "HeaderTypeImageFetchers": "{0} Recolectores de Imágenes", - "HeaderTypeText": "Introduzca Texto", + "HeaderTypeImageFetchers": "Recolectores de imágenes para {0}", + "HeaderTypeText": "Introducir texto", "HeaderUpcomingOnTV": "Próximamente en TV", - "HeaderUploadImage": "Subir Imagen", + "HeaderUploadImage": "Subir imagen", "HeaderUser": "Usuario", "HeaderUsers": "Usuarios", - "HeaderVideoQuality": "Calidad de Video", - "HeaderVideoType": "Tipo de Video", - "HeaderVideoTypes": "Tipos de Video", - "HeaderXmlDocumentAttribute": "Atributo del Documento XML", - "HeaderXmlDocumentAttributes": "Atributos del Documento XML", + "HeaderVideoQuality": "Calidad de video", + "HeaderVideoType": "Tipo de video", + "HeaderVideoTypes": "Tipos de video", + "HeaderXmlDocumentAttribute": "Atributo del documento XML", + "HeaderXmlDocumentAttributes": "Atributos del documento XML", "HeaderXmlSettings": "Configuración XML", "HeaderYears": "Años", "HeadersFolders": "Carpetas", "Help": "Ayuda", "Hide": "Ocultar", - "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de Agregadas Recientemente", + "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de últimos medios", "Home": "Inicio", - "HttpsRequiresCert": "Para habilitar las conexiones seguras, es necesario proporcionar un certificado SSL de confianza, como el de \"Lets Encrypt\". Por favor proporcione un certificado, o desactive las conexiones seguras.", + "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", "Identify": "Identificar", "Images": "Imágenes", "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", - "ImportMissingEpisodesHelp": "Si se habilita, se importara a su base de datos de Jellyfin información sobre episodios faltantes y se mostrara dentro de las temporadas y series. Esto podría ocasionar escaneos de biblioteca significativamente mas largos.", - "InstallingPackage": "Instalando {0}", + "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", + "InstallingPackage": "Instalando {0} (versión {1})", "InstantMix": "Mix instantáneo", - "ItemCount": "{0} ítems", - "Items": "Ítems", + "ItemCount": "{0} elementos", + "Items": "Elementos", "Kids": "Niños", "Label3DFormat": "Formato 3D:", - "LabelAbortedByServerShutdown": "(Abortada por apagado del servidor)", + "LabelAbortedByServerShutdown": "(Abortado por apagado del servidor)", "LabelAccessDay": "Día de la semana:", - "LabelAccessEnd": "Horario de fin:", - "LabelAccessStart": "Horario de comienzo:", + "LabelAccessEnd": "Hora de finalización:", + "LabelAccessStart": "Hora de inicio:", "LabelAirDays": "Se emite los días:", "LabelAirTime": "Duración:", "LabelAirsAfterSeason": "Transmisión después de la temporada:", "LabelAirsBeforeEpisode": "Transmisión antes del episodio:", "LabelAirsBeforeSeason": "Transmisión antes de la temporada:", "LabelAlbum": "Álbum:", - "LabelAlbumArtHelp": "PN usado para arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", - "LabelAlbumArtMaxHeight": "Altura máxima para arte del álbum:", - "LabelAlbumArtMaxHeightHelp": "Máxima resolución para arte del album expuesta via upnp:albumArtURI.", - "LabelAlbumArtMaxWidth": "Ancho máximo para arte del álbum:", - "LabelAlbumArtMaxWidthHelp": "Máxima resolución para arte del album expuesta via upnp:albumArtURI.", - "LabelAlbumArtPN": "PN para arte del álbum:", + "LabelAlbumArtHelp": "PN usado para el arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", + "LabelAlbumArtMaxHeight": "Altura máxima del arte del álbum:", + "LabelAlbumArtMaxHeightHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Ancho máximo del arte del álbum:", + "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtPN": "PN del arte del álbum:", "LabelAlbumArtists": "Artistas del álbum:", "LabelAll": "Todos", - "LabelAllowHWTranscoding": "Permitir transcodificacion de hardware", - "LabelAllowServerAutoRestart": "Permite al servidor reiniciar automáticamente para aplicar actualizaciones", + "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "LabelAllowServerAutoRestart": "Permite al servidor reiniciarse automáticamente para aplicar actualizaciones", "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante los períodos de inactividad cuando no haya usuarios activos.", - "LabelAllowedRemoteAddresses": "Filtrar IP remota:", - "LabelAllowedRemoteAddressesMode": "Modo de filtrado de IP remota:", - "LabelAppName": "Nombre del App", - "LabelAppNameExample": "Ejemplo: Sickbeard, NzbDrone", + "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", + "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", + "LabelAppName": "Nombre de la aplicación", + "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelArtists": "Artistas:", "LabelArtistsHelp": "Separar múltiples empleando ;", "LabelAudioLanguagePreference": "Idioma preferido de audio:", - "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente metadatos de internet:", - "LabelBindToLocalNetworkAddress": "Atar a la dirección de red local:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Forzar la dirección IP local a la que se vinculara el servidor http. Si se deja en blanco, el servidor vinculara todas las direcciones disponibles. Cambiando este valor se requerirá reiniciar el Servidor Jellyfin.", - "LabelBirthDate": "Fecha de Nacimiento:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", + "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", + "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBirthDate": "Fecha de nacimiento:", "LabelBirthYear": "Año de nacimiento:", "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", - "LabelBlockContentWithTags": "Bloquear ítems con etiquetas:", - "LabelBurnSubtitles": "Subtitulos quemados:", + "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", + "LabelBurnSubtitles": "Quemar subtítulos:", "LabelCache": "Caché:", - "LabelCachePath": "Ruta para el Caché:", - "LabelCachePathHelp": "Especifique una ubicación personalizada para los archivos de caché del servidor tales como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", + "LabelCachePath": "Ruta de la caché:", + "LabelCachePathHelp": "Especifica una ubicación personalizada para los archivos caché del servidor como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", "LabelCancelled": "Cancelado", "LabelCertificatePassword": "Contraseña del certificado:", - "LabelCertificatePasswordHelp": "Si su certificado requiere de una contraseña, por favor introdúzcala aquí.", + "LabelCertificatePasswordHelp": "Si tu certificado requiere una contraseña, por favor, introdúcela aquí.", "LabelChannels": "Canales:", "LabelCollection": "Colección:", "LabelCommunityRating": "Calificación de la comunidad:", - "LabelContentType": "Tipo de Contenido:", + "LabelContentType": "Tipo de contenido:", "LabelCountry": "País:", - "LabelCriticRating": "Calificación de la crítica:", + "LabelCriticRating": "Calificación de los críticos:", "LabelCurrentPassword": "Contraseña actual:", - "LabelCustomCertificatePath": "Ruta personalizada del certificado ssl:", - "LabelCustomCertificatePathHelp": "Trayectoria a un archivo PKCS #12 que contiene el certificado y la llave privada para habilitar soporte a TLS en un dominio personalizado.", + "LabelCustomCertificatePath": "Ruta del certificado SSL personalizado:", + "LabelCustomCertificatePathHelp": "Ruta a un archivo PKCS #12 que contiene un certificado y una clave privada para habilitar el soporte TLS en un dominio personalizado.", "LabelCustomCss": "CSS personalizado:", - "LabelCustomCssHelp": "Aplicar tu propio estilo personalizado a la interfaz web.", + "LabelCustomCssHelp": "Aplica tu propio estilo personalizado a la interfaz web.", "LabelCustomDeviceDisplayName": "Nombre a mostrar:", - "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjelo vacío para usar el nombre reportado por el dispositivo.", + "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjalo vacío para usar el nombre reportado por el dispositivo.", "LabelCustomRating": "Calificación personalizada:", - "LabelDashboardTheme": "Tema del panel de control del Servidor:", + "LabelDashboardTheme": "Tema del panel de control del servidor:", "LabelDateAdded": "Fecha de adición:", - "LabelDateAddedBehavior": "Comportamiento de fecha de adición para nuevo contenido:", - "LabelDateAddedBehaviorHelp": "Si se encuentra un valor en los metadados siempre será empleado antes que cualquiera de estas opciones.", - "LabelDateTimeLocale": "Configuración regional de Fecha y Hora:", + "LabelDateAddedBehavior": "Comportamiento de la fecha de adición para nuevo contenido:", + "LabelDateAddedBehaviorHelp": "Si un valor de metadatos está presente, siempre se utilizará antes de cualquiera de estas opciones.", + "LabelDateTimeLocale": "Configuración regional de fecha y hora:", "LabelDay": "Día:", "LabelDeathDate": "Fecha de defunción:", "LabelDefaultScreen": "Pantalla por defecto:", "LabelDefaultUser": "Usuario por defecto:", - "LabelDefaultUserHelp": "Determina que usuario de la biblioteca será mostrado en los dispositivos conectados. Este puede ser reemplazado para cada dispositivo empleando perfiles.", + "LabelDefaultUserHelp": "Determina qué biblioteca de usuario será mostrada en los dispositivos conectados. Esto puede ser reemplazado para cada dispositivo empleando perfiles.", "LabelDeviceDescription": "Descripción del dispositivo", "LabelDidlMode": "Modo DIDL:", "LabelDiscNumber": "Número de disco:", - "LabelDisplayLanguage": "Lenguaje de Despliege:", + "LabelDisplayLanguage": "Idioma de pantalla:", "LabelDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", - "LabelDisplayMissingEpisodesWithinSeasons": "Mostar episodios no disponibles en las temporadas", - "LabelDisplayMode": "Modo de Pantalla:", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "LabelDisplayMode": "Modo de pantalla:", "LabelDisplayName": "Nombre a mostrar:", "LabelDisplayOrder": "Orden para mostrar:", - "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en que fueron transmitidos", - "LabelDownMixAudioScale": "Fortalecimiento de audio durante el downmix:", - "LabelDownMixAudioScaleHelp": "Realzar el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", - "LabelDownloadLanguages": "Descargar lenguajes:", - "LabelDropImageHere": "Arrastre la imagen aquí, o de clic para navegar.", + "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en las que fueron transmitidas", + "LabelDownMixAudioScale": "Incremento del audio cuando se hace downmix:", + "LabelDownMixAudioScaleHelp": "Incrementa el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", + "LabelDownloadLanguages": "Descargar idiomas:", + "LabelDropImageHere": "Arrastre la imagen aquí o haz para explorar.", "LabelDropShadow": "Mostrar sombra:", - "LabelEasyPinCode": "Código pin sencillo:", + "LabelEasyPinCode": "Código PIN sencillo:", "LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL", - "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte del álbum. Otros podrían fallar al reproducir con esta opción habilitada.", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte de álbumes. Otros podrían fallar al reproducir con esta opción habilitada.", "LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos", - "LabelEnableAutomaticPortMapHelp": "Intentar mapear automáticamente el puerto público con el puerto local via UPnP. Esto podría no funcionar con algunos modelos de ruteadores.", + "LabelEnableAutomaticPortMapHelp": "Redirecciona automáticamente los puertos públicos de tu router a los puertos locales de tu servidor a través de UPnP. Esto puede no funcionar con algunos modelos de routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", "LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida", - "LabelEnableBlastAliveMessagesHelp": "Habilite esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en su red.", - "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de Detección de Clientes (segundos)", + "LabelEnableBlastAliveMessagesHelp": "Habilita esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en tu red.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de descubrimiento de clientes (segundos)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duración en segundos entre búsquedas SSDP realizadas por Jellyfin.", - "LabelEnableDlnaDebugLogging": "Habilitar el registro de DLNA en la bitácora", + "LabelEnableDlnaDebugLogging": "Habilitar el registro de depuración de DLNA", "LabelEnableDlnaDebugLoggingHelp": "Crea grandes archivos de registro y solo se debe usar cuando se requiera para solucionar problemas.", "LabelEnableDlnaPlayTo": "Habilitar Reproducir En mediante DLNA", - "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de su red y ofrece la capacidad de controlarlos remotamente.", + "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de tu red y ofrece la capacidad de controlarlos remotamente.", "LabelEnableDlnaServer": "Habilitar servidor DLNA", - "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en su red navegar y reproducir contenido.", - "LabelEnableHardwareDecodingFor": "Habilitar decodificacion por hardware para:", + "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en tu red explorar y reproducir contenido.", + "LabelEnableHardwareDecodingFor": "Habilitar decodificación por hardware para:", "LabelEnableRealtimeMonitor": "Activar monitoreo en tiempo real", - "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo que lo soporten.", + "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo soportados.", "LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada", - "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no renderisaran apropiadamente si hay múltiples imágenes incrustadas en el Didl.", - "LabelEndDate": "Fecha de Fin:", - "LabelEpisodeNumber": "Episodio numero:", + "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no se renderizarán correctamente si se incrustan varias imágenes en DIDL.", + "LabelEndDate": "Fecha de fin:", + "LabelEpisodeNumber": "Episodio número:", "LabelEvent": "Evento:", "LabelEveryXMinutes": "Cada:", - "LabelExtractChaptersDuringLibraryScan": "Extraer imágenes de capítulos durante la exploración de la biblioteca", - "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de capítulos cuando se importan videos durante el análisis de la biblioteca. De lo contrario, se extraerán durante la tarea programada de las imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", + "LabelExtractChaptersDuringLibraryScan": "Extraer las imágenes de los capítulos durante el escaneo de la biblioteca", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de los capítulos cuando se importan videos durante el escaneo de la biblioteca. De lo contrario, se extraerán durante la tarea programada imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", "LabelFailed": "Fallido", "LabelFileOrUrl": "Archivo o URL:", "LabelFinish": "Terminar", @@ -606,73 +605,73 @@ "LabelFriendlyName": "Nombre amistoso:", "LabelServerNameHelp": "Este nombre se usará para identificar el servidor y se predeterminará al nombre de la computadora del servidor.", "LabelGroupMoviesIntoCollections": "Agrupar películas en colecciones", - "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo ítem.", + "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo artículo.", "LabelH264Crf": "CRF de codificación H264:", - "LabelEncoderPreset": "Codificación H264 predefinido:", - "LabelHardwareAccelerationType": "Aceleración por Hardware:", - "LabelHardwareAccelerationTypeHelp": "Esta es una característica experimental que solo está disponible en los sistemas soportados.", - "LabelHomeNetworkQuality": "Calidad en Red Local:", - "LabelHomeScreenSectionValue": "Pagina de inicio sección {0}:", + "LabelEncoderPreset": "Codificación H264 y H265 preestablecida:", + "LabelHardwareAccelerationType": "Aceleración por hardware:", + "LabelHardwareAccelerationTypeHelp": "La aceleración por hardware requiere configuración adicional.", + "LabelHomeNetworkQuality": "Calidad en red local:", + "LabelHomeScreenSectionValue": "Sección {0} de la pantalla de inicio:", "LabelHttpsPort": "Número de puerto local HTTPS:", "LabelHttpsPortHelp": "El número de puerto TCP al que el servidor HTTPS de Jellyfin debería enlazar.", "LabelIconMaxHeight": "Altura máxima del ícono:", - "LabelIconMaxHeightHelp": "Máxima resolución de íconos expuestos vía upnp:icon.", - "LabelIconMaxWidth": "Ancho máximo de ícono:", - "LabelIconMaxWidthHelp": "Máxima resolución de íconos expuestos vía upnp:icon.", - "LabelIdentificationFieldHelp": "Una subcadena insensible a la diferencia entre minúsculas y mayúsculas o expresión regex.", - "LabelImageFetchersHelp": "Habilite y priorice sus recolectores de imágenes preferidos.", + "LabelIconMaxHeightHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxWidth": "Ancho máximo del ícono:", + "LabelIconMaxWidthHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIdentificationFieldHelp": "Una subcadena indiferente a las mayúsculas y minúsculas o una expresión regular (regex).", + "LabelImageFetchersHelp": "Habilita y prioriza tus recolectores de imágenes preferidos.", "LabelImageType": "Tipo de imagen:", "LabelImportOnlyFavoriteChannels": "Restringir a canales marcados como favoritos", - "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código pin sencillo para conexiones dentro de la red", - "LabelInNetworkSignInWithEasyPasswordHelp": "Utilice el código PIN fácil para acceder a los clientes en su red local. Su contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitará una contraseña dentro de su red local.", - "LabelInternetQuality": "Calidad en internet:", + "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código PIN sencillo para conexiones dentro de la red", + "LabelInNetworkSignInWithEasyPasswordHelp": "Utiliza el código PIN sencillo para acceder a los clientes en tu red local. Tu contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitarás una contraseña dentro de tu red local.", + "LabelInternetQuality": "Calidad en Internet:", "LabelKeepUpTo": "Mantener hasta:", "LabelKidsCategories": "Categorías infantiles:", "LabelKodiMetadataDateFormat": "Formato de fecha de estreno:", "LabelKodiMetadataDateFormatHelp": "Todas las fechas dentro de los archivos NFO serán analizadas usando este formato.", "LabelKodiMetadataEnableExtraThumbs": "Copiar extrafanart al campo extrathumbs", - "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con skins de Kodi.", + "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes estas pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con las pieles de Kodi.", "LabelKodiMetadataEnablePathSubstitution": "Habilitar sustitución de ruta", "LabelKodiMetadataEnablePathSubstitutionHelp": "Habilita la sustitución de rutas de imágenes usando la configuración de sustitución de rutas del servidor.", - "LabelKodiMetadataSaveImagePaths": "Guardar rutas de imágenes en los archivos nfo", - "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tiene nombres de imagenes que no se ajustan a los lineamientos de Kodi.", + "LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO", + "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.", "LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:", - "LabelKodiMetadataUserHelp": "Guarda los datos de visto en los archivos NFO para que otras aplicaciones los utilicen.", + "LabelKodiMetadataUserHelp": "Guarda los datos de visto en archivos NFO para que otras aplicaciones los utilicen.", "LabelLanNetworks": "Redes LAN:", "LabelLanguage": "Idioma:", "LabelLineup": "Programación:", "LabelLocalHttpServerPortNumber": "Número de puerto local HTTP:", "LabelLocalHttpServerPortNumberHelp": "El número de puerto TCP al que el servidor HTTP de Jellyfin debería enlazar.", - "LabelLockItemToPreventChanges": "Bloquear este ítem para evitar cambios futuros", + "LabelLockItemToPreventChanges": "Bloquear este elemento para evitar cambios futuros", "LabelLoginDisclaimer": "Aviso legal:", "LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.", - "LabelLogs": "Bitácoras:", - "LabelManufacturer": "Fabricante", + "LabelLogs": "Registros:", + "LabelManufacturer": "Fabricante:", "LabelManufacturerUrl": "URL del fabricante", - "LabelMatchType": "Tipo de Coincidencia:", - "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por ítem:", - "LabelMaxChromecastBitrate": "Tasa maxima de bits para El Chromecast:", + "LabelMatchType": "Tipo de coincidencia:", + "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:", + "LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:", "LabelMaxParentalRating": "Máxima clasificación parental permitida:", - "LabelMaxResumePercentage": "Porcentaje máximo de reanudación:", + "LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:", "LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.", - "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por ítem:", + "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por elemento:", "LabelMaxStreamingBitrate": "Calidad máxima de transmisión:", - "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima al hacer streaming.", - "LabelMessageText": "Texto del Mensaje:", - "LabelMessageTitle": "Título del Mensaje:", + "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima cuando se transmite.", + "LabelMessageText": "Texto del mensaje:", + "LabelMessageTitle": "Título del mensaje:", "LabelMetadata": "Metadatos:", - "LabelMetadataDownloadLanguage": "Lenguaje preferido para descargas:", - "LabelMetadataDownloadersHelp": "Habilite y priorice sus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", - "LabelMetadataPath": "Ruta para metadatos:", - "LabelMetadataPathHelp": "Especifique una ubicación personalizada para ilustraciones y metadatos descargados.", + "LabelMetadataDownloadLanguage": "Idioma preferido para las descargas:", + "LabelMetadataDownloadersHelp": "Habilita y prioriza tus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", + "LabelMetadataPath": "Ruta para los metadatos:", + "LabelMetadataPathHelp": "Especifique una ubicación personalizada para las ilustraciones y los metadatos descargados.", "LabelMetadataReaders": "Lectores de metadatos:", - "LabelMetadataReadersHelp": "Ordene sus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", + "LabelMetadataReadersHelp": "Ordena tus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", "LabelMetadataSavers": "Grabadores de metadatos:", - "LabelMetadataSaversHelp": "Seleccione los formatos de archivo con los que se guardaran sus metadatos.", + "LabelMetadataSaversHelp": "Selecciona los formatos de archivo con los que se guardarán tus metadatos.", "LabelMethod": "Método:", "LabelMinBackdropDownloadWidth": "Anchura mínima de descarga de imágenes de fondo:", "LabelMinResumeDuration": "Duración mínima para la reanudación:", - "LabelMinResumeDurationHelp": "La duración de vídeo más corta en segundos que guardará la ubicación de reproducción y le permitirá reanudar la reproducción.", + "LabelMinResumeDurationHelp": "La duración de video más corta en segundos que guardará la ubicación de reproducción y te permitirá reanudarla.", "LabelMinResumePercentage": "Porcentaje mínimo para reanudar:", "LabelMinResumePercentageHelp": "Los medios se asumen como no reproducidos si se detienen antes de este tiempo.", "LabelMinScreenshotDownloadWidth": "Anchura mínima de descarga de capturas de pantalla:", @@ -683,296 +682,296 @@ "LabelMonitorUsers": "Monitorear actividad desde:", "LabelMovieCategories": "Categorías de películas:", "LabelMoviePrefix": "Prefijo de la película:", - "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúzcalo aquí para que el servidor pueda manejarlo correctamente.", - "LabelMovieRecordingPath": "Ruta para grabaciones de Películas (opcional):", - "LabelMusicStreamingTranscodingBitrate": "Tasa de transcodificación de música:", - "LabelMusicStreamingTranscodingBitrateHelp": "Especifique la tasa de bits máxima al transferir musica en tiempo real", + "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", "LabelName": "Nombre:", "LabelNewName": "Nuevo nombre:", - "LabelNewPassword": "Nueva contraseña:", + "LabelNewPassword": "Contraseña nueva:", "LabelNewPasswordConfirm": "Confirmación de contraseña nueva:", - "LabelNewsCategories": "Categorías de Noticias:", + "LabelNewsCategories": "Categorías de noticias:", "LabelNext": "Siguiente", "LabelNotificationEnabled": "Habilitar esta notificación", "LabelNumber": "Número:", "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", - "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente.", + "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", "LabelOriginalAspectRatio": "Relación de aspecto original:", - "LabelOriginalTitle": "Titulo original:", - "LabelOverview": "Sinopsis:", + "LabelOriginalTitle": "Título original:", + "LabelOverview": "Resumen:", "LabelParentNumber": "Número antecesor:", "LabelParentalRating": "Clasificación parental:", "LabelPassword": "Contraseña:", "LabelPasswordConfirm": "Contraseña (confirmar):", - "LabelPasswordRecoveryPinCode": "Código pin:", + "LabelPasswordRecoveryPinCode": "Código PIN:", "LabelPath": "Ruta:", "LabelPersonRole": "Rol:", "LabelPersonRoleHelp": "Ejemplo: Conductor de camión de helados", "LabelPlaceOfBirth": "Lugar de nacimiento:", - "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del lenguaje", - "LabelPlaylist": "Lista de Reproducción:", - "LabelPostProcessor": "Aplicación de Post Procesado:", - "LabelPostProcessorArguments": "Argumentos de linea de comando para el post-procesador:", - "LabelPostProcessorArgumentsHelp": "Use {path} como la ruta a el archivo de grabado.", + "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del idioma", + "LabelPlaylist": "Lista de reproducción:", + "LabelPostProcessor": "Aplicación de postprocesamiento:", + "LabelPostProcessorArguments": "Argumentos de la línea de comandos del post-procesador:", + "LabelPostProcessorArgumentsHelp": "Usar {path} como la ruta del archivo grabado.", "LabelPreferredDisplayLanguage": "Idioma de pantalla preferido:", "LabelPreferredDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", - "LabelPreferredSubtitleLanguage": "Idioma preferido para subtitulos:", + "LabelPreferredSubtitleLanguage": "Idioma preferido para los subtítulos:", "LabelPrevious": "Anterior", - "LabelProfileAudioCodecs": "Codecs de Audio:", - "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vació para aplicarlo a todos los codecs.", + "LabelProfileAudioCodecs": "Códecs de audio:", + "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los códecs.", "LabelProfileContainer": "Contenedor:", - "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vació para aplicarlo a todos los contenedores.", - "LabelProfileVideoCodecs": "Codecs de Video:", + "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los contenedores.", + "LabelProfileVideoCodecs": "Códecs de video:", "LabelProtocol": "Protocolo:", "LabelProtocolInfo": "Información del protocolo:", - "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responde a solicitudes GetProtocolInfo desde el dispositivo.", + "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responda a solicitudes GetProtocolInfo desde el dispositivo.", "LabelPublicHttpPort": "Número de puerto HTTP público:", "LabelPublicHttpPortHelp": "El número de puerto público que debe asignarse al puerto HTTP local.", "LabelPublicHttpsPort": "Número de puerto HTTPS público:", "LabelPublicHttpsPortHelp": "El número de puerto público que debe asignarse al puerto HTTPS local.", - "LabelReadHowYouCanContribute": "Infórmese de cómo puede contribuir.", + "LabelReadHowYouCanContribute": "Aprende cómo puedes contribuir.", "LabelReasonForTranscoding": "Motivo para transcodificar:", "LabelRecord": "Grabar:", - "LabelRecordingPath": "Ruta por defecto para grabaciones:", - "LabelRecordingPathHelp": "Especifique la locación para guardar grabaciones. Si se deja en blanco, se usara la carpeta de datos de programa del servidor.", + "LabelRecordingPath": "Ruta por defecto para las grabaciones:", + "LabelRecordingPathHelp": "Especifica la ubicación por defecto para guardar las grabaciones. Si se deja en blanco, se usará la carpeta de datos de programa del servidor.", "LabelRefreshMode": "Modo de actualización:", "LabelReleaseDate": "Fecha de estreno:", - "LabelRemoteClientBitrateLimit": "Limite de tasa de bits para transmisión por Internet (Mbps):", - "LabelRemoteClientBitrateLimitHelp": "Un límite opcional en la tasa de bits para cada transmisión para todos los dispositivos fuera de la red local. Esto es útil para evitar que los clientes soliciten una tasa de bits mayor a la que su conexión de internet puede soportar. Puede resultar en un incremente en la carga del CPU de su servidor para poder transmitir videos al vuelo a una resolución baja.", + "LabelRemoteClientBitrateLimit": "Limite de velocidad de bits para la transmisión por Internet (Mbps):", + "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", "LabelRuntimeMinutes": "Duración (minutos):", - "LabelSaveLocalMetadata": "Guardar ilustraciones en las carpetas de medios", - "LabelSaveLocalMetadataHelp": "Guardar ilustraciones directamente en las carpetas de medios los colocará en un lugar donde se pueden editar fácilmente.", - "LabelScheduledTaskLastRan": "Ejecutado hace {0}, tomando {1}.", - "LabelScreensaver": "Protector de Pantalla:", - "LabelSeasonNumber": "Temporada numero:", + "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", + "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", + "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", + "LabelScreensaver": "Protector de pantalla:", + "LabelSeasonNumber": "Temporada número:", "LabelSecureConnectionsMode": "Modo de conexión segura:", - "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas en vistas tales como Películas, Música y TV:", + "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:", "LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.", - "LabelSelectUsers": "Seleccionar Usuarios:", + "LabelSelectUsers": "Seleccionar usuarios:", "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", "LabelSendNotificationToUsers": "Enviar la notificación a:", "LabelSerialNumber": "Número de serie", - "LabelSeriesRecordingPath": "Ruta para grabaciones de Series (Opcional):", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", "LabelServerHost": "Servidor:", "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", - "LabelSimultaneousConnectionLimit": "Limite de transmisiones simultaneas:", - "LabelSkin": "Piel:", + "LabelSimultaneousConnectionLimit": "Límite de transmisiones simultáneas:", + "LabelSkin": "Apariencia:", "LabelSkipBackLength": "Longitud de salto hacia atrás:", "LabelSkipForwardLength": "Longitud de salto hacia adelante:", - "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el lenguaje de descarga", - "LabelSkipIfAudioTrackPresentHelp": "Desactive esto para asegurar que todos los videos tengan subtítulos, independientemente del lenguaje del audio.", - "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos embebidos", - "LabelSkipIfGraphicalSubsPresentHelp": "Manteniendo las versiones de texto de los subtitulos resultara una entrega mas eficiente de los mismos y disminuirá las posibilidades de que un video sea transcodificado.", - "LabelSonyAggregationFlags": "Banderas de agregación Sony:", + "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", + "LabelSkipIfAudioTrackPresentHelp": "Desactiva esto para asegurarte de que todos los videos tengan subtítulos, independientemente del idioma del audio.", + "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos incrustados", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos resultará en una entrega más eficiente y disminuirá las posibilidades de que un video sea transcodificado.", + "LabelSonyAggregationFlags": "Marcas de agregación Sony:", "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el namespace urn:schemas-sonycom:av.", "LabelSortBy": "Ordenar por:", - "LabelSortOrder": "Modo de ordenar:", - "LabelSortTitle": "Titulo para ordenar:", - "LabelSoundEffects": "Efectos de Sonido:", + "LabelSortOrder": "Clasificar ordenado:", + "LabelSortTitle": "Título para ordenar:", + "LabelSoundEffects": "Efectos de sonido:", "LabelSource": "Fuente:", - "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de Especiales:", - "LabelSportsCategories": "Categorías de Deportes:", + "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de especiales:", + "LabelSportsCategories": "Categorías de deportes:", "LabelStartWhenPossible": "Iniciar cuando sea posible:", "LabelStatus": "Estado:", "LabelStopWhenPossible": "Detener cuando sea posible:", "LabelStopping": "Deteniendo", - "LabelSubtitleDownloaders": "Recolectores de Subtitulos:", + "LabelSubtitleDownloaders": "Recolectores de subtítulos:", "LabelSubtitleFormatHelp": "Ejemplo: srt", "LabelSubtitlePlaybackMode": "Modo de subtítulo:", "LabelSubtitles": "Subtítulos", - "LabelSupportedMediaTypes": "Tipos de Medios Soportados:", + "LabelSupportedMediaTypes": "Tipos de medios soportados:", "LabelTVHomeScreen": "Modo de pantalla de TV:", "LabelTag": "Etiqueta:", "LabelTagline": "Eslogan:", "LabelTextBackgroundColor": "Color de fondo para el texto:", - "LabelTextColor": "Color de texto:", - "LabelTextSize": "Tamaño de texto:", + "LabelTextColor": "Color del texto:", + "LabelTextSize": "Tamaño del texto:", "LabelTheme": "Tema:", "LabelTime": "Hora:", - "LabelTimeLimitHours": "Límite de Tiempo (horas):", - "LabelTitle": "Titulo:", - "LabelTrackNumber": "Número de Pista:", - "LabelTranscodingAudioCodec": "Codec de audio:", + "LabelTimeLimitHours": "Límite de tiempo (horas):", + "LabelTitle": "Título:", + "LabelTrackNumber": "Número de pista:", + "LabelTranscodingAudioCodec": "Códec de audio:", "LabelTranscodingContainer": "Contenedor:", - "LabelTranscodingTempPathHelp": "Especifique una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deje en blanco para utilizar el predeterminado del servidor.", - "LabelTranscodingThreadCount": "Conteo de hilos de transcodificación:", - "LabelTranscodingThreadCountHelp": "Seleccione el número máximo de hilos a utilizar al transcodificar. La reducción del número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", - "LabelTranscodingVideoCodec": "Codec de video:", - "LabelTriggerType": "Tipo de Evento:", - "LabelTunerIpAddress": "Dirección IP del Sintonizador:", + "LabelTranscodingTempPathHelp": "Especifica una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deja en blanco para utilizar el predeterminado del servidor.", + "LabelTranscodingThreadCount": "Conteo de hilos de la transcodificación:", + "LabelTranscodingThreadCountHelp": "Selecciona el número máximo de hilos a utilizar al transcodificar. Reducir el número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", + "LabelTranscodingVideoCodec": "Códec de video:", + "LabelTriggerType": "Tipo de disparador:", + "LabelTunerIpAddress": "Dirección IP del sintonizador:", "LabelTunerType": "Tipo de sintonizador:", "LabelType": "Tipo:", - "LabelTypeMetadataDownloaders": "{0} recolectores de metadatos:", + "LabelTypeMetadataDownloaders": "Recolectores de metadatos para {0}:", "LabelTypeText": "Texto", - "LabelUseNotificationServices": "Emplear los siguientes servicios:", + "LabelUseNotificationServices": "Usar los siguientes servicios:", "LabelUser": "Usuario:", "LabelUserAgent": "Agente de usuario:", - "LabelUserLibrary": "Biblioteca del Usuario:", - "LabelUserLibraryHelp": "Seleccione la biblioteca de usuario que se mostrara en el dispositivo. Deje vacío para heredar la configuración por defecto.", + "LabelUserLibrary": "Biblioteca de usuario:", + "LabelUserLibraryHelp": "Selecciona la biblioteca de usuario que se mostrará en el dispositivo. Déjalo vacío para heredar la configuración por defecto.", "LabelUserRemoteClientBitrateLimitHelp": "Sobrescribe el valor global predeterminado establecido en la configuración de reproducción del servidor.", - "LabelUsername": "Nombre Usuario:", + "LabelUsername": "Nombre de usuario:", "LabelVaapiDevice": "Dispositivo VA API:", - "LabelVaapiDeviceHelp": "Este es un nodo de renderizado que es usado para aceleración por hardware.", + "LabelVaapiDeviceHelp": "Este es el nodo de renderizado que es usado para la aceleración por hardware.", "LabelValue": "Valor:", "LabelVersion": "Versión:", "LabelVersionInstalled": "{0} instalado", "LabelVersionNumber": "Versión {0}", - "LabelXDlnaCap": "X-DLNA cap:", + "LabelXDlnaCap": "X-DLNA límite:", "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", - "LabelXDlnaDoc": "X-DLNA doc:", + "LabelXDlnaDoc": "Documento X-DLNA:", "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.", "LabelYear": "Año:", - "LabelYourFirstName": "Su nombre:", - "LabelYoureDone": "¡Ha terminado!", - "LabelZipCode": "Código Postal:", - "LabelffmpegPath": "Ruta FFmpeg:", - "LabelffmpegPathHelp": "La ruta hacia el archivo de aplicación de ffmpeg, o la carpeta que contenga ffmpeg.", - "LanNetworksHelp": "Lista separada por comas de direcciones IP/mascaras de subred para las redes que serán consideradas como locales al enforzar restricciones de ancho de banda. Si se establece, todas las demás direcciones IP serán consideradas como redes externas y estarán sujetas a restricciones de ancho de banda. Si se deja en blanco, sólo la subred del servidor será considerada como red local.", + "LabelYourFirstName": "Tu nombre:", + "LabelYoureDone": "¡Has terminado!", + "LabelZipCode": "Código postal:", + "LabelffmpegPath": "Ruta del FFmpeg:", + "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", "Large": "Grande", - "LatestFromLibrary": "Más recientes - {0}", - "LearnHowYouCanContribute": "Aprenda como puede contribuír.", - "LibraryAccessHelp": "Seleccione las bibliotecas que desea compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", + "LatestFromLibrary": "Últimas - {0}", + "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", "Like": "Me gusta", "LinksValue": "Enlaces: {0}", "List": "Lista", - "Live": "En Vivo", - "LiveBroadcasts": "Transmisiones en vivo", - "LiveTV": "TV en Vivo", - "Logo": "Logotipo", + "Live": "En vivo", + "LiveBroadcasts": "Emisiones en vivo", + "LiveTV": "TV en vivo", + "Logo": "Logo", "ManageLibrary": "Administrar biblioteca", - "ManageRecording": "Administrar grabaciones", - "MapChannels": "Mapear Canales", - "MarkPlayed": "Marcar como Reproducido", - "MarkUnplayed": "Marcar como No Reproducido", - "MaxParentalRatingHelp": "El contenido con clasificación parental superior se ocultará para este usuario.", + "ManageRecording": "Administrar grabación", + "MapChannels": "Mapear canales", + "MarkPlayed": "Marcar como reproducido", + "MarkUnplayed": "Marcar como no reproducido", + "MaxParentalRatingHelp": "El contenido con una calificación más alta será ocultado a este usuario.", "MediaInfoAnamorphic": "Anamórfico", "MediaInfoAspectRatio": "Relación de aspecto", "MediaInfoBitDepth": "Profundidad de bit", - "MediaInfoBitrate": "Tasa de bits", + "MediaInfoBitrate": "Velocidad de bits", "MediaInfoChannels": "Canales", - "MediaInfoCodecTag": "Etiqueta de Codec", + "MediaInfoCodecTag": "Etiqueta de códec", "MediaInfoContainer": "Contenedor", "MediaInfoDefault": "Por defecto", "MediaInfoExternal": "Externo", "MediaInfoForced": "Forzado", - "MediaInfoFramerate": "Velocidad de cuadro", + "MediaInfoFramerate": "Velocidad de cuadros", "MediaInfoInterlaced": "Entrelazado", - "MediaInfoLanguage": "Lenguaje", + "MediaInfoLanguage": "Idioma", "MediaInfoLayout": "Esquema", "MediaInfoLevel": "Nivel", "MediaInfoPath": "Ruta", "MediaInfoPixelFormat": "Formato de pixel", - "MediaInfoProfile": "Perfíl", + "MediaInfoProfile": "Perfil", "MediaInfoRefFrames": "Tramas de referencia", "MediaInfoResolution": "Resolución", "MediaInfoSampleRate": "Tasa de muestreo", "MediaInfoSize": "Tamaño", "MediaInfoTimestamp": "Fecha y hora", - "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que esta reproduciendo el medio.", + "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que está reproduciendo el medio.", "Menu": "Menú", "MessageAlreadyInstalled": "Esta versión ya se encuentra instalada.", - "MessageAreYouSureDeleteSubtitles": "¿Está seguro de querer eliminar este archivo de subtitulos?", - "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Está seguro de querer eliminar esta carpeta de medios?", - "MessageConfirmDeleteGuideProvider": "¿Está seguro de querer eliminar este proveedor de guía?", - "MessageConfirmDeleteTunerDevice": "¿Está seguro de querer eliminar este dispositivo?", - "MessageConfirmProfileDeletion": "¿Está seguro de querer eliminar este perfil?", - "MessageConfirmRecordingCancellation": "¿cancelar esta grabación?", - "MessageConfirmRemoveMediaLocation": "¿Está seguro de querer eliminar esta ubicación?", - "MessageConfirmRestart": "¿Esta seguro de que desea reiniciar el Servidor Jellyfin?", - "MessageConfirmRevokeApiKey": "¿Esta seguro de querer revocar esta clave api? La conexión de la aplicación con el Servidor Jellyfin sera terminada abruptamente.", - "MessageConfirmShutdown": "¿Está seguro de que desea apagar el servidor?", - "MessageContactAdminToResetPassword": "Por favor contacte a su administrador para restablecer su contraseña.", + "MessageAreYouSureDeleteSubtitles": "¿Estás seguro de querer eliminar este subtítulo?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Estás seguro de querer remover esta carpeta de medios?", + "MessageConfirmDeleteGuideProvider": "¿Estás seguro de querer eliminar este proveedor de guía?", + "MessageConfirmDeleteTunerDevice": "¿Estás seguro de querer eliminar este dispositivo?", + "MessageConfirmProfileDeletion": "¿Estás seguro de querer eliminar este perfil?", + "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer remover esta ubicación?", + "MessageConfirmRestart": "¿Estás seguro de que deseas reiniciar el servidor Jellyfin?", + "MessageConfirmRevokeApiKey": "¿Estás seguro de querer revocar esta clave API? La conexión de la aplicación con el servidor Jellyfin será terminada abruptamente.", + "MessageConfirmShutdown": "¿Estás seguro de que deseas apagar el servidor?", + "MessageContactAdminToResetPassword": "Por favor, contacta a tu administrador para restablecer tu contraseña.", "MessageCreateAccountAt": "Crear una cuenta en {0}", - "MessageDeleteTaskTrigger": "¿Está seguro de querer eliminar este disparador de tarea?", - "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesite configurar el almacenamiento dentro de su \"FreeNAS Jail\" de manera que permita a Jellyfin accesarlo.", - "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el botón de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.", - "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, OpenSuse o Ubuntu, debe conceder al usuario del servicio al menos permisos de lectura a sus ubicaciones de almacenamiento.", - "MessageDownloadQueued": "Descargar cola.", - "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente mas largos.", - "MessageFileReadError": "Hubo un error al leer el archivo. Por favor intente de nuevo.", + "MessageDeleteTaskTrigger": "¿Estás seguro de querer eliminar este disparador de tarea?", + "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesites configurar el almacenamiento dentro de tu «FreeNAS Jail» de manera que permita a Jellyfin accederlo.", + "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el botón de Red no pueda localizar sus dispositivos. Por ejemplo, {0} o {1}.", + "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE o Ubuntu, debes conceder al usuario del servicio al menos permisos de lectura a tus ubicaciones de almacenamiento.", + "MessageDownloadQueued": "Descarga puesta en la cola.", + "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente más largos.", + "MessageFileReadError": "Hubo un error al leer el archivo. Por favor, intenta de nuevo.", "MessageForgotPasswordFileCreated": "El siguiente archivo fue creado en tu servidor y contiene instrucciones de como proceder:", - "MessageForgotPasswordInNetworkRequired": "Por favor intente de nuevo dentro de su red de hogar para iniciar el proceso de restablecimiento de contraseña.", - "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que desea usarlo.", - "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtelo de nuevo.", - "MessageInvalidUser": "Nombre del usuario o contraseña inválidos. Por favor intenta de nuevo.", - "MessageItemSaved": "Ítem guardado.", - "MessageItemsAdded": "Ítems agregados.", + "MessageForgotPasswordInNetworkRequired": "Por favor, intenta de nuevo dentro de tu red local para iniciar el proceso de restablecimiento de contraseña.", + "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que deseas usarlo.", + "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtalo de nuevo.", + "MessageInvalidUser": "Nombre de usuario o contraseña inválidos. Por favor, intenta de nuevo.", + "MessageItemSaved": "Elemento guardado.", + "MessageItemsAdded": "Elementos agregados.", "MessageLeaveEmptyToInherit": "Dejar vacío para heredar la configuración de un elemento superior o del valor predeterminado global.", "MessageNoAvailablePlugins": "No hay complementos disponibles.", - "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y regresa para ver tus recomendaciones.", - "MessageNoPluginsInstalled": "No tienes extensiones instaladas.", - "MessageNoTrailersFound": "No se encontraron tráilers. Instale el canal de tráilers para mejorar su experiencia con películas al agregar una biblioteca de tráilers desde el Internet.", + "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y luego regresa para ver tus recomendaciones.", + "MessageNoPluginsInstalled": "No tienes complementos instalados.", + "MessageNoTrailersFound": "No se encontraron trailers. Instala el canal de trailers para mejorar tu experiencia con películas al agregar una biblioteca de trailers desde Internet.", "MessageNothingHere": "Nada aquí.", "MessagePasswordResetForUsers": "Los siguientes usuarios han restablecido sus contraseñas. Ahora pueden iniciar sesión con los códigos PIN que se usaron para realizar el restablecimiento.", - "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Póngase en contacto con el administrador del servidor para obtener más información.", - "MessagePleaseEnsureInternetMetadata": "Por favor asegúrese que la descarga de metadatos de internet esta habilitada.", - "MessagePleaseWait": "Espere por favor. Esto podría tomar un minuto.", - "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor inicie sesión en su servidor local directamente.", - "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar su experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, conozca el impacto que pueden ocasionar en su Servidor Jellyfin, tales como exploración de la biblioteca que puede tomar más tiempo, procesamiento en segundo plano adicional y estabilidad del sistema reducida.", - "MessageReenableUser": "Vea abajo para volverlo a habilitar", - "MessageSettingsSaved": "Configuración guardada.", - "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se eliminarán de su biblioteca:", - "MessageUnableToConnectToServer": "No es posible conectarse al servidor seleccionado en este momento. Por favor asegúrese de que se encuentra en ejecución e inténtelo nuevamente.", - "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utilice el administrador de metadatos para establecer los tipos de contenido para las sub-carpetas.", - "MessageYouHaveVersionInstalled": "Actualmente cuenta con la vesión {0} instalada.", + "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Por favor, contacta al administrador del servidor para obtener más información.", + "MessagePleaseEnsureInternetMetadata": "Por favor, asegúrate de que la descarga de metadatos de Internet está habilitada.", + "MessagePleaseWait": "Por favor, espera. Esto podría tomar un minuto.", + "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor, inicia sesión en tu servidor local directamente.", + "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar tu experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor Jellyfin, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", + "MessageReenableUser": "Ver abajo para volver a habilitar", + "MessageSettingsSaved": "Configuraciones guardadas.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se removerán de tu biblioteca:", + "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", + "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utiliza el administrador de metadatos para establecer los tipos de contenido para las subcarpetas.", + "MessageYouHaveVersionInstalled": "Actualmente cuentas con la versión {0} instalada.", "Metadata": "Metadatos", - "MetadataManager": "Administrador de Metadatos", - "MetadataSettingChangeHelp": "Cambiar los ajustes de metadata afectará al contenido nuevo añadido a partir de ahora. Para actualizar el contenido existente, abra la pantalla de detalles y haga clic en actualizar, o realice actualizaciones masivas usando el administrador de metadata.", - "MinutesAfter": "minutos despues", - "MinutesBefore": "Minutos antes", + "MetadataManager": "Administrador de metadatos", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MinutesAfter": "minutos después", + "MinutesBefore": "minutos antes", "Mobile": "Móvil", "Monday": "Lunes", - "MoreFromValue": "Mas de {0}", + "MoreFromValue": "Más de {0}", "MoreUsersCanBeAddedLater": "Más usuarios pueden ser añadidos más tarde desde el panel de control.", "MoveLeft": "Mover a la izquierda", "MoveRight": "Mover a la derecha", - "MovieLibraryHelp": "Revisar la {0}Guía para nombrar películas{1}.", + "MovieLibraryHelp": "Revisar la {0}guía de nombrado de películas{1}.", "Movies": "Películas", "Mute": "Silenciar", - "MySubtitles": "Mis Subtitulos", + "MySubtitles": "Mis subtítulos", "Name": "Nombre", "Never": "Nunca", - "NewCollection": "Nueva Colección", - "NewCollectionHelp": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", + "NewCollection": "Nueva colección", + "NewCollectionHelp": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", "NewCollectionNameExample": "Ejemplo: Colección Guerra de las Galaxias", "NewEpisodes": "Episodios nuevos", "NewEpisodesOnly": "Solo episodios nuevos", "News": "Noticias", "Next": "Siguiente", - "NextUp": "A Continuación", - "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierre este cuadro de dialogo e introduzca a la información del dispositivo manualmente.", + "NextUp": "A continuación", + "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierra este diálogo e introduce la información del dispositivo manualmente.", "NoNextUpItemsMessage": "No se encontró nada. ¡Comienza a ver tus programas!", - "NoPluginConfigurationMessage": "El complemento no tiene configuraciones disponibles.", + "NoPluginConfigurationMessage": "Este complemento no tiene configuraciones disponibles.", "NoSubtitleSearchResultsFound": "No se encontraron resultados.", - "NoSubtitles": "Sin Subtitulos", - "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pero pueden ser activados manualmente durante la reproducción.", + "NoSubtitles": "Ninguno", + "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pueden ser activados manualmente durante la reproducción.", "None": "Ninguno", "NumLocationsValue": "{0} carpetas", "Off": "Apagar", "OneChannel": "Un canal", - "OnlyForcedSubtitles": "Únicamente subtítulos forzados", - "OnlyForcedSubtitlesHelp": "Se cargarán únicamente subtítulos marcados como forzados.", - "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB, etc.)", + "OnlyForcedSubtitles": "Solo forzados", + "OnlyForcedSubtitlesHelp": "Solo se cargarán subtítulos marcados como forzados.", + "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administradores", "OptionAlbum": "Álbum", - "OptionAlbumArtist": "Artista del Álbum", + "OptionAlbumArtist": "Artista del álbum", "OptionAllUsers": "Todos los usuarios", - "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera de transcodificación", - "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en Vivo", + "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera transcodificación", + "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en vivo", "OptionAllowContentDownloading": "Permitir descarga y sincronización de medios", "OptionAllowLinkSharing": "Permitir compartir medios en redes sociales", - "OptionAllowLinkSharingHelp": "Solo son compartidas paginas web que contengan información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Son compartidos por un tiempo limitado y expiraran después de {0} días.", - "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en Vivo", + "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", + "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", "OptionAllowMediaPlayback": "Permitir reproducción de medios", - "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a transcodificacion podría causar fallas en la reproducción in las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", - "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se consideran compartidos hasta que un usuario comienza a controlarlos.", + "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", "OptionAllowSyncTranscoding": "Permitir descarga y sincronización de medios que requieran transcodificación", "OptionAllowUserToManageServer": "Permitir a este usuario administrar el servidor", - "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin re-codificar", + "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin recodificar", "OptionAllowVideoPlaybackTranscoding": "Permitir la reproducción de video que requiera de transcodificación", "OptionArtist": "Artista", "OptionAscending": "Ascendente", @@ -981,129 +980,129 @@ "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", "OptionBlockBooks": "Libros", - "OptionBlockChannelContent": "Contenido de Canales de Internet", - "OptionBlockLiveTvChannels": "Canales de TV en Vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet", + "OptionBlockLiveTvChannels": "Canales de TV en vivo", "OptionBlockMovies": "Películas", "OptionBlockMusic": "Música", - "OptionBlockTrailers": "Tráilers", + "OptionBlockTrailers": "Trailers", "OptionBlockTvShows": "Programas de TV", - "OptionCommunityRating": "Calificación de la Comunidad", + "OptionCommunityRating": "Calificación de la comunidad", "OptionContinuing": "Continuando", - "OptionCriticRating": "Calificación de la Crítica", + "OptionCriticRating": "Calificación de los críticos", "OptionCustomUsers": "Personalizado", "OptionDaily": "Diario", - "OptionDateAdded": "Fecha de Adición", - "OptionDateAddedFileTime": "Emplear fecha de creación del archivo", - "OptionDateAddedImportTime": "Emplear la fecha de escaneo en la biblioteca", - "OptionDatePlayed": "Fecha de Reproducción", + "OptionDateAdded": "Fecha de adición", + "OptionDateAddedFileTime": "Usar la fecha de creación del archivo", + "OptionDateAddedImportTime": "Usar la fecha de escaneo en la biblioteca", + "OptionDatePlayed": "Fecha de reproducción", "OptionDescending": "Descendente", "OptionDisableUser": "Desactivar este usuario", - "OptionDisableUserHelp": "Si está desactivado, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", "OptionDislikes": "No me gusta", - "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar carpetas de medios simples", - "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas multimedia. Esto puede ser útil si desea tener una vista de carpeta sencilla.", + "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", + "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", "OptionDownloadArtImage": "Arte", - "OptionDownloadBackImage": "Reverso", - "OptionDownloadBannerImage": "Cartél", + "OptionDownloadBackImage": "Parte trasera", + "OptionDownloadBannerImage": "Banner", "OptionDownloadBoxImage": "Caja", - "OptionDownloadDiscImage": "DIsco", - "OptionDownloadImagesInAdvance": "Descargar las imágenes desde el inicio", - "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes son descargadas solo cuando son solicitadas por alguna aplicación Jellyfin. Habilite esta opción para descargar todas las imágenes desde por adelantado, conforme se vayan agregando mas medios. Esto podría causar escaneos de bibliotecas mas largos.", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", "OptionDownloadMenuImage": "Menú", "OptionDownloadPrimaryImage": "Principal", "OptionDownloadThumbImage": "Miniatura", "OptionDvd": "DVD", - "OptionEmbedSubtitles": "Embeber dentro del contenedor", + "OptionEmbedSubtitles": "Incrustar dentro del contenedor", "OptionEnableAccessFromAllDevices": "Habilitar acceso desde todos los dispositivos", "OptionEnableAccessToAllChannels": "Habilitar acceso a todos los canales", - "OptionEnableAccessToAllLibraries": "Habilitar el acceso a todas las bibliotecas", + "OptionEnableAccessToAllLibraries": "Habilitar acceso a todas las bibliotecas", "OptionEnableExternalContentInSuggestions": "Habilitar contenido externo en las sugerencias", "OptionEnableExternalContentInSuggestionsHelp": "Permitir que los trailers de Internet y los programas de televisión en vivo se incluyan en el contenido sugerido.", "OptionEnableForAllTuners": "Habilitar para todos los dispositivos sintonizadores", - "OptionEnableM2tsMode": "Habilitar modo M2ts", - "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegs.", + "OptionEnableM2tsMode": "Habilitar modo M2TS", + "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegts.", "OptionEnded": "Finalizado", "OptionEquals": "Igual a", "OptionEstimateContentLength": "Estimar la duración del contenido cuando se transcodifica", "OptionEveryday": "Todos los días", "OptionExternallyDownloaded": "Descarga externa", - "OptionExtractChapterImage": "Habilitar extracción de imágenes de capitulo", + "OptionExtractChapterImage": "Habilitar la extracción de imágenes de los capítulos", "OptionFavorite": "Favoritos", "OptionFriday": "Viernes", - "OptionHasSpecialFeatures": "Características Especiales", + "OptionHasSpecialFeatures": "Características especiales", "OptionHasSubtitles": "Subtítulos", - "OptionHasThemeSong": "Canción del Tema", - "OptionHasThemeVideo": "Video del Tema", - "OptionHasTrailer": "Tráiler", - "OptionHideUser": "Ocultar este usuario en las pantallas de inicio de sesión", + "OptionHasThemeSong": "Canción temática", + "OptionHasThemeVideo": "Video temático", + "OptionHasTrailer": "Trailer", + "OptionHideUser": "Ocultar este usuario de las pantallas de inicio de sesión", "OptionHideUserFromLoginHelp": "Útil para cuentas privadas o de administrador ocultas. El usuario tendrá que iniciar sesión manualmente introduciendo su nombre de usuario y contraseña.", "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", "OptionHomeVideos": "Fotos", - "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de byte", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes seran honradas pero se ignorará el encabezado de rango de byte.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", "OptionImdbRating": "Calificación de IMDb", "OptionLikes": "Me gusta", "OptionMax": "Máximo", - "OptionMissingEpisode": "Episodios Faltantes", + "OptionMissingEpisode": "Episodios faltantes", "OptionMonday": "Lunes", "OptionNameSort": "Nombre", - "OptionNew": "Nuevo...", + "OptionNew": "Nuevo…", "OptionNone": "Ninguno", "OptionOnAppStartup": "Al iniciar la aplicación", "OptionOnInterval": "En un intervalo", - "OptionParentalRating": "Clasificación Parental", + "OptionParentalRating": "Clasificación parental", "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", - "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como \"object.container.storageFolder\" en lugar de un tipo más específico, como \"object.container.person.musicArtist\".", + "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", - "OptionPlainVideoItemsHelp": "Se se habilita, todos los videos serán representados en DIDL como \"object.item.videoItem\" en lugar de un tipo más específico, como \"object.item.videoItem.movie\".", - "OptionPlayCount": "Contador", + "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlayCount": "Contador de reproducciones", "OptionPlayed": "Reproducido", - "OptionPremiereDate": "Fecha de Estreno", + "OptionPremiereDate": "Fecha de estreno", "OptionProfilePhoto": "Foto", - "OptionProfileVideoAudio": "Audio del Video", - "OptionProtocolHls": "Transmisión en vivo por Http", - "OptionReleaseDate": "Fecha de Estreno", - "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta busqueda de bytes al transcodificar", - "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas por tiempo muy bien.", - "OptionRequirePerfectSubtitleMatch": "Solo descargar subtitulos que corresponden perfectamente para mis archivos de video", - "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrara los subtitulos para incluir solo aquellos que han sido verificados exactamente con su archivo de video. Desmarcar esta opción incrementara las probabilidades de que los subtitulos sean descargados, a la vez que incrementara las posibilidades de obtener subtitulos mal sincronizados o con texto incorrecto.", + "OptionProfileVideoAudio": "Audio del video", + "OptionProtocolHls": "Transmisión en vivo por HTTP", + "OptionReleaseDate": "Fecha de estreno", + "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta la búsqueda de bytes cuando se transcodifica", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas de tiempo muy bien.", + "OptionRequirePerfectSubtitleMatch": "Solo descargar subtítulos que coincidan perfectamente con mis archivos de video", + "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrará los subtítulos para incluir solo aquellos que han sido probados y verificados exactamente con tu archivo de video. Desmarcar esta opción incrementará las probabilidades de que se descarguen subtítulos, pero incrementará las posibilidades de obtener subtítulos mal sincronizados o con texto incorrecto.", "OptionResElement": "Elemento res", "OptionResumable": "Reanudable", "OptionRuntime": "Duración", "OptionSaturday": "Sábado", "OptionSaveMetadataAsHidden": "Guardar metadatos e imágenes como archivos ocultos", - "OptionSaveMetadataAsHiddenHelp": "Cambiar esto aplicará a nuevos metadatos alamacenados en lo sucesivo. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el Servidor Jellyfin.", + "OptionSaveMetadataAsHiddenHelp": "Cambiar esto se aplicará a los nuevos metadatos guardados en el futuro. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el servidor Jellyfin.", "OptionSpecialEpisode": "Especiales", "OptionSubstring": "Subcadena", "OptionSunday": "Domingo", "OptionThursday": "Jueves", - "OptionTrackName": "Nombre de la Pista", + "OptionTrackName": "Nombre de la pista", "OptionTuesday": "Martes", - "OptionTvdbRating": "Calificación de Tvdb", - "OptionUnairedEpisode": "Episodios no Emitidos", + "OptionTvdbRating": "Calificación de TVDB", + "OptionUnairedEpisode": "Episodios no emitidos", "OptionUnplayed": "No reproducido", - "OptionWakeFromSleep": "Al Despertar", + "OptionWakeFromSleep": "Despertar de la suspensión", "OptionWednesday": "Miércoles", - "OptionWeekdays": "Entre semana", + "OptionWeekdays": "Días de semana", "OptionWeekends": "Fines de semana", "OptionWeekly": "Semanal", "OriginalAirDateValue": "Fecha de emisión original: {0}", - "Overview": "Sinopsis", - "PackageInstallCancelled": "{0} instalación cancelada.", - "PackageInstallCompleted": "{0} instalación completada.", - "PackageInstallFailed": "{0} instalación fallida.", - "ParentalRating": "Clasificación Parental", - "PasswordMatchError": "La Contraseña y la confirmación de la contraseña deben coincidir.", + "Overview": "Resumen", + "PackageInstallCancelled": "Instalación cancelada de {0} (versión {1}).", + "PackageInstallCompleted": "Instalación completada de {0} (versión {1}).", + "PackageInstallFailed": "Instalación fallida de {0} (versión {1}).", + "ParentalRating": "Clasificación parental", + "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben coincidir.", "PasswordResetComplete": "La contraseña ha sido restablecida.", - "PasswordResetConfirmation": "¿Está seguro de querer restablecer la contraseña?", - "PasswordResetHeader": "Restablecer Contraseña", + "PasswordResetConfirmation": "¿Estás seguro de querer restablecer la contraseña?", + "PasswordResetHeader": "Restablecer contraseña", "PasswordSaved": "Contraseña guardada.", "People": "Personas", - "PerfectMatch": "Coincidencia exacta", + "PerfectMatch": "Coincidencia perfecta", "Photos": "Fotos", "PictureInPicture": "Pantalla en pantalla", - "PinCodeResetComplete": "El código pin ha sido restablecido.", - "PinCodeResetConfirmation": "¿Esta seguro de querer restablecer el código pin?", + "PinCodeResetComplete": "El código PIN ha sido restablecido.", + "PinCodeResetConfirmation": "¿Estás seguro de que quieres restablecer el código PIN?", "PlaceFavoriteChannelsAtBeginning": "Colocar canales favoritos al inicio", "Play": "Reproducir", "PlayAllFromHere": "Reproducir todos desde aquí", @@ -1113,100 +1112,99 @@ "PlayNextEpisodeAutomatically": "Reproducir el siguiente episodio automáticamente", "Played": "Reproducido", "Playlists": "Listas de reproducción", - "PleaseAddAtLeastOneFolder": "Por favor agregue al menos una carpeta a esta biblioteca dando clic al botón de Agregar.", - "PleaseConfirmPluginInstallation": "Por favor haga clic en OK para confirmar que ha leido lo que se encuentra arriba y que desea proceder con la instalación del complemento.", - "PleaseEnterNameOrId": "Por favor introduzca un nombre o id externo.", - "PleaseRestartServerName": "Por favor reinicie el Servidor Jellyfin - {0}.", - "PleaseSelectTwoItems": "Por favor selecciona al menos dos ítems.", - "PluginInstalledMessage": "El complemento ha sido instalado exitosamente. El Servidor Jellyfin necesitará reiniciarse para que los cambios surtan efecto.", - "PreferEmbeddedTitlesOverFileNames": "Prefererir titulos embebidos por encima de los nombres de archivo", - "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el titulo mostrado por defecto cuando no hay disponibles metadatos en internet o localmente.", + "PleaseAddAtLeastOneFolder": "Por favor, agrega al menos una carpeta a esta biblioteca dando clic al botón Agregar.", + "PleaseConfirmPluginInstallation": "Por favor, haz clic en OK para confirmar que has leído lo que se encuentra arriba y que deseas proceder con la instalación del complemento.", + "PleaseEnterNameOrId": "Por favor, introduce un nombre o ID externo.", + "PleaseRestartServerName": "Por favor, reinicia el servidor Jellyfin - {0}.", + "PleaseSelectTwoItems": "Por favor, selecciona al menos dos elementos.", + "PluginInstalledMessage": "El complemento ha sido instalado con éxito. El servidor Jellyfin necesitará ser reiniciado para que los cambios surtan efecto.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incrustados a los nombres de archivo", + "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el título mostrado por defecto cuando no hay disponibles metadatos en Internet o localmente.", "PreferredNotRequired": "Preferido, más no es requerido", - "Premiere": "Premier", + "Premiere": "Estreno", "Premieres": "Estrenos", "Previous": "Anterior", "Primary": "Principal", "Producer": "Productor", - "ProductionLocations": "Lugares de produccion", + "ProductionLocations": "Lugares de producción", "Programs": "Programas", "Quality": "Calidad", "QueueAllFromHere": "Encolar todos desde aquí", - "Raised": "Elevacion", + "Raised": "Elevado", "Rate": "Calificación", "RecentlyWatched": "Visto recientemente", - "RecommendationBecauseYouLike": "Porque te gustó {0}", + "RecommendationBecauseYouLike": "Porque te gusta {0}", "RecommendationBecauseYouWatched": "Porque viste {0}", "RecommendationDirectedBy": "Dirigido por {0}", "RecommendationStarring": "Protagonizado por {0}", "Record": "Grabar", - "RecordSeries": "Grabar Series", + "RecordSeries": "Grabar series", "RecordingCancelled": "Grabación cancelada.", - "RecordingPathChangeMessage": "Cambiar su carpeta de grabaciones no moverá sus grabaciones existentes del la antigua locación a la nueva. Necesita moverlas manualmente si lo desea.", + "RecordingPathChangeMessage": "Cambiar la carpeta de grabaciones no moverá las grabaciones existentes de la antigua ubicación a la nueva. Necesitan moverse manualmente si se desea.", "RecordingScheduled": "Grabación programada.", "Recordings": "Grabaciones", "Refresh": "Actualizar", - "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de internet que que estén activados en el panel de control de su Servidor de Jellyfin.", + "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", "RefreshMetadata": "Actualizar metadatos", - "RefreshQueued": "Actualización programada.", + "RefreshQueued": "Actualización puesta en la cola.", "ReleaseDate": "Fecha de estreno", - "RememberMe": "Recuerdame", + "RememberMe": "Recuérdame", "RemoveFromCollection": "Remover de la colección", - "RemoveFromPlaylist": "Eliminar de la lista de reproducción", + "RemoveFromPlaylist": "Remover de la lista de reproducción", "Repeat": "Repetir", - "RepeatAll": "Repetir todas", + "RepeatAll": "Repetir todos", "RepeatEpisodes": "Repetir episodios", - "RepeatMode": "Modo de repeticion", + "RepeatMode": "Modo de repetición", "RepeatOne": "Repetir uno", - "ReplaceAllMetadata": "Remplazar todos los metadatos", + "ReplaceAllMetadata": "Reemplazar todos los metadatos", "ReplaceExistingImages": "Reemplazar imágenes existentes", "RequiredForAllRemoteConnections": "Requerido para todas las conexiones remotas", - "RestartPleaseWaitMessage": "Por favor espere mientras el Servidor Jellyfin cierra y reinicia. Este puede tomar un minuto o dos.", "ResumeAt": "Reanudar desde {0}", - "Rewind": "Regresar", + "Rewind": "Rebobinar", "RunAtStartup": "Ejecutar al iniciar", "Runtime": "Duración", "Saturday": "Sábado", "Save": "Guardar", - "SaveSubtitlesIntoMediaFolders": "Guardar subtitulos en las carpetas de medios", - "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar subtitulos junto al archivo de video permitirá administrarlos con mas facilidad.", - "ScanForNewAndUpdatedFiles": "Buscar archivos nuevos y actualizados", + "SaveSubtitlesIntoMediaFolders": "Guardar subtítulos en las carpetas de los medios", + "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar los subtítulos junto a los archivos de video permitirá administrarlos con más facilidad.", + "ScanForNewAndUpdatedFiles": "Escanear por archivos nuevos y actualizados", "ScanLibrary": "Escanear biblioteca", - "Schedule": "Programacion", + "Schedule": "Programación", "Screenshot": "Captura de pantalla", "Screenshots": "Capturas de pantalla", "Search": "Buscar", - "SearchForCollectionInternetMetadata": "Buscar en internet ilustraciones y metadatos", + "SearchForCollectionInternetMetadata": "Buscar en Internet por ilustraciones y metadatos", "SearchForMissingMetadata": "Buscar metadatos faltantes", - "SearchForSubtitles": "Buscar Subtitulos", + "SearchForSubtitles": "Buscar subtítulos", "SearchResults": "Resultados de la búsqueda", "SendMessage": "Enviar mensaje", "SeriesCancelled": "Serie cancelada.", - "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha transmisión, orden del dvd o por su numeración absoluta.", + "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha de emisión, orden del DVD o numeración absoluta.", "SeriesRecordingScheduled": "Grabación de series programadas.", - "SeriesSettings": "Configuración de la Serie", + "SeriesSettings": "Configuración de la serie", "SeriesYearToPresent": "{0} - Actualidad", - "ServerNameIsRestarting": "El Servidor Jellyfin - {0} se esta reiniciando.", - "ServerNameIsShuttingDown": "El Servidor Jellyfin - {0} se esta apagando.", - "ServerRestartNeededAfterPluginInstall": "El Servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", - "ServerUpdateNeeded": "Este Servidor Jellyfin necesita ser actualizado. Para descargar la ultima versión, por favor visite {0}", + "ServerNameIsRestarting": "El servidor Jellyfin - {0} se está reiniciando.", + "ServerNameIsShuttingDown": "El servidor Jellyfin - {0} se está apagando.", + "ServerRestartNeededAfterPluginInstall": "El servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", + "ServerUpdateNeeded": "Este servidor Jellyfin necesita ser actualizado. Para descargar la última versión, por favor, visita {0}", "Settings": "Configuración", "SettingsSaved": "Configuración guardada.", - "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión, si experimenta cualquier problema, recomendamos volver a los valores por defecto.", + "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión. Si experimentas cualquier problema, recomendamos volver a los valores por defecto.", "Share": "Compartir", "ShowAdvancedSettings": "Mostrar configuraciones avanzadas", "ShowIndicatorsFor": "Mostrar indicadores para:", - "ShowTitle": "Mostrar titulo", + "ShowTitle": "Mostrar título", "ShowYear": "Mostrar año", "Shows": "Programas", "Shuffle": "Aleatorio", - "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultaneas permitidas. Ingrese 0 para ilimitados.", + "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultáneas permitidas. Ingresa 0 para sin límite.", "SkipEpisodesAlreadyInMyLibrary": "No grabar episodios que ya se encuentran en mi biblioteca", "SkipEpisodesAlreadyInMyLibraryHelp": "Los episodios serán comparados usando el numero de temporada y de episodio, cuando estén disponibles.", "Small": "Pequeño", "SmallCaps": "Mayúsculas pequeñas", "Smaller": "Más pequeño", "Smart": "Inteligente", - "SmartSubtitlesHelp": "Los subtítulos que coincidan con el lenguaje preferido serán cargados cuando el audio se encuentre en un lenguaje extranjero.", + "SmartSubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados cuando el audio se encuentre en un idioma extranjero.", "Songs": "Canciones", "Sort": "Ordenar", "SortByValue": "Ordenar por {0}", @@ -1215,94 +1213,94 @@ "Sports": "Deportes", "StopRecording": "Detener grabación", "Studios": "Estudios", - "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción de Chromecast iniciada por este dispositivo.", - "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incrustan sus propios estilos.", - "SubtitleDownloadersHelp": "Habilite y priorice sus recolectores de subtitulos en orden de preferencia.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción en Chromecast iniciada por este dispositivo.", + "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incorporen sus propios estilos.", + "SubtitleDownloadersHelp": "Habilita y prioriza tus recolectores de subtítulos en orden de prioridad.", "Subtitles": "Subtítulos", "Suggestions": "Sugerencias", "Sunday": "Domingo", "Sync": "Sincronizar", - "SystemDlnaProfilesHelp": "Los perfiles del sistema son de sólo lectura. Los cambios a un perfíl de sistema serán guardados en un perfíl personalizado nuevo.", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de solo lectura. Los cambios a un perfil del sistema serán guardados en un nuevo perfil personalizado.", "TabAccess": "Acceso", "TabAdvanced": "Avanzado", - "TabAlbumArtists": "Artistas del Álbum", + "TabAlbumArtists": "Artistas del álbum", "TabAlbums": "Álbumes", "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canales", "TabCollections": "Colecciones", "TabContainers": "Contenedores", - "TabDashboard": "Panel de Control", + "TabDashboard": "Panel de control", "TabDevices": "Dispositivos", - "TabDirectPlay": "Reproducción Directa", - "TabDisplay": "Pantalla", + "TabDirectPlay": "Reproducción directa", + "TabDisplay": "Mostrar", "TabEpisodes": "Episodios", "TabFavorites": "Favoritos", "TabGenres": "Géneros", "TabGuide": "Guía", "TabLatest": "Recientes", - "TabLiveTV": "TV en Vivo", - "TabLogs": "Bitácoras", + "TabLiveTV": "TV en vivo", + "TabLogs": "Registros", "TabMetadata": "Metadatos", "TabMovies": "Películas", "TabMusic": "Música", - "TabMusicVideos": "Videos Musicales", - "TabMyPlugins": "Mis Complementos", + "TabMusicVideos": "Videos musicales", + "TabMyPlugins": "Mis complementos", "TabNetworks": "Cadenas", "TabNfoSettings": "Configuración de NFO", "TabNotifications": "Notificaciones", "TabOther": "Otros", - "TabParentalControl": "Control Parental", + "TabParentalControl": "Control parental", "TabPassword": "Contraseña", "TabPlayback": "Reproducción", - "TabPlaylist": "Lista de Reproducción", + "TabPlaylist": "Lista de reproducción", "TabPlaylists": "Listas de reproducción", "TabPlugins": "Complementos", - "TabProfile": "Perfíl", + "TabProfile": "Perfil", "TabProfiles": "Perfiles", "TabRecordings": "Grabaciones", "TabResponses": "Respuestas", "TabResumeSettings": "Reanudar", - "TabScheduledTasks": "Tareas Programadas", + "TabScheduledTasks": "Tareas programadas", "TabServer": "Servidor", "TabSettings": "Configuración", "TabShows": "Programas", "TabSongs": "Canciones", "TabStreaming": "Transmisión", "TabSuggestions": "Sugerencias", - "TabTrailers": "Tráilers", + "TabTrailers": "Trailers", "TabTranscoding": "Transcodificación", - "TabUpcoming": "Proximamente", + "TabUpcoming": "Próximamente", "TabUsers": "Usuarios", "Tags": "Etiquetas", "TagsValue": "Etiquetas: {0}", - "TellUsAboutYourself": "Díganos sobre usted", - "ThemeSongs": "Canciones de Tema", - "ThemeVideos": "Videos de Tema", - "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones solo afectan subtitulo de este dispositivo", - "ThisWizardWillGuideYou": "Este asistente le guiará a través del proceso de instalación. Para comenzar, por favor seleccione su lenguaje preferido.", + "TellUsAboutYourself": "Háblanos de ti", + "ThemeSongs": "Canciones temáticas", + "ThemeVideos": "Videos temáticos", + "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones afectan a los subtítulos en este dispositivo", + "ThisWizardWillGuideYou": "Este asistente te guiará a través del proceso de instalación. Para comenzar, por favor, selecciona tu idioma preferido.", "Thumb": "Miniatura", "Thursday": "Jueves", - "TitleHardwareAcceleration": "Aceleración por Hardware", - "TitleHostingSettings": "Configuraciones de Alojamiento", + "TitleHardwareAcceleration": "Aceleración por hardware", + "TitleHostingSettings": "Configuraciones de alojamiento", "TitlePlayback": "Reproducción", - "TrackCount": "{0} Pistas", - "Trailers": "Tráilers", + "TrackCount": "{0} pistas", + "Trailers": "Trailers", "Transcoding": "Transcodificando", "Tuesday": "Martes", - "TvLibraryHelp": "Vea la {0}Guía para nombrar series de TV{1}.", + "TvLibraryHelp": "Revisa la {0}guía de nombrado de series de TV{1}.", "Uniform": "Uniforme", - "UninstallPluginConfirmation": "¿Está seguro de querer desinstalar {0}?", - "UninstallPluginHeader": "Desinstalar Complemento", - "Unmute": "Activar Sonido", + "UninstallPluginConfirmation": "¿Estás seguro de querer desinstalar {0}?", + "UninstallPluginHeader": "Desinstalar complemento", + "Unmute": "Activar sonido", "Unplayed": "No reproducido", "Unrated": "Sin clasificar", "Up": "Arriba", "Upload": "Subir", - "UserAgentHelp": "Proporcionar un encabezado HTTP personalizado de agente de usuario.", - "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con configuraciones de pantalla detalladas, estado de reproducción y controles parentales.", + "UserAgentHelp": "Proporciona un encabezado HTTP de agente de usuario personalizado.", + "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con detalladas configuraciones de pantalla, estado de reproducción y controles parentales.", "ValueAlbumCount": "{0} álbumes", - "ValueAudioCodec": "Códec de Audio: {0}", + "ValueAudioCodec": "Códec de audio: {0}", "ValueCodec": "Códec: {0}", "ValueConditions": "Condiciones: {0}", "ValueContainer": "Contenedor: {0}", @@ -1321,23 +1319,23 @@ "ValueSpecialEpisodeName": "Especial - {0}", "ValueTimeLimitMultiHour": "Límite de tiempo: {0} horas", "ValueTimeLimitSingleHour": "Límite de tiempo: 1 hora", - "ValueVideoCodec": "Códec de Video: {0}", + "ValueVideoCodec": "Códec de video: {0}", "VideoRange": "Rango de video", - "ViewAlbum": "Ver album", + "ViewAlbum": "Ver álbum", "ViewArtist": "Ver artista", "ViewPlaybackInfo": "Ver información de reproducción", "Watched": "Visto", "Wednesday": "Miércoles", "WelcomeToProject": "¡Bienvenido a Jellyfin!", - "Whitelist": "Permitidos", - "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre su biblioteca multimedia. Revise algunas de nuestras aplicaciones, y luego haga clic en Finalizar para ver el Panel de Control.", + "Whitelist": "Lista blanca", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre tu biblioteca de medios. Revisa alguna de nuestras aplicaciones, y luego haz clic en Finalizar para ver el Panel de control.", "Writer": "Escritor", "XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.", - "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separe varios con un \"|\".", - "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrado como películas. Separe varios con un \"|\".", - "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrado como programas noticiosos. Separe varios con un \"|\".", - "XmlTvPathHelp": "Una ruta a un archivo XML de TV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Usted es responsable de crear y actualizar el fichero.", - "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separe varios con un \"|\".", + "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separa varios con un «|».", + "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separa varios con un «|».", + "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas de noticias. Separa varios con un «|».", + "XmlTvPathHelp": "Una ruta a un archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Tú eres responsable de crear y actualizar el archivo.", + "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separa varios con un «|».", "Yes": "Sí", "Yesterday": "Ayer", "Actor": "Actor", @@ -1345,16 +1343,16 @@ "Auto": "Auto", "ButtonInfo": "Info", "ButtonNo": "No", - "ButtonOk": "Ok", + "ButtonOk": "OK", "ButtonTrailer": "Trailer", - "AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "Director": "Director", "Extras": "Extras", "General": "General", "HeaderAdmin": "Administrador", - "HeaderApp": "App", + "HeaderApp": "Aplicación", "HeaderError": "Error", - "HeaderFavoriteMovies": "Peliculas favoritas", + "HeaderFavoriteMovies": "Películas favoritas", "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos", @@ -1362,7 +1360,6 @@ "HeaderFavoriteSongs": "Canciones favoritas", "HeaderFavoriteVideos": "Videos favoritos", "HeaderHome": "Inicio", - "HeaderRestartingServer": "Reiniciando servidor", "HeaderVideos": "Videos", "Horizontal": "Horizontal", "LabelAudio": "Audio", @@ -1373,27 +1370,27 @@ "LabelServerName": "Nombre del servidor:", "LabelTranscodePath": "Ruta de transcodificación:", "LabelTranscodes": "Transcodificaciones:", - "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario se bloquee:", + "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario sea bloqueado:", "DashboardVersionNumber": "Versión: {0}", "DashboardServerName": "Servidor: {0}", "DashboardOperatingSystem": "Sistema operativo: {0}", "DashboardArchitecture": "Arquitectura: {0}", "LabelVideo": "Video", - "LabelWeb": "Web: ", + "LabelWeb": "Web:", "LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor", "LaunchWebAppOnStartupHelp": "Abre el cliente web en su navegador web predeterminado cuando se inicia el servidor. Esto no ocurrirá cuando se utilice la función de reinicio del servidor.", - "LeaveBlankToNotSetAPassword": "Puede dejar este campo en blanco para no establecer ninguna contraseña.", + "LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.", "MediaInfoCodec": "Códec", "MediaInfoSoftware": "Software", "MediaInfoStreamTypeAudio": "Audio", "MediaInfoStreamTypeData": "Dato", "MediaInfoStreamTypeEmbeddedImage": "Imagen incrustada", - "MediaInfoStreamTypeSubtitle": "Subtitulo", + "MediaInfoStreamTypeSubtitle": "Subtítulo", "MediaInfoStreamTypeVideo": "Video", - "MessageImageFileTypeAllowed": "Solo se admite archivos JPEG y PNG.", - "MessageImageTypeNotSelected": "Seleccione un tipo de imagen en el menú desplegable.", - "MessageNoCollectionsAvailable": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haga clic en el botón + para comenzar a crear colecciones.", - "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático del servidor.", + "MessageImageFileTypeAllowed": "Solo son soportados archivos JPEG y PNG.", + "MessageImageTypeNotSelected": "Por favor, selecciona un tipo de imagen del menú desplegable.", + "MessageNoCollectionsAvailable": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haz clic en el botón + para comenzar a crear colecciones.", + "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático de servidores.", "MusicAlbum": "Álbum de música", "MusicArtist": "Artista musical", "MusicVideo": "Video musical", @@ -1407,15 +1404,15 @@ "OptionIsHD": "HD", "OptionIsSD": "SD", "OptionList": "Lista", - "OptionLoginAttemptsBeforeLockout": "Determina cuántos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", - "OptionLoginAttemptsBeforeLockoutHelp": "Un valor cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", + "OptionLoginAttemptsBeforeLockout": "Determina cuantos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", + "OptionLoginAttemptsBeforeLockoutHelp": "Un valor de cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", "OptionPoster": "Póster", "OptionPosterCard": "Ficha de póster", "OptionProfileAudio": "Audio", "OptionProfileVideo": "Video", "OptionProtocolHttp": "HTTP", "OptionRegex": "Expresión regular", - "PasswordResetProviderHelp": "Elija un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", + "PasswordResetProviderHelp": "Elige un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", "PlaybackData": "Datos de reproducción", "Series": "Series", "SubtitleOffset": "Desplazamiento de subtítulos", @@ -1427,30 +1424,157 @@ "ValueSeriesCount": "{0} series", "Vertical": "Vertical", "OptionThumb": "Miniatura", - "OptionThumbCard": "Pequeña miniatura", - "HeaderFavoriteBooks": "Libros Favoritos", + "OptionThumbCard": "Miniatura", + "HeaderFavoriteBooks": "Libros favoritos", "LabelPleaseRestart": "Los cambios tendrán efecto después de recargar manualmente el cliente web.", "LabelPlayMethod": "Método de reproducción:", "LabelPlayer": "Reproductor:", "LabelFolder": "Carpeta:", - "LabelBaseUrlHelp": "Puede añadir un subdirectorio personalizado aquí para acceder al servidor desde una URL más única.", + "LabelBaseUrlHelp": "Añade un subdirectorio personalizado a la URL del servidor. Por ejemplo: http://ejemplo.com/<urlbase>", "LabelBaseUrl": "URL base:", "LabelBitrate": "Velocidad de bits:", "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", "LabelAudioCodec": "Códec de audio:", "LabelAudioChannels": "Canales de audio:", "LabelAudioBitrate": "Velocidad de bits de audio:", - "LabelAudioBitDepth": "Profundidad de bit de audio:", + "LabelAudioBitDepth": "Profundidad de bits de audio:", "FetchingData": "Obteniendo datos adicionales", "CopyStreamURLSuccess": "URL copiada con éxito.", "CopyStreamURL": "Copiar la URL de la transmisión", "ButtonAddImage": "Agregar imagen", "TabNetworking": "Redes", - "MusicLibraryHelp": "Revisar la {0}Guía para nombrar música{1}.", + "MusicLibraryHelp": "Revisar la {0}guía de nombrado de música{1}.", "MoreMediaInfo": "Información multimedia", "LabelVideoCodec": "Códec de video:", - "LabelVideoBitrate": "Velocidad de bits:", + "LabelVideoBitrate": "Velocidad de bits de video:", "LabelTranscodingProgress": "Progreso de la transcodificación:", "LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:", - "LabelSize": "Tamaño:" + "LabelSize": "Tamaño:", + "SelectAdminUsername": "Por favor, selecciona un nombre de usuario para la cuenta de administrador.", + "LabelDroppedFrames": "Cuadros saltados:", + "CopyStreamURLError": "Hubo un error al copiar la URL.", + "ButtonSplit": "Dividir", + "WeeklyAt": "{0}s a las {1}", + "OnApplicationStartup": "Cuando se inicia la aplicación", + "EveryXHours": "Cada {0} horas", + "EveryHour": "Cada hora", + "EveryXMinutes": "Cada {0} minutos", + "OnWakeFromSleep": "Al despertar de la suspensión", + "DailyAt": "Diariamente a las {0}", + "LastSeen": "Ultima vez visto {0}", + "PersonRole": "como {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor, asegúrate de tener acceso de escritura e inténtalo de nuevo.", + "PathNotFound": "No se pudo encontrar la ruta. Por favor, asegúrate de que la ruta es válida e inténtalo de nuevo.", + "Track": "Pista", + "Season": "Temporada", + "ReleaseGroup": "Grupo que lo estrenó", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información del episodio incrustada a los nombres de archivo", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información del episodio desde los metadatos incrustados si están disponibles.", + "PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no está enviando un formato de medios compatible.", + "Person": "Persona", + "OtherArtist": "Otro artista", + "OptionRandom": "Aleatorio", + "OptionForceRemoteSourceTranscoding": "Forzar transcodificación de fuentes remotas (como TV en vivo)", + "NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}¿Quisieras crear una ahora?{1}", + "Movie": "Película", + "MessageConfirmAppExit": "¿Deseas salir?", + "LabelVideoResolution": "Resolución de video:", + "LabelStreamType": "Tipo de transmisión:", + "LabelPlayerDimensions": "Dimensiones del reproductor:", + "LabelCorruptedFrames": "Cuadros corruptos:", + "HeaderNavigation": "Navegación", + "HeaderFavoritePeople": "Personas favoritas", + "Episode": "Episodio", + "ClientSettings": "Configuración del cliente", + "BoxSet": "Colección", + "AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.", + "Artist": "Artista", + "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", + "AllowFfmpegThrottling": "Regular transcodificaciones", + "AlbumArtist": "Artista del álbum", + "Album": "Álbum", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "LabelDeinterlaceMethod": "Método de desentrelazado:", + "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", + "Filter": "Filtro", + "New": "Nuevo", + "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor, contacta al administrador del servidor para más información.", + "LabelLibraryPageSizeHelp": "Establece el número de elementos a mostrar en una página de biblioteca. Establece en 0 para deshabilitar el paginado.", + "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", + "HeaderFavoritePlaylists": "Listas de reproducción favoritas", + "ButtonTogglePlaylist": "Lista de reproducción", + "ButtonToggleContextMenu": "Más", + "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM de todas formas todo el contenido será intentado, incluyendo los títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos interactivos.", + "TabDVR": "DVR", + "SaveChanges": "Guardar cambios", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRequireHttps": "Requerir HTTPS", + "LabelNightly": "Nocturno", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "LabelEnableHttpsHelp": "Permite al servidor escuchar en el puerto HTTPS configurado. Un certificado válido también debe ser configurado para que esto tenga efecto.", + "LabelEnableHttps": "Habilitar HTTPS", + "HeaderServerAddressSettings": "Configuración de la dirección del servidor", + "HeaderRemoteAccessSettings": "Opciones de acceso remoto", + "HeaderHttpsSettings": "Opciones HTTPS", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lista de claves API actualmente habilitadas", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso que este usuario tiene en la función SyncPlay. SyncPlay permite sincronizar la reproducción con otros dispositivos.", + "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", + "MessageSyncPlayErrorMissingSession": "¡Fallo al activar SyncPlay! Falta la sesión.", + "MessageSyncPlayErrorNoActivePlayer": "No se ha encontrado ningún reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Se produjo un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "El acceso a este contenido está restringido.", + "MessageSyncPlayJoinGroupDenied": "Permiso requerido para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permiso requerido para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "Fallo al unirse al grupo porque éste no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Permiso de reproducción requerido.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Empieza a reproducir algo primero.", + "MessageSyncPlayGroupWait": "{0} está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Deshabilitado para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir al usuario unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir al usuario crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia de tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tiempo compensado respecto al servidor:", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "EnableDetailsBannerHelp": "Mostrar una imagen banner en la parte superior de la página de detalles del elemento.", + "EnableDetailsBanner": "Banner de detalles", + "ButtonSyncPlay": "SyncPlay", + "ButtonPlayer": "Reproductor", + "ButtonCast": "Elenco", + "EnableBlurhashHelp": "Imágenes que aún están siendo cargadas serán mostradas difuminadas", + "EnableBlurhash": "Habilitar imágenes de fondo difuminadas", + "TabRepositories": "Repositorios", + "ShowMore": "Mostrar más", + "ShowLess": "Mostrar menos", + "MessageNoGenresAvailable": "Habilitar algunos proveedores de metadatos para traer los generos desde internet.", + "MessageAddRepository": "Si desea agregar un repositorio, haz click en el botón junto al encabezado y completa la información requerida.", + "LabelRepositoryNameHelp": "Un nombre personalizado para distinguir este repositorio de otros añadidos a tu servidor.", + "LabelRepositoryName": "Nombre del repositorio", + "LabelRepositoryUrlHelp": "La ubicación del repositorio que desea agregar.", + "LabelRepositoryUrl": "URL del repositorio", + "HeaderNewRepository": "Nuevo repositorio", + "MessageNoRepositories": "Sin repositorios.", + "EnableFasterAnimationsHelp": "Usar animaciones y transiciones más rapidas", + "EnableFasterAnimations": "Animaciones más rápidas", + "EnableDecodingColorDepth10Vp9": "Habilitar la decodificación por hardware de 10 bit para VP9", + "EnableDecodingColorDepth10Hevc": "Habilitar la decodificación por hardware de 10 bit para HEVC", + "ClearQueue": "Limpiar cola", + "StopPlayback": "Detener reproducción", + "Writers": "Escritores", + "ViewAlbumArtist": "Ver el artista del album" } diff --git a/src/strings/es.json b/src/strings/es.json index 10eb8655e16..640a51d6348 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1,28 +1,28 @@ { - "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", + "AccessRestrictedTryAgainLater": "Actualmente el acceso está restringido. Por favor, inténtalo de nuevo más tarde.", "Add": "Añadir", - "AddItemToCollectionHelp": "Agregue elementos a las colecciones buscándolos y haciendo clic con el botón derecho o tocando los menús para agregarlos a una colección.", - "AddToCollection": "Añadir a la colección", - "AddToPlaylist": "Añadir a la lista de reproducción", + "AddItemToCollectionHelp": "Puedes añadir elementos a las colecciones buscándolos en tu biblioteca. Una vez hecho esto, abre el menú y selecciona 'Añadir a una colección'.", + "AddToCollection": "Añadir a una colección", + "AddToPlaylist": "Añadir a una lista de reproducción", "AddedOnValue": "Añadido {0}", "AdditionalNotificationServices": "Visite el catálogo de extensiones para instalar servicios de notificación adicionales.", "Albums": "Álbumes", "Alerts": "Alertas", "All": "Todo", "AllChannels": "Todos los canales", - "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX)", "AllEpisodes": "Todos los episodios", "AllLanguages": "Todos los idiomas", "AllLibraries": "Todas las bibliotecas", - "AllowHWTranscodingHelp": "Permite al sintonizador transcodificar secuencias en vivo. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllowHWTranscodingHelp": "Permite al sintonizador convertir el contenido directamente. Esto puede ayudar a reducir la potencia requerida por el servidor.", "AllowMediaConversion": "Permitir convertir los medios", "AllowMediaConversionHelp": "Concede o deniega el acceso a la función de conversión de medios.", "AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha", - "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los vídeos y enviarse en texto sin formato para ayudar a evitar la transcodificación del vídeo. En algunos sistemas, esto puede llevar mucho tiempo y hacer que la reproducción de vídeo se bloquee durante el proceso de extracción. Deshabilite esta opción para tener subtítulos incrustados grabados con transcodificación de video cuando no sean compatibles de forma nativa con el cliente.", + "AllowOnTheFlySubtitleExtractionHelp": "Cuando el cliente sea compatible, los subtítulos pueden extraerse durante la reproducción para evitar convertir el vídeo. Sin embargo, y en algunos servidores, esto puede llevar mucho tiempo y hacer que la reproducción tenga cortes durante el proceso. Deshabilita esta opción para grabar los subtítulos directamente en el vídeo cuando no sean compatibles de forma nativa con el cliente.", "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", "AllowRemoteAccessHelp": "Si no está activado, todas las conexiones remotas serán bloqueadas.", "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP o entradas de IP / máscara de red para redes a las que se les permitirá conectarse de forma remota. Si se deja en blanco, se permitirán todas las direcciones remotas.", - "AlwaysPlaySubtitles": "Mostrar subtítulos siempre", + "AlwaysPlaySubtitles": "Siempre mostrar subtítulos", "AlwaysPlaySubtitlesHelp": "Los subtítulos que concuerden con la preferencia de idioma se cargarán independientemente del idioma de audio.", "Art": "Arte", "Artists": "Artistas", @@ -157,7 +157,7 @@ "Disc": "Disco", "Disconnect": "Desconectar", "Dislike": "No me gusta", - "DisplayModeHelp": "Seleccione el tipo de pantalla que está ejecutando Jellyfin.", + "DisplayModeHelp": "Seleccione el estilo de diseño que desea en la Interfaz.", "DoNotRecord": "No grabar", "Down": "Abajo", "Download": "Descargar", @@ -183,7 +183,7 @@ "ErrorAddingListingsToSchedulesDirect": "Ha habido un error añadiendo la alineación a tu cuenta de Schedules Direct. Schedules Direct solo permite un determinado número de alineaciones por cuenta. Necesitarás iniciar sesión en la web de Schedules Direct y quitar otras listas de tu cuenta antes de proceder.", "ErrorAddingMediaPathToVirtualFolder": "Ha habido un error añadiendo la ruta de los medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a esa ubicación.", "ErrorAddingTunerDevice": "Ha habido un error añadiendo el dispositivo sintonizador. Por favor, asegúrate de que es accesible e inténtalo otra vez.", - "ErrorAddingXmlTvFile": "Hubo un un error accediendo el archivo XML. Por favor, asegurese que el archivo existe e inténtalo de nuevo.", + "ErrorAddingXmlTvFile": "Ha sucedido un error accediendo al archivo XML. Por favor, asegúrate que el archivo existe e inténtalo de nuevo.", "ErrorGettingTvLineups": "Ha habido un error descargando la programación de TV. Por favor, asegúrese que la información es correcta e inténtalo de nuevo.", "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización tiene que ser mayor que la de inicio.", "ErrorPleaseSelectLineup": "Por favor selecciona una alineación e inténtalo otra vez. Si no hay alineaciones disponibles, revisa que tu nombre de usuario, contraseña y código postal son correctos.", @@ -192,7 +192,7 @@ "ExitFullscreen": "Quitar pantalla completa", "ExtraLarge": "Extragrande", "ExtractChapterImagesHelp": "La extracción de imágenes de capítulos permitirá a los clientes mostrar gráficamente la selección de escenas. El proceso puede ser lento, intensivo en recursos y requerir varios gigabytes de espacio. Se ejecuta cuando se añaden vídeos y también como tarea programable por la noche. El horario es configurable en el área de tareas programadas. No se recomienda ejecutar esta tarea durante las horas pico de uso.", - "FFmpegSavePathNotFound": "No podemos localizar FFmpeg usando la ruta que has ingresado. FFprobe también es necesario y debe existir en la misma carpeta. Estos componentes normalmente se agrupan juntos en la misma descarga. Compruebe la ruta y vuelva a intentarlo.", + "FFmpegSavePathNotFound": "No se ha podido localizar ffmpeg ni ffprobe en la ruta especificada. Comprueba la ruta y vuelva a intentarlo.", "FastForward": "Avanzar rápido", "Favorite": "Favorito", "Favorites": "Favoritos", @@ -249,7 +249,7 @@ "HeaderChannels": "Canales", "HeaderChapterImages": "Imágenes de capítulos", "HeaderCodecProfile": "Perfil de códec", - "HeaderCodecProfileHelp": "Perfiles de códec indican las limitaciones de un dispositivo cuando se reproducen códecs específicos. Si se aplica una limitación entonces el medio se transcodificará, incluso si el códec está configurado para reproducción directa.", + "HeaderCodecProfileHelp": "Los perfiles de los códecs indican las limitaciones de un dispositivo cuando reproduce un códec en específico. Si lo que se va a reproducir no está dentro de estos límites (por ejemplo, bitrate demasiado alto), el contenido se convertirá, incluso si el códec está configurado para reproducción directa.", "HeaderConfigureRemoteAccess": "Configurar acceso remoto", "HeaderConfirmPluginInstallation": "Confirmar la instalación del complemento", "HeaderConfirmProfileDeletion": "Confirmar borrado del perfil", @@ -257,7 +257,7 @@ "HeaderConnectToServer": "Conectar al servidor", "HeaderConnectionFailure": "Fallo de conexión", "HeaderContainerProfile": "Perfil de contenedor", - "HeaderContainerProfileHelp": "Perfiles de códec indican las limitaciones de un dispositivo mientras reproduce formatos específicos. Si se aplica una limitación entonces el medio se transcodificará, incluso si el formato está configurado para reproducción directa.", + "HeaderContainerProfileHelp": "Los perfiles de los contenedores indican las limitaciones de un dispositivo cuando reproduce contenido en un formato en específico. Si lo que se va a reproducir no está dentro de estos límites, el contenedor del contenido se cambiará, incluso si el formato está configurado para reproducción directa.", "HeaderContinueListening": "Continuar escuchando", "HeaderContinueWatching": "Continuar viendo", "HeaderCustomDlnaProfiles": "Perfiles personalizados", @@ -310,7 +310,7 @@ "HeaderLatestMovies": "Últimas películas", "HeaderLatestMusic": "Última música", "HeaderLatestRecordings": "Últimas grabaciones", - "HeaderLibraries": "Blibliotecas", + "HeaderLibraries": "Bibliotecas", "HeaderLibraryAccess": "Acceso a la biblioteca", "HeaderLibraryFolders": "Carpetas de la biblioteca", "HeaderLibraryOrder": "Orden de la biblioteca", @@ -375,8 +375,8 @@ "HeaderSelectServer": "Selecionar servidor", "HeaderSelectServerCachePath": "Seleccione la ruta para el caché del servidor", "HeaderSelectServerCachePathHelp": "Navega o introduce la ruta para alojar los archivos caché del servidor. Tienes que tener permisos de escritura en esa carpeta.", - "HeaderSelectTranscodingPath": "Seleccione la ruta temporal del transcodificador", - "HeaderSelectTranscodingPathHelp": "Busque o escriba la ruta de acceso que se utilizará para la transcodificación de archivos temporales. La carpeta debe tener permiso de escritura.", + "HeaderSelectTranscodingPath": "Ruta para los archivos temporales de las conversiones", + "HeaderSelectTranscodingPathHelp": "Busca o escribe la ruta que se utilizará para guardar los archivos temporales que se generarán mientras se convierten los archivos. Jellyfin debe tener permisos de escritura en la carpeta.", "HeaderSendMessage": "Enviar mensaje", "HeaderSeries": "Series", "HeaderSeriesOptions": "Opciones de series", @@ -399,12 +399,12 @@ "HeaderTags": "Etiquetas", "HeaderTaskTriggers": "Tareas de activación", "HeaderThisUserIsCurrentlyDisabled": "Este usuario está desactivado", - "HeaderTranscodingProfile": "Perfil de transcodificación", - "HeaderTranscodingProfileHelp": "Añadir perfiles de transcodificación para indicar qué formatos se deben utilizar cuando se requiera transcodificación.", + "HeaderTranscodingProfile": "Parámetros de conversión", + "HeaderTranscodingProfileHelp": "Añade los diferentes parámetros de conversión para este dispositivo, de manera que el servidor convierta automáticamente el contenido en un formato compatible para él.", "HeaderTunerDevices": "Sintonizadores", "HeaderTuners": "Sintonizadores", "HeaderTypeImageFetchers": "{0} capturadores de imágenes", - "HeaderTypeText": "Entrar texto", + "HeaderTypeText": "Introducir texto", "HeaderUpcomingOnTV": "Próximamente en la televisión", "HeaderUploadImage": "Subir imagen", "HeaderUser": "Usuario", @@ -420,12 +420,12 @@ "Help": "Ayuda", "Hide": "Ocultar", "HideWatchedContentFromLatestMedia": "Esconder medios vistos de los medios más recientes", - "HttpsRequiresCert": "Para activar la conexión segura, necesitas un certificado SSL de confianza, como Let's Encrypt. De lo contrario, desactive las conexiones seguras", + "HttpsRequiresCert": "Para activar la conexión segura, necesitas un certificado SSL de confianza, como Let's Encrypt. De lo contrario, desactive las conexiones seguras.", "Identify": "Identificar", "Images": "Imágenes", "ImportFavoriteChannelsHelp": "Si está activado, sólo los canales guardados como favoritos en el sintonizador se importarán.", "ImportMissingEpisodesHelp": "Si está activada, la información sobre los episodios que faltan se importará en su base de datos Jellyfin y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", - "InstallingPackage": "Instalando {0}", + "InstallingPackage": "Instalando {0} (versión {1})", "InstantMix": "Mix instantáneo", "ItemCount": "Elementos {0}", "Items": "Elemento", @@ -449,7 +449,7 @@ "LabelAlbumArtPN": "Carátula del album PN:", "LabelAlbumArtists": "Artistas de los álbumes:", "LabelAll": "Todo", - "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "LabelAllowHWTranscoding": "Activar la conversión acelerada por hardware", "LabelAllowServerAutoRestart": "Permitir al servidor reiniciarse automáticamente para aplicar las actualizaciones", "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante periodos de reposo, cuando no haya usuarios activos.", "LabelAllowedRemoteAddresses": "Filtro de dirección IP remota:", @@ -511,7 +511,7 @@ "LabelEmbedAlbumArtDidl": "Incorporar la carátula del álbum en didl", "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener la carátula del álbum. Otros pueden fallar al reproducir con esta opción habilitada.", "LabelEnableAutomaticPortMap": "Habilitar asignación de puertos automático", - "LabelEnableAutomaticPortMapHelp": "UPnP permite la configuración del router para acceso externo de forma fácil y automática. Esto puede no funcionar en algunos modelos de routers. Los cambios no se aplicarán hasta que el servidor sea reiniciado.", + "LabelEnableAutomaticPortMapHelp": "Reenvia automáticamente los puertos públicos de su Router a los puertos locales de su servidor a través de UPnP. Es posible que esto no funcione con algunos modelos de Routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", "LabelEnableBlastAliveMessages": "Explotar mensajes en vivo", "LabelEnableBlastAliveMessagesHelp": "Active aquí si el servidor no es detectado correctamente por otros dispositivos UPnP en su red.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de detección de cliente (segundos)", @@ -620,8 +620,8 @@ "LabelMoviePrefix": "Prefijo de película:", "LabelMoviePrefixHelp": "Si se aplica un prefijo a títulos de películas, escríbalo para que el servidor pueda manejarlo correctamente.", "LabelMovieRecordingPath": "Ruta de grabaciones de películas (opcional):", - "LabelMusicStreamingTranscodingBitrate": "Tasa de bits de transcodificación de música:", - "LabelMusicStreamingTranscodingBitrateHelp": "Especifique una tasa de bits máxima cuando transmita música.", + "LabelMusicStreamingTranscodingBitrate": "Tasa de bits para la reproducción de música:", + "LabelMusicStreamingTranscodingBitrateHelp": "Tasa de bits máxima para la música.", "LabelName": "Nombre:", "LabelNewName": "Nuevo nombre:", "LabelNewPassword": "Nueva contraseña:", @@ -667,14 +667,14 @@ "LabelPublicHttpsPort": "Puerto público HTTPS:", "LabelPublicHttpsPortHelp": "Puerto público que debe ser enlazado al puerto local HTTPS.", "LabelReadHowYouCanContribute": "Aprenda cómo contribuir.", - "LabelReasonForTranscoding": "Motivo de la transcodificación:", + "LabelReasonForTranscoding": "Motivo por el que se realiza la conversión:", "LabelRecord": "Grabar:", "LabelRecordingPath": "Ruta de grabaciones por defecto:", "LabelRecordingPathHelp": "Especifica la ubicación por defecto para guardar las grabaciones. Si lo dejas vacío se usará la carpeta de datos del servidor.", "LabelRefreshMode": "Modo de refresco:", "LabelReleaseDate": "Fecha de lanzamiento:", "LabelRemoteClientBitrateLimit": "Límite de la transmisión de tasa de bits por internet (Mbps):", - "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de tasa de bits para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta que la que su conexión a Internet puede manejar. Esto puede ocasionar una mayor carga de la CPU en su servidor para transcodificar vídeos sobre la marcha a una tasa de bits más baja.", + "LabelRemoteClientBitrateLimitHelp": "Especifica el bitrate máximo para los dispositivos que se encuentren fuera de la red local. Esto es útil para permitir la reproducción del contenido que tengas con una tasa de bits muy alta cuando la conexión a internet de tu servidor o la del cliente no sea lo suficientemente rápida. Esto ocasionará mayor carga, ya que el contenido que supere esta tasa de bits se convertirá para que esté dentro del límite establecido.", "LabelRuntimeMinutes": "Tiempo de ejecución (minutos):", "LabelSaveLocalMetadata": "Guardar imágenes y etiquetas en las carpetas de medios", "LabelSaveLocalMetadataHelp": "Guardar imágenes y etiquetas directamente en las carpetas en las que estén los elementos hará que se puedan editar más fácilmente.", @@ -694,7 +694,7 @@ "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", "LabelSkipIfAudioTrackPresentHelp": "Desactive esta opción para asegurar que todos los vídeos tienen subtítulos, sin importar el idioma de audio.", "LabelSkipIfGraphicalSubsPresent": "Saltar si el vídeo tiene subtítulos integrados", - "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos dará lugar a una entrega más eficiente y a disminuir la probabilidad de transcodificación de vídeo.", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de los subtítulos en texto plano puede hacer que la reproducción sea más eficiente en un número mayor de dispositivos, evitando la conversión del vídeo.", "LabelSonyAggregationFlags": "Agregación de banderas Sony:", "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el espacio de nombre urn:schemas-sonycom:av.", "LabelSortTitle": "Clasificar por título:", @@ -721,9 +721,9 @@ "LabelTrackNumber": "Número de pista:", "LabelTranscodingAudioCodec": "Códec de audio:", "LabelTranscodingContainer": "Contenedor:", - "LabelTranscodingTempPathHelp": "Establece la ruta personaliza para la transcodificación de archivos servidos a los clientes. Dejar en blanco para usar la ruta por defecto del servidor.", - "LabelTranscodingThreadCount": "Cantidad de instancias de transcodificación:", - "LabelTranscodingThreadCountHelp": "Selecciona el número máximo de instancias de transcodificación. Reducirlas disminuirá el uso del procesador pero puede no convertirá lo suficientemente rápido para una reproducción fluida.", + "LabelTranscodingTempPathHelp": "Establece la carpeta que se usará para almacenar los archivos temporales de las conversiones. Déjalo en blanco para usar la ruta por defecto.", + "LabelTranscodingThreadCount": "Núcleos a utilizar durante la conversión:", + "LabelTranscodingThreadCountHelp": "Selecciona el número de núcleos a utilizar para la conversión. A menos núcleos, menor será el uso del procesador, pero puede que la conversión no vaya lo suficientemente rápido para una reproducción fluida.", "LabelTranscodingVideoCodec": "Códec de video:", "LabelTriggerType": "Tipo de evento:", "LabelTunerIpAddress": "IP del sintonizador:", @@ -749,8 +749,8 @@ "LabelYourFirstName": "Tu nombre:", "LabelYoureDone": "¡Ya está!", "LabelZipCode": "Código postal:", - "LabelffmpegPath": "Ruta de FFmpeg:", - "LabelffmpegPathHelp": "a ruta de acceso al archivo de la aplicación FFmpeg o la carpeta que contiene FFmpeg.", + "LabelffmpegPath": "Ruta de ffmpeg:", + "LabelffmpegPathHelp": "Carpeta donde se encuentra ffmpeg, el componente utilizado para realizar las conversiones. No la cambies a no ser que sepas lo que haces.", "LanNetworksHelp": "Lista de direcciones IP separadas por comas o entradas de dirección IP / máscara de red para redes que se considerarán en la red local al imponer restricciones de ancho de banda. Si se establece, todas las demás direcciones IP se considerarán en la red externa y estarán sujetas a las restricciones de ancho de banda externo. Si se deja en blanco, solo se considera que la subred del servidor está en la red local.", "Large": "Grande", "LatestFromLibrary": "Reciente en {0}", @@ -821,7 +821,7 @@ "MessageNoAvailablePlugins": "No hay extensiones disponibles.", "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles. Comience ver y calificar sus películas y vuelva para ver las recomendaciones.", "MessageNoPluginsInstalled": "No hay extensiones instaladas.", - "MessageNoTrailersFound": "No se han encontrado tráilers. Instala el canal de tráilers para mejorar su experiencia añadiendo una biblioteca de tráilers por internet.", + "MessageNoTrailersFound": "Instale el canal de tráilers para mejorar su experiencia cinematográfica agregando una biblioteca de tráilers de Internet.", "MessageNothingHere": "Nada aquí.", "MessagePasswordResetForUsers": "Se ha restablecido las contraseñas a los siguientes usuarios. Ahora pueden iniciar sesión con los códigos PIN que usaron para el restablecimiento.", "MessagePleaseEnsureInternetMetadata": "Asegúrate de que la descarga de etiquetas desde internet está activada.", @@ -869,26 +869,26 @@ "OneChannel": "Un canal", "OnlyForcedSubtitles": "Solo subtítulos forzados", "OnlyForcedSubtitlesHelp": "Solo se cargarán los subtítulos marcados como forzados.", - "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB, etc.)", + "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administradores", "OptionAlbum": "Álbum", "OptionAlbumArtist": "Artista de álbum", "OptionAllUsers": "Todos los usuarios", - "OptionAllowAudioPlaybackTranscoding": "Permitir reproducción de audio que requiere transcodificación", + "OptionAllowAudioPlaybackTranscoding": "Activar la conversión del audio", "OptionAllowBrowsingLiveTv": "Permitir acceso a la televisión en directo", "OptionAllowContentDownloading": "Permitir la descarga de medios", "OptionAllowLinkSharing": "Permitir compartir los medios en redes sociales", "OptionAllowLinkSharingHelp": "Sólo se comparten las páginas web con información de medios. Los archivos nunca se comparten públicamente. Lo compartido expirará después de {0} días.", "OptionAllowManageLiveTv": "Habilitar la administración de grabación de la televisión en directo", "OptionAllowMediaPlayback": "Permitir la reproducción de medios", - "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación puede causar fallos de reproducción en aplicaciones Jellyfin debido a formatos de medios no compatibles.", + "OptionAllowMediaPlaybackTranscodingHelp": "Con la conversión, el servidor se asegura que cualquier cliente es capaz de reproducir el contenido, sin importar su formato. Al desactivar la conversión de alguno de estos elementos, es posible que los vídeos no tengan audio o que, directamente, no se pueda reproducir el archivo por no ser compatible con el dispositivo. Para evitar problemas con la reproducción es mejor dejarlas todas por defecto.", "OptionAllowRemoteControlOthers": "Habilitar el control remoto de otros usuarios", "OptionAllowRemoteSharedDevices": "Habilitar el control remoto de otros equipos compartidos", "OptionAllowRemoteSharedDevicesHelp": "Los equipos DLNA son considerados compartidos hasta que un usuario empiece a controlarlos.", - "OptionAllowSyncTranscoding": "Permitir la descarga que requiera transcodificación", + "OptionAllowSyncTranscoding": "Permitir la conversión del contenido cuando se descargue o se sincronice", "OptionAllowUserToManageServer": "Permite a este usuario administrar el servidor", - "OptionAllowVideoPlaybackRemuxing": "Permitir la reproducción de vídeo que requiere conversión sin necesidad de volver a codificar", - "OptionAllowVideoPlaybackTranscoding": "Permitir reproducción de vídeo que requiere transcodificación", + "OptionAllowVideoPlaybackRemuxing": "Activar el cambio de contenedor para el contenido cuyo audio y vídeo es compatible, pero no lo es su contenedor", + "OptionAllowVideoPlaybackTranscoding": "Activar la conversión del vídeo", "OptionArtist": "Artista", "OptionAscending": "Ascendente", "OptionAutomaticallyGroupSeries": "Combinar automáticamente series que se distribuyen en varias carpetas", @@ -935,7 +935,7 @@ "OptionEnableM2tsModeHelp": "Activar modo M2TS cuando se codifique a MPEGTS.", "OptionEnded": "Finalizado", "OptionEquals": "Igual", - "OptionEstimateContentLength": "Estimar la longitud del contenido al transcodificar", + "OptionEstimateContentLength": "Estimar la longitud del contenido al convertirse", "OptionEveryday": "Todos los días", "OptionExternallyDownloaded": "Descarga externa", "OptionExtractChapterImage": "Habilitar la extracción de imágenes de los capítulos", @@ -949,7 +949,7 @@ "OptionHideUserFromLoginHelp": "Útil para privado o cuentas de administradores escondidos. El usuario tendrá que acceder entrando su nombre de usuario y contraseña manualmente.", "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", "OptionHomeVideos": "Fotos", - "OptionIgnoreTranscodeByteRangeRequests": "Ignorar las solicitudes de intervalo de bytes de transcodificación", + "OptionIgnoreTranscodeByteRangeRequests": "En las conversiones, ignorar las solicitudes de un intervalo específico de bytes", "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si está activado, estas solicitudes serán atendidas pero ignorarán el encabezado de intervalo de bytes.", "OptionImdbRating": "Valoración IMDb", "OptionLikes": "Me gusta", @@ -957,7 +957,7 @@ "OptionMissingEpisode": "Episodios que faltan", "OptionMonday": "Lunes", "OptionNameSort": "Nombre", - "OptionNew": "Nuevo...", + "OptionNew": "Nuevo…", "OptionNone": "Nada", "OptionOnAppStartup": "Al iniciar la aplicación", "OptionOnInterval": "En un intervalo", @@ -973,8 +973,8 @@ "OptionProfileVideoAudio": "Vídeo y audio", "OptionProtocolHls": "Emisión HTTP en directo", "OptionReleaseDate": "Fecha de lanzamiento", - "OptionReportByteRangeSeekingWhenTranscoding": "Indicar que el servidor soporta la búsqueda de byte al transcodificar", - "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es necesario para algunos dispositivos que no buscan el tiempo muy bien.", + "OptionReportByteRangeSeekingWhenTranscoding": "Indicar que el servidor soporta la búsqueda de byte al convertir", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es necesario para los dispositivos que no cambian entre los diferentes puntos de tiempo del contenido correctamente.", "OptionRequirePerfectSubtitleMatch": "Sólo descargar subtítulos que son una combinación perfecta para mis archivos de vídeo", "OptionRequirePerfectSubtitleMatchHelp": "Requerir una coincidencia perfecta filtra los subtítulos para incluir sólo aquellos que coinciden con el archivo de vídeo. Desmarcando esta opción, aumentará la probabilidad de que los subtítulos se descarguen, pero puede que el texto del subtítulo no coincida con el vídeo.", "OptionResElement": "Elemento res", @@ -998,10 +998,10 @@ "OptionWeekly": "Semanal", "OriginalAirDateValue": "Fecha de emisión original: {0}", "Overview": "Sinopsis", - "PackageInstallCancelled": "{0} instalación cancelada.", - "PackageInstallCompleted": "{0} instalación completada.", - "PackageInstallFailed": "{0} instalación fallida.", - "ParentalRating": "Calificación de los padres", + "PackageInstallCancelled": "{0} (versión {1}) instalación cancelada.", + "PackageInstallCompleted": "{0} (versión {1}) instalación completada.", + "PackageInstallFailed": "{0} (versión {1}) instalación fallida.", + "ParentalRating": "Clasificación parental", "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben de ser iguales.", "PasswordResetComplete": "La contraseña se ha restablecido.", "PasswordResetConfirmation": "¿Esta seguro de que desea restablecer la contraseña?", @@ -1093,7 +1093,7 @@ "Share": "Compartir", "ShowAdvancedSettings": "Mostrar opciones avanzadas", "ShowIndicatorsFor": "Mostrar indicaciones para:", - "Shows": "Series", + "Shows": "Mostrar", "Shuffle": "Mezclar", "SimultaneousConnectionLimitHelp": "Número máximo de transmisiones simultáneas permitidas. Pon 0 para no tener límite.", "SkipEpisodesAlreadyInMyLibrary": "No grabar episodios que ya están en mi biblioteca", @@ -1160,7 +1160,7 @@ "TabSongs": "Canciones", "TabStreaming": "Transmisión", "TabSuggestions": "Sugerencias", - "TabTranscoding": "Transcodificación", + "TabTranscoding": "Conversión", "TabUpcoming": "Próximos", "TabUsers": "Usuarios", "Tags": "Etiquetas", @@ -1173,7 +1173,7 @@ "TitleHostingSettings": "Configuración del alojamiento", "TitlePlayback": "Reproducción", "TrackCount": "{0} pistas", - "Transcoding": "Transcodificación", + "Transcoding": "Conversión", "Tuesday": "Martes", "TvLibraryHelp": "Revisar la {0}guía de nombres de los programas de TV{1}.", "UninstallPluginConfirmation": "¿Esta seguro que desea desinstalar {0}?", @@ -1205,7 +1205,7 @@ "ValueTimeLimitSingleHour": "Tiempo límite: 1 hora", "ValueVideoCodec": "Códec de video: {0}", "ViewAlbum": "Ver album", - "ViewArtist": "Ver artista", + "ViewAlbumArtist": "Ver artista del álbum", "ViewPlaybackInfo": "Ver información de la reproducción", "Watched": "Visto", "Wednesday": "Miércoles", @@ -1228,13 +1228,13 @@ "Aired": "Emitido", "AnyLanguage": "Cualquier idioma", "Anytime": "En cualquier momento", - "AroundTime": "Aproximadamente {0}", + "AroundTime": "Aproximadamente", "Ascending": "Ascendente", "Audio": "Audio", "Auto": "Automático", "AutoBasedOnLanguageSetting": "Automático (basado en la configuración de idioma)", "Banner": "Pancarta", - "BurnSubtitlesHelp": "Determina si el servidor debería incrustar los subtítulos al convertir el vídeo dependiendo del formato de los subtítulos. Evitar incrustar los subtítulos mejorará el rendimiento del servidor. Selecciona Auto para incrustar subtítulos basados en imágenes (VOBSUB, PGS, SUB/IDX, etc.) y algunos subtítulos de tipo ASS/SSA.", + "BurnSubtitlesHelp": "Determina si el servidor debe grabar los subtítulos en el vídeo al transcodificar. Desactivar esta opción puede mejorar el rendimiento. Seleccione 'Auto' para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX) y ciertos subtítulos ASS o SSA.", "ButtonInfo": "Información", "ChangingMetadataImageSettingsNewContent": "Los cambios a la configuración de descarga de etiquetas e imágenes sólo se aplicará al nuevo contenido que se añada a la biblioteca. Para aplicar los cambios a los elementos existentes necesitarás actualizar las etiquetas manualmente.", "ColorPrimaries": "Colores primarios", @@ -1248,14 +1248,14 @@ "Descending": "Descendiente", "DirectStreamHelp1": "El tipo de archivo (H.264, AC3, etc.) y la resolución son compatibles con el dispositivo, pero no el contenedor (mkv, avi, wmv, etc.). El vídeo será re-empaquetado al vuelo antes de transmitirlo al dispositivo.", "DirectStreamHelp2": "La transmisión directa del archivo usa muy poco procesamiento sin ninguna pérdida de calidad en el vídeo.", - "Director": "Director", + "Director": "Dirección de", "Directors": "Directores", "Display": "Mostrar", "DisplayInMyMedia": "Mostrar en la pantalla de inicio", "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio al igual que \"últimos\" y \"continuar viendo\"", "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios ausentes en las temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe ser habilitado para la biblioteca de TV en la configuración del servidor.", - "DropShadow": "Sombra", + "DropShadow": "Eliminar sombra", "EditMetadata": "Editar etiquetas", "EnableBackdrops": "Imágenes de fondo", "EnableBackdropsHelp": "Mostrar imágenes de fondo en algunas páginas mientras se explora la biblioteca.", @@ -1328,10 +1328,7 @@ "No": "No", "Normal": "Normal", "Off": "Apagado", - "Option2Player": "2+", "Option3D": "3D", - "Option3Player": "3+", - "Option4Player": "4+", "OptionActor": "Actor", "OptionAuto": "Automático", "OptionAutomatic": "Automático", @@ -1354,12 +1351,11 @@ "HeaderFavoriteSongs": "Canciones favoritas", "HeaderFavoriteVideos": "Vídeos favoritos", "HeaderHome": "Inicio", - "HeaderRestartingServer": "Reiniciando servidor", "LabelAuthProvider": "Proveedor de autenticación:", "LabelPasswordResetProvider": "Proveedor de restablecimiento de contraseña:", "LabelServerName": "Nombre del servidor:", - "LabelTranscodePath": "Ruta de transcodificación:", - "LabelTranscodes": "Transcodificaciones:", + "LabelTranscodePath": "Ruta para los archivos temporales de las conversiones:", + "LabelTranscodes": "Archivos temporales de las conversiones:", "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario sea bloqueado:", "DashboardVersionNumber": "Versión: {0}", "DashboardServerName": "Servidor: {0}", @@ -1400,12 +1396,11 @@ "Premiere": "Estreno", "Raised": "Elevación", "RefreshDialogHelp": "Las etiquetas se actualizan basándose en las configuraciones y los servicios de internet activados desde el panel de control de Jellyfin.", - "RestartPleaseWaitMessage": "Por favor, espera mientras el servidor Jellyfin se reinicia. Esto puede tardar un minuto o dos.", "RunAtStartup": "Ejecutar al iniciar", "Series": "Series", "SeriesDisplayOrderHelp": "Ordena los episodios por fecha de emisión, orden de DVD o número absoluto.", "ShowTitle": "Mostrar título", - "ShowYear": "Año del show", + "ShowYear": "Mostrar año", "SmallCaps": "Letras minúsculas", "Smaller": "Más pequeño", "Sort": "Ordenar", @@ -1435,14 +1430,14 @@ "MoreMediaInfo": "Información del archivo", "LabelVideoCodec": "Codec de video:", "LabelVideoBitrate": "Bitrade de video:", - "LabelTranscodingProgress": "Progreso de la transcodificación:", - "LabelTranscodingFramerate": "Velocidad de fotogramas de la transcodificación:", + "LabelTranscodingProgress": "Progreso de la conversión:", + "LabelTranscodingFramerate": "Velocidad de la conversión:", "LabelSize": "Tamaño:", "LabelPleaseRestart": "Los cambios surtirán efecto tras recargar manualmente el cliente web.", "LabelPlayMethod": "Método de reproducción:", "LabelPlayer": "Reproductor:", "LabelFolder": "Carpeta:", - "LabelBaseUrlHelp": "Puede agregar un subdirectorio personalizado aquí para acceder al servidor desde una URL única.", + "LabelBaseUrlHelp": "Puede agregar aquí un subdirectorio personalizado para el acceso al servidor a través de una URL única.", "LabelBaseUrl": "URL base:", "LabelBitrate": "Bitrate:", "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", @@ -1455,18 +1450,18 @@ "MusicLibraryHelp": "Revisar la {0}guía de nombres de música{1}.", "FetchingData": "Obteniendo datos adicionales", "ButtonAddImage": "Añadir imagen", - "HeaderFavoritePeople": "Gente Favorita", + "HeaderFavoritePeople": "Gente favorita", "OptionRandom": "Aleatorio", - "SelectAdminUsername": "Por favor seleccione un nombre de usuario para la cuenta de administrador.", + "SelectAdminUsername": "Por favor seleccione un nombre de usuario para la cuenta administrador.", "ButtonSplit": "Dividir", "HeaderNavigation": "Navegación", "MessageConfirmAppExit": "¿Quieres salir?", - "EnableFastImageFadeInHelp": "Las imágenes que hayan terminado de cargarse mostrarán una pequeña animación", - "EnableFastImageFadeIn": "Cargar las imágenes suavemente", + "EnableFasterAnimationsHelp": "Las animaciones y transiciones durarán menos tiempo", + "EnableFasterAnimations": "Animaciones más rápidas", "CopyStreamURLError": "Ha habido un error copiando la dirección.", - "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o un remux se adelanta lo suficiente desde la posición de reproducción actual, pause el proceso para que consuma menos recursos. Esto es más útil cuando se reproduce de forma linear, sin saltar de posición de reproducción a menudo. Desactívelo si experimenta problemas de reproducción.", - "PlaybackErrorNoCompatibleStream": "Hay un problema con el perfil del cliente y el servidor no es capaz de enviar el contenido en un formato compatible.", - "OptionForceRemoteSourceTranscoding": "Forzar la transcodificación para fuentes remotas de medios (como LiveTV)", + "AllowFfmpegThrottlingHelp": "Las conversiones se pausarán cuando se adelanten lo suficiente desde la posición en la que se encuentre el reproductor. Puede reducir la carga en el servidor y es útil cuando se reproduce de forma continua, sin saltar entre intervalos de tiempo, pero puede que tengas que desactivarlo si experimentas problemas en la reproducción o cambias de posición frecuentemente mientras reproduces contenido.", + "PlaybackErrorNoCompatibleStream": "Este contenido no es compatible con este dispositivo y no se puede reproducir: No se puede obtener del servidor en un formato compatible.", + "OptionForceRemoteSourceTranscoding": "Forzar la conversión para fuentes externas (como la televisión en directo)", "NoCreatedLibraries": "Parece que aún no se han creado librearías. {0}¿Quiere crear una ahora?{1}", "LabelVideoResolution": "Resolución de video:", "LabelStreamType": "Tipo de stream:", @@ -1474,5 +1469,111 @@ "LabelDroppedFrames": "Frames perdidos:", "LabelCorruptedFrames": "Frames corruptos:", "AskAdminToCreateLibrary": "Solo un administrador puede crear librerías.", - "AllowFfmpegThrottling": "Acelerar transcodificación" + "AllowFfmpegThrottling": "Pausar las conversiones", + "ClientSettings": "Ajustes de cliente", + "PreferEmbeddedEpisodeInfosOverFileNames": "Priorizar la información embebida sobre los nombres de archivos", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Usar la información de episodio de los metadatos embebidos si está disponible.", + "PathNotFound": "No se encontró la ruta especificada. Asegúrate de que existe e inténtalo de nuevo.", + "WriteAccessRequired": "Jellyfin requiere de permisos de escritura en esta carpeta. Asegúrate de que existe este permiso e inténtalo de nuevo.", + "ListPaging": "{0}-{1} de {2}", + "PersonRole": "como {0}", + "LastSeen": "Última vez {0}", + "DailyAt": "Diariamente a las {0}", + "WeeklyAt": "Los {0}s a las {1}", + "OnWakeFromSleep": "Al reanudar el servidor", + "EveryXMinutes": "Cada {0} minutos", + "EveryHour": "Cada hora", + "EveryXHours": "Cada {0} horas", + "OnApplicationStartup": "Al iniciarse el servidor", + "Track": "Pista", + "Season": "Temporada", + "ReleaseGroup": "Grupo de salida", + "Person": "Persona", + "OtherArtist": "Otro artista", + "Movie": "Película", + "Episode": "Episodio", + "BoxSet": "Box Set", + "Artist": "Artista", + "AlbumArtist": "Artista del álbum", + "Album": "Álbum", + "LabelDeinterlaceMethod": "Metodo de desentrelazar:", + "DeinterlaceMethodHelp": "Seleccione el tipo de filtro que se aplicará para desentrelazar el contenido que esté entrelazado durante la conversión.", + "LabelLibraryPageSize": "Tamaño de la página de la biblioteca:", + "LabelLibraryPageSizeHelp": "Establece la cantidad de artículos a mostrar en una página de la biblioteca. Ponlo en 0 para desactivar la paginación.", + "UnsupportedPlayback": "No es posible desencriptar contenido protegido mediante DRM; sin embargo se intentará su reproducción. Algunos archivos pueden aparecer completamente negros debido a encriptación u otras características no soportadas, como títulos interactivos.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "MessageUnauthorizedUser": "No tiene autorización para acceder al servidor en este momento. Póngase en contacto con el administrador del servidor para obtener más información.", + "ButtonTogglePlaylist": "Lista de reproducción", + "ButtonToggleContextMenu": "Más", + "Filter": "Filtro", + "New": "Nuevo", + "HeaderFavoritePlaylists": "Lista reproducción favorita", + "ApiKeysCaption": "Lista de las claves API actuales", + "LabelNightly": "Nightly", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "HeaderServerAddressSettings": "Configuración de la dirección del Servidor", + "HeaderRemoteAccessSettings": "Opciones de Acceso Remoto", + "HeaderHttpsSettings": "Opciones HTTPS", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes de HTTP hacia HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRequireHttps": "Necesita HTTPS", + "LabelEnableHttpsHelp": "Permite que el servidor escuche en el puerto HTTPS configurado. También se debe configurar un certificado válido para que esto surta efecto.", + "LabelEnableHttps": "Activar HTTPS", + "TabDVR": "DVR", + "SaveChanges": "Guardar cambios", + "EnableBlurhash": "Mostrar una representación de las imágenes mientras cargan", + "EnableBlurhashHelp": "Aparecerá una representación de los colores de las imágenes antes de que terminen de cargar", + "HeaderDVR": "DVR", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso de este usuario para utilizar SyncPlay. SyncPlay te permite sincronizar la reproducción entre varios dispositivos.", + "MessageSyncPlayErrorMedia": "¡No se pudo activar SyncPlay! Error de medio.", + "MessageSyncPlayErrorMissingSession": "¡No se pudo activar SyncPlay! Sesión desconectada.", + "MessageSyncPlayErrorNoActivePlayer": "No hay reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Ocurrió un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "Acceso restringido a este contenido.", + "MessageSyncPlayJoinGroupDenied": "Requiere permiso para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Requiere permiso para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "No se pudo unir al grupo porque no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Requiere permiso para reproducir.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Reproduce algo primero.", + "MessageSyncPlayGroupWait": "{0} se está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay inactivo.", + "MessageSyncPlayEnabled": "SyncPlay activo.", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Inactivo para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir a usuarios unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir a usuarios crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Inhabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia del tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Huso horario de el servidor:", + "HeaderSyncPlayEnabled": "Syncplay activo", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "EnableDetailsBannerHelp": "Mostrar imagen de banner en el tope de la página de detalles del elemento.", + "EnableDetailsBanner": "Barra de Detalles", + "ShowMore": "Ver más", + "ShowLess": "Ver menos", + "ButtonSyncPlay": "SyncPlay", + "ButtonCast": "Enviar", + "MessageNoGenresAvailable": "Permitir a algunos proveedores de metadatos extraer géneros de Internet.", + "EnableDecodingColorDepth10Vp9": "Habilite la decodificación por hardware de 10 bits para VP9", + "EnableDecodingColorDepth10Hevc": "Habilite la decodificación por hardware de 10 bits para HEVC", + "ButtonPlayer": "Reproductor", + "TabRepositories": "Repositorios", + "MessageAddRepository": "Si desea agregar un repositorio, haga click en el botón cerca del encabezado y complete la información requerida.", + "LabelRepositoryNameHelp": "Un nombre personalizado para distinguir este repositorio de otros agregados a su servidor.", + "LabelRepositoryName": "Nombre del repositorio", + "LabelRepositoryUrlHelp": "La ubicación del repositorio que desea incluir.", + "LabelRepositoryUrl": "URL del repositorio", + "HeaderNewRepository": "Nuevo repositorio", + "MessageNoRepositories": "Sin repositorios.", + "Writers": "Escritores", + "StopPlayback": "Detener la reproducción", + "ClearQueue": "Borrar la cola" } diff --git a/src/strings/es_419.json b/src/strings/es_419.json new file mode 100644 index 00000000000..871f43a3722 --- /dev/null +++ b/src/strings/es_419.json @@ -0,0 +1,1572 @@ +{ + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronizar", + "Songs": "Canciones", + "Shows": "Programas", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "Movies": "Películas", + "HeaderNextUp": "A continuación", + "HeaderLiveTV": "TV en vivo", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "Collections": "Colecciones", + "Channels": "Canales", + "Books": "Libros", + "Artists": "Artistas", + "Albums": "Álbumes", + "TabLatest": "Recientes", + "HeaderUser": "Usuario", + "AlbumArtist": "Artista del álbum", + "Album": "Álbum", + "Aired": "Transmitido", + "AirDate": "Fecha de emisión", + "AdditionalNotificationServices": "Explora el catálogo de complementos para instalar servicios de notificaciones adicionales.", + "AddedOnValue": "Agregado {0}", + "AddToPlaylist": "Agregar a lista de reproducción", + "AddToPlayQueue": "Agregar a la cola de reproducción", + "AddToCollection": "Agregar a colección", + "AddItemToCollectionHelp": "Agrega elementos a las colecciones buscándolos y utilizando sus menúes al hacer clic derecho o al tocarlos para agregarlos a una colección.", + "Add": "Agregar", + "Actor": "Actor", + "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", + "Absolute": "Absoluto", + "YadifBob": "YADIF Bob", + "Trailers": "Trailers", + "TabTrailers": "Trailers", + "ReleaseGroup": "Grupo que lo estrenó", + "OptionThumbCard": "Miniatura de imagen", + "OptionResElement": "elemento reanudable", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionBluray": "Blu-ray", + "OptionBlockTrailers": "Trailers", + "LabelNightly": "Construcciones nocturnas", + "HeaderVideos": "Videos", + "Director": "Director", + "Depressed": "No presionado", + "BoxSet": "Caja", + "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM de todas formas todo el contenido será intentado, incluyendo los títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos interactivos.", + "OnApplicationStartup": "Cuando se inicia la aplicación", + "EveryXHours": "Cada {0} horas", + "EveryHour": "Cada hora", + "EveryXMinutes": "Cada {0} minutos", + "OnWakeFromSleep": "Al despertar de la suspensión", + "WeeklyAt": "{0}s a las {1}", + "DailyAt": "Diariamente a las {0}", + "LastSeen": "Ultima vez visto {0}", + "PersonRole": "como {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor, asegúrate de tener acceso de escritura e inténtalo de nuevo.", + "PathNotFound": "No se pudo encontrar la ruta. Por favor, asegúrate de que la ruta es válida e inténtalo de nuevo.", + "Yesterday": "Ayer", + "Yes": "Sí", + "Yadif": "YADIF", + "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separa varios con un «|».", + "XmlTvPathHelp": "Una ruta a un archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Tú eres responsable de crear y actualizar el archivo.", + "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas de noticias. Separa varios con un «|».", + "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separa varios con un «|».", + "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separa varios con un «|».", + "XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.", + "Writer": "Escritor", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre tu biblioteca de medios. Revisa alguna de nuestras aplicaciones, y luego haz clic en Finalizar para ver el Panel de control.", + "Whitelist": "Lista blanca", + "WelcomeToProject": "¡Bienvenido a Jellyfin!", + "Wednesday": "Miércoles", + "Watched": "Visto", + "ViewPlaybackInfo": "Ver información de reproducción", + "ViewArtist": "Ver artista", + "ViewAlbum": "Ver álbum", + "VideoRange": "Rango de video", + "Vertical": "Vertical", + "ValueVideoCodec": "Códec de video: {0}", + "ValueTimeLimitSingleHour": "Límite de tiempo: 1 hora", + "ValueTimeLimitMultiHour": "Límite de tiempo: {0} horas", + "ValueSongCount": "{0} canciones", + "ValueSeriesCount": "{0} series", + "ValueSeconds": "{0} segundos", + "ValueOneSong": "1 canción", + "ValueOneSeries": "1 serie", + "ValueOneMusicVideo": "1 video musical", + "ValueOneMovie": "1 película", + "ValueOneEpisode": "1 episodio", + "ValueOneAlbum": "1 álbum", + "ValueMusicVideoCount": "{0} videos musicales", + "ValueMovieCount": "{0} películas", + "ValueMinutes": "{0} min", + "ValueEpisodeCount": "{0} episodios", + "ValueDiscNumber": "Disco {0}", + "ValueContainer": "Contenedor: {0}", + "ValueConditions": "Condiciones: {0}", + "ValueCodec": "Códec: {0}", + "ValueAudioCodec": "Códec de audio: {0}", + "ValueAlbumCount": "{0} álbumes", + "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con detalladas configuraciones de pantalla, estado de reproducción y controles parentales.", + "UserAgentHelp": "Proporciona un encabezado HTTP de agente de usuario personalizado.", + "Upload": "Subir", + "Up": "Arriba", + "Unrated": "Sin clasificar", + "Unplayed": "No reproducido", + "Unmute": "Activar sonido", + "UninstallPluginHeader": "Desinstalar complemento", + "UninstallPluginConfirmation": "¿Estás seguro de querer desinstalar {0}?", + "Uniform": "Uniforme", + "TvLibraryHelp": "Revisa la {0}guía de nombrado de series de TV{1}.", + "Tuesday": "Martes", + "Transcoding": "Transcodificando", + "TrackCount": "{0} pistas", + "Track": "Pista", + "TitlePlayback": "Reproducción", + "TitleHostingSettings": "Configuraciones de alojamiento", + "TitleHardwareAcceleration": "Aceleración por hardware", + "Thursday": "Jueves", + "Thumb": "Miniatura", + "ThisWizardWillGuideYou": "Este asistente te guiará a través del proceso de instalación. Para comenzar, por favor, selecciona tu idioma preferido.", + "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones afectan a los subtítulos en este dispositivo", + "ThemeVideos": "Videos temáticos", + "ThemeSongs": "Canciones temáticas", + "TellUsAboutYourself": "Háblanos de ti", + "TagsValue": "Etiquetas: {0}", + "Tags": "Etiquetas", + "TabUsers": "Usuarios", + "TabUpcoming": "Próximamente", + "TabTranscoding": "Transcodificación", + "TabSuggestions": "Sugerencias", + "TabStreaming": "Transmisión", + "TabSongs": "Canciones", + "TabShows": "Programas", + "TabSettings": "Configuración", + "TabServer": "Servidor", + "TabSeries": "Series", + "TabScheduledTasks": "Tareas programadas", + "TabResumeSettings": "Reanudar", + "TabResponses": "Respuestas", + "TabRecordings": "Grabaciones", + "TabProfiles": "Perfiles", + "TabProfile": "Perfil", + "TabPlugins": "Complementos", + "TabPlaylists": "Listas de reproducción", + "TabPlaylist": "Lista de reproducción", + "TabPlayback": "Reproducción", + "TabPassword": "Contraseña", + "TabParentalControl": "Control parental", + "TabOther": "Otros", + "TabNotifications": "Notificaciones", + "TabNfoSettings": "Configuración de NFO", + "TabNetworking": "Redes", + "TabNetworks": "Cadenas", + "TabMyPlugins": "Mis complementos", + "TabMusicVideos": "Videos musicales", + "TabMusic": "Música", + "TabMovies": "Películas", + "TabMetadata": "Metadatos", + "TabLogs": "Registros", + "TabLiveTV": "TV en vivo", + "TabInfo": "Información", + "TabGuide": "Guía", + "TabGenres": "Géneros", + "TabFavorites": "Favoritos", + "TabEpisodes": "Episodios", + "TabDVR": "DVR", + "TabDisplay": "Mostrar", + "TabDirectPlay": "Reproducción directa", + "TabDevices": "Dispositivos", + "TabDashboard": "Panel de control", + "TabContainers": "Contenedores", + "TabCollections": "Colecciones", + "TabCodecs": "Códecs", + "TabChannels": "Canales", + "TabCatalog": "Catálogo", + "OptionPoster": "Póster", + "OptionPlayed": "Reproducido", + "OptionPlayCount": "Contador de reproducciones", + "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", + "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", + "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", + "OptionParentalRating": "Clasificación parental", + "OptionOnInterval": "En un intervalo", + "OptionOnAppStartup": "Al iniciar la aplicación", + "OptionNone": "Ninguno", + "OptionNew": "Nuevo…", + "OptionNameSort": "Nombre", + "OptionMonday": "Lunes", + "OptionMissingEpisode": "Episodios faltantes", + "OptionMax": "Máximo", + "OptionLoginAttemptsBeforeLockoutHelp": "Un valor de cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", + "OptionLoginAttemptsBeforeLockout": "Determina cuantos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", + "OptionList": "Lista", + "OptionLikes": "Me gusta", + "OptionIsSD": "SD", + "OptionIsHD": "HD", + "OptionImdbRating": "Calificación de IMDb", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", + "OptionHomeVideos": "Fotos", + "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", + "OptionHideUserFromLoginHelp": "Útil para cuentas privadas o de administrador ocultas. El usuario tendrá que iniciar sesión manualmente introduciendo su nombre de usuario y contraseña.", + "OptionHideUser": "Ocultar este usuario de las pantallas de inicio de sesión", + "OptionHasTrailer": "Trailer", + "OptionHasThemeVideo": "Video temático", + "OptionHasThemeSong": "Canción temática", + "OptionHasSubtitles": "Subtítulos", + "OptionHasSpecialFeatures": "Características especiales", + "OptionFriday": "Viernes", + "OptionFavorite": "Favoritos", + "OptionExtractChapterImage": "Habilitar la extracción de imágenes de los capítulos", + "OptionExternallyDownloaded": "Descarga externa", + "OptionEveryday": "Todos los días", + "OptionEstimateContentLength": "Estimar la duración del contenido cuando se transcodifica", + "OptionEquals": "Igual a", + "OptionEnded": "Finalizado", + "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegts.", + "OptionEnableM2tsMode": "Habilitar modo M2TS", + "OptionEnableForAllTuners": "Habilitar para todos los dispositivos sintonizadores", + "OptionEnableExternalContentInSuggestionsHelp": "Permitir que los trailers de Internet y los programas de televisión en vivo se incluyan en el contenido sugerido.", + "OptionEnableExternalContentInSuggestions": "Habilitar contenido externo en las sugerencias", + "OptionEnableAccessToAllLibraries": "Habilitar acceso a todas las bibliotecas", + "OptionEnableAccessToAllChannels": "Habilitar acceso a todos los canales", + "OptionEnableAccessFromAllDevices": "Habilitar acceso desde todos los dispositivos", + "OptionEmbedSubtitles": "Incrustar dentro del contenedor", + "OptionDvd": "DVD", + "OptionDownloadThumbImage": "Miniatura", + "OptionDownloadPrimaryImage": "Principal", + "OptionDownloadMenuImage": "Menú", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", + "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadBoxImage": "Caja", + "OptionDownloadBannerImage": "Banner", + "OptionDownloadBackImage": "Parte trasera", + "OptionDownloadArtImage": "Arte", + "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", + "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", + "OptionDislikes": "No me gusta", + "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUser": "Desactivar este usuario", + "OptionDescending": "Descendente", + "OptionDatePlayed": "Fecha de reproducción", + "OptionDateAddedImportTime": "Usar la fecha de escaneo en la biblioteca", + "OptionDateAddedFileTime": "Usar la fecha de creación del archivo", + "OptionDateAdded": "Fecha de adición", + "OptionDaily": "Diario", + "OptionCustomUsers": "Personalizado", + "OptionCriticRating": "Calificación de los críticos", + "OptionContinuing": "Continuando", + "OptionCommunityRating": "Calificación de la comunidad", + "OptionBlockTvShows": "Programas de TV", + "OptionBlockMusic": "Música", + "OptionBlockMovies": "Películas", + "OptionBlockLiveTvChannels": "Canales de TV en vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet", + "OptionBlockBooks": "Libros", + "OptionBanner": "Banner", + "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", + "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", + "OptionAutomatic": "Automático", + "OptionAuto": "Automático", + "OptionAscending": "Ascendente", + "OptionArtist": "Artista", + "OptionAllowVideoPlaybackTranscoding": "Permitir la reproducción de video que requiera de transcodificación", + "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin recodificar", + "OptionAllowUserToManageServer": "Permitir a este usuario administrar el servidor", + "OptionAllowSyncTranscoding": "Permitir descarga y sincronización de medios que requieran transcodificación", + "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", + "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", + "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlayback": "Permitir reproducción de medios", + "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", + "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", + "OptionAllowLinkSharing": "Permitir compartir medios en redes sociales", + "OptionAllowContentDownloading": "Permitir descarga y sincronización de medios", + "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en vivo", + "OptionForceRemoteSourceTranscoding": "Forzar transcodificación de fuentes remotas (como TV en vivo)", + "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera transcodificación", + "OptionAllUsers": "Todos los usuarios", + "OptionAlbumArtist": "Artista del álbum", + "OptionAlbum": "Álbum", + "OptionAdminUsers": "Administradores", + "Option3D": "3D", + "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", + "OnlyForcedSubtitlesHelp": "Solo se cargarán subtítulos marcados como forzados.", + "OnlyForcedSubtitles": "Solo forzados", + "OneChannel": "Un canal", + "Off": "Apagar", + "NumLocationsValue": "{0} carpetas", + "Normal": "Normal", + "None": "Ninguno", + "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pueden ser activados manualmente durante la reproducción.", + "NoSubtitles": "Ninguno", + "NoSubtitleSearchResultsFound": "No se encontraron resultados.", + "NoPluginConfigurationMessage": "Este complemento no tiene configuraciones disponibles.", + "NoNextUpItemsMessage": "No se encontró nada. ¡Comienza a ver tus programas!", + "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierra este diálogo e introduce la información del dispositivo manualmente.", + "NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}¿Quisieras crear una ahora?{1}", + "No": "No", + "NextUp": "A continuación", + "Next": "Siguiente", + "News": "Noticias", + "NewEpisodesOnly": "Solo episodios nuevos", + "NewEpisodes": "Episodios nuevos", + "NewCollectionNameExample": "Ejemplo: Colección Guerra de las Galaxias", + "NewCollectionHelp": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", + "NewCollection": "Nueva colección", + "Never": "Nunca", + "Name": "Nombre", + "MySubtitles": "Mis subtítulos", + "Mute": "Silenciar", + "MusicVideo": "Video musical", + "MusicLibraryHelp": "Revisar la {0}guía de nombrado de música{1}.", + "MusicArtist": "Artista musical", + "MusicAlbum": "Álbum de música", + "Movie": "Película", + "MovieLibraryHelp": "Revisar la {0}guía de nombrado de películas{1}.", + "MoveRight": "Mover a la derecha", + "MoveLeft": "Mover a la izquierda", + "MoreMediaInfo": "Información multimedia", + "MoreUsersCanBeAddedLater": "Más usuarios pueden ser añadidos más tarde desde el panel de control.", + "MoreFromValue": "Más de {0}", + "Monday": "Lunes", + "Mobile": "Móvil", + "MinutesBefore": "minutos antes", + "MinutesAfter": "minutos después", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MetadataManager": "Administrador de metadatos", + "Metadata": "Metadatos", + "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", + "MessageSyncPlayErrorMissingSession": "¡Fallo al activar SyncPlay! Falta la sesión.", + "MessageSyncPlayErrorNoActivePlayer": "No se ha encontrado ningún reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Se produjo un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "El acceso a este contenido está restringido.", + "MessageSyncPlayJoinGroupDenied": "Permiso requerido para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permiso requerido para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "Fallo al unirse al grupo porque éste no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Permiso de reproducción requerido.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Empieza a reproducir algo primero.", + "MessageSyncPlayGroupWait": "{0} está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "MessageYouHaveVersionInstalled": "Actualmente cuentas con la versión {0} instalada.", + "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utiliza el administrador de metadatos para establecer los tipos de contenido para las subcarpetas.", + "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se removerán de tu biblioteca:", + "MessageSettingsSaved": "Configuraciones guardadas.", + "MessageReenableUser": "Ver abajo para volver a habilitar", + "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar tu experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor Jellyfin, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", + "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor, inicia sesión en tu servidor local directamente.", + "MessagePleaseWait": "Por favor, espera. Esto podría tomar un minuto.", + "MessagePleaseEnsureInternetMetadata": "Por favor, asegúrate de que la descarga de metadatos de Internet está habilitada.", + "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Por favor, contacta al administrador del servidor para obtener más información.", + "MessagePasswordResetForUsers": "Los siguientes usuarios han restablecido sus contraseñas. Ahora pueden iniciar sesión con los códigos PIN que se usaron para realizar el restablecimiento.", + "MessageNothingHere": "Nada aquí.", + "MessageNoTrailersFound": "Instale el canal de Avances para mejorar su experiencia cinematográfica agregando una biblioteca de Avances desde Internet.", + "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático de servidores.", + "MessageNoPluginsInstalled": "No tienes complementos instalados.", + "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y luego regresa para ver tus recomendaciones.", + "MessageNoCollectionsAvailable": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haz clic en el botón + para comenzar a crear colecciones.", + "MessageNoAvailablePlugins": "No hay complementos disponibles.", + "MessageLeaveEmptyToInherit": "Dejar vacío para heredar la configuración de un elemento superior o del valor predeterminado global.", + "MessageItemsAdded": "Elementos agregados.", + "MessageItemSaved": "Elemento guardado.", + "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor, contacta al administrador del servidor para más información.", + "MessageInvalidUser": "Nombre de usuario o contraseña inválidos. Por favor, intenta de nuevo.", + "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtalo de nuevo.", + "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que deseas usarlo.", + "MessageImageTypeNotSelected": "Por favor, selecciona un tipo de imagen del menú desplegable.", + "MessageImageFileTypeAllowed": "Solo son soportados archivos JPEG y PNG.", + "MessageForgotPasswordInNetworkRequired": "Por favor, intenta de nuevo dentro de tu red local para iniciar el proceso de restablecimiento de contraseña.", + "MessageForgotPasswordFileCreated": "El siguiente archivo fue creado en tu servidor y contiene instrucciones de como proceder:", + "MessageFileReadError": "Hubo un error al leer el archivo. Por favor, intenta de nuevo.", + "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente más largos.", + "MessageDownloadQueued": "Descarga puesta en la cola.", + "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE o Ubuntu, debes conceder al usuario del servicio al menos permisos de lectura a tus ubicaciones de almacenamiento.", + "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesites configurar el almacenamiento dentro de tu «FreeNAS Jail» de manera que permita a Jellyfin accederlo.", + "List": "Lista", + "LinksValue": "Enlaces: {0}", + "Like": "Me gusta", + "LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", + "LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.", + "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LaunchWebAppOnStartupHelp": "Abre el cliente web en su navegador web predeterminado cuando se inicia el servidor. Esto no ocurrirá cuando se utilice la función de reinicio del servidor.", + "LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor", + "LatestFromLibrary": "Últimas - {0}", + "Large": "Grande", + "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", + "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LabelffmpegPath": "Ruta del FFmpeg:", + "LabelZipCode": "Código postal:", + "LabelYoureDone": "¡Has terminado!", + "LabelYourFirstName": "Tu nombre:", + "LabelYear": "Año:", + "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaDoc": "Documento X-DLNA:", + "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaCap": "X-DLNA límite:", + "LabelWeb": "Web:", + "LabelVideoResolution": "Resolución de video:", + "LabelVideoCodec": "Códec de video:", + "LabelVideoBitrate": "Velocidad de bits de video:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arquitectura: {0}", + "DashboardOperatingSystem": "Sistema operativo: {0}", + "DashboardServerName": "Servidor: {0}", + "DashboardVersionNumber": "Versión: {0}", + "LabelVersionInstalled": "{0} instalado", + "LabelVersion": "Versión:", + "LabelValue": "Valor:", + "LabelVaapiDeviceHelp": "Este es el nodo de renderizado que es usado para la aceleración por hardware.", + "LabelVaapiDevice": "Dispositivo VA API:", + "LabelUsername": "Nombre de usuario:", + "LabelUserRemoteClientBitrateLimitHelp": "Sobrescribe el valor global predeterminado establecido en la configuración de reproducción del servidor.", + "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario sea bloqueado:", + "LabelUserLibraryHelp": "Selecciona la biblioteca de usuario que se mostrará en el dispositivo. Déjalo vacío para heredar la configuración por defecto.", + "LabelUserLibrary": "Biblioteca de usuario:", + "LabelUserAgent": "Agente de usuario:", + "LabelUser": "Usuario:", + "LabelUseNotificationServices": "Usar los siguientes servicios:", + "LabelTypeText": "Texto", + "LabelTypeMetadataDownloaders": "Recolectores de metadatos para {0}:", + "LabelType": "Tipo:", + "LabelTunerType": "Tipo de sintonizador:", + "LabelTunerIpAddress": "Dirección IP del sintonizador:", + "LabelTriggerType": "Tipo de disparador:", + "LabelTranscodingVideoCodec": "Códec de video:", + "LabelTranscodingThreadCountHelp": "Selecciona el número máximo de hilos a utilizar al transcodificar. Reducir el número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", + "LabelTranscodingThreadCount": "Conteo de hilos de la transcodificación:", + "LabelTranscodingProgress": "Progreso de la transcodificación:", + "LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:", + "LabelTranscodes": "Transcodificaciones:", + "LabelTranscodingTempPathHelp": "Especifica una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deja en blanco para utilizar el predeterminado del servidor.", + "LabelTranscodePath": "Ruta de transcodificación:", + "LabelTranscodingContainer": "Contenedor:", + "LabelTranscodingAudioCodec": "Códec de audio:", + "LabelTrackNumber": "Número de pista:", + "LabelTitle": "Título:", + "LabelTimeLimitHours": "Límite de tiempo (horas):", + "LabelTime": "Hora:", + "LabelTheme": "Tema:", + "LabelTextSize": "Tamaño del texto:", + "LabelTextColor": "Color del texto:", + "LabelTextBackgroundColor": "Color de fondo para el texto:", + "LabelTagline": "Eslogan:", + "LabelTag": "Etiqueta:", + "LabelTVHomeScreen": "Modo de pantalla de TV:", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Deshabilitado para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir al usuario unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir al usuario crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia de tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tiempo compensado respecto al servidor:", + "LabelSupportedMediaTypes": "Tipos de medios soportados:", + "LabelSubtitles": "Subtítulos", + "LabelSubtitlePlaybackMode": "Modo de subtítulo:", + "LabelSubtitleFormatHelp": "Ejemplo: srt", + "LabelSubtitleDownloaders": "Recolectores de subtítulos:", + "LabelStreamType": "Tipo de transmisión:", + "LabelStopping": "Deteniendo", + "LabelStopWhenPossible": "Detener cuando sea posible:", + "LabelStatus": "Estado:", + "LabelStartWhenPossible": "Iniciar cuando sea posible:", + "LabelSportsCategories": "Categorías de deportes:", + "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de especiales:", + "LabelSource": "Fuente:", + "LabelSoundEffects": "Efectos de sonido:", + "LabelSortTitle": "Título para ordenar:", + "LabelSortOrder": "Clasificar ordenado:", + "LabelSortBy": "Ordenar por:", + "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el namespace urn:schemas-sonycom:av.", + "LabelSonyAggregationFlags": "Marcas de agregación Sony:", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos resultará en una entrega más eficiente y disminuirá las posibilidades de que un video sea transcodificado.", + "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos incrustados", + "LabelSkipIfAudioTrackPresentHelp": "Desactiva esto para asegurarte de que todos los videos tengan subtítulos, independientemente del idioma del audio.", + "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", + "LabelSkipForwardLength": "Longitud de salto hacia adelante:", + "LabelSkipBackLength": "Longitud de salto hacia atrás:", + "Sunday": "Domingo", + "Suggestions": "Sugerencias", + "Subtitles": "Subtítulos", + "SubtitleOffset": "Desplazamiento de subtítulos", + "SubtitleDownloadersHelp": "Habilita y prioriza tus recolectores de subtítulos en orden de prioridad.", + "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incorporen sus propios estilos.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción en Chromecast iniciada por este dispositivo.", + "Studios": "Estudios", + "StopRecording": "Detener grabación", + "Sports": "Deportes", + "SortName": "Nombre para ordenar", + "SortChannelsBy": "Ordenar canales por:", + "SortByValue": "Ordenar por {0}", + "Sort": "Ordenar", + "SmartSubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados cuando el audio se encuentre en un idioma extranjero.", + "Smart": "Inteligente", + "Smaller": "Más pequeño", + "SmallCaps": "Mayúsculas pequeñas", + "Small": "Pequeño", + "SkipEpisodesAlreadyInMyLibraryHelp": "Los episodios serán comparados usando el numero de temporada y de episodio, cuando estén disponibles.", + "SkipEpisodesAlreadyInMyLibrary": "No grabar episodios que ya se encuentran en mi biblioteca", + "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultáneas permitidas. Ingresa 0 para sin límite.", + "Filter": "Filtro", + "New": "Nuevo", + "Shuffle": "Aleatorio", + "ShowYear": "Mostrar año", + "ShowTitle": "Mostrar título", + "ShowIndicatorsFor": "Mostrar indicadores para:", + "ShowAdvancedSettings": "Mostrar configuraciones avanzadas", + "Share": "Compartir", + "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión. Si experimentas cualquier problema, recomendamos volver a los valores por defecto.", + "SettingsSaved": "Configuración guardada.", + "Settings": "Configuración", + "ServerUpdateNeeded": "Este servidor Jellyfin necesita ser actualizado. Para descargar la última versión, por favor, visita {0}", + "ServerRestartNeededAfterPluginInstall": "El servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", + "ServerNameIsShuttingDown": "El servidor Jellyfin - {0} se está apagando.", + "ServerNameIsRestarting": "El servidor Jellyfin - {0} se está reiniciando.", + "SeriesYearToPresent": "{0} - Actualidad", + "SeriesSettings": "Configuración de la serie", + "SeriesRecordingScheduled": "Grabación de series programadas.", + "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha de emisión, orden del DVD o numeración absoluta.", + "SeriesCancelled": "Serie cancelada.", + "Series": "Series", + "SendMessage": "Enviar mensaje", + "SelectAdminUsername": "Por favor, selecciona un nombre de usuario para la cuenta de administrador.", + "Season": "Temporada", + "SearchResults": "Resultados de la búsqueda", + "SearchForSubtitles": "Buscar subtítulos", + "SearchForMissingMetadata": "Buscar metadatos faltantes", + "SearchForCollectionInternetMetadata": "Buscar en Internet por ilustraciones y metadatos", + "Search": "Buscar", + "Screenshots": "Capturas de pantalla", + "Screenshot": "Captura de pantalla", + "Schedule": "Programación", + "ScanLibrary": "Escanear biblioteca", + "ScanForNewAndUpdatedFiles": "Escanear por archivos nuevos y actualizados", + "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar los subtítulos junto a los archivos de video permitirá administrarlos con más facilidad.", + "SaveSubtitlesIntoMediaFolders": "Guardar subtítulos en las carpetas de los medios", + "SaveChanges": "Guardar cambios", + "Save": "Guardar", + "Saturday": "Sábado", + "Runtime": "Duración", + "RunAtStartup": "Ejecutar al iniciar", + "Rewind": "Rebobinar", + "ResumeAt": "Reanudar desde {0}", + "ReplaceExistingImages": "Reemplazar imágenes existentes", + "ReplaceAllMetadata": "Reemplazar todos los metadatos", + "RepeatOne": "Repetir uno", + "RepeatMode": "Modo de repetición", + "RepeatEpisodes": "Repetir episodios", + "RepeatAll": "Repetir todos", + "Repeat": "Repetir", + "RemoveFromPlaylist": "Remover de la lista de reproducción", + "RemoveFromCollection": "Remover de la colección", + "RememberMe": "Recuérdame", + "ReleaseDate": "Fecha de estreno", + "RefreshQueued": "Actualización puesta en la cola.", + "RefreshMetadata": "Actualizar metadatos", + "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", + "Refresh": "Actualizar", + "Recordings": "Grabaciones", + "RecordingScheduled": "Grabación programada.", + "RecordingPathChangeMessage": "Cambiar la carpeta de grabaciones no moverá las grabaciones existentes de la antigua ubicación a la nueva. Necesitan moverse manualmente si se desea.", + "RecordingCancelled": "Grabación cancelada.", + "RecordSeries": "Grabar series", + "Record": "Grabar", + "RecommendationStarring": "Protagonizado por {0}", + "RecommendationDirectedBy": "Dirigido por {0}", + "RecommendationBecauseYouWatched": "Porque viste {0}", + "RecommendationBecauseYouLike": "Porque te gusta {0}", + "RecentlyWatched": "Visto recientemente", + "Rate": "Calificación", + "Raised": "Elevado", + "QueueAllFromHere": "Encolar todos desde aquí", + "Quality": "Calidad", + "Programs": "Programas", + "ProductionLocations": "Lugares de producción", + "Producer": "Productor", + "Primary": "Principal", + "Previous": "Anterior", + "Premieres": "Estrenos", + "Premiere": "Estreno", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información del episodio incrustada a los nombres de archivo", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información del episodio desde los metadatos incrustados si están disponibles.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el título mostrado por defecto cuando no hay disponibles metadatos en Internet o localmente.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incrustados a los nombres de archivo", + "PluginInstalledMessage": "El complemento ha sido instalado con éxito. El servidor Jellyfin necesitará ser reiniciado para que los cambios surtan efecto.", + "PleaseSelectTwoItems": "Por favor, selecciona al menos dos elementos.", + "PleaseRestartServerName": "Por favor, reinicia el servidor Jellyfin - {0}.", + "PleaseEnterNameOrId": "Por favor, introduce un nombre o ID externo.", + "PleaseConfirmPluginInstallation": "Por favor, haz clic en OK para confirmar que has leído lo que se encuentra arriba y que deseas proceder con la instalación del complemento.", + "PleaseAddAtLeastOneFolder": "Por favor, agrega al menos una carpeta a esta biblioteca dando clic al botón Agregar.", + "Played": "Reproducido", + "PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no está enviando un formato de medios compatible.", + "PlayNextEpisodeAutomatically": "Reproducir el siguiente episodio automáticamente", + "PlayNext": "Reproducir siguiente", + "PlayFromBeginning": "Reproducir desde el inicio", + "PlayCount": "Cantidad de reproducciones", + "PlaybackData": "Datos de reproducción", + "PlayAllFromHere": "Reproducir todos desde aquí", + "Play": "Reproducir", + "PlaceFavoriteChannelsAtBeginning": "Colocar canales favoritos al inicio", + "PinCodeResetConfirmation": "¿Estás seguro de que quieres restablecer el código PIN?", + "PinCodeResetComplete": "El código PIN ha sido restablecido.", + "PictureInPicture": "Pantalla en pantalla", + "Person": "Persona", + "PerfectMatch": "Coincidencia perfecta", + "People": "Personas", + "PasswordSaved": "Contraseña guardada.", + "PasswordResetProviderHelp": "Elige un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", + "PasswordResetHeader": "Restablecer contraseña", + "PasswordResetConfirmation": "¿Estás seguro de querer restablecer la contraseña?", + "PasswordResetComplete": "La contraseña ha sido restablecida.", + "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben coincidir.", + "ParentalRating": "Clasificación parental", + "PackageInstallFailed": "Instalación fallida de {0} (versión {1}).", + "PackageInstallCompleted": "Instalación completada de {0} (versión {1}).", + "PackageInstallCancelled": "Instalación cancelada de {0} (versión {1}).", + "Overview": "Resumen", + "OtherArtist": "Otro artista", + "OriginalAirDateValue": "Fecha de emisión original: {0}", + "OptionWeekly": "Semanal", + "OptionWeekends": "Fines de semana", + "OptionWeekdays": "Días de semana", + "OptionWednesday": "Miércoles", + "OptionWakeFromSleep": "Despertar de la suspensión", + "OptionUnplayed": "No reproducido", + "OptionUnairedEpisode": "Episodios no emitidos", + "OptionTvdbRating": "Calificación de TVDB", + "OptionTuesday": "Martes", + "OptionTrackName": "Nombre de la pista", + "OptionThumb": "Miniatura", + "OptionThursday": "Jueves", + "OptionSunday": "Domingo", + "OptionSubstring": "Subcadena", + "OptionSpecialEpisode": "Especiales", + "OptionSaveMetadataAsHiddenHelp": "Cambiar esto se aplicará a los nuevos metadatos guardados en el futuro. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el servidor Jellyfin.", + "OptionSaveMetadataAsHidden": "Guardar metadatos e imágenes como archivos ocultos", + "OptionSaturday": "Sábado", + "OptionRuntime": "Duración", + "OptionResumable": "Reanudable", + "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrará los subtítulos para incluir solo aquellos que han sido probados y verificados exactamente con tu archivo de video. Desmarcar esta opción incrementará las probabilidades de que se descarguen subtítulos, pero incrementará las posibilidades de obtener subtítulos mal sincronizados o con texto incorrecto.", + "OptionRequirePerfectSubtitleMatch": "Solo descargar subtítulos que coincidan perfectamente con mis archivos de video", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas de tiempo muy bien.", + "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta la búsqueda de bytes cuando se transcodifica", + "OptionReleaseDate": "Fecha de estreno", + "OptionRegex": "Expresión regular", + "OptionRandom": "Aleatorio", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "Transmisión en vivo por HTTP", + "OptionProfileVideoAudio": "Audio del video", + "OptionProfileVideo": "Video", + "OptionProfilePhoto": "Foto", + "OptionProfileAudio": "Audio", + "OptionPremiereDate": "Fecha de estreno", + "OptionPosterCard": "Ficha de póster", + "LabelSkin": "Apariencia:", + "LabelSize": "Tamaño:", + "LabelSimultaneousConnectionLimit": "Límite de transmisiones simultáneas:", + "LabelServerName": "Nombre del servidor:", + "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", + "LabelServerHost": "Servidor:", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", + "LabelSerialNumber": "Número de serie", + "LabelSendNotificationToUsers": "Enviar la notificación a:", + "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", + "LabelSelectUsers": "Seleccionar usuarios:", + "LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.", + "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:", + "LabelSeasonNumber": "Temporada número:", + "EnableFastImageFadeInHelp": "Habilita una animación más rápida de desvanecimiento para las imágenes cargadas.", + "EnableFastImageFadeIn": "Desvanecimiento rápido de animaciones", + "LabelScreensaver": "Protector de pantalla:", + "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", + "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", + "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", + "LabelRuntimeMinutes": "Duración (minutos):", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRequireHttps": "Requerir HTTPS", + "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", + "LabelRemoteClientBitrateLimit": "Limite de velocidad de bits para la transmisión por Internet (Mbps):", + "LabelReleaseDate": "Fecha de estreno:", + "LabelRefreshMode": "Modo de actualización:", + "LabelRecordingPathHelp": "Especifica la ubicación por defecto para guardar las grabaciones. Si se deja en blanco, se usará la carpeta de datos de programa del servidor.", + "LabelRecordingPath": "Ruta por defecto para las grabaciones:", + "LabelRecord": "Grabar:", + "LabelReasonForTranscoding": "Motivo para transcodificar:", + "LabelReadHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LabelPublicHttpsPortHelp": "El número de puerto público que debe asignarse al puerto HTTPS local.", + "LabelPublicHttpsPort": "Número de puerto HTTPS público:", + "LabelPublicHttpPortHelp": "El número de puerto público que debe asignarse al puerto HTTP local.", + "LabelPublicHttpPort": "Número de puerto HTTP público:", + "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responda a solicitudes GetProtocolInfo desde el dispositivo.", + "LabelProtocolInfo": "Información del protocolo:", + "LabelProtocol": "Protocolo:", + "LabelProfileVideoCodecs": "Códecs de video:", + "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los contenedores.", + "LabelProfileContainer": "Contenedor:", + "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los códecs.", + "LabelProfileCodecs": "Códecs:", + "LabelProfileAudioCodecs": "Códecs de audio:", + "LabelPrevious": "Anterior", + "LabelPreferredSubtitleLanguage": "Idioma preferido para los subtítulos:", + "LabelPreferredDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", + "LabelPreferredDisplayLanguage": "Idioma de pantalla preferido:", + "LabelPostProcessorArgumentsHelp": "Usar {path} como la ruta del archivo grabado.", + "LabelPostProcessorArguments": "Argumentos de la línea de comandos del post-procesador:", + "LabelPostProcessor": "Aplicación de postprocesamiento:", + "LabelPleaseRestart": "Los cambios tendrán efecto después de recargar manualmente el cliente web.", + "LabelPlayMethod": "Método de reproducción:", + "LabelPlaylist": "Lista de reproducción:", + "LabelPlayerDimensions": "Dimensiones del reproductor:", + "LabelPlayer": "Reproductor:", + "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del idioma", + "LabelPlaceOfBirth": "Lugar de nacimiento:", + "LabelPersonRoleHelp": "Ejemplo: Conductor de camión de helados", + "LabelPersonRole": "Rol:", + "LabelPath": "Ruta:", + "LabelPasswordRecoveryPinCode": "Código PIN:", + "LabelPasswordResetProvider": "Proveedor de restablecimiento de contraseña:", + "LabelPasswordConfirm": "Contraseña (confirmar):", + "LabelPassword": "Contraseña:", + "LabelParentalRating": "Clasificación parental:", + "LabelParentNumber": "Número antecesor:", + "LabelOverview": "Resumen:", + "LabelOriginalTitle": "Título original:", + "LabelOriginalAspectRatio": "Relación de aspecto original:", + "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", + "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", + "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", + "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", + "LabelNumber": "Número:", + "LabelNotificationEnabled": "Habilitar esta notificación", + "LabelNext": "Siguiente", + "LabelNewsCategories": "Categorías de noticias:", + "LabelNewPasswordConfirm": "Confirmación de contraseña nueva:", + "LabelNewPassword": "Contraseña nueva:", + "LabelNewName": "Nuevo nombre:", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "LabelName": "Nombre:", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", + "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", + "LabelMoviePrefix": "Prefijo de la película:", + "LabelMovieCategories": "Categorías de películas:", + "LabelMonitorUsers": "Monitorear actividad desde:", + "LabelModelUrl": "URL del modelo", + "LabelModelNumber": "Número del modelo", + "LabelModelName": "Nombre del modelo", + "LabelModelDescription": "Descripción del modelo", + "LabelMinScreenshotDownloadWidth": "Anchura mínima de descarga de capturas de pantalla:", + "LabelMinResumePercentageHelp": "Los medios se asumen como no reproducidos si se detienen antes de este tiempo.", + "LabelMinResumePercentage": "Porcentaje mínimo para reanudar:", + "LabelMinResumeDurationHelp": "La duración de video más corta en segundos que guardará la ubicación de reproducción y te permitirá reanudarla.", + "LabelMinResumeDuration": "Duración mínima para la reanudación:", + "LabelMinBackdropDownloadWidth": "Anchura mínima de descarga de imágenes de fondo:", + "LabelMethod": "Método:", + "LabelMetadataSaversHelp": "Selecciona los formatos de archivo con los que se guardarán tus metadatos.", + "LabelMetadataSavers": "Grabadores de metadatos:", + "LabelMetadataReadersHelp": "Ordena tus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", + "LabelMetadataReaders": "Lectores de metadatos:", + "LabelMetadataPathHelp": "Especifique una ubicación personalizada para las ilustraciones y los metadatos descargados.", + "LabelKidsCategories": "Categorías infantiles:", + "LabelKeepUpTo": "Mantener hasta:", + "LabelInternetQuality": "Calidad en Internet:", + "LabelInNetworkSignInWithEasyPasswordHelp": "Utiliza el código PIN sencillo para acceder a los clientes en tu red local. Tu contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitarás una contraseña dentro de tu red local.", + "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código PIN sencillo para conexiones dentro de la red", + "LabelImportOnlyFavoriteChannels": "Restringir a canales marcados como favoritos", + "LabelImageType": "Tipo de imagen:", + "LabelImageFetchersHelp": "Habilita y prioriza tus recolectores de imágenes preferidos.", + "LabelIdentificationFieldHelp": "Una subcadena indiferente a las mayúsculas y minúsculas o una expresión regular (regex).", + "LabelIconMaxWidthHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxWidth": "Ancho máximo del ícono:", + "LabelIconMaxHeightHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxHeight": "Altura máxima del ícono:", + "LabelHttpsPortHelp": "El número de puerto TCP al que el servidor HTTPS de Jellyfin debería enlazar.", + "LabelHttpsPort": "Número de puerto local HTTPS:", + "LabelHomeScreenSectionValue": "Sección {0} de la pantalla de inicio:", + "LabelHomeNetworkQuality": "Calidad en red local:", + "LabelHardwareAccelerationTypeHelp": "La aceleración por hardware requiere configuración adicional.", + "LabelHardwareAccelerationType": "Aceleración por hardware:", + "LabelEncoderPreset": "Codificación H264 y H265 preestablecida:", + "LabelH264Crf": "CRF de codificación H264:", + "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo artículo.", + "LabelGroupMoviesIntoCollections": "Agrupar películas en colecciones", + "LabelServerNameHelp": "Este nombre se usará para identificar el servidor y se predeterminará al nombre de la computadora del servidor.", + "LabelFriendlyName": "Nombre amistoso:", + "LabelFormat": "Formato:", + "LabelForgotPasswordUsernameHelp": "Introduce tu nombre de usuario, si lo recuerdas.", + "LabelFont": "Fuente:", + "LabelFolder": "Carpeta:", + "LabelFinish": "Terminar", + "LabelFileOrUrl": "Archivo o URL:", + "LabelFailed": "Fallido", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de los capítulos cuando se importan videos durante el escaneo de la biblioteca. De lo contrario, se extraerán durante la tarea programada imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", + "LabelExtractChaptersDuringLibraryScan": "Extraer las imágenes de los capítulos durante el escaneo de la biblioteca", + "LabelBaseUrlHelp": "Añade un subdirectorio personalizado a la URL del servidor. Por ejemplo: http://ejemplo.com/<urlbase>", + "LabelBaseUrl": "URL base:", + "LabelEveryXMinutes": "Cada:", + "LabelEvent": "Evento:", + "LabelEpisodeNumber": "Episodio número:", + "LabelEndDate": "Fecha de fin:", + "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no se renderizarán correctamente si se incrustan varias imágenes en DIDL.", + "LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada", + "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo soportados.", + "LabelEnableRealtimeMonitor": "Activar monitoreo en tiempo real", + "LabelEnableHttpsHelp": "Permite que el servidor escuche en el puerto HTTPS configurado. También se debe configurar un certificado válido para que esto surta efecto.", + "LabelEnableHttps": "Habilitar HTTPS", + "LabelEnableHardwareDecodingFor": "Habilitar decodificación por hardware para:", + "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en tu red explorar y reproducir contenido.", + "LabelEnableDlnaServer": "Habilitar servidor DLNA", + "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de tu red y ofrece la capacidad de controlarlos remotamente.", + "LabelEnableDlnaPlayTo": "Habilitar Reproducir En mediante DLNA", + "LabelEnableDlnaDebugLoggingHelp": "Crea grandes archivos de registro y solo se debe usar cuando se requiera para solucionar problemas.", + "LabelEnableDlnaDebugLogging": "Habilitar el registro de depuración de DLNA", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duración en segundos entre búsquedas SSDP realizadas por Jellyfin.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de descubrimiento de clientes (segundos)", + "LabelEnableBlastAliveMessagesHelp": "Habilita esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en tu red.", + "LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida", + "LabelEnableAutomaticPortMapHelp": "Redirecciona automáticamente los puertos públicos de tu router a los puertos locales de tu servidor a través de UPnP. Esto puede no funcionar con algunos modelos de routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", + "LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte de álbumes. Otros podrían fallar al reproducir con esta opción habilitada.", + "LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL", + "LabelEasyPinCode": "Código PIN sencillo:", + "LabelDynamicExternalId": "{0} Id:", + "LabelDropShadow": "Mostrar sombra:", + "LabelDroppedFrames": "Cuadros saltados:", + "LabelDropImageHere": "Arrastre la imagen aquí o haz para explorar.", + "LabelDownloadLanguages": "Descargar idiomas:", + "LabelDownMixAudioScaleHelp": "Incrementa el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", + "LabelDownMixAudioScale": "Incremento del audio cuando se hace downmix:", + "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en las que fueron transmitidas", + "LabelDisplayOrder": "Orden para mostrar:", + "LabelDisplayName": "Nombre a mostrar:", + "LabelDisplayMode": "Modo de pantalla:", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "LabelDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", + "LabelDisplayLanguage": "Idioma de pantalla:", + "LabelDiscNumber": "Número de disco:", + "LabelDidlMode": "Modo DIDL:", + "LabelDeviceDescription": "Descripción del dispositivo", + "LabelDeinterlaceMethod": "Método de desentrelazado:", + "LabelDefaultUserHelp": "Determina qué biblioteca de usuario será mostrada en los dispositivos conectados. Esto puede ser reemplazado para cada dispositivo empleando perfiles.", + "LabelDefaultUser": "Usuario por defecto:", + "LabelDefaultScreen": "Pantalla por defecto:", + "LabelDeathDate": "Fecha de defunción:", + "LabelDay": "Día:", + "LabelDateTimeLocale": "Configuración regional de fecha y hora:", + "LabelDateAddedBehaviorHelp": "Si un valor de metadatos está presente, siempre se utilizará antes de cualquiera de estas opciones.", + "LabelDateAddedBehavior": "Comportamiento de la fecha de adición para nuevo contenido:", + "LabelDateAdded": "Fecha de adición:", + "LabelDashboardTheme": "Tema del panel de control del servidor:", + "LabelCustomRating": "Calificación personalizada:", + "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjalo vacío para usar el nombre reportado por el dispositivo.", + "LabelCustomDeviceDisplayName": "Nombre a mostrar:", + "LabelCustomCssHelp": "Aplica tu propio estilo personalizado a la interfaz web.", + "LabelCustomCss": "CSS personalizado:", + "LabelCustomCertificatePathHelp": "Ruta a un archivo PKCS #12 que contiene un certificado y una clave privada para habilitar el soporte TLS en un dominio personalizado.", + "LabelCustomCertificatePath": "Ruta del certificado SSL personalizado:", + "LabelCurrentPassword": "Contraseña actual:", + "LabelCriticRating": "Calificación de los críticos:", + "LabelCountry": "País:", + "LabelCorruptedFrames": "Cuadros corruptos:", + "LabelContentType": "Tipo de contenido:", + "LabelCommunityRating": "Calificación de la comunidad:", + "LabelCollection": "Colección:", + "LabelChannels": "Canales:", + "LabelCertificatePasswordHelp": "Si tu certificado requiere una contraseña, por favor, introdúcela aquí.", + "LabelCertificatePassword": "Contraseña del certificado:", + "TabArtists": "Artistas", + "TabAlbums": "Álbumes", + "TabAlbumArtists": "Artistas del álbum", + "TabAdvanced": "Avanzado", + "TabAccess": "Acceso", + "TV": "TV", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de solo lectura. Los cambios a un perfil del sistema serán guardados en un nuevo perfil personalizado.", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso que este usuario tiene a la función SyncPlay. SyncPlay permite sincronizar la reproducción con otros dispositivos.", + "MessageDeleteTaskTrigger": "¿Estás seguro de querer eliminar este disparador de tarea?", + "MessageCreateAccountAt": "Crear una cuenta en {0}", + "MessageContactAdminToResetPassword": "Por favor, contacta a tu administrador para restablecer tu contraseña.", + "MessageConfirmShutdown": "¿Estás seguro de que deseas apagar el servidor?", + "MessageConfirmRevokeApiKey": "¿Estás seguro de querer revocar esta clave API? La conexión de la aplicación con el servidor Jellyfin será terminada abruptamente.", + "MessageConfirmRestart": "¿Estás seguro de que deseas reiniciar el servidor Jellyfin?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer remover esta ubicación?", + "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", + "MessageConfirmProfileDeletion": "¿Estás seguro de querer eliminar este perfil?", + "MessageConfirmDeleteTunerDevice": "¿Estás seguro de querer eliminar este dispositivo?", + "MessageConfirmDeleteGuideProvider": "¿Estás seguro de querer eliminar este proveedor de guía?", + "MessageConfirmAppExit": "¿Deseas salir?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Estás seguro de querer remover esta carpeta de medios?", + "MessageAreYouSureDeleteSubtitles": "¿Estás seguro de querer eliminar este subtítulo?", + "MessageAlreadyInstalled": "Esta versión ya se encuentra instalada.", + "Menu": "Menú", + "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que está reproduciendo el medio.", + "MediaInfoStreamTypeVideo": "Video", + "MediaInfoStreamTypeSubtitle": "Subtítulo", + "MediaInfoStreamTypeEmbeddedImage": "Imagen incrustada", + "MediaInfoStreamTypeData": "Dato", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoSoftware": "Software", + "MediaInfoTimestamp": "Fecha y hora", + "MediaInfoSize": "Tamaño", + "MediaInfoSampleRate": "Tasa de muestreo", + "MediaInfoResolution": "Resolución", + "MediaInfoRefFrames": "Tramas de referencia", + "MediaInfoProfile": "Perfil", + "MediaInfoPixelFormat": "Formato de pixel", + "MediaInfoPath": "Ruta", + "MediaInfoLevel": "Nivel", + "MediaInfoLayout": "Esquema", + "MediaInfoLanguage": "Idioma", + "MediaInfoInterlaced": "Entrelazado", + "MediaInfoFramerate": "Velocidad de cuadros", + "MediaInfoForced": "Forzado", + "MediaInfoExternal": "Externo", + "MediaInfoDefault": "Por defecto", + "MediaInfoContainer": "Contenedor", + "MediaInfoCodecTag": "Etiqueta de códec", + "MediaInfoCodec": "Códec", + "MediaInfoChannels": "Canales", + "MediaInfoBitrate": "Velocidad de bits", + "MediaInfoBitDepth": "Profundidad de bit", + "MediaInfoAspectRatio": "Relación de aspecto", + "MediaInfoAnamorphic": "Anamórfico", + "MaxParentalRatingHelp": "El contenido con una calificación más alta será ocultado a este usuario.", + "MarkUnplayed": "Marcar como no reproducido", + "MarkPlayed": "Marcar como reproducido", + "MapChannels": "Mapear canales", + "ManageRecording": "Administrar grabación", + "ManageLibrary": "Administrar biblioteca", + "Logo": "Logo", + "LiveTV": "TV en vivo", + "LiveBroadcasts": "Emisiones en vivo", + "Live": "En vivo", + "LabelCancelled": "Cancelado", + "LabelCachePathHelp": "Especifica una ubicación personalizada para los archivos caché del servidor como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", + "LabelCachePath": "Ruta de la caché:", + "LabelCache": "Caché:", + "LabelBurnSubtitles": "Quemar subtítulos:", + "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", + "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", + "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", + "LabelBitrate": "Velocidad de bits:", + "LabelBirthYear": "Año de nacimiento:", + "LabelBirthDate": "Fecha de nacimiento:", + "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", + "LabelAuthProvider": "Proveedor de autenticación:", + "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", + "LabelAudioLanguagePreference": "Idioma preferido de audio:", + "LabelAudioCodec": "Códec de audio:", + "LabelAudioChannels": "Canales de audio:", + "LabelAudioBitrate": "Velocidad de bits de audio:", + "LabelAudioBitDepth": "Profundidad de bits de audio:", + "LabelAudio": "Audio", + "LabelArtistsHelp": "Separar múltiples empleando ;", + "LabelArtists": "Artistas:", + "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", + "LabelAppName": "Nombre de la aplicación", + "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", + "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", + "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante los períodos de inactividad cuando no haya usuarios activos.", + "LabelAllowServerAutoRestart": "Permite al servidor reiniciarse automáticamente para aplicar actualizaciones", + "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "LabelAll": "Todos", + "LabelAlbumArtists": "Artistas del álbum:", + "LabelAlbumArtPN": "PN del arte del álbum:", + "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Ancho máximo del arte del álbum:", + "LabelAlbumArtMaxHeightHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Altura máxima del arte del álbum:", + "LabelAlbumArtHelp": "PN usado para el arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", + "LabelAlbum": "Álbum:", + "LabelAirsBeforeSeason": "Transmisión antes de la temporada:", + "LabelAirsBeforeEpisode": "Transmisión antes del episodio:", + "LabelAirsAfterSeason": "Transmisión después de la temporada:", + "LabelAirTime": "Duración:", + "LabelAirDays": "Se emite los días:", + "LabelAccessStart": "Hora de inicio:", + "LabelAccessEnd": "Hora de finalización:", + "LabelAccessDay": "Día de la semana:", + "LabelAbortedByServerShutdown": "(Abortado por apagado del servidor)", + "Label3DFormat": "Formato 3D:", + "Kids": "Niños", + "Items": "Elementos", + "ItemCount": "{0} elementos", + "InstantMix": "Mix instantáneo", + "InstallingPackage": "Instalando {0} (versión {1})", + "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", + "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", + "Images": "Imágenes", + "Identify": "Identificar", + "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", + "Horizontal": "Horizontal", + "Home": "Inicio", + "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de últimos medios", + "Hide": "Ocultar", + "Help": "Ayuda", + "HeadersFolders": "Carpetas", + "HeaderYears": "Años", + "HeaderXmlSettings": "Configuración XML", + "HeaderXmlDocumentAttributes": "Atributos del documento XML", + "HeaderXmlDocumentAttribute": "Atributo del documento XML", + "HeaderVideoTypes": "Tipos de video", + "HeaderVideoType": "Tipo de video", + "HeaderVideoQuality": "Calidad de video", + "HeaderUsers": "Usuarios", + "HeaderUploadImage": "Subir imagen", + "HeaderUpcomingOnTV": "Próximamente en TV", + "HeaderTypeText": "Introducir texto", + "HeaderTypeImageFetchers": "Recolectores de imágenes para {0}", + "HeaderTuners": "Sintonizador", + "HeaderTunerDevices": "Sintonizadores", + "HeaderTranscodingProfileHelp": "Agrega perfiles de transcodificación para indicar qué formatos deben ser usados cuando se requiere transcodificar.", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTracks": "Pistas", + "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", + "HeaderTaskTriggers": "Disparadores de tarea", + "HeaderTags": "Etiquetas", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulo describen los formatos de subtítulo soportados por el dispositivo.", + "HeaderSubtitleProfiles": "Perfiles de subtítulo", + "HeaderSubtitleProfile": "Perfil de subtítulo", + "HeaderSubtitleDownloads": "Descarga de subtítulos", + "HeaderSubtitleAppearance": "Apariencia de subtítulos", + "HeaderStopRecording": "Detener grabación", + "HeaderStatus": "Estado", + "HeaderStartNow": "Iniciar ahora", + "HeaderSpecialFeatures": "Características especiales", + "HeaderSpecialEpisodeInfo": "Información del episodio especial", + "HeaderSortOrder": "Clasificar ordenado", + "HeaderSortBy": "Ordenar por", + "HeaderShutdown": "Apagar", + "HeaderSetupLibrary": "Configura tus bibliotecas de medios", + "HeaderSettings": "Configuración", + "HeaderServerSettings": "Configuración del servidor", + "HeaderSelectServerCachePathHelp": "Explora o introduce la ruta a utilizar para los archivos caché del servidor. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", + "HeaderSelectServer": "Seleccionar servidor", + "HeaderSelectPath": "Seleccionar ruta", + "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", + "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", + "HeaderSecondsValue": "{0} segundos", + "HeaderSeasons": "Temporadas", + "HeaderSchedule": "Programación", + "HeaderScenes": "Escenas", + "HeaderRunningTasks": "Tareas en ejecución", + "HeaderRevisionHistory": "Historial de versiones", + "HeaderRestart": "Reiniciar", + "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", + "HeaderResponseProfile": "Perfil de respuesta", + "HeaderRemoveMediaLocation": "Remover ubicación de medios", + "HeaderRemoveMediaFolder": "Remover carpeta de medios", + "HeaderRemoteControl": "Control remoto", + "HeaderRemoteAccessSettings": "Opciones de acceso remoto", + "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", + "HeaderRecordingOptions": "Opciones de grabación", + "HeaderRecentlyPlayed": "Reproducido recientemente", + "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderProfileInformation": "Información del perfil", + "HeaderProfile": "Perfil", + "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", + "HeaderPluginInstallation": "Instalación de complemento", + "HeaderPleaseSignIn": "Por favor, inicia sesión", + "HeaderPlaybackError": "Error de reproducción", + "HeaderPlayback": "Reproducción de medios", + "HeaderPlayOn": "Reproducir en", + "HeaderPlayAll": "Reproducir todo", + "HeaderPinCodeReset": "Restablecer código PIN", + "HeaderPhotoAlbums": "Álbumes de fotos", + "HeaderPeople": "Personas", + "HeaderPendingInvitations": "Invitaciones pendientes", + "HeaderPaths": "Rutas", + "HeaderPasswordReset": "Restablecer contraseña", + "HeaderPassword": "Contraseña", + "HeaderParentalRatings": "Clasificación parental", + "HeaderOtherItems": "Otros elementos", + "HeaderOnNow": "Transmitiendo ahora", + "HeaderNextVideoPlayingInValue": "El siguiente video se reproducirá en {0}", + "HeaderNextEpisodePlayingInValue": "El siguiente episodio se reproducirá en {0}", + "HeaderNewDevices": "Nuevos dispositivos", + "HeaderNewApiKey": "Nueva clave API", + "HeaderNavigation": "Navegación", + "HeaderMyMediaSmall": "Mis medios (pequeño)", + "HeaderMyMedia": "Mis medios", + "HeaderMyDevice": "Mi dispositivo", + "HeaderMusicVideos": "Videos musicales", + "HeaderMusicQuality": "Calidad de la música", + "HeaderMovies": "Películas", + "HeaderMoreLikeThis": "Más como esto", + "HeaderMetadataSettings": "Configuración de metadatos", + "HeaderMediaInfo": "Info del medio", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderMedia": "Medios", + "HeaderLoginFailure": "Falló el inicio de sesión", + "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV", + "HeaderLiveTv": "TV en vivo", + "HeaderLibrarySettings": "Configuraciones de biblioteca", + "HeaderLibraryOrder": "Orden de las bibliotecas", + "HeaderLibraryFolders": "Carpetas de bibliotecas", + "HeaderLibraryAccess": "Acceso a bibliotecas", + "HeaderLibraries": "Bibliotecas", + "HeaderLatestRecordings": "Últimas grabaciones", + "HeaderLatestMusic": "Última música", + "HeaderLatestMovies": "Últimas películas", + "HeaderLatestMedia": "Últimos medios", + "HeaderLatestEpisodes": "Últimos episodios", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderKeepSeries": "Conservar serie", + "HeaderKeepRecording": "Conservar grabación", + "HeaderItems": "Elementos", + "HeaderInstantMix": "Mix instantáneo", + "HeaderInstall": "Instalar", + "HeaderImageSettings": "Configuración de imagen", + "HeaderImageOptions": "Opciones de imagen", + "HeaderIdentifyItemHelp": "Introduce uno o más criterios de búsqueda. Elimina criterios para expandir los resultados.", + "HeaderIdentificationHeader": "Encabezado de identificación", + "HeaderIdentificationCriteriaHelp": "Introduzca, al menos, un criterio de identificación.", + "HeaderIdentification": "Identificación", + "HeaderHttpsSettings": "Opciones HTTPS", + "HeaderHttpHeaders": "Encabezados HTTP", + "HeaderHome": "Inicio", + "HeaderGuideProviders": "Proveedores de Guías de TV", + "HeaderGenres": "Géneros", + "HeaderFrequentlyPlayed": "Reproducido frecuentemente", + "HeaderForgotPassword": "Olvidé mi contraseña", + "HeaderForKids": "Para niños", + "HeaderFilters": "Filtros", + "HeaderFetcherSettings": "Configuración del recolector", + "HeaderFetchImages": "Obtener imágenes:", + "HeaderFeatures": "Características", + "HeaderFeatureAccess": "Acceso a características", + "HeaderFavoritePlaylists": "Listas de reproducción favoritas", + "HeaderFavoriteVideos": "Videos favoritos", + "LabelMetadataPath": "Ruta para los metadatos:", + "LabelMetadataDownloadersHelp": "Habilita y prioriza tus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", + "LabelMetadataDownloadLanguage": "Idioma preferido para las descargas:", + "LabelMetadata": "Metadatos:", + "LabelMessageTitle": "Título del mensaje:", + "LabelMessageText": "Texto del mensaje:", + "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima cuando se transmite.", + "LabelMaxStreamingBitrate": "Calidad máxima de transmisión:", + "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por elemento:", + "LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.", + "LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:", + "LabelMaxParentalRating": "Máxima clasificación parental permitida:", + "LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:", + "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:", + "LabelMatchType": "Tipo de coincidencia:", + "LabelManufacturerUrl": "URL del fabricante", + "LabelManufacturer": "Fabricante:", + "LabelLogs": "Registros:", + "LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.", + "LabelLoginDisclaimer": "Aviso legal:", + "LabelLockItemToPreventChanges": "Bloquear este elemento para evitar cambios futuros", + "LabelLocalHttpServerPortNumberHelp": "El número de puerto TCP al que el servidor HTTP de Jellyfin debería enlazar.", + "LabelLocalHttpServerPortNumber": "Número de puerto local HTTP:", + "LabelLineup": "Programación:", + "LabelLibraryPageSizeHelp": "Establece el número de elementos a mostrar en una página de biblioteca. Establece en 0 para deshabilitar el paginado.", + "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", + "LabelLanguage": "Idioma:", + "LabelLanNetworks": "Redes LAN:", + "LabelKodiMetadataUserHelp": "Guarda los datos de visto en archivos NFO para que otras aplicaciones los utilicen.", + "LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:", + "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.", + "LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Habilita la sustitución de rutas de imágenes usando la configuración de sustitución de rutas del servidor.", + "LabelKodiMetadataEnablePathSubstitution": "Habilitar sustitución de ruta", + "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes estas pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con las pieles de Kodi.", + "LabelKodiMetadataEnableExtraThumbs": "Copiar extrafanart al campo extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Todas las fechas dentro de los archivos NFO serán analizadas usando este formato.", + "LabelKodiMetadataDateFormat": "Formato de fecha de estreno:", + "HeaderFavoritePeople": "Personas favoritas", + "HeaderFavoriteMovies": "Películas favoritas", + "HeaderFavoriteBooks": "Libros favoritos", + "HeaderExternalIds": "IDs externos:", + "HeaderError": "Error", + "HeaderEpisodes": "Episodios", + "HeaderEnabledFieldsHelp": "Desmarca un campo para bloquearlo y prevenir que sus datos sean cambiados.", + "HeaderEnabledFields": "Campos habilitados", + "HeaderEditImages": "Editar imágenes", + "HeaderEasyPinCode": "Código PIN sencillo", + "HeaderDVR": "DVR", + "HeaderDownloadSync": "Descargar y sincronizar", + "HeaderDisplay": "Pantalla", + "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", + "HeaderDirectPlayProfile": "Perfil de reproducción directa", + "HeaderDevices": "Dispositivos", + "HeaderDeviceAccess": "Acceso a dispositivos", + "HeaderDeveloperInfo": "Info del desarrollador", + "HeaderDetectMyDevices": "Detectar mis dispositivos", + "HeaderDeleteTaskTrigger": "Borrar disparador de tarea", + "HeaderDeleteProvider": "Eliminar proveedor", + "HeaderDeleteItems": "Eliminar elementos", + "HeaderDeleteItem": "Eliminar elemento", + "HeaderDeleteDevice": "Eliminar dispositivo", + "HeaderDefaultRecordingSettings": "Configuración predeterminada de grabaciones", + "HeaderDateIssued": "Fecha de emisión", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderContinueListening": "Continuar escuchando", + "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el formato ha sido configurado para reproducción directa.", + "HeaderContainerProfile": "Perfil del contenedor", + "HeaderConnectionFailure": "Falla de conexión", + "HeaderConnectToServer": "Conectarse al servidor", + "HeaderConfirmRevokeApiKey": "Revocar clave API", + "HeaderConfirmProfileDeletion": "Confirmar eliminación de perfil", + "HeaderConfirmPluginInstallation": "Confirmar instalación de complemento", + "HeaderConfigureRemoteAccess": "Configurar acceso remoto", + "HeaderCodecProfileHelp": "Los perfiles de códecs indican las limitaciones de un dispositivo al reproducir códecs específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el códec ha sido configurado para reproducción directa.", + "HeaderCodecProfile": "Perfil de códec", + "HeaderChapterImages": "Imágenes de los capítulos", + "HeaderChannels": "Canales", + "HeaderChannelAccess": "Acceso a los canales", + "HeaderCastCrew": "Reparto y equipo", + "HeaderCastAndCrew": "Reparto y equipo", + "HeaderCancelSeries": "Cancelar serie", + "HeaderCancelRecording": "Cancelar grabación", + "HeaderBranding": "Establecer marca", + "HeaderBooks": "Libros", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin clasificación o con información de clasificación desconocida:", + "HeaderAutomaticUpdates": "Actualizaciones automáticas", + "HeaderAudioSettings": "Configuración de audio", + "HeaderAudioBooks": "Audiolibros", + "HeaderAppearsOn": "Aparece en", + "HeaderApp": "Aplicación", + "ApiKeysCaption": "Lista de claves API actualmente habilitadas", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderApiKeys": "Claves API", + "HeaderApiKey": "Clave API", + "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", + "HeaderAlert": "Alerta", + "HeaderAlbums": "Álbumes", + "HeaderAdmin": "Administrador", + "HeaderAdditionalParts": "Partes adicionales", + "HeaderAddUser": "Agregar usuario", + "HeaderAddUpdateImage": "Agregar/Actualizar Imagen", + "HeaderAddToPlaylist": "Agregar a lista de reproducción", + "HeaderAddToCollection": "Agregar a colección", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderActivity": "Actividad", + "HeaderActiveRecordings": "Grabaciones activas", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderAccessScheduleHelp": "Crea una programación de acceso para limitar el acceso a ciertos horarios.", + "HeaderAccessSchedule": "Programación de acceso", + "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrate de que tu sistema operativo y controladores de video están actualizados. Si tienes dificultades reproduciendo videos después de habilitar esto, necesitarás volver a cambiar la configuración a Ninguno.", + "HDPrograms": "Programas en HD", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar el rendimiento, o uno más lento para mejorar la calidad.", + "H264CrfHelp": "El «Factor de transferencia constante» (CRF) es la configuración de calidad por defecto para el codificador x264. Puedes establecer valores entre 0 y 51, donde los valores más bajos dan como resultado mejor calidad (a expensas de archivos más grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, así que puedes usar este valor como punto de referencia.", + "GuideProviderSelectListings": "Elegir listados", + "GuideProviderLogin": "Iniciar sesión", + "Guide": "Guía", + "GuestStar": "Estrella invitada", + "GroupVersions": "Agrupar versiones", + "GroupBySeries": "Agrupar por series", + "Genre": "Género", + "General": "General", + "Fullscreen": "Pantalla completa", + "Friday": "Viernes", + "FormatValue": "Formato: {0}", + "FolderTypeUnset": "Contenido variado", + "FolderTypeTvShows": "Series de TV", + "FolderTypeMusicVideos": "Videos musicales", + "FolderTypeMusic": "Música", + "FolderTypeMovies": "Películas", + "FolderTypeBooks": "Libros", + "Filters": "Filtros", + "FileReadError": "Ha ocurrido un error al leer el archivo.", + "FileReadCancelled": "La lectura del archivo ha sido cancelada.", + "FileNotFound": "Archivo no encontrado.", + "File": "Archivo", + "FetchingData": "Obteniendo datos adicionales", + "Features": "Características", + "Favorite": "Favorito", + "FastForward": "Avance rápido", + "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que has introducido. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor, verifica la ruta e inténtalo de nuevo.", + "Extras": "Extras", + "ExtractChapterImagesHelp": "La extracción de imágenes de capítulos permitirá a los clientes mostrar menús gráficos de selección de escenas. El proceso puede ser lento, intensivo en recursos y puede requerir varios gigabytes de espacio. Se ejecuta cuando se descubren los videos y también como una tarea programada cada noche. La programación es configurable en el área de tareas programadas. No se recomienda ejecutar esta tarea durante las horas de mayor uso.", + "ExtraLarge": "Extra grande", + "ExitFullscreen": "Salir de la pantalla completa", + "EveryNDays": "Cada {0} días", + "ErrorSavingTvProvider": "Hubo un error al guardar el proveedor de TV. Por favor, asegúrate de que sea accesible e inténtalo de nuevo.", + "ErrorPleaseSelectLineup": "Por favor, selecciona una programación e inténtalo de nuevo. Si no hay disponible ninguna, entonces, por favor, verifica que tu nombre de usuario, contraseña, y código postal sean correctos.", + "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización debe ser mayor que la hora de inicio.", + "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrate de que tu información sea correcta e inténtalo de nuevo.", + "ErrorDeletingItem": "Hubo un error eliminando el elemento del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", + "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor, asegúrate de que el archivo existe e inténtalo de nuevo.", + "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor, asegúrate de que esté disponible e inténtalo de nuevo.", + "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a ese destino.", + "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de tu cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesites acceder al sitio web de Schedules Direct y eliminar otras programaciones de tu cuenta antes de continuar.", + "Episodes": "Episodios", + "Episode": "Episodio", + "EndsAtValue": "Termina a las {0}", + "Ended": "Finalizado", + "EnableDetailsBannerHelp": "Mostrar una imagen banner en la parte superior de la página de detalles del elemento.", + "EnableDetailsBanner": "Banner de detalles", + "EnableThemeVideosHelp": "Reproducir videos temáticos en el fondo mientras se navega por la biblioteca.", + "EnableThemeVideos": "Videos temáticos", + "EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.", + "EnableThemeSongs": "Canciones temáticas", + "EnableStreamLoopingHelp": "Habilita esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitar esto cuando no es requerido puede causar problemas.", + "EnableStreamLooping": "Repetir automáticamente las transmisiones en vivo", + "EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.", + "EnablePhotos": "Mostrar fotografías", + "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción actual.", + "EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción", + "EnableHardwareEncoding": "Habilitar codificación por hardware", + "EnableExternalVideoPlayersHelp": "Un menú de reproductor externo se mostrara cuando inicie la reproducción de un video.", + "EnableExternalVideoPlayers": "Reproductores de video externos", + "EnableDisplayMirroring": "Duplicado de pantalla", + "EnableColorCodedBackgrounds": "Fondos de colores codificados", + "EnableCinemaMode": "Modo cine", + "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", + "EnableBackdrops": "Imágenes de fondo", + "EditSubtitles": "Editar subtítulos", + "EditMetadata": "Editar metadatos", + "EditImages": "Editar imágenes", + "Edit": "Editar", + "EasyPasswordHelp": "Tu código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", + "DropShadow": "Sombra paralela", + "DrmChannelsNotImported": "Los canales con DRM no serán importados.", + "DownloadsValue": "{0} descargas", + "Download": "Descargar", + "Down": "Abajo", + "DoNotRecord": "No grabar", + "DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.", + "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", + "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como recientes o continuar viendo", + "DisplayInMyMedia": "Mostrar en la pantalla de inicio", + "Display": "Pantalla", + "Dislike": "No me gusta", + "Disconnect": "Desconectar", + "Disc": "DIsco", + "Disabled": "Desactivado", + "Directors": "Directores", + "DirectStreaming": "Transmisión directa", + "DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", + "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video será reempaquetado sobre la marcha antes de transmitirlo al dispositivo.", + "DirectPlaying": "Reproducción directa", + "DeviceAccessHelp": "Esto solo se aplica a los dispositivos que pueden ser identificados de manera única y no impedirá el acceso desde navegadores. Filtrar el acceso a los dispositivos de los usuarios les impedirá usar nuevos dispositivos hasta que hayan sido aprobados aquí.", + "DetectingDevices": "Detectando dispositivos", + "Desktop": "Escritorio", + "Descending": "Descendente", + "DeleteUserConfirmation": "¿Estás seguro de querer eliminar este usuario?", + "DeleteUser": "Eliminar usuario", + "DeleteMedia": "Eliminar medios", + "DeleteImageConfirmation": "¿Estás seguro de querer eliminar esta imagen?", + "DeleteImage": "Eliminar imagen", + "DeleteDeviceConfirmation": "¿Estás seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión con él.", + "Delete": "Eliminar", + "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", + "DefaultSubtitlesHelp": "Los subtítulos se cargan basados en los indicadores «por defecto» y «forzado» incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", + "DefaultMetadataLangaugeDescription": "Estos son sus valores por defecto y pueden ser personalizados en cada biblioteca.", + "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor, inténtalo de nuevo más tarde.", + "Default": "Por defecto", + "DeathDateValue": "Falleció: {0}", + "DatePlayed": "Fecha de reproducción", + "DateAdded": "Fecha de adición", + "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", + "CriticRating": "Calificación de los críticos", + "CopyStreamURLError": "Hubo un error al copiar la URL.", + "CopyStreamURLSuccess": "URL copiada con éxito.", + "CopyStreamURL": "Copiar la URL de la transmisión", + "Continuing": "Continuando", + "ContinueWatching": "Continuar viendo", + "Connect": "Conectar", + "ConfirmEndPlayerSession": "¿Deseas apagar Jellyfin en {0}?", + "ConfirmDeletion": "Confirmar eliminación", + "HeaderServerAddressSettings": "Configuración de la dirección del servidor", + "HeaderSeriesStatus": "Estado de la serie", + "HeaderSeriesOptions": "Opciones de serie", + "HeaderSeries": "Series", + "HeaderSendMessage": "Enviar mensaje", + "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", + "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteImage": "¿Eliminar imagen?", + "ConfigureDateAdded": "Configura cómo se determina la fecha de adición en el panel de control del servidor Jellyfin en la configuración de la biblioteca", + "Composer": "Compositor", + "CommunityRating": "Calificación de la comunidad", + "ColorTransfer": "Transferencia de color", + "ColorSpace": "Espacio de color", + "ColorPrimaries": "Colores primarios", + "ClientSettings": "Configuración del cliente", + "CinemaModeConfigurationHelp": "El modo cine lleva la experiencia del cine directamente a tu sala de estar con la capacidad de reproducir trailers e introducciones personalizadas antes de la función principal.", + "ChannelNumber": "Número de canal", + "ChannelNameOnly": "Canal {0} solamente", + "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", + "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicarán a contenido nuevo agregado a tu biblioteca. Para aplicar los cambios a los títulos existentes, necesitarás actualizar sus metadatos manualmente.", + "Categories": "Categorías", + "CancelSeries": "Cancelar serie", + "CancelRecording": "Cancelar grabación", + "ButtonWebsite": "Sitio web", + "ButtonViewWebsite": "Ver sitio web", + "ButtonUp": "Arriba", + "ButtonUninstall": "Desinstalar", + "ButtonTrailer": "Trailer", + "ButtonTogglePlaylist": "Lista de reproducción", + "ButtonToggleContextMenu": "Más", + "ButtonSubtitles": "Subtítulos", + "ButtonSubmit": "Enviar", + "ButtonSplit": "Dividir", + "ButtonStop": "Detener", + "ButtonStart": "Iniciar", + "ButtonSort": "Ordenar", + "ButtonSignOut": "Cerrar sesión", + "ButtonSignIn": "Iniciar sesión", + "ButtonShutdown": "Apagar", + "ButtonShuffle": "Aleatorio", + "ButtonSettings": "Configuración", + "ButtonSend": "Enviar", + "ButtonSelectView": "Seleccionar vista", + "ButtonSelectServer": "Seleccionar servidor", + "ButtonSelectDirectory": "Seleccionar directorio", + "ButtonSearch": "Búsqueda", + "ButtonScanAllLibraries": "Escanear todas las bibliotecas", + "ButtonSave": "Guardar", + "ButtonRevoke": "Revocar", + "ButtonResume": "Continuar", + "ButtonRestart": "Reiniciar", + "ButtonResetPassword": "Restablecer contraseña", + "ButtonResetEasyPassword": "Restablecer código PIN sencillo", + "ButtonRepeat": "Repetir", + "ButtonRename": "Renombrar", + "ButtonRemove": "Remover", + "ButtonRefreshGuideData": "Actualizar datos de la guía", + "ButtonRefresh": "Actualizar", + "ButtonQuickStartGuide": "Guía de inicio rápido", + "ButtonProfile": "Perfil", + "ButtonPreviousTrack": "Pista anterior", + "ButtonPlay": "Reproducir", + "ButtonPause": "Pausar", + "ButtonParentalControl": "Control parental", + "ButtonOpen": "Abrir", + "ButtonOk": "OK", + "ButtonOff": "Apagar", + "ButtonNextTrack": "Pista siguiente", + "ButtonNew": "Nuevo", + "ButtonNetwork": "Red", + "ButtonMore": "Más", + "ButtonManualLogin": "Inicio de sesión manual", + "ButtonLibraryAccess": "Acceso a biblioteca(s)", + "ButtonLearnMore": "Aprender más", + "ButtonInfo": "Info", + "ButtonHome": "Inicio", + "ButtonHelp": "Ayuda", + "ButtonGuide": "Guía", + "ButtonGotIt": "Hecho", + "ButtonFullscreen": "Pantalla completa", + "ButtonForgotPassword": "Olvidé mi contraseña", + "ButtonFilter": "Filtro", + "ButtonEditOtherUserPreferences": "Editar el perfil, la imagen y las preferencias personales de este usuario.", + "ButtonEditImages": "Editar imágenes", + "ButtonEdit": "Editar", + "ButtonDownload": "Descargar", + "ButtonDown": "Abajo", + "ButtonDeleteImage": "Eliminar imagen", + "ButtonDelete": "Eliminar", + "ButtonConnect": "Conectar", + "ButtonChangeServer": "Cambiar servidor", + "ButtonCancel": "Cancelar", + "ButtonBack": "Atrás", + "ButtonAudioTracks": "Pistas de audio", + "ButtonArrowUp": "Arriba", + "ButtonArrowRight": "Derecha", + "ButtonArrowLeft": "Izquierda", + "ButtonArrowDown": "Abajo", + "ButtonAddUser": "Agregar usuario", + "ButtonAddServer": "Agregar servidor", + "ButtonAddScheduledTaskTrigger": "Agregar disparador", + "ButtonAddMediaLibrary": "Agregar biblioteca de medios", + "ButtonAddImage": "Agregar imagen", + "ButtonAdd": "Agregar", + "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", + "BrowsePluginCatalogMessage": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", + "Browse": "Explorar", + "BoxRear": "Caja (parte trasera)", + "Box": "Caja", + "BookLibraryHelp": "Los libros de texto y audiolibros están soportados. Revisa la {0} guía de nombrado de libros {1}.", + "Blacklist": "Lista negra", + "BirthPlaceValue": "Lugar de nacimiento: {0}", + "BirthLocation": "Lugar de nacimiento", + "BirthDateValue": "Nacimiento: {0}", + "Banner": "Banner", + "Backdrops": "Imágenes de fondo", + "Backdrop": "Imagen de fondo", + "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del idioma)", + "Auto": "Auto", + "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "Audio": "Audio", + "AttributeNew": "Nuevo", + "AspectRatio": "Relación de aspecto", + "AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.", + "Ascending": "Ascendente", + "AsManyAsPossible": "Tantos como sea posible", + "Artist": "Artista", + "Art": "Arte", + "AroundTime": "Alrededor de", + "Anytime": "En cualquier momento", + "AnyLanguage": "Cualquier idioma", + "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados independientemente del idioma del audio.", + "AlwaysPlaySubtitles": "Siempre reproducir", + "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", + "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", + "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", + "AllowFfmpegThrottling": "Regular transcodificaciones", + "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", + "AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha", + "AllowMediaConversionHelp": "Permitir o denegar acceso a la función de convertir medios.", + "AllowMediaConversion": "Permitir conversión de medios", + "AllowHWTranscodingHelp": "Permite al sintonizador transcodificar las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllLibraries": "Todas las bibliotecas", + "AllLanguages": "Todos los idiomas", + "AllEpisodes": "Todos los episodios", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX...)", + "AllChannels": "Todos los canales", + "All": "Todo", + "Alerts": "Alertas", + "EnableBlurhash": "Habilitar marcadores de posición borrosos para imágenes", + "ShowMore": "Mostrar más", + "ShowLess": "Mostrar menos", + "EnableBlurhashHelp": "Las imágenes que aún se están cargando se mostrarán con un marcador de posición borroso", + "ButtonSyncPlay": "SyncPlay", + "MessageNoGenresAvailable": "Permitir a algunos proveedores de metadatos obtener géneros desde Internet.", + "MessageAddRepository": "Si desea agregar un repositorio, haga clic en el botón al lado del encabezado y complete la información solicitada.", + "LabelRepositoryNameHelp": "Un nombre personalizado para distinguir este repositorio de cualquier otro agregado a su servidor.", + "LabelRepositoryName": "Nombre del repositorio", + "LabelRepositoryUrlHelp": "La ubicación del manifiesto del repositorio que desea incluir.", + "LabelRepositoryUrl": "URL del repositorio", + "HeaderNewRepository": "Nuevo repositorio", + "MessageNoRepositories": "Sin repositorios.", + "EnableFasterAnimationsHelp": "Usar animaciones y transiciones más rápidas", + "EnableFasterAnimations": "Animaciones más rápidas", + "EnableDecodingColorDepth10Vp9": "Habilitar la decodificación de hardware de 10-Bit para VP9", + "EnableDecodingColorDepth10Hevc": "Habilitar la decodificación de hardware de 10-Bit para HEVC", + "ClearQueue": "Limpiar cola", + "StopPlayback": "Detener reproducción", + "ButtonPlayer": "Reproducir", + "ButtonCast": "Emitir", + "Writers": "Escritores", + "ViewAlbumArtist": "Ver Álbum de Artista", + "TabRepositories": "Repositorios" +} diff --git a/src/strings/es_DO.json b/src/strings/es_DO.json index 7c42778a1c6..9f5d2996317 100644 --- a/src/strings/es_DO.json +++ b/src/strings/es_DO.json @@ -12,5 +12,6 @@ "Books": "Libros", "Albums": "Álbumes", "Artists": "Artistas", - "Channels": "Canales" + "Channels": "Canales", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/fa.json b/src/strings/fa.json index d4b60fe7c69..877c0af4906 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -1,8 +1,8 @@ { "All": "همه", - "AllowMediaConversion": "اجازه تبدیل رسانه ها", - "AllowMediaConversionHelp": "دادن یا ندادن دسترسی به ویژگی تبدیل رسانه ها", - "AllowRemoteAccess": "اجازه دادن اتصال از راه دور به سرور Jellyfin", + "AllowMediaConversion": "اجازه تبدیل رسانه‌ها", + "AllowMediaConversionHelp": "دادن یا ندادن دسترسی به ویژگی تبدیل رسانه‌ها.", + "AllowRemoteAccess": "اجازه بدهید اتصال‌های از راه دور به این سرور Jellyfin متصل شوند.", "AllowRemoteAccessHelp": "اگرانتخاب نشود، تمامی اتصال های از راه دور بلوکه می شوند.", "Browse": "مرور کردن", "ButtonAddUser": "افزودن کاربر", @@ -19,11 +19,11 @@ "ButtonSort": "مرتب سازی", "DeleteMedia": "حذف رسانه", "Disabled": "غیرفعال شده", - "FolderTypeBooks": "کتاب ها", - "FolderTypeMovies": "سینمایی ها", - "FolderTypeMusic": "موسیقی", - "FolderTypeMusicVideos": "موزیک ویدئوها", - "FolderTypeTvShows": "سریال تلویزیونی", + "FolderTypeBooks": "کتاب‌ها", + "FolderTypeMovies": "فیلم‌ها", + "FolderTypeMusic": "موسیقی‌ها", + "FolderTypeMusicVideos": "موزیک ویدیوها", + "FolderTypeTvShows": "سریال‌های تلویزیونی", "HeaderAddUser": "اضافه کردن کاربر", "HeaderContinueWatching": "ادامه تماشا", "HeaderCustomDlnaProfiles": "پروفایل های سفارشی", @@ -33,9 +33,9 @@ "HeaderFilters": "فیلتر ها", "HeaderImageOptions": "گزینه های تصویر", "HeaderInstantMix": "درهم کردن فوری", - "HeaderKodiMetadataHelp": "برای فعال یا غیرفعال سازی متاداده های Nfo ، یک کتابخانه را در صفحه تنظیم کتابخانه Jellyfin ویرایش کرده و قسمت سرورهای متاداده را مسیردهی کنید.", + "HeaderKodiMetadataHelp": "برای فعال یا غیرفعال سازی ابرداده‌های Nfo ، یک کتابخانه را در صفحه تنظیم کتابخانه Jellyfin ویرایش کرده و قسمت سرورهای ابرداده را مسیردهی کنید.", "HeaderLatestEpisodes": "آخرین قسمت ها", - "HeaderNextUp": "بعدی", + "HeaderNextUp": "قسمت بعدی", "HeaderPaths": "مسیرها", "HeaderPlayAll": "پخش همه", "HeaderPreferredMetadataLanguage": "زبان مدنظر اطلاعات محتوی", @@ -45,15 +45,15 @@ "HeaderTypeImageFetchers": "{0} هماهنگ کننده تصویر", "HeaderUsers": "کاربران", "LabelAudioLanguagePreference": "اولویت زبان صدا:", - "LabelContentType": "نوع محتوی", - "LabelCountry": "کشور", + "LabelContentType": "نوع محتوا:", + "LabelCountry": "کشور:", "LabelCurrentPassword": "رمز فعلی:", - "LabelCustomCertificatePath": "مسیر اختصصای گواهینامه SSL:", + "LabelCustomCertificatePath": "مسیر اختصاصی گواهینامه SSL:", "LabelCustomCertificatePathHelp": "پچ به فایل PKCS #12 حاوی یک گواهینامه و کلید خصوصی است تا پشتیبانی از TLS را در یک دامنه شخصی فعال کند.", "LabelDisplayMissingEpisodesWithinSeasons": "نمایش قسمت های ناموجود در بین فصل ها", "LabelFinish": "پایان", - "LabelLanguage": "زبان", - "LabelMaxParentalRating": "حداکثر درجه سنی مجاز والدین", + "LabelLanguage": "زبان:", + "LabelMaxParentalRating": "حداکثر امتیاز سنی مجاز والدین:", "LabelNewPassword": "رمز جدید:", "LabelNewPasswordConfirm": "تایید رمز جدید:", "LabelNext": "بعدی", @@ -64,7 +64,7 @@ "LabelSecureConnectionsMode": "حالت اتصال ایمن:", "LabelSelectUsers": "انتخاب کاربران:", "LabelTimeLimitHours": "محدودیت زمان (ساعت):", - "LabelTypeMetadataDownloaders": "{0} دانلود کننده ی متاداده:", + "LabelTypeMetadataDownloaders": "{0} دانلود کننده فراداده:", "LabelYourFirstName": "اسم کوچک شما:", "LabelYoureDone": "به پایان رسید!", "LibraryAccessHelp": "انتخاب پوشه های رسانه برای اشتراک گذاری با این کاربر. مدیر سیستم میتواند با استفاده از مدیریت متاداده همه ی پوشه ها را ویرایش کند.", @@ -95,9 +95,9 @@ "TabArtists": "هنرمندان", "TabEpisodes": "قسمت ها", "TabGenres": "ژانرها", - "TabLatest": "آخرین", - "TabMetadata": "اطلاعات محتوی", - "TabMusicVideos": "موزیک ویدیو ها", + "TabLatest": "جدیدترین‌ها", + "TabMetadata": "فراداده", + "TabMusicVideos": "موزیک ویدیوها", "TabNetworks": "شبکه ها", "TabNotifications": "اعلان ها", "TabPassword": "رمز عبور", @@ -106,41 +106,1453 @@ "TabProfiles": "پروفایل ها", "TabShows": "سریال ها", "TabSongs": "آهنگ ها", - "TabSuggestions": "پیشنهادات", + "TabSuggestions": "پیشنهادها", "TabUpcoming": "بزودی", "TellUsAboutYourself": "در مورد خودتان به ما بگویید", - "ThisWizardWillGuideYou": "این ویزارد برای انجام تنظیمات به شما کمک می کند. برای شروع، لطفا زبان مورد نظر خود را انتخاب فرمایید", + "ThisWizardWillGuideYou": "این عمل برای انجام تنظیمات به شما کمک می‌کند. برای شروع، لطفا زبان مورد نظر خود را انتخاب کنید.", "UserProfilesIntro": "Jellyfin دارای پشتیبانی داخلی از پروفایل کاربران می باشد. با فعال سازی هر کاربر، او می تواند تنظیمات ، وضعیت پخش و کنترل والدین خاص خودش را داشته باشد.", "WelcomeToProject": "به Jellyfin خوش آمدید!", - "WizardCompleted": "همه چیزی که فعلا می خواهیم همین است.جمع آوری اطلاعات کتابخانه های شما هم اکنون توسط Jellyfin آغاز شده است. اپلیکیشن های ما را امتحان کنید و سپس بر روی پایان کلیک کنید تا پیشخوان سرور را مشاهده نمایید.", - "Albums": "آلبوم ها", + "WizardCompleted": "همه چیزی که فعلا می‌خواهیم همین است. جمع آوری اطلاعات کتابخانه‌های شما هم اکنون توسط Jellyfin آغاز شده است. اپلیکیشن‌های ما را امتحان کنید و سپس بر روی پایان کلیک کنید تا پیشخوان سرور را مشاهده نمایید.", + "Albums": "آلبوم‌ها", "Artists": "هنرمندان", - "Books": "کتاب ها", - "Channels": "کانال ها", - "Collections": "کلکسیون ها", - "Favorites": "مورد علاقه ها", - "Folders": "پوشه ها", + "Books": "کتاب‌ها", + "Channels": "کانال‌ها", + "Collections": "مجموعه‌ها", + "Favorites": "مورد علاقه‌ها", + "Folders": "پوشه‌ها", "Genres": "ژانرها", "HeaderAlbumArtists": "هنرمندان آلبوم", - "HeaderFavoriteShows": "سریال های مورد علاقه", - "HeaderFavoriteEpisodes": "قسمت های مورد علاقه", - "HeaderFavoriteAlbums": "آلبوم های مورد علاقه", + "HeaderFavoriteShows": "سریال‌های مورد علاقه", + "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه", + "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", "HeaderFavoriteArtists": "هنرمندان مورد علاقه", - "HeaderFavoriteSongs": "آهنگ های مورد علاقه", - "HeaderLiveTV": "پخش زنده تلویزیون", - "Movies": "فیلم های سینمایی", - "Photos": "عکس ها", - "Playlists": "لیست های پخش", - "Shows": "سریال ها", - "Songs": "آهنگ ها", - "Sync": "همگامسازی", - "ValueSpecialEpisodeName": "ویژه- {0}", - "AllEpisodes": "تمام قسمت ها", - "AllLanguages": "تمام زبان ها", - "AllLibraries": "تمام کتابخانه ها", - "AllowHWTranscodingHelp": "اگر فعال شود, اجازه میدهید تبدیل ( کم و زیاد کردن کیفیت ) درلحظه و توسط کارت دریافت سیگنال صورت گیرد. این کمک میکند به اینکه سرور جلیفین کمتر عمل تبدیل را انجام دهد.", - "AllowOnTheFlySubtitleExtraction": "اجازه میدهد در لحظه زیرنویس بازشود", + "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه", + "HeaderLiveTV": "پخش زنده", + "Movies": "فیلم‌ها", + "Photos": "عکس‌ها", + "Playlists": "لیست‌های پخش", + "Shows": "سریال‌ها", + "Songs": "موسیقی‌ها", + "Sync": "همگام‌سازی", + "ValueSpecialEpisodeName": "ویژه - {0}", + "AllEpisodes": "تمام قسمت‌ها", + "AllLanguages": "تمام زبان‌ها", + "AllLibraries": "تمام کتابخانه‌ها", + "AllowHWTranscodingHelp": "اگر فعال شود, اجازه می‌دهید تبدیل کیفیت در لحظه انجام شود. این ممکن است به کاهش کدگذاری لازم برای Jellyfin منجر بشود.", + "AllowOnTheFlySubtitleExtraction": "اجازه می‌دهد در لحظه زیرنویس بازشود", "Add": "افزودن", "Actor": "بازیگر", - "AccessRestrictedTryAgainLater": "دسترسی در حال حاضر محدود شده است. لطفا دوباره تلاش کنید." + "AccessRestrictedTryAgainLater": "دسترسی در حال حاضر محدود شده است. لطفا دوباره تلاش کنید.", + "ButtonShuffle": "مخلوط کردن", + "ButtonSettings": "تنظیمات", + "ButtonSend": "ارسال", + "ButtonSelectView": "انتخاب نما", + "ButtonSelectServer": "انتخاب سرور", + "ButtonSearch": "جستجو", + "ButtonScanAllLibraries": "اسکن تمام کتابخانه‌ها", + "ButtonRevoke": "ابطال", + "ButtonResume": "ادامه", + "ButtonRestart": "راه اندازی مجدد", + "ButtonResetEasyPassword": "بازنشانی کد پین آسان", + "ButtonRepeat": "تکرار", + "ButtonRename": "تغییر نام", + "ButtonRemove": "حذف", + "ButtonRefreshGuideData": "به‌روز‌رسانی داده‌ی راهنما", + "ButtonRefresh": "به‌روز‌رسانی", + "ButtonProfile": "نمایه", + "ButtonNextTrack": "ترانه پسین", + "ButtonPreviousTrack": "ترانه پیشین", + "ButtonPause": "مکث", + "ButtonParentalControl": "رتبه بندی والدین", + "ButtonOpen": "باز", + "ButtonOff": "خاموش", + "ButtonNetwork": "شبکه", + "ButtonMore": "بیشتر", + "ButtonManualLogin": "ورود دستی", + "ButtonLibraryAccess": "دسترسی به کتابخانه", + "ButtonLearnMore": "بیشتر بدانید", + "ButtonInfo": "اطلاعات", + "ButtonHome": "خانه", + "ButtonHelp": "کمک", + "ButtonGuide": "راهنما", + "ButtonGotIt": "متوجه شدم", + "ButtonFullscreen": "تمام صفحه", + "ButtonForgotPassword": "فراموشی گذرواژه", + "ButtonEditImages": "ویرایش عکس‌ها", + "ButtonEdit": "ویرایش", + "ButtonDownload": "بارگیری", + "ButtonDown": "پایین", + "ButtonDelete": "حذف", + "ButtonConnect": "اتصال", + "ButtonChangeServer": "تغییر سرور", + "ButtonBack": "بازگشت", + "ButtonArrowUp": "بالا", + "ButtonArrowRight": "راست", + "ButtonArrowLeft": "چپ", + "ButtonArrowDown": "پایین", + "ButtonAddServer": "افزودن سرور", + "ButtonAddScheduledTaskTrigger": "افزودن راه انداز", + "ButtonAddMediaLibrary": "افزودن کتابخانه رسانه", + "ButtonAddImage": "افزودن تصویر", + "ButtonAdd": "افزودن", + "BoxRear": "جعبه (پشت)", + "Box": "جعبه", + "Blacklist": "لیست سیاه", + "BirthPlaceValue": "محل تولد: {0}", + "BirthLocation": "محل تولد", + "BirthDateValue": "متولد: {0}", + "Banner": "سرصفحه", + "Backdrops": "پس زمینه‌ها", + "Backdrop": "پس زمینه", + "AutoBasedOnLanguageSetting": "خودکار (بر اساس تنظیمات زبانی)", + "Auto": "خودکار", + "Audio": "صدا", + "AttributeNew": "جدید", + "AspectRatio": "نسبت ابعاد", + "AskAdminToCreateLibrary": "از کاربر مدیر بخواهید که یک کتابخانه ایجاد کند.", + "Ascending": "بالا رونده", + "AsManyAsPossible": "تا حدی که ممکن است", + "AroundTime": "حدود", + "Anytime": "هر زمانی", + "AnyLanguage": "هر زبانی", + "AlwaysPlaySubtitles": "همیشه پخش کن", + "AllowFfmpegThrottling": "گلوگاه تبدیل کیفیت", + "AllChannels": "همه کانال‌ها", + "Alerts": "هشدارها", + "Aired": "پخش شده", + "AirDate": "تاریخ پخش", + "AddedOnValue": "{0} افزوده شد", + "AddToPlaylist": "افزودن به لیست پخش", + "AddToPlayQueue": "افزودن به صف پخش", + "AddToCollection": "افزودن به مجموعه", + "ExitFullscreen": "خروج از تمام صفحه", + "EveryNDays": "هر {0} روز", + "ErrorMessageStartHourGreaterThanEnd": "زمان پایان باید پس از زمان شروع باشد.", + "Episodes": "قسمت‌ها", + "EndsAtValue": "تمام شده در {0}", + "Ended": "تمام شده", + "EnableThemeVideos": "تم فیلم‌ها", + "EnableThemeSongs": "آهنگ‌های تم", + "EnableStreamLooping": "چرخش خودکار پخش‌های زنده", + "EnablePhotos": "نمایش عکس‌ها", + "EnableNextVideoInfoOverlay": "نمایش اطلاعات ودیوی بعدی حین پخش ویدیو", + "EnableHardwareEncoding": "فعال سازی رمزگذاری سخت افزاری", + "EnableExternalVideoPlayersHelp": "یک منوی پخش کننده ویدیوی خارجی، زمانی که شروع به پخش ویدیو می‌شود نمایش داده خواهد شد.", + "EnableExternalVideoPlayers": "پخش کننده ویدیوی خارجی", + "EnableDisplayMirroring": "نمایش آینه", + "EnableCinemaMode": "حالت سینما", + "EnableBackdrops": "پشت‌زمینه‌ها", + "EditSubtitles": "ویرایش زیرنویس‌ها", + "EditMetadata": "ویرایش فراداده", + "EditImages": "ویرایش عکس‌ها", + "Edit": "ویرایش", + "DropShadow": "سایه پشت زمینه", + "DrmChannelsNotImported": "کانال‌ها با DRM وارد نخواند شد.", + "DownloadsValue": "{0} بارگیری‌ها", + "Download": "بارگیری", + "Down": "پایین", + "DoNotRecord": "ضبط نکن", + "DisplayModeHelp": "سبک رابط کاربری مورد نظر خود را انتخاب کنید.", + "DisplayMissingEpisodesWithinSeasons": "قسمت‌های ناموجود در فصل‌ها را نمایش بده", + "DisplayInMyMedia": "نمایش در صفحه‌ی خانه", + "Display": "نمایش", + "Dislike": "دوست نداشتن", + "Disconnect": "قطع اتصال", + "Disc": "دیسک", + "Directors": "کارگردانان", + "Director": "کارگردان", + "DirectStreaming": "پخش مستقیم", + "DirectStreamHelp2": "پخش مستقیم فایل از قدرت پردازش بسیار کمی بدون از دست دادن کیفیت ویدیو استفاده می‌کند.", + "DirectPlaying": "پخش مستقیم", + "DetectingDevices": "در حال تشخیص دستگاه‌ها", + "Descending": "پایین رونده", + "Depressed": "پژمرده", + "DeleteUserConfirmation": "آیا اطمینان دارید که می‌خواهید این کاربر را حذف کنید؟", + "DeleteUser": "حذف کاربر", + "DeleteImageConfirmation": "آیا اطمینان دارید که می‌خواهید این تصویر را حذف کنید؟", + "DeleteImage": "حذف تصویر", + "DeleteDeviceConfirmation": "آیا از حذف این دستگاه اطمینان دارید؟ هنگامی که یک کاربر دوباره با آن دستگاه وارد شود، دوباره نمایش داده می‌شود.", + "Delete": "حذف", + "DefaultMetadataLangaugeDescription": "این موارد پیشفرض‌های شماست و می‌توانید برای هر کتابخانه آن را شخصی سازی کنید.", + "DefaultErrorMessage": "خطایی در پردازش درخواست رخ داد. لطفا اندکی بعد دوباره تلاش کنید.", + "Default": "پیشفرض", + "DeathDateValue": "تلف شد: {0}", + "DatePlayed": "تاریخ پخش شده", + "DateAdded": "تاریخ اضافه شده", + "CriticRating": "امتیاز منتقدان", + "CopyStreamURLError": "در کپی کردن آدرس خطایی رخ داد.", + "CopyStreamURLSuccess": "آدرس با موفقیت کپی شد.", + "CopyStreamURL": "کپی آدرس پخش", + "Continuing": "ادامه", + "ContinueWatching": "ادامه تماشا", + "Connect": "اتصال", + "ConfirmEndPlayerSession": "آیا می‌خواهید Jellyfin را روی {0} خاموش کنید؟", + "ConfirmDeletion": "تایید حذف", + "ConfirmDeleteImage": "حذف تصویر؟", + "Composer": "آهنگساز", + "CommunityRating": "امتیاز عمومی", + "ColorTransfer": "انتقال رنگ", + "ColorSpace": "فضای رنگی", + "ColorPrimaries": "مقدمات رنگی", + "ClientSettings": "تنظیمات مشتری", + "ChannelNumber": "شماره کانال", + "ChannelNameOnly": "تنها کانال {0}", + "Categories": "دسته‌بندی‌ها", + "CancelSeries": "لغو سریال‌ها", + "CancelRecording": "لغو ضبط", + "ButtonWebsite": "وبسایت", + "ButtonViewWebsite": "بازدید وبسایت", + "ButtonUp": "بالا", + "ButtonUninstall": "حذف نصب", + "ButtonTrailer": "تریلر", + "ButtonSubtitles": "زیرنویس‌ها", + "ButtonSubmit": "تایید", + "ButtonSplit": "جدا کردن", + "ButtonStop": "توقف", + "ButtonStart": "شروع", + "ButtonSignIn": "ورود", + "ButtonShutdown": "خاموش", + "ButtonSelectDirectory": "انتخاب مسیر", + "ButtonEditOtherUserPreferences": "نمایه، تصویر و ترجیحات شخصی این کاربر را ویرایش کنید.", + "BrowsePluginCatalogMessage": "برای مرور کردن افزونه‌های موجود، به فروشگاه افزونه‌های ما سر بزنید.", + "AuthProviderHelp": "ارائه دهنده تأیید اعتبار را انتخاب کنید تا برای تأیید اعتبار گذرواژه این کاربر استفاده شود.", + "HeaderRecordingPostProcessing": "در حال ضبط پس پردازش", + "HeaderRecordingOptions": "گزینه‌های ضبط", + "HeaderRecentlyPlayed": "به تازگی پخش شده", + "HeaderProfileInformation": "اطلاعات نمایه", + "HeaderProfile": "نمایه", + "HeaderPluginInstallation": "نصب افزونه", + "HeaderPleaseSignIn": "لطفا وارد شوید", + "HeaderPlaybackError": "خطای پخش", + "HeaderPlayback": "پخش رسانه", + "HeaderPlayOn": "پخش در", + "HeaderPinCodeReset": "بازنشانی پین کد", + "HeaderPhotoAlbums": "آلبوم‌های عکس", + "HeaderPeople": "افراد", + "HeaderPendingInvitations": "دعوت‌های در انتظار", + "HeaderPasswordReset": "بازنشانی گذرواژه", + "HeaderPassword": "گذرواژه", + "HeaderParentalRatings": "رتبه بندی والدین", + "HeaderOtherItems": "آیتم‌های دیگر", + "HeaderOnNow": "هم اکنون", + "HeaderNextVideoPlayingInValue": "پخش ویدیوی بعد در {0}", + "HeaderNextEpisodePlayingInValue": "پخش قسمت بعدی در {0}", + "HeaderNewDevices": "دستگاه جدید", + "HeaderNewApiKey": "کلید API جدید", + "HeaderMyMediaSmall": "رسانه‌ی من (کوچک)", + "HeaderMyMedia": "رسانه‌ی من", + "HeaderMyDevice": "دستگاه‌های من", + "HeaderMusicVideos": "موزیک ویدیوها", + "HeaderMusicQuality": "کیفیت آهنگ", + "HeaderMovies": "فیلم‌ها", + "HeaderMoreLikeThis": "موارد مشابه با این", + "HeaderMetadataSettings": "تنظیمات ابرداده", + "HeaderMediaInfo": "اطلاعات رسانه", + "HeaderMediaFolders": "پوشه‌های رسانه", + "HeaderMedia": "رسانه", + "HeaderLoginFailure": "ورود ناموفق", + "HeaderLiveTvTunerSetup": "تنظیم تلویزیون زنده", + "HeaderLiveTv": "تلویزیون زنده", + "HeaderLibrarySettings": "تنظیمات کتابخانه", + "HeaderLibraryOrder": "ترتیت کتابخانه", + "HeaderLibraryFolders": "پوشه‌های کتابخانه", + "HeaderLibraryAccess": "دسترسی به کتابخانه", + "HeaderLibraries": "کتابخانه‌ها", + "HeaderLatestRecordings": "جدیدترین‌ ضبط‌ها", + "HeaderLatestMusic": "جدیدترین‌ آهنگ‌ها", + "HeaderLatestMovies": "جدیدترین‌ فیلم‌ها", + "HeaderLatestMedia": "جدیدترین‌ رسانه‌ها", + "HeaderKeepSeries": "سریال ادامه دهید", + "HeaderKeepRecording": "ضبط را ادامه دهید", + "HeaderItems": "آیتم‌ها", + "HeaderInstall": "نصب", + "HeaderImageSettings": "تنظیمات عکس", + "HeaderIdentifyItemHelp": "یک یا بیشتر مورد برای جستجو وارد کنید. موارد را حذف کنید تا نتیجه جستجو را افزایش دهید.", + "HeaderIdentificationHeader": "سرفصل تعیین هویت", + "HeaderIdentificationCriteriaHelp": "حداقل یک مورد تعیین هویت وارد کنید.", + "HeaderIdentification": "تعیین هویت", + "HeaderHttpHeaders": "سرفصل‌های HTTP", + "HeaderHome": "خانه", + "HeaderGuideProviders": "ارائه دهنده داده راهنمای تلویزیونی", + "HeaderGenres": "ژانرها", + "HeaderFrequentlyPlayed": "اغلب پخش شده", + "HeaderForgotPassword": "فراموشی گذرواژه", + "HeaderForKids": "برای کودکان", + "HeaderFetchImages": "دریافت عکس‌ها:", + "HeaderFeatures": "برجسته‌ها", + "HeaderFeatureAccess": "دسترسی‌های برجسته", + "HeaderFavoriteVideos": "ویدیو‌های مورد علاقه", + "HeaderFavoritePeople": "افراد مورد علاقه", + "HeaderFavoriteMovies": "فیلم‌های مورد علاقه", + "HeaderFavoriteBooks": "کتاب‌های مورد علاقه", + "HeaderExternalIds": "ID های خارجی:", + "HeaderError": "خطا", + "HeaderEpisodes": "قسمت‌ها", + "HeaderEnabledFieldsHelp": "یک فیلد را برای جلوگیری از تغییر در داده‌ی آن علامت بزنید تا قفل بشود.", + "HeaderEnabledFields": "فیلد‌های فعال شده", + "HeaderEditImages": "ویرایش عکس‌ها", + "HeaderDownloadSync": "بارگیری و همگام‌سازی", + "HeaderDisplay": "نمایش", + "HeaderDirectPlayProfileHelp": "نمایه‌ی پخش مستقیم را اضافه کنید تا مشخص کنید با چه فرمی دستگاه می‌تواند محلی برخورد کند.", + "HeaderDirectPlayProfile": "نمایه‌ی پخش مستقیم", + "HeaderDevices": "دستگاه‌ها", + "HeaderDeveloperInfo": "اطلاعات توسعه دهنده", + "HeaderDetectMyDevices": "تشخیص دستگاه‌های من", + "HeaderDeleteTaskTrigger": "حذف راه انداز وظیفه", + "HeaderDeleteProvider": "حذف ارائه‌دهنده", + "HeaderDeleteItems": "حذف آیتم‌ها", + "HeaderDeleteItem": "حذف آیتم", + "HeaderDeleteDevice": "حذف دستگاه", + "HeaderDefaultRecordingSettings": "تنظمیات پیش‌فرض ضبط", + "HeaderDateIssued": "تاریخ صدور", + "HeaderCodecProfileHelp": "نمایه‌های کدک محدودیت‌های یک دستگاه را هنگام پخش کدک‌های خاص نشان می‌دهد. اگر محدودیتی اعمال شود، رسانه‌ها کد گذاری می‌شوند ، حتی اگر کدک برای پخش مستقیم پیکربندی شده باشد.", + "HeaderCodecProfile": "نمایه کدک", + "HeaderChapterImages": "عکس‌های سکانس", + "HeaderChannels": "کانال‌ها", + "HeaderChannelAccess": "دسترسی به کانال", + "HeaderCastCrew": "بازیگران و کارکنان", + "HeaderCastAndCrew": "بازیگران و کارکنان", + "HeaderCancelSeries": "لغو سریال", + "HeaderCancelRecording": "لغو ضبط", + "HeaderBooks": "کتاب‌ها", + "HeaderBlockItemsWithNoRating": "موارد مسدود شده با نقص یا عدم وجود اطلاعات امتیاز:", + "LabelSkipIfAudioTrackPresentHelp": "این گزینه را عدم انتخاب کنید تا اطمینان حاصل کنید که همه ویدیوها فارغ از زبان صوت، زیرنویس دارند.", + "LabelSkipIfAudioTrackPresent": "اگر صدای پیش‌فرض با زبان دانلودی یکسان است پرش کن", + "LabelSkipForwardLength": "میزان رفتن به جلو:", + "LabelSkipBackLength": "میزان بازگشت به عقب:", + "LabelSkin": "پوسته:", + "LabelSize": "سایز:", + "LabelSimultaneousConnectionLimit": "محدودیت پخش همزمان:", + "LabelServerName": "نام سرور:", + "LabelServerHostHelp": "192.168.1.100:8096 یا https://myserver.com", + "LabelServerHost": "میزبان:", + "LabelSerialNumber": "شماره سریال", + "LabelSendNotificationToUsers": "ارسال اعلان به:", + "LabelSelectVersionToInstall": "نسخه مورد نظر برای نصب را انتخاب کنید:", + "LabelVersionInstalled": "{0} نصب شده است", + "EncoderPresetHelp": "یک مقدار سریع‌تر انتخاب کنید تا کارایی بهبود پیدا کند یا یک مقدار کُندتر انتخاب کنید تا کیفیت بهبود پیدا کند.", + "ShowYear": "نمایش سال", + "ShowTitle": "نمایش عنوان", + "ButtonAudioTracks": "آهنگ‌ها", + "AlbumArtist": "هنرمند آلبوم", + "Album": "آلبوم", + "HeaderAddScheduledTaskTrigger": "افزودن فعال‌ساز", + "HeaderActivity": "فعالیت‌ها", + "HeaderActiveRecordings": "ضبط‌های فعال", + "HeaderActiveDevices": "دستگاه‌های فعال", + "HeaderAccessScheduleHelp": "یک زمان‌بندی دسترسی ایجاد کنید تا دسترسی به ساعاتی مشخص محدود شود.", + "HeaderAccessSchedule": "زمان‌بندی دسترسی", + "HandledByProxy": "توسط reverse proxy مدیریت می‌شود", + "HDPrograms": "برنامه‌های HD", + "Filters": "صافی‌ها", + "FileReadError": "خطایی هنگام خواندن فایل رخ داد.", + "FileReadCancelled": "خواندن فایل لغو شد.", + "FileNotFound": "فایل پیدا نشد.", + "File": "فایل", + "FetchingData": "در حال دریافت داده‌های اضافی", + "Features": "برجسته‌ها", + "Favorite": "مورد علاقه", + "FastForward": "سریع جلو", + "Extras": "موارد اضافی", + "ExtraLarge": "فوق العاده بزرگ", + "BoxSet": "جعبه ست", + "Art": "هنر", + "Artist": "هنرمند", + "AllComplexFormats": "کلیه فرمت‌های پیچیده (ASS ، SSA ، VOBSUB ، PGS ، SUB ، IDX ، ...)", + "GuideProviderLogin": "ورود", + "Guide": "راهنما", + "GuestStar": "ستاره‌ی مهمان", + "GroupVersions": "نسخه‌های گروه", + "GroupBySeries": "گروه بندی بر اساس سریال‌ها", + "Genre": "ژانر", + "General": "عمومی", + "Fullscreen": "تمام صفحه", + "Friday": "جمعه", + "FormatValue": "قالب‌ها: {0}", + "FolderTypeUnset": "محتواهای مخلوط", + "TabMyPlugins": "افزونه‌های من", + "TabMusic": "موسیقی‌ها", + "TabMovies": "فیلم‌ها", + "TabLogs": "واقعه نگار‌ها", + "TabLiveTV": "تلویزیون زنده", + "LatestFromLibrary": "جدیدترین‌های {0}", + "Large": "بزرگ", + "LabelffmpegPath": "مسیر FFmpeg:", + "LabelZipCode": "کدپستی:", + "LabelYear": "سال:", + "LabelWeb": "وب:", + "LabelVideoResolution": "کیفیت ویدیو:", + "LabelVideo": "ویدیو", + "DashboardArchitecture": "معماری: {0}", + "DashboardOperatingSystem": "سیستم عامل: {0}", + "DashboardServerName": "سرور: {0}", + "DashboardVersionNumber": "نسخه: {0}", + "LabelVersion": "نسخه:", + "LabelValue": "مقدار:", + "LabelVaapiDevice": "دستگاه VA API:", + "LabelUsername": "نام کاربری:", + "LabelUserLibrary": "کتابخانه کاربر:", + "LabelUserAgent": "عامل کاربر:", + "LabelUser": "کاربر:", + "LabelUseNotificationServices": "استفاده از سرویس‌های زیر:", + "LabelTypeText": "متن", + "LabelType": "نوع:", + "LabelKodiMetadataEnablePathSubstitution": "فعال سازی تعویض مسیر", + "LabelKodiMetadataDateFormatHelp": "تمام تاریخ‌های موجود در فایل‌های NFO با استفاده از این قالب تجزیه می‌شوند.", + "LabelKodiMetadataDateFormat": "قالب تاریخ انتشار:", + "LabelKidsCategories": "دسته‌بندی‌های کودکان:", + "OnApplicationStartup": "هنگام شروع برنامه", + "EveryXHours": "هر {0} ساعت", + "EveryHour": "هر ساعت", + "EveryXMinutes": "هر {0} دقیقه", + "OnWakeFromSleep": "هنگام بیداری از خواب", + "WeeklyAt": "{0} در {1}", + "DailyAt": "روزانه در {0}", + "LastSeen": "آخرین بازدید {0}", + "PersonRole": "در نقش {0}", + "ListPaging": "{0}-{1} از {2}", + "Yesterday": "دیروز", + "Yes": "بلی", + "YadifBob": "Yadif Bob", + "Yadif": "Yadif", + "ValueConditions": "شرایط: {0}", + "ValueCodec": "کدک: {0}", + "ValueAudioCodec": "کدک صدا: {0}", + "ValueAlbumCount": "{0} آلبوم", + "Upload": "آپلود", + "Up": "بالا", + "Unrated": "بدون امتیاز", + "Unplayed": "پخش نشده", + "Unmute": "صدادار", + "UninstallPluginHeader": "حذف نصب افزونه", + "UninstallPluginConfirmation": "آیا اطمینان دارید که می‌خواهید {0} را حذف نصب کنید؟", + "Uniform": "یکپارچه", + "Tuesday": "سه‌شنبه", + "Transcoding": "کدگذاری", + "Trailers": "تریلرها", + "Track": "آهنگ", + "TrackCount": "{0} آهنگ", + "TitlePlayback": "پخش", + "TitleHostingSettings": "تنظیمات میزبانی", + "TitleHardwareAcceleration": "تسریع کننده سخت افزاری", + "Thursday": "پنج‌شنبه", + "Thumb": "بندانگشتی", + "ThemeVideos": "تم ویدیوها", + "ThemeSongs": "آهنگ‌های تم", + "TagsValue": "برچسب‌ها: {0}", + "Tags": "برچسب‌ها", + "TabUsers": "کاربران", + "Absolute": "کامل", + "Writer": "نویسنده", + "Whitelist": "لیست سفید", + "Wednesday": "چهارشنبه‌ها", + "Watched": "مشاهده شده", + "ViewPlaybackInfo": "مشاهده اطلاعات پخش", + "ViewArtist": "مشاهده هنرمند", + "ViewAlbum": "مشاهده آلبوم", + "VideoRange": "محدوده ویدیو", + "Vertical": "عمودی", + "ValueVideoCodec": "کدک ویدیو: {0}", + "ValueTimeLimitSingleHour": "محدودیت زمانی: 1 ساعت", + "ValueTimeLimitMultiHour": "محدودیت زمانی: {0} ساعت", + "ValueSongCount": "{0} آهنگ", + "ValueSeriesCount": "{0} سریال", + "ValueSeconds": "{0} ثانیه", + "ValueOneSong": "1 آهنگ", + "ValueOneSeries": "1 سریال", + "ValueOneMusicVideo": "1 موزیک ویدیو", + "ValueOneMovie": "1 فیلم", + "ValueOneEpisode": "1 قسمت", + "ValueOneAlbum": "1 آلبوم", + "ValueMusicVideoCount": "{0} موزیک ویدیو", + "ValueMovieCount": "{0} فیلم", + "ValueMinutes": "{0} دقیقه", + "ValueEpisodeCount": "{0} قسمت", + "ValueDiscNumber": "دیسک {0}", + "LabelImportOnlyFavoriteChannels": "محدود کردن کانال‌هایی که به عنوان مورد علاقه انتخاب شده‌اند", + "LabelDateAdded": "تاریخ اضافه شده:", + "LabelDashboardTheme": "تم داشبورد سرور:", + "LabelCustomRating": "امتیازدهی سفارشی:", + "LabelCustomDeviceDisplayName": "نام نمایشی:", + "LabelCustomCssHelp": "ظاهر سفارشی مورد نظر خود را در رابط وب اعمال کنید.", + "LabelCustomCss": "CSS سفارشی:", + "LabelCriticRating": "امتیاز منتقدان:", + "LabelCorruptedFrames": "فریم‌های خراب شده:", + "LabelImageType": "نوع عکس:", + "LabelIconMaxWidth": "حداکثر عرض آیکن:", + "LabelIconMaxHeight": "حداکثر ارتفاع آیکن:", + "LabelHttpsPort": "شماره پورت HTTPS محلی:", + "LabelHomeNetworkQuality": "کیفیت شبکه خانگی:", + "LabelHardwareAccelerationTypeHelp": "تسریع کننده سخت افزاری نیاز به پیکربندی اضافی دارد.", + "LabelSupportedMediaTypes": "نوع‌ رسانه‌های پشتیبانی شده:", + "LabelSubtitles": "زیرنویس‌ها", + "LabelSubtitlePlaybackMode": "حالت زیرنویس:", + "LabelSubtitleFormatHelp": "مثال: srt", + "LabelSubtitleDownloaders": "دانلود کننده زیرنویس:", + "LabelStreamType": "نوع پخش:", + "LabelStopping": "در حال توقف", + "LabelStopWhenPossible": "هنگامی که ممکن است متوقف شود:", + "LabelStatus": "وضعیت:", + "LabelStartWhenPossible": "هنگامی که ممکن است شروع شود:", + "LabelSportsCategories": "دسته‌بندی‌های ورزشی:", + "LabelSpecialSeasonsDisplayName": "نام نمایشی فصل مخصوص:", + "LabelSource": "منبع:", + "LabelSoundEffects": "جلوه‌های صدا:", + "LabelSortTitle": "مرتب‌سازی عنوان:", + "LabelSortOrder": "ترتیب مرتب‌سازی:", + "LabelSortBy": "مرتب‌سازی بر اساس:", + "LabelSonyAggregationFlags": "پرچم‌های جمع‌آوری سونی:", + "LabelSkipIfGraphicalSubsPresent": "صرف نظر کردن اگر ویدیو پیش از این زیرنویس چسبیده در خود دارد", + "EnableColorCodedBackgrounds": "پشت‌ زمینه‌های کدگذاری شده رنگی", + "DisplayMissingEpisodesWithinSeasonsHelp": "این مورد همچنین باید برای کتابخانه های تلویزیون در پیکربندی سرور فعال شود.", + "DisplayInOtherHomeScreenSections": "در بخش‌های صفحه اصلی مانند آخرین رسانه‌ها و ادامه تماشا نمایش بده", + "Desktop": "دسکتاپ", + "CustomDlnaProfilesHelp": "برای هدف قرار دادن یک دستگاه جدید یا سرپوش گذاشتن روی نمایه سیستم ، یک نمایه سفارشی ایجاد کنید.", + "ConfirmDeleteItems": "حذف این مورد، آن را هم از فایل سیستمی و هم از کتابخانه رسانه شما حذف می کند. آیا اطمینان دارید که می‌خواهید ادامه دهید؟", + "ConfirmDeleteItem": "حذف این مورد، آن را هم از فایل سیستمی و هم از کتابخانه رسانه شما حذف می کند. آیا اطمینان دارید که می‌خواهید ادامه دهید؟", + "AlwaysPlaySubtitlesHelp": "زیرنویس‌های متناسب با توجه به اولویت زبان بدون در نظر گرفتن زبان صوتی ویدیو پخش می شوند.", + "AllowedRemoteAddressesHelp": "لیستی از آدرس های IP یا ورودی‌های IP/‌netmask برای شبکه هایی که به آنها امکان ارتباط از راه دور داده می‌شود ، با کاما از هم جدا شدند. در صورت خالی ماندن ، تمام آدرسهای راه دور مجاز خواهند بود.", + "AllowOnTheFlySubtitleExtractionHelp": "زیرنویس های جاسازی شده را می‌توان از ویدئو ها استخراج کرد و به منظور جلوگیری از کدگذاری فیلم ، به صورت متن ساده به بازدید کننده ارسال کرد. در بعضی از سیستم‌ها این می‌تواند مدت زیادی طول بکشد و باعث شود پخش فیلم در طول فرآیند استخراج متوقف شود. این گزینه را غیرفعال کنید تا زیرنویس‌های جاسازی شده با استفاده از رمزگذاری ویدیو در حالی که به طور محلی توسط دستگاه بازدیدکننده پشتیبانی نمی‌شوند ارسال شود.", + "AdditionalNotificationServices": "برای نصب سرویس‌های اعلان اضافی، در فروشگاه افزونه‌ها جستجو کنید.", + "OptionThumbCard": "کارت بندانگشتی", + "OptionThumb": "بندانگشتی", + "OptionThursday": "پنجشنبه", + "OptionSunday": "یکشنبه", + "OptionSubstring": "زیررشته", + "OptionSpecialEpisode": "ویژه‌ها", + "OptionSaveMetadataAsHidden": "ذخیره فراداده‌ها و عکس‌ها به عنوان فایل‌های پنهان", + "OptionSaturday": "شنبه", + "OptionRuntime": "زمان اجرا", + "OptionResumable": "قابل از سرگیری", + "OptionResElement": "عنصر res", + "OptionReleaseDate": "تاریخ انتشار", + "OptionRegex": "عبارت منظم", + "OptionRandom": "تصادفی", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "پخش مستقیم HTTP", + "OptionProfileVideoAudio": "صوتی تصویری", + "OptionProfilePhoto": "عکس", + "OptionProfileAudio": "صدا", + "OptionPremiereDate": "تاریخ پخش", + "OptionPosterCard": "کارتِ پوستر", + "OptionPoster": "پوستر", + "OptionPlayCount": "تعداد پخش", + "OptionPlainVideoItems": "نمایش همه فیلم‌ها به عنوان موارد ویدیویی ساده", + "OptionPlainStorageFolders": "نمایش همه پوشه‌ها به عنوان پوشه‌های ذخیره سازی ساده", + "OptionParentalRating": "رتبه بندی والدین", + "OptionOnInterval": "در یک فاصله", + "BookLibraryHelp": "کتاب‌های صوتی و متنی پشتیبانی می‌شوند. {0} راهنمای نامگذاری کتاب {1} را مرور کنید.", + "TabInfo": "اطلاعات", + "TabGuide": "راهنما", + "TabFavorites": "مورد علاقه‌ها", + "TabDisplay": "نمایش", + "TabDirectPlay": "پخش مستقیم", + "TabDevices": "دستگاه‌ها", + "TabDashboard": "داشبورد", + "TabCollections": "مجموعه‌ها", + "TabCodecs": "کدک‌ها", + "TabChannels": "کانال‌ها", + "TabCatalog": "فهرست", + "TV": "تلویزیون", + "Sunday": "یکشنبه", + "TabTranscoding": "کدگذاری", + "TabTrailers": "تریلرها", + "Suggestions": "پیشنهادها", + "Subtitles": "زیرنویس‌ها", + "Studios": "استودیو‌ها", + "StopRecording": "توقف ضبط", + "Sports": "ورزش‌ها", + "SortName": "مرتب سازی نام", + "SortChannelsBy": "مرتب سازی کانال‌ها بر اساس:", + "SortByValue": "مرتب شده بر اساس {0}", + "Sort": "مرتب سازی", + "Smart": "باهوش", + "Smaller": "کوچکتر", + "Small": "کوچک", + "ButtonTogglePlaylist": "لیست پخش", + "ButtonToggleContextMenu": "بیشتر", + "TheseSettingsAffectSubtitlesOnThisDevice": "این تنظیمات روی زیرنویس‌ها در این دستگاه تأثیر می‌گذارد", + "TabStreaming": "در حال پخش", + "TabSettings": "تنظیمات", + "TabServer": "سرور", + "TabSeries": "سریال‌ها", + "TabScheduledTasks": "وظایف زمان بندی شده", + "TabResumeSettings": "ادامه", + "TabResponses": "پاسخ‌ها", + "TabRecordings": "ضبط‌ها", + "TabPlugins": "افزونه‌ها", + "TabPlaylists": "لیست‌های پخش", + "TabPlayback": "پخش", + "TabParentalControl": "رتبه بندی والدین", + "TabOther": "سایر", + "TabNfoSettings": "تنظیمات NFO", + "TabNetworking": "شبکه سازی", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "این تنظیمات همچنین در مورد هر پخش Chromecast که توسط این دستگاه شروع شده است اعمال می شود.", + "SmartSubtitlesHelp": "زیرنویس‌های متناسب با توجه به اولویت زبان بدون در نظر گرفتن زبان صوتی ویدیو پخش می شوند.", + "SkipEpisodesAlreadyInMyLibrary": "قسمت‌هایی که هم اکنون در کتابخانه من موجود است را ضبط نکن", + "SimultaneousConnectionLimitHelp": "حداکثر تعداد پخش‌های مجاز همزمان. ۰ را برای بدون محدودیت وارد کنید.", + "MessagePluginConfigurationRequiresLocalAccess": "برای پیکربندی این افزونه، لطفاً مستقیماً به سرور محلی خود وارد شوید.", + "MessagePleaseWait": "لطفا صبر کنید. این ممکن است چند دقیقه طول بکشد.", + "MessageNoServersAvailable": "هیچ سروری با استفاده از کشف خودکار سرور یافت نشد.", + "MessageNoPluginsInstalled": "هیچ افزونه‌ی نصب نشده است.", + "MessageNoMovieSuggestionsAvailable": "هیچ پیشنهادی برای فیلم در دسترس نیست. شروع به تماشای و رتبه بندی فیلم‌های خود بکنید ، و سپس دوباره به دیدن پیشنهاد‌های خود بیایید.", + "MessageNoAvailablePlugins": "افزونه‌ای موجود نیست.", + "MessageItemsAdded": "آیتم‌ها اضافه شدند.", + "MessageItemSaved": "آیتم ذخیره شد.", + "MessageUnauthorizedUser": "در حال حاضر مجاز به دسترسی به سرور نیستید. لطفا برای اطلاعات بیشتر با مدیر سرور خود تماس بگیرید.", + "MessageInvalidUser": "نام کاربری یا گذرواژه نامعتبر است. لطفا دوباره تلاش کنید.", + "MessageInvalidForgotPasswordPin": "کد پین نامعتبر یا منقضی شده وارد شد. لطفا دوباره تلاش کنید.", + "MessageInstallPluginFromApp": "این افزونه باید از داخل برنامه‌ای که قصد استفاده از آن را دارید نصب شود.", + "PasswordResetHeader": "بازنشانی گذرواژه", + "PasswordResetConfirmation": "آیا واقعا تمایل به بازنشانی گذرواژه دارید؟", + "PasswordResetComplete": "گذرواژه بازنشانی شد.", + "PasswordMatchError": "گذرواژه و تکرار گذرواژه باید یکسان باشند.", + "PackageInstallFailed": "{0} (نسخه {1}) نصب به مشکل برخورد.", + "PackageInstallCompleted": "{0} (نسخه {1})نصب به پایان رسید.", + "PackageInstallCancelled": "{0} ( نسخه {1})نصب لغو شد.", + "Overview": "بررسی اجمالی", + "OtherArtist": "هنرمند دیگر", + "OriginalAirDateValue": "زمان پخش اصلی : {0}", + "OptionWeekly": "هفتگی", + "OptionWeekends": "آخر هفته ها", + "OptionWeekdays": "روز های هفته", + "OptionWednesday": "چهارشنبه", + "OptionWakeFromSleep": "از خواب بیدار شدن", + "OptionUnairedEpisode": "قسمت های پخش نشده", + "OptionTvdbRating": "نمره TVDB", + "OptionTuesday": "سه شنبه", + "OptionTrackName": "نام ترک", + "OptionRequirePerfectSubtitleMatchHelp": "نتیجه کامل زیرنویس ها را به صورتی فیلتر می کند که فقط مواردی را که دقیقا با فایل تصویری شما آزمایش و تأیید شده اند ،شامل شود. حذف این گزینه احتمال بارگیری زیرنویس ها را افزایش می دهد ، اما شانس متن زیرنویس ناهماهنگ یا غلط نیز افزایش می یابد.", + "ServerNameIsShuttingDown": "سرور جلی فین - {0} در حال خاموش شدن می باشد.", + "ServerNameIsRestarting": "سرور جلی فین - {0} در حال راه اندازی مجدد می باشد.", + "SeriesYearToPresent": "{0}- در حال حاضر", + "SeriesSettings": "تنظیمات سریال", + "SeriesRecordingScheduled": "ضبط کردن سریال زمانبندی شد.", + "SeriesDisplayOrderHelp": "قسمت ها را بر اساس تاریخ پخش شدن، ترتیب دی وی دی یا فقط شماره مرتب کنید.", + "SeriesCancelled": "سریال متوقف شده است.", + "Series": "سریال ها", + "SelectAdminUsername": "لطفا یک نام کاربری برای حساب مدیر انتخاب کنید.", + "Season": "فصل", + "SearchResults": "نتایج جستجو", + "SearchForSubtitles": "زیرنویس ها را جستجو کنید", + "SearchForMissingMetadata": "فراداده های گم شده را جستجو کنید", + "SearchForCollectionInternetMetadata": "در اینترنت برای پیدا کردن فراداده و عکس جستجو کنید", + "Search": "جستجو کردن", + "Screenshots": "نماگرفت ها", + "Screenshot": "نماگرفت", + "Schedule": "برنامه زمانی", + "ScanLibrary": "کتابخانه را اسکن کنید", + "ScanForNewAndUpdatedFiles": "فایل های جدید و به روز رسانی شده را اسکن کنید", + "ChannelAccessHelp": "کانال هایی که قصد اشتراک گذاری با این کاربر را دارید انتخاب کنید. مدیران توانایی ویرایش همه ی کانال ها را با استفاده از مدیریت فراداده دارند.", + "ChangingMetadataImageSettingsNewContent": "تغییر تنظیمات دانلود فراداده ها و عکس ها فقط بر روی محتواهای جدیدی که به کتابخانه اضافه می شوند، اعمال می شوند. برای اعمال تغییرات بر روی محتوای موجود، شما باید فراداده را به صورت دستی به روزرسانی کنید.", + "Refresh": "به‌روز‌رسانی", + "Recordings": "ضبط شده ها", + "RecordingScheduled": "ضبط برنامه ریزی شد.", + "RecordingPathChangeMessage": "با تغییر محل ذخیره فایل های ضبط شده، فایل های موجود به صورت خودکار منتقل نمی شوند. در صورت نیاز، شما باید خودتان این کار را انجام دهید.", + "RecordingCancelled": "ضبط شدن لغو شد.", + "RecordSeries": "ضبط کردن سریال ها", + "Record": "ضبط کردن", + "RecommendationStarring": "با هنرمندی {0}", + "RecommendationDirectedBy": "کارگردانی شده توسط {0}", + "RecommendationBecauseYouWatched": "برای اینکه تو {0} مشاهده کردی", + "RecommendationBecauseYouLike": "برای اینکه تو {0} را نیز دوست داری", + "RecentlyWatched": "اخیرا مشاهده شده", + "Rate": "ارزیابی کن", + "Raised": "مطرح شده", + "QueueAllFromHere": "همه را از اینجا در صف قرار بده", + "Quality": "کیفیت", + "Programs": "برنامه ها", + "ProductionLocations": "محل تولید", + "Producer": "تولید کننده", + "Primary": "اصلی", + "Previous": "قبلی", + "Premieres": "برتر ها", + "Premiere": "برتر", + "PreferEmbeddedEpisodeInfosOverFileNames": "اطلاعات تعبیه شده در فراداده را به اسم فایل ترجیح بده", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "این از اطلاعات قسمت در فراداده های تعبیه شده در صورت موجود استفاده می کند.", + "PreferEmbeddedTitlesOverFileNamesHelp": "این عنوان نمایش را به صورت پیش فرض تعیین می کند، زمانی که فراداده اینترنتی یا محلی موجود نباشند.", + "PreferEmbeddedTitlesOverFileNames": "عنوان های تعبیه شده را به نام فایل ترجیح بده", + "PluginInstalledMessage": "افزونه با موفقیت نصب شد. برای اعمال تغییرات سرور جلیفین نیاز به بارگذاری مجدد دارد.", + "PleaseSelectTwoItems": "لطفا حداقل دو مورد را انتخاب کنید.", + "PleaseRestartServerName": "لطفا سرور جلیفین را دوباره بارگذاری کنید - {0}.", + "PleaseEnterNameOrId": "لطفا یک نام یا شناسه خارجی را وارد کنید.", + "PleaseConfirmPluginInstallation": "لطفا با کلیک بر روی OK تایید کنید که شما متن بالا را خوانده اید و می خواهید که افزونه را نصب کنید.", + "PleaseAddAtLeastOneFolder": "لطفا حداقل یک پوشه به این کتابخانه با کلیک بر روی \"افزودن\" اضافه کنید.", + "Played": "اجرا شده", + "PlaybackErrorNoCompatibleStream": "سرویس گیرنده با مدیا سازگاری ندارد و سرور یک فرمت سازگار را ارسال نمی کند.", + "PlayNextEpisodeAutomatically": "قسمت بعدی را به صورت خودکار اجرا کن", + "PlayNext": "بعدی را اجرا کن", + "PlayFromBeginning": "از ابتدا اجرا کن", + "PlaybackData": "اجرای مجدد داده", + "PlayAllFromHere": "همه را از اینجا اجرا کن", + "Play": "اجرا کردن", + "PinCodeResetConfirmation": "آیا مطمئن هستید که می خواهید کد پین را باز نشانی کنید؟", + "PinCodeResetComplete": "کد پین بازنشانی شد.", + "PictureInPicture": "تصویر در تصویر", + "Person": "فرد", + "PerfectMatch": "همتای کامل", + "People": "افراد", + "MillisecondsUnit": "میلی‌ثانیه", + "LabelSyncPlayTimeOffset": "اختلاف زمانی با سرور:", + "LabelSeriesRecordingPath": "مسیر ضبط سریال‌ها (اختیاری):", + "LabelSelectFolderGroups": "به طور خودکار محتواهای پوشه‌های زیر را به فیلم ، موسیقی و تلویزیون گروه بندی شود:", + "LabelSeasonNumber": "شماره فصل:", + "ConfigureDateAdded": "تنظیم کنید که چگونه تاریخ اضافه شده در داشبورد سرور Jellyfin تحت تنظیمات کتابخانه تعیین می‌شود", + "CinemaModeConfigurationHelp": "حالت سینما تجربه تئاتر گونه را مستقیم به اتاق نشیمن شما می‌آورد با قابلیت پخش تریلرها و پیش نمایش‌ها قبل از سایر ویژگی‌های اصلی.", + "LaunchWebAppOnStartup": "نمای وب هنگامی که سرور آغاز به کار می‌کند باز بشود", + "NoSubtitles": "خالی", + "NoSubtitleSearchResultsFound": "نتیجه‌ای یافت نشد.", + "NoPluginConfigurationMessage": "این افزونه هیچ تنظیماتی برای پیکربندی ندارد.", + "No": "خیر", + "Next": "بعدی", + "News": "اخبار", + "NewEpisodesOnly": "فقط قسمت‌های جدید", + "NewEpisodes": "قسمت‌های جدید", + "NewCollectionNameExample": "مثال: مجموعه‌ی جنگ ستارگان", + "NewCollection": "مجموعه جدید", + "Never": "هرگز", + "Name": "نام", + "MySubtitles": "زیرنویس‌های من", + "Mute": "سکوت", + "MusicVideo": "موزیک ویدیو", + "MusicArtist": "هنرمند موسیقی", + "MusicAlbum": "آلبوم موسیقی", + "Movie": "فیلم", + "AddItemToCollectionHelp": "افزودن موارد به مجموعه ها با جستجوی آنها و استفاده از منوهای راست کلیک یا ضربه بزنید تا آنها را به مجموعه اضافه کنید.", + "AllowFfmpegThrottlingHelp": "هنگامی که یک transcode یا remux به اندازه کافی پیش از موقعیت پخش فعلی می شود ، روند را متوقف می کند تا منابع کمتری مصرف کند. این بیشتر مفید است در هنگام تماشای بدون به دنبال اغلب. اگر مسائل مربوط به پخش را تجربه کنید ، این را خاموش کنید.", + "DefaultSubtitlesHelp": "زیرنویس ها بر اساس پرچم های پیش فرض و اجباری در ابرداده تعبیه شده بارگذاری می شوند. تنظیمات زبان در نظر گرفته می شوند زمانی که گزینه های متعدد در دسترس هستند.", + "DeinterlaceMethodHelp": "روش deinterlacing برای استفاده از زمانی که transcoding محتوای هم آمیختن را انتخاب کنید.", + "DeviceAccessHelp": "این فقط برای دستگاههایی است که می توانند به صورت منحصر به فرد شناسایی شوند و از دسترسی مرورگر جلوگیری نکنند. فیلتر کردن دسترسی دستگاه کاربر مانع از استفاده از دستگاه های جدید می شود تا اینکه در اینجا تایید شده باشند.", + "DirectStreamHelp1": "رسانه ها با دستگاه مربوط به رزولوشن و نوع رسانه (H. 264 ، AC3 و غیره) سازگار است ، اما در یک ظرف فایل ناسازگار (mkv ، avi ، wmv و غیره) است. ویدیو قبل از پخش آن به دستگاه دوباره بسته بندی می شود.", + "EasyPasswordHelp": "کد pin آسان شما برای دسترسی آفلاین در مشتریان پشتیبانی شده استفاده می شود و همچنین برای ورود به سیستم در شبکه آسان است.", + "EnableBackdropsHelp": "Display backdrops in the background of some pages while browsing the library.", + "EnableNextVideoInfoOverlayHelp": "At the end of a video, display info about the next video coming up in the current playlist.", + "EnablePhotosHelp": "Images will be detected and displayed alongside other media files.", + "EnableStreamLoopingHelp": "Enable this if live streams only contain a few seconds of data and need to be continuously requested. Enabling this when not needed may cause problems.", + "EnableThemeSongsHelp": "Play theme songs in the background while browsing the library.", + "EnableThemeVideosHelp": "Play theme videos in the background while browsing the library.", + "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.", + "Episode": "Episode", + "ErrorAddingListingsToSchedulesDirect": "There was an error adding the lineup to your Schedules Direct account. Schedules Direct only allows a limited number of lineups per account. You may need to log into the Schedules Direct website and remove others listings from your account before proceeding.", + "ErrorAddingMediaPathToVirtualFolder": "There was an error adding the media path. Please ensure the path is valid and the Jellyfin Server process has access to that location.", + "ErrorAddingTunerDevice": "There was an error adding the tuner device. Please ensure it is accessible and try again.", + "ErrorAddingXmlTvFile": "There was an error accessing the XMLTV file. Please ensure the file exists and try again.", + "ErrorDeletingItem": "There was an error deleting the item from Jellyfin Server. Please check that Jellyfin Server has write access to the media folder and try again.", + "ErrorGettingTvLineups": "There was an error downloading TV lineups. Please ensure your information is correct and try again.", + "ErrorPleaseSelectLineup": "Please select a lineup and try again. If no lineups are available, then please check that your username, password, and postal code is correct.", + "ErrorSavingTvProvider": "There was an error saving the TV provider. Please ensure it is accessible and try again.", + "LabelExtractChaptersDuringLibraryScanHelp": "Generate chapter images when videos are imported during the library scan. Otherwise, they will be extracted during the chapter images scheduled task, allowing the regular library scan to complete faster.", + "LabelFailed": "Failed", + "LabelFileOrUrl": "File or URL:", + "HeaderSelectServerCachePath": "Select Server Cache Path", + "HeaderSelectServerCachePathHelp": "Browse or enter the path to use for server cache files. The folder must be writeable.", + "HeaderSelectTranscodingPath": "Select Transcoding Temporary Path", + "HeaderSelectTranscodingPathHelp": "Browse or enter the path to use for transcoding temporary files. The folder must be writeable.", + "LabelArtistsHelp": "Separate multiple using ;", + "LabelAudio": "Audio", + "LabelAudioBitDepth": "Audio bit depth:", + "LabelAudioBitrate": "Audio bitrate:", + "LabelAudioChannels": "Audio channels:", + "LabelAudioCodec": "Audio codec:", + "LabelAudioSampleRate": "Audio sample rate:", + "LabelAuthProvider": "Authentication Provider:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Automatically refresh metadata from the internet:", + "LabelBindToLocalNetworkAddress": "Bind to local network address:", + "LabelBindToLocalNetworkAddressHelp": "Optional. Override the local IP address to bind the http server to. If left empty, the server will bind to all availabile addresses. Changing this value requires restarting Jellyfin Server.", + "LabelBirthDate": "Birth date:", + "LabelBirthYear": "Birth year:", + "LabelBitrate": "Bitrate:", + "LabelBlastMessageInterval": "Alive message interval (seconds)", + "LabelBlastMessageIntervalHelp": "Determines the duration in seconds between blast alive messages.", + "LabelBlockContentWithTags": "Block items with tags:", + "LabelBurnSubtitles": "Burn subtitles:", + "LabelCache": "Cache:", + "LabelCachePath": "Cache path:", + "LabelCachePathHelp": "Specify a custom location for server cache files such as images. Leave blank to use the server default.", + "LabelCancelled": "Cancelled", + "LabelCertificatePassword": "Certificate password:", + "LabelCertificatePasswordHelp": "If your certificate requires a password, please enter it here.", + "LabelChannels": "Channels:", + "LabelCollection": "Collection:", + "LabelCommunityRating": "Community rating:", + "LabelCustomDeviceDisplayNameHelp": "Supply a custom display name or leave empty to use the name reported by the device.", + "LabelDateAddedBehavior": "Date added behavior for new content:", + "LabelDateAddedBehaviorHelp": "If a metadata value is present it will always be used before either of these options.", + "LabelDateTimeLocale": "Date time locale:", + "LabelDay": "Day:", + "LabelDeathDate": "Death date:", + "LabelDefaultScreen": "Default screen:", + "LabelDefaultUser": "Default user:", + "LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.", + "LabelDeinterlaceMethod": "Deinterlacing method:", + "LabelDeviceDescription": "Device description", + "LabelDidlMode": "DIDL mode:", + "LabelDiscNumber": "Disc number:", + "LabelDisplayLanguage": "Display language:", + "LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.", + "LabelDisplayMode": "Display mode:", + "LabelDisplayName": "Display name:", + "LabelDisplayOrder": "Display order:", + "LabelDisplaySpecialsWithinSeasons": "Display specials within seasons they aired in", + "LabelDownMixAudioScale": "Audio boost when downmixing:", + "LabelDownMixAudioScaleHelp": "Boost audio when downmixing. A value of one will preserve the original volume.", + "LabelDownloadLanguages": "Download languages:", + "LabelDropImageHere": "Drop image here, or click to browse.", + "LabelDroppedFrames": "Dropped frames:", + "LabelDropShadow": "Drop shadow:", + "LabelDynamicExternalId": "{0} Id:", + "LabelEasyPinCode": "Easy pin code:", + "LabelEmbedAlbumArtDidl": "Embed album art in Didl", + "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.", + "LabelEnableAutomaticPortMap": "Enable automatic port mapping", + "LabelEnableAutomaticPortMapHelp": "Automatically forward public ports on your router to local ports on your server via UPnP. This may not work with some router models or network configurations. Changes will not apply until after a server restart.", + "LabelEnableBlastAliveMessages": "Blast alive messages", + "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", + "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval (seconds)", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Jellyfin.", + "LabelEnableDlnaDebugLogging": "Enable DLNA debug logging", + "LabelEnableDlnaDebugLoggingHelp": "Create large log files and should only be used as needed for troubleshooting purposes.", + "LabelEnableDlnaPlayTo": "Enable DLNA Play To", + "LabelEnableDlnaPlayToHelp": "Detect devices within your network and offer the ability to remote control them.", + "LabelEnableDlnaServer": "Enable DLNA server", + "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play content.", + "LabelEnableHardwareDecodingFor": "Enable hardware decoding for:", + "LabelEnableHttps": "Enable HTTPS", + "LabelEnableHttpsHelp": "Enables the server to listen on the configured HTTPS port. A valid certificate must also be configured in order for this to take effect.", + "LabelEnableRealtimeMonitor": "Enable real time monitoring", + "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately, on supported file systems.", + "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", + "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.", + "LabelEndDate": "End date:", + "LabelEpisodeNumber": "Episode number:", + "LabelEvent": "Event:", + "LabelEveryXMinutes": "Every:", + "LabelBaseUrl": "Base URL:", + "LabelBaseUrlHelp": "Adds a custom subdirectory to the server URL. For example: http://example.com/<baseurl>", + "LabelExtractChaptersDuringLibraryScan": "Extract chapter images during the library scan", + "EnableDetailsBanner": "Details Banner", + "EnableDetailsBannerHelp": "Display a banner image at the top of the item details page.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, resource intensive, and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "FFmpegSavePathNotFound": "We're unable to locate FFmpeg using the path you've entered. FFprobe is also required and must exist in the same folder. These components are normally bundled together in the same download. Please check the path and try again.", + "LabelTagline": "Tagline:", + "RefreshDialogHelp": "Metadata is refreshed based on settings and internet services that are enabled in the Jellyfin Server dashboard.", + "RefreshMetadata": "Refresh metadata", + "RefreshQueued": "Refresh queued.", + "ReleaseDate": "Release date", + "ReleaseGroup": "Release Group", + "RememberMe": "Remember Me", + "RemoveFromCollection": "Remove from collection", + "RemoveFromPlaylist": "Remove from playlist", + "Repeat": "Repeat", + "RepeatAll": "Repeat all", + "RepeatEpisodes": "Repeat episodes", + "RepeatMode": "Repeat Mode", + "RepeatOne": "Repeat one", + "GuideProviderSelectListings": "Select Listings", + "H264CrfHelp": "The Constant Rate Factor (CRF) is the default quality setting for the x264 encoder. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, so you can use this as a starting point.", + "HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.", + "HeaderAddToCollection": "Add to Collection", + "HeaderAddToPlaylist": "Add to Playlist", + "HeaderAddUpdateImage": "Add/Update Image", + "HeaderAdditionalParts": "Additional Parts", + "HeaderAdmin": "Admin", + "HeaderAlbums": "Albums", + "HeaderAlert": "Alert", + "HeaderAllowMediaDeletionFrom": "Allow Media Deletion From", + "HeaderApiKey": "API Key", + "HeaderApiKeys": "API Keys", + "HeaderApiKeysHelp": "External applications are required to have an API key in order to communicate with Jellyfin Server. Keys are issued by logging in with a Jellyfin account, or by manually granting the application a key.", + "ApiKeysCaption": "List of the currently enabled API keys", + "HeaderApp": "App", + "LabelFont": "Font:", + "LabelForgotPasswordUsernameHelp": "Enter your username, if you remember it.", + "LabelFormat": "Format:", + "LabelFriendlyName": "Friendly name:", + "LabelServerNameHelp": "This name will be used to identify the server and will default to the server's computer name.", + "LabelGroupMoviesIntoCollections": "Group movies into collections", + "LabelGroupMoviesIntoCollectionsHelp": "When displaying movie lists, movies belonging to a collection will be displayed as one grouped item.", + "LabelH264Crf": "H264 encoding CRF:", + "LabelEncoderPreset": "H264 and H265 encoding preset:", + "LabelHardwareAccelerationType": "Hardware acceleration:", + "LabelHomeScreenSectionValue": "Home screen section {0}:", + "LabelHttpsPortHelp": "The TCP port number that Jellyfin's HTTPS server should bind to.", + "LabelIconMaxHeightHelp": "Maximum resolution of icons exposed via upnp:icon.", + "LabelIconMaxWidthHelp": "Maximum resolution of icons exposed via upnp:icon.", + "LabelIdentificationFieldHelp": "A case-insensitive substring or regex expression.", + "LabelImageFetchersHelp": "Enable and rank your preferred image fetchers in order of priority.", + "LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code", + "Off": "Off", + "OneChannel": "One channel", + "OnlyForcedSubtitles": "Only Forced", + "OnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.", + "OnlyImageFormats": "Only Image Formats (VOBSUB, PGS, SUB)", + "Option3D": "3D", + "OptionAdminUsers": "Administrators", + "OptionAlbum": "Album", + "OptionAlbumArtist": "Album Artist", + "OptionAllUsers": "All users", + "OptionAllowAudioPlaybackTranscoding": "Allow audio playback that requires transcoding", + "OptionForceRemoteSourceTranscoding": "Force transcoding of remote media sources (like LiveTV)", + "OptionAllowBrowsingLiveTv": "Allow Live TV access", + "OptionAllowContentDownloading": "Allow media downloading and syncing", + "OptionAllowLinkSharing": "Allow social media sharing", + "OptionAllowLinkSharingHelp": "Only web pages containing media information are shared. Media files are never shared publicly. Shares are time-limited and will expire after {0} days.", + "OptionAllowManageLiveTv": "Allow Live TV recording management", + "OptionAllowMediaPlayback": "Allow media playback", + "OptionAllowMediaPlaybackTranscodingHelp": "Restricting access to transcoding may cause playback failures in Jellyfin apps due to unsupported media formats.", + "OptionAllowRemoteControlOthers": "Allow remote control of other users", + "OptionAllowRemoteSharedDevices": "Allow remote control of shared devices", + "OptionAllowRemoteSharedDevicesHelp": "DLNA devices are considered shared until a user begins controlling them.", + "OptionAllowSyncTranscoding": "Allow media downloading and syncing that requires transcoding", + "OptionAllowUserToManageServer": "Allow this user to manage the server", + "OptionAllowVideoPlaybackRemuxing": "Allow video playback that requires conversion without re-encoding", + "OptionAllowVideoPlaybackTranscoding": "Allow video playback that requires transcoding", + "OptionArtist": "Artist", + "OptionAuto": "Auto", + "OptionAutomatic": "Auto", + "OptionAutomaticallyGroupSeries": "Automatically merge series that are spread across multiple folders", + "OptionAutomaticallyGroupSeriesHelp": "If enabled, series that are spread across multiple folders within this library will be automatically merged into a single series.", + "OptionBanner": "Banner", + "OptionBlockBooks": "Books", + "OptionBlockChannelContent": "Internet Channel Content", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockMovies": "Movies", + "OptionBlockMusic": "Music", + "OptionBlockTrailers": "Trailers", + "OptionBlockTvShows": "TV Shows", + "OptionBluray": "Blu-ray", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionCommunityRating": "Community Rating", + "OptionContinuing": "Continuing", + "OptionCriticRating": "Critic Rating", + "OptionCustomUsers": "Custom", + "OptionDaily": "Daily", + "OptionDateAdded": "Date Added", + "OptionDateAddedFileTime": "Use file creation date", + "OptionDateAddedImportTime": "Use date scanned into the library", + "OptionDatePlayed": "Date Played", + "OptionDisableUser": "Disable this user", + "OptionDisableUserHelp": "If disabled the server will not allow any connections from this user. Existing connections will be abruptly terminated.", + "OptionDisplayFolderView": "Display a folder view to show plain media folders", + "OptionDisplayFolderViewHelp": "Display folders alongside your other media libraries. This can be useful if you'd like to have a plain folder view.", + "OptionDownloadArtImage": "Art", + "OptionDownloadBackImage": "Back", + "OptionDownloadBannerImage": "Banner", + "OptionDownloadBoxImage": "Box", + "OptionDownloadDiscImage": "Disc", + "OptionDownloadImagesInAdvance": "Download images in advance", + "OptionDownloadImagesInAdvanceHelp": "By default, most images are only downloaded when requested by a Jellyfin app. Enable this option to download all images in advance, as new media is imported. This may cause significantly longer library scans.", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadMenuImage": "Menu", + "OptionDownloadPrimaryImage": "Primary", + "OptionDownloadThumbImage": "Thumb", + "OptionDvd": "DVD", + "OptionEmbedSubtitles": "Embed within container", + "OptionEnableExternalContentInSuggestions": "Enable external content in suggestions", + "OptionEnableExternalContentInSuggestionsHelp": "Allow internet trailers and live TV programs to be included within suggested content.", + "OptionEnableForAllTuners": "Enable for all tuner devices", + "OptionEnableM2tsMode": "Enable M2ts mode", + "OptionEnableM2tsModeHelp": "Enable m2ts mode when encoding to mpegts.", + "OptionEnded": "Ended", + "OptionEquals": "Equals", + "OptionEstimateContentLength": "Estimate content length when transcoding", + "OptionEveryday": "Every day", + "OptionExternallyDownloaded": "External download", + "OptionExtractChapterImage": "Enable chapter image extraction", + "OptionFriday": "Friday", + "OptionHasSpecialFeatures": "Special Features", + "OptionHasSubtitles": "Subtitles", + "OptionHasThemeSong": "Theme Song", + "OptionHasThemeVideo": "Theme Video", + "OptionHasTrailer": "Trailer", + "OptionHideUser": "Hide this user from login screens", + "OptionHideUserFromLoginHelp": "Useful for private or hidden administrator accounts. The user will need to sign in manually by entering their username and password.", + "HeaderSendMessage": "Send Message", + "HeaderSeries": "Series", + "HeaderSeriesOptions": "Series Options", + "HeaderSeriesStatus": "Series Status", + "HeaderServerAddressSettings": "Server Address Settings", + "HeaderServerSettings": "Server Settings", + "HeaderSettings": "Settings", + "HeaderShutdown": "Shutdown", + "HeaderSortBy": "Sort By", + "HeaderSortOrder": "Sort Order", + "HeaderSpecialEpisodeInfo": "Special Episode Info", + "HeaderSpecialFeatures": "Special Features", + "HeaderStartNow": "Start Now", + "HeaderStatus": "Status", + "HeaderStopRecording": "Stop Recording", + "HeaderSubtitleAppearance": "Subtitle Appearance", + "HeaderSubtitleDownloads": "Subtitle Downloads", + "HeaderSubtitleProfile": "Subtitle Profile", + "HeaderSubtitleProfiles": "Subtitle Profiles", + "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", + "HeaderSyncPlaySelectGroup": "Join a group", + "HeaderSyncPlayEnabled": "SyncPlay enabled", + "HeaderTags": "Tags", + "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", + "HeaderTracks": "Tracks", + "HeaderTranscodingProfile": "Transcoding Profile", + "HeaderTranscodingProfileHelp": "Add transcoding profiles to indicate which formats should be used when transcoding is required.", + "HeaderTunerDevices": "Tuner Devices", + "HeaderTuners": "Tuners", + "HeaderTypeText": "Enter Text", + "HeaderUpcomingOnTV": "Upcoming On TV", + "HeaderUploadImage": "Upload Image", + "HeaderUser": "User", + "HeaderVideoQuality": "Video Quality", + "HeaderVideoType": "Video Type", + "HeaderVideoTypes": "Video Types", + "HeaderVideos": "Videos", + "HeaderXmlDocumentAttribute": "Xml Document Attribute", + "HeaderXmlDocumentAttributes": "Xml Document Attributes", + "HeaderXmlSettings": "Xml Settings", + "HeaderYears": "Years", + "HeadersFolders": "Folders", + "Help": "Help", + "Hide": "Hide", + "HideWatchedContentFromLatestMedia": "Hide watched content from latest media", + "Home": "Home", + "Horizontal": "Horizontal", + "HttpsRequiresCert": "To enable secure connections, you will need to supply a trusted SSL certificate, such as Let's Encrypt. Please either supply a certificate, or disable secure connections.", + "Identify": "Identify", + "Images": "Images", + "ImportFavoriteChannelsHelp": "If enabled, only channels that are marked as favorite on the tuner device will be imported.", + "ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.", + "InstallingPackage": "Installing {0} (version {1})", + "InstantMix": "Instant mix", + "ItemCount": "{0} items", + "Items": "Items", + "Kids": "Kids", + "Label3DFormat": "3D format:", + "LabelAbortedByServerShutdown": "(Aborted by server shutdown)", + "LabelAccessDay": "Day of week:", + "LabelAccessEnd": "End time:", + "LabelAccessStart": "Start time:", + "LabelAirDays": "Air days:", + "LabelAirTime": "Air time:", + "LabelAirsAfterSeason": "Airs after season:", + "LabelAirsBeforeEpisode": "Airs before episode:", + "LabelAirsBeforeSeason": "Airs before season:", + "LabelAlbum": "Album:", + "LabelAlbumArtHelp": "PN used for album art, within the dlna:profileID attribute on upnp:albumArtURI. Some devices require a specific value, regardless of the size of the image.", + "LabelAlbumArtMaxHeight": "Album art max height:", + "LabelAlbumArtMaxHeightHelp": "Max resolution of album art exposed via upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Album art max width:", + "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", + "LabelAlbumArtPN": "Album art PN:", + "LabelAlbumArtists": "Album artists:", + "LabelAll": "All", + "LabelAllowHWTranscoding": "Allow hardware transcoding", + "LabelAllowServerAutoRestart": "Allow the server to restart automatically to apply updates", + "LabelAllowServerAutoRestartHelp": "The server will only restart during idle periods when no users are active.", + "LabelAllowedRemoteAddresses": "Remote IP address filter:", + "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode:", + "LabelAppName": "App name", + "LabelAppNameExample": "Example: Sickbeard, Sonarr", + "LabelArtists": "Artists:", + "OptionHlsSegmentedSubtitles": "HLS segmented subtitles", + "OptionHomeVideos": "Photos", + "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.", + "OptionImdbRating": "IMDb Rating", + "OptionIsHD": "HD", + "OptionIsSD": "SD", + "OptionList": "List", + "OptionLoginAttemptsBeforeLockout": "Determines how many incorrect login attempts can be made before lockout occurs.", + "OptionLoginAttemptsBeforeLockoutHelp": "A value of zero means inheriting the default of three attempts for normal users and five for administrators. Setting this to -1 will disable the feature.", + "OptionMax": "Max", + "OptionMissingEpisode": "Missing Episodes", + "OptionMonday": "Monday", + "OptionNameSort": "Name", + "OptionNew": "New…", + "OptionNone": "None", + "OptionOnAppStartup": "On application startup", + "LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time.", + "LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:", + "LabelMaxStreamingBitrate": "Maximum streaming quality:", + "LabelMaxStreamingBitrateHelp": "Specify a maximum bitrate when streaming.", + "LabelMessageText": "Message text:", + "LabelMessageTitle": "Message title:", + "LabelMetadata": "Metadata:", + "LabelMetadataDownloadLanguage": "Preferred download language:", + "LabelMetadataDownloadersHelp": "Enable and rank your preferred metadata downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information.", + "LabelMetadataPath": "Metadata path:", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata.", + "LabelMetadataReaders": "Metadata readers:", + "LabelMetadataReadersHelp": "Rank your preferred local metadata sources in order of priority. The first file found will be read.", + "LabelMetadataSavers": "Metadata savers:", + "LabelMetadataSaversHelp": "Choose the file formats to save your metadata to.", + "LabelMethod": "Method:", + "LabelMinBackdropDownloadWidth": "Minimum backdrop download width:", + "LabelMinResumeDuration": "Minimum resume duration:", + "LabelMinResumeDurationHelp": "The shortest video length in seconds that will save playback location and let you resume.", + "LabelMinResumePercentage": "Minimum resume percentage:", + "LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time.", + "LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:", + "LabelModelDescription": "Model description", + "LabelModelName": "Model name", + "LabelModelNumber": "Model number", + "LabelModelUrl": "Model URL", + "LabelMonitorUsers": "Monitor activity from:", + "LabelMovieCategories": "Movie categories:", + "LabelMoviePrefix": "Movie prefix:", + "LabelMoviePrefixHelp": "If a prefix is applied to movie titles, enter it here so the server can handle it properly.", + "LabelMovieRecordingPath": "Movie recording path (optional):", + "LabelMusicStreamingTranscodingBitrate": "Music transcoding bitrate:", + "LabelMusicStreamingTranscodingBitrateHelp": "Specify a max bitrate when streaming music.", + "LabelName": "Name:", + "LabelChromecastVersion": "Chromecast Version", + "LabelStable": "Stable", + "LabelNightly": "Nightly", + "LabelNewName": "New name:", + "LabelNewsCategories": "News categories:", + "LabelNotificationEnabled": "Enable this notification", + "LabelNumber": "Number:", + "LabelNumberOfGuideDays": "Number of days of guide data to download:", + "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", + "LabelOptionalNetworkPath": "(Optional) Shared network folder:", + "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly. For example, {0} or {1}.", + "LabelOriginalAspectRatio": "Original aspect ratio:", + "LabelOriginalTitle": "Original title:", + "LabelOverview": "Overview:", + "LabelParentNumber": "Parent number:", + "LabelParentalRating": "Parental rating:", + "LabelPassword": "Password:", + "LabelPasswordConfirm": "Password (confirm):", + "LabelPasswordResetProvider": "Password Reset Provider:", + "LabelPasswordRecoveryPinCode": "Pin code:", + "LabelPath": "Path:", + "LabelPersonRole": "Role:", + "LabelPersonRoleHelp": "Example: Ice cream truck driver", + "LabelPlaceOfBirth": "Place of birth:", + "LabelPlayDefaultAudioTrack": "Play default audio track regardless of language", + "LabelPlayer": "Player:", + "LabelPlayerDimensions": "Player dimensions:", + "LabelPlaylist": "Playlist:", + "LabelPlayMethod": "Play method:", + "LabelPleaseRestart": "Changes will take effect after manually reloading the web client.", + "LabelPostProcessor": "Post-processing application:", + "LabelPostProcessorArguments": "Post-processor command line arguments:", + "LabelPostProcessorArgumentsHelp": "Use {path} as the path to the recording file.", + "LabelPreferredDisplayLanguage": "Preferred display language:", + "LabelPreferredDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.", + "LabelPreferredSubtitleLanguage": "Preferred subtitle language:", + "LabelProfileAudioCodecs": "Audio codecs:", + "LabelProfileCodecs": "Codecs:", + "LabelProfileContainer": "Container:", + "LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.", + "LabelProfileVideoCodecs": "Video codecs:", + "LabelProtocol": "Protocol:", + "LabelProtocolInfo": "Protocol info:", + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "LabelPublicHttpPort": "Public HTTP port number:", + "LabelPublicHttpPortHelp": "The public port number that should be mapped to the local HTTP port.", + "LabelPublicHttpsPort": "Public HTTPS port number:", + "LabelPublicHttpsPortHelp": "The public port number that should be mapped to the local HTTPS port.", + "LabelReadHowYouCanContribute": "Learn how you can contribute.", + "LabelReasonForTranscoding": "Reason for transcoding:", + "LabelRecord": "Record:", + "LabelRecordingPath": "Default recording path:", + "LabelRecordingPathHelp": "Specify the default location to save recordings. If left empty, the server's program data folder will be used.", + "LabelRefreshMode": "Refresh mode:", + "LabelReleaseDate": "Release date:", + "LabelRemoteClientBitrateLimit": "Internet streaming bitrate limit (Mbps):", + "LabelRemoteClientBitrateLimitHelp": "An optional per-stream bitrate limit for all out of network devices. This is useful to prevent devices from requesting a higher bitrate than your internet connection can handle. This may result in increased CPU load on your server in order to transcode videos on the fly to a lower bitrate.", + "LabelRequireHttps": "Require HTTPS", + "LabelRequireHttpsHelp": "If checked, the server will automatically redirect all requests over HTTP to HTTPS. This has no effect if the server is not listening on HTTPS.", + "LabelRuntimeMinutes": "Run time (minutes):", + "LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.", + "LabelScreensaver": "Screensaver:", + "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", + "LabelSkipIfGraphicalSubsPresentHelp": "Keeping text versions of subtitles will result in more efficient delivery and decrease the likelihood of video transcoding.", + "LabelSonyAggregationFlagsHelp": "Determines the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.", + "LabelSyncPlayPlaybackDiff": "Playback time difference:", + "LabelSyncPlaySyncMethod": "Sync method:", + "LabelSyncPlayNewGroup": "New group", + "LabelSyncPlayNewGroupDescription": "Create a new group", + "LabelSyncPlayLeaveGroup": "Leave group", + "HeaderAppearsOn": "Appears On", + "HeaderAudioBooks": "Audio Books", + "HeaderAudioSettings": "Audio Settings", + "HeaderAutomaticUpdates": "Automatic Updates", + "HeaderBranding": "Branding", + "HeaderConfigureRemoteAccess": "Configure Remote Access", + "HeaderConfirmPluginInstallation": "Confirm Plugin Installation", + "HeaderConfirmProfileDeletion": "Confirm Profile Deletion", + "HeaderConfirmRevokeApiKey": "Revoke API Key", + "HeaderConnectToServer": "Connect to Server", + "HeaderConnectionFailure": "Connection Failure", + "HeaderContainerProfile": "Container Profile", + "HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.", + "HeaderContinueListening": "Continue Listening", + "HeaderDVR": "DVR", + "HeaderFavoritePlaylists": "Favorite Playlists", + "HeaderHttpsSettings": "HTTPS Settings", + "HeaderNavigation": "Navigation", + "HeaderProfileServerSettingsHelp": "These values control how Jellyfin Server will present itself to the device.", + "HeaderRemoteAccessSettings": "Remote Access Settings", + "HeaderRemoteControl": "Remote Control", + "HeaderRemoveMediaFolder": "Remove Media Folder", + "HeaderRemoveMediaLocation": "Remove Media Location", + "HeaderResponseProfile": "Response Profile", + "HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.", + "HeaderRestart": "Restart", + "HeaderRestartingServer": "Restarting Server", + "HeaderRevisionHistory": "Revision History", + "HeaderRunningTasks": "Running Tasks", + "HeaderScenes": "Scenes", + "HeaderSchedule": "Schedule", + "HeaderSeasons": "Seasons", + "HeaderSecondsValue": "{0} Seconds", + "HeaderSelectCertificatePath": "Select Certificate Path", + "HeaderSelectMetadataPath": "Select Metadata Path", + "HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.", + "HeaderSelectPath": "Select Path", + "HeaderSelectServer": "Select Server", + "LabelFolder": "Folder:", + "LabelSyncPlayLeaveGroupDescription": "Disable SyncPlay", + "LabelSyncPlayAccessCreateAndJoinGroups": "Allow user to create and join groups", + "LabelSyncPlayAccessJoinGroups": "Allow user to join groups", + "LabelSyncPlayAccessNone": "Disabled for this user", + "LabelSyncPlayAccess": "SyncPlay access", + "LabelTVHomeScreen": "TV mode home screen:", + "LabelTag": "Tag:", + "LabelTextBackgroundColor": "Text background color:", + "LabelTextColor": "Text color:", + "LabelTextSize": "Text size:", + "LabelTheme": "Theme:", + "LabelTime": "Time:", + "LabelTitle": "Title:", + "LabelTrackNumber": "Track number:", + "LabelTranscodingAudioCodec": "Audio codec:", + "LabelTranscodingContainer": "Container:", + "LabelTranscodePath": "Transcode path:", + "LabelTranscodingTempPathHelp": "Specify a custom path for the transcode files served to clients. Leave blank to use the server default.", + "LabelTranscodes": "Transcodes:", + "LabelTranscodingFramerate": "Transcoding framerate:", + "LabelTranscodingProgress": "Transcoding progress:", + "LabelTranscodingThreadCount": "Transcoding thread count:", + "LabelTranscodingThreadCountHelp": "Select the maximum number of threads to use when transcoding. Reducing the thread count will lower CPU usage but may not convert fast enough for a smooth playback experience.", + "LabelTranscodingVideoCodec": "Video codec:", + "LabelTriggerType": "Trigger Type:", + "LabelTunerIpAddress": "Tuner IP Address:", + "LabelTunerType": "Tuner type:", + "LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.", + "LabelUserLoginAttemptsBeforeLockout": "Failed login attempts before user is locked out:", + "LabelUserRemoteClientBitrateLimitHelp": "Override the default global value set in server playback settings.", + "LabelVaapiDeviceHelp": "This is the render node that is used for hardware acceleration.", + "LabelVideoBitrate": "Video bitrate:", + "LabelVideoCodec": "Video codec:", + "LabelXDlnaCap": "X-DLNA cap:", + "LabelXDlnaCapHelp": "Determines the content of the X_DLNACAP element in the urn:schemas-dlna-org:device-1-0 namespace.", + "LabelXDlnaDoc": "X-DLNA doc:", + "LabelXDlnaDocHelp": "Determines the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.", + "LabelffmpegPathHelp": "The path to the ffmpeg application file, or folder containing ffmpeg.", + "LanNetworksHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be considered on local network when enforcing bandwidth restrictions. If set, all other IP addresses will be considered to be on the external network and will be subject to the external bandwidth restrictions. If left blank, only the server's subnet is considered to be on the local network.", + "LaunchWebAppOnStartupHelp": "Open the web client in your default web browser when the server initially starts. This will not occur when using the restart server function.", + "LearnHowYouCanContribute": "Learn how you can contribute.", + "LeaveBlankToNotSetAPassword": "You can leave this field blank to set no password.", + "Like": "Like", + "LinksValue": "Links: {0}", + "List": "List", + "Live": "Live", + "LiveBroadcasts": "Live broadcasts", + "LiveTV": "Live TV", + "Logo": "Logo", + "ManageRecording": "Manage recording", + "MapChannels": "Map Channels", + "MarkPlayed": "Mark played", + "MarkUnplayed": "Mark unplayed", + "MediaInfoAnamorphic": "Anamorphic", + "MediaInfoAspectRatio": "Aspect ratio", + "MediaInfoBitDepth": "Bit depth", + "MediaInfoBitrate": "Bitrate", + "MediaInfoChannels": "Channels", + "MediaInfoCodec": "Codec", + "MediaInfoCodecTag": "Codec tag", + "MediaInfoContainer": "Container", + "MediaInfoDefault": "Default", + "MediaInfoExternal": "External", + "MediaInfoForced": "Forced", + "MediaInfoFramerate": "Framerate", + "MediaInfoInterlaced": "Interlaced", + "MediaInfoLanguage": "Language", + "MediaInfoLayout": "Layout", + "MediaInfoLevel": "Level", + "MediaInfoPath": "Path", + "MediaInfoPixelFormat": "Pixel format", + "MediaInfoProfile": "Profile", + "MediaInfoRefFrames": "Ref frames", + "MediaInfoResolution": "Resolution", + "MediaInfoSampleRate": "Sample rate", + "MediaInfoSize": "Size", + "MediaInfoTimestamp": "Timestamp", + "MediaInfoSoftware": "Software", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoStreamTypeData": "Data", + "MediaInfoStreamTypeEmbeddedImage": "Embedded Image", + "MediaInfoStreamTypeSubtitle": "Subtitle", + "MediaInfoStreamTypeVideo": "Video", + "MediaIsBeingConverted": "The media is being converted into a format that is compatible with the device that is playing the media.", + "Menu": "Menu", + "MessageAlreadyInstalled": "This version is already installed.", + "MessageAreYouSureDeleteSubtitles": "Are you sure you wish to delete this subtitle file?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "Are you sure you wish to remove this media folder?", + "MessageConfirmAppExit": "Do you want to exit?", + "MessageConfirmDeleteGuideProvider": "Are you sure you wish to delete this guide provider?", + "MessageConfirmDeleteTunerDevice": "Are you sure you wish to delete this device?", + "MessageConfirmProfileDeletion": "Are you sure you wish to delete this profile?", + "MessageConfirmRecordingCancellation": "Cancel recording?", + "MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?", + "MessageConfirmRestart": "Are you sure you wish to restart Jellyfin Server?", + "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Jellyfin Server will be abruptly terminated.", + "MessageConfirmShutdown": "Are you sure you wish to shutdown the server?", + "MessageContactAdminToResetPassword": "Please contact your system administrator to reset your password.", + "MessageCreateAccountAt": "Create an account at {0}", + "MessageDeleteTaskTrigger": "Are you sure you wish to delete this task trigger?", + "MessageDirectoryPickerBSDInstruction": "For BSD, you may need to configure storage within your FreeNAS Jail in order to allow Jellyfin to access it.", + "MessageDirectoryPickerLinuxInstruction": "For Linux on Arch Linux, CentOS, Debian, Fedora, openSUSE, or Ubuntu, you must grant the service user at least read access to your storage locations.", + "MessageDownloadQueued": "Download queued.", + "MessageEnablingOptionLongerScans": "Enabling this option may result in significantly longer library scans.", + "MessageFileReadError": "There was an error reading the file. Please try again.", + "MessageForgotPasswordFileCreated": "The following file has been created on your server and contains instructions on how to proceed:", + "MessageForgotPasswordInNetworkRequired": "Please try again within your home network to initiate the password reset process.", + "MessageImageFileTypeAllowed": "Only JPEG and PNG files are supported.", + "MessageImageTypeNotSelected": "Please select an image type from the drop-down menu.", + "MessageLeaveEmptyToInherit": "Leave empty to inherit settings from a parent item or the global default value.", + "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, and Albums. Click the + button to start creating collections.", + "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.", + "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the pin codes that were used to perform the reset.", + "MessagePlayAccessRestricted": "Playback of this content is currently restricted. Please contact your server administrator for more information.", + "MessagePluginInstallDisclaimer": "Plugins built by Jellyfin community members are a great way to enhance your Jellyfin experience with additional features and benefits. Before installing, please be aware of the effects they may have on your Jellyfin Server, such as longer library scans, additional background processing, and decreased system stability.", + "MessageReenableUser": "See below to reenable", + "MessageSettingsSaved": "Settings saved.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "The following media locations will be removed from your library:", + "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please ensure it is running and try again.", + "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.", + "OptionPlainStorageFoldersHelp": "If enabled, all folders are represented in DIDL as \"object.container.storageFolder\" instead of a more specific type, such as \"object.container.person.musicArtist\".", + "OptionPlainVideoItemsHelp": "If enabled, all videos are represented in DIDL as \"object.item.videoItem\" instead of a more specific type, such as \"object.item.videoItem.movie\".", + "OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.", + "OptionRequirePerfectSubtitleMatch": "Only download subtitles that are a perfect match for my video files", + "OptionSaveMetadataAsHiddenHelp": "Changing this will apply to new metadata saved going forward. Existing metadata files will be updated the next time they are saved by Jellyfin Server.", + "ParentalRating": "Parental rating", + "PasswordResetProviderHelp": "Choose a Password Reset Provider to be used when this user requests a password reset", + "PasswordSaved": "Password saved.", + "PlaceFavoriteChannelsAtBeginning": "Place favorite channels at the beginning", + "PlayCount": "Play count", + "ReplaceAllMetadata": "Replace all metadata", + "ReplaceExistingImages": "Replace existing images", + "RestartPleaseWaitMessage": "Please wait while Jellyfin Server shuts down and restarts. This may take a minute or two.", + "ResumeAt": "Resume from {0}", + "Rewind": "Rewind", + "RunAtStartup": "Run at startup", + "Runtime": "Runtime", + "Saturday": "Saturday", + "Save": "Save", + "SaveChanges": "Save changes", + "SaveSubtitlesIntoMediaFolders": "Save subtitles into media folders", + "SaveSubtitlesIntoMediaFoldersHelp": "Storing subtitles next to video files will allow them to be more easily managed.", + "SendMessage": "Send message", + "LabelInNetworkSignInWithEasyPasswordHelp": "Use the easy pin code to sign in to clients within your local network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.", + "LabelInternetQuality": "Internet quality:", + "LabelKeepUpTo": "Keep up to:", + "LabelKodiMetadataEnableExtraThumbs": "Copy extrafanart to extrathumbs field", + "LabelKodiMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Kodi skin compatibility.", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "LabelKodiMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelKodiMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Kodi guidelines.", + "LabelKodiMetadataUser": "Save user watch data to NFO files for:", + "LabelKodiMetadataUserHelp": "Save watch data to NFO files for other applications to utilize.", + "LabelLanNetworks": "LAN networks:", + "LabelLibraryPageSize": "Library page size:", + "LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.", + "LabelLineup": "Lineup:", + "LabelLocalHttpServerPortNumber": "Local HTTP port number:", + "LabelLocalHttpServerPortNumberHelp": "The TCP port number that Jellyfin's HTTP server should bind to.", + "LabelLockItemToPreventChanges": "Lock this item to prevent future changes", + "LabelLoginDisclaimer": "Login disclaimer:", + "LabelLoginDisclaimerHelp": "A message that will be displayed at the bottom of the login page.", + "LabelLogs": "Logs:", + "LabelManufacturer": "Manufacturer:", + "LabelManufacturerUrl": "Manufacturer URL", + "LabelMatchType": "Match type:", + "LabelMaxBackdropsPerItem": "Maximum number of backdrops per item:", + "LabelMaxChromecastBitrate": "Chromecast streaming quality:", + "LabelMaxResumePercentage": "Maximum resume percentage:", + "MessageYouHaveVersionInstalled": "You currently have version {0} installed.", + "MessageSyncPlayEnabled": "SyncPlay enabled.", + "MessageSyncPlayDisabled": "SyncPlay disabled.", + "MessageSyncPlayUserJoined": "{0} has joined the group.", + "MessageSyncPlayUserLeft": "{0} has left the group.", + "MessageSyncPlayGroupWait": "{0} is buffering...", + "MessageSyncPlayNoGroupsAvailable": "No groups available. Start playing something first.", + "MessageSyncPlayPlaybackPermissionRequired": "Playback permission required.", + "MessageSyncPlayGroupDoesNotExist": "Failed to join group because it does not exist.", + "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", + "MessageSyncPlayJoinGroupDenied": "Permission required to use SyncPlay.", + "MessageSyncPlayLibraryAccessDenied": "Access to this content is restricted.", + "MessageSyncPlayErrorAccessingGroups": "An error occurred while accessing groups list.", + "MessageSyncPlayErrorNoActivePlayer": "No active player found. SyncPlay has been disabled.", + "MessageSyncPlayErrorMissingSession": "Failed to enable SyncPlay! Missing session.", + "MessageSyncPlayErrorMedia": "Failed to enable SyncPlay! Media error.", + "Metadata": "Metadata", + "MetadataManager": "Metadata Manager", + "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", + "MinutesAfter": "minutes after", + "MinutesBefore": "minutes before", + "Mobile": "Mobile", + "Monday": "Monday", + "MoreFromValue": "More from {0}", + "MoreMediaInfo": "Media Info", + "MoveLeft": "Move left", + "MoveRight": "Move right", + "MovieLibraryHelp": "Review the {0}movie naming guide{1}.", + "MusicLibraryHelp": "Review the {0}music naming guide{1}.", + "NewCollectionHelp": "Collections allow you to create personalized groupings of movies and other library content.", + "NoCreatedLibraries": "Seems like you haven't created any libraries yet. {0}Would you like to create one now?{1}", + "NoNewDevicesFound": "No new devices found. To add a new tuner, close this dialog and enter the device information manually.", + "NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.", + "None": "None", + "Normal": "Normal", + "NumLocationsValue": "{0} folders", + "ServerRestartNeededAfterPluginInstall": "Jellyfin Server will need to be restarted after installing a plugin.", + "ServerUpdateNeeded": "This Jellyfin Server needs to be updated. To download the latest version, please visit {0}", + "Settings": "Settings", + "SettingsSaved": "Settings saved.", + "SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.", + "Share": "Share", + "ShowIndicatorsFor": "Show indicators for:", + "XmlDocumentAttributeListHelp": "These attributes are applied to the root element of every XML response.", + "XmlTvKidsCategoriesHelp": "Programs with these categories will be displayed as programs for children. Separate multiple with '|'.", + "XmlTvMovieCategoriesHelp": "Programs with these categories will be displayed as movies. Separate multiple with '|'.", + "XmlTvNewsCategoriesHelp": "Programs with these categories will be displayed as news programs. Separate multiple with '|'.", + "XmlTvPathHelp": "A path to a XMLTV file. Jellyfin will read this file and periodically check it for updates. You are responsible for creating and updating the file.", + "XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.", + "PathNotFound": "The path could not be found. Please ensure the path is valid and try again.", + "WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.", + "UnsupportedPlayback": "Jellyfin cannot decrypt content protected by DRM but all content will be attempted regardless, including protected titles. Some files may appear completely black due to encryption or other unsupported features, such as interactive titles.", + "ShowLess": "Show less", + "ShowMore": "Show more", + "Shuffle": "Shuffle", + "New": "New", + "Filter": "Filter", + "SkipEpisodesAlreadyInMyLibraryHelp": "Episodes will be compared using season and episode numbers, when available.", + "SmallCaps": "Small Caps", + "SubtitleAppearanceSettingsDisclaimer": "These settings will not apply to graphical subtitles (PGS, DVD, etc) or ASS/SSA subtitles that embed their own styles.", + "SubtitleDownloadersHelp": "Enable and rank your preferred subtitle downloaders in order of priority.", + "SubtitleOffset": "Subtitle Offset", + "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other devices.", + "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", + "TabContainers": "Containers", + "TabDVR": "DVR", + "TvLibraryHelp": "Review the {0}TV naming guide{1}.", + "UserAgentHelp": "Supply a custom user-agent HTTP header.", + "ValueContainer": "Container: {0}", + "EnableFastImageFadeIn": "Faster animations", + "EnableFastImageFadeInHelp": "Use faster animations and transitions", + "EnableBlurhash": "Enable blurred placeholders for images", + "EnableBlurhashHelp": "Images that are still being loaded will be displayed with a blurred placeholder", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/fi.json b/src/strings/fi.json index 55c5a1fbcdc..b50ed369e1a 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -4,7 +4,7 @@ "ButtonAddUser": "Lisää Käyttäjä", "ButtonCancel": "Peruuta", "ButtonDeleteImage": "Poista Kuva", - "ButtonResetPassword": "Uusi Salasana", + "ButtonResetPassword": "Nollaa salasana", "ButtonSave": "Tallenna", "ButtonSignOut": "Sign out", "Delete": "Poista", @@ -15,41 +15,41 @@ "FileNotFound": "Tiedostoa ei löydy.", "FileReadCancelled": "Tiedoston luku on peruutettu.", "FileReadError": "Virhe tiedoston luvun aikana.", - "FolderTypeTvShows": "TV", + "FolderTypeTvShows": "TV-sarjat", "HeaderCreatePassword": "Luo Salasana:", "HeaderParentalRating": "Parental Rating", - "HeaderSeries": "Jaksot", + "HeaderSeries": "Sarjat", "HeaderYear": "Year:", "LabelAudioLanguagePreference": "Äänen ensisijainen kieli:", "LabelConfigureSettings": "Muuta asetuksia", "LabelCountry": "Maa:", "LabelCurrentPassword": "Tämän hetkinen salsana:", - "LabelDisplayMissingEpisodesWithinSeasons": "Näytä puuttuvat jaksot tuotantokausissa", + "LabelDisplayMissingEpisodesWithinSeasons": "Näytä puuttuvat jaksot kausien sisällä", "LabelDownloadInternetMetadata": "Lataa kuvamateriaali ja metadata internetistä", "LabelFinish": "Valmis", "LabelFolderType": "Kansion tyyppi:", "LabelLanguage": "Kieli:", - "LabelMaxParentalRating": "Suurin sallittu vanhempien arvostelu:", + "LabelMaxParentalRating": "Suurin sallittu ikäraja:", "LabelNewPassword": "Uusi salasana:", "LabelNewPasswordConfirm": "Uuden salasanan varmistus:", "LabelNext": "Seuraava", "LabelPrevious": "Edellinen", - "LabelSaveLocalMetadata": "Tallenna kuvamateriaali ja metadata media kansioihin.", + "LabelSaveLocalMetadata": "Tallenna kuvamateriaali mediakansioihin", "LabelSaveLocalMetadataHelp": "Kuvamateriaalin ja metadatan tallentaminen suoraan kansioihin missä niitä on helppo muuttaa.", "LabelSubtitleLanguagePreference": "Tekstityksien ensisijainen kieli:", "LabelUnairedMissingEpisodesWithinSeasons": "Näytä julkaisemattomat jaksot tuotantokausissa", - "LabelYourFirstName": "Sinun ensimmäinen nimi:", - "LabelYoureDone": "Olet valmis!", - "LibraryAccessHelp": "Valitse media kansiot jotka haluat jakaa tämän käyttäjän kanssa. Järjestelmänvalvoja pystyy muokkaamaan kaikkia kansioita käyttäen metadata hallintaa.", - "MaxParentalRatingHelp": "Suuremman arvosanan takia, sisältö tulla piilottamaan käyttäjältä.", - "MoreUsersCanBeAddedLater": "Käyttäjiä voi lisätä lisää myöhemmin Dashboardista", + "LabelYourFirstName": "Etunimesi:", + "LabelYoureDone": "Valmista!", + "LibraryAccessHelp": "Valitse kirjastot, jotka haluat jakaa tämän käyttäjän kanssa. Järjestelmänvalvoja pystyy muokkaamaan kaikkia kansioita käyttäen metadatan hallintatyökalua.", + "MaxParentalRatingHelp": "Suuremman luokituksen sisältö piilotetaan käyttäjältä.", + "MoreUsersCanBeAddedLater": "Käyttäjiä voidaan lisätä myöhemmin lisää päänäkymästä.", "NoPluginsInstalledMessage": "Sinulla ei ole mitään lisäosia asennettuna.", "OptionRelease": "Virallinen Julkaisu", - "ParentalRating": "Parental Rating", + "ParentalRating": "Ikäraja", "Password": "Salasana", - "PasswordMatchError": "Salasana ja salasanan vahvistuksen pitää olla samat.", - "PasswordResetComplete": "Salasana on palauttettu.", - "PasswordResetConfirmation": "Oletko varma, että haluat palauttaa salasanan?", + "PasswordMatchError": "Salasanan ja salasanan vahvistuksen on oltava samat.", + "PasswordResetComplete": "Salasana on nollattu.", + "PasswordResetConfirmation": "Haluatko varmasti nollata salasanan?", "PasswordSaved": "Salasana tallennettu.", "Save": "Tallenna", "SettingsSaved": "Asetukset tallennettu.", @@ -59,7 +59,7 @@ "TabProfile": "Profiili", "TabProfiles": "Profiilit", "TellUsAboutYourself": "Kerro meille itsestäsi", - "ThisWizardWillGuideYou": "Tämä työkalu auttaa sinua asennus prosessin aikana. loittaaksesi valitse kieli.", + "ThisWizardWillGuideYou": "Tämä työkalu auttaa sinua asennusprosessin aikana. Valitse kieli aloittaaksesi.", "UninstallPluginConfirmation": "Oletko varma, että haluat poistaa {0}?", "UninstallPluginHeader": "Poista Lisäosa", "Users": "Käyttäjät", @@ -70,7 +70,6 @@ "AddToCollection": "Lisää kokoelmaan", "AddToPlayQueue": "Lisää toistojonoon", "AddToPlaylist": "Lisää toistolistalle", - "AddUserByManually": "Lisää paikallinen käyttäjä lisäämällä käyttäjän tiedot manuaalisesti.", "AddedOnValue": "Lisätty {0}", "AdditionalNotificationServices": "Selaa lisäosakatalogia asentaaksesi lisää ilmoituspalveluita.", "AirDate": "Ensiesityspäivä", @@ -88,35 +87,32 @@ "AddGuideProviderHelp": "Lisää lähde ohjelmaoppaalle.", "AddItemToCollectionHelp": "Lisää nimikkeitä etsimällä niitä ja käyttämällä hiiren oikeaa nappia tai valikkoa lisätäksesi ne kokoelmaan.", "Aired": "Esityspäivä", - "AllowHWTranscodingHelp": "Salli virittimen muuntaa bittivirtaa lennossa. Tämä voi vähentää tarvetta muunnokseen palvelimella.", + "AllowHWTranscodingHelp": "Salli virittimen muuntaa bittivirtaa lennossa. Tämä voi vähentää muunnoksen tarvetta Jellyfin-palvelimella.", "AllowMediaConversion": "Salli median muunto", "AllowMediaConversionHelp": "Salli tai kiellä pääsy median muunnostoimintoon.", "AllowOnTheFlySubtitleExtractionHelp": "Sisäiset tekstitykset voidaan lähettää päätelaitteille ilmitekstinä, jotta videota ei tarvitsisi uudelleenkoodata. Joissain järjestelmissä tämä voi viedä paljon aikaa ja aiheuttaa toiston pysähtymisen purun ajaksi. Poista tämä käytöstä polttaaksesi tekstiykset suoraan videoon, mikäli päätelaite ei tue tekstityksiä.", "AllowRemoteAccess": "Salli etäyhteydet tähän Jellyfin palvelimeen.", "AllowRemoteAccessHelp": "Jos merkki puuttuu, kaikki ulkopuoliset yhteydet estetään.", - "AllowSeasonalThemes": "Salli automaattiset vuodenaikateemat", - "AllowSeasonalThemesHelp": "Jos asetettu, vuodenaikateemat satunnaisesti yliajavat teema-asetuksesi.", "AllowedRemoteAddressesHelp": "Pilkuilla eroteltu lista IP-osoitteista tai IP/verkonpeite merkinnöistä verkoille, joille sallitaan etäyhteys palvelimeen. Tyhjäksi jätetty lista tarkoittaa, että kaikki osoitteet sallitaan.", "AlwaysPlaySubtitles": "Näytä aina tekstitykset", "AlwaysPlaySubtitlesHelp": "Oletuskieliasetusta vastaava tekstitys otetaan käyttöön ääniraidan kielestä huolimatta.", "AnamorphicVideoNotSupported": "Anamorfinen video ei ole tuettu", - "AndroidUnlockRestoreHelp": "Palauttaaksesi aikaisemman ostoksesi, varmista, että olet kirjautuneena samalla Google (tai Amazon) tunnuksella, jolla teit alkuperäisen oston. Varmista, että sovelluskauppa on päällä eikä sitä ole rajoitettu vanhempien lukolla. Varmista myös, että sinulla on toimiva internet -yhteys. Sinun tarvitsee tehdä taämä vain kerran.", - "AnyLanguage": "Mikä tahansa kieli", + "AnyLanguage": "Mikä tahansa", "Anytime": "Milloin tahansa", "AroundTime": "Noin {0}", "Art": "Taide", - "Artists": "Esiintyjät", + "Artists": "Artistit", "AsManyAsPossible": "Niin monta kuin mahdollista", "Ascending": "Nousevassa järjestyksessä", "AspectRatio": "Kuvasuhde", "AttributeNew": "Uusi", "Audio": "Ääni", "AuthProviderHelp": "Valitse todentamispalvelu, jota käytetään tämän käyttäjän salasanan todentamisessa.", - "Auto": "Automaattinen", + "Auto": "Auto", "AutoBasedOnLanguageSetting": "Automaattinen (perustuu kieliasetukseen)", "Backdrop": "Tausta", "Backdrops": "Taustat", - "Banner": "Juliste", + "Banner": "Lippu", "BirthDateValue": "Syntynyt: {0}", "BirthLocation": "Syntymäpaikka", "BirthPlaceValue": "Syntymäpaikka: {0}", @@ -145,9 +141,9 @@ "ButtonEdit": "Muokkaa", "ButtonEditImages": "Muokkaa kuvia", "ButtonEditOtherUserPreferences": "Muokkaa tämän käyttäjän profiilia, kuvaa ja henkilökohtaisia asetuksia.", - "ButtonFilter": "Suodin", - "ButtonForgotPassword": "Unohdin salasanani", - "ButtonFullscreen": "Täysi kuvaruutu", + "ButtonFilter": "Suodata", + "ButtonForgotPassword": "Unohtuiko salasana", + "ButtonFullscreen": "Kokonäyttötila", "ButtonGotIt": "Selvä", "ButtonGuide": "Opas", "ButtonHelp": "Apua", @@ -163,8 +159,8 @@ "ButtonNextTrack": "Seuraava raita", "ButtonOff": "Pois päältä", "ButtonOk": "Ok", - "ButtonOpen": "Avoin", - "BurnSubtitlesHelp": "Määrittää jos palvelimen pitäisi upottaa tekstitykset suoraan videotiedostoon muuntamisvaiheessa tekstitysformaatista riippuen. Upottamisen välttäminen parantaa palvelimen suorituskykyä. Valitse Automaattinen upottaaksesi sekä kuvapohjaiset- (esim. VOBSUB, PGS, SUB/IDX, jne.) että ASS/SSA tekstitysmuodot", + "ButtonOpen": "Avaa", + "BurnSubtitlesHelp": "Määrittää mikäli palvelimen pitäisi polttaa tekstitykset suoraan videoon muunnoksen aikana riippuen tekstitysten formaatista. Tekstitysten polttamisen välttäminen parantaa palvelimen suorituskykyä. Valitse Automaattinen polttaaksesi sekä kuva- (esim. VOBSUB, PGS, SUB/IDX, jne.) että tekstipohjaiset (ASS/SSA) formaatit.", "ButtonParentalControl": "Lapsilukko", "ButtonPause": "Tauko", "ButtonPlay": "Toista", @@ -249,14 +245,14 @@ "DisplayInOtherHomeScreenSections": "Näytä kotinäytöllä osastoja kuten viimeisin media ja jatka katselua", "DisplayMissingEpisodesWithinSeasons": "Näytä puuttuvat jaksot tuotantokausissa", "DisplayMissingEpisodesWithinSeasonsHelp": "Tämän pitää aktivoida TV-kirjastoille myös palvelimen asetuksissa.", - "DisplayModeHelp": "Valitse näyttölaitteen tyyppi jolla pyörität Jellyfiniä.", + "DisplayModeHelp": "Valitse ulkonäkö, jonka haluat käyttöliittymälle.", "DoNotRecord": "Älä tallenna", "Down": "Alas", "Download": "Lataa", "DownloadsValue": "{0} latausta", "DrmChannelsNotImported": "Kanavia joissa on tekijänoikeusesto-ohjelmia, ei ladata.", - "DropShadow": "Tiputa varjo", - "EasyPasswordHelp": "Sinun helppoa PIN-koodia käytetään offline-käytössä tuetuissa Jellyfin-sovelluksissa, ja voi myös nopeuttaa lan yhteyden kautta kirjautumista.", + "DropShadow": "Varjostus", + "EasyPasswordHelp": "Helppoa PIN-koodiasi käytetään offline-käyttöä tukevissa sovelluksissa, ja sitä voidaan myös käyttää verkossa kirjautumiseen.", "Edit": "Muokkaa", "EditImages": "Muokkaa kuvia", "EditMetadata": "Muokkaa metadataa", @@ -267,7 +263,7 @@ "ButtonAddImage": "Lisää kuva", "Movies": "Elokuvat", "HeaderNextUp": "Seuraavaksi", - "HeaderLiveTV": "TV-lähetykset", + "HeaderLiveTV": "Live-TV", "HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteEpisodes": "Lempijaksot", @@ -284,16 +280,16 @@ "ValueSpecialEpisodeName": "Erikois - {0}", "Sync": "Synkronoi", "Songs": "Kappaleet", - "Shows": "Ohjelmat", + "Shows": "Sarjat", "CopyStreamURLSuccess": "Osoite kopioitu onnistuneesti.", - "DeathDateValue": "Kuoli: {0}", - "CustomDlnaProfilesHelp": "Luo mukautettu profiili uutta laitetta varten, tai ohita järjestelmäprofiili.", + "DeathDateValue": "Kuoli: {}", + "CustomDlnaProfilesHelp": "Luo uusi profiili kohdistaaksesi uuteen laitteeseen tai ohittaaksesi järjestelmäprofiilin.", "EnableBackdrops": "Taustat", "ErrorAddingMediaPathToVirtualFolder": "Media-polkua lisätessä ilmeni ongelma. Varmista, että polku on kirjoitettu oikein ja Jellyfin Palvelimella pääsy sijaintiin.", "Episodes": "Jaksot", "EndsAtValue": "Päättyy {0}", "Ended": "Päättynyt", - "EnableThemeSongsHelp": "Soita tunnuslaulut taustalla, selatessasi kirjastoa.", + "EnableThemeSongsHelp": "Soita tunnussäveliä taustalla selatessasi kirjastoa.", "EnableThemeSongs": "Tunnuslaulut", "EnableStreamLoopingHelp": "Laita tämä päälle, jos suoratoistot sisältävät vain muutaman sekuntin verran dataa jota tarvitsee pyytää jatkuvasti. Tämän päälle laittaminen ilman toiminnon tarvetta voi aiheuttaa ongelmia.", "EnablePhotosHelp": "Kuvat tunnistetaan ja näytetään muiden media-tiedostojen ohessa.", @@ -307,11 +303,11 @@ "EnableBackdropsHelp": "Näytä taustat tietyillä sivuilla selatessasi kirjastoa.", "EnableExternalVideoPlayersHelp": "Videota soitettaessa näytetään erillinen valikko.", "Depressed": "Painettu", - "CopyStreamURLError": "Verkko-osoitteen kopioinnissa tapahtui virhe.", + "CopyStreamURLError": "Osoitteen kopioidessa tapahtui virhe.", "ButtonSplit": "jaa", "AskAdminToCreateLibrary": "Pyydä järjestelmän ylläpitäjää luomaan kirjasto.", - "EnableStreamLooping": "Auto-toista suoralähetykset", - "EnableNextVideoInfoOverlayHelp": "Videon lopussa, näytä soittolistassa seuraavaksi toistettavan videon tiedot.", + "EnableStreamLooping": "Looppaa suoralähetykset", + "EnableNextVideoInfoOverlayHelp": "Näytä videon lopussa tietoja seuraavasta videosta soittolistalla.", "ClientSettings": "Pääte-asetukset", "AllowFfmpegThrottlingHelp": "Kun uudelleenkoodaus tai remux ehtii tarpeeksi toiston edelle, keskeytä laskenta jotta laskentaresursseja kuluu vähemmän. Tämä on hyödyllistä jos katselet hyppimättä eri kohtiin. Älä käytä jos toiston kanssa ilmenee ongelmia.", "AllowFfmpegThrottling": "Rajoita uudelleenkoodaus", @@ -319,5 +315,964 @@ "ErrorAddingXmlTvFile": "XMLTV-tiedostoa käyttäessä tapahtui virhe. Varmista, että tiedosto on olemassa ja kokeile uudestaan.", "ErrorAddingTunerDevice": "Viritintä lisätessä ilmeni ongelma. Varmista, että se on kytketty oikein ja kokeile uudestaan.", "EnableThemeVideosHelp": "Soita tunnusvideoita taustalla, selatessasi kirjastoa.", - "EnableThemeVideos": "Teeman videot" + "EnableThemeVideos": "Tunnusvideot", + "AlbumArtist": "Albumin Artisti", + "Album": "Albumi", + "Played": "Toistetut", + "PlayFromBeginning": "Toista alusta", + "PlayNext": "Toista seuraava", + "Play": "Toista", + "PinCodeResetConfirmation": "Haluatko varmasti nollata PIN-koodin?", + "People": "Ihmiset", + "PasswordResetHeader": "Nollaa salasana", + "OriginalAirDateValue": "Alkuperäinen esityspäivä: {0}", + "OptionWeekly": "Viikottainen", + "OptionWeekends": "Viikonloput", + "OptionWeekdays": "Arkipäivät", + "OptionTvdbRating": "TVDB luokitus", + "OptionTrackName": "Raidan nimi", + "OptionThumbCard": "Pienoiskuvakortti", + "OptionThumb": "Pienoiskuva", + "OptionSubstring": "Substring", + "OptionSpecialEpisode": "Erikoisjaksot", + "OptionSaveMetadataAsHidden": "Tallenna metadata ja kuvat piilotettuina tiedostoina", + "OptionRuntime": "Kesto", + "OptionResumable": "Jatkettavissa oleva", + "OptionResElement": "res element", + "OptionReleaseDate": "Julkaisupäivä", + "OptionRegex": "Regex", + "OptionRandom": "Satunnainen", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "HTTP Suoratoisto", + "OptionProfileVideoAudio": "Video Audio", + "OptionProfileVideo": "Video", + "OptionProfilePhoto": "Kuva", + "OptionProfileAudio": "Audio", + "OptionPremiereDate": "Ensi-iltapäivä", + "OptionPosterCard": "Julistekortti", + "OptionPoster": "Juliste", + "OptionPlayCount": "Toistokerrat", + "OptionPlayed": "Toistettu", + "OptionOnAppStartup": "Käynnistyksen yhteydessä", + "OptionNew": "Uusi…", + "OptionNameSort": "Nimi", + "OptionMonday": "Maanantai", + "OptionMissingEpisode": "Puuttuvat jaksot", + "OptionMax": "Maksimi", + "OptionList": "Lista", + "OptionLikes": "Likes", + "OptionIsSD": "SD", + "OptionIsHD": "HD", + "OptionImdbRating": "IMDb Luokitus", + "OptionHomeVideos": "Kuvat", + "OptionHideUser": "Piilota tämä käyttäjä kirjautumisnäkymästä", + "OptionHasTrailer": "Traileri", + "OptionHasThemeVideo": "Tunnusvideo", + "OptionHasThemeSong": "Tunnuskappale", + "OptionHasSubtitles": "Tekstitykset", + "OptionHasSpecialFeatures": "Erikoisominaisuudet", + "OptionFriday": "Perjantai", + "OptionFavorite": "Suosikit", + "OptionExtractChapterImage": "Ota käyttöön kappalekuvien luonti", + "OptionExternallyDownloaded": "Ulkoinen lataus", + "OptionEveryday": "Joka päivä", + "OptionEnded": "Loppuneet", + "OptionEnableM2tsMode": "Ota käyttöön M2ts tila", + "OptionEnableForAllTuners": "Ota käyttöön kaikille viritinlaitteille", + "OptionEnableAccessToAllLibraries": "Salli pääsy kaikkiin kirjastoihin", + "OptionEnableAccessToAllChannels": "Salli pääsy kaikille kanaville", + "OptionEnableAccessFromAllDevices": "Salli pääsy kaikista laitteista", + "OptionDvd": "DVD", + "OptionDownloadThumbImage": "Pienoiskuva", + "OptionDownloadPrimaryImage": "Ensisijainen", + "OptionDownloadMenuImage": "Valikko", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadImagesInAdvance": "Lataa kuvat etukäteen", + "OptionDislikes": "Disliket", + "OptionCustomUsers": "Mukautettu", + "OptionCriticRating": "Kriitikoiden luokitus", + "OptionContinuing": "Jatkuvat", + "OptionCommunityRating": "Yhteisön luokitus", + "OptionBlockLiveTvChannels": "Live-TV kanavat", + "OptionBanner": "Lippu", + "OnlyForcedSubtitlesHelp": "Vain pakotetuiksi merkityt tekstitykset ladataan.", + "OnlyImageFormats": "Vain kuvaformaatit (VOBSUB, PGS, SUB)", + "OnlyForcedSubtitles": "Vain pakotetut", + "NoSubtitlesHelp": "Tekstityksiä ei ladata oletuksena. Ne voidaan silti kytkeä päälle manuaalisesti toiston aikana.", + "News": "Uutiset", + "Never": "Ei koskaan", + "MessageReenableUser": "Ottaaksesi uudelleen käyttöön, katso alempaa", + "MessagePluginConfigurationRequiresLocalAccess": "Kirjaudu suoraan paikalliselle palvelimellesi muokataksesi tätä liitännäistä.", + "MessagePleaseEnsureInternetMetadata": "Varmista, että metadatan lataus on käytössä.", + "MessageNoServersAvailable": "Automaattisen palvelintunnistuksen avulla ei löydy palvelimia.", + "MessageUnauthorizedUser": "Sinulla ei ole lupaa käyttää palvelinta tällä hetkellä. Ota yhteyttä palvelimen järjestelmänvalvojaan saadaksesi lisätietoja.", + "MessageInvalidForgotPasswordPin": "PIN-koodi on kelpaa tai vanhentunut. Yritä uudelleen.", + "MessageImageTypeNotSelected": "Valitse kuvatyyppi pudotusvalikosta.", + "MessageImageFileTypeAllowed": "Vain JPEG ja PNG tiedostomuotoja tuetaan.", + "MessageContactAdminToResetPassword": "Ota yhteyttä järjestelmänvalvojaan nollataksesi salasanasi.", + "MessageConfirmShutdown": "Haluatko varmasti sammuttaa palvelimen?", + "LabelUserLibrary": "Käyttäjän kirjasto:", + "LabelTranscodingProgress": "Transkoodauksen edistyminen:", + "LabelTranscodingFramerate": "Transkoodauksen ruudunpäivitysnopeus:", + "LabelTranscodes": "Transkoodaukset:", + "LabelTrackNumber": "Raidan numero:", + "LabelTitle": "Nimi:", + "LabelTagline": "Tunnisterivi:", + "LabelSubtitlePlaybackMode": "Tekstitystila:", + "LabelSortOrder": "Lajittelujärjestys:", + "LabelSerialNumber": "Sarjanumero", + "LabelSendNotificationToUsers": "Lähetä ilmoitus:", + "LabelSelectVersionToInstall": "Valitse asennettava versio:", + "LabelPublicHttpsPortHelp": "Paikalliseen HTTPS-porttiin liitettävä julkisen portin numero.", + "LabelPublicHttpPortHelp": "Paikalliseen HTTP-porttiin liitettävä julkisen portin numero.", + "LabelPreferredDisplayLanguage": "Ensisijainen näyttökieli:", + "LabelOriginalTitle": "Alkuperäinen nimi:", + "LabelOriginalAspectRatio": "Alkuperäinen kuvasuhde:", + "LabelEnableAutomaticPortMapHelp": "Automaattisesti ohjaa reitittimesi julkiset portit palvelimesi paikallisiin portteihin UPnP:n kautta. Tämä ei välttämättä toimi kaikkien reitittimien tai verkkoasetusten kanssa. Muutokset tulevat voimaan vasta palvelimen uudelleenkäynnistyksen yhteydessä.", + "LabelEnableAutomaticPortMap": "Salli reitittimen porttien automaattinen avaaminen (UPnP)", + "LabelDownloadLanguages": "Latauskielet:", + "LabelDisplaySpecialsWithinSeasons": "Näytä erityiset jaksot kausien sisällä, jolloin ne ilmestyivät", + "LabelDisplayOrder": "Näyttöjärjestys:", + "LabelDisplayName": "Näyttönimi:", + "LabelDisplayMode": "Näyttötila:", + "LabelDateTimeLocale": "Päivämäärä ja aika:", + "LabelCustomRating": "Mukautettu luokitus:", + "LabelCustomDeviceDisplayName": "Näyttönimi:", + "LabelCustomCss": "Mukautettu CSS:", + "LabelCertificatePassword": "Sertifikaatin salasana:", + "LabelAudio": "Audio", + "LabelArtistsHelp": "Erota useita käyttämällä ;", + "LabelAppNameExample": "Esimerkiksi: Sickbeard, Sonarr", + "LabelAppName": "Sovelluksen nimi", + "LabelAllowedRemoteAddressesMode": "Etä-IP-osoitesuodattimen tila:", + "LabelAllowedRemoteAddresses": "Etä-IP-osoitesuodatin:", + "LabelAllowServerAutoRestartHelp": "Palvelin käynnistyy uudelleen vain hiljaisina aikoina, kun yksikään käyttäjä ei ole aktiivinen.", + "LabelAllowServerAutoRestart": "Salli palvelimen automaattinen uudelleenkäynnistys päivitysten asentamiseksi", + "LabelAllowHWTranscoding": "Salli laitteistolla transkoodaus", + "LabelAlbumArtMaxWidth": "Albumin kuvan maksimileveys:", + "LabelAlbumArtMaxHeight": "Albumin kuvan maksimikorkeus:", + "LabelAbortedByServerShutdown": "(Keskeytetty palvelimen sammutuksen takia)", + "Identify": "Tunnista", + "Horizontal": "Horisontaalinen", + "HideWatchedContentFromLatestMedia": "Piilota toistettu sisältö \"uusin media\"-luettelosta", + "HeaderUpcomingOnTV": "Tulossa televisiossa", + "HeaderTypeImageFetchers": "{0} Kuvien hakijat", + "HeaderTranscodingProfile": "Transkoodausprofiili", + "HeaderTracks": "Raidat", + "HeaderThisUserIsCurrentlyDisabled": "Tämä käyttäjä on poistettu käytöstä", + "HeaderSystemDlnaProfiles": "Järjestelmäprofiilit", + "HeaderSubtitleDownloads": "Tekstitysten lataukset", + "HeaderSpecialFeatures": "Lisäominaisuudet", + "HeaderSpecialEpisodeInfo": "Erikoisjakson tiedot", + "HeaderSortOrder": "Lajittelujärjestys", + "HeaderSetupLibrary": "Aseta mediakirjastosi", + "HeaderSeriesStatus": "Sarjan status", + "HeaderSeriesOptions": "Sarjan asetukset", + "HeaderSelectTranscodingPath": "Valitse transkoodauksen väliaikainen polku", + "HeaderSchedule": "Ajastus", + "HeaderScenes": "Kohtaukset", + "HeaderResponseProfile": "Vastausprofiili", + "HeaderRemoveMediaLocation": "Poista mediasijainti", + "HeaderRecordingOptions": "Tallennusasetukset", + "HeaderRecentlyPlayed": "Äskettäin toistetut", + "HeaderProfileServerSettingsHelp": "Nämä arvot mukauttavat sitä, kuinka Jellyfin-palvelin esittää itsensä laitteelle.", + "HeaderProfileInformation": "Profiili-informaatio", + "HeaderPreferredMetadataLanguage": "Ensisijainen metadatan kieli", + "HeaderPinCodeReset": "Nollaa PIN-koodi", + "HeaderPhotoAlbums": "Kuva-albumit", + "HeaderPendingInvitations": "Odottavat kutsut", + "HeaderPaths": "Polut", + "HeaderPasswordReset": "Salasanan nollaus", + "HeaderNextVideoPlayingInValue": "Seuraava video alkaa {0}", + "HeaderNextEpisodePlayingInValue": "Seuraava jakso alkaa {0}", + "HeaderNewDevices": "Uudet laitteet", + "HeaderMyMediaSmall": "Minun mediani (pieni)", + "HeaderMyMedia": "Minun mediani", + "HeaderMyDevice": "Minun laitteeni", + "HeaderLoginFailure": "Kirjautumisvirhe", + "HeaderIdentifyItemHelp": "Anna yksi tai useampi hakukriteeri. Poista kriteerejä lisätäksesi hakutuloksia.", + "HeaderIdentificationCriteriaHelp": "Lisää ainakin yksi tunnistuskriteeri.", + "HeaderFeatures": "Ominaisuudet", + "HeaderFavoriteVideos": "Suosikkivideot", + "HeaderFavoritePeople": "Suosikki-ihmiset", + "HeaderFavoriteMovies": "Suosikkielokuvat", + "HeaderFavoriteBooks": "Suosikkikirjat", + "HeaderExternalIds": "Ulkoiset IDt:", + "HeaderDirectPlayProfile": "Suoratoistoprofiili", + "HeaderEasyPinCode": "Helppo PIN-koodi", + "HeaderDownloadSync": "Lataus ja synkronointi", + "HeaderDeveloperInfo": "Kehittäjäinfo", + "HeaderDetectMyDevices": "Havaitse laitteitani", + "HeaderDeleteProvider": "Poista tarjoaja", + "HeaderDefaultRecordingSettings": "Oletus tallennusasetukset", + "HeaderDateIssued": "Antopäivä", + "HeaderCustomDlnaProfiles": "Mukautetut profiilit", + "HeaderConfirmRevokeApiKey": "Peru API-avain", + "HeaderConfirmProfileDeletion": "Vahvista profiilin poisto", + "HeaderConfirmPluginInstallation": "Vahvista liitännäisen asennus", + "HeaderConfigureRemoteAccess": "Määritä etäkäyttö", + "HeaderChapterImages": "Kappalekuvat", + "HeaderChannels": "Kanavat", + "HeaderApp": "Sovellus", + "HeaderAllowMediaDeletionFrom": "Salli median poisto", + "HeaderAlert": "Hälytys", + "HeaderActivity": "Toiminta", + "HandledByProxy": "Reverse proxyn hoitama", + "HDPrograms": "HD-ohjelmat", + "OptionDownloadArtImage": "Taide", + "OptionDownloadDiscImage": "Levy", + "OptionDownloadBoxImage": "Laatikko", + "OptionDownloadBannerImage": "Lippu", + "OptionDownloadBackImage": "Tausta", + "OptionDisableUser": "Poista tämä käyttäjä käytöstä", + "OptionDescending": "Laskeva", + "OptionDatePlayed": "Toistopäivä", + "OptionDateAddedImportTime": "Käytä kirjastoon skannauspäivää", + "OptionDateAddedFileTime": "Käytä tiedoston luontipäivää", + "OptionDateAdded": "Lisäyspäivä", + "OptionDaily": "Päivittäinen", + "OptionBluray": "Blu-ray", + "TabTrailers": "Trailerit", + "OptionBlockTvShows": "TV-sarjat", + "OptionBlockTrailers": "Trailerit", + "OptionBlockMusic": "Musiikki", + "OptionBlockMovies": "Elokuvat", + "HeaderMovies": "Elokuvat", + "HeaderMoreLikeThis": "Lisää tällaista", + "HeaderMetadataSettings": "Metadata-asetukset", + "MoreMediaInfo": "Mediainfo", + "HeaderMediaInfo": "Mediainfo", + "HeaderMediaFolders": "Mediakansiot", + "HeaderMedia": "Media", + "HeaderLiveTv": "Live-TV", + "HeaderLibraryFolders": "Kirjaston kansiot", + "HeaderLatestMedia": "Uusin media", + "HeaderLatestRecordings": "Uusimmat tallenteet", + "HeaderLatestMusic": "Uusin musiikki", + "HeaderLatestMovies": "Uusimmat elokuvat", + "HeaderLatestEpisodes": "Uusimmat jaksot", + "HeaderInstall": "Asenna", + "HeaderGenres": "Tyylilajit", + "HeaderFrequentlyPlayed": "Usein toistetut", + "HeaderFetcherSettings": "Hakijan asetukset", + "HeaderFetchImages": "Hae kuvia:", + "HeaderFilters": "Suodattimet", + "OptionBlockBooks": "Kirjat", + "Filters": "Suodattimet", + "FastForward": "Hyppää eteenpäin", + "YadifBob": "YADIF Bob", + "MessageInvalidUser": "Väärä käyttäjätunnus tai salasana. Yritä uudelleen.", + "MessageConfirmRestart": "Haluatko varmasti uudelleenkäynnistää Jellyfin-palvelimen?", + "MessageConfirmProfileDeletion": "Haluatko varmasti poistaa tämän profiilin?", + "MessageConfirmDeleteTunerDevice": "Haluatko varmasti poistaa tämän laitteen?", + "MessageConfirmAppExit": "Haluatko poistua?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "Haluatko varmasti poistaa tämän mediakansion?", + "MessageAreYouSureDeleteSubtitles": "Haluatko varmasti poistaa tämän tekstitystiedoston?", + "MessageAlreadyInstalled": "Tämä versio on jo asennettu.", + "Menu": "Valikko", + "MediaInfoStreamTypeVideo": "Video", + "MediaInfoStreamTypeSubtitle": "Tekstitys", + "MediaInfoStreamTypeData": "Data", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoSoftware": "Ohjelmisto", + "MediaInfoTimestamp": "Aikaleima", + "MediaInfoResolution": "Resoluutio", + "MediaInfoSize": "Koko", + "MediaInfoProfile": "Profiili", + "MediaInfoLevel": "Taso", + "MediaInfoPath": "Polku", + "MediaInfoLanguage": "Kieli", + "MediaInfoInterlaced": "Lomiteltu", + "MediaInfoFramerate": "Ruudunpäivitysnopeus", + "MediaInfoForced": "Pakotettu", + "MediaInfoDefault": "Oletus", + "MediaInfoExternal": "Ulkoinen", + "MediaInfoChannels": "Kanavat", + "MediaInfoAspectRatio": "Kuvasuhde", + "MarkUnplayed": "Merkitse toistamattomaksi", + "MarkPlayed": "Merkitse toistetuksi", + "ManageRecording": "Hallitse tallennusta", + "ManageLibrary": "Hallitse kirjastoa", + "Logo": "Logo", + "LiveTV": "Live-TV", + "LiveBroadcasts": "Suorat lähetykset", + "Live": "Suora", + "List": "Lista", + "LinksValue": "Linkkejä: {0}", + "LearnHowYouCanContribute": "Katso, miten voit auttaa.", + "Large": "Suuri", + "LabelffmpegPath": "FFmpeg polku:", + "LabelZipCode": "Postinumero:", + "LabelYear": "Vuosi:", + "LabelVideoResolution": "Videon resoluutio:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arkkitehtuuri: {0}", + "DashboardOperatingSystem": "Käyttöjärjestelmä: {0}", + "DashboardServerName": "Palvelin: {0}", + "DashboardVersionNumber": "Versio: {0}", + "LabelVersionInstalled": "{0} asennettu", + "LabelVersion": "Versio:", + "LabelValue": "Arvo:", + "LabelUsername": "Käyttäjätunnus:", + "LabelUser": "Käyttäjä:", + "LabelUseNotificationServices": "Käytä seuraavia palveluita:", + "LabelTypeText": "Teksti", + "LabelTypeMetadataDownloaders": "{0} metadatan lataajat:", + "LabelType": "Tyyppi:", + "LabelTunerType": "Virittimen tyyppi:", + "LabelTunerIpAddress": "Virittimen IP-osoite:", + "LabelTranscodingThreadCountHelp": "Valitse enimmäismäärä säikeitä, joita käytetään transkoodatessa. Säikeiden vähentäminen laskee prosessorin käyttöä, mutta myös lisää riskiä, että uudelleenkoodaus ei tapahdu riittävän nopeasti virheetöntä toistoa varten.", + "LabelTranscodingThreadCount": "Transkoodaus säikeidein lukumäärä:", + "LabelTranscodePath": "Transkoodauksen polku:", + "LabelTimeLimitHours": "Aikaraja (tunteina):", + "LabelTime": "Aika:", + "LabelTheme": "Teema:", + "LabelTextSize": "Tekstin koko:", + "LabelTextColor": "Tekstin väri:", + "LabelTextBackgroundColor": "Tekstin taustaväri:", + "LabelSupportedMediaTypes": "Tuetut mediatyypit:", + "LabelTag": "Tunniste:", + "LabelSubtitles": "Tekstitykset", + "LabelSubtitleFormatHelp": "Esimerkki: srt", + "LabelStatus": "Status:", + "LabelSource": "Lähde:", + "LabelSize": "Koko:", + "LabelServerName": "Palvelimen nimi:", + "LabelServerHostHelp": "192.168.1.100:8096 tai https://myserver.com", + "LabelSelectUsers": "Valitse käyttäjät:", + "LabelSeasonNumber": "Kauden numero:", + "LabelScreensaver": "Näytönsäästäjä:", + "LabelReasonForTranscoding": "Transkoodauksen syy:", + "LabelReadHowYouCanContribute": "Katso, miten voit auttaa.", + "LabelPublicHttpsPort": "Julkinen HTTPS-porttinumero:", + "LabelPublicHttpPort": "Julkinen HTTP-porttinumero:", + "LabelProtocolInfo": "Protokollan info:", + "LabelProtocol": "Protokolla:", + "LabelPreferredSubtitleLanguage": "Ensisijainen tekstityksen kieli:", + "LabelPreferredDisplayLanguageHelp": "Jellyfinin kääntäminen on käynnissä oleva projekti.", + "LabelPlayerDimensions": "Soittimen mitat:", + "LabelPlayer": "Soitin:", + "LabelPlaylist": "Soittolista:", + "LabelPlaceOfBirth": "Synnyinpaikka:", + "LabelPersonRoleHelp": "Esimerkki: Jäätelöauton ajaja", + "LabelPersonRole": "Rooli:", + "LabelPath": "Polku:", + "LabelPasswordRecoveryPinCode": "PIN-koodi:", + "LabelPasswordConfirm": "Salasanan varmistus:", + "LabelPassword": "Salasana:", + "LabelOptionalNetworkPath": "(Valinnainen) Jaettu verkkokansio:", + "LabelNumber": "Numero:", + "LabelNotificationEnabled": "Ota tämä ilmoitus käyttöön", + "LabelNewName": "Uusi nimi:", + "LabelName": "Nimi:", + "LabelMovieCategories": "Elokuvakategoriat:", + "LabelMinScreenshotDownloadWidth": "Pienin kuvakaappauksen latauksen leveys:", + "LabelMinResumePercentageHelp": "Kohteita pidetään toistamattomina, jos toisto keskeytetään ennen tätä aikaa.", + "LabelMinResumeDuration": "Minimi jatkamisen kesto (sekuntia):", + "LabelMinResumePercentage": "Vähimmäisaika jatkoa varten (%):", + "LabelMinResumeDurationHelp": "Kohteiden, joiden toistoaika on tätä lyhyempi, ei voi jatkaa.", + "LabelMethod": "Metodi:", + "LabelMetadataSaversHelp": "Valitse tiedostomuodot, joihin metadata tallennetaan.", + "LabelMetadataSavers": "Metadatan tallentajat:", + "LabelMetadataDownloadLanguage": "Ensisijainen latauskieli:", + "LabelMetadataReaders": "Metadatan lukijat:", + "LabelMetadataPath": "Metadatan polku:", + "LabelMetadata": "Metadata:", + "LabelMessageTitle": "Viestin otsikko:", + "LabelCachePath": "Välimuistin polku:", + "LabelCache": "Välimuisti:", + "LabelBurnSubtitles": "Polta tekstitykset:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Päivitä metadata automaattisesti:", + "LabelAuthProvider": "Todennuksen tarjoaja:", + "ExtraLarge": "Suurin", + "EveryNDays": "Joka {0} päivä", + "Raised": "Korotettu", + "TabShows": "Sarjat", + "Yesterday": "Eilen", + "Yes": "Kyllä", + "Unplayed": "Toistamattomat", + "Unmute": "Lopeta vaimennus", + "Tuesday": "Tiistai", + "Transcoding": "Transkoodaus", + "Trailers": "Trailerit", + "TitlePlayback": "Toistaminen", + "Thursday": "Torstai", + "TheseSettingsAffectSubtitlesOnThisDevice": "Nämä asetukset vaikuttavat tekstityksiin tällä laitteella", + "ThemeVideos": "Tunnusvideot", + "ThemeSongs": "Tunnuslaulut", + "TagsValue": "Tunnisteet: {0}", + "Tags": "Tunnisteet", + "TabUsers": "Käyttäjät", + "TabUpcoming": "Tulevat", + "TabTranscoding": "Transkoodaus", + "TabSuggestions": "Ehdotukset", + "TabSongs": "Kappaleet", + "TabSettings": "Asetukset", + "TabServer": "Palvelin", + "TabSeries": "Sarjat", + "TabScheduledTasks": "Ajastetut tehtävät", + "TabResumeSettings": "Jatka", + "TabResponses": "Vastaukset", + "TabRecordings": "Tallennukset", + "TabPlugins": "Liitännäiset", + "TabPlaylists": "Soittolistat", + "TabPlaylist": "Soittolista", + "TabPlayback": "Toistaminen", + "TabNfoSettings": "NFO-asetukset", + "TabNetworks": "Verkot", + "TabMyPlugins": "Omat liittännäiseni", + "TabMusicVideos": "Musiikkivideot", + "TabMusic": "Musiikki", + "TabMovies": "Elokuvat", + "TabMetadata": "Metadata", + "TabLogs": "Lokit", + "TabLiveTV": "Live-TV", + "TabLatest": "Uusimmat", + "TabInfo": "Tiedot", + "TabGenres": "Tyylilajit", + "TabFavorites": "Suosikit", + "TabEpisodes": "Jaksot", + "TabDisplay": "Näyttö", + "TabDirectPlay": "Suoratoisto", + "TabDevices": "Laitteet", + "TabDashboard": "Päänäkymä", + "TabCollections": "Kokoelmat", + "TabChannels": "Kanavat", + "TabCatalog": "Luettelo", + "TabArtists": "Artistit", + "TabAlbums": "Albumit", + "TabAlbumArtists": "Albumin artistit", + "TabAdvanced": "Edistynyt", + "TV": "TV", + "Sunday": "Sunnuntai", + "Subtitles": "Tekstitykset", + "Studios": "Studiot", + "StopRecording": "Lopeta tallennus", + "Sort": "Järjestä", + "Smart": "Älykäs", + "SkipEpisodesAlreadyInMyLibrary": "Älä tallenna jaksoja, jotka ovat jo kirjastossani", + "Shuffle": "Satunnaistoisto", + "ShowTitle": "Näytä nimi", + "ShowYear": "Näytä vuosi", + "ShowAdvancedSettings": "Näytä edistyneet asetukset", + "Share": "Jaa", + "Settings": "Asetukset", + "ServerRestartNeededAfterPluginInstall": "Jellyfin palvelin täytyy uudelleenkäynnistää liitännäisen asennuksen jälkeen.", + "ServerNameIsShuttingDown": "Jellyfin palvelin - {0} on sammumassa.", + "ServerNameIsRestarting": "Jellyfin palvelin - {0} uudelleenkäynnistyy.", + "Series": "Sarjat", + "SendMessage": "Lähetä viesti", + "SelectAdminUsername": "Valitse käyttäjätunnus järjestelmänvalvojan tilille.", + "SearchForCollectionInternetMetadata": "Etsi kuvamateriaalia ja metadataa internetistä", + "SearchForMissingMetadata": "Etsi puuttuvaa metadataa", + "Season": "Kausi", + "SearchResults": "Tulokset", + "SearchForSubtitles": "Etsi tekstityksiä", + "Search": "Etsi", + "Screenshots": "Kuvakaappaukset", + "Screenshot": "Kuvakaappaus", + "Schedule": "Ajasta", + "ScanLibrary": "Skannaa kirjasto", + "SaveSubtitlesIntoMediaFolders": "Tallenna tekstitykset mediakansioihin", + "Saturday": "Lauantai", + "ResumeAt": "Jatka kohdasta {0}", + "RequiredForAllRemoteConnections": "Vaadittu kaikille etäyhteyksille", + "ReplaceExistingImages": "Korvaa olemassaolevat kuvat", + "ReplaceAllMetadata": "Korvaa kaikki metadata", + "RepeatEpisodes": "Toista jaksot uudelleen", + "RepeatAll": "Toista kaikki uudelleen", + "Repeat": "Toista uudelleen", + "RemoveFromPlaylist": "Poista soittolistalta", + "RemoveFromCollection": "Poista kokoelmasta", + "RememberMe": "Muista minut", + "ReleaseDate": "Julkaisupäivä", + "RefreshMetadata": "Päivitä metadata", + "RefreshDialogHelp": "Metadata päivitetään asetuksien ja Internet palveluiden perusteella, jotka ovat kytkettynä päälle Jellyfin palvelimen päänäkymässä.", + "Refresh": "Päivitä", + "Recordings": "Tallennukset", + "RecordingScheduled": "Tallennus ajastettu.", + "RecordingCancelled": "Tallennus peruttu.", + "RecordSeries": "Tallenna sarja", + "Record": "Tallenna", + "OptionAutomatic": "Auto", + "OptionAuto": "Auto", + "OptionAscending": "Nousevassa järjestyksessä", + "OptionArtist": "Artisti", + "OptionAllowVideoPlaybackTranscoding": "Salli transkoodausta vaativan videon toistaminen", + "OptionAllowVideoPlaybackRemuxing": "Salli videon toistaminen, joka vaatii muuntamista ilman koodausta", + "OptionAllowMediaPlaybackTranscodingHelp": "Transkoodauksen estäminen voi aiheuttaa toistovirheitä Jellyfin-sovelluksissa ei-tuettujen mediaformaattien takia.", + "OptionAllowLinkSharingHelp": "Vain mediatietoja sisältävät web-sivut jaetaan. Mediatiedostoja ei koskaan jaeta julkisesti. Jaot ovat määräaikaisia ja päättyvät {0} päivän kuluttua.", + "OptionAllowUserToManageServer": "Salli tämän käyttäjän hallita palvelinta", + "OptionAllowSyncTranscoding": "Salli transkoodausta vaativan median lataaminen ja synkronointi", + "OptionAllowRemoteSharedDevicesHelp": "DLNA-laitteet katsotaan jaetuiksi kunnes käyttäjä alkaa ohjata niitä.", + "OptionAllowRemoteSharedDevices": "Salli jaettujen laitteiden etäohjaaminen", + "OptionAllowRemoteControlOthers": "Salli muiden käyttäjien etäohjaaminen", + "OptionAllowManageLiveTv": "Salli Live-TV tallenteiden hallinta", + "OptionAllowMediaPlayback": "Salli median toisto", + "OptionAllowContentDownloading": "Salli median lataaminen ja synkronointi", + "OptionAllowBrowsingLiveTv": "Salli Live-TV käyttöoikeus", + "HeaderPluginInstallation": "Liitännäisen asennus", + "HeaderPlaybackError": "Toistovirhe", + "HeaderPlayback": "Median toisto", + "HeaderPlayOn": "Toista laitteella", + "OptionAllowLinkSharing": "Salli jakaminen sosiaaliseen mediaan", + "OptionAllowAudioPlaybackTranscoding": "Salli äänen toistaminen joka vaatii uudelleenpakkausta", + "OptionAllUsers": "Kaikki käyttäjät", + "OptionAlbumArtist": "Albumin artisti", + "OptionAlbum": "Albumi", + "OptionAdminUsers": "Järjestelmänvalvojat", + "Option3D": "3D", + "MusicVideo": "Musiikkivideo", + "MoveRight": "Siirry oikealle", + "MoveLeft": "Siirry vasemmalle", + "Mobile": "Mobiili", + "EveryXHours": "Joka {0} tunti", + "EveryXMinutes": "Joka {0} minuutti", + "EveryHour": "Joka tunti", + "LastSeen": "Viimeksi nähty {0}", + "Yadif": "YADIF", + "Writer": "Kirjoittaja", + "WelcomeToProject": "Tervetuloa Jellyfiniin!", + "Wednesday": "Keskiviikko", + "ViewArtist": "Näytä artisti", + "ViewAlbum": "Näytä albumi", + "Vertical": "Vertikaalinen", + "ValueSongCount": "{0} kappaletta", + "ValueSeriesCount": "{0} sarjaa", + "ValueSeconds": "{0} sekuntia", + "ValueOneSong": "1 kappale", + "ValueOneSeries": "1 sarja", + "ValueOneMusicVideo": "1 musiikkivideo", + "ValueOneMovie": "1 elokuva", + "ValueOneEpisode": "1 jakso", + "ValueOneAlbum": "1 albumi", + "ValueMusicVideoCount": "{0} musiikkivideota", + "ValueMovieCount": "{0} elokuvaa", + "ValueMinutes": "{0} min", + "ValueEpisodeCount": "{0} jaksoa", + "ValueDiscNumber": "Levy {0}", + "ValueAlbumCount": "{0} albumia", + "Up": "Ylös", + "OnApplicationStartup": "Käynnistyksen yhteydessä", + "NumLocationsValue": "{0} kansiota", + "NoSubtitleSearchResultsFound": "Ei tuloksia.", + "NoPluginConfigurationMessage": "Tällä liitännäisellä ei ole asetuksia muokattavaksi.", + "NoCreatedLibraries": "Vaikuttaa siltä, ettet ole luonut vielä yhtään kirjastoa. {0} Haluaisitko luoda sellaisen nyt?{1}", + "No": "Ei", + "NextUp": "Seuraavana", + "Next": "Seuraava", + "NewEpisodesOnly": "Vain uudet jaksot", + "NewEpisodes": "Uusia jaksoja", + "NewCollectionNameExample": "Esimerkki: Star Wars Kokoelma", + "NewCollectionHelp": "Kokoelmat mahdollistavat elokuvien ja muun kirjastosisällön personalisoidun ryhmittämisen.", + "NewCollection": "Uusi kokoelma", + "Mute": "Vaimenna", + "Name": "Nimi", + "MySubtitles": "Minun tekstitykseni", + "MusicArtist": "Musiikkiartisti", + "MusicAlbum": "Musiikkialbumi", + "Movie": "Elokuva", + "Monday": "Maanantai", + "MetadataManager": "Metadatan hallintatyökalu", + "Metadata": "Metadata", + "MessageYouHaveVersionInstalled": "Sinulla on versio {0} asennettuna.", + "MessageSettingsSaved": "Asetukset tallennettu.", + "MessagePleaseWait": "Ole hyvä ja odota. Tämä voi kestää hetken.", + "MessageNothingHere": "Täällä ei ole mitään.", + "MessageNoPluginsInstalled": "Sinulla ei ole asennettuna yhtään liitännäistä.", + "MessageNoAvailablePlugins": "Ei saatavilla olevia liitännäisiä.", + "InstallingPackage": "Asennetaan {0} (versio {1})", + "HeaderVideoTypes": "Videotyypit", + "HeaderVideoType": "Videotyyppi", + "HeaderUploadImage": "Lataa kuva", + "HeaderTypeText": "Kirjoita teksti", + "HeaderTunerDevices": "Viritinlaitteet", + "HeaderTuners": "Virittimet", + "HeaderTaskTriggers": "Tehtävän laukaisijat", + "HeaderSubtitleProfilesHelp": "Tekstitysprofiilit kuvaavat tämän laitteen tukemia tekstitysformaatteja.", + "HeaderSubtitleProfiles": "Tekstitysprofiilit", + "HeaderSubtitleProfile": "Tekstitysprofiili", + "HeaderStartNow": "Aloita nyt", + "HeaderSortBy": "Lajittele", + "HeaderSelectServerCachePath": "Valitse palvelimen välimuistin polku", + "HeaderSelectPath": "Valitse polku", + "HeaderSelectCertificatePath": "Valitse sertifikaatin polku", + "HeaderSelectMetadataPath": "Valitse metadatan polku", + "HeaderSecondsValue": "{0} Sekuntia", + "HeaderRunningTasks": "Käynnissä olevat tehtävät", + "HeaderRevisionHistory": "Versiohistoria", + "HeaderRemoveMediaFolder": "Poista mediakansio", + "HeaderRemoteControl": "Etäohjaus", + "HeaderPleaseSignIn": "Ole hyvä ja kirjaudu sisään", + "BoxSet": "Laatikkosarja", + "LabelManufacturerUrl": "Valmistajan verkko-osoite", + "LabelManufacturer": "Valmistaja:", + "LabelLogs": "Lokit:", + "LabelLanNetworks": "Lähiverkot:", + "LabelKodiMetadataDateFormat": "Julkaisupäivämäärän muoto:", + "LabelImageType": "Kuvan tyyppi:", + "LabelIconMaxWidth": "Ikonin enimmäisleveys:", + "LabelIconMaxHeight": "Ikonin enimmäiskorkeus:", + "LabelGroupMoviesIntoCollections": "Ryhmitä elokuvat kokoelmiin", + "LabelFormat": "Muoto:", + "LabelFont": "Kirjasinlaji:", + "LabelFolder": "Kansio:", + "LabelEpisodeNumber": "Jaksonumero:", + "LabelDropShadow": "Varjostus:", + "LabelDeathDate": "Kuolinpäivä:", + "LabelDay": "Päivä:", + "LabelDateAdded": "Lisäyspäivämäärä:", + "LabelCollection": "Kokoelma:", + "LabelBirthYear": "Syntymävuosi:", + "LabelBirthDate": "Syntymäaika:", + "LabelArtists": "Artistit:", + "LabelAll": "Kaikki", + "LabelAlbum": "Albumi:", + "LabelAirTime": "Lähetysaika:", + "LabelAccessDay": "Viikonpäivä:", + "Label3DFormat": "3D-formaatti:", + "Kids": "Lapset", + "Images": "Kuvat", + "Hide": "Piilota", + "HeadersFolders": "Kansiot", + "HeaderYears": "Vuodet", + "HeaderVideos": "Videot", + "HeaderVideoQuality": "Kuvanlaatu", + "HeaderUsers": "Käyttäjät", + "HeaderUser": "Käyttäjä", + "HeaderTags": "Tunnisteet", + "HeaderSubtitleAppearance": "Tekstityksen ulkonäkö", + "HeaderStatus": "Tila", + "HeaderShutdown": "Sammuta", + "HeaderServerSettings": "Palvelimen asetukset", + "HeaderSettings": "Asetukset", + "HeaderSendMessage": "Lähetä viesti", + "HeaderSelectServer": "Valitse palvelin", + "HeaderSeasons": "Kaudet", + "HeaderRestart": "Uudelleenkäynnistys", + "HeaderProfile": "Profiili", + "HeaderPlayAll": "Toista kaikki", + "HeaderPeople": "Ihmiset", + "HeaderPassword": "Salasana", + "HeaderNewApiKey": "Uusi API-avain", + "HeaderNavigation": "Navigaatio", + "HeaderMusicVideos": "Musiikkivideot", + "HeaderMusicQuality": "Musiikin laatu", + "HeaderLibraries": "Kirjastot", + "HeaderIdentification": "Tunnistautuminen", + "HeaderForgotPassword": "Unohtuiko salasana", + "HeaderForKids": "Lapsille", + "HeaderError": "Virhe", + "HeaderEpisodes": "Jaksot", + "HeaderEditImages": "Muokkaa kuvia", + "HeaderDisplay": "Näyttö", + "HeaderDevices": "Laitteet", + "HeaderDeleteItems": "Poista valitut", + "HeaderDeleteItem": "Poista valittu", + "HeaderDeleteDevice": "Poista laite", + "HeaderContinueListening": "Jatka kuuntelua", + "HeaderConnectionFailure": "Yhteys epäonnistui", + "HeaderConnectToServer": "Yhdistä palvelimeen", + "HeaderAudioSettings": "Ääniasetukset", + "GroupBySeries": "Ryhmitä sarjan perusteella", + "Fullscreen": "Kokonäyttötila", + "HeaderBooks": "Kirjat", + "HeaderAutomaticUpdates": "Automaattiset päivitykset", + "HeaderAudioBooks": "Äänikirjat", + "HeaderApiKeys": "API-avaimet", + "HeaderApiKey": "API-avain", + "HeaderAdmin": "Ylläpitäjä", + "HeaderAlbums": "Albumit", + "HeaderAddUser": "Lisää käyttäjä", + "HeaderAddUpdateImage": "Lisää/Päivitä kuva", + "HeaderAddToPlaylist": "Lisää soittolistaan", + "HeaderAddToCollection": "Lisää kokoelmaan", + "HeaderActiveDevices": "Aktiiviset laitteet", + "Friday": "Perjantai", + "Premiere": "Ensiesitys", + "Producer": "Tuottaja", + "Quality": "Laatu", + "LabelMessageText": "Viestin sisältö:", + "LabelMaxScreenshotsPerItem": "Kuvakaappausten enimmäismäärä per kohde:", + "LabelLoginDisclaimerHelp": "Viesti, joka näytetään kirjautumissivun alareunassa.", + "LabelLockItemToPreventChanges": "Lukitse tämä kohde estääksesi tulevat muutokset", + "LabelLocalHttpServerPortNumberHelp": "TCP-portin numero, jota Jellyfinin HTTP-palvelimen tulee kuunnella.", + "LabelLocalHttpServerPortNumber": "Paikallisen HTTP-portin numero:", + "LabelKodiMetadataSaveImagePaths": "Tallenna kuvien polut NFO-tiedostojen sisälle", + "LabelKidsCategories": "Lasten kategoriat:", + "LabelHttpsPortHelp": "TCP-portin numero, jota Jellyfinin HTTPS-palvelimen tulee kuunnella.", + "LabelHttpsPort": "Paikallisen HTTPS-portin numero:", + "LabelHardwareAccelerationTypeHelp": "Laitteistokiihdytys vaatii ylimääräistä määritystä.", + "LabelHardwareAccelerationType": "Laitteistokiihdytys:", + "LabelEncoderPreset": "H264 and H265 encoding preset:", + "LabelH264Crf": "H264 encoding CRF:", + "LabelForgotPasswordUsernameHelp": "Anna käyttäjätunnuksesi, jos muistat sen.", + "LabelEveryXMinutes": "Joka:", + "LabelEndDate": "Päättymispäivä:", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Määrittää, kuinka usein Jellyfin etsii SSDP-protokollaa käyttäviä laitteita.", + "LabelEnableDlnaDebugLoggingHelp": "Luo suuria lokitiedostoja ja tulisi käyttää vain tarvittaessa vianmääritystä varten.", + "LabelEnableDlnaServerHelp": "Sallii verkon UPnP-laitteiden selata ja toistaa sisältöä tältä palvelimelta.", + "LabelEnableDlnaServer": "Ota DLNA-palvelin käyttöön", + "LabelEnableDlnaPlayTo": "Salli DLNA toisto", + "LabelEnableDlnaDebugLogging": "Ota DLNA:n virheenjäljitys käyttöön", + "LabelEnableBlastAliveMessages": "Lähetä hereilläolo -viesti", + "LabelEnableBlastAliveMessagesHelp": "Ota tämä käyttöön, jos muilla verkon UPnP-laitteilla on ongelmia palvelimen havaitsemisessa.", + "LabelEnableDlnaClientDiscoveryInterval": "Asiakaslaitteiden havaintoväli (sekunteina)", + "LabelEasyPinCode": "Helppo PIN-koodi:", + "LabelDynamicExternalId": "{0} Id:", + "LabelDisplayLanguageHelp": "Jellyfinin kääntäminen on käynnissä oleva projekti.", + "LabelDisplayLanguage": "Näytön kieli:", + "LabelDiscNumber": "Levynumero:", + "LabelDeviceDescription": "Laitteen kuvaus", + "LabelDefaultScreen": "Oletusnäyttö:", + "LabelDefaultUser": "Oletuskäyttäjä:", + "LabelDashboardTheme": "Palvelimen päänäkymän teema:", + "LabelCustomCertificatePathHelp": "Polku PKCS # 12-tiedostoon, joka sisältää sertifikaatin ja yksityisen avaimen, jotta TLS-tuki voidaan sallia henkilökohtaiselle verkkotunnukselle.", + "LabelCustomCertificatePath": "Mukautetun SSL-sertifikaatin polku:", + "LabelContentType": "Sisältötyyppi:", + "LabelChannels": "Kanavat:", + "LabelCertificatePasswordHelp": "Jos sertifikaattisi vaatii salasanaa, laita se tähän.", + "OptionWednesday": "Keskiviikko", + "OptionTuesday": "Tiistai", + "OptionThursday": "Torstai", + "OptionSunday": "Sunnuntai", + "OptionSaturday": "Lauantai", + "LabelRuntimeMinutes": "Pituus (minuutteja):", + "LabelReleaseDate": "Julkaisupäivä:", + "Genre": "Tyylilaji", + "FolderTypeBooks": "Kirjat", + "FolderTypeMusicVideos": "Musiikkivideot", + "FolderTypeMusic": "Musiikki", + "FolderTypeMovies": "Elokuvat", + "File": "Tiedosto", + "Favorite": "Suosikki", + "Extras": "Extrat", + "ExitFullscreen": "Poistu kokonäyttötilasta", + "Episode": "Jakso", + "ButtonTogglePlaylist": "Soittolista", + "ButtonToggleContextMenu": "Lisää", + "Artist": "Artisti", + "RefreshQueued": "Päivitys odottamassa.", + "SeriesCancelled": "Sarja peruttu.", + "MediaInfoRefFrames": "Ref frames", + "LabelXDlnaDoc": "X-DLNA doc:", + "LabelXDlnaCap": "X-DLNA cap:", + "Guide": "Opas", + "DailyAt": "Päivittäin klo {0}", + "Uniform": "Yhtenäinen", + "TrackCount": "{0} raitaa", + "Track": "Raita", + "TitleHardwareAcceleration": "Laitteistokiihdytys", + "Thumb": "Pikkukuva", + "TabStreaming": "Suoratoisto", + "TabParentalControl": "Lapsilukko", + "TabOther": "Muut", + "TabNotifications": "Ilmoitukset", + "TabNetworking": "Verkkoasetukset", + "TabGuide": "Opas", + "TabContainers": "Säiliöt", + "TabCodecs": "Codecit", + "Suggestions": "Ehdotukset", + "Sports": "Urheilu", + "Smaller": "Pienin", + "SmallCaps": "Small Caps", + "Small": "Pieni", + "Filter": "Suodata", + "New": "Uusi", + "Runtime": "Kesto", + "PlayAllFromHere": "Toista kaikki tästä lähtien", + "PlayNextEpisodeAutomatically": "Toista seuraava jakso automaattisesti", + "PlayCount": "Toistokerrat", + "PlaybackData": "Toistamisen tiedot", + "PinCodeResetComplete": "PIN-koodi on nollattu.", + "Overview": "Yleiskatsaus", + "OptionWakeFromSleep": "Herätä lepotilasta", + "OptionUnairedEpisode": "Julkaisemattomat jaksot", + "OptionParentalRating": "Ikäraja", + "OptionNone": "Ei mitään", + "OptionIgnoreTranscodeByteRangeRequests": "Ohita transkoodauksen tavualuepyynnöt", + "OptionHlsSegmentedSubtitles": "HLS segmentoidut tekstitykset", + "OptionEnableExternalContentInSuggestions": "Ota käyttöön ulkoinen sisältö ehdotuksissa", + "OptionEmbedSubtitles": "Upota säiliöön", + "OptionForceRemoteSourceTranscoding": "Pakota etämedialähteiden transkoodaus (kuten Live-TV)", + "OneChannel": "Yksi kanava", + "Off": "Poissa päältä", + "Normal": "Normaali", + "None": "Ei mitään", + "NoSubtitles": "Ei mitään", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Seuraavat mediasijainnit tullaan poistamaan kirjastostasi:", + "MessageItemsAdded": "Kohteet lisätty.", + "MessageItemSaved": "Kohde tallennettu.", + "MessageEnablingOptionLongerScans": "Tämän vaihtoehdon käyttöönottaminen voi johtaa huomattavasti pidempiin kirjastojen skannauksiin.", + "MessageDownloadQueued": "Lataus asetettu jonoon.", + "MessageConfirmRevokeApiKey": "Haluatko varmasti perua tämän API-avaimen? Sovelluksen yhteys Jellyfin-palvelimeen lopetetaan äkillisesti.", + "MessageConfirmRemoveMediaLocation": "Haluatko varmasti poistaa tämän sijainnin?", + "MessageConfirmRecordingCancellation": "Peruuta tallennus?", + "MessageConfirmDeleteGuideProvider": "Haluatko varmasti poistaa tämän ohjelmaoppaan tarjoajan?", + "MediaInfoStreamTypeEmbeddedImage": "Upotettu kuva", + "MediaInfoSampleRate": "Näytteenottotaajuus", + "MediaInfoPixelFormat": "Pikseliformaatti", + "MediaInfoLayout": "Asettelu", + "MediaInfoContainer": "Säiliö", + "MediaInfoCodecTag": "Codec-tunniste", + "MediaInfoCodec": "Codec", + "MediaInfoBitrate": "Bitrate", + "Like": "Tykkää", + "LabelVideoBitrate": "Videon bitrate:", + "LabelWeb": "Web:", + "LabelVideoCodec": "Videon codec:", + "LabelTranscodingVideoCodec": "Video codec:", + "LabelSkipIfGraphicalSubsPresent": "Ohita, jos video sisältää upotetut tekstitykset", + "LabelInternetQuality": "Verkkoyhteyden laatu:", + "LabelEmbedAlbumArtDidl": "Upota albumin kuvamateriaali Didl:iin", + "LabelDeinterlaceMethod": "Lomituksen poistamismenetelmä:", + "LabelAudioBitDepth": "Audion bittisyvyys:", + "LabelAlbumArtists": "Albumin artistit:", + "Items": "Kohteet", + "ItemCount": "{0} kohdetta", + "Home": "Koti", + "Help": "Apua", + "HeaderXmlSettings": "Xml-asetukset", + "HeaderXmlDocumentAttributes": "Xml-dokumentin attribuutit", + "HeaderXmlDocumentAttribute": "Xml-dokumentin attribuutti", + "HeaderStopRecording": "Lopeta tallennus", + "HeaderRecordingPostProcessing": "Tallennuksen jälkikäsittely", + "HeaderParentalRatings": "Ikärajat", + "HeaderOtherItems": "Muut kohteet", + "HeaderLiveTvTunerSetup": "Live-TV virittimen määritys", + "HeaderLibrarySettings": "Kirjaston asetukset", + "HeaderItems": "Kohteet", + "HeaderGuideProviders": "TV-ohjelmaoppaiden tarjoajat", + "HeaderFavoritePlaylists": "Suosikki soittolistat", + "HeaderEnabledFields": "Käytössä olevat kentät", + "HeaderCodecProfile": "Codec-profiili", + "HeaderContainerProfile": "Säiliöprofiili", + "PleaseSelectTwoItems": "Valitse ainakin kaksi kohdetta.", + "PleaseRestartServerName": "Ole hyvä ja uudelleenkäynnistä Jellyfin-palvelin - {0}.", + "PleaseEnterNameOrId": "Anna nimi tai ulkoinen ID.", + "PreferEmbeddedTitlesOverFileNames": "Suosi upotettuja otsikoita tiedostonimien sijasta", + "PreferredNotRequired": "Suositeltava, mutta ei vaadittu", + "Premieres": "Ensiesitys", + "Primary": "Ensisijainen", + "Previous": "Edellinen", + "RecommendationBecauseYouWatched": "Koska katsoit {0}", + "RecommendationBecauseYouLike": "Koska pidät {0}", + "RecentlyWatched": "Äskettäin katsotut", + "Programs": "Ohjelmat", + "LabelServerNameHelp": "Tätä nimeä käytetään palvelimen tunnistamiseen. Jos tyhjä, käytetään tietokoneen nimeä.", + "LabelEnableRealtimeMonitorHelp": "Tiedostojen muutokset käsitellään välittömästi tuetuissa tiedostojärjestelmissä.", + "LabelEnableRealtimeMonitor": "Ota reaaliaikainen seuranta käyttöön", + "LabelDropImageHere": "Pudota kuva tähän, tai paina selataksesi.", + "LabelDroppedFrames": "Pudotettuja kuvia:", + "LabelDidlMode": "DIDL tila:", + "LabelCustomDeviceDisplayNameHelp": "Tätä nimeä käytetään laitteen tunnistamiseen. Jos tyhjä, käytetään laitteen nimeä.", + "LabelCustomCssHelp": "Käytä omaa CSS: ää web-käyttöliittymään.", + "LabelCorruptedFrames": "Korruptoituneita kuvia:", + "LabelCriticRating": "Kriitikoiden luokitus:", + "LabelCommunityRating": "Yhteisön luokitus:", + "LabelCancelled": "Peruttu", + "LabelBlockContentWithTags": "Estä kohteet tunnisteilla:", + "LabelBitrate": "Bitrate:", + "LabelAudioSampleRate": "Audion näytteenottotaajuus:", + "LabelAudioChannels": "Audiokanavia:", + "LabelAudioBitrate": "Audion bitrate:", + "LabelAudioCodec": "Audio codec:", + "LabelTranscodingContainer": "Säiliö:", + "LabelTranscodingAudioCodec": "Audio codec:", + "LabelSubtitleDownloaders": "Tekstitysten lataajat:", + "LabelSpecialSeasonsDisplayName": "Erikoiskauden näyttönimi:", + "LabelSoundEffects": "Ääniefektit:", + "LabelSortTitle": "Lajitteluotsikko:", + "LabelSkipIfAudioTrackPresent": "Ohita, jos oletusääniraita vastaa latauskieltä", + "LabelSkipBackLength": "Taaksepäin hyppäämisen pituus:", + "LabelSkipForwardLength": "Eteenpäin hyppäämisen pituus:", + "LabelScheduledTaskLastRan": "Viimeisin ajo {0}, kesto {1}.", + "LabelRemoteClientBitrateLimitHelp": "Valinnainen bittinopeusraja yksittäisille suoratoistaville laitteille lähiverkon ulkopuolella. Tämä on hyödyllistä estääkseen laitteita pyytämästä suurempia bittinopeuksia kuin Internet-yhteys voi käsitellä. Tämä voi johtaa palvelimen prosessorin kuormituksen lisääntymiseen, jotta videot voidaan transkoodata pienempiin bittinopeuksiin.", + "LabelRemoteClientBitrateLimit": "Internetistä suoratoiston enimmäisnopeus (Mbps):", + "LabelRefreshMode": "Päivitystila:", + "LabelRecordingPathHelp": "Määritä oletussijainti tallennuksille. Jätä tyhjäksi käyttääksesi palvelimen datakansiota.", + "LabelRecordingPath": "Oletustallennuspolku:", + "LabelProtocolInfoHelp": "Arvo, jota käytetään vastattaessa laitteen GetProtocolInfo-pyyntöihin.", + "LabelProfileCodecsHelp": "Pilkulla erotettuna. Jätä tyhjäksi käyttääksesi kaikissa codeceissa.", + "LabelProfileContainersHelp": "Pilkulla erotettuna. Jätä tyhjäksi käyttääksesi kaikissa säiliöissä.", + "LabelProfileVideoCodecs": "Video codecs:", + "LabelProfileContainer": "Säiliö:", + "LabelProfileCodecs": "Codecs:", + "LabelProfileAudioCodecs": "Audio codecs:", + "LabelPostProcessor": "Jälkikäsittelysovellus:", + "LabelPostProcessorArguments": "Jälkikäsittelysovelluksen komentoriviargumentit:", + "LabelPleaseRestart": "Muutokset tulevat voimaan kun web-asiakasohjelma päivitetään manuaalisesti (reload).", + "LabelPlayDefaultAudioTrack": "Toista oletusraita kielestä riippumatta", + "LabelOverview": "Yleiskatsaus:", + "LabelPasswordResetProvider": "Salasanan nollauksen palveluntarjoaja:", + "LabelParentalRating": "Ikäraja:", + "LabelOptionalNetworkPathHelp": "Jos tämä kansio on jaettu verkossasi, polun jakaminen voi mahdollistaa muilla laitteilla olevien Jellyfin-sovellusten pääsyn suoraan mediatiedostoihin. Esimerkiksi {0} tai {1}.", + "LabelMovieRecordingPath": "Elokuvien tallenteiden polku (valinnainen):", + "LabelMusicStreamingTranscodingBitrateHelp": "Määritä enimmäisnopeus musiikkia suoratoistettaessa.", + "LabelMusicStreamingTranscodingBitrate": "Musiikin transkoodauksen bitrate:", + "LabelMinBackdropDownloadWidth": "Taustan latauksen vähimmäisleveys:", + "LabelMetadataReadersHelp": "Järjestä ensisijaiset paikallisen metadatan lähteet prioriteettijärjestykseen. Ensimmäinen löydetty tiedosto luetaan.", + "LabelMetadataPathHelp": "Määrää mukautettu sijainti ladatulle kuvamateriaalille ja metadatalle.", + "LabelMetadataDownloadersHelp": "Ota käyttöön ja järjestä ensisijaiset metadatan lataajat prioriteettijärjestykseen. Alemman prioriteetin omaavia lataajia käytetään vain täyttämään puuttuvia tietoja.", + "LabelMaxResumePercentageHelp": "Kohteita pidetään toistettuina, jos toisto pysäytetään tämän ajan kuluttua.", + "LabelMaxResumePercentage": "Enimmäisaika jatkoa varten (%):", + "LabelMaxChromecastBitrate": "Chromecastin suoratoiston laatu:", + "LabelMaxBackdropsPerItem": "Taustojen maksimimäärä per kohde:", + "LabelLoginDisclaimer": "Sisäänkirjautumisen vastuuvapauslauseke:", + "LabelLibraryPageSize": "Kirjastosivun kohteiden määrä:", + "LabelLibraryPageSizeHelp": "Asettaa kirjastosivulla näytettävien kohteiden määrän. Arvo 0 poistaa sivutuksen käytöstä.", + "Unrated": "Luokittelematon", + "ExtractChapterImagesHelp": "Pikkukuvien luominen mahdollistaa sovellusten näyttää graafiikkaa valintavalikoissa. Prosessi voi olla hidas, prosessoria kuormittava ja saattaa vaatia useita gigatavuja tilaa. Se suoritetaan, kun videoita havaitaan, ja myös yöksi suunniteltuna tehtävänä. Aikataulu on konfiguroitavissa ajoitetuissa tehtävissä. Tätä tehtävää ei ole suositeltavaa suorittaa korkean kuormituksen aikana.", + "OnWakeFromSleep": "Lepotilasta poistuttaessa", + "WeeklyAt": "{0}sin klo {1}", + "Whitelist": "Sallitut", + "Watched": "Katsotut", + "ViewPlaybackInfo": "Näytä toistamisen tiedot", + "ValueVideoCodec": "Video Codec: {0}", + "ValueTimeLimitSingleHour": "Aikaraja: 1 tunti", + "ValueTimeLimitMultiHour": "Aikaraja: {0} tuntia", + "ValueContainer": "Säiliö: {0}", + "ValueConditions": "Ehdot: {0]", + "ValueCodec": "Codec: {0}", + "ValueAudioCodec": "Audio Codec: {0}", + "SeriesYearToPresent": "{0} - Nykyhetki", + "DeinterlaceMethodHelp": "Valitse lomituksen poistomenetelmä, jota käytetään lomitetun sisällön transkoodaukseen.", + "FormatValue": "Muoto: {0}", + "General": "Yleinen", + "FolderTypeUnset": "Sekalainen sisältö", + "FetchingData": "Noudetaan lisätietoja", + "Features": "Ominaisuudet", + "ErrorMessageStartHourGreaterThanEnd": "Loppumisajan on oltava myöhemmin kuin aloitusaika.", + "MediaInfoAnamorphic": "Anamorfinen", + "ErrorGettingTvLineups": "TV esiintyjälistan lataamisessa tapahtui virhe. Varmista, että tiedot on oikein ja yritä uudelleen.", + "EnableDetailsBannerHelp": "Näyttää julistekuvan yksityiskohdat -sivun ylälaidassa.", + "EnableDetailsBanner": "Yksityiskohtien banneri", + "ButtonSyncPlay": "SyncPlay", + "HeaderAccessSchedule": "Käyttöoikeusaikataulu", + "HeaderAccessScheduleHelp": "Luo käyttöoikeusaikataulu rajoittaaksesi käyttöä tietylle aikavälille.", + "HardwareAccelerationWarning": "Hardwarekiihdytyksen käyttöönotto voi aiheuttaa epävakautta joissain ympäristöissä. Varmista että sekä käyttöjärjestelmäsi että videoajurisi ovat ajan tasalla. Mikäli huomaat ongelmia videotoistossa säädettyäsi tätä asetusta, sinun täytyy muuttaa asetus takaisin kohtaan \"Ei mitään\".", + "EncoderPresetHelp": "Valitse nopeampi arvo kohentaaksesi suorituskykyä tai hitaampi arvo parantaaksesi kuvanlaatua.", + "H264CrfHelp": "Constant Rate Factor (CRF) on x264 -enkooderin kuvanlaadun vakioasetus. Voit valita arvon lukujen 0 ja 51 väliltä, jossa matalammat arvot tarkoittavat parempaa kuvanlaatua (suurempien tiedostokokojen hinnalla). Järkevät arvot ovat väliltä 18-28. Vakioarvo x264:lle on 23, joten voit käyttää sitä lähtökohtana.", + "GuideProviderSelectListings": "Valitse listaukset", + "GuideProviderLogin": "Kirjaudu", + "GuestStar": "Vieraileva tähti", + "GroupVersions": "Ryhmitä versiot", + "FFmpegSavePathNotFound": "Emme löytäneet FFmpegiä syöttämästäsi tiedostopolusta. Huomioi, että myös FFprobe vaaditaan ja sen täytyy sijaita samassa kansiossa. Nämä komponentit jaetaan normaalisti samassa paketissa. Varmista, että syöttämäsi polku on oikein ja yritä uudestaan.", + "ErrorSavingTvProvider": "TV-palveluntarjoajaa lisättäessä tapahtui virhe. Varmista sen saatavuus ja yritä uudestaan.", + "ErrorPleaseSelectLineup": "Valitse lineup ja yritä uudestaan. Mikäli lineuppeja ei ole saatavilla, varmista että käyttäjätunnuksesi, salasanasi sekä postinumerosi ovat oikein.", + "ErrorAddingListingsToSchedulesDirect": "Lineuppia Schedules Direct -käyttäjätunnuksellesi lisättäessä ilmeni virhe. Schedules Direct sallii vain rajallisen määrän lineuppeja yhdelle käyttäjätunnukselle. Mikäli haluat jatkaa, voit esimerkiksi kirjautua Schedules Direct -sivustolle ja poistaa muita listauksia käyttäjätunnukseltasi.", + "EnableDecodingColorDepth10Vp9": "Salli 10-bittinen hardware dekoodaus (VP9)", + "EnableDecodingColorDepth10Hevc": "Salli 10-bittinen hardware dekoodaus (HEVC)", + "HeaderCastCrew": "Näyttelijät ja henkilökunta", + "HeaderCastAndCrew": "Näyttelijät ja henkilökunta", + "HeaderCancelSeries": "Peruuta sarja", + "HeaderCancelRecording": "Peruuta tallennus", + "HeaderBranding": "Brändäys", + "HeaderBlockItemsWithNoRating": "Estä kaikki joissa ei ole luokitusta tai jonka luokitusta ei voida tunnistaa:", + "HeaderAppearsOn": "Esiintyy seuraavissa", + "ApiKeysCaption": "Lista aktiivisista API-avaimista", + "HeaderApiKeysHelp": "Ulkoiset sovellukset tarvitsevat API-avaimen voidakseen toimia Jellyfin -palvelimen kanssa. Avaimet myönnetään joko kirjautumalla sisään Jellyfin -käyttäjätunnuksella tai myöntämällä sellainen sovellukselle manuaalisesti.", + "HeaderAdditionalParts": "Muut osat", + "HeaderAddScheduledTaskTrigger": "Lisää laukaisin", + "HeaderActiveRecordings": "Käynnissä olevat nauhoitukset" } diff --git a/src/strings/fr-ca.json b/src/strings/fr-ca.json index 131be8a57f3..5f03a9d43f6 100644 --- a/src/strings/fr-ca.json +++ b/src/strings/fr-ca.json @@ -90,14 +90,14 @@ "Alerts": "Alertes", "All": "Tout", "AllChannels": "Toutes les chaînes", - "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB/IDX etc)", + "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB/IDX, ...)", "AllEpisodes": "Tous les épisodes", "AllLanguages": "Toutes les langues", "AllLibraries": "Toutes les médiathèques", "AllowMediaConversion": "Autoriser la conversion des médias", "AllowMediaConversionHelp": "Autoriser ou refuser l'accès à la fonctionnalité de conversion des médias.", "AllowOnTheFlySubtitleExtraction": "Autoriser l'extraction des sous-titres à la volée", - "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et distribués aux applications Jellyfin au format texte pour éviter le transcodage. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour graver les sous-titres avec un transcodage quand l'appareil ne les prend pas en charge nativement.", + "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et distribués aux clients en format texte pour éviter le transcodage. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour graver les sous-titres avec un transcodage quand l'appareil ne les prend pas en charge nativement.", "AllowRemoteAccess": "Autoriser les connexions distantes à ce serveur Jellyfin.", "AllowRemoteAccessHelp": "Si l'option est désactivée, toutes les connexions distantes seront bloquées.", "Artists": "Artistes", @@ -134,13 +134,13 @@ "BirthLocation": "Lieu de naissance", "BirthPlaceValue": "Lieu de naissance : {0}", "Blacklist": "Liste noire", - "BookLibraryHelp": "Les livres audios et numériques sont supportés. Consultez le {0}Guide de nommage pour livre de Jellyfin{1}.", + "BookLibraryHelp": "Les livres audios et formats textes sont supportés. Consultez le {0} Guide de nommage de livres {1}.", "Box": "Boîtier", "BoxRear": "Dos de boîtier", "Browse": "Parcourir", "BrowsePluginCatalogMessage": "Explorer notre catalogue des plugins pour voir les plugins disponibles.", - "AllowHWTranscodingHelp": "Si l'option est activée, permet au récepteur TV de transcoder les flux à la volée. Cela peut aider à réduire le transcodage requis par le serveur Jellyfin.", - "BurnSubtitlesHelp": "Détermine si le serveur doit graver les sous-titres lors de la conversion vidéo en fonction du format des sous-titres. Éviter la gravure des sous-titres améliorera les performances du serveur. Sélectionnez Auto pour graver les formats basés sur l'image (par exemple, VOBSUB, PGS, SUB/IDX etc) ainsi que certains sous-titres ASS/SSA", + "AllowHWTranscodingHelp": "Permets au syntonisateur de transcoder les flux à la volée. Cela peut aider à réduire le transcodage requis par le serveur.", + "BurnSubtitlesHelp": "Détermine si le serveur doit graver les sous-titres lors du transcodage vidéo. Éviter ceci améliorera les performances du serveur. Sélectionnez Auto pour graver les formats basés sur l'image (par exemple, VOBSUB, PGS, SUB/IDX etc) ainsi que certains sous-titres ASS/SSA.", "ButtonAccept": "Accepter", "ButtonAdd": "Ajouter", "ButtonAddMediaLibrary": "Ajouter une médiathèque", @@ -185,5 +185,81 @@ "ButtonManualLogin": "Connexion manuelle", "ButtonMenu": "Menu", "ButtonMore": "Plus", - "ButtonNetwork": "Réseau" + "ButtonNetwork": "Réseau", + "AspectRatio": "Format de visionnement", + "AskAdminToCreateLibrary": "Demander un administrateur de créer une médiathèque.", + "Artist": "Artiste", + "AllowFfmpegThrottlingHelp": "Quand un transcodage ou rémux se déplace après la position de relecture, suspendre le processus pour consommer moins de ressources. Ceci est le plus utile pour chercher moins. Désactiver s'il y a des problèmes de relecture.", + "AllowFfmpegThrottling": "Restreindre la vitesse de transcodage", + "AlbumArtist": "Artiste de l'Album", + "Album": "Album", + "AuthProviderHelp": "Sélectionner un fournisseur d'authentification pour authentifier le mot de passe de cet utilisateur.", + "HeaderFavoriteSongs": "Chansons favorites", + "HeaderFavoriteShows": "Séries favorites", + "HeaderFavoriteEpisodes": "Épisodes favoris", + "HeaderFavoriteArtists": "Artistes favoris", + "HeaderFavoriteAlbums": "Albums favoris", + "ButtonSyncPlay": "SyncPlay", + "Default": "Défaut", + "DeathDateValue": "Mort: {0}", + "DatePlayed": "Date écoutée", + "DateAdded": "Date d'ajout", + "CriticRating": "Évaluation critique", + "CopyStreamURLError": "Une erreur est survenue en essayant de copier l'URL.", + "CopyStreamURLSuccess": "L'URL a été copié avec succès.", + "CopyStreamURL": "Copier l'URL du stream", + "ContinueWatching": "Continuer à visionner", + "Connect": "Connexion", + "ConfirmEndPlayerSession": "Voulez-vous éteindre Jellyfin sur {0}?", + "ConfirmDeletion": "Confirmer la suppression", + "ConfirmDeleteItems": "Supprimer ceux-ci les effacera du système de fichiers et votre médiathèque. Êtes-vous sûr de vouloir continuer?", + "ConfirmDeleteImage": "Effacer l'image?", + "ClientSettings": "Paramètres du client", + "ChannelNumber": "Numéro de canal", + "ChannelNameOnly": "Canal {0} seulment", + "ChannelAccessHelp": "Sélectionner les canaux que vous désirer partager avec cet usager. Les administrateurs seront capable de modifier tout les canaux en utilisant le gestionnaire des métadonnées.", + "Categories": "Catégories", + "CancelSeries": "Annuler la série", + "CancelRecording": "Annuler l'enregistrement", + "ButtonWebsite": "Site web", + "ButtonViewWebsite": "Voir le site web", + "ButtonUp": "Vers le haut", + "ButtonUninstall": "Désinstaller", + "ButtonTogglePlaylist": "Liste de lecture", + "ButtonToggleContextMenu": "Plus", + "ButtonSubtitles": "Sous-titres", + "ButtonSubmit": "Soumettre", + "ButtonStop": "Arrêt", + "ButtonStart": "Démarrer", + "ButtonSort": "Trier", + "ButtonSignIn": "Se connecter", + "ButtonShutdown": "Éteindre", + "ButtonShuffle": "Lecture aléatoire", + "ButtonSettings": "Paramètres", + "ButtonSend": "Envoyer", + "ButtonSelectServer": "Sélectionner le serveur", + "ButtonSelectDirectory": "Sélectionner le répertoire", + "ButtonSearch": "Rechercher", + "ButtonScanAllLibraries": "Analyser toutes les médiathèques", + "ButtonSave": "Sauvegarder", + "ButtonRevoke": "Révoquer", + "ButtonResume": "Reprendre la lecture", + "ButtonResetPassword": "Réinitialiser le mot de passe", + "ButtonResetEasyPassword": "Remettre à nouveau le code NIP facile", + "ButtonRepeat": "Répéter", + "ButtonRename": "Renommer", + "ButtonRemove": "Enlever", + "ButtonRefreshGuideData": "Rafraîchir les données de guide", + "ButtonRefresh": "Rafraîchir", + "ButtonProfile": "Profil", + "ButtonPreviousTrack": "Piste précédente", + "ButtonPlay": "Lecture", + "ButtonPause": "Pause", + "ButtonParentalControl": "Contrôle parentale", + "ButtonOpen": "Ouvrir", + "ButtonOk": "OK", + "ButtonNextTrack": "Prochaine piste", + "ButtonNew": "Nouveau", + "ButtonAddImage": "Ajouter l'image", + "BoxSet": "Coffret" } diff --git a/src/strings/fr.json b/src/strings/fr.json index aab47271c03..d72b88a9429 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -3,18 +3,18 @@ "AccessRestrictedTryAgainLater": "L'accès est actuellement restreint. Veuillez réessayer plus tard.", "Actor": "Acteur(trice)", "Add": "Ajouter", - "AddItemToCollectionHelp": "Ajoutez des éléments à des collections en les recherchant et en utilisant leurs menus contextuels (clic droit ou appui long).", + "AddItemToCollectionHelp": "Ajoutez des éléments à des collections en les recherchant et en utilisant leurs menus contextuels (clic droit ou appui long) pour les ajouter à une collection.", "AddToCollection": "Ajouter à la collection", "AddToPlayQueue": "Ajouter à la file d'attente", "AddToPlaylist": "Ajouter à la liste de lecture", "AddedOnValue": "Ajouté le {0}", "AdditionalNotificationServices": "Visitez le catalogue d'extensions pour installer des services de notifications supplémentaires.", "AirDate": "Date de diffusion", - "Aired": "Diffusé", + "Aired": "Diffusé sur", "Alerts": "Alertes", "All": "Tout", "AllChannels": "Toutes les chaînes", - "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc…)", + "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", "AllEpisodes": "Tous les épisodes", "AllLanguages": "Toutes les langues", "AllLibraries": "Toutes les médiathèques", @@ -23,15 +23,15 @@ "AllowMediaConversion": "Autoriser la conversion des médias", "AllowMediaConversionHelp": "Autoriser ou refuser l'accès à la fonctionnalité de conversion des médias.", "AllowOnTheFlySubtitleExtraction": "Autoriser l'extraction des sous-titres à la volée", - "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et distribués aux clients au format texte pour éviter le transcodage. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour graver les sous-titres avec un transcodage quand l'appareil ne les prend pas en charge nativement.", + "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et envoyés vers les clients au format texte afin d'éviter le transcodage vidéo. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour conserver les sous-titres pendant le transcodage si l'appareil client ne les prend pas en charge nativement.", "AllowRemoteAccess": "Autoriser les connexions distantes à ce serveur Jellyfin.", "AllowRemoteAccessHelp": "Si l'option est désactivée, toutes les connexions distantes seront bloquées.", "AllowedRemoteAddressesHelp": "Liste d'adresses IP ou d'IP/masque de sous-réseau séparées par des virgules qui seront autorisées à se connecter à distance. Si la liste est vide, toutes les adresses distantes seront autorisées.", - "AlwaysPlaySubtitles": "Toujours lancer les sous-titres", + "AlwaysPlaySubtitles": "Toujours afficher les sous-titres", "AlwaysPlaySubtitlesHelp": "Les sous-titres correspondant à la préférence linguistique seront chargés indépendamment de la langue de l'audio.", "AnyLanguage": "N'importe quel langage", "Anytime": "N'importe quand", - "AroundTime": "Aux environs de {0}", + "AroundTime": "Aux environs de", "Artists": "Artistes", "AsManyAsPossible": "Autant que possible", "Ascending": "Croissant", @@ -45,13 +45,13 @@ "BirthLocation": "Lieu de naissance", "BirthPlaceValue": "Lieu de naissance : {0}", "Blacklist": "Liste noire", - "BookLibraryHelp": "Les livres audios et numériques sont supportés. Consultez le {0}Guide de nommage pour livre{1}.", + "BookLibraryHelp": "Les livres audios et numériques sont supportés. Consultez le {0} guide de nommage pour livre {1}.", "Books": "Livres", "Box": "Boîtier", "BoxRear": "Dos de boîtier", "Browse": "Parcourir", "BrowsePluginCatalogMessage": "Explorer notre catalogue des plugins pour voir les plugins disponibles.", - "BurnSubtitlesHelp": "Détermine si le serveur doit incruster les sous-titres lors de la conversion vidéo en fonction du format des sous-titres. Éviter l'incrustation des sous-titres améliorera les performances du serveur. Sélectionnez Auto pour incruster les formats basés sur l'image (VOBSUB, PGS, SUB/IDX etc) et certains sous-titres ASS/SSA.", + "BurnSubtitlesHelp": "Détermine si le serveur doit incruster les sous-titres lors du transcodage de la vidéo. Les performances seront grandement améliorées sans incrustation. Sélectionnez Auto pour incruster par image les formats (VOBSUB, PGS, SUB, IDX etc) et certains sous-titres ASS ou SSA.", "ButtonAdd": "Ajouter", "ButtonAddMediaLibrary": "Ajouter une médiathèque", "ButtonAddScheduledTaskTrigger": "Ajouter un déclencheur", @@ -100,7 +100,7 @@ "ButtonRemove": "Supprimer", "ButtonRename": "Renommer", "ButtonRepeat": "Répéter", - "ButtonResetEasyPassword": "Réinitialiser le code Easy PIN", + "ButtonResetEasyPassword": "Réinitialiser le code easy PIN", "ButtonResetPassword": "Réinitialiser le mot de passe", "ButtonRestart": "Redémarrer", "ButtonResume": "Reprendre", @@ -130,7 +130,7 @@ "CancelRecording": "Annuler l'enregistrement", "CancelSeries": "Annuler la série", "Categories": "Catégories", - "ChangingMetadataImageSettingsNewContent": "Les modifications des réglages de téléchargement des métadonnées et des visuels ne seront appliquées qu'au nouveau contenu ajouté à votre médiathèque. Pour appliquer ces changements aux titres existants, vous devrez actualiser leurs métadonnées manuellement.", + "ChangingMetadataImageSettingsNewContent": "Les modifications des paramètres de téléchargement des métadonnées et des illustrations ne seront appliquées qu'au contenu nouvellement ajouté à votre médiathèque. Pour appliquer ces changements aux titres pré-existants, vous devrez actualiser leurs métadonnées manuellement.", "ChannelAccessHelp": "Sélectionnez les chaînes à partager avec cet utilisateur. Les administrateurs pourront modifier toutes les chaînes en utilisant le gestionnaire de métadonnées.", "ChannelNameOnly": "Seulement la chaîne {0}", "ChannelNumber": "Numéro de chaîne", @@ -186,7 +186,7 @@ "DisplayInOtherHomeScreenSections": "Afficher dans les sections de l’écran d’accueil comme Ajouts récents et Reprendre", "DisplayMissingEpisodesWithinSeasons": "Afficher les épisodes manquants dans les saisons", "DisplayMissingEpisodesWithinSeasonsHelp": "Cette option doit aussi être activée pour les médiathèques TV dans les paramètres du serveur.", - "DisplayModeHelp": "Sélectionner le type d'écran sur lequel vous utilisez Jellyfin.", + "DisplayModeHelp": "Sélectionner l'agencement que vous désirez pour l'interface.", "DoNotRecord": "Ne pas enregistrer", "Down": "Bas", "Download": "Téléchargement", @@ -199,10 +199,10 @@ "EditMetadata": "Éditer les métadonnées", "EditSubtitles": "Modifier les sous-titres", "EnableBackdrops": "Images d'arrière-plans", - "EnableBackdropsHelp": "Afficher les images d'arrière-plans sur certaines pages pendant la navigation dans la médiathèque.", + "EnableBackdropsHelp": "Afficher des images d'arrière-plans sur certaines pages pendant la navigation dans la médiathèque.", "EnableCinemaMode": "Mode cinéma", "EnableColorCodedBackgrounds": "Fonds avec code couleur", - "EnableDisplayMirroring": "Partage d'écran", + "EnableDisplayMirroring": "Affichage en miroir", "EnableExternalVideoPlayers": "Lecteurs vidéo externes", "EnableExternalVideoPlayersHelp": "Une liste des lecteurs externes sera affichée au lancement de la lecture d'une vidéo.", "EnableHardwareEncoding": "Activer l'encodage matériel", @@ -231,7 +231,7 @@ "EveryNDays": "Tous les {0} jours", "ExitFullscreen": "Sortir du plein écran", "ExtraLarge": "Très Grand", - "ExtractChapterImagesHelp": "L'extraction d'images de chapitre permettra aux applications d'afficher des menus visuels pour la sélection des scènes. Le processus peut être long, consommateur de ressources et peut nécessiter de nombreux gigaoctets de stockage. Il s'exécute lorsque des vidéos sont découvertes et également comme tâche planifiée. La planification peut être modifiée dans les options du planificateur de tâches. Il n'est pas conseillé d'exécuter cette tâche pendant les heures d'usage intensif.", + "ExtractChapterImagesHelp": "L'extraction d'images de chapitre permettra aux applications d'afficher des menus visuels pour la sélection des scènes. Le processus peut être lent, gourmand en ressources et peut nécessiter plusieurs gigaoctets de stockage. Il s'exécute lorsque des vidéos sont découvertes et également comme tâche planifiée. L'horaire est configurable dans les options du planificateur de tâches. Il n'est pas recommandé d'exécuter cette tâche pendant les heures d'usage intensif.", "FFmpegSavePathNotFound": "Nous ne pouvons pas localiser FFmpeg en utilisant le chemin que vous avez saisi. FFprobe est également nécessaire et doit exister dans le même dossier. Ces composants sont généralement regroupés dans le même téléchargement. Veuillez vérifier le chemin et essayer à nouveau.", "FastForward": "Avance rapide", "Favorite": "Favori", @@ -274,7 +274,7 @@ "HeaderAddUser": "Ajouter un utilisateur", "HeaderAdditionalParts": "Parties additionelles", "HeaderAdmin": "Administrateur", - "HeaderAlbumArtists": "Artistes d'album", + "HeaderAlbumArtists": "Artistes", "HeaderAlert": "Alerte", "HeaderAllowMediaDeletionFrom": "Autoriser la suppression de médias à partir de", "HeaderApiKey": "Clé API", @@ -476,7 +476,7 @@ "Identify": "Identifier", "ImportFavoriteChannelsHelp": "Activez cette option pour n'importer que les chaînes ajoutées aux favoris sur le tuner.", "ImportMissingEpisodesHelp": "Les informations à propos des épisodes manquants seront importées dans votre base de donnée Jellyfin et affichées dans les saisons et séries. Cela peut accroître significativement la durée d'actualisation de la médiathèque.", - "InstallingPackage": "Installation de {0}", + "InstallingPackage": "Installation de {0} (version {1})", "InstantMix": "Mix instantané", "ItemCount": "{0} éléments", "Items": "Éléments", @@ -569,7 +569,7 @@ "LabelEmbedAlbumArtDidl": "Intégrer les images d'album dans le DIDL", "LabelEmbedAlbumArtDidlHelp": "Certains appareils préfèrent cette méthode pour obtenir les images d'album. D'autres peuvent échouer à lire avec cette option activée.", "LabelEnableAutomaticPortMap": "Autoriser le mapping automatique de port", - "LabelEnableAutomaticPortMapHelp": "Essayer de mapper automatiquement le port public au port local via UPnP. Cela peut ne pas fonctionner avec certains modèles de routeurs. La modification de ce paramètre ne sera effective qu’après redémarrage du serveur.", + "LabelEnableAutomaticPortMapHelp": "Mapper automatiquement les ports publics vers des ports locaux via UPnP. Cela peut ne pas fonctionner avec certains modèles de routeurs. La modification de ce paramètre ne prendra effet qu'après redémarrage du serveur.", "LabelEnableBlastAliveMessages": "Diffuser des message de présence", "LabelEnableBlastAliveMessagesHelp": "Activer cette option si le serveur n'est pas détecté de manière fiable par les autres appareils UPnP sur votre réseau.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalle de découverte des clients (secondes)", @@ -624,7 +624,7 @@ "LabelKidsCategories": "Catégories jeunesse :", "LabelKodiMetadataDateFormat": "Format de la date de sortie :", "LabelKodiMetadataDateFormatHelp": "Toutes les dates des fichiers NFO seront anlysés en utilisant ce format.", - "LabelKodiMetadataEnableExtraThumbs": "Copier les extrafanart vers le champ extrathumbs", + "LabelKodiMetadataEnableExtraThumbs": "Copier les extrafanart dans les champs extrathumbs", "LabelKodiMetadataEnableExtraThumbsHelp": "Pendant le téléchargement, les images peuvent être enregistrées en tant qu'extrafanart et extrathumbs pour améliorer la compatibilité avec le skin Kodi.", "LabelKodiMetadataEnablePathSubstitution": "Activer la substitution des chemins", "LabelKodiMetadataEnablePathSubstitutionHelp": "Active la substitution du chemin des images en utilisant les paramètres de substitution des chemins du serveur.", @@ -658,7 +658,7 @@ "LabelMetadataDownloadLanguage": "Langue de téléchargement préférée :", "LabelMetadataDownloadersHelp": "Activez et classez vos sources de téléchargement de métadonnées préférées dans l'ordre de priorité. Les plus basses seront utilisées uniquement pour remplir les informations manquantes.", "LabelMetadataPath": "Chemin des métadonnées :", - "LabelMetadataPathHelp": "Veuillez spécifier un emplacement personnalisé pour les images et les métadonnées téléchargées.", + "LabelMetadataPathHelp": "Veuillez spécifier un emplacement personnalisé pour les illustrations et les métadonnées téléchargées.", "LabelMetadataReaders": "Lecteurs de métadonnées :", "LabelMetadataReadersHelp": "Classez vos sources locales de métadonnées préférées dans l'ordre de priorité. Le premier fichier trouvé sera lu.", "LabelMetadataSavers": "Enregistreurs de métadonnées :", @@ -692,7 +692,7 @@ "LabelNumberOfGuideDays": "Nombre de jours de données du guide à télécharger :", "LabelNumberOfGuideDaysHelp": "Télécharger plus de journées du guide permet de programmer des enregistrements plus longtemps à l'avance et de visualiser plus de contenus, mais prendra également plus de temps. Automatique permettra une sélection automatique basée sur le nombre de chaînes.", "LabelOptionalNetworkPath": "(Optionnel) Dossier réseau partagé :", - "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner accès au chemin du dossier réseau peut autoriser les applications Jellyfin sur d'autres appareils à avoir accès à ses fichiers directement.", + "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner le chemin d'accès au dossier réseau peut permettre aux applications Jellyfin sur d'autres appareils d'avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", "LabelOriginalAspectRatio": "Ratio d'aspect original :", "LabelOriginalTitle": "Titre original :", "LabelOverview": "Synopsis :", @@ -737,8 +737,8 @@ "LabelRemoteClientBitrateLimit": "Limite de débit de streaming Internet (Mbps) :", "LabelRemoteClientBitrateLimitHelp": "Une limite de débit optionnelle par streaming pour les connexions hors du réseau local. Utile pour éviter que les appareils ne demandent un débit supérieur à ce que votre connexion internet peu fournir. Cela peut augmenter la charge du processeur de votre serveur pour transcoder les vidéos à la volée à un débit plus faible.", "LabelRuntimeMinutes": "Durée (minutes) :", - "LabelSaveLocalMetadata": "Enregistrer les images dans les dossiers multimédia", - "LabelSaveLocalMetadataHelp": "L'enregistrement des images dans les dossiers multimédia les placera à un endroit où elles seront facilement modifiables.", + "LabelSaveLocalMetadata": "Enregistrer les illustrations dans les dossiers des médias", + "LabelSaveLocalMetadataHelp": "L'enregistrement des illustrations dans les dossiers des médias les placera à un endroit où elles seront facilement modifiables.", "LabelScheduledTaskLastRan": "Dernière exécution {0}, durée {1}.", "LabelScreensaver": "Économiseur d'écran :", "LabelSeasonNumber": "Numéro de saison :", @@ -896,7 +896,7 @@ "MessageNoAvailablePlugins": "Aucune extension disponible.", "MessageNoMovieSuggestionsAvailable": "Aucune suggestion de film n'est actuellement disponible. Commencez à regarder et à noter vos films pour avoir des suggestions.", "MessageNoPluginsInstalled": "Vous n'avez aucune extension installée.", - "MessageNoTrailersFound": "Aucune bande-annonce trouvée. Installez la chaîne Trailers pour améliorer votre expérience, par l'ajout d'une médiathèque de bandes-annonces disponibles sur Internet.", + "MessageNoTrailersFound": "Installez la chaîne Trailers pour améliorer votre expérience cinéma, par l'ajout d'une médiathèque de bandes-annonces disponibles sur Internet.", "MessageNothingHere": "Il n'y a rien ici.", "MessagePasswordResetForUsers": "Les mot de passes de ces utilisateurs ont été réinitialisés. Ils peuvent maintenant se connecter avec le code PIN utilisé pour la réinitialisation.", "MessagePlayAccessRestricted": "La lecture de ce contenu est actuellement restreinte. Contactez l'administrateur de votre serveur pour plus d'informations.", @@ -948,7 +948,7 @@ "OneChannel": "Une chaîne", "OnlyForcedSubtitles": "Seulement les sous-titres forcés", "OnlyForcedSubtitlesHelp": "Seuls les sous-titres marqués comme forcés seront chargés.", - "OnlyImageFormats": "Seulement les formats image (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Seulement les formats image (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administrateurs", "OptionAlbumArtist": "Artiste de l'album", "OptionAllUsers": "Tous les utilisateurs", @@ -1038,7 +1038,7 @@ "OptionMissingEpisode": "Épisodes manquantes", "OptionMonday": "Lundi", "OptionNameSort": "Nom", - "OptionNew": "Nouveau...", + "OptionNew": "Nouveau…", "OptionNone": "Aucun", "OptionOnAppStartup": "Au démarrage de l'application", "OptionOnInterval": "Par intervalle", @@ -1058,7 +1058,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Nécessaire pour certains appareils qui ne sont pas capables d'effectuer une recherche dans le temps correctement.", "OptionRequirePerfectSubtitleMatch": "Télécharger uniquement les sous-titres qui correspondent parfaitement à mes fichiers vidéo", "OptionRequirePerfectSubtitleMatchHelp": "En activant cette option, seuls les sous-titres ayant été testés et vérifiés avec votre fichier vidéo seront téléchargés. En désactivant cette option, vous aurez plus de chance que des sous-titres soient téléchargés, mais ils risquent d'être décalés ou incorrects.", - "OptionResElement": "res element", + "OptionResElement": "Résolution d'élément", "OptionResumable": "Reprise possible", "OptionRuntime": "Durée", "OptionSaturday": "Samedi", @@ -1080,9 +1080,9 @@ "OptionWeekly": "Hebdomadaire", "OriginalAirDateValue": "Date de diffusion originale : {0}", "Overview": "Synopsis", - "PackageInstallCancelled": "L'installation de {0} a été annulée.", - "PackageInstallCompleted": "L'installation de {0} est terminée.", - "PackageInstallFailed": "L'installation de {0} a échoué.", + "PackageInstallCancelled": "L'installation de {0} (version {1}) a été annulée.", + "PackageInstallCompleted": "L'installation de {0} (version {1}) est terminée.", + "PackageInstallFailed": "L'installation de {0} (version {1}) a échoué.", "ParentalRating": "Classification parentale", "PasswordMatchError": "Le mot de passe et sa confirmation doivent correspondre.", "PasswordResetComplete": "Le mot de passe a été réinitialisé.", @@ -1120,7 +1120,7 @@ "Programs": "Programmes", "Quality": "Qualité", "QueueAllFromHere": "Tout mettre en file d'attente à partir d'ici", - "Raised": "Augmenter", + "Raised": "Élevé", "Rate": "Débit", "RecentlyWatched": "Lu récemment", "RecommendationBecauseYouLike": "Parce que vous aimez {0}", @@ -1149,7 +1149,6 @@ "ReplaceAllMetadata": "Remplacer toutes les métadonnées", "ReplaceExistingImages": "Remplacer les images existantes", "RequiredForAllRemoteConnections": "Obligatoire pour toutes les connexions externes", - "RestartPleaseWaitMessage": "Veuillez patienter pendant que le serveur Jellyfin s'arrête et redémarre. Cela peut prendre une minute ou deux.", "ResumeAt": "Reprendre à {0}", "Rewind": "Rembobiner", "RunAtStartup": "Exécuter au démarrage", @@ -1164,7 +1163,7 @@ "Screenshot": "Capture d'écran", "Screenshots": "Captures d'écran", "Search": "Recherche", - "SearchForCollectionInternetMetadata": "Rechercher sur Internet les images et les métadonnées", + "SearchForCollectionInternetMetadata": "Rechercher les illustrations et les métadonnées sur Internet", "SearchForMissingMetadata": "Rechercher les métadonnées manquantes", "SearchForSubtitles": "Rechercher des sous-titres", "SearchResults": "Résultats de la recherche", @@ -1386,7 +1385,6 @@ "HeaderFavoriteArtists": "Artistes préférés", "HeaderFavoriteSongs": "Chansons préférées", "HeaderFavoriteVideos": "Vidéos préférées", - "HeaderRestartingServer": "Redémarrage du serveur", "LabelServerName": "Nom du serveur :", "DashboardVersionNumber": "Version : {0}", "DashboardServerName": "Serveur : {0}", @@ -1399,7 +1397,7 @@ "AuthProviderHelp": "Sélectionner un fournisseur d'authentification pour authentifier le mot de passe de cet utilisateur.", "PasswordResetProviderHelp": "Choisissez un Fournisseur de réinitialisation de mot de passe à utiliser lorsqu'un utilisateur demande la réinitialisation de son mot de passe", "HeaderHome": "Accueil", - "LabelUserLoginAttemptsBeforeLockout": "Essais manqués restants avant verrouillage utilisateur :", + "LabelUserLoginAttemptsBeforeLockout": "Tentatives de connexion échouées avant que l'utilisateur ne soit verrouillé :", "DashboardOperatingSystem": "Système d'Exploitation: {0}", "DashboardArchitecture": "Architecture : {0}", "LaunchWebAppOnStartup": "Démarrer l'interface web dans mon navigateur quand le serveur est démarré", @@ -1409,7 +1407,7 @@ "MessageNoServersAvailable": "Aucun serveur n'a été trouvé en utilisant la recherche automatique de serveur.", "OptionBanner": "Bannière", "OptionList": "Liste", - "OptionLoginAttemptsBeforeLockout": "Détermine le nombre de tentative de connexion infructueuse avant le blocage d'accès.", + "OptionLoginAttemptsBeforeLockout": "Définis le nombre de tentatives de connexion échouées avant blocage du compte.", "OptionPoster": "Affiche", "OptionPosterCard": "Affichette", "OptionThumb": "Vignette", @@ -1437,18 +1435,18 @@ "LabelVideoBitrate": "Débit vidéo :", "LabelTranscodingProgress": "Progression du transcodage :", "LabelTranscodingFramerate": "Taux de rafraîchissement du transcodage :", - "LabelPleaseRestart": "Les changements prendront effet lors d'un rechargement manuel du client web.", + "LabelPleaseRestart": "Les changements prendront effet après un rechargement manuel du client web.", "LabelPlayMethod": "Méthode de lecture :", "LabelPlayer": "Lecteur :", - "LabelBaseUrl": "Lien d'origine :", + "LabelBaseUrl": "URL de base :", "LabelAudioSampleRate": "Taux d’échantillonnage audio :", "LabelAudioCodec": "Codec audio :", "LabelAudioChannels": "Canaux audio :", "HeaderFavoriteBooks": "Livres préférés", "FetchingData": "Récuperer des données suplémentaires", - "CopyStreamURLSuccess": "URL copiée.", + "CopyStreamURLSuccess": "URL copiée avec succès.", "CopyStreamURL": "Copier l'URL du flux", - "LabelBaseUrlHelp": "Vous pouvez ajouter un sous-répertoire personalisé ici pour accéder au serveur depuis une URL plus exclusive.", + "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple : http://exemple.com/<baseurl>", "HeaderFavoritePeople": "Personnes préférées", "OptionRandom": "Aléatoire", "ButtonSplit": "Séparer", @@ -1458,17 +1456,122 @@ "MessageConfirmAppExit": "Voulez-vous quitter ?", "LabelVideoResolution": "Résolution vidéo :", "LabelStreamType": "Type de flux :", - "EnableFastImageFadeInHelp": "Activer un transition plus rapide pour images téléchargées", - "EnableFastImageFadeIn": "Transition d'image rapide", - "LabelPlayerDimensions": "Dimension du lecteur:", - "LabelDroppedFrames": "Images perdues:", - "LabelCorruptedFrames": "Images corrompues:", - "CopyStreamURLError": "Il y a eu une erreur lors de la copie du URL.", + "LabelPlayerDimensions": "Dimension du lecteur :", + "LabelDroppedFrames": "Images perdues :", + "LabelCorruptedFrames": "Images corrompues :", + "CopyStreamURLError": "Une erreur est survenue lors de la copie de l'URL.", "AskAdminToCreateLibrary": "Demander à un administrateur de créer une médiathèque.", - "AllowFfmpegThrottlingHelp": "Quand le transcodage ou le remultiplexage est suffisamment loin de la position de lecture, le processus se mettra en pause afin d’économiser des ressources. Plus utile lors d’une lecture continue. À désactiver en cas de problèmes de lecture.", - "AllowFfmpegThrottling": "Adapter la Vitesse du Transcodage", - "NoCreatedLibraries": "Il semblerait que vous n'ayez créé aucune librairie. {0}Voulez-vous en créer une maintenant ?{1}", - "PlaybackErrorNoCompatibleStream": "Problème de profil client, le serveur n’a pas pu envoyer un format média compatible.", + "AllowFfmpegThrottlingHelp": "Quand le transcodage ou le remultiplexage est suffisamment en avant de la position de lecture, le processus se mettra en pause afin d’économiser des ressources. Plus utile lors d’une lecture continue. À désactiver en cas de problèmes de lecture.", + "AllowFfmpegThrottling": "Adapter la vitesse du transcodage", + "NoCreatedLibraries": "Il semble que vous n'ayez pas encore créé de bibliothèques. {0}Voulez-vous en créer une maintenant ?{1}", + "PlaybackErrorNoCompatibleStream": "Ce client n'est pas compatible avec le média et le serveur n'envoie pas de format compatible.", "PreferEmbeddedEpisodeInfosOverFileNames": "Préférer les informations intégrées aux noms de fichiers", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Utilise les informations des métadonnées intégrées, si disponible." + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Utilise les informations des métadonnées intégrées, si disponible.", + "ClientSettings": "Paramètres Client", + "Track": "Piste", + "Season": "Saison", + "ReleaseGroup": "Groupe de Parution", + "Person": "Personne", + "OtherArtist": "Autre Artiste", + "Movie": "Film", + "Episode": "Épisode", + "BoxSet": "Coffret", + "Artist": "Artiste", + "AlbumArtist": "Artiste de l'Album", + "Album": "Album", + "OnApplicationStartup": "Au démarrage de l'application", + "EveryXHours": "Toutes les {0} heures", + "EveryHour": "Toutes les heures", + "EveryXMinutes": "Toutes les {0} minutes", + "OnWakeFromSleep": "À la sortie de veille", + "WeeklyAt": "{0} à {1}", + "DailyAt": "Tous les jours à {0}", + "LastSeen": "Vu pour la dernière fois {0}", + "PersonRole": "en tant que {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "Le serveur Jellyfin a besoin d'un accès en écriture à ce dossier. Merci de vérifier l’accès en écriture et réessayez.", + "PathNotFound": "Le chemin d'accès n'a pas pu être trouvé. Merci de le vérifier et de réessayer.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "LabelDeinterlaceMethod": "Méthode de désentrelacement :", + "DeinterlaceMethodHelp": "Sélectionnes la méthode de désentrelacement à utiliser lors du transcodage de contenu entrelacé.", + "LabelLibraryPageSize": "Taille de la page de la médiathèque :", + "LabelLibraryPageSizeHelp": "Définit la quantité d'éléments à afficher sur une page de médiathèque. Définir à 0 afin de désactiver la pagination.", + "UnsupportedPlayback": "Jellyfin ne peut pas décoder du contenu protégé par un système de gestion des droits numériques, mais une tentative de lecture sera effectuée sur tout le contenu, y compris les titres protégés. Certains fichiers peuvent apparaître complètement noir, du fait de protections ou de fonctionnalités non supportées, comme les titres interactifs.", + "MessageUnauthorizedUser": "Vous n'êtes pas autorisé à accéder au serveur pour le moment. Veuillez contacter l'administrateur de votre serveur pour plus d'informations.", + "ButtonTogglePlaylist": "Liste de lecture", + "ButtonToggleContextMenu": "Plus", + "Filter": "Filtre", + "New": "Nouveau", + "HeaderFavoritePlaylists": "Listes de lecture favorites", + "TabDVR": "DVR", + "LabelChromecastVersion": "Version de Chromecast", + "LabelEnableHttpsHelp": "Autorise le serveur à écouter les requêtes HTTPS sur le port configuré. Un certificat valide doit être configuré pour permettre ce mode de fonctionnement.", + "LabelEnableHttps": "Activer HTTPS", + "HeaderServerAddressSettings": "Paramètres adresses serveur", + "HeaderRemoteAccessSettings": "Paramètres d'accès distant", + "HeaderHttpsSettings": "Paramètres HTTPS", + "HeaderDVR": "Enregistreur vidéo numérique", + "ApiKeysCaption": "Liste actuelle des clés API actives", + "SaveChanges": "Enregistrer les modifications", + "LabelRequireHttpsHelp": "Si activé, le serveur va automatiquement rediriger toutes les requêtes en HTTP vers HTTPS. Cette option n'a aucun effet si le serveur n'écoute pas HTTPS.", + "LabelRequireHttps": "Nécessite HTTPS", + "LabelNightly": "De nuit", + "LabelStable": "Stable", + "EnableDetailsBanner": "Bannière des détails", + "EnableDetailsBannerHelp": "Affichez une image de bannière en haut de la page de détails de l'article.", + "HeaderSyncPlaySelectGroup": "Rejoindre un groupe", + "LabelSyncPlayAccessCreateAndJoinGroups": "Autoriser l'utilisateur à créer ou rejoindre un groupe", + "LabelSyncPlayLeaveGroupDescription": "Désactiver SyncPlay", + "LabelSyncPlayLeaveGroup": "Quitter le groupe", + "LabelSyncPlayNewGroupDescription": "Créer un nouveau groupe", + "LabelSyncPlayNewGroup": "Nouveau groupe", + "LabelSyncPlaySyncMethod": "Méthode de synchronisation :", + "LabelSyncPlayPlaybackDiff": "Décalage de la lecture :", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Décalage de temps avec le serveur :", + "HeaderSyncPlayEnabled": "SyncPlay activé", + "MessageSyncPlayLibraryAccessDenied": "L'accès à ce contenu est restreint.", + "MessageSyncPlayJoinGroupDenied": "Permission requise pour utiliser SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permission requise pour créer un groupe.", + "MessageSyncPlayGroupDoesNotExist": "Impossible de rejoindre le groupe car il n'existe pas.", + "MessageSyncPlayPlaybackPermissionRequired": "Autorisation de lecture requise.", + "MessageSyncPlayNoGroupsAvailable": "Aucun groupe disponible. Commencez par lire quelque chose.", + "MessageSyncPlayGroupWait": "{0} est en train de charger...", + "MessageSyncPlayUserLeft": "{0} a quitté le groupe.", + "MessageSyncPlayUserJoined": "{0} a rejoint le groupe.", + "MessageSyncPlayDisabled": "SyncPlay désactivé.", + "MessageSyncPlayEnabled": "SyncPlay activé.", + "LabelSyncPlayAccess": "Accès SyncPlay", + "LabelSyncPlayAccessNone": "Désactivé pour cet utilisateur", + "LabelSyncPlayAccessJoinGroups": "Autoriser l'utilisateur à rejoindre un groupe", + "SyncPlayAccessHelp": "Sélectionner le niveau d'accès de cet utilisateur pour la fonctionnalité SyncPlay. SyncPlay permet de synchroniser la lecture avec d'autres appareils.", + "MessageSyncPlayErrorMedia": "Impossible d'activer SyncPlay ! Erreur média.", + "MessageSyncPlayErrorMissingSession": "Impossible d'activer SyncPlay ! Session manquante.", + "MessageSyncPlayErrorNoActivePlayer": "Aucun lecteur actif trouvé. SyncPlay a été désactivé.", + "MessageSyncPlayErrorAccessingGroups": "Une erreur s'est produite pendant l'accès à la liste de groupes.", + "ShowMore": "Voir plus", + "ShowLess": "Voir moins", + "EnableBlurhashHelp": "Les images qui sont encore en cours de chargement seront remplacées par une image générique floue", + "EnableBlurhash": "Utilise des images génériques floues à la place des images", + "ButtonCast": "Diffuser", + "ButtonSyncPlay": "SyncPlay", + "TabRepositories": "Dépôts", + "MessageNoGenresAvailable": "Utiliser des fournisseurs de métadonnées pour récupérer les genres depuis internet.", + "MessageAddRepository": "Si vous souhaitez ajouter un dépôt, cliquez sur le bouton près de l'entête et renseignez les informations demandées.", + "LabelRepositoryNameHelp": "Un nom personnalisé pour distinguer ce dépôt des autres ajoutés sur votre serveur.", + "LabelRepositoryName": "Nom du dépôt", + "LabelRepositoryUrlHelp": "La localisation du manifeste du dépôt que vous voulez inclure.", + "LabelRepositoryUrl": "URL du dépôt", + "HeaderNewRepository": "Nouveau dépôt", + "MessageNoRepositories": "Pas de dépôts.", + "EnableFasterAnimationsHelp": "Utiliser des animations et des transitions plus rapides", + "EnableFasterAnimations": "Animations plus rapides", + "EnableDecodingColorDepth10Vp9": "Activer le décodage hardware 10-Bit pour VP9", + "EnableDecodingColorDepth10Hevc": "Activer le décodage hardware 10-Bit pour HEVC", + "ClearQueue": "Vider la file d'attente", + "StopPlayback": "Arrêter", + "ButtonPlayer": "Démarrer", + "Writers": "Écrivains", + "ViewAlbumArtist": "Voir l'album de l'artiste" } diff --git a/src/strings/gl.json b/src/strings/gl.json index 5341462de70..e87ce467703 100644 --- a/src/strings/gl.json +++ b/src/strings/gl.json @@ -11,5 +11,6 @@ "AirDate": "Data de emisión", "Aired": "Emitido", "AddToPlaylist": "Engadir á lista de reprodución", - "Add": "Engadir" + "Add": "Engadir", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/gsw.json b/src/strings/gsw.json index a154e4fc8e6..803b707f610 100644 --- a/src/strings/gsw.json +++ b/src/strings/gsw.json @@ -7,7 +7,7 @@ "ButtonQuickStartGuide": "Schnellstart Instruktione", "ButtonResetPassword": "Passwort zrug setze", "ButtonSave": "Speichere", - "ButtonSignOut": "Sign out", + "ButtonSignOut": "Uslogge", "ButtonSort": "Sortiere", "ChannelAccessHelp": "Wähl en Kanal us, um de mit dem User z'teile. Administratore werded immer d'Möglichkeit ha alli Kanäl mitm Metadate Manager z'bearbeite.", "Continuing": "Fortlaufend", @@ -29,7 +29,7 @@ "HeaderLatestSongs": "Letschti Songs", "HeaderLatestTrailers": "Letschti Trailer", "HeaderManagement": "Verwaltig", - "HeaderNextUp": "Als nächts", + "HeaderNextUp": "Als Nächstes", "HeaderNowPlaying": "Jetz am spelle", "HeaderParentalRating": "Parental Rating", "HeaderPaths": "Pfad", @@ -131,28 +131,26 @@ "Wednesday": "Mittwoch", "WelcomeToProject": "Willkomme bi Jellyfin!", "WizardCompleted": "Das esch alles wo mer momentan müend wüsse. Jellyfin het i de zwüscheziit agfange informatione über diini medie-bibliothek z'sammle. Lueg der es paar vo eusne Apps a und denn klick uf Beende um zum Server Dashboard z'cho.", - "Albums": "Albom", - "Artists": "Könstler", - "Books": "Büecher", - "Channels": "Kanäu", - "Collections": "Sammlige", - "Favorites": "Favorite", + "Albums": "Alben", + "Artists": "Künstler", + "Books": "Bücher", + "Channels": "Kanäle", + "Collections": "Sammlungen", + "Favorites": "Favoriten", "Folders": "Ordner", "Genres": "Genres", - "HeaderAlbumArtists": "Albom-Könstler", - "HeaderContinueWatching": "Wiiterluege", - "HeaderFavoriteAlbums": "Lieblingsalbe", - "HeaderFavoriteArtists": "Lieblings-Interprete", - "HeaderFavoriteEpisodes": "Lieblingsepisode", - "HeaderFavoriteShows": "Lieblingsserie", + "HeaderAlbumArtists": "Album-Künstler", + "HeaderContinueWatching": "weiter schauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblings-Künstler", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-Färnseh", + "HeaderLiveTV": "Live-Fernseh", "HeaderRecordingGroups": "Ufnahmegruppe", - "LabelIpAddressValue": "IP-Adrässe: {0}", "LabelRunningTimeValue": "Loufziit: {0}", "MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde", "MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde", - "MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde", "Movies": "Film", "Photos": "Fotis", "Playlists": "Wedergabeliste", @@ -167,5 +165,6 @@ "VersionNumber": "Version {0}", "Absolute": "Absolut", "Actor": "Schauspiler", - "AccessRestrictedTryAgainLater": "Zuegriff isch momentan beschränkt. Probier bitte später nomau. " + "AccessRestrictedTryAgainLater": "Zuegriff isch momentan beschränkt. Probier bitte später nomau. ", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/he.json b/src/strings/he.json index 381b9bc1816..31bc228bb2f 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -1,14 +1,14 @@ { "Actor": "שחקן", "Add": "הוסף", - "AddToCollection": "להוסיף לאוסף", - "AddToPlayQueue": "הוסף לתור הפעלה", + "AddToCollection": "הוסף לאוסף", + "AddToPlayQueue": "הוסף לרשימת ניגון", "AddToPlaylist": "הוסף לרשימת ניגון", - "AdditionalNotificationServices": "עיין בקטלוג התוספים להתקנת שרותי התראות נוספים", + "AdditionalNotificationServices": "עיין ברשימת התוספים להתקנת שירותי התראות נוספים.", "All": "הכל", "AllChannels": "כל הערוצים", "AllEpisodes": "כל הפרקים", - "AllLibraries": "כל הספרייה", + "AllLibraries": "כל הספריות", "Anytime": "בכל עת", "AroundTime": "בסביבות {0}", "AsManyAsPossible": "כמה שיותר", @@ -52,7 +52,7 @@ "ConfirmDeleteItems": "מחיקת פריטים אלה תמחק אותם הן ממערכת הקבצים והן מספריית המדיה שלך. האם אתה בטוח שברצונך להמשיך?", "ConfirmDeletion": "אשר מחיקה", "Continuing": "ממשיך", - "CustomDlnaProfilesHelp": "צור פרופיל מותאם אישית למכשיר חדש או לעקוף פרופיל מערכת", + "CustomDlnaProfilesHelp": "צור פרופיל מותאם אישית למכשיר חדש או לעקיפת פרופיל מערכת.", "DefaultErrorMessage": "אירעה שגיאה בעיבוד הבקשה. בבקשה נסה שוב מאוחר יותר.", "Delete": "מחק", "DeleteImage": "מחק תמונה", @@ -130,7 +130,7 @@ "HeaderSeries": "סדרה", "HeaderSeriesOptions": "אפשרויות סדרה", "HeaderServerSettings": "הגדרות שרת", - "HeaderSetupLibrary": "הגדר את ספריית המדיה שלך.", + "HeaderSetupLibrary": "הגדר את ספריית המדיה שלך", "HeaderSpecialEpisodeInfo": "פרטי אפיזודות מיוחדות", "HeaderSpecialFeatures": "מאפיינים מיוחדים", "HeaderStatus": "מצב", @@ -164,7 +164,7 @@ "LabelChannels": "ערוצים:", "LabelCollection": "אוספים:", "LabelCommunityRating": "דירוג הקהילה:", - "LabelContentType": "סוג התוכן", + "LabelContentType": "סוג התוכן:", "LabelCountry": "מדינה:", "LabelCriticRating": "דירוג ביקורת:", "LabelCurrentPassword": "סיסמא נוכחית:", @@ -221,7 +221,7 @@ "LabelNext": "הבא", "LabelNotificationEnabled": "אפשר התראה זו", "LabelNumber": "מספר:", - "LabelNumberOfGuideDays": "מספר ימי לוח שידורים להורדה", + "LabelNumberOfGuideDays": "מספר ימים להורדה מלוח השידורים:", "LabelNumberOfGuideDaysHelp": "הורדת יותר ימי לוח שידורים מאפשרת יכולת לתכנן ולראות יותר תוכניות קדימה, אבל גם זמן ההורדה יעלה. מצב אוטומטי ייקבע לפני מספר הערוצים.", "LabelOriginalAspectRatio": "יחס גובה-רוחב מקורי:", "LabelOriginalTitle": "כותרת מקורית:", @@ -234,14 +234,14 @@ "LabelPersonRoleHelp": "דוגמה: נהג משאית גלידה", "LabelPlaceOfBirth": "מקום לידה:", "LabelPlaylist": "רשימת ניגון:", - "LabelPreferredDisplayLanguage": "שפת ממשק מועדפת", - "LabelPreferredDisplayLanguageHelp": "תרגום הממשק של Jellyfin הוא תהליך ממושך", + "LabelPreferredDisplayLanguage": "שפת תצוגה מועדפת:", + "LabelPreferredDisplayLanguageHelp": "תרגום Jellyfin הוא תהליך מתמשך.", "LabelPrevious": "הקודם", - "LabelProfileAudioCodecs": "מקודדי צליל", - "LabelProfileCodecs": "מקודדים", + "LabelProfileAudioCodecs": "מקודדי שמע:", + "LabelProfileCodecs": "מקודדים:", "LabelProfileVideoCodecs": "‮מקודדי וידאו:", "LabelPublicHttpPort": "מספר פורט HTTP פומבי", - "LabelReadHowYouCanContribute": "למד איך תוכל לתרום", + "LabelReadHowYouCanContribute": "למד איך אתה יכול לתרום.", "LabelRecord": "הקלטה:", "LabelRefreshMode": "מצב רענון:", "LabelReleaseDate": "תאריך הוצאה:", @@ -254,17 +254,17 @@ "LabelSortTitle": "מיין כותרת:", "LabelStartWhenPossible": "התחל ברגע שניתן:", "LabelStatus": "סטטוס:", - "LabelStopWhenPossible": "הפסק ברגע שאפשר", + "LabelStopWhenPossible": "הפסק כשיתאפשר:", "LabelTagline": "שורת תיוג:", "LabelTime": "זמן:", - "LabelTimeLimitHours": "הגבלת זמן (בשעות)", + "LabelTimeLimitHours": "מגבלת זמן (שעות):", "LabelTitle": "כותרת:", "LabelTrackNumber": "קטע מספר:", "LabelTriggerType": "סוגר טריגר:", "LabelType": "סוג:", "LabelUseNotificationServices": "השתמש בשירותים הבאים:", "LabelUser": "משתמש:", - "LabelUserLibrary": "ספריית משתמש", + "LabelUserLibrary": "ספריית משתמש:", "LabelYear": "שנה:", "LabelYourFirstName": "שמך הפרטי:", "LabelYoureDone": "סיימת!", @@ -277,12 +277,12 @@ "MaxParentalRatingHelp": "תוכן עם דירוג גובה יותר יוסתר מהמשתמש.", "MessageAreYouSureDeleteSubtitles": "האם אתה בטוח שברצונך למחוק קובץ כתובית זה?", "MessageConfirmRecordingCancellation": "‏האם אתה בטוח שברצונך לבטל הקלטה זו?", - "MessageDownloadQueued": "הורד תור", + "MessageDownloadQueued": "הורדה נוספה לתור.", "MessageItemSaved": "הפריט נשמר.", "MessageItemsAdded": "פריטים נוספו.", - "MessageLeaveEmptyToInherit": "השאר ריק כדי לרשת את ההגדרות מפריט אב, או את ערך ברירת המחדל הגלובלי.", + "MessageLeaveEmptyToInherit": "השאר ריק כדי לרשת את ההגדרות מפריט אב או את ערך ברירת המחדל הגלובלי.", "MessageNothingHere": "אין כאן כלום.", - "MessagePleaseEnsureInternetMetadata": "בבקשה וודא כי הורדת מידע מהאינטרנט מאופשרת", + "MessagePleaseEnsureInternetMetadata": "אנא וודא כי הורדת מטא-דאטה מהאינטרנט מופעלת.", "MinutesAfter": "דקות אחרי", "MinutesBefore": "דקות לפני", "Monday": "שני", @@ -397,7 +397,7 @@ "RecentlyWatched": "נצפה לאחרונה", "Record": "הקלט", "RecordSeries": "הקלט סדרה", - "RecordingCancelled": "בטל הקלטה", + "RecordingCancelled": "הקלטה בוטלה.", "RecordingScheduled": "ההקלטה מתוזמנת.", "Refresh": "רענון", "RefreshDialogHelp": "המטא נתונים מתרעננים על סמך הגדרות ושירותי אינטרנט שמופעלים בלוח המחוונים של מרכז אמבי.", @@ -419,7 +419,7 @@ "SearchForMissingMetadata": "חפש מטא נתונים חסרים", "SearchForSubtitles": "חיפוש של כתוביות", "SearchResults": "תוצאות חיפוש", - "SeriesCancelled": "בטל סדרות", + "SeriesCancelled": "סדרה בוטלה.", "SeriesRecordingScheduled": "הקלטת סדרה מתוזמנת.", "SeriesSettings": "הגדרות סדרה", "SeriesYearToPresent": "{0} - היום", @@ -510,8 +510,8 @@ "Artists": "אומנים", "Books": "ספרים", "Absolute": "מוחלט", - "AccessRestrictedTryAgainLater": "הגישה כרגע מוגבלת. אנא נסה שוב מאוחר יותר.", - "AddedOnValue": "נוסף {0}", + "AccessRestrictedTryAgainLater": "הגישה מוגבלת כרגע, נסה שוב במועד מאוחר יותר.", + "AddedOnValue": "נוספו {0}", "Blacklist": "רשימה שחורה", "Banner": "באנר", "Auto": "אוטומטי", @@ -528,10 +528,10 @@ "Audio": "שמע", "AspectRatio": "יחס גובה-רוחב", "AlwaysPlaySubtitlesHelp": "כתוביות בשפה המועדפת ייטענו ללא קשר לשפת השמע.", - "AlwaysPlaySubtitles": "הפעל כתוביות תמיד", + "AlwaysPlaySubtitles": "הפעל תמיד", "AllowRemoteAccessHelp": "אם לא מסומן, כל החיבורים המרוחקים ייחסמו.", "AllowRemoteAccess": "אפשר חיבור מרוחק לשרת Jellyfin זה.", - "AllowMediaConversionHelp": "אפשר או חסום גישה להמרת מדיה.", + "AllowMediaConversionHelp": "הענק או דחה גישה להמרת מדיה.", "Aired": "שודר", "AirDate": "תאריך שידור", "Yesterday": "אתמול", @@ -548,8 +548,8 @@ "HeaderContinueWatching": "המשך לצפות", "AllowOnTheFlySubtitleExtraction": "אפשר חילוץ כתוביות בזמן אמת", "AllowHWTranscodingHelp": "אפשר למלקט לקודד הזרמות בזמן אמת. זה עשוי לעזור בהפחתת הקידוד שנעשה ע\"י השרת.", - "AllComplexFormats": "כל הפורמטים המורכבים (ASS, SSA, VOBSUB, PGS, SUB\\IDX וכדומה)", - "AddItemToCollectionHelp": "הוסף פריטים לאוספים ע\"י חיפושם ושימוש בלחצן ימני או הקשה על התפריט כדי להוסיף אותם לאוסף.", + "AllComplexFormats": "כל הפורמטים המורכבים (ASS, SSA, VOBSUB, PGS, SUB/IDX)", + "AddItemToCollectionHelp": "הוסף חפצים לאוסף על ידי חיפושם ושימוש בתפריט הלחצן הימני או לחצן התפריט כדי להוסיפם לאוסף.", "Songs": "שירים", "Shows": "סדרות", "DownloadsValue": "{0} הורדות", @@ -607,7 +607,6 @@ "HeaderSelectServer": "בחר שרת", "HeaderSecondsValue": "{0} שניות", "HeaderSeasons": "עונות", - "HeaderRestartingServer": "מאתחל שרת", "HeaderRestart": "הפעלה מחדש", "HeaderProfileInformation": "מידע פרופיל", "HeaderProfile": "פרופיל", @@ -741,5 +740,40 @@ "Browse": "עיין", "BoxRear": "מארז (מאחור)", "BookLibraryHelp": "ניתן להוסיף ספרים מוקלטים וספרים כתובים. עיינו {0}במדריך מתן שמות לספרים{1}.", - "Desktop": "שולחן עבודה" + "Desktop": "שולחן עבודה", + "MessageUnauthorizedUser": "אין לך גישה לשרת ברגע זה. אנא צור קשר עם מנהל השרת למידע נוסף.", + "MessageDeleteTaskTrigger": "האם אתה בטוח שברצונך למחוק את מפעיל המשימה הזה?", + "LastSeen": "נראה לאחרונה ב-{0}", + "PersonRole": "כ-{0}", + "ListPaging": "{0}-{1} מתוך {2}", + "EveryXHours": "בכל {0} שעות", + "OnApplicationStartup": "באתחול היישום", + "DeleteUserConfirmation": "האם אתה בטוח שברצונך למחוק את המשתמש?", + "DeleteDeviceConfirmation": "האם אתה בטוח שברצונך למחוק את המכשיר? הוא יופיע שוב בפעם הבאה שמשתמש ייכנס באמצעותו.", + "ColorSpace": "מרחב צבע", + "CinemaModeConfigurationHelp": "מצב קולנוע מביא את חוויית הקולנוע היישר אל הסלון עם האפשרות להפעיל טריילרים וקדימונים מותאמים אישית לפני הסרט.", + "ChannelAccessHelp": "בחר את הערוצים לשיתוף עם משתמש זה. מנהלים יוכלו לערוך את כל הערוצים בעזרת \"מנהל המטא-דאטה\".", + "ButtonResetEasyPassword": "אתחל קוד פין פשוט", + "ButtonOff": "כיבוי", + "ButtonLibraryAccess": "הרשאות גישה לספרייה", + "BurnSubtitlesHelp": "מחליט אם על השרת לצרוב כתוביות בזמן קידוד וידאו. הימנעות מכך תשפר מאוד את הביצועים. בחר \"אוטומטי\" לצריבת כתוביות על בסיס פורמט תמונה (VOBSUB, PGS, SUB, IDX) וכתוביות ASS או SSA מסויימות.", + "Artist": "אמן", + "AllowedRemoteAddressesHelp": "רשימת IP \\ מיסוך רשת המופרדת בפסיקים עבור רשתות שיורשו להתחבר מרחוק. במידה ותישאר ריקה, כל הכתובות יורשו להתחבר.", + "Album": "אלבום", + "AlbumArtist": "אמן האלבום", + "LabelSortOrder": "מיין סדר:", + "ShowYear": "הצג שנה", + "ShowTitle": "הצג כותרת", + "DropShadow": "צייר צל", + "Playlists": "רשימות הפעלה", + "Raised": "מורם", + "LabelSpecialSeasonsDisplayName": "שם תצוגת \"עונה מיוחדת\":", + "LabelSource": "מקור:", + "LabelSoundEffects": "אפקטי סאונד:", + "ButtonTogglePlaylist": "רשימת ניגון", + "ButtonToggleContextMenu": "עוד", + "ButtonSyncPlay": "SyncPlay", + "ButtonPlayer": "נגן", + "StopPlayback": "הפסק הפעלה", + "ClearQueue": "נקה תור" } diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index 122f4aa98b2..48b82c601f0 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -21,11 +21,11 @@ "AddToCollection": "संग्रह में जोड़ें", "Add": "जोड़ें", "Actor": "अभिनेता", - "AccessRestrictedTryAgainLater": "अभी प्रवेश प्रतिबंधित है। थोड़ी देर बाद कोशिश करें।", + "AccessRestrictedTryAgainLater": "वर्तमान में पहुंच प्रतिबंधित है। कृपया बाद में पुनः प्रयास करें.", "AllowHWTranscodingHelp": "ट्यूनर को निरंतर रूप से धाराओं को ट्रांसकोड करने दें। यह सर्वर द्वारा ट्रांसकोडिंग को कम करने में मदद कर सकता है।", "AllLanguages": "सभी भाषाएं", "AllEpisodes": "सभी प्रकरण", - "AllComplexFormats": "सभी जटिल प्रारूप (ASS, SSA, VOBSUB, PGS, SUB / IDX, आदि)", + "AllComplexFormats": "सभी जटिल प्रारूप (ASS, SSA, VOBSUB, PGS, SUB, IDX,…)", "AllChannels": "सभी चैनल्स", "Alerts": "चेतावनियां", "Albums": "संग्रहिकाएँ", @@ -34,5 +34,88 @@ "AddedOnValue": "जोड़ दिया", "AddToPlaylist": "प्लेलिस्ट में जोड़ें", "AllowMediaConversionHelp": "मीडिया परिवर्तन के लिये अनुमति दें", - "AllowMediaConversion": "मीडिया रूपांतरण की अनुमति दें" + "AllowMediaConversion": "मीडिया रूपांतरण की अनुमति दें", + "ButtonOk": "ठीक", + "ButtonOff": "बंद", + "ButtonNextTrack": "आगे धावन पथ", + "ButtonNew": "नया", + "ButtonNetwork": "संजाल", + "ButtonMore": "अधिक", + "ButtonManualLogin": "मैनुअल लॉगिन", + "ButtonLibraryAccess": "पुस्तकालय का उपयोग", + "ButtonLearnMore": "और अधिक जानें", + "ButtonInfo": "जानकारी", + "ButtonHome": "घर", + "ButtonHelp": "मदद", + "ButtonGuide": "मार्गदर्शक", + "ButtonGotIt": "समझ गया", + "ButtonFullscreen": "पूर्ण स्क्रीन", + "ButtonForgotPassword": "पासवर्ड भूल गए", + "ButtonFilter": "निस्पंदन", + "ButtonEditOtherUserPreferences": "इस उपयोगकर्ता की प्रोफ़ाइल, छवि और व्यक्तिगत प्राथमिकताएँ संपादित करें।", + "ButtonEditImages": "छवियों को संपादित करें", + "ButtonEdit": "संपादित करें", + "ButtonDownload": "डाउनलोड", + "ButtonDown": "नीचे", + "ButtonDeleteImage": "छवि हटाएं", + "ButtonDelete": "हटाएं", + "ButtonConnect": "जुडिये", + "ButtonChangeServer": "सर्वर बदलें", + "ButtonCancel": "रद्द करना", + "ButtonBack": "वापस", + "ButtonAudioTracks": "ऑडियो ट्रैक्स", + "ButtonArrowUp": "ऊपर", + "ButtonArrowRight": "दाएँ", + "ButtonArrowLeft": "बाएं", + "ButtonArrowDown": "नीचे", + "ButtonAddUser": "उपयोगकर्ता जोड़ें", + "ButtonAddServer": "सर्वर जोड़े", + "ButtonAddScheduledTaskTrigger": "ट्रिगर जोड़ें", + "ButtonAddMediaLibrary": "मीडिया लाइब्रेरी जोड़ें", + "ButtonAddImage": "छवि जोड़ें", + "ButtonAdd": "जोड़ें", + "UnsupportedPlayback": "Jellyfin DRM द्वारा संरक्षित सामग्री को डिक्रिप्ट नहीं कर सकता है, लेकिन सभी सामग्री की परवाह किए बिना, संरक्षित शीर्षकों सहित प्रयास किया जाएगा। एन्क्रिप्शन या अन्य असमर्थित सुविधाओं जैसे इंटरेक्टिव शीर्षक के कारण कुछ फाइलें पूरी तरह से काली दिखाई दे सकती हैं।", + "BoxRear": "बॉक्स (पीछे)", + "Box": "बॉक्स", + "Books": "पुस्तकें", + "BookLibraryHelp": "ऑडियो और पाठ्य पुस्तकें समर्थित हैं। {0} पुस्तक नामकरण गाइड {1} की समीक्षा करें।", + "Blacklist": "काला सूची में डालना", + "BirthPlaceValue": "जन्म स्थान: {0}", + "BirthLocation": "जन्म स्थान", + "BirthDateValue": "जन्म: {0}", + "Banner": "झंडा", + "Backdrops": "पृष्ठभूमि", + "Backdrop": "पृष्ठभूमि", + "AutoBasedOnLanguageSetting": "ऑटो (भाषा सेटिंग के आधार पर)", + "Auto": "ऑटो", + "AuthProviderHelp": "इस उपयोगकर्ता के पासवर्ड को प्रमाणित करने के लिए एक प्रमाणीकरण प्रदाता का उपयोग करें।", + "Audio": "नया", + "AttributeNew": "नया", + "AspectRatio": "आस्पेक्ट अनुपात", + "AskAdminToCreateLibrary": "लाइब्रेरी बनाने के लिए किसी व्यवस्थापक से पूछें।", + "Ascending": "आरोही", + "AsManyAsPossible": "जितने अधिक संभव हों", + "Artists": "कलाकार की", + "Artist": "कलाकार", + "Art": "कला", + "AroundTime": "लगभग", + "Anytime": "किसी भी समय", + "AnyLanguage": "कोई भी भाषा", + "AlwaysPlaySubtitlesHelp": "भाषा की वरीयता से मेल खाने वाले उपशीर्षक ऑडियो भाषा की परवाह किए बिना लोड किए जाएंगे।", + "AlwaysPlaySubtitles": "हमेशा खेलो", + "AllowedRemoteAddressesHelp": "कोमा ने नेटवर्क के लिए आईपी पते या आईपी / नेटमास्क प्रविष्टियों की सूची को अलग कर दिया है जिन्हें दूरस्थ रूप से कनेक्ट करने की अनुमति दी जाएगी। यदि खाली छोड़ दिया जाता है, तो सभी दूरस्थ पते की अनुमति दी जाएगी।", + "AllowRemoteAccessHelp": "अनियंत्रित होने पर, सभी दूरस्थ कनेक्शन अवरुद्ध हो जाएंगे।", + "AllowRemoteAccess": "इस जेलिफ़िन सर्वर को दूरस्थ कनेक्शन की अनुमति दें।", + "AllowFfmpegThrottlingHelp": "जब एक ट्रांसकोड या रीमूक्स वर्तमान प्लेबैक स्थिति से काफी आगे हो जाता है, तो प्रक्रिया को रोकें ताकि यह कम संसाधनों का उपभोग करेगा। अक्सर मांग किए बिना देखने पर यह सबसे उपयोगी है। यदि आप प्लेबैक समस्याओं का अनुभव करते हैं तो इसे बंद कर दें।", + "AllowFfmpegThrottling": "थ्रोटल ट्रांसकोड", + "AllowOnTheFlySubtitleExtractionHelp": "वीडियो ट्रांसकोडिंग को रोकने में मदद करने के लिए एंबेडेड सबटाइटल वीडियो से निकाले जा सकते हैं और सादे पाठ में ग्राहकों तक पहुंचाए जाते हैं। कुछ प्रणालियों पर यह एक लंबा समय ले सकता है और निष्कर्षण प्रक्रिया के दौरान वीडियो प्लेबैक को स्टाल करने का कारण बन सकता है। जब वे क्लाइंट डिवाइस द्वारा मूल रूप से समर्थित नहीं होते हैं, तो वीडियो ट्रांसकोडिंग के साथ जले हुए एम्बेडेड उपशीर्षक को अक्षम करें।", + "AlbumArtist": "चित्राधार कलाकार", + "AllowOnTheFlySubtitleExtraction": "मक्खी पर उपशीर्षक निष्कर्षण की अनुमति दें", + "Album": "एल्बम", + "AddItemToCollectionHelp": "उनके लिए खोज करके संग्रह में आइटम जोड़ें और उन्हें संग्रह में जोड़ने के लिए उनके राइट-क्लिक या टैप मेनू का उपयोग करें।", + "ButtonSyncPlay": "SyncPlay", + "BrowsePluginCatalogMessage": "उपलब्ध प्लगिन्स देखने के लिए हमारे कैटलॉग को ब्राउज़ करें।", + "Browse": "ब्राउज़", + "BoxSet": "बॉक्स सेट", + "BurnSubtitlesHelp": "निर्धारित करता है कि वीडियो ट्रांसकोडिंग करते समय सर्वर को उपशीर्षक बर्न-इन करना चाहिए। इससे बचने से प्रदर्शन में बहुत सुधार होगा। छवि आधारित उपशीर्षक (VOBSUB, PGS, SUB, IDX, …) एवं ASS अथवा SSA जैसे उपशीर्षक बर्न-इन करने के लिए ऑटो का चयन करें।" } diff --git a/src/strings/hr.json b/src/strings/hr.json index 9710c134544..69f5dfaa651 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -7,14 +7,14 @@ "AdditionalNotificationServices": "Pretražite katalog dodataka kako bi instalirali dodatne servise za obavijesti.", "AllChannels": "Svi kanali", "AllEpisodes": "Sve epizode", - "AllowHWTranscodingHelp": "Ako je omogućeno, omogućite TV/radio uređaju da konvertira strujanja u letu. Ovo može pomoći smanjiti konvertiranje koje zahtijeva Jellyfin Server.", + "AllowHWTranscodingHelp": "Omogućite TV/radio uređaju da konvertira strujanja u letu. Ovo može pomoći smanjiti konvertiranje koje zahtijeva Jellyfin Server.", "Anytime": "Bilo kada", - "AroundTime": "Oko {0}", + "AroundTime": "Oko", "AsManyAsPossible": "Što više je moguće", "AttributeNew": "Novo", "Backdrops": "Pozadine", "BirthDateValue": "Rođen: {0}", - "BirthLocation": "Lokacije rođenja", + "BirthLocation": "Lokacija rođenja", "BirthPlaceValue": "Mjesto rođenja: {0}", "BrowsePluginCatalogMessage": "Pregledajte dostupne dodatke u našem katalogu.", "ButtonAdd": "Dodaj", @@ -120,35 +120,35 @@ "DoNotRecord": "Ne snimi", "Download": "Preuzimanje", "DrmChannelsNotImported": "Kanali s DRM se neće uvesti.", - "EasyPasswordHelp": "Vaš laki PIN kod se koristi za izvan-mrežni pristup s podržanim Jellyfin aplikacijama, a također se može koristiti za jednostavnu mrežnu prijavu.", + "EasyPasswordHelp": "Vaš laki PIN kod se koristi za izvan-mrežni pristup na podržanim klijentima i također se može koristiti za jednostavnu mrežnu prijavu.", "Edit": "Izmjeni", "EditImages": "Uređivanje slika", "EditSubtitles": "Uredi titlove", - "EnableColorCodedBackgrounds": "Omogući kodirane boje pozadine", - "EnablePhotos": "Omogući slike", - "EnablePhotosHelp": "Slike će biti otkrite i prikazivane zajedno s drugim medijskim datotekama.", + "EnableColorCodedBackgrounds": "Kodirane boje pozadine", + "EnablePhotos": "Prikaži slike", + "EnablePhotosHelp": "Slike će biti otkrivene i prikazivane zajedno s drugim medijskim datotekama.", "Ended": "Završeno", "EndsAtValue": "Završava u {0}", "ErrorAddingListingsToSchedulesDirect": "Došlo je do pogreške prilikom dodavanja postava vašim zakazanim direktnim računima. Raspored dopušta samo ograničen broj postava po računu. Možda ćete morati se prijavite u zakazanim \"Direct\" web stranicama i ukloniti unose drugih s računa prije nastavka.", "ErrorAddingMediaPathToVirtualFolder": "Došlo je do pogreške prilikom dodavanja putanje medija. Provjerite dali je putanja valjana i da proces Jellyfin Server-a ima pristup tom mjestu.", "ErrorAddingTunerDevice": "Došlo je do pogreške prilikom dodavanja uređaja TV/radio pretraživača. Provjerite da je dostupan i pokušajte ponovno.", - "ErrorGettingTvLineups": "Došlo je do pogreške prilikom preuzimanja tv postave. Provjerite dali su vaše informacije točne i pokušajte ponovno.", + "ErrorGettingTvLineups": "Došlo je do pogreške prilikom preuzimanja TV postave. Provjerite dali su vaše informacije točne i pokušajte ponovno.", "ErrorMessageStartHourGreaterThanEnd": "Vrijeme završetka mora biti veće od početka.", "ErrorPleaseSelectLineup": "Odaberite postavu i pokušajte ponovno. Ako niti jedna postava nije dostupna provjerite dali su korisničko ime, lozinka i poštanski broj točni.", "ErrorSavingTvProvider": "Došlo je do pogreške prilikom snimanja TV pružatelja. Provjerite da je dostupan i pokušajte ponovno.", - "ExtractChapterImagesHelp": "Izdvajanje slika poglavlja omogućiti će Jellyfin aplikaciji za prikaz grafičkih izbornika za odabir scena. Proces može biti spor, CPU intenzivno korišten i može zahtijevati nekoliko gigabajta prostora. Ono se pokreće kad je otkriven video, a također i kao noćni zadatak. Raspored je podesiv u području rasporeda zadataka. Ne preporučuje se za pokretanje ovog zadatka tijekom sati čestog korištenja.", + "ExtractChapterImagesHelp": "Izdvajanje slika poglavlja omogućiti će klijentima prikaz grafičkih izbornika za odabir scena. Proces može biti spor, resursi intenzivno korišteni i može zahtijevati nekoliko gigabajta prostora. Ono se pokreće kad je otkriven video, a također i kao noćni zadatak. Raspored je podesiv u području rasporeda zadataka. Ne preporučuje se za pokretanje ovog zadatka tijekom sati čestog korištenja.", "FFmpegSavePathNotFound": "Nismo mogli locirati FFmpeg korištenjem putanje koju ste unijeli. FFprobe je također potreban i mora postojati u istoj mapi. Te komponente su obično u paketu zajedno u istom preuzimanju. Provjerite putanju i pokušajte ponovno.", "Favorite": "Omiljeni", "File": "Datoteka", "FileNotFound": "Datoteka nije pronađena.", "FileReadCancelled": "Učitavanje datoteke je prekinuto.", - "FileReadError": "Prilikom učitavanja datoteke desila se greška", + "FileReadError": "Prilikom učitavanja datoteke dogodila se greška.", "FolderTypeBooks": "Knjige", "FolderTypeMovies": "Filmovi", "FolderTypeMusic": "Glazba", - "FolderTypeMusicVideos": "Glazbeni videi", + "FolderTypeMusicVideos": "Glazbeni spotovi", "FolderTypeTvShows": "TV", - "FolderTypeUnset": "Isključi (miješani sadržaj)", + "FolderTypeUnset": "Miješani sadržaj", "Friday": "Petak", "Genres": "Žanrovi", "GroupVersions": "Verzija grupe", @@ -158,7 +158,7 @@ "H264CrfHelp": "Konstante brzine faktora (CRF) je postavka zadane kvalitete za x264 enkodera. Možete postaviti vrijednosti između 0 i 51, gdje će niže vrijednosti rezultirati boljom kvalitetom (na račun veće veličine datoteka). Razumne vrijednosti su između 18 i 28. Zadana za x264 je 23, tako da to možete koristiti kao početnu točku.", "EncoderPresetHelp": "Odaberite bržu vrijednost za poboljšanje performansi ili sporiju za poboljšanje kvalitete.", "HDPrograms": "HD programi", - "HardwareAccelerationWarning": "Omogućavanje hardverskog ubrzanja može uzrokovati nestabilnostima u nekim sredinama. Pobrinite se da Vaš operativni sustav i video drajveri su u potpunosti ažurni. Ako imate poteškoća s reprodukcijom videa nakon omogućavanja ovoga, morat ćete promijeniti postavku natrag na Automatski.", + "HardwareAccelerationWarning": "Omogućavanje hardverskog ubrzanja može uzrokovati nestabilnostima u nekim sredinama. Pobrinite se da Vaš operativni sustav i video drajveri su u potpunosti ažurni. Ako imate poteškoća s reprodukcijom videa nakon omogućavanja ovoga, morat ćete promijeniti postavku natrag na Ništa.", "HeaderAccessSchedule": "Raspored pristupa", "HeaderAccessScheduleHelp": "Napravite raspored pristupa da bi ograničili pristup određenim satima.", "HeaderActiveDevices": "Aktivni uređaji", @@ -189,7 +189,7 @@ "HeaderCodecProfileHelp": "Profili kodeka definiraju ograničenja kada uređaji izvode sadržaj u specifičnom kodeku. Ako se ograničenja podudaraju tada će sadržaj biti transkodiran, iako je kodek konfiguriran za direktno izvođenje.", "HeaderConfirmPluginInstallation": "Potvrdi instalaciju dodatka", "HeaderConfirmProfileDeletion": "Potvrdite brisanje profila", - "HeaderConfirmRevokeApiKey": "Opozovi Api ključ", + "HeaderConfirmRevokeApiKey": "Opozovi API ključ", "HeaderConnectToServer": "Spoji se na Server", "HeaderConnectionFailure": "Neuspjelo spajanje", "HeaderContainerProfile": "Profil spremnika", @@ -221,7 +221,7 @@ "HeaderForgotPassword": "Zaboravili ste lozinku", "HeaderFrequentlyPlayed": "Često izvođeno", "HeaderGuideProviders": "Pružatelji vodiča", - "HeaderHttpHeaders": "Http zaglavlja", + "HeaderHttpHeaders": "HTTP zaglavlja", "HeaderIdentification": "Identifikacija", "HeaderIdentificationCriteriaHelp": "Unesite barem jedan kriterij za identifikaciju.", "HeaderIdentificationHeader": "Identifikacija zaglavlja", @@ -244,7 +244,7 @@ "HeaderLoginFailure": "Neuspjela prijava", "HeaderMedia": "Medij", "HeaderMediaFolders": "Medijska mapa", - "HeaderMediaInfo": "Info medija:", + "HeaderMediaInfo": "Info medija", "HeaderMetadataSettings": "Postavke meta-podataka", "HeaderMoreLikeThis": "Više ovakvih", "HeaderMovies": "Filmovi", @@ -278,8 +278,8 @@ "HeaderScenes": "Scene", "HeaderSchedule": "Raspored", "HeaderSeasons": "Sezone", - "HeaderSelectCertificatePath": "Odaberi put certifikata:", - "HeaderSelectMetadataPath": "Odaberite putanju meta-podataka:", + "HeaderSelectCertificatePath": "Odaberi putanju certifikata", + "HeaderSelectMetadataPath": "Odaberite putanju meta-podataka", "HeaderSelectMetadataPathHelp": "Pregledajte ili unesite putanju za pohranu meta-podataka. U mapu se mora moći pisati.", "HeaderSelectPath": "Odaberi putanju", "HeaderSelectServer": "Odaberi Server", @@ -288,7 +288,7 @@ "HeaderSelectTranscodingPath": "Odaberite privremenu putanju konvertiranja", "HeaderSelectTranscodingPathHelp": "Pregledajte ili unesite putanju za korištenje konvertiranja privremenih datoteka. U mapu se mora moći pisati.", "HeaderSendMessage": "Pošalji poruku", - "HeaderSeries": "Series:", + "HeaderSeries": "Serija", "HeaderSeriesOptions": "Opcije serija", "HeaderServerSettings": "Postavke Servera", "HeaderSettings": "Postavke", @@ -325,7 +325,7 @@ "Identify": "Identificiraj", "Images": "Slike", "ImportFavoriteChannelsHelp": "Ako je omogućeno, samo kanali koji su označeni kao omiljeni na uređaju TV/radio pretraživača će se uvesti.", - "InstallingPackage": "Instaliranje {0}", + "InstallingPackage": "Instaliranje {0} (verzija {1})", "InstantMix": "Trenutno miješanje", "ItemCount": "{0} stavaka", "Kids": "Djeca", @@ -344,13 +344,13 @@ "LabelAlbumArtMaxWidth": "Maksimalna širina Album art-a:", "LabelAlbumArtMaxWidthHelp": "Maksimalna rezolucija albuma izloženih putem UPnP:albumArtURI.", "LabelAlbumArtPN": "Grafika albuma PN:", - "LabelAlbumArtists": "Izvođači albuma", + "LabelAlbumArtists": "Izvođači albuma:", "LabelAll": "Sve", "LabelAllowHWTranscoding": "Dopusti hardversko konvertiranje", "LabelAllowServerAutoRestart": "Dopusti serveru da se automatski resetira kako bi proveo nadogradnje", - "LabelAllowServerAutoRestartHelp": "Server će se resetirati dok je u statusu mirovanja, odnosno kada nema aktivnih korisnika.", + "LabelAllowServerAutoRestartHelp": "Server će se resetirati samo dok je u statusu mirovanja kada nema aktivnih korisnika.", "LabelAppName": "Ime aplikacije", - "LabelAppNameExample": "Primjer: Sickbeard, NzbDrone", + "LabelAppNameExample": "Primjer: Sickbeard, Sonarr", "LabelArtists": "Izvođači:", "LabelArtistsHelp": "Odvoji višestruko koristeći ;", "LabelAudioLanguagePreference": "Postavke audio jezika:", @@ -362,7 +362,7 @@ "LabelBlastMessageIntervalHelp": "Određuje trajanje u sekundama između svake poruke dostupnosti servera.", "LabelCache": "Predmemorija:", "LabelCachePath": "Putanja predmemorije:", - "LabelCachePathHelp": "Odredite prilagođenu lokaciju za predmemorijske datoteke servera, kao što su slike. Ostavite prazno za korištenje zadanog poslužitelja.", + "LabelCachePathHelp": "Odredite prilagođenu lokaciju za predmemorijske datoteke servera kao što su slike. Ostavite prazno za korištenje zadanog poslužitelja.", "LabelCancelled": "Otkazan", "LabelChannels": "Kanali:", "LabelCollection": "Kolekcija:", @@ -371,12 +371,12 @@ "LabelCountry": "Zemlja:", "LabelCriticRating": "Ocjene kritike:", "LabelCurrentPassword": "Sadašnja lozinka:", - "LabelCustomCss": "Prilagođen css:", - "LabelCustomCssHelp": "Primijenite svoj vlastiti prilagođeni css na web sučelje.", + "LabelCustomCss": "Prilagođeni CSS:", + "LabelCustomCssHelp": "Primijenite svoj vlastiti prilagođeni stil na web sučelje.", "LabelCustomDeviceDisplayName": "Prikaz naziva:", "LabelCustomDeviceDisplayNameHelp": "Navedite naziv prilagođenog prikaza ili ostaviti prazno za korištenje naziva koji je izvijestio uređaj.", "LabelCustomRating": "Prilagođena ocjena:", - "LabelDateAdded": "Datumu dodavanja", + "LabelDateAdded": "Datum dodavanja:", "LabelDateAddedBehavior": "Ponašanje datuma dodanog za novi sadržaj:", "LabelDateAddedBehaviorHelp": "Ako je prisutna vrijednost meta-podataka uvijek će se koristiti prije bilo kojih od ovih opcija.", "LabelDay": "Dan:", @@ -384,29 +384,29 @@ "LabelDefaultUser": "Zadani korisnik:", "LabelDefaultUserHelp": "Određuje koja će biblioteka biti prikazana na spojenim uređajima. Ovo se može zaobići za svaki uređaj koristeći profile.", "LabelDeviceDescription": "Opis uređaja", - "LabelDidlMode": "Didl način:", + "LabelDidlMode": "DIDL način:", "LabelDisplayMissingEpisodesWithinSeasons": "Prikaži epizode koje nedostaju unutar sezone", "LabelDisplayName": "Prikaz naziva:", "LabelDisplayOrder": "Poredak prikaza:", "LabelDisplaySpecialsWithinSeasons": "Prikaz specijalnih dodataka unutar sezona u kojima su emitirani", "LabelDownMixAudioScale": "Pojačaj zvuk kada radiš downmix:", - "LabelDownMixAudioScaleHelp": "Pojačaj zvuk kada radiš downmix. Postavi na 1 ako želiš zadržati orginalnu jačinu zvuka.", + "LabelDownMixAudioScaleHelp": "Pojačaj zvuk prilikom downmix-a. Vrijednost 1 će zadržati originalnu jačinu zvuka.", "LabelDownloadLanguages": "Jezici za preuzimanje:", "LabelEasyPinCode": "Lagan PIN kod:", "LabelEmbedAlbumArtDidl": "Ugradi grafike albuma u Didl", "LabelEmbedAlbumArtDidlHelp": "Neki uređaji podržavaju ovu metodu za prikaz grafike albuma. Drugi bi mogli imati problema sa ovom opcijom uključenom.", "LabelEnableAutomaticPortMap": "Omogući automatsko mapiranje porta", - "LabelEnableAutomaticPortMapHelp": "Pokušaj automatski mapirati javni port za lokalni port preko UPnP. Možda neće raditi s nekim modelima router-a.", + "LabelEnableAutomaticPortMapHelp": "Automatski proslijedi javni port na svom ruteru na lokalni port preko UPnP. Možda neće raditi sa nekim modelima router-a ili mrežnim konfiguracijama. Promjene se neće primijeniti do restarta servera.", "LabelEnableBlastAliveMessages": "Objavi poruke dostupnosti", "LabelEnableBlastAliveMessagesHelp": "Omogući ovo ako server nije prikazan kao siguran za druge UPnP uređaje na mreži.", "LabelEnableDlnaClientDiscoveryInterval": "Interval za otkrivanje kljenata (sekunde)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Određuje trajanje u sekundama između SSDP pretraživanja obavljenih od Jellyfin-a.", - "LabelEnableDlnaDebugLogging": "Omogući DLNA logiranje grešaka.", - "LabelEnableDlnaDebugLoggingHelp": "Ovo će kreirati iznimno velike log datoteke i jedino se preporuča koristiti u slučaju problema.", + "LabelEnableDlnaDebugLogging": "Omogući DLNA logiranje grešaka", + "LabelEnableDlnaDebugLoggingHelp": "Kreiraj iznimno velike log datoteke i preporuča se koristiti jedino u slučaju problema.", "LabelEnableDlnaPlayTo": "Omogući DLNA izvođenje na", - "LabelEnableDlnaPlayToHelp": "Jellyfin može otkriti uređaje unutar svoje mreže i ponuditi mogućnost da ih daljinski upravlja.", - "LabelEnableDlnaServer": "Omogući Dlna server", - "LabelEnableDlnaServerHelp": "Omogućuje UPnP uređajima na mreži da pregledavaju i pokreću Jellyfin sadržaj.", + "LabelEnableDlnaPlayToHelp": "Otkrij uređaje unutar svoje mreže i ponudi mogućnost da ih daljinski upravlja.", + "LabelEnableDlnaServer": "Omogući DLNA server", + "LabelEnableDlnaServerHelp": "Omogućuje UPnP uređajima na mreži da pregledavaju i pokreću sadržaj.", "LabelEnableRealtimeMonitor": "Omogući nadgledanje u realnom vremenu", "LabelEnableRealtimeMonitorHelp": "Promjene će biti procesuirane odmah, nad podržanim datotekama sistema.", "LabelEnableSingleImageInDidlLimit": "Ograničenje na jednu ugrađenu sliku", @@ -416,12 +416,12 @@ "LabelEvent": "Događaj:", "LabelEveryXMinutes": "Svaki:", "LabelExtractChaptersDuringLibraryScan": "Izvadi slike poglavlja dok traje skeniranje biblioteke", - "LabelExtractChaptersDuringLibraryScanHelp": "Ako je omogućeno, slike poglavlje će se izdvojiti kad se videozapisi uvezu tijekom skeniranja biblioteke. Ako je onemogućeno izdvojiti će se u rasporedu zadatka slika poglavlja, čime se omogućuje da se skeniranje redovne biblioteke završiti brže.", + "LabelExtractChaptersDuringLibraryScanHelp": "Generiraj slike poglavlje kad se videozapisi uvezu tijekom skeniranja biblioteke. U suprotnom, izdvajanje će se odraditi u rasporedu zadatka slika poglavlja, čime se omogućuje da se skeniranje redovne biblioteke završi brže.", "LabelFailed": "Neuspješno", "LabelFileOrUrl": "Datoteka ili url:", "LabelFinish": "Kraj", "LabelForgotPasswordUsernameHelp": "Unesite korisničko ime, ako se sjećate.", - "LabelFriendlyName": "Prijateljsko ime", + "LabelFriendlyName": "Prijateljsko ime:", "LabelServerNameHelp": "Ovo ime će se koristiti za identifikaciju servera. Ako ostavite prazno, ime računala će se koristi kao identifikator.", "LabelGroupMoviesIntoCollections": "Grupiraj filmove u kolekciju", "LabelGroupMoviesIntoCollectionsHelp": "Kada se prikazuje lista filmova, filmovi koji pripadaju kolekciji biti će prikazani kao jedna stavka.", @@ -475,7 +475,7 @@ "LabelMetadataDownloadersHelp": "Omogućite i poredajte željene preuzimatelje meta-podataka po redu prioriteta. Manjeg prioriteta preuzimatelji koristit će se samo za ispunjavanje nedostajućih informacija.", "LabelMetadataPath": "Put meta-podataka:", "LabelMetadataPathHelp": "Odredite prilagođenu lokaciju za preuzete ilustracije i meta-podatke.", - "LabelMetadataReaders": "Čitači meta-podataka", + "LabelMetadataReaders": "Čitači meta-podataka:", "LabelMetadataReadersHelp": "Poredajte željene lokalne izvore meta-podataka po redu prioriteta. Prva nađena datoteka biti će čitana.", "LabelMetadataSavers": "Snimači meta-podataka:", "LabelMetadataSaversHelp": "Odaberite formate datoteka za spremanje meta-podataka.", @@ -567,7 +567,7 @@ "LabelStopWhenPossible": "Zaustavi kada je moguće:", "LabelStopping": "Zaustavljanje", "LabelSubtitleFormatHelp": "Npr.: srt", - "LabelSubtitlePlaybackMode": "Način titlova prijevoda", + "LabelSubtitlePlaybackMode": "Način prijevoda:", "LabelSupportedMediaTypes": "Podržani tipovi medija:", "LabelTag": "Oznaka:", "LabelTagline": "Slogan:", @@ -652,15 +652,15 @@ "MessageDirectoryPickerBSDInstruction": "Za BSD možda ćete morati podesiti pohranu unutar vašega FreeNAS kako bi se omogućilo Jellyfin-u pristup.", "MessageDirectoryPickerInstruction": "Mrežne putanje mogu se unijeti ručno u slučaju da gumb Mreže ne uspije locirati vaše uređaje. Na primjer, {0} ili {1}.", "MessageDirectoryPickerLinuxInstruction": "Za Linux na Arch Linux, CentOS, Debian, Fedora, OpenSuse ili Ubuntu morate dati korisniku Jellyfin sistema barem pristup čitanja vašim lokacijama za skladištenje.", - "MessageDownloadQueued": "Preuzimanje na čekanju", - "MessageFileReadError": "Prilikom učitavanja datoteke desila se greška", + "MessageDownloadQueued": "Preuzimanje na čekanju.", + "MessageFileReadError": "Prilikom učitavanja datoteke desila se greška. Pokušajte ponovno.", "MessageForgotPasswordFileCreated": "Sljedeća datoteka je stvorena na vašem poslužitelju i sadrži upute o tome kako postupiti:", "MessageForgotPasswordInNetworkRequired": "Molim pokušajte ponovno unutar kućne mreže za pokretanje postupka za poništavanje zaporke.", "MessageInstallPluginFromApp": "Ovaj dodatak mora biti instaliran unutar aplikacije u kojoj ga namjeravate koristiti.", "MessageInvalidForgotPasswordPin": "Upisan je neispravan ili zastarjele pin. Molim, pokušajte ponovno.", "MessageInvalidUser": "Pogrešno korisničko ime ili lozinka. Molim, pokušajte ponovo.", "MessageItemSaved": "Stavka je snimljena.", - "MessageItemsAdded": "Stavke su dodane", + "MessageItemsAdded": "Stavke su dodane.", "MessageLeaveEmptyToInherit": "Ostavite prazno da naslijedi postavke od roditelja stavke ili globalnu zadanu vrijednost.", "MessageNoAvailablePlugins": "Nema odgovarajućih dodataka.", "MessageNoMovieSuggestionsAvailable": "Filmski prijedlozi nisu trenutno dostupni. Počnite s gledanjem i ocjenjivanjem svoje filmove, a zatim se vratite da biste vidjeli svoje preporuke.", @@ -672,7 +672,7 @@ "MessagePluginConfigurationRequiresLocalAccess": "Za podešavanje ovog dodatka prijavite se izravno na lokalni server.", "MessagePluginInstallDisclaimer": "Dodaci izgrađeni od strane članova Jellyfin zajednice su sjajan način kako bi unaprijedili Vaše iskustvo Jellyfin s dodatnim značajkama i prednostima. Prije instaliranja budite svjesni učinaka koje mogu imati na vaš Jellyfin Server, kao što je duže skeniranje biblioteke, dodatna pozadinska obrada, a smanjena stabilnost sustava.", "MessageReenableUser": "Pogledajte dolje za ponovno omogućenje", - "MessageSettingsSaved": "Postavke snimljene", + "MessageSettingsSaved": "Postavke spremljene.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Sljedeće lokacije medija biti će uklonjene iz vaše Jellyfin biblioteke:", "MessageUnableToConnectToServer": "Nismo u mogućnosti spojiti se na odabrani poslužitelj. Provjerite dali je pokrenut i pokušajte ponovno.", "MessageUnsetContentHelp": "Sadržaj će biti prikazan kao obične mape. Za najbolje rezultate upotrijebite upravitelj meta-podataka za postavljanje vrste sadržaja pod-mapa.", @@ -785,14 +785,14 @@ "OptionMissingEpisode": "Epizode koje nedostaju", "OptionMonday": "Ponedjeljak", "OptionNameSort": "Nazivu", - "OptionNew": "Novo...", + "OptionNew": "Novo…", "OptionNone": "Ništa", "OptionOnAppStartup": "Kada se aplikacija pokrene", "OptionOnInterval": "U intervalu", "OptionParentalRating": "Roditeljska ocjena", "OptionPlainStorageFolders": "Prikaži sve mape kako jednostavne mape za skladištenje", "OptionPlainStorageFoldersHelp": "Ako je omogućeno, sve mape se prezentiraju u DIDL-u kao \"objekt.spremnik.skladišnaMapa\" umjesto više specijaliziranog tipa kao \"objekt.spremnik.osoba.glazbaIzvođač\".", - "OptionPlainVideoItems": "Prikaži sav video kao jednostavne video stavke.", + "OptionPlainVideoItems": "Prikaži sve video zapise kao jednostavne video stavke", "OptionPlainVideoItemsHelp": "Ako je omogućeno, sav video se prezentira u DIDL-u kao \"objekt.stavka.videoStavka\" umjesto više specijaliziranog tipa kao \"objekt.stavka.videoStavka.film\".", "OptionPlayCount": "Broju izvođenja", "OptionPlayed": "Izvođeni", @@ -862,7 +862,7 @@ "RecordingScheduled": "Snimka je zakazana.", "Refresh": "Osviježi", "RefreshDialogHelp": "Meta-podaci se osvježavaju na temelju postavki i internet usluga koje su omogućene u nadzornoj ploči Jellyfin Server-a.", - "RefreshQueued": "Osviježi stavke na čekanju", + "RefreshQueued": "Osviježi stavke na čekanju.", "ReleaseDate": "Datum izdavanja", "RememberMe": "Zapamti me", "RemoveFromCollection": "Ukloni iz kolekcije", @@ -890,7 +890,7 @@ "ServerNameIsShuttingDown": "Jellyfin Server - {0} se gasi.", "ServerUpdateNeeded": "Jellyfin Server treba ažurirati. Da biste preuzeli najnoviju verziju, posjetite {0}", "Settings": "Postavke", - "SettingsSaved": "Postavke snimljene", + "SettingsSaved": "Postavke spremljene.", "SettingsWarning": "Mijenjanje ove vrijednosti može uzrokovati nestabilnost ili kvarove na povezivanju. Ako naiđete na bilo kakve probleme, preporučamo da ih promijenite natrag na zadane.", "Share": "Dijeli", "ShowIndicatorsFor": "Prikaži pokazatelja za:", @@ -1006,8 +1006,237 @@ "AccessRestrictedTryAgainLater": "Dostup je trenutačno ograničen. Pokušajte poslije ponovno.", "Albums": "Albumi", "All": "Sve", - "AllComplexFormats": "Svi kompleksni formati (ASS, SSA, VOBSUB, PGS, SUB/IDX, itd.)", + "AllComplexFormats": "Svi kompleksni formati (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "Books": "Knjige", "Channels": "Kanali", - "Collections": "Kolekcije" + "Collections": "Kolekcije", + "Artists": "Izvođači", + "DownloadsValue": "{0} preuzimanja", + "Down": "Dolje", + "Playlists": "Popis za reprodukciju", + "AllowMediaConversionHelp": "Dopusti ili odbij pristup mogućnosti konverzije datoteke.", + "AllLibraries": "Sve biblioteke", + "Aired": "Prikazano", + "AirDate": "Datum prikazivanja", + "AddedOnValue": "Dodano {0}", + "Songs": "Pjesme", + "Shows": "Serije", + "Photos": "Slike", + "HeaderFavoriteSongs": "Omiljene pjesme", + "HeaderFavoriteArtists": "Omiljeni izvođači", + "HeaderFavoriteAlbums": "Omiljeni albumi", + "HeaderFavoriteEpisodes": "Omiljene epizode", + "HeaderFavoriteShows": "Omiljene serije", + "HeaderContinueWatching": "Nastavi gledati", + "HeaderAlbumArtists": "Izvođači na albumu", + "Folders": "Mape", + "Favorites": "Favoriti", + "ButtonCast": "Uloge", + "EveryXHours": "Svakih {0} sati", + "EveryHour": "Svaki sat", + "OnApplicationStartup": "Prilikom pokretanja aplikacije", + "Backdrop": "Pozadina", + "Auto": "Automatski", + "Audio": "Audio", + "Artist": "Izvođač", + "AnyLanguage": "Bilo koji jezik", + "AlwaysPlaySubtitlesHelp": "Prijevodi koji odgovaraju odabranom jeziku će uvijek biti odabrani unatoč jeziku audio zapisa.", + "AlwaysPlaySubtitles": "Uvijek prikaži prijevod", + "AllowRemoteAccessHelp": "Ako je odznačeno, svi udaljeni pristupi će biti blokirani.", + "AllowRemoteAccess": "Dopusti udaljene pristupe na ovaj Jellyfin server.", + "AllowOnTheFlySubtitleExtraction": "Dopusti izvlačenje prijevoda u hodu", + "AllowMediaConversion": "Dopusti konverziju datoteke", + "AllLanguages": "Svi jezici", + "Alerts": "Upozorenja", + "AlbumArtist": "Izvođač na albumu", + "Album": "Album", + "AddToPlayQueue": "Dodaj u red izvođenja", + "Banner": "Zaglavlje", + "AutoBasedOnLanguageSetting": "Automatski (prema jezičnim postavkama)", + "AspectRatio": "Omjer", + "Ascending": "Uzlazno", + "Art": "Grafike", + "Absolute": "Apsolutno", + "CopyStreamURLSuccess": "URL uspješno kopiran.", + "CopyStreamURL": "Kopiraj URL streama", + "ContinueWatching": "Nastavi gledati", + "Connect": "Spoji", + "ClientSettings": "Postavke klijenta", + "ButtonTogglePlaylist": "Lista izvođenja", + "ButtonToggleContextMenu": "Više", + "ButtonSplit": "Odvoji", + "ButtonStop": "Stop", + "ButtonScanAllLibraries": "Skeniraj sve biblioteke", + "ButtonInfo": "Info", + "ButtonFilter": "Filtriraj", + "ButtonAddImage": "Dodaj sliku", + "Box": "Kutija", + "AskAdminToCreateLibrary": "Traži administratora da kreira biblioteku.", + "PictureInPicture": "Slika u slici", + "OtherArtist": "Ostali izvođači", + "OptionThumb": "Sličica", + "OptionProtocolHttp": "HTTP", + "OptionProfileVideo": "Video", + "OptionProfileAudio": "Audio", + "OptionPoster": "Poster", + "OptionList": "Lista", + "OptionIsSD": "SD", + "OptionIsHD": "HD", + "OptionDvd": "DVD", + "OptionDownloadLogoImage": "Logo", + "OptionBluray": "Blu-ray", + "OptionBanner": "Zaglavlje", + "Option3D": "3D", + "OneChannel": "Jedan kanal", + "Off": "Isključi", + "Normal": "Normalno", + "None": "Ništa", + "NoSubtitles": "Ništa", + "No": "Ne", + "NextUp": "Slijedi", + "Next": "Slijedeće", + "Never": "Nikada", + "MusicVideo": "Glazbeni spot", + "Movie": "Film", + "Metadata": "Meta podaci", + "MessageSyncPlayLibraryAccessDenied": "Pristup ovom sadržaju je ograničen.", + "MessageSyncPlayDisabled": "SyncPlay onemogućen.", + "MessageSyncPlayEnabled": "SyncPlay omogućen.", + "MessagePleaseWait": "Molimo pričekajte. Ovo može potrajati nekoliko minuta.", + "LabelRepositoryName": "Naziv repozitorija", + "LabelRepositoryUrl": "URL repozitorija", + "HeaderNewRepository": "Novi repozitorij", + "MessageNoRepositories": "Nema repozitorija.", + "MessageConfirmAppExit": "Da li želite izaći?", + "Menu": "Meni", + "MediaInfoStreamTypeVideo": "Video", + "MediaInfoStreamTypeSubtitle": "Prijevod", + "MediaInfoStreamTypeData": "Podaci", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoSoftware": "Softver", + "Logo": "Logo", + "List": "Lista", + "LabelYear": "Godina:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arhitektura: {0}", + "DashboardOperatingSystem": "Operativni sustav: {0}", + "DashboardServerName": "Server: {0}", + "DashboardVersionNumber": "Verzija: {0}", + "LabelVersion": "Verzija:", + "LabelTheme": "Tema:", + "LabelTextSize": "Veličina teksta:", + "LabelTextColor": "Boja teksta:", + "LabelSyncPlayAccess": "SyncPlay pristup", + "LabelSyncPlayAccessNone": "Onemogućeno za ovog korisnika", + "LabelSyncPlayAccessJoinGroups": "Dozvoli korisniku da se pridruži grupama", + "LabelSyncPlayAccessCreateAndJoinGroups": "Dozvoli korisniku da kreira i pridruži se grupama", + "LabelSyncPlayLeaveGroupDescription": "Onemogući SyncPlay", + "LabelSyncPlayLeaveGroup": "Napusti grupu", + "LabelSyncPlayNewGroupDescription": "Kreiraj novu grupu", + "LabelSyncPlayNewGroup": "Nova grupa", + "MillisecondsUnit": "ms", + "LabelSubtitles": "Prijevodi", + "LabelStatus": "Status:", + "LabelSoundEffects": "Zvučni efekti:", + "LabelSortOrder": "Redoslijed sortiranja:", + "LabelSortBy": "Sortiranje po:", + "LabelSize": "Veličina:", + "LabelServerName": "Naziv servera:", + "EnableFasterAnimations": "Brže animacije", + "LabelReasonForTranscoding": "Razlog transkodiranja:", + "LabelPreferredSubtitleLanguage": "Preferirani jezik prijevoda:", + "LabelStable": "Stabilna", + "LabelLanNetworks": "LAN mreže:", + "LabelInternetQuality": "Internet kvaliteta:", + "LabelHomeNetworkQuality": "Kvaliteta kućne mreže:", + "LabelFormat": "Format:", + "LabelFont": "Font:", + "LabelFolder": "Mapa:", + "LabelEnableHttps": "Omogući HTTPS", + "LabelEnableHardwareDecodingFor": "Omogući hardversko dekodiranje za:", + "LabelDisplayLanguageHelp": "Prevođenje Jellyfin-a je projekt u tijeku.", + "LabelDisplayLanguage": "Jezik prikaza:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Automatski osvježi meta podatke sa interneta:", + "LabelAudioChannels": "Audio kanali:", + "LabelAudio": "Audio", + "LabelAlbum": "Album:", + "Label3DFormat": "3D format:", + "Items": "Stavke", + "Horizontal": "Horizontalno", + "Home": "Početna", + "Hide": "Sakrij", + "HeaderSyncPlayEnabled": "SyncPlay omogućen", + "HeaderSyncPlaySelectGroup": "Pridruži se grupi", + "HeaderSubtitleDownloads": "Preuzimanje prijevoda", + "HeaderSubtitleAppearance": "Prikaz prijevoda", + "HeaderStopRecording": "Zaustavi snimanje", + "HeaderStatus": "Status", + "HeaderSeriesStatus": "Status serije", + "HeaderSecondsValue": "{0} sekundi", + "HeaderRemoteAccessSettings": "Postavke udaljenog pristupa", + "HeaderPlaybackError": "Pogreška reprodukcije", + "HeaderPlayAll": "Reproduciraj sve", + "HeaderOnNow": "Trenutno", + "HeaderNextVideoPlayingInValue": "Slijedeći video se reproducira za {0}", + "HeaderNextEpisodePlayingInValue": "Slijedeća epizoda se reproducira za {0}", + "HeaderNewDevices": "Novi uređaji", + "HeaderNavigation": "Navigacija", + "HeaderMyDevice": "Moj uređaj", + "HeaderLibrarySettings": "Postavke biblioteke", + "HeaderHome": "Početna", + "HeaderGenres": "Žanrovi", + "HeaderFavoritePeople": "Omiljeni ljudi", + "HeaderFavoriteMovies": "Omiljeni filmovi", + "HeaderFavoriteBooks": "Omiljene knjige", + "HeaderDVR": "DVR", + "HeaderDownloadSync": "Preuzmi i sinkroniziraj", + "HeaderContinueListening": "Nastavi slušati", + "HeaderConfigureRemoteAccess": "Konfiguriraj udaljeni pristup", + "HeaderAudioBooks": "Audio knjige", + "ApiKeysCaption": "Popis trenutno dostupnih API ključeva", + "HeaderAllowMediaDeletionFrom": "Dozvoli brisanje datoteka iz", + "HeaderAlbums": "Albumi", + "HeaderAdmin": "Administrator", + "Guide": "Vodič", + "GroupBySeries": "Grupiraj po serijama", + "Genre": "Žanr", + "General": "Općenito", + "Fullscreen": "Prikaz cijelog ekrana", + "Filters": "Filteri", + "FetchingData": "Dohvaćanje dodatnih podataka", + "Features": "Mogućnosti", + "Extras": "Dodaci", + "ExtraLarge": "Ekstra veliko", + "ExitFullscreen": "Izađi iz prikaza cijelog ekrana", + "EveryNDays": "Svakih {0} dana", + "Episodes": "Epizode", + "Episode": "Epizoda", + "EnableHardwareEncoding": "Omogući hardversko enkodiranje", + "EnableExternalVideoPlayers": "Vanjski video player", + "EnableDecodingColorDepth10Vp9": "Omogući 10-Bitno hardversko dekodiranje za VP9", + "EnableDecodingColorDepth10Hevc": "Omogući 10-Bitno hardversko dekodiranje za HEVC", + "EnableCinemaMode": "Kino mod", + "EnableBackdrops": "Pozadine", + "EditMetadata": "Izmijeni meta podatke", + "DisplayMissingEpisodesWithinSeasons": "Prikaži epizode koje nedostaju unutar sezona", + "DisplayInMyMedia": "Prikaz na početnom ekranu", + "Display": "Prikaz", + "Disconnect": "Odspoji", + "Disc": "Disk", + "Disabled": "Onemogućeno", + "Directors": "Režiseri", + "DirectPlaying": "Direktna reprodukcija", + "DetectingDevices": "Tražim uređaje", + "Descending": "Silazno", + "DefaultMetadataLangaugeDescription": "Ovo su vaše zadane postavke te se mogu prilagoditi na razini biblioteke.", + "Default": "Zadano", + "DatePlayed": "Datum reprodukcije", + "DateAdded": "Datum dodavanja", + "CriticRating": "Rejting kritičara", + "CopyStreamURLError": "Došlo je do greške prilikom kopiranja URLa.", + "ConfirmEndPlayerSession": "Da li želite ugasiti Jellyfin na {0}?", + "CommunityRating": "Rejting zajednice", + "Browse": "Pretraži", + "BoxRear": "Kutija (stražnja)", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/hu.json b/src/strings/hu.json index f8dfecb6c09..5559e2f10ab 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -101,8 +101,8 @@ "EnableBackdrops": "Háttérképek", "EnableBackdropsHelp": "A háttérképek a könyvtár böngészése közben néhány oldal hátterében jelennek meg.", "EnableHardwareEncoding": "Hardveres kódolás engedélyezése", - "EnableThemeSongs": "Főcím dalok", - "EnableThemeSongsHelp": "Főcím dalok lejátszása háttérben a könyvtár böngészése közben.", + "EnableThemeSongs": "Főcímdalok", + "EnableThemeSongsHelp": "Főcímdalok lejátszása háttérben a könyvtár böngészése közben.", "EndsAtValue": "Várható befejezés {0}", "Episodes": "Epizódok", "ExitFullscreen": "Kilépés a teljes képernyőből", @@ -167,7 +167,7 @@ "HeaderLibrarySettings": "Médiatár beállítások", "HeaderLiveTvTunerSetup": "Élő TV tuner beállítása", "HeaderMedia": "Média", - "HeaderMediaFolders": "Média Könyvtárak", + "HeaderMediaFolders": "Médiakönyvtárak", "HeaderMediaInfo": "Média Infó", "HeaderMetadataSettings": "Metaadat Beállítások", "HeaderMoreLikeThis": "Több ehhez hasonló", @@ -224,7 +224,7 @@ "Home": "Kezdőlap", "Identify": "Azonosítás", "Images": "Képek", - "InstallingPackage": "{0} Telepítése", + "InstallingPackage": "{0} ({1} verzió) telepítése", "Label3DFormat": "3D formátum:", "LabelAlbumArtists": "Album előadók:", "LabelAll": "Összes", @@ -244,7 +244,7 @@ "LabelCustomDeviceDisplayName": "Megjelenítendő név:", "LabelCustomDeviceDisplayNameHelp": "Adj meg egy egyedi nevet, vagy hagyd üresen a készülék által elküldött név használatához.", "LabelCustomRating": "Egyéni értékelés:", - "LabelDashboardTheme": "Szerver vezérlőpult kinézet:", + "LabelDashboardTheme": "Szerver vezérlőpult kinézete:", "LabelDateAdded": "Hozzáadva:", "LabelDateTimeLocale": "Dátum és idő formátum:", "LabelDay": "Nap:", @@ -424,7 +424,7 @@ "OptionMissingEpisode": "Hiányzó Epizódok", "OptionMonday": "Hétfő", "OptionNameSort": "Név", - "OptionNew": "Új...", + "OptionNew": "Új…", "OptionParentalRating": "Korhatár besorolás", "OptionPlayCount": "Lejátszások száma", "OptionPlayed": "Megnézett", @@ -443,9 +443,9 @@ "OptionWednesday": "Szerda", "OptionWeekly": "Heti", "OriginalAirDateValue": "Eredeti vetítés dátuma: {0}", - "PackageInstallCancelled": "{0} telepítése megszakítva.", - "PackageInstallCompleted": "{0} telepítése befejezve.", - "PackageInstallFailed": "{0} telepítése nem sikerült.", + "PackageInstallCancelled": "{0} ({1} verzió) telepítése megszakítva.", + "PackageInstallCompleted": "{0} ({1} verzió) telepítése befejezve.", + "PackageInstallFailed": "{0} ({1} verzió) telepítése nem sikerült.", "ParentalRating": "Korhatár besorolás", "People": "Személyek", "Play": "Lejátszás", @@ -584,15 +584,15 @@ "Aired": "Adásban", "Albums": "Albumok", "AllChannels": "Minden csatorna", - "AllComplexFormats": "Minden összetett formátum (ASS, SSA, VOBSUB, PGS, SUB/IDX, stb.)", + "AllComplexFormats": "Minden összetett formátum (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllowMediaConversion": "Média konvertálás engedélyezése", "AllowMediaConversionHelp": "Add meg vagy tiltsd le a média konvertálás funkcióhoz való hozzáférést.", "AllowRemoteAccess": "Engedélyezze a távoli kapcsolatokat a Jellyfin szerverhez.", "AllowRemoteAccessHelp": "Ha nincs bekapcsolva, minden távoli kapcsolat blokkolva lesz.", - "AlwaysPlaySubtitles": "Mindig legyen felirat", + "AlwaysPlaySubtitles": "Mindig jelenjen meg", "AnyLanguage": "Bármelyik nyelv", "Anytime": "Bármikor", - "AroundTime": "{0} körül", + "AroundTime": "kb.", "AsManyAsPossible": "Amennyi lehetséges", "AspectRatio": "Képarány", "Auto": "Auto", @@ -604,7 +604,7 @@ "AlwaysPlaySubtitlesHelp": "A nyelvi beállításoknak megfelelő feliratok az audió nyelvétől függetlenül kerülnek betöltésre.", "Artists": "Előadók", "Blacklist": "Feketelista", - "BookLibraryHelp": "Az audió- és szövegkönyvek támogatottak. Nézd meg a {0} könyvelnevezési útmutatót {1}.", + "BookLibraryHelp": "Lehetőség van audió és hangoskönyvek visszajátszására. Nézd meg a {0} könyvelnevezési útmutatót {1}.", "BrowsePluginCatalogMessage": "Böngéssz a Bővítmény katalógusunkban a rendelkezésre álló bővítmények megtekintéséhez.", "AddItemToCollectionHelp": "Adj elemeket a gyűjteményekhez, ehhez keresed meg őket, majd kattints jobb egérgombbal, vagy kattints a menüre és add hozzá a gyűjteményhez.", "AllowedRemoteAddressesHelp": "Vesszővel válaszd el az IP-címek vagy IP / netmask címek listáját annak a hálózatnak amelyből távolról csatlakozhatnak. Ha üresen marad, az összes távoli cím megengedett.", @@ -630,7 +630,7 @@ "ChannelAccessHelp": "Válaszd ki a megosztani kívánt csatornákat a felhasználóval. A rendszergazdák a metaadatkezelő segítségével szerkeszthetik az összes csatornát.", "ChannelNameOnly": "Csak {0} csatorna", "ChannelNumber": "Csatorna száma", - "CinemaModeConfigurationHelp": "A Cinema Mode igazi mozi élményt nyújt előzetessel és egyedi intróval a film vetítése előtt.", + "CinemaModeConfigurationHelp": "A Mozi mód igazi mozi élményt nyújt előzetessel és egyedi intróval a film vetítése előtt.", "ColorSpace": "Színtér", "ColorTransfer": "Színátvitel", "Composer": "Zeneszerző", @@ -663,13 +663,13 @@ "Disconnect": "Szétkapcsol", "DisplayInMyMedia": "Megjelenítés a kezdőképernyőn", "DisplayInOtherHomeScreenSections": "Megjelenítés a kezdőképernyőn, mint például a Legújabb Média, és a Folyamatban lévő filmek", - "DisplayModeHelp": "Válaszd ki a Jellyfin-n futó képernyő típusát.", + "DisplayModeHelp": "Válaszd ki a használni kívánt elrendezést.", "DoNotRecord": "Ne rögzítsen", "DownloadsValue": "{0} letöltés", "DrmChannelsNotImported": "A csatornák DRM-el nem kerülnek importálásra.", "DropShadow": "Árnyék", "EasyPasswordHelp": "Az egyszerű PIN kódot az offline hozzáféréshez használják a támogatott kliens alkalmazásokban, valamint hálózaton belüli bejelentkezéshez is használható.", - "EnableCinemaMode": "Cinema Mode", + "EnableCinemaMode": "Mozi mód", "EnableColorCodedBackgrounds": "Színkódolt háttérképek", "EnableDisplayMirroring": "Kijelző tükrözés", "EnableExternalVideoPlayers": "Külső videolejátszók", @@ -758,7 +758,7 @@ "HeaderMusicQuality": "Zene minősége", "HeaderNewApiKey": "Új API kulcs", "HeaderNewDevices": "Új eszközök", - "HeaderNextEpisodePlayingInValue": "Következő epizód lejátszása {0} múlva", + "HeaderNextEpisodePlayingInValue": "A következő epizód {0} múlva automatikusan elindul", "HeaderNextVideoPlayingInValue": "Következő videó lejátszása {0}", "HeaderOtherItems": "Egyéb elemek", "HeaderPasswordReset": "Jelszó visszaállítása", @@ -786,10 +786,10 @@ "MediaInfoCodecTag": "Kódek címke", "Photos": "Fényképek", "Playlists": "Lejátszási listák", - "Shows": "Műsorok", + "Shows": "Sorozatok", "Songs": "Dalok", "ValueSpecialEpisodeName": "Special - {0}", - "EnableThemeVideos": "Videók témák", + "EnableThemeVideos": "Videótémák", "EnableThemeVideosHelp": "Videó témájának lejátszása háttérben a könyvtár böngészése közben.", "HeaderBlockItemsWithNoRating": "Blokkolja azokat az elemeket amelyek tiltott, vagy nem felismerhető minősítésűek:", "HeaderSchedule": "Ütemezés", @@ -797,7 +797,7 @@ "HeaderSpecialEpisodeInfo": "Speciális epizód információ", "HeaderStartNow": "Indítás most", "HeaderStopRecording": "Felvétel leállítása", - "HeaderSubtitleAppearance": "Felirat megjelenés", + "HeaderSubtitleAppearance": "Felirat kinézete", "HeaderSubtitleProfile": "Felirat profil", "HeaderSubtitleProfiles": "Feliratok profilok", "HeaderSubtitleProfilesHelp": "A feliratprofilok leírják a készülék által támogatott feliratformátumokat.", @@ -853,7 +853,6 @@ "LabelCustomCertificatePath": "Egyéni SSL tanúsítvány elérési útvonala:", "LabelCustomCss": "Egyedi CSS:", "LabelCustomCssHelp": "Egyedi CSS stílusok alkalmazása a webes felülethez.", - "LabelCustomizeOptionsPerMediaType": "A média típusának testreszabása:", "LabelDeathDate": "Halálának dátuma:", "LabelDefaultScreen": "Alapértelmezett képernyő:", "LabelDefaultUser": "Alapértelmezett felhasználó:", @@ -869,7 +868,7 @@ "LabelArtistsHelp": "Ha több van használd a következő elválasztót ;", "LabelDisplayCollectionsView": "Jelenítse meg a Gyűjtemények menüpontot a filmgyűjtemények megjelenítéséhez", "LabelDisplayCollectionsViewHelp": "Ez külön menüpontot hoz létre a filmgyűjtemények megjelenítéséhez. Gyűjtemény létrehozásához kattints jobb gombbal vagy kattints a három pontra bármelyik filmen, és válaszd a 'Hozzáadás gyűjteményhez' lehetőséget. ", - "LabelEnableAutomaticPortMapHelp": "A szerver az UPnP segítségével a routeren megpróbálja automatikusan átirányítani a nyilvános portot a helyi portra. Előfordulhat, hogy egyes router modelleken ez nem működik. A módosítások újraindítás után lépnek életbe.", + "LabelEnableAutomaticPortMapHelp": "A szerver az UPnP segítségével a routeren megpróbálja automatikusan átirányítani a nyilvános portot a helyi portra. Előfordulhat, hogy egyes router modellek, vagy hálózati konfigurációk esetén ez nem működik. A módosítások újraindítás után lépnek életbe.", "LabelEnableBlastAliveMessagesHelp": "Engedélyezd ezt ha a szerver nem észleli megbízhatóan a hálózat más UPnP-eszközeit.", "LabelEnableDlnaClientDiscoveryInterval": "Kliens felderítési intervallum (másodperc)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "A Jellyfin által végrehajtott SSDP keresések időtartamát határozza meg másodpercben.", @@ -934,7 +933,7 @@ "LabelNewName": "Új név:", "LabelNewsCategories": "Hírek kategóriái:", "LabelNumber": "Szám:", - "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a Jellyfin alkalmazások más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz.", + "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a Jellyfin alkalmazások más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0{ vagy {1}.", "LabelPasswordConfirm": "Jelszó (megerősítés):", "LabelPlaceOfBirth": "Születési hely:", "LabelPostProcessor": "A feldolgozás utáni alkalmazás:", @@ -1081,11 +1080,11 @@ "NoNewDevicesFound": "Nem található új eszköz. Új tuner hozzáadásához zárd be ezt a párbeszédablakot és add meg kézzel az eszköz adatait.", "NoNextUpItemsMessage": "Nem található. Kezdj el nézni műsorokat!", "NoPluginConfigurationMessage": "Ez a bővítmény nem rendelkezik konfigurációs beállításokkal.", - "NoSubtitles": "Nincs felirat", + "NoSubtitles": "Nincs", "NoSubtitlesHelp": "A feliratok alapértelmezés szerint nem lesznek betöltve. Lejátszás közben kézzel is bekapcsolhatók.", "Off": "Ki", "OneChannel": "Egy csatorna", - "OnlyImageFormats": "Csak képformátumok (VOBSUB, PGS, SUB stb.)", + "OnlyImageFormats": "Csak képformátumok (VOBSUB, PGS, SUB)", "Option3D": "3D", "OptionAlbum": "Album", "OptionAlbumArtist": "Album előadó", @@ -1128,7 +1127,7 @@ "AddToPlayQueue": "Hozzáadás a lejátszási sorhoz", "AllowHWTranscodingHelp": "Lehetővé teszi a tuner számára, hogy át tudja kódolni a streameket valós időben. Ez segíthet csökkenteni a Szerver által igényelt átkódolást.", "AllowOnTheFlySubtitleExtraction": "Felirat kinyerésének engedélyezése valós időben", - "MessageNoTrailersFound": "Nincsenek előzetesek. Telepítsd a Trailer csatornát, hogy javítsd a filmélményt az internetes előzetesek könyvtárának hozzáadásával.", + "MessageNoTrailersFound": "Telepítsd a Trailer csatornát, hogy javítsd a filmélményt az internetes előzetesek könyvtárának hozzáadásával.", "OptionEnableM2tsMode": "M2ts mód engedélyezése", "OptionEnableM2tsModeHelp": "Engedélyezze az m2ts módot amikor mpegts kódolás történik.", "OptionEnded": "Befejezett", @@ -1197,11 +1196,10 @@ "RecordingScheduled": "A rögzítés ütemezett.", "Recordings": "Felvételek", "RefreshQueued": "Frissítés sorba állítva.", - "RemoveFromCollection": "Gyűjteményből eltávolítani", + "RemoveFromCollection": "Törlés a gyűjteményből", "RemoveFromPlaylist": "Lejátszási listáról eltávolítani", "RepeatEpisodes": "Epizódok ismétlése", "RequiredForAllRemoteConnections": "Minden távoli kapcsolathoz szükséges", - "RestartPleaseWaitMessage": "Kérlek várj, amíg a Jellyfin Szerver leáll és újraindul. Ez eltarthat egy-két percig.", "ResumeAt": "Folytatás: {0}", "RunAtStartup": "Futtassa indításkor", "SaveSubtitlesIntoMediaFolders": "Mentse a feliratokat a média mappákba", @@ -1254,7 +1252,7 @@ "UserProfilesIntro": "A Jellyfin beépített támogatást tartalmaz a felhasználói profilokhoz lehetővé téve, hogy minden felhasználó rendelkezzen saját megjelenítési beállításokkal, lejátszási és szülői felügyelettel.", "ValueAlbumCount": "{0} album", "ValueConditions": "Feltételek: {0}", - "ValueDiscNumber": "Lemez {0}", + "ValueDiscNumber": "{0}. lemez", "ValueMinutes": "{0} perc", "ValueOneAlbum": "1 album", "ValueOneEpisode": "1 epizód", @@ -1302,7 +1300,7 @@ "AllowOnTheFlySubtitleExtractionHelp": "A beágyazott feliratokat ki lehet nyerni a videókból és elküldeni az alkalmazásoknak sima szöveg formátumba, hogy ne legyen átkódolás. Néhány eszközön ez hosszú ideig is eltarthat, valamint a videó lejátszás megakadhat az eltávolítási folyamat futása közben. Ezt kikapcsolva a beágyazott feliratok videó átkódolással beégetésre kerülnek azon kliens eszközökre melyek nem támogatják a külső feliratokat.", "Art": "ClearArt", "AuthProviderHelp": "Válaszd ki az azonosítási szolgáltatást amely ezen felhasználó jelszavának ellenőrzését valósítja meg.", - "BurnSubtitlesHelp": "Meghatározza, hogy a szervernek be kell-e égetnie a feliratot videó átkódolás esetén a felirat típusának függvényében. A beégetés elkerülésével a szerver teljesítménye javul. Válaszd az Auto lehetőséget a kép alapú feliratok (pl. VOBSUB, PGS, SUB/IDX, stb) és bizonyos ASS/SSA feliratok beégetéséhez.", + "BurnSubtitlesHelp": "Meghatározza, hogy a szervernek be kell-e égetnie a feliratot videó átkódolás esetén a felirat típusának függvényében. Ennek elkerülésével a szerver teljesítménye javul. Válaszd az Auto lehetőséget a kép alapú feliratok (pl. VOBSUB, PGS, SUB/IDX, stb.) és bizonyos ASS/SSA feliratok beégetéséhez.", "ButtonAddScheduledTaskTrigger": "Vezérlő Hozzáadása", "ButtonGuide": "Műsorújság", "ButtonRefreshGuideData": "Műsorújság Frissítése", @@ -1325,7 +1323,6 @@ "HeaderFavoriteVideos": "Kedvenc Videók", "HeaderGuideProviders": "TV műsorújság Szolgáltatók", "HeaderHome": "Kezdőlap", - "HeaderRestartingServer": "Szerver újraindítása", "HeaderUpcomingOnTV": "Következő TV műsorok", "ImportFavoriteChannelsHelp": "Ha engedélyezve van, csak a tuner eszközön kedvencként megjelölt csatornák kerülnek importálásra.", "LabelAlbumArtHelp": "A használandó PN érték az albumborítók esetében, mely a upnp:albumArtURI dlna:profileID tulajdonságában szerepel. Néhány eszköz meghatározott értéket vár el, függetlenül a kép méretétől.", @@ -1394,8 +1391,6 @@ "ButtonSplit": "Szétvág", "Absolute": "Abszolút", "LabelSkipIfAudioTrackPresentHelp": "Vedd ki a pipát, ha minden videóhoz szeretnél feliratot az audio nyelvétől függetlenül.", - "EnableFastImageFadeInHelp": "Gyorsabb előtűnés animáció a betöltött képekhez", - "EnableFastImageFadeIn": "Gyors kép-előtűnés", "SubtitleOffset": "Felirat eltolása", "SeriesDisplayOrderHelp": "Rakd sorba az epizódokat az adásba kerülésük dátuma, a DVD sorszám, vagy az abszolút számozás szerint.", "SelectAdminUsername": "Kérjük válassz felhasználónevet az adminisztrátor fiók számára.", @@ -1415,7 +1410,7 @@ "OptionBlockChannelContent": "Internetes csatornatartalmak", "OptionBanner": "Banner", "OnlyForcedSubtitlesHelp": "Csak a kényszerítettnek jelölt feliratok fognak megjelenni.", - "OnlyForcedSubtitles": "Csak kényszerített feliratok", + "OnlyForcedSubtitles": "Csak kényszerített", "Normal": "Normális", "MusicVideo": "Videoklip", "MusicLibraryHelp": "Nézd meg a {0}zenék elnevezési útmutatóját{1}.", @@ -1466,13 +1461,120 @@ "LabelParentNumber": "Szülő száma:", "LabelMetadataReadersHelp": "Rangsorold az előnyben részesített metaadat forrásokat. Az a forrás kerül sorsolásra, amelyben először találunk információt.", "LabelLineup": "Felhozatal:", - "LabelBaseUrlHelp": "Ide hozzáadhatsz egy egyéni alkönyvtárat, hogy a szerverhez egyedibb URL-címről férj hozzá.", + "LabelBaseUrlHelp": "Egyedi alkönyvtárat ad hozzá a szervered URL címéhez. Például: http://pelda.com/<alapurl>", "ErrorPleaseSelectLineup": "Kérjük, válassz ki egy felhozatalt, és próbáld újra. Ha nem állnak rendelkezésre felsorolások, akkor ellenőrizd, hogy helyes-e felhasználóneved, jelszavad és irányítószámod.", "ErrorAddingListingsToSchedulesDirect": "Hiba történt a felhozatal hozzáadása közben a Schedules Direct fiókhoz. A Schedules Direct csak korlátozott számú fiók hozzáadását támogatja. Lehetséges, hogy be kell jelentkezned a Schedules Direct weboldalán és eltávolítani néhány más listát a fiókodról mielőtt továbblépsz.", "DeviceAccessHelp": "Ez csak azokra az eszközökre alkalmazható, amelyek egyedileg vannak azonosítva és nem gátolják meg a böngészőből való elérést. A felhasználói eszközök kiszűrése meg fogja akadályozni az új eszközök használatát addig, amíg itt nem engedélyezed őket.", - "PlaybackErrorNoCompatibleStream": "Hiba történt a kliens felmérése közben és a szerver nem küld kompatibilis formátumot az eszközre.", + "PlaybackErrorNoCompatibleStream": "Ez a kliens nem kompatibilis ezzel a médiával és a szerver nem tud kompatibilis streamet küldeni az eszközre.", "AllowFfmpegThrottlingHelp": "Ha az átkódolás vagy remux eléggé előtöltődött a jelenlegi lejátszási pozícióhoz képest, ez megállítja a folyamatot, hogy kevesebb erőforrást vegyen igénybe. Ez akkor hasznos, ha ritkán ugrálsz előre a lejátszott videókban. Kapcsold ki, ha lejátszási problémákba ütközöl.", "AllowFfmpegThrottling": "Átkódolás visszafogása", - "PreferEmbeddedEpisodeInfosOverFileNames": "Inkább a beágyazott epizódokra vonatkozó információkat részesítse előnyben a fájlnevekkel szemben", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Ez a beágyazott metaadatok epizódinformációit használja, ha rendelkezésre állnak." + "PreferEmbeddedEpisodeInfosOverFileNames": "Az epizódból elérhető beágyazott információkat használja inkább, fájlnevek helyett", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Ez az epizóddal kapcsolatos beágyazott metaadatokat használja, ha azok elérhetőek.", + "OnApplicationStartup": "Az alkalmazás indításakor", + "EveryXHours": "{0} óránként", + "EveryHour": "Óránként", + "EveryXMinutes": "{0} percenként", + "OnWakeFromSleep": "Alvó módból visszatéréskor", + "WeeklyAt": "Minden {0} ekkor: {1}", + "DailyAt": "Naponta ekkor: {0}", + "LastSeen": "Utoljára elérhető {0}", + "PersonRole": "mint {0}", + "ListPaging": "{0}-{1} / {2}", + "WriteAccessRequired": "A Jellyfin Szerver írási jogosultságot igényel ehhez a könyvtárhoz. Kérjük, ellenőrizd, hogy van-e jogod írni ide, majd próbáld újra.", + "PathNotFound": "Az elérési út nem található. Kérjük, ellenőrizd, hogy az elérési út megfelelő-e, majd próbáld újra.", + "Track": "Szám", + "Season": "Évad", + "Person": "Személy", + "OtherArtist": "Más előadók", + "Movie": "Film", + "Episode": "Epizód", + "ClientSettings": "Kliens beállítások", + "BoxSet": "Dobozos kiadások", + "Artist": "Előadó", + "AlbumArtist": "Album előadó", + "Album": "Album", + "LabelLibraryPageSizeHelp": "Az oldalnként megmutatott elemek száma. Nullára állítva a lapozási funkció ki lesz kapcsolva.", + "LabelLibraryPageSize": "Könyvtár oldalméret:", + "LabelDeinterlaceMethod": "Deinterlacing mód:", + "DeinterlaceMethodHelp": "Válassza ki a váltottsorosság megszűntetéséhez használandó módszert a váltottsoros tartalmak transzkódolásakor.", + "UnsupportedPlayback": "Jellyfin nem tud DRM-titkosított tartalmak dekriptálására, ettől függetlenül a lejátszással mindig megpróbálkozik. Néhány fájl emiatt teljesen fekete képernyőt ad, amely vagy a titkosítás miatt van, vagy nem olyan nem támogatott tartalmak miatt, mint az interaktív címek.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "ReleaseGroup": "Kiadócsoport", + "MessageUnauthorizedUser": "Jelenleg nincs jogosultságod a szerverhez való hozzáféréshez. Kérjük, lépj kapcsolatba az adminisztrátorral további információkért!", + "ButtonTogglePlaylist": "Lejátszási listák", + "ButtonToggleContextMenu": "Továbbiak", + "Filter": "Szűrés", + "New": "Új", + "HeaderFavoritePlaylists": "Kedvenc lejátszási listák", + "ApiKeysCaption": "A jelenleg engedélyezett API kulcsok listája", + "LabelNightly": "Éjszakai", + "LabelStable": "Stabil", + "LabelChromecastVersion": "Chromecast verzió", + "LabelEnableHttpsHelp": "Engedélyezi a kiszolgálónak a kommunikációt HTTPS protokollon keresztül. Érvényes tanúsítványt is be kell állítani az érvénybe léptetéshez.", + "LabelRequireHttpsHelp": "Bekapcsolást követően minden egyes HTTP-kérést átirányít HTTPS protokollra. A már meglévő HTTPS kéréseket nem módosítja.", + "LabelRequireHttps": "HTTPS megkövetelése", + "LabelEnableHttps": "HTTPS engedélyezése", + "HeaderServerAddressSettings": "Kiszolgáló Címének Beállításai", + "HeaderRemoteAccessSettings": "Távoli Hozzáférés Beállításai", + "HeaderHttpsSettings": "HTTPS Beállítások", + "TabDVR": "DVR", + "HeaderDVR": "DVR", + "SaveChanges": "Változtatások mentése", + "MessageSyncPlayGroupWait": "{0} pufferel…", + "MessageSyncPlayUserLeft": "{0} elhagyta a csoportot.", + "MessageSyncPlayUserJoined": "{0} csatlakozott a csoporthoz.", + "MessageSyncPlayDisabled": "SyncPlay letiltva.", + "MessageSyncPlayEnabled": "SyncPlay engedélyezve.", + "LabelSyncPlayAccess": "SyncPlay hozzáférés", + "LabelSyncPlayAccessCreateAndJoinGroups": "A felhasználó létrehozhat csoportokat és csatlakozhat hozzájuk", + "LabelSyncPlayLeaveGroupDescription": "SyncPlay letiltása", + "LabelSyncPlayLeaveGroup": "Csoport elhagyása", + "LabelSyncPlayNewGroupDescription": "Új csoport létrehozása", + "LabelSyncPlayNewGroup": "Új csoport", + "LabelSyncPlaySyncMethod": "Szinkronizálási mód:", + "MillisecondsUnit": "ms", + "HeaderSyncPlayEnabled": "SyncPlay engedélyezve", + "HeaderSyncPlaySelectGroup": "Csatlakozás csoporthoz", + "SyncPlayAccessHelp": "Válaszd ki, hogy ez a felhasználó milyen szinten férhet hozzá a SyncPlay funkcióhoz. A SyncPlay lehetőséget biztosít a lejátszások közötti szinkronizációra.", + "MessageSyncPlayErrorMedia": "Nem sikerült a SyncPlay engedélyezése! Média hiba.", + "MessageSyncPlayErrorMissingSession": "A SyncPlay lejátszása sikertelen! Hiányzó munkamenet.", + "MessageSyncPlayErrorNoActivePlayer": "Nem található aktív lejátszó. A SyncPlay letiltásra került.", + "MessageSyncPlayErrorAccessingGroups": "Hiba történt a csoportok listájának betöltésekor.", + "MessageSyncPlayLibraryAccessDenied": "A tartalomhoz való hozzáférés korlátozva van.", + "MessageSyncPlayJoinGroupDenied": "A SyncPlay használatához jogosultság szükséges.", + "MessageSyncPlayCreateGroupDenied": "Jogosultság szükséges a csoportok létrehozásához.", + "MessageSyncPlayGroupDoesNotExist": "Nem sikerült csatlakozni a csoporthoz, mivel az nem létezik.", + "MessageSyncPlayPlaybackPermissionRequired": "Lejátszási jogosultság szükséges.", + "MessageSyncPlayNoGroupsAvailable": "Nincsenek elérhető csoportok. Először kezdj el lejátszani valamit.", + "LabelSyncPlayAccessNone": "Letiltva ennél a felhasználónál", + "LabelSyncPlayAccessJoinGroups": "A felhasználó csoportokhoz való csatlakozásának engedélyezése", + "LabelSyncPlayPlaybackDiff": "Lejátszási időkülönbség:", + "LabelSyncPlayTimeOffset": "Időeltolás a szerverhez képest:", + "EnableDetailsBannerHelp": "Megjelenít egy banner képet a részletes információoldal tetején.", + "EnableDetailsBanner": "Banner a részletes oldalon", + "EnableBlurhashHelp": "A még betöltés alatt álló képek helyén egy elmosódott helyettesítő képet jelenít meg", + "EnableBlurhash": "Elmosódott helyettesítőképek engedélyezése", + "ShowMore": "Továbbiak megtekintése", + "ShowLess": "Kevesebb mutatása", + "ButtonCast": "Vetítés", + "ButtonSyncPlay": "SyncPlay", + "MessageNoGenresAvailable": "Engedélyezz néhány metaadat szolgáltatót, hogy műfaj adatokat tölthess le az internetről.", + "EnableFasterAnimationsHelp": "Gyorsabb animációk és áttűnések használata", + "EnableFasterAnimations": "Gyorsabb animációk", + "EnableDecodingColorDepth10Vp9": "10 bites hardveres dekódolás engedélyezése VP9-hez", + "EnableDecodingColorDepth10Hevc": "10 bites hardveres dekódolás engedélyezése HEVC-hez", + "TabRepositories": "Tárolók", + "MessageAddRepository": "Ha új tárolót szeretnél hozzáadni, kattints a gombra a fejlécben, és add meg a szükséges adatokat.", + "LabelRepositoryNameHelp": "Egy egyedi név, amivel megkülönböztetheted a tárolót a többi, a szervezhez hozzáadott tárolótól.", + "LabelRepositoryName": "Tároló neve", + "LabelRepositoryUrlHelp": "A hivatkozni kívánt tároló manifeszt helye.", + "LabelRepositoryUrl": "Tároló URL-je", + "HeaderNewRepository": "Új tároló", + "MessageNoRepositories": "Nincs tároló.", + "Writers": "Írók", + "ClearQueue": "Sor ürítése", + "StopPlayback": "Lejátszás leállítása", + "ViewAlbumArtist": "Album előadójának megtekintése", + "ButtonPlayer": "Lejátszó" } diff --git a/src/strings/id.json b/src/strings/id.json index 0c3ef799c0c..9d347b7dcc9 100644 --- a/src/strings/id.json +++ b/src/strings/id.json @@ -192,8 +192,8 @@ "AddToPlayQueue": "Tambah ke dalam antrean putar", "AddToCollection": "Tambah ke dalam koleksi", "AddItemToCollectionHelp": "Tambahkan item ke dalam koleksi melalui pencarian dan gunakan klik kanan atau ketuk menu untuk menambahkannya ke dalam koleksi.", - "AccessRestrictedTryAgainLater": "Akses sedang dibatas. Silakan coba kembali nanti.", - "Absolute": "Mutlak", + "AccessRestrictedTryAgainLater": "Akses sedang dibatasi. Mohon tunggu beberapa saat lagi", + "Absolute": "Absolut", "Songs": "Lagu", "Playlists": "Daftar putar", "ValueSpecialEpisodeName": "Spesial - {0}", @@ -204,5 +204,6 @@ "Alerts": "Peringatan", "AddedOnValue": "Ditambahkan {0}", "AllowFfmpegThrottling": "Transcode Tercekik", - "AllowOnTheFlySubtitleExtractionHelp": "Subtitle yang melekat di video dapat dikeluarkan dan dikirimkan kepada klien dalam bentuk text biasa dengan tujuan untuk menghalau terjadinya transcoding pada video. Pada beberapa system ini membutuhkan waktu yang lama dan dapat menyebabkan video playback menjadi terhenti dikarenakan proses ekstraksi. Non-aktifkan fitur ini untuk membiarkan subtitle langsung dilekatkan kepada video dengan cara transcoding ketika klien tidak mendukung fitur ini." + "AllowOnTheFlySubtitleExtractionHelp": "Subtitle yang melekat di video dapat dikeluarkan dan dikirimkan kepada klien dalam bentuk text biasa dengan tujuan untuk menghalau terjadinya transcoding pada video. Pada beberapa system ini membutuhkan waktu yang lama dan dapat menyebabkan video playback menjadi terhenti dikarenakan proses ekstraksi. Non-aktifkan fitur ini untuk membiarkan subtitle langsung dilekatkan kepada video dengan cara transcoding ketika klien tidak mendukung fitur ini.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/is-is.json b/src/strings/is-is.json index 70fc891193e..ff8f924e226 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -46,7 +46,7 @@ "OptionContinuing": "Heldur áfram", "OptionBlockTvShows": "Sjónvarpsþættir", "OptionBlockMusic": "Tónlist", - "OptionBlockTrailers": "Stiklur", + "OptionBlockTrailers": "Sýnishorn", "AllowOnTheFlySubtitleExtractionHelp": "Hægt er að sækja texta sem eru innbyggðir í myndaskrá og senda þá beint til notanda á textaformi til þess að sleppa við að umbreyta (transcode) myndaskránni. Í sumum tölvum getur þetta tekið langan tíma og valdið hikstum á meðan verið er að sækja textan. Afvirkjaðu þetta til þess að láta alla texta vera brennda inn í myndaskránna ef tæki notenda styður ekki að spila skránna beint.", "AccessRestrictedTryAgainLater": "Aðgangur bannaður í augnablikinu. Vinsamlegast reynið síðar.", "Actor": "Leikari", @@ -93,7 +93,7 @@ "AllowOnTheFlySubtitleExtraction": "Leyfa að taka út texta á meðan það er í keyrslu", "AllowRemoteAccess": "Leyfa fjartengingar í þennan Jellyfin þjón.", "AllowRemoteAccessHelp": "Ef þetta er afhakað, allar fjartengingar, þ.e. í gegnum internetið, verða bannaðar.", - "AlwaysPlaySubtitles": "Alltaf spila texta", + "AlwaysPlaySubtitles": "Spila alltaf", "AnyLanguage": "Öll tungumál", "AroundTime": "Um {0}", "Art": "List", @@ -165,8 +165,8 @@ "HeaderProfile": "Prófíll", "HeaderPeople": "Fólk", "HeaderPassword": "Lykilorð", - "HeaderLatestMovies": "Nýjustu Kvikmyndir", - "HeaderLatestEpisodes": "Nýjustu Þættirnir", + "HeaderLatestMovies": "Kvikmyndir, nýlega bætt við", + "HeaderLatestEpisodes": "Þættir, nýlega bætt við", "HeaderHome": "Heim", "HeaderFavoriteVideos": "Uppáhalds Myndbönd", "HeaderFavoriteMovies": "Uppáhalds Kvikmyndir", @@ -217,7 +217,7 @@ "ButtonViewWebsite": "Skoða vefsíðu", "ButtonUp": "Upp", "ButtonUninstall": "Fjarlægja", - "ButtonTrailer": "Stikla", + "ButtonTrailer": "Sýnishorn", "ButtonSubtitles": "Texti", "ButtonSort": "Flokka", "ButtonSignIn": "Innskráning", @@ -241,17 +241,9 @@ "ConfirmDeleteImage": "Eyða mynd?", "ButtonRename": "Endurnefna", "Sync": "Samstilla", - "Never": "", - "News": "", "ButtonRevoke": "Afturkalla", "ButtonRepeat": "Endurtaka", - "MusicArtist": "", - "MusicAlbum": "", - "No": "", "Monday": "Mánudagur", - "Name": "", - "Mute": "", - "MusicVideo": "", "ButtonRefresh": "Endurhlaða", "ButtonParentalControl": "Foreldraeftirlit", "ButtonOff": "Af", @@ -290,8 +282,271 @@ "AllowedRemoteAddressesHelp": "Kommu aðskilinn listi yfir ip tölur eða ip-númeramát fyrir net sem mega fjartengjas. Ef þetta er autt eru allar fjartengingar leyfðar.", "AllowHWTranscodingHelp": "Leyfa viðtæki að umbreyta straumi í rauntíma.Þetta getur minnkað álag á þjón.", "ValueSpecialEpisodeName": "Sérstakt - {0}", - "Shows": "Þættir", + "Shows": "Sýningar", "Playlists": "Spilunarlisti", "ButtonScanAllLibraries": "Skanna Öll Gagnasöfn", - "AllLibraries": "Öll gagnasöfn" + "AllLibraries": "Öll gagnasöfn", + "RefreshMetadata": "Endurhlaða lýsigögn", + "Refresh": "Endurhlaða", + "ReleaseDate": "Útgáfudagur", + "RememberMe": "Muna eftir mér", + "RepeatAll": "Endurtaka allt", + "Repeat": "Endurtaka", + "RemoveFromPlaylist": "Fjarlægja úr spilunarlista", + "RemoveFromCollection": "Fjarlægja úr safni", + "HeaderHttpsSettings": "HTTPS Stillingar", + "HeaderFavoriteBooks": "Uppáhalds Bækur", + "HeaderEditImages": "Breyta ljósmyndum", + "HeaderContinueListening": "Halda áfram að hlusta", + "ChannelNumber": "Númer rásar", + "ChannelNameOnly": "Aðeins rás {0}", + "ButtonSubmit": "Senda", + "ButtonShutdown": "Slökkva á netþjón", + "EnableThemeVideosHelp": "Spila þema myndbönd í bakgrunni þegar gagnasafn er skoðað.", + "EnableThemeSongsHelp": "Spila þema lög í bakgrunni þegar gagnasafn er skoðað.", + "FormatValue": "Snið: {0}", + "Genre": "Tegund", + "HeaderActiveDevices": "Virk Tæki", + "HeaderAddToCollection": "Bæta í Safn", + "HeaderAddUpdateImage": "Bæta við/uppfæra ljósmynd", + "HeaderAddToPlaylist": "Bæta við á Spilunarlista", + "HeaderAlert": "Viðvörun", + "HeaderAppearsOn": "Birtist á", + "HeaderChannels": "Rásir", + "HeaderDetectMyDevices": "Finna tækin mín", + "HeaderFavoritePeople": "Uppáhalds Fólk", + "HeaderFavoritePlaylists": "Uppáhalds spilunarlistar", + "HeaderFilters": "Síur", + "HeaderForgotPassword": "Gleymt lykilorð", + "HeaderForKids": "Fyrir Krakka", + "HeaderFrequentlyPlayed": "Oft Spilað", + "HeaderGenres": "Flokkar", + "HeaderLatestMusic": "Tónlist, nýlega bætt við", + "HeaderMetadataSettings": "Stillingar lýsigagna", + "HeaderMedia": "Margmiðlunarsafn", + "HeaderLiveTv": "Sjónvarp í beinni útsendingu", + "HeaderLoginFailure": "Innskráning Mistókst", + "HeaderMyDevice": "Tækið mitt", + "HeaderMusicVideos": "Tónlistarmyndbönd", + "HeaderMusicQuality": "Tónlistargæði", + "HeaderMovies": "Kvikmyndir", + "HeaderNewDevices": "Ný tæki", + "HeaderPasswordReset": "Endurstilla Lykilorð", + "HeaderPhotoAlbums": "Myndaalbúm", + "OnApplicationStartup": "Við ræsingu forrits", + "EveryXHours": "Hverjum {0} klukkustundum", + "EveryHour": "Hverja klukkustund", + "Episode": "Þáttur", + "EnableThemeVideos": "Þema myndbönd", + "EnableThemeSongs": "Þema lög", + "EnablePhotos": "Birta myndir", + "EnableHardwareEncoding": "Virkja vélbúnaðarkóðun", + "LabelScreensaver": "Skjáhvíla:", + "LabelRequireHttps": "Krefjast HTTPS", + "LabelReleaseDate": "Útgáfudagur:", + "LabelReasonForTranscoding": "Ástæða fyrir umkóðun:", + "LabelPlayMethod": "Spilunaraðferð:", + "LabelPlaylist": "Spilunarlisti:", + "LabelPlayer": "Spilari:", + "LabelPlayDefaultAudioTrack": "Spila sjálfgefna hljóðrás óháð tungumáli", + "LabelPlaceOfBirth": "Fæðingarstaður:", + "LabelPersonRoleHelp": "Dæmi: Ísbílstjóri", + "LabelPersonRole": "Hlutverk:", + "LabelPath": "Slóð:", + "LabelPasswordRecoveryPinCode": "PIN númer:", + "LabelPasswordConfirm": "Lykilorð (staðfesta):", + "LabelPassword": "Lykilorð:", + "LabelOverview": "Yfirlit:", + "LabelOriginalTitle": "Upphaflegur Titill:", + "LabelOriginalAspectRatio": "Upprunalegt skjáhlutfall:", + "LabelNumber": "Númer:", + "LabelNotificationEnabled": "Virkja þessa tilkynningu", + "LabelNewsCategories": "Frétta flokkar:", + "LabelNewPasswordConfirm": "Staðfesta nýtt lykilorð:", + "LabelNewPassword": "Nýtt lykilorð:", + "LabelNewName": "Nýtt nafn:", + "LabelName": "Nafn:", + "LabelLanNetworks": "LAN net:", + "MessageImageFileTypeAllowed": "Aðeins JPEG og PNG skrár eru studdar.", + "DrmChannelsNotImported": "Rásir með DRM verða ekki fluttar inn.", + "DoNotRecord": "Ekki taka upp", + "DisplayModeHelp": "Veldu útlit fyrir viðmótið.", + "DisplayMissingEpisodesWithinSeasons": "Birta þætti sem vantar inn í þáttaraðir", + "DisplayInMyMedia": "Birta á heimaskjá", + "Display": "Birta", + "Dislike": "Mislíka", + "Disabled": "Óvirkt", + "Directors": "Leikstjórar", + "DirectStreamHelp2": "Beint streymi á skrá notar mjög litið vinnsluafl án þess að tapa myndgæðum.", + "Descending": "Niður", + "DeleteImageConfirmation": "Ertu viss um að þú viljir eyða þessari mynd?", + "DefaultErrorMessage": "Villa varð við vinnslu beiðninnar. Reyndu aftur síðar.", + "DeathDateValue": "Dó: {0}", + "DatePlayed": "Dagsetning spilað", + "DateAdded": "Dagsetning bætt við", + "CriticRating": "Einkunn gagnrýnanda", + "CopyStreamURLError": "Villa varð við afritun vefslóðar.", + "CopyStreamURLSuccess": "Afrit af vefslóð tókst.", + "CopyStreamURL": "Afrita vefslóð streymis", + "Continuing": "Áframhaldandi", + "ConfirmEndPlayerSession": "Langar þig að slökkva á Jellyfin á {0}?", + "ConfirmDeletion": "Staðfesta eyðingu", + "ConfirmDeleteItem": "Ef þessari skrá er eytt verður hún fjarlægð úr bæði skráarkerfinu og miðlasafninu. Ertu viss um að þú viljir halda áfram?", + "Composer": "Tónskáld", + "ClientSettings": "Stillingar biðlara", + "ButtonTogglePlaylist": "Spilunarlisti", + "ButtonToggleContextMenu": "Meira", + "ButtonSplit": "Skipta", + "ButtonStop": "Stöðva", + "ButtonResetEasyPassword": "Endurstilla Easy PIN númer", + "ButtonRefreshGuideData": "Uppfæra sjónvarpsþáttagögn", + "Artist": "Listamaður", + "AllowFfmpegThrottling": "Takmarka Umkóðun", + "Album": "Plata", + "SettingsSaved": "Stillingar vistaðar.", + "Settings": "Stillingar", + "Series": "Seríur", + "SendMessage": "Senda skilaboð", + "SelectAdminUsername": "Veldu notandanafn fyrir stjórnanda aðganginn þinn.", + "Season": "Sería", + "SearchResults": "Leitarniðurstöður", + "SearchForSubtitles": "Leita að skjátexta", + "Search": "Leita", + "Screenshots": "Skjámyndir", + "Screenshot": "Skjámynd", + "ScanLibrary": "Skanna gagnasafn", + "SaveSubtitlesIntoMediaFolders": "Vista skjátexta í miðlamöppur", + "SaveChanges": "Vista breytingar", + "Save": "Vista", + "Saturday": "Laugardagur", + "RunAtStartup": "Keyra við ræsingu", + "Rewind": "Spóla til baka", + "AlbumArtist": "Höfundur plötu", + "OptionHasTrailer": "Sýnishorn", + "ViewArtist": "Skoða listamann", + "ValueSongCount": "{0} lög", + "ValueSeriesCount": "{0} Þáttaraðir", + "ValueSeconds": "{0} sekúndur", + "ValueOneSong": "1 lag", + "ValueOneSeries": "1 Þáttaröð", + "ValueOneMusicVideo": "1 tónlistarmyndband", + "ValueOneMovie": "1 kvikmynd", + "ValueOneEpisode": "1 þáttur", + "Up": "Upp", + "Unplayed": "Óspilað", + "UninstallPluginHeader": "Fjarlægja Viðbót", + "Tuesday": "Þriðjudagur", + "Transcoding": "Umkóðun", + "Trailers": "Sýnishorn", + "TitlePlayback": "Spilun", + "Thursday": "Fimmtudagur", + "ThemeVideos": "Þemu myndbönd", + "ThemeSongs": "Þemu lög", + "TellUsAboutYourself": "Segðu okkur frá sjálfum þér", + "TabUsers": "Notendur", + "TabUpcoming": "Væntanlegt", + "TabTranscoding": "Umkóðun", + "TabTrailers": "Sýnishorn", + "TabSuggestions": "Tillögur", + "TabSongs": "Lög", + "TabResumeSettings": "Halda áfram", + "TabProfile": "Prófíll", + "TabPlugins": "Viðbætur", + "TabOther": "Annað", + "TabNetworks": "Netkerfi", + "TabMyPlugins": "Mínar viðbætur", + "TabMusicVideos": "Tónlistarmyndbönd", + "TabMusic": "Tónlist", + "TabMovies": "Kvikmyndir", + "PleaseRestartServerName": "Vinsamlegast endurræstu Jellyfin netþjóninn - {0}.", + "Previous": "Fyrri", + "Premiere": "Frumsýning", + "Producer": "Framleiðandi", + "Quality": "Gæði", + "RecentlyWatched": "Nýlega horft á", + "RecommendationBecauseYouLike": "Af því að þér líkar {0}", + "RecommendationBecauseYouWatched": "Af því að þú horfðir á {0}", + "RecommendationDirectedBy": "Leikstýrt af {0}", + "SortChannelsBy": "Raða rásum eftir:", + "SortByValue": "Raða eftir {0}", + "Sort": "Raða", + "Filter": "Sía", + "New": "Nýtt", + "Shuffle": "Stokka", + "ShowYear": "Sýna ár", + "ShowTitle": "Sýna titil", + "Share": "Deila", + "LabelDefaultUser": "Sjálfgefinn notandi:", + "LabelDefaultScreen": "Sjálfgefinn skjár:", + "LabelDeathDate": "Dánardagur:", + "LabelDay": "Dagur:", + "LabelCurrentPassword": "Núverandi lykilorð:", + "LabelCollection": "Safn:", + "LabelChannels": "Rásir:", + "LabelCachePath": "Slóð skyndiminnis:", + "LabelCache": "Skyndiminni:", + "LabelBurnSubtitles": "Brenna skjátexta:", + "LabelBitrate": "Bitahraði:", + "LabelBirthYear": "Fæðingarár:", + "LabelBirthDate": "Fæðingardagur:", + "LabelAudio": "Hljóð", + "LabelArtists": "Listamenn:", + "LabelAppNameExample": "Dæmi: Sickbeard, Sonarr", + "LabelAll": "Allt", + "LabelAccessDay": "Vikudagur:", + "Kids": "Krakkar", + "Hide": "Fela", + "Help": "Hjálp", + "Home": "Heim", + "HeaderYears": "Ár", + "HeaderVideos": "Myndbönd", + "HeaderVideoQuality": "Myndgæði", + "HeaderUsers": "Notendur", + "HeaderUser": "Notandi", + "TabMetadata": "Lýsigögn", + "TabGenres": "Flokkar", + "TabFavorites": "Eftirlæti", + "TabEpisodes": "Þættir", + "TabDirectPlay": "Bein Spilun", + "TabAdvanced": "Ítarlegt", + "Sunday": "Sunnudagur", + "Suggestions": "Tillögur", + "Subtitles": "Skjátexti", + "LabelMetadataPath": "Slóð lýsigagna:", + "LabelMetadata": "Lýsigögn:", + "LabelMessageTitle": "Titill skilaboðs:", + "LabelMessageText": "Texti skilaboðs:", + "LabelMaxStreamingBitrate": "Hámarks gæði streymis:", + "LabelLineup": "Uppröðun:", + "LabelKodiMetadataDateFormat": "Snið útgáfudags:", + "LabelInternetQuality": "Gæði Internets:", + "LabelIconMaxWidth": "Hámarksbreidd tákns:", + "LabelHomeNetworkQuality": "Gæði heimanets:", + "LabelHardwareAccelerationType": "Hröðun vélbúnaðar:", + "LabelFriendlyName": "Vinalegt nafn:", + "LabelFormat": "Snið:", + "LabelForgotPasswordUsernameHelp": "Sláðu inn notandanafnið þitt, ef þú manst eftir því.", + "LabelFont": "Leturgerð:", + "LabelFolder": "Mappa:", + "LabelEpisodeNumber": "Þáttur númer:", + "LabelEnableRealtimeMonitor": "Virkja vöktun í rauntíma", + "LabelEnableHardwareDecodingFor": "Gera vélbúnaðarafkóðun virka fyrir:", + "LabelDroppedFrames": "Felldir rammar:", + "LabelDiscNumber": "Númer disks:", + "LabelDeviceDescription": "Lýsing tækis", + "LabelDashboardTheme": "Þema mælaborðs:", + "LabelCustomCss": "Sérsniðin CSS:", + "LabelCriticRating": "Einkunn gagnrýnanda:", + "LabelCorruptedFrames": "Skemmdir rammar:", + "LabelCancelled": "Hætt við", + "LabelAppName": "Heiti forrits", + "LabelAllowServerAutoRestart": "Leyfa netþjóni að endurræsa sig sjálfkrafa til þess að uppfæra sig", + "LabelAllowHWTranscoding": "Leyfa vélbúnaðarumkóðun", + "Label3DFormat": "3D snið:", + "HeaderIdentification": "Auðkenning", + "ConfirmDeleteItems": "Ef þessum skrám er eytt verða þær fjarlægðar úr bæði stýrikerfinu og miðlasafninu. Ertu viss um að þú viljir halda áfram?", + "CommunityRating": "Mat samfélagsins", + "ButtonStart": "Byrja", + "BoxSet": "Kassasett", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/it.json b/src/strings/it.json index cf9e06910e1..69919fbd76c 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -14,21 +14,21 @@ "Albums": "Album", "All": "Tutto", "AllChannels": "Tutti i canali", - "AllComplexFormats": "Tutti i formati complessi (ASS, SSA, VOBSUB, PGS, SUB / IDX, ecc.)", + "AllComplexFormats": "Tutti i formati complessi (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllEpisodes": "Tutti gli episodi", "AllLanguages": "Tutte le lingue", "AllLibraries": "Tutte le librerie", "AllowHWTranscodingHelp": "Abilita il sintonizzatore per codificare i flussi al volo. Ciò potrebbe contribuire a ridurre la transcodifica richiesta dal server.", "AllowOnTheFlySubtitleExtraction": "Consenti l'estrazione sottotitoli al volo", - "AllowOnTheFlySubtitleExtractionHelp": "I sottotitoli incorporati possono essere estratti dai video e consegnati ad applicazioni in testo semplice per evitare la transcodifica dei video. In alcuni sistemi questo può richiedere molto tempo e causare un rallentamento della riproduzione video durante il processo di estrazione. Disattivare questa opzione per avere i sottotitoli incorporati con la transcodifica video quando non sono supportati nativamente dal dispositivo client.", + "AllowOnTheFlySubtitleExtractionHelp": "I sottotitoli incorporati possono essere estratti dai video e consegnati ai client in testo semplice per evitare la transcodifica dei video. In alcuni sistemi questo può richiedere molto tempo e causare un rallentamento della riproduzione video durante il processo di estrazione. Disattivare questa opzione per avere i sottotitoli incorporati con la transcodifica video quando non sono supportati nativamente dal dispositivo client.", "AllowRemoteAccess": "Abilita connessioni remote a questo Server Jellyfin.", "AllowRemoteAccessHelp": "Se deselezionato, tutte le connessioni remote saranno bloccate.", "AllowedRemoteAddressesHelp": "Elenco separato da virgola di indirizzi IP o voci IP / maschera di rete per reti che potranno connettersi da remoto. Se lasciato vuoto, saranno consentiti tutti gli indirizzi remoti.", - "AlwaysPlaySubtitles": "Visualizza sempre i sottotitoli", + "AlwaysPlaySubtitles": "Riproduci sempre", "AlwaysPlaySubtitlesHelp": "I sottotitoli corrispondenti alla lingua preferita saranno caricati a prescindere dalla lingua dell'audio.", "AnyLanguage": "Qualsiasi lingua", "Anytime": "In qualsiasi momento", - "AroundTime": "Circa {0}", + "AroundTime": "Circa", "Artists": "Artisti", "AsManyAsPossible": "Tutto il possibile", "Ascending": "Crescente", @@ -40,12 +40,12 @@ "BirthDateValue": "Nato il: {0}", "BirthLocation": "Luogo di nascita", "BirthPlaceValue": "nato a: {0}", - "BookLibraryHelp": "Libri e audiolibri sono supportati. Rivedere {0}la guida ai nomi dei libri di Jellyfin{1}.", + "BookLibraryHelp": "Libri e audiolibri sono supportati. Rivedi la guida {0} ai nomi dei libri {1} di Jellyfin.", "Books": "Libri", "BoxRear": "Box (retro)", "Browse": "Esplora", "BrowsePluginCatalogMessage": "Sfoglia il catalogo dei Plugins.", - "BurnSubtitlesHelp": "Determina se il server deve applicare i sottotitoli quando si convertono video in base al formato dei sottotitoli. Evitando di applicare i sottotitoli migliorerà le prestazioni del server. Selezionare Auto per applicare formati basati sull'immagine (VOBSUB, PGS, SUB / IDX, ecc.) e alcuni sottotitoli ASS / SSA.", + "BurnSubtitlesHelp": "Determina se il server deve imprimere i sottotitoli quando i video vengono convertiti. Evitare ciò migliorerà di molto le prestazioni. Selezionare Auto per imprimere formati basati sull'immagine (VOBSUB, PGS, SUB, IDX, ...) e alcuni sottotitoli ASS o SSA.", "ButtonAdd": "Aggiungi", "ButtonAddMediaLibrary": "Aggiungi raccolta multimediale", "ButtonAddScheduledTaskTrigger": "Aggiungi operazione", @@ -93,7 +93,7 @@ "ButtonRename": "Rinomina", "ButtonRepeat": "Ripeti", "ButtonResetEasyPassword": "Resetta codice PIN", - "ButtonResetPassword": "Ripristina Password", + "ButtonResetPassword": "Reset Password", "ButtonRestart": "Riavvia", "ButtonResume": "Riprendi", "ButtonRevoke": "Revocare", @@ -175,7 +175,7 @@ "DisplayInOtherHomeScreenSections": "Mostra le sezioni della schermata home come gli ultimi media e continua a guardare", "DisplayMissingEpisodesWithinSeasons": "Visualizza gli episodi mancanti nelle stagioni", "DisplayMissingEpisodesWithinSeasonsHelp": "Questo deve anche essere abilitato per le librerie TV nella configurazione del server.", - "DisplayModeHelp": "Scegli il tipo di schermo su cui stai utilizzando Jellyfin.", + "DisplayModeHelp": "Seleziona lo stile del layout che vuoi per l'interfaccia.", "DoNotRecord": "Non registrare", "Down": "Giù", "Download": "Scarica", @@ -202,7 +202,7 @@ "EnableStreamLoopingHelp": "Abilita questo se gli streaming in diretta contengono solo pochi secondi di dati e devono essere costantemente richiesti. L'abilitazione di questa funzione quando non è servita può causare problemi.", "EnableThemeSongs": "Canzoni a tema", "EnableThemeSongsHelp": "Le canzoni a tema saranno riprodotte mentre visualizzi la tua libreria.", - "EnableThemeVideos": "VIdeo a tema", + "EnableThemeVideos": "Video a tema", "EnableThemeVideosHelp": "Riproduzione dei video a tema sullo sfondo mentre visualizzi la tua libreria.", "Ended": "Finito", "EndsAtValue": "Finirà alle {0}", @@ -264,7 +264,7 @@ "HeaderAddUser": "Aggiungi utente", "HeaderAdditionalParts": "Parti addizionali", "HeaderAdmin": "Admin", - "HeaderAlbumArtists": "Artisti dell' Album", + "HeaderAlbumArtists": "Artisti degli Album", "HeaderAlbums": "Album", "HeaderAlert": "Avviso", "HeaderAllowMediaDeletionFrom": "Abilita Eliminazione Media Da", @@ -461,7 +461,7 @@ "Images": "Immagini", "ImportFavoriteChannelsHelp": "Se abilitata, solo i canali che sono contrassegnati come preferiti sul dispositivo di sintonizzazione verranno importati.", "ImportMissingEpisodesHelp": "Se abilitato, le informazioni relative agli episodi mancanti saranno importate nel database di Jellyfin e mostrate all'interno di Serie e Stagioni. Questo può causare scansioni della libreria più lente.", - "InstallingPackage": "Installazione di {0}", + "InstallingPackage": "Installazione di {0} (versione {1})", "InstantMix": "Mix istantaneo", "ItemCount": "{0} elementi", "Items": "Elementi", @@ -551,7 +551,7 @@ "LabelEmbedAlbumArtDidl": "Inserisci le copertine degli Album in Didl", "LabelEmbedAlbumArtDidlHelp": "Alcuni dispositivi preferiscono questo metodo per ottenere le copertine degli album. Altri possono non riuscire a riprodurli con questa opzione abilitata.", "LabelEnableAutomaticPortMap": "Abilita mappatura automatica delle porte", - "LabelEnableAutomaticPortMapHelp": "Tenta di mappare automaticamente la porta pubblica sulla porta locale tramite UPnP. Questo potrebbe non funzionare con alcuni modelli di router. I cambiamenti non saranno applicati fino ad un riavvio del server.", + "LabelEnableAutomaticPortMapHelp": "Automaticamente inoltra le porte pubbliche del router sul quelle locali del server tramite UPnP. Potrebbe non funzionare con alcuni modelli di router. I cambiamenti non saranno applicati fino ad il riavvio del server.", "LabelEnableBlastAliveMessages": "Invia segnale di presenza", "LabelEnableBlastAliveMessagesHelp": "Attivare questa opzione se il server non viene rilevato in modo affidabile da altri dispositivi UPnP in rete.", "LabelEnableDlnaClientDiscoveryInterval": "Intervallo di ricerca dispositivi (secondi)", @@ -572,7 +572,7 @@ "LabelEvent": "Evento:", "LabelEveryXMinutes": "Tutti:", "LabelExtractChaptersDuringLibraryScan": "Estrarre immagini capitolo durante la scansione della libreria", - "LabelExtractChaptersDuringLibraryScanHelp": "Genera le immagini del capitolo quando i video vengono importati durante la scansione della libreria. Altrimenti verranno estratti durante l'operazione pianificata di estrazione delle immagini capitolo, permettendo la scansione della libreria più velocemente.", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera le immagini capitolo quando i video vengono importati durante la scansione della libreria. Alternativamente, verranno estratti durante l'operazione pianificata di estrazione delle immagini capitolo, permettendo la scansione della libreria più velocemente.", "LabelFailed": "Fallito", "LabelFileOrUrl": "File o URL:", "LabelFinish": "Finito", @@ -671,7 +671,7 @@ "LabelNumberOfGuideDays": "Numero di giorni per i quali scaricare i dati della guida:", "LabelNumberOfGuideDaysHelp": "Scaricando più giorni si avrà la possibilità di pianificare in anticipo più programmi e vedere più liste, ma il tempo di download si allungherà. 'Auto': MB sceglierà automaticamente in base al numero di canali.", "LabelOptionalNetworkPath": "Cartella condivisa (Opzionale):", - "LabelOptionalNetworkPathHelp": "Se questa cartella è condivisa sulla rete, fornendo il percorso di condivisione di rete si può consentire alle applicazioni Jellyfin su altri dispositivi di accedere direttamente ai file multimediali.", + "LabelOptionalNetworkPathHelp": "Se questa cartella è condivisa sulla rete, fornendo il percorso di condivisione di rete si può consentire alle applicazioni Jellyfin su altri dispositivi di accedere direttamente ai file multimediali. Ad esempio {0} oppure {1}.", "LabelOriginalAspectRatio": "Aspetto originale:", "LabelOriginalTitle": "Titolo originale:", "LabelOverview": "Trama:", @@ -861,7 +861,7 @@ "MessageNoAvailablePlugins": "Nessun plugin disponibile.", "MessageNoMovieSuggestionsAvailable": "Nessun suggerimento di film attualmente disponibile. Iniziare a guardare e valutare i vostri film, e poi tornare per i suggerimenti.", "MessageNoPluginsInstalled": "Non hai plugin installati.", - "MessageNoTrailersFound": "Nessun trailer trovato. Installa il canale dei Trailer per migliorare la tua esperienza cinematografica importando una libreria di trailer da internet.", + "MessageNoTrailersFound": "Installa il canale dei trailer per migliorare la tua esperienza cinematografica aggiungendo una libreria di trailer da internet.", "MessageNothingHere": "Non c'è niente qui.", "MessagePasswordResetForUsers": "I seguenti utenti havvo avuto le loro password resettate. Adesso possono accedere con i codici pin che sono stati utilizzati per eseguire il reset.", "MessagePlayAccessRestricted": "Le riproduzione di questi contenuti è bloccata. Per favore contatta il tuo amministratore del server per maggiori informazioni.", @@ -901,16 +901,16 @@ "NoNextUpItemsMessage": "Trovato niente. Inizia a guardare i tuoi programmi!", "NoPluginConfigurationMessage": "Questo Plugin non ha impostazioni da configurare.", "NoSubtitleSearchResultsFound": "Nessun risultato.", - "NoSubtitles": "Nessun Sottotitolo", + "NoSubtitles": "Nessuno", "NoSubtitlesHelp": "I sottotitoli non verranno caricati per impostazione predefinita.Possono essere ancora caricati manualmente durante la riproduzione.", "None": "Nessuno", "Normal": "Normale", "NumLocationsValue": "{0} cartelle", "Off": "Spento", "OneChannel": "Un canale", - "OnlyForcedSubtitles": "Solo i sottotitoli forzati", + "OnlyForcedSubtitles": "Solo forzati", "OnlyForcedSubtitlesHelp": "Solo i sottotitoli contrassegnati come forzati saranno caricati.", - "OnlyImageFormats": "Solo formati immagine (VOBSUB, PGS, SUB, ecc)", + "OnlyImageFormats": "Solo formati immagine (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Amministratori", "OptionAlbumArtist": "Artista Album", "OptionAllUsers": "Tutti gli utenti", @@ -947,8 +947,8 @@ "OptionCustomUsers": "Personalizza", "OptionDaily": "Giornaliero", "OptionDateAdded": "Aggiunto il", - "OptionDateAddedFileTime": "Utilizzare file di data di creazione", - "OptionDateAddedImportTime": "Utilizza la data scansionato in biblioteca", + "OptionDateAddedFileTime": "Utilizzare la data di creazione del file", + "OptionDateAddedImportTime": "Utilizza la data di scansione nella libreria", "OptionDatePlayed": "Visto il", "OptionDescending": "Decrescente", "OptionDisableUser": "Disabilita questo utente", @@ -995,7 +995,7 @@ "OptionMissingEpisode": "Episodi mancanti", "OptionMonday": "Lunedì", "OptionNameSort": "Nome", - "OptionNew": "Nuovo...", + "OptionNew": "Nuovo…", "OptionNone": "Nessuno", "OptionOnAppStartup": "All'avvio", "OptionOnInterval": "Su intervallo", @@ -1013,7 +1013,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Questo è necessario per alcuni dispositivi che non hanno l'avanzamento rapido che funziona bene.", "OptionRequirePerfectSubtitleMatch": "Scarica solo i sottotitoli che corrispondono perfettamente ai miei file video", "OptionRequirePerfectSubtitleMatchHelp": "La richiesta di una corrispondenza perfetta filtrerà i sottotitoli per includere solo quelli che sono stati testati e verificati con il file video esatto. Deselezionando questo aumenterà la probabilità che i sottotitoli vengono scaricati, ma aumenteranno le probabilità di testo sottotitolato impreciso o errato.", - "OptionResElement": "elemento res", + "OptionResElement": "res element", "OptionResumable": "Interrotto", "OptionRuntime": "Durata", "OptionSaturday": "Sabato", @@ -1035,14 +1035,14 @@ "OptionWeekly": "Settimanale", "OriginalAirDateValue": "Prima messa in onda (originale): {0}", "Overview": "Trama", - "PackageInstallCancelled": "Installazione di {0} annullata.", - "PackageInstallCompleted": "Installazione di {0} completa.", - "PackageInstallFailed": "Installazione di {0} fallita.", + "PackageInstallCancelled": "Installazione di {0} (versione {1}) annullata.", + "PackageInstallCompleted": "Installazione di {0} (versione {1}) completata.", + "PackageInstallFailed": "Installazione di {0} (versione {1}) fallita.", "ParentalRating": "Classificazione per genitori", "PasswordMatchError": "Le password non coincidono.", - "PasswordResetComplete": "la password è stata ripristinata.", - "PasswordResetConfirmation": "Sei sicuro di voler ripristinare la password?", - "PasswordResetHeader": "Ripristina Password", + "PasswordResetComplete": "Reset della password eseguito.", + "PasswordResetConfirmation": "Sicuro di voler eseguire il reset della password?", + "PasswordResetHeader": "Reset Password", "PasswordSaved": "Password salvata.", "People": "Attori", "PerfectMatch": "Corrispondenza perfetta", @@ -1076,7 +1076,7 @@ "Programs": "Programmi", "Quality": "Qualità", "QueueAllFromHere": "In coda tutto da qui in poi", - "Raised": "Sospeso", + "Raised": "Rilievo", "Rate": "Vota", "RecentlyWatched": "Visti di recente", "RecommendationBecauseYouLike": "Perché ti piace {0}", @@ -1105,7 +1105,6 @@ "ReplaceAllMetadata": "Sostituisci tutti i metadati", "ReplaceExistingImages": "Sovrascrivi immagini esistenti", "RequiredForAllRemoteConnections": "Richiesto per tutte le connessioni remote", - "RestartPleaseWaitMessage": "Per piacere aspetta mentre Jellyfin Server si arresta e riavvia. Questo può richiedere un minuto o due.", "ResumeAt": "Riprendi da {0}", "Rewind": "Riavvolgi", "RunAtStartup": "Esegui all'avvio", @@ -1157,7 +1156,7 @@ "SortChannelsBy": "Ordina canali per:", "SortName": "Nome ordinamento", "Sports": "Sport", - "StopRecording": "Ferma registrazione", + "StopRecording": "Interrompi registrazione", "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Queste impostazioni si applicano anche a qualsiasi riproduzione di Chromecast avviata da questo dispositivo.", "SubtitleAppearanceSettingsDisclaimer": "Queste impostazioni non si applicano a sottotitoli grafici (PGS, DVD, ecc.) o sottotitoli ASS/SSA che hanno i propri stili.", "SubtitleDownloadersHelp": "Abilita e classifica i tuoi downloader di sottotitoli preferiti in ordine di priorità.", @@ -1168,7 +1167,7 @@ "SystemDlnaProfilesHelp": "I profili di sistema sono in sola lettura. Le modifiche ad un profilo di sistema verranno salvate in un nuovo profilo personalizzato.", "TabAccess": "Accesso", "TabAdvanced": "Avanzato", - "TabAlbumArtists": "Artisti degli album", + "TabAlbumArtists": "Artisti degli Album", "TabAlbums": "Album", "TabArtists": "Artisti", "TabCatalog": "Catalogo", @@ -1202,7 +1201,7 @@ "TabProfiles": "Profili", "TabRecordings": "Registrazioni", "TabResponses": "Risposte", - "TabResumeSettings": "Ripristina", + "TabResumeSettings": "Riprendi", "TabScheduledTasks": "Operazioni Pianificate", "TabSeries": "Serie TV", "TabSettings": "Impostazioni", @@ -1312,9 +1311,8 @@ "HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteSongs": "Brani Preferiti", "HeaderFavoriteVideos": "Video Preferiti", - "HeaderFetcherSettings": "Impostazioni Fetcher", + "HeaderFetcherSettings": "Impostazioni del Fetcher", "HeaderImageOptions": "Opzioni Immagine", - "HeaderRestartingServer": "Riavvio Server", "Home": "Home", "LabelAlbum": "Album:", "LabelAudio": "Audio", @@ -1342,7 +1340,7 @@ "MediaInfoStreamTypeVideo": "Video", "MessageNoCollectionsAvailable": "Le collezioni ti consentono di fruire di raggruppamenti personalizzati di Film, Serie e Album. Clicca il tasto + per iniziare a creare collezioni.", "MessageNoServersAvailable": "Nessun server è stato trovato usando la ricerca automatica di server.", - "LabelBaseUrlHelp": "Puoi aggiungere una sottodirectory personalizzata qui per accedere al server da un URL più originale.", + "LabelBaseUrlHelp": "Aggiunge una cartella personalizzata all'URL del server, ad esempio http://example.com/<baseurl>", "OptionAlbum": "Album", "LabelPasswordResetProvider": "Provider per il Reset della Password:", "LabelServerName": "Nome del Server:", @@ -1355,7 +1353,7 @@ "OptionProtocolHls": "Streaming in Diretta HTTP", "OptionDownloadArtImage": "Art", "OptionMax": "Massimo", - "PasswordResetProviderHelp": "Scegli un Provider Reset Password da utilizzare quando questo utente richiede un reset della password", + "PasswordResetProviderHelp": "Scegli un Provider Reset Password da utilizzare quando questo utente ne richiede il reset", "PlaybackData": "Dati di Riproduzione", "TagsValue": "Tag: {0}", "Whitelist": "Lista bianca", @@ -1455,9 +1453,7 @@ "MessageConfirmAppExit": "Vuoi uscire?", "HeaderNavigation": "Navigazione", "CopyStreamURLError": "Si è verificato un errore nel copiare l'indirizzo.", - "EnableFastImageFadeInHelp": "Abilita la dissolvenza veloce per le immagini caricate", - "EnableFastImageFadeIn": "Dissolvenza immagine veloce", - "PlaybackErrorNoCompatibleStream": "C'era un problema con il profiling del client e il server non sta inviando un formato compatibile.", + "PlaybackErrorNoCompatibleStream": "Il client è incompatibile con il media e il server non sta inviando un formato compatibile.", "OptionForceRemoteSourceTranscoding": "Forza la transcodifica da fonti di media remoti (come LiveTV)", "NoCreatedLibraries": "Sembra che tu non abbia ancora creato delle librerie. {0}Vuoi crearne una adesso?{1}", "LabelVideoResolution": "Risoluzione video:", @@ -1467,5 +1463,114 @@ "LabelCorruptedFrames": "Frame corrotti:", "AskAdminToCreateLibrary": "Chiedi ad un amministratore di creare una libreria.", "AllowFfmpegThrottlingHelp": "Quando una transcodifica o un remux sono abbastanza avanti rispetto alla corrente posizione di riproduzione, pausa il processo così da consumare meno risorse. Questo è utile quando si guarda un video senza avanzare spesso durante la riproduzione. Disattiva questa opzione se stai avendo problemi di riproduzione.", - "AllowFfmpegThrottling": "Acceleratore Transcodifica" + "AllowFfmpegThrottling": "Acceleratore Transcodifica", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferisci le informazioni incorporate nell'episodio rispetto ai nomi dei file", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Questo utilizza le informazioni dell'episodio provenienti dai metadata incorporati, se disponibili.", + "ClientSettings": "Impostazioni del client", + "Album": "Album", + "OnWakeFromSleep": "Al risveglio", + "Person": "Persona", + "LabelDeinterlaceMethod": "Metodo di deinterlacciamento:", + "DeinterlaceMethodHelp": "Metodo di deinterlacciamento da usare durante la transcodifica.", + "Artist": "Artista", + "OnApplicationStartup": "All'avvio", + "EveryXHours": "Ogni {0} ore", + "EveryHour": "Ogni ora", + "EveryXMinutes": "Ogni {0} minuti", + "WeeklyAt": "Tutti i {0} alle {1}", + "DailyAt": "Tutti i giorni alle {0}", + "LastSeen": "Visto l'ultima volta {0}", + "PersonRole": "nel ruolo di {0}", + "ListPaging": "{0}-{1} di {2}", + "WriteAccessRequired": "Jellyfin Server richiede il permesso di scrittura su questa cartella. Verificare l'autorizzazione e riprovare.", + "PathNotFound": "Percorso non trovato. Assicurarsi che sia valido e riprovare.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Traccia", + "Season": "Stagione", + "OtherArtist": "Altri Artisti", + "Movie": "Film", + "LabelLibraryPageSizeHelp": "Numero di elementi presenti nella paginazione della libreria. Il valore 0 disabilita la paginazione.", + "LabelLibraryPageSize": "Elementi nella paginazione della libreria:", + "Episode": "Episodio", + "BoxSet": "Cofanetto", + "AlbumArtist": "Artisti dell'Album", + "ReleaseGroup": "Release Group", + "UnsupportedPlayback": "Jellyfin non è in grado di decriptare i contenuti protetti da DRM ma tutti i contenuti verranno tentati a prescindere, compresi quelli protetti. Alcuni file potrebbero apparire completamente neri a causa della crittografia o di altre funzionalità non supportate, come i titoli interattivi.", + "MessageUnauthorizedUser": "Non sei autorizzato ad accedere in questo momento al server. Contatta l'amministratore per ulteriori dettagli.", + "ButtonTogglePlaylist": "Playlist", + "ButtonToggleContextMenu": "Altro", + "HeaderFavoritePlaylists": "Playlist Favorite", + "Filter": "Filtro", + "New": "Nuovo", + "ApiKeysCaption": "Elenco chiavi API abilitate", + "LabelStable": "Stabile", + "LabelChromecastVersion": "Versione Chromecast", + "LabelRequireHttpsHelp": "Se selezionata, il server reindirizzerà tutte le richieste HTTP a HTTPS. Vale solo se il sever è configurato per l'ascolto in HTTPS.", + "LabelRequireHttps": "Richiede HTTPS", + "LabelEnableHttpsHelp": "Abilita il server all'ascolto sulla porta HTTPS configurata. Il certificato deve essere configurato e valido per l'abilitazione.", + "LabelEnableHttps": "Abilita HTTPS", + "HeaderServerAddressSettings": "Configurazione Indirizzo Server", + "HeaderRemoteAccessSettings": "Configurazione Access Remoto", + "HeaderHttpsSettings": "Configurazione HTTPS", + "TabDVR": "DVR", + "SaveChanges": "Salva modifiche", + "HeaderDVR": "DVR", + "LabelNightly": "Nightly", + "SyncPlayAccessHelp": "Selezionare il livello d'accesso di questo utente a SyncPlay che permetterà di riprodurre contemporaneamente su diversi dispositivi.", + "MessageSyncPlayErrorMedia": "Impossibile abilitare SyncPlay! Errore media.", + "MessageSyncPlayErrorMissingSession": "Impossibile abilitare SyncPlay! Sessione mancante.", + "MessageSyncPlayErrorNoActivePlayer": "Nessun player attivo. SyncPlay è stato disabilitato.", + "MessageSyncPlayErrorAccessingGroups": "Errore durante l'accesso alla lista dei gruppi.", + "MessageSyncPlayLibraryAccessDenied": "L'accesso a questo contenuto è negato.", + "MessageSyncPlayJoinGroupDenied": "E' Necessario il permesso per l'utilizzo di SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "E' necessario il permesso di creazione di un gruppo.", + "MessageSyncPlayGroupDoesNotExist": "Impossibile unirsi al gruppo perchè non esiste.", + "MessageSyncPlayPlaybackPermissionRequired": "Permesso di riproduzione necessario.", + "MessageSyncPlayNoGroupsAvailable": "Nessun gruppo disponibile. Inizia a riprodurre qualcosa.", + "MessageSyncPlayGroupWait": "{0} sta bufferizzando...", + "MessageSyncPlayUserLeft": "{0} ha lasciato il gruppo.", + "MessageSyncPlayUserJoined": "{0} si è unito al gruppo.", + "MessageSyncPlayDisabled": "SyncPlay disabilitato.", + "MessageSyncPlayEnabled": "SyncPlay abilitato.", + "LabelSyncPlayAccess": "Accesso SyncPlay", + "LabelSyncPlayAccessNone": "Disabilitato per questo utente", + "LabelSyncPlayAccessJoinGroups": "Permetti all'utente di unirsi ai gruppi", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permetti all'utente di creare e unirsi ai gruppi", + "LabelSyncPlayLeaveGroupDescription": "Disabilita SyncPlay", + "LabelSyncPlayLeaveGroup": "Lascia il gruppo", + "LabelSyncPlayNewGroupDescription": "Crea un nuovo gruppo", + "LabelSyncPlayNewGroup": "Nuovo gruppo", + "LabelSyncPlaySyncMethod": "Metodo Sync:", + "LabelSyncPlayPlaybackDiff": "Differenza oraria nella riproduzione:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Differenza temporale con il server:", + "HeaderSyncPlayEnabled": "SyncPlay abilitato", + "HeaderSyncPlaySelectGroup": "Unisciti a un gruppo", + "EnableDetailsBannerHelp": "Mostra il banner nella parte superiore della pagina di dettaglio dell'elemento.", + "EnableDetailsBanner": "Banner Dettagli", + "EnableBlurhashHelp": "Le immagini ancora da caricare saranno mostrate inizialmente sfocate", + "EnableBlurhash": "Abilita i segnaposto sfocati per le immagini", + "ShowMore": "Mostra di più", + "ShowLess": "Mostra meno", + "ButtonCast": "Cast", + "ButtonSyncPlay": "SyncPlay", + "EnableFasterAnimationsHelp": "Utilizza animazioni e transizioni veloci", + "EnableFasterAnimations": "Animazioni veloci", + "EnableDecodingColorDepth10Vp9": "Abilita la decodifica hardware 10-Bit per VP9", + "EnableDecodingColorDepth10Hevc": "Abilita la decodifica hardware 10-Bit per HEVC", + "TabRepositories": "Repository", + "MessageNoGenresAvailable": "Abilita un metadata provider per recuperare i generi da internet.", + "MessageAddRepository": "Cliccare sul bottone vicino all'header se si intende aggiungere un nuovo repository.", + "LabelRepositoryNameHelp": "Nome personalizzato per distinguere questo repository dagli altri sul tuo server.", + "LabelRepositoryName": "Nome Repository", + "LabelRepositoryUrlHelp": "URL del repository manifest che si vuole includere.", + "LabelRepositoryUrl": "URL Repository", + "HeaderNewRepository": "Nuovo Repository", + "MessageNoRepositories": "Nessun repository.", + "ButtonPlayer": "Player", + "ViewAlbumArtist": "Visualizza artista dell'album", + "Writers": "Scrittori", + "ClearQueue": "Svuota la coda", + "StopPlayback": "Interrompi riproduzione" } diff --git a/src/strings/ja.json b/src/strings/ja.json index 4a15d798c31..60c814843a6 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -14,7 +14,7 @@ "Alerts": "アーティスト", "All": "すべて", "AllChannels": "すべてのチャンネル", - "AllComplexFormats": "すべての複雑なフォーマット (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "すべての複雑なフォーマット (ASS, SSA, VOBSUB, PGS, SUB, IDX など)", "AllEpisodes": "すべてのエピソード", "AllLanguages": "すべての言語", "AllLibraries": "すべてのライブラリ", @@ -22,15 +22,15 @@ "AllowMediaConversion": "メディアの変換を許可する", "AllowMediaConversionHelp": "メディア変換機能へのアクセスを許可もしくは、拒否します。", "AllowOnTheFlySubtitleExtraction": "字幕の抽出の許可/拒否", - "AllowOnTheFlySubtitleExtractionHelp": "埋め込まれた字幕は、ビデオのトランスコーディングを防ぐために、ビデオから抽出してプレーンテキストでクライアントに配信できます。 システムによっては、これに時間がかかり、抽出中にビデオの再生が止まることがあります。 これを無効にし、埋め込み字幕がクライアントデバイスでネイティブにサポートされていない場合、ビデオのトランスコーディングが強制されます。", + "AllowOnTheFlySubtitleExtractionHelp": "埋め込まれた字幕は、ビデオのトランスコードを防ぐために、ビデオから抽出してプレーンテキストでクライアントに配信できます。 システムによっては、これに時間がかかり、抽出中にビデオの再生が止まることがあります。 これを無効にし、埋め込み字幕がクライアントデバイスでネイティブにサポートされていない場合、ビデオのトランスコードが強制されます。", "AllowRemoteAccess": "Jellyfin Serverへのリモート接続の許可。", "AllowRemoteAccessHelp": "チェックが入っていない場合すべてのリモート接続をブロックします。", "AllowedRemoteAddressesHelp": "リモート接続を許可するネットワークのIPアドレスまたはIPアドレス/マスク長 空白のままにすると、すべてのリモートアドレスが許可されます。", - "AlwaysPlaySubtitles": "字幕を常に有効にする", + "AlwaysPlaySubtitles": "常に有効", "AlwaysPlaySubtitlesHelp": "言語に合った字幕が音声言語に関係なく読み込まれます。", - "AnyLanguage": "いずれかの言語", + "AnyLanguage": "任意の言語", "Anytime": "いつでも", - "AroundTime": "{0}頃", + "AroundTime": "だいたい", "Art": "アート", "Artists": "アーティスト", "AsManyAsPossible": "できるだけ多く", @@ -47,13 +47,13 @@ "BirthLocation": "生まれた場所", "BirthPlaceValue": "生まれた場所: {0}", "Blacklist": "ブラックリスト", - "BookLibraryHelp": "オーディオブックとテキストブックに対応しています。{0}Jellyfinブック命名ガイド{1}.", + "BookLibraryHelp": "オーディオブックとテキストブックに対応しています。{0} の資料、ブック命名ガイド {1} を参照。", "Books": "ブック", "Box": "ボックス", "BoxRear": "ボックス(後)", "Browse": "ブラウズ", "BrowsePluginCatalogMessage": "利用可能なプラグインを表示するには、プラグインカタログを参照してください。", - "BurnSubtitlesHelp": "字幕フォーマットに応じて、ビデオを変換するときにサーバーが字幕を直接書き込むかどうかを決定します。 字幕の書き込みを避けると、サーバーのパフォーマンスが向上します。 特定のASS / SSA字幕だけでなく画像ベースのフォーマット(VOBSUB, PGS, SUB/IDX, etc)を焼くには自動を選択します", + "BurnSubtitlesHelp": "ビデオのトランスコード時にサーバーが字幕を焼付けるかどうかを決定します。 この字幕焼付けを避けると、サーバーのパフォーマンスが非常に向上します。 画像ベースの形式 (VOBSUB, PGS, SUB, IDX など) と特定の ASS または SSA 字幕でだけ焼付けを行うには、自動を選んでください。", "ButtonAdd": "追加", "ButtonAddMediaLibrary": "メディアライブラリを追加", "ButtonAddScheduledTaskTrigger": "トリガーを追加", @@ -186,7 +186,7 @@ "DisplayInOtherHomeScreenSections": "最新のメディアなどをホーム画面に表示する", "DisplayMissingEpisodesWithinSeasons": "シーズンの欠けているエピソードを表示する", "DisplayMissingEpisodesWithinSeasonsHelp": "これは サーバーのTVライブラリ設定でも有効にする必要があります。", - "DisplayModeHelp": "ellyfinを実行している画面の種類を選択します。", + "DisplayModeHelp": "インターフェース用のお好みのレイアウトスタイルを選びます。", "DoNotRecord": "記録しない", "Down": "下", "Download": "ダウンロード", @@ -435,7 +435,6 @@ "HeaderResponseProfile": "レスポンスプロファイル", "HeaderResponseProfileHelp": "レスポンスプロファイルは、特定の種類のメディアを再生するときにデバイスに送信される情報をカスタマイズする方法を提供します。", "HeaderRestart": "リスタート", - "HeaderRestartingServer": "サーバーの再起動", "HeaderRevisionHistory": "更新履歴", "HeaderRunningTasks": "実行中のタスク", "HeaderScenes": "シーン", @@ -831,7 +830,7 @@ "HeaderXmlDocumentAttribute": "XMLドキュメント属性", "HeaderXmlDocumentAttributes": "XMLドキュメント属性", "Images": "画像", - "InstallingPackage": "インストール中 {0}", + "InstallingPackage": "{0} (バージョン {1} )をインストールしています", "ItemCount": "{0} アイテム", "Items": "アイテム", "Kids": "子供", @@ -1139,5 +1138,56 @@ "LabelEnableBlastAliveMessages": "アライブメッセージを配信する", "LabelDateAddedBehaviorHelp": "メタデータある場合、これらのオプションの前にメタデータ使います。", "AskAdminToCreateLibrary": "管理者にライブラリを作成する依頼をしてください。", - "AllowFfmpegThrottling": "トランスコードをスロットルする" + "AllowFfmpegThrottling": "トランスコードをスロットルする", + "Episode": "エピソード", + "ClientSettings": "クライアント設定", + "Artist": "アーティスト", + "AlbumArtist": "アルバム アーティスト", + "Album": "アルバム", + "ButtonSyncPlay": "SyncPlay", + "DeinterlaceMethodHelp": "インターレースコンテンツをトランスコードする際に使用するデインターレース方式を選びます。", + "ButtonTogglePlaylist": "プレイリスト", + "ButtonToggleContextMenu": "さらに表示", + "BoxSet": "ボックスセット", + "AllowFfmpegThrottlingHelp": "トランスコードや remux が現在の再生位置から十分に先に進んだ場合、処理を一時停止してリソースの消費を抑えます。これは、あまり早送り・早戻しをしないで視聴する場合に便利です。再生に問題が発生した場合は、この機能をオフにしてください。", + "LabelDisplaySpecialsWithinSeasons": "放送されたシーズン内のスペシャルを表示", + "LabelDeinterlaceMethod": "インターレス解除方法:", + "LabelDefaultUserHelp": "接続されたデバイスに表示するユーザーライブラリを決定します。これは、プロファイルを使用して各デバイスに対してオーバーライドすることができます。", + "LabelDateAddedBehavior": "新規コンテンツの追加日のふるまい:", + "LabelCustomCertificatePathHelp": "カスタムドメインでTLSサポートを有効にするための証明書と秘密鍵を含むPKCS #12ファイルのパス。", + "LabelCachePathHelp": "画像などのサーバーキャッシュファイルの場所を指定します。空欄にしておくと、サーバーのデフォルトを使います。", + "LabelBlastMessageIntervalHelp": "ブラスト アライブ メッセージ間の時間を秒単位で指定します。", + "LabelBindToLocalNetworkAddressHelp": "追加の設定。http サーバをバインドするローカル IP アドレスを上書きします。空のままにしておくと、サーバは利用可能なすべてのアドレスにバインドします。この値を変更するには、Jellyfin Server を再起動する必要があります。", + "LabelAlbumArtMaxWidthHelp": "upnp:albumArtURI で公開するアルバムアートの最大解像度。", + "LabelAlbumArtMaxHeightHelp": "upnp:albumArtURI で公開するアルバムアートの最大解像度。", + "LabelAlbumArtHelp": "upnp:albumArtURI の dlna:profileID 属性で、アルバムアートに使われるPN。デバイスによっては、画像のサイズと無関係に特定の値を要求するものもあります。", + "HeaderSyncPlayEnabled": "SyncPlay を有効にしました", + "HeaderSyncPlaySelectGroup": "グループに参加", + "HeaderServerAddressSettings": "サーバー アドレス設定", + "HeaderRemoteAccessSettings": "リモート接続設定", + "HeaderHttpsSettings": "HTTPS 設定", + "HeaderFavoritePlaylists": "お気に入りのプレイリスト", + "HeaderDVR": "DVR", + "ApiKeysCaption": "現在有効にしている API キー一覧", + "EnableDetailsBannerHelp": "項目の詳細ページのトップに、バナー画像を表示します。", + "EnableDetailsBanner": "詳細画面バナー", + "EnableDecodingColorDepth10Vp9": "VP9 の10ビット ハードウェア デコードを有効に", + "EnableDecodingColorDepth10Hevc": "HEVC の 10ビット ハードウェア デコードを有効に", + "LabelEnableBlastAliveMessagesHelp": "ネットワーク上の他の UPnP デバイスによってサーバーが確実に検出されない場合、この設定を有効にします。", + "LabelEnableAutomaticPortMapHelp": "ルーター上のパブリックポートを、UPnP 経由でサーバーのローカルポートに自動的に転送します。これはルータのモデルやネットワーク構成によっては動作しない場合があります。変更はサーバーを再起動するまで適用されません。", + "LabelEmbedAlbumArtDidlHelp": "一部のデバイスでは、アルバムアートを取得するためにこの方法が好まれています。その他のデバイスでは、このオプションを有効にしても再生できない場合があります。", + "LabelDownMixAudioScaleHelp": "ダウンミックス時にオーディオの音量を増幅します。値が 1 の場合、元の音量を維持します。", + "LabelEnableHttps": "HTTPS を有効にする", + "LabelEnableDlnaPlayToHelp": "ネットワーク内のデバイスを検出し、それらをリモートコントロールできるようにします。", + "LabelEnableDlnaPlayTo": "DLNA 再生を有効にする", + "LabelEnableDlnaDebugLoggingHelp": "巨大なログファイルを作成します。トラブルシューティングでの必要な際にだけ使用してください。", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Jellyfin が実行する SSDP 検索の間隔を決めます(秒単位)。", + "LabelGroupMoviesIntoCollectionsHelp": "ムービーリストを表示する際、コレクションに属するムービーを1つのグループとして表示します。", + "LabelServerNameHelp": "この名前はサーバーを識別するために使用します。デフォルトではサーバーのコンピュータ名です。", + "LabelExtractChaptersDuringLibraryScanHelp": "ライブラリー スキャン中に動画を取り込んだときに、チャプター画像を生成します。もしくは、スケジュールタスクの中でチャプター画像を抽出することで、通常のライブラリー スキャンをより速く完了させることができます。", + "LabelExtractChaptersDuringLibraryScan": "ライブラリーをスキャンしながら、チャプター画像を生成する", + "LabelBaseUrlHelp": "サーバーの URL にカスタム サブディレクトリを加えます。例 : http://example.com/<baseurl>", + "LabelEnableSingleImageInDidlLimitHelp": "Didl 内に複数の画像が埋め込まれている場合、一部のデバイスでは正しくレンダリングされません。", + "LabelEnableRealtimeMonitorHelp": "ファイルへの変更は、サポートされているファイルシステム上ですぐに処理されます。", + "LabelEnableHttpsHelp": "構成された HTTPS ポートからサーバーがリッスンするのを有効にします。この機能を有効にするには、適切な証明書を設定する必要があります。" } diff --git a/src/strings/kk.json b/src/strings/kk.json index 852e408a3b1..5532af90f59 100644 --- a/src/strings/kk.json +++ b/src/strings/kk.json @@ -15,7 +15,7 @@ "Alerts": "Eskertýler", "All": "Bári", "AllChannels": "Barlyq arnalar", - "AllComplexFormats": "Barlyq kúrdeli pishimderi (ASS, SSA, VOBSUB, PGS, SUB/IDX jáne t.b.)", + "AllComplexFormats": "Barlyq kúrdeli pishimder (ASS, SSA, VOBSUB, PGS, SUB, IDX j.t.b.)", "AllEpisodes": "Barlyq bólimder", "AllLanguages": "Barlyq tilder", "AllLibraries": "Barlyq tasyǵyshhanalar", @@ -23,17 +23,16 @@ "AllowMediaConversion": "Tasyǵyshderekterdi túrlendirýge ruqsat etý", "AllowMediaConversionHelp": "Tasyǵyshderekterdi túrlendirý ereksheligine qatynaý úshin jol berý nemese tıym salý.", "AllowOnTheFlySubtitleExtraction": "Naqty ýaqytta sýbtıtrlerdi shyǵarýǵa ruqsat etý", - "AllowOnTheFlySubtitleExtractionHelp": "Beıne qaıta kodtaýyna tıym salýǵa kómektesý úshin endirilgen sýbtıtrler beıneden shyǵarylyp, klıentterge kádimgi mátinben jetkizilý múmkin. Keıbir júıelerde buǵan uzaq ýaqyt ketýi jáne shyǵarý úrdisi barysynda beıne oınatý toqtatylýy múmkin. Tutynǵysh qurylǵyda olarǵa qoldaý kórsetilmegen kezde, beıne qaıta kodtalý arqyly endirilgen sýbtıtrler jazylýy úshin ony óshirińiz.", + "AllowOnTheFlySubtitleExtractionHelp": "Beıne qaıta kodtaýyna tıym salýǵa kómektesý úshin, endirilgen sýbtıtrler beıneden shyǵarylyp, klıentterge kádimgi mátinben jetkizilý múmkin. Keıbir júıelerde buǵan uzaq ýaqyt ketýi jáne shyǵarý úrdisi barysynda beıne oınatý toqtatylýy múmkin. Tutynǵysh qurylǵyda olarǵa qoldaý kórsetilmegen kezde, beıne qaıta kodtalý arqyly endirilgen sýbtıtrler jazylýy úshin ony óshirińiz.", "AllowRemoteAccess": "Osy Jellyfin Serverine syrtqy qosylymdar úshin ruqsat etý.", "AllowRemoteAccessHelp": "Eger jalaýsha alastalǵan bolsa, baryq syrtqy baılanystar qursaýlanady.", "AllowedRemoteAddressesHelp": "Qashyqtan qosylýǵa ruqsat etiletin jeliler úshin útirlermen bólingen IP-mekenjaılarynyń tizbesi nemese IP/netmask jazbalar Eger bos qaldyrylsa, barlyq qashyqtaǵy mekenjaılarǵa ruqsat etiledi.", - "AlwaysPlaySubtitles": "Sýbtıtrlerdi árqashan oınatý", + "AlwaysPlaySubtitles": "Árqashan oınatý", "AlwaysPlaySubtitlesHelp": "Til teńshelimine sáıkes kelgen sýbtıtrler dybys tiline qatyssyz júkteledi.", "AnamorphicVideoNotSupported": "Anamorftyq beıne úshin qoldaý kórsetilmeıdi", - "AndroidUnlockRestoreHelp": "Aldyńǵy satyp alýdy qalpyna keltirý úshin, bastapqyda satyp alý jasalǵan naq sol Google (nemese Amazon) tirkelgisimen qurylǵyǵa kirińiz. Qoldanba dúkeni qosylǵan jáne kezkelgen ata-ana shekteýsiz, jáne belsendi ınternet baılanysy bar ekenine kóz jetkizińiz. Aldyńǵy satyp alý qalpyna keltirý úshin muny tek qana bir ret isteý kerek.", "AnyLanguage": "Qaı-qaısy til", "Anytime": "Árkezde", - "AroundTime": "{0} aınalasynda", + "AroundTime": "Shamamen", "Art": "Oıý sýret", "Artists": "Oryndaýshylar", "AsManyAsPossible": "Múmkindiginshe kóp", @@ -50,13 +49,13 @@ "BirthLocation": "Týǵan orny", "BirthPlaceValue": "Týǵan orny: {0}", "Blacklist": "Qara tizim", - "BookLibraryHelp": "Dybystyq jáne mátindik kitaptar qoldaýda. {0}Kitapty ataý nusqaýlyǵyn{1} qarap shyǵý.", + "BookLibraryHelp": "Dybystyq jáne mátindik kitaptar qoldaýda. {0} Kitapty ataý nusqaýlyǵyn {1} qarap shyǵý.", "Books": "Kitaptar", "Box": "Qorap", "BoxRear": "Qorap arty", "Browse": "Sharlaý", "BrowsePluginCatalogMessage": "Qoljetimdi plagındermen tanysý úshin plagın tizimdemesin sholyńyz.", - "BurnSubtitlesHelp": "Sýbtıtrler pishimine baılanysty beıneni túrlendirgen kezde server sýbtıtrlerdi jazyýyn anyqtaıdy. Sýbtıtrler jazýdy qashqaqtaý serverdiń ónimdiligin jaqsartady. Sýretke negizdelgen pishimderdi (VOBSUB, PGS, SUB/IDX j.t.b.) jáne keıbir ASS/SSA sýbtıtrlerin jazý úshin Avtomattyny tańdańyz.", + "BurnSubtitlesHelp": "Beıneni qaıta kodtaǵan kezde server sýbtıtrlerdi jazyýyn anyqtaıdy. Onan qashqaqtaý serverdiń ónimdiligin biraz jaqsartady. Sýretke negizdelgen pishimderdi (VOBSUB, PGS, SUB, IDX, j.t.b.) jáne keıbir ASS nemese SSA sýbtıtrlerin jazý úshin Avtomattyny tańdańyz.", "ButtonAdd": "Ústeý", "ButtonAddMediaLibrary": "Tasyǵyshhana ústeý", "ButtonAddScheduledTaskTrigger": "Trıger ústeý", @@ -198,7 +197,7 @@ "DisplayInOtherHomeScreenSections": "Basqy ekran bólimderinde beıneleý (mys. Eń sońǵy tasyǵyshderekter jáne Kórýdi jalǵastyrý)", "DisplayMissingEpisodesWithinSeasons": "Joq bólimderdi maýsym ishinde beıneleý", "DisplayMissingEpisodesWithinSeasonsHelp": "Bul sondaı-aq server konfıgýrasýasyndaǵy TD tasyǵyshhanalary úshin qosýlýy qajet.", - "DisplayModeHelp": "Jellyfin iske qosylǵanda ekran túrin tańdańyz.", + "DisplayModeHelp": "Interfeıs úshin laıyqty ornalasý mánerin tańdańyz.", "DoNotRecord": "Jazýǵa bolmaıdy", "Down": "Tómenge", "Download": "Júktep alý", @@ -503,7 +502,7 @@ "Images": "Sýretter", "ImportFavoriteChannelsHelp": "Qosylǵanda, túner qurylǵysyndaǵy tańdaýly retinde belgilengen ǵana arnalar shetten ákelinetin bolady.", "ImportMissingEpisodesHelp": "Qosylǵanda, joq epızodtar týraly aqparat sizdiń Jellyfin derekqorǵa ákelinedi jáne maýsymdar men telehıkaıalar aıasynda paıda bolady. Tasyǵyshhana skanerleýde bul aıtarlyqtaı uzaq ýaqyt alýy múmkin.", - "InstallingPackage": "{0} ornatylýda", + "InstallingPackage": "{0} ({1} nusqasy) ornatylýda", "InstantMix": "Lezdik qospalaý", "ItemCount": "{0} tarmaq", "Items": "Tarmaqtar", @@ -600,7 +599,7 @@ "LabelEmbedAlbumArtDidl": "DIDL ishine álbom sýretin endirý", "LabelEmbedAlbumArtDidlHelp": "Álbom sýretin alý úshin keıbir qurylǵylar osy ádisti qalaıdy. Basqalar úshin, osy opsıa qosylǵanda, oınatý sátsiz bolýy múmkin.", "LabelEnableAutomaticPortMap": "Avtomatty port salǵastyrýyn qosý", - "LabelEnableAutomaticPortMapHelp": "Jarıa portty jergilikti portqa UPnP arqyly avtomatty salǵastyrý áreketi. Bul keıbir jol josparlaǵysh ulgilerimen jumys istemeıtini múmkin. Ózgerister server qaıta iske qosylǵansha deıin qoldanylmaıdy.", + "LabelEnableAutomaticPortMapHelp": "Jol josparlaǵysh jarıa portyn server jergilikti portyna UPnP arqyly avtomatty qaıta jiberý. Bul keıbir jol josparlaǵysh ulgilerimen ne jeli ishqyrylymdarymen jumys istemeıtini múmkin. Ózgerister server qaıta iske qosylǵansha deıin qoldanylmaıdy.", "LabelEnableBlastAliveMessages": "Belsendilikti tekserý habarlaryn jaýdyrý", "LabelEnableBlastAliveMessagesHelp": "Eger jelidegi basqa UPnP qurylǵylarymen server nyq tabylmasa buny qosyńyz.", "LabelEnableDlnaClientDiscoveryInterval": "Klıentterdi taýyp ashý aralyǵy, s", @@ -723,7 +722,7 @@ "LabelNumberOfGuideDays": "Júktep alý úshin Telegıd derekterindegi kún sany:", "LabelNumberOfGuideDaysHelp": "Kóbirek kúndi júktep alý Telegıd derekteriniń qundylyǵyn kóteredi de aldyn-ala josparlaý úshin qabiletin jáne kóbirek tizbeler kórýdi qamtamasyz etedi, biraq bul júkteý ýaqytyn da sozdyrady. Avtomatty tańdaý arna sanyna negizdelinedi.", "LabelOptionalNetworkPath": "(Mindetti emes) Ortaqtastyrylǵan jelilik qalta:", - "LabelOptionalNetworkPathHelp": "Eger osy qalta óz jelińizde ortaqtastyrylsa, jelide ortaqtastyrylǵan joldy qamtamasyz etý basqa qurylǵylardaǵy Jellyfin-baǵdarlamalarǵa tasyǵysh faıldaryna tikeleı qol jetkizýge múmkindik beredi.", + "LabelOptionalNetworkPathHelp": "Eger osy qalta óz jelińizde ortaqtastyrylsa, jelide ortaqtastyrylǵan joldy qamtamasyz etý basqa qurylǵylardaǵy Jellyfin-baǵdarlamalarǵa tasyǵysh faıldaryna tikeleı qol jetkizýge múmkindik beredi. Mysaly, {0} nemese {1}.", "LabelOriginalAspectRatio": "Bastapqy pishimdik araqatynasy:", "LabelOriginalTitle": "Bastapqy ataýy:", "LabelOverview": "Jalpy sholý:", @@ -834,7 +833,6 @@ "LabelTypeText": "Mátin", "LabelUnairedMissingEpisodesWithinSeasons": "Kórsetilmegen bólimderdi maýsym ishinde beıneleý", "LabelUnknownLanguage": "Belgisiz til", - "LabelUploadSpeedLimit": "Júktep salý qarqynynyń shegi, Mbıt/s:", "LabelUrl": "URL:", "LabelUseNotificationServices": "Kelesi qyzmetterdi paıdalaný:", "LabelUser": "Paıdalanýshy:", @@ -872,7 +870,6 @@ "LiveBroadcasts": "Tikeleı taratymdar", "LiveTV": "Efır", "LiveTvFeatureDescription": "Jellyfin Server ornatylǵan úılesimdi TD-túner qurylǵysy arqyly kezkelgen Jellyfin-qoldanbaǵa TD-efırdi tikeleı jiberý.", - "LiveTvRequiresUnlock": "", "LiveTvUpdateAvailable": "(Jańartý qoljetimdi)", "LoginDisclaimer": "Jellyfin jeke tasyǵyshhanańyzdy (mysaly, úılik beıneler men fotosýretterdi) basqarýǵa kómektesý úshin arnalǵan. Bizdiń paıdalaný sharttaryn qarańyz. Kezkelgen Jellyfin baǵdarlamalyq jasaqtamasyn paıdalnǵanda osy sharttardyń qabyldaýyn bildiredi.", "Logo": "Logotıp", @@ -950,7 +947,6 @@ "MessagePluginConfigurationRequiresLocalAccess": "Osy plagındi teńsheý úshin jergilikti serverińizge tikeleı kirińiz.", "MessagePluginInstallDisclaimer": "Jellyfin qaýymdastyǵy múshelerimen qurylǵan plagınder Jellyfin tájirıbeńizdi qosymsha múmkindiktermen jáne jeńildiktermen jaqsartý úshin jaqsy tásili bolyp tabylady. Ornatpas buryn, olar Jellyfin serverińizge tasyǵyshhanany uzaq skanerleý, qosymsha óńdik óńdetý jáne júıeniń turaqtylyǵyn tómendetý sıaqty áserler etýge múmkin bolýyna habardar bolyńyz.", "MessageReenableUser": "Qaıta qosý úshin tómende qarańyz", - "MessageServerConfigurationUpdated": "Server konfıgýrasıasy jańartyldy", "MessageSettingsSaved": "Parametrler saqtaldy.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Tasyǵyshhanańyzdan kelesi tasyǵysh ornalasýlary alastalady:", "MessageUnableToConnectToServer": "Tańdalǵan serverge qosylýymyz dál qazir múmkin emes. Bul iske qosylǵanyna kóz jetkizińiz jáne áreketti keıin qaıtalańyz.", @@ -986,16 +982,16 @@ "NoNextUpItemsMessage": "Eshteńe tabylmady. Kórsetimderińizdi qaraı bastańyz!", "NoPluginConfigurationMessage": "Osy plagınde teńsheletin parametrler joq.", "NoSubtitleSearchResultsFound": "Eshqandaı nátıjeler tabylmady.", - "NoSubtitles": "Sýbtıtrlersiz", + "NoSubtitles": "Eshqandaı", "NoSubtitlesHelp": "Ádepkide sýbtıtrler júktelmeıdi. Olardy oınatý kezinde áli de qolmen qosýǵa bolady.", "None": "Eshqandaı", "Normal": "Kádimgi", "NumLocationsValue": "{0} qalta", "Off": "Óshir", "OneChannel": "Bir arnadan", - "OnlyForcedSubtitles": "Tek qana májbúrli sýbtıtrler", + "OnlyForcedSubtitles": "Tek qana májbúrli", "OnlyForcedSubtitlesHelp": "Tek qana májbúrli dep belgilengen sýbtıtrler júkteledi.", - "OnlyImageFormats": "Tek keskin pishimder (VOBSUB, PGS, SUB j.t.b.)", + "OnlyImageFormats": "Tek keskin pishimder (VOBSUB, PGS jáne SUB)", "Open": "Ashý", "OptionActor": "Aktór", "OptionActors": "Aktórler", @@ -1093,7 +1089,7 @@ "OptionMissingEpisode": "Joq bólimder", "OptionMonday": "dúısenbi", "OptionNameSort": "Aty", - "OptionNew": "Jańa...", + "OptionNew": "Jańa…", "OptionNone": "Eshqandaı", "OptionOnAppStartup": "Qoldanba iske qosylǵanda", "OptionOnInterval": "Aralyqta", @@ -1138,9 +1134,9 @@ "OptionWeekly": "Apta saıyn", "OriginalAirDateValue": "Bastapqy efır: {0}", "Overview": "Jalpy sholý", - "PackageInstallCancelled": "{0} ornatylýy boldyrylmady.", - "PackageInstallCompleted": "{0} ornatylýy aıaqtaldy.", - "PackageInstallFailed": "{0} ornatylýy sátsiz.", + "PackageInstallCancelled": "{0} ({1} nusqasy) ornatylýy boldyrylmady.", + "PackageInstallCompleted": "{0} ({1} nusqasy) ornatylýy aıaqtaldy.", + "PackageInstallFailed": "{0} ({1} nusqasy) ornatylýy sátsiz.", "ParentalRating": "Jastas sanaty", "PasswordMatchError": "Paróli men Paróldi rastaý óristeri sáıkes bolý kerek.", "PasswordResetComplete": "Paról ysyryldy.", @@ -1209,7 +1205,6 @@ "ReplaceAllMetadata": "Barlyq metaderekterdi aýystyrý", "ReplaceExistingImages": "Bar sýretterdi aýystyrý", "RequiredForAllRemoteConnections": "Barlyq qashyqtaǵy qosylymdar úshin qajet", - "RestartPleaseWaitMessage": "Jellyfin Server jumysy aıaqtalyp, qaıta iske qosylǵansha deıin kúte turyńyz. Bul bir-eki mınótqa sozylýy múmkin.", "ResumeAt": "{0} bastap jalǵastyrý", "Rewind": "Shegindirý", "RunAtStartup": "Iske qosylýdan bastap oryndaý", @@ -1229,7 +1224,6 @@ "SearchForSubtitles": "Sýbtıtrlerdi izdeý", "SearchResults": "Izdeý nátıjeleri", "SecondaryAudioNotSupported": "Dybys jolshyǵyn aýystyrý úshin qoldaý kórsetilmeıdi", - "SelectCameraUploadServers": "Kameradan fotosýretterdi kelesi serverlerge júktep salý:", "SendMessage": "Habar jiberý", "Series": "Telehıkaıa", "SeriesCancelled": "Telehıkaıa boldyrylmady.", @@ -1346,8 +1340,6 @@ "TrackCount": "{0} jolshyq", "Trailers": "Treılerler", "Transcoding": "Qaıta kodtaýda", - "TryMultiSelect": "Úndesken bólekteýdi synap kórý", - "TryMultiSelectMessage": "Birneshe tasyǵysh derekter elementterin óńdeý úshin, kezkelgen posterdi jaı ǵana tintýir batyrmaǵa basyp turyp nuqyńyz jáne basqarýyn qalaǵan elementterdi bólekteńiz. Synap kórińiz!", "Tuesday": "seısenbi", "TvLibraryHelp": "{0}TD-kórsetimdi ataý nusqaýlyǵyn{1} qarap shyǵý.", "Uniform": "Biryńǵaı", @@ -1418,7 +1410,6 @@ "HeaderFavoriteSongs": "Tańdaýly áýender", "HeaderFavoriteVideos": "Tandaýly beıneler", "HeaderHome": "Basqy", - "HeaderRestartingServer": "Serverdi qaıta iske qosý", "LabelAuthProvider": "Túpnusqalyq rastamasyn jetkizýshi:", "LabelPasswordResetProvider": "Paróldi ysyrý jetkizýshisi:", "LabelServerName": "Server aty:", @@ -1460,7 +1451,7 @@ "SubtitleOffset": "Sýbtıtrler yǵysýy", "TabNetworking": "Jelilik jumys", "HeaderFavoriteBooks": "Tańdaýly kitaptar", - "LabelBaseUrlHelp": "Serverge neǵurlym erekshe URL mekenjaıynan kirý úshin osynda jeke ishki katalogty qosýǵa bolady.", + "LabelBaseUrlHelp": "Serverdiń URL-mekenjaıyna jeke ishki katalogty qosady. Mysaly: http://example.com/<baseurl>", "MoreMediaInfo": "Tasyǵyshderekter týraly", "LabelVideoCodec": "Beınelik kodek:", "LabelVideoBitrate": "Beıne qarqyny:", @@ -1491,8 +1482,6 @@ "MessageConfirmAppExit": "Shyǵýdy qalaısyz ba?", "LabelVideoResolution": "Beıne ajyratymdylyǵy:", "LabelStreamType": "Aǵyn túri:", - "EnableFastImageFadeInHelp": "Júktelgen sýretter úshin shapshan kórsetilýin qosý", - "EnableFastImageFadeIn": "Sýrettiń shapshan kórsetilýi", "LabelPlayerDimensions": "Oınatqysh ólshemderi:", "LabelDroppedFrames": "Ótkizilgen kadrlar:", "LabelCorruptedFrames": "Búlingen kadrlar:", @@ -1501,6 +1490,59 @@ "ButtonSplit": "Bólý", "AskAdminToCreateLibrary": "Tasýǵyshanany jasaý úshin ákimshiden suraý.", "AllowFfmpegThrottling": "Qaıta kodtaýdy retteý", - "PlaybackErrorNoCompatibleStream": "Klıent profaılyn jasaýda másele oryn aldy jáne server úılesimdi pishiminde tasyǵysh derekterin jibermedi.", - "AllowFfmpegThrottlingHelp": "Qaıta kodtaý nemese qaıta býmalaý aǵymdyq oınatý jaıǵasymynan edáýir alǵa ozǵanda, qor kózderin azdaý tutynatyndaı etip údiristi kidirtedi. Bul jıi izdemeı qaraý kezinde paıdaly. Eger oınatý máseleleri bolsa, ony óshirińiz." + "PlaybackErrorNoCompatibleStream": "Bul klıent tasyǵysh derektermen úılesimsiz jáne server úılesimdi pishiminde tasyǵysh derekterin jibermedi.", + "AllowFfmpegThrottlingHelp": "Qaıta kodtaý nemese qaıta býmalaý aǵymdyq oınatý jaıǵasymynan edáýir alǵa ozǵanda, qor kózderin azdaý tutynatyndaı etip údiristi kidirtedi. Bul jıi izdemeı qaraý kezinde paıdaly. Eger oınatý máseleleri bolsa, ony óshirińiz.", + "Album": "Álbom", + "DeinterlaceMethodHelp": "Jol aralyq jaımaly mazmundy qaıta kodtaý kezinde paıdalaný úshin jol aralyq jaımany joıý ádisin tańdańyz.", + "LabelDeinterlaceMethod": "Jol aralyq jaımany joıý ádisi:", + "YadifBob": "YADIF eki eseleýimen", + "OnApplicationStartup": "Qoldanba iske qosylǵanda", + "EveryXHours": "Ár {0} saǵ", + "EveryHour": "Ár saǵat", + "EveryXMinutes": "Ár {0} mın", + "OnWakeFromSleep": "Uıqylyqtan oıanǵanda", + "WeeklyAt": "{0} {1} kezinde", + "DailyAt": "Kúnde {0} kezinde", + "LastSeen": "Sońǵy kóringeni {0}", + "PersonRole": "- {0}", + "ListPaging": "{0}-{1} {2} ishinen", + "Yadif": "YADIF", + "Track": "Jolshyq", + "Season": "Maýsym", + "ReleaseGroup": "Shyǵarýshy top", + "Person": "Tulǵa", + "OtherArtist": "Basqa oryndaýshy", + "Movie": "Fılm", + "LabelLibraryPageSize": "Tasyǵyshhana betiniń ólshemi:", + "Episode": "Bólim", + "ClientSettings": "Týtynýshy parametrleri", + "ButtonTogglePlaylist": "Oýnatý tizimi", + "ButtonToggleContextMenu": "Kúbirek", + "BoxSet": "Jıyntyq", + "Artist": "Ornatýshy", + "AlbumArtist": "Álbom ornatýshysy", + "MessageSyncPlayDisabled": "SyncPlay óshirýli.", + "MessageSyncPlayEnabled": "SyncPlay qosýly.", + "LabelSyncPlayAccess": "SyncPlay qatynasy", + "LabelSyncPlayLeaveGroupDescription": "SyncPlay óshirý", + "LabelSyncPlayLeaveGroup": "Toptan shyǵý", + "LabelSyncPlayNewGroupDescription": "Jańa topty jasaý", + "LabelSyncPlayNewGroup": "Jańa top", + "LabelSyncPlaySyncMethod": "Úndestirý ádisi:", + "LabelSyncPlayPlaybackDiff": "Oınatý ýaqytynyń aıyrmashylyǵy:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Server ýaqtynan aýytqýy:", + "LabelRequireHttps": "HTTPS qajet etedi", + "LabelNightly": "Túngi", + "LabelStable": "Turaqty", + "LabelChromecastVersion": "Chromecast nusqasy", + "LabelEnableHttps": "HTTPS qosý", + "HeaderSyncPlayEnabled": "SyncPlay qosýly", + "HeaderSyncPlaySelectGroup": "Topty qosý", + "HeaderServerAddressSettings": "Server mekenjaıy parametrleri", + "HeaderRemoteAccessSettings": "Qashyqtan qatynaý parametrleri", + "HeaderHttpsSettings": "HTTPS parametrleri", + "HeaderFavoritePlaylists": "Tańdaýly oınatý tizimder", + "HeaderDVR": "DVR", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/ko.json b/src/strings/ko.json index 833f8ccb105..660b3d34a0c 100644 --- a/src/strings/ko.json +++ b/src/strings/ko.json @@ -256,7 +256,7 @@ "Images": "이미지", "ImportFavoriteChannelsHelp": "활성화 시, 튜너 장치에서 즐겨찾기로 표시된 채널만 불러옵니다.", "ImportMissingEpisodesHelp": "이 기능을 사용하면 누락 된 에피소드에 대한 정보가 Jellyfin 데이터베이스로 가져와 시즌 및 시리즈 내에서 표시됩니다. 이로 인해 상당히 긴 라이브러리 스캔이 발생할 수 있습니다.", - "InstallingPackage": "{0} 설치 중", + "InstallingPackage": "{0} 설치 중 ( {1} 버전)", "InstantMix": "인스턴트 믹스", "ItemCount": "{0} 항목", "Label3DFormat": "3D 포맷:", @@ -295,7 +295,7 @@ "LabelCriticRating": "평론가 평점:", "LabelCurrentPassword": "현재 비밀번호:", "LabelCustomCertificatePath": "사용자 지정 SSL 인증서 경로:", - "LabelCustomCertificatePathHelp": "자기 소유의 SSL 인증서을 제공하십시오. PFX 파일 형식이어야 합니다.", + "LabelCustomCertificatePathHelp": "커스텀 도메인에서 TLS를 지원할 수 있도록 인증서 및 개인키가 포함 된 PKCS #12 파일의 경로입니다.", "LabelCustomCss": "사용자 지정 CSS:", "LabelCustomCssHelp": "사용자 정의 스타일링을 웹 인터페이스에 적용합니다.", "LabelCustomDeviceDisplayName": "표시 이름:", @@ -810,7 +810,7 @@ "AirDate": "방영 일자", "Aired": "방영됨", "Alerts": "알림", - "AllComplexFormats": "모든 복잡한 포맷 (ASS, SSA, VOBSUB, PGS, SUB/IDX 등)", + "AllComplexFormats": "모든 복잡한 포맷 (ASS, SSA, VOBSUB, PGS, SUB, IDX,...)", "AllLibraries": "모든 라이브러리", "AllowMediaConversion": "미디어 변환 허용", "AllowOnTheFlySubtitleExtraction": "실시간 자막 추출 허용", @@ -818,10 +818,10 @@ "AllowRemoteAccess": "이 Jellyfin 서버에 원격 접속을 허용합니다.", "AllowRemoteAccessHelp": "체크 해제 시 모든 외부 접속은 차단됩니다.", "AllowedRemoteAddressesHelp": "외부 접속을 허용할 IP 혹은 IP 넷마스크를 반점(,)으로 구분하여 입력하십시오. 공란일시, 모든 외부 접속이 허용됩니다.", - "AlwaysPlaySubtitles": "항상 자막 표시", + "AlwaysPlaySubtitles": "항상 표시", "AlwaysPlaySubtitlesHelp": "오디오 언어를 불문하고 언어 설정에 적합한 자막을 불러옵니다.", "AnyLanguage": "모든 언어", - "AroundTime": "대략 {0}", + "AroundTime": "대략", "Art": "아트", "AsManyAsPossible": "최대한 많이", "Ascending": "오름차순", @@ -976,7 +976,7 @@ "OptionEnableForAllTuners": "모든 튜너 장치 활성화", "OptionBanner": "배너", "Option3D": "3D", - "OnlyImageFormats": "이미지 포맷만 (VOBSUB, PGS, SUB 등)", + "OnlyImageFormats": "이미지 포맷만 (VOBSUB, PGS, SUB)", "Off": "끄기", "NumLocationsValue": "{0} 폴더", "Normal": "보통", @@ -1093,7 +1093,6 @@ "HeaderTuners": "튜너", "HeaderStopRecording": "녹화 중지", "HeaderStartNow": "지금 시작", - "HeaderRestartingServer": "서버 재시작", "HeaderPhotoAlbums": "사진 앨범", "HeaderNewDevices": "새 장치", "HeaderMyDevice": "내 장치", @@ -1142,7 +1141,7 @@ "HeaderApiKeysHelp": "외부 애플리케이션은 Jellyfin 서버와 통신하기 위해 API키를 가지고 있어야 합니다. 키는 Jellyfin 계정으로 로그인하거나 수동으로 키를 부여하여 발급할 수 있습니다.", "HeaderAllowMediaDeletionFrom": "미디어 제거 허용", "HardwareAccelerationWarning": "하드웨어 가속을 활성화하면 일부 환경에서 불안정해질 수 있습니다. 운영체제 및 비디오 드라이버가 최신 상태인지 확인하십시오. 이 기능을 활성화한 후 비디오를 재생하는 데 어려움이 있을 경우 설정을 다시 '사용 안 함'으로 변경하십시오.", - "GuestStar": "게스트 스타", + "GuestStar": "게스트 출연", "GroupBySeries": "시리즈별로 그룹화", "Genre": "장르", "General": "일반", @@ -1231,11 +1230,11 @@ "ConfigureDateAdded": "라이브러리 설정의 Jellyfin 서버 대시보드에서 추가된 날짜를 결정하는 방법 구성", "EnableStreamLoopingHelp": "라이브 스트림에 몇 초의 데이터만 포함되어 있고 지속적으로 요청해야하는 경우, 이 옵션을 활성화하십시오. 필요하지 않은 경우, 이 기능을 사용하면 문제가 발생할 수 있습니다.", "FolderTypeUnset": "혼합 콘텐츠", - "BurnSubtitlesHelp": "자막 포맷에 따라 비디오를 변환할 때 서버에서 자막을 구워야 하는지를 결정합니다. 자막을 굽지 않으면 서버 성능이 향상됩니다. 이미지 기반 포맷(VOBSUB, PGS, SUB/IDX 등) 및 특정 ASS/SSA 자막을 구우려면 자동을 선택하십시오.", + "BurnSubtitlesHelp": "영상을 트랜스코딩할 때 자막 포맷에 따라 서버에서 영상 안에 자막을 입힐지를 결정합니다. 영상에 자막을 입히지 않으면 서버 성능이 향상됩니다. 일부 ASS/SSA 자막을 포함하여 자막 포맷(VOBSUB, PGS, SUB/IDX 등)에 따라 자막을 영상에 입히려면 자동을 선택하십시오.", "EnableNextVideoInfoOverlay": "재생 중에 다음 비디오 정보 표시", "EnablePhotosHelp": "이미지가 다른 미디어 파일과 함께 감지되어 표시됩니다.", "ButtonParentalControl": "자녀 보호", - "DisplayModeHelp": "Jellyfin이 실행 중인 화면의 유형을 선택하십시오.", + "DisplayModeHelp": "원하는 인터페이스 레이아웃 스타일을 선택하십시오.", "DefaultSubtitlesHelp": "자막은 내장된 메타데이터에 있는 기본 플래그와 강제 플래그를 기반으로 불러옵니다. 언어 선호도는 여러 옵션을 사용할 수 있을 때 고려됩니다.", "Disc": "디스크", "EnableExternalVideoPlayersHelp": "비디오 재생을 시작할 때 외부 재생기 메뉴가 표시됩니다.", @@ -1265,9 +1264,8 @@ "MediaInfoBitDepth": "비트뎁스", "LabelPostProcessor": "후처리 애플리케이션:", "RefreshQueued": "새로 고침 대기 중", - "NoPluginConfigurationMessage": "", + "NoPluginConfigurationMessage": "이 플러그인은 설정할 것이 없습니다.", "OptionExtractChapterImage": "챕터 이미지 추출 활성화", - "RestartPleaseWaitMessage": "Jellyfin 서버가 종료되었다가 다시 시작될 때까지 기다리십시오. 1-2분 정도 걸릴 수 있습니다.", "Up": "위", "EasyPasswordHelp": "쉬운 핀 코드는 지원되는 기기에서 오프라인 접근을 할 때나 내부 내트워크 로그인에서 사용됩니다.", "CriticRating": "평점", @@ -1325,7 +1323,7 @@ "MediaInfoPixelFormat": "픽셀 형식", "MapChannels": "채널 매핑", "LaunchWebAppOnStartupHelp": "서버가 처음 시작되면 웹 브라우저에서 웹 클라이언트를 실행하십시오. 서버 재시작의 경우에는 적용되지 않습니다.", - "Large": "큰", + "Large": "크게", "LanNetworksHelp": "대역폭을 강제로 제한할 때 로컬 네트워크로 간주되는 쉼표로 구분된 IP 주소 및 IP/서브넷 마스크 목록입니다. 지정될 경우 모든 다른 IP 주소는 외부 네트워크로 간주되며 외부 대역폭 제한이 적용됩니다. 공백일 경우 서버의 서브넷만이 로컬 네트워크로 간주됩니다.", "LabelffmpegPathHelp": "ffmpeg 실행 파일 혹은 ffmpeg를 포함하는 폴더 경로입니다.", "LabelXDlnaDocHelp": "urn:schemas-dlna-org:device-1-0 네임스페이스에 포함된 X_DLNADOC 요소의 내용을 결정합니다.", @@ -1342,9 +1340,7 @@ "LabelSkipIfGraphicalSubsPresent": "비디오에 내장 자막이 있으면 건너뛰기", "LabelSkipIfAudioTrackPresentHelp": "오디오 언어와 상관없이 모든 비디오가 자막을 갖추도록 하려면 이 항목을 체크하지 마십시오.", "LabelSelectFolderGroupsHelp": "체크되지 않은 폴더는 폴더 고유의 보기 방식으로 표시됩니다.", - "LabelSelectFolderGroups": "자동으로 이하의 폴더의 항목을 영화, 음악, TV 등의 보기 방식으로 정렬:", - "EnableFastImageFadeInHelp": "로드된 이미지에 더 빠른 페이드 인 효과를 적용", - "EnableFastImageFadeIn": "빠른 이미지 페이드 인 효과", + "LabelSelectFolderGroups": "자동으로 이하의 폴더의 항목을 영화, 음악, TV 와 같은 보기 방식으로 그룹화:", "LabelScheduledTaskLastRan": "최근 실행: {0}, 소모시간: {1}.", "LabelRemoteClientBitrateLimitHelp": "(선택) 모든 외부 네트워크로 접속된 장치들에 적용되는 각 스트리밍별 비트레이트 제한입니다. 이는 서버의 인터넷이 처리할 수 있는 한계보다 더 높은 비트레이트를 요청하는 것을 방지할 수 있습니다. 비디오를 더 낮은 비트레이트로 트랜스코딩하기 위해 서버에 높은 CPU 부하를 줄 수 있습니다.", "LabelReasonForTranscoding": "트랜스코딩 원인:", @@ -1377,5 +1373,65 @@ "LabelEmbedAlbumArtDidlHelp": "일부 디바이스는 앨범아트를 얻을 때 이 방법을 선호합니다. 다른 디바이스에서는 실패할 수 있습니다.", "LabelDefaultScreen": "기본 화면:", "LabelDateTimeLocale": "날짜/시간 로케일:", - "XmlTvPathHelp": "XMLTV 파일을 저장할 경로를 설정합니다. Jellyfin은 이 파일을 읽어 주기적으로 변경 사항을 확인합니다. 파일 생성 및 파일 업데이트는 사용자가 수동으로 해야 합니다." + "XmlTvPathHelp": "XMLTV 파일을 저장할 경로를 설정합니다. Jellyfin은 이 파일을 읽어 주기적으로 변경 사항을 확인합니다. 파일 생성 및 파일 업데이트는 사용자가 수동으로 해야 합니다.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "다음과 같은 미디어 저장소들을 라이브러리에서 제거합니다:", + "MessageReenableUser": "재활성화는 아래를 참조하십시오", + "MessagePluginConfigurationRequiresLocalAccess": "이 플러그인을 구성하려면 로컬 서버에 직접 로그인하십시오.", + "MessageNoCollectionsAvailable": "컬렉션을 사용하면 영화, 시리즈 및 앨범의 개인화 된 그룹을 즐길 수 있습니다. + 버튼을 클릭하여 컬렉션 만들기를 시작합니다.", + "LabelPlayerDimensions": "플레이어 화면 크기:", + "LabelParentNumber": "부모 번호:", + "LabelLineup": "라인업:", + "LabelDroppedFrames": "드롭 프레임:", + "LabelDeinterlaceMethod": "디인터레이싱 방법:", + "LabelCustomDeviceDisplayNameHelp": "기기에 표시할 이름을 지정하거나 공란으로 두어 등록된 기기 명을 사용합니다.", + "Episode": "에피소드", + "EnableColorCodedBackgrounds": "컬러코드가 삽입된 배경", + "DropShadow": "하단 그림자", + "Depressed": "압축된", + "DeinterlaceMethodHelp": "비월주사식 콘텐츠를 순차주사로 변환할시 사용할 비월제거 방법을 선택하십시오.", + "ClientSettings": "클라이언트 설정", + "BoxSet": "박스 세트", + "Artist": "아티스트", + "AlbumArtist": "앨범 아티스트", + "Album": "앨범", + "NoCreatedLibraries": "라이브러리가 없습니다. {0}지금 생성하겠습니까?{1}", + "NewCollectionHelp": "영화 및 다른 라이브러리 콘텐츠들을 묶어 개인화된 컬렉션을 구성할 수 있습니다.", + "Never": "항상 안 함", + "Movie": "영화", + "MoveRight": "오른쪽으로 이동", + "MoveLeft": "왼쪽으로 이동", + "MoreFromValue": "{0} 에서 더 자세히", + "MetadataSettingChangeHelp": "변경된 메타데이터 설정은 새 콘텐츠에 적용됩니다. 기존의 콘텐츠에 적용하려면 상세 화면에서 새로 고침 버튼을 누르거나 메타데이터 매니저를 통해 일괄적으로 새로 고침을 수행하십시오.", + "MessagePluginInstallDisclaimer": "Jellyfin 커뮤니티에서 만들어진 플러그인은 Jellyfin의 기능과 편의성을 향상시킬 수 있습니다. 다만 이러한 플러그인은 라이브러리 스캔 속도 저하, 추가 백그라운드 프로세싱, 시스템 불안정과 같은 문제를 야기할 수 있다는 것을 유념하시기 바랍니다.", + "LabelLibraryPageSizeHelp": "라이브러리 페이지에 표시될 항목 수를 조절합니다. 0으로 지정 시 페이징을 비활성화합니다.", + "LabelLibraryPageSize": "라이브러리 페이지 크기:", + "LabelEnableBlastAliveMessages": "서버 활성화 메세지", + "OptionEnableExternalContentInSuggestionsHelp": "제안 항목에 인터넷 예고편과 라이브 TV 프로그램이 포함되도록 허용합니다.", + "OptionEnableExternalContentInSuggestions": "제안 항목에 외부 콘텐츠 허용", + "OptionDownloadImagesInAdvanceHelp": "기본적으로 대부분의 이미지는 Jellyfin 앱에서 요청할 때에만 다운로드됩니다. 새 미디어를 추가할 때 모든 이미지를 미리 다운로드하려면 이 옵션을 활성화하십시오. 라이브러리 스캔이 심각하게 지연될 수도 있습니다.", + "OptionDownloadImagesInAdvance": "미리 이미지 다운로드", + "OptionDisplayFolderView": "일반적인 미디어 폴더를 볼 수 있는 폴더 보기를 표시합니다", + "OptionAutomaticallyGroupSeriesHelp": "활성화하면 라이브러리 내의 여러 폴더에 분산된 시리즈를 하나의 시리즈로 병합합니다.", + "OptionAutomaticallyGroupSeries": "여러 폴더에 분산된 시리즈를 자동으로 병합합니다", + "OptionAllowVideoPlaybackRemuxing": "변환이 필요한 비디오를 재인코딩하지 않고 재생하는 것을 허용", + "OptionAllowSyncTranscoding": "트랜스코딩이 필요한 미디어의 다운로드 및 싱크 허용", + "OptionAllowMediaPlaybackTranscodingHelp": "트랜스코딩 접근을 제한하면 Jellyfin 앱에서 지원되지 않는 미디어 형식을 재생할 때 문제가 발생할 수 있습니다.", + "OptionForceRemoteSourceTranscoding": "원격 미디어 소스를 강제 트랜스코딩 (라이브 TV 등)", + "OnlyForcedSubtitlesHelp": "'강제'로 표시된 자막만 불러옵니다.", + "OnlyForcedSubtitles": "강제로 설정한 자막만", + "OneChannel": "단채널", + "NoSubtitlesHelp": "자막을 자동으로 불러오지 않습니다. 재생 중에 수동으로 켤 수 있습니다.", + "MusicLibraryHelp": "{0}음악 이름 지정 규칙{1}을 확인하십시오.", + "MovieLibraryHelp": "{0}영화 이름 지정 규칙{1}을 확인하십시오.", + "MessageUnauthorizedUser": "현재 서버에 접속할 권한이 없습니다. 자세한 정보는 서버 관리자에게 문의하십시오.", + "HeaderFavoritePlaylists": "즐겨찾는 플레이리스트", + "ButtonTogglePlaylist": "플레이리스트", + "ButtonToggleContextMenu": "더보기", + "Rate": "평", + "PerfectMatch": "정확히 일치", + "OtherArtist": "다른 아티스트", + "ButtonSyncPlay": "SyncPlay", + "HeaderDVR": "DVR", + "EnableDecodingColorDepth10Vp9": "10비트 VP9하드웨어 디코딩 사용합니다", + "EnableDecodingColorDepth10Hevc": "10비트 HEVC하드웨어 디코딩 사용합니다" } diff --git a/src/strings/lt-lt.json b/src/strings/lt-lt.json index c87628316c0..c8c2b6e448f 100644 --- a/src/strings/lt-lt.json +++ b/src/strings/lt-lt.json @@ -149,7 +149,7 @@ "Help": "Padėti", "Identify": "Identifikuoti", "Images": "Atvaizdai", - "InstallingPackage": "Diegiama {0}", + "InstallingPackage": "Diegiama {0} (versija {1})", "InstantMix": "Leisti miksą", "ItemCount": "{0} elementų", "Kids": "Vaikams", @@ -538,7 +538,7 @@ "AllLanguages": "Visos kalbos", "AllowMediaConversion": "Leisti medijos konvertavimą", "AllowRemoteAccess": "Leisti nuotolinius prisijungimus prie šio Jellyfin serverio.", - "AnyLanguage": "Bet kokia kalba", + "AnyLanguage": "Bet Kokia Kalba", "Artists": "Atlikėjai", "Audio": "Garsas", "Auto": "Auto", @@ -616,7 +616,7 @@ "AllowMediaConversionHelp": "Leisti arba uždrausti medijos konvertavimą.", "AlwaysPlaySubtitles": "Visada rodyti subtitrus", "AutoBasedOnLanguageSetting": "Auto (pagal kalbos parinktį)", - "BookLibraryHelp": "Garso ir tekstinės knygos yra palaikomos. Peržiūrėkite {0}knygų vardinimo gidą{1}.", + "BookLibraryHelp": "Garso ir tekstinės knygos yra palaikomos. Peržiūrėkite {0} knygų vardinimo gidą {1}.", "ButtonEditOtherUserPreferences": "Keisti šio vartotojo profilį, paveikslą ir asmeninius nustatymus.", "ButtonResetEasyPassword": "Atstatyti pin kodą", "ButtonShuffle": "Sumaišyti", @@ -630,7 +630,7 @@ "Disconnect": "Atsijungti", "DisplayInMyMedia": "Rodyti pradiniame ekrane", "DisplayMissingEpisodesWithinSeasons": "Rodyti sezonuose trūkstamas serijas", - "DisplayModeHelp": "Pasirinkite ekrano tipą, kuriame veikia Jellyfin.", + "DisplayModeHelp": "Pasirinkite sąsajos išdėstymo stilių.", "Down": "Žemyn", "DownloadsValue": "{0} atsisiuntimų", "DrmChannelsNotImported": "Kanalai su DRM nebus įkeliami.", @@ -641,9 +641,9 @@ "EnablePhotos": "Rodyti nuotraukas", "EnablePhotosHelp": "Nuotraukos bus rodomos šalia kitų medijos failų.", "EnableThemeSongs": "Teminės dainos", - "AspectRatio": "Vaizdo santykis", + "AspectRatio": "Vaizdo Santykis", "Ascending": "Didėjančia tvarka", - "AllComplexFormats": "Visi sudėtingi formatai (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Visi Sudėtingi Formatai (ASS, SSA, VOBSUB, PGS, SUB/IDX, t.t.)", "AllowHWTranscodingHelp": "Leisti imtuvui perkoduoti srautus grojant. Tai gali sumažinti perkodavimus reikalingus serveriui.", "AuthProviderHelp": "Pasirinkite autentifikavimo paslaugos teikėją šio vartotojo slaptažodžio autentifikavimui.", "AddItemToCollectionHelp": "Pridėkite įrašus į kolekciją. Suraskite įrašą, bei naudokite jo meniu, kad pridėti į kolekciją.", @@ -688,8 +688,8 @@ "HeaderDirectPlayProfileHelp": "Pridėti tiesioginio leidimo profilius, kad nurodyti kokius formatus įrenginys palaiko be perkodavimo.", "CopyStreamURLSuccess": "Srauto nuoroda nukopijuota.", "DefaultMetadataLangaugeDescription": "Tai yra numatytieji nustatymai. Jie gali būti keičiami kiekvienai bibliotekai atskirai.", - "AllowOnTheFlySubtitleExtractionHelp": "Įterptus subtitrus iš vaizdo įrašo galima išgauti ir klientams pateikti paprastu tekstu, kad būtų išvengta vaizdo įrašų perkodavimo. Kai kuriose sistemose tai gali užtrukti ilgą laiką ir gali sustabdyti vaizdo atkūrimą subtitrų išgavimo metu. Išjunkite tai, kad subtitrus būtu įrašomi į vaizdo įrašą naudojant perkodavimą, jei jie yra nepalaikomi kliento įrenginio.", - "BurnSubtitlesHelp": "Nustato, ar konvertuojant vaizdo įrašą serveris turėtų įrašyti subtitrus, atsižvelgiant į subtitrų formatą. Subtitrų įrašymo išvengimas pagerina serverio našumą. Pasirinkite „Auto“, jei norite įrašyti atvaizdais paremtus formatus (VOBSUB, PGS, SUB / IDX ir kt.) Ir tam tikrus ASS / SSA subtitrus.", + "AllowOnTheFlySubtitleExtractionHelp": "Įterptus subtitrus iš vaizdo įrašo galima išgauti ir klientams pateikti paprastu tekstu, kad būtų išvengta vaizdo įrašų perkodavimo. Kai kuriose sistemose tai gali užtrukti ilgą laiką ir gali sustabdyti vaizdo atkūrimą subtitrų išgavimo metu. Išjunkite tai, kad subtitrus būtu įrašomi į vaizdo įrašą naudojant perkodavimą, jei jie yra nepalaikomi kliento įrenginio.", + "BurnSubtitlesHelp": "Nustato, ar perkoduojant vaizdo įrašą serveris turėtų įrašyti subtitrus, atsižvelgiant į subtitrų formatą. Išvengiant subtitrų įrašymo gali pagerinti serverio našumą. Pasirinkite „Auto“, jei norite įrašyti atvaizdais paremtus formatus (VOBSUB, PGS, SUB, IDX, ...) Ir tam tikrus ASS arba SSA subtitrus.", "DefaultSubtitlesHelp": "Subtitrai įkeliami atsižvelgiant į numatytuosius ir priverstinius žymenis įterptuose metaduomenyse. Kalbos nustatymai įvertinami, kai yra keletas variantų.", "HeaderDeleteProvider": "Ištrinti paslaugos teikėją", "HeaderDeleteTaskTrigger": "Ištrinti užduoties trigerį", @@ -780,7 +780,6 @@ "LabelAllowHWTranscoding": "Leisti naudoti aparatinę įrangą perkodavimui", "LabelAllowedRemoteAddresses": "Nuotolinis IP adresų filtras:", "LabelAllowedRemoteAddressesMode": "Nuotolinio IP adresų filtro režimas:", - "HeaderRestartingServer": "Serveris perkraunamas", "HeaderLoginFailure": "Prisijungimo klaida", "Hide": "Paslėpti", "LabelAll": "Visi", @@ -812,7 +811,7 @@ "HeaderTranscodingProfileHelp": "Pridėti perkodavimo profilius, kad nurodyti, kokius formatus reikia naudoti, kai reikia perkoduoti.", "HeaderTunerDevices": "Tiunerio prietaisai", "HeaderTuners": "Tiuneris", - "HeaderTypeImageFetchers": "{0} atvaizdų parsiuntėjai", + "HeaderTypeImageFetchers": "{0} atvaizdų persiuntėjai", "HeaderTypeText": "Įvesti tekstą", "HeaderUpcomingOnTV": "Laukiama per TV", "HeaderUploadImage": "Įkelti atvaizdą", @@ -927,7 +926,7 @@ "Guide": "Gidas", "GuideProviderLogin": "Prisijungti", "HandledByProxy": "Valdomas atvirkštiniu \"proxy\" serveriu", - "HardwareAccelerationWarning": "Įjungus aparatinės įrangos spartinimą, kai kuriose diegimo aplinkose gali atsirasti nestabilumas. Įsitikinkite, kad jūsų operacinė sistema ir vaizdo tvarkyklės yra visiškai atnaujintos. Jei įjungus šį vaizdo įrašą kyla problemų, turite pakeisti nustatymą į Automatinis.", + "HardwareAccelerationWarning": "Įjungus aparatinės įrangos spartinimą, kai kuriose diegimo aplinkose gali atsirasti nestabilumas. Įsitikinkite, kad jūsų operacinė sistema ir vaizdo tvarkyklės yra visiškai atnaujintos. Jei įjungus šį vaizdo įrašą kyla problemų, turite pakeisti nustatymą į \"Joks\".", "HeaderAdmin": "Administratorius", "HeaderAlbums": "Albumai", "HeaderAlert": "Perspėjimas", @@ -1003,5 +1002,18 @@ "HeaderSelectMetadataPath": "Metaduomenų kelio išrinkimas", "HeaderSelectMetadataPathHelp": "Suraskite arba įrašykite kelią metaduomenų saugojimui. Aplankalas turi būti su rašymo teise.", "HeaderSelectPath": "Išrinkti kelią", - "HeaderSelectServer": "Išrinkti serverį" + "HeaderSelectServer": "Išrinkti serverį", + "LabelCorruptedFrames": "Sugadinti kadrai:", + "HeaderNavigation": "Navigacija", + "HeaderFavoritePlaylists": "Mėgstami Grojaraščiai", + "ApiKeysCaption": "Įjungtų API raktų sąrašas", + "Episode": "Episodas", + "CopyStreamURLError": "Klaida kopijuojant URL.", + "ClientSettings": "Kliento Nustatymai", + "ButtonTogglePlaylist": "Grojaraštis", + "ButtonToggleContextMenu": "Daugiau", + "ButtonSplit": "Skirstyti", + "AskAdminToCreateLibrary": "Prašyti administratoriaus, kad sukurtų mediateka.", + "Album": "Albumas", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/lv.json b/src/strings/lv.json index 8c19b23ad74..18346bbda0d 100644 --- a/src/strings/lv.json +++ b/src/strings/lv.json @@ -16,7 +16,7 @@ "MinutesAfter": "minūtes pēc", "MetadataManager": "Metadatu Pārvaldnieks", "Metadata": "Metadati", - "MessageYouHaveVersionInstalled": "", + "MessageYouHaveVersionInstalled": "Jums pašlaik ir uzstādīta versija {0}.", "MessageUnableToConnectToServer": "Mēs pašlaik nevaram sazināties ar izvēlēto serveri. Pārliecinies ka tas strādā, un mēģini vēlreiz.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Sekojošie multvides ceļi tiks noņemti no tavas bibliotēkas:", "MessageSettingsSaved": "Iestatījumi saglabāti.", @@ -239,7 +239,7 @@ "Kids": "Bērni", "Items": "Vienumi", "ItemCount": "{0} vienumi", - "InstallingPackage": "Instalē {0}", + "InstallingPackage": "Instalē {0} (versija {1})", "Images": "Attēli", "Identify": "Identificēt", "Horizontal": "Horizontāls", @@ -266,7 +266,6 @@ "HeaderSeasons": "Sezonas", "HeaderSchedule": "Grafiks", "HeaderRevisionHistory": "Revīziju Vēsture", - "HeaderRestartingServer": "Restartē Serveri", "HeaderRestart": "Restartēt", "HeaderRemoveMediaLocation": "Noņemt Multvides Atrašanās Vietu", "HeaderRemoveMediaFolder": "Noņemt Multvides Mapi", @@ -455,7 +454,7 @@ "DrmChannelsNotImported": "Kanāli ar DRM netiks importēti.", "DownloadsValue": "{0} lejupielādes", "Download": "Lejupielādēt", - "DisplayModeHelp": "Izvēlies ekrāna veidu, uz kura tu izmanto Jellyfin.", + "DisplayModeHelp": "Izvēlies izkārtojuma veidu, kuru tu gribi priekš šī interfeisa.", "DoNotRecord": "Neierakstīt", "DisplayInOtherHomeScreenSections": "Rādīt mājas ekrāna sadaļās kā jaunākā multvide un turpini skatīties", "DisplayInMyMedia": "Rādīt mājas ekrānā", @@ -603,19 +602,19 @@ "AsManyAsPossible": "Cik vien iespējams", "Artists": "Izpildītāji", "Art": "Māksla", - "AroundTime": "Ap {0}", + "AroundTime": "Ap", "Anytime": "Jebkad", - "AnyLanguage": "Jebkura valoda", - "AlwaysPlaySubtitles": "Vienmēr rādīt subtitrus", + "AnyLanguage": "Jebkura Valoda", + "AlwaysPlaySubtitles": "Vienmēr Rādīt", "AllowedRemoteAddressesHelp": "Ar komatiem atdalīts IP adrešu vai IP/tīkla masku saraksts, kas norāda uz tīkliem, kas var pieslēgties attālināti. Ja atstāts tukšs, visas attālinātās adreses tiks atļautas.", "AllowRemoteAccessHelp": "Ja atķeksēts, visi attālinātie savienojumi tiks bloķēti.", "AllowRemoteAccess": "Atļaut attālinātus savienojumus šim Jellyfin Serverim.", - "AllowOnTheFlySubtitleExtraction": " ", + "AllowOnTheFlySubtitleExtraction": "Atļaut subtitru izvilkšanu atskaņošanas laikā", "AllowMediaConversion": "Atļaut multimēdiju pārveidošanu", "AllLibraries": "Visas bibliotēkas", "AllLanguages": "Visas valodas", "AllEpisodes": "Visas epizodes", - "AllComplexFormats": "Visi sarezģītie formāti (ASS, SSA, VOBSUB, PGS, SUB/IDX, utt.)", + "AllComplexFormats": "Visi Sarežģītie formāti (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllChannels": "Visi kanāli", "All": "Viss", "Alerts": "Paziņojumi", @@ -951,8 +950,8 @@ "HeaderCustomDlnaProfiles": "Pielāgoti Profili", "HeaderConfirmProfileDeletion": "Apstiprināt Profila Dzēšanu", "HeaderChapterImages": "Nodaļu Attēli", - "HeaderCastCrew": "Lomas/Apkalpe", - "HeaderCastAndCrew": "Lomas/Apkalpe", + "HeaderCastCrew": "Lomas & Apkalpe", + "HeaderCastAndCrew": "Lomas un Apkalpe", "HeaderAppearsOn": "Redzams", "FFmpegSavePathNotFound": "Mēs nespējām atrast FFmpeg norādītajā ceļā. FFprobe arī ir vajadzīgs, un tam ir jāatrodas tajā pašā mapē. Šīs komponentes parasti tiek apvienotas vienā un tajā pašā lejupielādē. Lūdzu pārbaudiet ceļu un mēģiniet vēlreiz.", "HeaderAdditionalParts": "Papildus Ceļi", @@ -1001,7 +1000,7 @@ "ButtonArrowLeft": "Kreisi", "ButtonArrowDown": "Lejup", "ButtonAddScheduledTaskTrigger": "Pievienot Trigeru", - "BookLibraryHelp": "Audio un teksta grāmatas tiek atbalstītas. Pārskati {0}grāmatu nosaukumu instrukciju{1}.", + "BookLibraryHelp": "Audio un teksta grāmatas tiek atbalstītas. Pārskati {0} grāmatu nosaukumu instrukciju {1}.", "Blacklist": "Melnais saraksts", "AuthProviderHelp": "Izvēlies Autentifikācijas Nodrošinājumu, kas tiks izmantots lai autentificētu šī lietotāja paroli.", "AspectRatio": "Attēla Proporcijas", @@ -1081,5 +1080,158 @@ "OptionAllowBrowsingLiveTv": "Atļaut Tiešraides TV piekļuvi", "MediaInfoForced": "Piespiests", "LabelPublicHttpPortHelp": "Publiskai porta numurs, kas tiks kartēts uz vietējo HTTP portu.", - "LabelOptionalNetworkPath": "(Neobligāts) Koplietota tīkla mape:" + "LabelOptionalNetworkPath": "(Neobligāts) Koplietota tīkla mape:", + "LabelLibraryPageSizeHelp": "Uzstāda vienumu daudzumu, ko rādīt bibliotēkas lapā. Uzstādi uz 0 lai atspējotu lapošanu.", + "LabelLibraryPageSize": "Bibliotēkas lapu lielums:", + "LabelKodiMetadataUserHelp": "Saglabāt skatīšanās datus uz NFO datnēm, lai tos varētu izmantot citas lietotnes.", + "LabelKodiMetadataDateFormatHelp": "Visi datumi iekš NFO datnēm tiks parsēti ar šo formātu.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Izmanto vieglo pin kodu lai piekļūtu klientiem savā vietējā tīklā. Tava parastā parole būs vajadzīga ārpus mājām. Ja pin kods ir atstāts tukšs, tev nevajadzēs paroli savā vietējā mājas tīklā.", + "LabelInNetworkSignInWithEasyPassword": "Iespējot iekštīkla piekļuvi ar manu vieglo pin kodu", + "LabelImportOnlyFavoriteChannels": "Ierobežot uz kanāliem, kas atzīmēti kā favorīti", + "LabelGroupMoviesIntoCollectionsHelp": "Kad tiks rādīti filmu saraksti, filmas kas pieder pie kolekcijas tiks attēlotas kā viens grupēts vienums.", + "LabelServerNameHelp": "Šis vārds tiks izmantots lai identificētu tavu serveri un noklusējumā ir servera datora vārds.", + "LabelExtractChaptersDuringLibraryScan": "Izvilkt sadaļu attēlus bibliotēkas skenēšanas laikā", + "LabelBaseUrlHelp": "Šeit tu vari pievienot pielāgotas apakš-mapes lai piekļūtu serverim no unikālāka URL.", + "LabelBaseUrl": "Pamata URL:", + "LabelEnableSingleImageInDidlLimitHelp": "Dažas ierīces pareizi neatskaņos ja vairāki attēli ir iegulti iekš Didl.", + "LabelEnableSingleImageInDidlLimit": "Ierobežot uz vienu iegulto attēlu", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Nosaka laiku sekundēs starp Jellyfin veiktajiem SSDP meklējumiem.", + "LabelEmbedAlbumArtDidlHelp": "Dažas ierīces labprātāk izmanto šo metodi lai saņemtu albumu vākus. Citas var neatskaņot ar šo opciju ieslēgtu.", + "LabelDroppedFrames": "Nomestie kadri:", + "LabelDownMixAudioScaleHelp": "Pastiprināt audio lejupmiksēšanas laikā. Vērtība viens paturēs oriģinālo skaļumu.", + "LabelDownMixAudioScale": "Audio pastiprinājums lejupmiksējot:", + "LabelDisplayMissingEpisodesWithinSeasons": "Rādīt trūkstošās epizodes sezonās", + "LabelDateAddedBehaviorHelp": "Ja atrodas metadatu vērtība, tā vienmēr tiks izmantota pirms jebkuras no šīm opcijām.", + "LabelDashboardTheme": "Servera vadības paneļa tēma:", + "LabelCustomDeviceDisplayNameHelp": "Ievadi pielāgotu displeja vārdu vai atstāj tukšu lai izmantotu ierīces noteikto.", + "LabelCachePathHelp": "Nosaki pielāgotu atrašanās vietu priekš keša datnēm kā attēliem. Atstāj tukšu lai izmantotu servera noklusējumu.", + "LabelAllowedRemoteAddressesMode": "Attālās IP adreses filtra režīms:", + "LabelAllowedRemoteAddresses": "Attālās IP adreses filtrs:", + "LabelAlbumArtPN": "Albumu vāku PN:", + "LabelAirsBeforeSeason": "Tiešraidē pirms sezonas:", + "LabelAirsBeforeEpisode": "Tiešraidē pirms epizodes:", + "LabelAirsAfterSeason": "Tiešraidē pēc sezonas:", + "HeaderSubtitleProfilesHelp": "Subtitru profili apraksta ierīces atbalstītos subtitru formātus.", + "HeaderPendingInvitations": "Gaidošie Uzaicinājumi", + "HeaderKeepSeries": "Paturēt Sēriju", + "HeaderFavoritePlaylists": "Atskaņošanas Sarakstu Favorīti", + "ApiKeysCaption": "Saraksts ar pašlaik iespējotajām API atslēgām", + "EncoderPresetHelp": "Izvēlies ātrāku vērtību lai uzlabotu veiktspēju, vai lēnāku vērtību lai uzlabotu kvalitāti.", + "FetchingData": "Iegūst papildu datus", + "ErrorDeletingItem": "Notika kļūda dzēšot vienumu no Jellyfin Servera. Lūdzu pārliecinies vai Jellyfin Server ir rakstoša piekļuve pie satura mapes un mēģini vēlreiz.", + "ErrorAddingTunerDevice": "Kļūda pievienojot tūnera ierīci. Lūdzu pārliecinies ka tā ir pieejama un mēģini vēlreiz.", + "ErrorAddingMediaPathToVirtualFolder": "Notika kļūda pievienojot satura ceļu. Lūdzu pārliecinies ka ceļš ir derīgs un ka Jellyfin Servera procesam ir piekļuve tai vietai.", + "Episode": "Epizode", + "DeviceAccessHelp": "Tas attiecas tikai uz ierīcēm, kas var tikt unikāli identificētas un neaizliegs piekļuvi no pārlūka. Filtrējot lietotāju ierīču piekļuvi neatļaus tiem izmantot jaunas ierīces, līdz tās nav tikušas šeit atļautas.", + "DeinterlaceMethodHelp": "Izvēlies rindpārlēces sakļaušanas (deinterlacing) metodi kad tiek trans-kodēts rindpārlēces izvērsts (interlaced) saturs.", + "CustomDlnaProfilesHelp": "Izveido pielāgotu profilu priekš jaunas ierīces, vai pārraksti sistēmas profilu.", + "ColorTransfer": "Krāsu pārsūtīšana", + "ClientSettings": "Klientu Iestatījumi", + "ButtonTogglePlaylist": "Atskaņošanas Saraksts", + "ButtonToggleContextMenu": "Vairāk", + "BurnSubtitlesHelp": "Nosaka, vai serverim ir jāiededzina subtitri video trans-kodēšanas laikā. To nedarot tiks stipri palielināta veiktspēja. Izvēlies Auto lai iededzinātu uz attēliem bāzētus formātus (VOBSUB, PGS, SUB, IDX, …) un noteiktus ASS vai SSA subtitrus.", + "Artist": "Izpildītājs", + "AllowOnTheFlySubtitleExtractionHelp": "Iegultie subtitri var tikt izvilkto no video un nogādāti klientiem kā parasts teksts, lai nevajadzētu veikt lieku video trans kodēšanu. Uz dažām sistēmām tas var aizņemt ilgu laiku un likt video atskaņošanai uzkārties izvilkšanas procesa laikā. Atspējo šo lai iegultos subtitrus iededzinātu video trans kodēšanas veidā, kad tos noklusēti neatbalsta klienta ierīce.", + "AlbumArtist": "Albuma Izpildītājs", + "Album": "Albums", + "PleaseRestartServerName": "Lūdzu atsāknē savu Jellyfin Serveri - {0}.", + "PlayNextEpisodeAutomatically": "Atskaņot nākamo epizodi automātiski", + "PlayFromBeginning": "Atskaņot no sākuma", + "PlayCount": "Atskaņošanas reizes", + "PlaybackData": "Atskaņošanas Dati", + "Person": "Persona", + "PerfectMatch": "Ideāla saderība", + "PasswordResetHeader": "Atiestatīt Paroli", + "PasswordResetConfirmation": "Vai tu tiešām gribi atiestatīt paroli?", + "PasswordResetComplete": "Parole ir atiestatīta.", + "PasswordMatchError": "Parolei un paroles pārbaudei ir jāsakrīt.", + "ParentalRating": "Vecāku vērtējums", + "PackageInstallFailed": "{0} (versija {1}) instalācija neizdevās.", + "PackageInstallCompleted": "{0} (versija {1}) instalācija pabeigta.", + "PackageInstallCancelled": "{0} (versija {1}) instalācija atcelta.", + "Overview": "Pārskats", + "OtherArtist": "Cits izpildītājs", + "OriginalAirDateValue": "Oriģinālais tiešraides datums: {0}", + "OptionWeekly": "Iknedēļu", + "OptionWeekends": "Nedēļas nogalēs", + "OptionWeekdays": "Darba dienās", + "OptionWednesday": "Trešdiena", + "OptionWakeFromSleep": "Modināt no miega", + "OptionUnplayed": "Neatskaņots", + "OptionTuesday": "Otrdiena", + "OptionTrackName": "Celiņa Nosaukums", + "OptionThumb": "Sīktēls", + "OptionThursday": "Ceturtdiena", + "OptionSunday": "Svētdiena", + "OptionSaveMetadataAsHidden": "Saglabāt metadatus un attēlus kā paslēptas datnes", + "OptionSaturday": "Sestdiena", + "OptionRequirePerfectSubtitleMatch": "Lejupielādēt tikai subtitrus, kas perfekti sakrīt ar manām video datnēm", + "OptionRegex": "Regex", + "OptionProtocolHls": "HTTP Tiešraides Straumes", + "OptionProfilePhoto": "Attēls", + "OptionPremiereDate": "Pirmizrādes Datums", + "OptionPoster": "Plakāts", + "OptionPlayCount": "Atskaņošanas Skaits", + "OptionOnAppStartup": "Lietotnes sāknēšanā", + "OptionNameSort": "Nosaukums", + "OptionMissingEpisode": "Trūkstošās Epizodes", + "OptionMax": "Maksimums", + "OptionLoginAttemptsBeforeLockoutHelp": "Vērtība nulle nozīmē noklusējuma trīs mēģinājumu priekš lietotājiem un piecu priekš administratoriem izmantošanu. Uzstādot uz -1 atspējos funkciju.", + "OptionLoginAttemptsBeforeLockout": "Nosaka, cik daudz nepareizi piekļuves mēģinājumi var notikt pirms notiek bloķēšana.", + "OptionImdbRating": "IMDb Vērtējums", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Ja iespējots, šie pieprasījumi tiks veikti bet ignorēs baitu diapazona hederu.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorēt trans-kodēšanas baitu diapazona pieprasījumus", + "OptionHomeVideos": "Attēli", + "OptionHlsSegmentedSubtitles": "HLS segmentēti subtitri", + "OptionHideUserFromLoginHelp": "Noderīgs lai paslēptu privātus vai paslēptus administratora kontus. Šim lietotājam būs jāieiet manuāli, ievadot savu lietotājvārdu un paroli.", + "OptionHideUser": "Paslēpt šo lietotāju no piekļuves ekrāniem", + "OptionHasThemeVideo": "Tēmu Video", + "OptionExternallyDownloaded": "Ārējā lejupielāde", + "OptionEstimateContentLength": "Novērtēt satura ilgumu trans-kodēšanas laikā", + "OptionEquals": "Vienāds ar", + "OptionEnableM2tsModeHelp": "Iespējo m2ts režīmu iekodējot uz mpegts.", + "OptionEnableM2tsMode": "Iespējot M2ts režīmu", + "OptionEnableForAllTuners": "Iespējot visām tūnera ierīcēm", + "OptionEnableExternalContentInSuggestionsHelp": "Atļaut interneta treilerus un tiešraides TV programmām tikt iekļautām ieteiktajā saturā.", + "OptionEnableExternalContentInSuggestions": "Iespējot ārējo saturu iekš ieteikumiem", + "OptionEnableAccessToAllChannels": "Iespējot piekļuvi visiem kanāliem", + "OptionEnableAccessFromAllDevices": "Iespējot piekļuvi no visām ierīcēm", + "OptionEmbedSubtitles": "Ievietot iekš konteinera", + "OptionDownloadThumbImage": "Sīktēls", + "OptionDownloadPrimaryImage": "Galvenais", + "OptionDownloadImagesInAdvanceHelp": "Noklusējumā, lielākā daļa attēlu tiek lejupielādēti kad tie ir pieprasīti Jellyfin lietotnē. Iespējo šo opciju lai iepriekšēji lejupielādētu visus attēlus, kad jauns saturs tiek importēts. Tas var radīt ievērojami ilgākus bibliotēkas skenējumus.", + "OptionDownloadImagesInAdvance": "Lejupielādēt attēlus iepriekš", + "OptionDownloadBackImage": "Aizmugure", + "OptionDownloadArtImage": "Māksla", + "OptionDisableUserHelp": "Ja atspējots, serveris neatļaus nekādus savienojumus no šī lietotāja. Esošie savienojumi tiks pēkšņi apturēti.", + "OptionDisableUser": "Atspējot šo lietotāju", + "OptionDatePlayed": "Atskaņošanas Datums", + "OptionDateAddedImportTime": "Izmantot datumu, kad tika ieskanēts bibliotēkā", + "OptionDateAddedFileTime": "Izmantot datņu izveides datumu", + "OptionDateAdded": "Pievienošanas Datums", + "OptionCriticRating": "Kritiķu Vēŗtējums", + "OptionCommunityRating": "Kopienas Vērtējums", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionBlockLiveTvChannels": "Tiešraides TV Kanāli", + "OptionAllowVideoPlaybackTranscoding": "Atļaut video atskaņošanu, kas prasa trans-kodēšanu", + "OptionAllowVideoPlaybackRemuxing": "Atļaut video atskaņošanu, kas prasa pārveidošanu bez pārkodēšanas", + "OptionAllowUserToManageServer": "Atļaut šim lietotājam pārvaldīt serveri", + "OptionAllowSyncTranscoding": "Atļaut satura lejupielādi un sinhronizēšanu, kas prasa trans-kodēšanu", + "OptionAllowRemoteSharedDevicesHelp": "DLNA ierīces tiek uzskatītas kā koplietotas līdz lietotājs sāk tās vadīt.", + "OptionAllowRemoteSharedDevices": "Atļaut koplietotu ierīču attālinātu vadību", + "OptionAllowMediaPlayback": "Atļaut satura atskaņošanu", + "OptionAllowContentDownloading": "Atļaut satura lejupielādi un sinhronizāciju", + "OptionForceRemoteSourceTranscoding": "Piespiest trans-kodēšanu no attāliem satura avotiem (kā Tiešraides TV)", + "OptionAllowAudioPlaybackTranscoding": "Atļaut audio atskaņošanu, kas prasa trans-kodēšanu", + "OnlyForcedSubtitlesHelp": "Tikai subtitri, kas atzīmēti kā piespiedu tiks ielādēti.", + "OnlyForcedSubtitles": "Tikai Piespiedu", + "MoreUsersCanBeAddedLater": "Papildus lietotāji var tikt pievienoti vēlāk no vadības paneļa.", + "MessagePluginConfigurationRequiresLocalAccess": "Lai konfigurētu šo paplašinājumu lūdzu tieši ieej savā lokālajā serverī.", + "MessagePleaseEnsureInternetMetadata": "Lūdzu pārliecinies vai metadatu lejupielāde no interneta ir iespējota.", + "MessageUnauthorizedUser": "Jūs neesat autorizēti lai piekļūtu serverim šajā brīdī. Lūdzu sazinieties ar savu servera administratoru priekš papildus informācijas.", + "MessageInstallPluginFromApp": "Šis paplašinājums ir jāuzstāda no lietotnes, kurā jūs to vēlaties izmantot.", + "LabelEmbedAlbumArtDidl": "Ievietot albumu vākus iekš Didl", + "LabelSelectFolderGroups": "Automātiski grupēt saturu no sekojošām datnēm skatos kā Filmas, Mūzika un TV:", + "AllowFfmpegThrottlingHelp": "Kad trans-kodējums vai remux tiek pietiekami tālu priekšā pašreizējai atskaņošanas vietai, process tiks pauzēts lai patērētu mazāk resursu. Tas ir noderīgākais skatoties bez biežas pārlēkšanas. Atspējo šo ja saskaries ar atskaņošanas problēmām.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/mr.json b/src/strings/mr.json new file mode 100644 index 00000000000..37cbc403dec --- /dev/null +++ b/src/strings/mr.json @@ -0,0 +1,103 @@ +{ + "ButtonOpen": "उघडा", + "ButtonOk": "ऑन", + "ButtonOff": "ऑफ", + "ButtonNextTrack": "पुढचा ट्रॅक", + "ButtonNew": "नवीन", + "ButtonNetwork": "नेटवर्क", + "ButtonMore": "अजून", + "ButtonLearnMore": "अधिक माहिती", + "ButtonInfo": "माहिती", + "ButtonHelp": "मदत", + "ButtonGuide": "गाईड", + "ButtonGotIt": "समजले", + "ButtonForgotPassword": "पासवर्ड विसरलो", + "ButtonEditImages": "चित्र संपादित करा", + "ButtonEdit": "संपादित करा", + "ButtonDownload": "डाउनलोड करा", + "ButtonDown": "खाली", + "ButtonDeleteImage": "चित्र काढून टाका", + "ButtonDelete": "काढून टाका", + "ButtonChangeServer": "सर्व्हर बदला", + "ButtonCancel": "रद्द करा", + "ButtonBack": "मागे", + "ButtonAudioTracks": "ऑडिओ ट्रॅक", + "ButtonArrowUp": "वर", + "ButtonArrowRight": "उजवीकडे", + "ButtonArrowLeft": "डावीकडे", + "ButtonArrowDown": "खाली", + "ButtonAddUser": "प्रयोक्ता जोडा", + "ButtonAddServer": "सर्व्हर जोडा", + "ButtonAdd": "जोडा", + "Books": "पुस्तकं", + "Blacklist": "ब्लॅकलिस्ट", + "BirthPlaceValue": "जन्म ठिकाण: {0}", + "BirthLocation": "जन्मस्थान", + "BirthDateValue": "जन्म: {0}", + "Backdrops": "पार्श्वभूमी", + "Backdrop": "पार्श्वभूमी", + "Auto": "आपोआप", + "Audio": "ऑडिओ", + "AttributeNew": "नवीन", + "AspectRatio": "अ‍ॅस्पेक्ट रेशो", + "AsManyAsPossible": "जमतील तितके", + "Artists": "संगीतकार", + "Artist": "संगीतकार", + "Anytime": "कधीही", + "AnyLanguage": "कोणतीही भाषा", + "AlwaysPlaySubtitles": "नेहमीच प्ले करा", + "AllLibraries": "सर्व संग्रहालय", + "AllLanguages": "सर्व भाषा", + "AllEpisodes": "सर्व भाग", + "AllChannels": "सर्व वाहिन्या", + "All": "सर्व", + "Albums": "अल्बम", + "AlbumArtist": "अल्बम संगीतकार", + "Album": "अल्बम", + "AddedOnValue": "{0} जोडले", + "Add": "जोडा", + "Actor": "अभिनेता", + "EnableBackdrops": "पार्श्वभूमी", + "EditSubtitles": "सबटायटल संपादित करा", + "EditMetadata": "मेटाडेटा संपादित करा", + "EditImages": "चित्र संपादित करा", + "Edit": "संपादित करा", + "DrmChannelsNotImported": "डी.आर.एम. असलेल्या वाहिन्या आयात केल्या जाणार नाहीत.", + "DownloadsValue": "{0} डाउनलोड", + "Download": "डाउनलोड", + "Down": "खाली", + "DoNotRecord": "रेकॉर्ड करू नका", + "Directors": "दिग्दर्शक", + "Director": "दिग्दर्शक", + "Desktop": "डेस्कटॉप", + "DeleteImageConfirmation": "तुम्हाला नक्की हे चित्र काढून टाकायचे आहे का?", + "Delete": "काढून टाका", + "DeleteImage": "चित्र काढून टाका", + "ConfirmEndPlayerSession": "{0} येथील जेलिफिन बंद करावे का?", + "Composer": "संगीत दिग्दर्शक", + "Channels": "वाहिन्या", + "ChannelNumber": "वाहिनी क्रमांक", + "Categories": "वर्ग", + "CancelRecording": "रेकॉर्डिंग रद्द करा", + "ButtonWebsite": "संकेतस्थळ", + "ButtonViewWebsite": "संकेतस्थळ पाहा", + "ButtonUp": "वर", + "ButtonTrailer": "ट्रेलर", + "ButtonSubtitles": "सबटायटल", + "ButtonStop": "थांबा", + "ButtonStart": "सुरू करा", + "ButtonSettings": "सेटिंग्झ", + "ButtonSend": "पाठवा", + "ButtonSelectView": "दृष्य निवडा", + "ButtonSelectServer": "सर्व्हर निवडा", + "ButtonSelectDirectory": "डिरेक्टरी निवडा", + "ButtonSearch": "शोधा", + "ButtonScanAllLibraries": "सर्व संग्रहालय स्कॅन करा", + "ButtonRename": "नाव बदला", + "ButtonRemove": "काढून टाका", + "ButtonPreviousTrack": "मागचा ट्रॅक", + "ButtonPlay": "प्ले", + "ButtonPause": "पॉझ", + "ButtonSyncPlay": "SyncPlay", + "Collections": "संग्रह" +} diff --git a/src/strings/ms.json b/src/strings/ms.json index c377e52af1f..d5f9259c21e 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -102,5 +102,17 @@ "HeaderContinueWatching": "Terus Menonton", "Genres": "Genre-genre", "Collections": "Koleksi", - "Channels": "Saluran" + "Channels": "Saluran", + "ButtonSyncPlay": "SyncPlay", + "Movies": "Filem", + "HeaderNextUp": "Seterusnya", + "HeaderLiveTV": "TV Siaran Langsung", + "HeaderFavoriteSongs": "Lagu-lagu Kegemaran", + "HeaderFavoriteShows": "Rancangan-rancangan Kegemaran", + "HeaderFavoriteEpisodes": "Episod-episod Kegemaran", + "HeaderFavoriteArtists": "Artis-artis Kegemaran", + "HeaderFavoriteAlbums": "Album-album Kegemaran", + "HeaderAlbumArtists": "Album Artis-artis", + "Folders": "Fail-fail", + "Favorites": "Kegemaran" } diff --git a/src/strings/nb.json b/src/strings/nb.json index 3d863d17e07..2e18df6b9c8 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -21,7 +21,7 @@ "AllowRemoteAccess": "Tillat eksterne tilkoblinger til denne Jellyfin-serveren.", "AllowRemoteAccessHelp": "Alle tilkoblinger via nettverk vil blokkeres dersom du ikke aktiverer denne innstillingen.", "Anytime": "Når som helst", - "AroundTime": "Rundt {0}", + "AroundTime": "Rundt", "Artists": "Artister", "AsManyAsPossible": "Så mange som mulig", "AspectRatio": "Størrelsesforhold", @@ -31,7 +31,7 @@ "BirthDateValue": "Født: {0}", "BirthLocation": "Fødested", "BirthPlaceValue": "Fødested: {0}", - "BookLibraryHelp": "Lyd- og tekstbøker støttes. Se igjennom {0}navneguiden for bøker{1}.", + "BookLibraryHelp": "Lyd- og tekstbøker støttes. Se igjennom {0} navneguiden for bøker {1}.", "Books": "Bøker", "BrowsePluginCatalogMessage": "Bla i tilleggskatalogen vår for å se tilgjengelige applikasjonstillegg.", "ButtonAdd": "Legg til", @@ -382,7 +382,7 @@ "Images": "Bilder", "ImportFavoriteChannelsHelp": "Hvis aktivert, vil kun kanaler som er markert som favoritt på tuneren bli importert.", "ImportMissingEpisodesHelp": "Hvis aktivert, vil informasjon om manglende episoder importeres til Jellyfin-databasen og de vil vises under sesonger og serier. Dette kan føre til at skanning av biblioteket tar betydelig lengre tid.", - "InstallingPackage": "Installerer {0}", + "InstallingPackage": "Installerer {0} (versjon {1})", "InstantMix": "Direktemiks", "ItemCount": "{0} elementer", "Items": "Elementer", @@ -457,7 +457,7 @@ "LabelEmbedAlbumArtDidl": "Bygg inn albumbilder i DIDL", "LabelEmbedAlbumArtDidlHelp": "Noen enheter foretrekker denne metoden for å motta albumomslag. Andre vil kunne få problemer med avspilling hvis dette alternativet er aktivert.", "LabelEnableAutomaticPortMap": "Aktiver automatisk portmapping", - "LabelEnableAutomaticPortMapHelp": "Forsøk å automatisk koble den eksterne porten til den lokale porten via UPnP. Dette fungerer ikke med alle rutere. Endringer trer ikke kraft før serveren startes på nytt.", + "LabelEnableAutomaticPortMapHelp": "Automatisk koble eksterne porter på ruteren til lokale porter på serveren din via UPnP. Dette fungerer ikke med alle rutere eller nettverkskonfigurasjoner. Endringer trer ikke kraft før serveren startes på nytt.", "LabelEnableBlastAliveMessages": "Kringkast keepalive-meldinger", "LabelEnableBlastAliveMessagesHelp": "Aktiver hvis serveren ikke blir konsekvent oppdaget av andre UPnP-enheter på nettverket ditt.", "LabelEnableDlnaClientDiscoveryInterval": "Oppdateringsintervall for klient (i sekunder)", @@ -566,7 +566,7 @@ "LabelNumberOfGuideDays": "Antall dager med TV-guidedata som skal lastes ned:", "LabelNumberOfGuideDaysHelp": "Nedlasting av TV-guide for flere dager gir muligheten for å planlegge lenger i forveien og for å se flere listinger, men vil også føre til at nedlastningen tar lenger tid. Auto vil velge basert på antall kanaler.", "LabelOptionalNetworkPath": "(Valgfritt) Delt nettverksmappe:", - "LabelOptionalNetworkPathHelp": "Hvis denne mappen er delt på nettverket, vil Jellyfin-apper på andre enheter kunne få tilgang til mediefilene direkte dersom du spesifiserer nettverksbanen her.", + "LabelOptionalNetworkPathHelp": "Hvis denne mappen er delt på nettverket, vil Jellyfin-apper på andre enheter kunne få tilgang til mediefilene direkte dersom du spesifiserer nettverksbanen her. For eksempel, {0} eller {1}.", "LabelOriginalAspectRatio": "Originalt sideforhold:", "LabelOriginalTitle": "Original tittel:", "LabelOverview": "Oversikt:", @@ -729,7 +729,7 @@ "MessageNoAvailablePlugins": "Ingen tilgjengelige programtillegg.", "MessageNoMovieSuggestionsAvailable": "Ingen filmforslag er tilgjengelige enda. Begynn å se og vurdere filmer, og kom tilbake hit etterpå for se anbefalingene dine.", "MessageNoPluginsInstalled": "Du har ingen programtillegg installert.", - "MessageNoTrailersFound": "Ingen trailere funnet. Installer trailer-tilleggskanalen for å forbedre filmopplevelsen ved å legge til et bibliotek med trailere fra Internett.", + "MessageNoTrailersFound": "Installer trailer-tilleggskanalen for å forbedre filmopplevelsen ved å legge til et bibliotek med trailere fra Internett.", "MessageNothingHere": "Ingenting her.", "MessagePasswordResetForUsers": "Følgende brukere har fått passordet sitt tilbakestilt. De kan nå logge inn med PIN-kodene som ble brukt til å utføre tilbakestillingen.", "MessagePlayAccessRestricted": "Avspilling av dette innholdet er for tiden begrenset. Ta kontakt med serverens administrator for mer informasjon.", @@ -850,7 +850,7 @@ "OptionMissingEpisode": "Manglende episoder", "OptionMonday": "Mandag", "OptionNameSort": "Navn", - "OptionNew": "Ny...", + "OptionNew": "Ny…", "OptionNone": "Ingen", "OptionOnAppStartup": "Ved applikasjonsoppstart", "OptionOnInterval": "Per intervall", @@ -891,9 +891,9 @@ "OptionWeekly": "Ukentlig", "OriginalAirDateValue": "Original utgivelsedato: {0}", "Overview": "Oversikt", - "PackageInstallCancelled": "{0}-installasjon avbrutt.", - "PackageInstallCompleted": "{0}-installering fullført.", - "PackageInstallFailed": "{0}-installasjon feilet.", + "PackageInstallCancelled": "Installasjon av {0} (versjon {1}) avbrutt.", + "PackageInstallCompleted": "Installasjon av {0} (versjon {1}) fullført.", + "PackageInstallFailed": "Installasjon av {0} (versjon {1}) feilet.", "ParentalRating": "Aldersgrense", "PasswordMatchError": "Passord og passordbekreftelsen må matche.", "PasswordResetComplete": "Passordet har blitt tilbakestilt.", @@ -1124,14 +1124,14 @@ "Art": "Omslagsbilde", "AnyLanguage": "Hvilket som helst språk", "AlwaysPlaySubtitlesHelp": "Undertekster som matcher foretrukket språk vil bli tatt i bruk uansett språk på lydspor.", - "AlwaysPlaySubtitles": "Bruk alltid undertekster", + "AlwaysPlaySubtitles": "Bruk alltid", "AllowedRemoteAddressesHelp": "Kommaseparert liste over IP-addresser eller IP/nettverksmaske for nettverk som er godkjent for eksterne tilkoblinger. Dersom feltet står tomt er alle adresser tillatt.", "AllowOnTheFlySubtitleExtractionHelp": "Integrerte undertekster kan hentes ut fra videoer og bli levert til klienter i klartekst for å unngå omkoding av video. På noen systemer kan dette ta lang tid og føre til opphold i avspillingen samtidig som prosessen pågår. Deaktiver innstillingen for å brenne inn underteksten i videoen ved hjelp av omkoding når undertekstformatet ikke er støttet av klienten.", "AllowOnTheFlySubtitleExtraction": "Tillat at undertekster hentes ut fortløpende", "AllLanguages": "Alle språk", - "AllComplexFormats": "Alle avanserte formater (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alle avanserte formater (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AccessRestrictedTryAgainLater": "Tilgang er for øyeblikket begrenset. Vennligst prøv igjen senere.", - "BurnSubtitlesHelp": "Angir om serveren skal brenne inn teksting når videoer konverteres, basert på tekstformatet. Ytelsen på serveren vil forbedres dersom tekstingen ikke brennes inn. Velg Automatisk for å brenne inn bildebaserte formater (VOBSUB, PGS, SUB/IDX, osv.) og enkelte ASS/SSA-undertekster.", + "BurnSubtitlesHelp": "Angir om serveren skal brenne inn teksting når videoer konverteres, basert på tekstformatet. Ytelsen på serveren vil forbedres dersom tekstingen ikke brennes inn. Velg Automatisk for å brenne inn bildebaserte formater (VOBSUB, PGS, SUB, IDX, ...) og enkelte ASS eller SSA-undertekster.", "General": "Generelt", "ChangingMetadataImageSettingsNewContent": "Endringer gjort i innstillinger for metadata eller omslagsbilder vil kun gjelde nytt innhold i biblioteket ditt. For å endre eksisterende innhold, må du oppdatere dets metadata manuelt.", "DefaultSubtitlesHelp": "Undertekster lastes inn basert på flaggene \"standard\" og \"tvungen\" i videoens integrerte metadata. Språkpreferanser tas høyde for dersom flere valg er tilgjengelig.", @@ -1218,7 +1218,7 @@ "Unmute": "Skru på lyd", "OptionIsHD": "HD", "ButtonAddImage": "Legg til bilde", - "DisplayModeHelp": "Velg hvilken slags skjerm du bruker Jellyfin på.", + "DisplayModeHelp": "Velg hvilket brukergrensesnitt oppsett du vil ha.", "DownloadsValue": "{0} nedlastninger", "EnableNextVideoInfoOverlayHelp": "Vis informasjon om den neste videoen i spillelisten ved slutten av en video.", "ExtractChapterImagesHelp": "Uthenting av kapittelbilder vil gjøre det mulig for klienter å vise bilder i menyer for å velge kapitel. Denne prosessen kan være treg, ressurskrevende, og kan kreve flere gigabyte med lagringsplass. Prosessen kjører når videoer oppdages, samt som en daglig planlagt hendelse. Tidsplanen kan endres i innstillinger for planlagte hendelser. Det anbefales ikke at denne prosessen kjøres når det er mange aktive brukere innlogget.", @@ -1226,7 +1226,6 @@ "HeaderKodiMetadataHelp": "For å aktivere eller deaktivere NFO-metadata, gå til bibliotekoppsettet i Jellyfin og finn valgene for lagring av metadata.", "OptionArtist": "Artist", "HeaderPhotoAlbums": "Fotoalbum", - "HeaderRestartingServer": "Serveren starter på nytt", "HeaderSecondsValue": "{0} sekunder", "LabelAllowedRemoteAddresses": "Filter for eksterne IP-adresser:", "LabelAllowedRemoteAddressesMode": "Modus for filter for eksterne IP-adresser:", @@ -1261,7 +1260,7 @@ "LaunchWebAppOnStartupHelp": "Åpne web-klienten i din standard nettleser når serveren starter opp. Dette vil ikke skje ved omstart av serveren.", "LearnHowYouCanContribute": "Finn ut hvordan du kan bidra.", "SeriesYearToPresent": "{0} - Nå", - "LabelBaseUrlHelp": "Du kan legge til din egen undermappe her for å få tilgang til serveren fra en mer unik URL.", + "LabelBaseUrlHelp": "Legger til en egendefinert undermappe til serverens nettadresse. For eksempel: http://example.com/<baseurl>", "LabelFont": "Skrifttype:", "LabelMatchType": "Matchtype:", "OptionPosterCard": "Plakatkort", @@ -1378,12 +1377,12 @@ "MusicAlbum": "Musikkalbum", "MusicArtist": "Musikkartist", "Next": "Neste", - "NoSubtitles": "Ingen teksting", + "NoSubtitles": "Ingen", "NoSubtitlesHelp": "Undertekster vil ikke lastes inn som standard. De kan fortsatt aktiveres manuelt under avspilling.", "Normal": "Normal", - "OnlyForcedSubtitles": "Kun tvungen teksting", + "OnlyForcedSubtitles": "Kun tvungen", "OnlyForcedSubtitlesHelp": "Bare last inn undertekster merket som tvungen.", - "OnlyImageFormats": "Bare bildeformater (VOBSUB, PGS, SUB, osv.)", + "OnlyImageFormats": "Bare bildeformater (VOBSUB, PGS, SUB)", "OptionAllowMediaPlaybackTranscodingHelp": "Begrensning av tilgang til omkoding kan føre til avspillingsfeil i Jellyfin-apper grunnet at medieformater ikke støtes.", "OptionAllowSyncTranscoding": "Tillat nedlastning og synkronisering av media som krever omkoding", "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", @@ -1407,7 +1406,6 @@ "Previous": "Forrige", "Primary": "Primær", "RequiredForAllRemoteConnections": "Påkrevd for alle eksterne tilkoblinger", - "RestartPleaseWaitMessage": "Vennligst vent mens Jellyfin-serveren skrus av og restartes. Dette kan ta et minutt eller to.", "RunAtStartup": "Kjør ved oppstart", "SaveSubtitlesIntoMediaFolders": "Lagre undertekster i mediemapper", "SaveSubtitlesIntoMediaFoldersHelp": "Lagring av undertekster ved siden av videofilene vil gjøre det lettere å behandle dem.", @@ -1451,11 +1449,9 @@ "HeaderFavoritePeople": "Favorittpersoner", "Raised": "Opphøyet", "ButtonSplit": "Del opp", - "SelectAdminUsername": "Vennligst velg et brukernavn for administrator kontoen. ", + "SelectAdminUsername": "Vennligst velg et brukernavn for administrator-kontoen.", "HeaderNavigation": "Navigering", "MessageConfirmAppExit": "Vil du avslutte?", - "EnableFastImageFadeInHelp": "Bruk rask inntoning av animasjon for lastede bilder", - "EnableFastImageFadeIn": "Rask bilde inntoning", "CopyStreamURLError": "Det var en feil under kopiering av URL'en.", "LabelVideoResolution": "Oppløsning på video:", "LabelPlayerDimensions": "Dimensjoner på avspiller:", @@ -1465,7 +1461,116 @@ "OptionForceRemoteSourceTranscoding": "Tvunget omkoding av eksterne media-kilder (som Direkte-TV)", "NoCreatedLibraries": "Det virker som at du ikke har opprettet noen biblioteker ennå. {0}Vil du opprette et nå?{1}", "AskAdminToCreateLibrary": "Spør en administrator om å lage et bibliotek.", - "PlaybackErrorNoCompatibleStream": "Det oppstod et problem med klientprofilering, og serveren sender ikke et kompatibelt medieformat.", + "PlaybackErrorNoCompatibleStream": "Denne klienten er ikke kompatibel med mediet, og serveren sender ikke et kompatibelt medieformat.", "AllowFfmpegThrottlingHelp": "Når en omkoding eller ompakking kommer langt nok foran den nåværende avspillingsposisjonen stoppes prosessen slik at den bruker mindre ressurser. Dette er mest nyttig når du sjeldent bytter posisjon i videoen. Slå av dette hvis du opplever problemer med avspilling.", - "AllowFfmpegThrottling": "Begrens hastighet på omkoding" + "AllowFfmpegThrottling": "Begrens hastighet på omkoding", + "Episode": "Episode", + "ClientSettings": "Klientinstillinger", + "BoxSet": "Samleboks", + "Artist": "Artist", + "AlbumArtist": "Albumartist", + "Album": "Album", + "LabelLibraryPageSizeHelp": "Velger hvor mange elementer som skal bli vist på en bibliotek side. Velg 0 for å deaktivere.", + "LabelLibraryPageSize": "Biblioteks side størrelse:", + "LabelDeinterlaceMethod": "Deinterlacing metode:", + "HeaderFavoritePlaylists": "Favorittspillelister", + "DeinterlaceMethodHelp": "Velg deinterlacing metoden som skal bli brukt når man transkoder interlaced innhold.", + "ButtonTogglePlaylist": "Spilleliste", + "ButtonToggleContextMenu": "Mer", + "EnableBlurhashHelp": "Bilder som fortsatt lastes inn vil vises med en tåkete plassholder", + "EnableBlurhash": "Aktiver tåkete plassholdere for bilder", + "UnsupportedPlayback": "Jellyfin kan ikke dekryptere innhold beskyttet med DRM, men alt innhold vil bli forsøkt uansett, inkludert beskyttede titler. Noen filer kan fremstå helt svarte grunnet kryptering eller andre ikke støttede funksjoner, som interaktive titler.", + "OnApplicationStartup": "Ved oppstart av applikasjonen", + "EveryXHours": "Hver {0}. time", + "EveryHour": "Hver time", + "EveryXMinutes": "Hvert {0}. minutt", + "OnWakeFromSleep": "Ved oppvåkning fra hvilemodus", + "WeeklyAt": "{0}er kl. {1}", + "DailyAt": "Daglig kl. {0}", + "LastSeen": "Sist sett {0}", + "PersonRole": "som {0}", + "ListPaging": "{0}-{1} av {2}", + "WriteAccessRequired": "Jellyfin Server krever skrivetilgang til denne mappen. Vennligst påse at skrivetilgang er gitt og prøv igjen.", + "PathNotFound": "Banen ble ikke funnet. Påse at banen er gyldig og prøv igjen.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Spor", + "TabDVR": "Opptak", + "SyncPlayAccessHelp": "Velg tilgangsnivået denne brukeren skal ha til SyncPlay-funksjonen. SyncPlay muliggjør synkronisert avspilling med andre enheter.", + "Filter": "Filter", + "New": "Ny", + "ShowMore": "Vis mer", + "ShowLess": "Vis mindre", + "Season": "Sesong", + "SaveChanges": "Lagre endringer", + "ReleaseGroup": "Utgivelsesgruppe", + "PreferEmbeddedEpisodeInfosOverFileNames": "Foretrekk innebygd episodeinformasjon framfor filnavn", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Dette bruker episodeinformasjonen fra innebygd metadata hvis tilgjengelig.", + "Person": "Person", + "OtherArtist": "Annen artist", + "Movie": "Film", + "MessageSyncPlayErrorMedia": "Kunne ikke aktivere SyncPlay! Mediefeil.", + "MessageSyncPlayErrorMissingSession": "Kunne ikke aktivere SyncPlay! Mangler sesjon.", + "MessageSyncPlayErrorNoActivePlayer": "Ingen aktiv avspiller funnet. SyncPlay har blitt deaktivert.", + "MessageSyncPlayErrorAccessingGroups": "En feil oppstod under lasting av gruppelisten.", + "MessageSyncPlayLibraryAccessDenied": "Tilgang til dette innholdet er begrenset.", + "MessageSyncPlayJoinGroupDenied": "Tillatelse kreves for å bruke SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Tillatelse kreves for å opprette en gruppe.", + "MessageSyncPlayGroupDoesNotExist": "Kunne ikke bli med i gruppen fordi den ikke finnes.", + "MessageSyncPlayPlaybackPermissionRequired": "Tillatelse for avspilling kreves.", + "MessageSyncPlayNoGroupsAvailable": "Ingen grupper tilgjengelige. Begynn å spill noe først.", + "MessageSyncPlayGroupWait": "{0} bufrer...", + "MessageSyncPlayUserLeft": "{0} har forlatt gruppen.", + "MessageSyncPlayUserJoined": "{0} har blitt med i gruppen.", + "MessageSyncPlayDisabled": "SyncPlay deaktivert.", + "MessageSyncPlayEnabled": "SyncPlay aktivert.", + "MessageUnauthorizedUser": "Du har ikke autorisert tilgang til serveren akkurat nå. Vennligst kontakt serveradministratoren din for mer informasjon.", + "LabelSyncPlayAccess": "SyncPlay-tilgang", + "LabelSyncPlayAccessNone": "Deaktivert for denne brukeren", + "LabelSyncPlayAccessJoinGroups": "Tillat brukeren å bli med i grupper", + "LabelSyncPlayAccessCreateAndJoinGroups": "Tillat brukeren å lage og bli med i grupper", + "LabelSyncPlayLeaveGroupDescription": "Deaktiver SyncPlay", + "LabelSyncPlayLeaveGroup": "Forlat gruppe", + "LabelSyncPlayNewGroupDescription": "Lag en ny gruppe", + "LabelSyncPlayNewGroup": "Ny gruppe", + "LabelSyncPlaySyncMethod": "Synkroniseringsmetode:", + "LabelSyncPlayPlaybackDiff": "Tidsforskjell på avspilling:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tidsforskyvning mot serveren:", + "LabelRequireHttpsHelp": "Hvis valgt, vil serveren automatisk omdirigere alle HTTP-forespørsler til HTTPS. Dette har ingen effekt dersom serveren ikke lytter etter HTTPS.", + "LabelRequireHttps": "Krev HTTPS", + "LabelNightly": "Nattlig", + "LabelStable": "Stabil", + "LabelChromecastVersion": "Chromecast-versjon", + "LabelEnableHttpsHelp": "Aktiverer at serveren skal lytte på den valgte HTTPS-porten. Et gyldig sertifikat må også være konfigurert for at dette skal tre i kraft.", + "LabelEnableHttps": "Aktiver HTTPS", + "HeaderSyncPlayEnabled": "SyncPlay aktivert", + "HeaderSyncPlaySelectGroup": "Bli med i en gruppe", + "HeaderServerAddressSettings": "Innstillinger for serveradresse", + "HeaderRemoteAccessSettings": "Innstillinger for fjerntilgang", + "HeaderHttpsSettings": "HTTPS-innstillinger", + "HeaderDVR": "Opptak", + "ApiKeysCaption": "Liste over aktive API-nøkler", + "EnableDetailsBannerHelp": "Viser et bildebanner øverst på detaljsiden.", + "EnableDetailsBanner": "Detaljebanner", + "ButtonSyncPlay": "SyncPlay", + "LabelRepositoryName": "Navn på kilde", + "LabelRepositoryUrlHelp": "Lokasjonen til kilde-manifestet som du ønsker å inkludere.", + "LabelRepositoryUrl": "URL for kilde", + "HeaderNewRepository": "Ny kilde", + "MessageNoRepositories": "Ingen kilder.", + "EnableFasterAnimationsHelp": "Bruk raskere animasjoner og overganger", + "EnableFasterAnimations": "Raskere animasjoner", + "EnableDecodingColorDepth10Vp9": "Aktiver maskinvaredekoding for VP9", + "EnableDecodingColorDepth10Hevc": "Aktiver maskinvaredekoding for 10-Bit HEVC", + "ButtonPlayer": "Spiller", + "ButtonCast": "Rollebesetning", + "Writers": "Forfattere", + "TabRepositories": "Kilder", + "MessageNoGenresAvailable": "Aktiver noen metadata-kilder for å hente sjangre fra internett.", + "MessageAddRepository": "Hvis du ønsker å legge til en ny kilde klikker du på knappen ved siden av overskriften og fyller ut forespurt informasjon.", + "LabelRepositoryNameHelp": "Et egendefinert navn for å skille denne kilden fra andre som er lagt til på serveren din.", + "ClearQueue": "Tøm køen", + "ViewAlbumArtist": "Vis albumartist", + "StopPlayback": "Stopp avspilling" } diff --git a/src/strings/nl.json b/src/strings/nl.json index 2acfe83414b..242c3d7f157 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -14,7 +14,7 @@ "Alerts": "Meldingen", "All": "Alle", "AllChannels": "Alle kanalen", - "AllComplexFormats": "Alle complexe formaten (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alle Complexe Formaten (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", "AllEpisodes": "Alle afleveringen", "AllLanguages": "Alle talen", "AllLibraries": "Alle bibliotheken", @@ -26,7 +26,7 @@ "AllowRemoteAccess": "Externe verbindingen met deze Jellyfin Server toestaan.", "AllowRemoteAccessHelp": "Indien niet aangevinkt worden alle externe verbindingen geblokkeerd.", "AllowedRemoteAddressesHelp": "Komma-gescheiden lijst van IP-adressen of IP/netmask adressen voor netwerken die op afstand verbinding mogen maken. Indien blanco, worden alle externe adressen toegestaan.", - "AlwaysPlaySubtitles": "Altijd ondertitels weergeven", + "AlwaysPlaySubtitles": "Altijd afspelen", "AlwaysPlaySubtitlesHelp": "Ondertitels die met de taalvoorkeur overeenkomen worden weergegeven, ongeacht de audiotaal.", "AnyLanguage": "Elke taal", "Anytime": "Op elk moment", @@ -49,7 +49,7 @@ "BoxRear": "Hoes (achterkant)", "Browse": "Bladeren", "BrowsePluginCatalogMessage": "Bekijk de Plugin catalogus voor beschikbare Plug-ins.", - "BurnSubtitlesHelp": "Bepaalt of de server ondertitels moet inbranden wanneer video's op basis van het soort ondertitels geconverteerd moeten worden. Het inbranden van ondertitels heeft een negatief effect op de server performance. Selecteer Automatisch om op afbeelding gebaseerde formaten (VOBSUB, PGS, SUB/IDX etc.) en bepaalde ASS/SSA ondertitels in te branden.", + "BurnSubtitlesHelp": "Bepaalt of de server ondertitels moet branden bij het transcoderen van video's. Als u dit vermijd, worden de prestaties aanzienlijk verbeterd. Selecteer Auto om op afbeeldingen gebaseerde formaten (VOBSUB, PGS, SUB/IDX etc.) en bepaalde ASS/SSA ondertitels te branden.", "ButtonAdd": "Toevoegen", "ButtonAddMediaLibrary": "Voeg Media Bibliotheek toe", "ButtonAddScheduledTaskTrigger": "Trigger Toevoegen", @@ -178,7 +178,7 @@ "DisplayInOtherHomeScreenSections": "In secties van het startscherm weergeven, zoals \"Recente Media\" en \"Verder Kijken\"", "DisplayMissingEpisodesWithinSeasons": "Toon ontbrekende afleveringen binnen een seizoen", "DisplayMissingEpisodesWithinSeasonsHelp": "Dit moet ook worden ingeschakeld voor TV bibliotheken in de server configuratie.", - "DisplayModeHelp": "Selecteer het scherm type waar u Jellyfin op draait", + "DisplayModeHelp": "Selecteer het schermtype waar Jellyfin op draait.", "DoNotRecord": "Niet opnemen", "Down": "Omlaag", "Download": "Downloaden", @@ -195,10 +195,10 @@ "EnableColorCodedBackgrounds": "Kleurgecodeerde achtergronden", "EnableDisplayMirroring": "Beeld spiegelen", "EnableExternalVideoPlayers": "Externe video spelers", - "EnableExternalVideoPlayersHelp": "Een menu voor externe spelers zal worden getoond bij het afspelen van video's", + "EnableExternalVideoPlayersHelp": "Een menu voor externe spelers wordt getoond bij het afspelen van video's.", "EnableHardwareEncoding": "Activeer hardwaredecodering", "EnableNextVideoInfoOverlay": "Toon informatie over de volgende video tijdens het afspelen", - "EnableNextVideoInfoOverlayHelp": "Toon informatie over de volgende video in de afspeellijst aan het einde van de video", + "EnableNextVideoInfoOverlayHelp": "Toon informatie over de volgende video in de afspeellijst aan het einde van de video.", "EnablePhotos": "Foto's weergeven", "EnablePhotosHelp": "Afbeeldingen worden herkend en weergegeven naast andere mediabestanden.", "EnableStreamLooping": "Livestreams automatisch herhalen", @@ -254,7 +254,7 @@ "EncoderPresetHelp": "Kies een hogere waarde om de prestaties, of een tragere waarde om de kwaliteit te verbeteren.", "HDPrograms": "HD Programma's", "HandledByProxy": "Behandeld door reverse proxy", - "HardwareAccelerationWarning": "Hardwareversnelling inschakelen kan instabiliteit veroorzaken in sommige omgevingen. Zorg ervoor dat uw besturingssysteem en videostuurprogramma's volledig up-to-date zijn. Als u problemen ondervindt bij het afspelen van video, nadat u dit hebt ingeschakeld, moet u de instelling terugzetten naar Auto.", + "HardwareAccelerationWarning": "Hardwareversnelling inschakelen kan instabiliteit veroorzaken in sommige omgevingen. Zorg ervoor dat uw besturingssysteem en videostuurprogramma's volledig up-to-date zijn. Als u problemen ondervindt bij het afspelen van video, nadat u dit hebt ingeschakeld, moet u de instelling terugzetten naar geen.", "HeaderAccessSchedule": "Schema Toegang", "HeaderAccessScheduleHelp": "Maak een toegangsschema om de toegang tot bepaalde tijden te beperken.", "HeaderActiveDevices": "Actieve apparaten", @@ -278,7 +278,7 @@ "HeaderAudioBooks": "Luisterboeken", "HeaderAudioSettings": "Audio Instellingen", "HeaderAutomaticUpdates": "Automatische updates", - "HeaderBlockItemsWithNoRating": "Blokkeer items met geen of niet herkende keurinformatie.", + "HeaderBlockItemsWithNoRating": "Blokkeer items met geen of niet herkende beoordelingsinformatie:", "HeaderBooks": "Boeken", "HeaderBranding": "Huisstijl", "HeaderCancelRecording": "Opname Annuleren", @@ -411,7 +411,7 @@ "HeaderSelectTranscodingPath": "Selecteer Tijdelijke Transcodeer Pad", "HeaderSelectTranscodingPathHelp": "Bladeren of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet beschrijfbaar zijn.", "HeaderSendMessage": "Stuur bericht", - "HeaderSeries": "Series:", + "HeaderSeries": "Series", "HeaderSeriesOptions": "Series Opties", "HeaderSeriesStatus": "Seriestatus", "HeaderServerSettings": "Server Instellingen", @@ -461,9 +461,9 @@ "Images": "Afbeeldingen", "ImportFavoriteChannelsHelp": "Bij inschakelen zullen alleen kanalen geïmporteerd worden die op de tuner als favoriet aangemerkt zijn.", "ImportMissingEpisodesHelp": "Indien ingeschakeld, wordt informatie over ontbrekende afleveringen in uw Jellyfin de database geïmporteerd en weergegeven in de seizoenen en series. Dit kan aanzienlijk langere bibliotheekscans veroorzaken.", - "InstallingPackage": "Installeren van {0}", + "InstallingPackage": "Installeren van {0} (versie {1})", "Kids": "Kinderen", - "Label3DFormat": "3D formaat", + "Label3DFormat": "3D formaat:", "LabelAbortedByServerShutdown": "(Afgebroken door afsluiten van de server)", "LabelAccessDay": "Dag van de week:", "LabelAccessEnd": "Eind tijd:", @@ -491,7 +491,7 @@ "LabelArtists": "Artiest:", "LabelArtistsHelp": "Scheidt meerdere met een ;", "LabelAudioLanguagePreference": "Voorkeurs audiotaal:", - "LabelAutomaticallyRefreshInternetMetadataEvery": "Vernieuw metagegevens automatisch van het internet", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Vernieuw metagegevens automatisch van het internet:", "LabelBindToLocalNetworkAddress": "Binden aan het lokale netwerk adres:", "LabelBindToLocalNetworkAddressHelp": "Optioneel. Overrule het lokale IP-adres om aan de http-server te binden. Indien leeg gelaten, zal de server binden aan alle beschikbare adressen. Het veranderen van deze waarde vereist herstarten van Jellyfin Server.", "LabelBirthDate": "Geboortedatum:", @@ -506,7 +506,7 @@ "LabelCertificatePassword": "Certificaat paswoord:", "LabelCertificatePasswordHelp": "Als je certificaat een paswoord vereist, vul het dan hier in alstublieft.", "LabelChannels": "Kanalen:", - "LabelCollection": "Collectie", + "LabelCollection": "Collectie:", "LabelCommunityRating": "Beoordeling gemeenschap:", "LabelContentType": "Inhoud type:", "LabelCountry": "Land:", @@ -547,7 +547,7 @@ "LabelEmbedAlbumArtDidl": "Insluiten van albumhoezen in Didl", "LabelEmbedAlbumArtDidlHelp": "Sommige apparaten prefereren deze methode voor het verkrijgen van albumhoezen. Anderen kunnen falen om af te spelen met deze optie ingeschakeld.", "LabelEnableAutomaticPortMap": "Schakel automatisch poort vertalen in", - "LabelEnableAutomaticPortMapHelp": "Probeer om de publieke poort automatisch te vertalen naar de lokale poort via UPnP. Dit werkt niet op alle routers.", + "LabelEnableAutomaticPortMapHelp": "Publieke poort automatisch doorsturen naar een lokale poort via UPnP. Dit werkt niet op alle routers en netwerk configuraties. De wijzigingen worden pas actief na een herstart van de server.", "LabelEnableBlastAliveMessages": "Alive berichten zenden", "LabelEnableBlastAliveMessagesHelp": "Zet dit aan als de server niet betrouwbaar door andere UPnP-apparaten op uw netwerk wordt gedetecteerd.", "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients (seconden)", @@ -563,7 +563,7 @@ "LabelEnableRealtimeMonitorHelp": "Wijzigingen aan bestanden worden op ondersteunde bestandssystemen direct verwerkt.", "LabelEnableSingleImageInDidlLimit": "Beperk tot één enkele ingesloten afbeelding", "LabelEnableSingleImageInDidlLimitHelp": "Sommige apparaten zullen niet goed weergeven als er meerdere afbeeldingen ingesloten zijn in Didl.", - "LabelEndDate": "Eind datum|", + "LabelEndDate": "Eind datum:", "LabelEpisodeNumber": "Afleveringsnummer:", "LabelEvent": "Gebeurtenis:", "LabelEveryXMinutes": "Iedere:", @@ -581,7 +581,7 @@ "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films die tot een collectie behoren worden weergegeven als een gegroepeerd object.", "LabelEncoderPreset": "H264 codering preset:", "LabelHardwareAccelerationType": "Hardware acceleratie:", - "LabelHardwareAccelerationTypeHelp": "Dit is een experimentele functie die alleen beschikbaar is op ondersteunde systemen.", + "LabelHardwareAccelerationTypeHelp": "Hardwarematige versnelling vereist extra configuratie.", "LabelHomeNetworkQuality": "Thuisnetwerk kwaliteit:", "LabelHomeScreenSectionValue": "Beginscherm sectie {0}:", "LabelHttpsPort": "Lokale HTTPS poort nummer:", @@ -666,14 +666,14 @@ "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", "LabelOptionalNetworkPath": "(Optioneel) Gedeelde netwerkmap:", - "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen.", + "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen. Bijvoorbeeld {0} or {1}.", "LabelOriginalAspectRatio": "Originele aspect ratio:", "LabelOriginalTitle": "Orginele titel:", "LabelOverview": "Overzicht:", "LabelParentNumber": "Bovenliggend nummer:", "LabelParentalRating": "Kijkwijzer classificatie:", "LabelPassword": "Wachtwoord:", - "LabelPasswordConfirm": "Wachtworod (Bevestig)", + "LabelPasswordConfirm": "Wachtwoord (Bevestig):", "LabelPasswordRecoveryPinCode": "Pincode:", "LabelPath": "Pad:", "LabelPersonRole": "Rol:", @@ -681,17 +681,17 @@ "LabelPlaceOfBirth": "Geboorteplaats:", "LabelPlayDefaultAudioTrack": "Standaard audio spoor afspelen ongeacht de taal", "LabelPlaylist": "Afspeellijst:", - "LabelPostProcessor": "Nabewerkings toepassing:", - "LabelPostProcessorArguments": "Nabewerkings command line argumenten:", + "LabelPostProcessor": "Nabewerkings- toepassing:", + "LabelPostProcessorArguments": "Nabewerkings command lijn argumenten:", "LabelPostProcessorArgumentsHelp": "Gebruik {path} als het pad naar het opnamebestand.", - "LabelPreferredDisplayLanguage": "Voorkeurs weergavetaal:", + "LabelPreferredDisplayLanguage": "Voorkeur weergavetaal:", "LabelPreferredDisplayLanguageHelp": "Vertaling van Jellyfin is een voortdurend project.", - "LabelPreferredSubtitleLanguage": "Voorkeurstaal ondertitels:", + "LabelPreferredSubtitleLanguage": "Voorkeurstaal ondertiteling:", "LabelPrevious": "Vorige", "LabelProfileAudioCodecs": "Geluidscodecs:", - "LabelProfileCodecsHelp": "Gescheiden door een komma. Deze kan leeg gelaten worden om te laten gelden voor alle codecs.", - "LabelProfileContainersHelp": "Gescheiden door een komma. Deze kan leeg gelaten worden om te laten gelden voor alle containers.", - "LabelProtocol": "Protokol:", + "LabelProfileCodecsHelp": "Gescheiden door een komma. Dit kan leeg worden gelaten om te laten gelden voor alle codecs.", + "LabelProfileContainersHelp": "Gescheiden door een komma. Dit kan leeg worden gelaten om te laten gelden voor alle containers.", + "LabelProtocol": "Protocol:", "LabelProtocolInfoHelp": "De waarde die wordt gebruikt bij het reageren op GetProtocolInfo verzoeken van het apparaat.", "LabelPublicHttpPort": "Publieke HTTP poort nummer:", "LabelPublicHttpPortHelp": "Het publieke poortnummer dat moet worden toegewezen aan de lokale HTTP poort.", @@ -702,12 +702,12 @@ "LabelRecord": "Opnemen:", "LabelRecordingPath": "Standaard opname pad:", "LabelRecordingPathHelp": "Geef de standaard locatie op om opnamen op te slaan. Indien leeg gelaten, zal de map van de server-programma gegevens worden gebruikt.", - "LabelRefreshMode": "Vernieuw-modus", + "LabelRefreshMode": "Ververs-modus:", "LabelReleaseDate": "Uitgave datum:", "LabelRemoteClientBitrateLimit": "Internet streaming bitrate limiet (Mbps):", - "LabelRemoteClientBitrateLimitHelp": "Een optionele bitrate limiet per stream voor alle apparaten buiten het netwerk. Dit is handig om te voorkomen dat apparaten een hogere bitrate vragen dan je internetverbinding aan kan. Dit kan een verhoogde belasting van de CPU in je server veroorzaken om om videos direct te transcoderen naar een lagere bitrate.", + "LabelRemoteClientBitrateLimitHelp": "Een optionele bitrate per stream limiet voor alle apparaten buiten het netwerk. Dit is handig om te voorkomen dat apparaten een hogere bitrate vragen dan je internetverbinding aan kan. Dit kan een verhoogde belasting van de CPU in je server veroorzaken om videos direct te transcoderen naar een lagere bitrate.", "LabelRuntimeMinutes": "Speelduur (minuten):", - "LabelSaveLocalMetadata": "Afbeeldingen opslaan in de mediamappen", + "LabelSaveLocalMetadata": "Afbeeldingen opslaan in mediamappen", "LabelSaveLocalMetadataHelp": "Door afbeeldingen op te slaan in de mediamappen kunnen ze makkelijker worden aangepast.", "LabelScheduledTaskLastRan": "Laatste keer {0}, duur {1}.", "LabelScreensaver": "Schermbeveiliging:", @@ -722,31 +722,31 @@ "LabelSeriesRecordingPath": "Serieopname pad (optioneel):", "LabelServerHost": "Server:", "LabelServerHostHelp": "192.168.1.100:8096 of https://mijnserver.nl", - "LabelSimultaneousConnectionLimit": "Gelijktijdige streams limiet:", - "LabelSkipBackLength": "Terugspoellengte", - "LabelSkipForwardLength": "Vooruitspoellengte", + "LabelSimultaneousConnectionLimit": "Gelijktijdige stream limiet:", + "LabelSkipBackLength": "Terugspoellengte:", + "LabelSkipForwardLength": "Vooruitspoellengte:", "LabelSkipIfAudioTrackPresent": "Overslaan als het standaard geluidsspoor overeenkomt met de taal van de download", - "LabelSkipIfAudioTrackPresentHelp": "Vink dit uit om ervoor te zorgen dat alle video's ondertiteings krijgen, ongeacht de geluidstaal.", + "LabelSkipIfAudioTrackPresentHelp": "Vink dit uit om ervoor te zorgen dat alle video's ondertitelingen krijgen, ongeacht de geluidstaal.", "LabelSkipIfGraphicalSubsPresent": "Overslaan als de video al ingesloten ondertiteling heeft", "LabelSkipIfGraphicalSubsPresentHelp": "Tekstversies van ondertiteling opslaan zal video's efficiënter overbrengen en de kans op transcodering van video's verkleinen.", "LabelSonyAggregationFlags": "Sony aggregatie vlaggen:", "LabelSonyAggregationFlagsHelp": "Bepaalt de inhoud van het aggregationFlags element in de urn: schemas-sonycom av namespace.", "LabelSortBy": "Sorteren op:", - "LabelSortOrder": "Sorteervolgorde:", + "LabelSortOrder": "Sorteer volgorde:", "LabelSortTitle": "Sorteer titel:", "LabelSoundEffects": "Geluidseffecten:", "LabelSource": "Bron:", - "LabelSpecialSeasonsDisplayName": "De weergavenaam van de speciale seizoen:", + "LabelSpecialSeasonsDisplayName": "De weergavenaam van een speciaal seizoen:", "LabelSportsCategories": "Sport categorieën:", "LabelStartWhenPossible": "Start indien mogelijk:", "LabelStopWhenPossible": "Stop indien mogelijk:", "LabelStopping": "Stoppen", - "LabelSubtitleDownloaders": "Ondertiteldownloaders:", + "LabelSubtitleDownloaders": "Ondertiteling downloaders:", "LabelSubtitleFormatHelp": "Voorbeeld: srt", - "LabelSubtitlePlaybackMode": "Ondertitel mode:", - "LabelSubtitles": "Ondertitels", + "LabelSubtitlePlaybackMode": "Ondertitel modus:", + "LabelSubtitles": "Ondertiteling", "LabelSupportedMediaTypes": "Ondersteunde Media Types:", - "LabelTVHomeScreen": "TV mode begin scherm", + "LabelTVHomeScreen": "TV mode begin scherm:", "LabelTextBackgroundColor": "Tekst achtergrond kleur:", "LabelTextColor": "Tekst kleur:", "LabelTextSize": "Tekst grootte:", @@ -756,9 +756,9 @@ "LabelTitle": "Titel:", "LabelTrackNumber": "Tracknummer:", "LabelTranscodingAudioCodec": "Geluidscodec:", - "LabelTranscodingTempPathHelp": "Specificeer een eigen pad voor de transcode bestanden die geleverd worden aan cliënten. Laat leeg om het server standaard te gebruiken.", - "LabelTranscodingThreadCount": "Aantal transcodeer threads:", - "LabelTranscodingThreadCountHelp": "Selecteer het maximale aantal threads die gebruikt mogen worden om te transcoderen. Bij een lager aantal zal het CPU gebruik lager zijn, maar kan de afspeelkwaliteit minder zijn.", + "LabelTranscodingTempPathHelp": "Specificeer een eigen pad voor de transcode bestanden die geleverd worden aan gebruikers. Laat leeg om de server standaard te gebruiken.", + "LabelTranscodingThreadCount": "Aantal transcodeer draden:", + "LabelTranscodingThreadCountHelp": "Selecteer het maximale aantal draden die gebruikt mogen worden om te kunnen transcoderen. Bij een lager aantal zal het processorgebruik lager zijn, maar kan de afspeelkwaliteit minder zijn voor een vloeiende ervaring.", "LabelTunerIpAddress": "Tuner IP adres:", "LabelTunerType": "Soort Tuner:", "LabelTypeText": "Tekst", @@ -766,8 +766,8 @@ "LabelUser": "Gebruiker:", "LabelUserAgent": "User-agent:", "LabelUserLibrary": "Gebruikers Bibliotheek:", - "LabelUserLibraryHelp": "Selecteer welke gebruikers bibliotheek weergegeven moet worden op het apparaat. Laat leeg standaardinstelling te gebruiken.", - "LabelUserRemoteClientBitrateLimitHelp": "Schrijf de standaard globale waarde gedefineerd in de server afspeel instellingen.", + "LabelUserLibraryHelp": "Selecteer welke gebruikers bibliotheek weergegeven moeten worden op het apparaat. Laat leeg om de standaardinstelling te gebruiken.", + "LabelUserRemoteClientBitrateLimitHelp": "Schrijf de standaard globale waarde gedefinieerd in de server afspeel instellingen.", "LabelUsername": "Gebruikersnaam:", "LabelVaapiDevice": "VA API Apparaat:", "LabelVaapiDeviceHelp": "Dit is de render knooppunt dat wordt gebruikt voor hardwareversnelling.", @@ -792,11 +792,11 @@ "List": "Lijst", "LiveBroadcasts": "Live uitzendingen", "ManageLibrary": "Bibliotheek beheren", - "ManageRecording": "Beheren opnames", + "ManageRecording": "Beheer opnames", "MapChannels": "Map Kanalen", - "MarkPlayed": "Markeren als Afgespeeld", - "MarkUnplayed": "Markeren als Niet Afgespeeld", - "MaxParentalRatingHelp": "Media met een hogere classificatie wordt niet weergegeven", + "MarkPlayed": "Markeren als afgespeeld", + "MarkUnplayed": "Markeren als niet afgespeeld", + "MaxParentalRatingHelp": "Media met een hogere classificatie wordt niet weergegeven voor deze gebruiker.", "MediaInfoAnamorphic": "Anamorf", "MediaInfoAspectRatio": "Beeld verhouding", "MediaInfoBitDepth": "Bitdiepte", @@ -811,48 +811,48 @@ "MediaInfoPixelFormat": "Pixel formaat", "MediaInfoProfile": "Profiel", "MediaInfoResolution": "Resolutie", - "MediaInfoSampleRate": "Samplesnelheid", + "MediaInfoSampleRate": "Bemonsteringsfrequentie", "MediaInfoSize": "Grootte", "MediaInfoTimestamp": "Tijdstempel", - "MediaIsBeingConverted": "De media wordt geconverteerd naar een formaat dat compatible is met het apparaat dat wordt gebruikt om de media af te spelen.", - "MessageAlreadyInstalled": "Deze versie is al geïnstalleerd", - "MessageAreYouSureDeleteSubtitles": "Weet u zeker dat u dit ondertitelbestand wilt verwijderen?", + "MediaIsBeingConverted": "De media wordt geconverteerd naar een formaat dat leesbaar is met het apparaat dat wordt gebruikt om de media af te spelen.", + "MessageAlreadyInstalled": "Deze versie is al geïnstalleerd.", + "MessageAreYouSureDeleteSubtitles": "Weet u zeker dat u dit ondertitelingsbestand wilt verwijderen?", "MessageAreYouSureYouWishToRemoveMediaFolder": "Weet u zeker dat u deze media map wilt verwijderen?", "MessageConfirmDeleteGuideProvider": "Weet u zeker dat u deze gidsprovider wilt verwijderen?", "MessageConfirmDeleteTunerDevice": "Weet u zeker dat u dit apparaat wilt verwijderen?", "MessageConfirmProfileDeletion": "Weet u zeker dat u dit profiel wilt verwijderen?", - "MessageConfirmRecordingCancellation": "Opnemen annuleren?", + "MessageConfirmRecordingCancellation": "Opname annuleren?", "MessageConfirmRemoveMediaLocation": "Weet u zeker dat u deze locatie wilt verwijderen?", "MessageConfirmRestart": "Weet u zeker dat u Jellyfin Server wilt herstarten?", - "MessageConfirmRevokeApiKey": "Weet u zeker dat u deze api key in wilt trekken? De verbinding met Jellyfin Server zal direct verbroken worden.", + "MessageConfirmRevokeApiKey": "Weet u zeker dat u deze api sleutel in wilt trekken? De verbinding met Jellyfin Server zal direct verbroken worden.", "MessageConfirmShutdown": "Weet u zeker dat u de server wilt afsluiten?", "MessageContactAdminToResetPassword": "Neem contact op met de server beheerder om uw wachtwoord te resetten.", "MessageCreateAccountAt": "Maak een account bij {0}", - "MessageDeleteTaskTrigger": "Weet u zeker dat u deze taak trigger wilt verwijderen?", + "MessageDeleteTaskTrigger": "Weet u zeker dat u deze signaal taak wilt verwijderen?", "MessageDirectoryPickerBSDInstruction": "Voor BSD kan het noodzakelijk zijn opslag op uw FreeNAS Jail te configureren voordat Jellyfin het kan benaderen.", - "MessageDirectoryPickerInstruction": "Netwerk paden kunnen handmatig worden ingevoerd in het geval de Netwerk knop faalt om uw apparatuur te lokaliseren . Bijvoorbeeld: {0} of {1}.", + "MessageDirectoryPickerInstruction": "Netwerk paden kunnen handmatig worden ingevoerd in het geval de Netwerk knop faalt om uw apparatuur te lokaliseren. Bijvoorbeeld: {0} of {1}.", "MessageDirectoryPickerLinuxInstruction": "Voor Linux op Arch Linux, CentOS, Debian, Fedora, openSUSE, of Ubuntu, moet u de service-gebruiker ten minste leestoegang tot uw opslaglocaties verlenen.", "MessageDownloadQueued": "Download in de wachtrij geplaatst.", - "MessageEnablingOptionLongerScans": "Inschakelen van deze optie kan leiden tot langere doorlooptijd van bibliotheek scans.", + "MessageEnablingOptionLongerScans": "Het inschakelen van deze optie kan leiden tot langere doorlooptijd van bibliotheek scans.", "MessageFileReadError": "Er is een fout opgetreden bij het lezen van het bestand. Probeer het opnieuw.", - "MessageForgotPasswordFileCreated": "Het volgende bestand met instructies hoe nu verder te gaan is gemaakt:", + "MessageForgotPasswordFileCreated": "Het volgende bestand is gecreëerd op uw server en bevat instructies om verder te gaan:", "MessageForgotPasswordInNetworkRequired": "Probeer de wachtwoord herstel procedure opnieuw vanuit uw thuisnetwerk.", "MessageInstallPluginFromApp": "Deze plugin moet geïnstalleerd worden vanuit de app waarin u het wilt gebruiken.", "MessageInvalidForgotPasswordPin": "Er is een ongeldige of verlopen pincode ingegeven. Probeer opnieuw.", - "MessageInvalidUser": "Foutieve gebruikersnaam of wachtwoord. Probeer opnieuw.", + "MessageInvalidUser": "Incorrecte gebruikersnaam of wachtwoord. Probeer opnieuw.", "MessageItemSaved": "Item opgeslagen.", - "MessageItemsAdded": "Items toegevoegd", + "MessageItemsAdded": "Items toegevoegd.", "MessageLeaveEmptyToInherit": "Leeg laten om instellingen van bovenliggend item of de algemene waarde over te nemen.", "MessageNoAvailablePlugins": "Geen beschikbare Plugins.", "MessageNoMovieSuggestionsAvailable": "Er zijn momenteel geen film suggesties beschikbaar. Begin met het bekijken en waardeer uw films, kom daarna terug om uw aanbevelingen te bekijken.", - "MessageNoPluginsInstalled": "U heeft geen Plugins geïnstalleerd.", - "MessageNoTrailersFound": "Geen trailers gevonden. Installeer het Trailers kanaal en verbeter uw film ervaring door middel van een bibliotheek met internet trailers.", + "MessageNoPluginsInstalled": "U heeft geen plugins geïnstalleerd.", + "MessageNoTrailersFound": "Installeer het Trailers kanaal om uw film ervaring te verbeteren door middel van het toevoegen van een bibliotheek met internet trailers.", "MessageNothingHere": "Lijst is leeg.", - "MessagePasswordResetForUsers": "De volgende gebruikers hebben hun wachtwoord laten resetten. Zij kunnen nu inloggen met de pin codes die gebruikt werden om de reset te voltooien.", + "MessagePasswordResetForUsers": "De volgende gebruikers hebben hun wachtwoord laten herstellen. Zij kunnen nu inloggen met de pin codes die gebruikt werden om de herstel te voltooien.", "MessagePlayAccessRestricted": "Afspelen hiervan is op dit moment niet toegestaan. Neem contact op met uw server beheerder voor meer informatie.", "MessagePleaseEnsureInternetMetadata": "Zorg ervoor dat het downloaden van internet-metadata is ingeschakeld.", "MessagePleaseWait": "Even geduld. Dit kan even duren.", - "MessagePluginConfigurationRequiresLocalAccess": "Meld svp. op de lokale server aan om deze plugin te configureren.", + "MessagePluginConfigurationRequiresLocalAccess": "Om deze plugin te configuren moet u zich aanmelden direct op de lokale server.", "MessagePluginInstallDisclaimer": "Plugins ontwikkeld door leden van de Jellyfin gemeenschap zijn een geweldige manier om uw Jellyfin ervaring met extra functies en voordelen te verbeteren. Alvorens te installeren, dient u zich bewust te zijn van de gevolgen die zij kunnen hebben op uw Jellyfin Server, zoals langere bibliotheek scans, extra achtergrondinformatie verwerking, en een verminderde stabiliteit van het systeem.", "MessageReenableUser": "Zie hieronder hoe opnieuw in te schakelen", "MessageSettingsSaved": "Instellingen opgeslagen.", @@ -882,13 +882,13 @@ "NewEpisodesOnly": "Alleen nieuwe afleveringen", "News": "Nieuws", "Next": "Volgende", - "NextUp": "Volgende", + "NextUp": "Hierna", "No": "Nee", "NoNewDevicesFound": "Er zijn geen nieuwe apparaten gevonden. Sluit dit melding en voer handmatig de apparaat gegevens in om een nieuwe tuner toe te voegen.", "NoNextUpItemsMessage": "Niets gevonden. Start met kijken!", "NoPluginConfigurationMessage": "Deze plugin heeft geen instellingen te configureren.", "NoSubtitleSearchResultsFound": "Geen resultaten gevonden.", - "NoSubtitles": "Geen ondertitels", + "NoSubtitles": "Geen ondertiteling", "NoSubtitlesHelp": "Ondertitels worden niet standaard weergegeven. Deze kunnen tijdens het afspelen handmatig worden ingeschakeld.", "None": "Geen", "Normal": "Normaal", @@ -896,14 +896,14 @@ "OneChannel": "Eén kanaal", "OnlyForcedSubtitles": "Alleen geforceerde ondertitels", "OnlyForcedSubtitlesHelp": "Alleen als geforceerd gemarkeerde ondertitels worden geladen.", - "OnlyImageFormats": "Alleen image formaten (VOBSUP, PGS, SUB, etc.)", + "OnlyImageFormats": "Alleen beeld formaten (VOBSUP, PGS, SUB, etc.)", "OptionAdminUsers": "Beheerders", "OptionAlbumArtist": "Albumartiest", "OptionAllUsers": "Alle gebruikers", "OptionAllowAudioPlaybackTranscoding": "Afspelen van geluid via transcoding toestaan", "OptionAllowBrowsingLiveTv": "Live TV toegang toestaan", "OptionAllowContentDownloading": "Media downloaden en synchroniseren toestaan", - "OptionAllowLinkSharing": "Sta social media delen toe", + "OptionAllowLinkSharing": "Sta het delen op social media toe", "OptionAllowLinkSharingHelp": "Alleen webpagina's met media-informatie worden gedeeld. Media-bestanden worden nooit publiekelijk gedeeld. Gedeelde items zijn beperkt in tijd en verlopen na {0} dagen.", "OptionAllowManageLiveTv": "Live TV opname beheer toestaan", "OptionAllowMediaPlayback": "Media afspelen toestaan", @@ -914,7 +914,7 @@ "OptionAllowSyncTranscoding": "Het downloaden en synchroniseren van media via transcoding toestaan", "OptionAllowUserToManageServer": "Deze gebruiker kan de server beheren", "OptionAllowVideoPlaybackRemuxing": "Sta afspelen toe van video die conversie vereist zonder re-encoding", - "OptionAllowVideoPlaybackTranscoding": "Afspelen van video via transcoding toestaan", + "OptionAllowVideoPlaybackTranscoding": "Afspelen van video die transcoderen vereisen toestaan", "OptionArtist": "Artiest", "OptionAscending": "Oplopend", "OptionAutomatic": "Automatisch", @@ -927,8 +927,8 @@ "OptionBlockMusic": "Muziek", "OptionBlockTvShows": "TV Series", "OptionBluray": "Blu-ray", - "OptionCommunityRating": "Gemeenschaps Waardering", - "OptionContinuing": "Wordt vervolgd...", + "OptionCommunityRating": "Algemene Waardering", + "OptionContinuing": "Wordt vervolgd", "OptionCriticRating": "Kritieken", "OptionCustomUsers": "Aangepast", "OptionDaily": "Dagelijks", @@ -937,7 +937,7 @@ "OptionDateAddedImportTime": "Gebruik scan datum", "OptionDatePlayed": "Datum afgespeeld", "OptionDescending": "Aflopend", - "OptionDisableUser": "Dit account uitschakelen", + "OptionDisableUser": "Deze gebruiker uitschakelen", "OptionDisableUserHelp": "Indien uitgeschakeld zal de server geen verbindingen van deze gebruiker toestaan. Bestaande verbindingen zullen abrupt worden beëindigd.", "OptionDislikes": "Niet leuk", "OptionDisplayFolderView": "Toon een mappenweergave als u gewoon Mediamappen wilt weergeven", @@ -956,10 +956,10 @@ "OptionEnableExternalContentInSuggestionsHelp": "Laat internet trailers en live-tv-programma's op te nemen binnen de voorgestelde inhoud.", "OptionEnableForAllTuners": "Inschakelen voor alle tuners", "OptionEnableM2tsMode": "M2ts-modus inschakelen", - "OptionEnableM2tsModeHelp": "m2ts-modus bij het encoderen naar mpegts inschakelen", + "OptionEnableM2tsModeHelp": "M2ts-modus bij het encoderen naar mpegts inschakelen.", "OptionEnded": "Gestopt", "OptionEquals": "Is gelijk aan", - "OptionEstimateContentLength": "Lengte schatten van de inhoud bij het transcoderen", + "OptionEstimateContentLength": "Lengte inschatten van de inhoud bij het transcoderen", "OptionEveryday": "Elke dag", "OptionExternallyDownloaded": "Externe download", "OptionExtractChapterImage": "Inschakelen uitpakken van hoofdstuk afbeeldingen", @@ -967,10 +967,10 @@ "OptionFriday": "Vrijdag", "OptionHasSpecialFeatures": "Extra's", "OptionHasSubtitles": "Ondertiteling", - "OptionHasThemeSong": "Herkenningsmelodie", + "OptionHasThemeSong": "Thema Lied", "OptionHasThemeVideo": "Thema Video", "OptionHideUser": "Verberg deze gebruiker op de aanmeldschermen", - "OptionHideUserFromLoginHelp": "Handig voor pivé of verborgen beheer accounts. De gebruiker zal handmatig m.b.v. gebruikersnaam en wachtwoord aan moeten melden.", + "OptionHideUserFromLoginHelp": "Handig voor pivé of verborgen beheer accounts. De gebruiker zal handmatig moeten inloggen met een gebruikersnaam en wachtwoord.", "OptionHlsSegmentedSubtitles": "HLS gesegmenteerde ondertiteling", "OptionHomeVideos": "Foto's", "OptionIgnoreTranscodeByteRangeRequests": "Transcodeer byte range-aanvragen negeren", @@ -980,7 +980,7 @@ "OptionMissingEpisode": "Ontbrekende Afleveringen", "OptionMonday": "Maandag", "OptionNameSort": "Naam", - "OptionNew": "Nieuw ...", + "OptionNew": "Nieuw…", "OptionNone": "Geen", "OptionOnAppStartup": "Op applicatie start", "OptionOnInterval": "Op interval", @@ -1020,9 +1020,9 @@ "OptionWeekly": "Wekelijks", "OriginalAirDateValue": "Originele uitzenddatum: {0}", "Overview": "Overzicht", - "PackageInstallCancelled": "{0} installatie geannuleerd.", - "PackageInstallCompleted": "{0} installatie voltooid.", - "PackageInstallFailed": "{0} installatie is mislukt.", + "PackageInstallCancelled": "{0} (versie {1}) installatie geannuleerd.", + "PackageInstallCompleted": "{0} (versie {1}) installatie voltooid.", + "PackageInstallFailed": "{0} (versie {1}) installatie is mislukt.", "ParentalRating": "Kijkwijzer classificatie", "PasswordMatchError": "Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.", "PasswordResetComplete": "Het wachtwoord is opnieuw ingesteld.", @@ -1064,7 +1064,7 @@ "Raised": "Verhoogd", "Rate": "Waardeer", "RecentlyWatched": "Onlangs bekeken", - "RecommendationBecauseYouLike": "Omdat u {0} leuk vond.", + "RecommendationBecauseYouLike": "Omdat u {0} leuk vond", "RecommendationBecauseYouWatched": "Omdat u keek naar {0}", "RecommendationDirectedBy": "Geregisseerd door {0}", "RecommendationStarring": "In de hoofdrollen {0}", @@ -1072,12 +1072,12 @@ "RecordSeries": "Series Opnemen", "RecordingCancelled": "Opname geannuleerd.", "RecordingPathChangeMessage": "Bij het wijzigen van uw opnamemap zullen bestaande opnamen niet migreren van de oude locatie naar de nieuwe. U moet deze desgewenst handmatig verplaatsen.", - "RecordingScheduled": "Opname schema", + "RecordingScheduled": "Opname schema.", "Recordings": "Opnames", "Refresh": "Vernieuwen", "RefreshDialogHelp": "Metadata wordt vernieuwd op basis van de instellingen en internet diensten die zijn ingeschakeld in het dashboard van de Jellyfin Server.", "RefreshMetadata": "Metadata vernieuwen", - "RefreshQueued": "Vernieuwen wachtrij", + "RefreshQueued": "Verversen wachtrij.", "ReleaseDate": "Uitgave datum", "RememberMe": "Onthoud mij", "RemoveFromCollection": "Verwijder uit collectie", @@ -1085,12 +1085,11 @@ "Repeat": "Herhaling", "RepeatAll": "Alle herhalen", "RepeatEpisodes": "Herhaal afleveringen", - "RepeatMode": "Herhaal mode", + "RepeatMode": "Herhaal modus", "RepeatOne": "Eén herhalen", "ReplaceAllMetadata": "Alle metadata vervangen", "ReplaceExistingImages": "Bestaande afbeeldingen vervangen", "RequiredForAllRemoteConnections": "Vereist voor alle externe verbindingen", - "RestartPleaseWaitMessage": "Wacht totdat Jellyfin Server is afgesloten en opnieuw is gestart. Dit kan een paar minuten duren.", "ResumeAt": "Hervatten vanaf {0}", "Rewind": "Terugspoelen", "RunAtStartup": "Uitvoeren bij opstarten", @@ -1132,7 +1131,7 @@ "SkipEpisodesAlreadyInMyLibrary": "Neem geen afleveringen op die al in mijn bibliotheek aanwezig zijn", "SkipEpisodesAlreadyInMyLibraryHelp": "Afleveringen zullen worden vergeleken met behulp van seizoen en aflevering nummers, indien beschikbaar.", "Small": "Klein", - "SmallCaps": "Klein Kapitaal", + "SmallCaps": "Kleine letters", "Smaller": "Kleiner", "Smart": "Slim", "SmartSubtitlesHelp": "Ondertitels worden weergegeven in de voorkeurstaal als de audio in een andere taal zijn.", @@ -1201,7 +1200,7 @@ "ThemeSongs": "Themamuziek", "ThemeVideos": "Themavideo's", "TheseSettingsAffectSubtitlesOnThisDevice": "Deze instellingen betreffen ondertitels op dit apparaat", - "ThisWizardWillGuideYou": "Deze wizard helpt u door het setup-proces. Om te beginnen selecteert u eerst de gewenste taal.", + "ThisWizardWillGuideYou": "Deze helper helpt u door het opzet proces heen. Om te beginnen selecteert u eerst de gewenste taal.", "Thumb": "Miniatuur", "Thursday": "Donderdag", "TitleHardwareAcceleration": "Hardware versnelling", @@ -1249,7 +1248,7 @@ "XmlTvKidsCategoriesHelp": "Programma's met deze categorieën wordt weergegeven als programma's voor kinderen. Scheid meerdere met '|'.", "XmlTvMovieCategoriesHelp": "Programma's met deze categorieën wordt weergegeven als films. Scheid meerdere met '|'.", "XmlTvNewsCategoriesHelp": "Programma's met deze categorieën wordt weergegeven als nieuwsprogramma's. Scheid meerdere met '|'.", - "XmlTvPathHelp": "Een pad naar een XML-TV-bestand. Jellyfin zal dit bestand regelmatig lezen en controleren voor updates. U bent verantwoordelijk voor het maken en bijwerken van het bestand.", + "XmlTvPathHelp": "Een pad naar een XML-TV-bestand. Jellyfin zal dit bestand regelmatig lezen en controleren voor updates. U bent verantwoordelijk voor het maken en bijwerken van dit bestand.", "XmlTvSportsCategoriesHelp": "Programma's met deze categorieën wordt weergegeven als sportprogramma's. Scheid meerdere met '|'.", "Yes": "Ja", "Yesterday": "Gisteren", @@ -1271,8 +1270,8 @@ "Genre": "Genre", "Genres": "Genres", "HeaderAlbums": "Albums", - "HeaderCastAndCrew": "Cast & Crew", - "HeaderCastCrew": "Cast & Crew", + "HeaderCastAndCrew": "Acteurs en medewerkers", + "HeaderCastCrew": "Acteurs & medewerkers", "Art": "Afbeeldingen", "HeaderLiveTV": "Live TV", "HeaderDetails": "Details", @@ -1282,15 +1281,13 @@ "HeaderHttpHeaders": "HTTP Headers", "HeaderImageLogo": "Logo", "HeaderMenu": "Menu", - "HeaderOffline": "Offline", - "HeaderOfflineDownloads": "Offline Media", "HeaderResetTuner": "Ontvanger resetten", "HeaderReviews": "Beoordelingen", "HeaderStatus": "Status", "HeaderSync": "Synchronisatie", "HeaderTV": "TV", "HeaderTopPlugins": "Top Plugins", - "AuthProviderHelp": "Selecteer een Authenticatie Provider om het wachtwoord van deze gebruiker te verifiëren", + "AuthProviderHelp": "Selecteer een Authenticatie Provider om het wachtwoord van deze gebruiker te verifiëren.", "HeaderFavoriteMovies": "Favoriete Films", "HeaderFavoriteShows": "Favoriete shows", "HeaderFavoriteEpisodes": "Favoriete afleveringen", @@ -1298,12 +1295,11 @@ "HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteSongs": "Favoriete nummers", "HeaderFavoriteVideos": "Favoriete Films", - "HeaderInstantMix": "Instant Mix", + "HeaderInstantMix": "Directe Mix", "HeaderItems": "Items", "HeaderJellyfinServer": "Jellyfin Server", "HeaderLiveTv": "Live TV", "HeaderMedia": "Media", - "HeaderRestartingServer": "Server wordt herstart", "HeaderTracks": "Nummers", "HeaderTuners": "Stemmers", "InstantMix": "Instant mix", @@ -1322,23 +1318,23 @@ "LabelProfileCodecs": "Codecs:", "LabelProfileContainer": "Container:", "LabelProfileVideoCodecs": "Video codecs:", - "LabelProtocolInfo": "Protocool info:", + "LabelProtocolInfo": "Protocol info:", "LabelServerName": "Server naam:", - "LabelSkin": "Skin:", + "LabelSkin": "Uiterlijk:", "ButtonAddImage": "Voeg afbeelding toe", "LabelSize": "Grootte:", - "CopyStreamURLSuccess": "URL gekopieerd", - "CopyStreamURL": "Kopieer stream URL ", + "CopyStreamURLSuccess": "URL succesvol gekopieerd.", + "CopyStreamURL": "Kopieer Stream URL", "TabLiveTV": "Live TV", "ValueAlbumCount": "{0} albums", "FetchingData": "Meer data op aan het halen", "HeaderFavoriteBooks": "Favoriete Boeken", - "LabelUserLoginAttemptsBeforeLockout": "Mislukte login pogingen voordat de gebruiker buitengesloten wordt:", + "LabelUserLoginAttemptsBeforeLockout": "Mislukte aanmeld pogingen voordat de gebruiker buitengesloten wordt:", "OptionHasTrailer": "Trailer", "OptionMax": "Max", "LabelBaseUrl": "Basis URL:", - "LabelTranscodingProgress": "Transcoding voortgang:", - "LabelTriggerType": "Trigger Type:", + "LabelTranscodingProgress": "Transcoderen voortgang:", + "LabelTriggerType": "Signaal Type:", "LaunchWebAppOnStartup": "Lanceer de web interface wanneer de server start", "MediaInfoBitrate": "Bitrate", "MediaInfoInterlaced": "Interlaced", @@ -1358,13 +1354,13 @@ "SubtitleOffset": "Ondertiteling Compensatie", "LabelXDlnaDoc": "X-DLNA doc:", "LiveTV": "Live TV", - "LabelTag": "Tag:", + "LabelTag": "Label:", "Live": "Live", "OptionDvd": "DVD", "OptionResElement": "res element", "TV": "TV", "HeaderHome": "Thuis", - "MediaInfoStreamTypeEmbeddedImage": "Embedded Afbeelding", + "MediaInfoStreamTypeEmbeddedImage": "Ingeladen Afbeelding", "LabelTypeMetadataDownloaders": "{0} metadata downloaders:", "OptionLoginAttemptsBeforeLockout": "Bepaald hoeveel foutieve login pogingen plaats kunnen vinden voor dat de gebruiker buitengesloten wordt.", "Premiere": "Première", @@ -1372,7 +1368,7 @@ "LabelAudioSampleRate": "Audio sample rate:", "OptionIsHD": "HD", "OptionIsSD": "SD", - "OptionSpecialEpisode": "Specials", + "OptionSpecialEpisode": "Extra's", "TabContainers": "Containers", "TabDashboard": "Dashboard", "TabNetworking": "Netwerken", @@ -1387,7 +1383,7 @@ "OptionThumbCard": "Miniatuur kaart", "PlaybackData": "Afspeel Data", "PasswordResetProviderHelp": "Kies een wachtwoord reset provider om te gebruiken wanneer deze gebruiker een wachtwoord reset aanvraagt", - "Screenshots": "Screenshots", + "Screenshots": "Schermafdruk", "Series": "Series", "TabAlbums": "Albums", "TabGenres": "Genres", @@ -1401,25 +1397,25 @@ "MediaInfoCodec": "Codec", "Menu": "Menu", "OptionThumb": "Miniatuur", - "LabelTranscodingFramerate": "Transcoding framerate:", + "LabelTranscodingFramerate": "Transcoderen beeldverversing:", "LabelType": "Type:", "HeaderFavoritePeople": "Favoriete Mensen", "LabelAudioBitrate": "Audio bitrate:", "LabelAudioCodec": "Audio codec:", "LabelAudioChannels": "Audio kanalen:", "LabelBitrate": "Bitrate:", - "LabelBaseUrlHelp": "Hier kunt u een eigen subdirectory toevoegen om de server te bereiken doormiddel van een meer unieke URL.", + "LabelBaseUrlHelp": "Voegt een aangepaste submap toe aan de server-URL. Bijvoorbeeld: http://example.com/<baseurl>", "LabelFolder": "Folder:", "LabelLineup": "Lineup:", "LabelPlayer": "Speler:", "LabelPlayMethod": "Afspeel methode:", - "LabelPleaseRestart": "Verandering zullen toegepast worden na het handmatig herladen van de web cliënt.", + "LabelPleaseRestart": "De wijzigingen zullen worden toegepast na het handmatig herladen van de web cliënt.", "LabelStatus": "Status:", - "LabelTagline": "Tagline:", + "LabelTagline": "Label tekst:", "LabelTranscodingContainer": "Container:", - "LabelTranscodePath": "Transcode pad:", - "LabelTranscodes": "Transcodes:", - "DashboardOperatingSystem": "Besturingsysteem: {0}", + "LabelTranscodePath": "Transcodeer pad:", + "LabelTranscodes": "Transcoderen:", + "DashboardOperatingSystem": "Besturingssysteem: {0}", "LabelWeb": "Web:", "LaunchWebAppOnStartupHelp": "Open de web cliënt in uw standaard browser wanneer de server voor de eerste keer start. Dit zal niet voorkomen tijdens gebruik van de server herstart functie.", "LeaveBlankToNotSetAPassword": "U kunt dit veld leeg laten om geen wachtwoord in te stellen.", @@ -1438,17 +1434,17 @@ "Logo": "Logo", "MediaInfoCodecTag": "Codec tag", "MediaInfoContainer": "Container", - "MediaInfoFramerate": "Framerate", - "MediaInfoRefFrames": "Ref frames", + "MediaInfoFramerate": "Beeldverversing", + "MediaInfoRefFrames": "Ref beeld", "MediaInfoSoftware": "Software", - "MessageImageFileTypeAllowed": "Alleen JPEG en PNG bestanden zijn ondersteund.", - "MessageImageTypeNotSelected": "Selecteer alstublieft een afbeelding type van het drop-down menu.", + "MessageImageFileTypeAllowed": "Alleen JPEG en PNG bestanden worden ondersteund.", + "MessageImageTypeNotSelected": "Selecteer een afbeelding type van het menu alstublieft .", "MessageNoCollectionsAvailable": "Collecties staan u toe om te genieten van gepersonaliseerde groeperingen van Films, Series en Albums. Klik de + knop om te beginnen met het maken van collecties.", - "MessageNoServersAvailable": "Geen servers zijn gevonden doormiddel van het automatisch server ontdekken.", + "MessageNoServersAvailable": "Geen servers zijn gevonden door middel van het automatisch ontdekken van een server.", "Metadata": "Metadata", "MetadataManager": "Metadata Beheerder", "MusicAlbum": "Muziek Album", - "NumLocationsValue": "{0} folders", + "NumLocationsValue": "{0} mappen", "OptionBanner": "Banner", "OptionBlockTrailers": "Trailers", "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", @@ -1461,5 +1457,114 @@ "TabStreaming": "Streamen", "TabTrailers": "Trailers", "OptionAuto": "Auto", - "OptionProfileVideo": "Video" + "OptionProfileVideo": "Video", + "AlbumArtist": "Album Artiest", + "Album": "Album", + "DeinterlaceMethodHelp": "Selecteer de deinterlacingmethode die u wilt gebruiken bij het transcoderen van geïnterlinieerde inhoud.", + "CopyStreamURLError": "Er trad een fout op tijdens het kopieren van de URL.", + "ClientSettings": "Client instellingen", + "ButtonSplit": "Splitsen", + "BoxSet": "Box Set", + "AskAdminToCreateLibrary": "Vraag een beheerder om een bibliotheek te maken.", + "Artist": "Artiest", + "AllowFfmpegThrottlingHelp": "Wanneer een transcode of remux ver genoeg voorloopt op de huidige afspeelpositie, pauzeer het proces, zodat het minder middelen verbruikt. Dit is vooral handig wanneer u kijkt zonder vaak te zoeken. Schakel dit uit als u afspeelproblemen ondervindt.", + "AllowFfmpegThrottling": "Throttle Transcodes", + "LabelPlayerDimensions": "Afspeellengte:", + "LabelLibraryPageSizeHelp": "Kies het aantal artikelen dat wordt weergegeven op een bibliotheekpagina. Kies 0 om dit te verbergen.", + "LabelLibraryPageSize": "Bibliotheekpagina grootte:", + "LabelDroppedFrames": "Vervallen beelden:", + "LabelDeinterlaceMethod": "Deinterlacing methode:", + "LabelCorruptedFrames": "Corrupte beelden:", + "HeaderNavigation": "Navigeren", + "Episode": "Aflevering", + "Season": "Seizoen", + "ReleaseGroup": "Uitgave groep", + "PreferEmbeddedEpisodeInfosOverFileNames": "Verkies ingeladen afleveringsinformatie boven bestandsnaam", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Dit gebruikt de afleveringsinformatie van de ingeladen metadata als deze aanwezig is.", + "PlaybackErrorNoCompatibleStream": "Deze machine is niet leesbaar met de media en de server verstuurd geen leesbare media formaten.", + "Person": "Persoon", + "OtherArtist": "Andere Artiesten", + "OptionForceRemoteSourceTranscoding": "Forceer het transcoderen van op afstand bediende media bronnen (zoals LiveTV)", + "NoCreatedLibraries": "Het lijkt erop dat er geen bibliotheek is gecreëerd. {0}Wilt u er nu een aanmaken?{1}", + "Movie": "Film", + "MessageUnauthorizedUser": "U bent niet gemachtigd om toegang tot de server te krijgen op dit moment. Neem contact op met de server beheerder voor meer informatie.", + "MessageConfirmAppExit": "Wilt u afsluiten?", + "LabelVideoResolution": "Video resolutie:", + "LabelStreamType": "Stream type:", + "UnsupportedPlayback": "Jellyfin kan DRM beschermde inhoud niet ontsleutelen. Alle inhoud zal geprobeerd worden om te ontsleutelen inclusief beschermde titels. Sommige bestanden kunnen volledig zwart zijn vanwege de versleuteling of andere niet ondersteunde functies, zoals interactieve titels.", + "OnApplicationStartup": "Op het opstarten van de applicatie", + "EveryXHours": "Elke {0} uren", + "EveryHour": "Elk uur", + "EveryXMinutes": "Elke {0} minuten", + "OnWakeFromSleep": "Op het wakker worden vanuit slaapstand", + "WeeklyAt": "{0}s op {1}", + "DailyAt": "Dagelijks op {0}", + "LastSeen": "Laatst bekeken {0}", + "PersonRole": "als {0}", + "ListPaging": "{0}-{1} van de {2}", + "WriteAccessRequired": "De Jellyfin Server vereist schrijftoegang tot deze map. Zorg evoor dat Jellyfin schrijftoegang tot de map heeft en probeer opnieuw.", + "PathNotFound": "Het pad kan niet gevonden worden. Zorg ervoor dat het pad correct is en probeer opnieuw.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Nummer", + "SelectAdminUsername": "Selecteer een gebruikersnaam voor het beheerder account.", + "HeaderFavoritePlaylists": "Favoriete afspeellijsten", + "ButtonTogglePlaylist": "Afspeellijst", + "ButtonToggleContextMenu": "Meer", + "LabelRequireHttpsHelp": "Indien aangevinkt, zal de server alle verzoeken via HTTP automatisch omleiden naar HTTPS. Dit heeft geen effect als de server niet luistert op HTTPS.", + "EnableDetailsBanner": "Details Banner", + "MessageSyncPlayNoGroupsAvailable": "Geen groepen beschikbaar. Begin eerst iets te spelen.", + "EnableDetailsBannerHelp": "Toon een bannerafbeelding bovenaan de pagina met itemdetails.", + "TabDVR": "DVR", + "SyncPlayAccessHelp": "Selecteer het toegangsniveau dat deze gebruiker heeft tot de SyncPlay-functie. SyncPlay maakt het mogelijk om het afspelen met andere gebruikers te synchroniseren.", + "Filter": "Filter", + "New": "Nieuw", + "SaveChanges": "Wijzigingen opslaan", + "MessageSyncPlayErrorMedia": "Kan SyncPlay niet inschakelen! Media fout.", + "MessageSyncPlayErrorMissingSession": "Kan SyncPlay niet inschakelen! Ontbrekende sessie.", + "MessageSyncPlayErrorNoActivePlayer": "Geen actieve speler gevonden. SyncPlay is uitgeschakeld.", + "MessageSyncPlayErrorAccessingGroups": "Er is een fout opgetreden bij het openen van de groepslijst.", + "MessageSyncPlayLibraryAccessDenied": "Toegang tot deze inhoud is beperkt.", + "MessageSyncPlayJoinGroupDenied": "Toestemming vereist om SyncPlay te gebruiken.", + "MessageSyncPlayCreateGroupDenied": "Toestemming vereist om een groep te maken.", + "MessageSyncPlayGroupDoesNotExist": "Kan niet deelnemen aan de groep omdat deze niet bestaat.", + "MessageSyncPlayPlaybackPermissionRequired": "Afspeelrechten vereist.", + "MessageSyncPlayGroupWait": "{0} is aan het bufferen...", + "MessageSyncPlayUserLeft": "{0} i heeft de groep verlaten.", + "MessageSyncPlayUserJoined": "{0} is lid geworden van de groep.", + "MessageSyncPlayDisabled": "SyncPlay uitgeschakeld.", + "MessageSyncPlayEnabled": "SyncPlay ingeschakeld.", + "LabelSyncPlayAccess": "SyncPlay toegang", + "LabelSyncPlayAccessNone": "Uitgeschakeld voor deze gebruiker", + "LabelSyncPlayAccessJoinGroups": "Sta de gebruiker toe om groepen te maken", + "LabelSyncPlayAccessCreateAndJoinGroups": "Sta de gebruiker toe om groepen te maken en eraan deel te nemen", + "LabelSyncPlayLeaveGroupDescription": "SyncPlay uitschakelen", + "LabelSyncPlayLeaveGroup": "Groep verlaten", + "LabelSyncPlayNewGroupDescription": "Maak een nieuwe groep", + "LabelSyncPlayNewGroup": "Nieuwe groep", + "LabelSyncPlaySyncMethod": "Sync methode:", + "LabelSyncPlayPlaybackDiff": "Verschil in afspeeltijd:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tijd offset met de server:", + "LabelRequireHttps": "HTTPS verplichten", + "LabelNightly": "Nightly", + "LabelStable": "Stabiel", + "LabelChromecastVersion": "Chromecast versie", + "LabelEnableHttpsHelp": "Hiermee kan de server luisteren op de geconfigureerde HTTPS-poort. Hiervoor moet ook een geldig certificaat worden geconfigureerd.", + "LabelEnableHttps": "HTTPS inschakelen", + "HeaderSyncPlayEnabled": "SyncPlay ingeschakeld", + "HeaderSyncPlaySelectGroup": "Word lid van een groep", + "HeaderServerAddressSettings": "Server adres instellingen", + "HeaderRemoteAccessSettings": "Externe toegang instellingen", + "HeaderHttpsSettings": "HTTPS instellingen", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lijst met de momenteel ingeschakelde API-sleutels", + "ButtonSyncPlay": "SyncPlay", + "ShowMore": "Laat meer zien", + "ShowLess": "Laat minder zien", + "MessageNoGenresAvailable": "Stel sommige metadataproviders in staat om genres van internet te halen.", + "EnableFasterAnimationsHelp": "Gebruik snellere animaties en overgangen", + "EnableFasterAnimations": "Snellere animaties", + "EnableDecodingColorDepth10Vp9": "Schakel 10-bits hardwarecodering in voor VP9", + "EnableDecodingColorDepth10Hevc": "Schakel 10-bits hardwarecodering in voor HEVC" } diff --git a/src/strings/pl.json b/src/strings/pl.json index 32e9a97980d..fcb35e06431 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -54,7 +54,7 @@ "BoxRear": "Pudełko (tył)", "Browse": "Przeglądaj", "BrowsePluginCatalogMessage": "Przejrzyj nasz katalog wtyczek żeby zobaczyć dostępne wtyczki.", - "BurnSubtitlesHelp": "Określa czy serwer powinien wypalać napisy podczas konwersji wideo, w zależności od formatu napisów. Unikanie wypalania napisów poprawia wydajność serwera. Wybierz Automatycznie, w celu wypalania zarówno napisów w formatach graficznych (np. VOBSUB, PGS, SUB/IDX, itp.), jak i pewnych napisów ASS/SSA.", + "BurnSubtitlesHelp": "Określa czy serwer powinien wypalać napisy podczas konwersji wideo, w zależności od formatu napisów. Unikanie wypalania napisów znacząco poprawia wydajność serwera. Wybierz Automatycznie, w celu wypalania zarówno napisów w formatach graficznych (np. VOBSUB, PGS, SUB/IDX), jak i pewnych napisów ASS lub SSA.", "ButtonAdd": "Dodaj", "ButtonAddMediaLibrary": "Dodaj media do biblioteki", "ButtonAddScheduledTaskTrigger": "Dodaj wyzwalacz", @@ -190,7 +190,7 @@ "DisplayInOtherHomeScreenSections": "Wyświetlaj na ekranie startowym sekcje Ostatnio dodane i Kontynuuj odtwarzanie", "DisplayMissingEpisodesWithinSeasons": "Wyświetlaj w sezonach brakujące odcinki", "DisplayMissingEpisodesWithinSeasonsHelp": "Ta opcja musi zostać dodatkowo aktywowana w bibliotece seriali, w konfiguracji serwera.", - "DisplayModeHelp": "Określa typ urządzenia, na którym uruchomiono Jellyfin.", + "DisplayModeHelp": "Wybierz styl układu interfejsu.", "DoNotRecord": "Nie nagrywaj", "Down": "W dół", "Download": "Pobierz", @@ -490,7 +490,7 @@ "Images": "Obrazy", "ImportFavoriteChannelsHelp": "Jeśli aktywne, tylko kanały oznaczone jako ulubione na tunerze, będą importowane.", "ImportMissingEpisodesHelp": "W przypadku aktywacji tej opcji, informacje o brakujących odcinkach zostaną zaimportowane do bazy Jellyfin i będą wyświetlane na listach sezonów i seriali. Może to jednak znacznie wydłużyć czas skanowania biblioteki.", - "InstallingPackage": "Instalowanie {0}", + "InstallingPackage": "Instalowanie {0} (wersja {1})", "InstantMix": "Szybki remiks", "ItemCount": "{0} pozycje", "Items": "Pozycje", @@ -953,16 +953,16 @@ "NoNextUpItemsMessage": "Nie znaleziono niczego. Zacznij oglądać swoje seriale!", "NoPluginConfigurationMessage": "Ta wtyczka nie ma żadnych ustawień.", "NoSubtitleSearchResultsFound": "Brak wyników wyszukiwania.", - "NoSubtitles": "Brak napisów", + "NoSubtitles": "Brak", "NoSubtitlesHelp": "Domyślnie napisy nie będą wczytywane. Można je ciągle włączyć ręcznie podczas odtwarzania.", "None": "Brak", "Normal": "Normalny", "NumLocationsValue": "{0} foldery", "Off": "Wyłączone", "OneChannel": "Jeden kanał", - "OnlyForcedSubtitles": "Tylko wymuszone napisy", + "OnlyForcedSubtitles": "Tylko wymuszone", "OnlyForcedSubtitlesHelp": "Tylko napisy oznaczone jako wymuszone będą wczytywane.", - "OnlyImageFormats": "Tylko formaty graficzne (VOBSUB, PGS, SUB, itp.)", + "OnlyImageFormats": "Tylko Formaty Graficzne (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administratorzy", "OptionAlbumArtist": "Wykonawca albumu", "OptionAllUsers": "Wszyscy użytkownicy", @@ -1100,7 +1100,7 @@ "OptionWeekly": "Cotygodniowo", "OriginalAirDateValue": "Data pierwszej emisji: {0}", "Overview": "Opis", - "PackageInstallCancelled": "Instalacja {0} anulowana.", + "PackageInstallCancelled": "Instalacja {0} (wersja {1}) anulowana.", "PackageInstallCompleted": "Instalacja {0} zakończona.", "PackageInstallFailed": "Instalacja {0} nieudana.", "ParentalRating": "Kategoria wiekowa", @@ -1171,7 +1171,6 @@ "ReplaceAllMetadata": "Zastępuj wszystkie metadane", "ReplaceExistingImages": "Zastępuj istniejące obrazy", "RequiredForAllRemoteConnections": "Wymagane dla wszystkich zdalnych połączeń", - "RestartPleaseWaitMessage": "Czekaj na zamknięcie i ponowne uruchomienie serwera Jellyfin. To może trwać ok. jednej, dwóch minut.", "ResumeAt": "Wznów odtwarzanie od {0}", "Rewind": "Do tyłu", "RunAtStartup": "Uruchamiaj po starcie", @@ -1368,7 +1367,6 @@ "HeaderFavoriteSongs": "Ulubione utwory", "HeaderFavoriteVideos": "Ulubione Filmy", "HeaderHome": "Strona główna", - "HeaderRestartingServer": "Ponowne uruchamianie serwera", "LabelAuthProvider": "Dostawca autentykacji:", "LabelDynamicExternalId": "{0} Id:", "LabelFormat": "Format:", @@ -1438,8 +1436,8 @@ "LabelTranscodingFramerate": "Transkodowanie w kl/s:", "LabelSize": "Wielkość:", "LabelPleaseRestart": "Zmiany zaczną obowiązywać po ręcznym przeładowaniu klienta WWW.", - "LabelPlayMethod": "Meroda odtwarzania:", - "LabelPlayer": "Gracz:", + "LabelPlayMethod": "Metoda odtwarzania:", + "LabelPlayer": "Odtwarzacz:", "LabelBaseUrlHelp": "Możesz tutaj dodać niestandardowy podkatalog, aby uzyskać dostęp do serwera z bardziej unikalnego adresu URL.", "LabelBaseUrl": "Podstawowy adres URL:", "LabelBitrate": "Bitrate:", @@ -1466,6 +1464,21 @@ "NoCreatedLibraries": "Wygląda na to, że nie utworzyłeś jeszcze żadnych bibliotek. {0}Czy chcesz utworzyć jedną teraz?{1}", "LabelVideoResolution": "Rozdzielczość wideo:", "LabelStreamType": "Typ transmisji:", - "EnableFastImageFadeInHelp": "Włącz szybszą animację pojawiania się dla załadowanych obrazów", - "EnableFastImageFadeIn": "Szybkie pojawianie się obrazów" + "Artist": "Artysta", + "AlbumArtist": "Album artysty", + "Album": "Album", + "Person": "Osoba", + "OtherArtist": "Inny artysta", + "Movie": "Film", + "MessageUnauthorizedUser": "Nie masz dostępu do zasobów serwera. Skontaktuj się z administratorem sieci, aby uzyskać więcej informacji.", + "LabelLibraryPageSizeHelp": "Ustaw liczbę pozycji pokazywanych na stronie biblioteki. Ustaw 0, aby wyłączyć podział na strony.", + "LabelLibraryPageSize": "Rozmiar strony biblioteki:", + "LabelDeinterlaceMethod": "Metoda usuwania przeplotu:", + "HeaderFavoritePlaylists": "Ulubione Playlisty", + "Episode": "Odcinek", + "DeinterlaceMethodHelp": "Wybierz metodę usuwania przeplotu używaną podczas transkodowania.", + "ClientSettings": "Ustawienia klienta", + "ButtonTogglePlaylist": "Playlista", + "ButtonToggleContextMenu": "Więcej", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/pr.json b/src/strings/pr.json new file mode 100644 index 00000000000..ea270240450 --- /dev/null +++ b/src/strings/pr.json @@ -0,0 +1,29 @@ +{ + "TabLogs": "Crow's Nest", + "HeaderAdmin": "Cap'n", + "WelcomeToProject": "Ahoy, matey! This be Jellyfin!", + "ButtonOk": "Aye", + "DisplayInMyMedia": "Show on the Poop Deck", + "HeaderCastAndCrew": "Mateys", + "HeaderMusicQuality": "Sea Shanty Strength", + "HeaderLatestMusic": "Latest Sea Shanties", + "FolderTypeMusic": "Sea Shanties", + "OptionBlockBooks": "Tall Tales", + "HeaderFavoriteBooks": "Fav'rit Tales", + "HeaderBooks": "Tall Tales", + "HeaderAudioBooks": "Spoken Tales", + "FolderTypeBooks": "Tall Tales", + "Books": "Tall Tales", + "LabelHomeNetworkQuality": "Sail strength:", + "Home": "Poop Deck", + "HeaderHome": "Poop Deck", + "DisplayInOtherHomeScreenSections": "Show on Poop Deck such as latest booty and continue plundering", + "ButtonHome": "Poop deck", + "HeaderCastCrew": "Mateys", + "Add": "Upend", + "Actor": "Privateer", + "AccessRestrictedTryAgainLater": "Ye arn't appearin' on the list o' the livin'!", + "ButtonSyncPlay": "SyncPlay", + "AddedOnValue": "Upended {0}", + "AddToCollection": "Add to ye collection" +} diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index f1bffad58ce..d0ed0aacd82 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -30,7 +30,7 @@ "AlwaysPlaySubtitlesHelp": "As legendas que combinarem com a preferência de idioma serão carregadas independente do idioma do áudio.", "AnyLanguage": "Qualquer idioma", "Anytime": "A qualquer momento", - "AroundTime": "Aproximadamente {0}", + "AroundTime": "Aproximadamente", "Art": "Arte", "Artists": "Artistas", "AsManyAsPossible": "Quantos forem possíveis", @@ -44,13 +44,13 @@ "BirthDateValue": "Nascimento: {0}", "BirthLocation": "Local de nascimento", "BirthPlaceValue": "Local de nascimento: {0}", - "BookLibraryHelp": "Livros de áudio e texto são suportados. Verifique o {0}guia de nomes de livros{1}.", + "BookLibraryHelp": "Livros de áudio e texto são suportados. Revise o {0}Guia de Nomes de Livros no Jellyfin{1}.", "Books": "Livros", "Box": "Caixa", "BoxRear": "Caixa (traseira)", "Browse": "Navegar", "BrowsePluginCatalogMessage": "Navegue pelo nosso catálogo de plugins para ver os plugins disponíveis.", - "BurnSubtitlesHelp": "Determina se o servidor deveria gravar as legendas no vídeo ao convertê-lo, dependendo do formato da legenda. Evitar a gravação da legenda irá melhorar a performance do servidor. Selecione Auto para gravar formatos de legenda baseados em imagem baseado nos formatos (ex. VOBSUB, PGS, SUB/IDX, etc.) e algumas legendas ASS/SSA.", + "BurnSubtitlesHelp": "Determina se o servidor deveria gravar as legendas no vídeo ao convertê-lo, dependendo do formato da legenda. Evitar a gravação da legenda irá melhorar a performance do servidor. Selecione Auto para gravar legendas baseados em imagem dos tipos (ex. VOBSUB, PGS, SUB/IDX, etc.) e algumas legendas ASS/SSA.", "ButtonAdd": "Adicionar", "ButtonAddMediaLibrary": "Adicionar Biblioteca de Mídia", "ButtonAddScheduledTaskTrigger": "Adicionar Disparador", @@ -182,7 +182,7 @@ "DisplayInOtherHomeScreenSections": "Exibir nas seções da tela inicial como mídia recente e continuar assistindo", "DisplayMissingEpisodesWithinSeasons": "Exibir episódios em falta nas temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Isto também deve ser ativado para as bibliotecas de TV na configuração do servidor.", - "DisplayModeHelp": "Seleciona o tipo de tela para executar o Jellyfin.", + "DisplayModeHelp": "Selecione o estilo de layout que deseje para a interface.", "DoNotRecord": "Não gravar", "Down": "Baixo", "DrmChannelsNotImported": "Canais com DRM não serão importados.", @@ -218,7 +218,7 @@ "ErrorAddingTunerDevice": "Ocorreu um erro ao adicionar o sintonizador. Por favor, verifique se está acessível e tente novamente.", "ErrorAddingXmlTvFile": "Ocorreu um erro ao acessar o arquivo XmlTV. Por favor, verifique se o arquivo existe e tente novamente.", "ErrorDeletingItem": "Ocorreu um erro ao excluir o item do Servidor Jellyfin. Por favor, verifique se o Servidor Jellyfin possui acesso de gravação na pasta de mídia e tente novamente.", - "ErrorGettingTvLineups": "Ocorreu um erro ao baixar a programação da TV. Por favor, verifique se sua informação está correta e tente novamente.", + "ErrorGettingTvLineups": "Ocorreu um erro ao fazer download da programação de TV. Por favor, certifique-se que sua informação esteja correta e tente novamente.", "ErrorMessageStartHourGreaterThanEnd": "A hora final deve ser maior que a hora inicial.", "ErrorPleaseSelectLineup": "Por favor, selecione a programação e tente novamente. Se não houver programações disponíveis, verifique se o seu nome de usuário, senha e código postal estão corretos.", "ErrorSavingTvProvider": "Um erro ocorreu ao salvar o provedor de TV. Por favor, verifique se está acessível e tente novamente.", @@ -474,7 +474,7 @@ "Images": "Imagens", "ImportFavoriteChannelsHelp": "Se ativado, apenas canais que estão marcados como favoritos no sintonizador serão importados.", "ImportMissingEpisodesHelp": "Se ativado, as informações dos episódios que faltam serão importadas para seu banco de dados do Jellyfin e exibidas dentro das temporadas e séries. Isto pode causar lentidão no rastreamento da biblioteca.", - "InstallingPackage": "Instalando {0}", + "InstallingPackage": "Instalando {0} (versão{1})", "InstantMix": "Mix instântaneo", "ItemCount": "{0} itens", "Items": "itens", @@ -564,9 +564,9 @@ "LabelDynamicExternalId": "Id de {0}:", "LabelEasyPinCode": "Código pin fácil:", "LabelEmbedAlbumArtDidl": "Arte do álbum incorporada no Didl", - "LabelEmbedAlbumArtDidlHelp": "Alguns dispositivos preferem este método para obter a arte do álbum. Outros podem falhar ao reproduzir com esta opção ativada.", + "LabelEmbedAlbumArtDidlHelp": "Alguns dispositivos preferem este método para obter a arte do álbum. Outros podem falhar para reproduzir com esta opção ativada.", "LabelEnableAutomaticPortMap": "Ativar mapeamento automático de portas", - "LabelEnableAutomaticPortMapHelp": "Tentativa de mapear automaticamente a porta pública para a porta local através de UPnP. Isto poderá não funcionar em alguns modelos de roteadores.", + "LabelEnableAutomaticPortMapHelp": "Tentar mapear automaticamente a porta pública para a porta local do seu servidor através de UPnP. Pode não funcionar em alguns modelos de roteadores. As mudanças não serão aplicadas até a reinicialização do servidor.", "LabelEnableBlastAliveMessages": "Mensagens ao vivo", "LabelEnableBlastAliveMessagesHelp": "Ative esta função se o servidor não for detectado por outros dispositivos UPnP em sua rede.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo para descoberta do cliente (segundos)", @@ -601,7 +601,7 @@ "LabelH264Crf": "CRF de codificação H264:", "LabelEncoderPreset": "Preset de codificação H264:", "LabelHardwareAccelerationType": "Aceleração de hardware:", - "LabelHardwareAccelerationTypeHelp": "Esta é uma função experimental disponível apenas em sistemas suportados.", + "LabelHardwareAccelerationTypeHelp": "A aceleração de hardware requer configuração adicional.", "LabelHomeNetworkQuality": "Qualidade da rede local:", "LabelHomeScreenSectionValue": "Seção {0} da tela inicial:", "LabelHttpsPort": "Número da porta local de HTTPS:", @@ -637,7 +637,7 @@ "LabelLockItemToPreventChanges": "Bloquear este item para evitar alterações futuras", "LabelLoginDisclaimer": "Aviso legal no login:", "LabelLoginDisclaimerHelp": "Um aviso será exibido na parte inferior da página de login.", - "LabelManufacturer": "Fabricante", + "LabelManufacturer": "Fabricante:", "LabelManufacturerUrl": "URL do fabricante", "LabelMatchType": "Tipo de correspondência:", "LabelMaxBackdropsPerItem": "Número máximo de imagens de fundo por item:", @@ -676,7 +676,7 @@ "LabelMoviePrefixHelp": "Se os títulos dos filmes devem ter um prefixo, digite-o aqui para que o servidor possa usá-lo corretamente.", "LabelMovieRecordingPath": "Local de gravação de filme (opcional):", "LabelMusicStreamingTranscodingBitrate": "Bitrate da transcodificação de músicas:", - "LabelMusicStreamingTranscodingBitrateHelp": "Define o bitrate máximo do streaming de músicas", + "LabelMusicStreamingTranscodingBitrateHelp": "Define o bitrate máximo do streaming de músicas.", "LabelName": "Nome:", "LabelNewName": "Novo nome:", "LabelNewPassword": "Nova senha:", @@ -688,7 +688,7 @@ "LabelNumberOfGuideDays": "Número de dias de dados do guia para baixar:", "LabelNumberOfGuideDaysHelp": "Baixar mais dias do guia da TV permite agendar com maior antecedência e visualizar mais listas, mas também levará mais tempo para baixar. Se selecionar Automático, será escolhido o período baseado no número de canais.", "LabelOptionalNetworkPath": "(Opcional) Pasta compartilhada em rede:", - "LabelOptionalNetworkPathHelp": "Se esta pasta estiver compartilhada em sua rede, fornecendo o local do compartilhamento em rede permitirá que os apps Jellyfin em outros dispositivos acessem arquivos de mídia diretamente.", + "LabelOptionalNetworkPathHelp": "Se esta pasta estiver compartilhada em sua rede, informar o caminho do compartilhamento permitirá que os apps Jellyfin em outros dispositivos acessem arquivos de mídia diretamente. Por exemplo, {0} ou {1}.", "LabelOriginalAspectRatio": "Proporção original da tela:", "LabelOriginalTitle": "Título original:", "LabelOverview": "Sinopse:", @@ -725,7 +725,7 @@ "LabelReasonForTranscoding": "Motivo da transcodificação:", "LabelRecord": "Gravar:", "LabelRecordingPath": "Local de gravação padrão:", - "LabelRecordingPathHelp": "Define o local padrão para salvar as gravações. Se deixar em branco, a pasta de dados do programa do servidor será usada.", + "LabelRecordingPathHelp": "Define o local padrão para salvar as gravações. Se deixar não preenchido, a pasta de dados do programa do servidor será usada.", "LabelRefreshMode": "Modo de atualização:", "LabelReleaseDate": "Data do lançamento:", "LabelRemoteClientBitrateLimit": "Limite do bitrate para streaming da internet (Mbps):", @@ -812,7 +812,7 @@ "LabelYoureDone": "Pronto!", "LabelZipCode": "CEP:", "LabelffmpegPath": "Local do FFmpeg:", - "LabelffmpegPathHelp": "O local para o arquivo de aplicação ffmpeg, ou pasta contendo ffmpeg.", + "LabelffmpegPathHelp": "O local para o programa ffmpeg, ou pasta contendo ffmpeg.", "LanNetworksHelp": "Lista separada por vírgula de endereços IP ou entradas IP/máscara de rede para redes que serão consideradas como redes locais ao forçar restrições de banda. Se definida, todos os outros endereços IP serão considerados como estando em uma rede externa e estarão sujeitos a restrições de banda externa. Se deixada em branco, apenas a sub-rede do servidor é considerada como rede local.", "Large": "Grande", "LatestFromLibrary": "Recentes {0}", @@ -867,7 +867,7 @@ "MessageDeleteTaskTrigger": "Deseja realmente excluir este disparador de tarefa?", "MessageDirectoryPickerBSDInstruction": "Para BSD, você precisará configurar o armazenamento dentro de seu Jail do FreeNAS para permitir que o Jellyfin tenha acesso a ele.", "MessageDirectoryPickerInstruction": "Os locais de rede podem ser inseridos manualmente caso o botão de rede falhe em localizar seus dispositivos. Por exemplo, {0} ou {1}.", - "MessageDirectoryPickerLinuxInstruction": "Para Linux no Arch Linux, CentOS, Debian, Fedora, openSUSE ou Ubuntu, você deve permitir que o usuário do serviço tenha ao menos acesso de leitura ao seu armazenamento.", + "MessageDirectoryPickerLinuxInstruction": "Sistemas operacionais Arch Linux, CentOS, Debian, Fedora, openSUSE ou Ubuntu, devem permitir que a conta de serviço tenha ao menos acesso de leitura nos locais de armazenamento.", "MessageDownloadQueued": "Download enfileirado.", "MessageEnablingOptionLongerScans": "Ativar esta opção pode resultar em rastreamentos de biblioteca significativamente mais demorados.", "MessageFileReadError": "Ocorreu um erro ao ler o arquivo. Por favor, tente novamente.", @@ -882,7 +882,7 @@ "MessageNoAvailablePlugins": "Não existem plugins disponíveis.", "MessageNoMovieSuggestionsAvailable": "Não existem sugestões de filmes disponíveis atualmente. Comece a assistir e avaliar seus filmes e então retorne para ver suas recomendações.", "MessageNoPluginsInstalled": "Você não tem plugins instalados.", - "MessageNoTrailersFound": "Nenhum trailer encontrado. Instale o canal Trailer para melhorar sua experiência com filmes, adicionando uma biblioteca de trailers da internet.", + "MessageNoTrailersFound": "Instale o canal de trailers para melhorar sua experiência com filmes, adicionando uma biblioteca de trailers da internet.", "MessageNothingHere": "Nada aqui.", "MessagePasswordResetForUsers": "As senhas foram removidas dos seguintes usuários. Para entrar, eles devem acessar com o código pin usado para fazer a redefinição de senha.", "MessagePlayAccessRestricted": "A reprodução para este conteúdo está atualmente restrita. Por favor, contate o administrador do servidor para mais informações.", @@ -898,7 +898,7 @@ "MessageYouHaveVersionInstalled": "Você possui a versão {0} instalada.", "Metadata": "Metadados", "MetadataManager": "Gerenciador de Metadados", - "MetadataSettingChangeHelp": "Alterar as configurações dos metadados afetará o novo conteúdo que será adicionado. Para atualizar o conteúdo existente, abra a tela de detalhes e clique no botão de atualizar ou atualize usando o gerenciador de metadados.", + "MetadataSettingChangeHelp": "Alterar as configurações dos metadados afetará o novo conteúdo que será adicionado. Para atualizar o conteúdo existente, abra a tela de detalhes e clique no botão de atualizar, ou atualize usando o gerenciador de metadados.", "MinutesAfter": "minutos após", "MinutesBefore": "minutos antes", "Mobile": "Celular", @@ -924,9 +924,9 @@ "No": "Não", "NoNewDevicesFound": "Nenhum novo dispositivo encontrado. Para adicionar um novo sintonizador, feche esta mensagem e digite as informações do dispositivo manualmente.", "NoNextUpItemsMessage": "Nada encontrado. Comece a assistir suas séries!", - "NoPluginConfigurationMessage": "Este plugin não tem configurações disponíveis.", + "NoPluginConfigurationMessage": "Este plugin não permite alterar configurações.", "NoSubtitleSearchResultsFound": "Nenhum resultado encontrado.", - "NoSubtitles": "Nenhuma legenda", + "NoSubtitles": "Não há legendas", "NoSubtitlesHelp": "Legendas não serão carregadas por padrão. Elas podem ser carregadas manualmente durante a reprodução.", "None": "Nenhum(a)", "NumLocationsValue": "{0} pastas", @@ -949,10 +949,10 @@ "OptionAllowMediaPlaybackTranscodingHelp": "Restringir o acesso à transcodificação pode ocasionar falhas na reprodução nos apps do Jellyfin devido a formatos de mídias não suportados.", "OptionAllowRemoteControlOthers": "Permitir controle remoto de outros usuários", "OptionAllowRemoteSharedDevices": "Permitir controle remoto de dispositivos compartilhados", - "OptionAllowRemoteSharedDevicesHelp": "Dispositivos DLNA são considerados compartilhados até que um usuário comece a controlá-los.", + "OptionAllowRemoteSharedDevicesHelp": "Dispositivos DLNA são autorizados até que um usuário altere as permissões.", "OptionAllowSyncTranscoding": "Permitir download e sincronização de mídia que necessite de transcodificação", "OptionAllowUserToManageServer": "Permitir este usuário administrar o servidor", - "OptionAllowVideoPlaybackRemuxing": "Permitir reprodução de vídeos que requeiram conversão sem re-encodação", + "OptionAllowVideoPlaybackRemuxing": "Permitir reprodução de vídeos que requerem conversão sem recodificar", "OptionAllowVideoPlaybackTranscoding": "Permitir reprodução de vídeo que necessite de transcodificação", "OptionArtist": "Artista", "OptionAscending": "Crescente", @@ -976,15 +976,15 @@ "OptionDescending": "Decrescente", "OptionDisableUser": "Desativar este usuário", "OptionDisableUserHelp": "Se desativado, o servidor não permitirá nenhuma conexão deste usuário. Conexões existentes serão encerradas imediatamente.", - "OptionDislikes": "Não Curtidos", - "OptionDisplayFolderView": "Exibe uma visualização de pasta para exibir pastas de mídias", - "OptionDisplayFolderViewHelp": "Exibe pastas ao lado de suas outras biblioteca de mídia. Isto pode ser útil se quiser uma visualização por pasta.", + "OptionDislikes": "Não gostei", + "OptionDisplayFolderView": "Exibe visualização em pastas para exibir pastas de mídias", + "OptionDisplayFolderViewHelp": "Exibição em pastas ao lado das biblioteca de mídia. Isto pode ser útil para visualizar por pastas.", "OptionDownloadArtImage": "Arte", "OptionDownloadBackImage": "Traseira", "OptionDownloadBoxImage": "Caixa", "OptionDownloadDiscImage": "Disco", "OptionDownloadImagesInAdvance": "Fazer download de imagens antecipadamente", - "OptionDownloadImagesInAdvanceHelp": "Por padrão, a maioria das imagens são baixadas só quando um app Jellyfin solicita. Ativando esta opção, baixará todas as imagens antecipadamente, assim que novas mídias são importadas. Isto pode ocasionar um tempo maior para escanear a biblioteca.", + "OptionDownloadImagesInAdvanceHelp": "Por padrão, a maioria das imagens são baixadas somente quando um app Jellyfin solicita. Ativar esta opção, baixará todas as imagens antecipadamente, ao importar novas mídias. Isto pode ocasionar um tempo maior para escanear a biblioteca.", "OptionDownloadPrimaryImage": "Principal", "OptionDownloadThumbImage": "Miniatura", "OptionDvd": "DVD", @@ -1021,7 +1021,7 @@ "OptionMissingEpisode": "Episódios em Falta", "OptionMonday": "Segunda-feira", "OptionNameSort": "Nome", - "OptionNew": "Novo...", + "OptionNew": "Novo…", "OptionNone": "Nenhum", "OptionOnAppStartup": "Ao iniciar a aplicação", "OptionOnInterval": "Em um intervalo", @@ -1030,7 +1030,7 @@ "OptionPlainStorageFoldersHelp": "Se ativado, todas as pastas são representadas no DIDL como \"object.container.storageFolder\" ao invés de um tipo mais específico como, por exemplo, \"object.container.person.musicArtist\".", "OptionPlainVideoItems": "Exibir todos os vídeos como itens de vídeo", "OptionPlainVideoItemsHelp": "Se ativado, todos os vídeos são representados no DIDL como \"object.item.videoItem\" ao invés de um tipo mais específico como, por exemplo, \"object.item.videoItem.movie\".", - "OptionPlayCount": "Número de Reproduções", + "OptionPlayCount": "Contagem de Reproduções", "OptionPlayed": "Reproduzido", "OptionPremiereDate": "Data da Estréia", "OptionProfileAudio": "Áudio", @@ -1039,7 +1039,7 @@ "OptionProfileVideoAudio": "Áudio do Vídeo", "OptionReleaseDate": "Data de Lançamento", "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que o servidor suporta busca de byte quando transcodificar", - "OptionReportByteRangeSeekingWhenTranscodingHelp": "Isto é necessário para alguns dispositivos que não buscam o tempo muito bem.", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Isto é necessário para avançar ou retroceder o tempo em alguns dispositivos.", "OptionRequirePerfectSubtitleMatch": "Fazer download apenas de legendas que correspondam exatamente aos meus arquivos de vídeo", "OptionRequirePerfectSubtitleMatchHelp": "Ao solicitar uma combinação perfeita, filtrará as legendas para incluir somente aquelas que foram testadas e verificadas com o arquivo de vídeo. Ao desmarcar isto, aumentará a quantidade de legendas baixadas, mas aumentará as chances de ter legendas que não estejam sincronizadas.", "OptionResElement": "elemento res", @@ -1063,9 +1063,9 @@ "OptionWeekly": "Semanal", "OriginalAirDateValue": "Data de exibição original: {0}", "Overview": "Sinopse", - "PackageInstallCancelled": "Instalação de {0} cancelada.", - "PackageInstallCompleted": "Instalação de {0} concluída.", - "PackageInstallFailed": "Instalação de {0} falhou.", + "PackageInstallCancelled": "Instalação da versão {0} cancelada.", + "PackageInstallCompleted": "Instalação da versão {0} concluída.", + "PackageInstallFailed": "A Instalação da versão {0} falhou.", "ParentalRating": "Classificação etária", "PasswordMatchError": "A senha e a confirmação da senha devem ser iguais.", "PasswordResetComplete": "A senha foi redefinida.", @@ -1121,7 +1121,7 @@ "RefreshMetadata": "Atualizar metadados", "RefreshQueued": "Atualização enfileirada.", "ReleaseDate": "Data de lançamento", - "RememberMe": "Lembre-me", + "RememberMe": "Lembrar-me", "RemoveFromCollection": "Remover da coletânea", "RemoveFromPlaylist": "Remover da lista de reprodução", "Repeat": "Repetir", @@ -1132,7 +1132,6 @@ "ReplaceAllMetadata": "Substituir todos os metadados", "ReplaceExistingImages": "Substituir imagens existentes", "RequiredForAllRemoteConnections": "Necessário para todas as conexões remotas", - "RestartPleaseWaitMessage": "Por favor, aguarde enquanto o Servidor Jellyfin reinicia. Isto pode levar um ou dois minutos.", "ResumeAt": "Retomar de {0}", "Rewind": "Retroceder", "RunAtStartup": "Executar ao iniciar", @@ -1265,7 +1264,7 @@ "Up": "Para cima", "Upload": "Carregar", "UserAgentHelp": "Fornece um cabeçalho HTTP personalizado para o user-agent.", - "UserProfilesIntro": "O Jellyfin inclui suporte para perfis de usuários com configurações precisas de exibição, estado de reprodução e controle dos pais.", + "UserProfilesIntro": "O Jellyfin inclui suporte para perfis de usuário com configurações de exibição granular, estado de reprodução e controle dos pais.", "ValueAlbumCount": "{0} álbuns", "ValueAudioCodec": "Codec de Áudio: {0}", "ValueConditions": "Condições: {0}", @@ -1324,7 +1323,7 @@ "Horizontal": "Horizontal", "LabelAbortedByServerShutdown": "(Abortado devido ao desligamento do servidor)", "LabelCache": "Cache:", - "LabelLogs": "Logs:", + "LabelLogs": "Registros:", "LabelProfileCodecs": "Codecs:", "LabelSkin": "Tema:", "LabelStatus": "Status:", @@ -1343,17 +1342,16 @@ "OptionAuto": "Automático", "AuthProviderHelp": "Seleciona um provedor de autenticação que será usado para autenticar a senha do usuário.", "HeaderFavoriteMovies": "Filmes Favoritos", - "HeaderFavoriteShows": "Séries Favoritas", - "HeaderFavoriteEpisodes": "Episódios Favoritos", + "HeaderFavoriteShows": "Séries favoritas", + "HeaderFavoriteEpisodes": "Episódios favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos", - "HeaderFavoriteArtists": "Artistas Favoritos", - "HeaderFavoriteSongs": "Músicas Favoritas", - "HeaderFavoriteVideos": "Vídeos Favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteSongs": "Músicas favoritas", + "HeaderFavoriteVideos": "Videos favoritos", "HeaderHome": "Inicio", - "HeaderRestartingServer": "Reiniciando Servidor", - "LabelAuthProvider": "Provedor de Autenticação:", + "LabelAuthProvider": "Provedor de autenticação:", "LabelServerName": "Nome do servidor:", - "LabelTranscodePath": "Local de transcodificação:", + "LabelTranscodePath": "Pasta de transcodificação:", "LabelTranscodes": "Transcodificação:", "LabelUserLoginAttemptsBeforeLockout": "Tentativas de login com falha antes que o usuário seja bloqueado:", "DashboardVersionNumber": "Versão: {0}", @@ -1361,7 +1359,7 @@ "DashboardOperatingSystem": "Sistema Operacional: {0}", "DashboardArchitecture": "Arquitetura: {0}", "LabelPasswordResetProvider": "Provedor para Redefinir a Senha:", - "LabelWeb": "Web: ", + "LabelWeb": "Web:", "OptionBluray": "Blu-ray", "LabelProfileContainer": "Formato:", "LabelTranscodingContainer": "Formato:", @@ -1392,7 +1390,7 @@ "OptionIsSD": "SD", "OptionList": "Lista", "OptionLoginAttemptsBeforeLockout": "Determinar quantas tentativas de logins incorretas podem ser feitas antes de ocorrer o bloqueio.", - "OptionLoginAttemptsBeforeLockoutHelp": "Um valor de zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Configurar para -1 desativará o recurso.", + "OptionLoginAttemptsBeforeLockoutHelp": "O valor zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Configurar para -1 desativará o recurso.", "OptionPoster": "Poster", "OptionPosterCard": "Cartaz", "OptionProtocolHls": "Streaming ao Vivo por HTTP", @@ -1434,7 +1432,7 @@ "LabelPlayMethod": "Método de Reprodução:", "LabelPlayer": "Reprodutor:", "LabelFolder": "Pasta:", - "LabelBaseUrlHelp": "Você pode adicionar um subdiretório aqui para acessar o servidor de uma única URL.", + "LabelBaseUrlHelp": "Você pode adicionar um subdiretório aqui para acessar o servidor de uma única URL. Por exemplo:http://exemplo.com/<baseurl>", "LabelBaseUrl": "URL Base:", "LabelBitrate": "Bitrate:", "LabelAudioSampleRate": "Taxa de amostragem do áudio:", @@ -1456,16 +1454,123 @@ "MessageConfirmAppExit": "Você quer sair?", "LabelVideoResolution": "Resolução de vídeo:", "LabelStreamType": "Tipo de stream:", - "EnableFastImageFadeIn": "Fade-in rápido da imagem", "LabelPlayerDimensions": "Dimensões do player:", "LabelCorruptedFrames": "Quadros corrompidos:", "HeaderNavigation": "Navegação", "CopyStreamURLError": "Houve um erro ao copiar a URL.", "ButtonSplit": "Dividir", "AskAdminToCreateLibrary": "Peça a um administrador para criar uma biblioteca.", - "AllowFfmpegThrottling": "Limitar transcodificação", - "PlaybackErrorNoCompatibleStream": "Houve um erro na criação de perfil do cliente e o servidor não está enviando um formato de mídia compatível.", - "EnableFastImageFadeInHelp": "Habilitar animações rápidas de aparecimento para imagens carregadas", + "AllowFfmpegThrottling": "Transcodes do Acelerador", + "PlaybackErrorNoCompatibleStream": "Este cliente não é compatível com a media e o servidor não está enviando um formato de mídia compatível.", "LabelDroppedFrames": "Quadros caídos:", - "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução." + "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução.", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações dos episódios incorporadas nos arquivos ao invés dos nomes", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esta opção habilita as informações dos episódios incorporadas nos metadados dos arquivos(se estiverem disponíveis).", + "ClientSettings": "Configurações do cliente", + "OnApplicationStartup": "Na inicialização do aplicativo", + "EveryXHours": "A cada {0} horas", + "EveryHour": "A cada hora", + "EveryXMinutes": "A cada {0} minutos", + "OnWakeFromSleep": "Ao acordar da suspensão", + "WeeklyAt": "{0} às {1}", + "DailyAt": "Diariamente à {0}", + "LastSeen": "Visto pela última vez {0}", + "PersonRole": "como {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "O servidor Jellyfin necessita de acesso de escrita para essa pasta. Garanta o acesso e tente novamente.", + "PathNotFound": "O caminho não pôde ser encontrado. Por favor certifique-se da validade e tente novamente.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Trilha", + "Season": "Temporada", + "ReleaseGroup": "Grupo de Lançamento", + "Person": "Pessoa", + "OtherArtist": "Outro Artista", + "Movie": "Filme", + "LabelLibraryPageSizeHelp": "Selecione a quantidade de itens a aparecer na página da biblioteca. Coloque 0 para desabilitar a paginação.", + "LabelLibraryPageSize": "Tamanho da página da biblioteca:", + "LabelDeinterlaceMethod": "Método de desentrelaçamento:", + "Episode": "Episódio", + "DeinterlaceMethodHelp": "Selecione o método de desentrelaçamento a ser usado ao transcodificar o conteúdo entrelaçado.", + "BoxSet": "Coleção", + "Artist": "Artista", + "AlbumArtist": "Artista do Album", + "Album": "Album", + "UnsupportedPlayback": "O Jellyfin não pode descriptografar conteúdo protegido por DRM, porém mesmo assim fará uma tentativa para todo tipo de conteúdo, incluindo títulos protegidos. A imagem de alguns arquivos pode aparecer completamente preta devido a criptografia ou outros recursos não suportados, como títulos interativos.", + "MessageUnauthorizedUser": "Você não está autorizado a acessar o servidor neste momento. Por favor, contate o administrador do servidor para mais informações.", + "ButtonTogglePlaylist": "Playlist", + "ButtonToggleContextMenu": "Mais", + "Filter": "Filtro", + "New": "Novo", + "HeaderFavoritePlaylists": "Playlists Favoritas", + "ApiKeysCaption": "Lista de chaves API ativadas no momento", + "TabDVR": "DVR", + "SaveChanges": "Salvar mudanças", + "LabelRequireHttpsHelp": "Se selecionado, o servidor vai automaticamente redirecionar todas as solicitações HTTP para HTTPS. Isso não terá efeito se o servidor não estiver escutando HTTPS.", + "LabelRequireHttps": "Necessita HTTPS", + "LabelNightly": "Nightly", + "LabelStable": "Estável", + "LabelChromecastVersion": "Versão do Chromecast", + "LabelEnableHttpsHelp": "Permite que o servidor escute na porta HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttps": "Habilitar HTTPS", + "HeaderServerAddressSettings": "Configurações da localização do servidor", + "HeaderRemoteAccessSettings": "Configurações de acesso remoto", + "HeaderHttpsSettings": "Configurações HTTPS", + "HeaderDVR": "DVR", + "LabelSyncPlayTimeOffset": "Diferença de tempo com o servidor:", + "SyncPlayAccessHelp": "Selecione o nível de acesso desse usuário aos recursos do SyncPlay. SyncPlay habilita a reprodução sincronizada com outros dispositivos.", + "MessageSyncPlayErrorMedia": "Falha ao ativar SyncPlay! Erro de mídia.", + "MessageSyncPlayErrorMissingSession": "Falha ao ativar SyncPlay! Sessão em falta.", + "MessageSyncPlayErrorNoActivePlayer": "Nenhum reprodutor ativo encontrado. SyncPlay foi desativado.", + "MessageSyncPlayErrorAccessingGroups": "Ocorreu um erro ao acessar a lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "O acesso a esse conteúdo é restrito.", + "MessageSyncPlayJoinGroupDenied": "Permissão necessária para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permissão necessária para criar um grupo.", + "MessageSyncPlayGroupDoesNotExist": "Falha ao participar de grupo pois o mesmo não existe.", + "MessageSyncPlayPlaybackPermissionRequired": "É necessária permissão de reprodução.", + "MessageSyncPlayNoGroupsAvailable": "Nenhum grupo disponível. Comece a reproduzir algo primeiro.", + "MessageSyncPlayGroupWait": "{0} está carregando...", + "MessageSyncPlayUserLeft": "{0} deixou o grupo.", + "MessageSyncPlayUserJoined": "{0} se juntou ao grupo.", + "MessageSyncPlayDisabled": "SyncPlay desativado.", + "MessageSyncPlayEnabled": "SyncPlay ativado.", + "LabelSyncPlayAccess": "Acesso ao SyncPlay", + "LabelSyncPlayAccessNone": "Desativado para esse usuário", + "LabelSyncPlayAccessJoinGroups": "Permitir que o usuário participe de grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir que o usuário crie e participe em grupos", + "LabelSyncPlayLeaveGroupDescription": "Desativar SyncPlay", + "LabelSyncPlayLeaveGroup": "Deixar grupo", + "LabelSyncPlayNewGroupDescription": "Criar novo grupo", + "LabelSyncPlayNewGroup": "Novo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronização:", + "LabelSyncPlayPlaybackDiff": "Diferença no tempo de reprodução:", + "MillisecondsUnit": "ms", + "HeaderSyncPlayEnabled": "SyncPlay ativado", + "HeaderSyncPlaySelectGroup": "Entrar em um grupo", + "EnableDetailsBanner": "Banner de detalhes", + "EnableDetailsBannerHelp": "Exibe um banner na parte superior da página de detalhes do item.", + "EnableBlurhashHelp": "Imagens que ainda estão carregando vão mostrar em seu lugar representações", + "EnableBlurhash": "Habilitar efeito borrado para imagens previas", + "ShowMore": "Mostrar mais", + "ShowLess": "Mostrar menos", + "ButtonSyncPlay": "SyncPlay", + "ButtonCast": "Elenco", + "MessageNoGenresAvailable": "Habilitar alguns provedores de metadados a baixar gêneros da internet.", + "EnableFasterAnimationsHelp": "Usar animações e transições mais rápidas", + "EnableFasterAnimations": "Animações mais rápidas", + "EnableDecodingColorDepth10Vp9": "Habilitar decodificação de hardware de 10 bits para VP9", + "EnableDecodingColorDepth10Hevc": "Habilitar decodificação de hardware de 10 bits para HEVC", + "TabRepositories": "Repositórios", + "MessageAddRepository": "Se você deseja adicionar um repositório, clique no botão próximo ao cabeçalho e preencha as informações requisitadas.", + "LabelRepositoryNameHelp": "Um nome personalizado para distinguir esse repositório dos outros adicionados ao seu servidor.", + "LabelRepositoryName": "Nome do repositório", + "LabelRepositoryUrlHelp": "A localização do manifesto do repositório que você deseja incluir.", + "LabelRepositoryUrl": "URL do repositório", + "HeaderNewRepository": "Novo repositório", + "MessageNoRepositories": "Não há repositórios.", + "ButtonPlayer": "Reprodutor", + "Writers": "Escritores", + "ClearQueue": "Limpar fila", + "StopPlayback": "Parar reprodução", + "ViewAlbumArtist": "Ver artista do álbum" } diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index d4a81580f2d..b482189ffba 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -320,7 +320,7 @@ "LabelEmbedAlbumArtDidl": "Incorporar a capa do álbum no DIDL", "LabelEmbedAlbumArtDidlHelp": "Alguns dispositivos preferem este método para obter a capa do álbum. Noutros pode falhar a reprodução com esta opção ativada.", "LabelEnableAutomaticPortMap": "Ativar mapeamento automático de portas", - "LabelEnableAutomaticPortMapHelp": "Tenta mapear automaticamente o porto público para o porto local através de UPnP. Isto poderá não funcionar em alguns modelos de routers.", + "LabelEnableAutomaticPortMapHelp": "Automaticamente encaminha o porto público para o porto local através de UPnP. Isto poderá não funcionar em alguns modelos de routers ou devido às configurações da rede. As alterações só serão aplicadas após o reiniciar do servidor", "LabelEnableBlastAliveMessages": "Enviar mensagens de reconhecimento", "LabelEnableBlastAliveMessagesHelp": "Ativar esta opção se o servidor não for convenientemente detetado por outros dispositivos UPnP na rede.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo para descoberta de clientes (segundos)", @@ -605,7 +605,7 @@ "OptionMissingEpisode": "Episódios em Falta", "OptionMonday": "Segunda", "OptionNameSort": "Nome", - "OptionNew": "Nova...", + "OptionNew": "Nova…", "OptionNone": "Nenhum", "OptionOnAppStartup": "Ao iniciar a aplicação", "OptionOnInterval": "Num intervalo", @@ -768,13 +768,13 @@ "AllowMediaConversion": "Permitir conversão multimédia", "AllowMediaConversionHelp": "Permitir ou negar acesso à funcionalidade de conversão multimédia.", "AllowOnTheFlySubtitleExtraction": "Permitir a extração de legendas em tempo real", - "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes para evitar transcodificação. Em certos dispositivos, esta poderá ser uma operação demorada e pode causar paragens de reprodução durante o processo de extração. Desative esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", + "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes para evitar transcodificação. Em certos dispositivos, esta poderá ser uma operação demorada e pode causar interrupções de reprodução durante o processo de extração. Desative esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", "AllowRemoteAccess": "Permitir ligações remotas a este Jellyfin Server.", "AllowRemoteAccessHelp": "Se inativo, todas as ligações remotas serão bloqueadas.", "AllowedRemoteAddressesHelp": "Lista de IP ou IP/Máscara, separados por vírgulas, com permissão para se ligar remotamente. Se deixado em branco, todos os endereços remotos serão permitidos.", - "AlwaysPlaySubtitles": "Mostrar sempre legendas", + "AlwaysPlaySubtitles": "Reproduzir Sempre", "AlwaysPlaySubtitlesHelp": "Legendas correspondentes à língua preferencial vão ser sempre carregadas, independentemente do idioma do áudio.", - "AnyLanguage": "Qualquer idioma", + "AnyLanguage": "Qualquer linguagem", "Artists": "Artistas", "Ascending": "Crescente", "AspectRatio": "Proporção", @@ -785,7 +785,7 @@ "BirthPlaceValue": "Local de nascimento: {0}", "Blacklist": "Lista Negra", "Books": "Livros", - "BurnSubtitlesHelp": "Determina se o servidor deve integrar as legendas durante a conversão de vídeo, dependendo do formato da legenda. Evitar integração de legendas melhora o desempenho do servidor. Selecione Auto para que legendas baseadas em imagem (VOBSUB, PGS, SUB/IDX), e certos formatos ASS/SSA sejam integrados.", + "BurnSubtitlesHelp": "Determina se o servidor deve integrar as legendas durante a conversão de vídeo. Evitar a integração de legendas melhora o desempenho do servidor. Selecione Auto para que legendas baseadas em imagem (VOBSUB, PGS, SUB/IDX), e certos formatos ASS/SSA sejam integrados.", "Channels": "Canais", "Collections": "Coleções", "Favorites": "Favoritos", @@ -831,7 +831,7 @@ "Browse": "Procurar", "BoxRear": "Caixa (verso)", "Box": "Caixa", - "BookLibraryHelp": "Livros de texto e áudio são suportados. Consulte o guia de nomenclatura de livros{1}.", + "BookLibraryHelp": "Livros digitais e áudio livros são suportados. Consulte o guia de nomenclatura de livros{1}.", "BirthLocation": "Local de nascimento", "AsManyAsPossible": "Tantos quanto possível", "Art": "Capa", @@ -963,7 +963,7 @@ "LabelAbortedByServerShutdown": "(Abortado - Servidor encerrado)", "Kids": "Crianças", "Items": "Itens", - "InstallingPackage": "A instalar {0}", + "InstallingPackage": "A instalar {0} (version {1})", "HttpsRequiresCert": "Para activar ligações seguras, é necessário fornecer um certificado SSL confiável. Forneça um certificado SSL ou desactive as ligações seguras.", "DirectStreamHelp1": "O tipo de multimédia (H.264, AC3, etc.) e a sua resolução são compatíveis com o dispositivo, no entanto, o formato (mkv, avi, wmv, etc.) não é. O conteúdo é reempacotado em tempo real antes de ser enviado para o dispositivo.", "DirectPlaying": "Reprodução direta", @@ -997,7 +997,6 @@ "Runtime": "Duração", "RunAtStartup": "Executar no arranque", "ResumeAt": "Retomar a partir de {0}", - "RestartPleaseWaitMessage": "Por favor aguarde enquanto o Servidor Jellyfin reinicia. Poderá demorar alguns minutos.", "RequiredForAllRemoteConnections": "Necessário para todas as ligações externas", "ReplaceAllMetadata": "Substituir todos os metadados", "RepeatOne": "Repetir este", @@ -1042,7 +1041,7 @@ "HeaderPlayOn": "Reproduzir Em", "HeaderNextVideoPlayingInValue": "Reprodução do próximo vídeo a iniciar em {0}", "HeaderNextEpisodePlayingInValue": "Reprodução do próximo episódio a iniciar em {0}", - "HardwareAccelerationWarning": "Ativar a aceleração por hardware pode causar instabilidade em alguns ambientes. Garanta que o sistema operativo e os controladores da placa gráfica estão completamente atualizados. Se tiver dificuldades em reproduzir vídeo depois de alterar esta opção, pode ser necessário repôr em \\\"Auto\\\".", + "HardwareAccelerationWarning": "Ativar a aceleração por hardware pode causar instabilidade em alguns ambientes. Garanta que o sistema operativo e os controladores da placa gráfica estão completamente atualizados. Se tiver dificuldades em reproduzir vídeo depois de alterar esta opção, pode ser necessário repôr as definições para o parâmetro \\\"Nenhum\\\".", "Display": "Visualização", "ManageLibrary": "Gerir biblioteca", "HeaderLibraryOrder": "Ordenação da Biblioteca", @@ -1103,7 +1102,7 @@ "HeaderNewDevices": "Novos Dispositivos", "HeaderRecordingOptions": "Opções de Gravação", "HeaderSortOrder": "Direção de Ordenação", - "LabelBaseUrlHelp": "Pode adicionar uma sub-pasta personalizada aqui para aceder ao servidor através de um URL mais direto.", + "LabelBaseUrlHelp": "Adiciona uma sub-pasta personalizada ao URL do servidor. Por exemplo: http://exemplo.com/<baseurl>", "LabelMoviePrefixHelp": "Se aplicar um prefixo aos títulos dos filmes, introduza-o aqui para que o servidor consiga tratá-los corretamente.", "LabelPleaseRestart": "As alterações produzirão efeito depois de recarregar a página web.", "LabelRecordingPathHelp": "Especifique a localização por defeito para guardar as gravações. Se for deixado em branco, será utilizada a pasta base do servidor.", @@ -1113,7 +1112,7 @@ "HeaderMusicQuality": "Qualidade da Música", "HeaderMyDevice": "O Meu Dispositivo", "HeaderSortBy": "Ordenar Por", - "LabelOptionalNetworkPathHelp": "Se esta pasta estiver partilhada na rede, fornecer o caminho de rede pode permitir aos clientes aceder diretamente aos ficheiros multimédia.", + "LabelOptionalNetworkPathHelp": "Se esta pasta estiver partilhada na rede, fornecer o caminho de rede pode permitir aos clientes aceder diretamente aos ficheiros multimédia. For example, {0} or {1}.", "LabelPersonRoleHelp": "Exemplo: motorista da carrinha de gelados", "LabelPlayer": "Reprodutor:", "LabelServerName": "Nome do servidor:", @@ -1182,7 +1181,6 @@ "CopyStreamURL": "Copiar URL da transmissão", "Disc": "Disco", "EnableBackdrops": "Imagens de Fundo", - "HeaderRestartingServer": "A reiniciar o Servidor", "HeaderTags": "Etiquetas", "LabelLogs": "Registos:", "LabelSortTitle": "Título para ordenação:", @@ -1328,7 +1326,7 @@ "No": "Não", "OptionRegex": "Expressão Regular", "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", - "NoSubtitles": " Sem legendas", + "NoSubtitles": "Sem legendas", "NoSubtitleSearchResultsFound": "Sem resultados.", "NoNewDevicesFound": "Não foi encontrado nenhum dispositivo novo. Para adicionar um novo sintonizador, feche este diálogo e introduza manualmente as informações do dispositivo.", "NextUp": "A Seguir", @@ -1339,7 +1337,7 @@ "Name": "Nome", "MusicVideo": "Videoclip", "MusicArtist": "Artista de Música", - "MusicAlbum": " Álbum de Música", + "MusicAlbum": "Álbum de Música", "MoreMediaInfo": "Informações", "MediaInfoBitrate": "Taxa de Bits", "LabelUserAgent": "User-Agent:", @@ -1428,7 +1426,7 @@ "LabelXDlnaDoc": "X-DLNA doc:", "LabelXDlnaCap": "X-DLNA cap:", "LabelVaapiDeviceHelp": "Este é o nó de renderização usado para aceleração de hardware.", - "LabelVaapiDevice": "VA API Dispositivo:", + "LabelVaapiDevice": "Dispositivo VAAPI:", "LabelTypeMetadataDownloaders": "{0} transferências de metadados:", "LabelTheme": "Tema:", "LabelTVHomeScreen": "TV modo ecrã de casa:", @@ -1454,5 +1452,55 @@ "AllowFfmpegThrottlingHelp": "Suspende o processo de transcodificação assim que este avance o suficiente após o ponto de reprodução atual, para poupança de recursos. Esta funcionalidade é mais útil quando a reprodução é maioritariamente contínua, sem avançar ou recuar manualmente. Desative esta opção caso haja problemas de reprodução.", "AllowFfmpegThrottling": "Reduzir Taxa de Transcodificação", "PreferEmbeddedTitlesOverFileNamesHelp": "Determina o título a apresentar por defeito quando não é possível carregar metadados locais nem da Internet.", - "OptionSaveMetadataAsHiddenHelp": "Alterar esta definição apenas afetará metadados guardados futuramente. Ficheiros existentes serão atualizados assim que forem alterados pelo Servidor Jellyfin." + "OptionSaveMetadataAsHiddenHelp": "Alterar esta definição apenas afetará metadados guardados futuramente. Ficheiros existentes serão atualizados assim que forem alterados pelo Servidor Jellyfin.", + "ButtonSyncPlay": "SyncPlay", + "LabelRepositoryUrl": "URL do Repositório", + "HeaderNewRepository": "Novo Repositório", + "MessageNoRepositories": "Sem repositórios.", + "MessageUnauthorizedUser": "Não está autorizado a aceder ao servidor neste momento. Por favor contacte o administador deste servidor para informação mais detalhada.", + "LabelSyncPlayAccess": "Acesso \"SyncPlay\"", + "LabelSyncPlayAccessNone": "Desativar para este utilizador", + "LabelSyncPlayAccessJoinGroups": "Permitir utilizador a aderir a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Autorizar utilizadores a criar e aderir a grupos", + "LabelSyncPlayLeaveGroupDescription": "Desativar \"SyncPlay\"", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Criar novo grupo", + "LabelSyncPlayNewGroup": "Novo grupo", + "LabelSyncPlaySyncMethod": "Método de Sincronização:", + "LabelSyncPlayPlaybackDiff": "Diferença no tempo de Reprodução:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Diferença de tempo com o servidor:", + "EnableFasterAnimationsHelp": "Utilizar animações e transições mais rápidas", + "EnableFasterAnimations": "Animações Rápidas", + "LabelRequireHttpsHelp": "Se selecionado, o servidor irá automaticamente redirecionar todos os pedidos em HTTP para HTTPS. Isto não surte efeito caso o servidor não esteja configurado em HTTPS.", + "LabelRequireHttps": "Exigir HTTPS", + "LabelNightly": "\"Nightly\"", + "LabelStable": "Estável", + "LabelChromecastVersion": "Versão do \"Chromecast\"", + "LabelLibraryPageSizeHelp": "Define a quantidade de items a apresentar na página de uma Biblioteca. Para desativar a existência de paginação, introduza o valor 0.", + "LabelLibraryPageSize": "Tamanho da página da Biblioteca:", + "LabelEnableHttpsHelp": "Permite que o servidor escute na porta HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttps": "Ativar HTTPS", + "LabelDeinterlaceMethod": "Método de desentrelaçamento:", + "HeaderSyncPlayEnabled": "SyncPlay ativado", + "HeaderSyncPlaySelectGroup": "Aderir a um grupo", + "HeaderServerAddressSettings": "Definições da Localização do Servidor", + "HeaderRemoteAccessSettings": "Definições do Acesso Remoto", + "HeaderHttpsSettings": "Definições de HTTPS", + "HeaderFavoritePlaylists": "Listas de Reprodução Favoritas", + "HeaderDVR": "Gravações (DVR)", + "ApiKeysCaption": "Lista das chaves de API atualmente ativadas", + "Episode": "Episódio", + "EnableDetailsBannerHelp": "Mostra uma imagem no topo da página dos detalhes do item.", + "EnableDetailsBanner": "Cartaz de Detalhes", + "EnableDecodingColorDepth10Vp9": "Ativar descodificação de hardware de 10-Bits para VP9", + "EnableDecodingColorDepth10Hevc": "Ativar descodificação de hardware de 10-Bits para HEVC", + "DeinterlaceMethodHelp": "Selecionar um método de desentrelaçamento para converter conteúdo entrelaçado.", + "ClientSettings": "Definições do Cliente", + "ButtonTogglePlaylist": "Lista de Reprodução", + "ButtonToggleContextMenu": "Mais", + "BoxSet": "Coleção", + "Artist": "Artista", + "AlbumArtist": "Artista do Álbum", + "Album": "Álbum" } diff --git a/src/strings/pt.json b/src/strings/pt.json index 179c264aa47..9d6f3faf371 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1,7 +1,6 @@ { "HeaderTypeImageFetchers": "{0} Pesquisadores de Imagens", "HeaderSubtitleDownloads": "Transferências de Legendas", - "HeaderRestartingServer": "A reiniciar o servidor", "HeaderKodiMetadataHelp": "Para ativar ou desativar metadados, editar uma biblioteca nas configurações e localizar a seção dos metadados.", "HeaderFetcherSettings": "Definições do Pesquisador", "HeaderFavoritePeople": "Pessoas Preferidas", @@ -113,7 +112,7 @@ "Screenshot": "Captura de Ecrã", "Schedule": "Agendamentos", "ScanForNewAndUpdatedFiles": "Procurar ficheiros novos ou actualizados", - "SaveSubtitlesIntoMediaFoldersHelp": "Guardar ficheiros de legendas junto aos ficheiros vídeo facilita a gestão.", + "SaveSubtitlesIntoMediaFoldersHelp": "Salvar arquivos de legendas junto aos arquivos vídeo facilita o gerenciamento.", "SaveSubtitlesIntoMediaFolders": "Guardar legendas nas pastas multimédia", "Save": "Guardar", "Saturday": "Sábado", @@ -121,7 +120,6 @@ "RunAtStartup": "Executar no arranque", "Rewind": "Retroceder", "ResumeAt": "Retomar a partir de {0}", - "RestartPleaseWaitMessage": "Por favor aguarde enquanto o Servidor Jellyfin reinicia. Isto poderá demorar alguns minutos.", "RequiredForAllRemoteConnections": "Necessário para todas as ligações externas", "ReplaceExistingImages": "Substituir imagens existentes", "ReplaceAllMetadata": "Substituir todos os metadados", @@ -270,7 +268,7 @@ "LabelNewPassword": "Nova palavra-passe:", "LabelNewName": "Novo nome:", "LabelName": "Nome:", - "LabelMusicStreamingTranscodingBitrateHelp": "Defina a taxa máxima de transmissão de música", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifique uma taxa de bits máxima ao transmitir músicas.", "LabelMusicStreamingTranscodingBitrate": "Taxa de transcodificação de música:", "LabelMovieRecordingPath": "Caminho para gravação de filmes (opcional):", "LabelMoviePrefixHelp": "Se aplicar um prefixo aos títulos dos filmes, introduza-o aqui para que o servidor consiga tratá-los corretamente.", @@ -307,7 +305,7 @@ "LabelMaxBackdropsPerItem": "Número máximo de imagens de fundo por item:", "LabelMatchType": "Tipo de correspondência:", "LabelManufacturerUrl": "URL do Fabricante", - "LabelManufacturer": "Fabricante", + "LabelManufacturer": "Fabricante:", "LabelLoginDisclaimerHelp": "Este aviso será mostrado na parte inferior da página de login.", "LabelLoginDisclaimer": "Aviso legal de login:", "LabelLockItemToPreventChanges": "Bloquear este item para evitar alterações futuras", @@ -404,12 +402,12 @@ "LabelDeathDate": "Data de falecimento:", "LabelRefreshMode": "Mode de actualização:", "LabelRecord": "Registo:", - "LabelPasswordResetProvider": "Provedor de actualização de palavra-passe", + "LabelPasswordResetProvider": "Provedor de redefinição de senha:", "LabelMetadataSaversHelp": "Escolha os formato em que deseja guardar os seus metadados.", "LabelMetadataReadersHelp": "Ordene as fontes locais de metadados por ordem de prioridade. O primeiro ficheiro a ser encontrado será lido.", "LabelMetadataReaders": "Leirores de metadados", "LabelMetadataDownloadersHelp": "Active e ordene os seus pesquisadores de metadados por ordem de prioridade. Pesquisadores com menor prioridade só serão utilizados para completar informação em falta.", - "LabelLogs": "Registos", + "LabelLogs": "Histórico:", "LabelKodiMetadataUserHelp": "Guardar dados de utilização em NFO para que outras aplicações os utilizem.", "LabelKodiMetadataUser": "Guardar dados de utilização em NFO para:", "LabelImageFetchersHelp": "Activar e ordenar os pesquisadores de imagens por ordem de preferência.", @@ -891,7 +889,7 @@ "EditImages": "Editar imagens", "Edit": "Editar", "EasyPasswordHelp": "O código PIN é utilizado para acesso off-line em clientes suportados e pode ser usado para um acesso fácil dentro da rede.", - "DropShadow": "Sombreado", + "DropShadow": "sombra projetada", "DrmChannelsNotImported": "Canais com proteção DRM não serão importados.", "DownloadsValue": "{0} transferências", "Download": "Transferir", @@ -914,7 +912,7 @@ "DirectPlaying": "Reprodução directa", "DeviceAccessHelp": "Apenas se aplica a dispositivos que podem ser identificados como únicos e que não impedem o acesso ao navegador. Filtrar o acesso a dispositivos a um utilizador, impede-o de utilizar novos dispositivos, até estes serem aprovados aqui.", "DetectingDevices": "Procurando dispositivos", - "Desktop": "Desktop", + "Desktop": "Área de Trabalho", "Descending": "Descendente", "Depressed": "Baixo relevo", "DeleteUserConfirmation": "Tem a certeza que deseja apagar este utilizador?", @@ -1079,7 +1077,7 @@ "BoxRear": "Caixa (verso)", "Box": "Caixa", "Books": "Livros", - "BookLibraryHelp": "Livros de texto e áudio são suportados. Consulte o guia de nomenclatura de livros{1}.", + "BookLibraryHelp": "Livros de texto e áudio são suportados. Consulte o guia {0} de nomenclatura de livros {1}.", "Blacklist": "Lista Negra", "BirthPlaceValue": "Local de nascimento: {0}", "BirthLocation": "Local de nascimento", @@ -1099,16 +1097,16 @@ "AnyLanguage": "Qualquer idioma", "Artists": "Artistas", "AsManyAsPossible": "Tantos quanto possível", - "AllowedRemoteAddressesHelp": "Lista de endereços IP ou IP/Máscara, separados por vírgulas, com permissão para se ligar remotamente. Se deixado em branco, todos os endereços remotos serão permitidos.", - "AllowRemoteAccessHelp": "Se esta opção não for seleccionada, todas as ligações remotas serão bloqueadas.", + "AllowedRemoteAddressesHelp": "Lista separada por vírgula de endereços IP ou entradas de máscara de IP/rede para redes que terão permissão para se conectar remotamente. Se deixado em branco, todos os endereços remotos serão permitidos.", + "AllowRemoteAccessHelp": "Se desmarcada, todas as conexões remotas serão bloqueadas.", "AllowRemoteAccess": "Permitir ligações remotas a este Servidor Jellyfin.", - "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes para evitar transcodificação. Em certos dispositivos, é uma operação demorada e pode causar paragens de reprodução durante o processo de extracção. Desactive esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", - "AllowOnTheFlySubtitleExtraction": "Permitir a extracção de legendas em tempo real", - "AllowHWTranscodingHelp": "Permitir o sintonizador converter emissões em tempo real. Poderá reduzir a necessidade do servidor converter o conteúdo.", + "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes de forma a evitar transcodificação. Em certos dispositivos, esta operação pode demorar algum tempo e causar paragens de reprodução durante o processo de extração. Desative esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", + "AllowOnTheFlySubtitleExtraction": "Permitir a extração de legendas em tempo real", + "AllowHWTranscodingHelp": "Permita que o sintonizador transcodifique os fluxos em tempo real. Isso pode ajudar a reduzir a transcodificação exigida pelo servidor.", "AllLibraries": "Todas as bibliotecas", "AllLanguages": "Todos os idiomas", "AllEpisodes": "Todos os episódios", - "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", "AllChannels": "Todos os canais", "All": "Todos", "Alerts": "Alertas", @@ -1119,14 +1117,14 @@ "AddedOnValue": "{0} foi adicionado", "AddToPlaylist": "Adicionar à lista de reprodução", "AddToPlayQueue": "Adicionar à fila de reprodução", - "AddToCollection": "Adicionar à colecção", - "AddItemToCollectionHelp": "Adicione itens às colecções pesquisando-os e utilizando o respetivo menu de toque ou clique direito para os adicionar a uma colecção.", + "AddToCollection": "Adicionar à coleção", + "AddItemToCollectionHelp": "Adicione itens às coleções pesquisando-os e utilizando o respetivo menu de toque ou clique direito para os adicionar a uma coleção.", "Add": "Adicionar", - "Actor": "Actor", - "AccessRestrictedTryAgainLater": "O acesso está actualmente restrito. Por favor, tente mais tarde.", + "Actor": "Ator", + "AccessRestrictedTryAgainLater": "O acesso está atualmente restrito. Por favor, tente mais tarde.", "Absolute": "Absoluto", - "AlwaysPlaySubtitlesHelp": "Legendas correspondentes à língua preferida serão sempre carregadas, independentemente do idioma do áudio.", - "SearchForMissingMetadata": "Procurar metadados em falta", + "AlwaysPlaySubtitlesHelp": "As legendas correspondentes à preferência de idioma serão carregadas, independentemente do idioma do áudio.", + "SearchForMissingMetadata": "Procurar metadados ausentes", "ScanLibrary": "Analisar biblioteca", "HeaderDeleteItem": "Remover item", "HeaderDeleteDevice": "Apagar Dispositivo", @@ -1175,7 +1173,7 @@ "HeaderActiveDevices": "Dispositivos Activos", "HeaderAccessScheduleHelp": "Crie uma restrição horária de acesso para limitar o acesso ao Jellyfin a determinadas horas.", "HeaderAccessSchedule": "Restrição Horária de Acesso", - "HardwareAccelerationWarning": "Activar a aceleração por hardware pode causar instabilidade em alguns ambientes. Garanta que o sistema operativo e os controladores da placa gráfica estão actualizados. Se tiver dificuldades em reproduzir vídeo depois de alterar esta opção, pode ser necessário repor a configuração \\\"None\\\".", + "HardwareAccelerationWarning": "A ativação da aceleração de hardware pode causar instabilidade em alguns ambientes. Verifique se o sistema operacional e os drivers de vídeo estão totalmente atualizados. Se você tiver dificuldade em reproduzir o vídeo depois de ativar isso, precisará alterar a configuração novamente para Nenhum.", "HandledByProxy": "Gerido pelo proxy inverso", "HDPrograms": "Programas HD", "EncoderPresetHelp": "Escolha um valor mais rápido para melhorar o desempenho, ou um valor mais lento para melhorar a qualidade.", @@ -1210,7 +1208,7 @@ "HeaderNavigation": "Navegar", "CopyStreamURLError": "Ocorreu um erro ao copiar o URL.", "ButtonSplit": "Dividir", - "AskAdminToCreateLibrary": "Peça a um administrador para criar a biblioteca.", + "AskAdminToCreateLibrary": "Peça a um administrador para criar uma biblioteca.", "AllowFfmpegThrottling": "Transcodificação com falhas", "DashboardOperatingSystem": "Sistema Operativo", "LabelUserLoginAttemptsBeforeLockout": "Número de tentativas de login falhadas antes do bloqueio do utilizador:", @@ -1227,14 +1225,14 @@ "MoreMediaInfo": "Informações sobre mídia", "MoreFromValue": "Mais de {0}", "MediaInfoRefFrames": "Quadros de referência", - "MediaInfoContainer": "Container", - "MediaInfoAnamorphic": "Anamorphic", + "MediaInfoContainer": "Recipiente", + "MediaInfoAnamorphic": "Anamórfico", "LabelVideoResolution": "Resolução do vídeo:", - "LabelTypeMetadataDownloaders": "{0} metadata downloaders:", + "LabelTypeMetadataDownloaders": "{0} download de metadados:", "LabelTranscodePath": "Caminho da transcodificação:", - "OnlyImageFormats": "Somente formatos de imagem (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Somente formatos de imagem (VOBSUB, PGS, SUB)", "OnlyForcedSubtitlesHelp": "Apenas as legendas marcadas como forçadas serão carregadas.", - "OnlyForcedSubtitles": "Apenas legendas forçadas", + "OnlyForcedSubtitles": "Somente legendas forçadas", "Off": "Desligar", "NumLocationsValue": "{0} pastas", "Normal": "Normal", @@ -1276,10 +1274,10 @@ "Like": "Gostei", "LeaveBlankToNotSetAPassword": "Você pode deixar esse campo em branco para definir nenhuma senha.", "LearnHowYouCanContribute": "Aprenda como você pode contribuir.", - "LaunchWebAppOnStartupHelp": "Open the web client in your default web browser when the server initially starts. This will not occur when using the restart server function.", + "LaunchWebAppOnStartupHelp": "Abra o cliente da web no seu navegador da web padrão quando o servidor iniciar. Isso não ocorrerá ao usar a função de reinicialização do servidor.", "LaunchWebAppOnStartup": "Inicie a interface da web ao iniciar o servidor", "Large": "Amplo", - "LanNetworksHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be considered on local network when enforcing bandwidth restrictions. If set, all other IP addresses will be considered to be on the external network and will be subject to the external bandwidth restrictions. If left blank, only the server's subnet is considered to be on the local network.", + "LanNetworksHelp": "Lista separada por vírgula de endereços IP ou entradas de máscara de rede/IP para redes que serão consideradas na rede local ao impor restrições de largura de banda. Se definido, todos os outros endereços IP serão considerados na rede externa e estarão sujeitos às restrições de largura de banda externa. Se deixado em branco, apenas a sub-rede do servidor é considerada na rede local.", "LabelffmpegPathHelp": "O caminho para o arquivo do aplicativo ffmpeg ou pasta que contém o ffmpeg.", "LabelffmpegPath": "FFmpeg caminho:", "LabelYear": "Ano:", @@ -1295,7 +1293,7 @@ "LabelVersion": "Versão:", "LabelVaapiDeviceHelp": "Este é o nó de renderização usado para aceleração de hardware.", "LabelVaapiDevice": "VA API Dispositivo:", - "LabelUserAgent": "Agente de usuário", + "LabelUserAgent": "Agente de usuário:", "LabelTranscodes": "Transcodificação:", "LabelTranscodingFramerate": "Transcodificação frame por segundo:", "LabelTranscodingProgress": "Progresso da transcodificação:", @@ -1311,11 +1309,9 @@ "LabelSpecialSeasonsDisplayName": "Nome de exibição da temporada especial:", "LabelSoundEffects": "Efeitos sonoros:", "LabelSortTitle": "Classificar título:", - "LabelSortOrder": "Ordem da Ordenação", + "LabelSortOrder": "Ordem de classificação:", "LabelSortBy": "Ordenar por:", - "LabelSkin": "Skin:", - "EnableFastImageFadeInHelp": "Habilite uma animação mais rápida para imagens carregadas", - "EnableFastImageFadeIn": "Efeito de imagem fade-in rápido", + "LabelSkin": "Pele:", "LabelRemoteClientBitrateLimitHelp": "Um limite opcional de taxa de bits por fluxo para todos os dispositivos fora da rede. Isso é útil para impedir que os dispositivos solicitem uma taxa de bits mais alta do que a sua conexão à Internet pode suportar. Isso pode resultar no aumento da carga da CPU no servidor para transcodificar vídeos em tempo real para uma taxa de bits mais baixa.", "LabelPlayerDimensions": "Dimensões do reprodutor:", "LabelParentNumber": "Número pai:", @@ -1326,5 +1322,100 @@ "ClientSettings": "Configurações do Cliente", "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux se aproximar da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais útil ao assistir sem procurar com frequência. Desative isso se você tiver problemas de reprodução.", "MySubtitles": "Minhas legendas", - "Name": "Nome" + "Name": "Nome", + "Never": "Nunca", + "Artist": "Artista", + "LabelDeinterlaceMethod": "Método de desentrelaçamento:", + "DeinterlaceMethodHelp": "Selecione o método de desentrelaçamento para converter conteúdo entrelaçado.", + "Movie": "Filme", + "LabelLibraryPageSize": "Tamanho da página da biblioteca:", + "Album": "Álbum", + "LabelLibraryPageSizeHelp": "Escolher a quantidade itens a exibir numa página de biblioteca. Escolha \"0\" para desabilitar a exibição em páginas.", + "Episode": "Episódio", + "OptionRequirePerfectSubtitleMatch": "Baixar apenas legendas que correspondem corretamente aos arquivos de vídeo", + "OptionRandom": "Aleatório", + "OptionPoster": "Encarte", + "OptionLoginAttemptsBeforeLockout": "Determinar a quantidade de tentativas de login incorretas até que ocorra bloqueio.", + "OptionList": "Listar", + "OptionIsSD": "Definição padrão", + "OptionIsHD": "Alta definição", + "OptionHomeVideos": "Fotos", + "OptionHasTrailer": "Trailer", + "OptionEnableExternalContentInSuggestions": "Habilitar sugestão de conteúdo externo", + "OptionDownloadMenuImage": "Menu", + "OptionDownloadLogoImage": "Logotipo", + "OptionDownloadBannerImage": "Encarte", + "OptionDisplayFolderViewHelp": "Exiba pastas ao lado de outras bibliotecas de mídia. Isso pode ser útil se você quiser ter uma visualização simples de pastas.", + "OptionDisplayFolderView": "Exibir uma exibição de pasta para mostrar pastas de mídia simples", + "OptionBluray": "Bluray", + "OptionBanner": "Poster", + "OptionAuto": "Automático", + "OptionAllowVideoPlaybackRemuxing": "Permitir execução de vídeo que requer conversão sem recodificar", + "OptionAllowLinkSharingHelp": "Somente páginas da web que contêm informações sobre mídia são compartilhadas. Os arquivos de mídia nunca são compartilhados publicamente. O tempo de compartilhamento é limitado e expira após {0} dias.", + "Option3D": "3D", + "NextUp": "Próximo", + "Next": "Próximo", + "NewEpisodesOnly": "apenas novos episódios", + "NewEpisodes": "Novos episódios", + "NewCollectionHelp": "Coleções permitem criar agrupamentos personalizados de filmes ou de outros conteúdos da biblioteca.", + "BoxSet": "Coleção", + "AlbumArtist": "Álbum do Artista", + "Quality": "Qualidade", + "Previous": "Anterior", + "PreferredNotRequired": "Preferível, mas não obrigatório", + "PictureInPicture": "vídeo destacado", + "OptionThumb": "Miniatura", + "OptionRequirePerfectSubtitleMatchHelp": "Solicitar a \"correspondência perfeita\" filtrará as legendas incluindo apenas aquelas que foram testadas com o arquivo de vídeo. Desmarcar isto aumentará a probabilidade de baixar legendas, mas poderá obter legendas incorretas ou não sincronizadas.", + "StopRecording": "Parar gravação", + "ShowYear": "Exibir ano", + "ShowTitle": "Exibir título", + "SettingsWarning": "Mudar estes valores pode causar instabilidade ou falhas de conexão. Se tiver problemas, recomendamos restaurar os valores originais.", + "ServerNameIsShuttingDown": "Servidor Jellyfin - {0} está desligando.", + "ServerNameIsRestarting": "Servidor Jellyfin - {0} está reiniciando.", + "SeriesYearToPresent": "{0} - Atualmente", + "SeriesCancelled": "Série cancelada.", + "SelectAdminUsername": "Por favor, selecione um usuário para a conta de administrador.", + "Season": "Temporada", + "RepeatEpisodes": "Repetir episódios", + "RepeatAll": "Repetir todos", + "RemoveFromCollection": "Remover da coleção", + "RememberMe": "Lembrar-me", + "ReleaseDate": "Data do lançamento", + "RefreshMetadata": "Atualizar metadados", + "RecentlyWatched": "Visto recentemente", + "OptionEnableForAllTuners": "Ativar para todos os dispositivos sintonizadores", + "OptionCaptionInfoExSamsung": "Informações da legenda (Samsung)", + "OptionBlockTrailers": "trechos de um filme", + "OptionAutomaticallyGroupSeriesHelp": "Se ativada, as séries espalhadas por várias pastas nesta biblioteca serão automaticamente mescladas em uma única série.", + "OptionAutomaticallyGroupSeries": "Mesclar automaticamente séries que estão espalhadas por várias pastas", + "OptionAllowSyncTranscoding": "Permitir download e sincronização de mídia que requeiram transcodificação", + "OptionForceRemoteSourceTranscoding": "Forçar a transcodificação de fontes de mídia remota (como LiveTV)", + "MessageUnauthorizedUser": "Você não está autorizado a acessar o servidor no momento. Entre em contato com o administrador do servidor para obter mais informações.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incorporados sobre nomes de arquivos", + "OptionSaveMetadataAsHiddenHelp": "Alterar isso será aplicado aos novos metadados salvos daqui para frente. Os arquivos de metadados existentes serão atualizados na próxima vez em que forem salvos pelo Jellyfin Server.", + "OptionRegex": "Regex", + "OptionLoginAttemptsBeforeLockoutHelp": "Um valor zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Definir como -1 desativará o recurso.", + "OptionExtractChapterImage": "Ativar extração de imagem de capítulo", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações de episódios incorporados sobre nomes de arquivos", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso usa as informações do episódio dos metadados incorporados, se disponíveis.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Isso determina o título quando nenhum metadado da Internet ou local está disponível.", + "PlaybackErrorNoCompatibleStream": "Este cliente não é compatível com a mídia e o servidor não está enviando um formato de mídia compatível.", + "Person": "Pessoa", + "OtherArtist": "Outro artista", + "OptionThumbCard": "Cartão de polegar", + "OptionPosterCard": "Cartão de pôster", + "LabelRequireHttpsHelp": "Se marcado, o servidor redirecionará automaticamente todas as solicitações por HTTP para HTTPS. Isso não terá efeito se o servidor não estiver escutando HTTPS.", + "LabelRequireHttps": "Requer HTTPS", + "LabelNightly": "À noite", + "LabelChromecastVersion": "Versão do Chromecast", + "LabelEnableHttpsHelp": "Permite que o servidor escute na postagem HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttps": "Ativar HTTPS", + "HeaderServerAddressSettings": "Configurações de endereço do servidor", + "HeaderRemoteAccessSettings": "Configurações de acesso remoto", + "HeaderHttpsSettings": "Configurações HTTPS", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lista das chaves de API ativadas no momento", + "ButtonTogglePlaylist": "Lista de leitura", + "ButtonToggleContextMenu": "Mais", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/ro.json b/src/strings/ro.json index 427ccff4853..c1ef5caf1a5 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -229,7 +229,7 @@ "AllLibraries": "Toate librăriile", "AllLanguages": "Toate limbile", "AllEpisodes": "Toate episoadele", - "AllComplexFormats": "Toate formatele complexe (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Toate formatele complexe (ASS, SSA, VOBSUB, PGS, SUB, IDX)", "AllChannels": "Toate canalele", "Alerts": "Alerte", "Albums": "Albume", @@ -249,14 +249,14 @@ "ButtonStop": "Stop", "ButtonSubmit": "Trimite", "Collections": "Colecții", - "AllowRemoteAccess": "Permite conexiuni externe către serverul Jellyfin.", - "AllowRemoteAccessHelp": "Dacă este nebifat, toate conexiunile externe vor fi blocate.", - "AlwaysPlaySubtitles": "Întotdeauna folosește subtitrări", - "AnyLanguage": "Orice limbă", + "AllowRemoteAccess": "Permite conexiuni externe către acest server Jellyfin.", + "AllowRemoteAccessHelp": "Dacă este neselectat, toate conexiunile externe vor fi blocate.", + "AlwaysPlaySubtitles": "Întotdeauna arată", + "AnyLanguage": "Orice Limbă", "Anytime": "Oricând", "Art": "Artă", "AlwaysPlaySubtitlesHelp": "Subtitrările care se potrivesc cu preferințele limbii utilizate vor fi încărcate indiferent de limba audio.", - "AroundTime": "Împrejur {0}", + "AroundTime": "Împrejur", "AsManyAsPossible": "Cât mai mulți cu putință", "Ascending": "Ascendent", "AspectRatio": "Raportul aspectului", @@ -288,7 +288,7 @@ "Director": "Regizor", "AllowOnTheFlySubtitleExtractionHelp": "Subtitrările încorporate pot fi extrase din video și transmise către client în mod text pentru a preveni transcodarea videoului. Pe unele sisteme acest lucru poate dura mult timp și poate cauza oprirea redării video în timpul procesului de extragere. Dezactivează opțiunea pentru a avea subtitrările încorporate incluse în videoul transcodat atunci când nu sunt nativ suportate de către dispozitivul client.", "BirthLocation": "Locul nașterii", - "BurnSubtitlesHelp": "Determină dacă serverul ar trebui să includă subtitrări când face conversia video depinzând de formatul subtitrărilor. Evitând includerea subtitrărilor va îmbunătăți performanța serverului. Selectează Auto pentru includerea formaturilor bazate pe imagini (VOBSUB, PGS, SUB/IDX, etc) și anumitor subtitrări ASS/SSA.", + "BurnSubtitlesHelp": "Determină dacă serverul ar trebui să includă subtitrări când face transcodarea video. Evitând acest lucru va îmbunătăți performanța serverului. Selectează Auto pentru includerea formaturilor bazate pe imagini (VOBSUB, PGS, SUB, IDX, ...) și anumitor subtitrări ASS sau SSA.", "ButtonPreviousTrack": "Calea anterioară", "ButtonRevoke": "Revocă", "ButtonSettings": "Setări", @@ -304,14 +304,14 @@ "Delete": "Șterge", "DeleteImage": "Șterge Imaginea", "DeleteUserConfirmation": "Sigur doriți să ștergeți acest utilizator?", - "Depressed": "Depresat", + "Depressed": "Deprimat", "Descending": "Descendent", "DetectingDevices": "Detectez dispozitive", "DirectPlaying": "Redare directă", "DirectStreamHelp2": "Transmiterea directă a unui fișier utilizează foarte puțină putere de procesare fără pierderi în calitatea video.", "DirectStreaming": "Transmitere directă", "Artists": "Artiști", - "BookLibraryHelp": "Cărți audio și text sunt suportate. Verifică {0}ghidul numirii cărților{1}.", + "BookLibraryHelp": "Cărți audio și text sunt suportate. Verifică {0} ghidul numirii cărților{1}.", "ButtonAddImage": "Adaugă Imagine", "ButtonArrowUp": "Sus", "ButtonAudioTracks": "Cale Audio", @@ -493,7 +493,6 @@ "HeaderRemoveMediaLocation": "Eliminați locația Media", "HeaderResponseProfile": "Profilul de răspuns", "HeaderRestart": "Repornire", - "HeaderRestartingServer": "Se repornește serverul", "HeaderRevisionHistory": "Istoricul reviziilor", "HeaderRunningTasks": "În desfășurare", "HeaderScenes": "Scene", @@ -511,7 +510,7 @@ "GuestStar": "Vedeta invitata", "GuideProviderSelectListings": "Selectați Listări", "EncoderPresetHelp": "Alegeți o valoare mai rapidă pentru a îmbunătăți performanța sau o valoare mai lentă pentru a îmbunătăți calitatea.", - "HardwareAccelerationWarning": "Activarea accelerării hardware poate provoca instabilitate în anumite medii. Asigurați-vă că sistemul de operare și driverele video sunt complet actualizate. Dacă întâmpinați dificultăți pentru a reda video după activarea acestei opțiuni, va trebui să schimbați setarea la Nimic.", + "HardwareAccelerationWarning": "Activarea accelerării hardware poate provoca instabilitate în anumite medii. Asigurați-vă că sistemul de operare și driverele video sunt complet actualizate. Dacă întâmpinați dificultăți pentru a reda video după activarea acestei opțiuni, va trebui să schimbați setarea la inapoi la Nimic.", "HeaderAccessSchedule": "Program de Acces", "HeaderAccessScheduleHelp": "Creați un program de acces pentru a limita accesul la anumite ore.", "HeaderActiveDevices": "Dispozitive active", @@ -588,7 +587,7 @@ "DisplayInOtherHomeScreenSections": "Afișați în secțiuni ecranul principal, cum ar fi cele mai noi suporturi și continuați să vizionați", "DisplayMissingEpisodesWithinSeasons": "Afișați episoade lipsă din sezoane", "DisplayMissingEpisodesWithinSeasonsHelp": "Acesta trebuie de asemenea activat pentru bibliotecile TV din configurația serverului.", - "DisplayModeHelp": "Selectați tipul de ecran pe care executați Jellyfin.", + "DisplayModeHelp": "Selectați stilul schemei pe care îl doriți pentru interfață.", "Download": "Descarcă", "DrmChannelsNotImported": "Canalele cu DRM nu vor fi importate.", "DropShadow": "Umbra", @@ -823,7 +822,7 @@ "LabelFailed": "Eșuat", "LabelExtractChaptersDuringLibraryScanHelp": "Generați imagini de capitol atunci când videoclipurile sunt importate în timpul scanării bibliotecii. În caz contrar, acestea vor fi extrase în timpul sarcinii programate de extragere a imaginilor capitolului, permițând scanarea bibliotecă obișnuită să se completeze mai rapid.", "LabelExtractChaptersDuringLibraryScan": "Extrageți imagini de capitol în timpul scanării bibliotecii", - "LabelBaseUrlHelp": "Puteți adăuga aici un subdirector personalizat pentru a accesa serverul de pe o adresă URL mai unică.", + "LabelBaseUrlHelp": "Adaugă aici un subdirector personalizat la URL-ul serverului. De exemplu: http://example.com/<baseurl>", "LabelBaseUrl": "Adresa URL de bază:", "LabelEveryXMinutes": "La fiecare:", "LabelEvent": "Eveniment:", @@ -932,7 +931,7 @@ "Items": "Articole", "ItemCount": "{0} articole", "InstantMix": "Mix instant", - "InstallingPackage": "Instalare {0}", + "InstallingPackage": "Instalare {0} (versiune {1})", "ImportFavoriteChannelsHelp": "Dacă este activat, vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", "Images": "Imagini", "Identify": "Identifică", @@ -1000,16 +999,16 @@ "OptionAlbum": "Album", "OptionAdminUsers": "Administratorii", "Option3D": "3D", - "OnlyImageFormats": "Numai formate de imagine (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Numai formate de imagine (VOBSUB, PGS, SUB)", "OnlyForcedSubtitlesHelp": "Se vor încărca doar subtitrările marcate drept forțate.", - "OnlyForcedSubtitles": "Numai subtitrări forțate", + "OnlyForcedSubtitles": "Numai forțate", "OneChannel": "Un canal", "Off": "Oprit", "NumLocationsValue": "{0} dosare", "Normal": "Normal", "None": "Nici unul", "NoSubtitlesHelp": "Subtitrările nu vor fi încărcate în mod implicit. Acestea pot fi însă activate manual în timpul redării.", - "NoSubtitles": "Fără subtitrare", + "NoSubtitles": "Fără", "NoSubtitleSearchResultsFound": "Nici un rezultat găsit.", "NoPluginConfigurationMessage": "Acest plugin nu are setări de configurat.", "NoNewDevicesFound": "Nu s-au găsit dispozitive noi. Pentru a adăuga un nou tuner, închideți acest dialog și introduceți informațiile dispozitivului manual.", @@ -1049,7 +1048,7 @@ "MessagePleaseWait": "Te rog așteaptă. Poate dura un minut.", "MessagePlayAccessRestricted": "Redarea acestui conținut este în prezent restricționată. Vă rugăm să contactați administratorul serverului pentru mai multe informații.", "MessagePasswordResetForUsers": "Următorii utilizatori au resetat parolele. Acum se pot conecta cu codurile pin care au fost utilizate pentru a efectua resetarea.", - "MessageNoTrailersFound": "Nu s-au găsit trailere. Instalați canalul Trailer pentru a îmbunătăți experiența dvs. de film adăugând o bibliotecă de trailere din internet.", + "MessageNoTrailersFound": "Instalați canalul Trailer pentru a îmbunătăți experiența dvs. de film adăugând o bibliotecă de trailere din internet.", "MessageNoServersAvailable": "Nu au fost găsite servere folosind descoperirea automată de servere.", "MessageNoPluginsInstalled": "Nu aveți plugin-uri instalate.", "MessageNoMovieSuggestionsAvailable": "În prezent, nu există sugestii de film. Începeți să vizionați și să evaluați filmele, apoi reveniți pentru a vedea recomandările dvs.", @@ -1227,7 +1226,6 @@ "RunAtStartup": "Rulați la pornire", "Rewind": "Derulează", "ResumeAt": "Reluați de la {0}", - "RestartPleaseWaitMessage": "Vă rugăm să așteptați cât Jellyfin Server se închide și repornește. Acest lucru poate dura un minut sau două.", "RequiredForAllRemoteConnections": "Obligatoriu pentru toate conexiunile distante", "ReplaceExistingImages": "Înlocuiți toate imaginile", "ReplaceAllMetadata": "Înlocuiți toate metadatele", @@ -1238,7 +1236,7 @@ "Repeat": "Repetă", "RemoveFromPlaylist": "Scoateți din lista de redare", "RemoveFromCollection": "Scoateți din colecție", - "RememberMe": "Ține-mă minte", + "RememberMe": "Ține-mă Minte", "ReleaseDate": "Data lansării", "RefreshQueued": "Actualizare adăugată în coadă.", "RefreshMetadata": "Actualizați metadatele", @@ -1295,9 +1293,9 @@ "PasswordResetConfirmation": "Sigur doriți să resetați parola?", "PasswordResetComplete": "Parola a fost resetată.", "PasswordMatchError": "Confirmarea parolei și parola trebuie să corespundă.", - "PackageInstallFailed": "{0} instalare eșuată.", - "PackageInstallCompleted": "{0} instalare efectuată.", - "PackageInstallCancelled": "{0} instalarea a fost anulată.", + "PackageInstallFailed": "{0} (versiune{1}) instalare eșuată.", + "PackageInstallCompleted": "{0} (versiune {1}) instalare efectuată.", + "PackageInstallCancelled": "{0} (versiune {1}) instalarea a fost anulată.", "Overview": "Prezentare generală", "OriginalAirDateValue": "Data inițială de emisie: {0}", "OptionWeekly": "Săptămânal", @@ -1334,7 +1332,7 @@ "OptionOnInterval": "La un interval", "OptionOnAppStartup": "La pornirea aplicației", "OptionNone": "Nici unul", - "OptionNew": "Nou...", + "OptionNew": "Nou…", "OptionMissingEpisode": "Episoade lipsă", "OptionMax": "Max", "OptionLoginAttemptsBeforeLockoutHelp": "O valoare zero înseamnă că va moșteni valoarea implicită de trei încercări pentru utilizatorii normali și cinci pentru administratori. Setarea acestei opțiuni la -1 va dezactiva funcția.", @@ -1359,9 +1357,9 @@ "OptionEnableExternalContentInSuggestions": "Activați conținut extern în sugestii", "OptionEmbedSubtitles": "Inclus în container", "OptionDownloadLogoImage": "Siglă", - "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații din Jellyfin. Activați această opțiune pentru a descărca în prealabil toate imaginile, în timp ce fișierele media noi sunt importate. Acest lucru poate provoca scanări ale bibliotecii semnificativ mai lungi.", + "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații din Jellyfin. Activați această opțiune pentru a descărca în prealabil toate imaginile, pe măsură ce fișierele media sunt importate. Acest lucru poate provoca scanări ale bibliotecii semnificativ mai lungi.", "OptionDownloadImagesInAdvance": "Descărcați imaginile în avans", - "OptionDownloadDiscImage": "Placă", + "OptionDownloadDiscImage": "Disc", "OptionDisplayFolderViewHelp": "Afișați dosarele alături de celelalte biblioteci media. Acest lucru poate fi util dacă doriți să aveți o vizualizare direct în dosar.", "OptionDisplayFolderView": "Afișați o vizualizare de dosar pentru a afișa dosarele media simple", "OptionDateAddedImportTime": "Utilizați data scanării în bibliotecă", @@ -1454,8 +1452,6 @@ "HeaderNavigation": "Navigare", "MessageConfirmAppExit": "Vrei să ieși?", "CopyStreamURLError": "A apărut o eroare la copierea adresei URL.", - "EnableFastImageFadeInHelp": "Activați animația mai rapidă de tranziție pentru imaginile încărcate", - "EnableFastImageFadeIn": "Tranziție a imaginii rapidă", "LabelVideoResolution": "Rezoluția video:", "LabelStreamType": "Tipul streamului:", "LabelPlayerDimensions": "Dimensiunile soft redare:", @@ -1464,7 +1460,113 @@ "OptionForceRemoteSourceTranscoding": "Forțați transcodarea surselor media distante (cum ar fi LiveTV)", "NoCreatedLibraries": "Se pare că nu ați creat încă biblioteci. {0} Doriți să creați una acum? {1}", "AskAdminToCreateLibrary": "Cereți unui administrator să creeze o bibliotecă.", - "PlaybackErrorNoCompatibleStream": "A apărut o problemă cu profilarea clientului, iar serverul nu trimite un format media compatibil.", - "AllowFfmpegThrottlingHelp": "Când un transcod sau un remux se află destul de departe înainte de poziția actuală de redare, întrerupeți procesul, astfel încât să consume mai puține resurse. Acest lucru este cel mai util când priviți fără a derula des. Dezactivați acestă opțiune dacă întâmpinați probleme de redare.", - "AllowFfmpegThrottling": "Accelerare Transcod-uri" + "PlaybackErrorNoCompatibleStream": "Clientul nu este compatibil cu formatul media, iar serverul nu trimite un format media compatibil.", + "AllowFfmpegThrottlingHelp": "Când un transcod sau un remux se află destul de departe înainte de poziția actuală de redare, întrerupeți procesul, astfel încât să consume mai puține resurse. Acest lucru este util atunci când priviți fără a derula des. Dezactivați acestă opțiune dacă întâmpinați probleme de redare.", + "AllowFfmpegThrottling": "Limitare Transcod-uri", + "Track": "Cale", + "Season": "Sezon", + "ReleaseGroup": "Gruparea lansării", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferați informația despre episod încorporată în fișier decât numele fișierelor", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Aceasta folosește informația despre episod din metadatele încorporate, dacă sunt disponibile.", + "Person": "Persoană", + "OtherArtist": "Alt artist", + "Movie": "Film", + "Episode": "Episod", + "ClientSettings": "Setări pentru client", + "BoxSet": "Set de colecție", + "Artist": "Artist", + "AlbumArtist": "Artistul albumului", + "Album": "Album", + "OnApplicationStartup": "La pornirea aplicației", + "EveryXHours": "La fiecare {0} ore", + "EveryHour": "În fiecare oră", + "EveryXMinutes": "La fiecare {0} minute", + "OnWakeFromSleep": "La trezire din somn", + "WeeklyAt": "{0} la {1}", + "DailyAt": "Zilnic la {0}", + "LastSeen": "Văzut ultima dată {0}", + "PersonRole": "ca {0}", + "ListPaging": "{0}-{1} din {2}", + "WriteAccessRequired": "Jellyfin Server necesită acces de scriere la acest folder. Vă rugăm să vă asigurați accesul la scriere și încercați din nou.", + "PathNotFound": "Calea nu a fost găsită. Vă rugăm să vă asigurați de validitatea căii și încercați din nou.", + "YadifBob": "Gigi Bob", + "Yadif": "YADIF", + "LabelDeinterlaceMethod": "Metoda de intercalare:", + "DeinterlaceMethodHelp": "Selectați metoda de intercalat pe care să o utilizați la transcodarea conținutului intercalat.", + "UnsupportedPlayback": "Jellyfin nu poate decripta conținut protejat de DRM, dar tot conținutul va fi încercat indiferent de titlurile protejate. Unele fișiere pot părea complet negre din cauza criptării sau a altor funcții neacceptate, cum ar fi titluri interactive.", + "LabelLibraryPageSizeHelp": "Setează cantitatea de elemente de afișat pe o pagină a bibliotecii. Setați la 0 pentru a dezactiva paginarea.", + "LabelLibraryPageSize": "Mărimea paginii Bibliotecă:", + "MessageUnauthorizedUser": "Nu sunteți autorizat să accesați serverul în acest moment. Vă rugăm să contactați administratorul serverului pentru mai multe informații.", + "ButtonTogglePlaylist": "Listă de redare", + "ButtonToggleContextMenu": "Mai mult", + "Filter": "Filtru", + "New": "Nou", + "HeaderFavoritePlaylists": "Listă Favorită", + "ApiKeysCaption": "Lista cheilor API active", + "LabelRequireHttpsHelp": "Dacă e selectat, serverul va redirecta automat toate cererile HTTP către HTTPS. Dacă nu se ascultă pe HTTPS, nu are niciun efect.", + "LabelRequireHttps": "Trebuie HTTPS", + "LabelNightly": "Ultimă", + "LabelStable": "Stabilă", + "LabelChromecastVersion": "Versiunea de Chromecast", + "LabelEnableHttpsHelp": "Activează serverul să asculte pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", + "LabelEnableHttps": "Activați HTTPS", + "HeaderServerAddressSettings": "Setările adresei serverului", + "HeaderRemoteAccessSettings": "Setări pentru aces remote", + "HeaderHttpsSettings": "Setări https", + "TabDVR": "DVR", + "SaveChanges": "Salvează modificările", + "HeaderDVR": "DVR", + "SyncPlayAccessHelp": "Selectați nivelul de acces pe care îl are acest utilizator la funcția SyncPlay. SyncPlay permite sincronizarea redării cu alte dispozitive.", + "MessageSyncPlayErrorMedia": "Eroare la activarea SyncPlay! Eroare media.", + "MessageSyncPlayErrorMissingSession": "Eroare la activarea SyncPlay! Sesiune lipsă.", + "MessageSyncPlayErrorNoActivePlayer": "Nu a fost găsit niciun soft de redare activ. SyncPlay a fost dezactivat.", + "MessageSyncPlayErrorAccessingGroups": "A apărut o eroare la accesarea listei de grupuri.", + "MessageSyncPlayLibraryAccessDenied": "Accesul la acest conținut este restricționat.", + "MessageSyncPlayJoinGroupDenied": "Permisiune necesară pentru a utiliza SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permisiune necesară pentru crearea unui grup.", + "MessageSyncPlayGroupDoesNotExist": "Nu a reușit să se alăture grupului, deoarece nu există.", + "MessageSyncPlayPlaybackPermissionRequired": "Este necesară permisiunea de redare.", + "MessageSyncPlayNoGroupsAvailable": "Nu există grupuri disponibile. Începe să redai ceva mai întâi.", + "MessageSyncPlayGroupWait": "{0} se încarcă...", + "MessageSyncPlayUserLeft": "{0} a părăsit grupul.", + "MessageSyncPlayUserJoined": "{0} s-a alăturat grupului.", + "MessageSyncPlayDisabled": "SyncPlay dezactivat.", + "MessageSyncPlayEnabled": "SyncPlay activat.", + "LabelSyncPlayAccess": "Acces SyncPlay", + "LabelSyncPlayAccessNone": "Dezactivat pentru acest utilizator", + "LabelSyncPlayAccessJoinGroups": "Permiteți utilizatorului să se alăture grupurilor", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permiteți utilizatorului să creeze și să se alăture grupurilor", + "LabelSyncPlayLeaveGroupDescription": "Dezactivează SyncPlay", + "LabelSyncPlayLeaveGroup": "Parăsește grup", + "LabelSyncPlayNewGroupDescription": "Crează un grup nou", + "LabelSyncPlayNewGroup": "Grup nou", + "LabelSyncPlaySyncMethod": "Metoda de sincronizare:", + "LabelSyncPlayPlaybackDiff": "Diferența de timp de redare:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Decalare de timp cu serverul:", + "HeaderSyncPlayEnabled": "SyncPlay activat", + "HeaderSyncPlaySelectGroup": "Alăturați-vă unui grup", + "EnableDetailsBannerHelp": "Afișați o imagine de bandou în partea de sus a paginii cu detalii ale articolului.", + "EnableDetailsBanner": "Detalii Bandou", + "EnableBlurhashHelp": "Imaginile care sunt în curs de încărcare vor fi afișate cu un marcaj întinat", + "EnableBlurhash": "Activați marcatoarele întinate pentru imagini", + "ShowMore": "Arată mai mult", + "ShowLess": "Arată mai puțin", + "ButtonCast": "Proiectează", + "ButtonSyncPlay": "SyncPlay", + "MessageNoGenresAvailable": "Permiteți unor furnizori de metadate să tragă genul media de pe internet.", + "EnableFasterAnimationsHelp": "Utilizați animații și tranziții mai rapide", + "EnableFasterAnimations": "Animații mai rapide", + "EnableDecodingColorDepth10Vp9": "Activați decodarea hardware pe 10 biți pentru VP9", + "EnableDecodingColorDepth10Hevc": "Activați decodarea hardware pe 10 biți pentru HEVC", + "LabelRepositoryName": "Nume Repertoriu", + "LabelRepositoryUrlHelp": "Locația manifestului repertoriului pe care doriți să o includeți.", + "LabelRepositoryUrl": "URL Repertoriu", + "HeaderNewRepository": "Repertoriu Nou", + "MessageNoRepositories": "Fără repertoriu.", + "ButtonPlayer": "Redare", + "Writers": "Scriitori", + "TabRepositories": "Repertorii", + "MessageAddRepository": "Dacă doriți să adăugați un repertoriu, faceți clic pe butonul de lângă antet și completați informațiile solicitate.", + "LabelRepositoryNameHelp": "Un nume personalizat pentru a distinge acest repertoriu de altele adăugate la serverul dvs." } diff --git a/src/strings/ru.json b/src/strings/ru.json index d50bcaccaa5..9212ee4fb48 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -3,19 +3,19 @@ "AccessRestrictedTryAgainLater": "В настоящее время доступ запрещён. Повторите попытку позже.", "Actor": "Актёр", "Add": "Добавить", - "AddItemToCollectionHelp": "Добавляйте элементы в коллекции проведя их поиск, и с помощью правого щелчка по ним или по касанию меню, чтобы присоединить ко коллекции.", + "AddItemToCollectionHelp": "Добавляйте элементы в коллекции, выполняя их поиск, и с помощью правой кнопки мыши или касания меню присоедините их к коллекции.", "AddToCollection": "Добавить в коллекцию", "AddToPlayQueue": "Добавить в очередь воспроизведения", "AddToPlaylist": "Добавить в плей-лист", "AddedOnValue": "Добавлено {0}", - "AdditionalNotificationServices": "Просмотрите каталог плагинов, чтобы установить дополнительные услуги для уведомлений.", + "AdditionalNotificationServices": "Просмотрите каталог плагинов, чтобы установить дополнительные сервисы уведомлений.", "AirDate": "Дата эфира", "Aired": "Эфирный", "Albums": "Альбомы", "Alerts": "Оповещения", "All": "Все", "AllChannels": "Все каналы", - "AllComplexFormats": "Все комлексные форматы (ASS, SSA, VOBSUB, PGS, SUB/IDX и т.д.)", + "AllComplexFormats": "Все комплексные форматы (ASS, SSA, VOBSUB, PGS, SUB, IDX и др.)", "AllEpisodes": "Все эпизоды", "AllLanguages": "Все языки", "AllLibraries": "Все медиатеки", @@ -23,38 +23,38 @@ "AllowMediaConversion": "Разрешить преобразование медиаданных", "AllowMediaConversionHelp": "Предоставить или запретить доступ к компоненте преобразования медиаданных.", "AllowOnTheFlySubtitleExtraction": "Разрешить динамическое извлечение субтитров", - "AllowOnTheFlySubtitleExtractionHelp": "Внедрённые субтитры возможно извлекать из видео и доставлять клиентам в виде обычного текста, в целях предотвращения перекодировки видео. На некоторых системах это может занять продолжительное время и вызывать задержки воспроизведения видео в процессе извлечения. Отключите это, для прошивки внедрённых субтитров во время перекодировки видео, при отсутствии встроенной поддержки их в клиентском устройстве.", - "AllowRemoteAccess": "Разрешение удалённого доступа к данному серверу Jellyfin Server.", + "AllowOnTheFlySubtitleExtractionHelp": "Встроенные субтитры могут быть извлечены из видео и доставлены клиентам в виде обычного текста, в целях предотвращения перекодировки видео. На некоторых системах это может занять продолжительное время и вызвать задержки воспроизведения видео в процессе извлечения. Отключите этот параметр, чтобы встроенные субтитры записывались при перекодировании видео, если они изначально не поддерживаются клиентским устройством.", + "AllowRemoteAccess": "Разрешить удалённый доступ к данному серверу Jellyfin Server.", "AllowRemoteAccessHelp": "Если флажок снят, то все удалённые подключения будут заблокированы.", - "AllowedRemoteAddressesHelp": "Список разделённых запятыми IP-адресов или записей IP/netmask для сетей, которым разрешено удалённое соединение. Если не заполнять, то будут использованы все внешние адреса.", - "AlwaysPlaySubtitles": "Всегда воспроизводить с субтитрами", + "AllowedRemoteAddressesHelp": "Список разделённых запятыми IP-адресов или записей IP/netmask сетей, которым разрешено удалённое соединение. Если оставить это поле пустым, то будут разрешены все удаленные адреса.", + "AlwaysPlaySubtitles": "Воспроизводить всегда", "AlwaysPlaySubtitlesHelp": "Субтитры, соответствующие настройке языка, будут загружаться независимо от языка аудио.", "AnyLanguage": "Любой язык", "Anytime": "В любое время", - "AroundTime": "Около {0}", + "AroundTime": "Около", "Art": "Виньетка", "Artists": "Исполнители", "AsManyAsPossible": "Как можно больше", "Ascending": "По возрастанию", - "AspectRatio": "Соот-ие сторон", + "AspectRatio": "Соотношение сторон", "AttributeNew": "Новинка", "Audio": "Аудио", "Auto": "Авто", "AutoBasedOnLanguageSetting": "Авто (на основе настройки языка)", - "Backdrop": "Задник", - "Backdrops": "Задники", + "Backdrop": "Фон", + "Backdrops": "Фоны", "Banner": "Баннер", "BirthDateValue": "Дата рождения: {0}", "BirthLocation": "Место рождения", "BirthPlaceValue": "Место рождения: {0}", "Blacklist": "Чёрный список", - "BookLibraryHelp": "Поддерживаются аудио и текстовые книги. Просмотрите {0}руководство по именованию книг{1}.", + "BookLibraryHelp": "Поддерживаются аудио и текстовые книги. Просмотрите {0} руководство по именованию книг {1}.", "Books": "Книги", "Box": "Коробка", - "BoxRear": "Спинка коробки", + "BoxRear": "Коробка (задняя часть)", "Browse": "Навигация", "BrowsePluginCatalogMessage": "Просмотрите каталог плагинов, чтобы ознакомиться с имеющимися плагинами.", - "BurnSubtitlesHelp": "Определяется, должен ли сервер внедрять субтитры при преобразовании видео в зависимости от формата субтитров. Избегание внедрения субтитров улучшит производительность сервера. Выберите «Авто» для записи основанных на графике форматов (VOBSUB, PGS, SUB/IDX и т.п.) и некоторых субтитров ASS/SSA.", + "BurnSubtitlesHelp": "Определяется, должен ли сервер внедрять субтитры при перекодировании. Избежание этого значительно улучшит производительность. Выберите «Авто» для записи основанных на графике форматов (VOBSUB, PGS, SUB, IDX и др.) и некоторых субтитров ASS или SSA.", "ButtonAdd": "Добавить", "ButtonAddMediaLibrary": "Добавить медиатеку", "ButtonAddScheduledTaskTrigger": "Добавить триггер", @@ -70,12 +70,12 @@ "ButtonChangeServer": "Сменить сервер", "ButtonConnect": "Подсоединиться", "ButtonDelete": "Удалить", - "ButtonDeleteImage": "Удалить рисунок", + "ButtonDeleteImage": "Удалить изображение", "ButtonDown": "Вниз", "ButtonDownload": "Загрузить", "ButtonEdit": "Править", - "ButtonEditImages": "Править рисунки", - "ButtonEditOtherUserPreferences": "Править профиль, рисунок и личные настройки этого пользователя.", + "ButtonEditImages": "Править изображения", + "ButtonEditOtherUserPreferences": "Править профиль, изображение и персональные настройки этого пользователя.", "ButtonFilter": "Фильтр", "ButtonForgotPassword": "Напомнить пароль", "ButtonFullscreen": "Полный экран", @@ -83,21 +83,21 @@ "ButtonGuide": "Телегид", "ButtonHelp": "Справка", "ButtonHome": "Главное", - "ButtonInfo": "Инфо...", + "ButtonInfo": "Инфо", "ButtonLearnMore": "Подробнее", "ButtonLibraryAccess": "Доступ к медиатеке", "ButtonManualLogin": "Войти вручную", "ButtonMore": "Ещё", "ButtonNetwork": "Сеть", "ButtonNew": "Новое", - "ButtonNextTrack": "След. дорожка", + "ButtonNextTrack": "Следующая дорожка", "ButtonOff": "Откл", "ButtonOk": "Ок", "ButtonOpen": "Открыть", - "ButtonParentalControl": "Управлять содержанием", + "ButtonParentalControl": "Родительский контроль", "ButtonPause": "Пауза", - "ButtonPlay": "Воспр.", - "ButtonPreviousTrack": "Пред. дорожка", + "ButtonPlay": "Воспроизведение", + "ButtonPreviousTrack": "Предыдущая дорожка", "ButtonProfile": "Профиль", "ButtonQuickStartGuide": "Руководство по запуску", "ButtonRefresh": "Обновить", @@ -127,6 +127,8 @@ "ButtonStop": "Остановить", "ButtonSubmit": "Подтвердить", "ButtonSubtitles": "Субтитры", + "ButtonToggleContextMenu": "Ещё", + "ButtonTogglePlaylist": "Плей-лист", "ButtonTrailer": "Трейлер", "ButtonUninstall": "Удалить", "ButtonUp": "Вверх", @@ -147,27 +149,27 @@ "CommunityRating": "Общественная оценка", "Composer": "Композитор", "ConfigureDateAdded": "Как конфигурировать дату добавления определяется в Панели Jellyfin Server в параметрах Медиатеки", - "ConfirmDeleteImage": "Удалить рисунок?", + "ConfirmDeleteImage": "Удалить изображение?", "ConfirmDeleteItem": "При удалении данного элемента, он удалится и из файловой системы, и из медиатеки. Вы действительно хотите продолжить?", "ConfirmDeleteItems": "При удалении данных элементов, он удалится и из файловой системы, и из медиатеки. Вы действительно хотите продолжить?", "ConfirmDeletion": "Подтверждение удаления", "ConfirmEndPlayerSession": "Вы хотите завершить работу Jellyfin на {0}?", - "Connect": "Подсоединиться", + "Connect": "Соединиться", "ContinueWatching": "Продолжение просмотра", "Continuing": "Продолжающееся", "CriticRating": "Оценка критиков", "CustomDlnaProfilesHelp": "Создайте настраиваемый профиль, назначаемый для нового устройства или переопределите системный профиль.", "DateAdded": "Дата добавления", "DatePlayed": "Дата воспроизведения", - "DeathDateValue": "Кончина: {0}", - "Default": "Умолчание", + "DeathDateValue": "Дата смерти: {0}", + "Default": "По умолчанию", "DefaultErrorMessage": "Произошла ошибка при обработке запроса. Повторите попытку позже.", "DefaultMetadataLangaugeDescription": "Они являются значениями по умолчанию и могут быть подстроены индивидуально для каждой медиатеки.", "DefaultSubtitlesHelp": "Загрузки субтитров определяются флагами \"По умолчанию\" и \"Форсированные\" во внедрённых метаданных. Языковые настройки учитываются при наличии нескольких опций.", "Delete": "Удалить", "DeleteDeviceConfirmation": "Вы действительно хотите удалить данное устройство? Оно появится снова в следующий раз, когда пользователь войдёт с него.", - "DeleteImage": "Удалить рисунок", - "DeleteImageConfirmation": "Вы действительно хотите удалить данный рисунок?", + "DeleteImage": "Удалить изображение", + "DeleteImageConfirmation": "Вы действительно хотите удалить данное изображение?", "DeleteMedia": "Удалить медиаданные", "DeleteUser": "Удалить пользователя", "DeleteUserConfirmation": "Вы действительно хотите удалить этого пользователя?", @@ -191,7 +193,7 @@ "DisplayInOtherHomeScreenSections": "Показывать в разделах главного экрана (нпр., новейшие медиаданные, продолжение просмотра и т.п.)", "DisplayMissingEpisodesWithinSeasons": "Отображать отсутствующие эпизоды в пределах сезонов", "DisplayMissingEpisodesWithinSeasonsHelp": "Это также должно быть включено для ТВ-медиатек в конфигурации сервера.", - "DisplayModeHelp": "Выберите тип экрана, где запущен Jellyfin.", + "DisplayModeHelp": "Выберите желательный стиль разметки для интерфейса.", "DoNotRecord": "Не записывать", "Down": "Вниз", "Download": "Загрузить", @@ -200,11 +202,11 @@ "DropShadow": "Теневая", "EasyPasswordHelp": "Простой PIN-код используется для автономного доступа на поддерживаемых клиентах и может также использоваться для удобного внутрисетевого входа.", "Edit": "Правка", - "EditImages": "Править рисунки", + "EditImages": "Править изображения", "EditMetadata": "Править метаданные", "EditSubtitles": "Править субтитры", - "EnableBackdrops": "Задники", - "EnableBackdropsHelp": "Задники будут отображаются фоном на некоторых страницах при просмотре медиатеки.", + "EnableBackdrops": "Фоны", + "EnableBackdropsHelp": "Фоны будут отображаются на заднем плане на некоторых страницах при просмотре медиатеки.", "EnableCinemaMode": "Режим кинозала", "EnableColorCodedBackgrounds": "Обозначеннные цветом фоны", "EnableDisplayMirroring": "Дублирование отображения", @@ -214,8 +216,8 @@ "EnableNextVideoInfoOverlay": "Показывать сведения о следующем видео во время воспроизведения", "EnableNextVideoInfoOverlayHelp": "В конце видео отображать информацию о последующем видео в текущем плей-листе.", "EnablePhotos": "Отображать фотографии", - "EnablePhotosHelp": "Рисунки будут обнаруживаться и отображаться наряду с другими медиафайлами.", - "EnableStreamLooping": "Автоциклирование трансляций", + "EnablePhotosHelp": "Изображения будут обнаруживаться и отображаться наряду с другими медиафайлами.", + "EnableStreamLooping": "Зацикливание трансляций", "EnableStreamLoopingHelp": "Включайте, если трансляции содержат данные только на несколько секунд и необходимо непрерывно их запрашивать. Включение этого без необходимости может породить проблемы.", "EnableThemeSongs": "Тематические композиции", "EnableThemeSongsHelp": "Воспроизведение тематических композиций в фоновом режиме при навигации по медиатеке.", @@ -236,7 +238,7 @@ "EveryNDays": "Каждые {0} дней", "ExitFullscreen": "Выход с полного экрана", "ExtraLarge": "Очень крупный", - "ExtractChapterImagesHelp": "Извлечение рисунков сцен предоставляет возможности клиентам для отображения графических меню выбора сцены. Данный процесс может быть медленным, потребляет ресурсы, и могут понадобиться несколько гигабайт пространства. Он работает при обнаружении видеофайлов, а также, как задача, назначенная на ночь. Расписание возможно перенастроить в области Назначенных задач. Не рекомендуется запускать данную задачу в часы пик.", + "ExtractChapterImagesHelp": "Извлечение изображений сцен предоставляет возможности клиентам для отображения графических меню выбора сцены. Данный процесс может быть медленным, потребляет ресурсы, и могут понадобиться несколько гигабайт пространства. Он работает при обнаружении видеофайлов, а также, как задача, назначенная на ночь. Расписание возможно перенастроить в области Назначенных задач. Не рекомендуется запускать данную задачу в часы пик.", "Extras": "Допматериалы", "FFmpegSavePathNotFound": "Мы не смогли обнаружить FFmpeg по введённому вами пути. FFprobe также необходим и должен быть в той же самой папке. Эти компоненты обычно поставляются вместе в одном загрузочном пакете. Проверьте путь и повторите попытку.", "FastForward": "Быстро вперёд", @@ -263,11 +265,11 @@ "Genres": "Жанры", "GroupBySeries": "Группирование по сериалам", "GroupVersions": "Сгруппировать версии", - "GuestStar": "Пригл. актёр", + "GuestStar": "Приглашенный актёр", "Guide": "Телегид", "GuideProviderLogin": "Вход", "GuideProviderSelectListings": "Выбор перечней", - "H264CrfHelp": "Постоянное значение оценки (Constant Rate Factor, CRF) - параметр качества по умолчанию для кодёра x264. Возможно задавать значения от 0 до 51, где меньшие значения привели бы к улучшению качества (за счёт бо́льших размеров файлов). Разумными являются значения от 18 до 28. Стандартно для x264 - 23, так что вы можете использовать это в качестве отправной точки.", + "H264CrfHelp": "Постоянное значение оценки (Constant Rate Factor, CRF) - параметр качества по умолчанию для кодёра x264. Возможно задавать значения от 0 до 51, где меньшие значения привели бы к улучшению качества (за счёт увеличения размеров файлов). Приемлемыми являются значения от 18 до 28. Стандартно для x264 - 23, так что вы можете использовать это в качестве отправной точки.", "EncoderPresetHelp": "Выберите значение быстрее для улучшения производительности, или значение медленнее для улучшения качества.", "HDPrograms": "HD-передачи", "HandledByProxy": "Обрабатывается обратным прокси", @@ -280,7 +282,7 @@ "HeaderAddScheduledTaskTrigger": "Добавление триггера", "HeaderAddToCollection": "Добавить в коллекцию", "HeaderAddToPlaylist": "Добавление в плей-лист", - "HeaderAddUpdateImage": "Добавление/Обновление рисунка", + "HeaderAddUpdateImage": "Добавление/Обновление изображения", "HeaderAddUser": "Добавить пользователя", "HeaderAdditionalParts": "Дополнительные части", "HeaderAdmin": "Администрирование", @@ -305,7 +307,7 @@ "HeaderCastCrew": "Снимались и снимали", "HeaderChannelAccess": "Доступ ко каналам", "HeaderChannels": "Каналы", - "HeaderChapterImages": "Рисунки сцен", + "HeaderChapterImages": "Изображения сцен", "HeaderCodecProfile": "Профиль кодеков", "HeaderCodecProfileHelp": "Профили кодеков обозначают ограничения устройства при воспроизведении с определёнными кодеками. Если применяется ограничение, то медиаданные перекодируются, даже если кодек настроен для прямого воспроизведения.", "HeaderConfigureRemoteAccess": "Конфигурирование удалённого доступа", @@ -335,7 +337,7 @@ "HeaderDisplay": "Отображение", "HeaderDownloadSync": "Загрузка и синхро", "HeaderEasyPinCode": "Простой PIN-код", - "HeaderEditImages": "Править рисунки", + "HeaderEditImages": "Править изображения", "HeaderEnabledFields": "Включённые поля", "HeaderEnabledFieldsHelp": "Снимите флажок, чтобы зафиксировать поле и защитить его данные от изменнений.", "HeaderEpisodes": "Эпизоды", @@ -343,7 +345,7 @@ "HeaderExternalIds": "Внешние идентификаторы:", "HeaderFeatureAccess": "Доступ к компонентам", "HeaderFeatures": "Материалы", - "HeaderFetchImages": "Отборка рисунков:", + "HeaderFetchImages": "Отборка изображений:", "HeaderFetcherSettings": "Параметры отборщика", "HeaderFilters": "Фильтры", "HeaderForKids": "Детям", @@ -356,8 +358,8 @@ "HeaderIdentificationCriteriaHelp": "Введите хотя бы одно условие распознания.", "HeaderIdentificationHeader": "Заголовок для распознания", "HeaderIdentifyItemHelp": "Введите одно или несколько условий поиска. Изымите условие, чтобы прирастить найденные результаты.", - "HeaderImageOptions": "Опции рисунка", - "HeaderImageSettings": "Параметры рисунков", + "HeaderImageOptions": "Параметры изображения", + "HeaderImageSettings": "Настройки изображения", "HeaderInstall": "Установка", "HeaderInstantMix": "Автомикс", "HeaderItems": "Элементы", @@ -404,7 +406,7 @@ "HeaderPeople": "Люди", "HeaderPhotoAlbums": "Фотоальбомы", "HeaderPinCodeReset": "Сброс PIN-кода", - "HeaderPlayAll": "Воспр. все", + "HeaderPlayAll": "Воспроизвести все", "HeaderPlayOn": "Воспроизведение", "HeaderPlayback": "Воспроизведение медиаданных", "HeaderPlaybackError": "Ошибка воспроизведения", @@ -467,10 +469,10 @@ "HeaderTranscodingProfileHelp": "Добавьте профили перекодировки, чтобы указать, какие форматы следует использовать, когда требуется перекодировка.", "HeaderTunerDevices": "Тюнерные устройства", "HeaderTuners": "Тюнеры", - "HeaderTypeImageFetchers": "{0} отборщики рисунков", + "HeaderTypeImageFetchers": "{0} отборщики изображений", "HeaderTypeText": "Ввод текста", "HeaderUpcomingOnTV": "Ожидаемое на ТВ", - "HeaderUploadImage": "Выкладка рисунка", + "HeaderUploadImage": "Загрузка изображения", "HeaderUser": "Пользователь", "HeaderUsers": "Пользователи", "HeaderVideoQuality": "Качество видео", @@ -489,10 +491,10 @@ "Horizontal": "Горизонтально", "HttpsRequiresCert": "Чтобы включить HTTPS для внешних подключений, вам нужно будет предоставить доверенный SSL-cертификат, например, Let's Encrypt. Предоставьте сертификат или отключите защищенные соединения.", "Identify": "Распознать", - "Images": "Рисунки", + "Images": "Изображения", "ImportFavoriteChannelsHelp": "При включении, будут импортированы только каналы, которые обозначены как избранное на тюнерном устройстве.", "ImportMissingEpisodesHelp": "При включении, информация об отсутствующих эпизодах будет импортирована в вашу базу данных Jellyfin и отображаться в пределах сезонов и сериалов. Это может увеличить время сканирования медиатеки.", - "InstallingPackage": "Устанавливается {0}", + "InstallingPackage": "Устанавливается {0} (версия {1})", "InstantMix": "Автомикс", "ItemCount": "{0} элемент(а/ов)", "Items": "Элементы", @@ -508,7 +510,7 @@ "LabelAirsBeforeEpisode": "Эпизод airs_before:", "LabelAirsBeforeSeason": "Сезон airs_before:", "LabelAlbum": "Альбом:", - "LabelAlbumArtHelp": "PN используемое для альбомных обложек, внутри атрибута dlna:profileID при upnp:albumArtURI. Некоторым устройствам требуется специфическое значение, вне зависимости от размера рисунка.", + "LabelAlbumArtHelp": "PN используемое для альбомных обложек, внутри атрибута dlna:profileID при upnp:albumArtURI. Некоторым устройствам требуется специфическое значение, вне зависимости от размера изображения.", "LabelAlbumArtMaxHeight": "Макс. высота облома альбома:", "LabelAlbumArtMaxHeightHelp": "Максимальное разрешение обложек альбома, представляемых с помощью upnp:albumArtURI.", "LabelAlbumArtMaxWidth": "Макс. ширина обложки альбома:", @@ -538,7 +540,7 @@ "LabelBurnSubtitles": "Внедрение субтитров:", "LabelCache": "Кэш:", "LabelCachePath": "Путь к кешу:", - "LabelCachePathHelp": "Укажите произвольное расположение для файлов серверного кэша, например, рисунков. Оставьте поле незаполненным, чтобы использовать значение по умолчанию.", + "LabelCachePathHelp": "Укажите произвольное расположение для файлов серверного кэша, например, изображений. Оставьте поле незаполненным, чтобы использовать значение по умолчанию.", "LabelCancelled": "Отменено", "LabelCertificatePassword": "Пароль сертификата:", "LabelCertificatePasswordHelp": "Если для вашего сертификата требуется пароль, то введите его здесь.", @@ -579,13 +581,13 @@ "LabelDownMixAudioScale": "Коэффициент усиления при понижающем микшировании:", "LabelDownMixAudioScaleHelp": "Коэффициент компенсирующего усиления звука при понижающем до стерео микшировании. Значение 1 сохраняет исходный уровень.", "LabelDownloadLanguages": "Загружаемые языки:", - "LabelDropImageHere": "Перетащите рисунок сюда или щёлкните для навигации.", + "LabelDropImageHere": "Перетащите изображение сюда или щёлкните для навигации.", "LabelDropShadow": "Окантовка:", "LabelEasyPinCode": "Простой PIN-код:", "LabelEmbedAlbumArtDidl": "Внедрять альбомные обложки в DIDL", "LabelEmbedAlbumArtDidlHelp": "Для некоторых устройств данный метод получения альбомных обложек является предпочтительным. Остальные могут быть не в состоянии воспроизводить, при включении данной опции.", "LabelEnableAutomaticPortMap": "Включить автоматическое сопоставление портов", - "LabelEnableAutomaticPortMapHelp": "Попытаться автоматически сопоставить публичный порт с локальным портом с помощью UPnP. Это может не работать с некоторыми моделями маршрутизаторов. Изменения не применяются до перезапуска сервера.", + "LabelEnableAutomaticPortMapHelp": "Автоматическое перенаправление публичных портов маршрутизатора на локальные порты сервера через UPnP. Это может не работать с некоторыми моделями маршрутизаторов или сетевых конфигураций. Изменения не применяются до перезапуска сервера.", "LabelEnableBlastAliveMessages": "Бомбардировать сообщениями проверки активности", "LabelEnableBlastAliveMessagesHelp": "Включите, если сервер надёжно не обнаруживается иными UPnP устройствами в своей сети.", "LabelEnableDlnaClientDiscoveryInterval": "Интервал обнаружения клиентов", @@ -599,14 +601,14 @@ "LabelEnableHardwareDecodingFor": "Включить аппаратное декодирование для:", "LabelEnableRealtimeMonitor": "Включить отслеживание в реальном времени", "LabelEnableRealtimeMonitorHelp": "В поддерживаемых файловых системах правки файлов будут обрабатываться незамедлительно.", - "LabelEnableSingleImageInDidlLimit": "Лимитировать до единственного внедрённого рисунка", - "LabelEnableSingleImageInDidlLimitHelp": "На некоторых устройствах не отрисовывается нормально, если внедрены несколько рисунков внутри DIDL.", + "LabelEnableSingleImageInDidlLimit": "Ограничить единственным встроенным изображением", + "LabelEnableSingleImageInDidlLimitHelp": "На некоторых устройствах не отрисовывается нормально, если встроено несколько изображений внутри DIDL.", "LabelEndDate": "Конечная дата:", "LabelEpisodeNumber": "Номер эпизода:", "LabelEvent": "Событие:", "LabelEveryXMinutes": "Каждые:", - "LabelExtractChaptersDuringLibraryScan": "Извлекать рисунки сцен в процессе сканирования медиатеки", - "LabelExtractChaptersDuringLibraryScanHelp": "Генерируются рисунки сцен при импорте видео в процессе сканирования медиатеки. В противном случае, они будут извлечены в процессе назначенной задачи «Рисунки сцен», позволяя регулярному сканированию медиатеки завершаться быстрее.", + "LabelExtractChaptersDuringLibraryScan": "Извлекать изображения сцен в процессе сканирования медиатеки", + "LabelExtractChaptersDuringLibraryScanHelp": "Генерируются изображения сцен при импорте видео в процессе сканирования медиатеки. В противном случае, они будут извлечены в процессе назначенной задачи «Изображения сцен», позволяя регулярному сканированию медиатеки завершаться быстрее.", "LabelFailed": "Неудачно", "LabelFileOrUrl": "Файл или URL:", "LabelFinish": "Завершить", @@ -630,8 +632,8 @@ "LabelIconMaxWidth": "Макс. ширина значка:", "LabelIconMaxWidthHelp": "Максимальное разрешение значков представляемых с помощью upnp:icon.", "LabelIdentificationFieldHelp": "Подстрока без учёта регистра, либо регулярное выражение.", - "LabelImageFetchersHelp": "Включите и ранжируйте предпочитаемые отборщики рисунков в порядке приоритета.", - "LabelImageType": "Тип рисунка:", + "LabelImageFetchersHelp": "Включите и ранжируйте предпочитаемые отборщики изображений в порядке приоритета.", + "LabelImageType": "Тип изображения:", "LabelImportOnlyFavoriteChannels": "Ограничиваться каналами обозначенными как избранное", "LabelInNetworkSignInWithEasyPassword": "Включить внутрисетевой вход со своим простым PIN-кодом", "LabelInNetworkSignInWithEasyPasswordHelp": "Используется простой PIN-код для входа в клиенты внутри своей локальной сети. Ваш обычный пароль будет необходим только вне дома. Если PIN-код не заполнен, то внутри своей домашней сети не потребуется пароль.", @@ -641,11 +643,11 @@ "LabelKodiMetadataDateFormat": "Формат даты выпуска:", "LabelKodiMetadataDateFormatHelp": "Все даты в пределах NFO-файлов будут разбираться по данному формату.", "LabelKodiMetadataEnableExtraThumbs": "Копировать extrafanart в поле extrathumbs", - "LabelKodiMetadataEnableExtraThumbsHelp": "При загрузке рисунков, их возможно сохранять внутрь extrafanart и extrathumbs для максимальной совместимости с оболочкой Kodi.", + "LabelKodiMetadataEnableExtraThumbsHelp": "Загружаемые изображения могут быть сохранены внутри полей extrafanart и extrathumbs для максимальной совместимости с оболочкой Kodi.", "LabelKodiMetadataEnablePathSubstitution": "Включить подстановки путей", - "LabelKodiMetadataEnablePathSubstitutionHelp": "Включаются подстановки путей к рисункам с помощью параметров подстановки путей сервера.", - "LabelKodiMetadataSaveImagePaths": "Сохранять пути рисунков в пределах NFO-файлов", - "LabelKodiMetadataSaveImagePathsHelp": "Рекомендуется, если имена файлов рисунков не соответствуют руководящим принципам Kodi.", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Включаются подстановки путей к изображениям с помощью параметров подстановки путей сервера.", + "LabelKodiMetadataSaveImagePaths": "Сохранять пути изображений в пределах NFO-файлов", + "LabelKodiMetadataSaveImagePathsHelp": "Рекомендуется, если имена файлов изображений не соответствуют руководящим принципам Kodi.", "LabelKodiMetadataUser": "Сохранение в NFO-файле данных о просмотре пользователем:", "LabelKodiMetadataUserHelp": "Сохраняет данные о просмотрах в NFO-файлах для использования в других приложениях.", "LabelLanNetworks": "Домашние сети:", @@ -660,14 +662,14 @@ "LabelManufacturer": "Производитель:", "LabelManufacturerUrl": "URL производителя", "LabelMatchType": "Тип соответствия:", - "LabelMaxBackdropsPerItem": "Макс. число задников на элемент:", + "LabelMaxBackdropsPerItem": "Максимальное число фонов на элемент:", "LabelMaxChromecastBitrate": "Качество трансляции Chromecast:", "LabelMaxParentalRating": "Макс. допустимая возрастная категория:", "LabelMaxResumePercentage": "Макс. доля для возобновления, %:", "LabelMaxResumePercentageHelp": "Произведения предполагаются воспроизведёнными полностью, при остановке с данного момента.", "LabelMaxScreenshotsPerItem": "Макс. число снимков экрана на элемент:", "LabelMaxStreamingBitrate": "Макс. качество трансляции:", - "LabelMaxStreamingBitrateHelp": "Укажите макс. потоковую скорость трансляции.", + "LabelMaxStreamingBitrateHelp": "Укажите максимальный битрейт трансляции.", "LabelMessageText": "Текст сообщения:", "LabelMessageTitle": "Заголовок сообщения:", "LabelMetadata": "Метаданные:", @@ -680,12 +682,12 @@ "LabelMetadataSavers": "Хранители метаданных:", "LabelMetadataSaversHelp": "Выберите форматы файлов, куда будут сохраняться метаданные.", "LabelMethod": "Метод:", - "LabelMinBackdropDownloadWidth": "Мин. ширина загружаемого задника:", - "LabelMinResumeDuration": "Мин. длительность для возобновления:", + "LabelMinBackdropDownloadWidth": "Минимальная ширина загружаемого фона:", + "LabelMinResumeDuration": "Минимальная длительность для возобновления:", "LabelMinResumeDurationHelp": "Наименьшая длительность видео в секундах, при которой сохраняется позиция воспроизведения и позволяется возобновление.", - "LabelMinResumePercentage": "Мин. доля для возобновления, %:", + "LabelMinResumePercentage": "Минимальная доля для возобновления, %:", "LabelMinResumePercentageHelp": "Произведения предполагаются не воспроизведёнными, при остановке до данного момента.", - "LabelMinScreenshotDownloadWidth": "Мин. ширина загружаемого снимка экрана:", + "LabelMinScreenshotDownloadWidth": "Минимальная ширина загружаемого снимка экрана:", "LabelModelDescription": "Описание модели", "LabelModelName": "Наименование модели", "LabelModelNumber": "Номер модели", @@ -695,8 +697,8 @@ "LabelMoviePrefix": "Префикс фильма:", "LabelMoviePrefixHelp": "При применении к названиям фильмов префикса, введите его здесь, чтобы он правильно обрабатывался на сервере.", "LabelMovieRecordingPath": "Путь к записываемым фильмам (необязательно):", - "LabelMusicStreamingTranscodingBitrate": "Поток. скорость перекодировки музыки:", - "LabelMusicStreamingTranscodingBitrateHelp": "Укажите максимальную потоковую скорость при трансляции музыки.", + "LabelMusicStreamingTranscodingBitrate": "Битрейт перекодировки музыки:", + "LabelMusicStreamingTranscodingBitrateHelp": "Укажите максимальный битрейт при трансляции музыки.", "LabelName": "Имя:", "LabelNewName": "Новое название:", "LabelNewPassword": "Новый пароль:", @@ -708,8 +710,8 @@ "LabelNumberOfGuideDays": "Число дней для загрузки данных телегида:", "LabelNumberOfGuideDaysHelp": "Больше дней загрузки данных телегида обеспечивает возможность заблаговременно назначать расписание и просматривать больше перечней, однако это займёт больше времени для загрузки. При значении «Авто» выбор определяется числом каналов.", "LabelOptionalNetworkPath": "(Необязательно) Общедоступная сетевая папка:", - "LabelOptionalNetworkPathHelp": "Если данная папка общедоступна в своей сети, предоставление пути к сетевой папке может позволить Jellyfin-приложениям на других устройствах получить прямой доступ к медиафайлам.", - "LabelOriginalAspectRatio": "Исходное соот-ие сторон:", + "LabelOptionalNetworkPathHelp": "Если данная папка является общей в сети, указание пути к сетевой папке может позволить Jellyfin-приложениям на других устройствах иметь прямой доступ к медиафайлам. Например, {0} или {1}.", + "LabelOriginalAspectRatio": "Исходное соотношение сторон:", "LabelOriginalTitle": "Оригинальное название:", "LabelOverview": "Обзор:", "LabelParentNumber": "Родительский номер:", @@ -750,8 +752,8 @@ "LabelRecordingPathHelp": "Укажите стандартное расположение для сохранения записей. Если поле пусто, то используется папка program data сервера.", "LabelRefreshMode": "Режим обновления:", "LabelReleaseDate": "Дата выпуска:", - "LabelRemoteClientBitrateLimit": "Предел потоковой скорости интернет-трансляции, Мбит/с:", - "LabelRemoteClientBitrateLimitHelp": "Необязательный предел скорости на поток для каждого из сетевых устройств. Это целесообразно, чтобы не допускать запрашивание устройствами более высокой скорости, чем способно пропустить интернет-соединение. Это может привести к увеличению загрузки процессора на вашем сервере, при динамическом перекодировании видео до более низкой скорости.", + "LabelRemoteClientBitrateLimit": "Ограничение битрейта интернет-трансляции, Мбит/с:", + "LabelRemoteClientBitrateLimitHelp": "Необязательное ограничение битрейта для каждого из сетевых устройств. Может потребоваться, чтобы не допускать использования устройствами большего битрейта, чем способно пропустить интернет-соединение. Может привести к росту загрузки процессора на вашем сервере, так как потребуется динамическое перекодирование видео для снижения битрейта.", "LabelRuntimeMinutes": "Длительность, мин:", "LabelSaveLocalMetadata": "Сохранять иллюстрации внутри медиапапок", "LabelSaveLocalMetadataHelp": "При сохранении иллюстраций внутри медиапапок, те помещаются в месте, где их можно легко править.", @@ -822,7 +824,7 @@ "LabelUserAgent": "Агент пользователя:", "LabelUserLibrary": "Медиатека пользователя:", "LabelUserLibraryHelp": "Выберите, чью медиатеку отображать на устройстве. Не заполняйте, чтобы наследовать параметр по умолчанию.", - "LabelUserRemoteClientBitrateLimitHelp": "Переопределияются глобальные значения по умолчанию, установленные в параметрах воспроизведения сервера.", + "LabelUserRemoteClientBitrateLimitHelp": "Переопределяются глобальные значения по умолчанию, установленные в параметрах воспроизведения сервера.", "LabelUsername": "Имя пользователя:", "LabelVaapiDevice": "Устройство VA-API:", "LabelVaapiDeviceHelp": "Это является узлом отрисовки, который используется для аппаратного ускорения.", @@ -833,7 +835,7 @@ "LabelVideo": "Видео", "LabelXDlnaCap": "Свойства X-Dlna:", "LabelXDlnaCapHelp": "Определяется содержание из элемента X_DLNACAP во пространстве имён urn:schemas-dlna-org:device-1-0.", - "LabelXDlnaDoc": "Схема X-Dlna:", + "LabelXDlnaDoc": "Схема X-DLNA:", "LabelXDlnaDocHelp": "Определяется содержание из элемента X_DLNADOC во пространстве имён urn:schemas-dlna-org:device-1-0.", "LabelYear": "Год:", "LabelYourFirstName": "Ваше имя:", @@ -856,18 +858,18 @@ "ManageLibrary": "Управление медиатекой", "ManageRecording": "Управлять записью", "MapChannels": "Сопоставить каналы", - "MarkPlayed": "Отметить как воспр-ое", - "MarkUnplayed": "Отметить как невоспр-ое", + "MarkPlayed": "Отметить как воспроизведенное", + "MarkUnplayed": "Отметить как невоспроизведенное", "MaxParentalRatingHelp": "Содержание с более высокой возр. категорией будет скрыто от этого пользователя.", "MediaInfoAnamorphic": "Анаморфность", - "MediaInfoAspectRatio": "Соот-ие сторон", + "MediaInfoAspectRatio": "Соотношение сторон", "MediaInfoBitDepth": "Глубина цвета", - "MediaInfoBitrate": "Поток. ск-ть", + "MediaInfoBitrate": "Битрейт", "MediaInfoChannels": "Каналы", "MediaInfoCodec": "Кодек", "MediaInfoCodecTag": "Тег кодека", "MediaInfoContainer": "Контейнер", - "MediaInfoDefault": "Умолчание", + "MediaInfoDefault": "По умолчанию", "MediaInfoExternal": "Внешние", "MediaInfoForced": "Форсир-ые", "MediaInfoFramerate": "Ч-та кадров", @@ -880,7 +882,7 @@ "MediaInfoProfile": "Профиль", "MediaInfoRefFrames": "Опорные кадры", "MediaInfoResolution": "Разрешение", - "MediaInfoSampleRate": "Ч-та дискр-ии", + "MediaInfoSampleRate": "Частота дискретизации", "MediaInfoSize": "Размер", "MediaInfoTimestamp": "Метка времени", "MediaIsBeingConverted": "Медиаданные преобразуются в формат, совместимый с устройством, которое воспроизводит эти медиаданные.", @@ -917,7 +919,7 @@ "MessageNoAvailablePlugins": "Плагинов не имеется.", "MessageNoMovieSuggestionsAvailable": "В настоящее время предложений фильмов не имеются. Начните смотреть и оценивать свои фильмы, а затем вернитесь, чтобы просмотреть рекомендации.", "MessageNoPluginsInstalled": "Нет установленных плагинов.", - "MessageNoTrailersFound": "Трейлеры не найдены. Установите канал трейлеров, чтобы повысить своё впечатление от фильма путём добавления собрания интернет-трейлеров.", + "MessageNoTrailersFound": "Установите канал трейлеров, чтобы повысить своё впечатление от фильма путём добавления собрания интернет-трейлеров.", "MessageNothingHere": "Здесь ничего нет.", "MessagePasswordResetForUsers": "Следующие пользователи сбросили свои пароли. Теперь они могут войти с помощью PIN-кодов, которые использовались для сброса.", "MessagePlayAccessRestricted": "Воспроизведение данного содержания в настоящее время ограничено. За дополнительными сведениями обратитесь к администратору сервера.", @@ -961,16 +963,16 @@ "NoNextUpItemsMessage": "Ничего не найдено. Начните смотреть свои ТВ-передачи!", "NoPluginConfigurationMessage": "В данном плагине нет параметров конфигурирования.", "NoSubtitleSearchResultsFound": "Результатов не найдено.", - "NoSubtitles": "Без субтитров", + "NoSubtitles": "Ничего", "NoSubtitlesHelp": "По умолчанию, субтитры не будут загружаться. Они могут быть все ещё включены вручную во время воспроизведения.", "None": "Ничего", "Normal": "Обычный", "NumLocationsValue": "{0} пап(ки/ок)", "Off": "Выкл", "OneChannel": "Один канал", - "OnlyForcedSubtitles": "Только форсированные субтитры", + "OnlyForcedSubtitles": "Только форсированные", "OnlyForcedSubtitlesHelp": "Загружены будут только форсированные субтитры.", - "OnlyImageFormats": "Только графические форматы (VOBSUB, PGS, SUB и т.д.)", + "OnlyImageFormats": "Только графические форматы (VOBSUB, PGS и SUB)", "OptionAdminUsers": "Администраторы", "OptionAlbum": "Альбом", "OptionAlbumArtist": "Исп. альбома", @@ -1003,7 +1005,7 @@ "OptionBlockMusic": "Музыка", "OptionBlockTrailers": "Трейлеры", "OptionBlockTvShows": "ТВ-передачи", - "OptionBluray": "BluRay", + "OptionBluray": "Blu-ray", "OptionCommunityRating": "Пользовательский рейтинг", "OptionContinuing": "Продолжающееся", "OptionCriticRating": "Оценка критиков", @@ -1020,12 +1022,12 @@ "OptionDisplayFolderView": "Отображать аспект Папки для просмотра обычных медиапапок", "OptionDisplayFolderViewHelp": "Отображение аспекта \"Папки\" рядом с другими вашими медиатеками. Это может быть полезно, если вы хотите вид обычных папок.", "OptionDownloadArtImage": "Виньетка", - "OptionDownloadBackImage": "Задник", + "OptionDownloadBackImage": "Фон", "OptionDownloadBannerImage": "Баннер", "OptionDownloadBoxImage": "DVD-бокс", "OptionDownloadDiscImage": "Диск", - "OptionDownloadImagesInAdvance": "Загружать рисунки заблаговременно", - "OptionDownloadImagesInAdvanceHelp": "По умолчанию, большинство рисунков загружаются только при запросе от Jellyfin-приложения. Включите данную опцию, чтобы загружать все рисунки заблаговременно, при импорте новых медиаданных. Это может привести к существенно длительным сканированиям медиатеки.", + "OptionDownloadImagesInAdvance": "Загружать изображения заблаговременно", + "OptionDownloadImagesInAdvanceHelp": "По умолчанию, большинство изображений загружаются только при запросе от Jellyfin-приложения. Включите данную опцию, чтобы загружать все изображения заблаговременно, при импорте новых медиаданных. Это может привести к существенно длительным сканированиям медиатеки.", "OptionDownloadLogoImage": "Логотип", "OptionDownloadMenuImage": "Меню", "OptionDownloadPrimaryImage": "Основной", @@ -1045,7 +1047,7 @@ "OptionEstimateContentLength": "Рассчитывать длину содержимого при перекодировке", "OptionEveryday": "Ежедневно", "OptionExternallyDownloaded": "Внешние загружаемые", - "OptionExtractChapterImage": "Включить извлечение рисунков сцен", + "OptionExtractChapterImage": "Включить извлечение изображений сцен", "OptionFavorite": "Избранное", "OptionFriday": "пятница", "OptionHasSpecialFeatures": "Доп. материалы", @@ -1065,7 +1067,7 @@ "OptionMissingEpisode": "Отсутствующие эпизоды", "OptionMonday": "понедельник", "OptionNameSort": "Название", - "OptionNew": "Новое...", + "OptionNew": "Новое…", "OptionNone": "Ничего", "OptionOnAppStartup": "При запуске приложения", "OptionOnInterval": "В интервале", @@ -1092,7 +1094,7 @@ "OptionResumable": "Возможно возобновление", "OptionRuntime": "Длительность", "OptionSaturday": "суббота", - "OptionSaveMetadataAsHidden": "Сохранять метаданные и рисунки в виде скрытых файлов", + "OptionSaveMetadataAsHidden": "Сохранять метаданные и изображения в виде скрытых файлов", "OptionSaveMetadataAsHiddenHelp": "Это изменение будет применено к новым метаданным сохраняемым в будущем. Существующие файлы метаданных будут обновлены в следующий раз, когда они будут сохраняться на Jellyfin Server.", "OptionSpecialEpisode": "Спецэпизоды", "OptionSubstring": "Подстрока", @@ -1110,10 +1112,10 @@ "OptionWeekly": "Еженедельно", "OriginalAirDateValue": "Дата исходного эфира: {0}", "Overview": "Обзор", - "PackageInstallCancelled": "Установка {0} отменена.", - "PackageInstallCompleted": "Установка {0} завершена.", - "PackageInstallFailed": "Установка {0} неудачна.", - "ParentalRating": "Возр. кат-ия", + "PackageInstallCancelled": "Установка {0} (версия {1}) отменена.", + "PackageInstallCompleted": "Установка {0} (версия {1}) завершена.", + "PackageInstallFailed": "Установка {0} (версия {1}) неудачна.", + "ParentalRating": "Возрастная категория", "PasswordMatchError": "Пароль и подтверждение пароля должны совпадать.", "PasswordResetComplete": "Пароль был сброшен.", "PasswordResetConfirmation": "Вы действительно хотите сбросить пароль?", @@ -1126,10 +1128,10 @@ "PinCodeResetComplete": "PIN-код был сброшен.", "PinCodeResetConfirmation": "Вы действительно хотите сбросить PIN-код?", "PlaceFavoriteChannelsAtBeginning": "Разместить избранные каналы в начале", - "Play": "Воспр.", - "PlayAllFromHere": "Воспр. все отсюда", + "Play": "Воспроизведение", + "PlayAllFromHere": "Воспроизвести все отсюда", "PlayCount": "Кол. воспроизведений", - "PlayFromBeginning": "Воспр. с начала", + "PlayFromBeginning": "Воспроизвести с начала", "PlayNext": "Воспроизвести следующее", "PlayNextEpisodeAutomatically": "Воспроизводить последующий эпизод автоматически", "Played": "Воспроизведено", @@ -1179,9 +1181,8 @@ "RepeatMode": "Режим повтора", "RepeatOne": "Повторить раз", "ReplaceAllMetadata": "Замена всех метаданных", - "ReplaceExistingImages": "Замена имеющихся рисунков", + "ReplaceExistingImages": "Замена имеющихся изображений", "RequiredForAllRemoteConnections": "Требуется для всех внешних подключений", - "RestartPleaseWaitMessage": "Подождите, пока Jellyfin Server выключится и перезапустится. Это может занять минуту или две.", "ResumeAt": "Возобновить с {0}", "Rewind": "Отмотать", "RunAtStartup": "Запускать при старте системы", @@ -1221,6 +1222,8 @@ "ShowYear": "Отображать год", "Shows": "Передачи", "Shuffle": "Перемешать", + "New": "Новинка", + "Filter": "Фильтр", "SimultaneousConnectionLimitHelp": "Максимальное количество разрешённых одновременных потоков. Введите 0, чтобы снять ограничения.", "SkipEpisodesAlreadyInMyLibrary": "Не записывать эпизоды, которые уже находятся в моей медиатеке", "SkipEpisodesAlreadyInMyLibraryHelp": "Эпизоды будут сравниваться с помощью номеров сезонов и эпизодов, когда они имеются.", @@ -1243,7 +1246,7 @@ "Subtitles": "Субтитры", "Suggestions": "Предлагаемое", "Sunday": "воскресенье", - "Sync": "Синхро", + "Sync": "Синхронизация", "SystemDlnaProfilesHelp": "Системные профили доступны только для чтения. Правки системного профиля будут сохранены в новом настраиваемом профиле.", "TV": "ТВ", "TabAccess": "Доступ", @@ -1258,7 +1261,7 @@ "TabContainers": "Контейнеры", "TabDashboard": "Панель", "TabDevices": "Устройства", - "TabDirectPlay": "Прямое воспр-ие", + "TabDirectPlay": "Прямое воспроизведение", "TabDisplay": "Отображение", "TabEpisodes": "Эпизоды", "TabFavorites": "Избранное", @@ -1346,7 +1349,7 @@ "ValueSeconds": "{0} сек", "ValueSeriesCount": "{0} сериал(а/ов)", "ValueSongCount": "{0} композици(и/й)", - "ValueSpecialEpisodeName": "Спецэпизод - {0}", + "ValueSpecialEpisodeName": "Специальный эпизод - {0}", "ValueTimeLimitMultiHour": "Временной лимит: {0} час(а/ов)", "ValueTimeLimitSingleHour": "Временной лимит: 1 час", "ValueVideoCodec": "Видео кодек: {0}", @@ -1373,7 +1376,7 @@ "LabelDynamicExternalId": "{0} Ид:", "LeaveBlankToNotSetAPassword": "Оставьте пустым, чтобы не назначать пароль.", "MessageImageFileTypeAllowed": "Поддерживаются только файлы JPEG и PNG.", - "MessageImageTypeNotSelected": "Выберите тип рисунка из выпадающего меню.", + "MessageImageTypeNotSelected": "Выберите тип изображения из выпадающего меню.", "OptionResElement": "res-элемент", "AuthProviderHelp": "Выберите поставщика аутентификации, который будет использоваться для аутентификации пароля этого пользователя.", "HeaderFavoriteMovies": "Избранные фильмы", @@ -1384,7 +1387,6 @@ "HeaderFavoriteSongs": "Избранные композиции", "HeaderFavoriteVideos": "Избранные видео", "HeaderHome": "Главное", - "HeaderRestartingServer": "Перезапустить сервер", "LabelAuthProvider": "Поставщик аутентификации:", "LabelPasswordResetProvider": "Поставщик сброса пароля:", "LabelServerName": "Имя сервера:", @@ -1401,7 +1403,7 @@ "MediaInfoSoftware": "ПО", "MediaInfoStreamTypeAudio": "Аудио", "MediaInfoStreamTypeData": "Данные", - "MediaInfoStreamTypeEmbeddedImage": "Внедрённый рисунок", + "MediaInfoStreamTypeEmbeddedImage": "Встроенное изображение", "MediaInfoStreamTypeSubtitle": "Субтитры", "MediaInfoStreamTypeVideo": "Видео", "MessageNoCollectionsAvailable": "Коллекции позволяют вам наслаждаться персонализированными группами фильмов, сериалов и альбомов. Нажмите кнопку +, чтобы начать создавать коллекции.", @@ -1419,36 +1421,36 @@ "OptionLoginAttemptsBeforeLockoutHelp": "При значении 0 наследуются по умолчанию три попытки для обычных пользователей и пять для администратора. При установке этого значения в -1 функция отключается.", "OptionPoster": "Постер", "OptionPosterCard": "Постер-карта", - "OptionThumb": "Бегунок", - "OptionThumbCard": "Бегунок-карта", + "OptionThumb": "Эскиз", + "OptionThumbCard": "Эскиз-карта", "PasswordResetProviderHelp": "Выберите поставщика сброса пароля, который будет использоваться, когда этот пользователь запрашивает сброс пароля", "PlaybackData": "Данные воспроизведения", "SubtitleOffset": "Сдвиг субтитров", "TabNetworking": "Работа в сети", - "LabelBaseUrlHelp": "Здесь вы можете добавить пользовательский подкаталог для доступа к серверу с более уникального URL.", + "LabelBaseUrlHelp": "Добавляется пользовательский подкаталог к URL сервера. Например: http://example.com/<baseurl>", "LabelPlayer": "Проигрыватель:", "MoreMediaInfo": "О медиаданных", "LabelVideoCodec": "Видео кодек:", - "LabelVideoBitrate": "Потоковая скорость аудио:", + "LabelVideoBitrate": "Битрейт видео:", "LabelTranscodingProgress": "Прогресс перекодировки:", "LabelTranscodingFramerate": "Частота кадров перекодировки:", "LabelSize": "Размер:", "LabelPlayMethod": "Метод воспроизведения:", "LabelFolder": "Папка:", "LabelBaseUrl": "Базовый URL:", - "LabelBitrate": "Поток. ск-ть:", + "LabelBitrate": "Битрейт:", "LabelAudioSampleRate": "Частота дискретизации аудио:", "LabelAudioCodec": "Аудио кодек:", "LabelAudioChannels": "Аудио каналы:", - "LabelAudioBitrate": "Потоковая скорость аудио:", + "LabelAudioBitrate": "Битрейт аудио:", "LabelAudioBitDepth": "Битовая глубина аудио:", "HeaderFavoriteBooks": "Избранные книги", "CopyStreamURL": "Копировать URL потока", "LabelPleaseRestart": "Изменения вступят в силу после перезагрузки веб-клиента вручную.", - "CopyStreamURLSuccess": "URL скопирован успешно.", + "CopyStreamURLSuccess": "URL скопирован успешно.", "MusicLibraryHelp": "Просмотрите {0}руководство по именованию музыки{1}.", "FetchingData": "Выборка дополнительных данных", - "ButtonAddImage": "Добавить рисунок", + "ButtonAddImage": "Добавить изображение", "HeaderFavoritePeople": "Избранные люди", "OptionRandom": "Случайный", "ButtonSplit": "Разделить", @@ -1456,8 +1458,6 @@ "HeaderNavigation": "Навигация", "LabelVideoResolution": "Разрешение видео:", "LabelStreamType": "Тип потока:", - "EnableFastImageFadeInHelp": "Включить быстрое появление анимации для загруженных рисунков", - "EnableFastImageFadeIn": "Быстрое появление рисунка", "LabelPlayerDimensions": "Размеры проигрывателя:", "LabelDroppedFrames": "Пропущенные кадры:", "LabelCorruptedFrames": "Испорченные кадры:", @@ -1466,6 +1466,111 @@ "NoCreatedLibraries": "Похоже, вы еще не создали ни одной медиатеки. {0}Желаете создать её сейчас?{1}", "AskAdminToCreateLibrary": "Попросите администратора создать медиатеку.", "AllowFfmpegThrottling": "Притормаживать перекодировку", - "PlaybackErrorNoCompatibleStream": "Возникла проблема с профилем клиента, и сервер не отправляет совместимый формат мультимедиа.", - "AllowFfmpegThrottlingHelp": "Когда перекодирование или переупаковка достаточно далеко опережают текущую позицию воспроизведения, процесс приостанавливается, так что он использует меньше ресурсов. Это наиболее полезно, когда вы редко меняете позиции в видео. Выключите это, если у вас возникли проблемы с воспроизведением." + "PlaybackErrorNoCompatibleStream": "Этот клиент несовместим с медиаданными, а сервер не отправляет медиаданные в совместимом формате.", + "AllowFfmpegThrottlingHelp": "Когда перекодирование или переупаковка достаточно далеко опережают текущую позицию воспроизведения, процесс приостанавливается, так что он использует меньше ресурсов. Это наиболее полезно, когда вы редко меняете позиции в видео. Выключите это, если у вас возникли проблемы с воспроизведением.", + "OnWakeFromSleep": "При пробуждении ото сна", + "YadifBob": "YADIF с удвоением", + "OnApplicationStartup": "При запуске приложения", + "EveryXHours": "Каждые {0} часов", + "EveryHour": "Каждый час", + "EveryXMinutes": "Каждые {0} мин", + "WeeklyAt": "{0} в {1}", + "DailyAt": "Ежедневно в {0}", + "PersonRole": "как {0}", + "ListPaging": "{0}-{1} из {2}", + "Yadif": "YADIF", + "Track": "Дорожка", + "Season": "Сезон", + "Person": "Персона", + "OtherArtist": "Другой исполнитель", + "Movie": "Фильм", + "LabelLibraryPageSize": "Размер страницы медиатеки:", + "Episode": "Эпизод", + "ClientSettings": "Параметры клиента", + "BoxSet": "Коллекция", + "Artist": "Исполнитель", + "AlbumArtist": "Исполнитель альбома", + "Album": "Альбом", + "LastSeen": "Последний раз был {0}", + "WriteAccessRequired": "Jellyfin Server требуются права на запись в эту папку. Обеспечьте доступ для записи и попробуйте снова.", + "PathNotFound": "Путь не может быть найден. Убедитесь, что путь правильный и попробуйте снова.", + "ReleaseGroup": "Релиз-группа", + "PreferEmbeddedEpisodeInfosOverFileNames": "Предпочитать встроенную информацию эпизода вместо имён файлов", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Используется информация об эпизоде из встроенных метаданных, если они доступны.", + "LabelLibraryPageSizeHelp": "Устанавливается количество элементов для отображения на странице медиатеки. Установите 0 для отключения нумерации страниц.", + "LabelDeinterlaceMethod": "Метод устранения гребёнки:", + "DeinterlaceMethodHelp": "Выберите метод устранения гребёнки, который будет использоваться при перекодировании чересстрочного содержания.", + "UnsupportedPlayback": "Jellyfin не может расшифровать содержимое, защищенное DRM, но в любом случае будет предпринята попытка расшифровки всего содержимого, включая защищенные заголовки. Некоторые файлы могут выглядеть полностью черными из-за шифрования или других неподдерживаемых функций, таких как интерактивные заголовки.", + "MessageUnauthorizedUser": "В настоящее время у вас нет доступа к серверу. Пожалуйста, свяжитесь с администратором сервера для получения дополнительной информации.", + "HeaderFavoritePlaylists": "Избранные плей-листы", + "LabelRequireHttpsHelp": "Если этот флажок установлен, сервер будет автоматически перенаправлять все запросы через HTTP на HTTPS. Это не имеет никакого эффекта, если сервер не слушает HTTPS.", + "LabelEnableHttpsHelp": "Позволяет серверу слушать HTTPS-порт. Для работы необходим действующий сертификат.", + "ApiKeysCaption": "Список действующих текущих API-ключей", + "TabDVR": "DVR", + "SaveChanges": "Сохранить изменения", + "LabelRequireHttps": "Требуется HTTPS", + "LabelNightly": "Ночная", + "LabelStable": "Стабильная", + "LabelChromecastVersion": "Версия Chromecast", + "LabelEnableHttps": "Включить HTTPS", + "HeaderServerAddressSettings": "Параметры адреса сервера", + "HeaderRemoteAccessSettings": "Параметры удалённого доступа", + "HeaderHttpsSettings": "Параметры HTTPS", + "HeaderDVR": "DVR", + "MessageSyncPlayJoinGroupDenied": "Требуется разрешение для использования SyncPlay.", + "MessageSyncPlayDisabled": "SyncPlay отключен.", + "MessageSyncPlayEnabled": "SyncPlay включён.", + "LabelSyncPlayAccess": "Доступ к SyncPlay", + "LabelSyncPlayLeaveGroupDescription": "Отключить SyncPlay", + "HeaderSyncPlayEnabled": "SyncPlay включён", + "HeaderSyncPlaySelectGroup": "Присоединить группу", + "EnableDetailsBanner": "Баннер подробностей", + "EnableDetailsBannerHelp": "Отображает рисунок баннера вверху страницы подробностей элемента.", + "MessageSyncPlayErrorAccessingGroups": "Произошла ошибка при попытке доступа к списку групп.", + "MessageSyncPlayLibraryAccessDenied": "Доступ к данному содержанию ограничен.", + "MessageSyncPlayCreateGroupDenied": "Требуется разрешение для создания группы.", + "MessageSyncPlayGroupDoesNotExist": "Не удалось присоединиться к группе, поскольку она не существует.", + "MessageSyncPlayPlaybackPermissionRequired": "Требуется разрешение на воспроизведение.", + "MessageSyncPlayNoGroupsAvailable": "Никакие группы не доступны. Сначала начните воспроизводить что-нибудь.", + "MessageSyncPlayGroupWait": "{0} буферизуется…", + "MessageSyncPlayUserLeft": "{0} покинул группу.", + "MessageSyncPlayUserJoined": "{0} присоединил группу.", + "LabelSyncPlayAccessNone": "Отключено для данного пользователя", + "LabelSyncPlayAccessJoinGroups": "Разрешить пользователю присоединяться к группам", + "LabelSyncPlayAccessCreateAndJoinGroups": "Разрешить пользователю создавать и присоединять группы", + "LabelSyncPlayLeaveGroup": "Покинуть группу", + "LabelSyncPlayNewGroupDescription": "Создание новой группы", + "LabelSyncPlayNewGroup": "Новая группа", + "LabelSyncPlaySyncMethod": "Метод синхронизации:", + "LabelSyncPlayPlaybackDiff": "Разница времени воспроизведения:", + "MillisecondsUnit": "мс", + "LabelSyncPlayTimeOffset": "Сдвиг времени относительно сервера:", + "SyncPlayAccessHelp": "Выберите уровень доступа данного пользователя к функции SyncPlay. SyncPlay позволяет синхронизировать воспроизведение с другими устройствами.", + "MessageSyncPlayErrorMedia": "Не удалось включить SyncPlay! Ошибка медиаданных.", + "MessageSyncPlayErrorMissingSession": "Не удалось включить SyncPlay! Отсутствует сеанс.", + "MessageSyncPlayErrorNoActivePlayer": "Активный проигрыватель не найден. SyncPlay был отключен.", + "ShowMore": "Показать больше", + "ShowLess": "Показать меньше", + "EnableBlurhashHelp": "Рисунки, которые всё ещё загружаются, будут отображаться с размытым заполнением", + "EnableBlurhash": "Включить размытые заполнители для изображений", + "ButtonSyncPlay": "SyncPlay", + "ButtonCast": "В ролях", + "TabRepositories": "Репозитории", + "MessageNoGenresAvailable": "Разрешить поставщикам метаданных получать жанры из интернета.", + "MessageAddRepository": "Если вы хотите добавить репозиторий, нажмите кнопку рядом с заголовком и заполните необходимую информацию.", + "LabelRepositoryNameHelp": "Имя репозитория для показа на этом сервере.", + "LabelRepositoryName": "Название репозитория", + "LabelRepositoryUrlHelp": "Расположение манифеста добавляемого репозитория.", + "LabelRepositoryUrl": "URL репозитория", + "HeaderNewRepository": "Новый репозиторий", + "MessageNoRepositories": "Репозитории отсутствуют.", + "EnableFasterAnimationsHelp": "Использовать ускоренную анимацию и переходы", + "EnableFasterAnimations": "Ускоренная анимация", + "EnableDecodingColorDepth10Vp9": "Включить аппаратный декодер VP9 10-Bit", + "EnableDecodingColorDepth10Hevc": "Включить аппаратный декодер HEVC 10-Bit", + "StopPlayback": "Остановить проигрывание", + "Writers": "Сценаристы", + "ViewAlbumArtist": "Посмотреть альбом исполнителя", + "ClearQueue": "Очистить очередь", + "ButtonPlayer": "Проигрыватель" } diff --git a/src/strings/sk.json b/src/strings/sk.json index e0bebec52f7..a99d6865230 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -5,16 +5,16 @@ "Albums": "Albumy", "All": "Všetko", "AllChannels": "Všetky kanály", - "AllComplexFormats": "Všetky komplexné formáty (ASS, SSA, VOBSUB, PGS, SUB/IDX, atď.)", + "AllComplexFormats": "Všetky komplexné formáty (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllEpisodes": "Všetky epizódy", "AllLanguages": "Všetky jazyky", "AllLibraries": "Všetky knižnice", "AllowMediaConversion": "Povoliť konverziu médií", "AllowRemoteAccess": "Povoliť vzdialené pripojenia k tomuto Jellyfin serveru.", "AllowRemoteAccessHelp": "Nezaškrtnuté znamená, že všetky vzdialené pripojenia budú blokované.", - "AlwaysPlaySubtitles": "Vždy zobraziť titulky", + "AlwaysPlaySubtitles": "Vždy prehrať", "AnyLanguage": "Akýkoľvek jazyk", - "AroundTime": "Okolo {0}", + "AroundTime": "Okolo", "Artists": "Umelci", "AsManyAsPossible": "Najviac ako je možné", "Ascending": "Vzostupne", @@ -25,7 +25,7 @@ "BirthDateValue": "Narodený/á: {0}", "BirthLocation": "Miesto narodenia", "BirthPlaceValue": "Miesto narodenia: {0}", - "BookLibraryHelp": "Audioknihy a učebnice sú podporované. Prečítajte si {0}pravidlá pre názvy kníh v Jellyfine{1}.", + "BookLibraryHelp": "Audioknihy a učebnice sú podporované. Prečítajte si {0} pravidlá pre názvy kníh v Jellyfine {1}.", "Books": "Knihy", "ButtonAdd": "Pridať", "ButtonAddMediaLibrary": "Pridať knižnicu médií", @@ -236,7 +236,7 @@ "HeaderMedia": "Médiá", "HeaderMediaInfo": "Informácie o médiu", "HeaderMetadataSettings": "Nastavenia metadát", - "HeaderMoreLikeThis": "Podobné ako toto", + "HeaderMoreLikeThis": "Podobné položky", "HeaderMovies": "Filmy", "HeaderMusicQuality": "Kvalita hudby", "HeaderMusicVideos": "Hudobné videá", @@ -293,7 +293,7 @@ "HeaderTypeText": "Vložte text", "HeaderUploadImage": "Nahrať obrázok", "HeaderUser": "Používateľ", - "HeaderUsers": "Užívatelia", + "HeaderUsers": "Používatelia", "HeaderVideoQuality": "Kvalita videa", "HeaderVideos": "Videá", "HeaderXmlSettings": "Nastavenia XML", @@ -306,7 +306,7 @@ "Identify": "Identifikovať", "Images": "Obrázky", "ImportMissingEpisodesHelp": "Ak je možnosť povolená, informácie o chýbajúcich epizódach budú importované do Vašej Jellyfin databázy a budú zobrazené v sériách a seriáloch. Toto môže spôsobiť podstatne dlhšie skenovania knižníc.", - "InstallingPackage": "Inštalujem {0}", + "InstallingPackage": "Inštalujem {0} (verzia{1})", "ItemCount": "{0} položiek", "Items": "Položky", "Kids": "Detské", @@ -578,12 +578,12 @@ "NoNextUpItemsMessage": "Nič nenájdené. Začnite pozerať vaše seriály!", "NoPluginConfigurationMessage": "Tento zásuvný modul nemá žiadne nastavenia.", "NoSubtitleSearchResultsFound": "Žiadne výsledky.", - "NoSubtitles": "Žiadne titulky", + "NoSubtitles": "Žiadne", "None": "Žiadne", "Normal": "Normálne", "NumLocationsValue": "{0} priečinkov", "OneChannel": "Jeden kanál", - "OnlyForcedSubtitles": "Iba vynútené titulky", + "OnlyForcedSubtitles": "Iba vynútené", "OnlyForcedSubtitlesHelp": "Budú zobrazené iba titulky označené ako vynútené.", "OptionAdminUsers": "Administrátori", "OptionAllUsers": "Všetci používatelia", @@ -648,7 +648,7 @@ "OptionMissingEpisode": "Chýbajúce epizódy", "OptionMonday": "Pondelok", "OptionNameSort": "Názov", - "OptionNew": "Nové...", + "OptionNew": "Nové…", "OptionNone": "Žiadne", "OptionOnAppStartup": "Pri spustení aplikácie", "OptionParentalRating": "Rodičovské hodnotenie", @@ -677,9 +677,9 @@ "OptionWeekly": "Týždenne", "OriginalAirDateValue": "Pôvodný dátum vysielania: {0}", "Overview": "Prehľad", - "PackageInstallCancelled": "{0} inštalácia zrušená.", - "PackageInstallCompleted": "{0} inštalácia dokončená.", - "PackageInstallFailed": "{0} inštalácia zlyhala.", + "PackageInstallCancelled": "{0} (verzia{1}) inštalácia zrušená.", + "PackageInstallCompleted": "{0} (verzia{1}) inštalácia dokončená.", + "PackageInstallFailed": "{0} (verzia {1}) inštalácia zlyhala.", "ParentalRating": "Parental Rating", "PasswordMatchError": "Heslo a potvrdenie hesla sa musia zhodovať.", "PasswordResetComplete": "Heslo bolo obnovené.", @@ -721,14 +721,13 @@ "Refresh": "Obnoviť", "RefreshMetadata": "Obnoviť metadáta", "ReleaseDate": "Dátum vydania", - "RememberMe": "Zapamätať si ma", + "RememberMe": "Zapamätaj si ma", "RemoveFromCollection": "Odobrať z kolekcie", "Repeat": "Opakovať", "RepeatAll": "Opakovať všetko", "RepeatOne": "Opakovať jedno", "ReplaceAllMetadata": "Nahradiť všetky metadáta", "ReplaceExistingImages": "Nahradiť existujúce obrázky", - "RestartPleaseWaitMessage": "Počkajte prosím kým sa Jellyfin Server vypne a znova naštartuje. Môže to trvať minútu alebo dve.", "ResumeAt": "Pokračovať od {0}", "RunAtStartup": "Spustiť pri štarte", "Saturday": "Sobota", @@ -777,7 +776,7 @@ "TabCatalog": "Katalóg", "TabChannels": "Kanály", "TabCodecs": "Kodeky", - "TabCollections": "Zbierky", + "TabCollections": "Kolekcie", "TabContainers": "Kontajnery", "TabDashboard": "Dashboard", "TabDevices": "Zariadenia", @@ -934,7 +933,7 @@ "DirectStreamHelp2": "Priame streamovanie súboru používa veľmi málo procesorového výkonu bez straty kvality videa.", "DirectStreaming": "Priame streamovanie", "DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí byť povolené pre TV knižnice v nastavení servera.", - "DisplayModeHelp": "Zvoľte typ obrazovky, na ktorej používate Jellyfin.", + "DisplayModeHelp": "Vyberte štýl layoutu, ktorý chcete pre rozhranie.", "EnableCinemaMode": "Kino režim", "EnableNextVideoInfoOverlay": "Zobraziť informácie o nasledujúcom videu počas prehrávania", "EnableNextVideoInfoOverlayHelp": "Na konci videa sa zobrazia informácie o nasledujúcom videu v aktuálnom playliste.", @@ -1036,7 +1035,7 @@ "OptionEnableForAllTuners": "Povoliť pre všetky tunery", "OptionEnableExternalContentInSuggestions": "Povoliť externý obsah v návrhoch", "OptionBlockChannelContent": "Obsah internetového kanála", - "OnlyImageFormats": "Iba obrazové formáty (VOBSUB, PGS, SUB, atď)", + "OnlyImageFormats": "Iba obrazové formáty (VOBSUB, PGS, SUB)", "Off": "Vypnuté", "MusicVideo": "Videoklip", "MusicArtist": "Interpret", @@ -1045,7 +1044,6 @@ "HideWatchedContentFromLatestMedia": "Skryť pozreté položky zo zoznamu najnovších médií", "HeaderTranscodingProfile": "Profil prekódovania", "HeaderSeriesOptions": "Nastavenia seriálov", - "HeaderRestartingServer": "Server sa reštartuje", "HeaderParentalRatings": "Rodičovské hodnotenia", "HeaderEnabledFields": "Povolené polia", "HeaderAudioLanguages": "Jazyk zvuku", @@ -1180,7 +1178,7 @@ "CancelSeries": "Ukončiť seriál", "ButtonSplit": "Rozdeliť", "ButtonAddImage": "Pridať obrázok", - "BurnSubtitlesHelp": "Určuje, či má server vypaľovať titulky počas prevodu videa v závislosti na formáte tituliek. Vynechanie tejto možnosti výrazne zvýši výkon serveru. Vyberte možnosť Auto, pokiaľ chcete vypalovať do obrazu titulky v grafickom formáte (VOBSUB, PGS, SUB/IDX, atď.) a niektoré ASS/SSA titulky.", + "BurnSubtitlesHelp": "Určuje, či má server vpáliť titulky počas transkódovania videa. Vynechanie tejto možnosti výrazne zvýši výkon. Vyberte možnosť Auto, pokiaľ chcete vpáliť do obrazu titulky v grafickom formáte (VOBSUB, PGS, SUB, IDX, …) a niektoré ASS alebo SSA titulky.", "BrowsePluginCatalogMessage": "Prehliadnite si náš katalóg dostupných zásuvných modulov.", "Browse": "Prechádzať", "Blacklist": "Blacklist", @@ -1189,7 +1187,7 @@ "Art": "Umenie", "AlwaysPlaySubtitlesHelp": "Titulky odpovedajúce jazykovej preferencií sa načítajú bez ohľadu na jazyk zvuku.", "AllowedRemoteAddressesHelp": "Zoznam IP adries alebo IP/netmask záznamov pre siete oddelené čiarkami z ktorých sa dá vzdialene pripojiť. Pokiaľ zoznam bude prázdny, všetky adresy budú povolené.", - "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky môžu byť extrahované z videa a prenesené na klienta vo forme jednoduchého textu aby sa zabránilo transkódovaniu videa. Na niektorých systémoch táto operácia môže trvať dlhší čas a a spôsobiť sekanie videa počas počas extrahovania. Vypnutie tejto funkcie bude mať za následok, že titulky budú počas transkódovania vypálené do samotného videa pokiaľ klientské zariadenie natívne nepodporuje ich formát.", + "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky môžu byť extrahované z videa a prenesené na klienta vo forme jednoduchého textu, aby sa zabránilo transkódovaniu videa. Na niektorých systémoch táto operácia môže trvať dlhší čas a a spôsobiť sekanie videa počas počas extrahovania. Vypnutie tejto funkcie bude mať za následok, že titulky budú počas transkódovania vypálené do samotného videa pokiaľ klientské zariadenie natívne nepodporuje ich formát.", "Watched": "Pozreté", "TvLibraryHelp": "Pozrite sa na {0}sprievodcu pomenovania TV programov{1}.", "LabelLineup": "Lineup:", @@ -1269,7 +1267,7 @@ "OptionAllowVideoPlaybackRemuxing": "Povoliť prehrávanie videa, ktoré vyžaduje konverziu bez opätovného enkódovania", "OptionAllowSyncTranscoding": "Povoliť sťahovanie a synchronizáciu medií, ktoré vyžadujú transkódovanie", "OptionAllowMediaPlaybackTranscodingHelp": "Obmedzenie prístupu ku transkódovaniu môže spôsobiť zlyhania prehrávania v Jellyfin aplikáciách kvôli nepodporovaným formátom medií.", - "MessageNoTrailersFound": "Neboli nájdené žiadne trailery. Nainštalujte Trailer kanál pre rozšírenie vášho filmového zážitku pridaním knižnice trailerov z internetu.", + "MessageNoTrailersFound": "Nainštalujte Trailer kanál pre rozšírenie vášho filmového zážitku pridaním knižnice trailerov z internetu.", "LanNetworksHelp": "Zoznam IP adries alebo IP/netmask záznamov pre všetky siete oddelené čiarkami ktoré budú považované za lokálnu sieť pri vynucovaní obmedzenia šírky pásma. Pokiaľ je toto nastavené, všetky ostatné IP adresy budú považované za vonkajšiu sieť a budú podliehať obmedzeniam šírky pásma vonkajšej siete. Pokiaľ pole ostane prázdne, podsieť serveru bude považovaná za lokálnu sieť.", "LabelUserAgent": "User agent:", "LabelEnableBlastAliveMessagesHelp": "Povolte v prípade, že server nie je viditeľný inými UPnP zariadeniami na vašej sieti.", @@ -1292,7 +1290,7 @@ "MessagePlayAccessRestricted": "Prehrávanie tohoto obsahuje je aktuálne obmedzené. Prosím, kontaktujte svojho administrátora servera pre viac informácií.", "MessagePasswordResetForUsers": "Nasledujúci používatelia si nechali obnoviť heslo. Teraz sa môžu prihlásiť s PIN kódom, ktorý použijú k vykonaniu obnovy hesla.", "MessageNoServersAvailable": "Žiadne servery neboli nájdené pomocou automatického objavovania serverov.", - "MessageNoMovieSuggestionsAvailable": "Žiadne filmové návrhy nie sú v súčastnosti k dispozícií. Začnite pozerať a hodnotiť vaše filmy, potom sa sem vráťte pre vaše odporúčania.", + "MessageNoMovieSuggestionsAvailable": "V súčastnosti nie sú k dispozícií žiadne filmové návrhy. Začnite pozerať a hodnotiť vaše filmy, potom sa sem vráťte pre Vaše odporúčania.", "MessageNoCollectionsAvailable": "Kolekcie vám umožnia užiť si vlastné zoskupenia filmov, seriálov a albumov. Kliknite na tlačítko + pre začatie vytvárania kolekcie.", "MessageInstallPluginFromApp": "Tento zásuvný modul musí byť nainštalovaný z aplikácie, ktorú chcete používať.", "MessageImageTypeNotSelected": "Prosím, vyberte typ obrázku z rozbalovacieho menu.", @@ -1389,7 +1387,7 @@ "LabelPersonRoleHelp": "Príklad: Vodič nákladiaku so zmrzlinou", "LabelPasswordResetProvider": "Poskytovateľ obnovy hesla:", "LabelParentNumber": "Číslo rodiča:", - "LabelOptionalNetworkPathHelp": "Pokiaľ je tento priečinok zdielaný vo vašej sieti, môže poskytovanie cesty k zdielanému priečinku umožniť Jellyfin aplikáciám priamy prístup k mediálnym súborom.", + "LabelOptionalNetworkPathHelp": "Pokiaľ je tento priečinok zdielaný vo vašej sieti, môže poskytovanie cesty k zdielanému priečinku umožniť Jellyfin aplikáciám priamy prístup k mediálnym súborom. Napríklad, {0} alebo {1}.", "LabelNumberOfGuideDaysHelp": "Stiahnutím viacerých dní umožní sprievodca naplánovať a zobraziť viac vecí do budúcnosti, sťahovanie však môže trvať dlhšie. Auto vyberie možnosť podľa počtu kanálov.", "LabelNumberOfGuideDays": "Počet dní pre stiahnutie dát sprievodcu:", "LabelMusicStreamingTranscodingBitrateHelp": "Špecifikujte maximálny dátový tok pre streamovanie hudby.", @@ -1433,7 +1431,7 @@ "LabelFriendlyName": "Priateľský názov:", "LabelFolder": "Priečinok:", "LabelExtractChaptersDuringLibraryScanHelp": "Generovať obrázky kapitol počas toho, ako sú videá importované v prvotnom prehľadávaní knižnice. Inak sa budú extrahovať počas naplánovanej úlohy generovania obrázkov kapitol, čo dovoľuje rýchlejšie dokončenie bežného prehľadávania knižnice.", - "LabelBaseUrlHelp": "Tu môžete pridať vlastný podpriečinok, aby bolo možné pristupovať k serveru z viac unikátnej URL.", + "LabelBaseUrlHelp": "Pridá vlastný reťazec na URL adresu serveru, napr: http://priklad.sk/<vlastnyretazec>", "LabelBaseUrl": "Východzia URL:", "LabelEveryXMinutes": "Každý:", "LabelEnableSingleImageInDidlLimitHelp": "Niektoré zariadenia nebudú zobrazovať správne pokiaľ je viacero obrázkov uložených v Didl.", @@ -1442,7 +1440,7 @@ "LabelEnableDlnaDebugLogging": "Povoliť loggovanie DLNA debugu", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje dobu trvania v sekundách medzi SSDP vyhľadávaniami vykonanými Jellyfinom.", "LabelEnableDlnaClientDiscoveryInterval": "Interval pre objavenie klienta (sekundy)", - "LabelEnableAutomaticPortMapHelp": "Pokus o automatické namapovanie vejerného portu na lokálny port cez UPnP. Toto nemusí fungovať so všetkými modelmi routerov. Zmeny sa vykonajú až po reštarte servera.", + "LabelEnableAutomaticPortMapHelp": "Automatické namapovanie vejerného portu na lokálny port serveru cez UPnP. Toto nemusí fungovať so všetkými modelmi routerov alebo sieťových konfigurácií. Zmeny sa vykonajú až po reštarte servera.", "LabelEmbedAlbumArtDidlHelp": "Niektoré zariadenia preferujú túto metódu pre získavanie obrázku albumu. Ostatným môže zlyhať prehrávanie pokiaľ je táto možnosť povolená.", "LabelBlastMessageIntervalHelp": "Určuje dobu v sekundách medzi vysielaniami správ o serveri.", "LabelBindToLocalNetworkAddressHelp": "Voliteľné. Prepísať lokálnu IP adresu viazanú na http server. Pokiaľ zostane prázdna, server sa naviaže na všetky dostupné adresy. Pri zmene tejto hodnoty sa vyžaduje reštart Jellyfin Servera.", @@ -1457,8 +1455,6 @@ "MessageConfirmAppExit": "Chceli by ste odísiť?", "LabelVideoResolution": "Rozlíšenie videa:", "LabelStreamType": "Typ streamu:", - "EnableFastImageFadeInHelp": "Povoliť animáciu rýchleho rozjasnenia pre nahrané obrázky", - "EnableFastImageFadeIn": "Rýchle rozjasnenie obrázku", "LabelPlayerDimensions": "Rozmery prehrávača:", "LabelDroppedFrames": "Vynechané snímky:", "LabelCorruptedFrames": "Poškodené snímky:", @@ -1466,7 +1462,116 @@ "OptionForceRemoteSourceTranscoding": "Vynútiť transkódovanie vzdialených mediálnych zdrojov (ako napr. živá TV)", "NoCreatedLibraries": "Vyzerá to tak, že ste zatiaľ nevytvorili žiadnu knižnicu. {0}Chceli by ste nejakú vytvoriť teraz?{1}", "AskAdminToCreateLibrary": "Pokiaľ chcete vytvoriť knižnicu, musíte sa spýtať administrátora.", - "PlaybackErrorNoCompatibleStream": "Nastal problém s profilom klienta a server preto neposiela kompatibilný mediálny formát.", + "PlaybackErrorNoCompatibleStream": "Tento klient nie je kompatibilný s médiom a server neposiela kompatibilný mediálny formát.", "AllowFfmpegThrottlingHelp": "Keď sa transkódovanie alebo remuxovanie dostane do bodu, kedy je dostatočne vopred voči súčasnej polohe prehrávania, pozastaví proces aby spotrebovával menej zdrojov. Toto je najviac užitočné, keď sa pozerá obsah bez pretáčania. Vypnite túto možnosť, pokiaľ má vaše prehrávanie problémy.", - "AllowFfmpegThrottling": "Obmedzenie transkódovania" + "AllowFfmpegThrottling": "Obmedzenie transkódovania", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferovať vložen[ informáciu o epizóde pred názvom súboru", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Toto využíva informáciu o epizóde z vložených metadát, pokiaľ sú dostupne.", + "ClientSettings": "Nastavenie klienta", + "Album": "Album", + "DeinterlaceMethodHelp": "Vyberte metódu odstránenia prekladania obrazu videa pri transkódovaní prekladaného obsahu.", + "OnApplicationStartup": "Pri spustení aplikácie", + "EveryXHours": "Každých {0} hodín", + "EveryHour": "Každú hodinu", + "EveryXMinutes": "Každých {0} minút", + "OnWakeFromSleep": "Pri prebúdzaní zo spánku", + "WeeklyAt": "{0}s na {1}", + "DailyAt": "Denne o {0}", + "LastSeen": "Naposledy videný {0}", + "PersonRole": "ako {0}", + "ListPaging": "{0}-{1} z {2}", + "WriteAccessRequired": "Jellyfin Server vyžaduje práva na zapisovanie do tohoto priečinku. Prosím, uistite sa že má práva na zapisovanie a skúste to znova.", + "PathNotFound": "Táto cesta nebola nájdená. Prosím, uistite sa že cesta je správna a skúste to znovu.", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Track": "Stopa", + "Season": "Séria", + "ReleaseGroup": "Vydavateľ", + "Person": "Osoba", + "OtherArtist": "Ostatný umelci", + "Movie": "FIlm", + "LabelDeinterlaceMethod": "Metóda odstránenia prekladaného videa:", + "Episode": "Epizóda", + "BoxSet": "Box Set", + "Artist": "Umelec", + "AlbumArtist": "Umelec albumu", + "LabelLibraryPageSizeHelp": "Určuje množstvo položiek na zobrazenie na stránke knižnice. Nastavte 0 pre vypnutie stránkovania.", + "LabelLibraryPageSize": "Veľkosť stránky knižnice:", + "MessageUnauthorizedUser": "Momentálne nemáte oprávnenie na prístup k serveru. Prosím, kontaktujte svojho administrátora serveru pre viac informácií.", + "UnsupportedPlayback": "Jellyfin nemôže dešifrovať obsah chránený technológiou DRM, ale pokúsi sa o to, vrátane chránených titulov. Niektoré súbory sa môžu zobraziť ako kompletne čierne z dôvodu, že sú zašifrované alebo obsahujú nepodporované funckie, ako napríklad interaktívne funkcie.", + "Filter": "Filter", + "New": "Nové", + "HeaderFavoritePlaylists": "Obľúbené playlisty", + "ButtonTogglePlaylist": "Playlist", + "ButtonToggleContextMenu": "Viac", + "ApiKeysCaption": "Zoznam v súčasnosti povolených API kľúčov", + "LabelNightly": "Nočná", + "LabelStable": "Stabilná", + "LabelChromecastVersion": "Chromecast verzia", + "TabDVR": "DVR", + "LabelRequireHttpsHelp": "Pokiaľ je zaškrtnutý, server bude automaticky presmerovávať všetky HTTP požiadavky cez HTTPS. Toto nastavenie nemá žiadny efekt, pokiaľ server nepočúva na HTTPS.", + "LabelRequireHttps": "Vyžadovať HTTPS", + "LabelEnableHttpsHelp": "Umožní serveru počúvať na nastavenom HTTPS porte. K správnemu fungovaniu je nutné nakonfigurovať aj platný certifikát.", + "LabelEnableHttps": "Povoliť HTTPS", + "HeaderServerAddressSettings": "Nastavenie adresy servera", + "HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu", + "HeaderHttpsSettings": "Nastavenia HTTPS", + "HeaderDVR": "DVR", + "SaveChanges": "Uložiť zmeny", + "MessageSyncPlayErrorMedia": "Povolenie funkcie SyncPlay zlyhalo! Chyba média.", + "MessageSyncPlayErrorMissingSession": "Zapnutie funkcie SyncPlay zlyhalo! Aktívna relácia nebola nájdená.", + "MessageSyncPlayErrorNoActivePlayer": "Nebol nájdený žiadny aktívny prehrávač. Funkcia SyncPlay bola vypnutá.", + "MessageSyncPlayErrorAccessingGroups": "Pri načítaní zoznamu skupín sa vyskytla chyba.", + "MessageSyncPlayLibraryAccessDenied": "Prístup k tomuto obsahuje je obmedzený.", + "MessageSyncPlayJoinGroupDenied": "K použitiu funkcie SyncPlay je požadované povolenie.", + "MessageSyncPlayCreateGroupDenied": "K vytvoreniu skupiny je požadované povolenie.", + "MessageSyncPlayGroupDoesNotExist": "Pripojenie ku skupine zlyhalo, pretože skupina neexistuje.", + "MessageSyncPlayPlaybackPermissionRequired": "K prehrávaniu je potrebné povolenie.", + "MessageSyncPlayNoGroupsAvailable": "Nie je dostupná žiadna skupina. Skúste najskôr začať niečo prehrávať.", + "MessageSyncPlayGroupWait": "{0} sa načítava…", + "MessageSyncPlayUserLeft": "Používateľ {0} opustil skupinu.", + "MessageSyncPlayUserJoined": "Používateľ {0} sa pripojil k skupine.", + "MessageSyncPlayDisabled": "SyncPlay zakázaný.", + "MessageSyncPlayEnabled": "SyncPlay povolený.", + "LabelSyncPlayAccess": "Prístup k funkcií SyncPlay", + "LabelSyncPlayAccessNone": "Zakázať pre tohoto používateľa", + "LabelSyncPlayAccessJoinGroups": "Povoliť použivateľovi pripájať sa do skupín", + "LabelSyncPlayAccessCreateAndJoinGroups": "Povoliť používateľovi vytvárať a pripájať sa do skupín", + "LabelSyncPlayLeaveGroupDescription": "Zakázať SyncPlay", + "LabelSyncPlayLeaveGroup": "Opustiť skupinu", + "LabelSyncPlayNewGroupDescription": "Vytvoriť novú skupinu", + "LabelSyncPlayNewGroup": "Nová skupina", + "LabelSyncPlaySyncMethod": "Spôsob synchronizácie:", + "LabelSyncPlayPlaybackDiff": "Rozdiel v dobe prehrávania:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Časový rozdiel so serverom:", + "HeaderSyncPlayEnabled": "SyncPlay je povolené", + "HeaderSyncPlaySelectGroup": "Pripojiť sa k skupine", + "SyncPlayAccessHelp": "Vyberte úroveň prístupu pre tohto používateľa k funkcií SyncPlay. SyncPlay umožňuje zosynchronizovať prehrávanie s ostatnými zariadeniami.", + "EnableDetailsBannerHelp": "Zobrazí banner na vrchnej časti detailu položky.", + "EnableDetailsBanner": "Detail banneru", + "EnableBlurhashHelp": "Obrázky, ktoré sa stále načítavajú budú zobrazené ako dočasný obrázok s rozmazaným pozadím", + "EnableBlurhash": "Povoliť obrázok s rozmazaným pozadím pre chýbajúce obrázky", + "ShowMore": "Zobraziť viac", + "ShowLess": "Zobraziť menej", + "ButtonCast": "Prenášať", + "ButtonSyncPlay": "SyncPlay", + "MessageNoGenresAvailable": "Povoliť vybraným metadáta poskytovateľom stiahnuť žánre z internetu.", + "EnableFasterAnimationsHelp": "Použiť rýchlejšiu animáciu a prechody", + "EnableFasterAnimations": "Rýchlejšia animácia", + "EnableDecodingColorDepth10Vp9": "Povoliť 10-Bitové hardvérové dekódovanie pre VP9", + "EnableDecodingColorDepth10Hevc": "Povoliť 10-Bitové hardvérové dekódovanie pre HEVC", + "LabelRepositoryUrl": "URL adresa repozitára", + "HeaderNewRepository": "Nový repozitár", + "MessageNoRepositories": "Neexistujú žiadne repozitáre.", + "TabRepositories": "Repozitáre", + "MessageAddRepository": "Pokiaľ chcete pridať repozitár, kliknite na tlačidlo vedľa hlavičky a vyplňte požadované informácie.", + "LabelRepositoryNameHelp": "Vlastné pomenovanie, ktoré slúži na odlíšenie tohto repozitára od ostatných repozitárov pridaných na vašom serveri.", + "LabelRepositoryName": "Názov repozitára", + "LabelRepositoryUrlHelp": "Umiestnenie manifestu repozitára, ktorý chcete zahrnúť.", + "ButtonPlayer": "Prehrávač", + "Writers": "Scenáristi", + "ClearQueue": "Vymazať frontu", + "StopPlayback": "Zastaviť prehrávanie", + "ViewAlbumArtist": "Zobraziť interpreta albumu" } diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index a692edbd3f9..14bf6aa598c 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -77,7 +77,7 @@ "Alerts": "Alarmi", "All": "Vse", "AllChannels": "Vsi kanali", - "AllComplexFormats": "Vsi kompleksni formati (ASS, SSA, VOBSUB, PGS, SUB/IDX, itd.)", + "AllComplexFormats": "Vsi kompleksni formati (ASS, SSA, VOBSUB, PGS, SUB, IDX, itd.)", "AllEpisodes": "Vse epizode", "AllLanguages": "Vsi jeziki", "AllLibraries": "Vse knjižnice", @@ -100,10 +100,8 @@ "HeaderLiveTV": "TV v živo", "HeaderNextUp": "Sledi", "HeaderRecordingGroups": "Zbirke posnetkov", - "LabelIpAddressValue": "IP naslov: {0}", "LabelRunningTimeValue": "Čas trajanja: {0}", "MessageApplicationUpdated": "Jellyfin Server je bil posodobljen", - "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", "Movies": "Filmi", "AddItemToCollectionHelp": "Dodajte elemente v zbirke tako, da jih poiščete in jih z desnim klikom ali dotikom menija dodate v zbirko.", "AllowedRemoteAddressesHelp": "Z vejico ločen seznam IP naslovov ali IP/maska omrežij, ki jim je dovoljen oddaljeni dostop. Če pustite prazno, bodo dovoljeni vsi oddaljeni naslovi.", @@ -127,10 +125,6 @@ "AudioSampleRateNotSupported": "Frekvenca vzorčenja zvoka ni podprta", "Auto": "Samodejno", "AutoBasedOnLanguageSetting": "Samodejno (na podlagi nastavitve jezika)", - "AutomaticallyConvertNewContent": "Samodejno pretvori novo vsebino", - "AutomaticallyConvertNewContentHelp": "Nova vsebina dodana v to mapo bo samodejno pretvorjena.", - "AutomaticallySyncNewContent": "Samodejno prenesi novo vsebino", - "AutomaticallySyncNewContentHelp": "Nova vsebina dodana v to mapo bo samodejno prenesena na to napravo.", "Backdrop": "Ozadje", "Backdrops": "Ozadja", "BestFit": "Najboljše prileganje", @@ -318,7 +312,6 @@ "HeaderSchedule": "Urnik", "HeaderScenes": "Scene", "HeaderRunningTasks": "Aktivni procesi", - "HeaderRestartingServer": "Ponovno zaganjanje", "HeaderRestart": "Ponovni zagon", "HeaderResponseProfileHelp": "Profili odziva omogočajo prilagoditev informacij poslanih sprejemniku pri predvajanju določenih vrst predstavnosti.", "HeaderResponseProfile": "Profil odziva", @@ -378,7 +371,7 @@ "HeaderInstall": "Namesti", "HeaderImageSettings": "Nastavitve slike", "HeaderImageOptions": "Možnosti slike", - "HeaderIdentifyItemHelp": "Vnesite vsaj en kriterij iskanja. Odstranitr kriterije za več rezultatov.", + "HeaderIdentifyItemHelp": "Vnesite vsaj en kriterij iskanja. Odstranite kriterije za več rezultatov.", "HeaderIdentificationCriteriaHelp": "Vnesite vsaj en kriterij za identifikacijo.", "HeaderIdentification": "Identifikacija", "HeaderHome": "Domov", @@ -1217,5 +1210,71 @@ "TitleHardwareAcceleration": "Strojno pospeševanje", "Thursday": "Četrtek", "TabUsers": "Uporabniki", - "TabTrailers": "Napovedniki" + "TabTrailers": "Napovedniki", + "ClientSettings": "Nastavitve odjemalca", + "ButtonTogglePlaylist": "Seznam predvajanja", + "ButtonToggleContextMenu": "Več", + "Artist": "Izvajalec", + "AlbumArtist": "Izvajalec albuma", + "Album": "Album", + "LibraryAccessHelp": "Izberite knjižnice, ki bodo deljenje s tem uporabnikom. Upravitelji bodo lahko urejali metapodatke z upraviteljem metapodatkov.", + "LeaveBlankToNotSetAPassword": "To polje lahko pustite prazno za uporabo brez gesla.", + "LearnHowYouCanContribute": "Poglejte, kako lahko pomagate.", + "LaunchWebAppOnStartupHelp": "Ob prvem zagonu strežnika se bo v privzetem brskalniku odprl spletni vmesnik. To se ne bo zgodilo pri uporabi možnosti za ponovni zagon.", + "LaunchWebAppOnStartup": "Ob zagonu strežnika zaženi spletni vmesnik", + "LabelffmpegPathHelp": "Pot do datoteke aplikacije ffmpeg ali mape, ki jo vsebuje.", + "LabelffmpegPath": "Pot FFmpeg:", + "LabelZipCode": "Poštna številka:", + "LabelYear": "Leto:", + "LabelVideoResolution": "Ločljivost videa:", + "LabelVideoCodec": "Video kodek:", + "LabelVideoBitrate": "Bitna hitrost videa:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arhitektura: {0}", + "DashboardOperatingSystem": "Operacijski sistem: {0}", + "DashboardServerName": "Strežnik: {0}", + "LabelVersionInstalled": "{0} nameščeno", + "LabelValue": "Vrednost:", + "LabelVaapiDeviceHelp": "To je vozlišče naprave, ki bo uporabljena za strojno pospeševanje.", + "LabelVaapiDevice": "Naprava VAAPI:", + "LabelUsername": "Uporabniško ime:", + "LabelUserRemoteClientBitrateLimitHelp": "Povozi privzeto globalno nastavitev v nastavitvah strežnika.", + "LabelUserLoginAttemptsBeforeLockout": "Število spodletelih prijav preden bo profil uporabnika zaklenjen:", + "LabelUserLibraryHelp": "Izberite katera uporabniška knjižnica naj bo prikazana na napravi. Pustite prazno za uporabo privzetih nastavitev.", + "LabelUserLibrary": "Uporabniška knjižnica:", + "LabelUser": "Uporabnik:", + "LabelUseNotificationServices": "Uporabi naslednje storitve:", + "LabelTypeText": "Tekst", + "LabelTypeMetadataDownloaders": "{0} prenašalci metapodatkov:", + "LabelType": "Vrsta:", + "LabelTunerType": "Vrsta sprejemnika:", + "LabelSyncPlayAccess": "Dostop do SyncPlay", + "LabelSyncPlayAccessNone": "Onemogočeno za tega uporabnika", + "LabelSyncPlayAccessJoinGroups": "Dovoli uporabniku pridružitev skupinam", + "LabelSyncPlayAccessCreateAndJoinGroups": "Dovoli uporabniku ustvarjanje in pridružitev skupinam", + "LabelSyncPlayLeaveGroupDescription": "Onemogoči SyncPlay", + "LabelSyncPlayLeaveGroup": "Zapusti skupino", + "LabelSyncPlayNewGroupDescription": "Ustvari novo skupino", + "LabelSyncPlayNewGroup": "Nova skupina", + "LabelSyncPlaySyncMethod": "Način sinhronizacije:", + "LabelSyncPlayPlaybackDiff": "Razlika v času predvajanja:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Zamik časa na strežniku:", + "EnableFastImageFadeInHelp": "Uporabi hitrejše animacije in prehode", + "EnableFastImageFadeIn": "Hitrejše animacije", + "LabelRequireHttpsHelp": "Če je označeno, bo strežnik samodejno preusmeril vse HTTP zahteva na HTTPS. Nima vpliva, če strežnik ne posluša na HTTPS.", + "LabelRequireHttps": "Zahtevaj HTTPS", + "HeaderInstantMix": "Hitri miks", + "HeaderIdentificationHeader": "Identifikacija zaglavja", + "HeaderHttpsSettings": "Nastavitve HTTPS", + "HeaderHttpHeaders": "HTTP zaglavje", + "HeaderFavoritePlaylists": "Priljubljeni seznami predvajanja", + "HeaderDVR": "DVR", + "HeaderBlockItemsWithNoRating": "Blokiraj vsebine brez oziroma z neprepoznanimi starševskimi ocenami:", + "ApiKeysCaption": "Seznam trenutno uporabljenih API ključev", + "Episode": "Epizoda", + "EnableDetailsBannerHelp": "Prikaži sliko pasice na vrhu strani podrobnosti.", + "EnableDetailsBanner": "Pasica podrobnosti", + "DeinterlaceMethodHelp": "Izberite način razpletanja pri prekodiranju prepletenih vsebin.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/sr.json b/src/strings/sr.json index 394ef9352ed..d1ed50e87b1 100644 --- a/src/strings/sr.json +++ b/src/strings/sr.json @@ -36,7 +36,7 @@ "ButtonOpen": "Отвори", "ButtonOk": "Ок", "ButtonOff": "Искључи", - "ButtonNextTrack": "Следећа трака", + "ButtonNextTrack": "Следећа нумера", "ButtonNew": "Ново", "ButtonNetwork": "Мрежа", "ButtonMore": "Више", @@ -51,7 +51,7 @@ "ButtonFullscreen": "Пун екран", "ButtonForgotPassword": "Заборављена шифра", "ButtonFilter": "Филтер", - "ButtonDownload": "Скини (даунлоадуј)", + "ButtonDownload": "Преузми", "ButtonDown": "Доле", "ButtonDeleteImage": "Обриши слику", "ButtonDelete": "Обриши", @@ -75,7 +75,7 @@ "BoxRear": "Омот (позади)", "Box": "Омот", "Blacklist": "Црна листа", - "BirthPlaceValue": "Место рођења", + "BirthPlaceValue": "Место рођења: {0}", "BirthLocation": "Место рођења", "BirthDateValue": "Рођен", "AutoBasedOnLanguageSetting": "Аутоматски (зависи од подешавања језика)", @@ -95,5 +95,105 @@ "AllChannels": "Сви канали", "All": "Све", "Alerts": "Упозорења", - "Aired": "Емитовано" + "Aired": "Емитовано", + "Absolute": "Апсолутно", + "Default": "Уобичајено", + "DeathDateValue": "Преминуо: {0}", + "DatePlayed": "Датум емитовања", + "DateAdded": "Датум додавања", + "CustomDlnaProfilesHelp": "Направите прилагођени профил да бисте циљали на нови уређај или прегазили системски профил.", + "CriticRating": "Оцена критике", + "CopyStreamURLError": "Десила се грешка приликом копирања адресе.", + "CopyStreamURLSuccess": "Адреса копирана успешно.", + "CopyStreamURL": "Копирајте адресу стрим-а", + "Continuing": "Наставља", + "ContinueWatching": "Натавите гледање", + "Connect": "Повежи", + "ConfirmEndPlayerSession": "Да ли желите да искључите Јеллифин {0}?", + "ConfirmDeletion": "ПОтврдите брисање", + "ConfirmDeleteItems": "Брисањем ових ставки избрисаћете их и из система и из ваше библиотеке медија. Јесте ли сигурни да желите да наставите?", + "ConfirmDeleteItem": "Брисањем ове ставке избрисаћете је и из система и из ваше библиотеке медија. Јесте ли сигурни да желите да наставите?", + "ConfirmDeleteImage": "Обришите слику?", + "ConfigureDateAdded": "Конфигуришите како се датум додавања одређује на контролној табли Јеллифин Сервер-а под Подешавања библиотеке", + "Composer": "Композитор", + "CommunityRating": "Рејтинг заједнице", + "ColorTransfer": "Промена боја", + "ColorSpace": "Простор боја", + "ColorPrimaries": "Основне боје", + "ClientSettings": "Подешавања клијента", + "CinemaModeConfigurationHelp": "Режим биоскопа доноси позоришно искуство директно у вашу дневну собу са могућношћу приказинвања трејлера и прилагођених увода пре главне проекције.", + "ChannelNumber": "Број канала", + "ChannelNameOnly": "Само {0} канал", + "ChannelAccessHelp": "Изаберите канале које желите да делите са овим корисником. Администратори ће моћи да уређују све канале помоћу менаџера метаподатака.", + "ChangingMetadataImageSettingsNewContent": "Промене метаподатака или поставки преузимања уметничког дела примењују се само на нови садржај додат у вашу библиотеку. Да бисте применили промене на постојеће наслове, мора ћете ручно освежити њихове метаподатке.", + "Categories": "Категорије", + "CancelSeries": "Откажи серију", + "CancelRecording": "Откажи снимање", + "ButtonWebsite": "Веб сајт", + "ButtonViewWebsite": "Погледајте веб сајт", + "ButtonUp": "Горе", + "ButtonUninstall": "Деинсталирај", + "ButtonTrailer": "Трејлер", + "ButtonSubtitles": "Титлови", + "ButtonSubmit": "Пошаљите", + "ButtonSplit": "Подели", + "ButtonStop": "Заустави", + "ButtonStart": "Почни", + "ButtonSort": "Сортирај", + "ButtonSignOut": "Одјавите се", + "ButtonSignIn": "Пријавите се", + "ButtonShutdown": "Искључи", + "ButtonShuffle": "Промешај", + "ButtonSettings": "Подешавања", + "ButtonSend": "Пошаљи", + "ButtonSelectView": "Изаберите приказ", + "ButtonSelectServer": "Иѕаберите сервер", + "ButtonSelectDirectory": "Изаберите Директоријум", + "ButtonSearch": "Тражи", + "ButtonScanAllLibraries": "Скенирај све библиотеке", + "ButtonSave": "Сачувај", + "ButtonRevoke": "Опозови", + "ButtonResume": "Настави", + "ButtonRestart": "Покрени поново", + "ButtonResetPassword": "Ресетуј шифру", + "ButtonResetEasyPassword": "Ресетујте једноставан ПИН код", + "ButtonRepeat": "Пусти поново", + "ButtonRename": "Преименуј", + "ButtonRemove": "Уклони", + "ButtonRefreshGuideData": "Освежи податке водича", + "ButtonRefresh": "Освежи", + "ButtonQuickStartGuide": "Кратки водич за почетак", + "ButtonProfile": "Профил", + "ButtonPreviousTrack": "Претходна нумера", + "ButtonPlay": "Пусти", + "ButtonEditOtherUserPreferences": "Уредите профил корисника, слику и личне поставке.", + "ButtonEditImages": "Уреди слике", + "ButtonEdit": "Уреди", + "BurnSubtitlesHelp": "Одређује да ли сервер треба да кодира титлове приликом транскодирања видео записа. Избегавање тога увелико ће побољшати перформансе. Изаберите Аутоматски да бисте снимили формате засноване на слици (VOBSUB, PGS, SUB, IDX) и одређене ASS или SSA титлове.", + "BoxSet": "Бокс сет", + "BookLibraryHelp": "Подржани су аудио и текстуалне књиге. Прегледајте {0} водич за именовање књига {1}.", + "Banner": "Банер", + "Backdrops": "Позадине", + "Backdrop": "Позадина", + "Auto": "Ауто", + "AuthProviderHelp": "Изаберите провајдера аутентификације који ће се користити за аутентификацију лозинке овог корисника.", + "AspectRatio": "Однос страна", + "AskAdminToCreateLibrary": "Затражите од администратора да креира библиотеку.", + "Ascending": "Узлазни", + "AsManyAsPossible": "Што је више могуће", + "Artist": "Уметник", + "Art": "Уметност", + "Anytime": "Било када", + "AlwaysPlaySubtitlesHelp": "Титлови који одговарају језичким преференцијама учитават ће се без обзира на језик звука.", + "AllowedRemoteAddressesHelp": "Листа одвојена зарезима ИП адреса или уноса ИП / мрежне маске за мреже којима ће се омогућити даљинско повезивање. Ако је празно, све удаљене адресе ће бити дозвољене.", + "AllowRemoteAccessHelp": "Ако није укључено, све удаљене везе биће блокиране.", + "AllowFfmpegThrottlingHelp": "Кад се транскодовање довољно удаљи од тренутне позиције репродукције, паузирајте поступак тако да троши мање ресурса. Ово је најкорисније када се гледа без тражења. Искључите ово ако имате проблема са репродукцијом.", + "AllowFfmpegThrottling": "Регулише Транскодовање", + "AllowOnTheFlySubtitleExtractionHelp": "Уграђени титлови се могу издвојити из видео записа и доставити клијентима у обичном тексту како би се спречило транскодирање видео записа. У неким системима ово може дуго трајати и може проузроковати да се репродукција видео записа заустави током екстракције. Онемогућите ово да се уграђени титлови уграђују видео транскодирањем када их клијент не подржава.", + "AllComplexFormats": "Сви сложени формати (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AlbumArtist": "Извођач албума", + "Album": "Албум", + "AirDate": "Премијера", + "AdditionalNotificationServices": "Прегледајте каталог додатака да бисте инсталирали сервисе за обавештења.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/sv.json b/src/strings/sv.json index 595780f0704..621172f7eaa 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -1,8 +1,8 @@ { - "AccessRestrictedTryAgainLater": "För närvarande är åtkomst begränsad. Försök igen senare.", + "AccessRestrictedTryAgainLater": "För närvarande är åtkomsten begränsad. Försök igen senare.", "Actor": "Skådespelare", "Add": "Lägg till", - "AddItemToCollectionHelp": "Lägg till objekt till samlingar genom att först söka efter dem och sen högerklicka eller tappa upp menyn för att lägga till dem.", + "AddItemToCollectionHelp": "Lägg till objekt i samlingar genom att söka efter dem och använda deras högerklick- eller pekmeny för att lägga till dem i en samling.", "AddToCollection": "Lägg till i samling", "AddToPlayQueue": "Lägg till i spelkö", "AddToPlaylist": "Lägg till i spellista", @@ -13,20 +13,20 @@ "Albums": "Album", "All": "Alla", "AllChannels": "Alla kanaler", - "AllComplexFormats": "Alla komplexa format (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alla komplexa format (ASS, SSA, VOBSUB, PGS, SUB/IDX, ...)", "AllEpisodes": "Alla avsnitt", "AllLanguages": "Alla språk", "AllLibraries": "Alla bibliotek", "AllowHWTranscodingHelp": "Tillåt TV-mottagaren att omkoda strömmar. Det kan minska behovet av omkodning på Jellyfin Servern.", "AllowOnTheFlySubtitleExtraction": "Tillåt undertextsextrahering under uppspelning", - "AllowOnTheFlySubtitleExtractionHelp": "Inbäddade undertexter kan extraheras ur videor och skickas till klienter i textformat för att förhindra omkodning. I vissa system kan detta ta en lång tid och stoppa videouppspelningen under extraheringsprocessen. Avaktivera detta för att bränna in inbäddade undertexter genom omkodning när de inte stöds av klienten.", + "AllowOnTheFlySubtitleExtractionHelp": "Inbäddade undertexter kan extraheras ur videor och skickas till klienter i textformat för att förhindra omkodning. I vissa system kan detta ta en lång tid och förhindra videouppspelningen under extraheringsprocessen. Avaktivera detta för att bränna in inbäddade undertexter genom omkodning när de inte stöds av klienten.", "AllowRemoteAccess": "Tillåt fjärranslutningar till denna Jellyfin-server.", "AllowRemoteAccessHelp": "Om avaktiverat så blockeras alla fjärranslutningar.", - "AlwaysPlaySubtitles": "Visa alltid undertexter", + "AlwaysPlaySubtitles": "Visa alltid", "AlwaysPlaySubtitlesHelp": "Undertexter på det önskade språket kommer att laddas oavsett ljudspårets språk.", - "AnyLanguage": "Vilket språk som helst", + "AnyLanguage": "Alla språk", "Anytime": "När som helst", - "AroundTime": "Runt {0}", + "AroundTime": "Runt", "Art": "Grafik", "Artists": "Artister", "AsManyAsPossible": "Så många som möjligt", @@ -40,13 +40,13 @@ "BirthDateValue": "Född: {0}", "BirthLocation": "Födelseort", "BirthPlaceValue": "Födelseort:{0}", - "BookLibraryHelp": "Ljud- och textböcker stöds. Läs {0}boknamngivningsguide{1}.", + "BookLibraryHelp": "Ljud- och textböcker stöds. Läs {0} boknamngivningsguiden {1}.", "Books": "Böcker", "Box": "Omslag", "BoxRear": "Omslag (baksida)", "Browse": "Bläddra", "BrowsePluginCatalogMessage": "Besök katalogen för att se tillgängliga tillägg.", - "BurnSubtitlesHelp": "Avgör ifall servern ska \"bränna in\" undertexterna under videokonverteringen, beroende på undertextsformatet. Att undvika inbränning av undertexter kommer att förbättra prestandan på servern. Välj Automatisk för att bränna bild-baserade formats (ex. VOBSUB, PGS, SUB/IDX, etc.) och vissa ASS/SSA undertexter.", + "BurnSubtitlesHelp": "Avgör ifall servern ska \"bränna in\" undertexterna under transkodning. Att undvika detta förbättrar prestandan avsevärt. Välj \"Automatisk\" för att bränna bild-baserade format (ex. VOBSUB, PGS, SUB/IDX, ...) och vissa ASS/SSA-undertexter.", "ButtonAdd": "Lägg till", "ButtonAddMediaLibrary": "Lägg till mediabibliotek", "ButtonAddScheduledTaskTrigger": "Lägg till utlösare", @@ -173,7 +173,7 @@ "DisplayInOtherHomeScreenSections": "Visa sektioner på hemskärmen som till exempel senast media och fortsätt kolla på", "DisplayMissingEpisodesWithinSeasons": "Visa saknade avsnitt i säsonger", "DisplayMissingEpisodesWithinSeasonsHelp": "Detta måste också vara aktiverat för TV-bibliotek i serverkonfigurationen.", - "DisplayModeHelp": "Välj vilken typ av skärm som du kör Jellyfin på.", + "DisplayModeHelp": "Välj vilken layout du vill använda i gränssnittet.", "DoNotRecord": "Spela inte in", "Down": "Ner", "Download": "Ladda ned", @@ -450,7 +450,7 @@ "Images": "Bilder", "ImportFavoriteChannelsHelp": "Aktivera för att endast importera kanaler som är märkta som favoriter på den här TV-mottagaren.", "ImportMissingEpisodesHelp": "Om aktiverat importeras information om saknade episoder till din Jellyfin-databas och visas i seriesäsongerna. Detta kan innebära längre tidsåtgång för biblioteksskanningar.", - "InstallingPackage": "Installerar {0}", + "InstallingPackage": "Installerar {0} (version {1})", "InstantMix": "Omedelbar mix", "ItemCount": "{0} objekt", "Items": "Objekt", @@ -540,7 +540,7 @@ "LabelEmbedAlbumArtDidl": "Bädda in omslagsbilder i Didl", "LabelEmbedAlbumArtDidlHelp": "Vissa enheter föredrar den här metoden att ta fram omslagsbilder. Andra kanske avbryter avspelningen om detta val är aktiverat.", "LabelEnableAutomaticPortMap": "Aktivera automatisk koppling av portar", - "LabelEnableAutomaticPortMapHelp": "Automatisk länkning av publik och lokal port via UPnP. Detta kanske inte fungerar med alla routrar. Obs. Ingenting kommer att ändras förrän servern startats om.", + "LabelEnableAutomaticPortMapHelp": "Automatisk länkning av publik port på routern och lokal port på servern via UPnP. Detta kanske inte fungerar med alla routrar och nätverks konfigurationer. Obs. Ingenting kommer att ändras förrän servern startats om.", "LabelEnableBlastAliveMessages": "Skicka ut \"jag lever\"-meddelanden", "LabelEnableBlastAliveMessagesHelp": "Aktivera detta om andra UPnP-enheter på nätverket har problem att upptäcka servern.", "LabelEnableDlnaClientDiscoveryInterval": "Intervall för upptäckt av klienter (i sekunder)", @@ -898,9 +898,9 @@ "NumLocationsValue": "{0} mappar", "Off": "Av", "OneChannel": "En kanal", - "OnlyForcedSubtitles": "Endast tvingande undertexter", + "OnlyForcedSubtitles": "Endast påtvingad", "OnlyForcedSubtitlesHelp": "Endast undertexter markerade som tvingande kommer att laddas.", - "OnlyImageFormats": "Endast image-format (VOBSUB, PGS, SUB, etc)", + "OnlyImageFormats": "Endast bildbaserade format (VOBSUB, PGS, SUB, etc)", "OptionAdminUsers": "Administratörer", "OptionAlbumArtist": "Albumartist", "OptionAllUsers": "Alla användare", @@ -988,7 +988,7 @@ "OptionMissingEpisode": "Saknade avsnitt", "OptionMonday": "Måndag", "OptionNameSort": "Namn", - "OptionNew": "Ny...", + "OptionNew": "Ny…", "OptionNone": "Inga", "OptionOnAppStartup": "När servern startar", "OptionOnInterval": "Med visst intervall", @@ -1029,9 +1029,9 @@ "OptionWeekly": "Varje vecka", "OriginalAirDateValue": "Ursprungligt sändningsdatum: {0}", "Overview": "Översikt", - "PackageInstallCancelled": "Installationen av {0} avbröts.", - "PackageInstallCompleted": "Installationen av {0} slutfördes.", - "PackageInstallFailed": "Installationen av {0} misslyckades.", + "PackageInstallCancelled": "Installationen av {0} (version {1}) avbröts.", + "PackageInstallCompleted": "Installationen av {0} (version {1}) slutfördes.", + "PackageInstallFailed": "Installationen av {0} (version {1}) misslyckades.", "ParentalRating": "Parental Rating", "PasswordMatchError": "Lösenordet och bekräftelsen måste överensstämma.", "PasswordResetComplete": "Lösenordet har återställts.", @@ -1097,7 +1097,6 @@ "RepeatOne": "Upprepa en gång", "ReplaceAllMetadata": "Ersätt all metadata", "ReplaceExistingImages": "Skriv över befintliga bilder", - "RestartPleaseWaitMessage": "Vänligen vänta medan Jellyfin-servern stängs av och startar om. Detta kan ta en minut eller två.", "ResumeAt": "Återuppta från {0}", "RunAtStartup": "Kör vid uppstart", "Runtime": "Speltid", @@ -1317,7 +1316,6 @@ "HeaderImageOptions": "Bildalternativ", "Absolute": "Komplett", "HeaderFavoritePeople": "Favoritmänniskor", - "HeaderRestartingServer": "Startar om server", "HeaderStatus": "Status", "LabelPostProcessor": "Program för efterbehandling:", "LabelPleaseRestart": "Ändringar genomförs efter att webbklienten manuellt har laddats om .", @@ -1344,8 +1342,6 @@ "LabelSize": "Storlek:", "LabelServerName": "Servernamn:", "LabelSecureConnectionsMode": "Säker uppkopplings läge:", - "EnableFastImageFadeInHelp": "Aktivera snabbare fade-in animationer för laddade bilder", - "EnableFastImageFadeIn": "Snabb bild fade-in", "LabelPostProcessorArgumentsHelp": "Använd {path} som sökväg till inspelade filen.", "LabelPostProcessorArguments": "Post-processor kommandoradsargument:", "LabelDroppedFrames": "Tappade ramar:", @@ -1465,7 +1461,65 @@ "LabelTranscodingFramerate": "Omkodning framerate:", "LabelTranscodes": "Omkodningar:", "LabelTranscodePath": "Omkodning sökväg:", - "PlaybackErrorNoCompatibleStream": "Klienten kunde inte identifieras och servern skickar inte ett kompatibelt medieformat.", + "PlaybackErrorNoCompatibleStream": "Klienten är inte kompatibel med mediaformatet och servern skickar inte ett kompatibelt medieformat.", "AllowFfmpegThrottlingHelp": "När omkodningen går uppspelningen långt i förväg pausas den tills vidare för att spara resurser. Detta fungerar bäst när du kollar utan att spola mycket; stäng av ifall spelaren krånglar.", - "AllowFfmpegThrottling": "Begränsad omkodning" + "AllowFfmpegThrottling": "Begränsad omkodning", + "Album": "Album", + "EveryXHours": "Varje {0} timmar", + "EveryHour": "Varje timme", + "EveryXMinutes": "Varje {0} minut(er)", + "OnApplicationStartup": "Vid programstart", + "DailyAt": "Dagligen vid {0}", + "PersonRole": "som {0}", + "ListPaging": "{0}-{1} av {2}", + "WriteAccessRequired": "Jellyfin Server kräver skrivrättigheter till denna katalog. Säkerställ skrivrättigheter och försök igen.", + "PathNotFound": "Sökvägen hittades inte. Säkerställ att sökvägen är korrekt och försök igen.", + "Track": "Spår", + "Season": "Säsong", + "ReleaseGroup": "Releasegrupp", + "PreferEmbeddedEpisodeInfosOverFileNames": "Föredra inbäddad avsnittsinformation före filnamn", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Detta använder avsnittets information från inbäddad metadata om tillgängligt.", + "Person": "Person", + "OtherArtist": "Annan artist", + "Movie": "Film", + "Episode": "Avsnitt", + "ClientSettings": "Klientinställningar", + "BoxSet": "Samlingsbox", + "Artist": "Artist", + "ButtonTogglePlaylist": "Spellista", + "ButtonToggleContextMenu": "Mer", + "AlbumArtist": "Albumartist", + "LabelLibraryPageSize": "Bibliotekets sidstorlek:", + "LabelDeinterlaceMethod": "Deinterlacing-metod:", + "WeeklyAt": "{0}s vid {1}", + "LastSeen": "Senast sedd {0}", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Filter": "Filter", + "New": "Ny", + "MessageUnauthorizedUser": "Du har inte behörighet att komma åt servern just nu. Kontakta din serveradministratör för mer information.", + "HeaderFavoritePlaylists": "Favoritspellista", + "OnWakeFromSleep": "Vid start från vilande läge", + "UnsupportedPlayback": "Jellyfin kan inte dekryptera inehåll skyddat av DRM men allt inehåll kommer ändå försökas, även skyddade titlar. Vissa filer kan se helt svart ut på grund av kryptering eller andra funktioner som inte stöds, till exempel interaktiva titlar.", + "LabelLibraryPageSizeHelp": "Sätter en begränsad sidstorlek i bibliotek. Sätt 0 för att avaktivera begränsad sidstorlek.", + "ApiKeysCaption": "Lista av aktiva API-nycklar", + "DeinterlaceMethodHelp": "Välj metod för borttagning av inflätning vid konvertering av inflätat inehåll.", + "TabDVR": "PVR", + "SaveChanges": "Spara ändringar", + "LabelRequireHttps": "Kräv HTTPS", + "LabelChromecastVersion": "Chromecast-version", + "LabelEnableHttpsHelp": "Gör det möjligt för servern att lyssna på den konfigurerade HTTPS-porten. Ett giltigt certifikat måste också konfigureras för att detta ska fungera.", + "LabelEnableHttps": "Aktivera HTTPS", + "HeaderServerAddressSettings": "Serveradressinställningar", + "HeaderRemoteAccessSettings": "Inställningar för fjärråtkomst", + "HeaderHttpsSettings": "HTTPS-inställningar", + "HeaderDVR": "PVR", + "ButtonSyncPlay": "SyncPlay", + "LabelStable": "Stabil", + "HeaderSyncPlaySelectGroup": "Gå med i en grupp", + "EnableDecodingColorDepth10Vp9": "Aktivera 10-Bitars hårdvaru avcodning för VP9", + "EnableDecodingColorDepth10Hevc": "Aktivera 10-Bitars hårdvaru avcodning för HEVC", + "HeaderSyncPlayEnabled": "SyncPlay påslaget", + "EnableDetailsBannerHelp": "Visa en bannerbild högst upp på sidan för detaljsidan .", + "EnableDetailsBanner": "Information banner" } diff --git a/src/strings/tr.json b/src/strings/tr.json index 5588a3091e5..3e3c94d7426 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -124,8 +124,8 @@ "LabelSelectUsers": "Kullanıcıları seç:", "LabelSerialNumber": "Seri Numarası", "LabelStatus": "Durum:", - "LabelTime": "Zaman:", - "LabelTimeLimitHours": "Zaman limiti (saat):", + "LabelTime": "Süre:", + "LabelTimeLimitHours": "Süre limiti (saat):", "LabelType": "Tür:", "LabelUser": "Kullanıcı:", "LabelUserLibrary": "Kullanıcı Kütüphanesi:", @@ -317,7 +317,7 @@ "Director": "Yönetmen", "Directors": "Yöneticiler", "Disabled": "Deaktif", - "DisplayModeHelp": "Jellyfin’i çalıştırdığınız ekran türünü seçin.", + "DisplayModeHelp": "Arayüz için kullanılmasını istediğiniz şablonu seçin.", "DoNotRecord": "Kaydetme", "Down": "Aşağı", "Download": "İndir", @@ -365,7 +365,7 @@ "DisplayInMyMedia": "Ana ekranda görüntüleme", "DisplayInOtherHomeScreenSections": "En son medya gibi ana ekran bölümlerinde görüntüleyin ve izlemeye devam edin", "EnableBackdrops": "Arka planında", - "BurnSubtitlesHelp": "Altyazı formatına bağlı olarak video dönüştürülürken sunucunun altyazılarda yazıp yazmayacağını belirler. Altyazılarda yanmaktan kaçınmak, sunucu performansını iyileştirir. Görüntü tabanlı biçimleri (VOBSUB, PGS, SUB / IDX, vb.) Ve bazı ASS / SSA altyazılarını yazmak için Otomatik'i seçin.", + "BurnSubtitlesHelp": "Sunucunun video işlendiği esnada, altyazının görüntüye gömülmesini sağlar. Performansı çok düşürür, zorunda kalmadıkça bu özelliği seçmeyin. Görüntü tabanlı biçimleri (VOBSUB, PGS, SUB / IDX, vb.) Ve bazı ASS / SSA altyazıların görüntüye gömülmesi için Otomatik'i seçin.", "ConfirmDeleteItem": "Bu öğeyi silmek, onu hem dosya sisteminden hem de medya kütüphanenizden siler. Devam etmek istediğinize emin misiniz?", "ValueSpecialEpisodeName": "Özel - {0}", "DeviceAccessHelp": "Bu, yalnızca benzersiz şekilde tanımlanabilen ve tarayıcı erişimini engellemeyen cihazlar için geçerlidir. Kullanıcı cihazlarına erişimin filtrelenmesi, burada onaylanana kadar yeni cihazları kullanmalarını önler.", @@ -415,7 +415,7 @@ "DefaultSubtitlesHelp": "Altyazılar, gömülü meta verilerdeki varsayılan ve zorunlu bayraklara göre yüklenir. Birden fazla seçenek olduğunda dil tercihleri göz önünde bulundurulur.", "DeleteDeviceConfirmation": "Bu cihazı silmek istediğinden emin misin? Bir kullanıcı bir sonraki oturum açışında yeniden görünecektir.", "DisplayMissingEpisodesWithinSeasons": "Sezonlardaki eksik bölümleri görüntüleme", - "AlwaysPlaySubtitles": "Her zaman altyazıları oynat", + "AlwaysPlaySubtitles": "Altyazıları her zaman oynat", "CopyStreamURLSuccess": "URL başarıyla kopyalandı.", "DateAdded": "Ekleme Tarihi", "DatePlayed": "Oynanan tarih", @@ -569,7 +569,6 @@ "HeaderSchedule": "Zamanla", "HeaderRunningTasks": "Çalışan Görevler", "HeaderRevisionHistory": "Revizyon Geçmişi", - "HeaderRestartingServer": "Sunucu Yeniden Başlıyor", "HeaderRestart": "Yeniden Başlat", "HeaderRemoveMediaLocation": "Medya Konumunu Kaldır", "HeaderRemoveMediaFolder": "Medya Klasörünü Kaldır", @@ -663,7 +662,7 @@ "Label3DFormat": "3D Formatı:", "Kids": "Çocuklar", "ItemCount": "{0} nesne", - "InstallingPackage": "Yükleniyor {0}", + "InstallingPackage": "Yükleniyor {0} (versiyon {1})", "Images": "Resimler", "Identify": "Tanımla", "Horizontal": "Yatay", @@ -672,12 +671,12 @@ "HeaderYears": "Yıl", "HeaderXmlSettings": "Xml Ayarları", "HeaderXmlDocumentAttributes": "Xml Döküman Öznitelikleri", - "HeaderXmlDocumentAttribute": "Xml Dökümanı Öznitelik", + "HeaderXmlDocumentAttribute": "Xml Döküman Özniteliği", "HeaderUser": "Kullanıcı", "HeaderUploadImage": "Resim Yükle", "HeaderUpcomingOnTV": "TV'de Yaklaşan", "HeaderTypeText": "Metin Gir", - "HeaderTunerDevices": "Tuner Cihazları", + "HeaderTunerDevices": "Alıcı Cihazları", "HeaderThisUserIsCurrentlyDisabled": "Bu kullanıcı şu anda pasif", "HeaderTags": "Etiketler", "HeaderSubtitleProfiles": "Altyazı Profilleri", @@ -710,5 +709,65 @@ "OptionNone": "Hiçbiri", "None": "Hiçbiri", "HeaderNavigation": "Navigasyon", - "AllowFfmpegThrottling": "Video Kodlamasını Limitle" + "AllowFfmpegThrottling": "Video Kodlamasını Limitle", + "Artist": "Oyuncu", + "Album": "Albüm", + "EveryXMinutes": "Her {0} dakika", + "OnWakeFromSleep": "Uyku modundan çıkılarak uyanıldığında", + "DailyAt": "Her gün {0} 'de", + "LastSeen": "Son görülme {0}", + "WriteAccessRequired": "Jellyfin Sunucusu bu dizine yazma iznine ihtiyaç duymakta. Lütfen yazma izin ayarlarını kontrol edip tekrar deneyiniz.", + "PathNotFound": "Gösterilen yol bulunamadı. Lütfen gösterdiğiniz yolun doğru olduğundan emin olun ve tekrar deneyin.", + "EveryHour": "Her saat", + "EveryXHours": "Her {0} saatte bir", + "OnApplicationStartup": "Uygulama başladığında", + "HttpsRequiresCert": "Güvenli bağlantı kurulabilmesi için, Let's Encrypt gibi güvenilir bir sertifika sağlayıcısından edinilmiş sertifikayı tanıtmanız gerekmektedir. Lütfen bir sertifika tanımlayın ya da güvenli bağlantı seçeneğini devre dışı bırakın.", + "Home": "Anasayfa", + "HideWatchedContentFromLatestMedia": "İzlenmiş içeriği En Son Eklenenler klasöründe gösterme", + "HeaderTypeImageFetchers": "Görüntü İndirici", + "HeaderTracks": "Parçalar", + "HeaderSeriesStatus": "Dizi Durumu", + "HeaderSeriesOptions": "Dizi Seçenekleri", + "Episode": "Bölüm", + "DeinterlaceMethodHelp": "Tarama tipi görüntülerde titreşimi yok etmek için kullanılmasını istediğiniz metodu seçin.", + "ClientSettings": "Kullanıcı Ayarları", + "BoxSet": "Seri Filmler", + "AskAdminToCreateLibrary": "Kütüphane oluşturmak için yöneticiden izin iste.", + "AllowFfmpegThrottlingHelp": "Video dönüşüm işlemleri yeterince ilerlediyse kaynak tüketimini azaltmak için durdur. İleri/geri sarma işlemlerinin az yapıldığı durumlarda çok kullanışlıdır. Oynatım sorunları ile karşılaşırsanız bu özelliği kapatın.", + "AlbumArtist": "Sanatçı", + "HeaderTuners": "Alıcılar", + "HeaderTranscodingProfileHelp": "Kodlama gerekince hangi formatın kullanılacağını belirtmek için kodlama profili ekle.", + "ButtonTogglePlaylist": "Liste", + "OptionAllowRemoteSharedDevices": "Paylaşılan cihazların uzaktan kontrolüne izin ver", + "OptionAllowRemoteControlOthers": "Diğer kullanıcıların uzaktan kontrolüne izin ver", + "LabelTitle": "Başlık:", + "LabelTheme": "Tema:", + "LabelTextSize": "Metin boyutu:", + "LabelTextColor": "Metin rengi:", + "LabelTextBackgroundColor": "Metin arkaplan rengi:", + "LabelTag": "Etiket:", + "LabelSyncPlayAccessNone": "Bu kullanıcı için devre dışı", + "LabelSyncPlayAccessJoinGroups": "Kullanıcının gruplara katılmasına izin ver", + "LabelSyncPlayAccessCreateAndJoinGroups": "Kullanıcının gruplar oluşturmasına ve gruplara katılmasına izin ver", + "LabelSyncPlayLeaveGroup": "Gruptan ayrıl", + "LabelSyncPlayNewGroupDescription": "Yeni grup oluştur", + "LabelSyncPlayNewGroup": "Yeni grup", + "LabelSyncPlaySyncMethod": "Senkronizasyon yöntemi:", + "MillisecondsUnit": "ms", + "LabelSupportedMediaTypes": "Desteklenen Ortam Türleri:", + "LabelSubtitlePlaybackMode": "Altyazı biçimi:", + "LabelSubtitleFormatHelp": "Örnek: srt", + "LabelSubtitles": "Altyazılar", + "LabelStreamType": "Akış türü:", + "LabelSubtitleDownloaders": "Altyazı indiriciler:", + "LabelStopping": "Durduruluyor", + "LabelSoundEffects": "Ses efektleri:", + "LabelSortOrder": "Sıralama düzeni:", + "LabelSortBy": "Sıralama türü:", + "LabelSkipIfGraphicalSubsPresent": "Video halihazırda gömülü altyazı barındırıyorsa atla", + "LabelSkipIfAudioTrackPresent": "Varsayılan ses izi indirme diliyle uyuşuyorsa atla", + "LabelSize": "Boyut:", + "LabelSimultaneousConnectionLimit": "Eşzamanlı yayın limiti:", + "LabelServerName": "Sunucu adı:", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/uk.json b/src/strings/uk.json index 57bd6786435..0b939644748 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -20,9 +20,9 @@ "HeaderAutomaticUpdates": "Автоматичне оновлення", "HeaderBooks": "Книги", "HeaderDeleteDevice": "Видалить пристрій", - "HeaderLatestEpisodes": "Останні епізоди", - "HeaderLatestMedia": "Останні медіа", - "HeaderLatestMovies": "Останні фільми", + "HeaderLatestEpisodes": "Нещодавно переглянуті серії", + "HeaderLatestMedia": "Нещодавно переглянуті", + "HeaderLatestMovies": "Нещодавні фільми", "HeaderLatestMusic": "Остання музика", "HeaderMovies": "Фільми", "HeaderSeasons": "Сезони", @@ -93,21 +93,72 @@ "ValueSeriesCount": "{0} серій", "ValueSongCount": "{0} пісень", "AddToPlaylist": "Додати до списку відтворення", - "AccessRestrictedTryAgainLater": "Доступ тимчасово заборонений. Спробуйте пізніше.", + "AccessRestrictedTryAgainLater": "На цей момент доступ заборонений. Повторіть спробу пізніше.", "Actor": "Виконавець", - "AllLanguages": "Всі мови", - "AllLibraries": "Всі бібліотеки", + "AllLanguages": "Усі мови", + "AllLibraries": "Усі бібліотеки", "AddToCollection": "Додати до колекції", "AddToPlayQueue": "Додати до черги відтворення", "All": "Всі", "AllChannels": "Всі канали", "AllEpisodes": "Всі епізоди", - "AllowRemoteAccess": "Дозволити віддалене підключення до цього сервера Jellyfin", - "AlwaysPlaySubtitles": "Завжди відтворювати субтитри", + "AllowRemoteAccess": "Дозволити віддалене підключення до цього сервера Jellyfin.", + "AlwaysPlaySubtitles": "Завжди вмикати субтитри", "AnyLanguage": "Будь-яка мова", "Anytime": "Завжди", "Add": "Додати", "AddedOnValue": "Додано", "Albums": "Альбоми", - "Absolute": "Абсолютний" + "Absolute": "Абсолютний", + "HeaderFavoriteEpisodes": "Улюблені серії", + "Movies": "Фільми", + "Collections": "Колекції", + "Folders": "Директорії", + "HeaderNextUp": "Наступний", + "HeaderAlbumArtists": "Виконавці альбомів", + "HeaderFavoriteSongs": "Улюблені пісні", + "Favorites": "Улюблені", + "HeaderFavoriteAlbums": "Улюблені альбоми", + "Genres": "Жанри", + "Books": "Книги", + "Artists": "Виконавці", + "HeaderLiveTV": "Ефірне ТБ", + "Channels": "Канали", + "HeaderFavoriteArtists": "Улюблені виконавці", + "HeaderFavoriteShows": "Улюблені шоу", + "HeaderContinueWatching": "Продовжити перегляд", + "AddItemToCollectionHelp": "Додайте елементи до колекції за допомогою пошуку або кліком правої кнопкої миші чи натисненням на меню.", + "AllowedRemoteAddressesHelp": "Список з комами, в якості розділювачів, визначає IP-адреси та IP/мережеві маски для мереж, яким дозволено підключатись віддалено. Якщо залишити строку пустою, то усі віддалені підключення будуть дозволені.", + "AllowRemoteAccessHelp": "Якщо не відмічено прапорцем, усі віддалені підключення будуть заблоковані.", + "AllowFfmpegThrottling": "Примусово обмежити перекодування", + "AllowMediaConversionHelp": "Надайте або забороніть доступ для можливості перетворення медіа.", + "AllowMediaConversion": "Дозволити перетворення медіа", + "Alerts": "Термінові сповіщення", + "AlbumArtist": "Виконавець альбому", + "Album": "Альбом", + "AdditionalNotificationServices": "Пошук у каталозі плагінів для встановлення додаткових сервісів сповіщень.", + "ShowYear": "Відобразити рік", + "ShowTitle": "Відобразити назву", + "Raised": "Піднятий", + "OptionResElement": "кожний елемент", + "DropShadow": "Тінь", + "Blacklist": "Чорний список", + "BirthLocation": "Місце народження", + "Banner": "Обкладинка", + "AutoBasedOnLanguageSetting": "Автоматично (на основі поточної мови)", + "Auto": "Автоматично", + "AuthProviderHelp": "Оберіть сервіс аутентифікації, який буде використаний з поточним паролем користувача.", + "Audio": "Аудіо", + "AttributeNew": "Новий", + "AspectRatio": "Відношення сторін", + "AskAdminToCreateLibrary": "Попросіть адміністратора створити бібліотеку.", + "Ascending": "У порядку зростання", + "AsManyAsPossible": "Настільки багато наскільки можливо", + "Artist": "Виконавець", + "Art": "Мистецтво", + "AllowOnTheFlySubtitleExtractionHelp": "Вбудовані субтитри можуть бути експортовані з відео і надіслані, по черзі, клієнтам у вигляді тексту. Це допоможе уникнути перекодування відео. На деяких системах, перекодування може зайняти тривалий час і зупинити відтворення відео, для того щоб забезпечити експортування. Вимкнення цієї функції дозволить вбудованим субтитрам бути інтегрованим у відео, під час перекодування, якщо вбудовані субтитри не підтримуються на стороні клієнта.", + "AllowOnTheFlySubtitleExtraction": "Дозволити експортування субтитрів «на льоту»", + "AllowHWTranscodingHelp": "Дозволити клієнту перекодування на «на льоту». Це дозволить відмикати перекодування, якщо вона вимагається сервером.", + "AllComplexFormats": "Усі складні формати (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/ur_PK.json b/src/strings/ur_PK.json new file mode 100644 index 00000000000..2942bba2a6d --- /dev/null +++ b/src/strings/ur_PK.json @@ -0,0 +1,3 @@ +{ + "ButtonSyncPlay": "SyncPlay" +} diff --git a/src/strings/vi.json b/src/strings/vi.json index e32272bdaf6..a5b319a86f7 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -30,7 +30,7 @@ "HeaderLatestEpisodes": "Các tập phim mới nhất", "HeaderLatestMovies": "Phim mới nhất", "HeaderRecentlyPlayed": "Phát gần đây", - "HeaderSeries": "Series:", + "HeaderSeries": "Series", "HeaderStatus": "Trạng thái", "HeaderSystemDlnaProfiles": "Hồ sơ hệ thống", "HeaderUsers": "dùng", @@ -42,7 +42,7 @@ "LabelEnableDlnaPlayTo": "Cho phép DLNA chạy để", "LabelEvent": "Sự kiện:", "LabelFinish": "Kết thúc", - "LabelLanguage": "Ngôn ngữ", + "LabelLanguage": "Ngôn ngữ:", "LabelName": "Tên:", "LabelNewPassword": "Mật khẩu mới:", "LabelNewPasswordConfirm": "Xác nhận mật khẩu mới:", @@ -51,7 +51,7 @@ "LabelSaveLocalMetadata": "Lưu các ảnh nghệ thuật và metadata vào trong các thư mục media", "LabelSaveLocalMetadataHelp": "Lưu các ảnh nghệ thuật và metadata vào trong các thư mục media, sẽ đưa chúng vào một nơi bạn có thể chỉnh sửa dễ dàng hơn.", "LabelTime": "Thời gian:", - "LabelYourFirstName": "Tên của Bạn", + "LabelYourFirstName": "Tên của bạn:", "LabelYoureDone": "Bạn đã hoàn thành!", "MaxParentalRatingHelp": "Nội dung với đánh giá cao hơn sẽ được ẩn đi từ người dùng này.", "MessageNothingHere": "Không có gì ở đây.", @@ -87,7 +87,7 @@ "OptionTrackName": "Tên bài", "ParentalRating": "Parental Rating", "PasswordMatchError": "Mật khẩu và mật khẩu xác nhận cần phải khớp nhau .", - "PasswordResetComplete": "Mật khẩu đã được reset", + "PasswordResetComplete": "Mật khẩu đã được cài đặt lại.", "PasswordResetConfirmation": "Bạn có chắc muốn reset mật khẩu?", "PasswordSaved": "Mật khẩu đã được lưu.", "Saturday": "Thứ Bảy", @@ -116,7 +116,7 @@ "ThisWizardWillGuideYou": "Thủ thuật này sẽ hướng dẫn quá trình cài đặt cho bạn. Để bắt đầu, vui lòng lựa chọn ngôn ngữ bạn ưa thích.", "UninstallPluginConfirmation": "Bạn có chắc muốn gỡ bỏ{0}?", "UninstallPluginHeader": "Gỡ bỏ Plugin", - "AccessRestrictedTryAgainLater": "Truy cập hiện đang hạn chế. Hãy thử lại sau.", + "AccessRestrictedTryAgainLater": "Truy cập hiện đang bị hạn chế. Hãy thử lại sau.", "AddToCollection": "Thêm vào bộ sưu tập", "Actor": "Diễn viên", "ButtonRevoke": "Thu hồi", @@ -149,7 +149,7 @@ "ButtonFullscreen": "Toàn màn hình", "ButtonForgotPassword": "Quên mật khẩu", "ButtonFilter": "Lọc", - "ButtonEditOtherUserPreferences": "Chỉnh sửa hồ sơ, hình ảnh và sở thích cá nhân.", + "ButtonEditOtherUserPreferences": "Chỉnh sửa thông tin, hình ảnh và sở thích cá nhân.", "ButtonEditImages": "Sửa hình ảnh", "ButtonEdit": "Sửa", "ButtonDownload": "Tải", @@ -167,11 +167,11 @@ "ButtonAddScheduledTaskTrigger": "Thêm kích hoạt", "ButtonAddMediaLibrary": "Thêm thư viện Media", "ButtonAddImage": "Thêm hình ảnh", - "BurnSubtitlesHelp": "Xác định xem máy chủ có ghi phụ đề khi chuyển đổi video hay không tùy thuộc vào định dạng phụ đề. Tránh ghi trong phụ đề sẽ cải thiện hiệu suất máy chủ. Chọn Tự động để ghi hình ảnh dựa trên các định dạng (VOBSUB, PGS, SUB / IDX, v.v.) và phụ đề ASS/SSA nhất định.", + "BurnSubtitlesHelp": "Xác định xem máy chủ có nên ghi phụ đề khi chuyển đổi video hay không. Tránh thực hiện việc này sẽ cải thiện hiệu suất máy chủ đáng kể. Chọn Tự động để ghi các phụ đề có định dạng dựa trên hình ảnh (VOBSUB, PGS, SUB, IDX, ...) và một vài phụ đề ASS/SSA nhất định.", "Browse": "Duyệt", "BoxRear": "Hộp (mặt sau)", "Books": "Sách", - "BookLibraryHelp": "Âm thanh và sách văn bản được hỗ trợ. Xem lại {0}hướng dẫn đặt tên sách{1}.", + "BookLibraryHelp": "Âm thanh và sách văn bản được hỗ trợ. Xem lại {0} hướng dẫn đặt tên sách {1}.", "Blacklist": "Danh sách đen", "BirthPlaceValue": "Nơi sinh: {0}", "BirthLocation": "Nơi sinh", @@ -188,15 +188,15 @@ "Ascending": "Tăng dần", "AsManyAsPossible": "Càng nhiều càng tốt", "Artists": "Nghệ Sĩ", - "AroundTime": "Xunh quanh {0}", + "AroundTime": "Khoảng", "Anytime": "Bất cứ lúc nào", - "AnyLanguage": "Bất kỳ ngôn ngữ", + "AnyLanguage": "Ngôn Ngữ Bất Kỳ", "AlwaysPlaySubtitlesHelp": "Phụ đề phù hợp với sở thích ngôn ngữ sẽ được tải bất kể ngôn ngữ âm thanh.", "AlwaysPlaySubtitles": "Luôn hiển thị phụ đề", "AllowedRemoteAddressesHelp": "Danh sách địa chỉ IP được phân tách bằng dấu phẩy hoặc các mục IP/netmask cho các mạng sẽ được phép kết nối từ xa. Nếu để trống, tất cả các địa chỉ sẽ được cho phép.", "AllowRemoteAccessHelp": "Nếu không được chọn, tất cả các kết nối từ xa sẽ bị chặn.", "AllowRemoteAccess": "Cho phép kết nối từ xa đến Máy chủ Jellyfin.", - "AllowOnTheFlySubtitleExtractionHelp": "Phụ đề nhúng có thể được trích xuất từ video và dùng như văn bản thuần túy để giúp ngăn chặn chuyển mã video. Trên một số hệ thống, việc này có thể mất nhiều thời gian và khiến quá trình phát video bị đình trệ trong quá trình trích xuất. Vô hiệu hóa điều này để có phụ đề nhúng được ghi trong chuyển mã video khi chúng không được thiết bị khách hỗ trợ.", + "AllowOnTheFlySubtitleExtractionHelp": "Phụ đề nhúng có thể được trích xuất từ video và dùng như văn bản thuần túy để giúp ngăn chặn chuyển mã video. Trên một số hệ thống, việc này có thể mất nhiều thời gian và khiến quá trình phát video bị đình trệ trong quá trình trích xuất. Vô hiệu hóa điều này để có phụ đề nhúng được ghi trong video khi chuyển mã trong trường hợp chúng không được hỗ trợ trên thiết bị phát.", "AllowOnTheFlySubtitleExtraction": "Cho phép trích xuất phụ đề trực tiếp", "AllowMediaConversionHelp": "Cấp hoặc từ chối truy cập vào tính năng chuyển đổi media.", "AllowMediaConversion": "Cho phép chuyển đổi media", @@ -204,7 +204,7 @@ "AllLibraries": "Tất cả các thư viện", "AllLanguages": "Tất cả các ngôn ngữ", "AllEpisodes": "Tất cả các tập phim", - "AllComplexFormats": "Tất cả các định dạng phức tạp (ASS, SSA, VOBSUB, PGS, SUB / IDX, v.v.)", + "AllComplexFormats": "Tất cả các định dạng phức tạp (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllChannels": "Tất cả các kênh", "Alerts": "Cảnh Báo", "Albums": "Albums", @@ -215,7 +215,7 @@ "AddToPlaylist": "Thêm vào danh sách phát", "AddToPlayQueue": "Thêm vào hàng đợi", "AddItemToCollectionHelp": "Thêm các mục vào bộ sưu tập bằng cách tìm kiếm và nhấp chuột phải hoặc nhấn vào menu để thêm chúng vào bộ sưu tập.", - "Absolute": "Tuyệt Đối", + "Absolute": "Tuyệt đối", "ButtonSend": "Gửi", "ButtonSelectView": "Chọn chế độ xem", "ButtonSelectServer": "Chọn máy chủ", @@ -236,5 +236,595 @@ "ButtonStart": "Bắt đầu", "ButtonSignIn": "Đăng nhập", "ButtonShutdown": "Tắt", - "ButtonSettings": "Cài đặt" + "ButtonSettings": "Cài đặt", + "DefaultErrorMessage": "Có lỗi xảy ra trong lúc xử lý yêu cầu của bạn. Xin hãy thử lại sau.", + "Default": "Mặc định", + "DeathDateValue": "Không hoạt động: {0}", + "DatePlayed": "Ngày phát", + "DateAdded": "Ngày thêm vào", + "CustomDlnaProfilesHelp": "Tạo một bộ thiết lập tuỳ chọn dành cho một thiết bị mới hoặc thay thế một thiết lập hệ thống.", + "CriticRating": "Đánh giá phê bình", + "CopyStreamURLError": "Có lỗi xảy ra lúc sao chép URL.", + "CopyStreamURLSuccess": "URL đã được sao chép.", + "CopyStreamURL": "Sao Chép URL Phát Sóng", + "Continuing": "Tiếp tục", + "ContinueWatching": "Tiếp tục xem", + "Connect": "Kết nối", + "ConfirmEndPlayerSession": "Bạn có muốn tắt máy chủ Jellyfin trên {0}?", + "ConfirmDeletion": "Xác Nhận Xoá", + "ConfirmDeleteItems": "Xoá những mục này sẽ xoá chúng khỏi ổ cứng và thư viện của bạn. Bạn có chắc chắn muốn tiếp tục?", + "ConfirmDeleteItem": "Xoá mục này sẽ xoá nó khỏi ổ cứng và thư viện của bạn. Bạn có chắc chắn muốn tiếp tục?", + "ConfirmDeleteImage": "Bạn có chắc chắn xoá ảnh này?", + "ConfigureDateAdded": "Thiết lập cách xác định \"ngày thêm vào\" trong mục cài đặt Thư Viện của phần quản lý máy chủ Jellyfin", + "Composer": "Tác giả", + "CommunityRating": "Đánh giá của cộng đồng", + "ColorTransfer": "Chuyển đổi màu", + "ColorSpace": "Bộ mã màu", + "ColorPrimaries": "Những màu chủ đạo", + "Collections": "Bộ sưu tập", + "ClientSettings": "Cài đặt thiết bị phát", + "CinemaModeConfigurationHelp": "Chế độ Cinema giúp bạn mang lại trải nghiệm rạp chiếu phim ngay tại phòng khách với khả năng phát trailers và những đoạn mở đầu tuỳ chọn trước phần chính của bộ phim.", + "ChannelNumber": "Kênh số", + "Channels": "Kênh", + "ChannelNameOnly": "Chỉ kênh {0}", + "ChannelAccessHelp": "Chọn những kênh để chia vẻ với người dùng này. Người quản lý sẽ có thể thay đổi toàn bộ kênh bằng cách sử dụng bộ quản lý thông tin.", + "ChangingMetadataImageSettingsNewContent": "Thay đổi về thiết lập của việc tải thông tin hoặc hình ảnh sẽ chỉ có tác dụng với những nội dung mới được thêm vào thư viện. Để những thiết lập mới có tác dụng với nội dung đã có sẵn, bạn sẽ phải cập nhật lại thông tin của chúng.", + "CancelSeries": "Ngưng series", + "ButtonTogglePlaylist": "Danh sách phát", + "ButtonToggleContextMenu": "Thêm", + "BoxSet": "Tuyển tập", + "Box": "Hộp", + "Banner": "Ảnh bìa", + "Art": "Nghệ thuật", + "Artist": "Nghệ Sĩ", + "AllowFfmpegThrottlingHelp": "Tạm dừng quá trình chuyển mã hoặc chuyển đổi định dạng để tiết kiệm tài nguyên máy chủ khi việc này đã đủ để phát so với vị trí hiện tại. Điều này hữu hiệu khi không tua nhanh thường xuyên lúc nghe nhạc hoặc xem phim. Hãy tắt tính năng này nếu có hiện tượng giật lag khi nghe nhạc hoặc xem phim.", + "AllowFfmpegThrottling": "Điều tiết sự chuyển mã", + "AlbumArtist": "Nghệ sĩ Album", + "Album": "Album", + "DisplayMissingEpisodesWithinSeasonsHelp": "Thiết lập này cũng phải được kích hoạt trong thiết lập máy chủ dành cho thư viện phim bộ.", + "DisplayMissingEpisodesWithinSeasons": "Hiển thị những tập phim bị thiếu trong mỗi phần", + "DisplayInOtherHomeScreenSections": "Những phần hiển thị trên trang chính như là nội dung mới nhất và nội dung tiếp theo", + "DisplayInMyMedia": "Hiển thị trên trang chính", + "Display": "Hiển thị", + "Dislike": "Không thích", + "Disconnect": "Ngắt kết nối", + "Disc": "Đĩa", + "Disabled": "Đã vô hiệu hoá", + "Directors": "Đạo Diễn", + "Director": "Đạo Diễn", + "DirectStreaming": "Phát trực tuyến", + "DirectStreamHelp2": "Phát trực tuyến sử dụng rất ít tài nguyên máy chủ mà không giảm chất lượng video.", + "DirectStreamHelp1": "Nội dung này tương thích với thiết bị về độ phân giải và dạng mã hoá (H.264, AC3, v.v.), nhưng lại không tương tích định dạng (mkv, avi, wmv, v.v.). Video sẽ được chuyển đổi định dạng trực tiếp ngay trước khi phát trên thiết bị.", + "DirectPlaying": "Phát trực tiếp", + "DeviceAccessHelp": "Thiết lập này chỉ áp dụng cho những thiết bị có thể định danh và sẽ không chặn được truy cập từ trình duyệt. Chọn lọc thiết bị người dùng sẽ chặn người dùng này truy cập từ những thiết bị mới cho đến khi được duyệt.", + "DetectingDevices": "Đang tìm kiếm thiết bị", + "Desktop": "Máy tính", + "Descending": "Giảm dần", + "Depressed": "Hạ xuống", + "DeleteUserConfirmation": "Bạn có chắc chắn muốn xoá người dùng này?", + "DeleteMedia": "Xoá nội dung", + "DeleteDeviceConfirmation": "Bạn có chắc chắn muốn xoá thiết bị này? Nó sẽ xuất hiện lại khi người dùng đăng nhập bằng thiết bị đó.", + "DeinterlaceMethodHelp": "Chọn phương pháp khử xen kẽ khi chuyển mã những nội dung sử dụng phương pháp quét xen kẽ.", + "DefaultSubtitlesHelp": "Phụ đề được sử dụng dựa vào thiết lập mặc định (default) và bắt buộc (forced) trong phần thông tin bổ trợ. Tuỳ chọn ưu tiên ngôn ngữ sẽ có tác dụng khi có nhiều phụ đề khác nhau.", + "DefaultMetadataLangaugeDescription": "Đây là thiết lập mặc định chung, bạn có thể tuỳ chỉnh thiết lập riêng cho từng thư viện.", + "DisplayModeHelp": "Chọn kiểu bố trí giao diện mà bạn muốn.", + "Download": "Tải xuống", + "Down": "Xuống", + "DoNotRecord": "Không ghi lại", + "EnableCinemaMode": "Chế độ rạp phim", + "EnableBackdropsHelp": "Hiển thị phông nền phía sau một số trang khi xem thư viện.", + "EnableBackdrops": "Phông nền", + "EditSubtitles": "Chỉnh sửa phụ đề", + "EditMetadata": "Chỉnh sửa thông tin", + "EditImages": "Chỉnh sửa hình ảnh", + "Edit": "Chỉnh sửa", + "EasyPasswordHelp": "Mã PIN tiện lợi được sử dụng cho việc truy cập offline trên những thiết bị được hỗ trợ và cũng có thể sử dụng dành cho truy cập dễ dàng trong nội mạng.", + "DropShadow": "Đổ Bóng", + "DrmChannelsNotImported": "Những kênh được bảo vệ bản quyền sẽ không được nhập vào.", + "DownloadsValue": "{0} đã tải xuống", + "EnableColorCodedBackgrounds": "Màu nền theo loại kênh", + "HeaderDateIssued": "Ngày Phát Hành", + "HeaderContinueWatching": "Tiếp Tục Xem", + "HeaderContinueListening": "Tiếp Tục Nghe", + "HeaderCodecProfileHelp": "Bộ giải mã chỉ ra những kiểu mã hoá nhất định mà một thiết bị có thể phát. Nếu một nội dung không thể phát, nó sẽ được chuyển mã, thậm chí nếu kiểu mã hoá đó được thiết lập phát trực tiếp.", + "HeaderContainerProfileHelp": "Bộ định dạng chỉ ra những định dạng nhất định mà một thiết bị có thể phát. Nếu nội dung có định dạng không thể phát, nội dung sẽ được chuyển đổi định dạng, kể cả khi định dạng đó được thiết lập phát trực tiếp.", + "HeaderContainerProfile": "Bộ Định Dạng", + "HeaderConnectionFailure": "Kế Nối Thất Bại", + "HeaderConnectToServer": "Kết Nối Đến Máy Chủ", + "HeaderConfirmRevokeApiKey": "Thu Hồi Quyền Truy Cập API Key", + "HeaderConfirmProfileDeletion": "Xác Nhận Xoá Hồ Sơ", + "HeaderConfirmPluginInstallation": "Xác Nhận Cài Đặt Plugin", + "HeaderConfigureRemoteAccess": "Thiết Lập Truy Cập Từ Xa", + "HeaderCodecProfile": "Bộ Giải Mã", + "HeaderChapterImages": "Hình Ảnh Phân Đoạn", + "HeaderChannels": "Kênh", + "HeaderChannelAccess": "Quyền Truy Cập Kênh", + "HeaderCastCrew": "Diễn Viên & Ê-kíp", + "HeaderCastAndCrew": "Diễn Viên & Ê-kíp", + "HeaderCancelSeries": "Huỷ Series", + "HeaderCancelRecording": "Dừng Ghi Âm/Ghi Hình", + "HeaderBranding": "Tạo Thương Hiệu", + "HeaderBooks": "Sách", + "HeaderBlockItemsWithNoRating": "Khoá những mục không có thông tin đánh giá hoặc thông tin không đáng tin cậy:", + "HeaderAudioSettings": "Cài Đặt Âm Thanh", + "HeaderAudioBooks": "Sách Nói", + "HeaderAppearsOn": "Xuất Bản Vào", + "HeaderApp": "Ứng dụng", + "HeaderApiKeysHelp": "Những ứng dụng khác cần có \"API key\" để tương tác với máy chủ Jellyfin. Những mã này được tạo ra bằng cách đăng nhập vào máy chủ Jellyfin, hoặc bạn có thể cung cấp cho ứng dụng một mã truy cập.", + "HeaderApiKeys": "API Keys", + "HeaderApiKey": "API Key", + "HeaderAllowMediaDeletionFrom": "Cho Phép Xoá Nội Dung", + "HeaderAlert": "Thông Báo", + "HeaderAlbums": "Albums", + "HeaderAlbumArtists": "Nghệ Sĩ Album", + "HeaderAdmin": "Quản Trị", + "HeaderAdditionalParts": "Phần Bổ Sung", + "HeaderAddUpdateImage": "Thêm/Thay Đổi Hình Ảnh", + "HeaderAddToPlaylist": "Thêm vào Danh Sách Phát", + "HeaderAddToCollection": "Thêm vào Bộ Sưu Tập", + "HeaderAddScheduledTaskTrigger": "Thêm Kích Hoạt Tác Vụ", + "HeaderActivity": "Hoạt Động", + "HeaderActiveRecordings": "Bản Ghi Hình/Ghi Âm Đang Hoạt Động", + "HardwareAccelerationWarning": "Bật tính năng hỗ trợ từ phần cứng có thể gây ra sự bất ổn trong một vài môi trường. Hãy chắc chắn rằng điều hành và driver thiết bị đồ hoạ đã được cập nhật mới nhất. Nếu gặp vấn đề khi phát video sau khi bật tính năng này, bạn nên tắt tính năng này.", + "HeaderActiveDevices": "Thiết Bị Đang Hoạt Động", + "HeaderAccessScheduleHelp": "Tạo một thời gian biểu để giới hạn quyền truy cập vào một số khung giờ nhất định.", + "HeaderAccessSchedule": "Thời Gian Truy Cập", + "HandledByProxy": "Được xử lý bằng phương thức đảo ngược proxy", + "HDPrograms": "Chương trình chất lượng cao (HD)", + "EncoderPresetHelp": "Chọn một giá trị nhanh hơn để cải thiện hiệu suất máy chủ, hoặc một giá trị chậm hơn để tăng chất lượng video.", + "H264CrfHelp": "Hệ Số Tỉ Lệ Cố Định (Constant Rate Factor (CRF)) là thiết lập chất lượng mặc định dành cho bộ mã hoá x264. Bạn có thể điều chỉnh giá trị trong khoảng 0 đến 51, trong đó giá trị càng nhỏ thì chất lượng càng tốt (đồng nghĩa với việc dung lượng tập tin lớn hơn). Giá trị vừa phải nằm trong khoảng từ 18 đến 28. Giá trị mặc định dành cho x264 là 23, vì thế bạn có thể sử dụng nó để bắt đầu điều chỉnh cho phù hợp.", + "GuideProviderSelectListings": "Chọn Danh Sách", + "GuideProviderLogin": "Đăng nhập", + "Guide": "Lịch phát sóng", + "GuestStar": "Ngôi sao khách mời", + "GroupVersions": "Chia nhóm theo phiên bản", + "GroupBySeries": "Chia nhóm theo bộ", + "Genres": "Thể loại", + "Genre": "Thể loại", + "General": "Tổng Hợp", + "Fullscreen": "Toàn màn hình", + "Friday": "Thứ Sáu", + "FormatValue": "Định dạng: {0}", + "Folders": "Thư mục", + "FolderTypeUnset": "Nội Dung Tổng Hợp", + "FolderTypeMusicVideos": "Video Âm Nhạc", + "FolderTypeMusic": "Nhạc", + "FolderTypeMovies": "Phim lẻ", + "FolderTypeBooks": "Sách", + "Filters": "Bộ lọc", + "File": "Tập tin", + "FetchingData": "Đang tải thêm thông tin", + "Features": "Mục đặc trưng", + "Favorites": "Mục yêu thích", + "Favorite": "Mục yêu thích", + "FastForward": "Tua nhanh", + "FFmpegSavePathNotFound": "Máy chủ không thể tìm thấy chương trình FFmpeg trong đường dẫn bạn đã nhập. FFprobe thì cũng cần thiết và phải nằm trong cùng thư mục bên trên. Những phần mềm này thường được tổng hợp cùng nhau trong một lượt tải. Hãy thử lại sau khi kiểm tra đường dẫn.", + "Extras": "Đặc biệt", + "ExtractChapterImagesHelp": "Trích xuất hình ảnh từ những phân đoạn chính sẽ cho phép thiết bị phát hiển thị bảng lựa chọn cảnh thông qua những hình ảnh đó. Quá trình đó có thể chậm, cần nhiều tài nguyên máy chủ, và có thể cần một vài gigabytes trống. Điều này sẽ được thực hiện khi video được tìm thấy, và cũng như trong tác vụ hàng đêm. Thời gian thực hiện tác vụ này có thể điều chỉnh trong phần cài đặt tác vụ thường xuyên. Không nên thực hiện quá trình này trong giờ sử dụng cao điểm.", + "ExtraLarge": "Rất Lớn", + "ExitFullscreen": "Thoát khỏi chế độ toàn màn hình", + "EveryNDays": "Mỗi {0} ngày", + "ErrorSavingTvProvider": "Có lỗi xảy ra khi lưu thông tin của nhà cung cấp TV này. Hãy thử lại khi chắc chắn rằng nó có thể truy cập.", + "ErrorPleaseSelectLineup": "Hãy chọn một danh sách và thử lại. Nếu không có danh sách nào sẵn sàng, hãy chắc chắn rằng thông tin tài khoản, mật khẩu, và mã bưu điện đều chính xác.", + "ErrorMessageStartHourGreaterThanEnd": "Thời gian kết thúc phải lớn hơn thời gian bắt đầu.", + "ErrorGettingTvLineups": "Có lỗi xảy ra khi tải danh sách TV này. Hãy thử lại khi chắc chắn rằng thông tin của bạn chính xác.", + "ErrorDeletingItem": "Có lỗi xảy ra khi xoá mục này khỏi máy chủ Jellyfin. Hãy thử lại sau khi kiểm tra chắc chắn rằng máy chủ Jellyfin có quyền ghi/xoá vào thư mục nội dung.", + "ErrorAddingXmlTvFile": "Có lỗi xảy ra khi truy cập tài liệu XMLTV. Hãy thử lại khi chắc chắn rằng tài liệu này tồn tại.", + "ErrorAddingTunerDevice": "Có lỗi xảy ra khi thêm vào thiết bị bắt sóng này. Hãy thử lại khi chắc chắn rằng nó có thể truy cập.", + "ErrorAddingMediaPathToVirtualFolder": "Có lỗi xảy ra khi thêm đường dẫn này. Hãy chắc chắn rằng đường dẫn này đúng và máy chủ Jellyfin có quyền truy cập dữ liệu ở đường dẫn đó.", + "ErrorAddingListingsToSchedulesDirect": "Có lỗi xảy ra khi thêm danh sách này vào tài khoản Schedules Direct của bạn. Schedules Direct chỉ cho phép một số lượng danh sách nhất định mỗi tài khoản. Bạn có thể cần phải đăng nhập vào trang web của Schedules Direct và xoá những danh sách khác trước khi có thể thêm danh sách mới.", + "Episodes": "Tập", + "Episode": "Tập", + "EndsAtValue": "Kết thúc tại {0}", + "Ended": "Đã kết thúc", + "EnableThemeVideosHelp": "Phát nền những video chủ đề khi lướt qua thư viện nội dung.", + "EnableThemeVideos": "Video chủ đề", + "EnableThemeSongsHelp": "Phát những bài hát chủ đề khi lướt qua thư viện nội dung.", + "EnableThemeSongs": "Nhạc chủ đề", + "EnableStreamLoopingHelp": "Cho phép tính năng này nếu live streams chỉ dài một vài giây và cần được lặp lại liên tục. Bật tính năng này khi không cần thiết có thể gây ra nhiều vấn đề khác.", + "EnableStreamLooping": "Tự động phát lại live streams", + "EnablePhotosHelp": "Hình ảnh sẽ được nhận diện và hiển thị bên cạnh những nội dung media.", + "EnablePhotos": "Hiển thị hình ảnh", + "EnableNextVideoInfoOverlayHelp": "Khi ở phần cuối video, hiển thị thông tin về video tiếp theo trong danh sách phát hiện tại.", + "EnableNextVideoInfoOverlay": "Hiển thị thông tin video tiếp theo ở phần cuối video", + "EnableHardwareEncoding": "Sử dụng phần cứng để hỗ trợ chuyển mã", + "EnableExternalVideoPlayersHelp": "Phần mềm phát video từ thiết bị sẽ được hiển thị khi bắt đầu phát video.", + "EnableExternalVideoPlayers": "Sử dụng phần mềm phát video từ thiết bị", + "EnableDisplayMirroring": "Cho phép trình chiếu trên thiết bị khác", + "HeaderProfileInformation": "Thông Tin Hồ Sơ", + "HeaderProfile": "Hồ Sơ", + "HeaderPreferredMetadataLanguage": "Ngôn Ngữ Thông Tin Bổ Trợ Yêu Thích", + "HeaderPluginInstallation": "Cài Đặt Plugin", + "HeaderPleaseSignIn": "Hãy đăng nhập", + "HeaderPlaybackError": "Lỗi Phát Sóng", + "HeaderPlayback": "Phát Nội Dung", + "HeaderPlayOn": "Phát Trên", + "HeaderPlayAll": "Phát Tất Cả", + "HeaderPinCodeReset": "Đặt Lại Mã PIN", + "HeaderPhotoAlbums": "Album Ảnh", + "HeaderPeople": "Nhân Vật", + "HeaderPendingInvitations": "Những Lời Mời Đang Chờ", + "HeaderPaths": "Đường Dẫn", + "HeaderPasswordReset": "Đặt Lại Mật Khẩu", + "HeaderPassword": "Mật Khẩu", + "HeaderParentalRatings": "Đánh Giá Của Phụ Huynh", + "HeaderOtherItems": "Những Mục Khác", + "HeaderOnNow": "Phát Sóng Hiện Tại", + "HeaderNextVideoPlayingInValue": "Video Tiếp Theo Sẽ Phát trong {0}", + "HeaderNextUp": "Tiếp Theo", + "HeaderNextEpisodePlayingInValue": "Tập Tiếp Theo Sẽ Được Chạy trong {0}", + "HeaderNewDevices": "Thiết Bị Mới", + "HeaderNewApiKey": "Mã API Mới", + "HeaderNavigation": "Điều Hướng", + "HeaderMyMediaSmall": "Nội Dung Của Tôi (cỡ nhỏ)", + "HeaderMyMedia": "Nội Dung Của Tôi", + "HeaderMyDevice": "Thiết Bị Của Tôi", + "HeaderMusicVideos": "Video Âm Nhạc", + "HeaderMusicQuality": "Chất Lượng Âm Nhạc", + "HeaderMovies": "Phim", + "HeaderMoreLikeThis": "Nội Dung Tương Tự", + "HeaderMetadataSettings": "Cài Đặt Dữ Liệu Bổ Trợ", + "HeaderMediaInfo": "Thông Tin Nội Dung", + "HeaderMediaFolders": "Thư Mục Chứa Nội Dung", + "HeaderMedia": "Nội Dung", + "HeaderLoginFailure": "Đăng Nhập Không Thành Công", + "HeaderLiveTvTunerSetup": "Cài Đặt Bộ Bắt Sóng TV", + "HeaderLiveTv": "Truyền Hình Trực Tiếp", + "HeaderLiveTV": "Truyền Hình Trực Tiếp", + "HeaderLibrarySettings": "Cài Đặt Thư Viện", + "HeaderLibraryOrder": "Thứ Tự Thư Viện", + "HeaderLibraryFolders": "Thư Mục Của Thư Viện", + "HeaderLibraryAccess": "Truy Cập Thư Viện", + "HeaderLibraries": "Thư Viện", + "HeaderLatestRecordings": "Bản Ghi Âm/Ghi Hình Mới Nhất", + "HeaderLatestMusic": "Âm Nhạc Mới Nhất", + "HeaderLatestMedia": "Nội Dung Mới Nhất", + "HeaderKodiMetadataHelp": "Để bật hoặc tắt dữ liệu từ NFO, hãy chỉnh sửa thư viện trong phần cài đặt thư viện của Jellyfin và điều chỉnh phần lưu trữ dữ liệu bổ trợ.", + "HeaderKeepSeries": "Lưu Series", + "HeaderKeepRecording": "Tiếp Tục Ghi Âm/Ghi Hình", + "HeaderItems": "Mục", + "HeaderInstantMix": "Trộn Lẫn Nhanh", + "HeaderInstall": "Cài Đặt", + "HeaderImageSettings": "Thiết Lập Hình Ảnh", + "HeaderImageOptions": "Tuỳ Chọn Hình Ảnh", + "HeaderIdentifyItemHelp": "Hãy nhập một hoặc nhiều điều kiện tìm kiếm. Xoá điều kiện để tăng số lượng kết quả tìm kiếm.", + "HeaderIdentificationHeader": "Thông Tin Nhận Biết Trong Phần Đầu", + "HeaderIdentificationCriteriaHelp": "Nhập vào ít nhật một thông tin nhận biết.", + "HeaderIdentification": "Thông Tin Nhận Biết", + "HeaderHttpHeaders": "HTTP Headers", + "HeaderHome": "Trang Chủ", + "HeaderGuideProviders": "Nhà Cung Cấp Lịch Phát Sóng TV", + "HeaderGenres": "Thể Loại", + "HeaderForgotPassword": "Quên Mật Khẩu", + "HeaderForKids": "Dành Cho Trẻ Em", + "HeaderFilters": "Bộ Lọc", + "HeaderFetcherSettings": "Cài Đặt Chương Trình Tải Xuống", + "HeaderFetchImages": "Tải Hình Ảnh:", + "HeaderFeatures": "Nổi Bật", + "HeaderFavoritePeople": "Nhân Vật Yêu Thích", + "HeaderFavoriteAlbums": "Album Yêu Thích", + "HeaderFavoriteBooks": "Sách Yêu Thích", + "HeaderFavoriteVideos": "Video Yêu Thích", + "HeaderFavoriteSongs": "Bài Hát Yêu Thích", + "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", + "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", + "HeaderFavoriteShows": "Chương Trình Yêu Thích", + "HeaderFavoriteMovies": "Phim Yêu Thích", + "HeaderExternalIds": "Mã Định Danh Từ Bên Ngoài:", + "HeaderError": "Lỗi", + "HeaderEpisodes": "Tập", + "HeaderEnabledFieldsHelp": "Bỏ chọn một mục để khoá lựa chọn đó và không để nó thay đổi.", + "HeaderEnabledFields": "Những Mục Được Kích Hoạt", + "HeaderEditImages": "Chỉnh Sửa Hình Ảnh", + "HeaderEasyPinCode": "Mã PIN Đơn Giản", + "HeaderDownloadSync": "Tải Xuống Và Đồng Bộ", + "HeaderDisplay": "Hiển Thị", + "HeaderDirectPlayProfileHelp": "Thêm thiết lập phát trực tiếp để chỉ ra những định dạng mà thiết bị có thể phát trực tiếp mà không cần chuyển mã.", + "HeaderDirectPlayProfile": "Thiết Lập Phát Trực Tiếp", + "HeaderDevices": "Thiết Bị", + "HeaderDeviceAccess": "Truy Cập Thiết Bị", + "HeaderDeveloperInfo": "Thông Tin Nhà Phát Triển", + "HeaderDetectMyDevices": "Tìm Kiếm Những Thiết Bị Của Tôi", + "HeaderDeleteTaskTrigger": "Xoá Kích Hoạt Tác Vụ", + "HeaderDeleteProvider": "Xoá Nhà Cung Cấp", + "HeaderDeleteItems": "Xoá Những Mục Này", + "HeaderDeleteItem": "Xoá Mục Này", + "HeaderDeleteDevice": "Xoá Thiết Bị", + "HeaderDefaultRecordingSettings": "Thiết Lập Ghi Âm/Ghi Hình Mặc Định", + "HeaderRecordingOptions": "Tuỳ Chọn Ghi Âm/Ghi Hình", + "HeaderProfileServerSettingsHelp": "Những thông tin này về máy chủ Jellyfin sẽ hiển thị trên các thiết bị phát được kết nối.", + "HeaderSettings": "Cài Đặt", + "HeaderServerSettings": "Cài Đặt Máy Chủ", + "HeaderSeriesStatus": "Trạng Thái Series", + "HeaderSeriesOptions": "Tuỳ Chọn Series", + "HeaderSendMessage": "Gửi Tin Nhắn", + "HeaderSelectTranscodingPathHelp": "Hãy chọn đường dẫn để lưu tạm những tập tin chuyển mã. Máy chủ phải có quyền ghi trên thư mục đó.", + "HeaderSelectTranscodingPath": "Chọn Đường Dẫn Chuyển Mã", + "HeaderSelectServerCachePathHelp": "Hãy chọn đường dẫn để lưu tập tin ghi tạm cho máy chủ. Máy chủ phải có quyền ghi trên thư mục đó.", + "HeaderSelectServerCachePath": "Chọn Đường Dẫn Ghi Tạm Cho Máy Chủ", + "HeaderSelectServer": "Chọn Máy Chủ", + "HeaderSelectPath": "Chọn Đường Dẫn", + "HeaderSelectMetadataPathHelp": "Hãy chọn đường dẫn mà bạn muốn lưu thông tin bổ trợ. Máy chủ phải có quyền ghi trên thư mục đó.", + "HeaderSelectMetadataPath": "Chọn Đường Dẫn Thông Tin Bổ Trợ", + "HeaderSelectCertificatePath": "Chọn Đường Dẫn Đến Chứng Chỉ", + "HeaderSecondsValue": "{0} Giây", + "HeaderSeasons": "Phần", + "HeaderSchedule": "Lịch Phát Sóng", + "HeaderScenes": "Phân Cảnh", + "HeaderRunningTasks": "Những Tác Vụ Hoạt Động", + "HeaderRevisionHistory": "Lịch Sử Chỉnh Sửa", + "HeaderRestart": "Khởi Động Lại", + "HeaderResponseProfileHelp": "Hồ sơ phản hồi là phương thức tuỳ chỉnh thông tin gửi về thiết bị phát khi phát một số nội dung nhất định.", + "HeaderResponseProfile": "Hồ Sơ Phản Hồi", + "HeaderRemoveMediaLocation": "Xoá Đường Dẫn Nội Dung", + "HeaderRemoveMediaFolder": "Xoá Thư Mục Nội Dung", + "HeaderRemoteControl": "Điều Khiển Từ Xa", + "HeaderRecordingPostProcessing": "Xử Lý Sau Khi Ghi Âm/Ghi Hình", + "HeaderSortOrder": "Thứ tự Sắp xếp", + "HeaderSortBy": "Sắp xếp theo", + "HeaderStartNow": "Bắt đầu", + "HeaderSetupLibrary": "Thiết lập thư viện nội dung của bạn", + "HeaderTracks": "Bài Hát", + "HeaderThisUserIsCurrentlyDisabled": "Người dùng này hiện tại đang bị khoá", + "HeaderTaskTriggers": "Kích Hoạt Tác Vụ", + "HeaderTags": "Nhãn", + "HeaderSubtitleProfilesHelp": "Hồ sơ phụ đề chỉ ra những định dạng phụ đề được hỗ trợ bởi thiết bị phát.", + "HeaderSubtitleProfiles": "Hồ Sơ Phụ Đề", + "HeaderSubtitleProfile": "Hồ Sơ Phụ Đề", + "HeaderSubtitleDownloads": "Tải Phụ Đề", + "HeaderSubtitleAppearance": "Giao Diện Phụ Đề", + "HeaderStopRecording": "Ngừng Ghi Hình/Ghi Âm", + "HeaderSpecialFeatures": "Những Phần Đặc Biệt Nổi Bật", + "HeaderSpecialEpisodeInfo": "Thông Tin Tập Đặc Biệt", + "HeaderShutdown": "Tắt Máy Chủ", + "LabelCustomDeviceDisplayNameHelp": "Cung cấp một tên hiển thị riêng hoặc bỏ trống để sử dụng tên có sẵn của thiết bị.", + "LabelCustomDeviceDisplayName": "Tên hiển thị:", + "LabelCustomCssHelp": "Áp dụng tuỳ chỉnh riêng của bạn vào giao diện trang web.", + "LabelCustomCss": "CSS tuỳ chọn:", + "LabelCustomCertificatePathHelp": "Đường dẫn đến tập tin PKCS #12 chứa chứng chỉ (certificate) và khoá riêng (private key) để bật tính năng TLS trên một tên miền tuỳ chọn.", + "LabelCustomCertificatePath": "Đường dẫn đến chứng chỉ SSL:", + "LabelCriticRating": "Đánh giá phê bình:", + "LabelCorruptedFrames": "Những khung hình bị lỗi:", + "LabelContentType": "Loại nội dung:", + "LabelCommunityRating": "Đánh giá của cộng đồng:", + "LabelCollection": "Bộ Sưu Tập:", + "LabelChannels": "Kênh:", + "LabelCertificatePasswordHelp": "Nếu chứng chỉ của bạn cần mật khẩu, hãy nhập nó ở đây.", + "LabelCertificatePassword": "Mật khẩu chứng chỉ:", + "LabelCancelled": "Đã Huỷ", + "LabelCachePathHelp": "Chọn một đường dẫn cho những tập tin lưu tạm như là hình ảnh. Bỏ trống để sử dụng cài đặt mặc định của máy chủ.", + "LabelCachePath": "Đường dẫn cache:", + "LabelCache": "Cache:", + "LabelBurnSubtitles": "Nhúng phụ đề:", + "LabelBlockContentWithTags": "Chặn những mục có nhãn:", + "LabelBlastMessageIntervalHelp": "Xác định thời gian tồn tại giữa các tin nhắn (tính bằng giây).", + "LabelBlastMessageInterval": "Thời gian tồn tại của tin nhắn (giây)", + "LabelBitrate": "Bitrate:", + "LabelBirthYear": "Năm sinh:", + "LabelBirthDate": "Ngày sinh:", + "LabelBindToLocalNetworkAddressHelp": "Không bắt buộc. Cài đặt đè địa chỉ IP nội bộ để kết nối đến máy chủ HTTP. Nếu bỏ trống, máy chủ sẽ cài đặt vào toàn bộ những địa chỉ nội bộ có sẵn. Nếu thay đổi tuỳ chọn này, cần phải khởi động lại máy chủ Jellyfin để có tác dụng.", + "LabelBindToLocalNetworkAddress": "Cài đặt vào địa chỉ nội bộ:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Tự động cập nhật dữ liệu bổ trợ từ Internet:", + "LabelAuthProvider": "Nhà Cung Cấp Xác Thực:", + "LabelAudioSampleRate": "Sample rate âm thanh:", + "LabelAudioCodec": "Bộ giải mã âm thanh:", + "LabelAudioChannels": "Các kênh âm thanh:", + "LabelAudioBitrate": "Bitrate của âm thanh:", + "LabelAudioBitDepth": "Chiều sâu của âm thanh:", + "LabelAudio": "Âm Thanh", + "LabelArtistsHelp": "Sử dụng dấu ; để tách rời nhiều nghệ", + "LabelArtists": "Nghệ sĩ:", + "LabelAppNameExample": "Ví dụ: Sickbeard, Sonarr", + "LabelAppName": "Tên ứng dụng", + "LabelAllowedRemoteAddressesMode": "Chế độ bộ lọc địa chỉ IP từ xa:", + "LabelAllowedRemoteAddresses": "Bộ lọc địa chỉ IP từ xa:", + "LabelAllowServerAutoRestartHelp": "Máy chủ chỉ khởi động lại trong thời gian rỗi khi không có người dùng đang sử dụng.", + "LabelAllowHWTranscoding": "Cho phép chuyển mã bằng phần cứng", + "LabelAll": "Tất Cả", + "LabelAlbumArtists": "Nghệ sĩ album:", + "LabelAlbumArtPN": "Bìa album PN:", + "LabelAlbumArtMaxWidthHelp": "Độ phân giải cao nhất của bìa album thông qua upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Chiều ngang lớn nhất của bìa album:", + "LabelAlbumArtMaxHeightHelp": "Độ phân giải cao nhất của bìa album thông qua upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Chiều cao lớn nhất của bìa album:", + "LabelAlbumArtHelp": "PN được sử dụng cho bìa album, trong dlna:profileID thuộc tính upnp:albumArtURI. Một vài thiết bị phát cần một giá trị đặc biệt, không ảnh hưởng đến kích thước của hình ảnh.", + "LabelAlbum": "Album:", + "LabelAirsBeforeSeason": "Phát sóng trước mùa:", + "LabelAirsBeforeEpisode": "Phát sóng trước tập:", + "LabelAirsAfterSeason": "Phát sóng sau mùa:", + "LabelAirTime": "Thời gian phát sóng:", + "LabelAirDays": "Ngày phát sóng:", + "LabelAccessStart": "Thời gian bắt đầu:", + "LabelAccessEnd": "Thời gian kết thúc:", + "LabelAccessDay": "Ngày trong tuần:", + "LabelAbortedByServerShutdown": "(Đã huỷ bởi máy chủ ngừng hoạt động)", + "Label3DFormat": "Định dạng 3D:", + "Kids": "Trẻ Em", + "Items": "Mục", + "ItemCount": "{0} mục", + "InstantMix": "Trộn Lẫn Nhanh", + "InstallingPackage": "Đang cài đặt {0} (phiên bản {1})", + "ImportMissingEpisodesHelp": "Nếu bật tuỳ chọn này, thông tin bị thiếu trong các tập phim sẽ được nhập vào cơ sở dữ liệu của máy chủ Jellyfin và hiển thị trong các phần và series. Điều này có thể làm việc quét thư viện lâu hơn rất nhiều.", + "ImportFavoriteChannelsHelp": "Nếu bật tuỳ chọn này, chỉ những kênh yêu thích trong thiết bị bắt sóng sẽ được nhập vào.", + "Images": "Hình Ảnh", + "Identify": "Nhận Dạng", + "HttpsRequiresCert": "Để bật kết nối bảo mật, bạn cần phải cung cấp một Chứng Chỉ SSL đáng tin cậy, ví dụ như \"Let's Encrypt\". Hãy cung cấp Chứng Chỉ SSL hoặc là tắt tính năng kết nối bảo mật.", + "Horizontal": "Nằm Ngang", + "Home": "Trang Chủ", + "HideWatchedContentFromLatestMedia": "Ẩn những nội dung đã xem khỏi phần nội dung mới nhất", + "Hide": "Ẩn", + "Help": "Trợ Giúp", + "HeadersFolders": "Thư Mục", + "HeaderYears": "Năm", + "HeaderXmlSettings": "Cài Đặt XML", + "HeaderXmlDocumentAttributes": "Những Thuộc Tính Tài Liệu XML", + "HeaderXmlDocumentAttribute": "Thuộc Tính Tài Liệu XML", + "HeaderVideos": "Videos", + "HeaderVideoTypes": "Kiểu Video", + "HeaderVideoType": "Kiểu Video", + "HeaderVideoQuality": "Chất Lượng Video", + "HeaderUser": "Người Dùng", + "HeaderUploadImage": "Tải Lên Hình Ảnh", + "HeaderUpcomingOnTV": "Sắp Phát Sóng Trên TV", + "HeaderTypeText": "Nhập nội dung", + "HeaderTypeImageFetchers": "{0} Trình Tải Hình Ảnh", + "HeaderTuners": "Bộ Điều Khiển Thu Phát Sóng", + "HeaderTunerDevices": "Thiết Bị Bắt Sóng", + "HeaderTranscodingProfileHelp": "Thêm hồ sơ chuyển mã để chỉ ra những định dạng nên dùng khi cần chuyển mã.", + "HeaderTranscodingProfile": "Hồ Sơ Chuyển Mã", + "LabelEnableAutomaticPortMap": "Kích hoạt tính năng tự động kết nối các port", + "LabelEmbedAlbumArtDidlHelp": "Một vài thiết bị ưu tiên phương pháp này để tải bìa album. Một số thiết bị khác có thể không phát được nếu tuỳ chọn này được kích hoạt.", + "LabelEmbedAlbumArtDidl": "Bìa album trong Didl", + "LabelEasyPinCode": "Mã PIN đơn giản:", + "LabelDynamicExternalId": "{0} Id:", + "LabelDropShadow": "Đổ bóng:", + "LabelDroppedFrames": "Những khung hình bị loại bỏ:", + "LabelDropImageHere": "Kéo thả hình ảnh vào đây, hoặc click để lựa chọn hình ảnh.", + "LabelDownloadLanguages": "Ngôn ngữ tải xuống:", + "LabelDownMixAudioScaleHelp": "Tăng cường âm lượng khi trộn âm thanh. Giá trị bằng 1 sẽ giữ nguyên âm lượng gốc.", + "LabelDownMixAudioScale": "Tăng cường âm lượng khi trộn âm thanh:", + "LabelDisplaySpecialsWithinSeasons": "Hiển thị những tập đặc biệt trong phần mà nó được phát sóng", + "LabelDisplayOrder": "Thứ tự hiển thị:", + "LabelDisplayName": "Tên hiển thị:", + "LabelDisplayMode": "Chế độ hiển thị:", + "LabelDisplayMissingEpisodesWithinSeasons": "Hiển thị những tập bị thiếu", + "LabelDisplayLanguageHelp": "Dự án chuyển ngữ Jellyfin là một dự án diễn ra ra liên tục.", + "LabelDisplayLanguage": "Ngôn ngữ hiển thị:", + "LabelDiscNumber": "Đĩa số:", + "LabelDidlMode": "Chế độ DIDL:", + "LabelDeviceDescription": "Mô tả thiết bị", + "LabelDeinterlaceMethod": "Phương pháp khử xen kẽ:", + "LabelDefaultUserHelp": "Xác định thư viện được hiển thị trên những thiết bị đã kết nối. Tuỳ chọn này có thể được ghi đè trên hồ sơ của từng thiết bị.", + "LabelDefaultUser": "Người dùng mặc định:", + "LabelDefaultScreen": "Màn hình mặc định:", + "LabelDeathDate": "Ngày mất:", + "LabelDateTimeLocale": "Ngày giờ địa phương:", + "LabelDateAddedBehaviorHelp": "Nếu có giá trị dữ liệu bổ trợ, nó sẽ luôn được sử dụng trước một trong các tùy chọn này.", + "LabelDateAddedBehavior": "Ngày thêm hành vi cho nội dung mới:", + "LabelDateAdded": "Ngày thêm vào:", + "LabelDashboardTheme": "Chủ đề bảng điều khiển máy chủ:", + "LabelCustomRating": "Đánh giá tuỳ chọn:", + "HeaderFavoritePlaylists": "Danh Sách Phát Yêu Thích", + "ApiKeysCaption": "Danh sách các mã API đang hoạt động", + "LabelBaseUrl": "URL cơ bản:", + "LabelEveryXMinutes": "Mỗi:", + "LabelEpisodeNumber": "Tập số:", + "LabelEndDate": "Ngày kết thúc:", + "LabelEnableSingleImageInDidlLimitHelp": "Một số thiết bị không hiển thị rõ ràng nếu có nhiều hình ảnh được nhúng trong Didl.", + "LabelEnableSingleImageInDidlLimit": "Giới hạn chỉ một hình ảnh nhúng", + "LabelEnableRealtimeMonitorHelp": "Thay đổi để nội dung sẽ được xử lý ngay lập tức trên các hệ thống được hỗ trợ.", + "LabelEnableRealtimeMonitor": "Bật tính năng theo dõi thời gian thực", + "LabelEnableHttpsHelp": "Cho phép máy chủ theo dõi port HTTPS đã được thiết lập. Cần phải có chứng chỉ hợp lệ để tính năng này có hiệu quả.", + "LabelEnableHttps": "Bật HTTPS", + "LabelEnableHardwareDecodingFor": "Bật tính năng giãi mã phần cứng cho:", + "LabelEnableDlnaServerHelp": "Cho phép các thiết bị UPnP trong mạng của bản để duyệt và phát nội dung.", + "LabelEnableDlnaServer": "Bật tính năng máy chủ DLNA", + "LabelEnableDlnaPlayToHelp": "Tìm kiếm thiết bị trong mạng của bạn và đưa ra khả năng điều khiển từ xa những thiết bị đó.", + "LabelEnableDlnaDebugLoggingHelp": "Tạo những tập tin gỡ lỗi lớn và chỉ nên được sử dụng khi cần thiết để xử lý sự cố.", + "LabelEnableDlnaDebugLogging": "Bật tính năng gỡ lỗi DLNA", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Xác định thời gian tính bằng giây giữa các tìm kiếm SSDP thực hiện bởi Jellyfin.", + "LabelEnableDlnaClientDiscoveryInterval": "Thời gian tìm kiếm thiết bị phát (giây)", + "LabelEnableBlastAliveMessagesHelp": "Bật tính năng này nếu máy chủ không thể kết nối chắc chắn với những thiết bị UPnP trong mạng của bạn.", + "LabelEnableBlastAliveMessages": "Phát tin nhắn trực tiếp", + "LabelEnableAutomaticPortMapHelp": "Tự động chuyển tiếp những port công khai trên bộ định tuyến đến port trên máy chủ thông qua UPnP. Cài đặt này có thể không hoạt động trên một số loại bộ định tuyến hoặc thiết lập mạng. Thay đổi sẽ được áp dụng sau khi khởi động lại máy chủ.", + "HeaderServerAddressSettings": "Cài Đặt Địa Chỉ Máy Chủ", + "HeaderRemoteAccessSettings": "Cài Đặt Truy Cập Từ Xa", + "HeaderHttpsSettings": "Cài Đặt HTTPS", + "HeaderDVR": "DVR", + "LabelExtractChaptersDuringLibraryScanHelp": "Trích xuất hình ảnh của video được nhập vào trong lúc quét thư viện. Nếu không thì hình này này sẽ được trích xuất thông qua những tác vụ định kì, giúp cho quá trình quét thư viện diễn ra nhanh hơn.", + "LabelExtractChaptersDuringLibraryScan": "Trích xuất hình ảnh từng chương khi quét thư viện", + "LabelBaseUrlHelp": "Thêm một thư mục tuỳ chọn vào đường dẫn máy chủ. Ví dụ: http://example.com/<baseurl>", + "LabelLoginDisclaimerHelp": "Một tin nhắn sẽ hiển thị ở phía cuối của trang đăng nhập.", + "LabelLoginDisclaimer": "Hiển thị khi đăng nhập:", + "LabelLockItemToPreventChanges": "Khoá mục này để ngăn những thay đổi trong tương lai", + "LabelLocalHttpServerPortNumberHelp": "TCP port mà máy chủ Jellyfin HTTP nên kết nối.", + "LabelLocalHttpServerPortNumber": "HTTP port nội bộ:", + "LabelLineup": "Danh sách diễn viên:", + "LabelLibraryPageSizeHelp": "Cài đặt số lượng mục hiển thị trong một trang thư viện. Cài đặt 0 để vô hiệu hoá việc phân trang.", + "LabelLibraryPageSize": "Kích thước trang của thư viện:", + "LabelLanNetworks": "Mạng nội bộ:", + "LabelKodiMetadataUserHelp": "Lưu dữ liệu xem vào tập tin NFO dành cho những ứng dụng khác sử dụng.", + "LabelKodiMetadataUser": "Lưu thông tin người xem vào tập tin NFO dành cho:", + "LabelKodiMetadataSaveImagePathsHelp": "Cài đặt này được khuyến cáo nếu bạn có những hình ảnh đặt tên không đúng với hướng dẫn của Kodi.", + "LabelKodiMetadataSaveImagePaths": "Lưu đường dẫn hình ảnh trong tập tin NFO", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Kích hoạt thay thế đường dẫn hình ảnh sử dụng cài đặt của máy chủ.", + "LabelKodiMetadataEnablePathSubstitution": "Kích hoạt thay thế đường dẫn", + "LabelKodiMetadataEnableExtraThumbsHelp": "Khi tải hình ảnh, chúng có thể được lưu vào cả extrafanart và extrathumbs để tối ưu hoá khả năng tương thích với giao diện Kodi.", + "LabelKodiMetadataEnableExtraThumbs": "Sao chép từ mục extrafanart đến mục extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Toàn bộ ngày trong tập tin NFO sẽ được đọc sử dụng định dạng này.", + "LabelKodiMetadataDateFormat": "Định dạng của ngày phát hành:", + "LabelKidsCategories": "Những thể loại của trẻ em:", + "LabelKeepUpTo": "Theo kịp:", + "LabelInternetQuality": "Chất lượng Internet:", + "LabelInNetworkSignInWithEasyPasswordHelp": "Sử dụng mã PIN đơn giản để đăng nhập thiết bị phát trong mạng nội bộ. Mật khẩu thông thường sẽ chỉ cần khi không truy cập nội mạng. Nếu mã PIN để trống, bạn sex không cần mật khẩu trong mạng nội bộ.", + "LabelInNetworkSignInWithEasyPassword": "Kích hoạt đăng nhập nội mạng bằng mã PIN đơn giản", + "LabelImportOnlyFavoriteChannels": "Giới hạn để chỉ nhập vào những kênh yêu thích", + "LabelImageType": "Loại hình ảnh:", + "LabelImageFetchersHelp": "Kích hoạt và xếp hạng chương trình tải hình ảnh theo thứ tự ưu tiên.", + "LabelIdentificationFieldHelp": "Một phần chuỗi không phân biệt viết hoa/thường hoặc regex expression.", + "LabelIconMaxWidthHelp": "Độ phân giải tối đa của biểu tượng hiển thị thông qua upnp:icon.", + "LabelIconMaxWidth": "Chiều ngang tối đa của biểu tượng:", + "LabelIconMaxHeightHelp": "Độ phân giải tối đa của biểu tượng hiển thị thông qua upnp:icon.", + "LabelIconMaxHeight": "Chiều cao tối đa của biểu tượng:", + "LabelHttpsPortHelp": "TCP port mà máy chủ Jellyfin HTTPS nên kết nối vào.", + "LabelHttpsPort": "HTTPS port trên máy chủ:", + "LabelHomeScreenSectionValue": "Mục trên trang chủ {0}:", + "LabelHomeNetworkQuality": "Chất lượng mạng trong nhà:", + "LabelHardwareAccelerationTypeHelp": "Hỗ trợ phần cần những thiết lập bổ sung.", + "LabelHardwareAccelerationType": "Hỗ trợ phần cứng:", + "LabelEncoderPreset": "Thiết lập cài sẵn của mã H264 và H265:", + "LabelH264Crf": "CRF của mã H264:", + "LabelGroupMoviesIntoCollectionsHelp": "Khi hiển thị danh sách phim, các bộ phim thuộc về một bộ sưu tập sẽ hiển thị trong một nhóm.", + "LabelGroupMoviesIntoCollections": "Nhóm phim vào bộ sưu tập", + "LabelServerNameHelp": "Tên này sẽ được sử dụng để phân biệt máy chủ và giá trị mặc định là tên của máy tính chủ.", + "LabelFriendlyName": "Tên thân thiện:", + "LabelFormat": "Định dạng:", + "LabelForgotPasswordUsernameHelp": "Nhập vào tên tài khoản nếu bạn nhớ nó.", + "LabelFont": "Kiểu chữ:", + "LabelFolder": "Thư mục:", + "LabelFileOrUrl": "Tệp hoặc URL:", + "LabelFailed": "Thất bại", + "LabelMaxResumePercentage": "Phần trăm tối đa có thể phát tiếp tục:", + "LabelMaxParentalRating": "Xếp hạng tối đa cho phép của phụ huynh:", + "LabelMaxChromecastBitrate": "Chất lượng truyền tải Chromecast:", + "LabelMaxBackdropsPerItem": "Số lượng phông nền tối đa mỗi mục:", + "LabelMatchType": "Loại tương thích:", + "LabelManufacturerUrl": "URL của nhà sản xuất", + "LabelManufacturer": "Nhà sản xuất:", + "LabelLogs": "Logs:", + "HeaderSyncPlayEnabled": "Kích hoạt SyncPlay", + "HeaderSyncPlaySelectGroup": "Tham gia nhóm", + "EnableDetailsBannerHelp": "Hiển thị một ảnh bìa ở đầu trang chi tiết.", + "EnableDetailsBanner": "Ảnh Bìa Chi Tiết", + "LabelMoviePrefixHelp": "Nếu một tiếp đầu ngữ được áp dụng cho tựa đề phim, nhập nó ở mục này để máy chủ có thể xử lý một cách phù hợp.", + "LabelMoviePrefix": "Tiếp đầu ngữ của tựa phim:", + "LabelMovieCategories": "Loại phim:", + "LabelMonitorUsers": "Theo dõi hoạt động từ:", + "LabelModelUrl": "Model URL", + "LabelModelNumber": "Mã số model", + "LabelModelName": "Tên model", + "LabelModelDescription": "Mô tả model", + "LabelMinScreenshotDownloadWidth": "Chiều ngang tối thiểu của hình ảnh tải xuống:", + "LabelMinResumePercentageHelp": "Nội dung sẽ được cho là chưa phát nếu dừng lại trước thời gian này.", + "LabelMinResumePercentage": "Phần trăm tối thiểu để phát tiếp tục:", + "LabelMinResumeDurationHelp": "Thời lượng ngắn nhất tính bằng giây mà vị trí phát sẽ được lưu và cho phép bạn phát tiếp tục.", + "LabelMinResumeDuration": "Thời lượng phát tiếp tục tối thiểu:", + "LabelMinBackdropDownloadWidth": "Chiều rộng tối thiểu của phông nền tải xuống:", + "LabelMethod": "Phương pháp:", + "LabelMetadataSaversHelp": "Chọn định dạng tập tin dữ liệu bổ trợ.", + "LabelMetadataSavers": "Định dạng dữ liệu bổ trợ:", + "LabelMetadataReadersHelp": "Sắp xếp thứ tự ưu tiên của những nguồn dữ liệu bổ trợ. Tập tin đầu tiên được tìm thấy sẽ được sử dụng.", + "LabelMetadataReaders": "Bộ đọc dữ liệu bổ trợ:", + "LabelMetadataPathHelp": "Thiết lập một đường dẫn tuỳ ý cho những hình ảnh và dữ liệu bổ trợ.", + "LabelMetadataPath": "Đường dẫn dữ liệu bổ trợ:", + "LabelMetadataDownloadersHelp": "Kích hoạt và sắp xếp thứ tự ưu tiên của các trình tải dữ liệu bổ trợ. Trình tải có thứ tự ưu tiên thấp sẽ chỉ sử dụng để tải những thông tin bị thiếu.", + "LabelMetadataDownloadLanguage": "Ngôn ngữ tải ưu tiên:", + "LabelMetadata": "Dữ liệu bổ trợ:", + "LabelMessageTitle": "Tiêu đề tin nhắn:", + "LabelMessageText": "Nội dung tin nhắn:", + "LabelMaxStreamingBitrateHelp": "Thiết lập bitrate tối đa khi truyền tải.", + "LabelMaxStreamingBitrate": "Chất lượng phát tối đa:", + "LabelMaxScreenshotsPerItem": "Số lượng ảnh chụp tối đa mỗi mục:", + "LabelMaxResumePercentageHelp": "Nội dung sẽ được cho là đã kết thúc nếu ngừng phát sau thời gian này.", + "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 1a3aa1e4de3..4ab15420c20 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1,35 +1,35 @@ { - "AccessRestrictedTryAgainLater": "目前访问受限。请稍后再试。", + "AccessRestrictedTryAgainLater": "目前访问受限,请稍后再试。", "Actor": "演员", "Add": "添加", - "AddItemToCollectionHelp": "通过搜索并使用鼠标右键单击或点击菜单将项目添加到集合中, 将项目添加到集合中。", - "AddToCollection": "加入收藏", + "AddItemToCollectionHelp": "通过搜索项目并右键或轻触得到的弹出菜单来将项目添加到集合中。", + "AddToCollection": "加入集合", "AddToPlayQueue": "添加至播放队列", - "AddToPlaylist": "添加到播放列表", + "AddToPlaylist": "添加至播放列表", "AddedOnValue": "已添加 {0}", "AdditionalNotificationServices": "浏览插件目录来安装额外的通知访问服务。", "AirDate": "播出日期", - "Aired": "已发布", + "Aired": "已播出", "Albums": "专辑", "Alerts": "警告", "All": "全部", "AllChannels": "所有频道", - "AllComplexFormats": "所有高级特效格式字幕(ASS, SSA, VOBSUB, PGS, SUB/IDX 等)", + "AllComplexFormats": "所有复杂格式字幕 (ASS, SSA, VOBSUB, PGS, SUB, IDX 等)", "AllEpisodes": "所有剧集", "AllLanguages": "所有语言", "AllLibraries": "所有媒体库", "AllowHWTranscodingHelp": "允许调谐器即时转码。这可能有助于减少Jellyfin 媒体服务器的转码工作。", "AllowMediaConversion": "允许媒体转换", - "AllowMediaConversionHelp": "授予或拒绝访问转换媒体功能。", - "AllowOnTheFlySubtitleExtraction": "允许同时提取字幕", + "AllowMediaConversionHelp": "授予或拒绝访问媒体转换功能。", + "AllowOnTheFlySubtitleExtraction": "允许实时提取字幕", "AllowOnTheFlySubtitleExtractionHelp": "为避免视频转码,可以从视频中提取内置的字幕,并以纯文本的形式发送给客户端。在某些系统中这个提取的进程可能会花费较长时间并导致视频播放出现卡顿。如果禁用这个选项,当内置字幕不能被客户端设备原生支持时,字幕将通过视频转码被烧录进视频中。", "AllowRemoteAccess": "允许与此 Jellyfin 服务器进行远程连接。", "AllowRemoteAccessHelp": "如果取消勾选,所有的远程连接将会被阻止。", - "AllowedRemoteAddressesHelp": "将允许远程连接的网络的 ip 地址或 ip/网掩码条目的逗号分隔列表。如果留空, 则允许所有远程地址。", - "AlwaysPlaySubtitles": "总是显示字幕", + "AllowedRemoteAddressesHelp": "允许远程连接的 IP 地址或 IP/子网掩码 的列表,以逗号分隔。如果留空,则允许所有远程地址。", + "AlwaysPlaySubtitles": "总是显示", "AlwaysPlaySubtitlesHelp": "无论音频为何种语言,都将加载与语言偏好匹配的字幕。", "Anytime": "任何时间", - "AroundTime": "{0} 左右", + "AroundTime": "大约", "Artists": "艺术家", "AsManyAsPossible": "尽可能多", "Ascending": "升序", @@ -48,7 +48,7 @@ "Books": "书籍", "Browse": "浏览", "BrowsePluginCatalogMessage": "浏览我们的插件目录来查看现有插件。", - "BurnSubtitlesHelp": "根据字幕格式确定服务器在转换视频时是否应压制字幕。避免压制字幕会提高服务器性能。选择“自动”以压制基于图像的字幕格式(如 VOBSUB, PGS, SUB/IDX 等)和一些复杂的 ASS/SSA 字幕。", + "BurnSubtitlesHelp": "服务器在转换视频时是否应压制字幕。避免压制字幕会提高服务器性能。选择“自动”以压制基于图像的字幕格式(如 VOBSUB, PGS, SUB, IDX 等)和一些复杂的 ASS/SSA 字幕。", "ButtonAdd": "添加", "ButtonAddMediaLibrary": "添加媒体库", "ButtonAddScheduledTaskTrigger": "添加触发器", @@ -217,7 +217,7 @@ "EveryNDays": "每 {0} 天", "ExitFullscreen": "退出全屏", "ExtraLarge": "特大", - "ExtractChapterImagesHelp": "提取章节图像将允许客户端显示一个图像形式的场景选择菜单。这个提取的过程可能会非常缓慢、占用大量 CPU 资源,并且可能需要几个GB的硬盘空间。提取将会在视频被发现时启动,同时也作为一个夜间计划任务运行。这个任务可以在“计划任务”选项中进行设置。不建议在高峰使用时间运行这个任务。", + "ExtractChapterImagesHelp": "提取剧集图片将允许客户端显示一个图像形式的场景选择菜单。这个提取的过程可能会非常缓慢、占用大量 CPU 资源,并且可能需要几个GB的硬盘空间。提取将会在视频被发现时启动,同时也作为一个夜间计划任务运行。这个任务可以在“计划任务”选项中进行设置。不建议在高峰使用时间运行这个任务。", "Extras": "额外", "FFmpegSavePathNotFound": "我们无法通过你输入的路径定位 FFmpeg。FFprobe 同样也是必要的并且应该被放在同一个文件夹中。他们通常会被打包在一起以供下载。请检查这个路径然后再试一次。", "FastForward": "快进", @@ -277,7 +277,7 @@ "HeaderCastCrew": "演职人员", "HeaderChannelAccess": "频道访问", "HeaderChannels": "频道", - "HeaderChapterImages": "章节图片", + "HeaderChapterImages": "剧集图片", "HeaderCodecProfile": "编解码器配置", "HeaderCodecProfileHelp": "编解码器的配置文件标明了设备播放特定编码时的限制。如果在限制之内则媒体将被转码,否则编解码器将被配置为直接播放。", "HeaderConfigureRemoteAccess": "配置远程访问", @@ -449,12 +449,12 @@ "Hide": "隐藏", "HideWatchedContentFromLatestMedia": "从最新媒体中隐藏已观看的内容", "Home": "首页", - "HttpsRequiresCert": "要启用安全连接, 您需要提供一个受信任的 SSL 证书, 如 \"允许加密\"。请提供证书或禁用安全连接。", + "HttpsRequiresCert": "要启用安全连接, 您需要提供一个受信任的 SSL 证书, 例如 Let's Encrypt 。请提供证书或禁用安全连接。", "Identify": "识别", "Images": "图片", "ImportFavoriteChannelsHelp": "如果启用,只有在协调器设备中被标记为我的最爱的频道才会被导入。", "ImportMissingEpisodesHelp": "如果启用,会将缺少的剧集信息导入到你的 Jellyfin 数据库并分季分剧显示。可能会大大延长媒体库扫描时间。", - "InstallingPackage": "正在安装 {0}", + "InstallingPackage": "正在安装 {0}(版本 {1})", "InstantMix": "即时混音", "ItemCount": "{0} 项", "Kids": "儿童", @@ -548,7 +548,7 @@ "LabelEmbedAlbumArtDidl": "在DIDL中嵌入专辑封面", "LabelEmbedAlbumArtDidlHelp": "有些设备首选这种方式获取专辑封面。启用该选项可能导致其他设备播放失败。", "LabelEnableAutomaticPortMap": "开启自动端口映射", - "LabelEnableAutomaticPortMapHelp": "尝试通过UPnP将公共端口自动映射到本地端口。这可能不适用于某些型号的路由器。需要服务器重新启动后才会应用更改。", + "LabelEnableAutomaticPortMapHelp": "通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", "LabelEnableBlastAliveMessages": "爆发活动信号", "LabelEnableBlastAliveMessagesHelp": "如果该服务器不能被网络中的其他UPnP设备检测到,请启用此选项。", "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔(秒)", @@ -568,8 +568,8 @@ "LabelEpisodeNumber": "集号:", "LabelEvent": "事件:", "LabelEveryXMinutes": "每:", - "LabelExtractChaptersDuringLibraryScan": "媒体库扫描过程中解压章节图像", - "LabelExtractChaptersDuringLibraryScanHelp": "当媒体库导入视频并扫描时,将提取章节图像。否则,章节图像将在之后的计划任务提取,而媒体库会更快完成扫描。", + "LabelExtractChaptersDuringLibraryScan": "媒体库扫描过程中解压剧集图片", + "LabelExtractChaptersDuringLibraryScanHelp": "当媒体库导入视频并扫描时,将提取剧集图片。否则,剧集图片将在之后的计划任务提取,而媒体库会更快完成扫描。", "LabelFailed": "失败", "LabelFileOrUrl": "文件或网址:", "LabelFinish": "完成", @@ -670,7 +670,7 @@ "LabelNumberOfGuideDays": "下载几天的节目指南:", "LabelNumberOfGuideDaysHelp": "下载更多天的节目指南可以帮你进一步查看节目列表并做出提前安排,但下载过程也将耗时更久。它将基于频道数量自动选择。", "LabelOptionalNetworkPath": "(可选的)共享的网络文件夹:", - "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件。", + "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件,例如 {0} 或者 {1}。", "LabelOriginalAspectRatio": "原始长宽比:", "LabelOriginalTitle": "原标题:", "LabelOverview": "内容概述:", @@ -871,7 +871,7 @@ "MessageNoAvailablePlugins": "没有可用的插件。", "MessageNoMovieSuggestionsAvailable": "没有可用的电影建议。开始观看你的电影并进行评分,再回过头来查看你的建议。", "MessageNoPluginsInstalled": "你没有安装插件。", - "MessageNoTrailersFound": "未发现任何预告片。安装 Trailer channel 以通过添加一个网络预告片媒体库来增强你的电影体验。", + "MessageNoTrailersFound": "安装 Trailer channel 以通过添加一个网络预告片媒体库来增强你的电影体验。", "MessageNothingHere": "这里没有可显示的内容。", "MessagePasswordResetForUsers": "下列用户的密码已被重置。他们现在可以用执行复位的pin码登录。", "MessagePleaseEnsureInternetMetadata": "请确认已启用从网络上下载媒体资料的选项。", @@ -921,9 +921,9 @@ "Normal": "普通", "NumLocationsValue": "{0} 个文件夹", "OneChannel": "一个频道", - "OnlyForcedSubtitles": "只显示强制字幕", + "OnlyForcedSubtitles": "仅强制字幕", "OnlyForcedSubtitlesHelp": "只有被标记为“强制”的字幕会被加载。", - "OnlyImageFormats": "仅图像格式(VOBSUB, PGS, SUB等)", + "OnlyImageFormats": "仅图像格式(VOBSUB, PGS, SUB 等)", "OptionAdminUsers": "管理员", "OptionAlbum": "专辑", "OptionAlbumArtist": "专辑艺术家", @@ -998,7 +998,7 @@ "OptionEstimateContentLength": "转码时,估计内容长度", "OptionEveryday": "每天", "OptionExternallyDownloaded": "外部下载", - "OptionExtractChapterImage": "开启章节图像提取", + "OptionExtractChapterImage": "开启剧集图片提取", "OptionFavorite": "我的最爱", "OptionFriday": "星期五", "OptionHasSpecialFeatures": "特殊功能", @@ -1020,7 +1020,7 @@ "OptionMissingEpisode": "缺少的剧集", "OptionMonday": "星期一", "OptionNameSort": "名字", - "OptionNew": "更新...", + "OptionNew": "新建…", "OptionNone": "没有", "OptionOnAppStartup": "在程序启动时", "OptionOnInterval": "在一个期间", @@ -1066,9 +1066,9 @@ "OptionWeekly": "每周", "OriginalAirDateValue": "首映日期:{0}", "Overview": "概述", - "PackageInstallCancelled": "{0} 安装被取消。", - "PackageInstallCompleted": "{0} 安装完成。", - "PackageInstallFailed": "{0} 安装失败。", + "PackageInstallCancelled": "{0}(版本 {1}) 的安装被取消。", + "PackageInstallCompleted": "{0}(版本 {1})安装完成。", + "PackageInstallFailed": "{0}(版本 {1})安装失败。", "ParentalRating": "家长分级", "PasswordMatchError": "密码和确认密码必须匹配。", "PasswordResetComplete": "密码已重置。", @@ -1134,7 +1134,7 @@ "RunAtStartup": "开机时启动", "Runtime": "播放时长", "Saturday": "星期六", - "Save": "储存", + "Save": "保存", "SaveSubtitlesIntoMediaFolders": "保存字幕到媒体所在文件夹", "SaveSubtitlesIntoMediaFoldersHelp": "将字幕存储在视频文件旁边可以跟方便的管理他们。", "ScanForNewAndUpdatedFiles": "扫描新的和有修改的文件", @@ -1313,7 +1313,7 @@ "Directors": "导演", "ColorTransfer": "色彩转换", "ConfirmDeleteItem": "这将同时在磁盘和媒体库中删除这个项目。确认删除?", - "ConfirmDeleteItems": "这将同时在磁盘和媒体库中删除这些项目。确认删除?", + "ConfirmDeleteItems": "从媒体库中删除这些项目时,也会同时从文件系统中删除。确定要删除吗?", "ConfirmEndPlayerSession": "确认要关闭位于{0}的Jellyfin吗?", "ValueSeconds": "{0}秒", "Features": "功能", @@ -1334,9 +1334,9 @@ "AuthProviderHelp": "选择用于验证此用户密码的身份验证提供者。", "ColorPrimaries": "基色", "ConfigureDateAdded": "在Jellyfin Server仪表板媒体库的设置里确认如何添加日期", - "DisplayModeHelp": "选择您正在运行Jellyfin的屏幕类型。", + "DisplayModeHelp": "选择您想要的界面布局风格。", "EnableColorCodedBackgrounds": "彩色背景", - "ErrorDeletingItem": "服务器删除项目时出错。请确认服务器具有对媒体文件夹的写入权限并重试。", + "ErrorDeletingItem": "从 Jellyfin Server 删除项目时出错。请确认 Jellyfin Server 是否拥有对媒体目录的写权限,然后重试。", "GroupBySeries": "按系列分组", "HeaderApp": "应用程序", "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但容器格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", @@ -1350,7 +1350,6 @@ "HeaderNextEpisodePlayingInValue": "下一集在 {0} 后播放", "HeaderNextVideoPlayingInValue": "下一部影片在 {0} 后播放", "HeaderPlayOn": "播放在", - "HeaderRestartingServer": "服务器重启中", "HeaderSeriesStatus": "系列状态", "HeaderStopRecording": "停止录制", "Horizontal": "横向", @@ -1358,7 +1357,7 @@ "LabelKeepUpTo": "保持:", "LabelPasswordResetProvider": "密码重置提供者:", "LabelPersonRoleHelp": "示例:冰淇淋卡车司机", - "LabelSelectFolderGroups": "自动将下列文件夹中的内容分组到视图中,如电影、音乐、剧集:", + "LabelSelectFolderGroups": "自动将来自下列文件夹的内容分组至电影、音乐、电视等视图中:", "LabelSelectFolderGroupsHelp": "未选中的文件夹将显示在自身的视图中。", "LabelUserLoginAttemptsBeforeLockout": "用户被封禁前可尝试的次数:", "DashboardVersionNumber": "版本:{0}", @@ -1398,7 +1397,6 @@ "Recordings": "录音", "RefreshDialogHelp": "元数据根据设置和Jellyfin服务器中启用的网络服务进行刷新。", "RepeatEpisodes": "重复剧集", - "RestartPleaseWaitMessage": "请等待Jellyfin服务关闭和重启,这将花费几分钟时间。", "Schedule": "日程", "Screenshot": "屏幕截图", "SeriesDisplayOrderHelp": "按播出日期、DVD 顺序或编号对剧集进行排序。", @@ -1407,7 +1405,6 @@ "Shows": "节目", "SkipEpisodesAlreadyInMyLibraryHelp": "将使用季和剧集编号对剧集进行比较。", "Smaller": "更小", - "StatsForNerds": "统计数据", "SubtitleSettings": "字幕设置", "SubtitleSettingsIntro": "要配置默认字幕外观和语言设置,请停止视频播放,然后单击应用程序右上角的用户图标。", "TagsValue": "标签:{0}", @@ -1456,7 +1453,7 @@ "ButtonAddImage": "添加图片", "LabelPlayer": "播放器:", "LabelBaseUrl": "基础 URL:", - "LabelBaseUrlHelp": "您可以在此处添加自定义子目录,以便从更唯一的 URL 访问服务器。", + "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>。", "MusicLibraryHelp": "重播 {0}音乐命名指南{1}。", "HeaderFavoritePeople": "最喜欢的人物", "OptionRandom": "随机", @@ -1465,15 +1462,121 @@ "HeaderNavigation": "导航", "CopyStreamURLError": "复制URL地址时发生错误。", "MessageConfirmAppExit": "你要退出吗?", - "EnableFastImageFadeIn": "快速图片淡入", - "EnableFastImageFadeInHelp": "为已加载的图片启用更快的图片淡入动画", "OptionForceRemoteSourceTranscoding": "强制远程转码(像电视直播一样)", "NoCreatedLibraries": "看上去您还未创建任何资料库。{0} 您想现在创建一个吗? {1}", "AskAdminToCreateLibrary": "请联系管理员以创建一个新的资料库。", - "PlaybackErrorNoCompatibleStream": "客户端配置文件存在问题,服务器未发送兼容的媒体格式。", - "AllowFfmpegThrottlingHelp": "当转码或再封装的进度大幅超过当前播放位置时,暂停该进程,以使其消耗更少的资源。在观看时不经常调整播放进度的情况下,这将非常有用。如果遇到播放问题,请关闭此功能。", + "PlaybackErrorNoCompatibleStream": "该客户端与媒体不兼容,服务器未发送兼容的媒体格式。", + "AllowFfmpegThrottlingHelp": "当转码或者再封装的进度距离当前播放进度足够远时,暂停这个过程以减少资源消耗。当观看视频时不经常调整播放进度的情况下,这个功能将非常有用。如果你遇到了播放问题,请关闭这个选项。", "AllowFfmpegThrottling": "限制转码速度", "PreferEmbeddedEpisodeInfosOverFileNames": "优先使用内置的剧集信息而不是文件名", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "这将在内置元数据含剧集信息时使用内置信息。", - "ClientSettings": "客户端设置" + "ClientSettings": "客户端设置", + "Track": "音轨", + "Season": "季", + "ReleaseGroup": "发行组", + "Person": "人物", + "OtherArtist": "其他艺术家", + "Movie": "电影", + "Episode": "剧集", + "BoxSet": "套装", + "Artist": "艺术家", + "AlbumArtist": "专辑艺术家", + "Album": "专辑", + "ListPaging": "{2} 的 {0}-{1}", + "WeeklyAt": "每周第 {0} 天的 {1}", + "OnWakeFromSleep": "当从睡眠中唤醒时", + "DailyAt": "每天的 {0}", + "LastSeen": "上次观看 {0}", + "PersonRole": "作为 {0}", + "OnApplicationStartup": "当应用启动时", + "EveryXHours": "每 {0} 小时", + "EveryHour": "每小时", + "EveryXMinutes": "每 {0} 分钟", + "WriteAccessRequired": "Jellyfin 服务端需要此文件夹的写入权限。请确认是否拥有写入权限并重试。", + "PathNotFound": "无法找到此路径。请确认路径有效并重试。", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "LabelDeinterlaceMethod": "反交错方法:", + "DeinterlaceMethodHelp": "选择对隔行扫描内容进行转码时所用的反交错方法。", + "LabelLibraryPageSize": "媒体库分页阈值:", + "LabelLibraryPageSizeHelp": "设置媒体库页面每页要显示的最多媒体个数。设置为 0 以禁用分页。", + "UnsupportedPlayback": "Jellyfin无法解密被DRM保护的内容,但仍然会尝试播放包括受保护内容在内的所有内容。某些文件由于被加密或包含不受支持的特性(如互动标题),在播放时可能显示为黑屏。", + "MessageUnauthorizedUser": "您目前无权访问服务器。更多有关信息,请与服务器管理员联系。", + "Filter": "过滤", + "New": "新的", + "HeaderFavoritePlaylists": "收藏的播放列表", + "ButtonTogglePlaylist": "播放列表", + "ButtonToggleContextMenu": "更多", + "HeaderServerAddressSettings": "服务器地址设置", + "HeaderRemoteAccessSettings": "远程访问设置", + "HeaderHttpsSettings": "HTTPS 设置", + "ApiKeysCaption": "当前启用的 API 密钥", + "TabDVR": "DVR", + "SaveChanges": "保存更改", + "LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。", + "LabelRequireHttps": "强制 HTTPS", + "LabelStable": "稳定版", + "LabelEnableHttpsHelp": "开启服务器对所配置 HTTPS 端口的监听。必须配置有效的证书才会生效。", + "LabelEnableHttps": "启用 HTTPS", + "LabelChromecastVersion": "Chromecast版本", + "HeaderDVR": "DVR", + "LabelNightly": "开发版", + "MessageSyncPlayErrorAccessingGroups": "访问群组列表时发生错误。", + "MessageSyncPlayLibraryAccessDenied": "对此内容的访问受到限制。", + "MessageSyncPlayCreateGroupDenied": "需要权限以创建群组。", + "MessageSyncPlayGroupDoesNotExist": "无法加入群组,因为该群组不存在。", + "MessageSyncPlayPlaybackPermissionRequired": "需要播放权限。", + "MessageSyncPlayGroupWait": "{0} 正在缓冲...", + "MessageSyncPlayUserLeft": "{0} 已离开群组。", + "MessageSyncPlayUserJoined": "{0} 已加入该群组。", + "LabelSyncPlayAccessNone": "禁用此用户", + "LabelSyncPlayAccessJoinGroups": "允许用户加入群组", + "LabelSyncPlayAccessCreateAndJoinGroups": "允许用户创建和加入群组", + "LabelSyncPlayLeaveGroup": "离开群组", + "LabelSyncPlayNewGroupDescription": "创建一个新的群组", + "LabelSyncPlayNewGroup": "新建群组", + "LabelSyncPlaySyncMethod": "同步方式:", + "MillisecondsUnit": "毫秒", + "LabelSyncPlayTimeOffset": "服务器时间偏移:", + "HeaderSyncPlayEnabled": "同步播放已启用", + "HeaderSyncPlaySelectGroup": "加入群组", + "EnableDetailsBannerHelp": "在项目详细信息页面的顶部显示横幅图片。", + "EnableDecodingColorDepth10": "启用 10-Bit 硬件解码", + "EnableDecodingColorDepth10Help": "在支持的硬件上启用 10-Bit 硬件解码。仅对 HEVC 和 VP9 格式起作用。如果你遇到了播放问题,请关闭这个选项。", + "LabelSyncPlayLeaveGroupDescription": "关闭同步播放", + "EnableDetailsBanner": "详细信息页面的横幅", + "ButtonCast": "投射", + "ButtonSyncPlay": "SyncPlay", + "EnableBlurhashHelp": "仍在加载的图片将显示带有模糊的占位符", + "EnableBlurhash": "为图片启用模糊的占位符", + "SyncPlayAccessHelp": "为此用户选择对同步播放功能的访问级别。同步播放让你可以和其他设备同步播放进度。", + "ShowMore": "显示更多", + "ShowLess": "显示更少", + "MessageSyncPlayErrorMedia": "同步播放启用失败!媒体错误。", + "MessageSyncPlayErrorMissingSession": "同步播放启用失败!找不到 Session。", + "MessageSyncPlayErrorNoActivePlayer": "未找到活动的用户。同步播放已被关闭。", + "MessageSyncPlayJoinGroupDenied": "需要权限以使用同步播放。", + "MessageSyncPlayNoGroupsAvailable": "暂无可用的群组。先播放点什么吧。", + "MessageSyncPlayDisabled": "同步播放已关闭。", + "MessageSyncPlayEnabled": "同步播放已开启。", + "LabelSyncPlayPlaybackDiff": "播放时间差距:", + "EnableFasterAnimationsHelp": "使用更快的动画和转场效果", + "EnableFasterAnimations": "更快的动画", + "EnableDecodingColorDepth10Vp9": "启用 VP9 10-Bit 硬件解码", + "EnableDecodingColorDepth10Hevc": "启用 HEVC 10-Bit 硬件解码", + "HeaderNewRepository": "新建存储库", + "TabRepositories": "存储库", + "MessageNoGenresAvailable": "启用一些元数据提供程序以便从互联网获取媒体风格。", + "MessageAddRepository": "如果要添加存储库,请单击标题旁边的按钮,然后填写所需的信息。", + "LabelRepositoryNameHelp": "一个自定义名称,用于区分该存储库和添加到服务器的任何其他存储库。", + "LabelRepositoryName": "存储库名称", + "LabelRepositoryUrlHelp": "您要添加的存储库清单的位置。", + "LabelRepositoryUrl": "存储库 URL", + "MessageNoRepositories": "暂无存储库。", + "LabelSyncPlayAccess": "同步播放访问控制", + "ButtonPlayer": "播放器", + "ClearQueue": "清空队列", + "StopPlayback": "停止播放", + "Writers": "作者", + "ViewAlbumArtist": "查看专辑艺术家" } diff --git a/src/strings/zh-hk.json b/src/strings/zh-hk.json index 2d9634d333c..551f6ec1ae6 100644 --- a/src/strings/zh-hk.json +++ b/src/strings/zh-hk.json @@ -1,7 +1,7 @@ { "Add": "添加", - "ButtonAdd": "新增", - "ButtonAddScheduledTaskTrigger": "新增觸發", + "ButtonAdd": "增加", + "ButtonAddScheduledTaskTrigger": "新增觸發點", "ButtonAddUser": "添加用戶", "ButtonCancel": "取消", "ButtonDelete": "删除", @@ -10,12 +10,12 @@ "ButtonFilter": "過濾", "ButtonHelp": "幫助", "ButtonManualLogin": "手動登入", - "ButtonNew": "最新", + "ButtonNew": "新增", "ButtonOk": "確定", "ButtonPlay": "播放", "ButtonQuickStartGuide": "快速入門指南", "ButtonRefresh": "重新整理", - "ButtonRefreshGuideData": "重新整理電視指南資料", + "ButtonRefreshGuideData": "重新整理指南資料", "ButtonRemove": "清除", "ButtonRename": "重新命名", "ButtonResetPassword": "重設密碼", @@ -286,7 +286,7 @@ "TabAdvanced": "進階", "TabAlbumArtists": "唱片歌手", "TabAlbums": "專輯", - "TabArtists": "歌手", + "TabArtists": "藝人", "TabCatalog": "目錄", "TabChannels": "頻道", "TabCollections": "藏品", @@ -339,7 +339,7 @@ "Anytime": "任何時間", "AnyLanguage": "任何語言", "Artists": "藝人", - "AsManyAsPossible": "盡可能地越多越好", + "AsManyAsPossible": "越多越好", "Audio": "音頻", "Auto": "自動", "AutoBasedOnLanguageSetting": "自動 (基於語言設定)", @@ -359,15 +359,152 @@ "HeaderFavoriteSongs": "最愛的歌曲", "HeaderFavoriteShows": "最愛的節目", "HeaderFavoriteEpisodes": "最愛的劇集", - "HeaderFavoriteArtists": "最愛藝術家", + "HeaderFavoriteArtists": "最愛的藝人", "HeaderFavoriteAlbums": "最愛專輯", "HeaderContinueWatching": "繼續觀看", - "HeaderAlbumArtists": "專輯藝術家", + "HeaderAlbumArtists": "專輯藝人", "Genres": "風格", "Folders": "檔案夾", "Favorites": "我的最愛", "Collections": "合輯", "Channels": "頻道", "Books": "圖書", - "Albums": "專輯" + "Albums": "專輯", + "Absolute": "絕對", + "AuthProviderHelp": "選擇用於驗證該用戶密碼的身份驗證提供程序。", + "AttributeNew": "新", + "AspectRatio": "長寬比", + "AskAdminToCreateLibrary": "要求管理員創建一個庫。", + "Ascending": "上升", + "Artist": "藝人", + "Art": "藝術", + "AroundTime": "大約", + "AlwaysPlaySubtitlesHelp": "無論語言是哪種音頻,都將加載與語言首選項匹配的字幕。", + "AllowedRemoteAddressesHelp": "IP地址或IP /網絡掩碼條目的逗號分隔列表,用於允許遠程連接的網絡。 如果保留為空白,將允許所有遠程地址。", + "AllowRemoteAccessHelp": "如果未選中,則將阻止所有遠程連接。", + "AllowRemoteAccess": "允許與此Jellyfin服務器的遠程連接。", + "AllowFfmpegThrottlingHelp": "當轉碼或無損複製進度遠超於當前播放位置,暫停進程可減少資源消耗。 在不經常觀看的情況下,此功能最為有用。 如果遇到播放問題,請關閉此功能。", + "AllowOnTheFlySubtitleExtractionHelp": "可以從視頻中提取嵌入式字幕,然後以純文本格式將其交付給客戶端,以幫助防止視頻轉碼。 在某些系統上,這可能需要很長時間,並且會導致提取過程中視頻播放停止。 如果客戶端設備本身不支持嵌入的字幕,則可以禁用此選項以通過視頻轉碼刻錄字幕。", + "AllowOnTheFlySubtitleExtraction": "允許即時提取字幕", + "AllowMediaConversionHelp": "授予或拒絕訪問轉換媒體功能的權限。", + "AllowMediaConversion": "允許媒體轉換", + "AllowHWTranscodingHelp": "允許調諧器即時轉碼。 這可減少使用伺服器轉碼。", + "AllLibraries": "所有媒體庫", + "AllEpisodes": "所有劇集", + "AllComplexFormats": "所有格式(ASS,SSA,VOBSUB,PGS,SUB,IDX等)", + "AllChannels": "所有頻道", + "Alerts": "警報", + "AlbumArtist": "專輯歌手", + "Album": "專輯", + "Aired": "已播出", + "AirDate": "播出日期", + "AdditionalNotificationServices": "瀏覽插件目錄以安裝其他通知服務。", + "AddToPlayQueue": "添加到播放隊列", + "AddToCollection": "添加到收藏", + "AddItemToCollectionHelp": "搜尋物件並使用右鍵點擊或點擊菜單將他們添加到收藏中。", + "AccessRestrictedTryAgainLater": "目前存取受限。 請稍後再試。", + "AllowFfmpegThrottling": "轉碼調節器", + "Dislike": "不喜歡", + "Disconnect": "中斷連接", + "Disc": "片", + "Disabled": "已停用", + "Directors": "導演", + "Director": "導演", + "DirectStreaming": "直接直播", + "DirectPlaying": "直接播放", + "DetectingDevices": "偵測裝置中", + "Desktop": "桌面", + "Descending": "倒序", + "DeleteUserConfirmation": "你確定要刪除此用戶嗎?", + "DeleteUser": "刪除用戶", + "DeleteMedia": "刪除媒體", + "DeleteImageConfirmation": "你確定要刪除此圖片嗎?", + "DeleteImage": "刪除圖片", + "DefaultErrorMessage": "處理此請求時發生錯誤,請稍後再嘗試。", + "Default": "預設", + "DateAdded": "日期已新增", + "CopyStreamURLError": "複製URL時發生錯誤。", + "CopyStreamURLSuccess": "成功複製URL。", + "CopyStreamURL": "複製直播URL", + "ContinueWatching": "繼續觀看", + "Connect": "連接", + "ConfirmEndPlayerSession": "你要關閉 {0} 的Jellyfin嗎?", + "ConfirmDeleteImage": "刪除圖片?", + "CommunityRating": "社群分數", + "ClientSettings": "客戶端設定", + "ChannelNumber": "頻道號碼", + "ChannelNameOnly": "只有頻道 {0}", + "Categories": "分類", + "CancelSeries": "取消片集", + "CancelRecording": "取消錄影", + "ButtonWebsite": "網頁", + "ButtonViewWebsite": "瀏覽網頁", + "ButtonUninstall": "解除安裝", + "ButtonTrailer": "預告", + "ButtonTogglePlaylist": "播放清單", + "ButtonToggleContextMenu": "更多", + "ButtonSplit": "分開", + "ButtonStop": "停止", + "ButtonStart": "開始", + "ButtonShutdown": "關閉", + "ButtonSettings": "設定", + "ButtonSend": "傳送", + "ButtonSelectServer": "選擇伺服器", + "ButtonSearch": "搜尋", + "ButtonScanAllLibraries": "掃瞄所有媒體櫃", + "ButtonRevoke": "撤銷", + "ButtonResume": "恢復", + "ButtonResetEasyPassword": "重設PIN碼", + "ButtonRepeat": "重複", + "ButtonProfile": "檔案", + "ButtonPause": "暫停", + "ButtonParentalControl": "家長控制", + "ButtonOpen": "開啟", + "ButtonNetwork": "網絡", + "ButtonMore": "更多", + "ButtonLearnMore": "了解更多", + "ButtonInfo": "資訊", + "ButtonHome": "主頁", + "ButtonGuide": "教學", + "ButtonGotIt": "了解", + "ButtonFullscreen": "全螢幕", + "ButtonForgotPassword": "忘記密碼", + "ButtonEditOtherUserPreferences": "更改用戶個人檔案,圖片及個人偏好。", + "ButtonEditImages": "更改圖片", + "ButtonDownload": "下載", + "ButtonConnect": "連接", + "ButtonChangeServer": "更換伺服器", + "ButtonBack": "返回", + "ButtonAudioTracks": "音軌", + "ButtonArrowUp": "箭咀上", + "ButtonArrowRight": "箭咀右", + "ButtonArrowLeft": "箭咀左", + "ButtonArrowDown": "箭咀下", + "ButtonAddServer": "新增伺服器", + "ButtonAddMediaLibrary": "新增媒體櫃", + "ButtonAddImage": "新增圖片", + "Browse": "瀏覽", + "Blacklist": "黑名單", + "BirthPlaceValue": "出生地點", + "BirthDateValue": "出生日期", + "Banner": "橫幅", + "Backdrops": "背景", + "Backdrop": "背景", + "AlwaysPlaySubtitles": "經常播放", + "Display": "顯示", + "EnableBackdrops": "背景", + "EditImages": "更改圖片", + "DownloadsValue": "下載數目 {0}", + "Download": "下載", + "DisplayInMyMedia": "在主頁顯示", + "ButtonSyncPlay": "SyncPlay", + "ButtonDown": "向下", + "BurnSubtitlesHelp": "確定若服務器對視頻進行轉碼時是否嵌入字幕。 關閉這功能將使用更少時間。 選擇'自動'可嵌入基於圖像的格式字幕(VOBSUB,PGS,SUB,IDX等)和某些ASS或SSA字幕。", + "BrowsePluginCatalogMessage": "瀏覽我們的插件目錄以查看可用的插件。", + "BoxRear": "盒裝(背面)", + "BoxSet": "套裝", + "Box": "盒裝", + "Composer": "作曲家", + "ButtonPreviousTrack": "上一曲", + "ButtonNextTrack": "下一曲" } diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index 718cb4d5726..4c090dcf255 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -45,7 +45,7 @@ "Edit": "編輯", "EditImages": "編輯圖片", "Ended": "完結", - "EndsAtValue": "完結於{0}", + "EndsAtValue": "於 {0} 結束", "Favorite": "加到最愛", "File": "檔案", "FileNotFound": "未找到檔案。", @@ -67,13 +67,13 @@ "HeaderDeleteItem": "刪除項目", "HeaderEasyPinCode": "簡易 PIN 碼", "HeaderFeatureAccess": "可以使用的功能", - "HeaderFetchImages": "抓取圖像:", + "HeaderFetchImages": "擷取圖像:", "HeaderForgotPassword": "忘記密碼", "HeaderFrequentlyPlayed": "經常播放", "HeaderGuideProviders": "節目表提供者", "HeaderImageSettings": "圖像設置", "HeaderInstantMix": "瞬時混播", - "HeaderLatestEpisodes": "最新節目單元", + "HeaderLatestEpisodes": "最新劇集", "HeaderLatestMovies": "最新電影", "HeaderLatestRecordings": "最新錄影的節目", "HeaderLatestSongs": "最新歌曲", @@ -106,7 +106,7 @@ "LabelAllowServerAutoRestart": "允許伺服器自動重新啟動去安裝更新資料", "LabelAllowServerAutoRestartHelp": "伺服器只會在沒有使用者在使用時重新啟動。", "LabelAudioLanguagePreference": "音頻語言偏好選項:", - "LabelCachePath": "緩存路徑:", + "LabelCachePath": "快取路徑:", "LabelCollection": "收藏櫃:", "LabelContentType": "內容類型:", "LabelCountry": "國家:", @@ -116,7 +116,7 @@ "LabelEnableDlnaClientDiscoveryInterval": "尋找客戶端時間間隔(秒)", "LabelEnableDlnaDebugLogging": "記錄 DLNA 除錯資料到日誌", "LabelEnableDlnaDebugLoggingHelp": "將會建立非常大的日誌檔案,建議在進行故障排除時啟用。", - "LabelEnableDlnaPlayTo": "播放到DLNA設備", + "LabelEnableDlnaPlayTo": "播放到 DLNA 設備", "LabelEnableRealtimeMonitor": "啟用實時監控", "LabelEnableRealtimeMonitorHelp": "支持的檔案系統上的更改,將會立即處理。", "LabelEvent": "事件:", @@ -149,15 +149,15 @@ "LabelTime": "時間:", "LabelTriggerType": "觸發類型:", "LabelUser": "使用者:", - "LabelYourFirstName": "你的名字:", - "LabelYoureDone": "完成!", + "LabelYourFirstName": "您的名字:", + "LabelYoureDone": "完成,耶!", "LibraryAccessHelp": "選擇媒體資料夾與此使用者共享。管理員將可以使用中繼資料管理器編輯所有的媒體資料夾。", "Like": "喜歡", "MaxParentalRatingHelp": "具有較高的家長評級內容將從這使用者被隱藏。", "MessageAreYouSureDeleteSubtitles": "您真的要刪除這個字幕檔嗎?", "MessageDownloadQueued": "下載已排程。", "MessageItemsAdded": "已新增項目。", - "MessageNoMovieSuggestionsAvailable": "目前並沒有推薦的電影。開始觀看並對您的電影評分後,我們就會為您推薦您可能會喜歡的內容。", + "MessageNoMovieSuggestionsAvailable": "目前並沒有推薦的電影,開始觀看並對您的電影評分後,我們就會為您推薦您可能會喜歡的內容。", "MessageNothingHere": "這裡沒有什麼。", "MessagePasswordResetForUsers": "下列使用者的密碼已被重新設置。該使用者現在可以使用在密碼重置時所使用之 PIN 代碼進行登入。", "MessagePleaseEnsureInternetMetadata": "請確保已啟用從互聯網下載媒體資料。", @@ -181,7 +181,7 @@ "OptionBluray": "藍光", "OptionCommunityRating": "社區評分", "OptionContinuing": "持續", - "OptionCriticRating": "評論家評價", + "OptionCriticRating": "影評人評價", "OptionDaily": "每日", "OptionDateAdded": "新增日期", "OptionDatePlayed": "播放日期", @@ -212,7 +212,7 @@ "OptionMissingEpisode": "缺少了的單元", "OptionMonday": "星期一", "OptionNameSort": "名字", - "OptionNew": "新增...", + "OptionNew": "新增…", "OptionOnAppStartup": "在伺服器啟動", "OptionOnInterval": "每時段", "OptionParentalRating": "家長評級", @@ -235,7 +235,7 @@ "OptionWeekly": "每週", "OriginalAirDateValue": "原始播出日期:{0}", "ParentalRating": "Parental Rating", - "PasswordMatchError": "密碼和密碼確認必須一致。", + "PasswordMatchError": "兩個密碼必須一致。", "PasswordResetComplete": "密碼已重設。", "PasswordResetConfirmation": "你確定要重設密碼?", "PasswordSaved": "密碼已保存。", @@ -245,8 +245,8 @@ "RecordingCancelled": "已取消排程錄製。", "RecordingScheduled": "已排程錄製。", "Refresh": "重新整理", - "RefreshDialogHelp": "詳細資料的更新方式會依據Jellyfin的設定及已經啟用的網路服務來進行。", - "ReplaceAllMetadata": "取代所有詳細資料", + "RefreshDialogHelp": "詳細資料的更新方式會依據 Jellyfin 的設定及已經啟用的網路服務來進行。", + "ReplaceAllMetadata": "取代所有中繼資料", "ReplaceExistingImages": "取代現有圖片", "Saturday": "星期六", "Save": "保存", @@ -291,19 +291,19 @@ "TabTrailers": "預告", "TabTranscoding": "轉碼中", "TabUpcoming": "接下來", - "TellUsAboutYourself": "請自我介紹一下你自己", - "ThisWizardWillGuideYou": "此精靈將帶你完成安裝過程。開始之前,請選擇您慣用的語言。", + "TellUsAboutYourself": "介紹一下自己", + "ThisWizardWillGuideYou": "此精靈將帶你完成安裝過程,開始之前,請選擇您慣用的語言。", "Thursday": "星期四", - "TrackCount": "{0}個曲目", + "TrackCount": "{0} 個曲目", "Tuesday": "星期二", - "UninstallPluginConfirmation": "你確定要卸載{0}?", - "UninstallPluginHeader": "卸載插件", + "UninstallPluginConfirmation": "你確定要解除安裝 {0}?", + "UninstallPluginHeader": "解除安裝插件", "UserProfilesIntro": "Jellyfin 可單獨對使用者進行設定,所有使用者擁有自己的顯示設置,播放狀態和家長控制。", "Users": "使用者", "VersionNumber": "版本 {0}", "Wednesday": "星期三", "WelcomeToProject": "歡迎使用 Jellyfin!", - "WizardCompleted": "這就是我們所需的全部資訊,Jellyfin 現在正在收集你的媒體櫃的資料,在這段時間內,不妨參考我們推出的應用程式。按一下完成進入伺服器總覽頁。", + "WizardCompleted": "這就是我們所需的全部資訊,Jellyfin 現在正在收集你的媒體櫃的資料,在這段時間內,不妨參考我們推出的應用程式。按一下完成進入控制台。", "Actor": "演員", "AddToPlayQueue": "加入播放清單", "AddToPlaylist": "加入播放列表", @@ -367,7 +367,7 @@ "ButtonConnect": "連結", "ButtonDown": "下", "ButtonDownload": "下載", - "ButtonEditOtherUserPreferences": "編輯此使用者的內容,大頭貼和個人設定。", + "ButtonEditOtherUserPreferences": "編輯此使用者的內容、大頭貼和個人設定。", "ButtonFullscreen": "全螢幕", "ButtonHelp": "幫助", "ButtonInfo": "詳細資料", @@ -413,12 +413,11 @@ "ChannelNumber": "頻道號碼", "Channels": "頻道", "CinemaModeConfigurationHelp": "劇影院模式直接為您的客廳帶來劇場級體驗,同時還可以播放預告片和自定開場白。", - "CinemaModeConfigurationHelp2": "Jellyfin 應用程式將有一個用於啟動或關閉劇院模式的設定。 電視的應用程式預設開啟劇院模式。", "CinemaModeFeatureDescription": "劇院模式用預告片和自定開場白帶給您最真實的劇院體驗。", "CloudSyncFeatureDescription": "將您的媒體備份到雲端當作簡單的備份,收藏和轉檔。", "Collections": "合輯", "Composer": "作曲家", - "ConfigureDateAdded": "調整伺服器怎麼判定媒體褲的「新增日期」", + "ConfigureDateAdded": "調整伺服器怎麼判定媒體庫的「新增日期」", "ConfirmDeleteImage": "刪除圖片?", "ConfirmDeleteItems": "刪除這些項目會將檔案從系統和媒體庫中刪除。你真的要繼續嗎?", "ConfirmEndPlayerSession": "您要在 {0} 秒後將 Jellyfin關機嗎?", @@ -462,7 +461,7 @@ "DisplayInOtherHomeScreenSections": "在“最新媒體”和“繼續觀看“等主畫面區塊中顯示", "DisplayMissingEpisodesWithinSeasons": "顯示每季缺少的劇集", "DisplayMissingEpisodesWithinSeasonsHelp": "必須在 Jellyfin 伺服器的電視媒體庫設定中啟用該功能。", - "DisplayModeHelp": "選擇您正在運行Jellyfin的螢幕類型。", + "DisplayModeHelp": "選擇您想要的界面樣式。", "DoNotRecord": "不要錄", "Down": "下", "DownloadItemLimitHelp": "非必要。 設置要下載的項目數限制。", @@ -498,25 +497,20 @@ "EnterFFmpegLocation": "輸入 FFmpeg 路徑", "Episodes": "劇集", "Error": "錯誤", - "ErrorAddingListingsToSchedulesDirect": "在將電視節目時間表添加到您的Schedules Direct賬號時出現錯誤。每個Schedules Direct賬號只允許有限的時間表。您在繼續前可能需要登入Schedules Direct網站并刪除賬號中的其它列表。", + "ErrorAddingListingsToSchedulesDirect": "在將電視節目時間表新增到您的 Schedules Direct 帳號時出現錯誤。每個 Schedules Direct 帳號只允許有限的時間表。您在繼續前可能需要登入 Schedules Direct 網站並刪除帳號中的其它列表。", "ErrorAddingGuestAccount1": "新增Jellyfin Connect時發生錯誤。你的賓客有建立Jellyfin帳號嗎?他們可以在 {0} 創建帳號。", "ErrorAddingGuestAccount2": "若你還是遇到問題,請發送email至 {0} 並附上你和他們的email帳號。", - "ErrorAddingJellyfinConnectAccount1": "新增Jellyfin Connect時發生錯誤。您有建立Jellyfin帳號嗎?您可以在 {0} 創建帳號。", - "ErrorAddingJellyfinConnectAccount2": "若你還是遇到問題,請用發生問題的email帳號發送email至 {0}。", - "ErrorAddingJellyfinConnectAccount3": "這個 Jellyfin 帳號已經被連接至一個本地帳號。一個 Jellyfin帳號 只能同時被連接到一個本機帳號。", "ErrorAddingMediaPathToVirtualFolder": "新增媒體路徑時發生錯誤,請確認路徑是否有效,且你的 Jellyfin 伺服器有對該位置的存取權。", "ErrorAddingTunerDevice": "新增調諧器設備時發生錯誤,請確認它是否可被存取後再試一次。", - "ErrorAddingXmlTvFile": "存取 XMLTV 文件時發生錯誤。請確認該檔案是否存在後再試一次。", + "ErrorAddingXmlTvFile": "存取 XMLTV 檔案時發生錯誤,請確認該檔案是否存在後再試一次。", "ErrorConnectServerUnreachable": "處理請求時發生錯誤。您的伺服器無法與我們位於 {0} 的 Jellyfin Connect伺服器溝通。請確認你的伺服器有網路連結且防火牆或其他安全性程式允許這個程式對外溝通。", - "ErrorDeletingItem": "從Jellyfin伺服器刪除項目時發生錯誤。請確認伺服器有那個磁碟的寫入權限並再試一次。", - "ErrorGettingTvLineups": "下載電視節目表時發生錯誤,請確認你的資訊是正確的然後再試一次。", + "ErrorDeletingItem": "從 Jellyfin 伺服器刪除項目時發生錯誤,請確認伺服器對該磁碟有寫入權限並再試一次。", + "ErrorGettingTvLineups": "下載電視節目表時發生錯誤,請確認你的資訊是否正確並重試。", "ErrorMessagePasswordNotMatchConfirm": "密碼和密碼確認必須吻合。", "ErrorMessageStartHourGreaterThanEnd": "結束時間必須在開始時間後。", "ErrorMessageUsernameInUse": "用戶名已存在。請重新選個名稱再試。", "ErrorPleaseSelectLineup": "請選擇節目表,然後再試一次。如果沒有可用的節目表,請檢查您的使用者名稱、密碼和郵遞區號是否正確。", - "ErrorReachingJellyfinConnect": "連接 Jellyfin Connect 伺服器時發生錯誤。請確認你的網絡狀態是否穩定後再試一次。", - "ErrorRemovingJellyfinConnectAccount": "移除 Jellyfin Connect 帳號時發生錯誤。請確認你的網絡狀態是否穩定後再試一次。", - "ErrorSavingTvProvider": "儲存電視供應商時發生錯誤。請確認它是可存取後再試一次。", + "ErrorSavingTvProvider": "儲存電視供應商時發生錯誤,請確認是否可存取並重試。", "EveryNDays": "每 {0} 天", "ExitFullscreen": "結束全螢幕", "ExtraLarge": "特大", @@ -535,22 +529,21 @@ "FolderTypeUnset": "混合內容", "Folders": "資料夾", "FormatValue": "格式:{0}", - "FreeAppsFeatureDescription": "享受免費的Jellyfin應用程式。", "Fullscreen": "全螢幕", "General": "一般", "Genre": "類型", "Genres": "風格", "GroupBySeries": "按系列分組", "GroupVersions": "按版本分組", - "GuestStar": "特邀明星", + "GuestStar": "客串", "GuestUserNotFound": "未找到用戶。請確保用戶名稱正確後重試,或者嘗試輸入他們的電子郵件地址。", "Guide": "指南", "GuideProviderSelectListings": "選擇列表", - "H264CrfHelp": "The Constant Rate Factor (CRF) 是 x264 編碼器的默認畫質設置。此方法允許編碼器自動分配位元速率來試著達到一定輸出品質。讓每個畫格得到它需要的位元數來保持所需的品質等級。CRF 會得到最佳的位元速率分配結果。", + "H264CrfHelp": "CRF 是 x264 編碼器的預設畫質設置。此方法允許編碼器自動分配位元速率來試著達到一定輸出品質。讓每個畫格得到它需要的位元數來保持所需的品質等級。CRF 會得到最佳的位元速率分配結果。", "EncoderPresetHelp": "速度越慢則會得到更好的壓縮編碼效率。", - "HDPrograms": "HD節目", + "HDPrograms": "HD 節目", "HandledByProxy": "由反向代理處理", - "HardwareAccelerationWarning": "啟動硬體加速可能在某些環境下導致系統不穩定。請確認你的作業系統和影片驅動程式是最新的。如果你在開啟此項後播放影片產生困難,那麼你需要將此選項設回”無“。", + "HardwareAccelerationWarning": "啟動硬體加速可能在某些環境下導致系統不穩定。請確認你的作業系統和影片驅動程式是最新的。如果你在開啟此項後難以播放影片,那麼你需要將此選項設回「無」。", "HeaderAccessSchedule": "存取時程", "HeaderAccessScheduleHelp": "建立一個存取時程以限制可存取的時段。", "HeaderActiveDevices": "運行中裝置", @@ -570,7 +563,7 @@ "HeaderAllowMediaDeletionFrom": "允許從中刪除媒體", "HeaderApiKey": "API 金鑰", "HeaderApiKeys": "API 金鑰", - "HeaderApiKeysHelp": "外部應用程式需要有一個 API 金鑰以用於和 Jellyfin 伺服器溝通。金鑰將在通過 Jellyfin 帳戶登入時自動發行,或者你可以手動為應用程式產生一個金鑰。", + "HeaderApiKeysHelp": "外部應用程式需要有一個 API 金鑰以用於和 Jellyfin 伺服器溝通。金鑰會在 Jellyfin 使用者登入時自動發行,或者你可以手動為應用程式產生一個金鑰。", "HeaderApp": "應用程式", "HeaderAppearsOn": "同時出現於", "HeaderAudio": "音訊", @@ -606,16 +599,15 @@ "HeaderConfirmRecordingCancellation": "確認取消錄製", "HeaderConfirmRecordingDeletion": "確認刪除錄影", "HeaderConfirmRemoveUser": "移除用戶", - "HeaderConfirmRevokeApiKey": "撤銷 API 金鑰", + "HeaderConfirmRevokeApiKey": "重置 API 金鑰", "HeaderConfirmSeriesCancellation": "確認系列取消", "HeaderConfirmation": "確認", "HeaderConnectToServer": "連結至伺服器", "HeaderConnectionFailure": "連結失敗", - "HeaderContainerProfile": "影片容器設定檔", + "HeaderContainerProfile": "影片載體設定", "HeaderContainerProfileHelp": "影片容器的設定檔標明了設備播放特定媒體格式時的限制。如果在限制之內則媒體將被轉碼,否則媒體格式將被設定為直接播放。", "HeaderContinueListening": "繼續聆聽", "HeaderContinueWatching": "繼續觀賞", - "HeaderConvertYourRecordings": "為你的錄制轉檔", "HeaderCredits": "製作人員名單", "HeaderDashboardUserPassword": "用戶的密碼被管理在每個用戶的私人配置設置中。", "HeaderDate": "日期", @@ -628,7 +620,7 @@ "HeaderDeleteTaskTrigger": "刪除任務觸發條件", "HeaderDestination": "目的地", "HeaderDetectMyDevices": "偵測我的設備", - "HeaderDeveloperInfo": "開發者訊息", + "HeaderDeveloperInfo": "開發者資訊", "HeaderDevice": "裝置", "HeaderDeviceAccess": "允許裝置存取", "HeaderDevices": "裝置", @@ -651,7 +643,7 @@ "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛演出者", "HeaderFavoriteCollections": "最愛收藏", - "HeaderFavoriteEpisodes": "最愛級數", + "HeaderFavoriteEpisodes": "最愛影集", "HeaderFavoriteMovies": "最愛電影", "HeaderFavoritePlaylists": "最愛播放列表", "HeaderFavoriteShows": "最愛節目", @@ -668,7 +660,7 @@ "HeaderHomePage": "主畫面", "HeaderHomeScreen": "主畫面", "HeaderHomeScreenSettings": "主畫面設定", - "HeaderHttpHeaders": "Http 標頭", + "HeaderHttpHeaders": "HTTP 標頭", "HeaderIdentification": "身份識別", "HeaderIdentificationCriteriaHelp": "至少輸入一個識別標準。", "HeaderIdentificationHeader": "身份識別標題", @@ -679,14 +671,7 @@ "HeaderImagePrimary": "主要", "HeaderImages": "圖片", "HeaderInstall": "安裝", - "HeaderInvitationSent": "邀請已傳送", - "HeaderInvitations": "邀請", - "HeaderInviteUser": "邀請使用者", - "HeaderInviteUserHelp": "通過 Jellyfin Connect 與朋友分享你的媒體比以往任何時候都更為簡單。", - "HeaderInviteWithJellyfinConnect": "通過 Jellyfin Connect 邀請", "HeaderItems": "項目", - "HeaderJellyfinAccountAdded": "Jellyfin 帳戶已添加", - "HeaderJellyfinAccountRemoved": "已移除 Jellyfin 帳戶", "HeaderKeepRecording": "繼續錄製", "HeaderKeepSeries": "保存系列", "HeaderKodiMetadataHelp": "要啟用或禁用 NFO 元數據, 請在 Jellyfin “建立媒體庫”頁面中編輯該媒體庫, 然後找到“元數據儲存”部分。", @@ -734,10 +719,6 @@ "HeaderNextVideoPlayingInValue": "下一部影片在 {0} 後播放", "HeaderNotifications": "通知", "HeaderNumberOfPlayers": "播放器", - "HeaderOffline": "離線", - "HeaderOfflineDownloads": "離線媒體", - "HeaderOfflineDownloadsDescription": "下載媒體以離線使用。", - "HeaderOfflineSync": "離線同步", "HeaderOnNow": "現正播放", "HeaderOptions": "選項", "HeaderOtherDisplaySettings": "顯示設定", @@ -824,14 +805,9 @@ "HeaderTryPlayback": "嘗試播放", "HeaderTunerDevices": "調諧器裝置", "BobAndWeaveWithHelp": "Bob and weave (高品質,轉檔慢)", - "ButtonPurchase": "購買", - "ButtonRestorePreviousPurchase": "恢復購買", - "ButtonUnlockWithPurchase": "購買已解鎖", - "LabelIpAddressValue": "IP 位置: {0}", "LabelRunningTimeValue": "運行時間: {0}", "MessageApplicationUpdated": "Jellyfin Server 已經更新", "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", - "MessageServerConfigurationUpdated": "伺服器設定已經更新", "Movies": "電影", "Photos": "相片", "Playlists": "播放清單", @@ -851,7 +827,6 @@ "HeaderProfileServerSettingsHelp": "這些數值將控制 Jellyfin 伺服器如何呈現給設備。", "HeaderResponseProfile": "回覆設定檔", "HeaderResponseProfileHelp": "當播放某些類型的媒體時,回覆設定檔提供一種方法來發送自定訊息到設備。", - "HeaderRestartingServer": "重新啟動伺服器", "HeaderSchedule": "日程表", "HeaderSelectCertificatePath": "選擇證書路徑", "HeaderSelectMetadataPath": "選擇數據路徑", @@ -871,9 +846,9 @@ "HeaderVideoType": "影片類型", "HeaderVideoTypes": "影片類型", "HeaderVideos": "影片", - "HeaderXmlDocumentAttribute": "XML檔案屬性", - "HeaderXmlDocumentAttributes": "XML檔案屬性", - "HeaderXmlSettings": "XML設置", + "HeaderXmlDocumentAttribute": "XML 檔案屬性", + "HeaderXmlDocumentAttributes": "XML 檔案屬性", + "HeaderXmlSettings": "XML 設置", "HeaderYears": "年份", "HeadersFolders": "資料夾", "Hide": "隱藏", @@ -883,14 +858,14 @@ "HttpsRequiresCert": "要啟用安全連線,您需要提供受信任的SSL證書,如 Let's Encrypt。 請提供證書,或停用安全連線。", "Identify": "識別", "Images": "圖片", - "ImportFavoriteChannelsHelp": "如果啟用,只有在調諧器設備中被標記為我的最愛的頻道才會被導入。", - "ImportMissingEpisodesHelp": "如果啟用,有關缺失劇集的數據導入您的Jellyfin媒體庫,並在季節和系列中顯示。 這可能會導致媒體庫掃描延長。", - "InstallingPackage": "正在安装 {0}", + "ImportFavoriteChannelsHelp": "若啟用,只有在調諧器設備中被標記為我的最愛的頻道才會被導入。", + "ImportMissingEpisodesHelp": "若啟用,有關缺失劇集的數據導入您的 Jellyfin 媒體庫,並在季節和系列中顯示。 這可能會導致媒體庫掃描延長。", + "InstallingPackage": "正在安装 {0}(版本 {1})", "InstantMix": "即時混音", "Items": "項目", "Kids": "兒童", - "Label3DFormat": "3D格式:", - "LabelAbortedByServerShutdown": "(因為伺服器關閉被中止)", + "Label3DFormat": "3D 格式:", + "LabelAbortedByServerShutdown": "(因為伺服器關閉被中止)", "LabelAccessDay": "一周中的何時:", "LabelAccessEnd": "結束時間:", "LabelAccessStart": "開始時間:", @@ -904,24 +879,24 @@ "LabelAlbumArtMaxHeight": "專輯封面最大高度:", "LabelAlbumArtMaxHeightHelp": "通過 upnp:albumArtURI 顯示的專輯封面超鏈接的最大分辨率。", "LabelAlbumArtMaxWidth": "專輯封面最大寬度:", - "LabelAlbumArtMaxWidthHelp": "通過upnp:albumArtURI顯示的專輯封面超鏈接的最大分辨率。", - "LabelAlbumArtPN": "專輯封面PN :", + "LabelAlbumArtMaxWidthHelp": "通過 upnp:albumArtURI 顯示的專輯封面超鏈接的最大解析度。", + "LabelAlbumArtPN": "專輯封面 PN :", "LabelAlbumArtists": "專輯作家:", "LabelAll": "所有", "LabelAllowHWTranscoding": "允許硬體轉碼", - "LabelAllowedRemoteAddresses": "遠端IP地址過濾:", - "LabelAllowedRemoteAddressesMode": "遠端IP地址過濾模式:", - "LabelAppName": "APP名稱", - "LabelAppNameExample": "例如: Sickbeard, Sonarr", + "LabelAllowedRemoteAddresses": "遠端 IP 過濾:", + "LabelAllowedRemoteAddressesMode": "遠端 IP 過濾模式:", + "LabelAppName": "APP 名稱", + "LabelAppNameExample": "例如:可愛蹦蹦主機、小安的 Jellyfin", "LabelArtists": "藝人:", "LabelArtistsHelp": "分開多重使用 ;", "LabelAudio": "音頻", "LabelAuthProvider": "認證提供者:", "LabelAutomaticallyRefreshInternetMetadataEvery": "從網路自動刷新數據:", "LabelBindToLocalNetworkAddress": "聯結本地網絡地址:", - "LabelBindToLocalNetworkAddressHelp": "(可選的)覆蓋 HTTP 服務器綁定的本地 IP 地址。如果留空,服務器將會監聽所有可用的地址。改變這個值需要重啟 Jellyfin 伺服器\n。", + "LabelBindToLocalNetworkAddressHelp": "(選用)覆蓋 HTTP 服務器綁定的本地 IP 地址。如果留空,服務器將會監聽所有可用的地址。改變這個值需要重啟 Jellyfin 伺服器。", "LabelBirthDate": "出生日期:", - "LabelBirthYear": "出生年份:", + "LabelBirthYear": "出生年:", "LabelBlastMessageInterval": "活動信號的時間間隔(秒)", "LabelBlastMessageIntervalHelp": "確定伺服器活動消息之間的持續時間(秒)。", "LabelBlockContentWithTags": "通過標籤鎖定內容:", @@ -940,17 +915,17 @@ "LabelCustomCssHelp": "於 Web 介面套用您的自訂樣式。", "LabelCustomDeviceDisplayName": "顯示名稱:", "Depressed": "凹陷", - "HeaderHome": "主頁", + "HeaderHome": "首頁", "HeaderSelectMetadataPathHelp": "瀏覽或者輸入路徑以用於保存中繼資料,請確保資料夾可以寫入。", "HeaderSelectServerCachePathHelp": "瀏覽或者輸入路徑以用於伺服器快取檔案。請確保該資料夾可以被寫入。", "LabelCustomDeviceDisplayNameHelp": "指定自訂的顯示名稱,或者留空以使用設備自己報告的名稱。", "LabelCustomRating": "自訂分級:", - "LabelDashboardTheme": "儀表板佈景主題:", + "LabelDashboardTheme": "控制台佈景主題:", "LabelDateAdded": "新增日期:", "LabelDateAddedBehavior": "新内容加入的日期應使用:", "LabelDateTimeLocale": "設定時區:", "LabelDeathDate": "死亡時間:", - "LabelDefaultScreen": "預設螢幕:", + "LabelDefaultScreen": "預設分頁:", "LabelDefaultUser": "預設使用者:", "LabelDeviceDescription": "裝置說明", "LabelDidlMode": "DIDL 模式:", @@ -966,13 +941,13 @@ "OptionMax": "最大", "LabelAudioBitDepth": "音訊位元深度:", "LabelBaseUrl": "根路徑:", - "LabelIconMaxHeight": "Icon 最高高度:", + "LabelIconMaxHeight": "圖示最高高度:", "LabelHttpsPortHelp": "Jellyfin 的 HTTPS 伺服器應綁定的 TCP 端口。", "LabelIconMaxHeightHelp": "通過 upnp:icon 的圖標最大解析度。", "CopyStreamURL": "複製串流連結", "MediaInfoDefault": "預設", "MediaInfoStreamTypeAudio": "音訊", - "LabelDateAddedBehaviorHelp": "如果原本就有中繼資料,則將始終在這些選項之一之前使用它。", + "LabelDateAddedBehaviorHelp": "如果原本就有中繼資料,將會優先使用其數據。", "LabelScreensaver": "螢幕保護程式:", "LabelSeasonNumber": "季:", "LabelDropImageHere": "拖移圖片到這裡,或是點擊來選取。", @@ -988,7 +963,7 @@ "LabelVersionInstalled": "{0} 已安裝", "DashboardVersionNumber": "版本:{0}", "DashboardServerName": "伺服器:{0}", - "NoSubtitles": "沒有字幕", + "NoSubtitles": "無", "List": "清單", "OptionAllowMediaPlayback": "允許播放媒體", "OneChannel": "單聲道", @@ -1010,7 +985,7 @@ "ValueCodec": "編碼:{0}", "ValueSongCount": "{0} 首歌", "LabelFileOrUrl": "檔案或路徑:", - "LabelKodiMetadataSaveImagePaths": "在 nfo 檔案中儲存圖片路徑", + "LabelKodiMetadataSaveImagePaths": "在 NFO 檔案中儲存圖片路徑", "LabelLanNetworks": "區域網路:", "LabelMetadataPathHelp": "指定自訂路徑來儲存下載的圖片與中繼資料。", "LabelZipCode": "郵遞區號:", @@ -1024,7 +999,7 @@ "ShowAdvancedSettings": "顯示進階選項", "ShowTitle": "顯示標題", "ShowYear": "顯示年份", - "Shuffle": "隨ㄔㄧ", + "Shuffle": "隨機播放", "Smart": "智慧", "HeaderFavoriteBooks": "最愛的書籍", "LabelAudioBitrate": "音訊比特率:", @@ -1040,7 +1015,7 @@ "LabelEnableDlnaServerHelp": "允許網絡上的 UPnP 設備瀏覽和播放內容。", "LabelEnableHardwareDecodingFor": "為以下啟用硬體解碼:", "LabelEpisodeNumber": "集:", - "LabelBaseUrlHelp": "您可以在此處新增自訂路徑來進入伺服器。", + "LabelBaseUrlHelp": "您可以在此處自訂伺服器URL路徑的子目錄。", "LabelExtractChaptersDuringLibraryScan": "於媒體庫掃描時擷取章節圖片", "LabelHttpsPort": "本地 HTTPS 端口:", "LabelFailed": "失敗", @@ -1130,7 +1105,7 @@ "ManageRecording": "管理錄影", "MessageAlreadyInstalled": "已安裝此版本。", "MessageConfirmRestart": "您確定要重新啟動嗎?", - "Metadata": "ˊ中繼資料", + "Metadata": "中繼資料", "OptionAllUsers": "所有使用者", "OptionHomeVideos": "圖片", "OptionPoster": "海報", @@ -1144,7 +1119,7 @@ "TV": "電視", "TabUsers": "使用者", "Trailers": "預告", - "LabelImageFetchersHelp": "按優先級啟用並排列您喜歡的圖片抓取器。", + "LabelImageFetchersHelp": "啟用並按優先順序排序您的首選圖片擷取器。", "LabelDownMixAudioScale": "縮混時的音訊增強:", "LabelDownMixAudioScaleHelp": "縮混時增強音訊。其中一個音軌將保持原始音量。", "LabelDownloadLanguages": "下載語言:", @@ -1162,7 +1137,7 @@ "LabelSelectUsers": "選擇使用者:", "LabelSelectVersionToInstall": "選擇要安裝的版本:", "LabelSendNotificationToUsers": "傳送通知給:", - "LabelSortBy": "排序依:", + "LabelSortBy": "排序按照:", "LabelVideoBitrate": "影片比特率:", "MediaInfoSize": "大小", "MediaInfoTimestamp": "時間戳", @@ -1295,7 +1270,7 @@ "Up": "上", "ValueOneSeries": "1 劇集", "Writer": "編劇", - "XmlTvMovieCategoriesHelp": "有這些類別的節目會被當作電影。用「|」分隔多個。", + "XmlTvMovieCategoriesHelp": "有這些類別的節目會被當作電影,以「|」來分隔多個項目。", "ValueSeriesCount": "{0} 劇集", "LabelHardwareAccelerationTypeHelp": "硬件加速需要額外的配置。", "LabelHomeNetworkQuality": "區域網路畫質:", @@ -1317,7 +1292,7 @@ "OptionEstimateContentLength": "轉檔時,估計內容長度", "OptionExternallyDownloaded": "外部下載", "OptionHlsSegmentedSubtitles": "HLS 分段字幕", - "OptionLoginAttemptsBeforeLockout": "確定在被封鎖之前可以登入失敗幾次。", + "OptionLoginAttemptsBeforeLockout": "在被封鎖之前可以登入失敗幾次。", "OptionRequirePerfectSubtitleMatch": "只下載與我的影片檔案完美匹配的字幕", "PlayCount": "播放次數", "Series": "電視劇", @@ -1342,7 +1317,7 @@ "LabelMoviePrefixHelp": "若前綴套用到電影標題,請在此處輸入它來方便伺服器能夠正確處理。", "LabelMovieRecordingPath": "電影錄製路徑(選用):", "LabelNotificationEnabled": "啟用這個通知", - "LabelProfileContainersHelp": "以逗號分隔。留空則適用於所有影片容器。", + "LabelProfileContainersHelp": "以逗號分隔,留空則適用於所有影片容器。", "LabelSelectFolderGroupsHelp": "未選中的資料夾將在其自己的檢視中顯示。", "LabelSerialNumber": "序號", "LabelSeriesRecordingPath": "電視劇錄影路徑(選用):", @@ -1398,7 +1373,7 @@ "MessageDirectoryPickerLinuxInstruction": "使用 Linux on Arch Linux、CentOS、Debian、Fedora、openSUSE 或 Ubuntu 作業系統,您必須授權使用者至少讀取權限來存取您的儲存路徑。", "MessageEnablingOptionLongerScans": "啟用這個選項可能會延長媒體庫的掃描時間。", "MessageFileReadError": "讀取檔案時發生錯誤。", - "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再試一次來開始密碼重置流程。", + "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再開始密碼重置流程。", "MessageForgotPasswordFileCreated": "已在伺服器上建立了以下檔案,並包含有關後續步驟說明:", "MessageNoAvailablePlugins": "沒有可用的模組。", "MessageNoServersAvailable": "無法自動偵測伺服器。", @@ -1414,7 +1389,7 @@ "News": "新聞", "NoNewDevicesFound": "找不到裝置,要添加新調諧器,請關閉此對話框並手動輸入裝置訊息。", "OnlyForcedSubtitles": "僅顯示強制字幕", - "OnlyImageFormats": "僅圖片格式(VOBSUB、PGS、SUB 等)", + "OnlyImageFormats": "圖片格式(VOBSUB、PGS、SUB)", "OptionAllowLinkSharingHelp": "只有網頁包含的媒體訊息會被共享,媒體檔案本身不會被公開共享,共享的內容會在 {0} 天後到期。", "OptionAllowRemoteSharedDevices": "允許遠端控制共享裝置", "OptionAllowSyncTranscoding": "允許需要轉檔的媒體下載和同步", @@ -1441,7 +1416,7 @@ "OptionSubstring": "子串", "OptionWeekdays": "工作日", "Overview": "概述", - "PackageInstallCancelled": "{0} 安裝被取消。", + "PackageInstallCancelled": "{0} (版本 {1})安裝被取消。", "PlayAllFromHere": "從這裡開始全部播放", "PleaseAddAtLeastOneFolder": "請點擊新增按鈕,新增至少一個資料夾到這個媒體庫。", "PleaseConfirmPluginInstallation": "點擊「OK」以確認您已經閱讀了上述內容並希望繼續安裝模組。", @@ -1459,7 +1434,7 @@ "ProductionLocations": "產地", "Programs": "節目", "Quality": "品質", - "PackageInstallFailed": "{0} 安裝失敗。", + "PackageInstallFailed": "{0} (版本 {1}) 安裝失敗。", "QueueAllFromHere": "將這裡的全部內容加入佇列", "Raised": "提高", "Rate": "評等", @@ -1474,7 +1449,7 @@ "Uniform": "輪廓", "Unmute": "取消靜音", "Unplayed": "尚未播放", - "TvLibraryHelp": "查看{0}電視命名指南{1}。", + "TvLibraryHelp": "查看 {0} 電視命名指南 {1} 。", "LabelMonitorUsers": "監控活動:", "LabelPleaseRestart": "改動將在手動重啟用戶端後生效。", "LabelProfileCodecsHelp": "以逗號分隔。留空則適用於所有編解碼器。", @@ -1482,14 +1457,13 @@ "LabelInNetworkSignInWithEasyPasswordHelp": "你可以在你的家庭網路中使用你的簡易 PIN 碼登錄 Jellyfin 應用程式,僅在你使用外部網路時才需要輸入密碼,如果 PIN 碼留空,那麼在你的區域網路中便不需輸入密碼。", "LabelReleaseDate": "釋出日期:", "LabelRemoteClientBitrateLimit": "網際網路串流傳輸位元率限制(Mbps):", - "LanNetworksHelp": "在强制頻寬限制時,認作本地網路上的 IP 地址或 IP/網路掩碼條目的逗號分隔列表。如果設置此項,所有其它 IP 地址將被視作在外部網路上,并且將受到外部頻寬限制。如果保留爲空,則只將服務器的子網視作本地網路。", + "LanNetworksHelp": "在強制頻寬限制時,認作本地網路上的 IP 地址或 IP/子網域遮罩項目的逗號分隔列表。若設置此項,所有其它 IP 地址將被視作在外部網路上,并且將受到外部頻寬限制。如果保留爲空,則只將服務器的子網域遮罩作本地網路。", "OptionIgnoreTranscodeByteRangeRequests": "忽略轉檔位元組範圍請求", "OptionIgnoreTranscodeByteRangeRequestsHelp": "如果啟用,這些請求會被兌現,但會忽略的位元組範圍標頭。", "OptionLoginAttemptsBeforeLockoutHelp": "若值為 0,則表示將允許普通使用者嘗試三次、管理員嘗試五次的預設值,設定為 -1 來停用此功能。", "OptionRequirePerfectSubtitleMatchHelp": "僅下載經過測試並確認跟此影片檔案完美匹配的字幕。取消勾選這個項目可以增加找到並下載字幕的可能性,但可能會下載時間軸、翻譯不正確的字幕。", "PluginInstalledMessage": "這個模組安裝成功,但 Jellyfin 伺服器需要重啟以使模組生效。", "RecordingPathChangeMessage": "更改錄製資料夾不會將現有錄製從舊位置遷移到新的,您需要手動移動它們。", - "RestartPleaseWaitMessage": "Jellyfin 伺服器將重新啟動,這將花費幾分鐘時間。", "LabelEmbedAlbumArtDidl": "於 Didl 中嵌入專輯封面", "LabelEnableAutomaticPortMapHelp": "自動嘗試映射公共連接埠到 UPnP 本地連接埠。這可能無法用於某些路由器。需重新啓動伺服器。", "LabelEmbedAlbumArtDidlHelp": "有些裝置使用這個方式來取得專輯封面,啟用這個選項可能導致其他設備播放失敗。", @@ -1507,8 +1481,8 @@ "SystemDlnaProfilesHelp": "系統設定檔案是唯讀的,更改系統設定檔案將被儲存到自訂的新設定檔案。", "LabelNumber": "編號:", "LabelNumberOfGuideDays": "下載電視指南日數:", - "OnlyForcedSubtitlesHelp": "只有標記為「強制」的字幕會被載入。", - "PackageInstallCompleted": "{0} 安裝完成。", + "OnlyForcedSubtitlesHelp": "僅被標記為「強制」的字幕會被載入。", + "PackageInstallCompleted": "{0} (版本 {1}) 安裝完成。", "OptionDisplayFolderViewHelp": "在其他媒體庫旁邊顯示資料夾,對想要一個普通的資料夾檢視很有用。", "LabelReasonForTranscoding": "轉檔原因:", "LabelRecord": "錄影:", @@ -1534,7 +1508,7 @@ "LabelSportsCategories": "體育分類:", "LabelStartWhenPossible": "當可能時自動開始:", "LabelVaapiDevice": "VA API 裝置:", - "DashboardArchitecture": "結構:{0}", + "DashboardArchitecture": "架構:{0}", "MediaInfoSampleRate": "採樣率", "MessageContactAdminToResetPassword": "請聯繫您的管理員來重置密碼。", "MessageUnsetContentHelp": "內容將顯示為純資料夾,建議使用中繼資料管理器設置子資料夾的內容類型。", @@ -1542,7 +1516,7 @@ "OptionCustomUsers": "自訂", "OptionDateAddedFileTime": "使用檔案建立日期", "OptionReportByteRangeSeekingWhenTranscodingHelp": "這對一些時間跳轉緩慢的裝置是必要的。", - "XmlTvNewsCategoriesHelp": "有這些類別的節目會被當作新聞節目。用「|」分隔多個。", + "XmlTvNewsCategoriesHelp": "有這些類別的節目會被當作新聞節目,以「|」來分隔多個項目。", "LabelKodiMetadataEnableExtraThumbsHelp": "為了相容 Kodi 主題,下載的圖片將被同時儲存在 extrafanart 和 extrathumbs 資料夾中。", "LabelInternetQuality": "網路畫質:", "LabelKodiMetadataEnablePathSubstitutionHelp": "允許將圖片的路徑以伺服器路徑來替換。", @@ -1575,16 +1549,16 @@ "LabelPostProcessorArguments": "處理器後命令行參數:", "LabelPostProcessorArgumentsHelp": "使用 {path} 作為錄製檔案的路徑。", "LabelPreferredDisplayLanguage": "首選語言:", - "LabelPreferredDisplayLanguageHelp": "翻譯 Jellyfin 是一個進行中的項目。", + "LabelPreferredDisplayLanguageHelp": "歡迎一起翻譯 Jellyfin!", "LabelPreferredSubtitleLanguage": "字幕語言偏好:", - "LabelProtocol": "協議:", + "LabelProtocol": "協議:", "LabelProtocolInfo": "協議資訊:", "LabelPublicHttpPort": "公開 HTTP 端口:", "LabelPublicHttpsPort": "公開 HTTPS 端口:", "LabelProtocolInfoHelp": "當響應來自裝置的 GetProtocolInfo(獲取協議訊息)請求時,該值將被使用。", "LabelPublicHttpPortHelp": "公開連接埠應映射到本地 HTTP 連接埠。", "LabelPublicHttpsPortHelp": "公開連接埠應映射到本地 HTTPS 連接埠。", - "LabelReadHowYouCanContribute": "了解如何作出貢獻。", + "LabelReadHowYouCanContribute": "瞭解如何一同貢獻。", "LabelSelectFolderGroups": "自動將以下資料夾中的內容分組到視圖中,例如電影、音樂和電視:", "LabelStatus": "狀態:", "LiveBroadcasts": "直播", @@ -1603,13 +1577,13 @@ "Vertical": "垂直", "VideoRange": "影片範圍", "ViewPlaybackInfo": "查看播放訊息", - "XmlTvSportsCategoriesHelp": "有這些類別的節目會被當作體育節目。用「|」分隔多個。", + "XmlTvSportsCategoriesHelp": "有這些類別的節目會被當作體育節目,以「|」來分隔多個項目。", "XmlTvPathHelp": "XML 電視檔案的路徑,Jellyfin 將讀取該檔案並定期檢查其更新,您負責建立和更新檔案。", "MessageInvalidForgotPasswordPin": "簡易代碼錯誤或已過期,請再試一次。", "OptionAllowVideoPlaybackTranscoding": "允許播放需要轉檔的影片", "Small": "小", "Smaller": "更小", - "XmlTvKidsCategoriesHelp": "有這些類別的節目會被當作兒童節目。用「|」分隔多個。", + "XmlTvKidsCategoriesHelp": "有這些類別的節目會被當作兒童節目,以「|」來分隔多個項目。", "TabResponses": "響應", "LabelDisplaySpecialsWithinSeasons": "顯示劇集季度中的特集", "LabelNumberOfGuideDaysHelp": "下載更多電視指南資料會提供更好時間表查看能力,但將需要更長的下載時間。自動基於頻道數目來選擇。", @@ -1628,14 +1602,12 @@ "SelectAdminUsername": "請為管理員賬戶選擇一個用戶名。", "CopyStreamURLError": "複製網址的時候發生錯誤.", "OptionSaveMetadataAsHiddenHelp": "更改此項將應用於以後保存的元數據。現有元數據文件將在下一次 Jellyfin 伺服器保存它們時被更新。", - "OptionAllowRemoteSharedDevicesHelp": "DLNA 設備在用戶對它們進行控制前都被視作共享的。", + "OptionAllowRemoteSharedDevicesHelp": "DLNA裝置將被視為共享中,直至有使用者控制。", "OptionForceRemoteSourceTranscoding": "强制遠端轉碼(像電視直播一樣)", "MessageConfirmAppExit": "您要退出嗎?", "LaunchWebAppOnStartupHelp": "伺服器啓動時在默認游覽器中打開網頁端。使用重啓伺服器功能時此項不生效。", "LabelVideoResolution": "視頻解析度:", "LabelStreamType": "串流類型:", - "EnableFastImageFadeInHelp": "對已載入的圖片啟用更快的淡入動畫", - "EnableFastImageFadeIn": "圖片快速淡入效果", "LabelPlayerDimensions": "播放器尺寸:", "LabelDroppedFrames": "丟棄的幀:", "LabelCorruptedFrames": "損壞的幀:", @@ -1646,6 +1618,59 @@ "AllowFfmpegThrottlingHelp": "當轉檔或重組進度大量超前目前播放進度時,將暫停轉檔節省消耗的資源。在不常跳播的時候最有效。如果遇到播放問題,請關閉此功能。", "AllowFfmpegThrottling": "限制轉檔", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "這將會使用內建劇集資料。", - "PlaybackErrorNoCompatibleStream": "用戶端偵測出了問題,伺服器也未傳送相容的媒體格式。", - "PreferEmbeddedEpisodeInfosOverFileNames": "優先使用內建劇集資訊而不是檔案名稱" + "PlaybackErrorNoCompatibleStream": "用戶端與該媒體不相容,伺服器也未傳送相容的媒體格式。", + "PreferEmbeddedEpisodeInfosOverFileNames": "優先使用內建劇集資訊而不是檔案名稱", + "OtherArtist": "其他歌手", + "Artist": "歌手", + "AlbumArtist": "專輯歌手", + "Album": "專輯", + "YadifBob": "YADIF Bob", + "WriteAccessRequired": "Jellyfin 伺服器需要此資料夾的寫入權限,請確認是否擁有寫入權限並重試。", + "PathNotFound": "無法找到此路徑,請確認路徑可用並重試。", + "Track": "音軌", + "Yadif": "YADIF", + "ListPaging": "{2} 的 {0}-{1}", + "PersonRole": "作為 {0}", + "LastSeen": "上次觀看 {0}", + "DailyAt": "每日的 {0}", + "WeeklyAt": "每週第 {0} 天的 {1}", + "OnWakeFromSleep": "當從睡眠中喚醒時", + "EveryXMinutes": "每 {0} 分鐘", + "EveryHour": "每小時", + "EveryXHours": "每 {0} 小時", + "OnApplicationStartup": "當應用程式啟動時", + "Season": "季", + "ReleaseGroup": "發行組織", + "Person": "人物", + "Movie": "電影", + "LabelLibraryPageSizeHelp": "設置媒體庫頁面每頁要顯示的最多媒體個數。設置為 0 來停用分頁。", + "LabelLibraryPageSize": "媒體庫分頁大小:", + "LabelDeinterlaceMethod": "反交錯方法:", + "Episode": "劇集", + "DeinterlaceMethodHelp": "選擇對隔行掃描內容進行轉碼時所用的反交錯方法。", + "BoxSet": "套裝", + "UnsupportedPlayback": "Jellyfin 無法解密受 DRM 保護的內容,但仍然會嘗試播放所有內容。某些檔案由於被加密或包含如互動標題等不受支援的內容,在播放時可能會沒有畫面。", + "MessageUnauthorizedUser": "您目前無權存取伺服器,請與您的伺服器管理員聯繫以獲取更多訊息。", + "Filter": "篩選器", + "New": "新增", + "ApiKeysCaption": "目前已啟用的API金鑰列表", + "ButtonTogglePlaylist": "播放清單", + "ButtonToggleContextMenu": "更多", + "ButtonSyncPlay": "SyncPlay", + "LabelRequireHttpsHelp": "如果選中,伺服器將自動將所有通過HTTP的請求重定向到HTTPS。 如果伺服器未在HTTPS上偵聽則此項無效。", + "EnableFasterAnimationsHelp": "使用更快的動畫和過渡效果", + "EnableFasterAnimations": "更快的動畫", + "LabelRequireHttps": "要求HTTPS", + "LabelNightly": "每日更新版", + "LabelStable": "穩定版", + "LabelChromecastVersion": "Chromecast版本", + "LabelEnableHttpsHelp": "使伺服器能夠偵聽已配置的HTTPS端口。 要使其生效,必須配置一個有效的證書。", + "LabelEnableHttps": "啟用HTTPS", + "HeaderServerAddressSettings": "伺服器地址設定", + "HeaderRemoteAccessSettings": "遠程訪問設定", + "HeaderHttpsSettings": "HTTPS設定", + "EnableDetailsBannerHelp": "在項目詳細信息頁面的頂部顯示橫幅圖像。", + "EnableDetailsBanner": "詳情橫幅", + "EnableDecodingColorDepth10Vp9": "啟用10比特VP9硬體解碼", + "EnableDecodingColorDepth10Hevc": "啟用10比特HEVC硬體解碼" } diff --git a/src/themes/appletv/theme.css b/src/themes/appletv/theme.css index 5ca517bea58..41540ce0dd9 100644 --- a/src/themes/appletv/theme.css +++ b/src/themes/appletv/theme.css @@ -43,7 +43,9 @@ html { } .backgroundContainer, -.dialog { +.dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu { background: #d5e9f2; -webkit-background-size: 100% 100%; background-size: 100% 100%; @@ -120,9 +122,10 @@ html { } .paperList, -.visualCardBox { - background-color: #fff; +.visualCardBox, +.cardBox:not(.visualCardBox) .cardPadder { background-color: rgba(0, 0, 0, 0.1); + border-radius: 0.5rem; } .defaultCardBackground1 { @@ -171,6 +174,10 @@ html { opacity: 0.5; } +.cardImageContainer { + border-radius: 0.5rem; +} + .formDialogHeader a, .toast { color: #fff; @@ -186,7 +193,8 @@ html { } .appfooter, -.formDialogFooter:not(.formDialogFooter-clear) { +.formDialogFooter:not(.formDialogFooter-clear), +.playlistSectionButton { color: rgba(0, 0, 0, 0.7); background: #303030; background: -webkit-gradient(linear, left top, right top, from(#bcbcbc), color-stop(#a7b4b7), color-stop(#beb5a5), color-stop(#adbec2), to(#b9c7cb)); @@ -224,7 +232,7 @@ html { color: #fff !important; } -.detailSticky { +.detailRibbon { background: #303030; background: -webkit-gradient(linear, left top, right top, from(#bcbcbc), color-stop(#a7b4b7), color-stop(#beb5a5), color-stop(#adbec2), to(#b9c7cb)); background: -webkit-linear-gradient(left, #bcbcbc, #a7b4b7, #beb5a5, #adbec2, #b9c7cb); @@ -254,11 +262,6 @@ html { background: #fff; } -.mediaInfoTimerIcon, -.starIcon { - color: #cb272a; -} - .emby-input, .emby-textarea { color: inherit; @@ -288,11 +291,24 @@ html { border-color: #fff; } -.emby-checkbox:checked + span + .checkboxOutline, -.itemProgressBarForeground { +.emby-checkbox:checked + span + .checkboxOutline { background-color: #00a4dc; } +.itemProgressBarForeground { + background: linear-gradient(90deg, rgba(0, 210, 201, 1) 0%, rgba(13, 194, 98, 1) 28%, rgba(0, 75, 185, 1) 100%); +} + +.itemProgressBar { + background: rgba(230, 230, 230, 0.8); + height: 0.18rem; +} + +.innerCardFooter { + border-radius: 0.14rem; + margin: 0.4rem 0.5rem 0.4rem 0.5rem; +} + .emby-checkbox:focus:not(:checked) + span + .checkboxOutline { border-color: #00a4dc; } @@ -433,15 +449,30 @@ html { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; } .card:focus .cardBox.visualCardBox, .card:focus .cardBox:not(.visualCardBox) .cardScalable { + border-radius: 0.5rem; border-color: #00a4dc !important; } +.card.show-focus:not(.show-animation) .cardBox.visualCardBox, +.card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable { + border-radius: 1rem; +} + +.blurhash-canvas { + border-radius: 0.5rem; +} + +.itemDetailImage, +.cardOverlayContainer { + border-radius: 0.5rem; +} + .metadataSidebarIcon { color: #00a4dc; } diff --git a/src/themes/blueradiance/theme.css b/src/themes/blueradiance/theme.css index b929c2a2962..7ecc4a74a59 100644 --- a/src/themes/blueradiance/theme.css +++ b/src/themes/blueradiance/theme.css @@ -42,6 +42,8 @@ html { } .dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu, html { background-color: #033361; } @@ -129,23 +131,23 @@ html { } .defaultCardBackground1 { - background-color: #d2b019; + background-color: #213440; } .defaultCardBackground2 { - background-color: #338abb; + background-color: #8c6565; } .defaultCardBackground3 { - background-color: #6b689d; + background-color: #57778c; } .defaultCardBackground4 { - background-color: #dd452b; + background-color: #8c8c49; } .defaultCardBackground5 { - background-color: #5ccea9; + background-color: #404024; } .cardText-secondary, @@ -179,7 +181,8 @@ html { color: rgba(255, 255, 255, 0.87); } -.appfooter { +.appfooter, +.playlistSectionButton { background: #033664; color: #ccc; color: rgba(255, 255, 255, 0.78); @@ -220,7 +223,7 @@ html { color: #fff !important; } -.detailSticky { +.detailRibbon { background: #303030; background: -webkit-gradient(linear, left top, right top, from(#291a31), color-stop(#033664), color-stop(#011432), color-stop(#141a3a), to(#291a31)); background: -webkit-linear-gradient(left, #291a31, #033664, #011432, #141a3a, #291a31); @@ -258,11 +261,6 @@ html { background: rgba(170, 170, 190, 0.2); } -.mediaInfoTimerIcon, -.starIcon { - color: #cb272a; -} - .emby-input, .emby-textarea { color: inherit; @@ -443,8 +441,12 @@ html { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; +} + +.cardBox:not(.visualCardBox) .cardPadder { + background-color: rgba(0, 0, 0, 0.5); } .card:focus .cardBox.visualCardBox, diff --git a/src/themes/dark/theme.css b/src/themes/dark/theme.css index 4363be991b3..be2b9269afa 100644 --- a/src/themes/dark/theme.css +++ b/src/themes/dark/theme.css @@ -36,6 +36,8 @@ html { .backgroundContainer, .dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu, html { background-color: #101010; } @@ -107,27 +109,27 @@ html { .formDialogHeader:not(.formDialogHeader-clear), .paperList, .visualCardBox { - background-color: #242424; + background-color: #202020; } .defaultCardBackground1 { - background-color: #d2b019; + background-color: #00455c; } .defaultCardBackground2 { - background-color: #338abb; + background-color: #44bae1; } .defaultCardBackground3 { - background-color: #6b689d; + background-color: #00a4db; } .defaultCardBackground4 { - background-color: #dd452b; + background-color: #1c4c5c; } .defaultCardBackground5 { - background-color: #5ccea9; + background-color: #007ea8; } .cardText-secondary, @@ -161,7 +163,8 @@ html { color: rgba(255, 255, 255, 0.87); } -.appfooter { +.appfooter, +.playlistSectionButton { background: #202020; color: #ccc; color: rgba(255, 255, 255, 0.78); @@ -200,11 +203,11 @@ html { background: rgba(30, 30, 30, 0.9); } -.detailSticky { +.detailRibbon { background: rgba(32, 32, 32, 0.8); } -.noBackdrop .detailSticky { +.noBackdrop .detailRibbon { background: #202020; } @@ -233,11 +236,6 @@ html { background: rgba(170, 170, 190, 0.2); } -.mediaInfoTimerIcon, -.starIcon { - color: #cb272a; -} - .emby-input, .emby-textarea { color: inherit; @@ -414,8 +412,8 @@ html { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; } .card:focus .cardBox.visualCardBox, @@ -434,6 +432,7 @@ html { .layout-desktop ::-webkit-scrollbar { width: 0.4em; + height: 0.4em; } ::-webkit-scrollbar-thumb:horizontal, @@ -450,3 +449,8 @@ html { .metadataSidebarIcon { color: #00a4dc; } + +.emby-button.detailFloatingButton { + background-color: #00a4dc; + color: #fff; +} diff --git a/src/themes/light/theme.css b/src/themes/light/theme.css index d84a1c3b672..c5161985cdf 100644 --- a/src/themes/light/theme.css +++ b/src/themes/light/theme.css @@ -51,10 +51,16 @@ html { background-color: rgba(255, 255, 255, 0.8); } -.dialog { +.dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu { background-color: #f0f0f0; } +.emby-scrollbuttons .paper-icon-button-light { + color: #000; +} + .paper-icon-button-light:hover:not(:disabled) { color: #00a4dc; background-color: rgba(0, 164, 220, 0.2); @@ -183,7 +189,8 @@ html { color: rgba(255, 255, 255, 0.87); } -.appfooter { +.appfooter, +.playlistSectionButton { background: #282828; color: #ccc; color: rgba(255, 255, 255, 0.78); @@ -218,7 +225,7 @@ html { color: #fff !important; } -.detailSticky { +.detailRibbon { background-color: #303030; color: #ccc; color: rgba(255, 255, 255, 0.87); @@ -231,7 +238,7 @@ html { } .listItem-border { - border-color: #f0f0f0 !important; + border-color: #a7a7a7 !important; } .listItem:focus { @@ -247,11 +254,6 @@ html { background: #fff; } -.mediaInfoTimerIcon, -.starIcon { - color: #cb272a; -} - .emby-input, .emby-textarea { color: inherit; @@ -425,8 +427,12 @@ html { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; +} + +.cardBox:not(.visualCardBox) .cardPadder { + background-color: #fff; } .card:focus .cardBox.visualCardBox, diff --git a/src/themes/purplehaze/theme.css b/src/themes/purplehaze/theme.css index 45f43abc2fb..1d82afc23a2 100644 --- a/src/themes/purplehaze/theme.css +++ b/src/themes/purplehaze/theme.css @@ -37,6 +37,8 @@ html { } .dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu, html { background-color: #230c33; } @@ -197,7 +199,7 @@ a[data-role=button] { } .cardContent { - border-radius: 1em; + border-radius: 0.8em; } .collapseContent, @@ -206,36 +208,40 @@ a[data-role=button] { .paperList, .visualCardBox { background-color: rgba(0, 0, 0, 0.5); - border-radius: 1em; + border-radius: 0.8em; } .cardOverlayContainer { border-radius: 0.8em; } +.blurhash-canvas { + border-radius: 0.8em; +} + .visualCardBox .cardOverlayContainer { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .defaultCardBackground1 { - background-color: #d2b019; + background-color: #9c20ab; } .defaultCardBackground2 { - background-color: #338abb; + background-color: #d799de; } .defaultCardBackground3 { - background-color: #6b689d; + background-color: #412378; } .defaultCardBackground4 { - background-color: #dd452b; + background-color: #857b48; } .defaultCardBackground5 { - background-color: #5ccea9; + background-color: #ab8820; } .cardText-secondary, @@ -269,7 +275,8 @@ a[data-role=button] { color: rgba(255, 255, 255, 0.87); } -.appfooter { +.appfooter, +.playlistSectionButton { background: #06256f; color: #ccc; color: rgba(255, 255, 255, 0.78); @@ -304,7 +311,7 @@ a[data-role=button] { color: #f8f8fe !important; } -.detailSticky { +.detailRibbon { background: #000420; background: -moz-linear-gradient(left, #000420 0%, #06256f 18%, #2b052b 38%, #2b052b 68%, #06256f 81%, #000420 100%); background: -webkit-linear-gradient(left, #000420 0%, #06256f 18%, #2b052b 38%, #2b052b 68%, #06256f 81%, #000420 100%); @@ -341,11 +348,6 @@ a[data-role=button] { background: rgba(170, 170, 190, 0.2); } -.mediaInfoTimerIcon, -.starIcon { - color: #f2b01e; -} - .emby-input, .emby-textarea { color: inherit; @@ -540,20 +542,30 @@ a[data-role=button] { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; } .personCard .cardScalable { - border-radius: 50%; + border-radius: 50% !important; border: 1px solid rgb(255, 255, 255); } +.cardBox:not(.visualCardBox) .cardPadder { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 0.8em; +} + .card:focus .cardBox.visualCardBox, .card:focus .cardBox:not(.visualCardBox) .cardScalable { border-color: #ff77f1 !important; } +.card.show-focus:not(.show-animation) .cardBox.visualCardBox, +.card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable { + border-radius: 1.3em; +} + .layout-desktop, .scrollY { scrollbar-width: thin; @@ -599,12 +611,6 @@ a[data-role=button] { width: 40vw; } -.personCard .cardPadder-overflowPortrait, -.personCard .cardPadder-portrait { - padding-bottom: 100%; - contain: strict; -} - .personCard .coveredImage { clip-path: circle(50% at 50% 50%); } @@ -616,3 +622,10 @@ a[data-role=button] { .personCard .cardOverlayButton-br { right: 20%; } + +.personCard .cardPadder-overflowPortrait, +.personCard .cardPadder-portrait { + padding-bottom: 100%; + contain: strict; + border-radius: 50% !important; +} diff --git a/src/themes/wmc/theme.css b/src/themes/wmc/theme.css index 6143b7fe2c9..4a7375a1290 100644 --- a/src/themes/wmc/theme.css +++ b/src/themes/wmc/theme.css @@ -48,7 +48,9 @@ html { } .backgroundContainer, -.dialog { +.dialog, +.nowPlayingPlaylist, +.nowPlayingContextMenu { background: -webkit-gradient(linear, left top, left bottom, from(#0f3562), color-stop(#1162a4), to(#03215f)); background: -webkit-linear-gradient(top, #0f3562, #1162a4, #03215f); background: -o-linear-gradient(top, #0f3562, #1162a4, #03215f); @@ -172,7 +174,8 @@ html { } .appfooter, -.formDialogFooter:not(.formDialogFooter-clear) { +.formDialogFooter:not(.formDialogFooter-clear), +.playlistSectionButton { background: #0c2450; background: -webkit-gradient(linear, left bottom, left top, from(#0c2450), to(#081b3b)); background: -webkit-linear-gradient(bottom, #0c2450, #081b3b); @@ -206,7 +209,7 @@ html { color: #fff !important; } -.detailSticky { +.detailRibbon { background-color: #081b3b; } @@ -240,11 +243,6 @@ html { background: rgba(170, 170, 190, 0.2); } -.mediaInfoTimerIcon, -.starIcon { - color: #cb272a; -} - .emby-input, .emby-textarea { color: inherit; @@ -423,8 +421,12 @@ html { color: #c33; } -.repeatButton-active { - color: #4285f4; +.buttonActive { + color: #00a4dc !important; +} + +.cardBox:not(.visualCardBox) .cardPadder { + background-color: #0f3562; } .card:focus .cardBox.visualCardBox, diff --git a/src/tv.html b/src/tv.html index 9a47c6fa8b7..ceb5c51b443 100644 --- a/src/tv.html +++ b/src/tv.html @@ -3,9 +3,9 @@
- - - + + +
@@ -59,9 +59,9 @@

${MessageNothingHere}

- - - + + +
diff --git a/src/useredit.html b/src/useredit.html index 0de3069dc7b..c3a613bed41 100644 --- a/src/useredit.html +++ b/src/useredit.html @@ -6,7 +6,7 @@ @@ -104,6 +104,16 @@

${HeaderPlayback}

${LabelUserRemoteClientBitrateLimitHelp}
+
+
+ +
${SyncPlayAccessHelp}
+
+

${HeaderAllowMediaDeletionFrom}

diff --git a/src/userlibraryaccess.html b/src/userlibraryaccess.html index 4653fd07ebd..8a6177585a2 100644 --- a/src/userlibraryaccess.html +++ b/src/userlibraryaccess.html @@ -6,7 +6,7 @@ diff --git a/src/usernew.html b/src/usernew.html index bca1e72feaf..fac036aa8d0 100644 --- a/src/usernew.html +++ b/src/usernew.html @@ -5,7 +5,7 @@

${HeaderAddUser}

- ${Help} + ${Help}
diff --git a/src/userparentalcontrol.html b/src/userparentalcontrol.html index 4cb5708db7d..2c13ec80128 100644 --- a/src/userparentalcontrol.html +++ b/src/userparentalcontrol.html @@ -4,7 +4,7 @@ @@ -31,7 +31,7 @@

${LabelBlockContentWithTags}

@@ -41,7 +41,7 @@

${LabelBlockContentWithTags}

${HeaderAccessSchedule}

diff --git a/src/userpassword.html b/src/userpassword.html index f38e3959990..119a0212de9 100644 --- a/src/userpassword.html +++ b/src/userpassword.html @@ -4,7 +4,7 @@ diff --git a/src/userprofiles.html b/src/userprofiles.html index 022e06b95f8..98237645dd0 100644 --- a/src/userprofiles.html +++ b/src/userprofiles.html @@ -6,9 +6,9 @@

${HeaderUsers}

- ${Help} + ${Help}
diff --git a/src/videoosd.html b/src/videoosd.html index c200360c05e..452c8a9af83 100644 --- a/src/videoosd.html +++ b/src/videoosd.html @@ -8,7 +8,7 @@

- autorenew + ${FetchingData}
@@ -25,47 +25,47 @@

@@ -76,7 +76,7 @@

diff --git a/src/wizardfinish.html b/src/wizardfinish.html index d3a0c4a8318..4d54a10cd48 100644 --- a/src/wizardfinish.html +++ b/src/wizardfinish.html @@ -5,11 +5,11 @@

${LabelYoureDone}

${WizardCompleted}

diff --git a/src/wizardlibrary.html b/src/wizardlibrary.html index 4fad6565819..3a5ca5069bd 100644 --- a/src/wizardlibrary.html +++ b/src/wizardlibrary.html @@ -3,7 +3,7 @@

${HeaderSetupLibrary}

- +

@@ -11,12 +11,12 @@

${HeaderSetupLibrary}


diff --git a/src/wizardremoteaccess.html b/src/wizardremoteaccess.html index c7ddfb7e84b..0718c2dc37b 100644 --- a/src/wizardremoteaccess.html +++ b/src/wizardremoteaccess.html @@ -13,7 +13,7 @@

${HeaderConfigureRemoteAccess}

${LabelEnableAutomaticPortMapHelp}
@@ -21,12 +21,12 @@

${HeaderConfigureRemoteAccess}


diff --git a/src/wizardsettings.html b/src/wizardsettings.html index f753919478d..d4f537cf987 100644 --- a/src/wizardsettings.html +++ b/src/wizardsettings.html @@ -16,12 +16,12 @@

${HeaderPreferredMetadataLanguage}

diff --git a/src/wizardstart.html b/src/wizardstart.html index 615feb3b04b..05e282bee34 100644 --- a/src/wizardstart.html +++ b/src/wizardstart.html @@ -4,7 +4,7 @@

${WelcomeToProject}

- + ${ButtonQuickStartGuide}
@@ -20,7 +20,7 @@

${WelcomeToProject}

diff --git a/src/wizarduser.html b/src/wizarduser.html index 90f492b0fee..3ce0b3ba746 100644 --- a/src/wizarduser.html +++ b/src/wizarduser.html @@ -22,12 +22,12 @@

${TellUsAboutYourself}

diff --git a/webpack.common.js b/webpack.common.js index 3859e78db89..e9163d98000 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,32 +1,32 @@ -const path = require("path"); +const path = require('path'); -const { CleanWebpackPlugin } = require("clean-webpack-plugin"); -const CopyPlugin = require("copy-webpack-plugin"); +const CopyPlugin = require('copy-webpack-plugin'); const Assets = [ - "alameda/alameda.js", - "native-promise-only/npo.js" + 'alameda/alameda.js', + 'native-promise-only/npo.js', ]; module.exports = { - context: path.resolve(__dirname, "src"), - entry: "./bundle.js", + context: path.resolve(__dirname, 'src'), + entry: './bundle.js', + stats: 'errors-only', resolve: { modules: [ - path.resolve(__dirname, "node_modules") + path.resolve(__dirname, 'node_modules') ] }, output: { - filename: "bundle.js", - path: path.resolve(__dirname, "dist"), - libraryTarget: "amd-require" + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + libraryTarget: 'amd-require' }, plugins: [ new CopyPlugin( Assets.map(asset => { return { from: path.resolve(__dirname, `./node_modules/${asset}`), - to: path.resolve(__dirname, "./dist/libraries") + to: path.resolve(__dirname, './dist/libraries') }; }) ) diff --git a/webpack.dev.js b/webpack.dev.js index 28617f8283e..3d0c2a48a07 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -1,34 +1,56 @@ -const path = require("path"); -const common = require("./webpack.common"); -const merge = require("webpack-merge"); +const path = require('path'); +const common = require('./webpack.common'); +const merge = require('webpack-merge'); +const packageConfig = require('./package.json'); module.exports = merge(common, { - mode: "development", + mode: 'development', output: { - filename: "bundle.js", - path: path.resolve(__dirname, "dist"), - libraryTarget: "amd-require" + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + libraryTarget: 'amd-require' }, + devtool: 'inline-source-map', module: { rules: [ { test: /\.js$/, - exclude: /node_modules/, - loader: "babel-loader" + exclude: /node_modules[\\/](?!date-fns|epubjs|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode|xmldom)/, + use: { + loader: 'babel-loader', + options: { + presets: packageConfig.babel.presets + } + } }, { test: /\.css$/i, - use: ["style-loader", "css-loader", "postcss-loader"] + use: [ + 'style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + config: { + path: __dirname + } + } + } + ] }, { test: /\.(png|jpg|gif)$/i, - use: ["file-loader"] + use: ['file-loader'] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, use: [ - 'file-loader', + 'file-loader' ] + }, + { + test: /\.(mp3)$/i, + use: ['file-loader'] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index 52d5050c387..52d6d0a8655 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -1,29 +1,49 @@ -const path = require("path"); -const common = require("./webpack.common"); -const merge = require("webpack-merge"); +const common = require('./webpack.common'); +const merge = require('webpack-merge'); +const packageConfig = require('./package.json'); module.exports = merge(common, { - mode: "production", + mode: 'production', module: { rules: [ { test: /\.js$/, - exclude: /node_modules/, - loader: "babel-loader" + exclude: /node_modules[\\/](?!date-fns|epubjs|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode|xmldom)/, + use: { + loader: 'babel-loader', + options: { + presets: packageConfig.babel.presets + } + } }, { test: /\.css$/i, - use: ["style-loader", "css-loader", "postcss-loader"] + use: [ + 'style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + config: { + path: __dirname + } + } + } + ] }, { test: /\.(png|jpg|gif)$/i, - use: ["file-loader"] + use: ['file-loader'] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, use: [ - 'file-loader', + 'file-loader' ] + }, + { + test: /\.(mp3)$/i, + use: ['file-loader'] } ] } diff --git a/yarn.lock b/yarn.lock index 0b382759e94..a8ae763dfa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,35 +2,64 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== +"@babel/code-frame@^7.0.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" + integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== + dependencies: + "@babel/highlight" "^7.10.3" + +"@babel/code-frame@^7.10.3", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - "@babel/highlight" "^7.8.3" + "@babel/highlight" "^7.10.4" -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== +"@babel/compat-data@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.4.tgz#706a6484ee6f910b719b696a9194f8da7d7ac241" + integrity sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw== dependencies: - browserslist "^4.9.1" + browserslist "^4.12.0" invariant "^2.2.4" semver "^5.5.0" -"@babel/core@>=7.2.2", "@babel/core@^7.8.6": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" +"@babel/core@>=7.2.2", "@babel/core@>=7.9.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e" + integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w== + dependencies: + "@babel/code-frame" "^7.10.3" + "@babel/generator" "^7.10.3" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helpers" "^7.10.1" + "@babel/parser" "^7.10.3" + "@babel/template" "^7.10.3" + "@babel/traverse" "^7.10.3" + "@babel/types" "^7.10.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.10.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.4.tgz#780e8b83e496152f8dd7df63892b2e052bf1d51d" + integrity sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -40,283 +69,312 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.9.0": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" - integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== +"@babel/generator@^7.10.3", "@babel/generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.4.tgz#e49eeed9fe114b62fa5b181856a43a5e32f5f243" + integrity sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng== dependencies: - "@babel/types" "^7.9.0" + "@babel/types" "^7.10.4" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" - integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.4" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" + integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-explode-assignable-expression" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== +"@babel/helper-compilation-targets@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" + integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" + "@babel/compat-data" "^7.10.4" + browserslist "^4.12.0" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" - integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-regex" "^7.8.3" +"@babel/helper-create-class-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz#2d4015d0136bd314103a70d84a7183e4b344a355" + integrity sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + +"@babel/helper-create-regexp-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" + integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-regex" "^7.10.4" regexpu-core "^4.7.0" -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== +"@babel/helper-define-map@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz#f037ad794264f729eda1889f4ee210b870999092" + integrity sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-function-name" "^7.10.4" + "@babel/types" "^7.10.4" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== +"@babel/helper-explode-assignable-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c" + integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A== dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-get-function-arity@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" - integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.4" -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== +"@babel/helper-hoist-variables@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" + integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.4" -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== +"@babel/helper-member-expression-to-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz#7cd04b57dfcf82fce9aeae7d4e4452fa31b8c7c4" + integrity sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A== dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-imports@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" - integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-transforms@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" - integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.9.0" - lodash "^4.17.13" + "@babel/types" "^7.10.4" -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" - integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + "@babel/types" "^7.10.4" -"@babel/helper-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" - integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== +"@babel/helper-module-transforms@^7.10.1", "@babel/helper-module-transforms@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz#ca1f01fdb84e48c24d7506bb818c961f1da8805d" + integrity sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q== dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/helper-simple-access@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" - integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== - dependencies: - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-split-export-declaration@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" - integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + "@babel/types" "^7.10.4" -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== +"@babel/helper-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.4.tgz#59b373daaf3458e5747dece71bbaf45f9676af6d" + integrity sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ== dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + lodash "^4.17.13" -"@babel/highlight@^7.8.3": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" - integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== - dependencies: - "@babel/helper-validator-identifier" "^7.9.0" +"@babel/helper-remap-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5" + integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-wrap-function" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-split-export-declaration@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" + integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helper-wrap-function@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" + integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helpers@^7.10.1", "@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/highlight@^7.10.3", "@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.3.tgz#043a5fc2ad8b7ea9facddc4e802a1f0f25da7255" - integrity sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A== +"@babel/parser@^7.10.3", "@babel/parser@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64" + integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== +"@babel/plugin-proposal-async-generator-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz#4b65abb3d9bacc6c657aaa413e56696f9f170fc6" + integrity sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== +"@babel/plugin-proposal-class-properties@^7.10.1", "@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-dynamic-import@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" + integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-dynamic-import" "^7.8.0" -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== +"@babel/plugin-proposal-json-strings@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" + integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== +"@babel/plugin-proposal-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" + integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" - integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== +"@babel/plugin-proposal-object-rest-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== +"@babel/plugin-proposal-optional-catch-binding@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" + integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== +"@babel/plugin-proposal-optional-chaining@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7" + integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" - integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== +"@babel/plugin-proposal-private-methods@^7.10.1", "@babel/plugin-proposal-private-methods@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" + integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" + integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.8" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-async-generators@^7.8.0": version "7.8.4" @@ -325,6 +383,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" + integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -346,12 +411,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" - integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.0": version "7.8.3" @@ -374,326 +439,337 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== +"@babel/plugin-syntax-top-level-await@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" + integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== +"@babel/plugin-transform-arrow-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" + integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== +"@babel/plugin-transform-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" + integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== +"@babel/plugin-transform-block-scoped-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" + integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== +"@babel/plugin-transform-block-scoping@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz#a670d1364bb5019a621b9ea2001482876d734787" + integrity sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" - integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" +"@babel/plugin-transform-classes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" + integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-define-map" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== +"@babel/plugin-transform-computed-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" + integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== +"@babel/plugin-transform-destructuring@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" + integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" - integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== +"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" + integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== +"@babel/plugin-transform-duplicate-keys@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" + integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== +"@babel/plugin-transform-exponentiation-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" + integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-for-of@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== +"@babel/plugin-transform-for-of@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" + integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== +"@babel/plugin-transform-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" + integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== +"@babel/plugin-transform-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" + integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== +"@babel/plugin-transform-member-expression-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" + integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-amd@^7.8.3", "@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== +"@babel/plugin-transform-modules-amd@^7.10.4", "@babel/plugin-transform-modules-amd@^7.9.6": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz#cb407c68b862e4c1d13a2fc738c7ec5ed75fc520" + integrity sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== +"@babel/plugin-transform-modules-commonjs@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" + integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== +"@babel/plugin-transform-modules-systemjs@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz#8f576afd943ac2f789b35ded0a6312f929c633f9" + integrity sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-hoist-variables" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" - integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== +"@babel/plugin-transform-modules-umd@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" + integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" + integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== +"@babel/plugin-transform-new-target@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" + integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== +"@babel/plugin-transform-object-super@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" + integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" -"@babel/plugin-transform-parameters@^7.8.7": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" - integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== +"@babel/plugin-transform-parameters@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz#7b4d137c87ea7adc2a0f3ebf53266871daa6fced" + integrity sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ== dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== +"@babel/plugin-transform-property-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" + integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-regenerator@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" - integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== +"@babel/plugin-transform-regenerator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" + integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== +"@babel/plugin-transform-reserved-words@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" + integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== +"@babel/plugin-transform-shorthand-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" + integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== +"@babel/plugin-transform-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" + integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== +"@babel/plugin-transform-sticky-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" + integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-regex" "^7.10.4" -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== +"@babel/plugin-transform-template-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz#e6375407b30fcb7fcfdbba3bb98ef3e9d36df7bc" + integrity sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== +"@babel/plugin-transform-typeof-symbol@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" + integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== +"@babel/plugin-transform-unicode-escapes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" + integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" + integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/polyfill@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.7.tgz#151ec24c7135481336168c3bd8b8bf0cf91c032f" - integrity sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w== + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.10.4.tgz#915e5bfe61490ac0199008e35ca9d7d151a8e45a" + integrity sha512-8BYcnVqQ5kMD2HXoHInBH7H1b/uP3KdnwCYXOqFnXqguOyuu443WXusbIUbWEfY3Z0Txk0M1uG/8YuAMhNl6zg== dependencies: core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/preset-env@^7.8.6": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" - integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== - dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" +"@babel/preset-env@^7.10.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" + integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== + dependencies: + "@babel/compat-data" "^7.10.4" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.10.4" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-json-strings" "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.0" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" - "@babel/plugin-transform-modules-umd" "^7.9.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.10.4" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.0" - browserslist "^4.9.1" + "@babel/types" "^7.10.4" + browserslist "^4.12.0" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -710,43 +786,50 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== +"@babel/runtime@^7.8.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" + integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.8.3", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" - integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.0" +"@babel/runtime@^7.9.2": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.3", "@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.10.3", "@babel/traverse@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.4.tgz#e642e5395a3b09cc95c8e74a27432b484b697818" + integrity sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" - integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== +"@babel/types@^7.10.3", "@babel/types@^7.10.4", "@babel/types@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee" + integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg== dependencies: - "@babel/helper-validator-identifier" "^7.9.0" + "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.13" to-fast-properties "^2.0.0" @@ -813,6 +896,21 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@stylelint/postcss-css-in-js@^0.37.1": + version "0.37.1" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz#41e5e7660f73d88227610e18c6ebb262d56ac125" + integrity sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw== + dependencies: + "@babel/core" ">=7.9.0" + +"@stylelint/postcss-markdown@^0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz#829b87e6c0f108014533d9d7b987dc9efb6632e8" + integrity sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw== + dependencies: + remark "^12.0.0" + unist-util-find-all-after "^3.0.1" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -837,6 +935,28 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/html-minifier-terser@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" + integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA== + +"@types/json-schema@^7.0.4": + version "7.0.5" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" + integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/localforage@0.0.34": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@types/localforage/-/localforage-0.0.34.tgz#5e31c32dd8791ec4b9ff3ef47c9cb55b2d0d9438" + integrity sha1-XjHDLdh5HsS5/z70fJy1Wy0NlDg= + dependencies: + localforage "*" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -848,9 +968,9 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*": - version "13.9.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" - integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + version "13.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" + integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -872,15 +992,15 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== -"@types/tapable@*": +"@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== "@types/uglify-js@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" - integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== + version "3.9.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.0.tgz#4490a140ca82aa855ad68093829e7fd6ae94ea87" + integrity sha512-3ZcoyPYHVOCcLpnfZwD47KFLr8W/mpUcgjpf1M4Q78TMJIw7KMAHSjiCLJp1z3ZrBR9pTLbe191O0TldFK5zcw== dependencies: source-map "^0.6.1" @@ -914,10 +1034,10 @@ "@types/source-list-map" "*" source-map "^0.6.1" -"@types/webpack@^4.4.31": - version "4.41.8" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.8.tgz#d2244f5f612ee30230a5c8c4ae678bce90d27277" - integrity sha512-mh4litLHTlDG84TGCFv1pZldndI34vkrW9Mks++Zx4KET7DRMoCXUvLbTISiuF4++fMgNnhV9cc1nCXJQyBYbQ== +"@types/webpack@^4.41.8": + version "4.41.12" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.12.tgz#0386ee2a2814368e2f2397abb036c0bf173ff6c3" + integrity sha512-BpCtM4NnBen6W+KEhrL9jKuZCXVtiH6+0b6cxdvNt2EwU949Al334PjQSl2BeAyvAX9mgoNNG21wvjP3xZJJ5w== dependencies: "@types/anymatch" "*" "@types/node" "*" @@ -1086,7 +1206,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@~1.3.4: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -1104,7 +1224,7 @@ acorn@5.X, acorn@^5.0.3: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== -acorn@^6.2.1: +acorn@^6.4.1: version "6.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== @@ -1137,10 +1257,10 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -1193,11 +1313,6 @@ ansi-gray@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -1344,15 +1459,14 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" array-initial@^1.0.0: version "1.1.0" @@ -1405,6 +1519,14 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" @@ -1454,6 +1576,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-metadata-inferer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.4.0.tgz#6be85ceeffcf267bd79db8e1ae731da44880b45f" + integrity sha512-tKHdBe8N/Vq2nLAm4YPBVREVZjMux6KrqyPfNQgIbDl0t7HaNSmy8w4OyVHYg/cvyn5BW7o7pVwpjPte89Zhcg== + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -1501,13 +1628,6 @@ async@1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1518,18 +1638,18 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^9.0.0, autoprefixer@^9.6.1, autoprefixer@^9.7.4: - version "9.7.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.5.tgz#8df10b9ff9b5814a8d411a5cfbab9c793c392376" - integrity sha512-URo6Zvt7VYifomeAfJlMFnYDhow1rk2bufwkbamPEAtQFcL11moLk4PnR7n9vlu7M+BkXAZkHFA0mIcY7tjQFg== +autoprefixer@^9.0.0, autoprefixer@^9.6.1, autoprefixer@^9.8.0, autoprefixer@^9.8.5: + version "9.8.5" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.5.tgz#2c225de229ddafe1d1424c02791d0c3e10ccccaa" + integrity sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg== dependencies: - browserslist "^4.11.0" - caniuse-lite "^1.0.30001036" - chalk "^2.4.2" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001097" + colorette "^1.2.0" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.27" - postcss-value-parser "^4.0.3" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: version "0.7.0" @@ -1549,6 +1669,15 @@ axios@0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +babel-eslint@^11.0.0-beta.2: + version "11.0.0-beta.2" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-11.0.0-beta.2.tgz#de1f06795aa0d8cedcf6ac943e63056f5b4a7048" + integrity sha512-D2tunrOu04XloEdU2XVUminUu25FILlGruZmffqH5OSnLDhCheKNvUoM1ihrexdUvhizlix8bjqRnsss4V/UIQ== + dependencies: + eslint-scope "5.0.0" + eslint-visitor-keys "^1.1.0" + semver "^6.3.0" + babel-loader@^8.0.6: version "8.1.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" @@ -1560,10 +1689,10 @@ babel-loader@^8.0.6: pify "^4.0.1" schema-utils "^2.6.5" -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" @@ -1649,11 +1778,6 @@ better-assert@~1.0.0: dependencies: callsite "1.0.0" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1744,39 +1868,16 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +blurhash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.3.tgz#dc325af7da836d07a0861d830bdd63694382483e" + integrity sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -1943,15 +2044,15 @@ browserslist@^1.1.3: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^4.0.0, browserslist@^4.11.0, browserslist@^4.6.4, browserslist@^4.8.3, browserslist@^4.9.1: - version "4.11.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.0.tgz#aef4357b10a8abda00f97aac7cd587b2082ba1ad" - integrity sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A== +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.12.2, browserslist@^4.6.4, browserslist@^4.8.5: + version "4.12.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" + integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== dependencies: - caniuse-lite "^1.0.30001035" - electron-to-chromium "^1.3.380" - node-releases "^1.1.52" - pkg-up "^3.1.0" + caniuse-lite "^1.0.30001088" + electron-to-chromium "^1.3.483" + escalade "^3.0.1" + node-releases "^1.1.58" bs-recipes@1.3.4: version "1.3.4" @@ -1996,11 +2097,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2016,9 +2112,9 @@ buffer@^4.3.0: isarray "^1.0.0" buffer@^5.2.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" - integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -2028,11 +2124,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2129,6 +2220,14 @@ camel-case@3.0.x: no-case "^2.2.0" upper-case "^1.1.1" +camel-case@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + dependencies: + pascal-case "^3.1.1" + tslib "^1.10.0" + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -2146,10 +2245,10 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" -camelcase-keys@^6.1.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.1.tgz#cd3e2d2d7db767aa3f247e4c2df93b4661008945" - integrity sha512-BPCNVH56RVIxQQIXskp5tLQXUNGQ6sXr7iCv1FHDt81xBOQ/1r6H8SPxf19InVP6DexWar4s87q9thfuk8X9HA== +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== dependencies: camelcase "^5.3.1" map-obj "^4.0.0" @@ -2175,6 +2274,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" + integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -2190,10 +2294,15 @@ caniuse-db@^1.0.30000639: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001036.tgz#8761fb6cd423ef2d3f8d96a21d898932252dc477" integrity sha512-plRkihXQyiDaFUXC7x/jAIXXTKiiaWvfAagsruh/vmstnRQ+a2a95HyENxiTr5WrkPSvmFUIvsRUalVFyeh2/w== -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001036: - version "1.0.30001036" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" - integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== +caniuse-db@^1.0.30001090: + version "1.0.30001093" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001093.tgz#5a1cae72d94df1156f40f15d9079456e1b29d050" + integrity sha512-XqXxHR6Z9IN0BXLKMaTJ1NZ+US74cbKritholD6uaDLUWHiDj0QilpSb708wOcoGz0PmPRsXT/6zE+bjx+QSMw== + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001088, caniuse-lite@^1.0.30001097: + version "1.0.30001099" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001099.tgz#540118fcc6842d1fde62f4ee5521d1ec6afdb40e" + integrity sha512-sdS9A+sQTk7wKoeuZBN/YMAHVztUfVnjDi4/UV3sDE8xoh7YR12hKW+pIdB3oqKGwr9XaFL2ovfzt9w8eUI5CA== caseless@~0.12.0: version "0.12.0" @@ -2215,15 +2324,6 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2235,6 +2335,15 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -2243,6 +2352,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-entities-html4@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" @@ -2268,7 +2385,7 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.8: +chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -2317,7 +2434,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-css@4.2.x: +"classlist.js@https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz": + version "1.2.20180112" + resolved "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz#9c7ab3ccdbde5c940f6f26f8fc59bfb6bc813bc4" + +clean-css@4.2.x, clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== @@ -2329,14 +2450,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -clean-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz#a99d8ec34c1c628a4541567aa7b457446460c62b" - integrity sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A== - dependencies: - "@types/webpack" "^4.4.31" - del "^4.1.1" - cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2345,9 +2458,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== cliui@^3.2.0: version "3.2.0" @@ -2358,15 +2471,6 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -2513,6 +2617,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.0.tgz#45306add826d196e8c87236ac05d797f25982e63" + integrity sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2530,10 +2639,10 @@ commander@^2.2.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@~2.19.0: version "2.19.0" @@ -2572,26 +2681,6 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2607,7 +2696,7 @@ concat-stream@^1.5.0, concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-with-sourcemaps@^1.0.0, concat-with-sourcemaps@^1.0.5: +concat-with-sourcemaps@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== @@ -2622,7 +2711,7 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" -connect-history-api-fallback@^1, connect-history-api-fallback@^1.6.0: +connect-history-api-fallback@^1: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== @@ -2657,18 +2746,18 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3, content-disposition@^0.5.2: +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - convert-source-map@1.X, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -2676,21 +2765,11 @@ convert-source-map@1.X, convert-source-map@^1.5.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -2735,11 +2814,11 @@ copy-webpack-plugin@^5.1.1: webpack-log "^2.0.0" core-js-compat@^3.6.2: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== dependencies: - browserslist "^4.8.3" + browserslist "^4.8.5" semver "7.0.0" core-js@^2.6.5: @@ -2747,10 +2826,10 @@ core-js@^2.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-js@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== +core-js@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -2809,17 +2888,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2837,6 +2905,17 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2882,23 +2961,24 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.4.2.tgz#d3fdb3358b43f233b78501c5ed7b1c6da6133202" - integrity sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA== +css-loader@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" + integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== dependencies: camelcase "^5.3.1" cssesc "^3.0.0" icss-utils "^4.1.1" loader-utils "^1.2.3" normalize-path "^3.0.0" - postcss "^7.0.23" + postcss "^7.0.32" postcss-modules-extract-imports "^2.0.0" postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.1.1" + postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.2" - schema-utils "^2.6.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.0" + semver "^6.3.0" css-prefers-color-scheme@^3.1.1: version "3.1.1" @@ -2940,6 +3020,14 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -3044,11 +3132,11 @@ cssnano@^4.1.10: postcss "^7.0.0" csso@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" - integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== dependencies: - css-tree "1.0.0-alpha.37" + css-tree "1.0.0-alpha.39" currently-unhandled@^0.4.1: version "0.4.1" @@ -3077,6 +3165,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba" + integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw== + dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" @@ -3091,14 +3184,14 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@3.X, debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: +debug@3.X: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3184,9 +3277,9 @@ decompress-unzip@^4.0.1: yauzl "^2.4.2" decompress@^4.0.0, decompress@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" - integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50= + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" + integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== dependencies: decompress-tar "^4.0.0" decompress-tarbz2 "^4.0.0" @@ -3197,18 +3290,6 @@ decompress@^4.0.0, decompress@^4.2.0: pify "^2.3.0" strip-dirs "^2.0.0" -deep-equal@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3221,14 +3302,6 @@ default-compare@^1.0.0: dependencies: kind-of "^5.0.2" -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - default-resolution@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" @@ -3263,19 +3336,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - del@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" @@ -3328,11 +3388,6 @@ detect-newline@2.X: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - dev-ip@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0" @@ -3347,14 +3402,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" - integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== - dependencies: - arrify "^1.0.1" - path-type "^3.0.0" - dir-glob@^2.0.0, dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -3369,25 +3416,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - dependencies: - buffer-indexof "^1.0.0" + esutils "^2.0.2" + isarray "^1.0.0" doctrine@^3.0.0: version "3.0.0" @@ -3396,13 +3431,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -document-register-element@^1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/document-register-element/-/document-register-element-1.14.3.tgz#3335d4578df6a1536a34595b91cca36dd5db61d7" - integrity sha512-SbJTzoQXLTcYxnpdDNRZXu/gwsGSShemXpvj6Pa6ujRwJFpJ41siil4tk4y+cQXnqylS6mc2Rtxp/PkMzfkqyQ== - dependencies: - lightercollective "^0.3.0" - dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -3418,12 +3446,12 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom7@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/dom7/-/dom7-2.1.3.tgz#a736f9c3bfbc4ca039a81cd095f97d1d7f3de19c" - integrity sha512-QTxHHDox+M6ZFz1zHPAHZKI3JOHY5iY4i9BK2uctlggxKQwRhO3q3HHFq1BKsT25Bm/ySSj70K6Wk/G4bs9rMQ== +dom7@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/dom7/-/dom7-2.1.5.tgz#a79411017800b31d8400070cdaebbfc92c1f6377" + integrity sha512-xnhwVgyOh3eD++/XGtH+5qBwYTgCm0aW91GFgPJ3XG+jlsRLyJivnbP0QmUBFhI+Oaz9FV0s7cxgXHezwOEBYA== dependencies: - ssr-window "^1.0.1" + ssr-window "^2.0.0" domain-browser@^1.1.1: version "1.2.0" @@ -3463,6 +3491,14 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +dot-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" + integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -3577,10 +3613,15 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.380: - version "1.3.382" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.382.tgz#cad02da655c33f7a3d6ca7525bd35c17e90f3a8f" - integrity sha512-gJfxOcgnBlXhfnUUObsq3n3ReU8CT6S8je97HndYRkKsNZMJJ38zO/pI5aqO7L3Myfq+E3pqPyKK/ynyLEQfBA== +electron-to-chromium@^1.2.7: + version "1.3.427" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.427.tgz#ea43d02908a8c71f47ebb46e09de5a3cf8236f04" + integrity sha512-/rG5G7Opcw68/Yrb4qYkz07h3bESVRJjUl4X/FrKLXzoUJleKm6D7K7rTTz8V5LUWnd+BbTOyxJX2XprRqHD8A== + +electron-to-chromium@^1.3.483: + version "1.3.488" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.488.tgz#9226229f5fbc825959210e81e0bb3e63035d1c06" + integrity sha512-NReBdOugu1yl8ly+0VDtiQ6Yw/1sLjnvflWq0gvY1nfUXU2PbA+1XAVuEb7ModnwL/MfUPjby7e4pAFnSHiy6Q== elliptic@^6.0.0: version "6.5.2" @@ -3595,7 +3636,7 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -eme-encryption-scheme-polyfill@^2.0.0: +eme-encryption-scheme-polyfill@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.0.1.tgz#b080b01bffd74c75c9cf8044c1cabedf3b83954f" integrity sha512-Wz+Ro1c0/2Wsx2RLFvTOO0m4LvYn+7cSnq3XOvRvLLBq8jbvUACH/zpU9s0/5+mQa5oaelkU69x+q0z/iWYrFA== @@ -3610,11 +3651,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3650,9 +3686,9 @@ engine.io-client@~3.2.0: yeast "0.1.2" engine.io-client@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700" - integrity sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA== + version "3.4.1" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.1.tgz#922ddb47eecdcb541136a93aeead24718fd05461" + integrity sha512-RJNmA+A9Js+8Aoq815xpGAsgWH1VoSYM//2VgIiu9lNOaHFfLpTjH4tOzktBpjIs5lvOfiNY1dwf+NuU6D38Mw== dependencies: component-emitter "1.2.1" component-inherit "0.0.3" @@ -3700,15 +3736,6 @@ engine.io@~3.2.0: engine.io-parser "~2.1.0" ws "~3.3.1" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - enhanced-resolve@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" @@ -3728,6 +3755,21 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== +epubjs@^0.3.85: + version "0.3.88" + resolved "https://registry.yarnpkg.com/epubjs/-/epubjs-0.3.88.tgz#bc365e7e21893cf2d92717ce10927c1071275347" + integrity sha512-VRumULpUELYmYwzypyfbDwoSIqDp2LXOXCtY3o55o3YDW5Zm32UjtZuX/xaWFGqyZORNNMWWQ8VlMaY1djnDYg== + dependencies: + "@types/localforage" "0.0.34" + core-js "^3.6.5" + event-emitter "^0.3.5" + jszip "^3.4.0" + localforage "^1.7.3" + lodash "^4.17.15" + marks-pane "^1.0.9" + path-webpack "0.0.3" + xmldom "^0.3.0" + errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -3742,7 +3784,24 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.17.2: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -3809,6 +3868,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" +escalade@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" + integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3824,15 +3888,69 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-compat@^3.5.1: + version "3.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-3.8.0.tgz#2348d6105e7e87b823ae3b97b349512a2a45a7f2" + integrity sha512-5CuWUSZXZkXLCQJBriEpndn/YWrvggDSHTpRJq++kR8GVcsWbTdp8Eh+nBA7JlrNi7ZJ/+kniOVXmn3bpnxuRA== + dependencies: + ast-metadata-inferer "^0.4.0" + browserslist "^4.12.2" + caniuse-db "^1.0.30001090" + core-js "^3.6.5" + find-up "^4.1.0" + lodash.memoize "4.1.2" + mdn-browser-compat-data "^1.0.28" + semver "7.3.2" + +eslint-plugin-eslint-comments@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" + integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-import@^2.21.2: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== -eslint-scope@^5.0.0: +eslint-scope@5.0.0, eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== @@ -3840,6 +3958,14 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -3910,11 +4036,11 @@ esprima@^4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" - integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^5.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -3928,10 +4054,10 @@ estraverse@^4.1.0, estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" - integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== esutils@^2.0.2: version "2.0.3" @@ -3956,28 +4082,16 @@ eventemitter3@1.x.x: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" integrity sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg= -eventemitter3@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== - -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== +eventemitter3@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== events@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" - evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -4064,42 +4178,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -4137,7 +4215,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@3.0.2, extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -4190,7 +4268,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== -fast-glob@^2.0.2, fast-glob@^2.2.6: +fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== @@ -4224,27 +4302,18 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + fastq@^1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.1.tgz#4570c74f2ded173e71cf0beb08ac70bb85826791" - integrity sha512-mpIH5sKYueh3YyeJwqtVo8sORi0CgtmkVbK6kZStpQlZBYQuTzG2CZ7idSiJuA7bY0SFCWUc5WIs+oYumGCQNw== + version "1.7.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.7.0.tgz#fcd79a08c5bd7ec5b55cd3f5c4720db551929801" + integrity sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ== dependencies: reusify "^1.0.4" -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -4253,9 +4322,9 @@ fd-slicer@~1.1.0: pend "~1.2.0" figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.3.5: version "1.7.0" @@ -4286,13 +4355,13 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-loader@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-5.1.0.tgz#cb56c070efc0e40666424309bd0d9e45ac6f2bb8" - integrity sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg== +file-loader@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" + integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== dependencies: - loader-utils "^1.4.0" - schema-utils "^2.5.0" + loader-utils "^2.0.0" + schema-utils "^2.6.5" file-type@5.2.0, file-type@^5.2.0: version "5.2.0" @@ -4378,19 +4447,6 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -4408,7 +4464,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.0.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= @@ -4437,16 +4493,6 @@ find-versions@^3.0.0: dependencies: semver-regex "^2.0.0" -findup-sync@3.0.0, findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -4457,6 +4503,16 @@ findup-sync@^2.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + fined@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" @@ -4483,9 +4539,9 @@ flat-cache@^2.0.1: write "1.0.3" flatted@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" - integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flatten@^1.0.2: version "1.0.3" @@ -4515,13 +4571,6 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb" - integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ== - dependencies: - debug "^3.0.0" - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4553,11 +4602,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -4701,10 +4745,10 @@ get-stdin@^6.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stdin@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" - integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" @@ -4815,13 +4859,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@2.0.0, global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -4831,6 +4868,13 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -4877,10 +4921,10 @@ globby@^10.0.0, globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154" - integrity sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg== +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -4889,17 +4933,6 @@ globby@^11.0.0: merge2 "^1.3.0" slash "^3.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globby@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" @@ -4912,19 +4945,6 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globby@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" - integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== - dependencies: - array-union "^1.0.1" - dir-glob "2.0.0" - fast-glob "^2.0.2" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - globby@^9.0.0: version "9.2.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" @@ -4960,13 +4980,20 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -gonzales-pe@^4.2.3, gonzales-pe@^4.2.4: +gonzales-pe@^4.2.3: version "4.2.4" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.4.tgz#356ae36a312c46fe0f1026dd6cb539039f8500d2" integrity sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ== dependencies: minimist "1.1.x" +gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + got@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" @@ -5011,9 +5038,9 @@ got@^8.3.1: url-to-options "^1.0.1" graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -5042,10 +5069,10 @@ gulp-babel@^8.0.0: through2 "^2.0.0" vinyl-sourcemaps-apply "^0.2.0" -gulp-cli@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc" - integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA== +gulp-cli@^2.2.0, gulp-cli@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.3.0.tgz#ec0d380e29e52aa45e47977f0d32e18fd161122f" + integrity sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A== dependencies: ansi-colors "^1.0.1" archy "^1.0.0" @@ -5055,7 +5082,7 @@ gulp-cli@^2.2.0: copy-props "^2.0.1" fancy-log "^1.3.2" gulplog "^1.0.0" - interpret "^1.1.0" + interpret "^1.4.0" isobject "^3.0.1" liftoff "^3.1.0" matchdep "^2.0.0" @@ -5063,7 +5090,7 @@ gulp-cli@^2.2.0: pretty-hrtime "^1.0.0" replace-homedir "^1.0.0" semver-greatest-satisfied-range "^1.1.0" - v8flags "^3.0.1" + v8flags "^3.2.0" yargs "^7.1.0" gulp-concat@^2.6.1: @@ -5151,12 +5178,12 @@ gulp-postcss@^8.0.0: vinyl-sourcemaps-apply "^0.2.1" gulp-sass@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-4.0.2.tgz#cfb1e3eff2bd9852431c7ce87f43880807d8d505" - integrity sha512-q8psj4+aDrblJMMtRxihNBdovfzGrXJp1l4JU0Sz4b/Mhsi2DPrKFYCGDwjIWRENs04ELVHxdOJQ7Vs98OFohg== + version "4.1.0" + resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-4.1.0.tgz#486d7443c32d42bf31a6b1573ebbdaa361de7427" + integrity sha512-xIiwp9nkBLcJDpmYHbEHdoWZv+j+WtYaKD6Zil/67F3nrAaZtWYN5mDwerdo7EvcdBenSAj7Xb2hx2DqURLGdA== dependencies: chalk "^2.3.0" - lodash.clonedeep "^4.3.2" + lodash "^4.17.11" node-sass "^4.8.3" plugin-error "^1.0.1" replace-ext "^1.0.0" @@ -5232,11 +5259,6 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -5250,7 +5272,7 @@ har-validator@~5.1.3: ajv "^6.5.5" har-schema "^2.0.0" -hard-rejection@^2.0.0: +hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== @@ -5357,12 +5379,13 @@ has@^1.0.0, has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" @@ -5372,22 +5395,27 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.x: +he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headroom.js@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/headroom.js/-/headroom.js-0.11.0.tgz#26547a932025e4243abf8ace001b4ce5e110ed20" + integrity sha512-yI4ciZRD1WH22wa5uJDg2kMtRvhJwUJWo2l41Eby0BoAD+lzXL98lf5jDFxP4Q5W3HmlrpfItSfmqc3jCtasbw== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hls.js@^0.13.1: - version "0.13.2" - resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.13.2.tgz#3e7dd28e3787c69c6aba42b64b11eb2c3c8c29f1" - integrity sha512-sIg2t4uGpWQLzuK1Iid9614WOKqxj4OYg+EbFbhhTDCsxpENBN+Du3yBFnoi+a83DuOOHdiQd1ydnti9loSGXw== +hls.js@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.0.tgz#a815553f206685137bf4a2533cd87ff358a87aa3" + integrity sha512-PHQJ7gJpkRkOlufn9z2ID+V93HgJQahhsHr8rCcRS0q2XuUBdidAHeYsWeVYvZ73ZILVDN0EYVBvHZM1g2zZUA== dependencies: - eventemitter3 "3.1.0" + eventemitter3 "^4.0.3" url-toolkit "^2.1.6" hmac-drbg@^1.0.0: @@ -5411,20 +5439,10 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -howler@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/howler/-/howler-2.1.3.tgz#07c88618f8767e879407a4d647fe2d6d5f15f121" - integrity sha512-PSGbOi1EYgw80C5UQbxtJM7TmzD+giJunIMBYyH3RVzHZx2fZLYBoes0SpVVHi/SFa1GoNtgXj/j6I7NOKYBxQ== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" +howler@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.0.tgz#0e2c780997ae65ab9a1a186333031beac1c63c6b" + integrity sha512-sGPkrAQy7jh5mNDbkRNG0F82R2HFDYNsQXBcX4smXQT0y0F4UMsa/+jXaGwWvcrajWr2tDB7JUkH7G5qSnuIyQ== hsl-regex@^1.0.0: version "1.0.0" @@ -5441,12 +5459,20 @@ html-comment-regex@^1.1.0, html-comment-regex@^1.1.2: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-entities@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= - -html-minifier@^3.2.3, html-minifier@^3.5.20: +html-minifier-terser@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#95d3df037f04835e9d1a09d1767c0e361a7de916" + integrity sha512-tiYE76O1zunboByeB/nFGwUEb263Z3nkNv6Lz2oLC1s6M36bLKfTrjQ+7ssVfaucVllE+N7hh/FbpbxvnIA+LQ== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + +html-minifier@^3.5.20: version "3.5.21" resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== @@ -5469,17 +5495,19 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== -html-webpack-plugin@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" - integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s= +html-webpack-plugin@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz#53bf8f6d696c4637d5b656d3d9863d89ce8174fd" + integrity sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w== dependencies: - html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" - pretty-error "^2.0.2" - tapable "^1.0.0" - toposort "^1.0.0" + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" util.promisify "1.0.0" htmlparser2@^3.10.0, htmlparser2@^3.3.0: @@ -5499,23 +5527,7 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@1.7.3, http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -5536,21 +5548,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - http-proxy@1.15.2: version "1.15.2" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.15.2.tgz#642fdcaffe52d3448d2bda3b0079e9409064da31" @@ -5559,15 +5556,6 @@ http-proxy@1.15.2: eventemitter3 "1.x.x" requires-port "1.x.x" -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -5616,10 +5604,10 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.1.1, ignore@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== +ignore@^5.0.4, ignore@^5.0.5, ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== imagemin-gifsicle@^7.0.0: version "7.0.0" @@ -5669,6 +5657,11 @@ imagemin@^7.0.0: p-pipe "^3.0.0" replace-ext "^1.0.0" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + immutable@^3: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" @@ -5714,14 +5707,6 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -import-local@2.0.0, import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -5772,7 +5757,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5811,18 +5796,15 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" +interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -interpret@1.2.0, interpret@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +intersection-observer@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.11.0.tgz#f4ea067070326f68393ee161cc0a2ca4c0040c6f" + integrity sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ== into-stream@^3.1.0: version "3.1.0" @@ -5844,26 +5826,6 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - irregular-plurals@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" @@ -5879,11 +5841,6 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" @@ -5924,11 +5881,6 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5956,10 +5908,10 @@ is-buffer@^2.0.0, is-buffer@^2.0.2: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== is-color-stop@^1.0.0: version "1.1.0" @@ -5992,7 +5944,7 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-decimal@^1.0.0: +is-decimal@^1.0.0, is-decimal@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== @@ -6134,25 +6086,11 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= -is-path-cwd@^2.0.0, is-path-cwd@^2.2.0: +is-path-cwd@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" - is-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" @@ -6163,6 +6101,11 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -6175,17 +6118,17 @@ is-png@^2.0.0: resolved "https://registry.yarnpkg.com/is-png/-/is-png-2.0.0.tgz#ee8cbc9e9b050425cedeeb4a6fb74a649b0a4a8d" integrity sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g== -is-promise@^2.1, is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-promise@^2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.4, is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== +is-regex@^1.0.5, is-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== dependencies: - has "^1.0.3" + has-symbols "^1.0.1" is-regexp@^1.0.0: version "1.0.0" @@ -6219,6 +6162,11 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-supported-regexp-flag@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca" @@ -6332,14 +6280,19 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -"jellyfin-noto@https://github.com/jellyfin/jellyfin-noto": - version "1.0.2" - resolved "https://github.com/jellyfin/jellyfin-noto#a441b179c833288fc372cadb408d32a76c5479f1" +jellyfin-apiclient@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.4.0.tgz#d8fedc88cc177597290687be31e38de3cd0d035a" + integrity sha512-v2lcSZwcbKh3YSrZkBwNM7tisxvUJHZawz0xpxIobEI6MHrQLo4oDdm1zHXN6Mku9uzbuBpbAV1tA6XJwVVTyA== -jquery@>=1.9.1, jquery@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" - integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== +"jellyfin-noto@https://github.com/jellyfin/jellyfin-noto": + version "1.0.3" + resolved "https://github.com/jellyfin/jellyfin-noto#b784602db063734c721a46563ae5d6577ec2b35d" + +jquery@>=1.9.1, jquery@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" + integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8, js-base64@^2.1.9: version "2.5.2" @@ -6404,16 +6357,6 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - -json5@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -6422,9 +6365,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" - integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== dependencies: minimist "^1.2.5" @@ -6445,13 +6388,23 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jstree@^3.3.7: - version "3.3.9" - resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.9.tgz#62b47cad3c4fda390d021e5c4f98ee5b3365198a" - integrity sha512-jRIbhg+BHrIs1Wm6oiJt3oKTVBE6sWS0PCp2/RlkIUqsLUPWUYgV3q8LfKoi1/E+YMzGtP6BuK4okk+0mwfmhQ== +jstree@^3.3.10: + version "3.3.10" + resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.10.tgz#28d5b464a6bd4b5a93ccc2e978d005845596a408" + integrity sha512-TDhwTy24ZKCVei0gLRxnH5PQuX77nqlG7bhQh+UDTeOxC2xdhDrS1x7YtbjLVlSxmH7USnA/WIeVOGN/m3D0QA== dependencies: jquery ">=1.9.1" +jszip@^3.4.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" + integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + junk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" @@ -6469,11 +6422,6 @@ keyv@3.0.0: dependencies: json-buffer "3.0.0" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -6503,10 +6451,10 @@ known-css-properties@^0.11.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.11.0.tgz#0da784f115ea77c76b81536d7052e90ee6c86a8a" integrity sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w== -known-css-properties@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.18.0.tgz#d6e00b56ee1d5b0d171fd86df1583cfb012c521f" - integrity sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw== +known-css-properties@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" + integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== last-run@^1.1.0: version "1.1.1" @@ -6537,13 +6485,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - lead@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" @@ -6576,6 +6517,24 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +"libass-wasm@https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv": + version "4.0.0" + resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639" + +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + liftoff@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" @@ -6590,11 +6549,6 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" -lightercollective@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.3.0.tgz#1f07638642ec645d70bdb69ab2777676f35a28f0" - integrity sha512-RFOLSUVvwdK3xA0P8o6G7QGXLIyy1L2qv5caEI7zXN5ciaEjbAriRF182kbsoJ1S1TgvpyGcN485fMky6qxOPw== - limiter@^1.0.5: version "1.1.5" resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" @@ -6616,6 +6570,16 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -6631,25 +6595,6 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - -loader-utils@^0.2.16: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -6659,6 +6604,22 @@ loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +localforage@*, localforage@^1.7.3: + version "1.7.4" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.4.tgz#88b59cc9b25ae54c76bb2c080b21ec832c22d3f6" + integrity sha512-3EmVZatmNVeCo/t6Te7P06h2alGwbq8wXlSkcSXMvDE2/edPmsVqTPlzGnZaqwZZDBs6v+kxWpqjVsqsNJT8jA== + dependencies: + lie "3.1.1" + localtunnel@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.9.2.tgz#0012fcabc29cf964c130a01858768aa2bb65b5af" @@ -6742,11 +6703,6 @@ lodash.clone@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= -lodash.clonedeep@^4.3.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.escape@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" @@ -6778,7 +6734,7 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.memoize@^4.1.2: +lodash.memoize@4.1.2, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= @@ -6836,10 +6792,10 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@~4.17.12: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@~4.17.12: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@^1.0.2: version "1.0.2" @@ -6855,12 +6811,12 @@ log-symbols@^2.0.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== dependencies: - chalk "^2.4.2" + chalk "^4.0.0" logalot@^2.0.0, logalot@^2.1.0: version "2.1.0" @@ -6870,11 +6826,6 @@ logalot@^2.0.0, logalot@^2.1.0: figures "^1.3.5" squeak "^1.0.0" -loglevel@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" - integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A== - longest-streak@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" @@ -6905,6 +6856,13 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" + lowercase-keys@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -6963,9 +6921,9 @@ make-dir@^2.0.0: semver "^5.6.0" make-dir@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -6976,13 +6934,6 @@ make-iterator@^1.0.0: dependencies: kind-of "^6.0.2" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -7020,6 +6971,18 @@ markdown-table@^1.1.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" + +marks-pane@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/marks-pane/-/marks-pane-1.0.9.tgz#c0b5ab813384d8cd81faaeb3bbf3397dc809c1b3" + integrity sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg== + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" @@ -7056,24 +7019,29 @@ mdast-util-compact@^1.0.0: dependencies: unist-util-visit "^1.1.0" +mdast-util-compact@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" + integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== + dependencies: + unist-util-visit "^2.0.0" + +mdn-browser-compat-data@^1.0.28: + version "1.0.29" + resolved "https://registry.yarnpkg.com/mdn-browser-compat-data/-/mdn-browser-compat-data-1.0.29.tgz#9edddaa953221050c6959a538c993e915e619220" + integrity sha512-R9/8Xi1d9by2Ag5O7Sur3zoe8k/61a+yYeC4f6S5UhbEZb2ICmYNZuprm+2IO9bBcT3Pa2BtEx+xKoX/8v8tPw== + dependencies: + extend "3.0.2" + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== memoizee@0.4.X: version "0.4.14" @@ -7089,7 +7057,7 @@ memoizee@0.4.X: next-tick "1" timers-ext "^0.1.5" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -7136,27 +7104,24 @@ meow@^5.0.0: trim-newlines "^2.0.0" yargs-parser "^10.0.0" -meow@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.0.tgz#4ff4641818d3502afcddc631f94cb6971a581cb3" - integrity sha512-iIAoeI01v6pmSfObAAWFoITAA4GgiT45m4SmJgoxtZfvI0fyZwhV4d0lTwiUXvAKIPlma05Feb2Xngl52Mj5Cg== +meow@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-7.0.1.tgz#1ed4a0a50b3844b451369c48362eb0515f04c1dc" + integrity sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw== dependencies: "@types/minimist" "^1.2.0" - camelcase-keys "^6.1.1" + arrify "^2.0.1" + camelcase "^6.0.0" + camelcase-keys "^6.2.2" decamelize-keys "^1.1.0" - hard-rejection "^2.0.0" - minimist-options "^4.0.1" + hard-rejection "^2.1.0" + minimist-options "^4.0.2" normalize-package-data "^2.5.0" - read-pkg-up "^7.0.0" + read-pkg-up "^7.0.1" redent "^3.0.0" trim-newlines "^3.0.0" - type-fest "^0.8.1" - yargs-parser "^18.1.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + type-fest "^0.13.1" + yargs-parser "^18.1.3" merge-stream@^2.0.0: version "2.0.0" @@ -7168,11 +7133,6 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -7208,34 +7168,24 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0, mime-db@^1.28.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -7275,7 +7225,7 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist-options@^4.0.1: +minimist-options@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.0.2.tgz#29c4021373ded40d546186725e57761e4b1984a7" integrity sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w== @@ -7323,9 +7273,9 @@ mixin-deep@^1.2.0: is-extendable "^1.0.1" "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" @@ -7355,29 +7305,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - multipipe@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" @@ -7396,9 +7328,9 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.12.1, nan@^2.13.2: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -7459,10 +7391,13 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== + dependencies: + lower-case "^2.0.1" + tslib "^1.10.0" node-gyp@^3.8.0: version "3.8.0" @@ -7511,17 +7446,38 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.52: - version "1.1.52" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" - integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== +node-releases@^1.1.58: + version "1.1.58" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" + integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== + +node-sass@^4.13.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== dependencies: - semver "^6.3.0" + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^3.0.0" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + in-publish "^2.0.0" + lodash "^4.17.15" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.13.2" + node-gyp "^3.8.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "2.2.5" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" -node-sass@^4.13.1, node-sass@^4.8.3: - version "4.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== +node-sass@^4.8.3: + version "4.14.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.0.tgz#a8e9d7720f8e15b4a1072719dcf04006f5648eeb" + integrity sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -7673,14 +7629,9 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-is@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -7750,7 +7701,7 @@ object.reduce@^1.0.0: for-own "^1.0.0" make-iterator "^1.0.0" -object.values@^1.1.0: +object.values@^1.1.0, object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== @@ -7760,11 +7711,6 @@ object.values@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -7772,11 +7718,6 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -7803,13 +7744,6 @@ opn@5.3.0: dependencies: is-wsl "^1.1.0" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -7838,13 +7772,6 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -7869,15 +7796,6 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-locale@^3.0.0, os-locale@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -7901,11 +7819,6 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-event@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" @@ -7930,11 +7843,6 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -7943,9 +7851,9 @@ p-limit@^1.1.0: p-try "^1.0.0" p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" @@ -7977,11 +7885,6 @@ p-map-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - p-map@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" @@ -7999,13 +7902,6 @@ p-reduce@^1.0.0: resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" @@ -8030,14 +7926,14 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -page@^1.11.5: - version "1.11.5" - resolved "https://registry.yarnpkg.com/page/-/page-1.11.5.tgz#0cfc8608be337f26f4377f31df0787aef0ca1af7" - integrity sha512-0JXUHc7Y8p1cPJQbhZSwaKO3p+bU3Rgny+OM5gJMKHWHvJKan/fsE5RUzEjRQolv9DzPOSVWfSOHz0lLxK19eA== +page@^1.11.6: + version "1.11.6" + resolved "https://registry.yarnpkg.com/page/-/page-1.11.6.tgz#5ef4efc7073749b8085ccdaa0dcd7c9e0de12fe3" + integrity sha512-P6e2JfzkBrPeFCIPplLP7vDDiU84RUUZMrWdsH4ZBGJ8OosnwFkcUkBHp1DTIjuipLliw9yQn/ZJsXZvarsO+g== dependencies: path-to-regexp "~1.2.1" -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -8058,6 +7954,14 @@ param-case@2.1.x: dependencies: no-case "^2.2.0" +param-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" + integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== + dependencies: + dot-case "^3.0.3" + tslib "^1.10.0" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -8089,6 +7993,18 @@ parse-entities@^1.0.2, parse-entities@^1.1.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -8147,11 +8063,19 @@ parseuri@0.0.5: dependencies: better-assert "~1.0.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.2: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -8189,11 +8113,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -8216,11 +8135,6 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - path-to-regexp@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.2.1.tgz#b33705c140234d873c8721c7b9fd8b541ed3aff9" @@ -8237,6 +8151,13 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -8249,6 +8170,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path-webpack@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/path-webpack/-/path-webpack-0.0.3.tgz#ff6dec749eec5a94605c04d5f63fc55607a03a16" + integrity sha1-/23sdJ7sWpRgXATV9j/FVgegOhY= + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -8302,6 +8228,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -8309,13 +8242,6 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -8340,15 +8266,6 @@ plur@^3.0.1: dependencies: irregular-plurals "^2.0.0" -portfinder@^1.0.25: - version "1.0.25" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" - integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - portscanner@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.1.1.tgz#eabb409e4de24950f5a2a516d35ae769343fbb96" @@ -8566,7 +8483,7 @@ postcss-initial@^3.0.0: lodash.template "^4.5.0" postcss "^7.0.2" -postcss-jsx@^0.36.0, postcss-jsx@^0.36.4: +postcss-jsx@^0.36.0: version "0.36.4" resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.4.tgz#37a68f300a39e5748d547f19a747b3257240bd50" integrity sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA== @@ -8713,7 +8630,7 @@ postcss-modules-local-by-default@^3.0.2: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.0" -postcss-modules-scope@^2.1.1: +postcss-modules-scope@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== @@ -8951,7 +8868,7 @@ postcss-resolve-nested-selector@^0.1.1: resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= -postcss-safe-parser@^4.0.0, postcss-safe-parser@^4.0.1: +postcss-safe-parser@^4.0.0, postcss-safe-parser@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== @@ -8966,20 +8883,20 @@ postcss-sass@^0.3.5: gonzales-pe "^4.2.3" postcss "^7.0.1" -postcss-sass@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.2.tgz#7d1f8ddf6960d329de28fb3ff43c9c42013646bc" - integrity sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ== +postcss-sass@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" + integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== dependencies: - gonzales-pe "^4.2.4" + gonzales-pe "^4.3.0" postcss "^7.0.21" -postcss-scss@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1" - integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug== +postcss-scss@^2.0.0, postcss-scss@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" + integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== dependencies: - postcss "^7.0.0" + postcss "^7.0.6" postcss-selector-matches@^4.0.0: version "4.0.0" @@ -9078,10 +8995,10 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" - integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" @@ -9102,10 +9019,10 @@ postcss@^5.0.0, postcss@^5.0.18: source-map "^0.5.6" supports-color "^3.2.3" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.31, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -9131,7 +9048,7 @@ pretty-bytes@^5.3.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== -pretty-error@^2.0.2: +pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= @@ -9144,11 +9061,6 @@ pretty-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -9174,14 +9086,6 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -9193,9 +9097,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -9259,11 +9163,6 @@ qs@6.2.3: resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" integrity sha1-HPyyXBCpsrSDBT/zn138kjOQjP4= -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -9278,10 +9177,10 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.11.1.tgz#ab021f275d463ce1b61e88f0ce6988b3e8fe7c2c" - integrity sha512-1ZvJOUl8ifkkBxu2ByVM/8GijMIPx+cef7u3yroO3Ogm4DOdZcF5dcrWTIlSHe3Pg/mtlt6/eFjObDfJureZZA== +query-string@^6.13.1: + version "6.13.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" + integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -9297,11 +9196,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -9327,21 +9221,11 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.0, range-parser@~1.2.1: +range-parser@~1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@^2.3.2: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -9367,6 +9251,14 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -9375,7 +9267,7 @@ read-pkg-up@^3.0.0: find-up "^2.0.0" read-pkg "^3.0.0" -read-pkg-up@^7.0.0: +read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== @@ -9393,6 +9285,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -9425,7 +9326,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1: +"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -9492,9 +9393,9 @@ regenerate-unicode-properties@^8.2.0: regenerate "^1.4.0" regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + version "1.4.1" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" + integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== regenerator-runtime@^0.13.4: version "0.13.5" @@ -9502,12 +9403,11 @@ regenerator-runtime@^0.13.4: integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== regenerator-transform@^0.14.2: - version "0.14.4" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" - integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== dependencies: "@babel/runtime" "^7.8.4" - private "^0.1.8" regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -9517,14 +9417,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -9543,9 +9435,9 @@ regexpu-core@^4.7.0: unicode-match-property-value-ecmascript "^1.2.0" regjsgen@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== regjsparser@^0.6.4: version "0.6.4" @@ -9554,7 +9446,7 @@ regjsparser@^0.6.4: dependencies: jsesc "~0.5.0" -relateurl@0.2.x: +relateurl@0.2.x, relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= @@ -9580,6 +9472,28 @@ remark-parse@^6.0.0: vfile-location "^2.0.0" xtend "^4.0.1" +remark-parse@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.2.tgz#5999bc0b9c2e3edc038800a64ff103d0890b318b" + integrity sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + remark-stringify@^6.0.0: version "6.0.4" resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088" @@ -9600,6 +9514,26 @@ remark-stringify@^6.0.0: unherit "^1.0.4" xtend "^4.0.1" +remark-stringify@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.0.0.tgz#33423ab8bf3076fb197f4cf582aaaf866b531625" + integrity sha512-cABVYVloFH+2ZI5bdqzoOmemcz/ZuhQSH6W6ZNYnLojAUUn3xtX7u+6BpnYp35qHoGr2NFBsERV14t4vCIeW8w== + dependencies: + ccount "^1.0.0" + is-alphanumeric "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + longest-streak "^2.0.1" + markdown-escapes "^1.0.0" + markdown-table "^2.0.0" + mdast-util-compact "^2.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + stringify-entities "^3.0.0" + unherit "^1.0.4" + xtend "^4.0.1" + remark@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df" @@ -9609,6 +9543,15 @@ remark@^10.0.1: remark-stringify "^6.0.0" unified "^7.0.0" +remark@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.0.tgz#d1c145c07341c9232f93b2f8539d56da15a2548c" + integrity sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A== + dependencies: + remark-parse "^8.0.0" + remark-stringify "^8.0.0" + unified "^9.0.0" + remove-bom-buffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" @@ -9647,7 +9590,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -9664,11 +9607,16 @@ replace-ext@0.0.1: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= -replace-ext@1.0.0, replace-ext@^1.0.0: +replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + replace-homedir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" @@ -9719,7 +9667,7 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@1.x.x, requires-port@^1.0.0: +requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= @@ -9729,13 +9677,6 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -9771,10 +9712,10 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -9806,11 +9747,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -9856,11 +9792,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" run-async@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-parallel@^1.1.9: version "1.1.9" @@ -9887,9 +9821,9 @@ rxjs@^5.5.6: symbol-observable "1.0.1" rxjs@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== dependencies: tslib "^1.9.0" @@ -9898,7 +9832,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -9915,10 +9849,20 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^13.3.2" + sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= + version "2.2.6" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.6.tgz#09fda0e4287480e3e4967b72a2d133ba09b8d827" + integrity sha512-MKuEYXFSGuRSi8FZ3A7imN1CeVn9Gpw0/SFJKdL1ejXJneI9a5rwlEZrKejhEFAA3O6yr3eIyl/WuvASvlT36g== dependencies: glob "^7.0.0" lodash "^4.0.0" @@ -9930,14 +9874,6 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -schema-utils@^0.4.5: - version "0.4.7" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" - integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== - dependencies: - ajv "^6.1.0" - ajv-keywords "^3.1.0" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -9947,14 +9883,20 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.4, schema-utils@^2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ== +schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== dependencies: - ajv "^6.12.0" + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" ajv-keywords "^3.4.1" +screenfull@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7" + integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ== + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -9970,18 +9912,6 @@ seek-bzip@^1.0.5: dependencies: commander "~2.8.1" -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== - dependencies: - node-forge "0.9.0" - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -10011,6 +9941,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -10040,31 +9975,12 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== -serve-index@1.9.1, serve-index@^1.9.1: +serve-index@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= @@ -10087,16 +10003,6 @@ serve-static@1.13.2: parseurl "~1.3.2" send "0.16.2" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - server-destroy@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" @@ -10107,6 +10013,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -10118,9 +10029,9 @@ set-value@^2.0.0, set-value@^2.0.1: split-string "^3.0.1" set-value@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.1.tgz#52c82af7653ba69eb1db92e81f5cdb32739b9e95" - integrity sha512-w6n3GUPYAWQj4ZyHWzD7K2FnFXHx9OTwJYbWg+6nXjG8sCLfs9DGv+KlqglKIIJx+ks7MlFuwFW2RBPb+8V+xg== + version "3.0.2" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" + integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== dependencies: is-plain-object "^2.0.4" @@ -10147,12 +10058,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shaka-player@^2.5.9: - version "2.5.9" - resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.9.tgz#007dc19df2bb5d3d959d278b2d894af05adffe38" - integrity sha512-XavLBqxvIbvLOPfk7VKZu5fbMJyVko9bBfzxmMWdX5bvQwUSjU7ZhV8v2tHqXQYafpHml1hlGHzKkLs7idouNQ== +shaka-player@^2.5.13: + version "2.5.13" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.13.tgz#f8c493b825c735fc86d619cba8b2eb2f2a382233" + integrity sha512-rEh7juGlTvvF10oD7+EukS12EysZXI2fiGvNLqO7GsBQ5R/sFwcTGEB8A6lWlHQXeGVbT+MxZWKMZwFE805G6A== dependencies: - eme-encryption-scheme-polyfill "^2.0.0" + eme-encryption-scheme-polyfill "^2.0.1" shebang-command@^1.2.0: version "1.2.0" @@ -10167,9 +10078,9 @@ shebang-regex@^1.0.0: integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== simple-swizzle@^0.2.2: version "0.2.2" @@ -10307,26 +10218,6 @@ socket.io@2.1.1: socket.io-client "2.1.1" socket.io-parser "~3.2.0" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.0.1" - sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -10370,9 +10261,9 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: urix "^0.1.0" source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -10405,22 +10296,22 @@ sparkles@^1.0.0: integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" @@ -10430,29 +10321,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2" - integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - specificity@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" @@ -10504,10 +10372,10 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssr-window@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ssr-window/-/ssr-window-1.0.1.tgz#30752a6a4666e7767f0b7e6aa6fc2fdbd0d9b369" - integrity sha512-dgFqB+f00LJTEgb6UXhx0h+SrG50LJvti2yMKMqAgzfUmUXZrLSv2fjULF7AWGwK25EXu8+smLR3jYsJQChPsg== +ssr-window@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ssr-window/-/ssr-window-2.0.0.tgz#98c301aef99523317f8d69618f0010791096efc4" + integrity sha512-NXzN+/HPObKAx191H3zKlYomE5WrVIkoCB5IaSdvKokxTpjBdWfr0RaP+1Z5KOfDT0ZVz+2tdtiBkhsEQ9p+0A== ssri@^6.0.1: version "6.0.1" @@ -10539,7 +10407,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -10640,7 +10508,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -10666,21 +10534,39 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.trimend@^1.0.0, string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0, string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -10711,6 +10597,17 @@ stringify-entities@^1.0.1: is-alphanumerical "^1.0.0" is-hexadecimal "^1.0.0" +stringify-entities@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.0.tgz#455abe501f8b7859ba5726a25a8872333c65b0a7" + integrity sha512-h7NJJIssprqlyjHT2eQt2W1F+MCcNmwPGlKb0bWEdET/3N44QN3QbUF/ueKCgAssyKRZ3Br9rQ7FcXjHr0qLHw== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.2" + is-hexadecimal "^1.0.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -10788,9 +10685,9 @@ strip-indent@^3.0.0: min-indent "^1.0.0" strip-json-comments@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== strip-outer@^1.0.0: version "1.0.1" @@ -10800,12 +10697,12 @@ strip-outer@^1.0.0: escape-string-regexp "^1.0.2" style-loader@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.1.3.tgz#9e826e69c683c4d9bf9db924f85e9abb30d5e200" - integrity sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw== + version "1.2.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" + integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== dependencies: - loader-utils "^1.2.3" - schema-utils "^2.6.4" + loader-utils "^2.0.0" + schema-utils "^2.6.6" style-search@^0.1.0: version "0.1.0" @@ -10863,57 +10760,57 @@ stylelint-order@^2.2.1: postcss "^7.0.2" postcss-sorting "^4.1.0" -stylelint-order@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-4.0.0.tgz#2a945c2198caac3ff44687d7c8582c81d044b556" - integrity sha512-bXV0v+jfB0+JKsqIn3mLglg1Dj2QCYkFHNfL1c+rVMEmruZmW5LUqT/ARBERfBm8SFtCuXpEdatidw/3IkcoiA== +stylelint-order@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-4.1.0.tgz#692d05b7d0c235ac66fcf5ea1d9e5f08a76747f6" + integrity sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw== dependencies: lodash "^4.17.15" - postcss "^7.0.26" + postcss "^7.0.31" postcss-sorting "^5.0.1" -stylelint@^13.1.0: - version "13.2.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.2.1.tgz#9101fcd70791856530049816ff53d980ecd561df" - integrity sha512-461ZV4KpUe7pEHHgMOsH4kkjF7qsjkCIMJYOf7QQC4cvgPUJ0z4Nj+ah5fvKl1rzqBqc5EZa6P0nna4CGoJX+A== +stylelint@^13.6.1: + version "13.6.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.6.1.tgz#cc1d76338116d55e8ff2be94c4a4386c1239b878" + integrity sha512-XyvKyNE7eyrqkuZ85Citd/Uv3ljGiuYHC6UiztTR6sWS9rza8j3UeQv/eGcQS9NZz/imiC4GKdk1EVL3wst5vw== dependencies: - autoprefixer "^9.7.4" + "@stylelint/postcss-css-in-js" "^0.37.1" + "@stylelint/postcss-markdown" "^0.36.1" + autoprefixer "^9.8.0" balanced-match "^1.0.0" - chalk "^3.0.0" + chalk "^4.1.0" cosmiconfig "^6.0.0" debug "^4.1.1" execall "^2.0.0" file-entry-cache "^5.0.1" - get-stdin "^7.0.0" + get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.0" + globby "^11.0.1" globjoin "^0.1.4" html-tags "^3.1.0" - ignore "^5.1.4" + ignore "^5.1.8" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.18.0" + known-css-properties "^0.19.0" leven "^3.1.0" lodash "^4.17.15" - log-symbols "^3.0.0" + log-symbols "^4.0.0" mathml-tag-names "^2.1.3" - meow "^6.0.1" + meow "^7.0.1" micromatch "^4.0.2" normalize-selector "^0.2.0" - postcss "^7.0.27" + postcss "^7.0.32" postcss-html "^0.36.0" - postcss-jsx "^0.36.4" postcss-less "^3.1.4" - postcss-markdown "^0.36.0" postcss-media-query-parser "^0.2.3" postcss-reporter "^6.0.1" postcss-resolve-nested-selector "^0.1.1" - postcss-safe-parser "^4.0.1" - postcss-sass "^0.4.2" - postcss-scss "^2.0.0" + postcss-safe-parser "^4.0.2" + postcss-sass "^0.4.4" + postcss-scss "^2.1.1" postcss-selector-parser "^6.0.2" postcss-syntax "^0.36.2" - postcss-value-parser "^4.0.3" + postcss-value-parser "^4.1.0" resolve-from "^5.0.0" slash "^3.0.0" specificity "^0.4.1" @@ -10923,7 +10820,7 @@ stylelint@^13.1.0: sugarss "^2.0.0" svg-tags "^1.0.0" table "^5.4.6" - v8-compile-cache "^2.1.0" + v8-compile-cache "^2.1.1" write-file-atomic "^3.0.3" stylelint@^9.1, stylelint@^9.10.1: @@ -10986,13 +10883,6 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -supports-color@6.1.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -11012,6 +10902,13 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -11051,13 +10948,13 @@ svgo@^1.0.0, svgo@^1.3.2: unquote "~1.1.1" util.promisify "~1.0.0" -swiper@^5.3.1: - version "5.3.6" - resolved "https://registry.yarnpkg.com/swiper/-/swiper-5.3.6.tgz#102b7f8145d734ec4c30e04602160382356b5948" - integrity sha512-FUz50g6RuvGAuXQWmR5lRPoA129leRUZ/p57ckr8+P5kR7VktElVQ47JGmWD86mOJCFfvMhUf0hinyC5UFL5iw== +swiper@^5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/swiper/-/swiper-5.4.5.tgz#a350f654bf68426dbb651793824925512d223c0f" + integrity sha512-7QjA0XpdOmiMoClfaZ2lYN6ICHcMm72LXiY+NF4fQLFidigameaofvpjEEiTQuw3xm5eksG5hzkaRsjQX57vtA== dependencies: - dom7 "^2.1.3" - ssr-window "^1.0.1" + dom7 "^2.1.5" + ssr-window "^2.0.0" symbol-observable@1.0.1: version "1.0.1" @@ -11139,10 +11036,10 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@^4.0.0, terser@^4.1.2: - version "4.6.7" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" - integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== +terser@^4.0.0, terser@^4.1.2, terser@^4.6.3: + version "4.6.13" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.13.tgz#e879a7364a5e0db52ba4891ecde007422c56a916" + integrity sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -11196,11 +11093,6 @@ through@^2.3.6, through@^2.3.8, through@~2.3.4: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -11310,11 +11202,6 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -toposort@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" - integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -11367,7 +11254,17 @@ trough@^1.0.0: dependencies: glob "^7.1.2" -tslib@^1.9.0: +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.10.0, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== @@ -11401,6 +11298,11 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -11411,14 +11313,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" @@ -11446,14 +11340,6 @@ ua-parser-js@0.7.17: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" integrity sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g== -uglify-es@^3.3.9: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" - uglify-js@3.4.x: version "3.4.10" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" @@ -11468,9 +11354,9 @@ ultron@~1.1.0: integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== unbzip2-stream@^1.0.9: - version "1.3.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a" - integrity sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg== + version "1.4.2" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" + integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== dependencies: buffer "^5.2.1" through "^2.3.8" @@ -11545,6 +11431,18 @@ unified@^7.0.0: vfile "^3.0.0" x-is-string "^0.1.0" +unified@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.0.0.tgz#12b099f97ee8b36792dbad13d278ee2f696eed1d" + integrity sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -11602,11 +11500,23 @@ unist-util-find-all-after@^1.0.2: dependencies: unist-util-is "^3.0.0" +unist-util-find-all-after@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz#95cc62f48812d879b4685a0512bf1b838da50e9a" + integrity sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw== + dependencies: + unist-util-is "^4.0.0" + unist-util-is@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== +unist-util-is@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" + integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== + unist-util-remove-position@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020" @@ -11614,6 +11524,13 @@ unist-util-remove-position@^1.0.0: dependencies: unist-util-visit "^1.1.0" +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" @@ -11633,6 +11550,14 @@ unist-util-visit-parents@^2.0.0: dependencies: unist-util-is "^3.0.0" +unist-util-visit-parents@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz#d4076af3011739c71d2ce99d05de37d545f4351d" + integrity sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" @@ -11640,6 +11565,15 @@ unist-util-visit@^1.1.0: dependencies: unist-util-visit-parents "^2.0.0" +unist-util-visit@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.2.tgz#3843782a517de3d2357b4c193b24af2d9366afb7" + integrity sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -11663,7 +11597,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.0, upath@^1.1.1: +upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== @@ -11699,14 +11633,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -11782,20 +11708,15 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== - -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" - integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== +v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8flags@^3.0.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" - integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== +v8flags@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== dependencies: homedir-polyfill "^1.0.1" @@ -11812,11 +11733,6 @@ value-or-function@^3.0.0: resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" @@ -11836,6 +11752,11 @@ vfile-location@^2.0.0: resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== +vfile-location@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.0.1.tgz#d78677c3546de0f7cd977544c367266764d31bb3" + integrity sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ== + vfile-message@*: version "2.0.3" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.3.tgz#0dd4f6879fb240a8099b22bd3755536c92e59ba5" @@ -11851,6 +11772,14 @@ vfile-message@^1.0.0: dependencies: unist-util-stringify-position "^1.1.1" +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803" @@ -11861,6 +11790,17 @@ vfile@^3.0.0: unist-util-stringify-position "^1.0.0" vfile-message "^1.0.0" +vfile@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.1.0.tgz#d79248957f43225d57ff67a56effc67bef08946e" + integrity sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + vinyl-fs@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" @@ -11930,105 +11870,20 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -watchpack@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== +watchpack@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" + integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA== dependencies: - chokidar "^2.0.2" + chokidar "^2.1.8" graceful-fs "^4.1.2" neo-async "^2.5.0" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - webcomponents.js@^0.7.24: version "0.7.24" resolved "https://registry.yarnpkg.com/webcomponents.js/-/webcomponents.js-0.7.24.tgz#2116fbfa1468ec416a7befdaa333e1d118f69c04" integrity sha1-IRb7+hRo7EFqe+/aozPh0Rj2nAQ= -webpack-cli@^3.3.10: - version "3.3.11" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" - integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== - dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" - -webpack-concat-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/webpack-concat-plugin/-/webpack-concat-plugin-3.0.0.tgz#db34ae230794b634061bc2944053ed407619f138" - integrity sha512-DLdDbZXyrFR99wyAVC9P06HLjr2XujBmQdSbnQMK2o01H9U2NHsN5W76jeTVeXDq5OLvZf8r/se65ftRo3Prow== - dependencies: - concat-with-sourcemaps "^1.0.5" - globby "^8.0.1" - schema-utils "^0.4.5" - uglify-es "^3.3.9" - upath "^1.1.0" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.10.3: - version "3.10.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" - integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.2.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.6" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.25" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.19" - sockjs-client "1.4.0" - spdy "^4.0.1" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "12.0.5" - webpack-log@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" @@ -12068,15 +11923,15 @@ webpack-stream@^5.2.1: webpack "^4.26.1" webpack@^4.26.1, webpack@^4.41.5: - version "4.42.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.1.tgz#ae707baf091f5ca3ef9c38b884287cfe8f1983ef" - integrity sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg== + version "4.43.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" + integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" "@webassemblyjs/wasm-edit" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.2.1" + acorn "^6.4.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" @@ -12093,32 +11948,18 @@ webpack@^4.26.1, webpack@^4.41.5: schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" + watchpack "^1.6.1" webpack-sources "^1.4.1" -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== - dependencies: - http-parser-js ">=0.4.0 <0.4.11" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== - webworkify@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g== -whatwg-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== +whatwg-fetch@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" + integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== which-module@^1.0.0: version "1.0.0" @@ -12205,13 +12046,6 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -12233,6 +12067,11 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= +xmldom@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a" + integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== + xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" @@ -12248,7 +12087,7 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== @@ -12264,11 +12103,11 @@ yallist@^3.0.2: integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yaml@^1.7.2: - version "1.8.3" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.8.3.tgz#2f420fca58b68ce3a332d0ca64be1d191dd3f87a" - integrity sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw== + version "1.9.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" + integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== dependencies: - "@babel/runtime" "^7.8.7" + "@babel/runtime" "^7.9.2" yargs-parser@^10.0.0: version "10.1.0" @@ -12277,15 +12116,7 @@ yargs-parser@^10.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^13.1.0: +yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -12293,10 +12124,10 @@ yargs-parser@^13.1.0: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1: - version "18.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.1.tgz#bf7407b915427fc760fcbbccc6c82b4f0ffcbd37" - integrity sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA== +yargs-parser@^18.1.3: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -12315,41 +12146,6 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@12.0.5: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.4.0.tgz#816e1a866d5598ccf34e5596ddce22d92da490d4" @@ -12389,6 +12185,22 @@ yargs@6.6.0: y18n "^3.2.1" yargs-parser "^4.2.0" +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + yargs@^7.0.0, yargs@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"

${ApiKeysCaption}
${HeaderApiKey}${HeaderApp}${HeaderDateIssued}${HeaderApiKey}${HeaderApp}${HeaderDateIssued}
'; - html += '"; - html += "'; - html += item.AccessToken; - html += "'; - html += item.AppName || ""; - html += "'; - var date = datetime.parseISO8601Date(item.DateCreated, true); - html += datetime.toLocaleDateString(date) + " " + datetime.getDisplayTime(date); - html += "
'; + html += ''; + html += ''; + html += item.AccessToken; + html += ''; + html += item.AppName || ''; + html += ''; + var date = datetime.parseISO8601Date(item.DateCreated, true); + html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); + html += '