1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge branch 'master' into improve-builds

This commit is contained in:
Joshua M. Boniface 2020-04-09 12:17:51 -04:00 committed by GitHub
commit 446dadc0eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
140 changed files with 4232 additions and 3479 deletions

View file

@ -2,8 +2,7 @@ trigger:
batch: true batch: true
branches: branches:
include: include:
- master - '*'
- release-*
tags: tags:
include: include:
- '*' - '*'
@ -13,12 +12,9 @@ pr:
- '*' - '*'
jobs: jobs:
- job: build - job: Build
displayName: 'Build' displayName: 'Build'
pool:
vmImage: 'ubuntu-latest'
strategy: strategy:
matrix: matrix:
Development: Development:
@ -27,13 +23,15 @@ jobs:
BuildConfiguration: production BuildConfiguration: production
Standalone: Standalone:
BuildConfiguration: standalone BuildConfiguration: standalone
maxParallel: 3
pool:
vmImage: 'ubuntu-latest'
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
displayName: 'Install Node' displayName: 'Install Node'
inputs: inputs:
versionSpec: '10.x' versionSpec: '12.x'
- task: Cache@2 - task: Cache@2
displayName: 'Check Cache' displayName: 'Check Cache'
@ -63,16 +61,14 @@ jobs:
- script: 'mv dist jellyfin-web' - script: 'mv dist jellyfin-web'
displayName: 'Rename Directory' displayName: 'Rename Directory'
condition: succeeded()
- task: PublishPipelineArtifact@1 - task: PublishPipelineArtifact@1
displayName: 'Publish Release' displayName: 'Publish Release'
condition: succeeded()
inputs: inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web' targetPath: '$(Build.SourcesDirectory)/jellyfin-web'
artifactName: 'jellyfin-web-$(BuildConfiguration)' artifactName: 'jellyfin-web-$(BuildConfiguration)'
- job: lint - job: Lint
displayName: 'Lint' displayName: 'Lint'
pool: pool:
@ -82,7 +78,7 @@ jobs:
- task: NodeTool@0 - task: NodeTool@0
displayName: 'Install Node' displayName: 'Install Node'
inputs: inputs:
versionSpec: '10.x' versionSpec: '12.x'
- task: Cache@2 - task: Cache@2
displayName: 'Check Cache' displayName: 'Check Cache'
@ -95,7 +91,7 @@ jobs:
displayName: 'Install Dependencies' displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true') condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn run lint' - script: 'yarn run lint --quiet'
displayName: 'Run ESLint' displayName: 'Run ESLint'
- script: 'yarn run stylelint' - script: 'yarn run stylelint'

View file

@ -1,15 +1,31 @@
env: env:
es6: true
browser: true
amd: 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
globals: globals:
# New browser globals # Browser globals
DataView: readonly
MediaMetadata: readonly MediaMetadata: readonly
Promise: readonly
# Deprecated browser globals
DocumentTouch: readonly
# Tizen globals # Tizen globals
tizen: readonly tizen: readonly
webapis: readonly webapis: readonly
@ -18,7 +34,6 @@ globals:
# Dependency globals # Dependency globals
$: readonly $: readonly
jQuery: readonly jQuery: readonly
queryString: readonly
requirejs: readonly requirejs: readonly
# Jellyfin globals # Jellyfin globals
ApiClient: writable ApiClient: writable
@ -34,8 +49,7 @@ globals:
getWindowLocationSearch: writable getWindowLocationSearch: writable
Globalize: writable Globalize: writable
Hls: writable Hls: writable
humaneDate: writable dfnshelper: writable
humaneElapsed: writable
LibraryMenu: writable LibraryMenu: writable
LinkParser: writable LinkParser: writable
LiveTvHelpers: writable LiveTvHelpers: writable
@ -46,9 +60,6 @@ globals:
UserParentalControlPage: writable UserParentalControlPage: writable
Windows: readonly Windows: readonly
extends:
- eslint:recommended
rules: rules:
block-spacing: ["error"] block-spacing: ["error"]
brace-style: ["error"] brace-style: ["error"]
@ -63,9 +74,14 @@ rules:
no-multiple-empty-lines: ["error", { "max": 1 }] no-multiple-empty-lines: ["error", { "max": 1 }]
no-trailing-spaces: ["error"] no-trailing-spaces: ["error"]
one-var: ["error", "never"] one-var: ["error", "never"]
semi: ["warn"] semi: ["error"]
space-before-blocks: ["error"] space-before-blocks: ["error"]
# TODO: Fix warnings and remove these rules # TODO: Fix warnings and remove these rules
no-redeclare: ["warn"] no-redeclare: ["warn"]
no-unused-vars: ["warn"] no-unused-vars: ["warn"]
no-useless-escape: ["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"]

583
.gitignore vendored
View file

@ -1,580 +1,11 @@
# config
config.json
# Created by https://www.gitignore.io/api/node,rider,macos,linux,windows,visualstudio,visualstudiocode # npm
# 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
dist dist
web web
node_modules node_modules
# ide
.idea
.vscode

3
babel.config.json Normal file
View file

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

View file

@ -18,16 +18,42 @@ const stream = require('webpack-stream');
const inject = require('gulp-inject'); const inject = require('gulp-inject');
const postcss = require('gulp-postcss'); const postcss = require('gulp-postcss');
const sass = require('gulp-sass'); const sass = require('gulp-sass');
const gulpif = require('gulp-if');
const lazypipe = require('lazypipe');
sass.compiler = require('node-sass') sass.compiler = require('node-sass');
let config;
if (mode.production()) { if (mode.production()) {
var config = require('./webpack.prod.js'); config = require('./webpack.prod.js');
} else { } 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() { function serve() {
browserSync.init({ browserSync.init({
server: { server: {
@ -36,51 +62,99 @@ function serve() {
port: 8080 port: 8080
}); });
watch(['src/**/*.js', '!src/bundle.js'], javascript); let events = ['add', 'change'];
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);
}
function standalone() { watch(options.javascript.query).on('all', function (event, path) {
return src(['src/standalone.js', 'src/scripts/apploader.js'], { base: './src/' }) if (events.includes(event)) {
.pipe(concat('scripts/apploader.js')) javascript(path);
.pipe(dest('dist/')); }
});
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() { function clean() {
return del(['dist/']); return del(['dist/']);
} }
function javascript() { let pipelineJavascript = lazypipe()
return src(['src/**/*.js', '!src/bundle.js'], { base: './src/' }) .pipe(function () {
.pipe(mode.development(sourcemaps.init({ loadMaps: true }))) return mode.development(sourcemaps.init({ loadMaps: true }));
.pipe(babel({ })
.pipe(function () {
return babel({
presets: [ presets: [
['@babel/preset-env'] ['@babel/preset-env']
] ]
})) });
.pipe(terser({ })
.pipe(function () {
return terser({
keep_fnames: true, keep_fnames: true,
mangle: false 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(dest('dist/'))
.pipe(browserSync.stream()); .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() { function webpack() {
return stream(config) return stream(config)
.pipe(dest('dist/')) .pipe(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function css() { function css(query) {
return src(['src/**/*.css', 'src/**/*.scss'], { base: './src/' }) return src(typeof query !== 'function' ? query : options.css.query, { base: './src/' })
.pipe(mode.development(sourcemaps.init({ loadMaps: true }))) .pipe(mode.development(sourcemaps.init({ loadMaps: true })))
.pipe(sass().on('error', sass.logError)) .pipe(sass().on('error', sass.logError))
.pipe(postcss()) .pipe(postcss())
@ -89,28 +163,28 @@ function css() {
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function html() { function html(query) {
return src(['src/**/*.html', '!src/index.html'], { base: './src/' }) return src(typeof query !== 'function' ? query : options.html.query, { base: './src/' })
.pipe(mode.production(htmlmin({ collapseWhitespace: true }))) .pipe(mode.production(htmlmin({ collapseWhitespace: true })))
.pipe(dest('dist/')) .pipe(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function images() { function images(query) {
return src(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], { base: './src/' }) return src(typeof query !== 'function' ? query : options.images.query, { base: './src/' })
.pipe(mode.production(imagemin())) .pipe(mode.production(imagemin()))
.pipe(dest('dist/')) .pipe(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function copy() { function copy(query) {
return src(['src/**/*.json', 'src/**/*.ico'], { base: './src/' }) return src(typeof query !== 'function' ? query : options.copy.query, { base: './src/' })
.pipe(dest('dist/')) .pipe(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function injectBundle() { function injectBundle() {
return src('src/index.html', { base: './src/' }) return src(options.injectBundle.query, { base: './src/' })
.pipe(inject( .pipe(inject(
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true } src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
)) ))
@ -118,6 +192,10 @@ function injectBundle() {
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle) function build(standalone) {
exports.standalone = series(exports.default, standalone) return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy), injectBundle);
exports.serve = series(exports.standalone, serve) }
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", "license": "GPL-2.0-or-later",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.6", "@babel/core": "^7.8.6",
"@babel/plugin-transform-modules-amd": "^7.8.3",
"@babel/polyfill": "^7.8.7", "@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.8.6", "@babel/preset-env": "^7.8.6",
"autoprefixer": "^9.7.4", "autoprefixer": "^9.7.4",
@ -17,12 +18,16 @@
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"del": "^5.1.0", "del": "^5.1.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"file-loader": "^5.0.2", "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": "^4.0.2",
"gulp-babel": "^8.0.0", "gulp-babel": "^8.0.0",
"gulp-cli": "^2.2.0", "gulp-cli": "^2.2.0",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-htmlmin": "^5.0.1", "gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^7.1.0", "gulp-imagemin": "^7.1.0",
"gulp-inject": "^5.0.5", "gulp-inject": "^5.0.5",
"gulp-mode": "^1.0.2", "gulp-mode": "^1.0.2",
@ -30,7 +35,8 @@
"gulp-sass": "^4.0.2", "gulp-sass": "^4.0.2",
"gulp-sourcemaps": "^2.6.5", "gulp-sourcemaps": "^2.6.5",
"gulp-terser": "^1.2.0", "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", "node-sass": "^4.13.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
@ -48,7 +54,9 @@
}, },
"dependencies": { "dependencies": {
"alameda": "^1.4.0", "alameda": "^1.4.0",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"date-fns": "^2.11.1",
"document-register-element": "^1.14.3", "document-register-element": "^1.14.3",
"flv.js": "^1.5.0", "flv.js": "^1.5.0",
"hls.js": "^0.13.1", "hls.js": "^0.13.1",
@ -56,14 +64,13 @@
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.4.1", "jquery": "^3.4.1",
"jstree": "^3.3.7", "jstree": "^3.3.7",
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova",
"libjass": "^0.11.0",
"material-design-icons-iconfont": "^5.0.1", "material-design-icons-iconfont": "^5.0.1",
"native-promise-only": "^0.8.0-a", "native-promise-only": "^0.8.0-a",
"page": "^1.11.5", "page": "^1.11.5",
"query-string": "^6.11.1", "query-string": "^6.11.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"shaka-player": "^2.5.9", "shaka-player": "^2.5.10",
"sortablejs": "^1.10.2", "sortablejs": "^1.10.2",
"swiper": "^5.3.1", "swiper": "^5.3.1",
"webcomponents.js": "^0.7.24", "webcomponents.js": "^0.7.24",
@ -72,6 +79,28 @@
"babel": { "babel": {
"presets": [ "presets": [
"@babel/preset-env" "@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/scrollManager.js",
"src/components/sanatizefilename.js",
"src/scripts/settings/webSettings.js",
"src/scripts/settings/appSettings.js",
"src/scripts/settings/userSettings.js",
"src/scripts/imagehelper.js",
"src/scripts/dfnshelper.js"
],
"plugins": [
"@babel/plugin-transform-modules-amd"
]
}
] ]
}, },
"browserslist": [ "browserslist": [

View file

@ -63,6 +63,10 @@ progress[aria-valuenow]::before {
} }
.adminDrawerLogo { .adminDrawerLogo {
display: none;
}
.layout-mobile .adminDrawerLogo {
padding: 1.5em 1em 1.2em; padding: 1.5em 1em 1.2em;
border-bottom: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0;
margin-bottom: 1em; margin-bottom: 1em;
@ -161,7 +165,7 @@ div[data-role=controlgroup] a.ui-btn-active {
@media all and (min-width: 40em) { @media all and (min-width: 40em) {
.content-primary { .content-primary {
padding-top: 7em; padding-top: 4.6em;
} }
.withTabs .content-primary { .withTabs .content-primary {

View file

@ -21,7 +21,7 @@
} }
.libraryPage { .libraryPage {
padding-top: 7em !important; padding-top: 7em;
} }
.itemDetailPage { .itemDetailPage {
@ -242,7 +242,7 @@
} }
.mainDrawer-scrollContainer { .mainDrawer-scrollContainer {
padding-bottom: 10vh; margin-bottom: 10vh;
} }
@media all and (min-width: 40em) { @media all and (min-width: 40em) {
@ -313,7 +313,7 @@
} }
.dashboardDocument .mainDrawer-scrollContainer { .dashboardDocument .mainDrawer-scrollContainer {
margin-top: 6em !important; margin-top: 4.6em !important;
} }
} }
@ -1119,3 +1119,50 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.itemsViewSettingsContainer > .button-flat { .itemsViewSettingsContainer > .button-flat {
margin: 0; 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,7 @@ _define("document-register-element", function() {
// fetch // fetch
var fetch = require("whatwg-fetch"); var fetch = require("whatwg-fetch");
_define("fetch", function() { _define("fetch", function() {
return fetch return fetch;
}); });
// query-string // query-string
@ -84,13 +84,6 @@ _define("webcomponents", function() {
return webcomponents; return webcomponents;
}); });
// libjass
var libjass = require("libjass");
require("libjass/libjass.css");
_define("libjass", function() {
return libjass;
});
// libass-wasm // libass-wasm
var libass_wasm = require("libass-wasm"); var libass_wasm = require("libass-wasm");
_define("JavascriptSubtitlesOctopus", function() { _define("JavascriptSubtitlesOctopus", function() {
@ -119,3 +112,20 @@ var polyfill = require("@babel/polyfill/dist/polyfill");
_define("polyfill", function () { _define("polyfill", function () {
return polyfill; return polyfill;
}); });
// domtokenlist-shim
var classlist = require("classlist.js");
_define("classlist-polyfill", function () {
return classlist;
});
// Date-FNS
var date_fns = require("date-fns");
_define("date-fns", function () {
return date_fns;
});
var date_fns_locale = require("date-fns/locale");
_define("date-fns/locale", function () {
return date_fns_locale;
});

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"; "use strict";
function getEntryHtml(entry, apiClient) { 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, { 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", type: "Primary",
tag: entry.UserPrimaryImageTag 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 { } else {
html += '<i class="listItemIcon material-icons" style="background-color:' + color + '">' + icon + '</i>'; 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 += entry.Name;
html += "</div>"; html += "</div>";
html += '<div class="listItemBodyText secondary">'; html += '<div class="listItemBodyText secondary">';
var date = datetime.parseISO8601Date(entry.Date, true); html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
html += datetime.toLocaleString(date).toLowerCase();
html += "</div>"; html += "</div>";
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">'; html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += entry.ShortOverview || ""; html += entry.ShortOverview || "";

View file

@ -511,9 +511,16 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
return baseRoute; return baseRoute;
} }
var popstateOccurred = false;
window.addEventListener('popstate', function () {
popstateOccurred = true;
});
function getHandler(route) { function getHandler(route) {
return function (ctx, next) { return function (ctx, next) {
ctx.isBack = popstateOccurred;
handleRoute(ctx, next, route); handleRoute(ctx, next, route);
popstateOccurred = false;
}; };
} }
@ -570,8 +577,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
function showDirect(path) { function showDirect(path) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
resolveOnNextShow = resolve, page.show(baseUrl()+path) resolveOnNextShow = resolve, page.show(baseUrl()+path);
}) });
} }
function show(path, options) { 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"; "use strict";
function getBaseProfileOptions(item) { function getBaseProfileOptions(item) {
@ -276,15 +276,17 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
features.push("otherapppromotions"); features.push("otherapppromotions");
features.push("displaymode"); features.push("displaymode");
features.push("targetblank"); features.push("targetblank");
// allows users to connect to more than one server
//features.push("multiserver");
features.push("screensaver"); 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"); features.push("subtitleappearancesettings");
} }
if (!browser.orsay && !browser.tizen) { if (!browser.orsay) {
features.push("subtitleburnsettings"); features.push("subtitleburnsettings");
} }
@ -381,7 +383,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
return window.NativeShell.AppHost.getDefaultLayout(); return window.NativeShell.AppHost.getDefaultLayout();
} }
return getDefaultLayout() return getDefaultLayout();
}, },
getDeviceProfile: getDeviceProfile, getDeviceProfile: getDeviceProfile,
init: function () { init: function () {

View file

@ -1,22 +1,29 @@
define(["focusManager", "layoutManager"], function (focusManager, layoutManager) { /* eslint-disable indent */
"use strict";
/**
* Module for performing auto-focus.
* @module components/autoFocuser
*/
import focusManager from "focusManager";
import layoutManager from "layoutManager";
/** /**
* Previously selected element. * 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; return layoutManager.tv;
} }
/** /**
* Start AutoFocuser * Start AutoFocuser.
*/ */
function enable() { export function enable() {
if (!isEnabled()) { if (!isEnabled()) {
return; return;
} }
@ -28,24 +35,19 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
console.debug("AutoFocuser enabled"); 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. * 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()) { if (!isEnabled()) {
return; return null;
} }
container = container || document.body; container = container || document.body;
var candidates = []; let candidates = [];
if (activeElement) { if (activeElement) {
// These elements are recreated // These elements are recreated
@ -62,10 +64,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
candidates.push(activeElement); candidates.push(activeElement);
} }
candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnResume"))); candidates = candidates.concat(Array.from(container.querySelectorAll(".btnResume")));
candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnPlay"))); candidates = candidates.concat(Array.from(container.querySelectorAll(".btnPlay")));
var focusedElement; let focusedElement;
candidates.every(function (element) { candidates.every(function (element) {
if (focusManager.isCurrentlyFocusable(element)) { if (focusManager.isCurrentlyFocusable(element)) {
@ -79,7 +81,7 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
if (!focusedElement) { if (!focusedElement) {
// FIXME: Multiple itemsContainers // FIXME: Multiple itemsContainers
var itemsContainer = container.querySelector(".itemsContainer"); const itemsContainer = container.querySelector(".itemsContainer");
if (itemsContainer) { if (itemsContainer) {
focusedElement = focusManager.autoFocus(itemsContainer); focusedElement = focusManager.autoFocus(itemsContainer);
@ -93,9 +95,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
return focusedElement; return focusedElement;
} }
return { /* eslint-enable indent */
export default {
isEnabled: isEnabled, isEnabled: isEnabled,
enable: enable, enable: enable,
autoFocus: autoFocus autoFocus: autoFocus
}; };
});

View file

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

File diff suppressed because it is too large Load diff

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"; "use strict";
return function (options) { 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) { function mapChannel(button, channelId, providerChannelId) {
loading.show(); loading.show();
var providerId = options.providerId; var providerId = options.providerId;
@ -26,7 +15,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
}, },
dataType: "json" dataType: "json"
}).then(function (mapping) { }).then(function (mapping) {
var listItem = parentWithClass(button, "listItem"); var listItem = dom.parentWithClass(button, "listItem");
button.setAttribute("data-providerid", mapping.ProviderChannelId); button.setAttribute("data-providerid", mapping.ProviderChannelId);
listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName); listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
loading.hide(); loading.hide();
@ -34,7 +23,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
} }
function onChannelsElementClick(e) { function onChannelsElementClick(e) {
var btnMap = parentWithClass(e.target, "btnMap"); var btnMap = dom.parentWithClass(e.target, "btnMap");
if (btnMap) { if (btnMap) {
var channelId = btnMap.getAttribute("data-id"); var channelId = btnMap.getAttribute("data-id");

View file

@ -188,9 +188,9 @@ define(['events'], function (events) {
return apiClient.getEndpointInfo().then(function (endpoint) { return apiClient.getEndpointInfo().then(function (endpoint) {
if (endpoint.IsInNetwork) { if (endpoint.IsInNetwork) {
return apiClient.getPublicSystemInfo().then(function (info) { return apiClient.getPublicSystemInfo().then(function (info) {
var localAddress = info.LocalAddress var localAddress = info.LocalAddress;
if (!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; localAddress = serverAddress;
} }
addToCache(serverAddress, localAddress); 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'; 'use strict';
var currentServerId; 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) { function onSubmit(e) {
loading.show(); loading.show();
var panel = parentWithClass(this, 'dialog'); var panel = dom.parentWithClass(this, 'dialog');
var collectionId = panel.querySelector('#selectCollectionToAddTo').value; var collectionId = panel.querySelector('#selectCollectionToAddTo').value;

View file

@ -7,11 +7,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
systemInfo = info; systemInfo = info;
return info; return info;
} }
) );
} }
function onDialogClosed() { function onDialogClosed() {
loading.hide() loading.hide();
} }
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) { function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
@ -24,7 +24,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
var promises = []; var promises = [];
if ("Network" === path) { if ("Network" === path) {
promises.push(ApiClient.getNetworkDevices()) promises.push(ApiClient.getNetworkDevices());
} else { } else {
if (path) { if (path) {
promises.push(ApiClient.getDirectoryContents(path, fileOptions)); 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/>" : ""; var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
html += '<div class="infoBanner" style="margin-bottom:1.5em;">'; html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
html += instruction; 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()) { if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) {
html += "<br/>"; html += "<br/>";
html += "<br/>"; html += "<br/>";
@ -101,7 +101,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
html += Globalize.translate("MessageDirectoryPickerLinuxInstruction"); html += Globalize.translate("MessageDirectoryPickerLinuxInstruction");
html += "<br/>"; html += "<br/>";
} }
html += "</div>" html += "</div>";
} }
html += '<form style="margin:auto;">'; html += '<form style="margin:auto;">';
html += '<div class="inputContainer" style="display: flex; align-items: center;">'; 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) { function alertText(text) {
alertTextWithOptions({ alertTextWithOptions({
text: text text: text
}) });
} }
function alertTextWithOptions(options) { function alertTextWithOptions(options) {
require(["alert"], function(alert) { require(["alert"], function(alert) {
alert(options) alert(options);
}) });
} }
function validatePath(path, validateWriteable, apiClient) { function validatePath(path, validateWriteable, apiClient) {
@ -163,21 +163,20 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
} }
}).catch(function(response) { }).catch(function(response) {
if (response) { if (response) {
// TODO All alerts (across the project), should use Globalize.translate()
if (response.status === 404) { 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(); return Promise.reject();
} }
if (response.status === 500) { if (response.status === 500) {
if (validateWriteable) { if (validateWriteable) {
alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again."); alertText(Globalize.translate("WriteAccessRequired"));
} else { } 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")) { if (lnkPath.classList.contains("lnkFile")) {
content.querySelector("#txtDirectoryPickerPath").value = path; content.querySelector("#txtDirectoryPickerPath").value = path;
} else { } 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); dlg.addEventListener("close", onDialogClosed);
dialogHelper.open(dlg); dialogHelper.open(dlg);
dlg.querySelector(".btnCloseDialog").addEventListener("click", function() { dlg.querySelector(".btnCloseDialog").addEventListener("click", function() {
dialogHelper.close(dlg) dialogHelper.close(dlg);
}); });
currentDialog = dlg; currentDialog = dlg;
dlg.querySelector("#txtDirectoryPickerPath").value = initialPath; dlg.querySelector("#txtDirectoryPickerPath").value = initialPath;
@ -294,9 +293,9 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
if (currentDialog) { if (currentDialog) {
dialogHelper.close(currentDialog); dialogHelper.close(currentDialog);
} }
} };
} }
var systemInfo; var systemInfo;
return directoryBrowser return directoryBrowser;
}); });

View file

@ -63,7 +63,7 @@
<div class="selectContainer fldDateTimeLocale hide"> <div class="selectContainer fldDateTimeLocale hide">
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}"> <select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
<option value="">${AutoBasedOnLanguageSetting}</option> <option value="">${Auto}</option>
<option value="ar">Arabic</option> <option value="ar">Arabic</option>
<option value="be-BY">Belarusian (Belarus)</option> <option value="be-BY">Belarusian (Belarus)</option>
<option value="bg-BG">Bulgarian (Bulgaria)</option> <option value="bg-BG">Bulgarian (Bulgaria)</option>

View file

@ -1,8 +1,18 @@
define([], function () { /* eslint-disable indent */
'use strict';
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))) { while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) {
elem = elem.parentNode; elem = elem.parentNode;
@ -14,8 +24,13 @@ define([], function () {
return elem; 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 // accept both string and array passed in
if (!Array.isArray(tagNames)) { if (!Array.isArray(tagNames)) {
tagNames = [tagNames]; tagNames = [tagNames];
@ -32,9 +47,14 @@ define([], function () {
return elem; 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) { function containsAnyClass(classList, classNames) {
for (let i = 0, length = classNames.length; i < length; i++) {
for (var i = 0, length = classNames.length; i < length; i++) {
if (classList.contains(classNames[i])) { if (classList.contains(classNames[i])) {
return true; return true;
} }
@ -42,8 +62,13 @@ define([], function () {
return false; 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 // accept both string and array passed in
if (!Array.isArray(classNames)) { if (!Array.isArray(classNames)) {
classNames = [classNames]; classNames = [classNames];
@ -60,9 +85,9 @@ define([], function () {
return elem; return elem;
} }
var supportsCaptureOption = false; let supportsCaptureOption = false;
try { try {
var opts = Object.defineProperty({}, 'capture', { const opts = Object.defineProperty({}, 'capture', {
// eslint-disable-next-line getter-return // eslint-disable-next-line getter-return
get: function () { get: function () {
supportsCaptureOption = true; supportsCaptureOption = true;
@ -73,29 +98,58 @@ define([], function () {
console.debug('error checking capture support'); 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) { if (!supportsCaptureOption) {
optionsOrCapture = options.capture; optionsOrCapture = optionsOrCapture.capture;
} }
target.addEventListener(type, handler, optionsOrCapture); 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) { if (!supportsCaptureOption) {
optionsOrCapture = options.capture; optionsOrCapture = optionsOrCapture.capture;
} }
target.removeEventListener(type, handler, optionsOrCapture); 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() { function clearWindowSize() {
windowSize = null; windowSize = null;
} }
function getWindowSize() { /**
* Returns window size.
* @returns {Object} Window size.
*/
export function getWindowSize() {
if (!windowSize) { if (!windowSize) {
windowSize = { windowSize = {
innerHeight: window.innerHeight, innerHeight: window.innerHeight,
@ -104,46 +158,60 @@ define([], function () {
if (!windowSizeEventsBound) { if (!windowSizeEventsBound) {
windowSizeEventsBound = true; windowSizeEventsBound = true;
addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true }); addEventListener(window, "orientationchange", clearWindowSize, { passive: true });
addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true }); addEventListener(window, 'resize', clearWindowSize, { passive: true });
} }
} }
return windowSize; return windowSize;
} }
var standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680]; /**
function getScreenWidth() { * Standard screen widths.
var width = window.innerWidth; */
var height = window.innerHeight; 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) { if (height > width) {
width = height * (16.0 / 9.0); width = height * (16.0 / 9.0);
} }
var closest = standardWidths.sort(function (a, b) { const closest = standardWidths.sort(function (a, b) {
return Math.abs(width - a) - Math.abs(width - b); return Math.abs(width - a) - Math.abs(width - b);
})[0]; })[0];
return closest; return closest;
} }
var _animationEvent; /**
function whichAnimationEvent() { * 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) { if (_animationEvent) {
return _animationEvent; return _animationEvent;
} }
var t; const el = document.createElement("div");
var el = document.createElement("div"); const animations = {
var animations = {
"animation": "animationend", "animation": "animationend",
"OAnimation": "oAnimationEnd", "OAnimation": "oAnimationEnd",
"MozAnimation": "animationend", "MozAnimation": "animationend",
"WebkitAnimation": "webkitAnimationEnd" "WebkitAnimation": "webkitAnimationEnd"
}; };
for (t in animations) { for (let t in animations) {
if (el.style[t] !== undefined) { if (el.style[t] !== undefined) {
_animationEvent = animations[t]; _animationEvent = animations[t];
return animations[t]; return animations[t];
@ -154,26 +222,36 @@ define([], function () {
return _animationEvent; 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'); 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) { if (_transitionEvent) {
return _transitionEvent; return _transitionEvent;
} }
var t; const el = document.createElement("div");
var el = document.createElement("div"); const transitions = {
var transitions = {
"transition": "transitionend", "transition": "transitionend",
"OTransition": "oTransitionEnd", "OTransition": "oTransitionEnd",
"MozTransition": "transitionend", "MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd" "WebkitTransition": "webkitTransitionEnd"
}; };
for (t in transitions) { for (let t in transitions) {
if (el.style[t] !== undefined) { if (el.style[t] !== undefined) {
_transitionEvent = transitions[t]; _transitionEvent = transitions[t];
return transitions[t]; return transitions[t];
@ -184,16 +262,17 @@ define([], function () {
return _transitionEvent; return _transitionEvent;
} }
return { /* eslint-enable indent */
export default {
parentWithAttribute: parentWithAttribute, parentWithAttribute: parentWithAttribute,
parentWithClass: parentWithClass, parentWithClass: parentWithClass,
parentWithTag: parentWithTag, parentWithTag: parentWithTag,
addEventListener: addEventListenerWithOptions, addEventListener: addEventListener,
removeEventListener: removeEventListenerWithOptions, removeEventListener: removeEventListener,
getWindowSize: getWindowSize, getWindowSize: getWindowSize,
getScreenWidth: getScreenWidth, getScreenWidth: getScreenWidth,
whichTransitionEvent: whichTransitionEvent, whichTransitionEvent: whichTransitionEvent,
whichAnimationEvent: whichAnimationEvent, whichAnimationEvent: whichAnimationEvent,
whichAnimationCancelEvent: whichAnimationCancelEvent whichAnimationCancelEvent: whichAnimationCancelEvent
}; };
});

View file

@ -1,8 +1,6 @@
define(['multi-download'], function (multiDownload) { import multiDownload from "multi-download";
'use strict';
return { export function download(items) {
download: function (items) {
if (window.NativeShell) { if (window.NativeShell) {
items.map(function (item) { items.map(function (item) {
@ -14,5 +12,3 @@ define(['multi-download'], function (multiDownload) {
})); }));
} }
} }
};
});

View file

@ -1,18 +1,13 @@
define([], function () { export function fileExists(path) {
'use strict';
return {
fileExists: function (path) {
if (window.NativeShell && window.NativeShell.FileSystem) { if (window.NativeShell && window.NativeShell.FileSystem) {
return window.NativeShell.FileSystem.fileExists(path); return window.NativeShell.FileSystem.fileExists(path);
} }
return Promise.reject(); return Promise.reject();
}, }
directoryExists: function (path) {
export function directoryExists(path) {
if (window.NativeShell && window.NativeShell.FileSystem) { if (window.NativeShell && window.NativeShell.FileSystem) {
return window.NativeShell.FileSystem.directoryExists(path); return window.NativeShell.FileSystem.directoryExists(path);
} }
return Promise.reject(); 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"; "use strict";
function renderOptions(context, selector, cssClass, items, isCheckedFn) { function renderOptions(context, selector, cssClass, items, isCheckedFn) {
@ -106,16 +106,6 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
events.trigger(instance, "filterchange"); 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) { function setVisibility(context, options) {
if (options.mode == "livetvchannels" || options.mode == "albums" || options.mode == "artists" || options.mode == "albumartists" || options.mode == "songs") { if (options.mode == "livetvchannels" || options.mode == "albums" || options.mode == "artists" || options.mode == "albumartists" || options.mode == "songs") {
hideByClass(context, "videoStandard"); hideByClass(context, "videoStandard");
@ -320,7 +310,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
triggerChange(self); triggerChange(self);
}); });
context.addEventListener("change", function (e) { context.addEventListener("change", function (e) {
var chkGenreFilter = parentWithClass(e.target, "chkGenreFilter"); var chkGenreFilter = dom.parentWithClass(e.target, "chkGenreFilter");
if (chkGenreFilter) { if (chkGenreFilter) {
var filterName = chkGenreFilter.getAttribute("data-filter"); var filterName = chkGenreFilter.getAttribute("data-filter");
var filters = query.Genres || ""; var filters = query.Genres || "";
@ -334,7 +324,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
triggerChange(self); triggerChange(self);
return; return;
} }
var chkTagFilter = parentWithClass(e.target, "chkTagFilter"); var chkTagFilter = dom.parentWithClass(e.target, "chkTagFilter");
if (chkTagFilter) { if (chkTagFilter) {
var filterName = chkTagFilter.getAttribute("data-filter"); var filterName = chkTagFilter.getAttribute("data-filter");
var filters = query.Tags || ""; var filters = query.Tags || "";
@ -348,7 +338,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
triggerChange(self); triggerChange(self);
return; return;
} }
var chkYearFilter = parentWithClass(e.target, "chkYearFilter"); var chkYearFilter = dom.parentWithClass(e.target, "chkYearFilter");
if (chkYearFilter) { if (chkYearFilter) {
var filterName = chkYearFilter.getAttribute("data-filter"); var filterName = chkYearFilter.getAttribute("data-filter");
var filters = query.Years || ""; var filters = query.Years || "";
@ -362,7 +352,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
triggerChange(self); triggerChange(self);
return; return;
} }
var chkOfficialRatingFilter = parentWithClass(e.target, "chkOfficialRatingFilter"); var chkOfficialRatingFilter = dom.parentWithClass(e.target, "chkOfficialRatingFilter");
if (chkOfficialRatingFilter) { if (chkOfficialRatingFilter) {
var filterName = chkOfficialRatingFilter.getAttribute("data-filter"); var filterName = chkOfficialRatingFilter.getAttribute("data-filter");
var filters = query.OfficialRatings || ""; var filters = query.OfficialRatings || "";

View file

@ -64,18 +64,18 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
} else { } else {
var noLibDescription; var noLibDescription;
if (user['Policy'] && user['Policy']['IsAdministrator']) { 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 { } else {
noLibDescription = Globalize.translate("AskAdminToCreateLibrary"); noLibDescription = Globalize.translate("AskAdminToCreateLibrary");
} }
html += '<div class="centerMessage padded-left padded-right">'; html += '<div class="centerMessage padded-left padded-right">';
html += '<h2>' + Globalize.translate("MessageNothingHere") + '</h2>'; html += '<h2>' + Globalize.translate("MessageNothingHere") + '</h2>';
html += '<p>' + noLibDescription + '</p>' html += '<p>' + noLibDescription + '</p>';
html += '</div>'; html += '</div>';
elem.innerHTML = html; elem.innerHTML = html;
var createNowLink = elem.querySelector("#button-createLibrary") var createNowLink = elem.querySelector("#button-createLibrary");
if (createNowLink) { if (createNowLink) {
createNowLink.addEventListener("click", function () { createNowLink.addEventListener("click", function () {
Dashboard.navigate("library.html"); Dashboard.navigate("library.html");
@ -640,7 +640,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
if (enableScrollX()) { if (enableScrollX()) {
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">'; 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 { } else {
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">'; 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()) { if (enableScrollX()) {
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">'; 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 { } else {
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">'; 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()) { if (enableScrollX()) {
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">'; 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 { } else {
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">'; html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
} }

View file

@ -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 // Appending #t=xxx to the query string doesn't seem to work with HLS
// For plain video files, not all browsers support it either // For plain video files, not all browsers support it either
var delay = browser.safari ? 2500 : 0;
if (delay) { if (element.duration >= seconds) {
setTimeout(function () { // media is ready, seek immediately
setCurrentTimeIfNeeded(element, seconds); setCurrentTimeIfNeeded(element, seconds);
}, delay);
} else { } 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); setCurrentTimeIfNeeded(element, seconds);
events.map(function(name) {
element.removeEventListener(name, onMediaChange);
});
}
};
events.map(function (name) {
element.addEventListener(name, onMediaChange);
});
} }
} }
} }

View file

@ -80,7 +80,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
if (track) { if (track) {
var format = (track.Codec || '').toLowerCase(); var format = (track.Codec || '').toLowerCase();
if (format === 'ssa' || format === 'ass') { if (format === 'ssa' || format === 'ass') {
// libjass is needed here
return false; return false;
} }
} }
@ -117,8 +116,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
}); });
} }
function normalizeTrackEventText(text) { function normalizeTrackEventText(text, useHtml) {
return text.replace(/\\N/gi, '\n'); var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, '');
return useHtml ? result.replace(/\n/gi, '<br>') : result;
} }
function setTracks(elem, tracks, item, mediaSource) { function setTracks(elem, tracks, item, mediaSource) {
@ -568,19 +568,19 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
self.resetSubtitleOffset = function() { self.resetSubtitleOffset = function() {
currentTrackOffset = 0; currentTrackOffset = 0;
showTrackOffset = false; showTrackOffset = false;
} };
self.enableShowingSubtitleOffset = function() { self.enableShowingSubtitleOffset = function() {
showTrackOffset = true; showTrackOffset = true;
} };
self.disableShowingSubtitleOffset = function() { self.disableShowingSubtitleOffset = function() {
showTrackOffset = false; showTrackOffset = false;
} };
self.isShowingSubtitleOffsetEnabled = function() { self.isShowingSubtitleOffsetEnabled = function() {
return showTrackOffset; return showTrackOffset;
} };
function getTextTrack() { function getTextTrack() {
var videoElement = self._mediaElement; var videoElement = self._mediaElement;
@ -652,7 +652,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
self.getSubtitleOffset = function() { self.getSubtitleOffset = function() {
return currentTrackOffset; return currentTrackOffset;
} };
function isAudioStreamSupported(stream, deviceProfile) { function isAudioStreamSupported(stream, deviceProfile) {
@ -1020,7 +1020,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
xhr.onerror = function (e) { xhr.onerror = function (e) {
reject(e); reject(e);
decrementFetchQueue(); decrementFetchQueue();
} };
xhr.send(); xhr.send();
}); });
@ -1047,101 +1047,38 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
lastCustomTrackMs = 0; lastCustomTrackMs = 0;
} }
function renderWithSubtitlesOctopus(videoElement, track, item) { function renderSsaAss(videoElement, track, item) {
var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || []; var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || [];
var apiClient = connectionManager.getApiClient(item);
var options = { var options = {
video: videoElement, video: videoElement,
subUrl: getTextTrackUrl(track, item), subUrl: getTextTrackUrl(track, item),
fonts: attachments.map(function (i) { fonts: attachments.map(function (i) {
return i.DeliveryUrl; return apiClient.getUrl(i.DeliveryUrl);
}), }),
workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js", workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js",
legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js", legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js",
onError: function() { onError: function() {
htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror'); 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) { require(['JavascriptSubtitlesOctopus'], function(SubtitlesOctopus) {
currentSubtitlesOctopus = new SubtitlesOctopus(options); 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() { function requiresCustomSubtitlesElement() {
// after a system update, ps4 isn't showing anything when creating a track element dynamically // 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) { if (!itemHelper.isLocalItem(item) || track.IsExternal) {
var format = (track.Codec || '').toLowerCase(); var format = (track.Codec || '').toLowerCase();
if (format === 'ssa' || format === 'ass') { if (format === 'ssa' || format === 'ass') {
// libjass is needed here
renderSsaAss(videoElement, track, item); renderSsaAss(videoElement, track, item);
return; return;
} }
@ -1274,7 +1210,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
data.TrackEvents.forEach(function (trackEvent) { data.TrackEvents.forEach(function (trackEvent) {
var trackCueObject = window.VTTCue || window.TextTrackCue; 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); trackElement.addCue(cue);
}); });
@ -1315,8 +1251,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
} }
if (selectedTrackEvent && selectedTrackEvent.Text) { if (selectedTrackEvent && selectedTrackEvent.Text) {
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text);
subtitleTextElement.classList.remove('hide'); subtitleTextElement.classList.remove('hide');
} else { } else {
@ -1493,11 +1428,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
} }
if (browser.safari || browser.iOS || browser.iPad) { if (browser.safari || browser.iOS || browser.iPad) {
list.push('AirPlay') list.push('AirPlay');
} }
list.push('SetBrightness'); list.push('SetBrightness');
list.push("SetAspectRatio") list.push("SetAspectRatio");
return list; return list;
} }
@ -1620,11 +1555,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
if (video) { if (video) {
if (isEnabled) { if (isEnabled) {
video.requestAirPlay().catch(function(err) { video.requestAirPlay().catch(function(err) {
console.error("Error requesting AirPlay", err) console.error("Error requesting AirPlay", err);
}); });
} else { } else {
document.exitAirPLay().catch(function(err) { 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; var mediaElement = this._mediaElement;
if (mediaElement) { if (mediaElement) {
if ("auto" === val) { if ("auto" === val) {
mediaElement.style.removeProperty("object-fit") mediaElement.style.removeProperty("object-fit");
} else { } else {
mediaElement.style["object-fit"] = val mediaElement.style["object-fit"] = val;
} }
} }
this._currentAspectRatio = val this._currentAspectRatio = val;
}; };
HtmlVideoPlayer.prototype.getAspectRatio = function () { HtmlVideoPlayer.prototype.getAspectRatio = function () {
@ -1779,7 +1714,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
}, { }, {
name: "Fill", name: "Fill",
id: "fill" id: "fill"
}] }];
}; };
HtmlVideoPlayer.prototype.togglePictureInPicture = function () { 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'; 'use strict';
var enableFocusTransform = !browser.slow && !browser.edge; var enableFocusTransform = !browser.slow && !browser.edge;
@ -109,7 +109,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
html += '<span style="margin-right: 10px;">'; html += '<span style="margin-right: 10px;">';
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0; var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount; html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>'; html += '</span>';
@ -126,21 +126,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
return html; 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) { function downloadRemoteImage(page, apiClient, url, type, provider) {
var options = getBaseRemoteOptions(); var options = getBaseRemoteOptions();
options.Type = type; options.Type = type;
@ -152,7 +138,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
apiClient.downloadRemoteImage(options).then(function () { apiClient.downloadRemoteImage(options).then(function () {
hasChanges = true; hasChanges = true;
var dlg = parentWithClass(page, 'dialog'); var dlg = dom.parentWithClass(page, 'dialog');
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
} }
@ -162,7 +148,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
} }
function getRemoteImageHtml(image, imageType, apiClient) { function getRemoteImageHtml(image, imageType, apiClient) {
var tagName = layoutManager.tv ? 'button' : 'div'; var tagName = layoutManager.tv ? 'button' : 'div';
var enableFooterButtons = !layoutManager.tv; var enableFooterButtons = !layoutManager.tv;
@ -293,7 +278,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
} }
function initEditor(page, apiClient) { function initEditor(page, apiClient) {
page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { page.querySelector('#selectBrowsableImageType').addEventListener('change', function () {
browsableImageType = this.value; browsableImageType = this.value;
browsableImageStartIndex = 0; browsableImageStartIndex = 0;
@ -319,14 +303,14 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
page.addEventListener('click', function (e) { page.addEventListener('click', function (e) {
var btnDownloadRemoteImage = parentWithClass(e.target, 'btnDownloadRemoteImage'); var btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
if (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')); downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
return; return;
} }
var btnImageCard = parentWithClass(e.target, 'btnImageCard'); var btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
if (btnImageCard) { if (btnImageCard) {
downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider')); 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) { function showEditor(itemId, serverId, itemType) {
loading.show(); loading.show();
require(['text!./imagedownloader.template.html'], function (template) { require(['text!./imagedownloader.template.html'], function (template) {
@ -380,7 +363,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
} }
function onDialogClosed() { function onDialogClosed() {
var dlg = this; var dlg = this;
if (layoutManager.tv) { if (layoutManager.tv) {
@ -397,9 +379,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
return { return {
show: function (itemId, serverId, itemType, imageType) { show: function (itemId, serverId, itemType, imageType) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
currentResolve = resolve; currentResolve = resolve;
currentReject = reject; currentReject = reject;
hasChanges = false; hasChanges = false;

View file

@ -1,11 +1,15 @@
define(["inputManager", "layoutManager"], function (inputManager, layoutManager) { /**
"use strict"; * Module for performing keyboard navigation.
* @module components/input/keyboardnavigation
*/
import inputManager from "inputManager";
import layoutManager from "layoutManager";
/** /**
* Key name mapping. * Key name mapping.
*/ */
// Add more to support old browsers const KeyNames = {
var KeyNames = {
13: "Enter", 13: "Enter",
19: "Pause", 19: "Pause",
27: "Escape", 27: "Escape",
@ -37,9 +41,9 @@ define(["inputManager", "layoutManager"], function (inputManager, layoutManager)
/** /**
* Keys used for keyboard navigation. * Keys used for keyboard navigation.
*/ */
var NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]; const NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
var hasFieldKey = false; let hasFieldKey = false;
try { try {
hasFieldKey = "key" in new KeyboardEvent("keydown"); hasFieldKey = "key" in new KeyboardEvent("keydown");
} catch (e) { } catch (e) {
@ -48,7 +52,7 @@ define(["inputManager", "layoutManager"], function (inputManager, layoutManager)
if (!hasFieldKey) { if (!hasFieldKey) {
// Add [a..z] // Add [a..z]
for (var i = 65; i <= 90; i++) { for (let i = 65; i <= 90; i++) {
KeyNames[i] = String.fromCharCode(i).toLowerCase(); KeyNames[i] = String.fromCharCode(i).toLowerCase();
} }
} }
@ -56,33 +60,33 @@ define(["inputManager", "layoutManager"], function (inputManager, layoutManager)
/** /**
* Returns key name from event. * Returns key name from event.
* *
* @param {KeyboardEvent} keyboard event * @param {KeyboardEvent} event - Keyboard event.
* @return {string} key name * @return {string} Key name.
*/ */
function getKeyName(event) { export function getKeyName(event) {
return KeyNames[event.keyCode] || event.key; return KeyNames[event.keyCode] || event.key;
} }
/** /**
* Returns _true_ if key is used for navigation. * Returns _true_ if key is used for navigation.
* *
* @param {string} key name * @param {string} key - Key name.
* @return {boolean} _true_ if key is used for navigation * @return {boolean} _true_ if key is used for navigation.
*/ */
function isNavigationKey(key) { export function isNavigationKey(key) {
return NavigationKeys.indexOf(key) != -1; return NavigationKeys.indexOf(key) != -1;
} }
function enable() { export function enable() {
document.addEventListener("keydown", function (e) { document.addEventListener("keydown", function (e) {
var key = getKeyName(e); const key = getKeyName(e);
// Ignore navigation keys for non-TV // Ignore navigation keys for non-TV
if (!layoutManager.tv && isNavigationKey(key)) { if (!layoutManager.tv && isNavigationKey(key)) {
return; return;
} }
var capture = true; let capture = true;
switch (key) { switch (key) {
case "ArrowLeft": case "ArrowLeft":
@ -157,9 +161,8 @@ define(["inputManager", "layoutManager"], function (inputManager, layoutManager)
// No need to check for gamepads manually at load time, the eventhandler will be fired for that // No need to check for gamepads manually at load time, the eventhandler will be fired for that
window.addEventListener("gamepadconnected", attachGamepadScript); window.addEventListener("gamepadconnected", attachGamepadScript);
return { export default {
enable: enable, enable: enable,
getKeyName: getKeyName, getKeyName: getKeyName,
isNavigationKey: isNavigationKey isNavigationKey: isNavigationKey
}; };
});

View file

@ -116,7 +116,7 @@ define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings",
} }
function createAttribute(label, value) { 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) { function showMediaInfoMore(itemId, serverId, template) {

View file

@ -296,8 +296,6 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
var html = ""; var html = "";
var providerIds = item.ProviderIds || {};
for (var i = 0, length = idList.length; i < length; i++) { for (var i = 0, length = idList.length; i < length; i++) {
var idInfo = idList[i]; var idInfo = idList[i];
@ -306,9 +304,12 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
html += '<div class="inputContainer">'; 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 + '"/>'; html += '<input is="emby-input" class="txtLookupId" data-providerkey="' + idInfo.Key + '" id="' + id + '" label="' + idLabel + '"/>';

View file

@ -36,7 +36,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>"; html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
} }
select.innerHTML = html; select.innerHTML = html;
}) });
} }
function populateRefreshInterval(select) { function populateRefreshInterval(select) {
@ -120,7 +120,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
html += plugin.Name; html += plugin.Name;
html += "</h3>"; html += "</h3>";
html += "</div>"; 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>";
html += '<div class="fieldDescription">' + globalize.translate("LabelMetadataDownloadersHelp") + "</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, {}); renderMetadataFetchers(parent, availableOptions, {});
renderSubtitleFetchers(parent, availableOptions, {}); renderSubtitleFetchers(parent, availableOptions, {});
renderImageFetchers(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() { }).catch(function() {
return Promise.resolve(); return Promise.resolve();
}) });
} }
function adjustSortableListElement(elem) { function adjustSortableListElement(elem) {
@ -296,8 +296,8 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
Type: type Type: type
}, currentLibraryOptions.TypeOptions.push(typeOptions)); }, currentLibraryOptions.TypeOptions.push(typeOptions));
var availableOptions = getTypeOptions(currentAvailableOptions || {}, type); var availableOptions = getTypeOptions(currentAvailableOptions || {}, type);
(new ImageOptionsEditor).show(type, typeOptions, availableOptions) (new ImageOptionsEditor).show(type, typeOptions, availableOptions);
}) });
} }
function onImageFetchersContainerClick(e) { function onImageFetchersContainerClick(e) {
@ -315,12 +315,12 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
var list = dom.parentWithClass(li, "paperList"); var list = dom.parentWithClass(li, "paperList");
if (btnSortable.classList.contains("btnSortableMoveDown")) { if (btnSortable.classList.contains("btnSortableMoveDown")) {
var next = li.nextSibling; 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 { } else {
var prev = li.previousSibling; 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);
} }
} }
@ -407,13 +407,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
function setSubtitleFetchersIntoOptions(parent, options) { function setSubtitleFetchersIntoOptions(parent, options) {
options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) { options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) {
return !elem.checked return !elem.checked;
}), function(elem) { }), function(elem) {
return elem.getAttribute("data-pluginname") return elem.getAttribute("data-pluginname");
}); });
options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) { options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) {
return elem.getAttribute("data-pluginname") return elem.getAttribute("data-pluginname");
}); });
} }
@ -455,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) { typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) {
return elem.checked return elem.checked;
}), function(elem) { }), function(elem) {
return elem.getAttribute("data-pluginname") return elem.getAttribute("data-pluginname");
}); });
typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) { typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) {
return elem.getAttribute("data-pluginname") return elem.getAttribute("data-pluginname");
}); });
} }
} }
@ -505,20 +505,20 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked, SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked,
RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked, RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked,
MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) {
return elem.checked return elem.checked;
}), function(elem) { }), function(elem) {
return elem.getAttribute("data-pluginname") return elem.getAttribute("data-pluginname");
}), }),
TypeOptions: [] TypeOptions: []
}; };
options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll(".localReaderOption"), function(elem) { 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) { options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
return elem.checked return elem.checked;
}), function(elem) { }), function(elem) {
return elem.getAttribute("data-lang") return elem.getAttribute("data-lang");
}); });
setSubtitleFetchersIntoOptions(parent, options); setSubtitleFetchersIntoOptions(parent, options);
setMetadataFetchersIntoOptions(parent, options); setMetadataFetchersIntoOptions(parent, options);
@ -531,7 +531,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
function getOrderedPlugins(plugins, configuredOrder) { function getOrderedPlugins(plugins, configuredOrder) {
plugins = plugins.slice(0); plugins = plugins.slice(0);
plugins.sort(function(a, b) { 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; return plugins;
} }
@ -558,10 +558,10 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches; parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches;
parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch; parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch;
Array.prototype.forEach.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { 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) { 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 || [])); renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || []));
renderMetadataFetchers(parent, parent.availableOptions, options); renderMetadataFetchers(parent, parent.availableOptions, options);
@ -578,5 +578,5 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
getLibraryOptions: getLibraryOptions, getLibraryOptions: getLibraryOptions,
setLibraryOptions: setLibraryOptions, setLibraryOptions: setLibraryOptions,
setAdvancedVisible: setAdvancedVisible setAdvancedVisible: setAdvancedVisible
} };
}); });

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 id = "txt1" + idInfo.Key;
var formatString = idInfo.UrlFormatString || ''; 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="inputContainer">';
html += '<div class="flex align-items-center">'; html += '<div class="flex align-items-center">';

View file

@ -173,15 +173,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
}; };
if (status === 'completed') { 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; notification.vibrate = true;
} else if (status === 'cancelled') { } 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') { } 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; notification.vibrate = true;
} else if (status === 'progress') { } 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 = notification.actions =
[ [

View file

@ -1633,29 +1633,29 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
self.supportSubtitleOffset = function(player) { self.supportSubtitleOffset = function(player) {
player = player || self._currentPlayer; player = player || self._currentPlayer;
return player && 'setSubtitleOffset' in player; return player && 'setSubtitleOffset' in player;
} };
self.enableShowingSubtitleOffset = function(player) { self.enableShowingSubtitleOffset = function(player) {
player = player || self._currentPlayer; player = player || self._currentPlayer;
player.enableShowingSubtitleOffset(); player.enableShowingSubtitleOffset();
} };
self.disableShowingSubtitleOffset = function(player) { self.disableShowingSubtitleOffset = function(player) {
player = player || self._currentPlayer; player = player || self._currentPlayer;
if (player.disableShowingSubtitleOffset) { if (player.disableShowingSubtitleOffset) {
player.disableShowingSubtitleOffset(); player.disableShowingSubtitleOffset();
} }
} };
self.isShowingSubtitleOffsetEnabled = function(player) { self.isShowingSubtitleOffsetEnabled = function(player) {
player = player || self._currentPlayer; player = player || self._currentPlayer;
return player.isShowingSubtitleOffsetEnabled(); return player.isShowingSubtitleOffsetEnabled();
} };
self.isSubtitleStreamExternal = function(index, player) { self.isSubtitleStreamExternal = function(index, player) {
var stream = getSubtitleStream(player, index); var stream = getSubtitleStream(player, index);
return stream ? getDeliveryMethod(stream) === 'External' : false; return stream ? getDeliveryMethod(stream) === 'External' : false;
} };
self.setSubtitleOffset = function (value, player) { self.setSubtitleOffset = function (value, player) {
player = player || self._currentPlayer; player = player || self._currentPlayer;
@ -1669,12 +1669,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
if (player.getSubtitleOffset) { if (player.getSubtitleOffset) {
return player.getSubtitleOffset(); return player.getSubtitleOffset();
} }
} };
self.canHandleOffsetOnCurrentSubtitle = function(player) { self.canHandleOffsetOnCurrentSubtitle = function(player) {
var index = self.getSubtitleStreamIndex(player); var index = self.getSubtitleStreamIndex(player);
return index !== -1 && self.isSubtitleStreamExternal(index, player); return index !== -1 && self.isSubtitleStreamExternal(index, player);
} };
self.seek = function (ticks, player) { self.seek = function (ticks, player) {

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'; 'use strict';
var currentServerId; 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) { function onSubmit(e) {
var panel = dom.parentWithClass(this, 'dialog');
var panel = parentWithClass(this, 'dialog');
var playlistId = panel.querySelector('#selectPlaylistToAddTo').value; var playlistId = panel.querySelector('#selectPlaylistToAddTo').value;
var apiClient = connectionManager.getApiClient(currentServerId); var apiClient = connectionManager.getApiClient(currentServerId);
@ -35,11 +21,9 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
function createPlaylist(apiClient, dlg) { function createPlaylist(apiClient, dlg) {
loading.show(); loading.show();
var url = apiClient.getUrl("Playlists", { var url = apiClient.getUrl("Playlists", {
Name: dlg.querySelector('#txtNewPlaylistName').value, Name: dlg.querySelector('#txtNewPlaylistName').value,
Ids: dlg.querySelector('.fldSelectedItemIds').value || '', Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
userId: apiClient.getCurrentUserId() userId: apiClient.getCurrentUserId()
@ -50,9 +34,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
type: "POST", type: "POST",
url: url, url: url,
dataType: "json" dataType: "json"
}).then(function (result) { }).then(function (result) {
loading.hide(); loading.hide();
var id = result.Id; var id = result.Id;
@ -63,16 +45,13 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
function redirectToPlaylist(apiClient, id) { function redirectToPlaylist(apiClient, id) {
appRouter.showItem(id, apiClient.serverId()); appRouter.showItem(id, apiClient.serverId());
} }
function addToPlaylist(apiClient, dlg, id) { function addToPlaylist(apiClient, dlg, id) {
var itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; var itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
if (id === 'queue') { if (id === 'queue') {
playbackManager.queue({ playbackManager.queue({
serverId: apiClient.serverId(), serverId: apiClient.serverId(),
ids: itemIds.split(',') ids: itemIds.split(',')
@ -85,7 +64,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
loading.show(); loading.show();
var url = apiClient.getUrl("Playlists/" + id + "/Items", { var url = apiClient.getUrl("Playlists/" + id + "/Items", {
Ids: itemIds, Ids: itemIds,
userId: apiClient.getCurrentUserId() userId: apiClient.getCurrentUserId()
}); });
@ -95,7 +73,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
url: url url: url
}).then(function () { }).then(function () {
loading.hide(); loading.hide();
dlg.submitted = true; dlg.submitted = true;
@ -108,7 +85,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
function populatePlaylists(editorOptions, panel) { function populatePlaylists(editorOptions, panel) {
var select = panel.querySelector('#selectPlaylistToAddTo'); var select = panel.querySelector('#selectPlaylistToAddTo');
loading.hide(); loading.hide();
@ -116,7 +92,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
panel.querySelector('.newPlaylistInfo').classList.add('hide'); panel.querySelector('.newPlaylistInfo').classList.add('hide');
var options = { var options = {
Recursive: true, Recursive: true,
IncludeItemTypes: "Playlist", IncludeItemTypes: "Playlist",
SortBy: 'SortName', SortBy: 'SortName',
@ -125,7 +100,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
var apiClient = connectionManager.getApiClient(currentServerId); var apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) { apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
var html = ''; var html = '';
if (editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) { if (editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) {
@ -135,7 +109,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
html += '<option value="">' + globalize.translate('OptionNew') + '</option>'; html += '<option value="">' + globalize.translate('OptionNew') + '</option>';
html += result.Items.map(function (i) { html += result.Items.map(function (i) {
return '<option value="' + i.Id + '">' + i.Name + '</option>'; return '<option value="' + i.Id + '">' + i.Name + '</option>';
}); });
@ -159,7 +132,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
function getEditorHtml(items) { function getEditorHtml(items) {
var html = ''; var html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">'; html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
@ -195,7 +167,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
function initEditor(content, options, items) { function initEditor(content, options, items) {
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
if (this.value) { if (this.value) {
content.querySelector('.newPlaylistInfo').classList.add('hide'); content.querySelector('.newPlaylistInfo').classList.add('hide');
@ -235,7 +206,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
PlaylistEditor.prototype.show = function (options) { PlaylistEditor.prototype.show = function (options) {
var items = options.items || {}; var items = options.items || {};
currentServerId = options.serverId; currentServerId = options.serverId;
@ -272,7 +242,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
initEditor(dlg, options, items); initEditor(dlg, options, items);
dlg.querySelector('.btnCancel').addEventListener('click', function () { dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
@ -281,7 +250,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
} }
return dialogHelper.open(dlg).then(function () { return dialogHelper.open(dlg).then(function () {
if (layoutManager.tv) { if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false); centerFocus(dlg.querySelector('.formDialogContent'), false, false);
} }

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'; 'use strict';
function parentWithClass(elem, className) {
while (!elem.classList || !elem.classList.contains(className)) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
function getEditorHtml() { function getEditorHtml() {
var html = ''; var html = '';
@ -65,7 +52,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager'
loading.show(); loading.show();
var instance = this; var instance = this;
var dlg = parentWithClass(e.target, 'dialog'); var dlg = dom.parentWithClass(e.target, 'dialog');
var options = instance.options; var options = instance.options;
var apiClient = connectionManager.getApiClient(options.serverId); var apiClient = connectionManager.getApiClient(options.serverId);

View file

@ -1,14 +1,11 @@
// From https://github.com/parshap/node-sanitize-filename // From https://github.com/parshap/node-sanitize-filename
define([], function () { const illegalRe = /[\/\?<>\\:\*\|":]/g;
'use strict';
var illegalRe = /[\/\?<>\\:\*\|":]/g;
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
var controlRe = /[\x00-\x1f\x80-\x9f]/g; const controlRe = /[\x00-\x1f\x80-\x9f]/g;
var reservedRe = /^\.+$/; const reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[\. ]+$/; const windowsTrailingRe = /[\. ]+$/;
function isHighSurrogate(codePoint) { function isHighSurrogate(codePoint) {
return codePoint >= 0xd800 && codePoint <= 0xdbff; return codePoint >= 0xd800 && codePoint <= 0xdbff;
@ -23,11 +20,11 @@ define([], function () {
throw new Error("Input must be string"); throw new Error("Input must be string");
} }
var charLength = string.length; const charLength = string.length;
var byteLength = 0; let byteLength = 0;
var codePoint = null; let codePoint = null;
var prevCodePoint = null; let prevCodePoint = null;
for (var i = 0; i < charLength; i++) { for (let i = 0; i < charLength; i++) {
codePoint = string.charCodeAt(i); codePoint = string.charCodeAt(i);
// handle 4-byte non-BMP chars // handle 4-byte non-BMP chars
// low surrogate // low surrogate
@ -56,12 +53,12 @@ define([], function () {
throw new Error("Input must be string"); throw new Error("Input must be string");
} }
var charLength = string.length; const charLength = string.length;
var curByteLength = 0; let curByteLength = 0;
var codePoint; let codePoint;
var segment; let segment;
for (var i = 0; i < charLength; i += 1) { for (let i = 0; i < charLength; i += 1) {
codePoint = string.charCodeAt(i); codePoint = string.charCodeAt(i);
segment = string[i]; segment = string[i];
@ -82,9 +79,8 @@ define([], function () {
return string; return string;
} }
return { export function sanitize(input, replacement) {
sanitize: function (input, replacement) { const sanitized = input
var sanitized = input
.replace(illegalRe, replacement) .replace(illegalRe, replacement)
.replace(controlRe, replacement) .replace(controlRe, replacement)
.replace(reservedRe, replacement) .replace(reservedRe, replacement)
@ -92,5 +88,3 @@ define([], function () {
.replace(windowsTrailingRe, replacement); .replace(windowsTrailingRe, replacement);
return truncate(sanitized, 255); return truncate(sanitized, 255);
} }
};
});

View file

@ -1,38 +1,46 @@
define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManager) { /* eslint-disable indent */
"use strict";
/**
* Module for controlling scroll behavior.
* @module components/scrollManager
*/
import dom from "dom";
import browser from "browser";
import layoutManager from "layoutManager";
/** /**
* Scroll time in ms. * Scroll time in ms.
*/ */
var ScrollTime = 270; const ScrollTime = 270;
/** /**
* Epsilon for comparing values. * 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 // 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. * Returns minimum vertical scroll.
* Scroll less than that value will be zeroed. * Scroll less than that value will be zeroed.
* *
* @return {number} minimum vertical scroll * @return {number} Minimum vertical scroll.
*/ */
function minimumScrollY() { function minimumScrollY() {
var topMenu = document.querySelector(".headerTop"); const topMenu = document.querySelector(".headerTop");
if (topMenu) { if (topMenu) {
return topMenu.clientHeight; return topMenu.clientHeight;
} }
return 0; return 0;
} }
var supportsSmoothScroll = "scrollBehavior" in document.documentElement.style; const supportsSmoothScroll = "scrollBehavior" in document.documentElement.style;
var supportsScrollToOptions = false; let supportsScrollToOptions = false;
try { 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 // eslint-disable-next-line getter-return
get: function () { get: function () {
supportsScrollToOptions = true; supportsScrollToOptions = true;
@ -47,10 +55,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Returns value clamped by range [min, max]. * Returns value clamped by range [min, max].
* *
* @param {number} value clamped value * @param {number} value - Clamped value.
* @param {number} min begining of range * @param {number} min - Begining of range.
* @param {number} max ending of range * @param {number} max - Ending of range.
* @return {number} clamped value * @return {number} Clamped value.
*/ */
function clamp(value, min, max) { function clamp(value, min, max) {
return value <= min ? min : value >= max ? max : value; 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. * 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. * 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} begin1 - Begining of range 1.
* @param {number} end1 ending of range 1 * @param {number} end1 - Ending of range 1.
* @param {number} begin2 begining of range 2 * @param {number} begin2 - Begining of range 2.
* @param {number} end2 ending of range 2 * @param {number} end2 - Ending of range 2.
* @return {number} delta: <0 move range1 to the left, >0 - to the right * @return {number} Delta: <0 move range1 to the left, >0 - to the right.
*/ */
function fitRange(begin1, end1, begin2, end2) { function fitRange(begin1, end1, begin2, end2) {
var delta1 = begin1 - begin2; const delta1 = begin1 - begin2;
var delta2 = end2 - end1; const delta2 = end2 - end1;
if (delta1 < 0 && delta1 < delta2) { if (delta1 < 0 && delta1 < delta2) {
return -delta1; return -delta1;
} else if (delta2 < 0) { } else if (delta2 < 0) {
@ -80,13 +88,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Ease value. * Ease value.
* *
* @param {number} t value in range [0, 1] * @param {number} t - Value in range [0, 1].
* @return {number} eased value in range [0, 1] * @return {number} Eased value in range [0, 1].
*/ */
function ease(t) { function ease(t) {
return t*(2 - t); // easeOutQuad === ease-out return t*(2 - t); // easeOutQuad === ease-out
} }
/**
* @typedef {Object} Rect
* @property {number} left - X coordinate of top-left corner.
* @property {number} top - Y coordinate of top-left corner.
* @property {number} width - Width.
* @property {number} height - Height.
*/
/** /**
* Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * 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 * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement
*/ */
function DocumentScroller() { class DocumentScroller {
} /**
* Horizontal scroll position.
DocumentScroller.prototype = { * @type {number}
*/
get scrollLeft() { get scrollLeft() {
return window.pageXOffset; return window.pageXOffset;
}, }
set scrollLeft(val) { set scrollLeft(val) {
window.scroll(val, window.pageYOffset); window.scroll(val, window.pageYOffset);
}, }
/**
* Vertical scroll position.
* @type {number}
*/
get scrollTop() { get scrollTop() {
return window.pageYOffset; return window.pageYOffset;
}, }
set scrollTop(val) { set scrollTop(val) {
window.scroll(window.pageXOffset, val); window.scroll(window.pageXOffset, val);
}, }
/**
* Horizontal scroll size (scroll width).
* @type {number}
*/
get scrollWidth() { get scrollWidth() {
return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth);
}, }
/**
* Vertical scroll size (scroll height).
* @type {number}
*/
get scrollHeight() { get scrollHeight() {
return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
}, }
/**
* Horizontal client size (client width).
* @type {number}
*/
get clientWidth() { get clientWidth() {
return Math.min(document.documentElement.clientWidth, document.body.clientWidth); return Math.min(document.documentElement.clientWidth, document.body.clientWidth);
}, }
/**
* Vertical client size (client height).
* @type {number}
*/
get clientHeight() { get clientHeight() {
return Math.min(document.documentElement.clientHeight, document.body.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 // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport
return { return {
left: 0, left: 0,
@ -142,26 +185,34 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
width: this.clientWidth, width: this.clientWidth,
height: this.clientHeight height: this.clientHeight
}; };
},
scrollTo: function() {
window.scrollTo.apply(window, arguments);
} }
};
var documentScroller = new DocumentScroller();
/** /**
* Returns parent element that can be scrolled. If no such, returns documentElement. * Scrolls window.
* @param {...mixed} args See window.scrollTo.
*/
scrollTo() {
window.scrollTo.apply(window, arguments);
}
}
/**
* 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 {HTMLElement} element - Element for which parent is being searched.
* @param {boolean} vertical search for vertical scrollable parent * @param {boolean} vertical - Search for vertical scrollable parent.
* @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller.
*/ */
function getScrollableParent(element, vertical) { function getScrollableParent(element, vertical) {
if (element) { if (element) {
var nameScroll = "scrollWidth"; let nameScroll = "scrollWidth";
var nameClient = "clientWidth"; let nameClient = "clientWidth";
var nameClass = "scrollX"; let nameClass = "scrollX";
if (vertical) { if (vertical) {
nameScroll = "scrollHeight"; nameScroll = "scrollHeight";
@ -169,7 +220,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
nameClass = "scrollY"; nameClass = "scrollY";
} }
var parent = element.parentElement; let parent = element.parentElement;
while (parent) { while (parent) {
// Skip 'emby-scroller' because it scrolls by itself // Skip 'emby-scroller' because it scrolls by itself
@ -187,20 +238,20 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* @typedef {Object} ScrollerData * @typedef {Object} ScrollerData
* @property {number} scrollPos current scroll position * @property {number} scrollPos - Current scroll position.
* @property {number} scrollSize scroll size * @property {number} scrollSize - Scroll size.
* @property {number} clientSize client size * @property {number} clientSize - Client size.
*/ */
/** /**
* Returns scroll data for specified orientation. * Returns scroller data for specified orientation.
* *
* @param {HTMLElement} scroller scroller * @param {HTMLElement} scroller - Scroller.
* @param {boolean} vertical vertical scroll data * @param {boolean} vertical - Vertical scroller data.
* @return {ScrollerData} scroll data * @return {ScrollerData} Scroller data.
*/ */
function getScrollerData(scroller, vertical) { function getScrollerData(scroller, vertical) {
var data = {}; let data = {};
if (!vertical) { if (!vertical) {
data.scrollPos = scroller.scrollLeft; 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. * Returns position of child of scroller for specified orientation.
* *
* @param {HTMLElement} scroller scroller * @param {HTMLElement} scroller - Scroller.
* @param {HTMLElement} element child of scroller * @param {HTMLElement} element - Child of scroller.
* @param {boolean} vertical vertical scroll * @param {boolean} vertical - Vertical scroll.
* @return {number} child position * @return {number} Child position.
*/ */
function getScrollerChildPos(scroller, element, vertical) { function getScrollerChildPos(scroller, element, vertical) {
var elementRect = element.getBoundingClientRect(); const elementRect = element.getBoundingClientRect();
var scrollerRect = scroller.getBoundingClientRect(); const scrollerRect = scroller.getBoundingClientRect();
if (!vertical) { if (!vertical) {
return scroller.scrollLeft + elementRect.left - scrollerRect.left; return scroller.scrollLeft + elementRect.left - scrollerRect.left;
@ -237,21 +288,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Returns scroll position for element. * Returns scroll position for element.
* *
* @param {ScrollerData} scrollerData scroller data * @param {ScrollerData} scrollerData - Scroller data.
* @param {number} elementPos child element position * @param {number} elementPos - Child element position.
* @param {number} elementSize child element size * @param {number} elementSize - Child element size.
* @param {boolean} centered scroll to center * @param {boolean} centered - Scroll to center.
* @return {number} scroll position * @return {number} Scroll position.
*/ */
function calcScroll(scrollerData, elementPos, elementSize, centered) { function calcScroll(scrollerData, elementPos, elementSize, centered) {
var maxScroll = scrollerData.scrollSize - scrollerData.clientSize; const maxScroll = scrollerData.scrollSize - scrollerData.clientSize;
var scroll; let scroll;
if (centered) { if (centered) {
scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; scroll = elementPos + (elementSize - scrollerData.clientSize) / 2;
} else { } 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; scroll = scrollerData.scrollPos - delta;
} }
@ -261,14 +312,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Calls scrollTo function in proper way. * Calls scrollTo function in proper way.
* *
* @param {HTMLElement} scroller scroller * @param {HTMLElement} scroller - Scroller.
* @param {ScrollToOptions} options scroll options * @param {ScrollToOptions} options - Scroll options.
*/ */
function scrollToHelper(scroller, options) { function scrollToHelper(scroller, options) {
if ("scrollTo" in scroller) { if ("scrollTo" in scroller) {
if (!supportsScrollToOptions) { if (!supportsScrollToOptions) {
var scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft);
var scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop);
scroller.scrollTo(scrollX, scrollY); scroller.scrollTo(scrollX, scrollY);
} else { } else {
scroller.scrollTo(options); scroller.scrollTo(options);
@ -286,14 +337,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Performs built-in scroll. * Performs built-in scroll.
* *
* @param {HTMLElement} xScroller horizontal scroller * @param {HTMLElement} xScroller - Horizontal scroller.
* @param {number} scrollX horizontal coordinate * @param {number} scrollX - Horizontal coordinate.
* @param {HTMLElement} yScroller vertical scroller * @param {HTMLElement} yScroller - Vertical scroller.
* @param {number} scrollY vertical coordinate * @param {number} scrollY - Vertical coordinate.
* @param {boolean} smooth smooth scrolling * @param {boolean} smooth - Smooth scrolling.
*/ */
function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
var scrollBehavior = smooth ? "smooth" : "instant"; const scrollBehavior = smooth ? "smooth" : "instant";
if (xScroller !== yScroller) { if (xScroller !== yScroller) {
scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); 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. * Resets scroll timer to stop scrolling.
@ -316,29 +370,29 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Performs animated scroll. * Performs animated scroll.
* *
* @param {HTMLElement} xScroller horizontal scroller * @param {HTMLElement} xScroller - Horizontal scroller.
* @param {number} scrollX horizontal coordinate * @param {number} scrollX - Horizontal coordinate.
* @param {HTMLElement} yScroller vertical scroller * @param {HTMLElement} yScroller - Vertical scroller.
* @param {number} scrollY vertical coordinate * @param {number} scrollY - Vertical coordinate.
*/ */
function animateScroll(xScroller, scrollX, yScroller, scrollY) { function animateScroll(xScroller, scrollX, yScroller, scrollY) {
var ox = xScroller.scrollLeft; const ox = xScroller.scrollLeft;
var oy = yScroller.scrollTop; const oy = yScroller.scrollTop;
var dx = scrollX - ox; const dx = scrollX - ox;
var dy = scrollY - oy; const dy = scrollY - oy;
if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) {
return; return;
} }
var start; let start;
function scrollAnim(currentTimestamp) { function scrollAnim(currentTimestamp) {
start = start || currentTimestamp; start = start || currentTimestamp;
var k = Math.min(1, (currentTimestamp - start) / ScrollTime); let k = Math.min(1, (currentTimestamp - start) / ScrollTime);
if (k === 1) { if (k === 1) {
resetScrollTimer(); resetScrollTimer();
@ -348,8 +402,8 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
k = ease(k); k = ease(k);
var x = ox + dx*k; const x = ox + dx*k;
var y = oy + dy*k; const y = oy + dy*k;
builtinScroll(xScroller, x, yScroller, y, false); builtinScroll(xScroller, x, yScroller, y, false);
@ -362,11 +416,11 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
/** /**
* Performs scroll. * Performs scroll.
* *
* @param {HTMLElement} xScroller horizontal scroller * @param {HTMLElement} xScroller - Horizontal scroller.
* @param {number} scrollX horizontal coordinate * @param {number} scrollX - Horizontal coordinate.
* @param {HTMLElement} yScroller vertical scroller * @param {HTMLElement} yScroller - Vertical scroller.
* @param {number} scrollY vertical coordinate * @param {number} scrollY - Vertical coordinate.
* @param {boolean} smooth smooth scrolling * @param {boolean} smooth - Smooth scrolling.
*/ */
function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { 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. * Returns true if scroll manager is enabled.
*/ */
var isEnabled = function() { export function isEnabled() {
return layoutManager.tv; return layoutManager.tv;
}; }
/** /**
* Scrolls the document to a given position. * Scrolls the document to a given position.
* *
* @param {number} scrollX horizontal coordinate * @param {number} scrollX - Horizontal coordinate.
* @param {number} scrollY vertical coordinate * @param {number} scrollY - Vertical coordinate.
* @param {boolean} [smooth=false] smooth scrolling * @param {boolean} [smooth=false] - Smooth scrolling.
*/ */
var scrollTo = function(scrollX, scrollY, smooth) { export function scrollTo(scrollX, scrollY, smooth) {
smooth = !!smooth; smooth = !!smooth;
// Scroller is document itself by default // Scroller is document itself by default
var scroller = getScrollableParent(null, false); const scroller = getScrollableParent(null, false);
var xScrollerData = getScrollerData(scroller, false); const xScrollerData = getScrollerData(scroller, false);
var yScrollerData = getScrollerData(scroller, true); const yScrollerData = getScrollerData(scroller, true);
scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize);
scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.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. * Scrolls the document to a given element.
* *
* @param {HTMLElement} element target element of scroll task * @param {HTMLElement} element - Target element of scroll task.
* @param {boolean} [smooth=false] smooth scrolling * @param {boolean} [smooth=false] - Smooth scrolling.
*/ */
var scrollToElement = function(element, smooth) { export function scrollToElement(element, smooth) {
smooth = !!smooth; smooth = !!smooth;
var scrollCenterX = true; let scrollCenterX = true;
var scrollCenterY = true; let scrollCenterY = true;
var offsetParent = element.offsetParent; const offsetParent = element.offsetParent;
// In Firefox offsetParent.offsetParent is BODY // 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) // Scroll fixed elements to nearest edge (or do not scroll at all)
if (isFixed) { if (isFixed) {
scrollCenterX = scrollCenterY = false; scrollCenterX = scrollCenterY = false;
} }
var xScroller = getScrollableParent(element, false); const xScroller = getScrollableParent(element, false);
var yScroller = getScrollableParent(element, true); const yScroller = getScrollableParent(element, true);
var elementRect = element.getBoundingClientRect(); const elementRect = element.getBoundingClientRect();
var xScrollerData = getScrollerData(xScroller, false); const xScrollerData = getScrollerData(xScroller, false);
var yScrollerData = getScrollerData(yScroller, true); const yScrollerData = getScrollerData(yScroller, true);
var xPos = getScrollerChildPos(xScroller, element, false); const xPos = getScrollerChildPos(xScroller, element, false);
var yPos = getScrollerChildPos(yScroller, element, true); const yPos = getScrollerChildPos(yScroller, element, true);
var scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); const scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX);
var scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); let scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY);
// HACK: Scroll to top for top menu because it is hidden // HACK: Scroll to top for top menu because it is hidden
// FIXME: Need a marker to scroll top/bottom // FIXME: Need a marker to scroll top/bottom
@ -490,9 +544,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
}, {capture: true}); }, {capture: true});
} }
return { /* eslint-enable indent */
export default {
isEnabled: isEnabled, isEnabled: isEnabled,
scrollTo: scrollTo, scrollTo: scrollTo,
scrollToElement: scrollToElement scrollToElement: scrollToElement
}; };
});

View file

@ -10,12 +10,6 @@ define([], function () {
} }
}, },
canExec: false,
exec: function (options) {
// options.path
// options.arguments
return Promise.reject();
},
enableFullscreen: function () { enableFullscreen: function () {
if (window.NativeShell) { if (window.NativeShell) {
window.NativeShell.enableFullscreen(); window.NativeShell.enableFullscreen();

View file

@ -116,8 +116,8 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr
var linkUrl = info.stylesheetPath; var linkUrl = info.stylesheetPath;
unloadTheme(); unloadTheme();
var link = document.createElement('link');
var link = document.createElement('link');
link.setAttribute('rel', 'stylesheet'); link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css'); link.setAttribute('type', 'text/css');
link.onload = function () { 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'; '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) { function getImageUrl(item, options, apiClient) {
options = options || {}; options = options || {};
options.type = options.type || "Primary"; options.type = options.type || "Primary";
@ -11,7 +21,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
if (item.ImageTags && item.ImageTags[options.type]) { if (item.ImageTags && item.ImageTags[options.type]) {
options.tag = item.ImageTags[options.type]; options.tag = item.ImageTags[options.type];
return apiClient.getScaledImageUrl(item.Id, options); return apiClient.getScaledImageUrl(item.Id, options);
} }
@ -27,8 +36,14 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
return null; 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) { function getBackdropImageUrl(item, options, apiClient) {
options = options || {}; options = options || {};
options.type = options.type || "Backdrop"; options.type = options.type || "Backdrop";
@ -46,19 +61,19 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
return null; 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 apiClient = connectionManager.getApiClient(item.ServerId);
var imageOptions = {}; var imageOptions = {};
if (!original) {
imageOptions.maxWidth = screen.availWidth;
}
if (item.BackdropImageTags && item.BackdropImageTags.length) { if (item.BackdropImageTags && item.BackdropImageTags.length) {
return getBackdropImageUrl(item, imageOptions, apiClient); return getBackdropImageUrl(item, imageOptions, apiClient);
} else { } else {
if (item.MediaType === 'Photo') {
if (item.MediaType === 'Photo' && original) {
return apiClient.getItemDownloadUrl(item.Id); return apiClient.getItemDownloadUrl(item.Id);
} }
imageOptions.type = "Primary"; 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) { function getIcon(icon, cssClass, canFocus, autoFocus) {
var tabIndex = canFocus ? '' : ' tabindex="-1"'; var tabIndex = canFocus ? '' : ' tabindex="-1"';
autoFocus = autoFocus ? ' autofocus' : ''; 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) { function setUserScalable(scalable) {
try { try {
appHost.setUserScalable(scalable); appHost.setUserScalable(scalable);
} catch (err) { } catch (err) {
@ -83,23 +108,31 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
return function (options) { return function (options) {
var self = this; var self = this;
/** Initialized instance of Swiper. */
var swiperInstance; var swiperInstance;
var dlg; /** Initialized instance of the dialog containing the Swiper instance. */
var currentTimeout; var dialog;
var currentIntervalMs; /** Options of the slideshow components */
var currentOptions; 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 // Use autoplay on Chromecast since it is non-interactive.
if (browser.chromecast) { options.interactive = !browser.chromecast;
options.interactive = false;
}
/**
* Creates the HTML markup for the dialog and the OSD.
* @param {Object} options - Options used to create the dialog and slideshow.
*/
function createElements(options) { function createElements(options) {
currentOptions = options;
dlg = dialogHelper.createDialog({ dialog = dialogHelper.createDialog({
exitAnimationDuration: options.interactive ? 400 : 800, exitAnimationDuration: options.interactive ? 400 : 800,
size: 'fullscreen', size: 'fullscreen',
autoFocus: false, autoFocus: false,
@ -108,17 +141,15 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
removeOnClose: true removeOnClose: true
}); });
dlg.classList.add('slideshowDialog'); dialog.classList.add('slideshowDialog');
var html = ''; var html = '';
if (options.interactive) {
var actionButtonsOnTop = layoutManager.mobile;
html += '<div>';
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>'; html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
if (options.interactive && !layoutManager.tv) {
var actionButtonsOnTop = layoutManager.mobile;
html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false);
html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false);
@ -137,7 +168,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
if (!actionButtonsOnTop) { if (!actionButtonsOnTop) {
html += '<div class="slideshowBottomBar hide">'; html += '<div class="slideshowBottomBar hide">';
html += getIcon('pause', 'btnSlideshowPause slideshowButton', true, true); html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true);
if (appHost.supports('filedownload')) { if (appHost.supports('filedownload')) {
html += getIcon('file_download', 'btnDownload slideshowButton', true); html += getIcon('file_download', 'btnDownload slideshowButton', true);
} }
@ -148,33 +179,28 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
html += '</div>'; html += '</div>';
} }
html += '</div>';
} else { } else {
html += '<div class="slideshowImage"></div><h1 class="slideshowImageText"></h1>'; html += '<div class="slideshowImage"></div><h1 class="slideshowImageText"></h1>';
} }
dlg.innerHTML = html; dialog.innerHTML = html;
if (options.interactive) { if (options.interactive && !layoutManager.tv) {
dlg.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) {
dialogHelper.close(dialog);
dialogHelper.close(dlg);
}); });
dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage);
dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage);
var btnPause = dlg.querySelector('.btnSlideshowPause'); var btnPause = dialog.querySelector('.btnSlideshowPause');
if (btnPause) { if (btnPause) {
btnPause.addEventListener('click', playPause); btnPause.addEventListener('click', playPause);
} }
var btnDownload = dlg.querySelector('.btnDownload'); var btnDownload = dialog.querySelector('.btnDownload');
if (btnDownload) { if (btnDownload) {
btnDownload.addEventListener('click', download); btnDownload.addEventListener('click', download);
} }
var btnShare = dlg.querySelector('.btnShare'); var btnShare = dialog.querySelector('.btnShare');
if (btnShare) { if (btnShare) {
btnShare.addEventListener('click', share); btnShare.addEventListener('click', share);
} }
@ -182,78 +208,104 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
setUserScalable(true); setUserScalable(true);
dialogHelper.open(dlg).then(function () { dialogHelper.open(dialog).then(function () {
setUserScalable(false); setUserScalable(false);
stopInterval();
}); });
inputManager.on(window, onInputCommand); inputManager.on(window, onInputCommand);
document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
dlg.addEventListener('close', onDialogClosed); dialog.addEventListener('close', onDialogClosed);
if (options.interactive) { loadSwiper(dialog, options);
loadSwiper(dlg);
}
} }
/**
* Handles OSD changes when the autoplay is started.
*/
function onAutoplayStart() { function onAutoplayStart() {
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i');
if (btnSlideshowPause) { if (btnSlideshowPause) {
btnSlideshowPause.classList.remove("play_arrow"); btnSlideshowPause.classList.replace("play_arrow", "pause");
btnSlideshowPause.classList.add("pause");
} }
} }
/**
* Handles OSD changes when the autoplay is stopped.
*/
function onAutoplayStop() { function onAutoplayStop() {
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i');
if (btnSlideshowPause) { if (btnSlideshowPause) {
btnSlideshowPause.classList.remove("pause"); btnSlideshowPause.classList.replace("pause", "play_arrow");
btnSlideshowPause.classList.add("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) { if (currentOptions.slides) {
dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.slides.map(getSwiperSlideHtmlFromSlide).join(''); slides = currentOptions.slides;
} else { } else {
dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.items.map(getSwiperSlideHtmlFromItem).join(''); slides = currentOptions.items;
} }
require(['swiper'], function (Swiper) { require(['swiper'], function (Swiper) {
swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), {
swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), {
// Optional parameters
direction: 'horizontal', direction: 'horizontal',
loop: options.loop !== false, // Loop is disabled due to the virtual slides option not supporting it.
autoplay: { loop: false,
delay: options.interval || 8000 autoplay: !options.interactive,
keyboard: {
enabled: true
}, },
// Disable preloading of all images preloadImages: true,
preloadImages: false, slidesPerView: 1,
// Enable lazy loading slidesPerColumn: 1,
lazy: true,
loadPrevNext: true,
disableOnInteraction: false,
initialSlide: options.startIndex || 0, 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('autoplayStart', onAutoplayStart);
swiperInstance.on('autoplayStop', onAutoplayStop); 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({ return getSwiperSlideHtmlFromSlide({
imageUrl: getImgUrl(item), imageUrl: getImgUrl(item),
originalImage: getImgUrl(item, true), originalImage: getImgUrl(item, true),
@ -264,11 +316,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) { function getSwiperSlideHtmlFromSlide(item) {
var html = ''; var html = '';
html += '<div class="swiper-slide" data-imageurl="' + item.imageUrl + '" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">'; html += '<div class="swiper-slide" 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="slider-zoom-container">';
html += '<img src="' + item.originalImage + '" class="swiper-slide-img">';
html += '</div>';
if (item.title || item.subtitle) { if (item.title || item.subtitle) {
html += '<div class="slideText">'; html += '<div class="slideText">';
html += '<div class="slideTextInner">'; html += '<div class="slideTextInner">';
@ -290,42 +348,18 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
return html; return html;
} }
function previousImage() { /**
if (swiperInstance) { * Fetches the information of the currently displayed slide.
swiperInstance.slidePrev(); * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide.
} 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);
}
}
function getCurrentImageInfo() { function getCurrentImageInfo() {
if (swiperInstance) { if (swiperInstance) {
var slide = document.querySelector('.swiper-slide-active'); var slide = document.querySelector('.swiper-slide-active');
if (slide) { if (slide) {
return { return {
url: slide.getAttribute('data-original'), url: slide.getAttribute('data-original'),
shareUrl: slide.getAttribute('data-imageurl'), shareUrl: slide.getAttribute('data-original'),
itemId: slide.getAttribute('data-itemid'), itemId: slide.getAttribute('data-itemid'),
serverId: slide.getAttribute('data-serverid') serverId: slide.getAttribute('data-serverid')
}; };
@ -336,8 +370,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
} }
/**
* Starts a download for the currently displayed slide.
*/
function download() { function download() {
var imageInfo = getCurrentImageInfo(); var imageInfo = getCurrentImageInfo();
require(['fileDownloader'], function (fileDownloader) { require(['fileDownloader'], function (fileDownloader) {
@ -345,8 +381,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}); });
} }
/**
* Shares the currently displayed slide using the browser's built-in sharing feature.
*/
function share() { function share() {
var imageInfo = getCurrentImageInfo(); var imageInfo = getCurrentImageInfo();
navigator.share({ navigator.share({
@ -354,20 +392,29 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}); });
} }
/**
* Starts the autoplay feature of the Swiper instance.
*/
function play() { function play() {
if (swiperInstance.autoplay) { if (swiperInstance.autoplay) {
swiperInstance.autoplay.start(); swiperInstance.autoplay.start();
} }
} }
/**
* Pauses the autoplay feature of the Swiper instance;
*/
function pause() { function pause() {
if (swiperInstance.autoplay) { if (swiperInstance.autoplay) {
swiperInstance.autoplay.stop(); swiperInstance.autoplay.stop();
} }
} }
/**
* Toggles the autoplay feature of the Swiper instance.
*/
function playPause() { function playPause() {
var paused = !dlg.querySelector('.btnSlideshowPause i').classList.contains("pause"); var paused = !dialog.querySelector('.btnSlideshowPause i').classList.contains("pause");
if (paused) { if (paused) {
play(); play();
} else { } else {
@ -375,8 +422,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
} }
/**
* Closes the dialog and destroys the Swiper instance.
*/
function onDialogClosed() { function onDialogClosed() {
var swiper = swiperInstance; var swiper = swiperInstance;
if (swiper) { if (swiper) {
swiper.destroy(true, true); swiper.destroy(true, true);
@ -387,53 +436,38 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
} }
function startInterval(options) { /**
* Shows the OSD.
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');
}
function showOsd() { function showOsd() {
var bottom = dialog.querySelector('.slideshowBottomBar');
var bottom = getOsdBottom();
if (bottom) { if (bottom) {
slideUpToShow(bottom); slideUpToShow(bottom);
startHideTimer(); startHideTimer();
} }
} }
/**
* Hides the OSD.
*/
function hideOsd() { function hideOsd() {
var bottom = dialog.querySelector('.slideshowBottomBar');
var bottom = getOsdBottom();
if (bottom) { if (bottom) {
slideDownToHide(bottom); slideDownToHide(bottom);
} }
} }
var hideTimeout; /**
* Starts the timer used to automatically hide the OSD.
*/
function startHideTimer() { function startHideTimer() {
stopHideTimer(); stopHideTimer();
hideTimeout = setTimeout(hideOsd, 4000); hideTimeout = setTimeout(hideOsd, 3000);
} }
/**
* Stops the timer used to automatically hide the OSD.
*/
function stopHideTimer() { function stopHideTimer() {
if (hideTimeout) { if (hideTimeout) {
clearTimeout(hideTimeout); clearTimeout(hideTimeout);
@ -441,71 +475,76 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
} }
function slideUpToShow(elem) { /**
* Shows the OSD by sliding it into view.
if (!elem.classList.contains('hide')) { * @param {HTMLElement} element - Element containing the OSD.
*/
function slideUpToShow(element) {
if (!element.classList.contains('hide')) {
return; return;
} }
_osdOpen = true; _osdOpen = true;
elem.classList.remove('hide'); element.classList.remove('hide');
var onFinish = function () { var onFinish = function () {
focusManager.focus(elem.querySelector('.btnSlideshowPause')); focusManager.focus(element.querySelector('.btnSlideshowPause'));
}; };
if (!elem.animate) { if (!element.animate) {
onFinish(); onFinish();
return; return;
} }
requestAnimationFrame(function () { requestAnimationFrame(function () {
var keyframes = [ 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 } { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 }
]; ];
var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; var timing = { duration: 300, iterations: 1, easing: 'ease-out' };
elem.animate(keyframes, timing).onfinish = onFinish; element.animate(keyframes, timing).onfinish = onFinish;
}); });
} }
function slideDownToHide(elem) { /**
* Hides the OSD by sliding it out of view.
if (elem.classList.contains('hide')) { * @param {HTMLElement} element - Element containing the OSD.
*/
function slideDownToHide(element) {
if (element.classList.contains('hide')) {
return; return;
} }
var onFinish = function () { var onFinish = function () {
elem.classList.add('hide'); element.classList.add('hide');
_osdOpen = false; _osdOpen = false;
}; };
if (!elem.animate) { if (!element.animate) {
onFinish(); onFinish();
return; return;
} }
requestAnimationFrame(function () { requestAnimationFrame(function () {
var keyframes = [ var keyframes = [
{ transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, { 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' }; var timing = { duration: 300, iterations: 1, easing: 'ease-out' };
elem.animate(keyframes, timing).onfinish = onFinish; element.animate(keyframes, timing).onfinish = onFinish;
}); });
} }
var lastMouseMoveData; /**
* Shows the OSD when moving the mouse pointer or touching the screen.
function onPointerMove(e) { * @param {Event} event - Pointer movement event.
*/
var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); function onPointerMove(event) {
var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
if (pointerType === 'mouse') { if (pointerType === 'mouse') {
var eventX = e.screenX || 0; var eventX = event.screenX || 0;
var eventY = e.screenY || 0; var eventY = event.screenY || 0;
var obj = lastMouseMoveData; var obj = lastMouseMoveData;
if (!obj) { if (!obj) {
@ -528,125 +567,46 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
} }
} }
function onInputCommand(e) { /**
* Dispatches keyboard inputs to their proper handlers.
switch (e.detail.command) { * @param {Event} event - Keyboard input event.
*/
case 'left': function onInputCommand(event) {
if (!isOsdOpen()) { switch (event.detail.command) {
e.preventDefault();
e.stopPropagation();
previousImage();
}
break;
case 'right':
if (!isOsdOpen()) {
e.preventDefault();
e.stopPropagation();
nextImage();
}
break;
case 'up': case 'up':
case 'down': case 'down':
case 'select': case 'select':
case 'menu': case 'menu':
case 'info': case 'info':
case 'play':
case 'playpause':
case 'pause':
showOsd(); showOsd();
break; break;
case 'play':
play();
break;
case 'pause':
pause();
break;
case 'playpause':
playPause();
break;
default: default:
break; break;
} }
} }
function showNextImage(index, skipPreload) { /**
* Shows the slideshow component.
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;
}
}
self.show = function () { self.show = function () {
startInterval(options); createElements(options);
}; };
/**
* Hides the slideshow element.
*/
self.hide = function () { self.hide = function () {
var dialog = dialog;
var dialog = dlg;
if (dialog) { if (dialog) {
dialogHelper.close(dialog); dialogHelper.close(dialog);
} }
}; };

View file

@ -2,15 +2,11 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
"use strict"; "use strict";
function populateLanguages(select, languages) { function populateLanguages(select, languages) {
var html = ""; var html = "";
html += "<option value=''>" + globalize.translate('AnyLanguage') + "</option>"; html += "<option value=''>" + globalize.translate('AnyLanguage') + "</option>";
for (var i = 0, length = languages.length; i < length; i++) { for (var i = 0, length = languages.length; i < length; i++) {
var culture = languages[i]; var culture = languages[i];
html += "<option value='" + culture.ThreeLetterISOLanguageName + "'>" + culture.DisplayName + "</option>"; html += "<option value='" + culture.ThreeLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
} }
@ -18,7 +14,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
} }
function getSubtitleAppearanceObject(context) { function getSubtitleAppearanceObject(context) {
var appearanceSettings = {}; var appearanceSettings = {};
appearanceSettings.textSize = context.querySelector('#selectTextSize').value; appearanceSettings.textSize = context.querySelector('#selectTextSize').value;
@ -102,14 +97,12 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
} }
function onSubmit(e) { function onSubmit(e) {
var self = this; var self = this;
var apiClient = connectionManager.getApiClient(self.options.serverId); var apiClient = connectionManager.getApiClient(self.options.serverId);
var userId = self.options.userId; var userId = self.options.userId;
var userSettings = self.options.userSettings; var userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(function () { userSettings.setUserInfo(userId, apiClient).then(function () {
var enableSaveConfirmation = self.options.enableSaveConfirmation; var enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
}); });
@ -118,6 +111,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
return false; return false;
} }
@ -197,9 +191,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
var userSettings = self.options.userSettings; var userSettings = self.options.userSettings;
apiClient.getUser(userId).then(function (user) { apiClient.getUser(userId).then(function (user) {
userSettings.setUserInfo(userId, apiClient).then(function () { userSettings.setUserInfo(userId, apiClient).then(function () {
self.dataLoaded = true; self.dataLoaded = true;
var appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); var appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey);
@ -214,7 +206,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
}; };
SubtitleSettings.prototype.destroy = function () { SubtitleSettings.prototype.destroy = function () {
this.options = null; this.options = null;
}; };

View file

@ -1,7 +1,5 @@
<form style="margin:0 auto;"> <form style="margin:0 auto;">
<div class="verticalSection"> <div class="verticalSection">
<h2 class="sectionTitle"> <h2 class="sectionTitle">
${Subtitles} ${Subtitles}
</h2> </h2>
@ -9,6 +7,7 @@
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" id="selectSubtitleLanguage" label="${LabelPreferredSubtitleLanguage}"></select> <select is="emby-select" id="selectSubtitleLanguage" label="${LabelPreferredSubtitleLanguage}"></select>
</div> </div>
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" id="selectSubtitlePlaybackMode" label="${LabelSubtitlePlaybackMode}"> <select is="emby-select" id="selectSubtitlePlaybackMode" label="${LabelSubtitlePlaybackMode}">
<option value="Default">${Default}</option> <option value="Default">${Default}</option>
@ -23,6 +22,7 @@
<div class="fieldDescription subtitlesOnlyForcedHelp subtitlesHelp hide">${OnlyForcedSubtitlesHelp}</div> <div class="fieldDescription subtitlesOnlyForcedHelp subtitlesHelp hide">${OnlyForcedSubtitlesHelp}</div>
<div class="fieldDescription subtitlesNoneHelp subtitlesHelp hide">${NoSubtitlesHelp}</div> <div class="fieldDescription subtitlesNoneHelp subtitlesHelp hide">${NoSubtitlesHelp}</div>
</div> </div>
<div class="selectContainer fldBurnIn hide"> <div class="selectContainer fldBurnIn hide">
<select is="emby-select" id="selectSubtitleBurnIn" label="${LabelBurnSubtitles}"> <select is="emby-select" id="selectSubtitleBurnIn" label="${LabelBurnSubtitles}">
<option value="">${Auto}</option> <option value="">${Auto}</option>
@ -34,7 +34,6 @@
</div> </div>
<div class="verticalSection subtitleAppearanceSection hide"> <div class="verticalSection subtitleAppearanceSection hide">
<h2 class="sectionTitle"> <h2 class="sectionTitle">
${HeaderSubtitleAppearance} ${HeaderSubtitleAppearance}
</h2> </h2>
@ -61,6 +60,7 @@
<option value="extralarge">${ExtraLarge}</option> <option value="extralarge">${ExtraLarge}</option>
</select> </select>
</div> </div>
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" id="selectFont" label="${LabelFont}"> <select is="emby-select" id="selectFont" label="${LabelFont}">
<option value="">${Default}</option> <option value="">${Default}</option>
@ -71,12 +71,15 @@
<option value="smallcaps">${SmallCaps}</option> <option value="smallcaps">${SmallCaps}</option>
</select> </select>
</div> </div>
<div class="inputContainer hide"> <div class="inputContainer hide">
<input is="emby-input" id="inputTextBackground" label="${LabelTextBackgroundColor}" type="text" /> <input is="emby-input" id="inputTextBackground" label="${LabelTextBackgroundColor}" type="text" />
</div> </div>
<div class="inputContainer hide"> <div class="inputContainer hide">
<input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="text" /> <input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="text" />
</div> </div>
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" id="selectDropShadow" label="${LabelDropShadow}"> <select is="emby-select" id="selectDropShadow" label="${LabelDropShadow}">
<option value="none">${None}</option> <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"; "use strict";
var player; var player;
@ -10,6 +10,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
function init(instance) { function init(instance) {
var parent = document.createElement('div'); var parent = document.createElement('div');
document.body.appendChild(parent);
parent.innerHTML = template; parent.innerHTML = template;
subtitleSyncSlider = parent.querySelector(".subtitleSyncSlider"); subtitleSyncSlider = parent.querySelector(".subtitleSyncSlider");
@ -17,11 +18,19 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
subtitleSyncCloseButton = parent.querySelector(".subtitleSync-closeButton"); subtitleSyncCloseButton = parent.querySelector(".subtitleSync-closeButton");
subtitleSyncContainer = parent.querySelector(".subtitleSyncContainer"); 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"); subtitleSyncContainer.classList.add("hide");
subtitleSyncTextField.updateOffset = function(offset) { subtitleSyncTextField.updateOffset = function(offset) {
this.textContent = offset + "s"; this.textContent = offset + "s";
} };
subtitleSyncTextField.addEventListener("keypress", function(event) { subtitleSyncTextField.addEventListener("keypress", function(event) {
@ -57,7 +66,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
subtitleSyncSlider.updateOffset = function(percent) { subtitleSyncSlider.updateOffset = function(percent) {
// default value is 0s = 50% // default value is 0s = 50%
this.value = percent === undefined ? 50 : percent; this.value = percent === undefined ? 50 : percent;
} };
subtitleSyncSlider.addEventListener("change", function () { subtitleSyncSlider.addEventListener("change", function () {
// set new offset // set new offset
@ -87,8 +96,6 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
SubtitleSync.prototype.toggle("forceToHide"); SubtitleSync.prototype.toggle("forceToHide");
}); });
document.body.appendChild(parent);
instance.element = parent; instance.element = parent;
} }
@ -125,7 +132,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
elem.parentNode.removeChild(elem); elem.parentNode.removeChild(elem);
this.element = null; this.element = null;
} }
} };
SubtitleSync.prototype.toggle = function(action) { SubtitleSync.prototype.toggle = function(action) {
@ -159,7 +166,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
} }
/* eslint-enable no-fallthrough */ /* eslint-enable no-fallthrough */
} }
} };
return SubtitleSync; return SubtitleSync;
}); });

3
src/config.example.json Normal file
View file

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

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"; "use strict";
function showPlaybackInfo(btn, session) { function showPlaybackInfo(btn, session) {
@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
getNowPlayingName: function (session) { getNowPlayingName: function (session) {
var imgUrl = ""; var imgUrl = "";
var nowPlayingItem = session.NowPlayingItem; 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) { if (!nowPlayingItem) {
return { return {
html: "Last seen " + humaneDate(session.LastActivityDate), html: globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)),
image: imgUrl image: imgUrl
}; };
} }

View file

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

View file

@ -49,12 +49,12 @@ define(["loading", "libraryMenu", "globalize", "listViewStyle", "emby-button"],
} }
page.querySelector(".notificationList").innerHTML = html; page.querySelector(".notificationList").innerHTML = html;
loading.hide(); loading.hide();
}) });
} }
return function(view, params) { return function(view, params) {
view.addEventListener("viewshow", function() { view.addEventListener("viewshow", function() {
reload(view); reload(view);
}); });
} };
}); });

View file

@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e
} }
if (installedPlugin) { 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); $("#pCurrentVersion", page).show().html(currentVersionText);
} else { } else {
$("#pCurrentVersion", page).hide().html(""); $("#pCurrentVersion", page).hide().html("");

View file

@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby
return ip.Id == plugin.guid; return ip.Id == plugin.guid;
})[0]; })[0];
html += "<div class='cardText cardText-secondary'>"; 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>"; html += "</div>";
html += "</div>"; html += "</div>";

View file

@ -2,7 +2,7 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button"
"use strict"; "use strict";
function deletePlugin(page, uniqueid, name) { function deletePlugin(page, uniqueid, name) {
var msg = globalize.translate("UninstallPluginConfirmation").replace("{0}", name); var msg = globalize.translate("UninstallPluginConfirmation", name);
require(["confirm"], function (confirm) { require(["confirm"], function (confirm) {
confirm({ confirm({

View file

@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
html += "</div>"; html += "</div>";
context.querySelector(".taskTriggers").innerHTML = html; context.querySelector(".taskTriggers").innerHTML = html;
}, },
// TODO: Replace this mess with date-fns and remove datetime completely
getTriggerFriendlyName: function (trigger) { getTriggerFriendlyName: function (trigger) {
if ("DailyTrigger" == trigger.Type) { if ("DailyTrigger" == trigger.Type) {
return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
} }
if ("WeeklyTrigger" == trigger.Type) { 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) { if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) {
return "On wake from sleep"; return globalize.translate("OnWakeFromSleep");
} }
if (trigger.Type == "IntervalTrigger") { if (trigger.Type == "IntervalTrigger") {
@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
var hours = trigger.IntervalTicks / 36e9; var hours = trigger.IntervalTicks / 36e9;
if (hours == 0.25) { if (hours == 0.25) {
return "Every 15 minutes"; return globalize.translate("EveryXMinutes", "15");
} }
if (hours == 0.5) { if (hours == 0.5) {
return "Every 30 minutes"; return globalize.translate("EveryXMinutes", "30");
} }
if (hours == 0.75) { if (hours == 0.75) {
return "Every 45 minutes"; return globalize.translate("EveryXMinutes", "45");
} }
if (hours == 1) { if (hours == 1) {
return "Every hour"; return globalize.translate("EveryHour");
} }
return "Every " + hours + " hours"; return globalize.translate("EveryXHours", hours);
} }
if (trigger.Type == "StartupTrigger") { if (trigger.Type == "StartupTrigger") {
return "On application startup"; return globalize.translate("OnApplicationStartup");
} }
return trigger.Type; 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"; "use strict";
function reloadList(page) { function reloadList(page) {
@ -7,7 +7,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
}).then(function(tasks) { }).then(function(tasks) {
populateList(page, tasks); populateList(page, tasks);
loading.hide(); loading.hide();
}) });
} }
function populateList(page, tasks) { function populateList(page, tasks) {
@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
var html = ""; var html = "";
if (task.State === "Idle") { if (task.State === "Idle") {
if (task.LastExecutionResult) { 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") { if (task.LastExecutionResult.Status === "Failed") {
html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>"; html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
} else if (task.LastExecutionResult.Status === "Cancelled") { } else if (task.LastExecutionResult.Status === "Cancelled") {
@ -152,7 +155,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
ApiClient.startScheduledTask(id).then(function() { ApiClient.startScheduledTask(id).then(function() {
updateTaskButton(button, "Running"); updateTaskButton(button, "Running");
reloadList(view); reloadList(view);
}) });
}); });
$(".divScheduledTasks", view).on("click", ".btnStopTask", function() { $(".divScheduledTasks", view).on("click", ".btnStopTask", function() {
@ -161,7 +164,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
ApiClient.stopScheduledTask(id).then(function() { ApiClient.stopScheduledTask(id).then(function() {
updateTaskButton(button, ""); updateTaskButton(button, "");
reloadList(view); reloadList(view);
}) });
}); });
view.addEventListener("viewbeforehide", function() { view.addEventListener("viewbeforehide", function() {
@ -175,5 +178,5 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
reloadList(view); reloadList(view);
events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); 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"; "use strict";
function canDelete(deviceId) { function canDelete(deviceId) {
@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu
if (device.LastUserName) { if (device.LastUserName) {
deviceHtml += device.LastUserName; deviceHtml += device.LastUserName;
deviceHtml += ", " + humaneDate(device.DateLastActivity); deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix);
} }
deviceHtml += "&nbsp;"; deviceHtml += "&nbsp;";

View file

@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; 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) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { 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 += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>Protocol: " + (profile.Protocol || "Http") + "</p>"; 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) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { 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 += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; 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) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; 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; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";
@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; 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) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; 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; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";
@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; 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) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { 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) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; 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; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";

View file

@ -14,6 +14,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
$("#txtVaapiDevice", page).val(config.VaapiDevice || ""); $("#txtVaapiDevice", page).val(config.VaapiDevice || "");
page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || ""; page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || "";
page.querySelector("#txtH264Crf").value = config.H264Crf || ""; page.querySelector("#txtH264Crf").value = config.H264Crf || "";
page.querySelector("#selectDeinterlaceMethod").value = config.DeinterlaceMethod || "";
page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false; page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false;
page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false; page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false;
page.querySelector("#selectVideoDecoder").dispatchEvent(new CustomEvent("change", { 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.VaapiDevice = $("#txtVaapiDevice", form).val();
config.EncoderPreset = form.querySelector("#selectEncoderPreset").value; config.EncoderPreset = form.querySelector("#selectEncoderPreset").value;
config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0"); config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0");
config.DeinterlaceMethod = form.querySelector("#selectDeinterlaceMethod").value;
config.EnableSubtitleExtraction = form.querySelector("#chkEnableSubtitleExtraction").checked; config.EnableSubtitleExtraction = form.querySelector("#chkEnableSubtitleExtraction").checked;
config.EnableThrottling = form.querySelector("#chkEnableThrottling").checked; config.EnableThrottling = form.querySelector("#chkEnableThrottling").checked;
config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll(".chkDecodeCodec"), function (c) { config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll(".chkDecodeCodec"), function (c) {

View file

@ -591,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try { try {
var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString();
itemBirthday.classList.remove("hide"); itemBirthday.classList.remove("hide");
itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday); itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday);
} catch (err) { } catch (err) {
itemBirthday.classList.add("hide"); itemBirthday.classList.add("hide");
} }
@ -605,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try { try {
var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString();
itemDeathDate.classList.remove("hide"); itemDeathDate.classList.remove("hide");
itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday); itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday);
} catch (err) { } catch (err) {
itemDeathDate.classList.add("hide"); itemDeathDate.classList.add("hide");
} }
@ -618,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) { 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>"; 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.classList.remove("hide");
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap); itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap);
} else { } else {
itemBirthLocation.classList.add("hide"); itemBirthLocation.classList.add("hide");
} }
@ -703,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) { function renderLinks(linksElem, item) {
var html = []; 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 = []; var links = [];
if (!layoutManager.tv && item.HomePageUrl) { if (!layoutManager.tv && item.HomePageUrl) {
@ -736,7 +719,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
} }
if (links.length) { if (links.length) {
html.push(globalize.translate("LinksValue", links.join(", "))); html.push(links.join(", "));
} }
linksElem.innerHTML = html.join(", "); linksElem.innerHTML = html.join(", ");
@ -1032,13 +1015,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
context: context context: context
}) + '">' + p.Name + "</a>"; }) + '">' + p.Name + "</a>";
}).join(", "); }).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) { if (genres.length) {
elem.classList.remove("hide"); genresGroup.classList.remove("hide");
} else { } else {
elem.classList.add("hide"); genresGroup.classList.add("hide");
} }
} }
@ -1056,13 +1043,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
context: context context: context
}) + '">' + p.Name + "</a>"; }) + '">' + p.Name + "</a>";
}).join(", "); }).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) { if (directors.length) {
elem.classList.remove("hide"); directorsGroup.classList.remove("hide");
} else { } else {
elem.classList.add("hide"); directorsGroup.classList.add("hide");
} }
} }
@ -1120,7 +1111,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
reloadUserDataButtons(page, item); reloadUserDataButtons(page, item);
renderLinks(externalLinksElem, item); renderLinks(externalLinksElem, item);
renderUserInfo(page, item);
renderTags(page, item); renderTags(page, item);
renderSeriesAirTime(page, item, isStatic); renderSeriesAirTime(page, item, isStatic);
} }

View file

@ -14,7 +14,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
}, { }, {
href: "metadatanfo.html", href: "metadatanfo.html",
name: Globalize.translate("TabNfoSettings") name: Globalize.translate("TabNfoSettings")
}] }];
} }
return function(view, params) { return function(view, params) {
@ -27,7 +27,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
view.querySelector("#chkSaveMetadataHidden").checked = config.SaveMetadataHidden; view.querySelector("#chkSaveMetadataHidden").checked = config.SaveMetadataHidden;
}); });
ApiClient.getNamedConfiguration("metadata").then(function(metadata) { 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

@ -48,7 +48,7 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc
} }
function getBackdropShape() { function getBackdropShape() {
return enableScrollX() ? "overflowBackdrop" : "backdrop" return enableScrollX() ? "overflowBackdrop" : "backdrop";
} }
function renderActiveRecordings(context, promise) { function renderActiveRecordings(context, promise) {

View file

@ -7,10 +7,10 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
html += "<option value=''></option>"; html += "<option value=''></option>";
for (var i = 0, length = languages.length; i < length; i++) { for (var i = 0, length = languages.length; i < length; i++) {
var culture = languages[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) { function populateCountries(select) {
@ -19,25 +19,25 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
html += "<option value=''></option>"; html += "<option value=''></option>";
for (var i = 0, length = allCountries.length; i < length; i++) { for (var i = 0, length = allCountries.length; i < length; i++) {
var culture = allCountries[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) { function loadPage(page) {
var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector("#selectLanguage")), populateCountries(page.querySelector("#selectCountry"))]; var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector("#selectLanguage")), populateCountries(page.querySelector("#selectCountry"))];
Promise.all(promises).then(function(responses) { Promise.all(promises).then(function(responses) {
var config = responses[0]; 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() { function onSubmit() {
var form = this; var form = this;
return loading.show(), ApiClient.getServerConfiguration().then(function(config) { 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) config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
}), !1 }), !1;
} }
function getTabs() { function getTabs() {
@ -53,12 +53,12 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
}, { }, {
href: "metadatanfo.html", href: "metadatanfo.html",
name: Globalize.translate("TabNfoSettings") name: Globalize.translate("TabNfoSettings")
}] }];
} }
$(document).on("pageinit", "#metadataImagesConfigurationPage", function() { $(document).on("pageinit", "#metadataImagesConfigurationPage", function() {
$(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit) $(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit);
}).on("pageshow", "#metadataImagesConfigurationPage", function() { }).on("pageshow", "#metadataImagesConfigurationPage", function() {
libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this) libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this);
}) });
}); });

View file

@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
switch (recommendation.RecommendationType) { switch (recommendation.RecommendationType) {
case "SimilarToRecentlyPlayed": case "SimilarToRecentlyPlayed":
title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName);
break; break;
case "SimilarToLikedItem": case "SimilarToLikedItem":
title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName);
break; break;
case "HasDirectorFromRecentlyPlayed": case "HasDirectorFromRecentlyPlayed":
case "HasLikedDirector": case "HasLikedDirector":
title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName);
break; break;
case "HasActorFromRecentlyPlayed": case "HasActorFromRecentlyPlayed":
case "HasLikedActor": case "HasLikedActor":
title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationStarring", recommendation.BaselineItemName);
break; break;
} }

View file

@ -36,7 +36,7 @@ define(["jQuery", "loading", "libraryMenu"], function ($, loading, libraryMenu)
} }
$(document).on("pageinit", "#playbackConfigurationPage", function () { $(document).on("pageinit", "#playbackConfigurationPage", function () {
$(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit) $(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit);
}).on("pageshow", "#playbackConfigurationPage", function () { }).on("pageshow", "#playbackConfigurationPage", function () {
loading.show(); loading.show();
libraryMenu.setTabs("playback", 1, getTabs); libraryMenu.setTabs("playback", 1, getTabs);

View file

@ -1,4 +1,4 @@
define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) { define(["displaySettings", "userSettings", "autoFocuser"], function (DisplaySettings, userSettings, autoFocuser) {
"use strict"; "use strict";
return function (view, params) { return function (view, params) {
@ -11,7 +11,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"]
var settingsInstance; var settingsInstance;
var hasChanges; var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId(); var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
view.addEventListener("viewshow", function () { view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload); window.addEventListener("beforeunload", onBeforeUnload);
@ -22,7 +22,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"]
serverId: ApiClient.serverId(), serverId: ApiClient.serverId(),
userId: userId, userId: userId,
element: view.querySelector(".settingsContainer"), element: view.querySelector(".settingsContainer"),
userSettings: userSettings, userSettings: currentSettings,
enableSaveButton: false, enableSaveButton: false,
enableSaveConfirmation: false, enableSaveConfirmation: false,
autoFocus: autoFocuser.isEnabled() autoFocus: autoFocuser.isEnabled()

View file

@ -1,4 +1,4 @@
define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { define(["homescreenSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) {
"use strict"; "use strict";
return function (view, params) { return function (view, params) {
@ -11,7 +11,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin
var homescreenSettingsInstance; var homescreenSettingsInstance;
var hasChanges; var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId(); var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
view.addEventListener("viewshow", function () { view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload); window.addEventListener("beforeunload", onBeforeUnload);
@ -22,7 +22,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin
serverId: ApiClient.serverId(), serverId: ApiClient.serverId(),
userId: userId, userId: userId,
element: view.querySelector(".homeScreenSettingsContainer"), element: view.querySelector(".homeScreenSettingsContainer"),
userSettings: userSettings, userSettings: currentSettings,
enableSaveButton: false, enableSaveButton: false,
enableSaveConfirmation: false, enableSaveConfirmation: false,
autoFocus: autoFocuser.isEnabled() autoFocus: autoFocuser.isEnabled()

View file

@ -1,4 +1,4 @@
define(["apphost", "connectionManager", "listViewStyle", "emby-button"], function(appHost, connectionManager) { define(["apphost", "connectionManager", "layoutManager", "listViewStyle", "emby-button"], function(appHost, connectionManager, layoutManager) {
"use strict"; "use strict";
return function(view, params) { return function(view, params) {
@ -10,6 +10,10 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
Dashboard.selectServer(); Dashboard.selectServer();
}); });
view.querySelector(".clientSettings").addEventListener("click", function () {
window.NativeShell.openClientSettings();
});
view.addEventListener("viewshow", function() { view.addEventListener("viewshow", function() {
// this page can also be used by admins to change user preferences from the user edit page // this page can also be used by admins to change user preferences from the user edit page
var userId = params.userId || Dashboard.getCurrentUserId(); var userId = params.userId || Dashboard.getCurrentUserId();
@ -21,6 +25,12 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
page.querySelector(".lnkPlaybackPreferences").setAttribute("href", "mypreferencesplayback.html?userId=" + userId); page.querySelector(".lnkPlaybackPreferences").setAttribute("href", "mypreferencesplayback.html?userId=" + userId);
page.querySelector(".lnkSubtitlePreferences").setAttribute("href", "mypreferencessubtitles.html?userId=" + userId); page.querySelector(".lnkSubtitlePreferences").setAttribute("href", "mypreferencessubtitles.html?userId=" + userId);
if (window.NativeShell && window.NativeShell.AppHost.supports("clientsettings")) {
page.querySelector(".clientSettings").classList.remove("hide");
} else {
page.querySelector(".clientSettings").classList.add("hide");
}
if (appHost.supports("multiserver")) { if (appHost.supports("multiserver")) {
page.querySelector(".selectServer").classList.remove("hide"); page.querySelector(".selectServer").classList.remove("hide");
} else { } else {
@ -33,6 +43,12 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
page.querySelector(".adminSection").classList.add("hide"); page.querySelector(".adminSection").classList.add("hide");
} }
if (layoutManager.mobile) {
page.querySelector(".headerUsername").classList.add("hide");
page.querySelector(".adminSection").classList.add("hide");
page.querySelector(".userSection").classList.add("hide");
}
ApiClient.getUser(userId).then(function(user) { ApiClient.getUser(userId).then(function(user) {
page.querySelector(".headerUsername").innerHTML = user.Name; page.querySelector(".headerUsername").innerHTML = user.Name;
if (!user.Policy.IsAdministrator) { if (!user.Policy.IsAdministrator) {

View file

@ -1,4 +1,4 @@
define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { define(["playbackSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) {
"use strict"; "use strict";
return function (view, params) { return function (view, params) {
@ -11,7 +11,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading"
var settingsInstance; var settingsInstance;
var hasChanges; var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId(); var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
view.addEventListener("viewshow", function () { view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload); window.addEventListener("beforeunload", onBeforeUnload);
@ -22,7 +22,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading"
serverId: ApiClient.serverId(), serverId: ApiClient.serverId(),
userId: userId, userId: userId,
element: view.querySelector(".settingsContainer"), element: view.querySelector(".settingsContainer"),
userSettings: userSettings, userSettings: currentSettings,
enableSaveButton: false, enableSaveButton: false,
enableSaveConfirmation: false, enableSaveConfirmation: false,
autoFocus: autoFocuser.isEnabled() autoFocus: autoFocuser.isEnabled()

View file

@ -1,4 +1,4 @@
define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) { define(["subtitleSettings", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettings, autoFocuser) {
"use strict"; "use strict";
return function (view, params) { return function (view, params) {
@ -11,7 +11,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"
var subtitleSettingsInstance; var subtitleSettingsInstance;
var hasChanges; var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId(); var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
view.addEventListener("viewshow", function () { view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload); window.addEventListener("beforeunload", onBeforeUnload);
@ -22,7 +22,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"
serverId: ApiClient.serverId(), serverId: ApiClient.serverId(),
userId: userId, userId: userId,
element: view.querySelector(".settingsContainer"), element: view.querySelector(".settingsContainer"),
userSettings: userSettings, userSettings: currentSettings,
enableSaveButton: false, enableSaveButton: false,
enableSaveConfirmation: false, enableSaveConfirmation: false,
autoFocus: autoFocuser.isEnabled() autoFocus: autoFocuser.isEnabled()

View file

@ -1,4 +1,4 @@
define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize) { define(["loading", "dom", "globalize", "date-fns", "dfnshelper", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize, datefns, dfnshelper) {
"use strict"; "use strict";
function deleteUser(page, id) { function deleteUser(page, id) {
@ -125,10 +125,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light",
html += "</div>"; html += "</div>";
return html + "</div>"; return html + "</div>";
} }
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
function getLastSeenText(lastActivityDate) { function getLastSeenText(lastActivityDate) {
if (lastActivityDate) { if (lastActivityDate) {
return "Last seen " + humaneDate(lastActivityDate); return globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix));
} }
return ""; return "";

View file

@ -55,7 +55,7 @@
height: 1.83em; height: 1.83em;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
border: 2px solid currentcolor; border: 0.14em solid currentcolor;
border-radius: 0.14em; border-radius: 0.14em;
z-index: 2; z-index: 2;
display: flex; display: flex;

View file

@ -4,7 +4,6 @@
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding-left: 24px;
} }
.radio-label-block { .radio-label-block {
@ -31,67 +30,82 @@
border: none; border: none;
} }
.mdl-radio__outer-circle { .mdl-radio__circles {
position: absolute; position: relative;
top: 4px; margin-right: 0.54em;
left: 0; width: 1.08em;
display: inline-block; height: 1.08em;
box-sizing: border-box;
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
border: 2px solid currentcolor;
border-radius: 50%; border-radius: 50%;
z-index: 2; cursor: pointer;
} }
.mdl-radio__button:checked + .mdl-radio__label + .mdl-radio__outer-circle { .mdl-radio__circles svg {
border: 2px solid #00a4dc; width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
overflow: visible;
} }
.mdl-radio__button:disabled + .mdl-radio__label + .mdl-radio__outer-circle { .mdl-radio__button:disabled + .mdl-radio__circles {
border: 2px solid rgba(0, 0, 0, 0.26);
cursor: auto; cursor: auto;
} }
.mdl-radio__button:disabled + .mdl-radio__circles .mdl-radio__outer-circle {
color: rgba(0, 0, 0, 0.26);
}
.mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__outer-circle {
color: #00a4dc;
}
.mdl-radio__inner-circle { .mdl-radio__inner-circle {
position: absolute; transition-duration: 0.2s;
z-index: 1;
margin: 0;
top: 8px;
left: 4px;
box-sizing: border-box;
width: 8px;
height: 8px;
cursor: pointer;
transition-duration: 0.28s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-property: -webkit-transform; transition-property: -webkit-transform;
transition-property: transform; transition-property: transform;
transition-property: transform, -webkit-transform; transition-property: transform, -webkit-transform;
-webkit-transform: scale3d(0, 0, 0); -webkit-transform: scale(0);
transform: scale3d(0, 0, 0); transform: scale(0);
transform-origin: 50% 50%;
}
.mdl-radio__button:checked + .mdl-radio__circles .mdl-radio__inner-circle {
-webkit-transform: scale(1);
transform: scale(1);
}
.mdl-radio__button:disabled + .mdl-radio__circles .mdl-radio__inner-circle {
color: rgba(0, 0, 0, 0.26);
}
.mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__inner-circle {
color: #00a4dc;
}
.mdl-radio__focus-circle {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
width: 100%;
height: 100%;
margin: 0;
border-radius: 50%; border-radius: 50%;
background: #00a4dc; background: #00a4dc;
opacity: 0.26;
transition-duration: 0.2s;
transition-property: -webkit-transform;
transition-property: transform;
transition-property: transform, -webkit-transform;
-webkit-transform: scale(0);
transform: scale(0);
} }
.mdl-radio__button:checked + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle { .mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__focus-circle {
-webkit-transform: scale3d(1, 1, 1); -webkit-transform: scale(1.75);
transform: scale3d(1, 1, 1); transform: scale(1.75);
}
.mdl-radio__button:disabled + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
background: rgba(0, 0, 0, 0.26);
cursor: auto;
}
.mdl-radio__button:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0.76);
}
.mdl-radio__button:checked:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
box-shadow: 0 0 0 10px rgba(0, 164, 220, 0.26);
} }
.mdl-radio__label { .mdl-radio__label {

View file

@ -1,4 +1,4 @@
define(['css!./emby-radio', 'registerElement'], function () { define(['layoutManager', 'css!./emby-radio', 'registerElement'], function (layoutManager) {
'use strict'; 'use strict';
var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype);
@ -23,6 +23,7 @@ define(['css!./emby-radio', 'registerElement'], function () {
} }
EmbyRadioPrototype.attachedCallback = function () { EmbyRadioPrototype.attachedCallback = function () {
var showFocus = !layoutManager.mobile;
if (this.getAttribute('data-radio') === 'true') { if (this.getAttribute('data-radio') === 'true') {
return; return;
@ -37,13 +38,36 @@ define(['css!./emby-radio', 'registerElement'], function () {
labelElement.classList.add('mdl-radio'); labelElement.classList.add('mdl-radio');
labelElement.classList.add('mdl-js-radio'); labelElement.classList.add('mdl-js-radio');
labelElement.classList.add('mdl-js-ripple-effect'); labelElement.classList.add('mdl-js-ripple-effect');
if (showFocus) {
labelElement.classList.add('show-focus');
}
var labelTextElement = labelElement.querySelector('span'); var labelTextElement = labelElement.querySelector('span');
labelTextElement.classList.add('radioButtonLabel'); labelTextElement.classList.add('radioButtonLabel');
labelTextElement.classList.add('mdl-radio__label'); labelTextElement.classList.add('mdl-radio__label');
labelElement.insertAdjacentHTML('beforeend', '<span class="mdl-radio__outer-circle"></span><span class="mdl-radio__inner-circle"></span>'); var html = '';
html += '<div class="mdl-radio__circles">';
html += '<svg>';
html += '<defs>';
html += '<clipPath id="cutoff">';
html += '<circle cx="50%" cy="50%" r="50%" />';
html += '</clipPath>';
html += '</defs>';
html += '<circle class="mdl-radio__outer-circle" cx="50%" cy="50%" r="50%" fill="none" stroke="currentcolor" stroke-width="0.26em" clip-path="url(#cutoff)" />';
html += '<circle class="mdl-radio__inner-circle" cx="50%" cy="50%" r="25%" fill="currentcolor" />';
html += '</svg>';
if (showFocus) {
html += '<div class="mdl-radio__focus-circle"></div>';
}
html += '</div>';
this.insertAdjacentHTML('afterend', html);
this.addEventListener('keydown', onKeyDown); this.addEventListener('keydown', onKeyDown);
}; };

View file

@ -109,7 +109,7 @@
} }
.selectArrow { .selectArrow {
margin-top: 0.35em; margin-top: 1.2em;
font-size: 1.7em; font-size: 1.7em;
} }

View file

@ -144,7 +144,7 @@ define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registe
this.parentNode.insertBefore(label, this); this.parentNode.insertBefore(label, this);
if (this.classList.contains('emby-select-withcolor')) { if (this.classList.contains('emby-select-withcolor')) {
this.parentNode.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;">0</div><i class="selectArrow material-icons keyboard_arrow_down"></i></div>'); this.parentNode.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;display:none;">0</div><i class="selectArrow material-icons keyboard_arrow_down"></i></div>');
} }
}; };

View file

@ -402,7 +402,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli
this.addEventListener('keydown', onKeyDown); this.addEventListener('keydown', onKeyDown);
this.keyboardDraggingEnabled = true; this.keyboardDraggingEnabled = true;
} }
} };
/** /**
* Set steps for keyboard input. * Set steps for keyboard input.
@ -413,7 +413,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli
EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) {
this.keyboardStepDown = stepDown || stepUp || 1; this.keyboardStepDown = stepDown || stepUp || 1;
this.keyboardStepUp = stepUp || stepDown || 1; this.keyboardStepUp = stepUp || stepDown || 1;
} };
function setRange(elem, startPercent, endPercent) { function setRange(elem, startPercent, endPercent) {

View file

@ -136,6 +136,14 @@
<div class="fieldDescription">${H264CrfHelp}</div> <div class="fieldDescription">${H264CrfHelp}</div>
</div> </div>
<div class="selectContainer">
<select is="emby-select" id="selectDeinterlaceMethod" label="${LabelDeinterlaceMethod}">
<option value="yadif">${Yadif}</option>
<option value="yadif_bob">${YadifBob}</option>
</select>
<div class="fieldDescription">${DeinterlaceMethodHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label> <label>
<input is="emby-checkbox" type="checkbox" id="chkEnableSubtitleExtraction" /> <input is="emby-checkbox" type="checkbox" id="chkEnableSubtitleExtraction" />

View file

@ -111,22 +111,32 @@
<div class="detailImageContainer padded-left"></div> <div class="detailImageContainer padded-left"></div>
<div class="detailPageContent"> <div class="detailPageContent">
<div class="detailPagePrimaryContent padded-left padded-right"> <div class="detailPagePrimaryContent padded-left padded-right">
<div class="detailSection" style="margin-bottom: 0;"> <div class="detailSection">
<div class="itemMiscInfo nativeName hide"></div> <div class="itemMiscInfo nativeName hide"></div>
<div class="genres hide" style="margin: .7em 0;font-size:92%;"></div>
<div class="directors hide" style="margin: .7em 0;font-size:92%;"></div>
<form class="trackSelections flex align-items-center flex-wrap-wrap hide focuscontainer-x"> <div class="itemDetailsGroup">
<div class="selectContainer selectContainer-inline selectSourceContainer hide trackSelectionFieldContainer flex-shrink-zero"> <div class="detailsGroupItem genresGroup hide">
<div class="genresLabel label"></div>
<div class="genres content"></div>
</div>
<div class="detailsGroupItem directorsGroup hide">
<div class="directorsLabel label"></div>
<div class="directors content"></div>
</div>
</div>
<form class="trackSelections hide focuscontainer-x">
<div class="selectContainer selectSourceContainer hide trackSelectionFieldContainer flex-shrink-zero">
<select is="emby-select" class="selectSource detailTrackSelect" label=""></select> <select is="emby-select" class="selectSource detailTrackSelect" label=""></select>
</div> </div>
<div class="selectContainer selectContainer-inline selectVideoContainer hide trackSelectionFieldContainer flex-shrink-zero"> <div class="selectContainer selectVideoContainer hide trackSelectionFieldContainer flex-shrink-zero">
<select is="emby-select" class="selectVideo detailTrackSelect" label=""></select> <select is="emby-select" class="selectVideo detailTrackSelect" label=""></select>
</div> </div>
<div class="selectContainer selectContainer-inline selectAudioContainer hide trackSelectionFieldContainer flex-shrink-zero"> <div class="selectContainer selectAudioContainer hide trackSelectionFieldContainer flex-shrink-zero">
<select is="emby-select" class="selectAudio detailTrackSelect" label=""></select> <select is="emby-select" class="selectAudio detailTrackSelect" label=""></select>
</div> </div>
<div class="selectContainer selectContainer-inline selectSubtitlesContainer hide trackSelectionFieldContainer"> <div class="selectContainer selectSubtitlesContainer hide trackSelectionFieldContainer">
<select is="emby-select" class="selectSubtitles detailTrackSelect" label=""></select> <select is="emby-select" class="selectSubtitles detailTrackSelect" label=""></select>
</div> </div>
</form> </form>
@ -176,8 +186,8 @@
</div> </div>
<div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide"> <div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderAdditionalParts}</h2> <h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderAdditionalParts}</h2>
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div> <div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
</div> </div>
<div class="verticalSection itemVerticalSection moreFromSeasonSection hide"> <div class="verticalSection itemVerticalSection moreFromSeasonSection hide">
@ -203,17 +213,17 @@
<div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide"> <div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle padded-left padded-right">${HeaderUpcomingOnTV}</h2> <h2 class="sectionTitle padded-left padded-right">${HeaderUpcomingOnTV}</h2>
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list"></div> <div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-left padded-right"></div>
</div> </div>
<div id="specialsCollapsible" class="verticalSection detailVerticalSection hide"> <div id="specialsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderSpecialFeatures}</h2> <h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderSpecialFeatures}</h2>
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div> <div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
</div> </div>
<div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide"> <div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderMusicVideos}</h2> <h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMusicVideos}</h2>
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div> <div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
</div> </div>
<div id="scenesCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide"> <div id="scenesCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide">

View file

@ -2,19 +2,19 @@ Dashboard.confirm = function(message, title, callback) {
"use strict"; "use strict";
require(["confirm"], function(confirm) { require(["confirm"], function(confirm) {
confirm(message, title).then(function() { confirm(message, title).then(function() {
callback(!0) callback(!0);
}, function() { }, function() {
callback(!1) callback(!1);
}) });
}) });
}, Dashboard.showLoadingMsg = function() { }, Dashboard.showLoadingMsg = function() {
"use strict"; "use strict";
require(["loading"], function(loading) { require(["loading"], function(loading) {
loading.show() loading.show();
}) });
}, Dashboard.hideLoadingMsg = function() { }, Dashboard.hideLoadingMsg = function() {
"use strict"; "use strict";
require(["loading"], function(loading) { require(["loading"], function(loading) {
loading.hide() loading.hide();
}) });
}; };

View file

@ -2,9 +2,9 @@ define(["jQuery"], function($) {
"use strict"; "use strict";
$.fn.checked = function(value) { $.fn.checked = function(value) {
return !0 === value || !1 === value ? $(this).each(function() { return !0 === value || !1 === value ? $(this).each(function() {
this.checked = value this.checked = value;
}) : this.length && this[0].checked }) : this.length && this[0].checked;
}, $.fn.checkboxradio = function() { }, $.fn.checkboxradio = function() {
return this return this;
} };
}); });

View file

@ -1,6 +1,6 @@
define(["jQuery"], function($) { define(["jQuery"], function($) {
"use strict"; "use strict";
$.fn.selectmenu = function() { $.fn.selectmenu = function() {
return this return this;
} };
}); });

View file

@ -8,7 +8,7 @@
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<input is="emby-input" type="text" id="txtManualName" required="required" label="${LabelUser}" autocomplete="username" /> <input is="emby-input" type="text" id="txtManualName" required="required" label="${LabelUser}" autocomplete="username" autocapitalize="off" />
</div> </div>
<div class="inputContainer"> <div class="inputContainer">

View file

@ -47,6 +47,15 @@
</div> </div>
</div> </div>
</a> </a>
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="clientSettings listItem-border">
<div class="listItem">
<i class="material-icons listItemIcon listItemIcon-transparent devices_other"></i>
<div class="listItemBody">
<div class="listItemBodyText">${ClientSettings}</div>
</div>
</div>
</a>
</div> </div>
<div class="adminSection verticalSection verticalSection-extrabottompadding"> <div class="adminSection verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle" style="padding-left:.25em;">${HeaderAdmin}</h2> <h2 class="sectionTitle" style="padding-left:.25em;">${HeaderAdmin}</h2>

View file

@ -292,7 +292,7 @@ define([], function () {
} }
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
browser.touch = true; browser.touch = true;
} }
} }

View file

@ -433,14 +433,10 @@ define(['browser'], function (browser) {
var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts; var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts;
if (self.tizen && self.tizen.systeminfo) {
var v = tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version');
// DTS audio not supported in 2018 models (Tizen 4.0) // DTS audio not supported in 2018 models (Tizen 4.0)
if (v && parseFloat(v) >= parseFloat('4.0')) { if (browser.tizenVersion >= 4) {
supportsDts = false; supportsDts = false;
} }
}
if (supportsDts) { if (supportsDts) {
videoAudioCodecs.push('dca'); videoAudioCodecs.push('dca');
@ -766,6 +762,11 @@ define(['browser'], function (browser) {
maxH264Level = 51; maxH264Level = 51;
} }
// Support H264 Level 52 (Tizen 5.0) - app only
if (browser.tizenVersion >= 5 && window.NativeShell) {
maxH264Level = 52;
}
if (browser.tizen || browser.orsay || if (browser.tizen || browser.orsay ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {

104
src/scripts/dfnshelper.js Normal file
View file

@ -0,0 +1,104 @@
import { ar, be, bg, ca, cs, da, de, el, enGB, enUS, es, faIR, fi, fr, frCA, he, hi, hr, hu, id, it, kk, ko, lt, ms, nb, nl, pl, ptBR, pt, ro, ru, sk, sl, sv, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
import globalize from 'globalize';
export function getLocale() {
switch (globalize.getCurrentLocale()) {
case 'ar':
return ar;
case 'be-by':
return be;
case 'bg-bg':
return bg;
case 'ca':
return ca;
case 'cs':
return cs;
case 'da':
return da;
case 'de':
return de;
case 'el':
return el;
case 'en-gb':
return enGB;
case 'en-us':
return enUS;
case 'es':
return es;
case 'es-ar':
return es;
case 'es-mx':
return es;
case 'fa':
return faIR;
case 'fi':
return fi;
case 'fr':
return fr;
case 'fr-ca':
return frCA;
case 'gsw':
return de;
case 'he':
return he;
case 'hi-in':
return hi;
case 'hr':
return hr;
case 'hu':
return hu;
case 'id':
return id;
case 'it':
return it;
case 'kk':
return kk;
case 'ko':
return ko;
case 'lt-lt':
return lt;
case 'ms':
return ms;
case 'nb':
return nb;
case 'nl':
return nl;
case 'pl':
return pl;
case 'pt-br':
return ptBR;
case 'pt-pt':
return pt;
case 'ro':
return ro;
case 'ru':
return ru;
case 'sk':
return sk;
case 'sl-si':
return sl;
case 'sv':
return sv;
case 'tr':
return tr;
case 'uk':
return uk;
case 'vi':
return vi;
case 'zh-cn':
return zhCN;
case 'zh-hk':
return zhCN;
case 'zh-tw':
return zhTW;
default:
return enUS;
}
}
export const localeWithSuffix = { addSuffix: true, locale: getLocale() };
export default {
getLocale: getLocale,
localeWithSuffix: localeWithSuffix
};

View file

@ -1,4 +1,4 @@
define(['connectionManager', 'userSettings', 'events'], function (connectionManager, userSettings, events) { define(['userSettings', 'events'], function (userSettings, events) {
'use strict'; 'use strict';
var fallbackCulture = 'en-us'; var fallbackCulture = 'en-us';
@ -253,7 +253,6 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana
updateCurrentCulture(); updateCurrentCulture();
events.on(connectionManager, 'localusersignedin', updateCurrentCulture);
events.on(userSettings, 'change', function (e, name) { events.on(userSettings, 'change', function (e, name) {
if (name === 'language' || name === 'datetimelocale') { if (name === 'language' || name === 'datetimelocale') {
updateCurrentCulture(); updateCurrentCulture();
@ -269,6 +268,7 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana
defaultModule: defaultModule, defaultModule: defaultModule,
getCurrentLocale: getCurrentLocale, getCurrentLocale: getCurrentLocale,
getCurrentDateTimeLocale: getCurrentDateTimeLocale, getCurrentDateTimeLocale: getCurrentDateTimeLocale,
register: register register: register,
updateCurrentCulture: updateCurrentCulture
}; };
}); });

View file

@ -1,7 +1,8 @@
define(["browser"], function (browser) { /* eslint-disable indent */
"use strict";
function getDeviceIcon(device) { import browser from 'browser';
export function getDeviceIcon(device) {
var baseUrl = "assets/img/devices/"; var baseUrl = "assets/img/devices/";
switch (device.AppName || device.Client) { switch (device.AppName || device.Client) {
case "Samsung Smart TV": case "Samsung Smart TV":
@ -42,7 +43,7 @@ define(["browser"], function (browser) {
} }
} }
function getLibraryIcon(library) { export function getLibraryIcon(library) {
switch (library) { switch (library) {
case "movies": case "movies":
return "video_library"; return "video_library";
@ -71,8 +72,9 @@ define(["browser"], function (browser) {
} }
} }
return { /* eslint-enable indent */
export default {
getDeviceIcon: getDeviceIcon, getDeviceIcon: getDeviceIcon,
getLibraryIcon: getLibraryIcon getLibraryIcon: getLibraryIcon
}; };
});

View file

@ -83,7 +83,7 @@ define(["userSettings"], function (userSettings) {
if (html += '<div class="listPaging">', showControls) { if (html += '<div class="listPaging">', showControls) {
html += '<span style="vertical-align:middle;">'; html += '<span style="vertical-align:middle;">';
html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount; html += Globalize.translate("ListPaging", (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount);
html += "</span>"; html += "</span>";
} }

View file

@ -243,15 +243,20 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder" data-itemid="selectserver" href="selectserver.html?showuser=1"><i class="material-icons navMenuOptionIcon">wifi</i><span class="navMenuOptionText">' + globalize.translate("ButtonSelectServer") + "</span></a>"; html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder" data-itemid="selectserver" href="selectserver.html?showuser=1"><i class="material-icons navMenuOptionIcon">wifi</i><span class="navMenuOptionText">' + globalize.translate("ButtonSelectServer") + "</span></a>";
} }
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><i class="material-icons navMenuOptionIcon settings"></i><span class="navMenuOptionText">' + globalize.translate("ButtonSettings") + "</span></a>";
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><i class="material-icons navMenuOptionIcon exit_to_app"></i><span class="navMenuOptionText">' + globalize.translate("ButtonSignOut") + "</span></a>"; html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><i class="material-icons navMenuOptionIcon exit_to_app"></i><span class="navMenuOptionText">' + globalize.translate("ButtonSignOut") + "</span></a>";
html += "</div>"; html += "</div>";
} }
// add buttons to navigation drawer // add buttons to navigation drawer
navDrawerScrollContainer.innerHTML = html; navDrawerScrollContainer.innerHTML = html;
// bind logout button click to method
var btnLogout = navDrawerScrollContainer.querySelector(".btnLogout");
var btnSettings = navDrawerScrollContainer.querySelector(".btnSettings");
if (btnSettings) {
btnSettings.addEventListener("click", onSettingsClick);
}
var btnLogout = navDrawerScrollContainer.querySelector(".btnLogout");
if (btnLogout) { if (btnLogout) {
btnLogout.addEventListener("click", onLogoutClick); btnLogout.addEventListener("click", onLogoutClick);
} }
@ -598,6 +603,10 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
} }
} }
function onSettingsClick() {
Dashboard.navigate("mypreferencesmenu.html");
}
function onLogoutClick() { function onLogoutClick() {
Dashboard.logout(); Dashboard.logout();
} }

View file

@ -1,5 +1,7 @@
define(['appStorage', 'events'], function (appStorage, events) { /* eslint-disable indent */
'use strict';
import appStorage from 'appStorage';
import events from 'events';
function getKey(name, userId) { function getKey(name, userId) {
if (userId) { if (userId) {
@ -9,18 +11,23 @@ define(['appStorage', 'events'], function (appStorage, events) {
return name; return name;
} }
function AppSettings() { export function enableAutoLogin(val) {
}
AppSettings.prototype.enableAutoLogin = function (val) {
if (val != null) { if (val != null) {
this.set('enableAutoLogin', val.toString()); this.set('enableAutoLogin', val.toString());
} }
return this.get('enableAutoLogin') !== 'false'; return this.get('enableAutoLogin') !== 'false';
}; }
AppSettings.prototype.enableAutomaticBitrateDetection = function (isInNetwork, mediaType, val) { export function enableSystemExternalPlayers(val) {
if (val !== null) {
this.set('enableSystemExternalPlayers', val.toString());
}
return this.get('enableSystemExternalPlayers') === 'true';
}
export function enableAutomaticBitrateDetection(isInNetwork, mediaType, val) {
var key = 'enableautobitratebitrate-' + mediaType + '-' + isInNetwork; var key = 'enableautobitratebitrate-' + mediaType + '-' + isInNetwork;
if (val != null) { if (val != null) {
if (isInNetwork && mediaType === 'Audio') { if (isInNetwork && mediaType === 'Audio') {
@ -35,9 +42,9 @@ define(['appStorage', 'events'], function (appStorage, events) {
} else { } else {
return this.get(key) !== 'false'; return this.get(key) !== 'false';
} }
}; }
AppSettings.prototype.maxStreamingBitrate = function (isInNetwork, mediaType, val) { export function maxStreamingBitrate(isInNetwork, mediaType, val) {
var key = 'maxbitrate-' + mediaType + '-' + isInNetwork; var key = 'maxbitrate-' + mediaType + '-' + isInNetwork;
if (val != null) { if (val != null) {
if (isInNetwork && mediaType === 'Audio') { if (isInNetwork && mediaType === 'Audio') {
@ -53,43 +60,43 @@ define(['appStorage', 'events'], function (appStorage, events) {
} else { } else {
return parseInt(this.get(key) || '0') || 1500000; return parseInt(this.get(key) || '0') || 1500000;
} }
}; }
AppSettings.prototype.maxStaticMusicBitrate = function (val) { export function maxStaticMusicBitrate(val) {
if (val !== undefined) { if (val !== undefined) {
this.set('maxStaticMusicBitrate', val); this.set('maxStaticMusicBitrate', val);
} }
var defaultValue = 320000; var defaultValue = 320000;
return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue; return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue;
}; }
AppSettings.prototype.maxChromecastBitrate = function (val) { export function maxChromecastBitrate(val) {
if (val != null) { if (val != null) {
this.set('chromecastBitrate1', val); this.set('chromecastBitrate1', val);
} }
val = this.get('chromecastBitrate1'); val = this.get('chromecastBitrate1');
return val ? parseInt(val) : null; return val ? parseInt(val) : null;
}; }
AppSettings.prototype.syncOnlyOnWifi = function (val) { export function syncOnlyOnWifi(val) {
if (val != null) { if (val != null) {
this.set('syncOnlyOnWifi', val.toString()); this.set('syncOnlyOnWifi', val.toString());
} }
return this.get('syncOnlyOnWifi') !== 'false'; return this.get('syncOnlyOnWifi') !== 'false';
}; }
AppSettings.prototype.syncPath = function (val) { export function syncPath(val) {
if (val != null) { if (val != null) {
this.set('syncPath', val); this.set('syncPath', val);
} }
return this.get('syncPath'); return this.get('syncPath');
}; }
AppSettings.prototype.cameraUploadServers = function (val) { export function cameraUploadServers(val) {
if (val != null) { if (val != null) {
this.set('cameraUploadServers', val.join(',')); this.set('cameraUploadServers', val.join(','));
} }
@ -100,36 +107,42 @@ define(['appStorage', 'events'], function (appStorage, events) {
} }
return []; return [];
}; }
AppSettings.prototype.runAtStartup = function (val) { export function runAtStartup(val) {
if (val != null) { if (val != null) {
this.set('runatstartup', val.toString()); this.set('runatstartup', val.toString());
} }
return this.get('runatstartup') === 'true'; return this.get('runatstartup') === 'true';
}; }
AppSettings.prototype.set = function (name, value, userId) { export function set(name, value, userId) {
var currentValue = this.get(name, userId); var currentValue = this.get(name, userId);
appStorage.setItem(getKey(name, userId), value); appStorage.setItem(getKey(name, userId), value);
if (currentValue !== value) { if (currentValue !== value) {
events.trigger(this, 'change', [name]); events.trigger(this, 'change', [name]);
} }
};
AppSettings.prototype.get = function (name, userId) {
return appStorage.getItem(getKey(name, userId));
};
AppSettings.prototype.enableSystemExternalPlayers = function (val) {
if (val != null) {
this.set('enableSystemExternalPlayers', val.toString());
} }
return this.get('enableSystemExternalPlayers') === 'true'; export function get(name, userId) {
}; return appStorage.getItem(getKey(name, userId));
}
return new AppSettings(); /* eslint-enable indent */
});
export default {
enableAutoLogin: enableAutoLogin,
enableSystemExternalPlayers: enableSystemExternalPlayers,
enableAutomaticBitrateDetection: enableAutomaticBitrateDetection,
maxStreamingBitrate: maxStreamingBitrate,
maxStaticMusicBitrate: maxStaticMusicBitrate,
maxChromecastBitrate: maxChromecastBitrate,
syncOnlyOnWifi: syncOnlyOnWifi,
syncPath: syncPath,
cameraUploadServers: cameraUploadServers,
runAtStartup: runAtStartup,
set: set,
get: get
};

View file

@ -1,5 +1,7 @@
define(['appSettings', 'events'], function (appSettings, events) { /* eslint-disable indent */
'use strict';
import appSettings from 'appSettings';
import events from 'events';
function onSaveTimeout() { function onSaveTimeout() {
var self = this; var self = this;
@ -15,10 +17,7 @@ define(['appSettings', 'events'], function (appSettings, events) {
instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50); instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50);
} }
function UserSettings() { export function setUserInfo(userId, apiClient) {
}
UserSettings.prototype.setUserInfo = function (userId, apiClient) {
if (this.saveTimeout) { if (this.saveTimeout) {
clearTimeout(this.saveTimeout); clearTimeout(this.saveTimeout);
} }
@ -37,17 +36,17 @@ define(['appSettings', 'events'], function (appSettings, events) {
result.CustomPrefs = result.CustomPrefs || {}; result.CustomPrefs = result.CustomPrefs || {};
self.displayPrefs = result; self.displayPrefs = result;
}); });
}; }
UserSettings.prototype.getData = function () { export function getData() {
return this.displayPrefs; return this.displayPrefs;
}; }
UserSettings.prototype.importFrom = function (instance) { export function importFrom(instance) {
this.displayPrefs = instance.getData(); this.displayPrefs = instance.getData();
}; }
UserSettings.prototype.set = function (name, value, enableOnServer) { export function set(name, value, enableOnServer) {
var userId = this.currentUserId; var userId = this.currentUserId;
var currentValue = this.get(name, enableOnServer); var currentValue = this.get(name, enableOnServer);
var result = appSettings.set(name, value, userId); var result = appSettings.set(name, value, userId);
@ -62,18 +61,18 @@ define(['appSettings', 'events'], function (appSettings, events) {
} }
return result; return result;
}; }
UserSettings.prototype.get = function (name, enableOnServer) { export function get(name, enableOnServer) {
var userId = this.currentUserId; var userId = this.currentUserId;
if (enableOnServer !== false && this.displayPrefs) { if (enableOnServer !== false && this.displayPrefs) {
return this.displayPrefs.CustomPrefs[name]; return this.displayPrefs.CustomPrefs[name];
} }
return appSettings.get(name, userId); return appSettings.get(name, userId);
}; }
UserSettings.prototype.serverConfig = function (config) { export function serverConfig(config) {
var apiClient = this.currentApiClient; var apiClient = this.currentApiClient;
if (config) { if (config) {
return apiClient.updateUserConfiguration(this.currentUserId, config); return apiClient.updateUserConfiguration(this.currentUserId, config);
@ -82,135 +81,135 @@ define(['appSettings', 'events'], function (appSettings, events) {
return apiClient.getUser(this.currentUserId).then(function (user) { return apiClient.getUser(this.currentUserId).then(function (user) {
return user.Configuration; return user.Configuration;
}); });
}; }
UserSettings.prototype.enableCinemaMode = function (val) { export function enableCinemaMode(val) {
if (val != null) { if (val != null) {
return this.set('enableCinemaMode', val.toString(), false); return this.set('enableCinemaMode', val.toString(), false);
} }
val = this.get('enableCinemaMode', false); val = this.get('enableCinemaMode', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.enableNextVideoInfoOverlay = function (val) { export function enableNextVideoInfoOverlay(val) {
if (val != null) { if (val != null) {
return this.set('enableNextVideoInfoOverlay', val.toString()); return this.set('enableNextVideoInfoOverlay', val.toString());
} }
val = this.get('enableNextVideoInfoOverlay', false); val = this.get('enableNextVideoInfoOverlay', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.enableThemeSongs = function (val) { export function enableThemeSongs(val) {
if (val != null) { if (val != null) {
return this.set('enableThemeSongs', val.toString(), false); return this.set('enableThemeSongs', val.toString(), false);
} }
val = this.get('enableThemeSongs', false); val = this.get('enableThemeSongs', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.enableThemeVideos = function (val) { export function enableThemeVideos(val) {
if (val != null) { if (val != null) {
return this.set('enableThemeVideos', val.toString(), false); return this.set('enableThemeVideos', val.toString(), false);
} }
val = this.get('enableThemeVideos', false); val = this.get('enableThemeVideos', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.enableFastFadein = function (val) { export function enableFastFadein(val) {
if (val != null) { if (val != null) {
return this.set('fastFadein', val.toString(), false); return this.set('fastFadein', val.toString(), false);
} }
val = this.get('fastFadein', false); val = this.get('fastFadein', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.enableBackdrops = function (val) { export function enableBackdrops(val) {
if (val != null) { if (val != null) {
return this.set('enableBackdrops', val.toString(), false); return this.set('enableBackdrops', val.toString(), false);
} }
val = this.get('enableBackdrops', false); val = this.get('enableBackdrops', false);
return val !== 'false'; return val !== 'false';
}; }
UserSettings.prototype.language = function (val) { export function language(val) {
if (val != null) { if (val != null) {
return this.set('language', val.toString(), false); return this.set('language', val.toString(), false);
} }
return this.get('language', false); return this.get('language', false);
}; }
UserSettings.prototype.dateTimeLocale = function (val) { export function dateTimeLocale(val) {
if (val != null) { if (val != null) {
return this.set('datetimelocale', val.toString(), false); return this.set('datetimelocale', val.toString(), false);
} }
return this.get('datetimelocale', false); return this.get('datetimelocale', false);
}; }
UserSettings.prototype.skipBackLength = function (val) { export function skipBackLength(val) {
if (val != null) { if (val != null) {
return this.set('skipBackLength', val.toString()); return this.set('skipBackLength', val.toString());
} }
return parseInt(this.get('skipBackLength') || '10000'); return parseInt(this.get('skipBackLength') || '10000');
}; }
UserSettings.prototype.skipForwardLength = function (val) { export function skipForwardLength(val) {
if (val != null) { if (val != null) {
return this.set('skipForwardLength', val.toString()); return this.set('skipForwardLength', val.toString());
} }
return parseInt(this.get('skipForwardLength') || '30000'); return parseInt(this.get('skipForwardLength') || '30000');
}; }
UserSettings.prototype.dashboardTheme = function (val) { export function dashboardTheme(val) {
if (val != null) { if (val != null) {
return this.set('dashboardTheme', val); return this.set('dashboardTheme', val);
} }
return this.get('dashboardTheme'); return this.get('dashboardTheme');
}; }
UserSettings.prototype.skin = function (val) { export function skin(val) {
if (val != null) { if (val != null) {
return this.set('skin', val, false); return this.set('skin', val, false);
} }
return this.get('skin', false); return this.get('skin', false);
}; }
UserSettings.prototype.theme = function (val) { export function theme(val) {
if (val != null) { if (val != null) {
return this.set('appTheme', val, false); return this.set('appTheme', val, false);
} }
return this.get('appTheme', false); return this.get('appTheme', false);
}; }
UserSettings.prototype.screensaver = function (val) { export function screensaver(val) {
if (val != null) { if (val != null) {
return this.set('screensaver', val, false); return this.set('screensaver', val, false);
} }
return this.get('screensaver', false); return this.get('screensaver', false);
}; }
UserSettings.prototype.soundEffects = function (val) { export function soundEffects(val) {
if (val != null) { if (val != null) {
return this.set('soundeffects', val, false); return this.set('soundeffects', val, false);
} }
return this.get('soundeffects', false); return this.get('soundeffects', false);
}; }
UserSettings.prototype.loadQuerySettings = function (key, query) { export function loadQuerySettings(key, query) {
var values = this.get(key); var values = this.get(key);
if (values) { if (values) {
values = JSON.parse(values); values = JSON.parse(values);
@ -218,9 +217,9 @@ define(['appSettings', 'events'], function (appSettings, events) {
} }
return query; return query;
}; }
UserSettings.prototype.saveQuerySettings = function (key, query) { export function saveQuerySettings(key, query) {
var values = {}; var values = {};
if (query.SortBy) { if (query.SortBy) {
values.SortBy = query.SortBy; values.SortBy = query.SortBy;
@ -231,25 +230,24 @@ define(['appSettings', 'events'], function (appSettings, events) {
} }
return this.set(key, JSON.stringify(values)); return this.set(key, JSON.stringify(values));
}; }
UserSettings.prototype.getSubtitleAppearanceSettings = function (key) { export function getSubtitleAppearanceSettings(key) {
key = key || 'localplayersubtitleappearance3'; key = key || 'localplayersubtitleappearance3';
return JSON.parse(this.get(key, false) || '{}'); return JSON.parse(this.get(key, false) || '{}');
}; }
UserSettings.prototype.setSubtitleAppearanceSettings = function (value, key) { export function setSubtitleAppearanceSettings(value, key) {
key = key || 'localplayersubtitleappearance3'; key = key || 'localplayersubtitleappearance3';
return this.set(key, JSON.stringify(value), false); return this.set(key, JSON.stringify(value), false);
}; }
UserSettings.prototype.setFilter = function (key, value) { export function setFilter(key, value) {
return this.set(key, value, true); return this.set(key, value, true);
}; }
UserSettings.prototype.getFilter = function (key) { export function getFilter(key) {
return this.get(key, true); return this.get(key, true);
}; }
return UserSettings; /* eslint-enable indent */
});

View file

@ -0,0 +1,15 @@
let data;
function getConfig() {
if (data) return Promise.resolve(data);
return fetch("/config.json?nocache=" + new Date().getUTCMilliseconds()).then(function (response) {
data = response.json();
return data;
});
}
export function enableMultiServer() {
return getConfig().then(config => {
return config.multiserver;
});
}

View file

@ -323,11 +323,11 @@ var AppInfo = {};
} }
function getElementsPath() { function getElementsPath() {
return "elements" return "elements";
} }
function getScriptsPath() { function getScriptsPath() {
return "scripts" return "scripts";
} }
function getPlaybackManager(playbackManager) { function getPlaybackManager(playbackManager) {
@ -452,6 +452,9 @@ var AppInfo = {};
require(["autoFocuser"], function(autoFocuser) { require(["autoFocuser"], function(autoFocuser) {
autoFocuser.enable(); autoFocuser.enable();
}); });
require(['globalize', 'connectionManager', 'events'], function (globalize, connectionManager, events) {
events.on(connectionManager, 'localusersignedin', globalize.updateCurrentCulture);
});
}); });
}); });
} }
@ -574,6 +577,7 @@ var AppInfo = {};
} }
require(["mediaSession", "serverNotifications"]); require(["mediaSession", "serverNotifications"]);
require(["date-fns", "date-fns/locale"]);
if (!browser.tv && !browser.xboxOne) { if (!browser.tv && !browser.xboxOne) {
require(["components/playback/playbackorientation"]); require(["components/playback/playbackorientation"]);
@ -647,12 +651,12 @@ var AppInfo = {};
inputManager: "scripts/inputManager", inputManager: "scripts/inputManager",
datetime: "scripts/datetime", datetime: "scripts/datetime",
globalize: "scripts/globalize", globalize: "scripts/globalize",
dfnshelper: "scripts/dfnshelper",
libraryMenu: "scripts/librarymenu", libraryMenu: "scripts/librarymenu",
playlisteditor: componentsPath + "/playlisteditor/playlisteditor", playlisteditor: componentsPath + "/playlisteditor/playlisteditor",
medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator", medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator",
medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor", medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor",
imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor", imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor",
humanedate: componentsPath + "/humanedate",
apphost: componentsPath + "/apphost", apphost: componentsPath + "/apphost",
visibleinviewport: componentsPath + "/visibleinviewport", visibleinviewport: componentsPath + "/visibleinviewport",
qualityoptions: componentsPath + "/qualityoptions", qualityoptions: componentsPath + "/qualityoptions",
@ -690,12 +694,13 @@ var AppInfo = {};
"swiper", "swiper",
"queryString", "queryString",
"sortable", "sortable",
"libjass",
"webcomponents", "webcomponents",
"material-icons", "material-icons",
"jellyfin-noto", "jellyfin-noto",
"date-fns",
"page", "page",
"polyfill" "polyfill",
"classlist-polyfill"
] ]
}, },
urlArgs: urlArgs, urlArgs: urlArgs,
@ -704,6 +709,7 @@ var AppInfo = {};
}); });
require(["polyfill"]); require(["polyfill"]);
require(["classlist-polyfill"]);
// Expose jQuery globally // Expose jQuery globally
require(["jQuery"], function(jQuery) { require(["jQuery"], function(jQuery) {
@ -767,11 +773,9 @@ var AppInfo = {};
define("emby-textarea", [elementsPath + "/emby-textarea/emby-textarea"], returnFirstDependency); define("emby-textarea", [elementsPath + "/emby-textarea/emby-textarea"], returnFirstDependency);
define("emby-toggle", [elementsPath + "/emby-toggle/emby-toggle"], returnFirstDependency); define("emby-toggle", [elementsPath + "/emby-toggle/emby-toggle"], returnFirstDependency);
define("webSettings", [scriptsPath + "/settings/webSettings"], returnFirstDependency);
define("appSettings", [scriptsPath + "/settings/appSettings"], returnFirstDependency); define("appSettings", [scriptsPath + "/settings/appSettings"], returnFirstDependency);
define("userSettingsBuilder", [scriptsPath + "/settings/userSettingsBuilder"], returnFirstDependency); define("userSettings", [scriptsPath + "/settings/userSettings"], returnFirstDependency);
define("userSettings", ["userSettingsBuilder"], function(userSettingsBuilder) {
return new userSettingsBuilder();
});
define("chromecastHelper", [componentsPath + "/chromecast/chromecasthelpers"], returnFirstDependency); define("chromecastHelper", [componentsPath + "/chromecast/chromecasthelpers"], returnFirstDependency);
define("mediaSession", [componentsPath + "/playback/mediasession"], returnFirstDependency); define("mediaSession", [componentsPath + "/playback/mediasession"], returnFirstDependency);

View file

@ -1,27 +0,0 @@
define(["dom", "emby-button"], function (dom) {
"use strict";
function onSubmit(e) {
if (dom.parentWithClass(this, "page").querySelector(".chkAccept").checked) {
Dashboard.navigate("wizardfinish.html");
} else {
Dashboard.alert({
message: Globalize.translate("MessagePleaseAcceptTermsOfServiceBeforeContinuing"),
title: ""
});
}
e.preventDefault();
return false;
}
return function (view, params) {
view.querySelector(".wizardAgreementForm").addEventListener("submit", onSubmit);
view.addEventListener("viewshow", function () {
document.querySelector(".skinHeader").classList.add("noHomeButtonHeader");
});
view.addEventListener("viewhide", function () {
document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader");
});
};
});

View file

@ -918,8 +918,8 @@
"HeaderFavoriteEpisodes": "الحلقات المفضلة", "HeaderFavoriteEpisodes": "الحلقات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteArtists": "الفنانون المفضلون",
"Shows": "الحلقات", "Shows": "الحلقات",
"Books": "كتب", "Books": "الكتب",
"ValueSpecialEpisodeName": "مميز - {0}", "ValueSpecialEpisodeName": "خاص - {0}",
"HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderAlbumArtists": "فناني الألبومات", "HeaderAlbumArtists": "فناني الألبومات",
"Genres": "الأنواع", "Genres": "الأنواع",
@ -952,7 +952,7 @@
"Artists": "الفنانين", "Artists": "الفنانين",
"Art": "فن", "Art": "فن",
"Anytime": "اي وقت", "Anytime": "اي وقت",
"AnyLanguage": "اي لغة", "AnyLanguage": "أي لغة",
"AlwaysPlaySubtitlesHelp": "الترجمة التي تطابق تفضيلات اللغة سيتم تحميلها بغض النظر عن لغة الصوت.", "AlwaysPlaySubtitlesHelp": "الترجمة التي تطابق تفضيلات اللغة سيتم تحميلها بغض النظر عن لغة الصوت.",
"AlwaysPlaySubtitles": "شغل الترجمة دائماً", "AlwaysPlaySubtitles": "شغل الترجمة دائماً",
"AllowedRemoteAddressesHelp": "قائمة لعناوين IP أو إدخالات IP / قناع الشبكة مفصولة بفاصلة للشبكات التي سيتم السماح لها بالاتصال عن بعد. إذا تركت فارغة ، فسيتم السماح بجميع العناوين البعيدة.", "AllowedRemoteAddressesHelp": "قائمة لعناوين IP أو إدخالات IP / قناع الشبكة مفصولة بفاصلة للشبكات التي سيتم السماح لها بالاتصال عن بعد. إذا تركت فارغة ، فسيتم السماح بجميع العناوين البعيدة.",
@ -1040,5 +1040,10 @@
"DatePlayed": "تاريخ التشغيل", "DatePlayed": "تاريخ التشغيل",
"DateAdded": "تاريخ الاضافة", "DateAdded": "تاريخ الاضافة",
"CriticRating": "تقييم النقاد", "CriticRating": "تقييم النقاد",
"ResumeAt": "اكمل من {0}" "ResumeAt": "اكمل من {0}",
"AskAdminToCreateLibrary": "أطلب من الأدمن إنشاء مكتبة.",
"Artist": "الفنان",
"AllowFfmpegThrottling": "إبطاء الترميزات",
"AlbumArtist": "المؤدي",
"Album": "الألبوم"
} }

View file

@ -10,7 +10,7 @@
"All": "Всички", "All": "Всички",
"AllLibraries": "Всички библиотеки", "AllLibraries": "Всички библиотеки",
"Art": "Картина", "Art": "Картина",
"Artists": "Изпълнители", "Artists": "Артисти",
"AttributeNew": "Нови", "AttributeNew": "Нови",
"Audio": "Звук", "Audio": "Звук",
"Auto": "Автоматично", "Auto": "Автоматично",
@ -101,8 +101,7 @@
"Desktop": "Работен плот", "Desktop": "Работен плот",
"DeviceAccessHelp": "Това се отнася само за устройства, които могат да бъдат различени и няма да попречи на достъп от мрежов четец. Филтрирането на потребителски устройства ще предотврати използването им докато не бъдат одобрени тук.", "DeviceAccessHelp": "Това се отнася само за устройства, които могат да бъдат различени и няма да попречи на достъп от мрежов четец. Филтрирането на потребителски устройства ще предотврати използването им докато не бъдат одобрени тук.",
"Director": "Режисьор", "Director": "Режисьор",
"DirectorValue": "Режисьор: {0}", "Directors": "Режисьори",
"DirectorsValue": "Режисьори: {0}",
"Disc": "Диск", "Disc": "Диск",
"Dislike": "Нехаресване", "Dislike": "Нехаресване",
"Display": "Показване", "Display": "Показване",
@ -137,9 +136,8 @@
"FormatValue": "Формат: {0}", "FormatValue": "Формат: {0}",
"Friday": "Петък", "Friday": "Петък",
"Fullscreen": "Цял екран", "Fullscreen": "Цял екран",
"GenreValue": "Жанр: {0}", "Genre": "Жанр",
"Genres": "Жанрове", "Genres": "Жанрове",
"GenresValue": "Жанрове: {0}",
"GroupVersions": "Групиране на версиите", "GroupVersions": "Групиране на версиите",
"GuestStar": "Гостуваща звезда", "GuestStar": "Гостуваща звезда",
"Guide": "Справочник", "Guide": "Справочник",
@ -305,9 +303,9 @@
"LabelCriticRating": "Оценка на критиците:", "LabelCriticRating": "Оценка на критиците:",
"LabelCurrentPassword": "Текуща парола:", "LabelCurrentPassword": "Текуща парола:",
"LabelCustomCertificatePath": "Път към потребителския сертификат:", "LabelCustomCertificatePath": "Път към потребителския сертификат:",
"LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12, съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.", "LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12 (PKCS #12), съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.",
"LabelCustomCss": "CSS по избор:", "LabelCustomCss": "CSS по избор:",
"LabelCustomCssHelp": "Използвайте собствен CSS към уеб интерфейса.", "LabelCustomCssHelp": "Добавете собствен стил за Уеб-интерфейса.",
"LabelCustomDeviceDisplayName": "Показвано име:", "LabelCustomDeviceDisplayName": "Показвано име:",
"LabelCustomRating": "Оценка по избор:", "LabelCustomRating": "Оценка по избор:",
"LabelDashboardTheme": "Облик на сървърното табло:", "LabelDashboardTheme": "Облик на сървърното табло:",
@ -435,7 +433,7 @@
"LabelStatus": "Състояние:", "LabelStatus": "Състояние:",
"LabelStopWhenPossible": "Спирай, когато е възможно:", "LabelStopWhenPossible": "Спирай, когато е възможно:",
"LabelSubtitlePlaybackMode": "Режим на субтитрите:", "LabelSubtitlePlaybackMode": "Режим на субтитрите:",
"LabelSubtitles": "Субтитри:", "LabelSubtitles": "Субтитри",
"LabelSupportedMediaTypes": "Поддържани типове медия:", "LabelSupportedMediaTypes": "Поддържани типове медия:",
"LabelTag": "Етикет:", "LabelTag": "Етикет:",
"LabelTextColor": "Цвят на текста:", "LabelTextColor": "Цвят на текста:",
@ -788,7 +786,7 @@
"AllowMediaConversion": "Разрешаване на медийни преобразувания", "AllowMediaConversion": "Разрешаване на медийни преобразувания",
"AllLanguages": "Всички езици", "AllLanguages": "Всички езици",
"AllEpisodes": "Всички епизоди", "AllEpisodes": "Всички епизоди",
"AllComplexFormats": "Всички комплексни формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и т.н.)", "AllComplexFormats": "Всички общи формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и др. )",
"AllChannels": "Всички канали", "AllChannels": "Всички канали",
"Alerts": "Известия", "Alerts": "Известия",
"AdditionalNotificationServices": "Разгледайте каталога с добавки за допълнителни услуги за известяване.", "AdditionalNotificationServices": "Разгледайте каталога с добавки за допълнителни услуги за известяване.",
@ -812,7 +810,7 @@
"MediaInfoLayout": "Подредба", "MediaInfoLayout": "Подредба",
"MusicVideo": "Музикален клип", "MusicVideo": "Музикален клип",
"MediaInfoStreamTypeVideo": "Видео", "MediaInfoStreamTypeVideo": "Видео",
"LabelVideo": "Видео:", "LabelVideo": "Видео",
"HeaderVideoTypes": "Видове видеа", "HeaderVideoTypes": "Видове видеа",
"HeaderVideoType": "Вид на видеото", "HeaderVideoType": "Вид на видеото",
"EnableExternalVideoPlayers": "Външни възпроизводители", "EnableExternalVideoPlayers": "Външни възпроизводители",
@ -836,5 +834,43 @@
"AllowOnTheFlySubtitleExtraction": "Позволява моментално извличане на поднадписи", "AllowOnTheFlySubtitleExtraction": "Позволява моментално извличане на поднадписи",
"AllowHWTranscodingHelp": "Позволява на тунера да прекодира моментално. Това може да помогне за редуциране на прекодирането от сървъра.", "AllowHWTranscodingHelp": "Позволява на тунера да прекодира моментално. Това може да помогне за редуциране на прекодирането от сървъра.",
"AddItemToCollectionHelp": "Добавяне към колекция чрез търсенето им и използване на дясно-щракване с мишката или контекстното меню.", "AddItemToCollectionHelp": "Добавяне към колекция чрез търсенето им и използване на дясно-щракване с мишката или контекстното меню.",
"Absolute": "Aбсолютен" "Absolute": "Aбсолютен",
"LabelLanNetworks": "Локални мрежи:",
"LabelKodiMetadataSaveImagePathsHelp": "Това е препоръчително ако имате изображения, пътят към които не е съобразен с изискванията на Kodi",
"LabelKodiMetadataSaveImagePaths": "Записване на пътеките към изображенията в nfo файловете",
"LabelChannels": "Канали:",
"DropShadow": "Сянка",
"Raised": "Повишено",
"OptionResElement": "рес. елемент",
"ButtonChangeServer": "Смяна на сървър",
"ButtonAddImage": "Добавяне на изображение",
"BrowsePluginCatalogMessage": "За да видите наличните добавки, прегледайте каталога с добавките.",
"Box": "Кутия",
"AlwaysPlaySubtitlesHelp": "Поднадписите, съвпадащи с езика от настройките, ще се зареждат, независимо от езика на аудио то.",
"BookLibraryHelp": "Поддържат се звукови и текстови книги. Преглед на инструкция за наименоване {1} на книга {0}.",
"Blacklist": "Списък с блокирани",
"BirthLocation": "Месторождение",
"Banner": "Банер",
"AspectRatio": "Съотношение",
"AskAdminToCreateLibrary": "Помолете администратора за създаване на библиотека.",
"Ascending": "Възходящо",
"AsManyAsPossible": "Колкото е възможно повече",
"Artist": "Артист",
"AroundTime": "Към {0}",
"Anytime": "По всяко време",
"AnyLanguage": "Който и да е език",
"AlwaysPlaySubtitles": "Постоянно изпълнение",
"AllowRemoteAccessHelp": "Ако не е маркирано, всеки отдалечен достъп ще бъде блокиран.",
"AllowRemoteAccess": "Позволяване на отдалечен достъп до този Jellyfin сървър.",
"AllowFfmpegThrottling": "Подтискане на прекодирането",
"AllowMediaConversionHelp": "Даване или отнемане на права за функциите за конвертиране на медия.",
"AlbumArtist": "Изпълнител",
"Album": "Албум",
"ClientSettings": "Клиентски настройки",
"ChannelNumber": "Номер на канала",
"ChannelNameOnly": "Само {0} канал",
"CancelSeries": "Откажи сериите",
"CancelRecording": "Откажи записа",
"ButtonSplit": "Раздели",
"ButtonResetEasyPassword": "Нулиране на бързия ПИН код"
} }

View file

@ -14,7 +14,7 @@
"AllEpisodes": "Všechny epizody", "AllEpisodes": "Všechny epizody",
"AllLanguages": "Všechny jazyky", "AllLanguages": "Všechny jazyky",
"AllowHWTranscodingHelp": "Povolit tuneru překódování v reálném čase. Může snížit zátěž překódovávání požadované Jellyfin serverem.", "AllowHWTranscodingHelp": "Povolit tuneru překódování v reálném čase. Může snížit zátěž překódovávání požadované Jellyfin serverem.",
"AlwaysPlaySubtitles": "Vždy zobrazit titulky", "AlwaysPlaySubtitles": "Vždy zobrazovat",
"AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.", "AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.",
"Anytime": "Kdykoliv", "Anytime": "Kdykoliv",
"AroundTime": "Okolo {0}", "AroundTime": "Okolo {0}",
@ -158,7 +158,7 @@
"Display": "Zobrazení", "Display": "Zobrazení",
"DisplayMissingEpisodesWithinSeasons": "Zobrazit chybějící epizody", "DisplayMissingEpisodesWithinSeasons": "Zobrazit chybějící epizody",
"DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí být zapnuto pro knihovny TV v nastavení serveru.", "DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí být zapnuto pro knihovny TV v nastavení serveru.",
"DisplayModeHelp": "Zvolte typ obrazovky, na které používáte Jellyfin.", "DisplayModeHelp": "Vyberte styl rozvržení, který chcete pro rozhraní.",
"DoNotRecord": "Nenahrávat", "DoNotRecord": "Nenahrávat",
"Down": "Dolů", "Down": "Dolů",
"Download": "Stáhnout", "Download": "Stáhnout",
@ -426,7 +426,7 @@
"Images": "Obrázky", "Images": "Obrázky",
"ImportFavoriteChannelsHelp": "Pokud je povoleno, jen kanály označené jako oblíbené budou importována na zařízení tuneru.", "ImportFavoriteChannelsHelp": "Pokud je povoleno, jen kanály označené jako oblíbené budou importována na zařízení tuneru.",
"ImportMissingEpisodesHelp": "Pokud je povoleno, budou informace o chybějících epizodách importovány do databáze Jellyfin a zobrazí se v sezónách seriálu. To může způsobit podstatně delší skenování knihovny.", "ImportMissingEpisodesHelp": "Pokud je povoleno, budou informace o chybějících epizodách importovány do databáze Jellyfin a zobrazí se v sezónách seriálu. To může způsobit podstatně delší skenování knihovny.",
"InstallingPackage": "Instalace {0}", "InstallingPackage": "Instalace {0} (Verze {1})",
"InstantMix": "Okamžité míchání", "InstantMix": "Okamžité míchání",
"ItemCount": "{0} položek", "ItemCount": "{0} položek",
"Items": "Položky", "Items": "Položky",
@ -461,7 +461,7 @@
"LabelArtist": "Umělec", "LabelArtist": "Umělec",
"LabelArtists": "Umělci:", "LabelArtists": "Umělci:",
"LabelArtistsHelp": "Odděl pomocí ;", "LabelArtistsHelp": "Odděl pomocí ;",
"LabelAudio": "Zvuk:", "LabelAudio": "Zvuk",
"LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:", "LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:",
"LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:", "LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:",
"LabelBindToLocalNetworkAddressHelp": "Volitelné. Přepsat lokální IP adresu vazanou na http server. Pokud je ponecháno prázdné, server se sváže ke všem dostupným adresám (aplikace bude dostupná na všech síťových zařízení, které server nabízí). Změna této hodnoty vyžaduje restartování Jellyfin Serveru.", "LabelBindToLocalNetworkAddressHelp": "Volitelné. Přepsat lokální IP adresu vazanou na http server. Pokud je ponecháno prázdné, server se sváže ke všem dostupným adresám (aplikace bude dostupná na všech síťových zařízení, které server nabízí). Změna této hodnoty vyžaduje restartování Jellyfin Serveru.",
@ -709,7 +709,7 @@
"LabelStopping": "Zastavování", "LabelStopping": "Zastavování",
"LabelSubtitleFormatHelp": "Příklad: srt", "LabelSubtitleFormatHelp": "Příklad: srt",
"LabelSubtitlePlaybackMode": "Mód titulků:", "LabelSubtitlePlaybackMode": "Mód titulků:",
"LabelSubtitles": "Titulky:", "LabelSubtitles": "Titulky",
"LabelSupportedMediaTypes": "Podporované typy médií:", "LabelSupportedMediaTypes": "Podporované typy médií:",
"LabelTagline": "Slogan:", "LabelTagline": "Slogan:",
"LabelTextBackgroundColor": "Barva pozadí textu:", "LabelTextBackgroundColor": "Barva pozadí textu:",
@ -862,14 +862,14 @@
"NoNextUpItemsMessage": "Nic nenalezeno. Začněte sledovat Vaše oblíbené seriály!", "NoNextUpItemsMessage": "Nic nenalezeno. Začněte sledovat Vaše oblíbené seriály!",
"NoPluginConfigurationMessage": "Tento zásuvný modul nemá žádné nastavení.", "NoPluginConfigurationMessage": "Tento zásuvný modul nemá žádné nastavení.",
"NoSubtitleSearchResultsFound": "Žádné výsledky.", "NoSubtitleSearchResultsFound": "Žádné výsledky.",
"NoSubtitles": "Žádné titulky", "NoSubtitles": "Žádné",
"NoSubtitlesHelp": "Ve výchozím nastavení nebudou titulky načteny. Během přehrávání však mohou být manuálně zapnuty.", "NoSubtitlesHelp": "Ve výchozím nastavení nebudou titulky načteny. Během přehrávání však mohou být manuálně zapnuty.",
"None": "Žádný", "None": "Žádný",
"Normal": "Normální", "Normal": "Normální",
"NumLocationsValue": "{0} složky", "NumLocationsValue": "{0} složky",
"Off": "Vypnuto", "Off": "Vypnuto",
"OneChannel": "Jeden kanál", "OneChannel": "Jeden kanál",
"OnlyForcedSubtitles": "Pouze vynucené titulky", "OnlyForcedSubtitles": "Pouze vynucené",
"OnlyForcedSubtitlesHelp": "Jen vynucené titulky budou nahrány.", "OnlyForcedSubtitlesHelp": "Jen vynucené titulky budou nahrány.",
"OptionAdminUsers": "Administrátoři", "OptionAdminUsers": "Administrátoři",
"OptionAlbumArtist": "Umělec Alba", "OptionAlbumArtist": "Umělec Alba",
@ -1248,7 +1248,7 @@
"Blacklist": "Černá listina", "Blacklist": "Černá listina",
"BobAndWeaveWithHelp": "Bob and weave (vyšší kvalita, ale pomalejší)", "BobAndWeaveWithHelp": "Bob and weave (vyšší kvalita, ale pomalejší)",
"Browse": "Procházet", "Browse": "Procházet",
"BurnSubtitlesHelp": "Určuje, zda má server vypalovat titulky při převodu videa v závislosti na formátu titulků. Vynechání vypalování titulků zlepší výkon serveru. Chcete-li vypálit grafické formáty (VOBSUB, PGS, SUB / IDX atd.) a některé titulky ASS / SSA, vyberte možnost Auto.", "BurnSubtitlesHelp": "Určuje, zda má server vypalovat titulky při překódování videa. Vynechání tohoto zlepší výkon serveru. Chcete-li vypálit grafické formáty (VOBSUB, PGS, SUB / IDX atd.) a některé titulky ASS / SSA, vyberte možnost Auto.",
"ButtonInfo": "Info", "ButtonInfo": "Info",
"ButtonMenu": "Menu", "ButtonMenu": "Menu",
"ButtonOk": "Ok", "ButtonOk": "Ok",
@ -1276,8 +1276,7 @@
"DetectingDevices": "Hledání zařízení", "DetectingDevices": "Hledání zařízení",
"DirectPlayError": "Chyba přímého přehrávání", "DirectPlayError": "Chyba přímého přehrávání",
"DirectStreamHelp2": "Přímé streamování souboru používá velmi malý výkon bez ztráty kvality videa.", "DirectStreamHelp2": "Přímé streamování souboru používá velmi malý výkon bez ztráty kvality videa.",
"DirectorValue": "Režisér: {0}", "Directors": "Režiséři",
"DirectorsValue": "Režiséři: {0}",
"Disabled": "Vypnuto", "Disabled": "Vypnuto",
"DisplayInMyMedia": "Zobrazit na domovské obrazovce", "DisplayInMyMedia": "Zobrazit na domovské obrazovce",
"DisplayInOtherHomeScreenSections": "Zobrazení v sekcích domovské obrazovky, jako jsou nejnovější média, a pokračování ve sledování", "DisplayInOtherHomeScreenSections": "Zobrazení v sekcích domovské obrazovky, jako jsou nejnovější média, a pokračování ve sledování",
@ -1297,8 +1296,7 @@
"Filters": "Filtry", "Filters": "Filtry",
"Folders": "Složky", "Folders": "Složky",
"General": "Hlavní", "General": "Hlavní",
"GenreValue": "Žánr: {0}", "Genre": "Žánr",
"GenresValue": "Žánry: {0}",
"GroupBySeries": "Seskupit podle série", "GroupBySeries": "Seskupit podle série",
"HandledByProxy": "Zpracováno reverzním proxy", "HandledByProxy": "Zpracováno reverzním proxy",
"HeaderAddLocalUser": "Přidat místního uživatele", "HeaderAddLocalUser": "Přidat místního uživatele",
@ -1395,7 +1393,7 @@
"LabelUrl": "URL:", "LabelUrl": "URL:",
"LabelUserAgent": "User agent:", "LabelUserAgent": "User agent:",
"LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení přehrávání serveru.", "LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení přehrávání serveru.",
"LabelVideo": "Video:", "LabelVideo": "Video",
"LabelVideoCodec": "Video kodek:", "LabelVideoCodec": "Video kodek:",
"LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.", "LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.",
"LetterButtonAbbreviation": "A", "LetterButtonAbbreviation": "A",
@ -1572,5 +1570,29 @@
"NoCreatedLibraries": "Zdá se, že jste dosud nevytvořili žádnou knihovnu. {0}Chtěli byste nějakou vytvořit nyní?{1}", "NoCreatedLibraries": "Zdá se, že jste dosud nevytvořili žádnou knihovnu. {0}Chtěli byste nějakou vytvořit nyní?{1}",
"AskAdminToCreateLibrary": "Požádejte administrátora o vytvoření knihovny.", "AskAdminToCreateLibrary": "Požádejte administrátora o vytvoření knihovny.",
"AllowFfmpegThrottlingHelp": "Když se překódování nebo remux dostane dostatečně daleko dopředu od aktuální pozice přehrávání, pozastaví se proces, aby spotřeboval méně zdrojů. To je nejužitečnější při sledování bez častého vyhledávání. Pokud máte problémy s přehráváním, vypněte tuto funkci.", "AllowFfmpegThrottlingHelp": "Když se překódování nebo remux dostane dostatečně daleko dopředu od aktuální pozice přehrávání, pozastaví se proces, aby spotřeboval méně zdrojů. To je nejužitečnější při sledování bez častého vyhledávání. Pokud máte problémy s přehráváním, vypněte tuto funkci.",
"AllowFfmpegThrottling": "Omezit překódování" "AllowFfmpegThrottling": "Omezit překódování",
"BoxSet": "Sbírka",
"Track": "Stopa",
"Season": "Sezóna",
"ReleaseGroup": "Vydavatel",
"PreferEmbeddedEpisodeInfosOverFileNames": "Preferovat vložené informace o epizodě před názvy souborů",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Používá se informace o epizodě z vložených metadat, pokud jsou k dispozici.",
"Person": "Osoba",
"OtherArtist": "Ostatní interpreti",
"Movie": "Film",
"Episode": "Epizoda",
"ClientSettings": "Nastavení klienta",
"Artist": "Interpret",
"AlbumArtist": "Interpret alba",
"Album": "Album",
"OnApplicationStartup": "Při zapnutí aplikace",
"EveryXHours": "Každých {0} hodin",
"EveryHour": "Každou hodinu",
"EveryXMinutes": "Každých {0} minut",
"OnWakeFromSleep": "Při probuzení",
"DailyAt": "Denně v {0}",
"PersonRole": "jako {0}",
"ListPaging": "{0}-{1} ze {2}",
"WriteAccessRequired": "Jellyfin Server potřebuje oprávnění pro zápis v této složce. Zkontrolujte oprávnění a zkuste to znovu.",
"PathNotFound": "Cesta nebyla nalezena. Zkontrolujte, zda je platná a zkuste to znovu."
} }

View file

@ -1166,8 +1166,7 @@
"DirectStreamHelp1": "Medie filen er kompatibel med enheden i forhold til opløsning og medie type (H.264,AC3, etc.), men er i en ikke kompatibel fil container (mkv, avi, wmv, etc). Videoen vil blive genpakket live før den streames til enheden.", "DirectStreamHelp1": "Medie filen er kompatibel med enheden i forhold til opløsning og medie type (H.264,AC3, etc.), men er i en ikke kompatibel fil container (mkv, avi, wmv, etc). Videoen vil blive genpakket live før den streames til enheden.",
"DirectStreamHelp2": "Direkte Streaming af en fil bruger meget lidt processor kraft uden nogen tab af video kvalitet.", "DirectStreamHelp2": "Direkte Streaming af en fil bruger meget lidt processor kraft uden nogen tab af video kvalitet.",
"DirectStreaming": "Direkte streaming", "DirectStreaming": "Direkte streaming",
"DirectorValue": "Instruktør: {0}", "Directors": "Instruktører",
"DirectorsValue": "Instruktører: {0}",
"Disc": "Disk", "Disc": "Disk",
"Dislike": "Kan ikke lide", "Dislike": "Kan ikke lide",
"Display": "Visning", "Display": "Visning",
@ -1207,8 +1206,7 @@
"Features": "Funktioner", "Features": "Funktioner",
"Filters": "Filtre", "Filters": "Filtre",
"FormatValue": "Format: {0}", "FormatValue": "Format: {0}",
"GenreValue": "Genre: {0}", "Genre": "Genre",
"GenresValue": "Genrer: {0}",
"GroupBySeries": "Gruppér efter serie", "GroupBySeries": "Gruppér efter serie",
"Guide": "Vejledning", "Guide": "Vejledning",
"GuideProviderLogin": "Log Ind", "GuideProviderLogin": "Log Ind",
@ -1270,7 +1268,7 @@
"Label3DFormat": "3D format:", "Label3DFormat": "3D format:",
"LabelAlbum": "Album:", "LabelAlbum": "Album:",
"LabelArtist": "Kunstner", "LabelArtist": "Kunstner",
"LabelAudio": "Lyd:", "LabelAudio": "Lyd",
"LabelBitrateMbps": "Bitrate (Mbps):", "LabelBitrateMbps": "Bitrate (Mbps):",
"LabelBlockContentWithTags": "Blokér filer med mærkerne:", "LabelBlockContentWithTags": "Blokér filer med mærkerne:",
"LabelBurnSubtitles": "Brænd undertekster:", "LabelBurnSubtitles": "Brænd undertekster:",
@ -1315,7 +1313,7 @@
"LabelSortOrder": "Sorteringsorden:", "LabelSortOrder": "Sorteringsorden:",
"LabelSoundEffects": "Lydeffekter:", "LabelSoundEffects": "Lydeffekter:",
"LabelStatus": "Status:", "LabelStatus": "Status:",
"LabelSubtitles": "Undertekster:", "LabelSubtitles": "Undertekster",
"LabelSyncNoTargetsHelp": "Det ser ud til at du ikke har nogen apps der understøtter offline hentning.", "LabelSyncNoTargetsHelp": "Det ser ud til at du ikke har nogen apps der understøtter offline hentning.",
"LabelTVHomeScreen": "TV modus hjemmeskærm:", "LabelTVHomeScreen": "TV modus hjemmeskærm:",
"LabelTag": "Mærke:", "LabelTag": "Mærke:",
@ -1329,7 +1327,7 @@
"LabelUrl": "Link:", "LabelUrl": "Link:",
"LabelVersion": "Version:", "LabelVersion": "Version:",
"LabelVersionNumber": "Version {0}", "LabelVersionNumber": "Version {0}",
"LabelVideo": "Video:", "LabelVideo": "Video",
"LabelVideoCodec": "Video codec:", "LabelVideoCodec": "Video codec:",
"LabelWindowBackgroundColor": "Tekst baggrundsfarve:", "LabelWindowBackgroundColor": "Tekst baggrundsfarve:",
"LabelXDlnaCap": "X-DLNA begrænsning:", "LabelXDlnaCap": "X-DLNA begrænsning:",

Some files were not shown because too many files have changed in this diff Show more