1
0
Fork 0
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:
MrTimscampi 2020-04-12 22:11:28 +02:00
commit d2cd47d3e3
194 changed files with 5770 additions and 4213 deletions

View file

@ -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'

View file

@ -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
View file

@ -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

View file

@ -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
View file

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View file

@ -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);

View file

@ -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\""
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;
});

View file

@ -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 || "";

View file

@ -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) {

View file

@ -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);

View file

@ -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
};

View file

@ -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
}));
});

View file

@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) {
currentSlideshow = null;
}
};
}
};
});

File diff suppressed because it is too large Load diff

View file

@ -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

View 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);
});
}
};
}
});

View file

@ -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");

View file

@ -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);

View file

@ -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;

View file

@ -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();
});
};
}
});

View file

@ -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();
}
};
});

View file

@ -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) {

View file

@ -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;
});

View file

@ -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);

View file

@ -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" />

View file

@ -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
};

View file

@ -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;
}));
}
}

View file

@ -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();
}

View file

@ -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 || "";

View file

@ -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">';
}

View file

@ -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);
});
}
}
}

View file

@ -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);
};

View file

@ -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 () {

View file

@ -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
};
});

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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
};

View file

@ -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) {

View file

@ -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;

View file

@ -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 + '"/>';

View file

@ -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;

View file

@ -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
}
};
});

View file

@ -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>

View file

@ -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;
}

View file

@ -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"
};

View file

@ -188,5 +188,5 @@ define(["pluginManager"], function (pluginManager) {
}
}
};
}
};
});

View file

@ -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">';

View file

@ -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 =
[

View file

@ -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();
}
});
}

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);
}
};
});

View file

@ -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);
});
});
});
};
};
}
});

View file

@ -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);

View file

@ -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");

View file

@ -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);
}

View file

@ -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
};

View file

@ -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();

View file

@ -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 () {

View file

@ -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 = "&#xE037;";
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);
}
};

View file

@ -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;
};

View file

@ -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>

View file

@ -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;
});

View file

@ -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();

View file

@ -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
View file

@ -0,0 +1,3 @@
{
"multiserver": true
}

View file

@ -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) {

View file

@ -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
});

View file

@ -29,5 +29,5 @@ define(["datetime", "loading", "apphost", "listViewStyle", "emby-button", "flexS
loading.hide();
});
});
}
};
});

View file

@ -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);
});
}
};
});

View file

@ -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("");

View file

@ -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) : "&nbsp;";
html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : "&nbsp;";
html += "</div>";
html += "</div>";
html += "</div>";

View file

@ -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({

View file

@ -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;

View file

@ -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 = "&#xE037;";
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);
});
}
};
});

View file

@ -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 += "&nbsp;";

View file

@ -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>";

View file

@ -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) {

View file

@ -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"
});
});

View file

@ -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", "
}
});
});
}
};
});

View file

@ -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);
}

View file

@ -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) {

View file

@ -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"
});
}

View file

@ -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);
});
});

View file

@ -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;

View file

@ -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();
};

View file

@ -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") {

View file

@ -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;
}

View file

@ -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;

View file

@ -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 () {

View file

@ -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);
}

View file

@ -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();
};

View file

@ -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;

View file

@ -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);
}

View file

@ -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