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 master

This commit is contained in:
dkanada 2020-05-14 13:26:52 +09:00 committed by GitHub
commit 9a72c22680
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
587 changed files with 38027 additions and 26059 deletions

View file

@ -2,48 +2,80 @@ trigger:
batch: true batch: true
branches: branches:
include: include:
- master - '*'
- release-*
tags: tags:
include: include:
- '*' - '*'
pr:
branches:
include:
- '*'
jobs: jobs:
- job: main_build - job: Build
displayName: 'Main Build' displayName: 'Build'
dependsOn: lint strategy:
condition: succeeded() matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
displayName: 'Install Node.js' displayName: 'Install Node'
inputs: inputs:
versionSpec: '10.x' versionSpec: '12.x'
- script: | - task: Cache@2
yarn install displayName: 'Check Cache'
displayName: 'Install dependencies' inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: | - script: 'yarn install --frozen-lockfile'
test -d dist displayName: 'Install Dependencies'
displayName: 'Check dist directory' condition: ne(variables.CACHE_RESTORED, 'true')
- script: | - script: 'yarn build:development'
yarn pack --filename jellyfin-web.tgz displayName: 'Build Development'
displayName: 'Build package' condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
displayName: 'Build Bundle'
condition: eq(variables['BuildConfiguration'], 'production')
- script: 'yarn build:standalone'
displayName: 'Build Standalone'
condition: eq(variables['BuildConfiguration'], 'standalone')
- script: 'test -d dist'
displayName: 'Check Build'
- script: 'mv dist jellyfin-web'
displayName: 'Rename Directory'
- task: ArchiveFiles@2
displayName: 'Archive Directory'
inputs:
rootFolderOrFile: 'jellyfin-web'
includeRootFolder: true
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- task: PublishPipelineArtifact@1 - task: PublishPipelineArtifact@1
displayName: 'Publish package' displayName: 'Publish Release'
condition: succeeded()
inputs: inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web.tgz' targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
artifactName: 'jellyfin-web' artifactName: 'jellyfin-web-$(BuildConfiguration)'
- job: lint - job: Lint
displayName: 'Lint' displayName: 'Lint'
pool: pool:
@ -51,14 +83,23 @@ jobs:
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
displayName: 'Install Node.js' displayName: 'Install Node'
inputs: inputs:
versionSpec: '10.x' versionSpec: '12.x'
- script: | - task: Cache@2
yarn install displayName: 'Check Cache'
displayName: 'Install dependencies' inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: | - script: 'yarn install --frozen-lockfile'
yarn run lint displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn run lint --quiet'
displayName: 'Run ESLint' displayName: 'Run ESLint'
- script: 'yarn run stylelint'
displayName: 'Run Stylelint'

1
.copr/Makefile Symbolic link
View file

@ -0,0 +1 @@
../fedora/Makefile

5
.dependabot/config.yml Normal file
View file

@ -0,0 +1,5 @@
version: 1
update_configs:
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"

View file

@ -7,3 +7,6 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
[json]
indent_size = 2

View file

@ -1 +1,5 @@
libraries/ node_modules
dist
.idea
.vscode
src/libraries

193
.eslintrc.js Normal file
View file

@ -0,0 +1,193 @@
module.exports = {
root: true,
plugins: [
'promise',
'import',
'eslint-comments'
],
env: {
node: true,
es6: true,
es2017: true,
es2020: true
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true
}
},
extends: [
'eslint:recommended',
// 'plugin:promise/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:eslint-comments/recommended',
'plugin:compat/recommended'
],
rules: {
'block-spacing': ["error"],
'brace-style': ["error"],
'comma-dangle': ["error", "never"],
'comma-spacing': ["error"],
'eol-last': ["error"],
'indent': ["error", 4, { "SwitchCase": 1 }],
'keyword-spacing': ["error"],
'max-statements-per-line': ["error"],
'no-floating-decimal': ["error"],
'no-multi-spaces': ["error"],
'no-multiple-empty-lines': ["error", { "max": 1 }],
'no-trailing-spaces': ["error"],
'one-var': ["error", "never"],
'quotes': ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": false }],
'semi': ["error"],
'space-before-blocks': ["error"]
},
overrides: [
{
files: [
'./src/**/*.js'
],
env: {
node: false,
amd: true,
browser: true,
es6: true,
es2017: true,
es2020: true
},
globals: {
// Browser globals
'MediaMetadata': 'readonly',
// Tizen globals
'tizen': 'readonly',
'webapis': 'readonly',
// WebOS globals
'webOS': 'readonly',
// Dependency globals
'$': 'readonly',
'jQuery': 'readonly',
'requirejs': 'readonly',
// Jellyfin globals
'ApiClient': 'writable',
'AppInfo': 'writable',
'chrome': 'writable',
'ConnectionManager': 'writable',
'DlnaProfilePage': 'writable',
'Dashboard': 'writable',
'DashboardPage': 'writable',
'Emby': 'readonly',
'Events': 'writable',
'getParameterByName': 'writable',
'getWindowLocationSearch': 'writable',
'Globalize': 'writable',
'Hls': 'writable',
'dfnshelper': 'writable',
'LibraryMenu': 'writable',
'LinkParser': 'writable',
'LiveTvHelpers': 'writable',
'MetadataEditor': 'writable',
'pageClassOn': 'writable',
'pageIdOn': 'writable',
'PlaylistViewer': 'writable',
'UserParentalControlPage': 'writable',
'Windows': 'readonly'
},
rules: {
// TODO: Fix warnings and remove these rules
'no-redeclare': ["warn"],
'no-unused-vars': ["warn"],
'no-useless-escape': ["warn"],
// TODO: Remove after ES6 migration is complete
'import/no-unresolved': ["off"]
},
settings: {
polyfills: [
// Native Promises Only
'Promise',
// whatwg-fetch
'fetch',
// document-register-element
'document.registerElement',
// resize-observer-polyfill
'ResizeObserver',
// fast-text-encoding
'TextEncoder',
// intersection-observer
'IntersectionObserver',
// Core-js
'Object.assign',
'Object.is',
'Object.setPrototypeOf',
'Object.toString',
'Object.freeze',
'Object.seal',
'Object.preventExtensions',
'Object.isFrozen',
'Object.isSealed',
'Object.isExtensible',
'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf',
'Object.keys',
'Object.getOwnPropertyNames',
'Function.name',
'Function.hasInstance',
'Array.from',
'Array.arrayOf',
'Array.copyWithin',
'Array.fill',
'Array.find',
'Array.findIndex',
'Array.iterator',
'String.fromCodePoint',
'String.raw',
'String.iterator',
'String.codePointAt',
'String.endsWith',
'String.includes',
'String.repeat',
'String.startsWith',
'String.trim',
'String.anchor',
'String.big',
'String.blink',
'String.bold',
'String.fixed',
'String.fontcolor',
'String.fontsize',
'String.italics',
'String.link',
'String.small',
'String.strike',
'String.sub',
'String.sup',
'RegExp',
'Number',
'Math',
'Date',
'async',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'Reflect',
// Temporary while eslint-compat-plugin is buggy
'document.querySelector'
]
}
}
]
}

View file

@ -1,27 +0,0 @@
env:
es6: true
browser: true
amd: true
rules:
block-spacing: ["error"]
brace-style: ["error"]
comma-dangle: ["error", "never"]
comma-spacing: ["error"]
eol-last: ["off"]
indent: ["error", 4, { "SwitchCase": 1 }]
keyword-spacing: ["error"]
line-comment-position: ["off"]
max-statements-per-line: ["error"]
no-empty: ["error"]
no-extra-semi: ["error"]
no-floating-decimal: ["error"]
no-multi-spaces: ["error"]
no-multiple-empty-lines: ["error", { "max": 1 }]
no-trailing-spaces: ["error"]
no-void: ["off"]
one-var: ["error", "never"]
padding-line-between-statements: ["off"]
semi: ["off"]
space-before-blocks: ["error"]
yoda: ["off"]

36
.gitattributes vendored
View file

@ -1 +1,35 @@
/CONTRIBUTORS.md merge=union * text=auto
CONTRIBUTORS.md merge=union
README.md text
LICENSE text
*.css text
*.eot binary
*.gif binary
*.html text diff=html
*.ico binary
*.*ignore text
*.jpg binary
*.js text
*.json text
*.lock text -diff
*.map text -diff
*.md text
*.otf binary
*.png binary
*.py text diff=python
*.svg binary
*.ts text
*.ttf binary
*.sass text
*.vue text
*.webp binary
*.woff binary
*.woff2 binary
.editorconfig text
.gitattributes export-ignore
.gitignore export-ignore
*.gitattributes linguist-language=gitattributes

4
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,4 @@
.ci @dkanada @EraYaN
.github @jellyfin/core
build.sh @joshuaboniface
deployment @joshuaboniface

585
.gitignore vendored
View file

@ -1,578 +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
node_modules
# ide
.idea
.vscode

143
.stylelintrc Normal file
View file

@ -0,0 +1,143 @@
{
"plugins": [
"stylelint-no-browser-hacks/lib",
],
"rules": {
"at-rule-empty-line-before": [ "always", {
except: [
"blockless-after-same-name-blockless",
"first-nested",
],
ignore: ["after-comment"],
} ],
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
"at-rule-no-unknown": true,
"at-rule-semicolon-newline-after": "always",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always-multi-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always-multi-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-no-invalid-hex": true,
"comment-empty-line-before": [ "always", {
except: ["first-nested"],
ignore: ["stylelint-commands"],
} ],
"comment-no-empty": true,
"comment-whitespace-inside": "always",
"custom-property-empty-line-before": [ "always", {
except: [
"after-custom-property",
"first-nested",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
} ],
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always-multi-line",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-newline-after": "always-multi-line",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"font-family-no-duplicate-names": true,
"function-calc-no-invalid": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
"function-comma-space-after": "always-single-line",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-max-empty-lines": 0,
"function-name-case": "lower",
"function-parentheses-newline-inside": "always-multi-line",
"function-parentheses-space-inside": "never-single-line",
"function-whitespace-after": "always",
"indentation": 4,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-unknown": true,
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always-multi-line",
"media-query-list-comma-space-after": "always-single-line",
"media-query-list-comma-space-before": "never",
"no-descending-specificity": true,
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"plugin/no-browser-hacks": true,
"property-case": "lower",
"property-no-unknown": [
true,
{
"ignoreProperties": [
"user-drag"
]
}
],
"rule-empty-line-before": [ "always-multi-line", {
except: ["first-nested"],
ignore: ["after-comment"],
} ],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-space-before": "never",
"selector-max-empty-lines": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": [
"cue"
]
}
],
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"string-no-newline": true,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
}
}

View file

@ -32,6 +32,9 @@
- [bilde2910](https://github.com/bilde2910) - [bilde2910](https://github.com/bilde2910)
- [Daniel Hartung](https://github.com/dhartung) - [Daniel Hartung](https://github.com/dhartung)
- [Ryan Hartzell](https://github.com/ryan-hartzell) - [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [Sarab Singh](https://github.com/sarab97)
# Emby Contributors # Emby Contributors

View file

@ -45,20 +45,37 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies ### Dependencies
- Yarn - Yarn
- Gulp-cli
### Getting Started ### Getting Started
1. Clone or download this repository. 1. Clone or download this repository.
```sh ```sh
git clone https://github.com/jellyfin/jellyfin-web.git git clone https://github.com/jellyfin/jellyfin-web.git
cd jellyfin-web cd jellyfin-web
``` ```
2. Install build dependencies in the project directory. 2. Install build dependencies in the project directory.
```sh ```sh
yarn install yarn install
``` ```
3. Run the web client with webpack for local development. 3. Run the web client with webpack for local development.
```sh ```sh
yarn serve yarn serve
``` ```
4. Build the client with sourcemaps.
```sh
yarn build:development
```
You can build a nginx compatible version as well.
```sh
yarn build:standalone
```

110
build.sh Executable file
View file

@ -0,0 +1,110 @@
#!/usr/bin/env bash
# build.sh - Build Jellyfin binary packages
# Part of the Jellyfin Project
set -o errexit
set -o pipefail
usage() {
echo -e "build.sh - Build Jellyfin binary packages"
echo -e "Usage:"
echo -e " $0 -t/--type <BUILD_TYPE> -p/--platform <PLATFORM> [-k/--keep-artifacts] [-l/--list-platforms]"
echo -e "Notes:"
echo -e " * BUILD_TYPE can be one of: [native, docker] and must be specified"
echo -e " * native: Build using the build script in the host OS"
echo -e " * docker: Build using the build script in a standardized Docker container"
echo -e " * PLATFORM can be any platform shown by -l/--list-platforms and must be specified"
echo -e " * If -k/--keep-artifacts is specified, transient artifacts (e.g. Docker containers) will be"
echo -e " retained after the build is finished; the source directory will still be cleaned"
echo -e " * If -l/--list-platforms is specified, all other arguments are ignored; the script will print"
echo -e " the list of supported platforms and exit"
}
list_platforms() {
declare -a platforms
platforms=(
$( find deployment -maxdepth 1 -mindepth 1 -name "build.*" | awk -F'.' '{ $1=""; printf $2; if ($3 != ""){ printf "." $3; }; if ($4 != ""){ printf "." $4; }; print ""; }' | sort )
)
echo -e "Valid platforms:"
echo
for platform in ${platforms[@]}; do
echo -e "* ${platform} : $( grep '^#=' deployment/build.${platform} | sed 's/^#= //' )"
done
}
do_build_native() {
export IS_DOCKER=NO
deployment/build.${PLATFORM}
}
do_build_docker() {
if ! dpkg --print-architecture | grep -q 'amd64'; then
echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead."
exit 1
fi
if [[ ! -f deployment/Dockerfile.${PLATFORM} ]]; then
echo "Missing Dockerfile for platform ${PLATFORM}"
exit 1
fi
if [[ ${KEEP_ARTIFACTS} == YES ]]; then
docker_args=""
else
docker_args="--rm"
fi
docker build . -t "jellyfin-builder.${PLATFORM}" -f deployment/Dockerfile.${PLATFORM}
mkdir -p ${ARTIFACT_DIR}
docker run $docker_args -v "${SOURCE_DIR}:/jellyfin" -v "${ARTIFACT_DIR}:/dist" "jellyfin-builder.${PLATFORM}"
}
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-t|--type)
BUILD_TYPE="$2"
shift
shift
;;
-p|--platform)
PLATFORM="$2"
shift
shift
;;
-k|--keep-artifacts)
KEEP_ARTIFACTS=YES
shift
;;
-l|--list-platforms)
list_platforms
exit 0
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option $1"
usage
exit 1
;;
esac
done
if [[ -z ${BUILD_TYPE} || -z ${PLATFORM} ]]; then
usage
exit 1
fi
export SOURCE_DIR="$( pwd )"
export ARTIFACT_DIR="${SOURCE_DIR}/../bin/${PLATFORM}"
# Determine build type
case ${BUILD_TYPE} in
native)
do_build_native
;;
docker)
do_build_docker
;;
esac

9
build.yaml Normal file
View file

@ -0,0 +1,9 @@
---
# We just wrap `build` so this is really it
name: "jellyfin-web"
version: "10.6.0"
packages:
- debian.all
- fedora.all
- centos.all
- portable

96
bump_version Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env bash
# bump_version - increase the shared version and generate changelogs
set -o errexit
set -o pipefail
usage() {
echo -e "bump_version - increase the shared version and generate changelogs"
echo -e ""
echo -e "Usage:"
echo -e " $ bump_version <new_version>"
}
if [[ -z $1 ]]; then
usage
exit 1
fi
shared_version_file="src/components/apphost.js"
build_file="./build.yaml"
new_version="$1"
# Parse the version from shared version file
old_version="$(
grep "appVersion" ${shared_version_file} | head -1 \
| sed -E 's/var appVersion = "([0-9\.]+)";/\1/'
)"
echo "Old version in appHost is: $old_version"
# Set the shared version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
old_version="$(
grep "version:" ${build_file} \
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
)"
echo "Old version in ${build_file}: $old_version`"
# Set the build.yaml version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
if [[ ${new_version} == *"-"* ]]; then
new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
else
new_version_deb="${new_version}-1"
fi
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog
echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
" >> ${debian_changelog_temp}
cat ${debian_changelog_file} >> ${debian_changelog_temp}
# Move into place
mv ${debian_changelog_temp} ${debian_changelog_file}
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
fedora_spec_file="fedora/jellyfin.spec"
fedora_changelog_temp="$( mktemp )"
fedora_spec_temp_dir="$( mktemp -d )"
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp"
# Make a copy of our spec file for hacking
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
pushd ${fedora_spec_temp_dir}
# Split out the stuff before and after changelog
csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01
# Update the version in xx00
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# Remove the header from xx01
sed -i '/^%changelog/d' xx01
# Create new temp file with our changelog
echo -e "%changelog
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}" >> ${fedora_changelog_temp}
cat xx01 >> ${fedora_changelog_temp}
# Reassembble
cat xx00 ${fedora_changelog_temp} > ${fedora_spec_temp}
popd
# Move into place
mv ${fedora_spec_temp} ${fedora_spec_file}
# Clean up
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
# Stage the changed files for commit
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile*
git status

5
debian/changelog vendored Normal file
View file

@ -0,0 +1,5 @@
jellyfin-web (10.6.0-1) unstable; urgency=medium
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400

1
debian/compat vendored Normal file
View file

@ -0,0 +1 @@
8

16
debian/control vendored Normal file
View file

@ -0,0 +1,16 @@
Source: jellyfin-web
Section: misc
Priority: optional
Maintainer: Jellyfin Team <team@jellyfin.org>
Build-Depends: debhelper (>= 9),
npm | nodejs
Standards-Version: 3.9.4
Homepage: https://jellyfin.org/
Vcs-Git: https://github.org/jellyfin/jellyfin-web.git
Vcs-Browser: https://github.org/jellyfin/jellyfin-web
Package: jellyfin-web
Recommends: jellyfin-server
Architecture: all
Description: Jellyfin is the Free Software Media System.
This package provides the Jellyfin web client.

28
debian/copyright vendored Normal file
View file

@ -0,0 +1,28 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: jellyfin-web
Source: https://github.com/jellyfin/jellyfin-web
Files: *
Copyright: 2018-2020 Jellyfin Team
License: GPL-3.0
Files: debian/*
Copyright: 2020 Joshua Boniface <joshua@boniface.me>
License: GPL-3.0
License: GPL-3.0
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

6
debian/gbp.conf vendored Normal file
View file

@ -0,0 +1,6 @@
[DEFAULT]
pristine-tar = False
cleaner = fakeroot debian/rules clean
[import-orig]
filter = [ ".git*", ".hg*", ".vs*", ".vscode*" ]

1
debian/install vendored Normal file
View file

@ -0,0 +1 @@
web usr/share/jellyfin/

1
debian/po/POTFILES.in vendored Normal file
View file

@ -0,0 +1 @@
[type: gettext/rfc822deb] templates

57
debian/po/templates.pot vendored Normal file
View file

@ -0,0 +1,57 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: jellyfin-server\n"
"Report-Msgid-Bugs-To: jellyfin-server@packages.debian.org\n"
"POT-Creation-Date: 2015-06-12 20:51-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#. Type: note
#. Description
#: ../templates:1001
msgid "Jellyfin permission info:"
msgstr ""
#. Type: note
#. Description
#: ../templates:1001
msgid ""
"Jellyfin by default runs under a user named \"jellyfin\". Please ensure that the "
"user jellyfin has read and write access to any folders you wish to add to your "
"library. Otherwise please run jellyfin under a different user."
msgstr ""
#. Type: string
#. Description
#: ../templates:2001
msgid "Username to run Jellyfin as:"
msgstr ""
#. Type: string
#. Description
#: ../templates:2001
msgid "The user that jellyfin will run as."
msgstr ""
#. Type: note
#. Description
#: ../templates:3001
msgid "Jellyfin still running"
msgstr ""
#. Type: note
#. Description
#: ../templates:3001
msgid "Jellyfin is currently running. Please close it and try again."
msgstr ""

20
debian/rules vendored Executable file
View file

@ -0,0 +1,20 @@
#! /usr/bin/make -f
export DH_VERBOSE=1
%:
dh $@
# disable "make check"
override_dh_auto_test:
# disable stripping debugging symbols
override_dh_clistrip:
override_dh_auto_build:
npx yarn install
mv $(CURDIR)/dist $(CURDIR)/web
override_dh_auto_clean:
test -d $(CURDIR)/dist && rm -rf '$(CURDIR)/dist' || true
test -d $(CURDIR)/web && rm -rf '$(CURDIR)/web' || true
test -d $(CURDIR)/node_modules && rm -rf '$(CURDIR)/node_modules' || true

1
debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
1.0

7
debian/source/options vendored Normal file
View file

@ -0,0 +1,7 @@
tar-ignore='.git*'
tar-ignore='**/.git'
tar-ignore='**/.hg'
tar-ignore='**/.vs'
tar-ignore='**/.vscode'
tar-ignore='deployment'
tar-ignore='*.deb'

View file

@ -0,0 +1,27 @@
FROM centos:7
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare CentOS environment
RUN yum update -y \
&& yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
# Install recent NodeJS and Yarn
RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
&& yum install -y yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.all /build.sh
VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
ENTRYPOINT ["/build.sh"]

View file

@ -0,0 +1,25 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y debhelper mmv npm git
# Prepare Yarn
RUN npm install -g yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.all /build.sh
VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
ENTRYPOINT ["/build.sh"]

View file

@ -0,0 +1,21 @@
FROM fedora:31
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.all /build.sh
VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
ENTRYPOINT ["/build.sh"]

View file

@ -0,0 +1,25 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y mmv npm git
# Prepare Yarn
RUN npm install -g yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
ENTRYPOINT ["/build.sh"]

27
deployment/build.centos.all Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
#= CentOS 7 all .rpm
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

25
deployment/build.debian.all Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
#= Debian/Ubuntu all .deb
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
mkdir -p ${ARTIFACT_DIR}/
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
cp -a /tmp/yarn.lock yarn.lock
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

27
deployment/build.fedora.all Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
#= Fedora 29+ all .rpm
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

28
deployment/build.portable Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
#= Portable .NET DLL .tar.gz
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
# Get version
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
# Build archives
npx yarn install
mv dist/ jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist/
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

21
fedora/Makefile Normal file
View file

@ -0,0 +1,21 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin-web.spec)
srpm:
cd fedora/; \
SOURCE_DIR=.. \
WORKDIR="$${PWD}"; \
tar \
--transform "s,^\.,jellyfin-web-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='deployment' \
--exclude='*.deb' \
--exclude='*.rpm' \
--exclude='jellyfin-web-$(VERSION).tar.gz' \
-czf "jellyfin-web-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./
cd fedora/; \
rpmbuild -bs jellyfin-web.spec \
--define "_sourcedir $$PWD/" \
--define "_srcrpmdir $(outdir)"

43
fedora/jellyfin-web.spec Normal file
View file

@ -0,0 +1,43 @@
%global debug_package %{nil}
Name: jellyfin-web
Version: 10.6.0
Release: 1%{?dist}
Summary: The Free Software Media System web client
License: GPLv3
URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
Source0: jellyfin-web-%{version}.tar.gz
%if 0%{?centos}
BuildRequires: yarn
%else
BuildRequires nodejs-yarn
%endif
BuildArch: noarch
# Disable Automatic Dependency Processing
AutoReqProv: no
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
%prep
%autosetup -n jellyfin-web-%{version} -b 0
%build
%install
yarn install
%{__mkdir} -p %{buildroot}%{_datadir}
mv dist %{buildroot}%{_datadir}/jellyfin-web
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%files
%attr(755,root,root) %{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE
%changelog
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release

205
gulpfile.js Normal file
View file

@ -0,0 +1,205 @@
const { src, dest, series, parallel, watch } = require('gulp');
const browserSync = require('browser-sync').create();
const del = require('del');
const babel = require('gulp-babel');
const concat = require('gulp-concat');
const terser = require('gulp-terser');
const htmlmin = require('gulp-htmlmin');
const imagemin = require('gulp-imagemin');
const sourcemaps = require('gulp-sourcemaps');
const mode = require('gulp-mode')({
modes: ['development', 'production'],
default: 'development',
verbose: false
});
const stream = require('webpack-stream');
const inject = require('gulp-inject');
const postcss = require('gulp-postcss');
const sass = require('gulp-sass');
const gulpif = require('gulp-if');
const lazypipe = require('lazypipe');
sass.compiler = require('node-sass');
let config;
if (mode.production()) {
config = require('./webpack.prod.js');
} else {
config = require('./webpack.dev.js');
}
const options = {
javascript: {
query: ['src/**/*.js', '!src/bundle.js', '!src/standalone.js', '!src/scripts/apploader.js']
},
apploader: {
query: ['src/standalone.js', 'src/scripts/apploader.js']
},
css: {
query: ['src/**/*.css', 'src/**/*.scss']
},
html: {
query: ['src/**/*.html', '!src/index.html']
},
images: {
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
},
copy: {
query: ['src/**/*.json', 'src/**/*.ico']
},
injectBundle: {
query: 'src/index.html'
}
};
function serve() {
browserSync.init({
server: {
baseDir: './dist'
},
port: 8080
});
const events = ['add', 'change'];
watch(options.javascript.query).on('all', function (event, path) {
if (events.includes(event)) {
javascript(path);
}
});
watch(options.apploader.query, apploader(true));
watch('src/bundle.js', webpack);
watch(options.css.query).on('all', function (event, path) {
if (events.includes(event)) {
css(path);
}
});
watch(options.html.query).on('all', function (event, path) {
if (events.includes(event)) {
html(path);
}
});
watch(options.images.query).on('all', function (event, path) {
if (events.includes(event)) {
images(path);
}
});
watch(options.copy.query).on('all', function (event, path) {
if (events.includes(event)) {
copy(path);
}
});
watch(options.injectBundle.query, injectBundle);
}
function clean() {
return del(['dist/']);
}
const pipelineJavascript = lazypipe()
.pipe(function () {
return mode.development(sourcemaps.init({ loadMaps: true }));
})
.pipe(function () {
return babel({
presets: [
['@babel/preset-env']
]
});
})
.pipe(function () {
return terser({
keep_fnames: true,
mangle: false
});
})
.pipe(function () {
return mode.development(sourcemaps.write('.'));
});
function javascript(query) {
return src(typeof query !== 'function' ? query : options.javascript.query, { base: './src/' })
.pipe(pipelineJavascript())
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function apploader(standalone) {
function task() {
return src(options.apploader.query, { base: './src/' })
.pipe(gulpif(standalone, concat('scripts/apploader.js')))
.pipe(pipelineJavascript())
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
task.displayName = 'apploader';
return task;
}
function webpack() {
return stream(config)
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function css(query) {
return src(typeof query !== 'function' ? query : options.css.query, { base: './src/' })
.pipe(mode.development(sourcemaps.init({ loadMaps: true })))
.pipe(sass().on('error', sass.logError))
.pipe(postcss())
.pipe(mode.development(sourcemaps.write('.')))
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function html(query) {
return src(typeof query !== 'function' ? query : options.html.query, { base: './src/' })
.pipe(mode.production(htmlmin({ collapseWhitespace: true })))
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function images(query) {
return src(typeof query !== 'function' ? query : options.images.query, { base: './src/' })
.pipe(mode.production(imagemin()))
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function copy(query) {
return src(typeof query !== 'function' ? query : options.copy.query, { base: './src/' })
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function copyIndex() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function injectBundle() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(inject(
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
))
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function build(standalone) {
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy));
}
exports.default = series(build(false), copyIndex);
exports.standalone = series(build(true), injectBundle);
exports.serve = series(exports.standalone, serve);

View file

@ -5,39 +5,113 @@
"repository": "https://github.com/jellyfin/jellyfin-web", "repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"devDependencies": { "devDependencies": {
"autoprefixer": "^9.7.3", "@babel/core": "^7.9.6",
"@babel/plugin-transform-modules-amd": "^7.9.6",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.8.6",
"autoprefixer": "^9.7.6",
"babel-loader": "^8.0.6",
"browser-sync": "^2.26.7",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1", "copy-webpack-plugin": "^5.1.1",
"css-loader": "^2.1.0", "css-loader": "^3.4.2",
"eslint": "^5.16.0", "cssnano": "^4.1.10",
"file-loader": "^3.0.1", "del": "^5.1.0",
"html-webpack-plugin": "^3.2.0", "eslint": "^6.8.0",
"eslint-plugin-compat": "^3.5.1",
"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-promise": "^4.2.1",
"file-loader": "^6.0.0",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-cli": "^2.2.0",
"gulp-concat": "^2.6.1",
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-inject": "^5.0.5",
"gulp-mode": "^1.0.2",
"gulp-postcss": "^8.0.0",
"gulp-sass": "^4.0.2",
"gulp-sourcemaps": "^2.6.5",
"gulp-terser": "^1.2.0",
"html-webpack-plugin": "^4.3.0",
"lazypipe": "^1.0.2",
"node-sass": "^4.13.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"style-loader": "^0.23.1", "postcss-preset-env": "^6.7.0",
"webpack": "^4.41.0", "style-loader": "^1.1.3",
"webpack-cli": "^3.3.9", "stylelint": "^13.3.3",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-no-browser-hacks": "^1.2.1",
"stylelint-order": "^4.0.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-concat-plugin": "^3.0.0", "webpack-concat-plugin": "^3.0.0",
"webpack-dev-server": "^3.8.1", "webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2",
"webpack-stream": "^5.2.1"
}, },
"dependencies": { "dependencies": {
"alameda": "^1.3.0", "alameda": "^1.4.0",
"document-register-element": "^0.5.4", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.6.5",
"date-fns": "^2.13.0",
"document-register-element": "^1.14.3",
"fast-text-encoding": "^1.0.1",
"flv.js": "^1.5.0", "flv.js": "^1.5.0",
"hls.js": "^0.12.4", "headroom.js": "^0.11.0",
"howler": "^2.1.2", "hls.js": "^0.13.1",
"jquery": "^3.4.1", "howler": "^2.1.3",
"intersection-observer": "^0.10.0",
"jellyfin-apiclient": "^1.1.1",
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.5.1",
"jstree": "^3.3.7", "jstree": "^3.3.7",
"libjass": "^0.11.0", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
"material-design-icons-iconfont": "^5.0.1",
"native-promise-only": "^0.8.0-a", "native-promise-only": "^0.8.0-a",
"requirejs": "^2.3.5", "page": "^1.11.6",
"query-string": "^6.11.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"shaka-player": "^2.5.5", "screenfull": "^5.0.2",
"sortablejs": "^1.9.0", "shaka-player": "^2.5.11",
"swiper": "^3.4.2", "sortablejs": "^1.10.2",
"libass-wasm": "^2.1.1", "swiper": "^5.3.7",
"webcomponents.js": "^0.7.24", "webcomponents.js": "^0.7.24",
"whatwg-fetch": "^1.1.1" "whatwg-fetch": "^3.0.0"
},
"babel": {
"presets": [
"@babel/preset-env"
],
"overrides": [
{
"test": [
"src/components/autoFocuser.js",
"src/components/cardbuilder/cardBuilder.js",
"src/components/filedownloader.js",
"src/components/images/imageLoader.js",
"src/components/lazyloader/lazyloader-intersectionobserver.js",
"src/components/playback/mediasession.js",
"src/components/sanatizefilename.js",
"src/components/scrollManager.js",
"src/scripts/dfnshelper.js",
"src/scripts/dom.js",
"src/scripts/filesystem.js",
"src/scripts/imagehelper.js",
"src/scripts/inputManager.js",
"src/scripts/keyboardnavigation.js",
"src/scripts/settings/appSettings.js",
"src/scripts/settings/userSettings.js",
"src/scripts/settings/webSettings.js"
],
"plugins": [
"@babel/plugin-transform-modules-amd"
]
}
]
}, },
"browserslist": [ "browserslist": [
"last 2 Firefox versions", "last 2 Firefox versions",
@ -46,18 +120,21 @@
"last 2 Safari versions", "last 2 Safari versions",
"last 2 iOS versions", "last 2 iOS versions",
"last 2 Edge versions", "last 2 Edge versions",
"Chrome 27",
"Chrome 38", "Chrome 38",
"Chrome 47", "Chrome 47",
"Chrome 53", "Chrome 53",
"Chrome 56", "Chrome 56",
"Chrome 63", "Chrome 63",
"Explorer 11",
"Firefox ESR" "Firefox ESR"
], ],
"scripts": { "scripts": {
"serve": "webpack-dev-server --config webpack.dev.js --open", "serve": "gulp serve --development",
"build": "webpack --config webpack.prod.js", "prepare": "gulp --production",
"lint": "eslint \"src\"", "build:development": "gulp --development",
"prepare": "webpack --config webpack.prod.js" "build:production": "gulp --production",
"build:standalone": "gulp standalone --development",
"lint": "eslint \".\"",
"stylelint": "stylelint \"src/**/*.css\""
} }
} }

View file

@ -1,5 +1,16 @@
module.exports = { const packageConfig = require('./package.json');
const postcssPresetEnv = require('postcss-preset-env');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const config = () => ({
plugins: [ plugins: [
require('autoprefixer') // Explicitly specify browserslist to override ones from node_modules
// For example, Swiper has it in its package.json
postcssPresetEnv({browsers: packageConfig.browserslist}),
autoprefixer({overrideBrowserslist: packageConfig.browserslist}),
cssnano()
] ]
} });
module.exports = config;

41
scripts/scdup.py Normal file
View file

@ -0,0 +1,41 @@
import sys
import os
import json
# load every key in the source language
# check the keys in all translations
# remove keys that only exist in translations
cwd = os.getcwd()
langdir = cwd + '/../src/strings'
langlst = os.listdir(langdir)
langlst.remove('en-us.json')
print(langlst)
input('press enter to continue')
keysus = []
with open(langdir + '/' + 'en-us.json') as en:
langus = json.load(en)
for key in langus:
keysus.append(key)
for lang in langlst:
with open(langdir + '/' + lang, 'r') as f:
inde = 2
if '\n \"' in f.read():
inde = 4
f.close()
with open(langdir + '/' + lang, 'r+') as f:
langjson = json.load(f)
langjnew = {}
for key in langjson:
if key in keysus:
langjnew[key] = langjson[key]
f.seek(0)
f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False))
f.write('\n')
f.truncate()
f.close()
print('DONE')

40
scripts/scgen.py Normal file
View file

@ -0,0 +1,40 @@
import os
import subprocess
import json
# load all keys in the source language
# check entire codebase for usages
# print unused keys to a text file
# TODO: dynamic string usages cause false positives
cwd = os.getcwd()
langdir = cwd + '/../src/strings'
langlst = []
langlst.append('en-us.json')
# unused keys
dep = []
def grep(key):
command = 'grep -r -E "(\(\\\"|\(\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = p.stdout.readlines()
if output:
print('DONE: ' + key)
return True
print('UNUSED: ' + key)
dep.append(key)
return False
for lang in langlst:
with open(langdir + '/' + lang) as f:
langjson = json.load(f)
for key in langjson:
grep(key)
print(dep)
print('LENGTH: ' + str(len(dep)))
with open('scout.txt', 'w') as out:
for item in dep:
out.write(item + '\n')
out.close()

34
scripts/scrm.py Normal file
View file

@ -0,0 +1,34 @@
import sys
import os
import json
# load text file containing unused keys
# remove the keys from all string files
cwd = os.getcwd()
langdir = cwd + '/../src/strings'
langlst = os.listdir(langdir)
keys = []
with open('scout.txt', 'r') as f:
for line in f:
keys.append(line.strip('\n'))
for lang in langlst:
with open(langdir + '/' + lang, 'r') as f:
inde = 2
if '\n \"' in f.read():
inde = 4
f.close()
with open(langdir + '/' + lang, 'r+') as f:
langjson = json.load(f)
for key in keys:
langjson.pop(key, None)
f.seek(0)
f.write(json.dumps(langjson, indent=inde, sort_keys=False, ensure_ascii=False))
f.write('\n')
f.truncate()
f.close()
print('DONE')

View file

@ -5,12 +5,11 @@
<div class="verticalSection"> <div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center"> <div class="sectionTitleContainer flex align-items-center">
<h1 class="sectionTitle pluginName"></h1> <h1 class="sectionTitle pluginName"></h1>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/plugins/index.html">${Help}</a> <a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/plugins/index.html">${Help}</a>
</div> </div>
<p id="tagline" style="font-style: italic;"></p> <p id="overview" style="font-style: italic;"></p>
<p id="pPreviewImage"></p> <p id="description"></p>
<p id="overview"></p>
</div> </div>
<div class="verticalSection"> <div class="verticalSection">
@ -28,7 +27,6 @@
</button> </button>
<div class="fieldDescription">${ServerRestartNeededAfterPluginInstall}</div> <div class="fieldDescription">${ServerRestartNeededAfterPluginInstall}</div>
</div> </div>
<p id="nonServerMsg"></p>
</form> </form>
</div> </div>
</div> </div>
@ -37,9 +35,6 @@
<div is="emby-collapse" title="${HeaderDeveloperInfo}"> <div is="emby-collapse" title="${HeaderDeveloperInfo}">
<div class="collapseContent"> <div class="collapseContent">
<p id="developer"></p> <p id="developer"></p>
<p id="pViewWebsite" style="display: none;">
<a is="emby-linkbutton" class="button-link" href="#" target="_blank">${ButtonViewWebsite}</a>
</p>
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
<form class="addServerForm" style="margin: 0 auto;"> <form class="addServerForm" style="margin: 0 auto;">
<h1>${HeaderConnectToServer}</h1> <h1>${HeaderConnectToServer}</h1>
<div class="inputContainer"> <div class="inputContainer">
<input is="emby-input" type="text" id="txtServerHost" required="required" label="${LabelServerHost}" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off" /> <input is="emby-input" type="url" id="txtServerHost" required="required" label="${LabelServerHost}"/>
<div class="fieldDescription">${LabelServerHostHelp}</div> <div class="fieldDescription">${LabelServerHostHelp}</div>
</div> </div>
<br /> <br />

View file

@ -4,18 +4,19 @@
<div class="detailSectionHeader"> <div class="detailSectionHeader">
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">${HeaderApiKeys}</h2> <h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">${HeaderApiKeys}</h2>
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${ButtonAdd}"> <button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${ButtonAdd}">
<i class="md-icon">add</i> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
</div> </div>
<p>${HeaderApiKeysHelp}</p> <p>${HeaderApiKeysHelp}</p>
<br /> <br />
<table class="tblApiKeys detailTable"> <table class="tblApiKeys detailTable">
<caption class="clipForScreenReader">${ApiKeysCaption}</caption>
<thead> <thead>
<tr> <tr>
<th class="detailTableHeaderCell"></th> <th scope="col" class="detailTableHeaderCell"></th>
<th class="detailTableHeaderCell">${HeaderApiKey}</th> <th scope="col" class="detailTableHeaderCell">${HeaderApiKey}</th>
<th class="detailTableHeaderCell">${HeaderApp}</th> <th scope="col" class="detailTableHeaderCell">${HeaderApp}</th>
<th class="detailTableHeaderCell">${HeaderDateIssued}</th> <th scope="col" class="detailTableHeaderCell">${HeaderDateIssued}</th>
</tr> </tr>
</thead> </thead>
<tbody class="resultBody"></tbody> <tbody class="resultBody"></tbody>

View file

@ -1,262 +1,318 @@
.dashboardColumn, .dashboardColumn,
.dashboardSections { .dashboardSections {
flex-direction: column; flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
} }
.dashboardFooter { .dashboardFooter {
margin-top: 3.5em; margin-top: 3.5em;
text-align: center text-align: center;
} }
.dashboardFooter a { .dashboardFooter a {
margin: 0 .7em margin: 0 0.7em;
} }
progress { progress {
appearance: none; appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
margin: 0; margin: 0;
background: #ccc !important background: #ccc !important;
} }
progress[role]:after { progress[role]::after {
background-image: none background-image: none;
} }
progress::-webkit-progress-bar { progress::-webkit-progress-bar {
background: #ccc background: #ccc;
} }
progress::-moz-progress-bar { progress::-moz-progress-bar {
background-color: #00a4dc background-color: #00a4dc;
} }
progress::-webkit-progress-value { progress::-webkit-progress-value {
background-color: #00a4dc background-color: #00a4dc;
} }
progress[aria-valuenow]:before { progress[aria-valuenow]::before {
border-radius: .4em; border-radius: 0.4em;
background-color: #00a4dc background-color: #00a4dc;
} }
.localnav { .localnav {
margin-bottom: 2.2em !important margin-bottom: 2.2em !important;
} }
@media all and (min-width:50em) { @media all and (min-width: 50em) {
.type-interior > div[data-role=content],
.type-interior>.ui-panel-content-wrap>div[data-role=content], .type-interior > .ui-panel-content-wrap > div[data-role=content] {
.type-interior>div[data-role=content] {
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
padding-top: 0; padding-top: 0;
overflow: hidden overflow: hidden;
} }
} }
.dashboardDocument .dashboardEntryHeaderButton, .dashboardDocument .dashboardEntryHeaderButton,
.dashboardDocument .lnkManageServer { .dashboardDocument .lnkManageServer {
display: none !important display: none !important;
} }
.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;
display: block display: block;
} }
.adminDrawerLogo img { .adminDrawerLogo img {
height: 4em height: 4em;
}
a[data-role=button] {
background: #292929 !important;
background-clip: padding-box;
-webkit-font-smoothing: antialiased;
-webkit-user-select: none;
-webkit-background-clip: padding-box;
cursor: pointer !important;
font-family: inherit !important;
font-weight: 500 !important;
margin: 0 0.25em !important;
display: inline-block;
padding: 0.8em 1em;
text-align: center;
text-decoration: none !important;
} }
div[data-role=controlgroup] a[data-role=button] { div[data-role=controlgroup] a[data-role=button] {
display: inline-block !important; display: inline-block !important;
margin: 0 !important; margin: 0 !important;
-webkit-box-shadow: none !important;
box-shadow: none !important; box-shadow: none !important;
border-radius: 0 -webkit-border-radius: 0;
border-radius: 0;
} }
div[data-role=controlgroup] a[data-role=button]:first-child { div[data-role=controlgroup] a[data-role=button]:first-child {
border-bottom-left-radius: .3125em; -webkit-border-bottom-left-radius: 0.3125em;
border-top-left-radius: .3125em border-bottom-left-radius: 0.3125em;
-webkit-border-top-left-radius: 0.3125em;
border-top-left-radius: 0.3125em;
} }
div[data-role=controlgroup] a[data-role=button]:last-child { div[data-role=controlgroup] a[data-role=button]:last-child {
border-bottom-right-radius: .3125em; -webkit-border-bottom-right-radius: 0.3125em;
border-top-right-radius: .3125em border-bottom-right-radius: 0.3125em;
-webkit-border-top-right-radius: 0.3125em;
border-top-right-radius: 0.3125em;
} }
div[data-role=controlgroup] a[data-role=button]+a[data-role=button] { div[data-role=controlgroup] a[data-role=button] + a[data-role=button] {
border-left-width: 0 !important; border-left-width: 0 !important;
margin: 0 0 0 -.4em !important margin: 0 0 0 -0.4em !important;
} }
div[data-role=controlgroup] a.ui-btn-active { div[data-role=controlgroup] a.ui-btn-active {
background: #00a4dc !important; background: #00a4dc !important;
color: #292929 !important color: #292929 !important;
} }
.header .imageLink { .sessionAppInfo img {
display: inline-block max-width: 40px;
max-height: 40px;
margin-right: 8px;
} }
.header .imageLink img { .appLinks img {
height: 2.1em; height: 36px;
vertical-align: middle
}
.content-primary {
padding-top: 6em;
padding-right: 1em;
padding-left: 1em
}
.withTabs .content-primary {
padding-top: 9em !important
}
@media all and (min-width:40em) {
.content-primary {
padding-top: 7em
}
.withTabs .content-primary {
padding-top: 10em !important
}
}
@media all and (min-width:84em) {
.withTabs .content-primary {
padding-top: 7em !important
}
}
.content-primary ul:first-child {
margin-top: 0
}
.dashboardSections {
display: flex;
flex-direction: column
}
.dashboardColumn {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 1
}
.activeSession:not(.playingSession) .sessionNowPlayingContent {
display: none
}
.dashboardSection {
flex-shrink: 0;
margin: 0 0 2em
}
.dashboardSection h3 {
margin-top: .5em;
margin-bottom: .5em
}
.activeRecordingItems>.card {
width: 50%
}
@media all and (min-width:70em) {
.dashboardSections {
flex-wrap: wrap;
flex-direction: row
}
.dashboardColumn-2-60 {
width: 46%
}
.dashboardColumn-2-40 {
width: 27%
}
.dashboardSection {
padding: 0 1.5em
}
.activeRecordingItems>.card {
width: 25%
}
}
.premiumBanner img {
position: absolute;
text-align: right;
top: 0;
right: 0;
width: 4.4em;
height: 4.4em
}
.wizardContent {
max-width: 62em;
padding: .5em 2em 1em;
margin: 0 auto;
background: #fff
}
.wizardNavigation {
text-align: right
}
.wizardContent form {
max-width: 100%
} }
.wizardContent h2 img { .wizardContent h2 img {
height: 2.5em; height: 2.5em;
vertical-align: middle; vertical-align: middle;
margin-right: .5em; margin-right: 0.5em;
position: relative; position: relative;
top: -.3em top: -0.3em;
} }
.scheduledTaskPaperIconItem { .header .imageLink {
outline: 0 !important display: inline-block;
} }
.activeSession { .header .imageLink img {
width: 100% !important height: 2.1em;
vertical-align: middle;
} }
.activitylogUserPhoto { .content-primary {
height:1.71em; padding-top: 6em;
width:1.71em; padding-right: 1em;
border-radius:100%; padding-left: 1em;
margin-right:.5em;
background-size:cover;
background-repeat:no-repeat;
background-position:center;
} }
@media all and (min-width:40em) { .withTabs .content-primary {
.activeSession { padding-top: 9em !important;
width: 100% !important }
@media all and (min-width: 40em) {
.content-primary {
padding-top: 4.6em;
}
.withTabs .content-primary {
padding-top: 10em !important;
} }
} }
@media all and (min-width:50em) { @media all and (min-width: 84em) {
.withTabs .content-primary {
padding-top: 7em !important;
}
}
.content-primary ul:first-child {
margin-top: 0;
}
.dashboardSections {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.dashboardColumn {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
}
.sessionNowPlayingContent {
-webkit-background-size: cover;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.activeSession:not(.playingSession) .sessionNowPlayingContent {
display: none;
}
.dashboardSection {
-webkit-flex-shrink: 0;
flex-shrink: 0;
margin: 0 0 2em;
}
.dashboardSection h3 {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.activeRecordingItems > .card {
width: 50%;
}
@media all and (min-width: 70em) {
.dashboardSections {
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
flex-direction: row;
}
.dashboardColumn-2-60 {
width: 46%;
}
.dashboardColumn-2-40 {
width: 27%;
}
.dashboardSection {
padding: 0 1.5em;
}
.activeRecordingItems > .card {
width: 25%;
}
}
.wizardContent {
max-width: 62em;
padding: 0.5em 2em 1em;
margin: 0 auto;
background: #fff;
}
.wizardNavigation {
text-align: right;
}
.wizardContent form {
max-width: 100%;
}
.scheduledTaskPaperIconItem {
outline: 0 !important;
}
.activeSession {
width: 100% !important;
}
.activitylogUserPhoto {
height: 1.71em;
width: 1.71em;
border-radius: 100%;
margin-right: 0.5em;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
@media all and (min-width: 40em) {
.activeSession { .activeSession {
width: 50% !important width: 100% !important;
}
}
@media all and (min-width: 50em) {
.activeSession {
width: 50% !important;
} }
} }
.sessionCardFooter { .sessionCardFooter {
padding-top: .5em !important; padding-top: 0.5em !important;
padding-bottom: 1em !important; padding-bottom: 1em !important;
border-top: 1px solid #eee; border-top: 1px solid #eee;
text-align: center; text-align: center;
position: relative position: relative;
} }
.sessionAppInfo { .sessionAppInfo {
@ -265,22 +321,11 @@ div[data-role=controlgroup] a.ui-btn-active {
} }
.sessionCardButtons { .sessionCardButtons {
min-height: 2.7em min-height: 2.7em;
} }
.sessionCardButton { .sessionCardButton {
margin: 0 margin: 0;
}
.sessionNowPlayingContent {
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0
} }
.sessionNowPlayingInnerContent { .sessionNowPlayingInnerContent {
@ -289,23 +334,23 @@ div[data-role=controlgroup] a.ui-btn-active {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
font-weight: 400 font-weight: 400;
} }
.sessionNowPlayingContent-withbackground+.sessionNowPlayingInnerContent { .sessionNowPlayingContent-withbackground + .sessionNowPlayingInnerContent {
color: #fff !important; color: #fff !important;
background: rgba(0, 0, 0, .7) background: rgba(0, 0, 0, 0.7);
} }
.sessionAppName { .sessionAppName {
vertical-align: top; vertical-align: top;
max-width: 200px max-width: 200px;
} }
.sessionNowPlayingDetails { .sessionNowPlayingDetails {
display: flex; display: flex;
position: absolute; position: absolute;
bottom: 0px; bottom: 0;
width: 100%; width: 100%;
} }
@ -315,12 +360,6 @@ div[data-role=controlgroup] a.ui-btn-active {
padding: 0.8em 0.5em; padding: 0.8em 0.5em;
} }
.sessionAppInfo img {
max-width: 40px;
max-height: 40px;
margin-right: 8px;
}
.sessionNowPlayingTime { .sessionNowPlayingTime {
flex-shrink: 0; flex-shrink: 0;
align-self: flex-end; align-self: flex-end;
@ -332,6 +371,13 @@ div[data-role=controlgroup] a.ui-btn-active {
white-space: nowrap; white-space: nowrap;
} }
.playbackProgress,
.transcodingProgress {
margin: 0;
width: 100%;
background: transparent !important;
}
.activeSession .playbackProgress, .activeSession .playbackProgress,
.activeSession .transcodingProgress { .activeSession .transcodingProgress {
position: absolute; position: absolute;
@ -342,13 +388,6 @@ div[data-role=controlgroup] a.ui-btn-active {
width: 100%; width: 100%;
} }
.playbackProgress,
.transcodingProgress {
margin: 0px;
width: 100%;
background: transparent !important;
}
.playbackProgress > div { .playbackProgress > div {
z-index: 1000; z-index: 1000;
background-color: #00a4dc; background-color: #00a4dc;
@ -358,70 +397,70 @@ div[data-role=controlgroup] a.ui-btn-active {
background-color: #dd4919; background-color: #dd4919;
} }
@media all and (max-width:34.375em) { @media all and (max-width: 34.375em) {
.sessionAppName { .sessionAppName {
max-width: 160px max-width: 160px;
} }
} }
@media all and (max-width:31.25em) { @media all and (max-width: 31.25em) {
.sessionAppName { .sessionAppName {
max-width: 150px max-width: 150px;
} }
} }
.disabledUser { .disabledUser {
filter: grayscale(100%) -webkit-filter: grayscale(100%);
filter: grayscale(100%);
} }
.disabledUserBanner { .disabledUserBanner {
margin: 0 0 2em margin: 0 0 2em;
} }
.appLinks a { .appLinks a {
text-decoration: none !important
}
.appLinks a+a {
margin-left: 5px
}
.appLinks img {
height: 36px
}
a[data-role=button] {
background-clip: padding-box;
cursor: pointer !important;
font-family: inherit !important;
font-weight: 500 !important;
margin: 0 .25em !important;
display: inline-block;
padding: .8em 1em;
text-align: center;
text-decoration: none !important; text-decoration: none !important;
background: #292929 !important; }
.appLinks a + a {
margin-left: 5px;
}
@-webkit-keyframes rotating {
from {
-webkit-transform: rotate(0);
transform: rotate(0);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
} }
@keyframes rotating { @keyframes rotating {
from { from {
transform: rotate(0) -webkit-transform: rotate(0);
transform: rotate(0);
} }
to { to {
transform: rotate(360deg) -webkit-transform: rotate(360deg);
transform: rotate(360deg);
} }
} }
.rotatingCircle { .rotatingCircle {
animation: rotating 2s linear infinite -webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
} }
.pluginPreviewImg { .pluginPreviewImg {
box-shadow: 0 .0725em .29em 0 rgba(0, 0, 0, .37) -webkit-box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
} }
.ui-bar-a{ .ui-bar-a {
text-align: center; text-align: center;
padding: 0 20px; padding: 0 20px;
} }

View file

@ -1,7 +1,7 @@
.detailTableBodyCell, .detailTableBodyCell,
.detailTableHeaderCell { .detailTableHeaderCell {
border-spacing: 0; border-spacing: 0;
padding: .4em padding: 0.4em;
} }
.detailTable { .detailTable {
@ -9,11 +9,11 @@
border-spacing: 0; border-spacing: 0;
text-align: left; text-align: left;
width: 100%; width: 100%;
margin: 0 auto margin: 0 auto;
} }
.detailTableHeaderCell { .detailTableHeaderCell {
font-weight: 700; font-weight: 700;
text-align: left; text-align: left;
vertical-align: top vertical-align: top;
} }

View file

@ -1,34 +1,35 @@
html { html {
font-family: -apple-system, "Helvetica", system-ui, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", 'Open Sans', sans-serif; font-family: "Noto Sans", sans-serif;
}
html {
font-size: 93%; font-size: 93%;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%; text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
} }
h1, h2, h3 { h1,
/* For better bolding, since Helvetica does not support 500 weight, and 600 is too thick */ h2,
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", 'Open Sans', sans-serif; h3 {
font-family: "Noto Sans", sans-serif;
} }
h1 { h1 {
font-weight: 500; font-weight: 400;
font-size: 1.8em; font-size: 1.8em;
} }
h2 { h2 {
font-weight: 500; font-weight: 400;
font-size: 1.5em; font-size: 1.5em;
} }
h3 { h3 {
font-weight: 500; font-weight: 400;
font-size: 1.17em; font-size: 1.17em;
} }
.layout-tv { .layout-tv {
font-size: 2.5vh; font-size: 130%;
} }
.layout-mobile { .layout-mobile {

View file

@ -1,5 +1,5 @@
h1 { h1 {
font-weight: 500; font-weight: 400;
font-size: 1.8em; font-size: 1.8em;
} }
@ -8,12 +8,12 @@ h1 {
} }
h2 { h2 {
font-weight: 500; font-weight: 400;
font-size: 1.5em; font-size: 1.5em;
} }
h3 { h3 {
font-weight: 500; font-weight: 400;
font-size: 1.17em; font-size: 1.17em;
} }

View file

@ -1,8 +1,8 @@
html { html {
font-size: 82% !important font-size: 82% !important;
} }
.formDialogFooter { .formDialogFooter {
position: static !important; position: static !important;
margin: 0 -1em !important margin: 0 -1em !important;
} }

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
.guideVerticalScroller { .guideVerticalScroller {
padding-bottom: 15em padding-bottom: 15em;
} }
@media all and (min-width:62.5em) { @media all and (min-width: 62.5em) {
#guideTab { #guideTab {
padding-left: .5em padding-left: 0.5em;
} }
} }

View file

@ -1,25 +0,0 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'), url(flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff) format('woff');
}
.md-icon {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-feature-settings: "liga" 1;
line-height: 1;
overflow: hidden;
vertical-align: middle;
}

View file

@ -1,53 +1,57 @@
.editPageSidebar { .editPageSidebar {
display: block display: block;
} }
.editPageSidebar-withcontent { .editPageSidebar-withcontent {
display: none display: none;
} }
.libraryTree { .libraryTree {
margin-left: .25em margin-left: 0.25em;
} }
.offlineEditorNode { .offlineEditorNode {
color: #c33 color: #c33;
} }
.editorNode img { .editorNode img {
height: 18px; height: 18px;
margin: 0 .35em; margin: 0 0.35em;
vertical-align: middle; vertical-align: middle;
position: relative; position: relative;
top: -2px top: -2px;
} }
.jstree-anchor { .jstree-anchor {
font-weight: 400 !important font-weight: 400 !important;
} }
.jstree-wholerow-hovered { .jstree-wholerow-hovered {
background: #38c !important; background: #38c !important;
-webkit-border-radius: 0 !important;
border-radius: 0 !important; border-radius: 0 !important;
box-shadow: none !important -webkit-box-shadow: none !important;
box-shadow: none !important;
} }
.jstree-default .jstree-hovered { .jstree-default .jstree-hovered {
background: 0 0 !important; background: 0 0 !important;
-webkit-border-radius: 0 !important;
border-radius: 0 !important; border-radius: 0 !important;
-webkit-box-shadow: none !important;
box-shadow: none !important; box-shadow: none !important;
color: #fff !important color: #fff !important;
} }
.jstree-default .jstree-wholerow-clicked { .jstree-default .jstree-wholerow-clicked {
background: #00a4dc !important background: #00a4dc !important;
} }
.metadataSidebarIcon { .metadataSidebarIcon {
margin-right: .4em margin-right: 0.4em;
} }
@media all and (min-width:50em) { @media all and (min-width: 50em) {
.editPageSidebar { .editPageSidebar {
position: fixed; position: fixed;
top: 5.2em; top: 5.2em;
@ -55,21 +59,21 @@
left: 0; left: 0;
width: 30%; width: 30%;
border-right: 1px solid #555; border-right: 1px solid #555;
display: block display: block;
} }
.editPageInnerContent { .editPageInnerContent {
float: right; float: right;
width: 68.5% width: 68.5%;
} }
} }
@media all and (min-width:112.5em) { @media all and (min-width: 112.5em) {
.editPageSidebar { .editPageSidebar {
width: 25% width: 25%;
} }
.editPageInnerContent { .editPageInnerContent {
width: 73.5% width: 73.5%;
} }
} }

View file

@ -1,5 +1,6 @@
.scrollX { .scrollX {
overflow-x: auto; overflow-x: auto;
-webkit-overflow-scrolling: touch;
overflow-y: hidden; overflow-y: hidden;
white-space: nowrap; white-space: nowrap;
} }
@ -8,39 +9,50 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.hiddenScrollX, .layout-tv .scrollX { .hiddenScrollX,
scrollbar-width: none; .layout-tv .scrollX {
-ms-overflow-style: none;
} }
.hiddenScrollX-forced { .hiddenScrollX-forced {
scrollbar-width: none; overflow: -moz-scrollbars-none;
} }
.hiddenScrollX::-webkit-scrollbar, .layout-tv .scrollX::-webkit-scrollbar { .hiddenScrollX::-webkit-scrollbar,
.layout-tv .scrollX::-webkit-scrollbar {
height: 0 !important; height: 0 !important;
display: none; display: none;
} }
.scrollY { .scrollY {
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch;
overflow-x: hidden; overflow-x: hidden;
} }
.smoothScrollY { .smoothScrollY {
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch;
overflow-x: hidden; overflow-x: hidden;
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.hiddenScrollY, .layout-tv .smoothScrollY { .hiddenScrollY,
scrollbar-width: none; .layout-tv .smoothScrollY {
-ms-overflow-style: none;
/* Can't do this because it not only hides the scrollbar, but also prevents scrolling */
/* overflow: -moz-scrollbars-none; */
} }
.hiddenScrollY-forced { .hiddenScrollY-forced {
scrollbar-width: none; overflow: -moz-scrollbars-none;
} }
.hiddenScrollY::-webkit-scrollbar, .layout-tv .smoothScrollY::-webkit-scrollbar, .layout-tv .scrollY::-webkit-scrollbar { .hiddenScrollY::-webkit-scrollbar,
.layout-tv .smoothScrollY::-webkit-scrollbar,
.layout-tv .scrollY::-webkit-scrollbar {
width: 0 !important; width: 0 !important;
display: none; display: none;
} }

View file

@ -2,7 +2,23 @@ body,
html { html {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100% height: 100%;
}
.clipForScreenReader {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
.material-icons {
/* Fix font ligatures on older WebOS versions */
-webkit-font-feature-settings: "liga";
} }
.backgroundContainer { .backgroundContainer {
@ -11,52 +27,58 @@ html {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
contain: strict contain: strict;
} }
html { html {
line-height: 1.35 line-height: 1.35;
} }
.layout-mobile, .layout-mobile,
.layout-tv { .layout-tv {
user-select: none -webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
body { body {
overflow-x: hidden; overflow-x: hidden;
background-color: transparent !important; background-color: transparent !important;
-webkit-font-smoothing: antialiased;
} }
.mainAnimatedPage { .mainAnimatedPage {
contain: style size !important contain: style size !important;
} }
.pageContainer { .pageContainer {
overflow-x: visible !important overflow-x: visible !important;
} }
.bodyWithPopupOpen { .bodyWithPopupOpen {
overflow-y: hidden !important overflow-y: hidden !important;
} }
div[data-role=page] { div[data-role=page] {
outline: 0 outline: 0;
} }
.pageTitle { .pageTitle {
margin-top: 0; margin-top: 0;
font-family: inherit font-family: inherit;
} }
.fieldDescription { .fieldDescription {
padding-left: .15em; padding-left: 0.15em;
font-weight: 400; font-weight: 400;
white-space: normal !important white-space: normal !important;
} }
.fieldDescription+.fieldDescription { .fieldDescription + .fieldDescription {
margin-top: .3em margin-top: 0.3em;
} }
.content-primary, .content-primary,
@ -67,21 +89,34 @@ div[data-role=page] {
padding-bottom: 5em !important; padding-bottom: 5em !important;
} }
@media all and (min-width:50em) { @media all and (min-width: 50em) {
.readOnlyContent, .readOnlyContent,
form { form {
max-width: 54em max-width: 54em;
} }
} }
.headerHelpButton { .headerHelpButton {
margin-left: 1.25em !important; margin-left: 1.25em !important;
padding-bottom: .4em !important; padding-bottom: 0.4em !important;
padding-top: .4em !important padding-top: 0.4em !important;
} }
.mediaInfoContent { .mediaInfoContent {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
width: 85% width: 85%;
}
.headroom {
will-change: transform;
transition: transform 200ms linear;
}
.headroom--pinned {
transform: translateY(0%);
}
.headroom--unpinned {
transform: translateY(-100%);
} }

View file

@ -1,6 +1,9 @@
.chapterThumbTextContainer, .chapterThumbTextContainer,
.videoOsdBottom { .videoOsdBottom {
user-select: none; user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
} }
.osdPoster img, .osdPoster img,
@ -8,52 +11,59 @@
.videoOsdBottom { .videoOsdBottom {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0 right: 0;
} }
.osdHeader { .osdHeader {
transition: opacity .3s ease-out; -webkit-transition: opacity 0.3s ease-out;
-o-transition: opacity 0.3s ease-out;
transition: opacity 0.3s ease-out;
position: relative; position: relative;
z-index: 1; z-index: 1;
background: rgba(0, 0, 0, 0.7) !important; background: rgba(0, 0, 0, 0.7) !important;
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important; backdrop-filter: none !important;
color: #eee !important; color: #eee !important;
} }
.osdHeader-hidden { .osdHeader-hidden {
opacity: 0 opacity: 0;
} }
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) { .osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) {
display: none display: none;
} }
.chapterThumbContainer { .chapterThumbContainer {
-webkit-box-shadow: 0 0 1.9vh #000;
box-shadow: 0 0 1.9vh #000; box-shadow: 0 0 1.9vh #000;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1; flex-grow: 1;
position: relative position: relative;
} }
.chapterThumb { .chapterThumb {
background-position: center center; background-position: center center;
-webkit-background-size: contain;
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
border: 0; border: 0;
height: 20vh; height: 20vh;
min-width: 20vh min-width: 20vh;
} }
@media all and (orientation:portrait) { @media all and (orientation: portrait) {
.chapterThumb { .chapterThumb {
height: 30vw; height: 30vw;
min-width: 30vw min-width: 30vw;
} }
} }
@media all and (max-height:50em) and (orientation:landscape) { @media all and (max-height: 50em) and (orientation: landscape) {
.chapterThumb { .chapterThumb {
height: 30vh; height: 30vh;
min-width: 30vh min-width: 30vh;
} }
} }
@ -62,161 +72,218 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: rgba(0, 0, 0, .7); background: rgba(0, 0, 0, 0.7);
padding: .25em .5em; padding: 0.25em 0.5em;
user-select: none user-select: none;
} }
.chapterThumbText { .chapterThumbText {
padding: .25em 0; padding: 0.25em 0;
margin: 0; margin: 0;
opacity: 1 opacity: 1;
} }
.chapterThumbText-dim { .chapterThumbText-dim {
opacity: .6 opacity: 0.6;
} }
.videoOsdBottom { .videoOsdBottom {
position: fixed; position: fixed;
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
padding: 1%; padding: 1%;
display: -webkit-box;
display: -webkit-flex;
display: flex; display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
flex-direction: row; flex-direction: row;
will-change: opacity; will-change: opacity;
-webkit-transition: opacity 0.3s ease-out;
-o-transition: opacity 0.3s ease-out;
transition: opacity 0.3s ease-out; transition: opacity 0.3s ease-out;
color: #fff; color: #fff;
user-select: none user-select: none;
-webkit-touch-callout: none;
} }
.videoOsdBottom-hidden { .videoOsdBottom-hidden {
opacity: 0 opacity: 0;
} }
.osdControls { .osdControls {
flex-grow: 1 -webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
} }
.videoOsdBottom .buttons { .videoOsdBottom .buttons {
padding: .25em 0 0; padding: 0.25em 0 0;
display: -webkit-box;
display: -webkit-flex;
display: flex; display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center -webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
} }
.osdVolumeSliderContainer { .osdVolumeSliderContainer {
width: 9em; width: 9em;
flex-grow: 1 -webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
} }
.osdMediaInfo, .osdMediaInfo,
.volumeButtons { .volumeButtons {
display: flex; display: flex;
display: -webkit-box;
display: -webkit-flex;
align-items: center; align-items: center;
-webkit-box-align: center;
} }
.volumeButtons { .volumeButtons {
margin: 0 .5em 0 auto; margin: 0 0.5em 0 auto;
display: flex; display: flex;
align-items: center -webkit-align-items: center;
align-items: center;
} }
.osdTimeText { .osdTimeText {
margin-left: 1em; margin-left: 1em;
user-select: none -webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
.osdPoster { .osdPoster {
width: 10%; width: 10%;
position: relative; position: relative;
margin-right: .5em margin-right: 0.5em;
} }
.osdPoster img { .osdPoster img {
position: absolute; position: absolute;
height: auto; height: auto;
width: 100%; width: 100%;
-webkit-box-shadow: 0 0 1.9vh #000;
box-shadow: 0 0 1.9vh #000; box-shadow: 0 0 1.9vh #000;
border: .08em solid #222; border: 0.08em solid #222;
user-drag: none; user-drag: none;
user-select: none user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
} }
.osdTitle, .osdTitle,
.osdTitleSmall { .osdTitleSmall {
margin: 0 1em 0 0 margin: 0 1em 0 0;
} }
.osdMediaInfo { .osdMediaInfo {
display: flex; display: flex;
align-items: center -webkit-align-items: center;
align-items: center;
} }
.osdSecondaryMediaInfo { .osdSecondaryMediaInfo {
padding-left: .6em !important padding-left: 0.6em !important;
} }
.osdTextContainer { .osdTextContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex; display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center; align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
margin-bottom: .7em; margin-bottom: 0.7em;
padding-left: .5em padding-left: 0.5em;
} }
.osdMainTextContainer { .osdMainTextContainer {
align-items: baseline -webkit-box-align: baseline;
-webkit-align-items: baseline;
align-items: baseline;
} }
.osdMediaStatus { .osdMediaStatus {
margin-left: auto; margin-left: auto;
} }
@-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin { @keyframes spin {
100% { 100% {
transform:rotate(360deg); -webkit-transform: rotate(360deg);
transform: rotate(360deg);
} }
} }
.osdMediaStatus .animate { .osdMediaStatus .animate {
animation:spin 4s linear infinite; -webkit-animation: spin 4s linear infinite;
-moz-animation: spin 4s linear infinite;
animation: spin 4s linear infinite;
} }
.pageContainer { .pageContainer {
top: 0; top: 0;
position: fixed position: fixed;
} }
@media all and (max-width:30em) { @media all and (max-width: 30em) {
.btnFastForward, .btnFastForward,
.btnRewind, .btnRewind,
.osdMediaInfo, .osdMediaInfo,
.osdPoster { .osdPoster {
display: none !important display: none !important;
} }
} }
@media all and (max-width:33.75em) { @media all and (max-width: 33.75em) {
.videoOsdBottom .paper-icon-button-light { .videoOsdBottom .paper-icon-button-light {
margin: 0 margin: 0;
} }
} }
@media all and (max-width:43em) { @media all and (max-width: 43em) {
.videoOsdBottom .volumeButtons, .videoOsdBottom .volumeButtons,
.osdMediaStatus span { .osdMediaStatus span {
display: none !important display: none !important;
}
}
@media all and (max-width:50em) {
.videoOsdBottom .btnFastForward, .videoOsdBottom .btnRewind {
display: none !important
} }
} }
@media all and (max-width:75em) { @media all and (max-width: 50em) {
.videoOsdBottom .endsAtText { .videoOsdBottom .btnFastForward,
display: none !important .videoOsdBottom .btnRewind {
display: none !important;
}
}
@media all and (max-width: 75em) {
.videoOsdBottom .endsAtText {
display: none !important;
} }
} }

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M24 19H0a13.6 13.6 0 0 1 2.21-6.07A11.2 11.2 0 0 1 5.87 9.4l.41-.23-2.02-3.41a.51.51 0 0 1 .17-.7.5.5 0 0 1 .69.18l2.08 3.5a12.62 12.62 0 0 1 4.84-.9 12.2 12.2 0 0 1 4.75.9l2.07-3.5a.5.5 0 0 1 .7-.17.51.51 0 0 1 .16.7L17.7 9.19l.5.28a11.38 11.38 0 0 1 3.63 3.62A14.48 14.48 0 0 1 24 19zm-7.5-4.48a1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1 1 1 0 0 0-1 1zm-11 0a1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1 1 1 0 0 0-1 1z"/> <path d="M24 19H0a13.6 13.6 0 0 1 2.21-6.07A11.2 11.2 0 0 1 5.87 9.4l.41-.23-2.02-3.41a.51.51 0 0 1 .17-.7.5.5 0 0 1 .69.18l2.08 3.5a12.62 12.62 0 0 1 4.84-.9 12.2 12.2 0 0 1 4.75.9l2.07-3.5a.5.5 0 0 1 .7-.17.51.51 0 0 1 .16.7L17.7 9.19l.5.28a11.38 11.38 0 0 1 3.63 3.62A14.48 14.48 0 0 1 24 19zm-7.5-4.48a1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1 1 1 0 0 0-1 1zm-11 0a1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1 1 1 0 0 0-1 1z" fill="#fff"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 563 B

Before After
Before After

20
src/assets/img/fresh.svg Normal file
View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg3390" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="141.25" viewBox="0 0 138.75 141.25" width="138.75" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata id="metadata3396">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" fill="#f93208">
<path id="path3412" d="m20.154 40.829c-28.149 27.622-13.657 61.011-5.734 71.931 35.254 41.954 92.792 25.339 111.89-5.9071 4.7608-8.2027 22.554-53.467-23.976-78.009z"/>
<path id="path3471" d="m39.613 39.265 4.7778-8.8607 28.406-5.0384 11.119 9.2082z"/>
</g>
<g id="layer2">
<path id="path3437" d="m39.436 8.5696 8.9682-5.2826 6.7569 15.479c3.7925-6.3226 13.79-16.316 24.939-4.6684-4.7281 1.2636-7.5161 3.8553-7.7397 8.4768 15.145-4.1697 31.343 3.2127 33.539 9.0911-10.951-4.314-27.695 10.377-41.771 2.334 0.009 15.045-12.617 16.636-19.902 17.076 2.077-4.996 5.591-9.994 1.474-14.987-7.618 8.171-13.874 10.668-33.17 4.668 4.876-1.679 14.843-11.39 24.448-11.425-6.775-2.467-12.29-2.087-17.814-1.475 2.917-3.961 12.149-15.197 28.625-8.476z" fill="#02902e"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="145" height="140"><path fill="#0fc755" d="M47.4 35.342c-13.607-7.935-12.32-25.203 2.097-31.88 26.124-6.531 29.117 13.78 22.652 30.412-6.542 24.11 18.095 23.662 19.925 10.067 3.605-18.412 19.394-26.695 31.67-16.359 12.598 12.135 7.074 36.581-17.827 34.187-16.03-1.545-19.552 19.585.839 21.183 32.228 1.915 42.49 22.167 31.04 35.865-15.993 15.15-37.691-4.439-45.512-19.505-6.8-9.307-17.321.11-13.423 6.502 12.983 19.465 2.923 31.229-10.906 30.62-13.37-.85-20.96-9.06-13.214-29.15 3.897-12.481-8.595-15.386-16.57-5.45-11.707 19.61-28.865 13.68-33.976 4.19-3.243-7.621-2.921-25.846 24.119-23.696 16.688 4.137 11.776-12.561-.63-13.633-9.245-.443-30.501-7.304-22.86-24.54 7.34-11.056 24.958-11.768 33.348 6.293 3.037 4.232 8.361 11.042 18.037 5.033 3.51-5.197 1.21-13.9-8.809-20.135z"/></svg>

After

Width:  |  Height:  |  Size: 833 B

View file

@ -5,94 +5,174 @@
var _define = window.define; var _define = window.define;
// document-register-element // document-register-element
var docRegister = require("document-register-element"); var docRegister = require('document-register-element');
_define("document-register-element", function() { _define('document-register-element', function() {
return docRegister; return docRegister;
}); });
// fetch // fetch
var fetch = require("whatwg-fetch"); var fetch = require('whatwg-fetch');
_define("fetch", function() { _define('fetch', function() {
return fetch return fetch;
});
// query-string
var query = require('query-string');
_define('queryString', function() {
return query;
}); });
// flvjs // flvjs
var flvjs = require("flv.js").default; var flvjs = require('flv.js/dist/flv').default;
_define("flvjs", function() { _define('flvjs', function() {
return flvjs; return flvjs;
}); });
// jstree // jstree
var jstree = require("jstree"); var jstree = require('jstree');
require("jstree/dist/themes/default/style.css"); require('jstree/dist/themes/default/style.css');
_define("jstree", function() { _define('jstree', function() {
return jstree; return jstree;
}); });
// jquery // jquery
var jquery = require("jquery"); var jquery = require('jquery');
_define("jQuery", function() { _define('jQuery', function() {
return jquery; return jquery;
}); });
// hlsjs // hlsjs
var hlsjs = require("hls.js"); var hlsjs = require('hls.js');
_define("hlsjs", function() { _define('hlsjs', function() {
return hlsjs; return hlsjs;
}); });
// howler // howler
var howler = require("howler"); var howler = require('howler');
_define("howler", function() { _define('howler', function() {
return howler; return howler;
}); });
// native-promise-only
var nativePromise = require("native-promise-only");
_define("native-promise-only", function() {
return nativePromise;
});
// resize-observer-polyfill // resize-observer-polyfill
var resize = require("resize-observer-polyfill").default; var resize = require('resize-observer-polyfill').default;
_define("resize-observer-polyfill", function() { _define('resize-observer-polyfill', function() {
return resize; return resize;
}); });
// shaka // shaka
var shaka = require("shaka-player"); var shaka = require('shaka-player');
_define("shaka", function() { _define('shaka', function() {
return shaka; return shaka;
}); });
// swiper // swiper
var swiper = require("swiper"); var swiper = require('swiper/js/swiper');
require("swiper/dist/css/swiper.min.css"); require('swiper/css/swiper.min.css');
_define("swiper", function() { _define('swiper', function() {
return swiper; return swiper;
}); });
// sortable // sortable
var sortable = require("sortablejs").default; var sortable = require('sortablejs').default;
_define("sortable", function() { _define('sortable', function() {
return sortable; return sortable;
}); });
// webcomponents // webcomponents
var webcomponents = require("webcomponents.js/webcomponents-lite"); var webcomponents = require('webcomponents.js/webcomponents-lite');
_define("webcomponents", function() { _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 libassWasm = require('libass-wasm');
_define("JavascriptSubtitlesOctopus", function() { _define('JavascriptSubtitlesOctopus', function() {
return libass_wasm; return libassWasm;
});
// material-icons
var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css');
_define('material-icons', function() {
return materialIcons;
});
// noto font
var noto = require('jellyfin-noto');
_define('jellyfin-noto', function () {
return noto;
});
// page.js
var page = require('page');
_define('page', function() {
return page;
});
// core-js
var polyfill = require('@babel/polyfill/dist/polyfill');
_define('polyfill', function () {
return polyfill;
});
// domtokenlist-shim
var classlist = require('classlist.js');
_define('classlist-polyfill', function () {
return classlist;
});
// Date-FNS
var dateFns = require('date-fns');
_define('date-fns', function () {
return dateFns;
});
var dateFnsLocale = require('date-fns/locale');
_define('date-fns/locale', function () {
return dateFnsLocale;
});
var fast_text_encoding = require('fast-text-encoding');
_define('fast-text-encoding', function () {
return fast_text_encoding;
});
// intersection-observer
var intersection_observer = require('intersection-observer');
_define('intersection-observer', function () {
return intersection_observer;
});
// screenfull
var screenfull = require('screenfull');
_define('screenfull', function () {
return screenfull;
});
// headroom.js
var headroom = require('headroom.js/dist/headroom');
_define('headroom', function () {
return headroom;
});
// apiclient
var apiclient = require('jellyfin-apiclient');
_define('apiclient', function () {
return apiclient.ApiClient;
});
_define('events', function () {
return apiclient.Events;
});
_define('credentialprovider', function () {
return apiclient.Credentials;
});
_define('connectionManagerFactory', function () {
return apiclient.ConnectionManager;
});
_define('appStorage', function () {
return apiclient.AppStorage;
}); });

View file

@ -1,5 +1,5 @@
define(["dialogHelper", "datetime", "emby-select", "paper-icon-button-light", "formDialogStyle"], function (dialogHelper, datetime) { define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-button-light', 'formDialogStyle'], function (dialogHelper, datetime, globalize) {
"use strict"; 'use strict';
function getDisplayTime(hours) { function getDisplayTime(hours) {
var minutes = 0; var minutes = 0;
@ -13,32 +13,32 @@ define(["dialogHelper", "datetime", "emby-select", "paper-icon-button-light", "f
} }
function populateHours(context) { function populateHours(context) {
var html = ""; var html = '';
for (var i = 0; i < 24; i++) { for (var i = 0; i < 24; i++) {
html += '<option value="' + i + '">' + getDisplayTime(i) + "</option>"; html += '<option value="' + i + '">' + getDisplayTime(i) + '</option>';
} }
html += '<option value="24">' + getDisplayTime(0) + "</option>"; html += '<option value="24">' + getDisplayTime(0) + '</option>';
context.querySelector("#selectStart").innerHTML = html; context.querySelector('#selectStart').innerHTML = html;
context.querySelector("#selectEnd").innerHTML = html; context.querySelector('#selectEnd').innerHTML = html;
} }
function loadSchedule(context, schedule) { function loadSchedule(context, schedule) {
context.querySelector("#selectDay").value = schedule.DayOfWeek || "Sunday"; context.querySelector('#selectDay').value = schedule.DayOfWeek || 'Sunday';
context.querySelector("#selectStart").value = schedule.StartHour || 0; context.querySelector('#selectStart').value = schedule.StartHour || 0;
context.querySelector("#selectEnd").value = schedule.EndHour || 0; context.querySelector('#selectEnd').value = schedule.EndHour || 0;
} }
function submitSchedule(context, options) { function submitSchedule(context, options) {
var updatedSchedule = { var updatedSchedule = {
DayOfWeek: context.querySelector("#selectDay").value, DayOfWeek: context.querySelector('#selectDay').value,
StartHour: context.querySelector("#selectStart").value, StartHour: context.querySelector('#selectStart').value,
EndHour: context.querySelector("#selectEnd").value EndHour: context.querySelector('#selectEnd').value
}; };
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
return void alert(Globalize.translate("ErrorMessageStartHourGreaterThanEnd")); return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd'));
} }
context.submitted = true; context.submitted = true;
@ -50,32 +50,32 @@ define(["dialogHelper", "datetime", "emby-select", "paper-icon-button-light", "f
show: function (options) { show: function (options) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", "components/accessschedule/accessschedule.template.html", true); xhr.open('GET', 'components/accessschedule/accessschedule.template.html', true);
xhr.onload = function (e) { xhr.onload = function (e) {
var template = this.response; var template = this.response;
var dlg = dialogHelper.createDialog({ var dlg = dialogHelper.createDialog({
removeOnClose: true, removeOnClose: true,
size: "small" size: 'small'
}); });
dlg.classList.add("formDialog"); dlg.classList.add('formDialog');
var html = ""; var html = '';
html += Globalize.translateDocument(template); html += globalize.translateDocument(template);
dlg.innerHTML = html; dlg.innerHTML = html;
populateHours(dlg); populateHours(dlg);
loadSchedule(dlg, options.schedule); loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg); dialogHelper.open(dlg);
dlg.addEventListener("close", function () { dlg.addEventListener('close', function () {
if (dlg.submitted) { if (dlg.submitted) {
resolve(options.schedule); resolve(options.schedule);
} else { } else {
reject(); reject();
} }
}); });
dlg.querySelector(".btnCancel").addEventListener("click", function (e) { dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
dlg.querySelector("form").addEventListener("submit", function (e) { dlg.querySelector('form').addEventListener('submit', function (e) {
submitSchedule(dlg, options); submitSchedule(dlg, options);
e.preventDefault(); e.preventDefault();
return false; return false;

View file

@ -1,6 +1,6 @@
<div class="formDialogHeader"> <div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"> <button is="paper-icon-button-light" class="btnCancel autoSize" title="${LabelPrevious}" tabindex="-1">
<i class="md-icon">arrow_back</i> <span class="material-icons arrow_back" aria-hidden="true"></span>
</button> </button>
<h3 class="formDialogHeaderTitle"> <h3 class="formDialogHeaderTitle">
${HeaderAccessSchedule} ${HeaderAccessSchedule}

View file

@ -4,7 +4,7 @@
padding: 0; padding: 0;
border: none; border: none;
max-height: 84%; max-height: 84%;
border-radius: .1em !important; border-radius: 0.1em !important;
} }
.actionsheet-not-fullscreen { .actionsheet-not-fullscreen {
@ -24,7 +24,7 @@
.actionSheetContent { .actionSheetContent {
margin: 0 !important; margin: 0 !important;
padding: .4em 0 !important; padding: 0.4em 0 !important;
flex-direction: column; flex-direction: column;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -45,7 +45,7 @@
} }
.actionsheetListItemBody { .actionsheetListItemBody {
padding: .4em 1em .4em .6em !important; padding: 0.4em 1em 0.4em 0.6em !important;
} }
.actionSheetItemText { .actionSheetItemText {
@ -59,13 +59,13 @@
} }
.actionSheetItemAsideText { .actionSheetItemAsideText {
opacity: .7; opacity: 0.7;
font-size: 90%; font-size: 90%;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
flex-shrink: 0; flex-shrink: 0;
margin-left: 5em; margin-left: 5em;
margin-right: .5em; margin-right: 0.5em;
} }
.actionSheetScroller { .actionSheetScroller {
@ -83,14 +83,14 @@
} }
.actionsheetDivider { .actionsheetDivider {
height: .07em; height: 0.07em;
margin: .25em 0; margin: 0.25em 0;
flex-shrink: 0; flex-shrink: 0;
} }
.actionSheetTitle { .actionSheetTitle {
margin: .6em 0 .7em !important; margin: 0.6em 0 0.7em !important;
padding: 0 .9em; padding: 0 0.9em;
flex-grow: 0; flex-grow: 0;
} }
@ -100,7 +100,7 @@
} }
.actionsheetMenuItemIcon { .actionsheetMenuItemIcon {
margin: 0 .85em 0 .45em !important; margin: 0 0.85em 0 0.45em !important;
padding: 0 !important; padding: 0 !important;
} }
@ -110,6 +110,6 @@
.btnCloseActionSheet { .btnCloseActionSheet {
position: fixed; position: fixed;
top: .75em; top: 0.75em;
left: .5em; left: 0.5em;
} }

View file

@ -136,7 +136,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu
// Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation // Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation
if (options.items.length > 20) { if (options.items.length > 20) {
var minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200; var minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200;
style += "min-width:" + minWidth + "px;"; style += 'min-width:' + minWidth + 'px;';
} }
var i; var i;
@ -158,7 +158,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu
} }
if (layoutManager.tv) { if (layoutManager.tv) {
html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><i class="md-icon">arrow_back</i></button>'; html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
} }
// If any items have an icon, give them all an icon just to make sure they're all lined up evenly // If any items have an icon, give them all an icon just to make sure they're all lined up evenly
@ -226,9 +226,9 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu
if (itemIcon) { if (itemIcon) {
html += '<i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon">' + itemIcon + '</i>'; html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ' + itemIcon + '"></span>';
} else if (renderIcon && !center) { } else if (renderIcon && !center) {
html += '<i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon" style="visibility:hidden;">check</i>'; html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
} }
html += '<div class="listItemBody actionsheetListItemBody">'; html += '<div class="listItemBody actionsheetListItemBody">';

View file

@ -1,60 +1,59 @@
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) {
var html = ""; var html = '';
html += '<div class="listItem listItem-border">'; html += '<div class="listItem listItem-border">';
var color = "#00a4dc"; var color = '#00a4dc';
var icon = "notifications"; var icon = 'notifications';
if ("Error" == entry.Severity || "Fatal" == entry.Severity || "Warn" == entry.Severity) { if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) {
color = "#cc0000"; color = '#cc0000';
icon = "notification_important"; icon = 'notification_important';
} }
if (entry.UserId && entry.UserPrimaryImageTag) { if (entry.UserId && entry.UserPrimaryImageTag) {
html += '<i class="listItemIcon md-icon" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, { html += '<span class="listItemIcon material-icons dvr" 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;\"></span>";
} else { } else {
html += '<i class="listItemIcon md-icon" style="background-color:' + color + '">' + icon + '</i>'; html += '<span class="listItemIcon material-icons ' + icon + '" style="background-color:' + color + '"></span>';
} }
html += '<div class="listItemBody three-line">'; html += '<div class="listItemBody three-line">';
html += '<div class="listItemBodyText">'; html += '<div class="listItemBodyText">';
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 || '';
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
if (entry.Overview) { if (entry.Overview) {
html += '<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="' + entry.Id + '" title="' + globalize.translate("Info") + '"><i class="md-icon">info</i></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="' + entry.Id + '" title="' + globalize.translate('Info') + '"><span class="material-icons info"></span></button>';
} }
return html += "</div>"; return html += '</div>';
} }
function renderList(elem, apiClient, result, startIndex, limit) { function renderList(elem, apiClient, result, startIndex, limit) {
elem.innerHTML = result.Items.map(function (i) { elem.innerHTML = result.Items.map(function (i) {
return getEntryHtml(i, apiClient); return getEntryHtml(i, apiClient);
}).join(""); }).join('');
} }
function reloadData(instance, elem, apiClient, startIndex, limit) { function reloadData(instance, elem, apiClient, startIndex, limit) {
if (null == startIndex) { if (null == startIndex) {
startIndex = parseInt(elem.getAttribute("data-activitystartindex") || "0"); startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0');
} }
limit = limit || parseInt(elem.getAttribute("data-activitylimit") || "7"); limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7');
var minDate = new Date(); var minDate = new Date();
var hasUserId = "false" !== elem.getAttribute("data-useractivity"); var hasUserId = 'false' !== elem.getAttribute('data-useractivity');
if (hasUserId) { if (hasUserId) {
minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
@ -62,22 +61,22 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back
} }
ApiClient.getJSON(ApiClient.getUrl("System/ActivityLog/Entries", { ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', {
startIndex: startIndex, startIndex: startIndex,
limit: limit, limit: limit,
minDate: minDate.toISOString(), minDate: minDate.toISOString(),
hasUserId: hasUserId hasUserId: hasUserId
})).then(function (result) { })).then(function (result) {
elem.setAttribute("data-activitystartindex", startIndex); elem.setAttribute('data-activitystartindex', startIndex);
elem.setAttribute("data-activitylimit", limit); elem.setAttribute('data-activitylimit', limit);
if (!startIndex) { if (!startIndex) {
var activityContainer = dom.parentWithClass(elem, "activityContainer"); var activityContainer = dom.parentWithClass(elem, 'activityContainer');
if (activityContainer) { if (activityContainer) {
if (result.Items.length) { if (result.Items.length) {
activityContainer.classList.remove("hide"); activityContainer.classList.remove('hide');
} else { } else {
activityContainer.classList.add("hide"); activityContainer.classList.add('hide');
} }
} }
} }
@ -96,10 +95,10 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
} }
function onListClick(e) { function onListClick(e) {
var btnEntryInfo = dom.parentWithClass(e.target, "btnEntryInfo"); var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
if (btnEntryInfo) { if (btnEntryInfo) {
var id = btnEntryInfo.getAttribute("data-id"); var id = btnEntryInfo.getAttribute('data-id');
var items = this.items; var items = this.items;
if (items) { if (items) {
@ -115,7 +114,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
} }
function showItemOverview(item) { function showItemOverview(item) {
require(["alert"], function (alert) { require(['alert'], function (alert) {
alert({ alert({
text: item.Overview text: item.Overview
}); });
@ -125,28 +124,28 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
function ActivityLog(options) { function ActivityLog(options) {
this.options = options; this.options = options;
var element = options.element; var element = options.element;
element.classList.add("activityLogListWidget"); element.classList.add('activityLogListWidget');
element.addEventListener("click", onListClick.bind(this)); element.addEventListener('click', onListClick.bind(this));
var apiClient = connectionManager.getApiClient(options.serverId); var apiClient = connectionManager.getApiClient(options.serverId);
reloadData(this, element, apiClient); reloadData(this, element, apiClient);
var onUpdate = onActivityLogUpdate.bind(this); var onUpdate = onActivityLogUpdate.bind(this);
this.updateFn = onUpdate; this.updateFn = onUpdate;
events.on(serverNotifications, "ActivityLogEntry", onUpdate); events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
apiClient.sendMessage("ActivityLogEntryStart", "0,1500"); apiClient.sendMessage('ActivityLogEntryStart', '0,1500');
} }
ActivityLog.prototype.destroy = function () { ActivityLog.prototype.destroy = function () {
var options = this.options; var options = this.options;
if (options) { if (options) {
options.element.classList.remove("activityLogListWidget"); options.element.classList.remove('activityLogListWidget');
connectionManager.getApiClient(options.serverId).sendMessage("ActivityLogEntryStop", "0,1500"); connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
} }
var onUpdate = this.updateFn; var onUpdate = this.updateFn;
if (onUpdate) { if (onUpdate) {
events.off(serverNotifications, "ActivityLogEntry", onUpdate); events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
} }
this.items = null; this.items = null;

View file

@ -67,7 +67,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
html += '<div class="' + rowClassName + '">'; html += '<div class="' + rowClassName + '">';
if (options.mode === 'keyboard') { if (options.mode === 'keyboard') {
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><i class="md-icon alphaPickerButtonIcon">space_bar</i></button>'; html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>';
} else { } else {
letters = ['#']; letters = ['#'];
html += mapLetters(letters, vertical).join(''); html += mapLetters(letters, vertical).join('');
@ -77,7 +77,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
html += mapLetters(letters, vertical).join(''); html += mapLetters(letters, vertical).join('');
if (options.mode === 'keyboard') { if (options.mode === 'keyboard') {
html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><i class="md-icon alphaPickerButtonIcon">backspace</i></button>'; html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>';
html += '</div>'; html += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
@ -132,7 +132,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
if (alphaPickerButton) { if (alphaPickerButton) {
var value = alphaPickerButton.getAttribute('data-value'); var value = alphaPickerButton.getAttribute('data-value');
element.dispatchEvent(new CustomEvent("alphavalueclicked", { element.dispatchEvent(new CustomEvent('alphavalueclicked', {
cancelable: false, cancelable: false,
detail: { detail: {
value: value value: value
@ -241,7 +241,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
try { try {
btn = element.querySelector('.alphaPickerButton[data-value=\'' + value + '\']'); btn = element.querySelector('.alphaPickerButton[data-value=\'' + value + '\']');
} catch (err) { } catch (err) {
console.log('Error in querySelector: ' + err); console.error('error in querySelector: ' + err);
} }
if (btn && btn !== selected) { if (btn && btn !== selected) {
@ -262,7 +262,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
} }
if (applyValue) { if (applyValue) {
element.dispatchEvent(new CustomEvent("alphavaluechanged", { element.dispatchEvent(new CustomEvent('alphavaluechanged', {
cancelable: false, cancelable: false,
detail: { detail: {
value: value value: value

View file

@ -35,16 +35,15 @@
font-size: inherit; font-size: inherit;
min-width: initial; min-width: initial;
margin: 0; margin: 0;
padding: .1em .4em; padding: 0.1em 0.4em;
width: auto; width: auto;
border-radius: .1em; border-radius: 0.1em;
font-weight: normal; font-weight: normal;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 1; flex-grow: 1;
} }
@media all and (max-height: 50em) { @media all and (max-height: 50em) {
.alphaPicker-fixed { .alphaPicker-fixed {
bottom: 5em; bottom: 5em;
} }
@ -56,14 +55,12 @@
} }
@media all and (max-height: 49em) { @media all and (max-height: 49em) {
.alphaPicker-vertical { .alphaPicker-vertical {
font-size: 94%; font-size: 94%;
} }
} }
@media all and (max-height: 44em) { @media all and (max-height: 44em) {
.alphaPicker-vertical { .alphaPicker-vertical {
font-size: 90%; font-size: 90%;
} }
@ -75,14 +72,12 @@
} }
@media all and (max-height: 37em) { @media all and (max-height: 37em) {
.alphaPicker-vertical { .alphaPicker-vertical {
font-size: 82%; font-size: 82%;
} }
} }
@media all and (max-height: 32em) { @media all and (max-height: 32em) {
.alphaPicker-vertical { .alphaPicker-vertical {
font-size: 74%; font-size: 74%;
} }
@ -112,27 +107,17 @@
bottom: 1%; bottom: 1%;
} }
.alphaPicker-fixed-left {
left: .4em;
}
.alphaPicker-fixed-right { .alphaPicker-fixed-right {
right: .4em; right: 0.4em;
} }
@media all and (min-width: 62.5em) { @media all and (min-width: 62.5em) {
.alphaPicker-fixed-left {
left: 1em;
}
.alphaPicker-fixed-right { .alphaPicker-fixed-right {
right: 1em; right: 1em;
} }
} }
@media all and (max-height: 31.25em) { @media all and (max-height: 31.25em) {
.alphaPicker-fixed { .alphaPicker-fixed {
display: none !important; display: none !important;
} }

View file

@ -14,6 +14,9 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
}, },
showSettings: function () { showSettings: function () {
show('/settings/settings.html'); show('/settings/settings.html');
},
showNowPlaying: function () {
show('/nowplaying.html');
} }
}; };
@ -197,8 +200,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
var apiClient = this; var apiClient = this;
if (data.status === 401) { if (data.status === 403) {
if (data.errorCode === "ParentalControl") { if (data.errorCode === 'ParentalControl') {
var isCurrentAllowed = currentRouteInfo ? (currentRouteInfo.route.anonymous || currentRouteInfo.route.startup) : true; var isCurrentAllowed = currentRouteInfo ? (currentRouteInfo.route.anonymous || currentRouteInfo.route.startup) : true;
@ -265,6 +268,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
} }
function getMaxBandwidth() { function getMaxBandwidth() {
/* eslint-disable compat/compat */
if (navigator.connection) { if (navigator.connection) {
var max = navigator.connection.downlinkMax; var max = navigator.connection.downlinkMax;
if (max && max > 0 && max < Number.POSITIVE_INFINITY) { if (max && max > 0 && max < Number.POSITIVE_INFINITY) {
@ -276,6 +280,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
return max; return max;
} }
} }
/* eslint-enable compat/compat */
return null; return null;
} }
@ -367,7 +372,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
} }
function enableNativeHistory() { function enableNativeHistory() {
return page.enableNativeHistory(); return false;
} }
function authenticate(ctx, route, callback) { function authenticate(ctx, route, callback) {
@ -387,13 +392,13 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
var apiClient = connectionManager.currentApiClient(); var apiClient = connectionManager.currentApiClient();
var pathname = ctx.pathname.toLowerCase(); var pathname = ctx.pathname.toLowerCase();
console.log('appRouter - processing path request ' + pathname); console.debug('appRouter - processing path request ' + pathname);
var isCurrentRouteStartup = currentRouteInfo ? currentRouteInfo.route.startup : true; var isCurrentRouteStartup = currentRouteInfo ? currentRouteInfo.route.startup : true;
var shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup; var shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup;
if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) {
console.log('appRouter - route does not allow anonymous access, redirecting to login'); console.debug('appRouter - route does not allow anonymous access, redirecting to login');
beginConnectionWizard(); beginConnectionWizard();
return; return;
} }
@ -408,10 +413,10 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
if (apiClient && apiClient.isLoggedIn()) { if (apiClient && apiClient.isLoggedIn()) {
console.log('appRouter - user is authenticated'); console.debug('appRouter - user is authenticated');
if (route.isDefaultRoute) { if (route.isDefaultRoute) {
console.log('appRouter - loading skin home page'); console.debug('appRouter - loading skin home page');
loadUserSkinWithOptions(ctx); loadUserSkinWithOptions(ctx);
return; return;
} else if (route.roles) { } else if (route.roles) {
@ -425,7 +430,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
} }
} }
console.log('appRouter - proceeding to ' + pathname); console.debug('appRouter - proceeding to ' + pathname);
callback(); callback();
} }
@ -508,9 +513,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;
}; };
} }
@ -529,15 +541,15 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
} }
function param(name, url) { function param(name, url) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
var regexS = "[\\?&]" + name + "=([^&#]*)"; var regexS = '[\\?&]' + name + '=([^&#]*)';
var regex = new RegExp(regexS, "i"); var regex = new RegExp(regexS, 'i');
var results = regex.exec(url || getWindowLocationSearch()); var results = regex.exec(url || getWindowLocationSearch());
if (results == null) { if (results == null) {
return ""; return '';
} else { } else {
return decodeURIComponent(results[1].replace(/\+/g, " ")); return decodeURIComponent(results[1].replace(/\+/g, ' '));
} }
} }
@ -545,22 +557,31 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
page.back(); page.back();
} }
/**
* Pages of "no return" (when "Go back" should behave differently, probably quitting the application).
*/
var startPages = ['home', 'login', 'selectserver'];
function canGoBack() { function canGoBack() {
var curr = current(); var curr = current();
if (!curr) { if (!curr) {
return false; return false;
} }
if (curr.type === 'home') { if (!document.querySelector('.dialogContainer') && startPages.indexOf(curr.type) !== -1) {
return false; return false;
} }
return page.canGoBack(); if (enableHistory()) {
return history.length > 1;
}
return (page.len || 0) > 0;
} }
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) {
@ -658,7 +679,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
function pushState(state, title, url) { function pushState(state, title, url) {
state.navigate = false; state.navigate = false;
page.pushState(state, title, url); history.pushState(state, title, url);
} }
function setBaseRoute() { function setBaseRoute() {
@ -667,7 +689,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
baseRoute = baseRoute.substring(0, baseRoute.length - 1); baseRoute = baseRoute.substring(0, baseRoute.length - 1);
} }
console.log('Setting page base to ' + baseRoute); console.debug('setting page base to ' + baseRoute);
page.base(baseRoute); page.base(baseRoute);
} }
@ -708,7 +730,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
appRouter.getRoutes = getRoutes; appRouter.getRoutes = getRoutes;
appRouter.pushState = pushState; appRouter.pushState = pushState;
appRouter.enableNativeHistory = enableNativeHistory; appRouter.enableNativeHistory = enableNativeHistory;
appRouter.handleAnchorClick = page.handleAnchorClick; appRouter.handleAnchorClick = page.clickHandler;
appRouter.TransparencyLevel = { appRouter.TransparencyLevel = {
None: 0, None: 0,
Backdrop: 1, Backdrop: 1,

View file

@ -2,12 +2,12 @@
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
z-index: 1; z-index: 10;
bottom: 0; bottom: 0;
transition: transform 180ms linear; transition: transform 180ms linear;
contain: layout style; contain: layout style;
} }
.appfooter.headroom--unpinned { .appfooter.headroom--unpinned {
transform: translateY(100%)!important; transform: translateY(100%) !important;
} }

View file

@ -2,24 +2,18 @@ define(['browser', 'css!./appfooter'], function (browser) {
'use strict'; 'use strict';
function render(options) { function render(options) {
var elem = document.createElement('div'); var elem = document.createElement('div');
elem.classList.add('appfooter'); elem.classList.add('appfooter');
elem.classList.add('appfooter-blurred');
document.body.appendChild(elem); document.body.appendChild(elem);
return elem; return elem;
} }
function appFooter(options) { function appFooter(options) {
var self = this; var self = this;
self.element = render(options); self.element = render(options);
self.add = function (elem) { self.add = function (elem) {
self.element.appendChild(elem); self.element.appendChild(elem);
}; };

View file

@ -1,17 +1,17 @@
define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSettings, browser, events, htmlMediaHelper) { define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
"use strict"; 'use strict';
function getBaseProfileOptions(item) { function getBaseProfileOptions(item) {
var disableHlsVideoAudioCodecs = []; var disableHlsVideoAudioCodecs = [];
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge || browser.msie) { if (browser.edge || browser.msie) {
disableHlsVideoAudioCodecs.push("mp3"); disableHlsVideoAudioCodecs.push('mp3');
} }
disableHlsVideoAudioCodecs.push("ac3"); disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push("eac3"); disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push("opus"); disableHlsVideoAudioCodecs.push('opus');
} }
return { return {
@ -22,7 +22,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
function getDeviceProfileForWindowsUwp(item) { function getDeviceProfileForWindowsUwp(item) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
require(["browserdeviceprofile", "environments/windows-uwp/mediacaps"], function (profileBuilder, uwpMediaCaps) { require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) {
var profileOptions = getBaseProfileOptions(item); var profileOptions = getBaseProfileOptions(item);
profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); profileOptions.supportsDts = uwpMediaCaps.supportsDTS();
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(); profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
@ -40,26 +40,15 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
} }
return new Promise(function (resolve) { return new Promise(function (resolve) {
require(["browserdeviceprofile"], function (profileBuilder) { require(['browserdeviceprofile'], function (profileBuilder) {
var profile; var profile;
if (window.NativeShell) { if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else { } else {
profile = profileBuilder(getBaseProfileOptions(item)); var builderOpts = getBaseProfileOptions(item);
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
if (item && !options.isRetry && "allcomplexformats" !== appSettings.get("subtitleburnin")) { profile = profileBuilder(builderOpts);
if (!browser.orsay && !browser.tizen) {
profile.SubtitleProfiles.push({
Format: "ass",
Method: "External"
});
profile.SubtitleProfiles.push({
Format: "ssa",
Method: "External"
});
}
}
} }
resolve(profile); resolve(profile);
@ -68,12 +57,12 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
} }
function escapeRegExp(str) { function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
} }
function replaceAll(originalString, strReplace, strWith) { function replaceAll(originalString, strReplace, strWith) {
var strReplace2 = escapeRegExp(strReplace); var strReplace2 = escapeRegExp(strReplace);
var reg = new RegExp(strReplace2, "ig"); var reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith); return originalString.replace(reg, strWith);
} }
@ -81,7 +70,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
var keys = []; var keys = [];
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) {
var result = replaceAll(btoa(keys.join("|")), "=", "1"); var result = replaceAll(btoa(keys.join('|')), '=', '1');
return Promise.resolve(result); return Promise.resolve(result);
} }
@ -89,7 +78,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
} }
function getDeviceId() { function getDeviceId() {
var key = "_deviceId2"; var key = '_deviceId2';
var deviceId = appSettings.get(key); var deviceId = appSettings.get(key);
if (deviceId) { if (deviceId) {
@ -104,16 +93,16 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
function getDeviceName() { function getDeviceName() {
var deviceName; var deviceName;
deviceName = browser.tizen ? "Samsung Smart TV" : browser.web0s ? "LG Smart TV" : browser.operaTv ? "Opera TV" : browser.xboxOne ? "Xbox One" : browser.ps4 ? "Sony PS4" : browser.chrome ? "Chrome" : browser.edge ? "Edge" : browser.firefox ? "Firefox" : browser.msie ? "Internet Explorer" : browser.opera ? "Opera" : "Web Browser"; deviceName = browser.tizen ? 'Samsung Smart TV' : browser.web0s ? 'LG Smart TV' : browser.operaTv ? 'Opera TV' : browser.xboxOne ? 'Xbox One' : browser.ps4 ? 'Sony PS4' : browser.chrome ? 'Chrome' : browser.edge ? 'Edge' : browser.firefox ? 'Firefox' : browser.msie ? 'Internet Explorer' : browser.opera ? 'Opera' : browser.safari ? 'Safari' : 'Web Browser';
if (browser.ipad) { if (browser.ipad) {
deviceName += " iPad"; deviceName += ' iPad';
} else { } else {
if (browser.iphone) { if (browser.iphone) {
deviceName += " iPhone"; deviceName += ' iPhone';
} else { } else {
if (browser.android) { if (browser.android) {
deviceName += " Android"; deviceName += ' Android';
} }
} }
} }
@ -135,12 +124,12 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
} }
var element = document.documentElement; var element = document.documentElement;
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement("video").webkitEnterFullscreen; return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
} }
function getSyncProfile() { function getSyncProfile() {
return new Promise(function (resolve) { return new Promise(function (resolve) {
require(["browserdeviceprofile", "appSettings"], function (profileBuilder, appSettings) { require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) {
var profile; var profile;
if (window.NativeShell) { if (window.NativeShell) {
@ -156,7 +145,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
} }
function getDefaultLayout() { function getDefaultLayout() {
return "desktop"; return 'desktop';
} }
function supportsHtmlMediaAutoplay() { function supportsHtmlMediaAutoplay() {
@ -168,23 +157,25 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
return false; return false;
} }
var savedResult = appSettings.get(htmlMediaAutoplayAppStorageKey); return true;
return "true" === savedResult || "false" !== savedResult && null;
} }
function cueSupported() { function supportsCue() {
try { try {
var video = document.createElement("video"); var video = document.createElement('video');
var style = document.createElement("style"); var style = document.createElement('style');
style.textContent = "video::cue {background: inherit}";
style.textContent = 'video::cue {background: inherit}';
document.body.appendChild(style); document.body.appendChild(style);
document.body.appendChild(video); document.body.appendChild(video);
var cue = window.getComputedStyle(video, "::cue").background;
var cue = window.getComputedStyle(video, '::cue').background;
document.body.removeChild(style); document.body.removeChild(style);
document.body.removeChild(video); document.body.removeChild(video);
return !!cue.length; return !!cue.length;
} catch (err) { } catch (err) {
console.log("Error detecting cue support:" + err); console.error('error detecting cue support: ' + err);
return false; return false;
} }
} }
@ -192,150 +183,172 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
function onAppVisible() { function onAppVisible() {
if (isHidden) { if (isHidden) {
isHidden = false; isHidden = false;
console.log("triggering app resume event"); console.debug('triggering app resume event');
events.trigger(appHost, "resume"); events.trigger(appHost, 'resume');
} }
} }
function onAppHidden() { function onAppHidden() {
if (!isHidden) { if (!isHidden) {
isHidden = true; isHidden = true;
console.log("app is hidden"); console.debug('app is hidden');
} }
} }
var htmlMediaAutoplayAppStorageKey = "supportshtmlmediaautoplay0";
var supportedFeatures = function () { var supportedFeatures = function () {
var features = []; var features = [];
if (navigator.share) { if (navigator.share) {
features.push("sharing"); features.push('sharing');
} }
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
features.push("filedownload"); features.push('filedownload');
} }
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push("exit"); features.push('exit');
} else { } else {
features.push("exitmenu"); features.push('exitmenu');
features.push("plugins"); features.push('plugins');
} }
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push("externallinks"); features.push('externallinks');
features.push("externalpremium"); features.push('externalpremium');
} }
if (!browser.operaTv) { if (!browser.operaTv) {
features.push("externallinkdisplay"); features.push('externallinkdisplay');
} }
if (supportsVoiceInput()) { if (supportsVoiceInput()) {
features.push("voiceinput"); features.push('voiceinput');
}
if (!browser.tv && !browser.xboxOne) {
browser.ps4;
} }
if (supportsHtmlMediaAutoplay()) { if (supportsHtmlMediaAutoplay()) {
features.push("htmlaudioautoplay"); features.push('htmlaudioautoplay');
features.push("htmlvideoautoplay"); features.push('htmlvideoautoplay');
} }
if (browser.edgeUwp) { if (browser.edgeUwp) {
features.push("sync"); features.push('sync');
} }
if (supportsFullscreen()) { if (supportsFullscreen()) {
features.push("fullscreenchange"); features.push('fullscreenchange');
} }
if (browser.chrome || browser.edge && !browser.slow) { if (browser.chrome || browser.edge && !browser.slow) {
if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) { if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) {
features.push("imageanalysis"); features.push('imageanalysis');
} }
} }
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
features.push("physicalvolumecontrol"); features.push('physicalvolumecontrol');
} }
if (!browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.tv && !browser.xboxOne && !browser.ps4) {
features.push("remotecontrol"); features.push('remotecontrol');
} }
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
features.push("remotevideo"); features.push('remotevideo');
} }
features.push("displaylanguage"); features.push('displaylanguage');
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('screensaver');
//features.push("multiserver");
features.push("screensaver");
if (!browser.orsay && !browser.tizen && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || cueSupported())) { webSettings.enableMultiServer().then(enabled => {
features.push("subtitleappearancesettings"); if (enabled) features.push('multiserver');
});
if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
features.push('subtitleappearancesettings');
} }
if (!browser.orsay && !browser.tizen) { if (!browser.orsay) {
features.push("subtitleburnsettings"); features.push('subtitleburnsettings');
} }
if (!browser.tv && !browser.ps4 && !browser.xboxOne) { if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
features.push("fileinput"); features.push('fileinput');
} }
if (browser.chrome) { if (browser.chrome) {
features.push("chromecast"); features.push('chromecast');
} }
return features; return features;
}(); }();
if (supportedFeatures.indexOf("htmlvideoautoplay") === -1 && supportsHtmlMediaAutoplay() !== false) { /**
require(["autoPlayDetect"], function (autoPlayDetect) { * Do exit according to platform
autoPlayDetect.supportsHtmlMediaAutoplay().then(function () { */
appSettings.set(htmlMediaAutoplayAppStorageKey, "true"); function doExit() {
supportedFeatures.push("htmlvideoautoplay"); try {
supportedFeatures.push("htmlaudioautoplay"); if (window.NativeShell) {
}, function () { window.NativeShell.AppHost.exit();
appSettings.set(htmlMediaAutoplayAppStorageKey, "false"); } else if (browser.tizen) {
tizen.application.getCurrentApplication().exit();
} else if (browser.web0s) {
webOS.platformBack();
} else {
window.close();
}
} catch (err) {
console.error('error closing application: ' + err);
}
}
var exitPromise;
/**
* Ask user for exit
*/
function askForExit() {
if (exitPromise) {
return;
}
require(['actionsheet'], function (actionsheet) {
exitPromise = actionsheet.show({
title: globalize.translate('MessageConfirmAppExit'),
items: [
{id: 'yes', name: globalize.translate('Yes')},
{id: 'no', name: globalize.translate('No')}
]
}).then(function (value) {
if (value === 'yes') {
doExit();
}
}).finally(function () {
exitPromise = null;
}); });
}); });
} }
var deviceId; var deviceId;
var deviceName; var deviceName;
var appName = "Jellyfin Web"; var appName = 'Jellyfin Web';
var appVersion = "10.5.0"; var appVersion = '10.6.0';
var visibilityChange;
var visibilityState;
var appHost = { var appHost = {
getWindowState: function () { getWindowState: function () {
return document.windowState || "Normal"; return document.windowState || 'Normal';
}, },
setWindowState: function (state) { setWindowState: function (state) {
alert("setWindowState is not supported and should not be called"); alert('setWindowState is not supported and should not be called');
}, },
exit: function () { exit: function () {
if (window.NativeShell) { if (!!window.appMode && browser.tizen) {
window.NativeShell.AppHost.exit(); askForExit();
} else if (browser.tizen) {
try {
tizen.application.getCurrentApplication().exit();
} catch (err) {
console.log("error closing application: " + err);
}
} else { } else {
window.close(); doExit();
} }
}, },
supports: function (command) { supports: function (command) {
@ -346,14 +359,14 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
return -1 !== supportedFeatures.indexOf(command.toLowerCase()); return -1 !== supportedFeatures.indexOf(command.toLowerCase());
}, },
preferVisualCards: browser.android || browser.chrome, preferVisualCards: browser.android || browser.chrome,
moreIcon: browser.android ? "dots-vert" : "dots-horiz", moreIcon: browser.android ? 'more_vert' : 'more_horiz',
getSyncProfile: getSyncProfile, getSyncProfile: getSyncProfile,
getDefaultLayout: function () { getDefaultLayout: function () {
if (window.NativeShell) { if (window.NativeShell) {
return window.NativeShell.AppHost.getDefaultLayout(); return window.NativeShell.AppHost.getDefaultLayout();
} }
return getDefaultLayout() return getDefaultLayout();
}, },
getDeviceProfile: getDeviceProfile, getDeviceProfile: getDeviceProfile,
init: function () { init: function () {
@ -382,58 +395,44 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
return {}; return {};
}, },
setThemeColor: function (color) { setThemeColor: function (color) {
var metaThemeColor = document.querySelector("meta[name=theme-color]"); var metaThemeColor = document.querySelector('meta[name=theme-color]');
if (metaThemeColor) { if (metaThemeColor) {
metaThemeColor.setAttribute("content", color); metaThemeColor.setAttribute('content', color);
} }
}, },
setUserScalable: function (scalable) { setUserScalable: function (scalable) {
if (!browser.tv) { if (!browser.tv) {
var att = scalable ? "width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes" : "width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"; var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
document.querySelector("meta[name=viewport]").setAttribute("content", att); document.querySelector('meta[name=viewport]').setAttribute('content', att);
} }
} }
}; };
var doc = self.document;
if (doc) {
if (void 0 !== doc.visibilityState) {
visibilityChange = "visibilitychange";
visibilityState = "hidden";
} else {
if (void 0 !== doc.mozHidden) {
visibilityChange = "mozvisibilitychange";
visibilityState = "mozVisibilityState";
} else {
if (void 0 !== doc.msHidden) {
visibilityChange = "msvisibilitychange";
visibilityState = "msVisibilityState";
} else {
if (void 0 !== doc.webkitHidden) {
visibilityChange = "webkitvisibilitychange";
visibilityState = "webkitVisibilityState";
}
}
}
}
}
var isHidden = false; var isHidden = false;
var hidden;
var visibilityChange;
if (doc) { if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
doc.addEventListener(visibilityChange, function () { hidden = 'hidden';
if (document[visibilityState]) { visibilityChange = 'visibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
document.addEventListener(visibilityChange, function () {
/* eslint-disable-next-line compat/compat */
if (document[hidden]) {
onAppHidden(); onAppHidden();
} else { } else {
onAppVisible(); onAppVisible();
} }
}); }, false);
}
if (self.addEventListener) { if (self.addEventListener) {
self.addEventListener("focus", onAppVisible); self.addEventListener('focus', onAppVisible);
self.addEventListener("blur", onAppHidden); self.addEventListener('blur', onAppHidden);
} }
return appHost; return appHost;

View file

@ -1,71 +1,73 @@
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;
} }
window.addEventListener("focusin", function (e) { window.addEventListener('focusin', function (e) {
activeElement = e.target; activeElement = e.target;
}); });
console.log("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
if (activeElement.classList.contains("btnPreviousPage")) { if (activeElement.classList.contains('btnPreviousPage')) {
candidates.push(container.querySelector(".btnPreviousPage")); candidates.push(container.querySelector('.btnPreviousPage'));
candidates.push(container.querySelector(".btnNextPage")); candidates.push(container.querySelector('.btnNextPage'));
} else if (activeElement.classList.contains("btnNextPage")) { } else if (activeElement.classList.contains('btnNextPage')) {
candidates.push(container.querySelector(".btnNextPage")); candidates.push(container.querySelector('.btnNextPage'));
candidates.push(container.querySelector(".btnPreviousPage")); candidates.push(container.querySelector('.btnPreviousPage'));
} else if (activeElement.classList.contains("btnSelectView")) { } else if (activeElement.classList.contains('btnSelectView')) {
candidates.push(container.querySelector(".btnSelectView")); candidates.push(container.querySelector('.btnSelectView'));
} }
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

@ -1,4 +1,4 @@
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom) { define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) {
'use strict'; 'use strict';
function enableAnimation(elem) { function enableAnimation(elem) {
@ -180,8 +180,9 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./backdro
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
return item.BackdropImageTags.map(function (imgTag, index) { return item.BackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: "Backdrop", type: 'Backdrop',
tag: imgTag, tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index index: index
})); }));
}); });
@ -190,8 +191,9 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./backdro
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
return item.ParentBackdropImageTags.map(function (imgTag, index) { return item.ParentBackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: "Backdrop", type: 'Backdrop',
tag: imgTag, tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index index: index
})); }));
}); });
@ -236,10 +238,15 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./backdro
return true; return true;
} }
function enabled() {
return userSettings.enableBackdrops();
}
var rotationInterval; var rotationInterval;
var currentRotatingImages = []; var currentRotatingImages = [];
var currentRotationIndex = -1; var currentRotationIndex = -1;
function setBackdrops(items, imageOptions, enableImageRotation) { function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) {
var images = getImageUrls(items, imageOptions); var images = getImageUrls(items, imageOptions);
if (images.length) { if (images.length) {
@ -248,6 +255,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./backdro
clearBackdrop(); clearBackdrop();
} }
} }
}
function startRotation(images, enableImageRotation) { function startRotation(images, enableImageRotation) {
if (arraysEqual(images, currentRotatingImages)) { if (arraysEqual(images, currentRotatingImages)) {

View file

@ -1,12 +1,12 @@
define(["connectionManager"], function (connectionManager) { define(['connectionManager'], function (connectionManager) {
return function () { return function () {
var self = this; var self = this;
self.name = "Backdrop ScreenSaver"; self.name = 'Backdrop ScreenSaver';
self.type = "screensaver"; self.type = 'screensaver';
self.id = "backdropscreensaver"; self.id = 'backdropscreensaver';
self.supportsAnonymous = false; self.supportsAnonymous = false;
var currentSlideshow; var currentSlideshow;
@ -14,12 +14,12 @@ define(["connectionManager"], function (connectionManager) {
self.show = function () { self.show = function () {
var query = { var query = {
ImageTypes: "Backdrop", ImageTypes: 'Backdrop',
EnableImageTypes: "Backdrop", EnableImageTypes: 'Backdrop',
IncludeItemTypes: "Movie,Series,MusicArtist", IncludeItemTypes: 'Movie,Series,MusicArtist',
SortBy: "Random", SortBy: 'Random',
Recursive: true, Recursive: true,
Fields: "Taglines", Fields: 'Taglines',
ImageTypeLimit: 1, ImageTypeLimit: 1,
StartIndex: 0, StartIndex: 0,
Limit: 200 Limit: 200
@ -30,7 +30,7 @@ define(["connectionManager"], function (connectionManager) {
if (result.Items.length) { if (result.Items.length) {
require(["slideshow"], function (slideshow) { require(['slideshow'], function (slideshow) {
var newSlideShow = new slideshow({ var newSlideShow = new slideshow({
showTitle: true, showTitle: true,
@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) {
currentSlideshow = null; currentSlideshow = null;
} }
}; };
} };
}); });

View file

@ -1,23 +1,24 @@
button {
-webkit-border-fit: border !important;
}
button::-moz-focus-inner { button::-moz-focus-inner {
padding: 0; padding: 0;
border: 0; border: 0;
} }
button {
-webkit-border-fit: border !important;
}
.card { .card {
border: 0; border: 0;
font-size: inherit !important; font-size: inherit !important;
font-family: inherit !important; font-family: inherit !important;
text-transform: none; text-transform: none;
background-color: transparent !important;
background: none !important; background: none !important;
background-color: transparent !important;
margin: 0; margin: 0;
padding: 0; padding: 0;
display: block; display: block;
color: inherit !important; color: inherit !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none !important; outline: none !important;
cursor: pointer; cursor: pointer;
contain: layout style; contain: layout style;
@ -54,17 +55,27 @@ button {
contain: layout style; contain: layout style;
} }
.cardPadder-backdrop, .cardPadder-mixedBackdrop, .cardPadder-smallBackdrop, .cardPadder-overflowBackdrop, .cardPadder-overflowSmallBackdrop { .cardPadder-backdrop,
.cardPadder-mixedBackdrop,
.cardPadder-smallBackdrop,
.cardPadder-overflowBackdrop,
.cardPadder-overflowSmallBackdrop {
padding-bottom: 56.25%; padding-bottom: 56.25%;
contain: strict; contain: strict;
} }
.cardPadder-square, .cardPadder-mixedSquare, .cardPadder-overflowSquare, .overflowSquareCard-textCardPadder { .cardPadder-square,
.cardPadder-mixedSquare,
.cardPadder-overflowSquare,
.overflowSquareCard-textCardPadder {
padding-bottom: 100%; padding-bottom: 100%;
contain: strict; contain: strict;
} }
.cardPadder-portrait, .cardPadder-mixedPortrait, .cardPadder-overflowPortrait, .overflowPortraitCard-textCardPadder { .cardPadder-portrait,
.cardPadder-mixedPortrait,
.cardPadder-overflowPortrait,
.overflowPortraitCard-textCardPadder {
padding-bottom: 150%; padding-bottom: 150%;
contain: strict; contain: strict;
} }
@ -79,23 +90,26 @@ button {
margin: 0.6em; margin: 0.6em;
transition: none; transition: none;
border: 0 solid transparent; border: 0 solid transparent;
/* These both are needed in case cardBox is a button */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none !important; outline: none !important;
contain: layout; contain: layout;
contain: style; contain: style;
} }
.card.show-animation .cardBox {
will-change: transform;
transition: transform 200ms ease-out;
}
.card.show-focus:not(.show-animation) .cardBox { .card.show-focus:not(.show-animation) .cardBox {
margin: .4em; margin: 0.4em;
} }
.card.show-focus:not(.show-animation) .cardBox.visualCardBox, .card.show-focus:not(.show-animation) .cardBox.visualCardBox,
.card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable { .card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable {
border: .5em solid transparent; border: 0.5em solid transparent;
}
.card.show-animation .cardBox {
will-change: transform;
transition: transform 200ms ease-out;
} }
.card.show-animation:focus > .cardBox { .card.show-animation:focus > .cardBox {
@ -120,7 +134,7 @@ button {
.btnCardOptions { .btnCardOptions {
position: absolute; position: absolute;
bottom: .25em; bottom: 0.25em;
right: 0; right: 0;
margin: 0 !important; margin: 0 !important;
z-index: 1; z-index: 1;
@ -131,8 +145,8 @@ button {
position: absolute; position: absolute;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
top: .3em; top: 0.3em;
left: .3em; left: 0.3em;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
width: 1.6em; width: 1.6em;
@ -146,12 +160,14 @@ button {
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center center;
display: -webkit-flex;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative; position: relative;
background-clip: content-box !important; background-clip: content-box !important;
color: inherit; color: inherit;
/* This is only needed for scalable cards */ /* This is only needed for scalable cards */
height: 100%; height: 100%;
contain: strict; contain: strict;
@ -173,12 +189,16 @@ button {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
/* Needed in case this is a button */ /* Needed in case this is a button */
display: block; display: block;
/* Needed in case this is a button */ /* Needed in case this is a button */
margin: 0 !important; margin: 0 !important;
/* Needed in safari */ /* Needed in safari */
height: 100%; height: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none !important; outline: none !important;
contain: strict; contain: strict;
} }
@ -207,10 +227,6 @@ button {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
} }
.cardImageContainer {
display: flex;
}
.cardImage { .cardImage {
position: absolute; position: absolute;
top: 0; top: 0;
@ -226,6 +242,7 @@ button {
.cardImage-img { .cardImage-img {
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
/* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */ /* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */
min-height: 70%; min-height: 70%;
min-width: 70%; min-width: 70%;
@ -252,17 +269,17 @@ button {
} }
.cardFooter { .cardFooter {
padding: .3em .3em .5em .3em; padding: 0.3em 0.3em 0.5em 0.3em;
position: relative; position: relative;
} }
.visualCardBox { .visualCardBox {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
border-radius: .145em; border-radius: 0.145em;
} }
.innerCardFooter { .innerCardFooter {
background: rgba(0,0,0,.7); background: rgba(0, 0, 0, 0.7);
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -282,7 +299,7 @@ button {
} }
.cardText { .cardText {
padding: .06em .5em; padding: 0.06em 0.5em;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -294,7 +311,24 @@ button {
} }
.cardText-first { .cardText-first {
padding-top: .24em; padding-top: 0.24em;
}
.textActionButton {
border: 0 !important;
background: transparent;
padding: 0 !important;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none !important;
color: inherit;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
}
.textActionButton:hover {
text-decoration: underline;
} }
.cardText > .textActionButton { .cardText > .textActionButton {
@ -304,7 +338,7 @@ button {
} }
.innerCardFooter > .cardText { .innerCardFooter > .cardText {
padding: .3em .5em; padding: 0.3em 0.5em;
} }
.cardFooter-withlogo { .cardFooter-withlogo {
@ -336,36 +370,19 @@ button {
text-align: center; text-align: center;
} }
.textActionButton { .cardImageContainer .cardImageIcon {
border: 0 !important;
background: transparent;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
outline: none !important;
color: inherit;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
}
.textActionButton:hover {
text-decoration: underline;
}
.cardImageIcon {
font-size: 5em; font-size: 5em;
color: inherit; color: inherit;
} }
.cardImageIcon-small { .cardImageIcon-small {
font-size: 3em; font-size: 3em !important;
margin-bottom: .1em; margin-bottom: 0.1em;
} }
.cardIndicators { .cardIndicators {
right: .225em; right: 0.225em;
top: .225em; top: 0.225em;
position: absolute; position: absolute;
display: flex; display: flex;
align-items: center; align-items: center;
@ -382,16 +399,16 @@ button {
} }
.programAttributeIndicator { .programAttributeIndicator {
padding: .18em .5em; padding: 0.18em 0.5em;
color: #fff; color: #fff;
font-weight: 500; font-weight: 500;
} }
.cardOverlayButton { .cardOverlayButton {
color: rgba(255, 255, 255, .76); color: rgba(255, 255, 255, 0.76);
margin: 0; margin: 0;
z-index: 1; z-index: 1;
padding: .75em; padding: 0.75em;
font-size: 88%; font-size: 88%;
} }
@ -402,7 +419,7 @@ button {
} }
.cardOverlayButtonIcon { .cardOverlayButtonIcon {
background-color: rgba(0,0,0,.7) !important; background-color: rgba(0, 0, 0, 0.7) !important;
border-radius: 100em; border-radius: 100em;
width: 1.5em !important; width: 1.5em !important;
height: 1.5em !important; height: 1.5em !important;
@ -412,6 +429,12 @@ button {
font-size: 1.66956521739130434em !important; font-size: 1.66956521739130434em !important;
} }
.cardOverlayButtonIcon.material-icons {
/* material-icons override display, so we need to
make a better matching selector to set it to flex */
display: flex;
}
.cardOverlayButton-centered { .cardOverlayButton-centered {
bottom: initial; bottom: initial;
right: initial; right: initial;
@ -424,10 +447,10 @@ button {
height: 2.6em; height: 2.6em;
top: 50%; top: 50%;
left: 50%; left: 50%;
background-color: rgba(0,0,0,.5) !important; background-color: rgba(0, 0, 0, 0.5) !important;
border: .06em solid rgba(255,255,255,.6); border: 0.06em solid rgba(255, 255, 255, 0.6);
padding: .38em !important; padding: 0.38em !important;
color: rgba(255, 255, 255, .76); color: rgba(255, 255, 255, 0.76);
transition: transform 200ms ease-out; transition: transform 200ms ease-out;
} }
@ -474,13 +497,15 @@ button {
width: 33.333333333333333333333333333333%; width: 33.333333333333333333333333333333%;
} }
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 33.333333333333333333333333333333%; width: 33.333333333333333333333333333333%;
} }
} }
@media (min-width: 43.75em) { @media (min-width: 43.75em) {
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 25%; width: 25%;
} }
} }
@ -496,7 +521,8 @@ button {
width: 50%; width: 50%;
} }
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 20%; width: 20%;
} }
@ -516,7 +542,8 @@ button {
width: 25%; width: 25%;
} }
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 16.666666666666666666666666666667%; width: 16.666666666666666666666666666667%;
} }
@ -529,9 +556,9 @@ button {
} }
} }
@media (min-width: 87.5em) { @media (min-width: 87.5em) {
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 14.285714285714285714285714285714%; width: 14.285714285714285714285714285714%;
} }
@ -549,13 +576,15 @@ button {
width: 20%; width: 20%;
} }
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 12.5%; width: 12.5%;
} }
} }
@media (min-width: 120em) { @media (min-width: 120em) {
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 11.111111111111111111111111111111%; width: 11.111111111111111111111111111111%;
} }
} }
@ -565,7 +594,8 @@ button {
width: 25%; width: 25%;
} }
.squareCard, .portraitCard { .squareCard,
.portraitCard {
width: 10%; width: 10%;
} }
} }
@ -596,7 +626,8 @@ button {
width: 72vw; width: 72vw;
} }
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 40vw; width: 40vw;
} }
@ -621,29 +652,34 @@ button {
} }
@media (min-width: 43.75em) { @media (min-width: 43.75em) {
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 23.1vw; width: 23.1vw;
} }
} }
@media (min-width: 48.125em) { @media (min-width: 48.125em) {
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowBackdropCard,
.overflowSmallBackdropCard {
width: 30vw; width: 30vw;
} }
} }
@media (orientation: landscape) { @media (orientation: landscape) {
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowBackdropCard,
.overflowSmallBackdropCard {
width: 30vw; width: 30vw;
} }
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 23.1vw; width: 23.1vw;
} }
} }
@media (orientation: landscape) and (min-width: 48.125em) { @media (orientation: landscape) and (min-width: 48.125em) {
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowBackdropCard,
.overflowSmallBackdropCard {
width: 23.1vw; width: 23.1vw;
} }
} }
@ -655,55 +691,60 @@ button {
} }
@media (min-width: 50em) { @media (min-width: 50em) {
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 18.5vw; width: 18.5vw;
} }
} }
@media (min-width: 75em) { @media (min-width: 75em) {
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowBackdropCard,
.overflowSmallBackdropCard {
width: 23.1vw; width: 23.1vw;
} }
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 15.5vw; width: 15.5vw;
} }
} }
@media (min-width: 87.5em) { @media (min-width: 87.5em) {
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 13.3vw; width: 13.3vw;
} }
} }
@media (min-width: 100em) { @media (min-width: 100em) {
.overflowBackdropCard,
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowSmallBackdropCard {
width: 18.7vw; width: 18.7vw;
} }
.overflowSquareCard, .overflowPortraitCard { .overflowSquareCard,
.overflowPortraitCard {
width: 11.6vw; width: 11.6vw;
} }
} }
@media (min-width: 120em) { @media (min-width: 120em) {
.overflowSquareCard,
.overflowSquareCard, .overflowPortraitCard { .overflowPortraitCard {
width: 10.3vw; width: 10.41vw;
} }
} }
@media (min-width: 131.25em) { @media (min-width: 131.25em) {
.overflowSquareCard,
.overflowSquareCard, .overflowPortraitCard { .overflowPortraitCard {
width: 9.3vw; width: 9.3vw;
} }
} }
@media (min-width: 156.25em) { @media (min-width: 156.25em) {
.overflowBackdropCard,
.overflowBackdropCard, .overflowSmallBackdropCard { .overflowSmallBackdropCard {
width: 15.6vw; width: 15.6vw;
} }
} }
@ -720,7 +761,8 @@ button {
padding-bottom: 87.75%; padding-bottom: 87.75%;
} }
.itemsContainer-tv > .overflowSquareCard, .itemsContainer-tv > .overflowPortraitCard { .itemsContainer-tv > .overflowSquareCard,
.itemsContainer-tv > .overflowPortraitCard {
width: 15.6vw; width: 15.6vw;
} }
@ -729,9 +771,9 @@ button {
} }
.cardOverlayContainer { .cardOverlayContainer {
background: rgba(0,0,0,0.5); background: rgba(0, 0, 0, 0.5);
opacity: 0; opacity: 0;
transition: opacity .2s; transition: opacity 0.2s;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -748,7 +790,7 @@ button {
opacity: 0; opacity: 0;
transition: 0.2s; transition: 0.2s;
background: transparent; background: transparent;
padding: 0.5em; padding: 0.25em;
} }
.cardOverlayButtonIcon-hover { .cardOverlayButtonIcon-hover {
@ -760,7 +802,7 @@ button {
} }
.cardOverlayFab-primary { .cardOverlayFab-primary {
background-color: rgba(0,0,0,0.7); background-color: rgba(0, 0, 0, 0.7);
font-size: 130%; font-size: 130%;
padding: 0; padding: 0;
width: 3em; width: 3em;

File diff suppressed because it is too large Load diff

View file

@ -68,9 +68,9 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
return apiClient.getScaledImageUrl(item.Id, { return apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth, maxWidth: maxWidth * 2,
tag: chapter.ImageTag, tag: chapter.ImageTag,
type: "Chapter", type: 'Chapter',
index: index index: index
}); });
} }
@ -90,7 +90,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
var cardImageContainer = imgUrl ? ('<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + '">'); var cardImageContainer = imgUrl ? ('<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + '">');
if (!imgUrl) { if (!imgUrl) {
cardImageContainer += '<i class="md-icon cardImageIcon">local_movies</i>'; cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
} }
var nameHtml = ''; var nameHtml = '';

View file

@ -0,0 +1,34 @@
define([], function() {
'use strict';
if (window.appMode === 'cordova' || window.appMode === 'android') {
return {
load: function () {
window.chrome = window.chrome || {};
return Promise.resolve();
}
};
} else {
var ccLoaded = false;
return {
load: function () {
if (ccLoaded) {
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
var fileref = document.createElement('script');
fileref.setAttribute('type', 'text/javascript');
fileref.onload = function () {
ccLoaded = true;
resolve();
};
fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js');
document.querySelector('head').appendChild(fileref);
});
}
};
}
});

View file

@ -1,44 +1,33 @@
define(["dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function (dialogHelper, loading, connectionManager, globalize, actionsheet) { 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;
connectionManager.getApiClient(options.serverId).ajax({ connectionManager.getApiClient(options.serverId).ajax({
type: "POST", type: 'POST',
url: ApiClient.getUrl("LiveTv/ChannelMappings"), url: ApiClient.getUrl('LiveTv/ChannelMappings'),
data: { data: {
providerId: providerId, providerId: providerId,
tunerChannelId: channelId, tunerChannelId: channelId,
providerChannelId: providerChannelId providerChannelId: providerChannelId
}, },
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();
}); });
} }
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');
var providerChannelId = btnMap.getAttribute("data-providerid"); var providerChannelId = btnMap.getAttribute('data-providerid');
var menuItems = currentMappingOptions.ProviderChannels.map(function (m) { var menuItems = currentMappingOptions.ProviderChannels.map(function (m) {
return { return {
name: m.Name, name: m.Name,
@ -59,56 +48,56 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
function getChannelMappingOptions(serverId, providerId) { function getChannelMappingOptions(serverId, providerId) {
var apiClient = connectionManager.getApiClient(serverId); var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getJSON(apiClient.getUrl("LiveTv/ChannelMappingOptions", { return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', {
providerId: providerId providerId: providerId
})); }));
} }
function getMappingSecondaryName(mapping, providerName) { function getMappingSecondaryName(mapping, providerName) {
return (mapping.ProviderChannelName || "") + " - " + providerName; return (mapping.ProviderChannelName || '') + ' - ' + providerName;
} }
function getTunerChannelHtml(channel, providerName) { function getTunerChannelHtml(channel, providerName) {
var html = ""; var html = '';
html += '<div class="listItem">'; html += '<div class="listItem">';
html += '<i class="md-icon listItemIcon">dvr</i>'; html += '<span class="material-icons listItemIcon dvr"></span>';
html += '<div class="listItemBody two-line">'; html += '<div class="listItemBody two-line">';
html += '<h3 class="listItemBodyText">'; html += '<h3 class="listItemBodyText">';
html += channel.Name; html += channel.Name;
html += "</h3>"; html += '</h3>';
html += '<div class="secondary listItemBodyText">'; html += '<div class="secondary listItemBodyText">';
if (channel.ProviderChannelName) { if (channel.ProviderChannelName) {
html += getMappingSecondaryName(channel, providerName); html += getMappingSecondaryName(channel, providerName);
} }
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
html += '<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="' + channel.Id + '" data-providerid="' + channel.ProviderChannelId + '"><i class="md-icon">mode_edit</i></button>'; html += '<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="' + channel.Id + '" data-providerid="' + channel.ProviderChannelId + '"><span class="material-icons mode_edit"></span></button>';
return html += "</div>"; return html += '</div>';
} }
function getEditorHtml() { function getEditorHtml() {
var html = ""; var html = '';
html += '<div class="formDialogContent">'; html += '<div class="formDialogContent">';
html += '<div class="dialogContentInner dialog-content-centered">'; html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">'; html += '<form style="margin:auto;">';
html += "<h1>" + globalize.translate("HeaderChannels") + "</h1>"; html += '<h1>' + globalize.translate('HeaderChannels') + '</h1>';
html += '<div class="channels paperList">'; html += '<div class="channels paperList">';
html += "</div>"; html += '</div>';
html += "</form>"; html += '</form>';
html += "</div>"; html += '</div>';
return html += "</div>"; return html += '</div>';
} }
function initEditor(dlg, options) { function initEditor(dlg, options) {
getChannelMappingOptions(options.serverId, options.providerId).then(function (result) { getChannelMappingOptions(options.serverId, options.providerId).then(function (result) {
currentMappingOptions = result; currentMappingOptions = result;
var channelsElement = dlg.querySelector(".channels"); var channelsElement = dlg.querySelector('.channels');
channelsElement.innerHTML = result.TunerChannels.map(function (channel) { channelsElement.innerHTML = result.TunerChannels.map(function (channel) {
return getTunerChannelHtml(channel, result.ProviderName); return getTunerChannelHtml(channel, result.ProviderName);
}).join(""); }).join('');
channelsElement.addEventListener("click", onChannelsElementClick); channelsElement.addEventListener('click', onChannelsElementClick);
}); });
} }
@ -119,27 +108,27 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
var dialogOptions = { var dialogOptions = {
removeOnClose: true removeOnClose: true
}; };
dialogOptions.size = "small"; dialogOptions.size = 'small';
var dlg = dialogHelper.createDialog(dialogOptions); var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog"); dlg.classList.add('formDialog');
dlg.classList.add("ui-body-a"); dlg.classList.add('ui-body-a');
dlg.classList.add("background-theme-a"); dlg.classList.add('background-theme-a');
var html = ""; var html = '';
var title = globalize.translate("MapChannels"); var title = globalize.translate('MapChannels');
html += '<div class="formDialogHeader">'; html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">arrow_back</i></button>'; html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">'; html += '<h3 class="formDialogHeaderTitle">';
html += title; html += title;
html += "</h3>"; html += '</h3>';
html += "</div>"; html += '</div>';
html += getEditorHtml(); html += getEditorHtml();
dlg.innerHTML = html; dlg.innerHTML = html;
initEditor(dlg, options); initEditor(dlg, options);
dlg.querySelector(".btnCancel").addEventListener("click", function () { dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
dlg.addEventListener("close", resolve); dlg.addEventListener('close', resolve);
dialogHelper.open(dlg); dialogHelper.open(dlg);
}); });
}; };

View file

@ -37,69 +37,69 @@ define(['events'], function (events) {
// 5) It wasn't as smart as it could have been about what should be part of a // 5) It wasn't as smart as it could have been about what should be part of a
// URL and what should be part of human language. // URL and what should be part of human language.
var protocols = "(?:(?:http|https|rtsp|ftp):\\/\\/)"; var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)';
var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
+ "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
+ "\\@)"; + '\\@)';
// IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
// by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
// http://intermapper.com/ // http://intermapper.com/
var ipv6 = "(" var ipv6 = '('
+ "(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))" + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))'
+ "|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
+ "|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
+ "|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ "|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ "|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ "|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ "|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ ")(%.+)?"; + ')(%.+)?';
var ipv4 = "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\." var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.'
+ "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
+ "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
+ "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])"; + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])';
// This would have been a lot cleaner if JS RegExp supported conditionals... // This would have been a lot cleaner if JS RegExp supported conditionals...
var linkRegExpString = var linkRegExpString =
// begin match for protocol / username / password / host // begin match for protocol / username / password / host
"(?:" '(?:'
// ============================ // ============================
// If we have a recognized protocol at the beginning of the URL, we're // If we have a recognized protocol at the beginning of the URL, we're
// more relaxed about what we accept, because we assume the user wants // more relaxed about what we accept, because we assume the user wants
// this to be a URL, and we're not accidentally matching human language // this to be a URL, and we're not accidentally matching human language
+ protocols + "?" + protocols + '?'
// optional username:password@ // optional username:password@
+ credentials + "?" + credentials + '?'
// IP address (both v4 and v6) // IP address (both v4 and v6)
+ "(?:" + '(?:'
// IPv6 // IPv6
+ ipv6 + ipv6
// IPv4 // IPv4
+ "|" + ipv4 + '|' + ipv4
+ ")" + ')'
// end match for protocol / username / password / host // end match for protocol / username / password / host
+ ")" + ')'
// optional port number // optional port number
+ "(?:\\:\\d{1,5})?" + '(?:\\:\\d{1,5})?'
// plus optional path and query params (no unicode allowed here?) // plus optional path and query params (no unicode allowed here?)
+ "(?:" + '(?:'
+ "\\/(?:" + '\\/(?:'
// some characters we'll accept because it's unlikely human language // some characters we'll accept because it's unlikely human language
// would use them after a URL unless they were part of the url // would use them after a URL unless they were part of the url
+ "(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])" + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])'
+ "|(?:\\%[a-f0-9]{2})" + '|(?:\\%[a-f0-9]{2})'
// some characters are much more likely to be used AFTER a url and // some characters are much more likely to be used AFTER a url and
// were not intended to be included in the url itself. Mostly end // were not intended to be included in the url itself. Mostly end
// of sentence type things. It's also likely that the URL would // of sentence type things. It's also likely that the URL would
@ -108,9 +108,9 @@ define(['events'], function (events) {
// they must be followed by another character that we're reasonably // they must be followed by another character that we're reasonably
// sure is part of the url // sure is part of the url
+ "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
+ ")*" + ')*'
+ "|\\b|\$" + '|\\b|\$'
+ ")"; + ')';
// regex = XRegExp(regex,'gi'); // regex = XRegExp(regex,'gi');
var linkRegExp = RegExp(linkRegExpString, 'gi'); var linkRegExp = RegExp(linkRegExpString, 'gi');
@ -120,7 +120,7 @@ define(['events'], function (events) {
// if url doesn't begin with a known protocol, add http by default // if url doesn't begin with a known protocol, add http by default
function ensureProtocol(url) { function ensureProtocol(url) {
if (!url.match(protocolRegExp)) { if (!url.match(protocolRegExp)) {
url = "http://" + url; url = 'http://' + url;
} }
return url; return url;
} }
@ -131,8 +131,9 @@ define(['events'], function (events) {
var links = []; var links = [];
var match; var match;
// eslint-disable-next-line no-cond-assign
while (match = linkRegExp.exec(text)) { while (match = linkRegExp.exec(text)) {
// console.log(matches); console.debug(match);
var txt = match[0]; var txt = match[0];
var pos = match.index; var pos = match.index;
var len = txt.length; var len = txt.length;
@ -187,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.log("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

@ -5,7 +5,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
var currentResolve; var currentResolve;
var currentReject; var currentReject;
var PlayerName = 'Chromecast'; var PlayerName = 'Google Cast';
function sendConnectionResult(isOk) { function sendConnectionResult(isOk) {
@ -54,7 +54,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
// production version registered with google // production version registered with google
// replace this value if you want to test changes on another instance // replace this value if you want to test changes on another instance
var applicationID = "F007D354"; var applicationID = 'F007D354';
var messageNamespace = 'urn:x-cast:com.connectsdk'; var messageNamespace = 'urn:x-cast:com.connectsdk';
@ -105,7 +105,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
this.sessionListener.bind(this), this.sessionListener.bind(this),
this.receiverListener.bind(this)); this.receiverListener.bind(this));
console.log('chromecast.initialize'); console.debug('chromecast.initialize');
chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler);
}; };
@ -114,14 +114,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.onInitSuccess = function () { CastPlayer.prototype.onInitSuccess = function () {
this.isInitialized = true; this.isInitialized = true;
console.log("chromecast init success"); console.debug('chromecast init success');
}; };
/** /**
* Generic error callback function * Generic error callback function
*/ */
CastPlayer.prototype.onError = function () { CastPlayer.prototype.onError = function () {
console.log("chromecast error"); console.debug('chromecast error');
}; };
/** /**
@ -177,10 +177,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.receiverListener = function (e) { CastPlayer.prototype.receiverListener = function (e) {
if (e === 'available') { if (e === 'available') {
console.log("chromecast receiver found"); console.debug('chromecast receiver found');
this.hasReceivers = true; this.hasReceivers = true;
} else { } else {
console.log("chromecast receiver list empty"); console.debug('chromecast receiver list empty');
this.hasReceivers = false; this.hasReceivers = false;
} }
}; };
@ -190,15 +190,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.sessionUpdateListener = function (isAlive) { CastPlayer.prototype.sessionUpdateListener = function (isAlive) {
if (isAlive) { if (isAlive) {
console.log('sessionUpdateListener: already alive'); console.debug('sessionUpdateListener: already alive');
} else { } else {
this.session = null; this.session = null;
this.deviceState = DEVICE_STATE.IDLE; this.deviceState = DEVICE_STATE.IDLE;
this.castPlayerState = PLAYER_STATE.IDLE; this.castPlayerState = PLAYER_STATE.IDLE;
document.removeEventListener("volumeupbutton", onVolumeUpKeyDown, false); document.removeEventListener('volumeupbutton', onVolumeUpKeyDown, false);
document.removeEventListener("volumedownbutton", onVolumeDownKeyDown, false); document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false);
console.log('sessionUpdateListener: setting currentMediaSession to null'); console.debug('sessionUpdateListener: setting currentMediaSession to null');
this.currentMediaSession = null; this.currentMediaSession = null;
sendConnectionResult(false); sendConnectionResult(false);
@ -211,7 +211,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* session request in opt_sessionRequest. * session request in opt_sessionRequest.
*/ */
CastPlayer.prototype.launchApp = function () { CastPlayer.prototype.launchApp = function () {
console.log("chromecast launching app..."); console.debug('chromecast launching app...');
chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this));
}; };
@ -220,7 +220,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* @param {Object} e A chrome.cast.Session object * @param {Object} e A chrome.cast.Session object
*/ */
CastPlayer.prototype.onRequestSessionSuccess = function (e) { CastPlayer.prototype.onRequestSessionSuccess = function (e) {
console.log("chromecast session success: " + e.sessionId); console.debug('chromecast session success: ' + e.sessionId);
this.onSessionConnected(e); this.onSessionConnected(e);
}; };
@ -232,8 +232,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
this.session.addMediaListener(this.sessionMediaListener.bind(this)); this.session.addMediaListener(this.sessionMediaListener.bind(this));
this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); this.session.addUpdateListener(this.sessionUpdateListener.bind(this));
document.addEventListener("volumeupbutton", onVolumeUpKeyDown, false); document.addEventListener('volumeupbutton', onVolumeUpKeyDown, false);
document.addEventListener("volumedownbutton", onVolumeDownKeyDown, false); document.addEventListener('volumedownbutton', onVolumeDownKeyDown, false);
events.trigger(this, 'connect'); events.trigger(this, 'connect');
this.sendMessage({ this.sendMessage({
@ -262,7 +262,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* Callback function for launch error * Callback function for launch error
*/ */
CastPlayer.prototype.onLaunchError = function () { CastPlayer.prototype.onLaunchError = function () {
console.log("chromecast launch error"); console.debug('chromecast launch error');
this.deviceState = DEVICE_STATE.ERROR; this.deviceState = DEVICE_STATE.ERROR;
sendConnectionResult(false); sendConnectionResult(false);
}; };
@ -280,12 +280,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* Callback function for stop app success * Callback function for stop app success
*/ */
CastPlayer.prototype.onStopAppSuccess = function (message) { CastPlayer.prototype.onStopAppSuccess = function (message) {
console.log(message); console.debug(message);
this.deviceState = DEVICE_STATE.IDLE; this.deviceState = DEVICE_STATE.IDLE;
this.castPlayerState = PLAYER_STATE.IDLE; this.castPlayerState = PLAYER_STATE.IDLE;
document.removeEventListener("volumeupbutton", onVolumeUpKeyDown, false); document.removeEventListener('volumeupbutton', onVolumeUpKeyDown, false);
document.removeEventListener("volumedownbutton", onVolumeDownKeyDown, false); document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false);
this.currentMediaSession = null; this.currentMediaSession = null;
}; };
@ -296,7 +296,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.loadMedia = function (options, command) { CastPlayer.prototype.loadMedia = function (options, command) {
if (!this.session) { if (!this.session) {
console.log("no session"); console.debug('no session');
return Promise.reject(); return Promise.reject();
} }
@ -377,7 +377,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
}; };
CastPlayer.prototype.onPlayCommandSuccess = function () { CastPlayer.prototype.onPlayCommandSuccess = function () {
//console.log('Message was sent to receiver ok.'); console.debug('Message was sent to receiver ok.');
}; };
/** /**
@ -386,7 +386,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) {
//console.log("chromecast new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')'); console.debug('chromecast new media session ID:' + mediaSession.mediaSessionId + ' (' + how + ')');
this.currentMediaSession = mediaSession; this.currentMediaSession = mediaSession;
if (how === 'loadMedia') { if (how === 'loadMedia') {
@ -405,7 +405,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* @param {!Boolean} e true/false * @param {!Boolean} e true/false
*/ */
CastPlayer.prototype.onMediaStatusUpdate = function (e) { CastPlayer.prototype.onMediaStatusUpdate = function (e) {
//console.log("chromecast updating media: " + e); console.debug('chromecast updating media: ' + e);
if (e === false) { if (e === false) {
this.castPlayerState = PLAYER_STATE.IDLE; this.castPlayerState = PLAYER_STATE.IDLE;
} }
@ -417,7 +417,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
*/ */
CastPlayer.prototype.setReceiverVolume = function (mute, vol) { CastPlayer.prototype.setReceiverVolume = function (mute, vol) {
if (!this.currentMediaSession) { if (!this.currentMediaSession) {
//console.log('this.currentMediaSession is null'); console.debug('this.currentMediaSession is null');
return; return;
} }
@ -443,7 +443,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
* Callback function for media command success * Callback function for media command success
*/ */
CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) {
//console.log(info); console.debug(info);
}; };
function normalizeImages(state) { function normalizeImages(state) {
@ -482,7 +482,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
} else { } else {
query.Limit = query.Limit || 100; query.Limit = query.Limit || 100;
query.ExcludeLocationTypes = "Virtual"; query.ExcludeLocationTypes = 'Virtual';
query.EnableTotalRecordCount = false; query.EnableTotalRecordCount = false;
return apiClient.getItems(userId, query); return apiClient.getItems(userId, query);
@ -493,7 +493,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
events.on(instance._castPlayer, eventName, function (e, data) { events.on(instance._castPlayer, eventName, function (e, data) {
//console.log('cc: ' + eventName); console.debug('cc: ' + eventName);
var state = instance.getPlayerStateInternal(data); var state = instance.getPlayerStateInternal(data);
events.trigger(instance, eventName, [state]); events.trigger(instance, eventName, [state]);
@ -506,13 +506,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
instance._castPlayer = new CastPlayer(); instance._castPlayer = new CastPlayer();
// To allow the native android app to override // To allow the native android app to override
document.dispatchEvent(new CustomEvent("chromecastloaded", { document.dispatchEvent(new CustomEvent('chromecastloaded', {
detail: { detail: {
player: instance player: instance
} }
})); }));
events.on(instance._castPlayer, "connect", function (e) { events.on(instance._castPlayer, 'connect', function (e) {
if (currentResolve) { if (currentResolve) {
sendConnectionResult(true); sendConnectionResult(true);
@ -520,27 +520,27 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo());
} }
console.log('cc: connect'); console.debug('cc: connect');
// Reset this so that statechange will fire // Reset this so that statechange will fire
instance.lastPlayerData = null; instance.lastPlayerData = null;
}); });
events.on(instance._castPlayer, "playbackstart", function (e, data) { events.on(instance._castPlayer, 'playbackstart', function (e, data) {
console.log('cc: playbackstart'); console.debug('cc: playbackstart');
instance._castPlayer.initializeCastPlayer(); instance._castPlayer.initializeCastPlayer();
var state = instance.getPlayerStateInternal(data); var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "playbackstart", [state]); events.trigger(instance, 'playbackstart', [state]);
}); });
events.on(instance._castPlayer, "playbackstop", function (e, data) { events.on(instance._castPlayer, 'playbackstop', function (e, data) {
console.log('cc: playbackstop'); console.debug('cc: playbackstop');
var state = instance.getPlayerStateInternal(data); var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "playbackstop", [state]); events.trigger(instance, 'playbackstop', [state]);
var state = instance.lastPlayerData.PlayState || {}; var state = instance.lastPlayerData.PlayState || {};
var volume = state.VolumeLevel || 0.5; var volume = state.VolumeLevel || 0.5;
@ -553,12 +553,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
instance.lastPlayerData.PlayState.IsMuted = mute; instance.lastPlayerData.PlayState.IsMuted = mute;
}); });
events.on(instance._castPlayer, "playbackprogress", function (e, data) { events.on(instance._castPlayer, 'playbackprogress', function (e, data) {
//console.log('cc: positionchange'); console.debug('cc: positionchange');
var state = instance.getPlayerStateInternal(data); var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "timeupdate", [state]); events.trigger(instance, 'timeupdate', [state]);
}); });
bindEventForRelay(instance, 'timeupdate'); bindEventForRelay(instance, 'timeupdate');
@ -567,12 +567,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
bindEventForRelay(instance, 'volumechange'); bindEventForRelay(instance, 'volumechange');
bindEventForRelay(instance, 'repeatmodechange'); bindEventForRelay(instance, 'repeatmodechange');
events.on(instance._castPlayer, "playstatechange", function (e, data) { events.on(instance._castPlayer, 'playstatechange', function (e, data) {
//console.log('cc: playstatechange'); console.debug('cc: playstatechange');
var state = instance.getPlayerStateInternal(data); var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "pause", [state]); events.trigger(instance, 'pause', [state]);
}); });
} }
@ -630,24 +630,24 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
name: PlayerName, name: PlayerName,
id: PlayerName, id: PlayerName,
playerName: PlayerName, playerName: PlayerName,
playableMediaTypes: ["Audio", "Video"], playableMediaTypes: ['Audio', 'Video'],
isLocalPlayer: false, isLocalPlayer: false,
appName: PlayerName, appName: PlayerName,
deviceName: appName, deviceName: appName,
supportedCommands: [ supportedCommands: [
"VolumeUp", 'VolumeUp',
"VolumeDown", 'VolumeDown',
"Mute", 'Mute',
"Unmute", 'Unmute',
"ToggleMute", 'ToggleMute',
"SetVolume", 'SetVolume',
"SetAudioStreamIndex", 'SetAudioStreamIndex',
"SetSubtitleStreamIndex", 'SetSubtitleStreamIndex',
"DisplayContent", 'DisplayContent',
"SetRepeatMode", 'SetRepeatMode',
"EndSession", 'EndSession',
"PlayMediaSource", 'PlayMediaSource',
"PlayTrailers" 'PlayTrailers'
] ]
}; };
}; };
@ -664,10 +664,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
normalizeImages(data); normalizeImages(data);
//console.log(JSON.stringify(data)); console.debug(JSON.stringify(data));
if (triggerStateChange) { if (triggerStateChange) {
events.trigger(this, "statechange", [data]); events.trigger(this, 'statechange', [data]);
} }
return data; return data;
@ -686,6 +686,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
}); });
} }
if (options.items.length > 1 && options && options.ids) {
// Use the original request id array for sorting the result in the proper order
options.items.sort(function (a, b) {
return options.ids.indexOf(a.Id) - options.ids.indexOf(b.Id);
});
}
return this._castPlayer.loadMedia(options, command); return this._castPlayer.loadMedia(options, command);
}; };

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;
@ -37,7 +24,7 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage
function createCollection(apiClient, dlg) { function createCollection(apiClient, dlg) {
var url = apiClient.getUrl("Collections", { var url = apiClient.getUrl('Collections', {
Name: dlg.querySelector('#txtNewCollectionName').value, Name: dlg.querySelector('#txtNewCollectionName').value,
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
@ -45,9 +32,9 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage
}); });
apiClient.ajax({ apiClient.ajax({
type: "POST", type: 'POST',
url: url, url: url,
dataType: "json" dataType: 'json'
}).then(function (result) { }).then(function (result) {
@ -69,13 +56,13 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage
function addToCollection(apiClient, dlg, id) { function addToCollection(apiClient, dlg, id) {
var url = apiClient.getUrl("Collections/" + id + "/Items", { var url = apiClient.getUrl('Collections/' + id + '/Items', {
Ids: dlg.querySelector('.fldSelectedItemIds').value || '' Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
}); });
apiClient.ajax({ apiClient.ajax({
type: "POST", type: 'POST',
url: url url: url
}).then(function () { }).then(function () {
@ -106,8 +93,8 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage
var options = { var options = {
Recursive: true, Recursive: true,
IncludeItemTypes: "BoxSet", IncludeItemTypes: 'BoxSet',
SortBy: "SortName", SortBy: 'SortName',
EnableTotalRecordCount: false EnableTotalRecordCount: false
}; };
@ -243,13 +230,13 @@ define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManage
var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">'; html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">arrow_back</i></button>'; html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">'; html += '<h3 class="formDialogHeaderTitle">';
html += title; html += title;
html += '</h3>'; html += '</h3>';
if (appHost.supports('externallinks')) { if (appHost.supports('externallinks')) {
html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate('Help') + '"><i class="md-icon">info</i><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>'; html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate('Help') + '"><span class="material-icons info"></span><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>';
} }
html += '</div>'; html += '</div>';

View file

@ -1,8 +1,32 @@
define(['dialog', 'globalize'], function (dialog, globalize) { define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize) {
'use strict'; 'use strict';
return function (text, title) { function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
if (browser.tv && window.confirm) {
// Use the native confirm dialog
return function (options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
var text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text);
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
};
} else {
// Use our own dialog
return function (text, title) {
var options; var options;
if (typeof text === 'string') { if (typeof text === 'string') {
options = { options = {
@ -37,4 +61,5 @@ define(['dialog', 'globalize'], function (dialog, globalize) {
return Promise.reject(); return Promise.reject();
}); });
}; };
}
}); });

View file

@ -1,27 +0,0 @@
define([], function () {
'use strict';
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
return function (options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
var text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text);
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
};
});

View file

@ -32,7 +32,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
try { try {
parentNode.removeChild(elem); parentNode.removeChild(elem);
} catch (err) { } catch (err) {
console.log('Error removing dialog element: ' + err); console.error('error removing dialog element: ' + err);
} }
} }
} }
@ -141,7 +141,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
animateDialogOpen(dlg); animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) { if (isHistoryEnabled(dlg)) {
appRouter.pushState({ dialogId: hash }, "Dialog", '#' + hash); appRouter.pushState({ dialogId: hash }, 'Dialog', '#' + hash);
window.addEventListener('popstate', onHashChange); window.addEventListener('popstate', onHashChange);
} else { } else {
@ -169,6 +169,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}, { }, {
passive: true passive: true
}); });
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
if (e.target === dlg.dialogContainer) {
// Close the application dialog menu
close(dlg);
// Prevent the default browser context menu from appearing
e.preventDefault();
}
});
} }
function isHistoryEnabled(dlg) { function isHistoryEnabled(dlg) {
@ -242,9 +251,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
var onAnimationFinish = function () { var onAnimationFinish = function () {
focusManager.pushScope(dlg); focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') { if (dlg.getAttribute('data-autofocus') === 'true') {
focusManager.autoFocus(dlg); focusManager.autoFocus(dlg);
} }
if (document.activeElement && !dlg.contains(document.activeElement)) {
// Blur foreign element to prevent triggering of an action from the previous scope
document.activeElement.blur();
}
}; };
if (enableAnimation()) { if (enableAnimation()) {

View file

@ -15,10 +15,12 @@
.dialog { .dialog {
margin: 0; margin: 0;
border-radius: .2em; border-radius: 0.2em;
-webkit-font-smoothing: antialiased;
border: 0; border: 0;
padding: 0; padding: 0;
will-change: transform, opacity; will-change: transform, opacity;
/* Strict does not work well with actionsheet */ /* Strict does not work well with actionsheet */
contain: style paint; contain: style paint;
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4); box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4);
@ -50,13 +52,13 @@
to { to {
opacity: 0; opacity: 0;
transform: scale(.5); transform: scale(0.5);
} }
} }
@keyframes scaleup { @keyframes scaleup {
from { from {
transform: scale(.5); transform: scale(0.5);
opacity: 0; opacity: 0;
} }
@ -77,7 +79,6 @@
} }
@keyframes fadeout { @keyframes fadeout {
from { from {
opacity: 1; opacity: 1;
} }
@ -100,7 +101,6 @@
} }
@keyframes slidedown { @keyframes slidedown {
from { from {
opacity: 1; opacity: 1;
transform: none; transform: none;
@ -113,8 +113,8 @@
} }
@media all and (max-width: 80em), all and (max-height: 45em) { @media all and (max-width: 80em), all and (max-height: 45em) {
.dialog-fixedSize,
.dialog-fixedSize, .dialog-fullscreen-lowres { .dialog-fullscreen-lowres {
position: fixed !important; position: fixed !important;
top: 0 !important; top: 0 !important;
bottom: 0 !important; bottom: 0 !important;
@ -126,7 +126,6 @@
} }
@media all and (min-width: 80em) and (min-height: 45em) { @media all and (min-width: 80em) and (min-height: 45em) {
.dialog-medium { .dialog-medium {
width: 80%; width: 80%;
height: 80%; height: 80%;
@ -168,5 +167,5 @@
} }
.dialogBackdropOpened { .dialogBackdropOpened {
opacity: .5; opacity: 0.5;
} }

View file

@ -1,8 +1,8 @@
#ulDirectoryPickerList a { #ulDirectoryPickerList a {
padding-top: .4em; padding-top: 0.4em;
padding-bottom: .4em padding-bottom: 0.4em;
} }
.lblDirectoryPickerPath { .lblDirectoryPickerPath {
white-space: nowrap white-space: nowrap;
} }

View file

@ -1,4 +1,4 @@
define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-icon-button-light', 'css!./directorybrowser', 'formDialogStyle', 'emby-button'], function(loading, dialogHelper, dom) { define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-input', 'paper-icon-button-light', 'css!./directorybrowser', 'formDialogStyle', 'emby-button'], function(loading, dialogHelper, dom, globalize) {
'use strict'; 'use strict';
function getSystemInfo() { function getSystemInfo() {
@ -7,24 +7,24 @@ 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) {
if (path && typeof path !== 'string') { if (path && typeof path !== 'string') {
throw new Error("invalid path"); throw new Error('invalid path');
} }
loading.show(); loading.show();
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));
@ -37,31 +37,31 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
Promise.all(promises).then( Promise.all(promises).then(
function(responses) { function(responses) {
var folders = responses[0]; var folders = responses[0];
var parentPath = responses[1] || ""; var parentPath = responses[1] || '';
var html = ""; var html = '';
page.querySelector(".results").scrollTop = 0; page.querySelector('.results').scrollTop = 0;
page.querySelector("#txtDirectoryPickerPath").value = path || ""; page.querySelector('#txtDirectoryPickerPath').value = path || '';
if (path) { if (path) {
html += getItem("lnkPath lnkDirectory", "", parentPath, "..."); html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
} }
for (var i = 0, length = folders.length; i < length; i++) { for (var i = 0, length = folders.length; i < length; i++) {
var folder = folders[i]; var folder = folders[i];
var cssClass = "File" === folder.Type ? "lnkPath lnkFile" : "lnkPath lnkDirectory"; var cssClass = 'File' === folder.Type ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
html += getItem(cssClass, folder.Type, folder.Path, folder.Name); html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
} }
if (!path) { if (!path) {
html += getItem("lnkPath lnkDirectory", "", "Network", Globalize.translate("ButtonNetwork")); html += getItem('lnkPath lnkDirectory', '', 'Network', globalize.translate('ButtonNetwork'));
} }
page.querySelector(".results").innerHTML = html; page.querySelector('.results').innerHTML = html;
loading.hide(); loading.hide();
}, function() { }, function() {
if (updatePathOnError) { if (updatePathOnError) {
page.querySelector("#txtDirectoryPickerPath").value = ""; page.querySelector('#txtDirectoryPickerPath').value = '';
page.querySelector(".results").innerHTML = ""; page.querySelector('.results').innerHTML = '';
loading.hide(); loading.hide();
} }
} }
@ -69,74 +69,74 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
} }
function getItem(cssClass, type, path, name) { function getItem(cssClass, type, path, name) {
var html = ""; var html = '';
html += '<div class="listItem listItem-border ' + cssClass + '" data-type="' + type + '" data-path="' + path + '">'; html += '<div class="listItem listItem-border ' + cssClass + '" data-type="' + type + '" data-path="' + path + '">';
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">'; html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
html += '<div class="listItemBodyText">'; html += '<div class="listItemBodyText">';
html += name; html += name;
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
html += '<i class="md-icon" style="font-size:inherit;">arrow_forward</i>'; html += '<span class="material-icons arrow_forward" style="font-size:inherit;"></span>';
html += "</div>"; html += '</div>';
return html; return html;
} }
function getEditorHtml(options, systemInfo) { function getEditorHtml(options, systemInfo) {
var html = ""; var html = '';
html += '<div class="formDialogContent scrollY">'; html += '<div class="formDialogContent scrollY">';
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">'; html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
if (!options.pathReadOnly) { if (!options.pathReadOnly) {
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/>';
html += Globalize.translate("MessageDirectoryPickerBSDInstruction"); html += globalize.translate('MessageDirectoryPickerBSDInstruction');
html += "<br/>"; html += '<br/>';
} else if ("linux" === systemInfo.OperatingSystem.toLowerCase()) { } else if ('linux' === systemInfo.OperatingSystem.toLowerCase()) {
html += "<br/>"; html += '<br/>';
html += "<br/>"; html += '<br/>';
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;">';
html += '<div style="flex-grow:1;">'; html += '<div style="flex-grow:1;">';
var labelKey; var labelKey;
if (options.includeFiles !== true) { if (options.includeFiles !== true) {
labelKey = "LabelFolder"; labelKey = 'LabelFolder';
} else { } else {
labelKey = "LabelPath"; labelKey = 'LabelPath';
} }
var readOnlyAttribute = options.pathReadOnly ? " readonly" : ""; var readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
html += '<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ' + readOnlyAttribute + ' label="' + Globalize.translate(labelKey) + '"/>'; html += '<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ' + readOnlyAttribute + ' label="' + globalize.translate(labelKey) + '"/>';
html += "</div>"; html += '</div>';
if (!readOnlyAttribute) { if (!readOnlyAttribute) {
html += '<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="' + Globalize.translate("ButtonRefresh") + '"><i class="md-icon">search</i></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="' + globalize.translate('ButtonRefresh') + '"><span class="material-icons search"></span></button>';
} }
html += "</div>"; html += '</div>';
if (!readOnlyAttribute) { if (!readOnlyAttribute) {
html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>'; html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>';
} }
if (options.enableNetworkSharePath) { if (options.enableNetworkSharePath) {
html += '<div class="inputContainer" style="margin-top:2em;">'; html += '<div class="inputContainer" style="margin-top:2em;">';
html += '<input is="emby-input" id="txtNetworkPath" type="text" label="' + Globalize.translate("LabelOptionalNetworkPath") + '"/>'; html += '<input is="emby-input" id="txtNetworkPath" type="text" label="' + globalize.translate('LabelOptionalNetworkPath') + '"/>';
html += '<div class="fieldDescription">'; html += '<div class="fieldDescription">';
html += Globalize.translate("LabelOptionalNetworkPathHelp"); html += globalize.translate('LabelOptionalNetworkPathHelp');
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
} }
html += '<div class="formDialogFooter">'; html += '<div class="formDialogFooter">';
html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">' + Globalize.translate("ButtonOk") + "</button>"; html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">' + globalize.translate('ButtonOk') + '</button>';
html += "</div>"; html += '</div>';
html += "</form>"; html += '</form>';
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
html += "</div>"; html += '</div>';
return html; return html;
} }
@ -144,75 +144,74 @@ 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) {
return apiClient.ajax({ return apiClient.ajax({
type: "POST", type: 'POST',
url: apiClient.getUrl("Environment/ValidatePath"), url: apiClient.getUrl('Environment/ValidatePath'),
data: { data: {
ValidateWriteable: validateWriteable, ValidateWriteable: validateWriteable,
Path: path Path: path
} }
}).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();
}); });
} }
function initEditor(content, options, fileOptions) { function initEditor(content, options, fileOptions) {
content.addEventListener("click", function(e) { content.addEventListener('click', function(e) {
var lnkPath = dom.parentWithClass(e.target, "lnkPath"); var lnkPath = dom.parentWithClass(e.target, 'lnkPath');
if (lnkPath) { if (lnkPath) {
var path = lnkPath.getAttribute("data-path"); var path = lnkPath.getAttribute('data-path');
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);
} }
} }
}); });
content.addEventListener("click", function(e) { content.addEventListener('click', function(e) {
if (dom.parentWithClass(e.target, "btnRefreshDirectories")) { if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
var path = content.querySelector("#txtDirectoryPickerPath").value; var path = content.querySelector('#txtDirectoryPickerPath').value;
refreshDirectoryBrowser(content, path, fileOptions); refreshDirectoryBrowser(content, path, fileOptions);
} }
}); });
content.addEventListener("change", function(e) { content.addEventListener('change', function(e) {
var txtDirectoryPickerPath = dom.parentWithTag(e.target, "INPUT"); var txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
if (txtDirectoryPickerPath && "txtDirectoryPickerPath" === txtDirectoryPickerPath.id) { if (txtDirectoryPickerPath && 'txtDirectoryPickerPath' === txtDirectoryPickerPath.id) {
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions); refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
} }
}); });
content.querySelector("form").addEventListener("submit", function(e) { content.querySelector('form').addEventListener('submit', function(e) {
if (options.callback) { if (options.callback) {
var networkSharePath = this.querySelector("#txtNetworkPath"); var networkSharePath = this.querySelector('#txtNetworkPath');
networkSharePath = networkSharePath ? networkSharePath.value : null; networkSharePath = networkSharePath ? networkSharePath.value : null;
var path = this.querySelector("#txtDirectoryPickerPath").value; var path = this.querySelector('#txtDirectoryPickerPath').value;
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath)); validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
} }
e.preventDefault(); e.preventDefault();
@ -225,11 +224,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
if (options.path) { if (options.path) {
return Promise.resolve(options.path); return Promise.resolve(options.path);
} else { } else {
return ApiClient.getJSON(ApiClient.getUrl("Environment/DefaultDirectoryBrowser")).then( return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
function(result) { function(result) {
return result.Path || ""; return result.Path || '';
}, function() { }, function() {
return ""; return '';
} }
); );
} }
@ -254,35 +253,35 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
var systemInfo = responses[0]; var systemInfo = responses[0];
var initialPath = responses[1]; var initialPath = responses[1];
var dlg = dialogHelper.createDialog({ var dlg = dialogHelper.createDialog({
size: "medium-tall", size: 'medium-tall',
removeOnClose: true, removeOnClose: true,
scrollY: false scrollY: false
}); });
dlg.classList.add("ui-body-a"); dlg.classList.add('ui-body-a');
dlg.classList.add("background-theme-a"); dlg.classList.add('background-theme-a');
dlg.classList.add("directoryPicker"); dlg.classList.add('directoryPicker');
dlg.classList.add("formDialog"); dlg.classList.add('formDialog');
var html = ""; var html = '';
html += '<div class="formDialogHeader">'; html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><i class="md-icon">arrow_back</i></button>'; html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">'; html += '<h3 class="formDialogHeaderTitle">';
html += options.header || Globalize.translate("HeaderSelectPath"); html += options.header || globalize.translate('HeaderSelectPath');
html += "</h3>"; html += '</h3>';
html += "</div>"; html += '</div>';
html += getEditorHtml(options, systemInfo); html += getEditorHtml(options, systemInfo);
dlg.innerHTML = html; dlg.innerHTML = html;
initEditor(dlg, options, fileOptions); initEditor(dlg, options, fileOptions);
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;
var txtNetworkPath = dlg.querySelector("#txtNetworkPath"); var txtNetworkPath = dlg.querySelector('#txtNetworkPath');
if (txtNetworkPath) { if (txtNetworkPath) {
txtNetworkPath.value = options.networkSharePath || ""; txtNetworkPath.value = options.networkSharePath || '';
} }
if (!options.pathReadOnly) { if (!options.pathReadOnly) {
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true); refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
@ -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

@ -1,5 +1,5 @@
define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', 'apphost', 'focusManager', 'datetime', 'globalize', 'loading', 'connectionManager', 'skinManager', 'dom', 'events', 'emby-select', 'emby-checkbox', 'emby-button'], function (require, browser, layoutManager, appSettings, pluginManager, appHost, focusManager, datetime, globalize, loading, connectionManager, skinManager, dom, events) { define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', 'apphost', 'focusManager', 'datetime', 'globalize', 'loading', 'connectionManager', 'skinManager', 'dom', 'events', 'emby-select', 'emby-checkbox', 'emby-button'], function (require, browser, layoutManager, appSettings, pluginManager, appHost, focusManager, datetime, globalize, loading, connectionManager, skinManager, dom, events) {
"use strict"; 'use strict';
function fillThemes(select, isDashboard) { function fillThemes(select, isDashboard) {
select.innerHTML = skinManager.getThemes().map(function (t) { select.innerHTML = skinManager.getThemes().map(function (t) {
@ -180,11 +180,14 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#selectLanguage').value = userSettings.language() || ''; context.querySelector('#selectLanguage').value = userSettings.language() || '';
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
selectDashboardTheme.value = userSettings.dashboardTheme() || ''; selectDashboardTheme.value = userSettings.dashboardTheme() || '';
selectTheme.value = userSettings.theme() || ''; selectTheme.value = userSettings.theme() || '';
@ -214,8 +217,11 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value); userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value);
userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value);
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
userSettingsInstance.skin(context.querySelector('.selectSkin').value); userSettingsInstance.skin(context.querySelector('.selectSkin').value);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
if (user.Id === apiClient.getCurrentUserId()) { if (user.Id === apiClient.getCurrentUserId()) {

View file

@ -3,6 +3,7 @@
<h2 class="sectionTitle"> <h2 class="sectionTitle">
${Display} ${Display}
</h2> </h2>
<div class="selectContainer languageSection hide"> <div class="selectContainer languageSection hide">
<select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}"> <select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}">
<option value="">${Auto}</option> <option value="">${Auto}</option>
@ -55,14 +56,14 @@
<div class="fieldDescription"> <div class="fieldDescription">
<div>${LabelDisplayLanguageHelp}</div> <div>${LabelDisplayLanguageHelp}</div>
<div class="learnHowToContributeContainer hide" style="margin-top: .25em;"> <div class="learnHowToContributeContainer hide" style="margin-top: .25em;">
<a is="emby-linkbutton" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">${LearnHowYouCanContribute}</a> <a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">${LearnHowYouCanContribute}</a>
</div> </div>
</div> </div>
</div> </div>
<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>
@ -133,6 +134,7 @@
<div class="selectContainer selectDashboardThemeContainer hide"> <div class="selectContainer selectDashboardThemeContainer hide">
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select> <select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
</div> </div>
<div class="selectContainer hide selectScreensaverContainer"> <div class="selectContainer hide selectScreensaverContainer">
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select> <select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
</div> </div>
@ -141,6 +143,19 @@
<select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select> <select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select>
</div> </div>
<div class="inputContainer inputContainer-withDescription fldFadein">
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldFadein">
<label>
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
<span>${EnableFastImageFadeIn}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableFastImageFadeInHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide"> <div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" /> <input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
@ -148,6 +163,7 @@
</label> </label>
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div> <div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide"> <div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" /> <input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
@ -155,6 +171,7 @@
</label> </label>
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div> <div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide"> <div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" /> <input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />

View file

@ -1,181 +0,0 @@
define([], function () {
'use strict';
function parentWithAttribute(elem, name, value) {
while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) {
elem = elem.parentNode;
if (!elem || !elem.getAttribute) {
return null;
}
}
return elem;
}
function parentWithTag(elem, tagNames) {
// accept both string and array passed in
if (!Array.isArray(tagNames)) {
tagNames = [tagNames];
}
while (tagNames.indexOf(elem.tagName || '') === -1) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
function containsAnyClass(classList, classNames) {
for (var i = 0, length = classNames.length; i < length; i++) {
if (classList.contains(classNames[i])) {
return true;
}
}
return false;
}
function parentWithClass(elem, classNames) {
// accept both string and array passed in
if (!Array.isArray(classNames)) {
classNames = [classNames];
}
while (!elem.classList || !containsAnyClass(elem.classList, classNames)) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
var supportsCaptureOption = false;
try {
var opts = Object.defineProperty({}, 'capture', {
get: function () {
supportsCaptureOption = true;
}
});
window.addEventListener("test", null, opts);
} catch (e) {
console.log('error checking capture support');
}
function addEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
if (!supportsCaptureOption) {
optionsOrCapture = options.capture;
}
target.addEventListener(type, handler, optionsOrCapture);
}
function removeEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
if (!supportsCaptureOption) {
optionsOrCapture = options.capture;
}
target.removeEventListener(type, handler, optionsOrCapture);
}
var windowSize;
var windowSizeEventsBound;
function clearWindowSize() {
windowSize = null;
}
function getWindowSize() {
if (!windowSize) {
windowSize = {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth
};
if (!windowSizeEventsBound) {
windowSizeEventsBound = true;
addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true });
addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true });
}
}
return windowSize;
}
var _animationEvent;
function whichAnimationEvent() {
if (_animationEvent) {
return _animationEvent;
}
var t;
var el = document.createElement("div");
var animations = {
"animation": "animationend",
"OAnimation": "oAnimationEnd",
"MozAnimation": "animationend",
"WebkitAnimation": "webkitAnimationEnd"
};
for (t in animations) {
if (el.style[t] !== undefined) {
_animationEvent = animations[t];
return animations[t];
}
}
_animationEvent = 'animationend';
return _animationEvent;
}
function whichAnimationCancelEvent() {
return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel');
}
var _transitionEvent;
function whichTransitionEvent() {
if (_transitionEvent) {
return _transitionEvent;
}
var t;
var el = document.createElement("div");
var transitions = {
"transition": "transitionend",
"OTransition": "oTransitionEnd",
"MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd"
};
for (t in transitions) {
if (el.style[t] !== undefined) {
_transitionEvent = transitions[t];
return transitions[t];
}
}
_transitionEvent = 'transitionend';
return _transitionEvent;
}
return {
parentWithAttribute: parentWithAttribute,
parentWithClass: parentWithClass,
parentWithTag: parentWithTag,
addEventListener: addEventListenerWithOptions,
removeEventListener: removeEventListenerWithOptions,
getWindowSize: getWindowSize,
whichTransitionEvent: whichTransitionEvent,
whichAnimationEvent: whichAnimationEvent,
whichAnimationCancelEvent: whichAnimationCancelEvent
};
});

View file

@ -1,41 +1,41 @@
define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoader", "globalize", "layoutManager", "scrollStyles", "emby-itemscontainer"], function (loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) { define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'globalize', 'layoutManager', 'scrollStyles', 'emby-itemscontainer'], function (loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) {
"use strict"; 'use strict';
function enableScrollX() { function enableScrollX() {
return !layoutManager.desktop; return !layoutManager.desktop;
} }
function getThumbShape() { function getThumbShape() {
return enableScrollX() ? "overflowBackdrop" : "backdrop"; return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
} }
function getPosterShape() { function getPosterShape() {
return enableScrollX() ? "overflowPortrait" : "portrait"; return enableScrollX() ? 'overflowPortrait' : 'portrait';
} }
function getSquareShape() { function getSquareShape() {
return enableScrollX() ? "overflowSquare" : "square"; return enableScrollX() ? 'overflowSquare' : 'square';
} }
function getSections() { function getSections() {
return [{ return [{
name: "HeaderFavoriteMovies", name: 'HeaderFavoriteMovies',
types: "Movie", types: 'Movie',
id: "favoriteMovies", id: 'favoriteMovies',
shape: getPosterShape(), shape: getPosterShape(),
showTitle: false, showTitle: false,
overlayPlayButton: true overlayPlayButton: true
}, { }, {
name: "HeaderFavoriteShows", name: 'HeaderFavoriteShows',
types: "Series", types: 'Series',
id: "favoriteShows", id: 'favoriteShows',
shape: getPosterShape(), shape: getPosterShape(),
showTitle: false, showTitle: false,
overlayPlayButton: true overlayPlayButton: true
}, { }, {
name: "HeaderFavoriteEpisodes", name: 'HeaderFavoriteEpisodes',
types: "Episode", types: 'Episode',
id: "favoriteEpisode", id: 'favoriteEpisode',
shape: getThumbShape(), shape: getThumbShape(),
preferThumb: false, preferThumb: false,
showTitle: true, showTitle: true,
@ -44,9 +44,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
overlayText: false, overlayText: false,
centerText: true centerText: true
}, { }, {
name: "HeaderFavoriteVideos", name: 'HeaderFavoriteVideos',
types: "Video,MusicVideo", types: 'Video,MusicVideo',
id: "favoriteVideos", id: 'favoriteVideos',
shape: getThumbShape(), shape: getThumbShape(),
preferThumb: true, preferThumb: true,
showTitle: true, showTitle: true,
@ -54,9 +54,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
overlayText: false, overlayText: false,
centerText: true centerText: true
}, { }, {
name: "HeaderFavoriteArtists", name: 'HeaderFavoriteArtists',
types: "MusicArtist", types: 'MusicArtist',
id: "favoriteArtists", id: 'favoriteArtists',
shape: getSquareShape(), shape: getSquareShape(),
preferThumb: false, preferThumb: false,
showTitle: true, showTitle: true,
@ -66,9 +66,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
overlayPlayButton: true, overlayPlayButton: true,
coverImage: true coverImage: true
}, { }, {
name: "HeaderFavoriteAlbums", name: 'HeaderFavoriteAlbums',
types: "MusicAlbum", types: 'MusicAlbum',
id: "favoriteAlbums", id: 'favoriteAlbums',
shape: getSquareShape(), shape: getSquareShape(),
preferThumb: false, preferThumb: false,
showTitle: true, showTitle: true,
@ -78,9 +78,9 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
overlayPlayButton: true, overlayPlayButton: true,
coverImage: true coverImage: true
}, { }, {
name: "HeaderFavoriteSongs", name: 'HeaderFavoriteSongs',
types: "Audio", types: 'Audio',
id: "favoriteSongs", id: 'favoriteSongs',
shape: getSquareShape(), shape: getSquareShape(),
preferThumb: false, preferThumb: false,
showTitle: true, showTitle: true,
@ -88,7 +88,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
showParentTitle: true, showParentTitle: true,
centerText: true, centerText: true,
overlayMoreButton: true, overlayMoreButton: true,
action: "instantmix", action: 'instantmix',
coverImage: true coverImage: true
}]; }];
} }
@ -96,13 +96,13 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
function loadSection(elem, userId, topParentId, section, isSingleSection) { function loadSection(elem, userId, topParentId, section, isSingleSection) {
var screenWidth = dom.getWindowSize().innerWidth; var screenWidth = dom.getWindowSize().innerWidth;
var options = { var options = {
SortBy: "SortName", SortBy: 'SortName',
SortOrder: "Ascending", SortOrder: 'Ascending',
Filters: "IsFavorite", Filters: 'IsFavorite',
Recursive: true, Recursive: true,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo", Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
CollapseBoxSetItems: false, CollapseBoxSetItems: false,
ExcludeLocationTypes: "Virtual", ExcludeLocationTypes: 'Virtual',
EnableTotalRecordCount: false EnableTotalRecordCount: false
}; };
@ -120,7 +120,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
var promise; var promise;
if ("MusicArtist" === section.types) { if ('MusicArtist' === section.types) {
promise = ApiClient.getArtists(userId, options); promise = ApiClient.getArtists(userId, options);
} else { } else {
options.IncludeItemTypes = section.types; options.IncludeItemTypes = section.types;
@ -128,25 +128,25 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
} }
return promise.then(function (result) { return promise.then(function (result) {
var html = ""; var html = '';
if (result.Items.length) { if (result.Items.length) {
if (html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', !layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { if (html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', !layoutManager.tv && options.Limit && result.Items.length >= options.Limit) {
html += '<a is="emby-linkbutton" href="' + ("list.html?serverId=" + ApiClient.serverId() + "&type=" + section.types + "&IsFavorite=true") + '" class="more button-flat button-flat-mini sectionTitleTextButton">'; html += '<a is="emby-linkbutton" href="' + ('list.html?serverId=' + ApiClient.serverId() + '&type=' + section.types + '&IsFavorite=true') + '" class="more button-flat button-flat-mini sectionTitleTextButton">';
html += '<h2 class="sectionTitle sectionTitle-cards">'; html += '<h2 class="sectionTitle sectionTitle-cards">';
html += globalize.translate(section.name); html += globalize.translate(section.name);
html += "</h2>"; html += '</h2>';
html += '<i class="md-icon">chevron_right</i>'; html += '<span class="material-icons chevron_right"></span>';
html += "</a>"; html += '</a>';
} else { } else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + "</h2>"; html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';
} }
html += "</div>"; html += '</div>';
if (enableScrollX()) { if (enableScrollX()) {
var scrollXClass = "scrollX hiddenScrollX"; var scrollXClass = 'scrollX hiddenScrollX';
if (layoutManager.tv) { if (layoutManager.tv) {
scrollXClass += " smoothScrollX"; scrollXClass += ' smoothScrollX';
} }
html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' padded-left padded-right">'; html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' padded-left padded-right">';
@ -154,7 +154,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">'; html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
} }
var supportsImageAnalysis = appHost.supports("imageanalysis"); var supportsImageAnalysis = appHost.supports('imageanalysis');
var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle; var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle;
cardLayout = false; cardLayout = false;
html += cardBuilder.getCardsHtml(result.Items, { html += cardBuilder.getCardsHtml(result.Items, {
@ -172,7 +172,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
allowBottomPadding: !enableScrollX(), allowBottomPadding: !enableScrollX(),
cardLayout: cardLayout cardLayout: cardLayout
}); });
html += "</div>"; html += '</div>';
} }
elem.innerHTML = html; elem.innerHTML = html;
@ -183,7 +183,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
function loadSections(page, userId, topParentId, types) { function loadSections(page, userId, topParentId, types) {
loading.show(); loading.show();
var sections = getSections(); var sections = getSections();
var sectionid = getParameterByName("sectionid"); var sectionid = getParameterByName('sectionid');
if (sectionid) { if (sectionid) {
sections = sections.filter(function (s) { sections = sections.filter(function (s) {
@ -199,10 +199,10 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
var i; var i;
var length; var length;
var elem = page.querySelector(".favoriteSections"); var elem = page.querySelector('.favoriteSections');
if (!elem.innerHTML) { if (!elem.innerHTML) {
var html = ""; var html = '';
for (i = 0, length = sections.length; i < length; i++) { for (i = 0, length = sections.length; i < length; i++) {
html += '<div class="verticalSection section' + sections[i].id + '"></div>'; html += '<div class="verticalSection section' + sections[i].id + '"></div>';
@ -215,7 +215,7 @@ define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoad
for (i = 0, length = sections.length; i < length; i++) { for (i = 0, length = sections.length; i < length; i++) {
var section = sections[i]; var section = sections[i];
elem = page.querySelector(".section" + section.id); elem = page.querySelector('.section' + section.id);
promises.push(loadSection(elem, userId, topParentId, section, 1 === sections.length)); promises.push(loadSection(elem, userId, topParentId, section, 1 === sections.length));
} }

View file

@ -51,7 +51,7 @@ define([], function () {
function fetchWithTimeout(url, options, timeoutMs) { function fetchWithTimeout(url, options, timeoutMs) {
console.log('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url); console.debug('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url);
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -63,14 +63,14 @@ define([], function () {
fetch(url, options).then(function (response) { fetch(url, options).then(function (response) {
clearTimeout(timeout); clearTimeout(timeout);
console.log('fetchWithTimeout: succeeded connecting to url: ' + url); console.debug('fetchWithTimeout: succeeded connecting to url: ' + url);
resolve(response); resolve(response);
}, function (error) { }, function (error) {
clearTimeout(timeout); clearTimeout(timeout);
console.log('fetchWithTimeout: timed out connecting to url: ' + url); console.debug('fetchWithTimeout: timed out connecting to url: ' + url);
reject(); reject();
}); });
@ -86,28 +86,24 @@ define([], function () {
var value = params[key]; var value = params[key];
if (value !== null && value !== undefined && value !== '') { if (value !== null && value !== undefined && value !== '') {
values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); values.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
} }
} }
return values.join('&'); return values.join('&');
} }
function ajax(request) { function ajax(request) {
if (!request) { if (!request) {
throw new Error("Request cannot be null"); throw new Error('Request cannot be null');
} }
request.headers = request.headers || {}; request.headers = request.headers || {};
console.log('requesting url: ' + request.url); console.debug('requesting url: ' + request.url);
return getFetchPromise(request).then(function (response) { return getFetchPromise(request).then(function (response) {
console.debug('response status: ' + response.status + ', url: ' + request.url);
console.log('response status: ' + response.status + ', url: ' + request.url);
if (response.status < 400) { if (response.status < 400) {
if (request.dataType === 'json' || request.headers.accept === 'application/json') { if (request.dataType === 'json' || request.headers.accept === 'application/json') {
return response.json(); return response.json();
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) { } else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) {
@ -118,10 +114,8 @@ define([], function () {
} else { } else {
return Promise.reject(response); return Promise.reject(response);
} }
}, function (err) { }, function (err) {
console.error('request failed to url: ' + request.url);
console.log('request failed to url: ' + request.url);
throw err; throw err;
}); });
} }

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