mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge remote-tracking branch 'upstream/master' into fix-logo-size
This commit is contained in:
commit
d2cd47d3e3
194 changed files with 5770 additions and 4213 deletions
|
@ -2,8 +2,7 @@ trigger:
|
|||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- release-*
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
@ -13,65 +12,94 @@ pr:
|
|||
- '*'
|
||||
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'Build'
|
||||
- job: Build
|
||||
displayName: 'Build'
|
||||
|
||||
dependsOn: lint
|
||||
condition: succeeded()
|
||||
strategy:
|
||||
matrix:
|
||||
Development:
|
||||
BuildConfiguration: development
|
||||
Production:
|
||||
BuildConfiguration: production
|
||||
Standalone:
|
||||
BuildConfiguration: standalone
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
bundle:
|
||||
BuildConfiguration: Bundle
|
||||
standalone:
|
||||
BuildConfiguration: Standalone
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Install Node'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
|
||||
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'
|
||||
displayName: 'Install Dependencies'
|
||||
- script: 'yarn install --frozen-lockfile'
|
||||
displayName: 'Install Dependencies'
|
||||
condition: ne(variables.CACHE_RESTORED, 'true')
|
||||
|
||||
- script: 'yarn build'
|
||||
displayName: 'Build'
|
||||
- script: 'yarn build:development'
|
||||
displayName: 'Build Development'
|
||||
condition: eq(variables['BuildConfiguration'], 'development')
|
||||
|
||||
- script: 'test -d dist'
|
||||
displayName: 'Check Build'
|
||||
- script: 'yarn build:production'
|
||||
displayName: 'Build Bundle'
|
||||
condition: eq(variables['BuildConfiguration'], 'production')
|
||||
|
||||
- script: 'yarn pack --filename jellyfin-web.tgz'
|
||||
displayName: 'Bundle Release'
|
||||
- script: 'yarn build:standalone'
|
||||
displayName: 'Build Standalone'
|
||||
condition: eq(variables['BuildConfiguration'], 'standalone')
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Release'
|
||||
condition: succeeded()
|
||||
inputs:
|
||||
targetPath: '$(Build.SourcesDirectory)/jellyfin-web.tgz'
|
||||
artifactName: 'jellyfin-web'
|
||||
- script: 'test -d dist'
|
||||
displayName: 'Check Build'
|
||||
|
||||
- job: lint
|
||||
displayName: 'Lint'
|
||||
- script: 'mv dist jellyfin-web'
|
||||
displayName: 'Rename Directory'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Archive Directory'
|
||||
inputs:
|
||||
rootFolderOrFile: 'jellyfin-web'
|
||||
includeRootFolder: true
|
||||
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Install Node'
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Release'
|
||||
inputs:
|
||||
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
|
||||
artifactName: 'jellyfin-web-$(BuildConfiguration)'
|
||||
|
||||
- script: 'yarn install'
|
||||
displayName: 'Install Dependencies'
|
||||
- job: Lint
|
||||
displayName: 'Lint'
|
||||
|
||||
- script: 'yarn run lint'
|
||||
displayName: 'Run ESLint'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
- script: 'yarn run stylelint'
|
||||
displayName: 'Run Stylelint'
|
||||
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'
|
||||
|
|
128
.eslintrc.yml
128
.eslintrc.yml
|
@ -1,15 +1,32 @@
|
|||
env:
|
||||
es6: true
|
||||
browser: true
|
||||
amd: true
|
||||
browser: true
|
||||
es6: true
|
||||
es2017: true
|
||||
es2020: true
|
||||
|
||||
parserOptions:
|
||||
ecmaVersion: 2020
|
||||
sourceType: module
|
||||
ecmaFeatures:
|
||||
impliedStrict: true
|
||||
|
||||
plugins:
|
||||
- promise
|
||||
- import
|
||||
- eslint-comments
|
||||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:promise/recommended
|
||||
- plugin:import/errors
|
||||
- plugin:import/warnings
|
||||
- plugin:eslint-comments/recommended
|
||||
- plugin:compat/recommended
|
||||
|
||||
globals:
|
||||
# New browser globals
|
||||
DataView: readonly
|
||||
# Browser globals
|
||||
MediaMetadata: readonly
|
||||
Promise: readonly
|
||||
# Deprecated browser globals
|
||||
DocumentTouch: readonly
|
||||
# Tizen globals
|
||||
tizen: readonly
|
||||
webapis: readonly
|
||||
|
@ -18,7 +35,6 @@ globals:
|
|||
# Dependency globals
|
||||
$: readonly
|
||||
jQuery: readonly
|
||||
queryString: readonly
|
||||
requirejs: readonly
|
||||
# Jellyfin globals
|
||||
ApiClient: writable
|
||||
|
@ -34,8 +50,7 @@ globals:
|
|||
getWindowLocationSearch: writable
|
||||
Globalize: writable
|
||||
Hls: writable
|
||||
humaneDate: writable
|
||||
humaneElapsed: writable
|
||||
dfnshelper: writable
|
||||
LibraryMenu: writable
|
||||
LinkParser: writable
|
||||
LiveTvHelpers: writable
|
||||
|
@ -46,9 +61,6 @@ globals:
|
|||
UserParentalControlPage: writable
|
||||
Windows: readonly
|
||||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
|
||||
rules:
|
||||
block-spacing: ["error"]
|
||||
brace-style: ["error"]
|
||||
|
@ -63,9 +75,97 @@ rules:
|
|||
no-multiple-empty-lines: ["error", { "max": 1 }]
|
||||
no-trailing-spaces: ["error"]
|
||||
one-var: ["error", "never"]
|
||||
semi: ["warn"]
|
||||
semi: ["error"]
|
||||
space-before-blocks: ["error"]
|
||||
# TODO: Fix warnings and remove these rules
|
||||
no-redeclare: ["warn"]
|
||||
no-unused-vars: ["warn"]
|
||||
no-useless-escape: ["warn"]
|
||||
promise/catch-or-return: ["warn"]
|
||||
promise/always-return: ["warn"]
|
||||
promise/no-return-wrap: ["warn"]
|
||||
# TODO: Remove after ES6 migration is complete
|
||||
import/no-unresolved: ["warn"]
|
||||
|
||||
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.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
|
||||
|
|
584
.gitignore
vendored
584
.gitignore
vendored
|
@ -1,578 +1,10 @@
|
|||
# config
|
||||
config.json
|
||||
|
||||
# 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
|
||||
|
||||
# dist for webpack output
|
||||
# npm
|
||||
dist
|
||||
node_modules
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.vscode
|
||||
|
|
21
README.md
21
README.md
|
@ -50,25 +50,32 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
|||
### Getting Started
|
||||
|
||||
1. Clone or download this repository.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jellyfin/jellyfin-web.git
|
||||
cd jellyfin-web
|
||||
```
|
||||
|
||||
2. Install build dependencies in the project directory.
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. Run the web client with webpack for local development.
|
||||
|
||||
```sh
|
||||
yarn serve
|
||||
```
|
||||
|
||||
4. Build the client with sourcemaps.
|
||||
'''sh
|
||||
yarn
|
||||
'''
|
||||
Or without sourcemaps
|
||||
'''sh
|
||||
yarn --production
|
||||
'''
|
||||
|
||||
```sh
|
||||
yarn build:development
|
||||
```
|
||||
|
||||
You can build a nginx compatible version as well.
|
||||
|
||||
```sh
|
||||
yarn build:standalone
|
||||
```
|
3
babel.config.json
Normal file
3
babel.config.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
160
gulpfile.js
160
gulpfile.js
|
@ -10,7 +10,7 @@ const htmlmin = require('gulp-htmlmin');
|
|||
const imagemin = require('gulp-imagemin');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const mode = require('gulp-mode')({
|
||||
modes: ["development", "bundle", "standalone"],
|
||||
modes: ["development", "production"],
|
||||
default: "development",
|
||||
verbose: false
|
||||
});
|
||||
|
@ -18,16 +18,42 @@ const stream = require('webpack-stream');
|
|||
const inject = require('gulp-inject');
|
||||
const postcss = require('gulp-postcss');
|
||||
const sass = require('gulp-sass');
|
||||
const gulpif = require('gulp-if');
|
||||
const lazypipe = require('lazypipe');
|
||||
|
||||
sass.compiler = require('node-sass')
|
||||
sass.compiler = require('node-sass');
|
||||
|
||||
|
||||
if (mode.bundle() || mode.standalone()) {
|
||||
var config = require('./webpack.prod.js');
|
||||
let config;
|
||||
if (mode.production()) {
|
||||
config = require('./webpack.prod.js');
|
||||
} else {
|
||||
var config = require('./webpack.dev.js');
|
||||
config = require('./webpack.dev.js');
|
||||
}
|
||||
|
||||
const options = {
|
||||
javascript: {
|
||||
query: ['src/**/*.js', '!src/bundle.js', '!src/standalone.js', '!src/scripts/apploader.js']
|
||||
},
|
||||
apploader: {
|
||||
query: ['src/standalone.js', 'src/scripts/apploader.js']
|
||||
},
|
||||
css: {
|
||||
query: ['src/**/*.css', 'src/**/*.scss']
|
||||
},
|
||||
html: {
|
||||
query: ['src/**/*.html', '!src/index.html']
|
||||
},
|
||||
images: {
|
||||
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
|
||||
},
|
||||
copy: {
|
||||
query: ['src/**/*.json', 'src/**/*.ico']
|
||||
},
|
||||
injectBundle: {
|
||||
query: 'src/index.html'
|
||||
}
|
||||
};
|
||||
|
||||
function serve() {
|
||||
browserSync.init({
|
||||
server: {
|
||||
|
@ -36,51 +62,99 @@ function serve() {
|
|||
port: 8080
|
||||
});
|
||||
|
||||
watch(['src/**/*.js', '!src/bundle.js'], javascript);
|
||||
watch('src/bundle.js', webpack);
|
||||
watch('src/**/*.css', css);
|
||||
watch(['src/**/*.html', '!src/index.html'], html);
|
||||
watch(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], images);
|
||||
watch(['src/**/*.json', 'src/**/*.ico'], copy);
|
||||
watch('src/index.html', injectBundle);
|
||||
watch(['src/standalone.js', 'src/scripts/apploader.js'], standalone);
|
||||
}
|
||||
const events = ['add', 'change'];
|
||||
|
||||
function standalone() {
|
||||
return src(['src/standalone.js', 'src/scripts/apploader.js'], { base: './src/' })
|
||||
.pipe(concat('scripts/apploader.js'))
|
||||
.pipe(dest('dist/'));
|
||||
watch(options.javascript.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
javascript(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.apploader.query, apploader(true));
|
||||
|
||||
watch('src/bundle.js', webpack);
|
||||
|
||||
watch(options.css.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
css(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.html.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
html(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.images.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
images(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.copy.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
copy(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.injectBundle.query, injectBundle);
|
||||
}
|
||||
|
||||
function clean() {
|
||||
return del(['dist/']);
|
||||
}
|
||||
|
||||
function javascript() {
|
||||
return src(['src/**/*.js', '!src/bundle.js'], { base: './src/' })
|
||||
.pipe(mode.development(sourcemaps.init({ loadMaps: true })))
|
||||
.pipe(babel({
|
||||
const pipelineJavascript = lazypipe()
|
||||
.pipe(function () {
|
||||
return mode.development(sourcemaps.init({ loadMaps: true }));
|
||||
})
|
||||
.pipe(function () {
|
||||
return babel({
|
||||
presets: [
|
||||
['@babel/preset-env']
|
||||
]
|
||||
}))
|
||||
.pipe(terser({
|
||||
});
|
||||
})
|
||||
.pipe(function () {
|
||||
return terser({
|
||||
keep_fnames: true,
|
||||
mangle: false
|
||||
}))
|
||||
.pipe(mode.development(sourcemaps.write('.')))
|
||||
});
|
||||
})
|
||||
.pipe(function () {
|
||||
return mode.development(sourcemaps.write('.'));
|
||||
});
|
||||
|
||||
function javascript(query) {
|
||||
return src(typeof query !== 'function' ? query : options.javascript.query, { base: './src/' })
|
||||
.pipe(pipelineJavascript())
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function apploader(standalone) {
|
||||
function task() {
|
||||
return src(options.apploader.query, { base: './src/' })
|
||||
.pipe(gulpif(standalone, concat('scripts/apploader.js')))
|
||||
.pipe(pipelineJavascript())
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
task.displayName = 'apploader';
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
function webpack() {
|
||||
return stream(config)
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function css() {
|
||||
return src(['src/**/*.css', 'src/**/*.scss'], { base: './src/' })
|
||||
function css(query) {
|
||||
return src(typeof query !== 'function' ? query : options.css.query, { base: './src/' })
|
||||
.pipe(mode.development(sourcemaps.init({ loadMaps: true })))
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(postcss())
|
||||
|
@ -89,29 +163,28 @@ function css() {
|
|||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function html() {
|
||||
return src(['src/**/*.html', '!src/index.html'], { base: './src/' })
|
||||
.pipe(mode.bundle(htmlmin({ collapseWhitespace: true })))
|
||||
.pipe(mode.standalone(htmlmin({ collapseWhitespace: true })))
|
||||
function html(query) {
|
||||
return src(typeof query !== 'function' ? query : options.html.query, { base: './src/' })
|
||||
.pipe(mode.production(htmlmin({ collapseWhitespace: true })))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function images() {
|
||||
return src(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], { base: './src/' })
|
||||
.pipe(imagemin())
|
||||
function images(query) {
|
||||
return src(typeof query !== 'function' ? query : options.images.query, { base: './src/' })
|
||||
.pipe(mode.production(imagemin()))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function copy() {
|
||||
return src(['src/**/*.json', 'src/**/*.ico'], { base: './src/' })
|
||||
function copy(query) {
|
||||
return src(typeof query !== 'function' ? query : options.copy.query, { base: './src/' })
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function injectBundle() {
|
||||
return src('src/index.html', { base: './src/' })
|
||||
return src(options.injectBundle.query, { base: './src/' })
|
||||
.pipe(inject(
|
||||
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
|
||||
))
|
||||
|
@ -119,5 +192,10 @@ function injectBundle() {
|
|||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle)
|
||||
exports.serve = series(exports.default, standalone, serve)
|
||||
function build(standalone) {
|
||||
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy), injectBundle);
|
||||
}
|
||||
|
||||
exports.default = build(false);
|
||||
exports.standalone = build(true);
|
||||
exports.serve = series(exports.standalone, serve);
|
||||
|
|
53
package.json
53
package.json
|
@ -6,6 +6,7 @@
|
|||
"license": "GPL-2.0-or-later",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
"@babel/plugin-transform-modules-amd": "^7.8.3",
|
||||
"@babel/polyfill": "^7.8.7",
|
||||
"@babel/preset-env": "^7.8.6",
|
||||
"autoprefixer": "^9.7.4",
|
||||
|
@ -17,12 +18,17 @@
|
|||
"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.1.2",
|
||||
"eslint-plugin-import": "^2.20.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-concat": "^2.6.1",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-inject": "^5.0.5",
|
||||
"gulp-mode": "^1.0.2",
|
||||
|
@ -30,7 +36,8 @@
|
|||
"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.0.2",
|
||||
"lazypipe": "^1.0.2",
|
||||
"node-sass": "^4.13.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
|
@ -48,21 +55,25 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"alameda": "^1.4.0",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"core-js": "^3.6.4",
|
||||
"date-fns": "^2.11.1",
|
||||
"document-register-element": "^1.14.3",
|
||||
"fast-text-encoding": "^1.0.1",
|
||||
"flv.js": "^1.5.0",
|
||||
"hls.js": "^0.13.1",
|
||||
"howler": "^2.1.3",
|
||||
"intersection-observer": "^0.7.0",
|
||||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||
"jquery": "^3.4.1",
|
||||
"jstree": "^3.3.7",
|
||||
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus",
|
||||
"libjass": "^0.11.0",
|
||||
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"native-promise-only": "^0.8.0-a",
|
||||
"page": "^1.11.5",
|
||||
"query-string": "^6.11.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"shaka-player": "^2.5.9",
|
||||
"shaka-player": "^2.5.10",
|
||||
"sortablejs": "^1.10.2",
|
||||
"swiper": "^5.3.1",
|
||||
"webcomponents.js": "^0.7.24",
|
||||
|
@ -71,6 +82,29 @@
|
|||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"test": [
|
||||
"src/components/autoFocuser.js",
|
||||
"src/components/cardbuilder/cardBuilder.js",
|
||||
"src/components/dom.js",
|
||||
"src/components/filedownloader.js",
|
||||
"src/components/filesystem.js",
|
||||
"src/components/input/keyboardnavigation.js",
|
||||
"src/components/sanatizefilename.js",
|
||||
"src/components/scrollManager.js",
|
||||
"src/scripts/settings/appSettings.js",
|
||||
"src/scripts/settings/userSettings.js",
|
||||
"src/scripts/settings/webSettings.js",
|
||||
"src/scripts/dfnshelper.js",
|
||||
"src/scripts/imagehelper.js",
|
||||
"src/scripts/inputManager.js"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-modules-amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
|
@ -89,10 +123,11 @@
|
|||
"Firefox ESR"
|
||||
],
|
||||
"scripts": {
|
||||
"serve": "gulp serve",
|
||||
"build": "gulp --bundle",
|
||||
"build standalone": "gulp --standalone",
|
||||
"build development": "gulp",
|
||||
"serve": "gulp serve --development",
|
||||
"prepare": "gulp --production",
|
||||
"build:development": "gulp --development",
|
||||
"build:production": "gulp --production",
|
||||
"build:standalone": "gulp standalone --development",
|
||||
"lint": "eslint \"src\"",
|
||||
"stylelint": "stylelint \"src/**/*.css\""
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ progress[aria-valuenow]::before {
|
|||
}
|
||||
|
||||
.adminDrawerLogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-mobile .adminDrawerLogo {
|
||||
padding: 1.5em 1em 1.2em;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
margin-bottom: 1em;
|
||||
|
@ -161,7 +165,7 @@ div[data-role=controlgroup] a.ui-btn-active {
|
|||
|
||||
@media all and (min-width: 40em) {
|
||||
.content-primary {
|
||||
padding-top: 7em;
|
||||
padding-top: 4.6em;
|
||||
}
|
||||
|
||||
.withTabs .content-primary {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
.libraryPage {
|
||||
padding-top: 7em !important;
|
||||
padding-top: 7em;
|
||||
}
|
||||
|
||||
.itemDetailPage {
|
||||
|
@ -115,7 +115,7 @@
|
|||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: inline-flex;
|
||||
margin: 0.3em 0 0 0.5em;
|
||||
margin: 0 0 0 0.5em;
|
||||
height: 1.7em;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
|
@ -128,6 +128,10 @@
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.layout-mobile .pageTitleWithDefaultLogo {
|
||||
background-image: url(../img/icon-transparent.png);
|
||||
}
|
||||
|
||||
.headerLeft,
|
||||
.skinHeader {
|
||||
display: -webkit-box;
|
||||
|
@ -238,11 +242,10 @@
|
|||
}
|
||||
|
||||
.mainDrawer-scrollContainer {
|
||||
padding-bottom: 10vh;
|
||||
margin-bottom: 10vh;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
.dashboardDocument .adminDrawerLogo,
|
||||
.dashboardDocument .mainDrawerButton {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -268,12 +271,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 60em) {
|
||||
.libraryDocument .mainDrawerButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 84em) {
|
||||
.withSectionTabs .headerTop {
|
||||
padding-bottom: 0.55em;
|
||||
|
@ -316,7 +313,7 @@
|
|||
}
|
||||
|
||||
.dashboardDocument .mainDrawer-scrollContainer {
|
||||
margin-top: 6em !important;
|
||||
margin-top: 4.6em !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1098,3 +1095,50 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
|||
.itemsViewSettingsContainer > .button-flat {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layout-mobile #myPreferencesMenuPage {
|
||||
padding-top: 3.75em;
|
||||
}
|
||||
|
||||
.itemDetailsGroup {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.trackSelections {
|
||||
max-width: 44em;
|
||||
}
|
||||
|
||||
.detailsGroupItem,
|
||||
.trackSelections .selectContainer {
|
||||
display: flex;
|
||||
max-width: 44em;
|
||||
margin: 0 0 0.5em !important;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer {
|
||||
margin: 0 0 0.3em !important;
|
||||
}
|
||||
|
||||
.detailsGroupItem .label,
|
||||
.trackSelections .selectContainer .selectLabel {
|
||||
cursor: default;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 6.25em;
|
||||
margin: 0 0.6em 0 0;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .selectLabel {
|
||||
margin: 0 0.2em 0 0;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .detailTrackSelect {
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .selectArrowContainer .selectArrow {
|
||||
margin-top: 0;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,13 @@ _define("document-register-element", function() {
|
|||
// fetch
|
||||
var fetch = require("whatwg-fetch");
|
||||
_define("fetch", function() {
|
||||
return fetch
|
||||
return fetch;
|
||||
});
|
||||
|
||||
// query-string
|
||||
var query = require("query-string");
|
||||
_define("queryString", function() {
|
||||
return query;
|
||||
});
|
||||
|
||||
// flvjs
|
||||
|
@ -75,33 +81,25 @@ _define("sortable", function() {
|
|||
// webcomponents
|
||||
var webcomponents = require("webcomponents.js/webcomponents-lite");
|
||||
_define("webcomponents", function() {
|
||||
return webcomponents
|
||||
});
|
||||
|
||||
// libjass
|
||||
var libjass = require("libjass");
|
||||
require("libjass/libjass.css");
|
||||
_define("libjass", function() {
|
||||
return libjass;
|
||||
return webcomponents;
|
||||
});
|
||||
|
||||
// libass-wasm
|
||||
var libass_wasm = require("libass-wasm");
|
||||
var libassWasm = require("libass-wasm");
|
||||
_define("JavascriptSubtitlesOctopus", function() {
|
||||
return libass_wasm;
|
||||
return libassWasm;
|
||||
});
|
||||
|
||||
// material-icons
|
||||
var material_icons = require("material-design-icons-iconfont/dist/material-design-icons.css");
|
||||
var materialIcons = require("material-design-icons-iconfont/dist/material-design-icons.css");
|
||||
_define("material-icons", function() {
|
||||
return material_icons;
|
||||
return materialIcons;
|
||||
});
|
||||
|
||||
// Noto Sans
|
||||
|
||||
var jellyfin_noto = require("jellyfin-noto");
|
||||
// noto font
|
||||
var noto = require("jellyfin-noto");
|
||||
_define("jellyfin-noto", function () {
|
||||
return jellyfin_noto;
|
||||
return noto;
|
||||
});
|
||||
|
||||
// page.js
|
||||
|
@ -114,3 +112,30 @@ 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;
|
||||
});
|
||||
|
||||
var intersection_observer = require("intersection-observer");
|
||||
_define("intersection-observer", function () {
|
||||
return intersection_observer;
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) {
|
||||
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) {
|
||||
|
@ -16,7 +16,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
|
|||
html += '<i class="listItemIcon material-icons" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
type: "Primary",
|
||||
tag: entry.UserPrimaryImageTag
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr</i>"
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr</i>";
|
||||
} else {
|
||||
html += '<i class="listItemIcon material-icons" style="background-color:' + color + '">' + icon + '</i>';
|
||||
}
|
||||
|
@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
|
|||
html += entry.Name;
|
||||
html += "</div>";
|
||||
html += '<div class="listItemBodyText secondary">';
|
||||
var date = datetime.parseISO8601Date(entry.Date, true);
|
||||
html += datetime.toLocaleString(date).toLowerCase();
|
||||
html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
|
||||
html += "</div>";
|
||||
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
|
||||
html += entry.ShortOverview || "";
|
||||
|
|
|
@ -268,6 +268,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
|
|||
}
|
||||
|
||||
function getMaxBandwidth() {
|
||||
/* eslint-disable compat/compat */
|
||||
if (navigator.connection) {
|
||||
var max = navigator.connection.downlinkMax;
|
||||
if (max && max > 0 && max < Number.POSITIVE_INFINITY) {
|
||||
|
@ -279,6 +280,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
|
|||
return max;
|
||||
}
|
||||
}
|
||||
/* eslint-enable compat/compat */
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -511,9 +513,16 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
|
|||
return baseRoute;
|
||||
}
|
||||
|
||||
var popstateOccurred = false;
|
||||
window.addEventListener('popstate', function () {
|
||||
popstateOccurred = true;
|
||||
});
|
||||
|
||||
function getHandler(route) {
|
||||
return function (ctx, next) {
|
||||
ctx.isBack = popstateOccurred;
|
||||
handleRoute(ctx, next, route);
|
||||
popstateOccurred = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -570,8 +579,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) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSettings, browser, events, htmlMediaHelper) {
|
||||
define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], function (appSettings, browser, events, htmlMediaHelper, webSettings) {
|
||||
"use strict";
|
||||
|
||||
function getBaseProfileOptions(item) {
|
||||
|
@ -276,15 +276,17 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
features.push("otherapppromotions");
|
||||
features.push("displaymode");
|
||||
features.push("targetblank");
|
||||
// allows users to connect to more than one server
|
||||
//features.push("multiserver");
|
||||
features.push("screensaver");
|
||||
|
||||
if (!browser.orsay && !browser.tizen && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
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.tizen) {
|
||||
if (!browser.orsay) {
|
||||
features.push("subtitleburnsettings");
|
||||
}
|
||||
|
||||
|
@ -349,8 +351,6 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
var deviceName;
|
||||
var appName = "Jellyfin Web";
|
||||
var appVersion = "10.5.0";
|
||||
var visibilityChange;
|
||||
var visibilityState;
|
||||
|
||||
var appHost = {
|
||||
getWindowState: function () {
|
||||
|
@ -381,7 +381,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
return window.NativeShell.AppHost.getDefaultLayout();
|
||||
}
|
||||
|
||||
return getDefaultLayout()
|
||||
return getDefaultLayout();
|
||||
},
|
||||
getDeviceProfile: getDeviceProfile,
|
||||
init: function () {
|
||||
|
@ -424,40 +424,26 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
}
|
||||
};
|
||||
|
||||
var doc = self.document;
|
||||
var isHidden = false;
|
||||
var hidden;
|
||||
var visibilityChange;
|
||||
|
||||
if (doc) {
|
||||
if (void 0 !== doc.visibilityState) {
|
||||
visibilityChange = "visibilitychange";
|
||||
visibilityState = "hidden";
|
||||
if (typeof document.hidden !== "undefined") { /* eslint-disable-line compat/compat */
|
||||
hidden = "hidden";
|
||||
visibilityChange = "visibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
hidden = "webkitHidden";
|
||||
visibilityChange = "webkitvisibilitychange";
|
||||
}
|
||||
|
||||
document.addEventListener(visibilityChange, function () {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (document[hidden]) {
|
||||
onAppHidden();
|
||||
} else {
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
onAppVisible();
|
||||
}
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
doc.addEventListener(visibilityChange, function () {
|
||||
if (document[visibilityState]) {
|
||||
onAppHidden();
|
||||
} else {
|
||||
onAppVisible();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, false);
|
||||
|
||||
if (self.addEventListener) {
|
||||
self.addEventListener("focus", onAppVisible);
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
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;
|
||||
}
|
||||
|
@ -28,24 +35,19 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
console.debug("AutoFocuser enabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array from some source.
|
||||
*/
|
||||
var arrayFrom = Array.prototype.from || function (src) {
|
||||
return Array.prototype.slice.call(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -62,10 +64,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
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
|
||||
};
|
||||
|
|
|
@ -182,6 +182,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings"
|
|||
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
|
||||
type: "Backdrop",
|
||||
tag: imgTag,
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: index
|
||||
}));
|
||||
});
|
||||
|
@ -192,6 +193,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings"
|
|||
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
|
||||
type: "Backdrop",
|
||||
tag: imgTag,
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: index
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) {
|
|||
currentSlideshow = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -68,7 +68,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
|
|||
|
||||
return apiClient.getScaledImageUrl(item.Id, {
|
||||
|
||||
maxWidth: maxWidth,
|
||||
maxWidth: maxWidth * 2,
|
||||
tag: chapter.ImageTag,
|
||||
type: "Chapter",
|
||||
index: index
|
||||
|
|
34
src/components/castSenderApi.js
Normal file
34
src/components/castSenderApi.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
define([], function() {
|
||||
'use strict';
|
||||
|
||||
if (window.appMode === "cordova" || window.appMode === "android") {
|
||||
return {
|
||||
load: function () {
|
||||
window.chrome = window.chrome || {};
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var ccLoaded = false;
|
||||
return {
|
||||
load: function () {
|
||||
if (ccLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var fileref = document.createElement("script");
|
||||
fileref.setAttribute("type", "text/javascript");
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
|
@ -1,18 +1,7 @@
|
|||
define(["dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function (dialogHelper, loading, connectionManager, globalize, actionsheet) {
|
||||
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;
|
||||
|
@ -26,7 +15,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
|
|||
},
|
||||
dataType: "json"
|
||||
}).then(function (mapping) {
|
||||
var listItem = parentWithClass(button, "listItem");
|
||||
var listItem = dom.parentWithClass(button, "listItem");
|
||||
button.setAttribute("data-providerid", mapping.ProviderChannelId);
|
||||
listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
|
||||
loading.hide();
|
||||
|
@ -34,7 +23,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
|
|||
}
|
||||
|
||||
function onChannelsElementClick(e) {
|
||||
var btnMap = parentWithClass(e.target, "btnMap");
|
||||
var btnMap = dom.parentWithClass(e.target, "btnMap");
|
||||
|
||||
if (btnMap) {
|
||||
var channelId = btnMap.getAttribute("data-id");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,40 +1,65 @@
|
|||
define(['dialog', 'globalize'], function (dialog, globalize) {
|
||||
define(["browser", "dialog", "globalize"], function(browser, dialog, globalize) {
|
||||
'use strict';
|
||||
|
||||
return function (text, title) {
|
||||
function replaceAll(str, find, replace) {
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
var options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
text: text
|
||||
};
|
||||
} else {
|
||||
options = text;
|
||||
}
|
||||
|
||||
var items = [];
|
||||
|
||||
items.push({
|
||||
name: options.cancelText || globalize.translate('ButtonCancel'),
|
||||
id: 'cancel',
|
||||
type: 'cancel'
|
||||
});
|
||||
|
||||
items.push({
|
||||
name: options.confirmText || globalize.translate('ButtonOk'),
|
||||
id: 'ok',
|
||||
type: options.primary === 'delete' ? 'delete' : 'submit'
|
||||
});
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
return dialog(options).then(function (result) {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
if (browser.tv && window.confirm) {
|
||||
// Use the native confirm dialog
|
||||
return function (options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
var text = replaceAll(options.text || '', '<br/>', '\n');
|
||||
var result = confirm(text);
|
||||
|
||||
if (result) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Use our own dialog
|
||||
return function (text, title) {
|
||||
var options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
text: text
|
||||
};
|
||||
} else {
|
||||
options = text;
|
||||
}
|
||||
|
||||
var items = [];
|
||||
|
||||
items.push({
|
||||
name: options.cancelText || globalize.translate('ButtonCancel'),
|
||||
id: 'cancel',
|
||||
type: 'cancel'
|
||||
});
|
||||
|
||||
items.push({
|
||||
name: options.confirmText || globalize.translate('ButtonOk'),
|
||||
id: 'ok',
|
||||
type: options.primary === 'delete' ? 'delete' : 'submit'
|
||||
});
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
return dialog(options).then(function (result) {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function replaceAll(str, find, replace) {
|
||||
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
return function (options) {
|
||||
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
|
||||
var text = replaceAll(options.text || '', '<br/>', '\n');
|
||||
var result = confirm(text);
|
||||
|
||||
if (result) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
});
|
|
@ -169,6 +169,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
|
|||
}, {
|
||||
passive: true
|
||||
});
|
||||
|
||||
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
|
||||
if (e.target === dlg.dialogContainer) {
|
||||
// Close the application dialog menu
|
||||
close(dlg);
|
||||
// Prevent the default browser context menu from appearing
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isHistoryEnabled(dlg) {
|
||||
|
|
|
@ -7,11 +7,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
systemInfo = info;
|
||||
return info;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
loading.hide()
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
||||
|
@ -24,7 +24,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
var promises = [];
|
||||
|
||||
if ("Network" === path) {
|
||||
promises.push(ApiClient.getNetworkDevices())
|
||||
promises.push(ApiClient.getNetworkDevices());
|
||||
} else {
|
||||
if (path) {
|
||||
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
||||
|
@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
|
||||
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
|
||||
html += instruction;
|
||||
html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "<b>\\\\server</b>").replace("{1}", "<b>\\\\192.168.1.101</b>");
|
||||
html += Globalize.translate("MessageDirectoryPickerInstruction", "<b>\\\\server</b>", "<b>\\\\192.168.1.101</b>");
|
||||
if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) {
|
||||
html += "<br/>";
|
||||
html += "<br/>";
|
||||
|
@ -101,7 +101,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
html += Globalize.translate("MessageDirectoryPickerLinuxInstruction");
|
||||
html += "<br/>";
|
||||
}
|
||||
html += "</div>"
|
||||
html += "</div>";
|
||||
}
|
||||
html += '<form style="margin:auto;">';
|
||||
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
|
||||
|
@ -144,13 +144,13 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
function alertText(text) {
|
||||
alertTextWithOptions({
|
||||
text: text
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function alertTextWithOptions(options) {
|
||||
require(["alert"], function(alert) {
|
||||
alert(options)
|
||||
})
|
||||
alert(options);
|
||||
});
|
||||
}
|
||||
|
||||
function validatePath(path, validateWriteable, apiClient) {
|
||||
|
@ -163,21 +163,20 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
}
|
||||
}).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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -189,7 +188,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
if (lnkPath.classList.contains("lnkFile")) {
|
||||
content.querySelector("#txtDirectoryPickerPath").value = path;
|
||||
} else {
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true)
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -276,7 +275,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
dlg.addEventListener("close", onDialogClosed);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.querySelector(".btnCloseDialog").addEventListener("click", function() {
|
||||
dialogHelper.close(dlg)
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
currentDialog = dlg;
|
||||
dlg.querySelector("#txtDirectoryPickerPath").value = initialPath;
|
||||
|
@ -294,9 +293,9 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
if (currentDialog) {
|
||||
dialogHelper.close(currentDialog);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var systemInfo;
|
||||
return directoryBrowser
|
||||
return directoryBrowser;
|
||||
});
|
||||
|
|
|
@ -186,6 +186,8 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
|
|||
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,6 +217,8 @@ 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);
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
<div class="selectContainer fldDateTimeLocale hide">
|
||||
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
|
||||
<option value="">${AutoBasedOnLanguageSetting}</option>
|
||||
<option value="">${Auto}</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian (Belarus)</option>
|
||||
<option value="bg-BG">Bulgarian (Bulgaria)</option>
|
||||
|
@ -143,6 +143,11 @@
|
|||
<select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer inputContainer-withDescription fldFadein">
|
||||
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
|
||||
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldFadein">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
/* eslint-disable indent */
|
||||
|
||||
function parentWithAttribute(elem, name, value) {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -14,8 +24,13 @@ define([], function () {
|
|||
return elem;
|
||||
}
|
||||
|
||||
function parentWithTag(elem, tagNames) {
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
@ -32,9 +47,14 @@ define([], function () {
|
|||
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 (var i = 0, length = classNames.length; i < length; i++) {
|
||||
for (let i = 0, length = classNames.length; i < length; i++) {
|
||||
if (classList.contains(classNames[i])) {
|
||||
return true;
|
||||
}
|
||||
|
@ -42,8 +62,13 @@ define([], function () {
|
|||
return false;
|
||||
}
|
||||
|
||||
function parentWithClass(elem, classNames) {
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
@ -60,9 +85,9 @@ define([], function () {
|
|||
return elem;
|
||||
}
|
||||
|
||||
var supportsCaptureOption = false;
|
||||
let supportsCaptureOption = false;
|
||||
try {
|
||||
var opts = Object.defineProperty({}, 'capture', {
|
||||
const opts = Object.defineProperty({}, 'capture', {
|
||||
// eslint-disable-next-line getter-return
|
||||
get: function () {
|
||||
supportsCaptureOption = true;
|
||||
|
@ -73,29 +98,58 @@ define([], function () {
|
|||
console.debug('error checking capture support');
|
||||
}
|
||||
|
||||
function addEventListenerWithOptions(target, type, handler, options) {
|
||||
var optionsOrCapture = options;
|
||||
/**
|
||||
* 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 = options.capture;
|
||||
optionsOrCapture = optionsOrCapture.capture;
|
||||
}
|
||||
target.addEventListener(type, handler, optionsOrCapture);
|
||||
}
|
||||
|
||||
function removeEventListenerWithOptions(target, type, handler, options) {
|
||||
var optionsOrCapture = options;
|
||||
/**
|
||||
* 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 = options.capture;
|
||||
optionsOrCapture = optionsOrCapture.capture;
|
||||
}
|
||||
target.removeEventListener(type, handler, optionsOrCapture);
|
||||
}
|
||||
|
||||
var windowSize;
|
||||
var windowSizeEventsBound;
|
||||
/**
|
||||
* Cached window size.
|
||||
*/
|
||||
let windowSize;
|
||||
|
||||
/**
|
||||
* Flag of event listener bound.
|
||||
*/
|
||||
let windowSizeEventsBound;
|
||||
|
||||
/**
|
||||
* Resets cached window size.
|
||||
*/
|
||||
function clearWindowSize() {
|
||||
windowSize = null;
|
||||
}
|
||||
|
||||
function getWindowSize() {
|
||||
/**
|
||||
* Returns window size.
|
||||
* @returns {Object} Window size.
|
||||
*/
|
||||
export function getWindowSize() {
|
||||
if (!windowSize) {
|
||||
windowSize = {
|
||||
innerHeight: window.innerHeight,
|
||||
|
@ -104,30 +158,60 @@ define([], function () {
|
|||
|
||||
if (!windowSizeEventsBound) {
|
||||
windowSizeEventsBound = true;
|
||||
addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true });
|
||||
addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true });
|
||||
addEventListener(window, "orientationchange", clearWindowSize, { passive: true });
|
||||
addEventListener(window, 'resize', clearWindowSize, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
var _animationEvent;
|
||||
function whichAnimationEvent() {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
var t;
|
||||
var el = document.createElement("div");
|
||||
var animations = {
|
||||
const el = document.createElement("div");
|
||||
const animations = {
|
||||
"animation": "animationend",
|
||||
"OAnimation": "oAnimationEnd",
|
||||
"MozAnimation": "animationend",
|
||||
"WebkitAnimation": "webkitAnimationEnd"
|
||||
};
|
||||
for (t in animations) {
|
||||
for (let t in animations) {
|
||||
if (el.style[t] !== undefined) {
|
||||
_animationEvent = animations[t];
|
||||
return animations[t];
|
||||
|
@ -138,26 +222,36 @@ define([], function () {
|
|||
return _animationEvent;
|
||||
}
|
||||
|
||||
function whichAnimationCancelEvent() {
|
||||
|
||||
/**
|
||||
* Returns name of animation cancel event.
|
||||
* @returns {string} Name of animation cancel event.
|
||||
*/
|
||||
export function whichAnimationCancelEvent() {
|
||||
return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel');
|
||||
}
|
||||
|
||||
var _transitionEvent;
|
||||
function whichTransitionEvent() {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
var t;
|
||||
var el = document.createElement("div");
|
||||
var transitions = {
|
||||
const el = document.createElement("div");
|
||||
const transitions = {
|
||||
"transition": "transitionend",
|
||||
"OTransition": "oTransitionEnd",
|
||||
"MozTransition": "transitionend",
|
||||
"WebkitTransition": "webkitTransitionEnd"
|
||||
};
|
||||
for (t in transitions) {
|
||||
for (let t in transitions) {
|
||||
if (el.style[t] !== undefined) {
|
||||
_transitionEvent = transitions[t];
|
||||
return transitions[t];
|
||||
|
@ -168,15 +262,17 @@ define([], function () {
|
|||
return _transitionEvent;
|
||||
}
|
||||
|
||||
return {
|
||||
parentWithAttribute: parentWithAttribute,
|
||||
parentWithClass: parentWithClass,
|
||||
parentWithTag: parentWithTag,
|
||||
addEventListener: addEventListenerWithOptions,
|
||||
removeEventListener: removeEventListenerWithOptions,
|
||||
getWindowSize: getWindowSize,
|
||||
whichTransitionEvent: whichTransitionEvent,
|
||||
whichAnimationEvent: whichAnimationEvent,
|
||||
whichAnimationCancelEvent: whichAnimationCancelEvent
|
||||
};
|
||||
});
|
||||
/* 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
|
||||
};
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
define(['multi-download'], function (multiDownload) {
|
||||
'use strict';
|
||||
import multiDownload from "multi-download";
|
||||
|
||||
return {
|
||||
download: function (items) {
|
||||
export function download(items) {
|
||||
|
||||
if (window.NativeShell) {
|
||||
items.map(function (item) {
|
||||
window.NativeShell.downloadFile(item.url);
|
||||
});
|
||||
} else {
|
||||
multiDownload(items.map(function (item) {
|
||||
return item.url;
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
if (window.NativeShell) {
|
||||
items.map(function (item) {
|
||||
window.NativeShell.downloadFile(item.url);
|
||||
});
|
||||
} else {
|
||||
multiDownload(items.map(function (item) {
|
||||
return item.url;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
export function fileExists(path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.fileExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return {
|
||||
fileExists: function (path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.fileExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
},
|
||||
directoryExists: function (path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.directoryExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
});
|
||||
export function directoryExists(path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.directoryExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function (dialogHelper, globalize, connectionManager, events, browser, require) {
|
||||
define(["dom", "dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function (dom, dialogHelper, globalize, connectionManager, events, browser, require) {
|
||||
"use strict";
|
||||
|
||||
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
|
||||
|
@ -106,16 +106,6 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
events.trigger(instance, "filterchange");
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
function setVisibility(context, options) {
|
||||
if (options.mode == "livetvchannels" || options.mode == "albums" || options.mode == "artists" || options.mode == "albumartists" || options.mode == "songs") {
|
||||
hideByClass(context, "videoStandard");
|
||||
|
@ -320,7 +310,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
});
|
||||
context.addEventListener("change", function (e) {
|
||||
var chkGenreFilter = parentWithClass(e.target, "chkGenreFilter");
|
||||
var chkGenreFilter = dom.parentWithClass(e.target, "chkGenreFilter");
|
||||
if (chkGenreFilter) {
|
||||
var filterName = chkGenreFilter.getAttribute("data-filter");
|
||||
var filters = query.Genres || "";
|
||||
|
@ -334,7 +324,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkTagFilter = parentWithClass(e.target, "chkTagFilter");
|
||||
var chkTagFilter = dom.parentWithClass(e.target, "chkTagFilter");
|
||||
if (chkTagFilter) {
|
||||
var filterName = chkTagFilter.getAttribute("data-filter");
|
||||
var filters = query.Tags || "";
|
||||
|
@ -348,7 +338,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkYearFilter = parentWithClass(e.target, "chkYearFilter");
|
||||
var chkYearFilter = dom.parentWithClass(e.target, "chkYearFilter");
|
||||
if (chkYearFilter) {
|
||||
var filterName = chkYearFilter.getAttribute("data-filter");
|
||||
var filters = query.Years || "";
|
||||
|
@ -362,7 +352,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkOfficialRatingFilter = parentWithClass(e.target, "chkOfficialRatingFilter");
|
||||
var chkOfficialRatingFilter = dom.parentWithClass(e.target, "chkOfficialRatingFilter");
|
||||
if (chkOfficialRatingFilter) {
|
||||
var filterName = chkOfficialRatingFilter.getAttribute("data-filter");
|
||||
var filters = query.OfficialRatings || "";
|
||||
|
|
|
@ -64,18 +64,18 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
} else {
|
||||
var noLibDescription;
|
||||
if (user['Policy'] && user['Policy']['IsAdministrator']) {
|
||||
noLibDescription = Globalize.translate("NoCreatedLibraries", '<a id="button-createLibrary" class="button-link">', '</a>')
|
||||
noLibDescription = Globalize.translate("NoCreatedLibraries", '<a id="button-createLibrary" class="button-link">', '</a>');
|
||||
} else {
|
||||
noLibDescription = Globalize.translate("AskAdminToCreateLibrary");
|
||||
}
|
||||
|
||||
html += '<div class="centerMessage padded-left padded-right">';
|
||||
html += '<h2>' + Globalize.translate("MessageNothingHere") + '</h2>';
|
||||
html += '<p>' + noLibDescription + '</p>'
|
||||
html += '<p>' + noLibDescription + '</p>';
|
||||
html += '</div>';
|
||||
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");
|
||||
|
@ -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') {
|
||||
|
@ -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 += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><i class="material-icons homeLibraryIcon">' + icon + '</i><span class="homeLibraryText">' + item.Name + '</span></a>';
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><i class="material-icons homeLibraryIcon ' + icon + '"></i><span class="homeLibraryText">' + item.Name + '</span></a>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
@ -640,7 +640,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
@ -714,7 +714,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x" data-monitor="videoplayback,markplayed">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x" data-monitor="videoplayback,markplayed">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">';
|
||||
}
|
||||
|
@ -786,7 +786,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
@ -171,13 +171,29 @@ 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);
|
||||
} 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);
|
||||
});
|
||||
}
|
||||
};
|
||||
events.map(function (name) {
|
||||
element.addEventListener(name, onMediaChange);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -80,7 +80,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
if (track) {
|
||||
var format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
// libjass is needed here
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -117,8 +116,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
});
|
||||
}
|
||||
|
||||
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, '<br>') : result;
|
||||
}
|
||||
|
||||
function setTracks(elem, tracks, item, mediaSource) {
|
||||
|
@ -568,19 +568,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;
|
||||
|
@ -652,7 +652,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
|
||||
self.getSubtitleOffset = function() {
|
||||
return currentTrackOffset;
|
||||
}
|
||||
};
|
||||
|
||||
function isAudioStreamSupported(stream, deviceProfile) {
|
||||
|
||||
|
@ -1020,7 +1020,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
xhr.onerror = function (e) {
|
||||
reject(e);
|
||||
decrementFetchQueue();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
|
@ -1047,101 +1047,38 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
lastCustomTrackMs = 0;
|
||||
}
|
||||
|
||||
function renderWithSubtitlesOctopus(videoElement, track, item) {
|
||||
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",
|
||||
onError: function() {
|
||||
htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror');
|
||||
}
|
||||
},
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
function renderWithLibjass(videoElement, track, item) {
|
||||
|
||||
var rendererSettings = {};
|
||||
|
||||
if (browser.ps4) {
|
||||
// Text outlines are not rendering very well
|
||||
rendererSettings.enableSvg = false;
|
||||
} else if (browser.edge || browser.msie) {
|
||||
// svg not rendering at all
|
||||
rendererSettings.enableSvg = false;
|
||||
}
|
||||
|
||||
// probably safer to just disable everywhere
|
||||
rendererSettings.enableSvg = false;
|
||||
|
||||
require(['libjass', 'ResizeObserver'], function (libjass, ResizeObserver) {
|
||||
|
||||
libjass.ASS.fromUrl(getTextTrackUrl(track, item)).then(function (ass) {
|
||||
|
||||
var clock = new libjass.renderers.ManualClock();
|
||||
currentClock = clock;
|
||||
|
||||
// Create a DefaultRenderer using the video element and the ASS object
|
||||
var renderer = new libjass.renderers.WebRenderer(ass, clock, videoElement.parentNode, rendererSettings);
|
||||
|
||||
currentAssRenderer = renderer;
|
||||
|
||||
renderer.addEventListener("ready", function () {
|
||||
try {
|
||||
renderer.resize(videoElement.offsetWidth, videoElement.offsetHeight, 0, 0);
|
||||
|
||||
if (!self._resizeObserver) {
|
||||
self._resizeObserver = new ResizeObserver(onVideoResize, {});
|
||||
self._resizeObserver.observe(videoElement);
|
||||
}
|
||||
//clock.pause();
|
||||
} catch (ex) {
|
||||
//alert(ex);
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderSsaAss(videoElement, track, item) {
|
||||
if (supportsCanvas() && supportsWebWorkers()) {
|
||||
console.debug('rendering subtitles with SubtitlesOctopus');
|
||||
renderWithSubtitlesOctopus(videoElement, track, item);
|
||||
} else {
|
||||
console.debug('rendering subtitles with libjass');
|
||||
renderWithLibjass(videoElement, track, item);
|
||||
}
|
||||
}
|
||||
|
||||
function onVideoResize() {
|
||||
if (browser.iOS) {
|
||||
// the new sizes will be delayed for about 500ms with wkwebview
|
||||
setTimeout(resetVideoRendererSize, 500);
|
||||
} else {
|
||||
resetVideoRendererSize();
|
||||
}
|
||||
}
|
||||
|
||||
function resetVideoRendererSize() {
|
||||
var renderer = currentAssRenderer;
|
||||
if (renderer) {
|
||||
var videoElement = self._mediaElement;
|
||||
var width = videoElement.offsetWidth;
|
||||
var height = videoElement.offsetHeight;
|
||||
console.debug('videoElement resized: ' + width + 'x' + height);
|
||||
renderer.resize(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function requiresCustomSubtitlesElement() {
|
||||
|
||||
// after a system update, ps4 isn't showing anything when creating a track element dynamically
|
||||
|
@ -1231,7 +1168,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
if (!itemHelper.isLocalItem(item) || track.IsExternal) {
|
||||
var format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
// libjass is needed here
|
||||
renderSsaAss(videoElement, track, item);
|
||||
return;
|
||||
}
|
||||
|
@ -1274,7 +1210,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);
|
||||
});
|
||||
|
@ -1315,8 +1251,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 {
|
||||
|
@ -1493,11 +1428,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
}
|
||||
|
||||
if (browser.safari || browser.iOS || browser.iPad) {
|
||||
list.push('AirPlay')
|
||||
list.push('AirPlay');
|
||||
}
|
||||
|
||||
list.push('SetBrightness');
|
||||
list.push("SetAspectRatio")
|
||||
list.push("SetAspectRatio");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
@ -1620,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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1757,12 +1692,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
var mediaElement = this._mediaElement;
|
||||
if (mediaElement) {
|
||||
if ("auto" === val) {
|
||||
mediaElement.style.removeProperty("object-fit")
|
||||
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 () {
|
||||
|
@ -1779,7 +1714,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
}, {
|
||||
name: "Fill",
|
||||
id: "fill"
|
||||
}]
|
||||
}];
|
||||
};
|
||||
|
||||
HtmlVideoPlayer.prototype.togglePictureInPicture = function () {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
});
|
|
@ -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,7 +109,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
html += '<span style="margin-right: 10px;">';
|
||||
|
||||
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
|
||||
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount;
|
||||
html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount);
|
||||
|
||||
html += '</span>';
|
||||
|
||||
|
@ -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,7 +138,7 @@ 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);
|
||||
});
|
||||
}
|
||||
|
@ -162,7 +148,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function getRemoteImageHtml(image, imageType, apiClient) {
|
||||
|
||||
var tagName = layoutManager.tv ? 'button' : 'div';
|
||||
var enableFooterButtons = !layoutManager.tv;
|
||||
|
||||
|
@ -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,7 +318,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function showEditor(itemId, serverId, itemType) {
|
||||
|
||||
loading.show();
|
||||
|
||||
require(['text!./imagedownloader.template.html'], function (template) {
|
||||
|
@ -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;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', 'userSettings', 'require', 'css!./style'], function (lazyLoader, imageFetcher, layoutManager, browser, appSettings, userSettings, require) {
|
||||
'use strict';
|
||||
|
||||
var requestIdleCallback = window.requestIdleCallback || function (fn) {
|
||||
fn();
|
||||
};
|
||||
|
||||
var self = {};
|
||||
|
||||
function fillImage(elem, source, enableEffects) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,6 +373,7 @@ require(['apphost'], function (appHost) {
|
|||
}
|
||||
|
||||
function onFocusOrGamepadAttach(e) {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (isGamepadConnected() && document.hasFocus()) {
|
||||
console.log("Gamepad connected! Starting input loop");
|
||||
startInputLoop();
|
||||
|
@ -380,6 +381,7 @@ require(['apphost'], function (appHost) {
|
|||
}
|
||||
|
||||
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");
|
||||
stopInputLoop();
|
||||
|
|
|
@ -1,165 +1,170 @@
|
|||
define(["inputManager", "layoutManager"], function (inputManager, layoutManager) {
|
||||
"use strict";
|
||||
/**
|
||||
* Module for performing keyboard navigation.
|
||||
* @module components/input/keyboardnavigation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Key name mapping.
|
||||
*/
|
||||
// Add more to support old browsers
|
||||
var 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"
|
||||
};
|
||||
import inputManager from "inputManager";
|
||||
import layoutManager from "layoutManager";
|
||||
|
||||
/**
|
||||
* Keys used for keyboard navigation.
|
||||
*/
|
||||
var NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
|
||||
/**
|
||||
* 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"
|
||||
};
|
||||
|
||||
var hasFieldKey = false;
|
||||
try {
|
||||
hasFieldKey = "key" in new KeyboardEvent("keydown");
|
||||
} catch (e) {
|
||||
console.error("error checking 'key' field");
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFieldKey) {
|
||||
// Add [a..z]
|
||||
for (var 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() {
|
||||
document.addEventListener("keydown", function (e) {
|
||||
const key = getKeyName(e);
|
||||
|
||||
// Ignore navigation keys for non-TV
|
||||
if (!layoutManager.tv && isNavigationKey(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key name from event.
|
||||
*
|
||||
* @param {KeyboardEvent} keyboard event
|
||||
* @return {string} key name
|
||||
*/
|
||||
function getKeyName(event) {
|
||||
return KeyNames[event.keyCode] || event.key;
|
||||
}
|
||||
let capture = true;
|
||||
|
||||
/**
|
||||
* Returns _true_ if key is used for navigation.
|
||||
*
|
||||
* @param {string} key name
|
||||
* @return {boolean} _true_ if key is used for navigation
|
||||
*/
|
||||
function isNavigationKey(key) {
|
||||
return NavigationKeys.indexOf(key) != -1;
|
||||
}
|
||||
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;
|
||||
|
||||
function enable() {
|
||||
document.addEventListener("keydown", function (e) {
|
||||
var key = getKeyName(e);
|
||||
case "Back":
|
||||
inputManager.handle("back");
|
||||
break;
|
||||
|
||||
// Ignore navigation keys for non-TV
|
||||
if (!layoutManager.tv && isNavigationKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var 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":
|
||||
case "Escape":
|
||||
if (layoutManager.tv) {
|
||||
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:
|
||||
} else {
|
||||
capture = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
if (capture) {
|
||||
console.debug("disabling default event handling");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
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;
|
||||
|
||||
// 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"]);
|
||||
}
|
||||
default:
|
||||
capture = false;
|
||||
}
|
||||
|
||||
// No need to check for gamepads manually at load time, the eventhandler will be fired for that
|
||||
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
|
||||
if (navigator.getGamepads) { /* eslint-disable-line compat/compat */
|
||||
window.addEventListener("gamepadconnected", attachGamepadScript);
|
||||
}
|
||||
|
||||
return {
|
||||
enable: enable,
|
||||
getKeyName: getKeyName,
|
||||
isNavigationKey: isNavigationKey
|
||||
};
|
||||
});
|
||||
export default {
|
||||
enable: enable,
|
||||
getKeyName: getKeyName,
|
||||
isNavigationKey: isNavigationKey
|
||||
};
|
||||
|
|
|
@ -116,7 +116,7 @@ define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings",
|
|||
}
|
||||
|
||||
function createAttribute(label, value) {
|
||||
return '<span class="mediaInfoLabel">' + label + '</span><span class="mediaInfoAttribute">' + value + "</span>"
|
||||
return '<span class="mediaInfoLabel">' + label + '</span><span class="mediaInfoAttribute">' + value + "</span>";
|
||||
}
|
||||
|
||||
function showMediaInfoMore(itemId, serverId, template) {
|
||||
|
|
|
@ -352,6 +352,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter",
|
|||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
if (document.execCommand("copy")) {
|
||||
require(["toast"], function (toast) {
|
||||
toast(globalize.translate("CopyStreamURLSuccess"));
|
||||
|
@ -361,14 +362,19 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter",
|
|||
}
|
||||
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"));
|
||||
});
|
||||
}, textAreaCopy);
|
||||
}).catch(function () {
|
||||
textAreaCopy();
|
||||
});
|
||||
}
|
||||
getResolveFunction(resolve, id)();
|
||||
break;
|
||||
|
|
|
@ -296,8 +296,6 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
|
|||
|
||||
var html = "";
|
||||
|
||||
var providerIds = item.ProviderIds || {};
|
||||
|
||||
for (var i = 0, length = idList.length; i < length; i++) {
|
||||
|
||||
var idInfo = idList[i];
|
||||
|
@ -306,9 +304,12 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
|
|||
|
||||
html += '<div class="inputContainer">';
|
||||
|
||||
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 += '<input is="emby-input" class="txtLookupId" data-providerkey="' + idInfo.Key + '" id="' + id + '" label="' + idLabel + '"/>';
|
||||
|
||||
|
|
|
@ -4,10 +4,6 @@ define(['visibleinviewport', 'dom', 'browser'], function (visibleinviewport, dom
|
|||
var thresholdX;
|
||||
var thresholdY;
|
||||
|
||||
var requestIdleCallback = window.requestIdleCallback || function (fn) {
|
||||
fn();
|
||||
};
|
||||
|
||||
function resetThresholds() {
|
||||
|
||||
var threshold = 0.3;
|
||||
|
|
|
@ -36,7 +36,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = html;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function populateRefreshInterval(select) {
|
||||
|
@ -120,7 +120,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
html += plugin.Name;
|
||||
html += "</h3>";
|
||||
html += "</div>";
|
||||
i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_up"></i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_down"></i></button>'), html += "</div>"
|
||||
i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_up"></i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_down"></i></button>'), html += "</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
html += '<div class="fieldDescription">' + globalize.translate("LabelMetadataDownloadersHelp") + "</div>";
|
||||
|
@ -265,10 +265,10 @@ 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) {
|
||||
|
@ -296,8 +296,8 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
Type: type
|
||||
}, currentLibraryOptions.TypeOptions.push(typeOptions));
|
||||
var availableOptions = getTypeOptions(currentAvailableOptions || {}, type);
|
||||
(new ImageOptionsEditor).show(type, typeOptions, availableOptions)
|
||||
})
|
||||
(new ImageOptionsEditor).show(type, typeOptions, availableOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function onImageFetchersContainerClick(e) {
|
||||
|
@ -315,12 +315,12 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
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))
|
||||
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))
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,18 +396,24 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
parent.querySelector(".chkEnableEmbeddedTitlesContainer").classList.remove("hide");
|
||||
}
|
||||
|
||||
if (contentType === "tvshows") {
|
||||
parent.querySelector(".chkEnableEmbeddedEpisodeInfosContainer").classList.remove("hide");
|
||||
} else {
|
||||
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
|
||||
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")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -449,13 +455,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
}
|
||||
|
||||
typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) {
|
||||
return elem.checked
|
||||
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")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -493,25 +499,26 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
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
|
||||
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")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
|
||||
return elem.checked
|
||||
return elem.checked;
|
||||
}), function(elem) {
|
||||
return elem.getAttribute("data-lang")
|
||||
return elem.getAttribute("data-lang");
|
||||
});
|
||||
setSubtitleFetchersIntoOptions(parent, options);
|
||||
setMetadataFetchersIntoOptions(parent, options);
|
||||
|
@ -524,7 +531,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;
|
||||
}
|
||||
|
@ -545,15 +552,16 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
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")
|
||||
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"))
|
||||
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);
|
||||
|
@ -570,5 +578,5 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
getLibraryOptions: getLibraryOptions,
|
||||
setLibraryOptions: setLibraryOptions,
|
||||
setAdvancedVisible: setAdvancedVisible
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -28,6 +28,13 @@
|
|||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedTitlesOverFileNamesHelp}</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription chkEnableEmbeddedEpisodeInfosContainer hide advanced">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkEnableEmbeddedEpisodeInfos" />
|
||||
<span>${PreferEmbeddedEpisodeInfosOverFileNames}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedEpisodeInfosOverFileNamesHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription advanced">
|
||||
<label>
|
||||
|
|
|
@ -238,13 +238,6 @@
|
|||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.listItemMediaInfo {
|
||||
/* Don't display if flex not supported */
|
||||
display: none;
|
||||
align-items: center;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.listGroupHeader-first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
|
|||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
|
||||
var options = {
|
||||
width: width,
|
||||
maxWidth: width * 2,
|
||||
type: "Primary"
|
||||
};
|
||||
|
||||
|
@ -105,7 +105,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
|
|||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
|
||||
var options = {
|
||||
width: width,
|
||||
maxWidth: width * 2,
|
||||
type: "Primary"
|
||||
};
|
||||
|
||||
|
|
|
@ -188,5 +188,5 @@ define(["pluginManager"], function (pluginManager) {
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -465,7 +465,12 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
|
|||
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 += '<div class="inputContainer">';
|
||||
html += '<div class="flex align-items-center">';
|
||||
|
|
|
@ -6,6 +6,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
|
|||
document.removeEventListener('keydown', onOneDocumentClick);
|
||||
|
||||
if (window.Notification) {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
Notification.requestPermission();
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +27,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) {
|
||||
|
@ -173,15 +175,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
|
|||
};
|
||||
|
||||
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 =
|
||||
[
|
||||
|
|
|
@ -241,7 +241,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader',
|
|||
elem.addEventListener('click', function (e) {
|
||||
|
||||
if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT', 'A'])) {
|
||||
showRemoteControl(0);
|
||||
showRemoteControl();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1633,29 +1633,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 +1669,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) {
|
||||
|
||||
|
@ -3140,7 +3140,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
AllowVideoStreamCopy: false,
|
||||
AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null
|
||||
|
||||
}, true);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -3162,7 +3162,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
|
||||
// User clicked stop or content ended
|
||||
var state = self.getPlayerState(player);
|
||||
var streamInfo = getPlayerData(player).streamInfo;
|
||||
var data = getPlayerData(player);
|
||||
var streamInfo = data.streamInfo;
|
||||
|
||||
var nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null;
|
||||
|
||||
|
@ -3210,6 +3211,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
showPlaybackInfoErrorMessage(self, displayErrorCode, nextItem);
|
||||
} else if (nextItem) {
|
||||
self.nextTrack();
|
||||
} else {
|
||||
// Nothing more to play - clear data
|
||||
data.streamInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ define(['playbackManager', 'layoutManager', 'events'], function (playbackManager
|
|||
var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player);
|
||||
|
||||
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) {
|
||||
|
@ -40,6 +41,7 @@ define(['playbackManager', 'layoutManager', 'events'], function (playbackManager
|
|||
|
||||
if (orientationLocked && !playbackStopInfo.nextMediaType) {
|
||||
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock);
|
||||
|
||||
if (unlockOrientation) {
|
||||
|
|
|
@ -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", {
|
||||
|
||||
Name: dlg.querySelector('#txtNewPlaylistName').value,
|
||||
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
|
||||
userId: apiClient.getCurrentUserId()
|
||||
|
@ -50,9 +34,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
type: "POST",
|
||||
url: url,
|
||||
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(',')
|
||||
|
@ -85,7 +64,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
loading.show();
|
||||
|
||||
var url = apiClient.getUrl("Playlists/" + id + "/Items", {
|
||||
|
||||
Ids: itemIds,
|
||||
userId: apiClient.getCurrentUserId()
|
||||
});
|
||||
|
@ -95,7 +73,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
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,7 +92,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
panel.querySelector('.newPlaylistInfo').classList.add('hide');
|
||||
|
||||
var options = {
|
||||
|
||||
Recursive: true,
|
||||
IncludeItemTypes: "Playlist",
|
||||
SortBy: 'SortName',
|
||||
|
@ -125,7 +100,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
|
||||
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 += '<option value="">' + globalize.translate('OptionNew') + '</option>';
|
||||
|
||||
html += result.Items.map(function (i) {
|
||||
|
||||
return '<option value="' + i.Id + '">' + i.Name + '</option>';
|
||||
});
|
||||
|
||||
|
@ -159,7 +132,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function getEditorHtml(items) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function replaceAll(str, find, replace) {
|
||||
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
return function (options) {
|
||||
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
label: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
|
||||
var label = replaceAll(options.label || '', '<br/>', '\n');
|
||||
|
||||
var result = prompt(label, options.text || '');
|
||||
|
||||
if (result) {
|
||||
return Promise.resolve(result);
|
||||
} else {
|
||||
return Promise.reject(result);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,6 +1,10 @@
|
|||
define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function (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) {
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
function setInputProperties(dlg, options) {
|
||||
var txtInput = dlg.querySelector('#txtInput');
|
||||
|
||||
|
@ -13,7 +17,6 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
|
|||
}
|
||||
|
||||
function showDialog(options, template) {
|
||||
|
||||
var dialogOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
|
@ -71,34 +74,49 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
|
|||
dlg.style.minWidth = (Math.min(400, dom.getWindowSize().innerWidth - 50)) + 'px';
|
||||
|
||||
return dialogHelper.open(dlg).then(function () {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
|
||||
}
|
||||
|
||||
var value = submitValue;
|
||||
|
||||
if (value) {
|
||||
return value;
|
||||
if (submitValue) {
|
||||
return submitValue;
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return function (options) {
|
||||
if ((browser.tv || browser.xboxOne) && window.confirm) {
|
||||
return function (options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
label: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
require(['text!./prompt.template.html'], function (template) {
|
||||
var label = replaceAll(options.label || '', '<br/>', '\n');
|
||||
var result = prompt(label, options.text || '');
|
||||
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
showDialog(options, template).then(resolve, reject);
|
||||
if (result) {
|
||||
return Promise.resolve(result);
|
||||
} else {
|
||||
return Promise.reject(result);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function (options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
require(['text!./prompt.template.html'], function (template) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
showDialog(options, template).then(resolve, reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -122,9 +122,9 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL
|
|||
}
|
||||
|
||||
var url = item ? seriesImageUrl(item, {
|
||||
maxHeight: 300
|
||||
maxHeight: 300 * 2
|
||||
}) || imageUrl(item, {
|
||||
maxHeight: 300
|
||||
maxHeight: 300 * 2
|
||||
}) : null;
|
||||
|
||||
console.debug("updateNowPlayingInfo");
|
||||
|
|
|
@ -1,96 +1,90 @@
|
|||
// From https://github.com/parshap/node-sanitize-filename
|
||||
|
||||
define([], function () {
|
||||
'use strict';
|
||||
const illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
var illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[\. ]+$/;
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
}
|
||||
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
|
||||
function getByteLength(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
|
||||
function getByteLength(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var byteLength = 0;
|
||||
var codePoint = null;
|
||||
var prevCodePoint = null;
|
||||
for (var i = 0; i < charLength; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
// handle 4-byte non-BMP chars
|
||||
// low surrogate
|
||||
if (isLowSurrogate(codePoint)) {
|
||||
// when parsing previous hi-surrogate, 3 is added to byteLength
|
||||
if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) {
|
||||
byteLength += 1;
|
||||
} else {
|
||||
byteLength += 3;
|
||||
}
|
||||
} else if (codePoint <= 0x7f) {
|
||||
const charLength = string.length;
|
||||
let byteLength = 0;
|
||||
let codePoint = null;
|
||||
let prevCodePoint = null;
|
||||
for (let i = 0; i < charLength; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
// handle 4-byte non-BMP chars
|
||||
// low surrogate
|
||||
if (isLowSurrogate(codePoint)) {
|
||||
// when parsing previous hi-surrogate, 3 is added to byteLength
|
||||
if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) {
|
||||
byteLength += 1;
|
||||
} else if (codePoint >= 0x80 && codePoint <= 0x7ff) {
|
||||
byteLength += 2;
|
||||
} else if (codePoint >= 0x800 && codePoint <= 0xffff) {
|
||||
} else {
|
||||
byteLength += 3;
|
||||
}
|
||||
prevCodePoint = codePoint;
|
||||
} else if (codePoint <= 0x7f) {
|
||||
byteLength += 1;
|
||||
} else if (codePoint >= 0x80 && codePoint <= 0x7ff) {
|
||||
byteLength += 2;
|
||||
} else if (codePoint >= 0x800 && codePoint <= 0xffff) {
|
||||
byteLength += 3;
|
||||
}
|
||||
|
||||
return byteLength;
|
||||
prevCodePoint = codePoint;
|
||||
}
|
||||
|
||||
function truncate(string, byteLength) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var curByteLength = 0;
|
||||
var codePoint;
|
||||
var segment;
|
||||
|
||||
for (var i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
|
||||
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
|
||||
curByteLength += getByteLength(segment);
|
||||
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
} else if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
function truncate(string, byteLength) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
return {
|
||||
sanitize: function (input, replacement) {
|
||||
var sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return truncate(sanitized, 255);
|
||||
const charLength = string.length;
|
||||
let curByteLength = 0;
|
||||
let codePoint;
|
||||
let segment;
|
||||
|
||||
for (let i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
|
||||
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
curByteLength += getByteLength(segment);
|
||||
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
} else if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
export function sanitize(input, replacement) {
|
||||
const sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return truncate(sanitized, 255);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -47,10 +55,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,26 +185,34 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent element that can be scrolled. If no such, returns documentElement.
|
||||
* Default (document) scroller.
|
||||
*/
|
||||
const documentScroller = new DocumentScroller();
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
@ -169,7 +220,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
nameClass = "scrollY";
|
||||
}
|
||||
|
||||
var parent = element.parentElement;
|
||||
let parent = element.parentElement;
|
||||
|
||||
while (parent) {
|
||||
// Skip 'emby-scroller' because it scrolls by itself
|
||||
|
@ -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,14 +312,14 @@ 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 (!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);
|
||||
|
@ -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
|
||||
|
@ -490,9 +544,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
}, {capture: true});
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled: isEnabled,
|
||||
scrollTo: scrollTo,
|
||||
scrollToElement: scrollToElement
|
||||
};
|
||||
});
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
isEnabled: isEnabled,
|
||||
scrollTo: scrollTo,
|
||||
scrollToElement: scrollToElement
|
||||
};
|
||||
|
|
|
@ -10,12 +10,6 @@ define([], function () {
|
|||
}
|
||||
|
||||
},
|
||||
canExec: false,
|
||||
exec: function (options) {
|
||||
// options.path
|
||||
// options.arguments
|
||||
return Promise.reject();
|
||||
},
|
||||
enableFullscreen: function () {
|
||||
if (window.NativeShell) {
|
||||
window.NativeShell.enableFullscreen();
|
||||
|
|
|
@ -116,8 +116,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 () {
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
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', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
|
@ -11,7 +21,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
|
||||
if (item.ImageTags && item.ImageTags[options.type]) {
|
||||
|
||||
options.tag = item.ImageTags[options.type];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
}
|
||||
|
@ -27,8 +36,14 @@ 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";
|
||||
|
||||
|
@ -46,19 +61,19 @@ 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) {
|
||||
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') {
|
||||
return apiClient.getItemDownloadUrl(item.Id);
|
||||
}
|
||||
imageOptions.type = "Primary";
|
||||
|
@ -66,15 +81,25 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '<button is="paper-icon-button-light" class="autoSize ' + cssClass + '"' + tabIndex + autoFocus + '><i class="material-icons slideshowButtonIcon">' + icon + '</i></button>';
|
||||
return '<button is="paper-icon-button-light" class="autoSize ' + cssClass + '"' + tabIndex + autoFocus + '><i class="material-icons slideshowButtonIcon ' + icon + '"></i></button>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +108,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.
|
||||
options.interactive = !browser.chromecast;
|
||||
|
||||
/**
|
||||
* 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,17 +141,15 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
removeOnClose: true
|
||||
});
|
||||
|
||||
dlg.classList.add('slideshowDialog');
|
||||
dialog.classList.add('slideshowDialog');
|
||||
|
||||
var html = '';
|
||||
|
||||
if (options.interactive) {
|
||||
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
if (options.interactive && !layoutManager.tv) {
|
||||
var actionButtonsOnTop = layoutManager.mobile;
|
||||
|
||||
html += '<div>';
|
||||
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false);
|
||||
html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false);
|
||||
|
||||
|
@ -137,7 +168,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
if (!actionButtonsOnTop) {
|
||||
html += '<div class="slideshowBottomBar hide">';
|
||||
|
||||
html += getIcon('pause', 'btnSlideshowPause slideshowButton', true, true);
|
||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true);
|
||||
if (appHost.supports('filedownload')) {
|
||||
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||
}
|
||||
|
@ -148,33 +179,28 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
} else {
|
||||
html += '<div class="slideshowImage"></div><h1 class="slideshowImageText"></h1>';
|
||||
}
|
||||
|
||||
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,79 +208,106 @@ 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);
|
||||
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 i');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.innerHTML = "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 i');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.innerHTML = "";
|
||||
btnSlideshowPause.classList.replace("pause", "play_arrow");
|
||||
}
|
||||
}
|
||||
|
||||
function loadSwiper(dlg) {
|
||||
|
||||
/**
|
||||
* 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,
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
//title: item.Name,
|
||||
//description: item.Overview
|
||||
Id: item.Id,
|
||||
|
@ -262,11 +315,17 @@ 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 += '<div class="swiper-slide" data-imageurl="' + item.imageUrl + '" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
|
||||
html += '<img data-src="' + item.imageUrl + '" class="swiper-lazy swiper-slide-img">';
|
||||
html += '<div class="swiper-slide" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
|
||||
html += '<div class="slider-zoom-container">';
|
||||
html += '<img src="' + item.originalImage + '" class="swiper-slide-img">';
|
||||
html += '</div>';
|
||||
if (item.title || item.subtitle) {
|
||||
html += '<div class="slideText">';
|
||||
html += '<div class="slideTextInner">';
|
||||
|
@ -288,42 +347,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')
|
||||
};
|
||||
|
@ -334,8 +369,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a download for the currently displayed slide.
|
||||
*/
|
||||
function download() {
|
||||
|
||||
var imageInfo = getCurrentImageInfo();
|
||||
|
||||
require(['fileDownloader'], function (fileDownloader) {
|
||||
|
@ -343,8 +380,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({
|
||||
|
@ -352,21 +391,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').innerHTML !== "pause";
|
||||
var paused = !dialog.querySelector('.btnSlideshowPause i').classList.contains("pause");
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
|
@ -374,8 +421,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);
|
||||
|
@ -386,53 +435,38 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -440,71 +474,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) {
|
||||
|
@ -527,125 +566,46 @@ 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':
|
||||
case 'play':
|
||||
case 'playpause':
|
||||
case 'pause':
|
||||
showOsd();
|
||||
break;
|
||||
case 'play':
|
||||
play();
|
||||
break;
|
||||
case 'pause':
|
||||
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 the slideshow element.
|
||||
*/
|
||||
self.hide = function () {
|
||||
|
||||
var dialog = dlg;
|
||||
var dialog = dialog;
|
||||
if (dialog) {
|
||||
|
||||
dialogHelper.close(dialog);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,15 +2,11 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
"use strict";
|
||||
|
||||
function populateLanguages(select, languages) {
|
||||
|
||||
var html = "";
|
||||
|
||||
html += "<option value=''>" + globalize.translate('AnyLanguage') + "</option>";
|
||||
|
||||
for (var i = 0, length = languages.length; i < length; i++) {
|
||||
|
||||
var culture = languages[i];
|
||||
|
||||
html += "<option value='" + culture.ThreeLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
|
||||
|
@ -18,7 +14,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
}
|
||||
|
||||
function getSubtitleAppearanceObject(context) {
|
||||
|
||||
var appearanceSettings = {};
|
||||
|
||||
appearanceSettings.textSize = context.querySelector('#selectTextSize').value;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<form style="margin:0 auto;">
|
||||
|
||||
<div class="verticalSection">
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${Subtitles}
|
||||
</h2>
|
||||
|
@ -9,6 +7,7 @@
|
|||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectSubtitleLanguage" label="${LabelPreferredSubtitleLanguage}"></select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectSubtitlePlaybackMode" label="${LabelSubtitlePlaybackMode}">
|
||||
<option value="Default">${Default}</option>
|
||||
|
@ -23,6 +22,7 @@
|
|||
<div class="fieldDescription subtitlesOnlyForcedHelp subtitlesHelp hide">${OnlyForcedSubtitlesHelp}</div>
|
||||
<div class="fieldDescription subtitlesNoneHelp subtitlesHelp hide">${NoSubtitlesHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer fldBurnIn hide">
|
||||
<select is="emby-select" id="selectSubtitleBurnIn" label="${LabelBurnSubtitles}">
|
||||
<option value="">${Auto}</option>
|
||||
|
@ -34,7 +34,6 @@
|
|||
</div>
|
||||
|
||||
<div class="verticalSection subtitleAppearanceSection hide">
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${HeaderSubtitleAppearance}
|
||||
</h2>
|
||||
|
@ -61,6 +60,7 @@
|
|||
<option value="extralarge">${ExtraLarge}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectFont" label="${LabelFont}">
|
||||
<option value="">${Default}</option>
|
||||
|
@ -71,12 +71,15 @@
|
|||
<option value="smallcaps">${SmallCaps}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer hide">
|
||||
<input is="emby-input" id="inputTextBackground" label="${LabelTextBackgroundColor}" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="inputContainer hide">
|
||||
<input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectDropShadow" label="${LabelDropShadow}">
|
||||
<option value="none">${None}</option>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, template, css) {
|
||||
define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) {
|
||||
"use strict";
|
||||
|
||||
var player;
|
||||
|
@ -10,6 +10,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
function init(instance) {
|
||||
|
||||
var parent = document.createElement('div');
|
||||
document.body.appendChild(parent);
|
||||
parent.innerHTML = template;
|
||||
|
||||
subtitleSyncSlider = parent.querySelector(".subtitleSyncSlider");
|
||||
|
@ -17,11 +18,19 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
subtitleSyncCloseButton = parent.querySelector(".subtitleSync-closeButton");
|
||||
subtitleSyncContainer = parent.querySelector(".subtitleSyncContainer");
|
||||
|
||||
if (layoutManager.tv) {
|
||||
subtitleSyncSlider.classList.add("focusable");
|
||||
// HACK: Delay to give time for registered element attach (Firefox)
|
||||
setTimeout(function () {
|
||||
subtitleSyncSlider.enableKeyboardDragging();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
subtitleSyncContainer.classList.add("hide");
|
||||
|
||||
subtitleSyncTextField.updateOffset = function(offset) {
|
||||
this.textContent = offset + "s";
|
||||
}
|
||||
};
|
||||
|
||||
subtitleSyncTextField.addEventListener("keypress", function(event) {
|
||||
|
||||
|
@ -57,7 +66,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
subtitleSyncSlider.updateOffset = function(percent) {
|
||||
// default value is 0s = 50%
|
||||
this.value = percent === undefined ? 50 : percent;
|
||||
}
|
||||
};
|
||||
|
||||
subtitleSyncSlider.addEventListener("change", function () {
|
||||
// set new offset
|
||||
|
@ -87,8 +96,6 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
SubtitleSync.prototype.toggle("forceToHide");
|
||||
});
|
||||
|
||||
document.body.appendChild(parent);
|
||||
|
||||
instance.element = parent;
|
||||
}
|
||||
|
||||
|
@ -125,7 +132,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
elem.parentNode.removeChild(elem);
|
||||
this.element = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SubtitleSync.prototype.toggle = function(action) {
|
||||
|
||||
|
@ -159,7 +166,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SubtitleSync;
|
||||
});
|
||||
|
|
|
@ -264,17 +264,13 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em
|
|||
self.init = function () {
|
||||
options = options || {};
|
||||
|
||||
if (options.showCancelButton) {
|
||||
page.querySelector(".btnCancel").classList.remove("hide");
|
||||
} else {
|
||||
page.querySelector(".btnCancel").classList.add("hide");
|
||||
}
|
||||
// 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);
|
||||
|
||||
if (options.showSubmitButton) {
|
||||
page.querySelector(".btnSubmitListings").classList.remove("hide");
|
||||
} else {
|
||||
page.querySelector(".btnSubmitListings").classList.add("hide");
|
||||
}
|
||||
var hideSubmitButton = options.showSubmitButton === false;
|
||||
page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
|
||||
|
||||
$(".formLogin", page).on("submit", function () {
|
||||
submitLoginForm();
|
||||
|
|
|
@ -163,17 +163,13 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa
|
|||
self.init = function () {
|
||||
options = options || {};
|
||||
|
||||
if (false !== options.showCancelButton) {
|
||||
page.querySelector(".btnCancel").classList.remove("hide");
|
||||
} else {
|
||||
page.querySelector(".btnCancel").classList.add("hide");
|
||||
}
|
||||
// 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);
|
||||
|
||||
if (false !== options.showSubmitButton) {
|
||||
page.querySelector(".btnSubmitListings").classList.remove("hide");
|
||||
} else {
|
||||
page.querySelector(".btnSubmitListings").classList.add("hide");
|
||||
}
|
||||
var hideSubmitButton = options.showSubmitButton === false;
|
||||
page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
|
||||
|
||||
$("form", page).on("submit", function () {
|
||||
submitListingsForm();
|
||||
|
|
3
src/config.example.json
Normal file
3
src/config.example.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"multiserver": true
|
||||
}
|
|
@ -95,7 +95,7 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu
|
|||
}
|
||||
|
||||
function showServerConnectionFailure() {
|
||||
alertText(globalize.translate("MessageUnableToConnectToServer"), globalize.translate("HeaderConnectionFailure"));
|
||||
alertText(globalize.translate("MessageUnableToConnectToServer"));
|
||||
}
|
||||
|
||||
return function (view, params) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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) {
|
||||
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) {
|
||||
|
@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
|
|||
getNowPlayingName: function (session) {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
@ -634,8 +635,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
|
|||
return "<img src='" + iconUrl + "' />";
|
||||
},
|
||||
getNowPlayingImageUrl: function (item) {
|
||||
/* Screen width is multiplied by 0.2, as the there is currently no way to get the width of
|
||||
elements that aren't created yet. */
|
||||
if (item && item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
return ApiClient.getScaledImageUrl(item.Id, {
|
||||
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
|
||||
type: "Backdrop",
|
||||
tag: item.BackdropImageTags[0]
|
||||
});
|
||||
|
@ -643,6 +647,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",
|
||||
tag: item.ParentBackdropImageTags[0]
|
||||
});
|
||||
|
@ -650,6 +655,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",
|
||||
tag: item.BackdropImageTag
|
||||
});
|
||||
|
@ -659,6 +665,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",
|
||||
tag: imageTags.Thumb
|
||||
});
|
||||
|
@ -666,6 +673,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",
|
||||
tag: item.ParentThumbImageTag
|
||||
});
|
||||
|
@ -673,6 +681,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",
|
||||
tag: item.ThumbImageTag
|
||||
});
|
||||
|
@ -680,6 +689,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",
|
||||
tag: imageTags.Primary
|
||||
});
|
||||
|
@ -687,6 +697,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",
|
||||
tag: item.PrimaryImageTag
|
||||
});
|
||||
|
@ -694,6 +705,7 @@ 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",
|
||||
tag: item.AlbumPrimaryImageTag
|
||||
});
|
||||
|
|
|
@ -29,5 +29,5 @@ define(["datetime", "loading", "apphost", "listViewStyle", "emby-button", "flexS
|
|||
loading.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -49,12 +49,12 @@ define(["loading", "libraryMenu", "globalize", "listViewStyle", "emby-button"],
|
|||
}
|
||||
page.querySelector(".notificationList").innerHTML = html;
|
||||
loading.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return function(view, params) {
|
||||
view.addEventListener("viewshow", function() {
|
||||
reload(view);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e
|
|||
}
|
||||
|
||||
if (installedPlugin) {
|
||||
var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "<strong>" + installedPlugin.Version + "</strong>");
|
||||
var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled", "<strong>" + installedPlugin.Version + "</strong>");
|
||||
$("#pCurrentVersion", page).show().html(currentVersionText);
|
||||
} else {
|
||||
$("#pCurrentVersion", page).hide().html("");
|
||||
|
|
|
@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby
|
|||
return ip.Id == plugin.guid;
|
||||
})[0];
|
||||
html += "<div class='cardText cardText-secondary'>";
|
||||
html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : " ";
|
||||
html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : " ";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
|
|
|
@ -2,7 +2,7 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button"
|
|||
"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) {
|
||||
confirm({
|
||||
|
|
|
@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
|
|||
html += "</div>";
|
||||
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);
|
||||
return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
|
||||
}
|
||||
|
||||
if ("WeeklyTrigger" == trigger.Type) {
|
||||
return trigger.DayOfWeek + "s at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
|
||||
// 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";
|
||||
return globalize.translate("OnWakeFromSleep");
|
||||
}
|
||||
|
||||
if (trigger.Type == "IntervalTrigger") {
|
||||
|
@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
|
|||
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";
|
||||
return globalize.translate("OnApplicationStartup");
|
||||
}
|
||||
|
||||
return trigger.Type;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["jQuery", "loading", "events", "globalize", "serverNotifications", "humanedate", "listViewStyle", "emby-button"], function($, loading, events, globalize, serverNotifications) {
|
||||
define(["jQuery", "loading", "events", "globalize", "serverNotifications", "date-fns", "dfnshelper", "listViewStyle", "emby-button"], function ($, loading, events, globalize, serverNotifications, datefns, dfnshelper) {
|
||||
"use strict";
|
||||
|
||||
function reloadList(page) {
|
||||
|
@ -7,7 +7,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
}).then(function(tasks) {
|
||||
populateList(page, tasks);
|
||||
loading.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function populateList(page, tasks) {
|
||||
|
@ -49,7 +49,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
html += "</a>";
|
||||
html += "</div>";
|
||||
if (task.State === "Running") {
|
||||
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStopTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStop") + '"><i class="material-icons">stop</i></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStopTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStop") + '"><i class="material-icons stop"></i></button>';
|
||||
} else if (task.State === "Idle") {
|
||||
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStartTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStart") + '"><i class="material-icons play_arrow"></i></button>';
|
||||
}
|
||||
|
@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
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));
|
||||
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 += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
|
||||
} else if (task.LastExecutionResult.Status === "Cancelled") {
|
||||
|
@ -90,16 +93,22 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
return html;
|
||||
}
|
||||
|
||||
function setTaskButtonIcon(button, icon) {
|
||||
var inner = button.querySelector("i");
|
||||
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");
|
||||
elem.querySelector("i").innerHTML = "stop";
|
||||
setTaskButtonIcon(elem, "stop");
|
||||
elem.title = globalize.translate("ButtonStop");
|
||||
} else if (state === "Idle") {
|
||||
elem.classList.add("btnStartTask");
|
||||
elem.classList.remove("btnStopTask");
|
||||
elem.querySelector("i").innerHTML = "";
|
||||
setTaskButtonIcon(elem, "play_arrow");
|
||||
elem.title = globalize.translate("ButtonStart");
|
||||
}
|
||||
$(elem).parents(".listItem")[0].setAttribute("data-status", state);
|
||||
|
@ -146,7 +155,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
ApiClient.startScheduledTask(id).then(function() {
|
||||
updateTaskButton(button, "Running");
|
||||
reloadList(view);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
$(".divScheduledTasks", view).on("click", ".btnStopTask", function() {
|
||||
|
@ -155,7 +164,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
ApiClient.stopScheduledTask(id).then(function() {
|
||||
updateTaskButton(button, "");
|
||||
reloadList(view);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
view.addEventListener("viewbeforehide", function() {
|
||||
|
@ -169,5 +178,5 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
reloadList(view);
|
||||
events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "humanedate", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper) {
|
||||
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) {
|
||||
|
@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu
|
|||
|
||||
if (device.LastUserName) {
|
||||
deviceHtml += device.LastUserName;
|
||||
deviceHtml += ", " + humaneDate(device.DateLastActivity);
|
||||
deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix);
|
||||
}
|
||||
|
||||
deviceHtml += " ";
|
||||
|
|
|
@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,14 +319,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>Protocol: " + (profile.Protocol || "Http") + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,11 +404,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.Codec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.Codec || allText) + "</p>";
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
|
|
@ -14,6 +14,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
|
|||
$("#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", {
|
||||
|
@ -58,6 +59,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
|
|||
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) {
|
||||
|
|
|
@ -468,6 +468,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
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
|
||||
});
|
||||
|
@ -477,6 +478,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
} else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) {
|
||||
imgUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: "Primary",
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: 0,
|
||||
tag: item.ImageTags.Primary
|
||||
});
|
||||
|
@ -486,6 +488,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
imgUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: "Backdrop",
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: 0,
|
||||
tag: item.BackdropImageTags[0]
|
||||
});
|
||||
|
@ -495,6 +498,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
} else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
|
||||
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
|
||||
type: "Backdrop",
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: 0,
|
||||
tag: item.ParentBackdropImageTags[0]
|
||||
});
|
||||
|
@ -504,6 +508,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
} else if (item.ImageTags && item.ImageTags.Thumb) {
|
||||
imgUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: "Thumb",
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
index: 0,
|
||||
tag: item.ImageTags.Thumb
|
||||
});
|
||||
|
@ -586,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
try {
|
||||
var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString();
|
||||
itemBirthday.classList.remove("hide");
|
||||
itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday);
|
||||
itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday);
|
||||
} catch (err) {
|
||||
itemBirthday.classList.add("hide");
|
||||
}
|
||||
|
@ -600,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
try {
|
||||
var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString();
|
||||
itemDeathDate.classList.remove("hide");
|
||||
itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday);
|
||||
itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday);
|
||||
} catch (err) {
|
||||
itemDeathDate.classList.add("hide");
|
||||
}
|
||||
|
@ -613,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) {
|
||||
var gmap = '<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://maps.google.com/maps?q=' + item.ProductionLocations[0] + '">' + item.ProductionLocations[0] + "</a>";
|
||||
itemBirthLocation.classList.remove("hide");
|
||||
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap);
|
||||
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap);
|
||||
} else {
|
||||
itemBirthLocation.classList.add("hide");
|
||||
}
|
||||
|
@ -698,26 +703,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
}
|
||||
}
|
||||
|
||||
function renderUserInfo(page, item) {
|
||||
var lastPlayedElement = page.querySelector(".itemLastPlayed");
|
||||
|
||||
if (item.UserData && item.UserData.LastPlayedDate) {
|
||||
lastPlayedElement.classList.remove("hide");
|
||||
var datePlayed = datetime.parseISO8601Date(item.UserData.LastPlayedDate);
|
||||
lastPlayedElement.innerHTML = globalize.translate("DatePlayed") + ": " + datetime.toLocaleDateString(datePlayed) + " " + datetime.getDisplayTime(datePlayed);
|
||||
} else {
|
||||
lastPlayedElement.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
function renderLinks(linksElem, item) {
|
||||
var html = [];
|
||||
|
||||
if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) {
|
||||
var dateCreated = datetime.parseISO8601Date(item.DateCreated);
|
||||
html.push(globalize.translate("AddedOnValue", datetime.toLocaleDateString(dateCreated) + " " + datetime.getDisplayTime(dateCreated)));
|
||||
}
|
||||
|
||||
var links = [];
|
||||
|
||||
if (!layoutManager.tv && item.HomePageUrl) {
|
||||
|
@ -731,7 +719,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
}
|
||||
|
||||
if (links.length) {
|
||||
html.push(globalize.translate("LinksValue", links.join(", ")));
|
||||
html.push(links.join(", "));
|
||||
}
|
||||
|
||||
linksElem.innerHTML = html.join(", ");
|
||||
|
@ -761,44 +749,54 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
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
|
||||
});
|
||||
}
|
||||
|
@ -1017,13 +1015,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
context: context
|
||||
}) + '">' + p.Name + "</a>";
|
||||
}).join(", ");
|
||||
var elem = page.querySelector(".genres");
|
||||
elem.innerHTML = globalize.translate(genres.length > 1 ? "GenresValue" : "GenreValue", html);
|
||||
|
||||
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");
|
||||
if (genres.length) {
|
||||
elem.classList.remove("hide");
|
||||
genresGroup.classList.remove("hide");
|
||||
} else {
|
||||
elem.classList.add("hide");
|
||||
genresGroup.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1041,13 +1043,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
context: context
|
||||
}) + '">' + p.Name + "</a>";
|
||||
}).join(", ");
|
||||
var elem = page.querySelector(".directors");
|
||||
elem.innerHTML = globalize.translate(directors.length > 1 ? "DirectorsValue" : "DirectorValue", html);
|
||||
|
||||
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) {
|
||||
elem.classList.remove("hide");
|
||||
directorsGroup.classList.remove("hide");
|
||||
} else {
|
||||
elem.classList.add("hide");
|
||||
directorsGroup.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1105,7 +1111,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
|
||||
reloadUserDataButtons(page, item);
|
||||
renderLinks(externalLinksElem, item);
|
||||
renderUserInfo(page, item);
|
||||
renderTags(page, item);
|
||||
renderSeriesAirTime(page, item, isStatic);
|
||||
}
|
||||
|
@ -1805,7 +1810,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
require(["chaptercardbuilder"], function (chaptercardbuilder) {
|
||||
chaptercardbuilder.buildChapterCards(item, chapters, {
|
||||
itemsContainer: scenesContent,
|
||||
width: 400,
|
||||
backdropShape: "overflowBackdrop",
|
||||
squareShape: "overflowSquare"
|
||||
});
|
||||
|
@ -1858,7 +1862,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
itemsContainer: castContent,
|
||||
coverImage: true,
|
||||
serverId: item.ServerId,
|
||||
width: 160,
|
||||
shape: "overflowPortrait"
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
}, {
|
||||
href: "metadatanfo.html",
|
||||
name: Globalize.translate("TabNfoSettings")
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
return function(view, params) {
|
||||
|
@ -27,7 +27,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
view.querySelector("#chkSaveMetadataHidden").checked = config.SaveMetadataHidden;
|
||||
});
|
||||
ApiClient.getNamedConfiguration("metadata").then(function(metadata) {
|
||||
loadMetadataConfig(this, metadata)
|
||||
loadMetadataConfig(this, metadata);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -67,5 +67,5 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "emby-itemscontainer"], function (cardBuilder, imageLoader, libraryBrowser, loading, events) {
|
||||
define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "userSettings", "emby-itemscontainer"], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params, tabContent) {
|
||||
|
@ -7,12 +7,15 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em
|
|||
pageData = {
|
||||
query: {
|
||||
StartIndex: 0,
|
||||
Limit: 100,
|
||||
Fields: "PrimaryImageAspectRatio"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
|
@ -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,7 +53,9 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc
|
|||
}
|
||||
|
||||
function getBackdropShape() {
|
||||
return enableScrollX() ? "overflowBackdrop" : "backdrop"
|
||||
return enableScrollX() ? "overflowBackdrop" : "backdrop";
|
||||
}
|
||||
|
||||
function renderActiveRecordings(context, promise) {
|
||||
|
|
|
@ -256,6 +256,7 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl
|
|||
|
||||
if (virtualFolder.PrimaryImageItemId) {
|
||||
imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, {
|
||||
maxWidth: Math.round(dom.getScreenWidth() * 0.40),
|
||||
type: "Primary"
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
html += "<option value=''></option>";
|
||||
for (var i = 0, length = languages.length; i < length; i++) {
|
||||
var culture = languages[i];
|
||||
html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>"
|
||||
html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = html
|
||||
})
|
||||
select.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
function populateCountries(select) {
|
||||
|
@ -19,25 +19,25 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
html += "<option value=''></option>";
|
||||
for (var i = 0, length = allCountries.length; i < length; i++) {
|
||||
var culture = allCountries[i];
|
||||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>"
|
||||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = 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()
|
||||
})
|
||||
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
|
||||
config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
|
||||
}), !1;
|
||||
}
|
||||
|
||||
function getTabs() {
|
||||
|
@ -53,12 +53,12 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
}, {
|
||||
href: "metadatanfo.html",
|
||||
name: Globalize.translate("TabNfoSettings")
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
$(document).on("pageinit", "#metadataImagesConfigurationPage", function() {
|
||||
$(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit)
|
||||
$(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit);
|
||||
}).on("pageshow", "#metadataImagesConfigurationPage", function() {
|
||||
libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this)
|
||||
})
|
||||
libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, appHost) {
|
||||
define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "userSettings", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params, tabContent) {
|
||||
|
@ -16,11 +16,15 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB
|
|||
Fields: "PrimaryImageAspectRatio,SortName",
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary,Backdrop,Banner,Thumb",
|
||||
StartIndex: 0,
|
||||
Limit: pageSize
|
||||
StartIndex: 0
|
||||
},
|
||||
view: libraryBrowser.getSavedView(key) || "Poster"
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -180,7 +188,6 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB
|
|||
}
|
||||
|
||||
var self = this;
|
||||
var pageSize = 100;
|
||||
var data = {};
|
||||
var isLoading = false;
|
||||
|
||||
|
|
|
@ -184,12 +184,12 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader
|
|||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -250,9 +254,13 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser",
|
|||
ImageTypeLimit: 1,
|
||||
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") {
|
||||
|
|
|
@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
|
|||
|
||||
switch (recommendation.RecommendationType) {
|
||||
case "SimilarToRecentlyPlayed":
|
||||
title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "SimilarToLikedItem":
|
||||
title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "HasDirectorFromRecentlyPlayed":
|
||||
case "HasLikedDirector":
|
||||
title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "HasActorFromRecentlyPlayed":
|
||||
case "HasLikedActor":
|
||||
title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationStarring", recommendation.BaselineItemName);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) {
|
||||
define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "userSettings", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params, tabContent) {
|
||||
|
@ -16,11 +16,15 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "
|
|||
Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo",
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary,Backdrop,Banner,Thumb",
|
||||
StartIndex: 0,
|
||||
Limit: pageSize
|
||||
StartIndex: 0
|
||||
},
|
||||
view: libraryBrowser.getSavedView(key) || "Poster"
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -168,7 +176,6 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "
|
|||
}
|
||||
|
||||
var self = this;
|
||||
var pageSize = 100;
|
||||
var data = {};
|
||||
var isLoading = false;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) {
|
||||
define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "userSettings", "emby-itemscontainer"], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params, tabContent) {
|
||||
|
@ -30,11 +30,15 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser
|
|||
Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo",
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary,Backdrop,Banner,Thumb",
|
||||
StartIndex: 0,
|
||||
Limit: pageSize
|
||||
StartIndex: 0
|
||||
},
|
||||
view: libraryBrowser.getSavedView(key) || "Poster"
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -175,7 +183,6 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser
|
|||
var savedQueryKey;
|
||||
var pageData;
|
||||
var self = this;
|
||||
var pageSize = 100;
|
||||
var isLoading = false;
|
||||
|
||||
self.showFilterMenu = function () {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) {
|
||||
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) {
|
||||
|
@ -7,17 +7,22 @@ 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
|
||||
},
|
||||
query: queryValues,
|
||||
view: libraryBrowser.getSavedView(key) || "Poster"
|
||||
};
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,12 +107,12 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f
|
|||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby-itemscontainer"], function (events, libraryBrowser, imageLoader, listView, loading) {
|
||||
define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "userSettings", "emby-itemscontainer"], function (events, libraryBrowser, imageLoader, listView, loading, userSettings) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params, tabContent) {
|
||||
|
@ -14,12 +14,16 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby-
|
|||
IncludeItemTypes: "Audio",
|
||||
Recursive: true,
|
||||
Fields: "AudioInfo,ParentId",
|
||||
Limit: 100,
|
||||
StartIndex: 0,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary"
|
||||
}
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -334,18 +334,24 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
|
|||
|
||||
if (item) {
|
||||
var imgUrl = seriesImageUrl(item, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Primary"
|
||||
}) || seriesImageUrl(item, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Thumb"
|
||||
}) || imageUrl(item, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Primary"
|
||||
});
|
||||
|
||||
if (!imgUrl && secondaryItem && (imgUrl = seriesImageUrl(secondaryItem, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Primary"
|
||||
}) || seriesImageUrl(secondaryItem, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Thumb"
|
||||
}) || imageUrl(secondaryItem, {
|
||||
maxWidth: osdPoster.clientWidth * 2,
|
||||
type: "Primary"
|
||||
})), imgUrl) {
|
||||
return void (osdPoster.innerHTML = '<img src="' + imgUrl + '" />');
|
||||
|
@ -665,7 +671,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
|
|||
}
|
||||
|
||||
function onTimeUpdate(e) {
|
||||
if (isEnabled) {
|
||||
// Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint
|
||||
if (isEnabled && currentItem) {
|
||||
var now = new Date().getTime();
|
||||
|
||||
if (!(now - lastUpdateTime < 700)) {
|
||||
|
@ -1145,7 +1152,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
|
|||
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();
|
||||
}
|
||||
|
@ -1154,7 +1161,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
|
|||
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();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue