Merge branch 'master' into feature-manually-add-subtitle
57
.ci/azure-pipelines-build.yml
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
jobs:
|
||||||
|
- job: Build
|
||||||
|
displayName: 'Build'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
Development:
|
||||||
|
BuildConfiguration: development
|
||||||
|
Production:
|
||||||
|
BuildConfiguration: production
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Install Node'
|
||||||
|
inputs:
|
||||||
|
versionSpec: '12.x'
|
||||||
|
|
||||||
|
- task: Cache@2
|
||||||
|
displayName: 'Cache node_modules'
|
||||||
|
inputs:
|
||||||
|
key: 'yarn | yarn.lock'
|
||||||
|
path: 'node_modules'
|
||||||
|
|
||||||
|
- script: 'yarn install --frozen-lockfile'
|
||||||
|
displayName: 'Install Dependencies'
|
||||||
|
env:
|
||||||
|
SKIP_PREPARE: 'true'
|
||||||
|
|
||||||
|
- script: 'yarn build:development'
|
||||||
|
displayName: 'Build Development'
|
||||||
|
condition: eq(variables['BuildConfiguration'], 'development')
|
||||||
|
|
||||||
|
- script: 'yarn build:production'
|
||||||
|
displayName: 'Build Production'
|
||||||
|
condition: eq(variables['BuildConfiguration'], 'production')
|
||||||
|
|
||||||
|
- script: 'test -d dist'
|
||||||
|
displayName: 'Check Build'
|
||||||
|
|
||||||
|
- script: 'mv dist jellyfin-web'
|
||||||
|
displayName: 'Rename Directory'
|
||||||
|
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
displayName: 'Archive Directory'
|
||||||
|
inputs:
|
||||||
|
rootFolderOrFile: 'jellyfin-web'
|
||||||
|
includeRootFolder: true
|
||||||
|
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish Release'
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
|
||||||
|
artifactName: 'jellyfin-web-$(BuildConfiguration)'
|
29
.ci/azure-pipelines-lint.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
jobs:
|
||||||
|
- job: Lint
|
||||||
|
displayName: 'Lint'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Install Node'
|
||||||
|
inputs:
|
||||||
|
versionSpec: '12.x'
|
||||||
|
|
||||||
|
- task: Cache@2
|
||||||
|
displayName: 'Cache node_modules'
|
||||||
|
inputs:
|
||||||
|
key: 'yarn | yarn.lock'
|
||||||
|
path: 'node_modules'
|
||||||
|
|
||||||
|
- script: 'yarn install --frozen-lockfile'
|
||||||
|
displayName: 'Install Dependencies'
|
||||||
|
env:
|
||||||
|
SKIP_PREPARE: 'true'
|
||||||
|
|
||||||
|
- script: 'yarn run lint --quiet'
|
||||||
|
displayName: 'Run ESLint'
|
||||||
|
|
||||||
|
- script: 'yarn run stylelint'
|
||||||
|
displayName: 'Run Stylelint'
|
122
.ci/azure-pipelines-package.yml
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
jobs:
|
||||||
|
- job: BuildPackage
|
||||||
|
displayName: 'Build Packages'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
CentOS:
|
||||||
|
BuildConfiguration: centos
|
||||||
|
Debian:
|
||||||
|
BuildConfiguration: debian
|
||||||
|
Fedora:
|
||||||
|
BuildConfiguration: fedora
|
||||||
|
Portable:
|
||||||
|
BuildConfiguration: portable
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
|
||||||
|
displayName: 'Build Dockerfile'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
|
||||||
|
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
|
||||||
|
displayName: 'Run Dockerfile (unstable)'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
|
||||||
|
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
|
||||||
|
displayName: 'Run Dockerfile (stable)'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish Release'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
|
||||||
|
artifactName: 'jellyfin-web-$(BuildConfiguration)'
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Create target directory on repository server'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'inline'
|
||||||
|
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
||||||
|
|
||||||
|
- task: CopyFilesOverSSH@0
|
||||||
|
displayName: 'Upload artifacts to repository server'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
|
||||||
|
contents: '**'
|
||||||
|
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
||||||
|
|
||||||
|
- job: BuildDocker
|
||||||
|
displayName: 'Build Docker'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: JellyfinVersion
|
||||||
|
value: 0.0.0
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||||
|
displayName: Set release version (stable)
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
|
|
||||||
|
- task: Docker@2
|
||||||
|
displayName: 'Push Unstable Image'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
repository: 'jellyfin/jellyfin-web'
|
||||||
|
command: buildAndPush
|
||||||
|
buildContext: '.'
|
||||||
|
Dockerfile: 'deployment/Dockerfile.docker'
|
||||||
|
containerRegistry: Docker Hub
|
||||||
|
tags: |
|
||||||
|
unstable-$(Build.BuildNumber)
|
||||||
|
unstable
|
||||||
|
|
||||||
|
- task: Docker@2
|
||||||
|
displayName: 'Push Stable Image'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
|
inputs:
|
||||||
|
repository: 'jellyfin/jellyfin-web'
|
||||||
|
command: buildAndPush
|
||||||
|
buildContext: '.'
|
||||||
|
Dockerfile: 'deployment/Dockerfile.docker'
|
||||||
|
containerRegistry: Docker Hub
|
||||||
|
tags: |
|
||||||
|
stable-$(Build.BuildNumber)
|
||||||
|
$(JellyfinVersion)
|
||||||
|
|
||||||
|
- job: CollectArtifacts
|
||||||
|
displayName: 'Collect Artifacts'
|
||||||
|
dependsOn:
|
||||||
|
- BuildPackage
|
||||||
|
- BuildDocker
|
||||||
|
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Update Unstable Repository'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'inline'
|
||||||
|
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable'
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Update Stable Repository'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'inline'
|
||||||
|
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'
|
|
@ -12,94 +12,6 @@ pr:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Build
|
- template: azure-pipelines-build.yml
|
||||||
displayName: 'Build'
|
- template: azure-pipelines-lint.yml
|
||||||
|
- template: azure-pipelines-package.yml
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Development:
|
|
||||||
BuildConfiguration: development
|
|
||||||
Production:
|
|
||||||
BuildConfiguration: production
|
|
||||||
Standalone:
|
|
||||||
BuildConfiguration: standalone
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: NodeTool@0
|
|
||||||
displayName: 'Install Node'
|
|
||||||
inputs:
|
|
||||||
versionSpec: '12.x'
|
|
||||||
|
|
||||||
- task: Cache@2
|
|
||||||
displayName: 'Check Cache'
|
|
||||||
inputs:
|
|
||||||
key: 'yarn | yarn.lock'
|
|
||||||
path: 'node_modules'
|
|
||||||
cacheHitVar: CACHE_RESTORED
|
|
||||||
|
|
||||||
- script: 'yarn install --frozen-lockfile'
|
|
||||||
displayName: 'Install Dependencies'
|
|
||||||
condition: ne(variables.CACHE_RESTORED, 'true')
|
|
||||||
|
|
||||||
- script: 'yarn build:development'
|
|
||||||
displayName: 'Build Development'
|
|
||||||
condition: eq(variables['BuildConfiguration'], 'development')
|
|
||||||
|
|
||||||
- script: 'yarn build:production'
|
|
||||||
displayName: 'Build 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
|
|
||||||
displayName: 'Publish Release'
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
|
|
||||||
artifactName: 'jellyfin-web-$(BuildConfiguration)'
|
|
||||||
|
|
||||||
- job: Lint
|
|
||||||
displayName: 'Lint'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: NodeTool@0
|
|
||||||
displayName: 'Install Node'
|
|
||||||
inputs:
|
|
||||||
versionSpec: '12.x'
|
|
||||||
|
|
||||||
- task: Cache@2
|
|
||||||
displayName: 'Check Cache'
|
|
||||||
inputs:
|
|
||||||
key: 'yarn | yarn.lock'
|
|
||||||
path: 'node_modules'
|
|
||||||
cacheHitVar: CACHE_RESTORED
|
|
||||||
|
|
||||||
- script: 'yarn install --frozen-lockfile'
|
|
||||||
displayName: 'Install Dependencies'
|
|
||||||
condition: ne(variables.CACHE_RESTORED, 'true')
|
|
||||||
|
|
||||||
- script: 'yarn run lint --quiet'
|
|
||||||
displayName: 'Run ESLint'
|
|
||||||
|
|
||||||
- script: 'yarn run stylelint'
|
|
||||||
displayName: 'Run Stylelint'
|
|
||||||
|
|
|
@ -8,5 +8,5 @@ trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
|
||||||
[json]
|
[*.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
|
@ -2,4 +2,3 @@ node_modules
|
||||||
dist
|
dist
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
src/libraries
|
|
||||||
|
|
62
.eslintrc.js
|
@ -1,6 +1,9 @@
|
||||||
|
const restrictedGlobals = require('confusing-browser-globals');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'@babel',
|
||||||
'promise',
|
'promise',
|
||||||
'import',
|
'import',
|
||||||
'eslint-comments'
|
'eslint-comments'
|
||||||
|
@ -22,33 +25,40 @@ module.exports = {
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
// 'plugin:promise/recommended',
|
// 'plugin:promise/recommended',
|
||||||
'plugin:import/errors',
|
'plugin:import/errors',
|
||||||
'plugin:import/warnings',
|
|
||||||
'plugin:eslint-comments/recommended',
|
'plugin:eslint-comments/recommended',
|
||||||
'plugin:compat/recommended'
|
'plugin:compat/recommended'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'block-spacing': ["error"],
|
'block-spacing': ['error'],
|
||||||
'brace-style': ["error"],
|
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
|
||||||
'comma-dangle': ["error", "never"],
|
'comma-dangle': ['error', 'never'],
|
||||||
'comma-spacing': ["error"],
|
'comma-spacing': ['error'],
|
||||||
'eol-last': ["error"],
|
'eol-last': ['error'],
|
||||||
'indent': ["error", 4, { "SwitchCase": 1 }],
|
'indent': ['error', 4, { 'SwitchCase': 1 }],
|
||||||
'keyword-spacing': ["error"],
|
'keyword-spacing': ['error'],
|
||||||
'max-statements-per-line': ["error"],
|
'max-statements-per-line': ['error'],
|
||||||
'no-floating-decimal': ["error"],
|
'no-floating-decimal': ['error'],
|
||||||
'no-multi-spaces': ["error"],
|
'no-multi-spaces': ['error'],
|
||||||
'no-multiple-empty-lines': ["error", { "max": 1 }],
|
'no-multiple-empty-lines': ['error', { 'max': 1 }],
|
||||||
'no-trailing-spaces': ["error"],
|
'no-restricted-globals': ['error'].concat(restrictedGlobals),
|
||||||
'one-var': ["error", "never"],
|
'no-trailing-spaces': ['error'],
|
||||||
'quotes': ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": false }],
|
'@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
|
||||||
'semi': ["error"],
|
'one-var': ['error', 'never'],
|
||||||
'space-before-blocks': ["error"]
|
'padded-blocks': ['error', 'never'],
|
||||||
|
'prefer-const': ['error', {'destructuring': 'all'}],
|
||||||
|
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
|
||||||
|
'@babel/semi': ['error'],
|
||||||
|
'no-var': ['error'],
|
||||||
|
'space-before-blocks': ['error'],
|
||||||
|
'space-infix-ops': 'error',
|
||||||
|
'yoda': 'error'
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
'./src/**/*.js'
|
'./src/**/*.js'
|
||||||
],
|
],
|
||||||
|
parser: '@babel/eslint-parser',
|
||||||
env: {
|
env: {
|
||||||
node: false,
|
node: false,
|
||||||
amd: true,
|
amd: true,
|
||||||
|
@ -68,17 +78,12 @@ module.exports = {
|
||||||
// Dependency globals
|
// Dependency globals
|
||||||
'$': 'readonly',
|
'$': 'readonly',
|
||||||
'jQuery': 'readonly',
|
'jQuery': 'readonly',
|
||||||
'requirejs': 'readonly',
|
|
||||||
// Jellyfin globals
|
// Jellyfin globals
|
||||||
'ApiClient': 'writable',
|
'ApiClient': 'writable',
|
||||||
'AppInfo': 'writable',
|
|
||||||
'chrome': 'writable',
|
'chrome': 'writable',
|
||||||
'ConnectionManager': 'writable',
|
|
||||||
'DlnaProfilePage': 'writable',
|
'DlnaProfilePage': 'writable',
|
||||||
'Dashboard': 'writable',
|
|
||||||
'DashboardPage': 'writable',
|
'DashboardPage': 'writable',
|
||||||
'Emby': 'readonly',
|
'Emby': 'readonly',
|
||||||
'Events': 'writable',
|
|
||||||
'getParameterByName': 'writable',
|
'getParameterByName': 'writable',
|
||||||
'getWindowLocationSearch': 'writable',
|
'getWindowLocationSearch': 'writable',
|
||||||
'Globalize': 'writable',
|
'Globalize': 'writable',
|
||||||
|
@ -88,19 +93,17 @@ module.exports = {
|
||||||
'LinkParser': 'writable',
|
'LinkParser': 'writable',
|
||||||
'LiveTvHelpers': 'writable',
|
'LiveTvHelpers': 'writable',
|
||||||
'MetadataEditor': 'writable',
|
'MetadataEditor': 'writable',
|
||||||
'pageClassOn': 'writable',
|
|
||||||
'pageIdOn': 'writable',
|
|
||||||
'PlaylistViewer': 'writable',
|
'PlaylistViewer': 'writable',
|
||||||
'UserParentalControlPage': 'writable',
|
'UserParentalControlPage': 'writable',
|
||||||
'Windows': 'readonly'
|
'Windows': 'readonly'
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
// TODO: Fix warnings and remove these rules
|
// TODO: Fix warnings and remove these rules
|
||||||
'no-redeclare': ["warn"],
|
'no-redeclare': ['off'],
|
||||||
'no-unused-vars': ["warn"],
|
'no-useless-escape': ['off'],
|
||||||
'no-useless-escape': ["warn"],
|
'no-unused-vars': ['off'],
|
||||||
// TODO: Remove after ES6 migration is complete
|
// TODO: Remove after ES6 migration is complete
|
||||||
'import/no-unresolved': ["off"]
|
'import/no-unresolved': ['off']
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
polyfills: [
|
polyfills: [
|
||||||
|
@ -130,6 +133,7 @@ module.exports = {
|
||||||
'Object.getOwnPropertyDescriptor',
|
'Object.getOwnPropertyDescriptor',
|
||||||
'Object.getPrototypeOf',
|
'Object.getPrototypeOf',
|
||||||
'Object.keys',
|
'Object.keys',
|
||||||
|
'Object.entries',
|
||||||
'Object.getOwnPropertyNames',
|
'Object.getOwnPropertyNames',
|
||||||
'Function.name',
|
'Function.name',
|
||||||
'Function.hasInstance',
|
'Function.hasInstance',
|
||||||
|
@ -190,4 +194,4 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
|
|
4
.github/CODEOWNERS
vendored
|
@ -1,4 +1,6 @@
|
||||||
.ci @dkanada @EraYaN
|
.ci @dkanada @EraYaN
|
||||||
.github @jellyfin/core
|
.github @jellyfin/core
|
||||||
build.sh @joshuaboniface
|
fedora @joshuaboniface
|
||||||
|
debian @joshuaboniface
|
||||||
|
.copr @joshuaboniface
|
||||||
deployment @joshuaboniface
|
deployment @joshuaboniface
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug Report
|
||||||
about: Create a bug report
|
about: You have noticed a general issue or regression, and would like to report it
|
||||||
title: ''
|
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe The Bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
**To Reproduce**
|
**Steps To Reproduce**
|
||||||
<!-- Steps to reproduce the behavior: -->
|
<!-- Steps to reproduce the behavior: -->
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected Behavior**
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
**Logs**
|
**Logs**
|
||||||
|
@ -27,9 +24,9 @@ assignees: ''
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
**System (please complete the following information):**
|
**System (please complete the following information):**
|
||||||
- OS: [e.g. Docker, Debian, Windows]
|
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
|
||||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||||
- Jellyfin Version: [e.g. 10.0.1]
|
- Jellyfin Version: [e.g. 10.6.0]
|
||||||
|
|
||||||
**Additional context**
|
**Additional Context**
|
||||||
<!-- Add any other context about the problem here. -->
|
<!-- Add any other context about the problem here. -->
|
22
.github/ISSUE_TEMPLATE/2-playback-issue.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
name: Playback Issue
|
||||||
|
about: You have playback issues with some files
|
||||||
|
labels: playback
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe The Bug**
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Media Information**
|
||||||
|
<!-- Please paste any ffprobe or MediaInfo logs. -->
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
<!-- Add screenshots from the Playback Data and Media Info. -->
|
||||||
|
|
||||||
|
**System (please complete the following information):**
|
||||||
|
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
|
||||||
|
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||||
|
- Jellyfin Version: [e.g. 10.6.0]
|
||||||
|
|
||||||
|
**Additional Context**
|
||||||
|
<!-- Add any other context about the problem here. -->
|
13
.github/ISSUE_TEMPLATE/3-technical-discussion.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
name: Technical Discussion
|
||||||
|
about: You want to discuss technical aspects of changes you intend to make
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Explain the change and the motivations behind it.
|
||||||
|
|
||||||
|
For example, if you plan to rely on a new dependency, explain why and what
|
||||||
|
it brings to the project.
|
||||||
|
|
||||||
|
If you plan to make significant changes, go roughly over the steps you intend
|
||||||
|
to take and how you would divide the change in PRs of a manageable size. -->
|
9
.github/ISSUE_TEMPLATE/4-meta-issue.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
name: Meta Issue
|
||||||
|
about: You want to track a number of other issues as part of a larger project
|
||||||
|
labels: meta
|
||||||
|
---
|
||||||
|
|
||||||
|
* [ ] Issue 1 [#123]
|
||||||
|
* [ ] Issue 2 [#456]
|
||||||
|
* [ ] ...
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Feature Request
|
||||||
|
url: https://features.jellyfin.org/
|
||||||
|
about: Please head over to our feature request hub to vote on or submit a feature.
|
||||||
|
- name: Help Or Question
|
||||||
|
url: https://matrix.to/#/#jellyfin-troubleshooting:matrix.org
|
||||||
|
about: Please join the troubleshooting Matrix channel to get some help.
|
24
.github/SUPPORT.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Support
|
||||||
|
|
||||||
|
Jellyfin contributors have limited availability to address general support
|
||||||
|
questions. Please make sure you are using the latest version of Jellyfin.
|
||||||
|
|
||||||
|
When looking for support or information, please first search for your
|
||||||
|
question in these venues:
|
||||||
|
|
||||||
|
* [Jellyfin Forum](https://forum.jellyfin.org)
|
||||||
|
* [Jellyfin Documentation](https://docs.jellyfin.org)
|
||||||
|
* [Open or **closed** issues in the organization](https://github.com/issues?q=sort%3Aupdated-desc+org%3Ajellyfin+is%3Aissue+)
|
||||||
|
|
||||||
|
If you didn't find an answer in the resources above, contributors and other
|
||||||
|
users are reachable through the following channels:
|
||||||
|
|
||||||
|
* #jellyfin on [Matrix](https://matrix.to/#/#jellyfin:matrix.org%22) or [IRC](https://webchat.freenode.net/#jellyfin)
|
||||||
|
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](https://webchat.freenode.net/#jellyfin-troubleshooting)
|
||||||
|
* [/r/jellyfin on Reddit](https://www.reddit.com/r/jellyfin)
|
||||||
|
|
||||||
|
GitHub issues are for tracking enhancements and bugs, not general support.
|
||||||
|
|
||||||
|
The open source license grants you the freedom to use Jellyfin.
|
||||||
|
It does not guarantee commitments of other people's time.
|
||||||
|
Please be respectful and manage your expectations.
|
31
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '30 7 * * 6'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript' ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
queries: +security-extended
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
8
.gitignore
vendored
|
@ -1,14 +1,14 @@
|
||||||
# config
|
|
||||||
config.json
|
|
||||||
|
|
||||||
# npm
|
# npm
|
||||||
dist
|
dist
|
||||||
web
|
web
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# config
|
||||||
|
config.json
|
||||||
|
|
||||||
# ide
|
# ide
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
#log
|
# log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
|
@ -34,7 +34,13 @@
|
||||||
- [Ryan Hartzell](https://github.com/ryan-hartzell)
|
- [Ryan Hartzell](https://github.com/ryan-hartzell)
|
||||||
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
|
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
|
||||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||||
|
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||||
- [Sarab Singh](https://github.com/sarab97)
|
- [Sarab Singh](https://github.com/sarab97)
|
||||||
|
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
|
||||||
|
- [Andrei Oanca](https://github.com/OancaAndrei)
|
||||||
|
- [Cromefire_](https://github.com/cromefire)
|
||||||
|
- [Orry Verducci](https://github.com/orryverducci)
|
||||||
|
- [Camc314](https://github.com/camc314)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
- Yarn
|
- [Node.js](https://nodejs.org/en/download/)
|
||||||
|
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
|
||||||
- Gulp-cli
|
- Gulp-cli
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
@ -78,4 +79,4 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn build:standalone
|
yarn build:standalone
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
# We just wrap `build` so this is really it
|
# We just wrap `build` so this is really it
|
||||||
name: "jellyfin-web"
|
name: "jellyfin-web"
|
||||||
version: "10.6.0"
|
version: "10.7.0"
|
||||||
packages:
|
packages:
|
||||||
- debian.all
|
- debian.all
|
||||||
- fedora.all
|
- fedora.all
|
||||||
|
|
23
bump_version
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
set -o xtrace
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo -e "bump_version - increase the shared version and generate changelogs"
|
echo -e "bump_version - increase the shared version and generate changelogs"
|
||||||
|
@ -23,10 +24,7 @@ build_file="./build.yaml"
|
||||||
new_version="$1"
|
new_version="$1"
|
||||||
|
|
||||||
# Parse the version from shared version file
|
# Parse the version from shared version file
|
||||||
old_version="$(
|
old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )"
|
||||||
grep "appVersion" ${shared_version_file} | head -1 \
|
|
||||||
| sed -E 's/var appVersion = "([0-9\.]+)";/\1/'
|
|
||||||
)"
|
|
||||||
echo "Old version in appHost is: $old_version"
|
echo "Old version in appHost is: $old_version"
|
||||||
|
|
||||||
# Set the shared version to the specified new_version
|
# Set the shared version to the specified new_version
|
||||||
|
@ -34,11 +32,8 @@ old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' cha
|
||||||
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
|
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
|
||||||
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
|
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
|
||||||
|
|
||||||
old_version="$(
|
old_version="$( grep "version:" ${build_file} | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )"
|
||||||
grep "version:" ${build_file} \
|
echo "Old version in ${build_file}: ${old_version}"
|
||||||
| 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
|
# Set the build.yaml version to the specified new_version
|
||||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||||
|
@ -54,7 +49,7 @@ fi
|
||||||
debian_changelog_file="debian/changelog"
|
debian_changelog_file="debian/changelog"
|
||||||
debian_changelog_temp="$( mktemp )"
|
debian_changelog_temp="$( mktemp )"
|
||||||
# Create new temp file with our changelog
|
# Create new temp file with our changelog
|
||||||
echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium
|
echo -e "jellyfin-web (${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}
|
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}
|
||||||
|
|
||||||
|
@ -65,15 +60,15 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp}
|
||||||
mv ${debian_changelog_temp} ${debian_changelog_file}
|
mv ${debian_changelog_temp} ${debian_changelog_file}
|
||||||
|
|
||||||
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
|
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
|
||||||
fedora_spec_file="fedora/jellyfin.spec"
|
fedora_spec_file="fedora/jellyfin-web.spec"
|
||||||
fedora_changelog_temp="$( mktemp )"
|
fedora_changelog_temp="$( mktemp )"
|
||||||
fedora_spec_temp_dir="$( mktemp -d )"
|
fedora_spec_temp_dir="$( mktemp -d )"
|
||||||
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp"
|
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin-web.spec.tmp"
|
||||||
# Make a copy of our spec file for hacking
|
# Make a copy of our spec file for hacking
|
||||||
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
|
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
|
||||||
pushd ${fedora_spec_temp_dir}
|
pushd ${fedora_spec_temp_dir}
|
||||||
# Split out the stuff before and after changelog
|
# Split out the stuff before and after changelog
|
||||||
csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01
|
csplit jellyfin-web.spec "/^%changelog/" # produces xx00 xx01
|
||||||
# Update the version in xx00
|
# Update the version in xx00
|
||||||
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
|
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
|
||||||
# Remove the header from xx01
|
# Remove the header from xx01
|
||||||
|
@ -92,5 +87,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file}
|
||||||
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
|
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
|
||||||
|
|
||||||
# Stage the changed files for commit
|
# Stage the changed files for commit
|
||||||
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile*
|
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
|
||||||
git status
|
git status
|
||||||
|
|
6
debian/changelog
vendored
|
@ -1,3 +1,9 @@
|
||||||
|
jellyfin-web (10.7.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Forthcoming stable release
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:13:31 -0400
|
||||||
|
|
||||||
jellyfin-web (10.6.0-1) unstable; urgency=medium
|
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
|
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
|
||||||
|
|
1
debian/conffiles
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/usr/share/jellyfin/web/config.json
|
|
@ -1,7 +1,9 @@
|
||||||
FROM centos:7
|
FROM centos:7
|
||||||
|
|
||||||
# Docker build arguments
|
# Docker build arguments
|
||||||
ARG SOURCE_DIR=/jellyfin
|
ARG SOURCE_DIR=/jellyfin
|
||||||
ARG ARTIFACT_DIR=/dist
|
ARG ARTIFACT_DIR=/dist
|
||||||
|
|
||||||
# Docker run environment
|
# Docker run environment
|
||||||
ENV SOURCE_DIR=/jellyfin
|
ENV SOURCE_DIR=/jellyfin
|
||||||
ENV ARTIFACT_DIR=/dist
|
ENV ARTIFACT_DIR=/dist
|
||||||
|
@ -9,19 +11,19 @@ ENV IS_DOCKER=YES
|
||||||
|
|
||||||
# Prepare CentOS environment
|
# Prepare CentOS environment
|
||||||
RUN yum update -y \
|
RUN yum update -y \
|
||||||
&& yum install -y epel-release \
|
&& yum install -y epel-release \
|
||||||
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
|
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
|
||||||
|
|
||||||
# Install recent NodeJS and Yarn
|
# Install recent NodeJS and Yarn
|
||||||
RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
|
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 \
|
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
|
||||||
&& yum install -y yarn
|
&& yum install -y yarn
|
||||||
|
|
||||||
# Link to build script
|
# Link to build script
|
||||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.all /build.sh
|
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh
|
||||||
|
|
||||||
VOLUME ${SOURCE_DIR}/
|
VOLUME ${SOURCE_DIR}
|
||||||
|
|
||||||
VOLUME ${ARTIFACT_DIR}/
|
VOLUME ${ARTIFACT_DIR}
|
||||||
|
|
||||||
ENTRYPOINT ["/build.sh"]
|
ENTRYPOINT ["/build.sh"]
|
|
@ -1,7 +1,9 @@
|
||||||
FROM debian:10
|
FROM debian:10
|
||||||
|
|
||||||
# Docker build arguments
|
# Docker build arguments
|
||||||
ARG SOURCE_DIR=/jellyfin
|
ARG SOURCE_DIR=/jellyfin
|
||||||
ARG ARTIFACT_DIR=/dist
|
ARG ARTIFACT_DIR=/dist
|
||||||
|
|
||||||
# Docker run environment
|
# Docker run environment
|
||||||
ENV SOURCE_DIR=/jellyfin
|
ENV SOURCE_DIR=/jellyfin
|
||||||
ENV ARTIFACT_DIR=/dist
|
ENV ARTIFACT_DIR=/dist
|
||||||
|
@ -10,16 +12,16 @@ ENV IS_DOCKER=YES
|
||||||
|
|
||||||
# Prepare Debian build environment
|
# Prepare Debian build environment
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y debhelper mmv npm git
|
&& apt-get install -y debhelper mmv npm git
|
||||||
|
|
||||||
# Prepare Yarn
|
# Prepare Yarn
|
||||||
RUN npm install -g yarn
|
RUN npm install -g yarn
|
||||||
|
|
||||||
# Link to build script
|
# Link to build script
|
||||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.all /build.sh
|
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh
|
||||||
|
|
||||||
VOLUME ${SOURCE_DIR}/
|
VOLUME ${SOURCE_DIR}
|
||||||
|
|
||||||
VOLUME ${ARTIFACT_DIR}/
|
VOLUME ${ARTIFACT_DIR}
|
||||||
|
|
||||||
ENTRYPOINT ["/build.sh"]
|
ENTRYPOINT ["/build.sh"]
|
11
deployment/Dockerfile.docker
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
FROM node:alpine
|
||||||
|
|
||||||
|
ARG SOURCE_DIR=/src
|
||||||
|
ARG ARTIFACT_DIR=/jellyfin-web
|
||||||
|
|
||||||
|
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python
|
||||||
|
|
||||||
|
WORKDIR ${SOURCE_DIR}
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn install && mv dist ${ARTIFACT_DIR}
|
|
@ -1,7 +1,9 @@
|
||||||
FROM fedora:31
|
FROM fedora:31
|
||||||
|
|
||||||
# Docker build arguments
|
# Docker build arguments
|
||||||
ARG SOURCE_DIR=/jellyfin
|
ARG SOURCE_DIR=/jellyfin
|
||||||
ARG ARTIFACT_DIR=/dist
|
ARG ARTIFACT_DIR=/dist
|
||||||
|
|
||||||
# Docker run environment
|
# Docker run environment
|
||||||
ENV SOURCE_DIR=/jellyfin
|
ENV SOURCE_DIR=/jellyfin
|
||||||
ENV ARTIFACT_DIR=/dist
|
ENV ARTIFACT_DIR=/dist
|
||||||
|
@ -9,13 +11,13 @@ ENV IS_DOCKER=YES
|
||||||
|
|
||||||
# Prepare Fedora environment
|
# Prepare Fedora environment
|
||||||
RUN dnf update -y \
|
RUN dnf update -y \
|
||||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel
|
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel
|
||||||
|
|
||||||
# Link to build script
|
# Link to build script
|
||||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.all /build.sh
|
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
|
||||||
|
|
||||||
VOLUME ${SOURCE_DIR}/
|
VOLUME ${SOURCE_DIR}
|
||||||
|
|
||||||
VOLUME ${ARTIFACT_DIR}/
|
VOLUME ${ARTIFACT_DIR}
|
||||||
|
|
||||||
ENTRYPOINT ["/build.sh"]
|
ENTRYPOINT ["/build.sh"]
|
|
@ -1,16 +1,17 @@
|
||||||
FROM debian:10
|
FROM debian:10
|
||||||
|
|
||||||
# Docker build arguments
|
# Docker build arguments
|
||||||
ARG SOURCE_DIR=/jellyfin
|
ARG SOURCE_DIR=/jellyfin
|
||||||
ARG ARTIFACT_DIR=/dist
|
ARG ARTIFACT_DIR=/dist
|
||||||
|
|
||||||
# Docker run environment
|
# Docker run environment
|
||||||
ENV SOURCE_DIR=/jellyfin
|
ENV SOURCE_DIR=/jellyfin
|
||||||
ENV ARTIFACT_DIR=/dist
|
ENV ARTIFACT_DIR=/dist
|
||||||
ENV DEB_BUILD_OPTIONS=noddebs
|
|
||||||
ENV IS_DOCKER=YES
|
ENV IS_DOCKER=YES
|
||||||
|
|
||||||
# Prepare Debian build environment
|
# Prepare Debian build environment
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y mmv npm git
|
&& apt-get install -y mmv npm git
|
||||||
|
|
||||||
# Prepare Yarn
|
# Prepare Yarn
|
||||||
RUN npm install -g yarn
|
RUN npm install -g yarn
|
||||||
|
@ -18,8 +19,8 @@ RUN npm install -g yarn
|
||||||
# Link to build script
|
# Link to build script
|
||||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
|
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
|
||||||
|
|
||||||
VOLUME ${SOURCE_DIR}/
|
VOLUME ${SOURCE_DIR}
|
||||||
|
|
||||||
VOLUME ${ARTIFACT_DIR}/
|
VOLUME ${ARTIFACT_DIR}
|
||||||
|
|
||||||
ENTRYPOINT ["/build.sh"]
|
ENTRYPOINT ["/build.sh"]
|
||||||
|
|
41
deployment/build.centos
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o xtrace
|
||||||
|
|
||||||
|
# move to source directory
|
||||||
|
pushd ${SOURCE_DIR}
|
||||||
|
|
||||||
|
cp -a yarn.lock /tmp/yarn.lock
|
||||||
|
|
||||||
|
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||||
|
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||||
|
pushd fedora
|
||||||
|
|
||||||
|
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
|
||||||
|
|
||||||
|
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
|
||||||
|
sed -i "/%changelog/q" jellyfin-web.spec
|
||||||
|
|
||||||
|
cat <<EOF >>jellyfin-web.spec
|
||||||
|
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
|
||||||
|
EOF
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build rpm
|
||||||
|
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
|
||||||
|
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
|
||||||
|
|
||||||
|
# move the artifacts
|
||||||
|
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
|
||||||
|
|
||||||
|
if [[ ${IS_DOCKER} == YES ]]; then
|
||||||
|
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f fedora/jellyfin*.tar.gz
|
||||||
|
cp -a /tmp/yarn.lock yarn.lock
|
||||||
|
|
||||||
|
popd
|
|
@ -1,27 +0,0 @@
|
||||||
#!/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
|
|
39
deployment/build.debian
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o xtrace
|
||||||
|
|
||||||
|
# move to source directory
|
||||||
|
pushd ${SOURCE_DIR}
|
||||||
|
|
||||||
|
cp -a yarn.lock /tmp/yarn.lock
|
||||||
|
|
||||||
|
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||||
|
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||||
|
pushd debian
|
||||||
|
|
||||||
|
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
|
||||||
|
|
||||||
|
cat <<EOF >changelog
|
||||||
|
jellyfin-web (${BUILD_ID}-unstable) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
|
||||||
|
EOF
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build deb
|
||||||
|
dpkg-buildpackage -us -uc --pre-clean --post-clean
|
||||||
|
|
||||||
|
mkdir -p ${ARTIFACT_DIR}
|
||||||
|
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}
|
||||||
|
|
||||||
|
cp -a /tmp/yarn.lock yarn.lock
|
||||||
|
|
||||||
|
if [[ ${IS_DOCKER} == YES ]]; then
|
||||||
|
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd
|
|
@ -1,25 +0,0 @@
|
||||||
#!/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
|
|
41
deployment/build.fedora
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o xtrace
|
||||||
|
|
||||||
|
# move to source directory
|
||||||
|
pushd ${SOURCE_DIR}
|
||||||
|
|
||||||
|
cp -a yarn.lock /tmp/yarn.lock
|
||||||
|
|
||||||
|
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||||
|
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||||
|
pushd fedora
|
||||||
|
|
||||||
|
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
|
||||||
|
|
||||||
|
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
|
||||||
|
sed -i "/%changelog/q" jellyfin-web.spec
|
||||||
|
|
||||||
|
cat <<EOF >>jellyfin-web.spec
|
||||||
|
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
|
||||||
|
EOF
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build rpm
|
||||||
|
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
|
||||||
|
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
|
||||||
|
|
||||||
|
# move the artifacts
|
||||||
|
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}
|
||||||
|
|
||||||
|
if [[ ${IS_DOCKER} == YES ]]; then
|
||||||
|
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f fedora/jellyfin*.tar.gz
|
||||||
|
cp -a /tmp/yarn.lock yarn.lock
|
||||||
|
|
||||||
|
popd
|
|
@ -1,27 +0,0 @@
|
||||||
#!/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
|
|
|
@ -1,25 +1,27 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#= Portable .NET DLL .tar.gz
|
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o xtrace
|
set -o xtrace
|
||||||
|
|
||||||
# Move to source directory
|
# move to source directory
|
||||||
pushd ${SOURCE_DIR}
|
pushd ${SOURCE_DIR}
|
||||||
|
|
||||||
# Get version
|
# get version
|
||||||
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
|
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||||
|
version="${BUILD_ID}"
|
||||||
|
else
|
||||||
|
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
|
||||||
|
fi
|
||||||
|
|
||||||
# Build archives
|
# build archives
|
||||||
npx yarn install
|
npx yarn install
|
||||||
mv dist/ jellyfin-web_${version}
|
mv dist jellyfin-web_${version}
|
||||||
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
|
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
|
||||||
rm -rf dist/
|
rm -rf dist
|
||||||
|
|
||||||
# Move the artifacts out
|
# move the artifacts
|
||||||
mkdir -p ${ARTIFACT_DIR}/
|
mkdir -p ${ARTIFACT_DIR}
|
||||||
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
|
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}
|
||||||
|
|
||||||
if [[ ${IS_DOCKER} == YES ]]; then
|
if [[ ${IS_DOCKER} == YES ]]; then
|
||||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
|
|
||||||
Name: jellyfin-web
|
Name: jellyfin-web
|
||||||
Version: 10.6.0
|
Version: 10.7.0
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: The Free Software Media System web client
|
Summary: The Free Software Media System web client
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
|
@ -12,8 +12,11 @@ Source0: jellyfin-web-%{version}.tar.gz
|
||||||
%if 0%{?centos}
|
%if 0%{?centos}
|
||||||
BuildRequires: yarn
|
BuildRequires: yarn
|
||||||
%else
|
%else
|
||||||
BuildRequires nodejs-yarn
|
BuildRequires: nodejs-yarn
|
||||||
%endif
|
%endif
|
||||||
|
# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it
|
||||||
|
# ditto for Fedora's yarn RPM
|
||||||
|
BuildRequires: git
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
# Disable Automatic Dependency Processing
|
# Disable Automatic Dependency Processing
|
||||||
|
@ -39,5 +42,7 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||||
%{_datadir}/licenses/jellyfin/LICENSE
|
%{_datadir}/licenses/jellyfin/LICENSE
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- Forthcoming stable release
|
||||||
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
- Forthcoming stable release
|
- Forthcoming stable release
|
||||||
|
|
17
gulpfile.js
|
@ -45,7 +45,7 @@ const options = {
|
||||||
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
|
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
|
||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
query: ['src/**/*.json', 'src/**/*.ico']
|
query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3']
|
||||||
},
|
},
|
||||||
injectBundle: {
|
injectBundle: {
|
||||||
query: 'src/index.html'
|
query: 'src/index.html'
|
||||||
|
@ -181,16 +181,15 @@ function copy(query) {
|
||||||
.pipe(browserSync.stream());
|
.pipe(browserSync.stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyIndex() {
|
|
||||||
return src(options.injectBundle.query, { base: './src/' })
|
|
||||||
.pipe(dest('dist/'))
|
|
||||||
.pipe(browserSync.stream());
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectBundle() {
|
function injectBundle() {
|
||||||
return src(options.injectBundle.query, { base: './src/' })
|
return src(options.injectBundle.query, { base: './src/' })
|
||||||
.pipe(inject(
|
.pipe(inject(
|
||||||
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
|
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), {
|
||||||
|
relative: true,
|
||||||
|
transform: function (filepath) {
|
||||||
|
return `<script src="${filepath}" defer></script>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
))
|
))
|
||||||
.pipe(dest('dist/'))
|
.pipe(dest('dist/'))
|
||||||
.pipe(browserSync.stream());
|
.pipe(browserSync.stream());
|
||||||
|
@ -200,6 +199,6 @@ function build(standalone) {
|
||||||
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy));
|
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.default = series(build(false), copyIndex);
|
exports.default = series(build(false), injectBundle);
|
||||||
exports.standalone = series(build(true), injectBundle);
|
exports.standalone = series(build(true), injectBundle);
|
||||||
exports.serve = series(exports.standalone, serve);
|
exports.serve = series(exports.standalone, serve);
|
||||||
|
|
150
package.json
|
@ -5,27 +5,31 @@
|
||||||
"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": {
|
||||||
"@babel/core": "^7.9.6",
|
"@babel/core": "^7.12.3",
|
||||||
"@babel/plugin-transform-modules-amd": "^7.9.6",
|
"@babel/eslint-parser": "^7.12.1",
|
||||||
"@babel/polyfill": "^7.8.7",
|
"@babel/eslint-plugin": "^7.12.1",
|
||||||
"@babel/preset-env": "^7.8.6",
|
"@babel/plugin-proposal-class-properties": "^7.10.1",
|
||||||
"autoprefixer": "^9.7.6",
|
"@babel/plugin-proposal-private-methods": "^7.12.1",
|
||||||
"babel-loader": "^8.0.6",
|
"@babel/preset-env": "^7.12.1",
|
||||||
"browser-sync": "^2.26.7",
|
"autoprefixer": "^9.8.6",
|
||||||
|
"babel-loader": "^8.2.1",
|
||||||
|
"browser-sync": "^2.26.13",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"copy-webpack-plugin": "^5.1.1",
|
"confusing-browser-globals": "^1.0.10",
|
||||||
"css-loader": "^3.4.2",
|
"copy-webpack-plugin": "^6.0.3",
|
||||||
|
"css-loader": "^5.0.1",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"del": "^5.1.0",
|
"del": "^6.0.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.13.0",
|
||||||
"eslint-plugin-compat": "^3.5.1",
|
"eslint-plugin-compat": "^3.5.1",
|
||||||
"eslint-plugin-eslint-comments": "^3.1.2",
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"file-loader": "^6.0.0",
|
"expose-loader": "^1.0.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
"gulp-cli": "^2.2.0",
|
"gulp-cli": "^2.3.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-htmlmin": "^5.0.1",
|
"gulp-htmlmin": "^5.0.1",
|
||||||
"gulp-if": "^3.0.0",
|
"gulp-if": "^3.0.0",
|
||||||
|
@ -34,88 +38,75 @@
|
||||||
"gulp-mode": "^1.0.2",
|
"gulp-mode": "^1.0.2",
|
||||||
"gulp-postcss": "^8.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-sourcemaps": "^2.6.5",
|
"gulp-sourcemaps": "^3.0.0",
|
||||||
"gulp-terser": "^1.2.0",
|
"gulp-terser": "^1.4.1",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-loader": "^1.1.0",
|
||||||
|
"html-webpack-plugin": "^4.5.0",
|
||||||
"lazypipe": "^1.0.2",
|
"lazypipe": "^1.0.2",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^5.0.0",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"style-loader": "^1.1.3",
|
"source-map-loader": "^1.1.1",
|
||||||
"stylelint": "^13.3.3",
|
"style-loader": "^2.0.0",
|
||||||
|
"stylelint": "^13.7.2",
|
||||||
"stylelint-config-rational-order": "^0.1.2",
|
"stylelint-config-rational-order": "^0.1.2",
|
||||||
"stylelint-no-browser-hacks": "^1.2.1",
|
"stylelint-no-browser-hacks": "^1.2.1",
|
||||||
"stylelint-order": "^4.0.0",
|
"stylelint-order": "^4.1.0",
|
||||||
"webpack": "^4.41.5",
|
"webpack": "^5.4.0",
|
||||||
"webpack-cli": "^3.3.10",
|
"webpack-cli": "^4.0.0",
|
||||||
"webpack-concat-plugin": "^3.0.0",
|
|
||||||
"webpack-dev-server": "^3.11.0",
|
"webpack-dev-server": "^3.11.0",
|
||||||
"webpack-merge": "^4.2.2",
|
"webpack-merge": "^4.2.2",
|
||||||
"webpack-stream": "^5.2.1"
|
"webpack-stream": "^6.1.1",
|
||||||
|
"workbox-webpack-plugin": "^5.1.4",
|
||||||
|
"worker-plugin": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"alameda": "^1.4.0",
|
"alameda": "^1.4.0",
|
||||||
|
"blurhash": "^1.1.3",
|
||||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.7.0",
|
||||||
"date-fns": "^2.13.0",
|
"date-fns": "^2.16.1",
|
||||||
"document-register-element": "^1.14.3",
|
"epubjs": "^0.3.85",
|
||||||
"fast-text-encoding": "^1.0.1",
|
"fast-text-encoding": "^1.0.3",
|
||||||
"flv.js": "^1.5.0",
|
"flv.js": "^1.5.0",
|
||||||
"headroom.js": "^0.11.0",
|
"headroom.js": "^0.12.0",
|
||||||
"hls.js": "^0.13.1",
|
"hls.js": "^0.14.16",
|
||||||
"howler": "^2.1.3",
|
"howler": "^2.2.1",
|
||||||
"intersection-observer": "^0.10.0",
|
"intersection-observer": "^0.11.0",
|
||||||
"jellyfin-apiclient": "^1.1.1",
|
"jellyfin-apiclient": "^1.4.2",
|
||||||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"jstree": "^3.3.7",
|
"jstree": "^3.3.10",
|
||||||
|
"libarchive.js": "^1.3.0",
|
||||||
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
|
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
|
||||||
"material-design-icons-iconfont": "^5.0.1",
|
"material-design-icons-iconfont": "^6.1.0",
|
||||||
"native-promise-only": "^0.8.0-a",
|
"native-promise-only": "^0.8.0-a",
|
||||||
"page": "^1.11.6",
|
"page": "^1.11.6",
|
||||||
"query-string": "^6.11.1",
|
"pdfjs-dist": "2.5.207",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"sass": "^1.29.0",
|
||||||
|
"sass-loader": "^10.0.5",
|
||||||
"screenfull": "^5.0.2",
|
"screenfull": "^5.0.2",
|
||||||
"shaka-player": "^2.5.11",
|
"sortablejs": "^1.12.0",
|
||||||
"sortablejs": "^1.10.2",
|
"swiper": "^6.3.5",
|
||||||
"swiper": "^5.3.7",
|
|
||||||
"webcomponents.js": "^0.7.24",
|
"webcomponents.js": "^0.7.24",
|
||||||
"whatwg-fetch": "^3.0.0"
|
"whatwg-fetch": "^3.5.0",
|
||||||
|
"workbox-core": "^5.1.4",
|
||||||
|
"workbox-precaching": "^5.1.4"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
"@babel/preset-env"
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"useBuiltIns": "usage",
|
||||||
|
"corejs": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"overrides": [
|
"plugins": [
|
||||||
{
|
"@babel/plugin-proposal-class-properties",
|
||||||
"test": [
|
"@babel/plugin-proposal-private-methods"
|
||||||
"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/components/subtitleuploader/subtitleuploader.js",
|
|
||||||
"src/scripts/dfnshelper.js",
|
|
||||||
"src/scripts/dom.js",
|
|
||||||
"src/scripts/filesystem.js",
|
|
||||||
"src/scripts/imagehelper.js",
|
|
||||||
"src/scripts/inputManager.js",
|
|
||||||
"src/components/deletehelper.js",
|
|
||||||
"src/components/actionsheet/actionsheet.js",
|
|
||||||
"src/components/playmenu.js",
|
|
||||||
"src/components/indicators/indicators.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": [
|
||||||
|
@ -123,7 +114,7 @@
|
||||||
"last 2 Chrome versions",
|
"last 2 Chrome versions",
|
||||||
"last 2 ChromeAndroid versions",
|
"last 2 ChromeAndroid versions",
|
||||||
"last 2 Safari versions",
|
"last 2 Safari versions",
|
||||||
"last 2 iOS versions",
|
"iOS > 10",
|
||||||
"last 2 Edge versions",
|
"last 2 Edge versions",
|
||||||
"Chrome 27",
|
"Chrome 27",
|
||||||
"Chrome 38",
|
"Chrome 38",
|
||||||
|
@ -131,14 +122,15 @@
|
||||||
"Chrome 53",
|
"Chrome 53",
|
||||||
"Chrome 56",
|
"Chrome 56",
|
||||||
"Chrome 63",
|
"Chrome 63",
|
||||||
|
"Edge 18",
|
||||||
"Firefox ESR"
|
"Firefox ESR"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "gulp serve --development",
|
"start": "yarn serve",
|
||||||
"prepare": "gulp --production",
|
"serve": "webpack serve --config webpack.dev.js",
|
||||||
"build:development": "gulp --development",
|
"prepare": "./scripts/prepare.sh",
|
||||||
"build:production": "gulp --production",
|
"build:development": "webpack --config webpack.dev.js",
|
||||||
"build:standalone": "gulp standalone --development",
|
"build:production": "webpack --config webpack.prod.js",
|
||||||
"lint": "eslint \".\"",
|
"lint": "eslint \".\"",
|
||||||
"stylelint": "stylelint \"src/**/*.css\""
|
"stylelint": "stylelint \"src/**/*.css\""
|
||||||
}
|
}
|
||||||
|
|
33
scripts/duplicates.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
# load every string in the source language
|
||||||
|
# print all duplicate values to a file
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
source = cwd + '/../src/strings/en-us.json'
|
||||||
|
|
||||||
|
reverse = {}
|
||||||
|
duplicates = {}
|
||||||
|
|
||||||
|
with open(source) as en:
|
||||||
|
strings = json.load(en)
|
||||||
|
for key, value in strings.items():
|
||||||
|
if value not in reverse:
|
||||||
|
reverse[value] = [key]
|
||||||
|
else:
|
||||||
|
reverse[value].append(key)
|
||||||
|
|
||||||
|
for key, value in reverse.items():
|
||||||
|
if len(value) > 1:
|
||||||
|
duplicates[key] = value
|
||||||
|
|
||||||
|
print('LENGTH: ' + str(len(duplicates)))
|
||||||
|
with open('duplicates.txt', 'w') as out:
|
||||||
|
for item in duplicates:
|
||||||
|
out.write(json.dumps(item) + ': ')
|
||||||
|
out.write(json.dumps(duplicates[item]) + '\n')
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
print('DONE')
|
5
scripts/prepare.sh
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ -z "${SKIP_PREPARE}" ]; then
|
||||||
|
webpack --config webpack.prod.js
|
||||||
|
fi
|
|
@ -11,7 +11,7 @@ langlst = os.listdir(langdir)
|
||||||
|
|
||||||
keys = []
|
keys = []
|
||||||
|
|
||||||
with open('scout.txt', 'r') as f:
|
with open('unused.txt', 'r') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
keys.append(line.strip('\n'))
|
keys.append(line.strip('\n'))
|
||||||
|
|
|
@ -15,6 +15,8 @@ print(langlst)
|
||||||
input('press enter to continue')
|
input('press enter to continue')
|
||||||
|
|
||||||
keysus = []
|
keysus = []
|
||||||
|
missing = []
|
||||||
|
|
||||||
with open(langdir + '/' + 'en-us.json') as en:
|
with open(langdir + '/' + 'en-us.json') as en:
|
||||||
langus = json.load(en)
|
langus = json.load(en)
|
||||||
for key in langus:
|
for key in langus:
|
||||||
|
@ -32,10 +34,19 @@ for lang in langlst:
|
||||||
for key in langjson:
|
for key in langjson:
|
||||||
if key in keysus:
|
if key in keysus:
|
||||||
langjnew[key] = langjson[key]
|
langjnew[key] = langjson[key]
|
||||||
|
elif key not in missing:
|
||||||
|
missing.append(key)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False))
|
f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False))
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
f.truncate()
|
f.truncate()
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
print(missing)
|
||||||
|
print('LENGTH: ' + str(len(missing)))
|
||||||
|
with open('missing.txt', 'w') as out:
|
||||||
|
for item in missing:
|
||||||
|
out.write(item + '\n')
|
||||||
|
out.close()
|
||||||
|
|
||||||
print('DONE')
|
print('DONE')
|
|
@ -16,7 +16,7 @@ langlst.append('en-us.json')
|
||||||
dep = []
|
dep = []
|
||||||
|
|
||||||
def grep(key):
|
def grep(key):
|
||||||
command = 'grep -r -E "(\(\\\"|\(\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % 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)
|
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
output = p.stdout.readlines()
|
output = p.stdout.readlines()
|
||||||
if output:
|
if output:
|
||||||
|
@ -34,7 +34,7 @@ for lang in langlst:
|
||||||
|
|
||||||
print(dep)
|
print(dep)
|
||||||
print('LENGTH: ' + str(len(dep)))
|
print('LENGTH: ' + str(len(dep)))
|
||||||
with open('scout.txt', 'w') as out:
|
with open('unused.txt', 'w') as out:
|
||||||
for item in dep:
|
for item in dep:
|
||||||
out.write(item + '\n')
|
out.write(item + '\n')
|
||||||
out.close()
|
out.close()
|
BIN
src/assets/audio/silence.mp3
Normal file
|
@ -235,6 +235,15 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.localUsers .cardText-secondary {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customCssContainer textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (min-width: 70em) {
|
@media all and (min-width: 70em) {
|
||||||
.dashboardSections {
|
.dashboardSections {
|
||||||
-webkit-flex-wrap: wrap;
|
-webkit-flex-wrap: wrap;
|
||||||
|
|
|
@ -30,6 +30,10 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-items-flex-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.justify-content-center {
|
.justify-content-center {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +42,10 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.justify-content-space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-wrap-wrap {
|
.flex-wrap-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
html {
|
|
||||||
font-family: "Noto Sans", sans-serif;
|
|
||||||
font-size: 93%;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
text-size-adjust: 100%;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
font-family: "Noto Sans", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.17em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-tv {
|
|
||||||
font-size: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-mobile {
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
34
src/assets/css/fonts.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
@mixin font($weight: null, $size: null) {
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
font-weight: $weight;
|
||||||
|
font-size: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
@include font;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@include font(400, 1.8em);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@include font(400, 1.5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@include font(400, 1.17em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-tv {
|
||||||
|
/* Per WebOS and Tizen guidelines, fonts must be 20px minimum.
|
||||||
|
This takes the 16px baseline and multiplies it by 1.25 to get 20px. */
|
||||||
|
font-size: 125%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
h1 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-desktop h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.17em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (min-height: 720px) {
|
|
||||||
html {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
|
|
||||||
@media all and (min-height: 1000px) {
|
|
||||||
html {
|
|
||||||
font-size: 27px;
|
|
||||||
}
|
|
||||||
}
|
|
31
src/assets/css/fonts.sized.scss
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
@mixin header-font($size: null) {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
@media all and (min-height: 720px) {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
|
||||||
|
@media all and (min-height: 1000px) {
|
||||||
|
font-size: 27px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@include header-font(1.8em);
|
||||||
|
|
||||||
|
.layout-desktop & {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@include header-font(1.8em);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@include header-font(1.17em);
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
html {
|
|
||||||
font-size: 82% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formDialogFooter {
|
|
||||||
position: static !important;
|
|
||||||
margin: 0 -1em !important;
|
|
||||||
}
|
|
3
src/assets/css/ios.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
html {
|
||||||
|
font-size: 82% !important;
|
||||||
|
}
|
|
@ -24,14 +24,14 @@
|
||||||
padding-top: 7em !important;
|
padding-top: 7em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-mobile .libraryPage {
|
|
||||||
padding-top: 4em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemDetailPage {
|
.itemDetailPage {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-tv .itemDetailPage {
|
||||||
|
padding-top: 4.2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
.standalonePage {
|
.standalonePage {
|
||||||
padding-top: 4.5em !important;
|
padding-top: 4.5em !important;
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
contain: layout style paint;
|
contain: layout style paint;
|
||||||
|
transition: background ease-in-out 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-tv .skinHeader {
|
||||||
|
/* In TV layout, it makes more sense to keep the top bar at the top of the page
|
||||||
|
Having it follow the view only makes us lose vertical space, while not being focusable */
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hiddenViewMenuBar .skinHeader {
|
.hiddenViewMenuBar .skinHeader {
|
||||||
|
@ -178,6 +185,10 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-tv .sectionTabs {
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
.selectedMediaFolder {
|
.selectedMediaFolder {
|
||||||
background-color: #f2f2f2 !important;
|
background-color: #f2f2f2 !important;
|
||||||
}
|
}
|
||||||
|
@ -235,12 +246,6 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .searchTabButton,
|
|
||||||
.layout-mobile .searchTabButton,
|
|
||||||
.layout-tv .headerSearchButton {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainDrawer-scrollContainer {
|
.mainDrawer-scrollContainer {
|
||||||
padding-bottom: 10vh;
|
padding-bottom: 10vh;
|
||||||
}
|
}
|
||||||
|
@ -272,7 +277,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 84em) {
|
@media all and (max-width: 100em) {
|
||||||
.withSectionTabs .headerTop {
|
.withSectionTabs .headerTop {
|
||||||
padding-bottom: 0.55em;
|
padding-bottom: 0.55em;
|
||||||
}
|
}
|
||||||
|
@ -280,9 +285,13 @@
|
||||||
.sectionTabs {
|
.sectionTabs {
|
||||||
font-size: 83.5%;
|
font-size: 83.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-tv .sectionTabs {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 84em) {
|
@media all and (min-width: 100em) {
|
||||||
.headerTop {
|
.headerTop {
|
||||||
padding: 0.8em 0.8em;
|
padding: 0.8em 0.8em;
|
||||||
}
|
}
|
||||||
|
@ -438,16 +447,17 @@
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
height: 50vh;
|
height: 40vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
animation: backdrop-fadein 800ms ease-in normal both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-mobile .itemBackdrop {
|
.layout-mobile .itemBackdrop {
|
||||||
background-attachment: scroll;
|
background-attachment: scroll;
|
||||||
|
height: 26.5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .itemBackdrop::after,
|
.layout-desktop .itemBackdrop::after {
|
||||||
.layout-tv .itemBackdrop::after {
|
|
||||||
content: "";
|
content: "";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -455,18 +465,28 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .noBackdrop .itemBackdrop,
|
.layout-tv .itemBackdrop,
|
||||||
.layout-tv .noBackdrop .itemBackdrop {
|
.layout-desktop .noBackdrop .itemBackdrop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailPageContent {
|
.detailPageContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-left: 2%;
|
padding-left: 32.45vw;
|
||||||
padding-right: 2%;
|
padding-right: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-mobile .detailPageContent {
|
||||||
|
padding-left: 5%;
|
||||||
|
padding-right: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-desktop .detailPageContent .emby-scroller,
|
||||||
|
.layout-tv .detailPageContent .emby-scroller {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-desktop .noBackdrop .detailPageContent,
|
.layout-desktop .noBackdrop .detailPageContent,
|
||||||
.layout-tv .noBackdrop .detailPageContent {
|
.layout-tv .noBackdrop .detailPageContent {
|
||||||
margin-top: 2.5em;
|
margin-top: 2.5em;
|
||||||
|
@ -477,6 +497,10 @@
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detailSectionContent a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.personBackdrop {
|
.personBackdrop {
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
@ -495,7 +519,23 @@
|
||||||
|
|
||||||
.parentName {
|
.parentName {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.5em;
|
margin: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .parentName {
|
||||||
|
margin: 0.6em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.musicParentName {
|
||||||
|
margin: 0.15em 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .musicParentName {
|
||||||
|
margin: -0.25em 0 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .itemExternalLinks {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainDetailButtons {
|
.mainDetailButtons {
|
||||||
|
@ -503,8 +543,6 @@
|
||||||
-webkit-box-align: center;
|
-webkit-box-align: center;
|
||||||
-webkit-align-items: center;
|
-webkit-align-items: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
-webkit-flex-wrap: wrap;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,6 +558,35 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemName.originalTitle {
|
||||||
|
margin: 0.2em 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemName.parentNameLast {
|
||||||
|
margin: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .itemName.parentNameLast {
|
||||||
|
margin: 0.4em 0 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile h1.itemName,
|
||||||
|
.layout-mobile h1.parentName {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemName.parentNameLast.withOriginalTitle {
|
||||||
|
margin: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .itemName.parentNameLast.withOriginalTitle {
|
||||||
|
margin: 0.6em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .itemName.originalTitle {
|
||||||
|
margin: 0.5em 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.nameContainer {
|
.nameContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -546,6 +613,19 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-mobile .mainDetailButtons {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0.15em 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .subtitle {
|
||||||
|
margin: 0.2em 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
.detailPagePrimaryContainer {
|
.detailPagePrimaryContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -553,10 +633,14 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-tv .detailPagePrimaryContainer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-mobile .detailPagePrimaryContainer {
|
.layout-mobile .detailPagePrimaryContainer {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
padding: 0.5em 3.3% 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
||||||
|
@ -566,13 +650,18 @@
|
||||||
padding-left: 32.45vw;
|
padding-left: 32.45vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .detailSticky,
|
.layout-desktop .detailRibbon {
|
||||||
.layout-tv .detailSticky {
|
|
||||||
margin-top: -7.2em;
|
margin-top: -7.2em;
|
||||||
|
height: 7.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .noBackdrop .detailSticky,
|
.layout-tv .detailRibbon {
|
||||||
.layout-tv .noBackdrop .detailSticky {
|
margin-top: 0;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-desktop .noBackdrop .detailRibbon,
|
||||||
|
.layout-tv .noBackdrop .detailRibbon {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,6 +673,9 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-mobile .infoText {
|
.layout-mobile .infoText {
|
||||||
|
@ -594,12 +686,29 @@
|
||||||
margin: 1.25em 0;
|
margin: 1.25em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailImageContainer {
|
.layout-mobile .detailPageSecondaryContainer {
|
||||||
position: relative;
|
margin: 1em 0;
|
||||||
margin-top: -25vh;
|
}
|
||||||
|
|
||||||
|
.layout-mobile .detailImageContainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailImageContainer .card {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
float: left;
|
float: left;
|
||||||
width: 25vw;
|
width: 25vw;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailImageContainer .card.backdropCard {
|
||||||
|
top: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailImageContainer .card.squareCard {
|
||||||
|
top: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .noBackdrop .detailImageContainer,
|
.layout-desktop .noBackdrop .detailImageContainer,
|
||||||
|
@ -612,11 +721,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailLogo {
|
.detailLogo {
|
||||||
width: 30vw;
|
width: 25vw;
|
||||||
height: 25vh;
|
height: 16vh;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10vh;
|
top: 10vh;
|
||||||
right: 20vw;
|
right: 25vw;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,7 +750,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemDetailGalleryLink.defaultCardBackground {
|
.itemDetailGalleryLink.defaultCardBackground {
|
||||||
height: 23vw; /* Dirty hack to get it to look somewhat square. Less than ideal. */
|
/* Dirty hack to get it to look somewhat square. Less than ideal. */
|
||||||
|
height: 23vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
|
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
|
||||||
|
@ -655,14 +765,18 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .detailPageWrapperContainer,
|
.layout-desktop .itemBackdrop {
|
||||||
.layout-tv .detailPageWrapperContainer {
|
height: 40vh;
|
||||||
margin-top: 7.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
.layout-desktop .detailPageWrapperContainer,
|
||||||
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
|
.layout-tv .detailPageWrapperContainer {
|
||||||
padding-left: 3.3%;
|
margin-top: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-desktop .detailImageContainer .card,
|
||||||
|
.layout-tv .detailImageContainer .card {
|
||||||
|
top: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnPlaySimple {
|
.btnPlaySimple {
|
||||||
|
@ -677,13 +791,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
.emby-button.detailFloatingButton {
|
.emby-button.detailFloatingButton {
|
||||||
position: absolute;
|
font-size: 1.4em;
|
||||||
background-color: rgba(0, 0, 0, 0.5) !important;
|
margin-right: 0.5em !important;
|
||||||
z-index: 1;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin: -2.2em 0 0 -2.2em;
|
|
||||||
padding: 0.4em !important;
|
|
||||||
color: rgba(255, 255, 255, 0.76);
|
color: rgba(255, 255, 255, 0.76);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,16 +802,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
|
|
||||||
@media all and (max-width: 62.5em) {
|
@media all and (max-width: 62.5em) {
|
||||||
.parentName {
|
.parentName {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemDetailPage {
|
.itemDetailPage {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailimg-hidemobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 31.25em) {
|
@media all and (min-width: 31.25em) {
|
||||||
|
@ -750,7 +855,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
-webkit-align-items: center;
|
-webkit-align-items: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0.5em 0.7em !important;
|
padding: 0.7em 0.7em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 29em) {
|
@media all and (min-width: 29em) {
|
||||||
|
@ -797,9 +902,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailImageProgressContainer {
|
.detailImageProgressContainer {
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 22.786458333333332vw;
|
margin-top: -0.4vw;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailButton-text {
|
.detailButton-text {
|
||||||
|
@ -818,25 +923,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 62.5em) {
|
@media all and (min-width: 100em) {
|
||||||
.headerTop {
|
|
||||||
padding-left: 0.8em;
|
|
||||||
padding-right: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.headerTabs {
|
|
||||||
align-self: center;
|
|
||||||
width: auto;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: -4.2em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailFloatingButton {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.personBackdrop {
|
.personBackdrop {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
@ -845,6 +932,11 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
font-size: 108%;
|
font-size: 108%;
|
||||||
margin: 1.25em 0;
|
margin: 1.25em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-tv .mainDetailButtons {
|
||||||
|
font-size: 108%;
|
||||||
|
margin: 1em 0 1.25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 50em) {
|
@media all and (max-width: 50em) {
|
||||||
|
@ -866,6 +958,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detailVerticalSection .emby-scrollbuttons {
|
||||||
|
padding-top: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-tv .detailVerticalSection {
|
.layout-tv .detailVerticalSection {
|
||||||
margin-bottom: 3.4em !important;
|
margin-bottom: 3.4em !important;
|
||||||
}
|
}
|
||||||
|
@ -954,6 +1050,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
margin-bottom: 2.7em;
|
margin-bottom: 2.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-mobile .verticalSection-extrabottompadding {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.sectionTitleButton,
|
.sectionTitleButton,
|
||||||
.sectionTitleIconButton {
|
.sectionTitleIconButton {
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
|
@ -979,7 +1079,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
|
|
||||||
div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 1.25em;
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile :not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sectionTitleButton {
|
.sectionTitleButton {
|
||||||
|
@ -1046,13 +1152,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tv .padded-top-focusscale {
|
.layout-tv .padded-top-focusscale {
|
||||||
padding-top: 1em;
|
padding-top: 1.5em;
|
||||||
margin-top: -1em;
|
margin-top: -1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tv .padded-bottom-focusscale {
|
.layout-tv .padded-bottom-focusscale {
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1.5em;
|
||||||
margin-bottom: -1em;
|
margin-bottom: -1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-height: 31.25em) {
|
@media all and (min-height: 31.25em) {
|
||||||
|
@ -1132,6 +1238,12 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||||
|
|
||||||
.trackSelections .selectContainer .selectLabel {
|
.trackSelections .selectContainer .selectLabel {
|
||||||
margin: 0 0.2em 0 0;
|
margin: 0 0.2em 0 0;
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile .detailsGroupItem .label,
|
||||||
|
.layout-mobile .trackSelections .selectContainer .selectLabel {
|
||||||
|
flex-basis: 4.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trackSelections .selectContainer .detailTrackSelect {
|
.trackSelections .selectContainer .detailTrackSelect {
|
||||||
|
@ -1144,3 +1256,21 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overview-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-clamp-text {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 12;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 40em) {
|
||||||
|
.detail-clamp-text {
|
||||||
|
-webkit-line-clamp: 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
padding-bottom: 15em;
|
padding-bottom: 15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 62.5em) {
|
#guideTab {
|
||||||
#guideTab {
|
@media all and (min-width: 62.5em) {
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
.hiddenScrollX,
|
.hiddenScrollX,
|
||||||
.layout-tv .scrollX {
|
.layout-tv .scrollX {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hiddenScrollX-forced {
|
.hiddenScrollX-forced {
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
.hiddenScrollY,
|
.hiddenScrollY,
|
||||||
.layout-tv .smoothScrollY {
|
.layout-tv .smoothScrollY {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
/* Can't do this because it not only hides the scrollbar, but also prevents scrolling */
|
/* Can't do this because it not only hides the scrollbar, but also prevents scrolling */
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
body,
|
@mixin fullpage {
|
||||||
html {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
@include fullpage;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@include fullpage;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: transparent !important;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
.clipForScreenReader {
|
.clipForScreenReader {
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
clip-path: inset(50%);
|
clip-path: inset(50%);
|
||||||
|
@ -18,7 +29,7 @@ html {
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
/* Fix font ligatures on older WebOS versions */
|
/* Fix font ligatures on older WebOS versions */
|
||||||
-webkit-font-feature-settings: "liga";
|
font-feature-settings: "liga";
|
||||||
}
|
}
|
||||||
|
|
||||||
.backgroundContainer {
|
.backgroundContainer {
|
||||||
|
@ -30,26 +41,12 @@ html {
|
||||||
contain: strict;
|
contain: strict;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-mobile,
|
.layout-mobile,
|
||||||
.layout-tv {
|
.layout-tv {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
overflow-x: hidden;
|
|
||||||
background-color: transparent !important;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainAnimatedPage {
|
.mainAnimatedPage {
|
||||||
contain: style size !important;
|
contain: style size !important;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +59,7 @@ body {
|
||||||
overflow-y: hidden !important;
|
overflow-y: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[data-role=page] {
|
div[data-role="page"] {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +72,10 @@ div[data-role=page] {
|
||||||
padding-left: 0.15em;
|
padding-left: 0.15em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
|
||||||
|
|
||||||
.fieldDescription + .fieldDescription {
|
+ .fieldDescription {
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-primary,
|
.content-primary,
|
||||||
|
@ -89,9 +86,14 @@ div[data-role=page] {
|
||||||
padding-bottom: 5em !important;
|
padding-bottom: 5em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 50em) {
|
.readOnlyContent {
|
||||||
.readOnlyContent,
|
@media all and (min-width: 50em) {
|
||||||
form {
|
max-width: 54em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
@media all and (min-width: 50em) {
|
||||||
max-width: 54em;
|
max-width: 54em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,12 +113,39 @@ div[data-role=page] {
|
||||||
.headroom {
|
.headroom {
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transition: transform 200ms linear;
|
transition: transform 200ms linear;
|
||||||
|
|
||||||
|
&--pinned {
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--unpinned {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headroom--pinned {
|
.drawerContent {
|
||||||
transform: translateY(0%);
|
/* make sure the bottom of the drawer is visible when music is playing */
|
||||||
|
padding-bottom: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headroom--unpinned {
|
.force-scroll {
|
||||||
transform: translateY(-100%);
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scroll {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-auto-x {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-auto-y {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
}
|
}
|
|
@ -6,31 +6,43 @@
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdPoster img,
|
|
||||||
.pageContainer,
|
|
||||||
.videoOsdBottom {
|
.videoOsdBottom {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
position: fixed;
|
||||||
|
background: linear-gradient(0deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
|
||||||
|
padding-top: 7.5em;
|
||||||
|
padding-bottom: 1.75em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
will-change: opacity;
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
|
color: #fff;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdHeader {
|
.skinHeader-withBackground.osdHeader {
|
||||||
-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;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: rgba(0, 0, 0, 0.7) !important;
|
background: linear-gradient(180deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
|
||||||
-webkit-backdrop-filter: none !important;
|
backdrop-filter: none;
|
||||||
backdrop-filter: none !important;
|
color: #eee;
|
||||||
color: #eee !important;
|
height: 7.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdHeader-hidden {
|
.osdHeader-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) {
|
.osdHeader .headerTop {
|
||||||
|
max-height: 3.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,34 +99,17 @@
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoOsdBottom {
|
|
||||||
position: fixed;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
padding: 1%;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-orient: horizontal;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-webkit-flex-direction: row;
|
|
||||||
flex-direction: row;
|
|
||||||
will-change: opacity;
|
|
||||||
-webkit-transition: opacity 0.3s ease-out;
|
|
||||||
-o-transition: opacity 0.3s ease-out;
|
|
||||||
transition: opacity 0.3s ease-out;
|
|
||||||
color: #fff;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videoOsdBottom-hidden {
|
.videoOsdBottom-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdControls {
|
.osdControls {
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-webkit-flex-grow: 1;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
padding: 0 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-desktop .osdControls {
|
||||||
|
max-width: calc(100vh * 1.77 - 2vh);
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoOsdBottom .buttons {
|
.videoOsdBottom .buttons {
|
||||||
|
@ -146,7 +141,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.volumeButtons {
|
.volumeButtons {
|
||||||
margin: 0 0.5em 0 auto;
|
margin: 0 1em 0 0.29em;
|
||||||
display: flex;
|
display: flex;
|
||||||
-webkit-align-items: center;
|
-webkit-align-items: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -154,33 +149,13 @@
|
||||||
|
|
||||||
.osdTimeText {
|
.osdTimeText {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
|
margin-right: auto;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.osdPoster {
|
|
||||||
width: 10%;
|
|
||||||
position: relative;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.osdPoster img {
|
|
||||||
position: absolute;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
-webkit-box-shadow: 0 0 1.9vh #000;
|
|
||||||
box-shadow: 0 0 1.9vh #000;
|
|
||||||
border: 0.08em solid #222;
|
|
||||||
user-drag: 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;
|
||||||
|
@ -248,14 +223,7 @@
|
||||||
animation: spin 4s linear infinite;
|
animation: spin 4s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageContainer {
|
|
||||||
top: 0;
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 30em) {
|
@media all and (max-width: 30em) {
|
||||||
.btnFastForward,
|
|
||||||
.btnRewind,
|
|
||||||
.osdMediaInfo,
|
.osdMediaInfo,
|
||||||
.osdPoster {
|
.osdPoster {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.6 KiB |
1
src/assets/img/devices/edgechromium.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24"><title>Microsoft Edge icon</title><path d="M21.86 17.86q.14 0 .25.12.1.13.1.25t-.11.33l-.32.46-.43.53-.44.5q-.21.25-.38.42l-.22.23q-.58.53-1.34 1.04-.76.51-1.6.91-.86.4-1.74.64t-1.67.24q-.9 0-1.69-.28-.8-.28-1.48-.78-.68-.5-1.22-1.17-.53-.66-.92-1.44-.38-.77-.58-1.6-.2-.83-.2-1.67 0-1 .32-1.96.33-.97.87-1.8.14.95.55 1.77.41.82 1.02 1.5.6.68 1.38 1.21.78.54 1.64.9.86.36 1.77.56.92.2 1.8.2 1.12 0 2.18-.24 1.06-.23 2.06-.72l.2-.1.2-.05zm-15.5-1.27q0 1.1.27 2.15.27 1.06.78 2.03.51.96 1.24 1.77.74.82 1.66 1.4-1.47-.2-2.8-.74-1.33-.55-2.48-1.37-1.15-.83-2.08-1.9-.92-1.07-1.58-2.33T.36 14.94Q0 13.54 0 12.06q0-.81.32-1.49.31-.68.83-1.23.53-.55 1.2-.96.66-.4 1.35-.66.74-.27 1.5-.39.78-.12 1.55-.12.7 0 1.42.1.72.12 1.4.35.68.23 1.32.57.63.35 1.16.83-.35 0-.7.07-.33.07-.65.23v-.02q-.63.28-1.2.74-.57.46-1.05 1.04-.48.58-.87 1.26-.38.67-.65 1.39-.27.71-.42 1.44-.15.72-.15 1.38zM11.96.06q1.7 0 3.33.39 1.63.38 3.07 1.15 1.43.77 2.62 1.93 1.18 1.16 1.98 2.7.49.94.76 1.96.28 1 .28 2.08 0 .89-.23 1.7-.24.8-.69 1.48-.45.68-1.1 1.22-.64.53-1.45.88-.54.24-1.11.36-.58.13-1.16.13-.42 0-.97-.03-.54-.03-1.1-.12-.55-.1-1.05-.28-.5-.19-.84-.5-.12-.09-.23-.24-.1-.16-.1-.33 0-.15.16-.35.16-.2.35-.5.2-.28.36-.68.16-.4.16-.95 0-1.06-.4-1.96-.4-.91-1.06-1.64-.66-.74-1.52-1.28-.86-.55-1.79-.89-.84-.3-1.72-.44-.87-.14-1.76-.14-1.55 0-3.06.45T.94 7.55q.71-1.74 1.81-3.13 1.1-1.38 2.52-2.35Q6.68 1.1 8.37.58q1.7-.52 3.58-.52Z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 33 KiB |
178
src/bundle.js
|
@ -1,178 +0,0 @@
|
||||||
/**
|
|
||||||
* require.js module definitions bundled by webpack
|
|
||||||
*/
|
|
||||||
// Use define from require.js not webpack's define
|
|
||||||
var _define = window.define;
|
|
||||||
|
|
||||||
// document-register-element
|
|
||||||
var docRegister = require('document-register-element');
|
|
||||||
_define('document-register-element', function() {
|
|
||||||
return docRegister;
|
|
||||||
});
|
|
||||||
|
|
||||||
// fetch
|
|
||||||
var fetch = require('whatwg-fetch');
|
|
||||||
_define('fetch', function() {
|
|
||||||
return fetch;
|
|
||||||
});
|
|
||||||
|
|
||||||
// query-string
|
|
||||||
var query = require('query-string');
|
|
||||||
_define('queryString', function() {
|
|
||||||
return query;
|
|
||||||
});
|
|
||||||
|
|
||||||
// flvjs
|
|
||||||
var flvjs = require('flv.js/dist/flv').default;
|
|
||||||
_define('flvjs', function() {
|
|
||||||
return flvjs;
|
|
||||||
});
|
|
||||||
|
|
||||||
// jstree
|
|
||||||
var jstree = require('jstree');
|
|
||||||
require('jstree/dist/themes/default/style.css');
|
|
||||||
_define('jstree', function() {
|
|
||||||
return jstree;
|
|
||||||
});
|
|
||||||
|
|
||||||
// jquery
|
|
||||||
var jquery = require('jquery');
|
|
||||||
_define('jQuery', function() {
|
|
||||||
return jquery;
|
|
||||||
});
|
|
||||||
|
|
||||||
// hlsjs
|
|
||||||
var hlsjs = require('hls.js');
|
|
||||||
_define('hlsjs', function() {
|
|
||||||
return hlsjs;
|
|
||||||
});
|
|
||||||
|
|
||||||
// howler
|
|
||||||
var howler = require('howler');
|
|
||||||
_define('howler', function() {
|
|
||||||
return howler;
|
|
||||||
});
|
|
||||||
|
|
||||||
// resize-observer-polyfill
|
|
||||||
var resize = require('resize-observer-polyfill').default;
|
|
||||||
_define('resize-observer-polyfill', function() {
|
|
||||||
return resize;
|
|
||||||
});
|
|
||||||
|
|
||||||
// shaka
|
|
||||||
var shaka = require('shaka-player');
|
|
||||||
_define('shaka', function() {
|
|
||||||
return shaka;
|
|
||||||
});
|
|
||||||
|
|
||||||
// swiper
|
|
||||||
var swiper = require('swiper/js/swiper');
|
|
||||||
require('swiper/css/swiper.min.css');
|
|
||||||
_define('swiper', function() {
|
|
||||||
return swiper;
|
|
||||||
});
|
|
||||||
|
|
||||||
// sortable
|
|
||||||
var sortable = require('sortablejs').default;
|
|
||||||
_define('sortable', function() {
|
|
||||||
return sortable;
|
|
||||||
});
|
|
||||||
|
|
||||||
// webcomponents
|
|
||||||
var webcomponents = require('webcomponents.js/webcomponents-lite');
|
|
||||||
_define('webcomponents', function() {
|
|
||||||
return webcomponents;
|
|
||||||
});
|
|
||||||
|
|
||||||
// libass-wasm
|
|
||||||
var libassWasm = require('libass-wasm');
|
|
||||||
_define('JavascriptSubtitlesOctopus', function() {
|
|
||||||
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;
|
|
||||||
});
|
|
4
src/components/AppInfo.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isNativeApp: false
|
||||||
|
};
|
82
src/components/ServerConnections.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
|
||||||
|
import { appHost } from './apphost';
|
||||||
|
import Dashboard from '../scripts/clientUtils';
|
||||||
|
import AppInfo from './AppInfo';
|
||||||
|
import { setUserInfo } from '../scripts/settings/userSettings';
|
||||||
|
|
||||||
|
class ServerConnections extends ConnectionManager {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.localApiClient = null;
|
||||||
|
|
||||||
|
Events.on(this, 'localusersignedout', function () {
|
||||||
|
setUserInfo(null, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initApiClient() {
|
||||||
|
if (!AppInfo.isNativeApp) {
|
||||||
|
console.debug('creating ApiClient singleton');
|
||||||
|
|
||||||
|
const apiClient = new ApiClient(
|
||||||
|
Dashboard.serverAddress(),
|
||||||
|
appHost.appName(),
|
||||||
|
appHost.appVersion(),
|
||||||
|
appHost.deviceName(),
|
||||||
|
appHost.deviceId()
|
||||||
|
);
|
||||||
|
|
||||||
|
apiClient.enableAutomaticNetworking = false;
|
||||||
|
apiClient.manualAddressOnly = true;
|
||||||
|
|
||||||
|
this.addApiClient(apiClient);
|
||||||
|
|
||||||
|
this.setLocalApiClient(apiClient);
|
||||||
|
|
||||||
|
console.debug('loaded ApiClient singleton');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalApiClient(apiClient) {
|
||||||
|
if (apiClient) {
|
||||||
|
this.localApiClient = apiClient;
|
||||||
|
window.ApiClient = apiClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocalApiClient() {
|
||||||
|
return this.localApiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentApiClient() {
|
||||||
|
let apiClient = this.getLocalApiClient();
|
||||||
|
|
||||||
|
if (!apiClient) {
|
||||||
|
const server = this.getLastUsedServer();
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
apiClient = this.getApiClient(server.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLocalUserSignedIn(user) {
|
||||||
|
const apiClient = this.getApiClient(user.ServerId);
|
||||||
|
this.setLocalApiClient(apiClient);
|
||||||
|
return setUserInfo(user.Id, apiClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = new Credentials();
|
||||||
|
|
||||||
|
const capabilities = Dashboard.capabilities(appHost);
|
||||||
|
|
||||||
|
export default new ServerConnections(
|
||||||
|
credentials,
|
||||||
|
appHost.appName(),
|
||||||
|
appHost.appVersion(),
|
||||||
|
appHost.deviceName(),
|
||||||
|
appHost.deviceId(),
|
||||||
|
capabilities);
|
98
src/components/accessSchedule/accessSchedule.js
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
|
||||||
|
/* eslint-disable indent */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module for controlling user parental control from.
|
||||||
|
* @module components/accessSchedule/accessSchedule
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
|
import datetime from '../../scripts/datetime';
|
||||||
|
import globalize from '../../scripts/globalize';
|
||||||
|
import '../../elements/emby-select/emby-select';
|
||||||
|
import '../../elements/emby-button/paper-icon-button-light';
|
||||||
|
import '../formdialog.css';
|
||||||
|
|
||||||
|
function getDisplayTime(hours) {
|
||||||
|
let minutes = 0;
|
||||||
|
const pct = hours % 1;
|
||||||
|
|
||||||
|
if (pct) {
|
||||||
|
minutes = parseInt(60 * pct);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateHours(context) {
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < 24; i++) {
|
||||||
|
html += `<option value="${i}">${getDisplayTime(i)}</option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<option value="24">${getDisplayTime(0)}</option>`;
|
||||||
|
context.querySelector('#selectStart').innerHTML = html;
|
||||||
|
context.querySelector('#selectEnd').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSchedule(context, {DayOfWeek, StartHour, EndHour}) {
|
||||||
|
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday';
|
||||||
|
context.querySelector('#selectStart').value = StartHour || 0;
|
||||||
|
context.querySelector('#selectEnd').value = EndHour || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitSchedule(context, options) {
|
||||||
|
const updatedSchedule = {
|
||||||
|
DayOfWeek: context.querySelector('#selectDay').value,
|
||||||
|
StartHour: context.querySelector('#selectStart').value,
|
||||||
|
EndHour: context.querySelector('#selectEnd').value
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
|
||||||
|
return void alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.submitted = true;
|
||||||
|
options.schedule = Object.assign(options.schedule, updatedSchedule);
|
||||||
|
dialogHelper.close(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function show(options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
import('./accessSchedule.template.html').then(({default: template}) => {
|
||||||
|
const dlg = dialogHelper.createDialog({
|
||||||
|
removeOnClose: true,
|
||||||
|
size: 'small'
|
||||||
|
});
|
||||||
|
dlg.classList.add('formDialog');
|
||||||
|
let html = '';
|
||||||
|
html += globalize.translateHtml(template);
|
||||||
|
dlg.innerHTML = html;
|
||||||
|
populateHours(dlg);
|
||||||
|
loadSchedule(dlg, options.schedule);
|
||||||
|
dialogHelper.open(dlg);
|
||||||
|
dlg.addEventListener('close', () => {
|
||||||
|
if (dlg.submitted) {
|
||||||
|
resolve(options.schedule);
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
||||||
|
dialogHelper.close(dlg);
|
||||||
|
});
|
||||||
|
dlg.querySelector('form').addEventListener('submit', event => {
|
||||||
|
submitSchedule(dlg, options);
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable indent */
|
||||||
|
|
||||||
|
export default {
|
||||||
|
show: show
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="formDialogHeader">
|
<div class="formDialogHeader">
|
||||||
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${LabelPrevious}" tabindex="-1">
|
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${Previous}" tabindex="-1">
|
||||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<h3 class="formDialogHeaderTitle">
|
<h3 class="formDialogHeaderTitle">
|
||||||
|
@ -12,13 +12,13 @@
|
||||||
|
|
||||||
<div class="selectContainer">
|
<div class="selectContainer">
|
||||||
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
|
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
|
||||||
<option value="Sunday">${OptionSunday}</option>
|
<option value="Sunday">${Sunday}</option>
|
||||||
<option value="Monday">${OptionMonday}</option>
|
<option value="Monday">${Monday}</option>
|
||||||
<option value="Tuesday">${OptionTuesday}</option>
|
<option value="Tuesday">${Tuesday}</option>
|
||||||
<option value="Wednesday">${OptionWednesday}</option>
|
<option value="Wednesday">${Wednesday}</option>
|
||||||
<option value="Thursday">${OptionThursday}</option>
|
<option value="Thursday">${Thursday}</option>
|
||||||
<option value="Friday">${OptionFriday}</option>
|
<option value="Friday">${Friday}</option>
|
||||||
<option value="Saturday">${OptionSaturday}</option>
|
<option value="Saturday">${Saturday}</option>
|
||||||
<option value="Everyday">${OptionEveryday}</option>
|
<option value="Everyday">${OptionEveryday}</option>
|
||||||
<option value="Weekday">${OptionWeekdays}</option>
|
<option value="Weekday">${OptionWeekdays}</option>
|
||||||
<option value="Weekend">${OptionWeekends}</option>
|
<option value="Weekend">${OptionWeekends}</option>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
<div class="formDialogFooter">
|
<div class="formDialogFooter">
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
|
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
|
||||||
<span>${ButtonAdd}</span>
|
<span>${Add}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
|
@ -1,89 +0,0 @@
|
||||||
define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-button-light', 'formDialogStyle'], function (dialogHelper, datetime, globalize) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function getDisplayTime(hours) {
|
|
||||||
var minutes = 0;
|
|
||||||
var pct = hours % 1;
|
|
||||||
|
|
||||||
if (pct) {
|
|
||||||
minutes = parseInt(60 * pct);
|
|
||||||
}
|
|
||||||
|
|
||||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateHours(context) {
|
|
||||||
var html = '';
|
|
||||||
|
|
||||||
for (var i = 0; i < 24; i++) {
|
|
||||||
html += '<option value="' + i + '">' + getDisplayTime(i) + '</option>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<option value="24">' + getDisplayTime(0) + '</option>';
|
|
||||||
context.querySelector('#selectStart').innerHTML = html;
|
|
||||||
context.querySelector('#selectEnd').innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSchedule(context, schedule) {
|
|
||||||
context.querySelector('#selectDay').value = schedule.DayOfWeek || 'Sunday';
|
|
||||||
context.querySelector('#selectStart').value = schedule.StartHour || 0;
|
|
||||||
context.querySelector('#selectEnd').value = schedule.EndHour || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitSchedule(context, options) {
|
|
||||||
var updatedSchedule = {
|
|
||||||
DayOfWeek: context.querySelector('#selectDay').value,
|
|
||||||
StartHour: context.querySelector('#selectStart').value,
|
|
||||||
EndHour: context.querySelector('#selectEnd').value
|
|
||||||
};
|
|
||||||
|
|
||||||
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
|
|
||||||
return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd'));
|
|
||||||
}
|
|
||||||
|
|
||||||
context.submitted = true;
|
|
||||||
options.schedule = Object.assign(options.schedule, updatedSchedule);
|
|
||||||
dialogHelper.close(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (options) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', 'components/accessschedule/accessschedule.template.html', true);
|
|
||||||
|
|
||||||
xhr.onload = function (e) {
|
|
||||||
var template = this.response;
|
|
||||||
var dlg = dialogHelper.createDialog({
|
|
||||||
removeOnClose: true,
|
|
||||||
size: 'small'
|
|
||||||
});
|
|
||||||
dlg.classList.add('formDialog');
|
|
||||||
var html = '';
|
|
||||||
html += globalize.translateDocument(template);
|
|
||||||
dlg.innerHTML = html;
|
|
||||||
populateHours(dlg);
|
|
||||||
loadSchedule(dlg, options.schedule);
|
|
||||||
dialogHelper.open(dlg);
|
|
||||||
dlg.addEventListener('close', function () {
|
|
||||||
if (dlg.submitted) {
|
|
||||||
resolve(options.schedule);
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
|
|
||||||
dialogHelper.close(dlg);
|
|
||||||
});
|
|
||||||
dlg.querySelector('form').addEventListener('submit', function (e) {
|
|
||||||
submitSchedule(dlg, options);
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,30 +1,22 @@
|
||||||
import dialogHelper from 'dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
import layoutManager from 'layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
import globalize from 'globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
import dom from 'dom';
|
import dom from '../../scripts/dom';
|
||||||
import 'emby-button';
|
import '../../elements/emby-button/emby-button';
|
||||||
import 'css!./actionsheet';
|
import './actionSheet.css';
|
||||||
import 'material-icons';
|
import 'material-design-icons-iconfont';
|
||||||
import 'scrollStyles';
|
import '../../assets/css/scrollstyles.css';
|
||||||
import 'listViewStyle';
|
import '../../components/listview/listview.css';
|
||||||
|
|
||||||
function getOffsets(elems) {
|
function getOffsets(elems) {
|
||||||
|
const results = [];
|
||||||
let results = [];
|
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
let box;
|
for (const elem of elems) {
|
||||||
for (let elem of elems) {
|
const box = elem.getBoundingClientRect();
|
||||||
// Support: BlackBerry 5, iOS 3 (original iPhone)
|
|
||||||
// If we don't have gBCR, just use 0,0 rather than error
|
|
||||||
if (elem.getBoundingClientRect) {
|
|
||||||
box = elem.getBoundingClientRect();
|
|
||||||
} else {
|
|
||||||
box = { top: 0, left: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
top: box.top,
|
top: box.top,
|
||||||
|
@ -38,12 +30,11 @@ function getOffsets(elems) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPosition(options, dlg) {
|
function getPosition(options, dlg) {
|
||||||
|
|
||||||
const windowSize = dom.getWindowSize();
|
const windowSize = dom.getWindowSize();
|
||||||
const windowHeight = windowSize.innerHeight;
|
const windowHeight = windowSize.innerHeight;
|
||||||
const windowWidth = windowSize.innerWidth;
|
const windowWidth = windowSize.innerWidth;
|
||||||
|
|
||||||
let pos = getOffsets([options.positionTo])[0];
|
const pos = getOffsets([options.positionTo])[0];
|
||||||
|
|
||||||
if (options.positionY !== 'top') {
|
if (options.positionY !== 'top') {
|
||||||
pos.top += (pos.height || 0) / 2;
|
pos.top += (pos.height || 0) / 2;
|
||||||
|
@ -80,19 +71,18 @@ function getPosition(options, dlg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function centerFocus(elem, horiz, on) {
|
function centerFocus(elem, horiz, on) {
|
||||||
require(['scrollHelper'], function (scrollHelper) {
|
import('../../scripts/scrollHelper').then((scrollHelper) => {
|
||||||
const fn = on ? 'on' : 'off';
|
const fn = on ? 'on' : 'off';
|
||||||
scrollHelper.centerFocus[fn](elem, horiz);
|
scrollHelper.centerFocus[fn](elem, horiz);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function show(options) {
|
export function show(options) {
|
||||||
|
|
||||||
// items
|
// items
|
||||||
// positionTo
|
// positionTo
|
||||||
// showCancel
|
// showCancel
|
||||||
// title
|
// title
|
||||||
let dialogOptions = {
|
const dialogOptions = {
|
||||||
removeOnClose: true,
|
removeOnClose: true,
|
||||||
enableHistory: options.enableHistory,
|
enableHistory: options.enableHistory,
|
||||||
scrollY: false
|
scrollY: false
|
||||||
|
@ -105,7 +95,6 @@ export function show(options) {
|
||||||
isFullscreen = true;
|
isFullscreen = true;
|
||||||
dialogOptions.autoFocus = true;
|
dialogOptions.autoFocus = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
dialogOptions.modal = false;
|
dialogOptions.modal = false;
|
||||||
dialogOptions.entryAnimation = options.entryAnimation;
|
dialogOptions.entryAnimation = options.entryAnimation;
|
||||||
dialogOptions.exitAnimation = options.exitAnimation;
|
dialogOptions.exitAnimation = options.exitAnimation;
|
||||||
|
@ -114,7 +103,7 @@ export function show(options) {
|
||||||
dialogOptions.autoFocus = false;
|
dialogOptions.autoFocus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dlg = dialogHelper.createDialog(dialogOptions);
|
const dlg = dialogHelper.createDialog(dialogOptions);
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
dlg.classList.add('actionsheet-fullscreen');
|
dlg.classList.add('actionsheet-fullscreen');
|
||||||
|
@ -140,10 +129,9 @@ export function show(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderIcon = false;
|
let renderIcon = false;
|
||||||
let icons = [];
|
const icons = [];
|
||||||
let itemIcon;
|
let itemIcon;
|
||||||
for (let item of options.items) {
|
for (const item of options.items) {
|
||||||
|
|
||||||
itemIcon = item.icon || (item.selected ? 'check' : null);
|
itemIcon = item.icon || (item.selected ? 'check' : null);
|
||||||
|
|
||||||
if (itemIcon) {
|
if (itemIcon) {
|
||||||
|
@ -153,7 +141,9 @@ export function show(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
if (layoutManager.tv) {
|
||||||
html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></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
|
||||||
|
@ -166,7 +156,6 @@ export function show(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
|
|
||||||
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
|
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
|
||||||
}
|
}
|
||||||
if (options.text) {
|
if (options.text) {
|
||||||
|
@ -197,10 +186,11 @@ export function show(options) {
|
||||||
menuItemClass += ' actionsheet-xlargeFont';
|
menuItemClass += ' actionsheet-xlargeFont';
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let [i, item] of options.items.entries()) {
|
// 'options.items' is HTMLOptionsCollection, so no fancy loops
|
||||||
|
for (let i = 0; i < options.items.length; i++) {
|
||||||
|
const item = options.items[i];
|
||||||
|
|
||||||
if (item.divider) {
|
if (item.divider) {
|
||||||
|
|
||||||
html += '<div class="actionsheetDivider"></div>';
|
html += '<div class="actionsheetDivider"></div>';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -214,7 +204,7 @@ export function show(options) {
|
||||||
itemIcon = icons[i];
|
itemIcon = icons[i];
|
||||||
|
|
||||||
if (itemIcon) {
|
if (itemIcon) {
|
||||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ' + itemIcon + '"></span>';
|
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
|
||||||
} else if (renderIcon && !center) {
|
} else if (renderIcon && !center) {
|
||||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
|
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
|
||||||
}
|
}
|
||||||
|
@ -226,13 +216,13 @@ export function show(options) {
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
if (item.secondaryText) {
|
if (item.secondaryText) {
|
||||||
html += '<div class="listItemBodyText secondary">' + item.secondaryText + '</div>';
|
html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
if (item.asideText) {
|
if (item.asideText) {
|
||||||
html += '<div class="listItemAside actionSheetItemAsideText">' + item.asideText + '</div>';
|
html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
|
@ -240,7 +230,7 @@ export function show(options) {
|
||||||
|
|
||||||
if (options.showCancel) {
|
if (options.showCancel) {
|
||||||
html += '<div class="buttons">';
|
html += '<div class="buttons">';
|
||||||
html += '<button is="emby-button" type="button" class="btnCloseActionSheet">' + globalize.translate('ButtonCancel') + '</button>';
|
html += `<button is="emby-button" type="button" class="btnCloseActionSheet">${globalize.translate('ButtonCancel')}</button>`;
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
@ -251,15 +241,13 @@ export function show(options) {
|
||||||
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
|
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
|
const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
|
||||||
if (btnCloseActionSheet) {
|
if (btnCloseActionSheet) {
|
||||||
btnCloseActionSheet.addEventListener('click', function () {
|
btnCloseActionSheet.addEventListener('click', function () {
|
||||||
dialogHelper.close(dlg);
|
dialogHelper.close(dlg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seeing an issue in some non-chrome browsers where this is requiring a double click
|
|
||||||
//var eventName = browser.firefox ? 'mousedown' : 'click';
|
|
||||||
let selectedId;
|
let selectedId;
|
||||||
|
|
||||||
let timeout;
|
let timeout;
|
||||||
|
@ -270,26 +258,20 @@ export function show(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
let isResolved;
|
let isResolved;
|
||||||
|
|
||||||
dlg.addEventListener('click', function (e) {
|
dlg.addEventListener('click', function (e) {
|
||||||
|
|
||||||
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
|
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
|
||||||
|
|
||||||
if (actionSheetMenuItem) {
|
if (actionSheetMenuItem) {
|
||||||
selectedId = actionSheetMenuItem.getAttribute('data-id');
|
selectedId = actionSheetMenuItem.getAttribute('data-id');
|
||||||
|
|
||||||
if (options.resolveOnClick) {
|
if (options.resolveOnClick) {
|
||||||
|
|
||||||
if (options.resolveOnClick.indexOf) {
|
if (options.resolveOnClick.indexOf) {
|
||||||
|
|
||||||
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
|
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
|
||||||
|
|
||||||
resolve(selectedId);
|
resolve(selectedId);
|
||||||
isResolved = true;
|
isResolved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resolve(selectedId);
|
resolve(selectedId);
|
||||||
isResolved = true;
|
isResolved = true;
|
||||||
|
@ -298,11 +280,9 @@ export function show(options) {
|
||||||
|
|
||||||
dialogHelper.close(dlg);
|
dialogHelper.close(dlg);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dlg.addEventListener('close', function () {
|
dlg.addEventListener('close', function () {
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
if (layoutManager.tv) {
|
||||||
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
|
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
|
||||||
}
|
}
|
|
@ -1,13 +1,23 @@
|
||||||
define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', 'serverNotifications', 'connectionManager', 'emby-button', 'listViewStyle'], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) {
|
import { Events } from 'jellyfin-apiclient';
|
||||||
'use strict';
|
import globalize from '../scripts/globalize';
|
||||||
|
import dom from '../scripts/dom';
|
||||||
|
import * as datefns from 'date-fns';
|
||||||
|
import dfnshelper from '../scripts/dfnshelper';
|
||||||
|
import serverNotifications from '../scripts/serverNotifications';
|
||||||
|
import '../elements/emby-button/emby-button';
|
||||||
|
import './listview/listview.css';
|
||||||
|
import ServerConnections from './ServerConnections';
|
||||||
|
import alert from './alert';
|
||||||
|
|
||||||
|
/* eslint-disable indent */
|
||||||
|
|
||||||
function getEntryHtml(entry, apiClient) {
|
function getEntryHtml(entry, apiClient) {
|
||||||
var html = '';
|
let html = '';
|
||||||
html += '<div class="listItem listItem-border">';
|
html += '<div class="listItem listItem-border">';
|
||||||
var color = '#00a4dc';
|
let color = '#00a4dc';
|
||||||
var icon = 'notifications';
|
let icon = 'notifications';
|
||||||
|
|
||||||
if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) {
|
if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') {
|
||||||
color = '#cc0000';
|
color = '#cc0000';
|
||||||
icon = 'notification_important';
|
icon = 'notification_important';
|
||||||
}
|
}
|
||||||
|
@ -34,10 +44,14 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
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') + '"><span class="material-icons info"></span></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>';
|
html += '</div>';
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderList(elem, apiClient, result, startIndex, limit) {
|
function renderList(elem, apiClient, result, startIndex, limit) {
|
||||||
|
@ -47,14 +61,15 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadData(instance, elem, apiClient, startIndex, limit) {
|
function reloadData(instance, elem, apiClient, startIndex, limit) {
|
||||||
if (null == startIndex) {
|
if (startIndex == null) {
|
||||||
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();
|
const minDate = new Date();
|
||||||
var hasUserId = 'false' !== elem.getAttribute('data-useractivity');
|
const hasUserId = elem.getAttribute('data-useractivity') !== 'false';
|
||||||
|
|
||||||
|
// TODO: Use date-fns
|
||||||
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
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,7 +85,7 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
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');
|
const activityContainer = dom.parentWithClass(elem, 'activityContainer');
|
||||||
|
|
||||||
if (activityContainer) {
|
if (activityContainer) {
|
||||||
if (result.Items.length) {
|
if (result.Items.length) {
|
||||||
|
@ -87,7 +102,7 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
}
|
}
|
||||||
|
|
||||||
function onActivityLogUpdate(e, apiClient, data) {
|
function onActivityLogUpdate(e, apiClient, data) {
|
||||||
var options = this.options;
|
const options = this.options;
|
||||||
|
|
||||||
if (options && options.serverId === apiClient.serverId()) {
|
if (options && options.serverId === apiClient.serverId()) {
|
||||||
reloadData(this, options.element, apiClient);
|
reloadData(this, options.element, apiClient);
|
||||||
|
@ -95,14 +110,14 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
}
|
}
|
||||||
|
|
||||||
function onListClick(e) {
|
function onListClick(e) {
|
||||||
var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
|
const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
|
||||||
|
|
||||||
if (btnEntryInfo) {
|
if (btnEntryInfo) {
|
||||||
var id = btnEntryInfo.getAttribute('data-id');
|
const id = btnEntryInfo.getAttribute('data-id');
|
||||||
var items = this.items;
|
const items = this.items;
|
||||||
|
|
||||||
if (items) {
|
if (items) {
|
||||||
var item = items.filter(function (i) {
|
const item = items.filter(function (i) {
|
||||||
return i.Id.toString() === id;
|
return i.Id.toString() === id;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
|
@ -114,43 +129,43 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
|
||||||
}
|
}
|
||||||
|
|
||||||
function showItemOverview(item) {
|
function showItemOverview(item) {
|
||||||
require(['alert'], function (alert) {
|
alert({
|
||||||
alert({
|
text: item.Overview
|
||||||
text: item.Overview
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActivityLog(options) {
|
class ActivityLog {
|
||||||
|
constructor(options) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
var element = options.element;
|
const 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);
|
const apiClient = ServerConnections.getApiClient(options.serverId);
|
||||||
reloadData(this, element, apiClient);
|
reloadData(this, element, apiClient);
|
||||||
var onUpdate = onActivityLogUpdate.bind(this);
|
const 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');
|
||||||
}
|
}
|
||||||
|
destroy() {
|
||||||
ActivityLog.prototype.destroy = function () {
|
const 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');
|
ServerConnections.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
|
||||||
}
|
}
|
||||||
|
|
||||||
var onUpdate = this.updateFn;
|
const onUpdate = this.updateFn;
|
||||||
|
|
||||||
if (onUpdate) {
|
if (onUpdate) {
|
||||||
events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
|
Events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.items = null;
|
this.items = null;
|
||||||
this.options = null;
|
this.options = null;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ActivityLog;
|
export default ActivityLog;
|
||||||
});
|
|
||||||
|
/* eslint-enable indent */
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize) {
|
|
||||||
'use strict';
|
import browser from '../scripts/browser';
|
||||||
|
import dialog from './dialog/dialog';
|
||||||
|
import globalize from '../scripts/globalize';
|
||||||
|
|
||||||
|
/* eslint-disable indent */
|
||||||
|
|
||||||
function replaceAll(originalString, strReplace, strWith) {
|
function replaceAll(originalString, strReplace, strWith) {
|
||||||
var reg = new RegExp(strReplace, 'ig');
|
const reg = new RegExp(strReplace, 'ig');
|
||||||
return originalString.replace(reg, strWith);
|
return originalString.replace(reg, strWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (text, title) {
|
export default function (text, title) {
|
||||||
|
let options;
|
||||||
var options;
|
|
||||||
if (typeof text === 'string') {
|
if (typeof text === 'string') {
|
||||||
options = {
|
options = {
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -21,7 +24,7 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
|
||||||
if (browser.tv && window.alert) {
|
if (browser.tv && window.alert) {
|
||||||
alert(replaceAll(options.text || '', '<br/>', '\n'));
|
alert(replaceAll(options.text || '', '<br/>', '\n'));
|
||||||
} else {
|
} else {
|
||||||
var items = [];
|
const items = [];
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
name: globalize.translate('ButtonGotIt'),
|
name: globalize.translate('ButtonGotIt'),
|
||||||
|
@ -31,7 +34,7 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
|
||||||
|
|
||||||
options.buttons = items;
|
options.buttons = items;
|
||||||
|
|
||||||
return dialog(options).then(function (result) {
|
return dialog.show(options).then(function (result) {
|
||||||
if (result === 'ok') {
|
if (result === 'ok') {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
@ -41,5 +44,6 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
}
|
||||||
});
|
|
||||||
|
/* eslint-enable indent */
|
||||||
|
|
313
src/components/alphaPicker/alphaPicker.js
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
/* eslint-disable indent */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module alphaPicker.
|
||||||
|
* @module components/alphaPicker/alphaPicker
|
||||||
|
*/
|
||||||
|
|
||||||
|
import focusManager from '../focusManager';
|
||||||
|
import layoutManager from '../layoutManager';
|
||||||
|
import dom from '../../scripts/dom';
|
||||||
|
import './style.css';
|
||||||
|
import '../../elements/emby-button/paper-icon-button-light';
|
||||||
|
import 'material-design-icons-iconfont';
|
||||||
|
|
||||||
|
const selectedButtonClass = 'alphaPickerButton-selected';
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
const scope = this;
|
||||||
|
const selected = scope.querySelector(`.${selectedButtonClass}`);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
focusManager.focus(selected);
|
||||||
|
} else {
|
||||||
|
focusManager.autoFocus(scope, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlphaPickerButtonClassName(vertical) {
|
||||||
|
let alphaPickerButtonClassName = 'alphaPickerButton';
|
||||||
|
|
||||||
|
if (layoutManager.tv) {
|
||||||
|
alphaPickerButtonClassName += ' alphaPickerButton-tv';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertical) {
|
||||||
|
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
|
||||||
|
}
|
||||||
|
|
||||||
|
return alphaPickerButtonClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLetterButton(l, vertical) {
|
||||||
|
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapLetters(letters, vertical) {
|
||||||
|
return letters.map(l => {
|
||||||
|
return getLetterButton(l, vertical);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(element, options) {
|
||||||
|
element.classList.add('alphaPicker');
|
||||||
|
|
||||||
|
if (layoutManager.tv) {
|
||||||
|
element.classList.add('alphaPicker-tv');
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertical = element.classList.contains('alphaPicker-vertical');
|
||||||
|
|
||||||
|
if (!vertical) {
|
||||||
|
element.classList.add('focuscontainer-x');
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
let letters;
|
||||||
|
|
||||||
|
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
|
||||||
|
|
||||||
|
let rowClassName = 'alphaPickerRow';
|
||||||
|
|
||||||
|
if (vertical) {
|
||||||
|
rowClassName += ' alphaPickerRow-vertical';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<div class="${rowClassName}">`;
|
||||||
|
if (options.mode === 'keyboard') {
|
||||||
|
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
|
||||||
|
} else {
|
||||||
|
letters = ['#'];
|
||||||
|
html += mapLetters(letters, vertical).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
||||||
|
html += mapLetters(letters, vertical).join('');
|
||||||
|
|
||||||
|
if (options.mode === 'keyboard') {
|
||||||
|
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>`;
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
html += `<div class="${rowClassName}">`;
|
||||||
|
html += '<br/>';
|
||||||
|
html += mapLetters(letters, vertical).join('');
|
||||||
|
html += '</div>';
|
||||||
|
} else {
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = html;
|
||||||
|
|
||||||
|
element.classList.add('focusable');
|
||||||
|
element.focus = focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AlphaPicker {
|
||||||
|
constructor(options) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
const element = options.element;
|
||||||
|
const itemsContainer = options.itemsContainer;
|
||||||
|
const itemClass = options.itemClass;
|
||||||
|
|
||||||
|
let itemFocusValue;
|
||||||
|
let itemFocusTimeout;
|
||||||
|
|
||||||
|
function onItemFocusTimeout() {
|
||||||
|
itemFocusTimeout = null;
|
||||||
|
self.value(itemFocusValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alphaFocusedElement;
|
||||||
|
let alphaFocusTimeout;
|
||||||
|
|
||||||
|
function onAlphaFocusTimeout() {
|
||||||
|
alphaFocusTimeout = null;
|
||||||
|
|
||||||
|
if (document.activeElement === alphaFocusedElement) {
|
||||||
|
const value = alphaFocusedElement.getAttribute('data-value');
|
||||||
|
self.value(value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAlphaPickerInKeyboardModeClick(e) {
|
||||||
|
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||||
|
|
||||||
|
if (alphaPickerButton) {
|
||||||
|
const value = alphaPickerButton.getAttribute('data-value');
|
||||||
|
|
||||||
|
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
|
||||||
|
cancelable: false,
|
||||||
|
detail: {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAlphaPickerClick(e) {
|
||||||
|
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||||
|
|
||||||
|
if (alphaPickerButton) {
|
||||||
|
const value = alphaPickerButton.getAttribute('data-value');
|
||||||
|
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
|
||||||
|
this.value(null, true);
|
||||||
|
} else {
|
||||||
|
this.value(value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAlphaPickerFocusIn(e) {
|
||||||
|
if (alphaFocusTimeout) {
|
||||||
|
clearTimeout(alphaFocusTimeout);
|
||||||
|
alphaFocusTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||||
|
|
||||||
|
if (alphaPickerButton) {
|
||||||
|
alphaFocusedElement = alphaPickerButton;
|
||||||
|
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onItemsFocusIn(e) {
|
||||||
|
const item = dom.parentWithClass(e.target, itemClass);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
const prefix = item.getAttribute('data-prefix');
|
||||||
|
if (prefix && prefix.length) {
|
||||||
|
itemFocusValue = prefix[0];
|
||||||
|
if (itemFocusTimeout) {
|
||||||
|
clearTimeout(itemFocusTimeout);
|
||||||
|
}
|
||||||
|
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enabled = function (enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
if (itemsContainer) {
|
||||||
|
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.mode === 'keyboard') {
|
||||||
|
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.valueChangeEvent !== 'click') {
|
||||||
|
element.addEventListener('focus', onAlphaPickerFocusIn, true);
|
||||||
|
} else {
|
||||||
|
element.addEventListener('click', onAlphaPickerClick.bind(this));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (itemsContainer) {
|
||||||
|
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||||
|
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
|
||||||
|
element.removeEventListener('click', onAlphaPickerClick.bind(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render(element, options);
|
||||||
|
|
||||||
|
this.enabled(true);
|
||||||
|
this.visible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
value(value, applyValue) {
|
||||||
|
const element = this.options.element;
|
||||||
|
let btn;
|
||||||
|
let selected;
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (value != null) {
|
||||||
|
value = value.toUpperCase();
|
||||||
|
this._currentValue = value;
|
||||||
|
|
||||||
|
if (this.options.mode !== 'keyboard') {
|
||||||
|
selected = element.querySelector(`.${selectedButtonClass}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('error in querySelector:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn && btn !== selected) {
|
||||||
|
btn.classList.add(selectedButtonClass);
|
||||||
|
}
|
||||||
|
if (selected && selected !== btn) {
|
||||||
|
selected.classList.remove(selectedButtonClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._currentValue = value;
|
||||||
|
|
||||||
|
selected = element.querySelector(`.${selectedButtonClass}`);
|
||||||
|
if (selected) {
|
||||||
|
selected.classList.remove(selectedButtonClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyValue) {
|
||||||
|
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
|
||||||
|
cancelable: false,
|
||||||
|
detail: {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(name, fn) {
|
||||||
|
const element = this.options.element;
|
||||||
|
element.addEventListener(name, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(name, fn) {
|
||||||
|
const element = this.options.element;
|
||||||
|
element.removeEventListener(name, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
visible(visible) {
|
||||||
|
const element = this.options.element;
|
||||||
|
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
values() {
|
||||||
|
const element = this.options.element;
|
||||||
|
const elems = element.querySelectorAll('.alphaPickerButton');
|
||||||
|
const values = [];
|
||||||
|
for (let i = 0, length = elems.length; i < length; i++) {
|
||||||
|
values.push(elems[i].getAttribute('data-value'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
const element = this.options.element;
|
||||||
|
focusManager.autoFocus(element, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const element = this.options.element;
|
||||||
|
this.enabled(false);
|
||||||
|
element.classList.remove('focuscontainer-x');
|
||||||
|
this.options = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable indent */
|
||||||
|
export default AlphaPicker;
|
|
@ -12,7 +12,6 @@
|
||||||
.alphaPicker-fixed {
|
.alphaPicker-fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 5.5em;
|
bottom: 5.5em;
|
||||||
z-index: 999999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alphaPickerRow {
|
.alphaPickerRow {
|
|
@ -1,321 +0,0 @@
|
||||||
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var selectedButtonClass = 'alphaPickerButton-selected';
|
|
||||||
|
|
||||||
function focus() {
|
|
||||||
var scope = this;
|
|
||||||
var selected = scope.querySelector('.' + selectedButtonClass);
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
focusManager.focus(selected);
|
|
||||||
} else {
|
|
||||||
focusManager.autoFocus(scope, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAlphaPickerButtonClassName(vertical) {
|
|
||||||
|
|
||||||
var alphaPickerButtonClassName = 'alphaPickerButton';
|
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
|
||||||
alphaPickerButtonClassName += ' alphaPickerButton-tv';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vertical) {
|
|
||||||
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
|
|
||||||
}
|
|
||||||
|
|
||||||
return alphaPickerButtonClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLetterButton(l, vertical) {
|
|
||||||
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + '</button>';
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapLetters(letters, vertical) {
|
|
||||||
|
|
||||||
return letters.map(function (l) {
|
|
||||||
return getLetterButton(l, vertical);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(element, options) {
|
|
||||||
|
|
||||||
element.classList.add('alphaPicker');
|
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
|
||||||
element.classList.add('alphaPicker-tv');
|
|
||||||
}
|
|
||||||
|
|
||||||
var vertical = element.classList.contains('alphaPicker-vertical');
|
|
||||||
|
|
||||||
if (!vertical) {
|
|
||||||
element.classList.add('focuscontainer-x');
|
|
||||||
}
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
var letters;
|
|
||||||
|
|
||||||
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
|
|
||||||
|
|
||||||
var rowClassName = 'alphaPickerRow';
|
|
||||||
|
|
||||||
if (vertical) {
|
|
||||||
rowClassName += ' alphaPickerRow-vertical';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<div class="' + rowClassName + '">';
|
|
||||||
if (options.mode === 'keyboard') {
|
|
||||||
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>';
|
|
||||||
} else {
|
|
||||||
letters = ['#'];
|
|
||||||
html += mapLetters(letters, vertical).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
|
||||||
html += mapLetters(letters, vertical).join('');
|
|
||||||
|
|
||||||
if (options.mode === 'keyboard') {
|
|
||||||
html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
||||||
html += '<div class="' + rowClassName + '">';
|
|
||||||
html += '<br/>';
|
|
||||||
html += mapLetters(letters, vertical).join('');
|
|
||||||
html += '</div>';
|
|
||||||
} else {
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
element.innerHTML = html;
|
|
||||||
|
|
||||||
element.classList.add('focusable');
|
|
||||||
element.focus = focus;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AlphaPicker(options) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
this.options = options;
|
|
||||||
|
|
||||||
var element = options.element;
|
|
||||||
var itemsContainer = options.itemsContainer;
|
|
||||||
var itemClass = options.itemClass;
|
|
||||||
|
|
||||||
var itemFocusValue;
|
|
||||||
var itemFocusTimeout;
|
|
||||||
|
|
||||||
function onItemFocusTimeout() {
|
|
||||||
itemFocusTimeout = null;
|
|
||||||
self.value(itemFocusValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
var alphaFocusedElement;
|
|
||||||
var alphaFocusTimeout;
|
|
||||||
|
|
||||||
function onAlphaFocusTimeout() {
|
|
||||||
|
|
||||||
alphaFocusTimeout = null;
|
|
||||||
|
|
||||||
if (document.activeElement === alphaFocusedElement) {
|
|
||||||
var value = alphaFocusedElement.getAttribute('data-value');
|
|
||||||
self.value(value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAlphaPickerInKeyboardModeClick(e) {
|
|
||||||
|
|
||||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
|
||||||
|
|
||||||
if (alphaPickerButton) {
|
|
||||||
var value = alphaPickerButton.getAttribute('data-value');
|
|
||||||
|
|
||||||
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
|
|
||||||
cancelable: false,
|
|
||||||
detail: {
|
|
||||||
value: value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAlphaPickerClick(e) {
|
|
||||||
|
|
||||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
|
||||||
|
|
||||||
if (alphaPickerButton) {
|
|
||||||
var value = alphaPickerButton.getAttribute('data-value');
|
|
||||||
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
|
|
||||||
self.value(null, true);
|
|
||||||
} else {
|
|
||||||
self.value(value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAlphaPickerFocusIn(e) {
|
|
||||||
|
|
||||||
if (alphaFocusTimeout) {
|
|
||||||
clearTimeout(alphaFocusTimeout);
|
|
||||||
alphaFocusTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
|
||||||
|
|
||||||
if (alphaPickerButton) {
|
|
||||||
alphaFocusedElement = alphaPickerButton;
|
|
||||||
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onItemsFocusIn(e) {
|
|
||||||
|
|
||||||
var item = dom.parentWithClass(e.target, itemClass);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
var prefix = item.getAttribute('data-prefix');
|
|
||||||
if (prefix && prefix.length) {
|
|
||||||
|
|
||||||
itemFocusValue = prefix[0];
|
|
||||||
if (itemFocusTimeout) {
|
|
||||||
clearTimeout(itemFocusTimeout);
|
|
||||||
}
|
|
||||||
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.enabled = function (enabled) {
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
|
|
||||||
if (itemsContainer) {
|
|
||||||
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.mode === 'keyboard') {
|
|
||||||
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.valueChangeEvent !== 'click') {
|
|
||||||
element.addEventListener('focus', onAlphaPickerFocusIn, true);
|
|
||||||
} else {
|
|
||||||
element.addEventListener('click', onAlphaPickerClick.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (itemsContainer) {
|
|
||||||
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
|
|
||||||
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
|
|
||||||
element.removeEventListener('click', onAlphaPickerClick.bind(this));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render(element, options);
|
|
||||||
|
|
||||||
this.enabled(true);
|
|
||||||
this.visible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlphaPicker.prototype.value = function (value, applyValue) {
|
|
||||||
|
|
||||||
var element = this.options.element;
|
|
||||||
var btn;
|
|
||||||
var selected;
|
|
||||||
|
|
||||||
if (value !== undefined) {
|
|
||||||
if (value != null) {
|
|
||||||
|
|
||||||
value = value.toUpperCase();
|
|
||||||
this._currentValue = value;
|
|
||||||
|
|
||||||
if (this.options.mode !== 'keyboard') {
|
|
||||||
selected = element.querySelector('.' + selectedButtonClass);
|
|
||||||
|
|
||||||
try {
|
|
||||||
btn = element.querySelector('.alphaPickerButton[data-value=\'' + value + '\']');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('error in querySelector: ' + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btn && btn !== selected) {
|
|
||||||
btn.classList.add(selectedButtonClass);
|
|
||||||
}
|
|
||||||
if (selected && selected !== btn) {
|
|
||||||
selected.classList.remove(selectedButtonClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._currentValue = value;
|
|
||||||
|
|
||||||
selected = element.querySelector('.' + selectedButtonClass);
|
|
||||||
if (selected) {
|
|
||||||
selected.classList.remove(selectedButtonClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applyValue) {
|
|
||||||
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
|
|
||||||
cancelable: false,
|
|
||||||
detail: {
|
|
||||||
value: value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._currentValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.on = function (name, fn) {
|
|
||||||
var element = this.options.element;
|
|
||||||
element.addEventListener(name, fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.off = function (name, fn) {
|
|
||||||
var element = this.options.element;
|
|
||||||
element.removeEventListener(name, fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.visible = function (visible) {
|
|
||||||
|
|
||||||
var element = this.options.element;
|
|
||||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.values = function () {
|
|
||||||
|
|
||||||
var element = this.options.element;
|
|
||||||
var elems = element.querySelectorAll('.alphaPickerButton');
|
|
||||||
var values = [];
|
|
||||||
for (var i = 0, length = elems.length; i < length; i++) {
|
|
||||||
|
|
||||||
values.push(elems[i].getAttribute('data-value'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.focus = function () {
|
|
||||||
|
|
||||||
var element = this.options.element;
|
|
||||||
focusManager.autoFocus(element, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
AlphaPicker.prototype.destroy = function () {
|
|
||||||
|
|
||||||
var element = this.options.element;
|
|
||||||
this.enabled(false);
|
|
||||||
element.classList.remove('focuscontainer-x');
|
|
||||||
this.options = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return AlphaPicker;
|
|
||||||
});
|
|
|
@ -1,17 +1,17 @@
|
||||||
define(['browser', 'css!./appfooter'], function (browser) {
|
import './appFooter.css';
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function render(options) {
|
function render(options) {
|
||||||
var elem = document.createElement('div');
|
const elem = document.createElement('div');
|
||||||
elem.classList.add('appfooter');
|
elem.classList.add('appfooter');
|
||||||
|
|
||||||
document.body.appendChild(elem);
|
document.body.appendChild(elem);
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
function appFooter(options) {
|
class appFooter {
|
||||||
var self = this;
|
constructor(options) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
self.element = render(options);
|
self.element = render(options);
|
||||||
self.add = function (elem) {
|
self.add = function (elem) {
|
||||||
|
@ -26,12 +26,11 @@ define(['browser', 'css!./appfooter'], function (browser) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
destroy() {
|
||||||
appFooter.prototype.destroy = function () {
|
const self = this;
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.element = null;
|
self.element = null;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return appFooter;
|
export default new appFooter({});
|
||||||
});
|
|
|
@ -1,439 +1,412 @@
|
||||||
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function getBaseProfileOptions(item) {
|
import appSettings from '../scripts/settings/appSettings';
|
||||||
var disableHlsVideoAudioCodecs = [];
|
import browser from '../scripts/browser';
|
||||||
|
import { Events } from 'jellyfin-apiclient';
|
||||||
|
import * as htmlMediaHelper from '../components/htmlMediaHelper';
|
||||||
|
import * as webSettings from '../scripts/settings/webSettings';
|
||||||
|
import globalize from '../scripts/globalize';
|
||||||
|
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||||
|
|
||||||
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
|
function getBaseProfileOptions(item) {
|
||||||
if (browser.edge || browser.msie) {
|
const disableHlsVideoAudioCodecs = [];
|
||||||
disableHlsVideoAudioCodecs.push('mp3');
|
|
||||||
}
|
|
||||||
|
|
||||||
disableHlsVideoAudioCodecs.push('ac3');
|
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
|
||||||
disableHlsVideoAudioCodecs.push('eac3');
|
if (browser.edge) {
|
||||||
disableHlsVideoAudioCodecs.push('opus');
|
disableHlsVideoAudioCodecs.push('mp3');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
disableHlsVideoAudioCodecs.push('ac3');
|
||||||
enableMkvProgressive: false,
|
disableHlsVideoAudioCodecs.push('eac3');
|
||||||
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
|
disableHlsVideoAudioCodecs.push('opus');
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceProfileForWindowsUwp(item) {
|
return {
|
||||||
return new Promise(function (resolve, reject) {
|
enableMkvProgressive: false,
|
||||||
require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) {
|
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
|
||||||
var profileOptions = getBaseProfileOptions(item);
|
};
|
||||||
profileOptions.supportsDts = uwpMediaCaps.supportsDTS();
|
}
|
||||||
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
|
|
||||||
profileOptions.audioChannels = uwpMediaCaps.getAudioChannels();
|
|
||||||
resolve(profileBuilder(profileOptions));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeviceProfile(item, options) {
|
function getDeviceProfile(item, options = {}) {
|
||||||
options = options || {};
|
return new Promise(function (resolve) {
|
||||||
|
let profile;
|
||||||
|
|
||||||
if (self.Windows) {
|
if (window.NativeShell) {
|
||||||
return getDeviceProfileForWindowsUwp(item);
|
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
|
||||||
|
} else {
|
||||||
|
const builderOpts = getBaseProfileOptions(item);
|
||||||
|
profile = profileBuilder(builderOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
resolve(profile);
|
||||||
require(['browserdeviceprofile'], function (profileBuilder) {
|
});
|
||||||
var profile;
|
}
|
||||||
|
|
||||||
if (window.NativeShell) {
|
function escapeRegExp(str) {
|
||||||
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
|
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
|
||||||
} else {
|
}
|
||||||
var builderOpts = getBaseProfileOptions(item);
|
|
||||||
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
|
|
||||||
profile = profileBuilder(builderOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(profile);
|
function replaceAll(originalString, strReplace, strWith) {
|
||||||
});
|
const strReplace2 = escapeRegExp(strReplace);
|
||||||
});
|
const reg = new RegExp(strReplace2, 'ig');
|
||||||
|
return originalString.replace(reg, strWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDeviceId() {
|
||||||
|
const keys = [];
|
||||||
|
|
||||||
|
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), window.btoa) {
|
||||||
|
const result = replaceAll(btoa(keys.join('|')), '=', '1');
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeRegExp(str) {
|
return new Date().getTime();
|
||||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function replaceAll(originalString, strReplace, strWith) {
|
function getDeviceId() {
|
||||||
var strReplace2 = escapeRegExp(strReplace);
|
if (!deviceId) {
|
||||||
var reg = new RegExp(strReplace2, 'ig');
|
const key = '_deviceId2';
|
||||||
return originalString.replace(reg, strWith);
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateDeviceId() {
|
deviceId = appSettings.get(key);
|
||||||
var keys = [];
|
|
||||||
|
|
||||||
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) {
|
if (!deviceId) {
|
||||||
var result = replaceAll(btoa(keys.join('|')), '=', '1');
|
deviceId = generateDeviceId();
|
||||||
return Promise.resolve(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(new Date().getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeviceId() {
|
|
||||||
var key = '_deviceId2';
|
|
||||||
var deviceId = appSettings.get(key);
|
|
||||||
|
|
||||||
if (deviceId) {
|
|
||||||
return Promise.resolve(deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateDeviceId().then(function (deviceId) {
|
|
||||||
appSettings.set(key, deviceId);
|
appSettings.set(key, deviceId);
|
||||||
return deviceId;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceName() {
|
return deviceId;
|
||||||
var deviceName;
|
}
|
||||||
deviceName = browser.tizen ? 'Samsung Smart TV' : browser.web0s ? 'LG Smart TV' : browser.operaTv ? 'Opera TV' : browser.xboxOne ? 'Xbox One' : browser.ps4 ? 'Sony PS4' : browser.chrome ? 'Chrome' : browser.edge ? 'Edge' : browser.firefox ? 'Firefox' : browser.msie ? 'Internet Explorer' : browser.opera ? 'Opera' : browser.safari ? 'Safari' : 'Web Browser';
|
|
||||||
|
function getDeviceName() {
|
||||||
|
if (!deviceName) {
|
||||||
|
if (browser.tizen) {
|
||||||
|
deviceName = 'Samsung Smart TV';
|
||||||
|
} else if (browser.web0s) {
|
||||||
|
deviceName = 'LG Smart TV';
|
||||||
|
} else if (browser.operaTv) {
|
||||||
|
deviceName = 'Opera TV';
|
||||||
|
} else if (browser.xboxOne) {
|
||||||
|
deviceName = 'Xbox One';
|
||||||
|
} else if (browser.ps4) {
|
||||||
|
deviceName = 'Sony PS4';
|
||||||
|
} else if (browser.chrome) {
|
||||||
|
deviceName = 'Chrome';
|
||||||
|
} else if (browser.edgeChromium) {
|
||||||
|
deviceName = 'Edge Chromium';
|
||||||
|
} else if (browser.edge) {
|
||||||
|
deviceName = 'Edge';
|
||||||
|
} else if (browser.firefox) {
|
||||||
|
deviceName = 'Firefox';
|
||||||
|
} else if (browser.opera) {
|
||||||
|
deviceName = 'Opera';
|
||||||
|
} else if (browser.safari) {
|
||||||
|
deviceName = 'Safari';
|
||||||
|
} else {
|
||||||
|
deviceName = 'Web Browser';
|
||||||
|
}
|
||||||
|
|
||||||
if (browser.ipad) {
|
if (browser.ipad) {
|
||||||
deviceName += ' iPad';
|
deviceName += ' iPad';
|
||||||
} else {
|
} else if (browser.iphone) {
|
||||||
if (browser.iphone) {
|
deviceName += ' iPhone';
|
||||||
deviceName += ' iPhone';
|
} else if (browser.android) {
|
||||||
} else {
|
deviceName += ' Android';
|
||||||
if (browser.android) {
|
|
||||||
deviceName += ' Android';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deviceName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportsVoiceInput() {
|
return deviceName;
|
||||||
if (!browser.tv) {
|
}
|
||||||
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function supportsVoiceInput() {
|
||||||
|
if (!browser.tv) {
|
||||||
|
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsFullscreen() {
|
||||||
|
if (browser.tv) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportsFullscreen() {
|
const element = document.documentElement;
|
||||||
if (browser.tv) {
|
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var element = document.documentElement;
|
function getDefaultLayout() {
|
||||||
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
|
return 'desktop';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSyncProfile() {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) {
|
|
||||||
var profile;
|
|
||||||
|
|
||||||
if (window.NativeShell) {
|
|
||||||
profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings);
|
|
||||||
} else {
|
|
||||||
profile = profileBuilder();
|
|
||||||
profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(profile);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultLayout() {
|
|
||||||
return 'desktop';
|
|
||||||
}
|
|
||||||
|
|
||||||
function supportsHtmlMediaAutoplay() {
|
|
||||||
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.mobile) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function supportsHtmlMediaAutoplay() {
|
||||||
|
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportsCue() {
|
if (browser.mobile) {
|
||||||
try {
|
return false;
|
||||||
var video = document.createElement('video');
|
|
||||||
var style = document.createElement('style');
|
|
||||||
|
|
||||||
style.textContent = 'video::cue {background: inherit}';
|
|
||||||
document.body.appendChild(style);
|
|
||||||
document.body.appendChild(video);
|
|
||||||
|
|
||||||
var cue = window.getComputedStyle(video, '::cue').background;
|
|
||||||
document.body.removeChild(style);
|
|
||||||
document.body.removeChild(video);
|
|
||||||
|
|
||||||
return !!cue.length;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('error detecting cue support: ' + err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAppVisible() {
|
return true;
|
||||||
if (isHidden) {
|
}
|
||||||
isHidden = false;
|
|
||||||
console.debug('triggering app resume event');
|
function supportsCue() {
|
||||||
events.trigger(appHost, 'resume');
|
try {
|
||||||
}
|
const video = document.createElement('video');
|
||||||
|
const style = document.createElement('style');
|
||||||
|
|
||||||
|
style.textContent = 'video::cue {background: inherit}';
|
||||||
|
document.body.appendChild(style);
|
||||||
|
document.body.appendChild(video);
|
||||||
|
|
||||||
|
const cue = window.getComputedStyle(video, '::cue').background;
|
||||||
|
document.body.removeChild(style);
|
||||||
|
document.body.removeChild(video);
|
||||||
|
|
||||||
|
return !!cue.length;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('error detecting cue support: ' + err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAppVisible() {
|
||||||
|
if (isHidden) {
|
||||||
|
isHidden = false;
|
||||||
|
Events.trigger(appHost, 'resume');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAppHidden() {
|
||||||
|
if (!isHidden) {
|
||||||
|
isHidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportedFeatures = function () {
|
||||||
|
const features = [];
|
||||||
|
|
||||||
|
if (navigator.share) {
|
||||||
|
features.push('sharing');
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAppHidden() {
|
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||||
if (!isHidden) {
|
features.push('filedownload');
|
||||||
isHidden = true;
|
|
||||||
console.debug('app is hidden');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedFeatures = function () {
|
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||||
var features = [];
|
features.push('exit');
|
||||||
|
} else {
|
||||||
|
features.push('exitmenu');
|
||||||
|
features.push('plugins');
|
||||||
|
}
|
||||||
|
|
||||||
if (navigator.share) {
|
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
||||||
features.push('sharing');
|
features.push('externallinks');
|
||||||
}
|
features.push('externalpremium');
|
||||||
|
}
|
||||||
|
|
||||||
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
if (!browser.operaTv) {
|
||||||
features.push('filedownload');
|
features.push('externallinkdisplay');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
if (supportsVoiceInput()) {
|
||||||
features.push('exit');
|
features.push('voiceinput');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsHtmlMediaAutoplay()) {
|
||||||
|
features.push('htmlaudioautoplay');
|
||||||
|
features.push('htmlvideoautoplay');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.edgeUwp) {
|
||||||
|
features.push('sync');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsFullscreen()) {
|
||||||
|
features.push('fullscreenchange');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
|
||||||
|
features.push('physicalvolumecontrol');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||||
|
features.push('remotecontrol');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
||||||
|
features.push('remotevideo');
|
||||||
|
}
|
||||||
|
|
||||||
|
features.push('displaylanguage');
|
||||||
|
features.push('otherapppromotions');
|
||||||
|
features.push('displaymode');
|
||||||
|
features.push('targetblank');
|
||||||
|
features.push('screensaver');
|
||||||
|
|
||||||
|
webSettings.getMultiServer().then(enabled => {
|
||||||
|
if (enabled) features.push('multiserver');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||||
|
features.push('subtitleappearancesettings');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!browser.orsay) {
|
||||||
|
features.push('subtitleburnsettings');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
||||||
|
features.push('fileinput');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.chrome || browser.edgeChromium) {
|
||||||
|
features.push('chromecast');
|
||||||
|
}
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do exit according to platform
|
||||||
|
*/
|
||||||
|
function doExit() {
|
||||||
|
try {
|
||||||
|
if (window.NativeShell) {
|
||||||
|
window.NativeShell.AppHost.exit();
|
||||||
|
} else if (browser.tizen) {
|
||||||
|
tizen.application.getCurrentApplication().exit();
|
||||||
|
} else if (browser.web0s) {
|
||||||
|
webOS.platformBack();
|
||||||
} else {
|
} else {
|
||||||
features.push('exitmenu');
|
window.close();
|
||||||
features.push('plugins');
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('error closing application: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
let exitPromise;
|
||||||
features.push('externallinks');
|
|
||||||
features.push('externalpremium');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.operaTv) {
|
/**
|
||||||
features.push('externallinkdisplay');
|
* Ask user for exit
|
||||||
}
|
*/
|
||||||
|
function askForExit() {
|
||||||
if (supportsVoiceInput()) {
|
if (exitPromise) {
|
||||||
features.push('voiceinput');
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsHtmlMediaAutoplay()) {
|
|
||||||
features.push('htmlaudioautoplay');
|
|
||||||
features.push('htmlvideoautoplay');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.edgeUwp) {
|
|
||||||
features.push('sync');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsFullscreen()) {
|
|
||||||
features.push('fullscreenchange');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.chrome || browser.edge && !browser.slow) {
|
|
||||||
if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) {
|
|
||||||
features.push('imageanalysis');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
|
|
||||||
features.push('physicalvolumecontrol');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
|
||||||
features.push('remotecontrol');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
|
||||||
features.push('remotevideo');
|
|
||||||
}
|
|
||||||
|
|
||||||
features.push('displaylanguage');
|
|
||||||
features.push('otherapppromotions');
|
|
||||||
features.push('displaymode');
|
|
||||||
features.push('targetblank');
|
|
||||||
features.push('screensaver');
|
|
||||||
|
|
||||||
webSettings.enableMultiServer().then(enabled => {
|
|
||||||
if (enabled) features.push('multiserver');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
|
||||||
features.push('subtitleappearancesettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.orsay) {
|
|
||||||
features.push('subtitleburnsettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
|
||||||
features.push('fileinput');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.chrome) {
|
|
||||||
features.push('chromecast');
|
|
||||||
}
|
|
||||||
|
|
||||||
return features;
|
|
||||||
}();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do exit according to platform
|
|
||||||
*/
|
|
||||||
function doExit() {
|
|
||||||
try {
|
|
||||||
if (window.NativeShell) {
|
|
||||||
window.NativeShell.AppHost.exit();
|
|
||||||
} 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;
|
import('../components/actionSheet/actionSheet').then((actionsheet) => {
|
||||||
|
exitPromise = actionsheet.show({
|
||||||
/**
|
title: globalize.translate('MessageConfirmAppExit'),
|
||||||
* Ask user for exit
|
items: [
|
||||||
*/
|
{id: 'yes', name: globalize.translate('Yes')},
|
||||||
function askForExit() {
|
{id: 'no', name: globalize.translate('No')}
|
||||||
if (exitPromise) {
|
]
|
||||||
return;
|
}).then(function (value) {
|
||||||
}
|
if (value === 'yes') {
|
||||||
|
|
||||||
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 deviceName;
|
|
||||||
var appName = 'Jellyfin Web';
|
|
||||||
var appVersion = '10.6.0';
|
|
||||||
|
|
||||||
var appHost = {
|
|
||||||
getWindowState: function () {
|
|
||||||
return document.windowState || 'Normal';
|
|
||||||
},
|
|
||||||
setWindowState: function (state) {
|
|
||||||
alert('setWindowState is not supported and should not be called');
|
|
||||||
},
|
|
||||||
exit: function () {
|
|
||||||
if (!!window.appMode && browser.tizen) {
|
|
||||||
askForExit();
|
|
||||||
} else {
|
|
||||||
doExit();
|
doExit();
|
||||||
}
|
}
|
||||||
},
|
}).finally(function () {
|
||||||
supports: function (command) {
|
exitPromise = null;
|
||||||
if (window.NativeShell) {
|
});
|
||||||
return window.NativeShell.AppHost.supports(command);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1 !== supportedFeatures.indexOf(command.toLowerCase());
|
let deviceId;
|
||||||
},
|
let deviceName;
|
||||||
preferVisualCards: browser.android || browser.chrome,
|
const appName = 'Jellyfin Web';
|
||||||
moreIcon: browser.android ? 'more_vert' : 'more_horiz',
|
const appVersion = '10.7.0';
|
||||||
getSyncProfile: getSyncProfile,
|
|
||||||
getDefaultLayout: function () {
|
|
||||||
if (window.NativeShell) {
|
|
||||||
return window.NativeShell.AppHost.getDefaultLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDefaultLayout();
|
export const appHost = {
|
||||||
},
|
getWindowState: function () {
|
||||||
getDeviceProfile: getDeviceProfile,
|
return document.windowState || 'Normal';
|
||||||
init: function () {
|
},
|
||||||
if (window.NativeShell) {
|
setWindowState: function () {
|
||||||
return window.NativeShell.AppHost.init();
|
alert('setWindowState is not supported and should not be called');
|
||||||
}
|
},
|
||||||
|
exit: function () {
|
||||||
deviceName = getDeviceName();
|
if (!!window.appMode && browser.tizen) {
|
||||||
getDeviceId().then(function (id) {
|
askForExit();
|
||||||
deviceId = id;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deviceName: function () {
|
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName;
|
|
||||||
},
|
|
||||||
deviceId: function () {
|
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId;
|
|
||||||
},
|
|
||||||
appName: function () {
|
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
|
||||||
},
|
|
||||||
appVersion: function () {
|
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
|
||||||
},
|
|
||||||
getPushTokenInfo: function () {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
setThemeColor: function (color) {
|
|
||||||
var metaThemeColor = document.querySelector('meta[name=theme-color]');
|
|
||||||
|
|
||||||
if (metaThemeColor) {
|
|
||||||
metaThemeColor.setAttribute('content', color);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setUserScalable: function (scalable) {
|
|
||||||
if (!browser.tv) {
|
|
||||||
var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
|
|
||||||
document.querySelector('meta[name=viewport]').setAttribute('content', att);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var isHidden = false;
|
|
||||||
var hidden;
|
|
||||||
var visibilityChange;
|
|
||||||
|
|
||||||
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
|
|
||||||
hidden = 'hidden';
|
|
||||||
visibilityChange = 'visibilitychange';
|
|
||||||
} else if (typeof document.webkitHidden !== 'undefined') {
|
|
||||||
hidden = 'webkitHidden';
|
|
||||||
visibilityChange = 'webkitvisibilitychange';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener(visibilityChange, function () {
|
|
||||||
/* eslint-disable-next-line compat/compat */
|
|
||||||
if (document[hidden]) {
|
|
||||||
onAppHidden();
|
|
||||||
} else {
|
} else {
|
||||||
onAppVisible();
|
doExit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supports: function (command) {
|
||||||
|
if (window.NativeShell) {
|
||||||
|
return window.NativeShell.AppHost.supports(command);
|
||||||
}
|
}
|
||||||
}, false);
|
|
||||||
|
|
||||||
if (self.addEventListener) {
|
return supportedFeatures.indexOf(command.toLowerCase()) !== -1;
|
||||||
self.addEventListener('focus', onAppVisible);
|
},
|
||||||
self.addEventListener('blur', onAppHidden);
|
preferVisualCards: browser.android || browser.chrome,
|
||||||
|
getDefaultLayout: function () {
|
||||||
|
if (window.NativeShell) {
|
||||||
|
return window.NativeShell.AppHost.getDefaultLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultLayout();
|
||||||
|
},
|
||||||
|
getDeviceProfile: getDeviceProfile,
|
||||||
|
init: function () {
|
||||||
|
if (window.NativeShell) {
|
||||||
|
return window.NativeShell.AppHost.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deviceId: getDeviceId(),
|
||||||
|
deviceName: getDeviceName()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deviceName: function () {
|
||||||
|
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||||
|
},
|
||||||
|
deviceId: function () {
|
||||||
|
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||||
|
},
|
||||||
|
appName: function () {
|
||||||
|
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
||||||
|
},
|
||||||
|
appVersion: function () {
|
||||||
|
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
||||||
|
},
|
||||||
|
getPushTokenInfo: function () {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
setUserScalable: function (scalable) {
|
||||||
|
if (!browser.tv) {
|
||||||
|
const 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return appHost;
|
let isHidden = false;
|
||||||
});
|
let hidden;
|
||||||
|
let visibilityChange;
|
||||||
|
|
||||||
|
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
|
||||||
|
hidden = 'hidden';
|
||||||
|
visibilityChange = 'visibilitychange';
|
||||||
|
} else if (typeof document.webkitHidden !== 'undefined') {
|
||||||
|
hidden = 'webkitHidden';
|
||||||
|
visibilityChange = 'webkitvisibilitychange';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener(visibilityChange, function () {
|
||||||
|
/* eslint-disable-next-line compat/compat */
|
||||||
|
if (document[hidden]) {
|
||||||
|
onAppHidden();
|
||||||
|
} else {
|
||||||
|
onAppVisible();
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (window.addEventListener) {
|
||||||
|
window.addEventListener('focus', onAppVisible);
|
||||||
|
window.addEventListener('blur', onAppHidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load app host on module load
|
||||||
|
appHost.init();
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
* @module components/autoFocuser
|
* @module components/autoFocuser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import focusManager from 'focusManager';
|
import focusManager from './focusManager';
|
||||||
import layoutManager from 'layoutManager';
|
import layoutManager from './layoutManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previously selected element.
|
* Previously selected element.
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) {
|
import browser from '../../scripts/browser';
|
||||||
'use strict';
|
import { playbackManager } from '../playback/playbackmanager';
|
||||||
|
import dom from '../../scripts/dom';
|
||||||
|
import * as userSettings from '../../scripts/settings/userSettings';
|
||||||
|
import './backdrop.css';
|
||||||
|
import ServerConnections from '../ServerConnections';
|
||||||
|
|
||||||
|
/* eslint-disable indent */
|
||||||
|
|
||||||
function enableAnimation(elem) {
|
function enableAnimation(elem) {
|
||||||
if (browser.slow) {
|
if (browser.slow) {
|
||||||
|
@ -22,71 +28,70 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Backdrop() {
|
class Backdrop {
|
||||||
}
|
load(url, parent, existingBackdropImage) {
|
||||||
|
const img = new Image();
|
||||||
|
const self = this;
|
||||||
|
|
||||||
Backdrop.prototype.load = function (url, parent, existingBackdropImage) {
|
img.onload = () => {
|
||||||
var img = new Image();
|
if (self.isDestroyed) {
|
||||||
var self = this;
|
return;
|
||||||
|
|
||||||
img.onload = function () {
|
|
||||||
if (self.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var backdropImage = document.createElement('div');
|
|
||||||
backdropImage.classList.add('backdropImage');
|
|
||||||
backdropImage.classList.add('displayingBackdropImage');
|
|
||||||
backdropImage.style.backgroundImage = "url('" + url + "')";
|
|
||||||
backdropImage.setAttribute('data-url', url);
|
|
||||||
|
|
||||||
backdropImage.classList.add('backdropImageFadeIn');
|
|
||||||
parent.appendChild(backdropImage);
|
|
||||||
|
|
||||||
if (!enableAnimation(backdropImage)) {
|
|
||||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
|
||||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
|
||||||
}
|
}
|
||||||
internalBackdrop(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var onAnimationComplete = function () {
|
const backdropImage = document.createElement('div');
|
||||||
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
backdropImage.classList.add('backdropImage');
|
||||||
|
backdropImage.classList.add('displayingBackdropImage');
|
||||||
|
backdropImage.style.backgroundImage = `url('${url}')`;
|
||||||
|
backdropImage.setAttribute('data-url', url);
|
||||||
|
|
||||||
|
backdropImage.classList.add('backdropImageFadeIn');
|
||||||
|
parent.appendChild(backdropImage);
|
||||||
|
|
||||||
|
if (!enableAnimation(backdropImage)) {
|
||||||
|
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||||
|
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||||
|
}
|
||||||
|
internalBackdrop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAnimationComplete = () => {
|
||||||
|
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
if (backdropImage === self.currentAnimatingElement) {
|
||||||
|
self.currentAnimatingElement = null;
|
||||||
|
}
|
||||||
|
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||||
|
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||||
once: true
|
once: true
|
||||||
});
|
});
|
||||||
if (backdropImage === self.currentAnimatingElement) {
|
|
||||||
self.currentAnimatingElement = null;
|
internalBackdrop(true);
|
||||||
}
|
|
||||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
|
||||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
img.src = url;
|
||||||
once: true
|
|
||||||
});
|
|
||||||
|
|
||||||
internalBackdrop(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = url;
|
|
||||||
};
|
|
||||||
|
|
||||||
Backdrop.prototype.cancelAnimation = function () {
|
|
||||||
var elem = this.currentAnimatingElement;
|
|
||||||
if (elem) {
|
|
||||||
elem.classList.remove('backdropImageFadeIn');
|
|
||||||
this.currentAnimatingElement = null;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Backdrop.prototype.destroy = function () {
|
cancelAnimation() {
|
||||||
this.isDestroyed = true;
|
const elem = this.currentAnimatingElement;
|
||||||
this.cancelAnimation();
|
if (elem) {
|
||||||
};
|
elem.classList.remove('backdropImageFadeIn');
|
||||||
|
this.currentAnimatingElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var backdropContainer;
|
destroy() {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.cancelAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let backdropContainer;
|
||||||
function getBackdropContainer() {
|
function getBackdropContainer() {
|
||||||
if (!backdropContainer) {
|
if (!backdropContainer) {
|
||||||
backdropContainer = document.querySelector('.backdropContainer');
|
backdropContainer = document.querySelector('.backdropContainer');
|
||||||
|
@ -101,7 +106,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
return backdropContainer;
|
return backdropContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearBackdrop(clearAll) {
|
export function clearBackdrop(clearAll) {
|
||||||
clearRotation();
|
clearRotation();
|
||||||
|
|
||||||
if (currentLoadingBackdrop) {
|
if (currentLoadingBackdrop) {
|
||||||
|
@ -109,7 +114,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
currentLoadingBackdrop = null;
|
currentLoadingBackdrop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var elem = getBackdropContainer();
|
const elem = getBackdropContainer();
|
||||||
elem.innerHTML = '';
|
elem.innerHTML = '';
|
||||||
|
|
||||||
if (clearAll) {
|
if (clearAll) {
|
||||||
|
@ -119,7 +124,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
internalBackdrop(false);
|
internalBackdrop(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var backgroundContainer;
|
let backgroundContainer;
|
||||||
function getBackgroundContainer() {
|
function getBackgroundContainer() {
|
||||||
if (!backgroundContainer) {
|
if (!backgroundContainer) {
|
||||||
backgroundContainer = document.querySelector('.backgroundContainer');
|
backgroundContainer = document.querySelector('.backgroundContainer');
|
||||||
|
@ -135,31 +140,27 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasInternalBackdrop;
|
let hasInternalBackdrop;
|
||||||
function internalBackdrop(enabled) {
|
function internalBackdrop(enabled) {
|
||||||
hasInternalBackdrop = enabled;
|
hasInternalBackdrop = enabled;
|
||||||
setBackgroundContainerBackgroundEnabled();
|
setBackgroundContainerBackgroundEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasExternalBackdrop;
|
let hasExternalBackdrop;
|
||||||
function externalBackdrop(enabled) {
|
export function externalBackdrop(enabled) {
|
||||||
hasExternalBackdrop = enabled;
|
hasExternalBackdrop = enabled;
|
||||||
setBackgroundContainerBackgroundEnabled();
|
setBackgroundContainerBackgroundEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandom(min, max) {
|
let currentLoadingBackdrop;
|
||||||
return Math.floor(Math.random() * (max - min) + min);
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentLoadingBackdrop;
|
|
||||||
function setBackdropImage(url) {
|
function setBackdropImage(url) {
|
||||||
if (currentLoadingBackdrop) {
|
if (currentLoadingBackdrop) {
|
||||||
currentLoadingBackdrop.destroy();
|
currentLoadingBackdrop.destroy();
|
||||||
currentLoadingBackdrop = null;
|
currentLoadingBackdrop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var elem = getBackdropContainer();
|
const elem = getBackdropContainer();
|
||||||
var existingBackdropImage = elem.querySelector('.displayingBackdropImage');
|
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
|
||||||
|
|
||||||
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
|
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
|
||||||
if (existingBackdropImage.getAttribute('data-url') === url) {
|
if (existingBackdropImage.getAttribute('data-url') === url) {
|
||||||
|
@ -168,7 +169,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
existingBackdropImage.classList.remove('displayingBackdropImage');
|
existingBackdropImage.classList.remove('displayingBackdropImage');
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance = new Backdrop();
|
const instance = new Backdrop();
|
||||||
instance.load(url, elem, existingBackdropImage);
|
instance.load(url, elem, existingBackdropImage);
|
||||||
currentLoadingBackdrop = instance;
|
currentLoadingBackdrop = instance;
|
||||||
}
|
}
|
||||||
|
@ -176,9 +177,9 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
function getItemImageUrls(item, imageOptions) {
|
function getItemImageUrls(item, imageOptions) {
|
||||||
imageOptions = imageOptions || {};
|
imageOptions = imageOptions || {};
|
||||||
|
|
||||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||||
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((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,
|
||||||
|
@ -189,7 +190,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
}
|
}
|
||||||
|
|
||||||
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((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,
|
||||||
|
@ -203,13 +204,13 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageUrls(items, imageOptions) {
|
function getImageUrls(items, imageOptions) {
|
||||||
var list = [];
|
const list = [];
|
||||||
var onImg = function (img) {
|
const onImg = img => {
|
||||||
list.push(img);
|
list.push(img);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var i = 0, length = items.length; i < length; i++) {
|
for (let i = 0, length = items.length; i < length; i++) {
|
||||||
var itemImages = getItemImageUrls(items[i], imageOptions);
|
const itemImages = getItemImageUrls(items[i], imageOptions);
|
||||||
itemImages.forEach(onImg);
|
itemImages.forEach(onImg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +230,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
|
|
||||||
// If you don't care about the order of the elements inside
|
// If you don't care about the order of the elements inside
|
||||||
// the array, you should sort both arrays here.
|
// the array, you should sort both arrays here.
|
||||||
for (var i = 0; i < a.length; ++i) {
|
for (let i = 0; i < a.length; ++i) {
|
||||||
if (a[i] !== b[i]) {
|
if (a[i] !== b[i]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -242,12 +243,12 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
return userSettings.enableBackdrops();
|
return userSettings.enableBackdrops();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rotationInterval;
|
let rotationInterval;
|
||||||
var currentRotatingImages = [];
|
let currentRotatingImages = [];
|
||||||
var currentRotationIndex = -1;
|
let currentRotationIndex = -1;
|
||||||
function setBackdrops(items, imageOptions, enableImageRotation) {
|
export function setBackdrops(items, imageOptions, enableImageRotation) {
|
||||||
if (enabled()) {
|
if (enabled()) {
|
||||||
var images = getImageUrls(items, imageOptions);
|
const images = getImageUrls(items, imageOptions);
|
||||||
|
|
||||||
if (images.length) {
|
if (images.length) {
|
||||||
startRotation(images, enableImageRotation);
|
startRotation(images, enableImageRotation);
|
||||||
|
@ -279,7 +280,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newIndex = currentRotationIndex + 1;
|
let newIndex = currentRotationIndex + 1;
|
||||||
if (newIndex >= currentRotatingImages.length) {
|
if (newIndex >= currentRotatingImages.length) {
|
||||||
newIndex = 0;
|
newIndex = 0;
|
||||||
}
|
}
|
||||||
|
@ -289,7 +290,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearRotation() {
|
function clearRotation() {
|
||||||
var interval = rotationInterval;
|
const interval = rotationInterval;
|
||||||
if (interval) {
|
if (interval) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
|
@ -299,7 +300,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
currentRotationIndex = -1;
|
currentRotationIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBackdrop(url, imageOptions) {
|
export function setBackdrop(url, imageOptions) {
|
||||||
if (url && typeof url !== 'string') {
|
if (url && typeof url !== 'string') {
|
||||||
url = getImageUrls([url], imageOptions)[0];
|
url = getImageUrls([url], imageOptions)[0];
|
||||||
}
|
}
|
||||||
|
@ -312,10 +313,11 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
/* eslint-enable indent */
|
||||||
setBackdrops: setBackdrops,
|
|
||||||
setBackdrop: setBackdrop,
|
export default {
|
||||||
clear: clearBackdrop,
|
setBackdrops: setBackdrops,
|
||||||
externalBackdrop: externalBackdrop
|
setBackdrop: setBackdrop,
|
||||||
};
|
clearBackdrop: clearBackdrop,
|
||||||
});
|
externalBackdrop: externalBackdrop
|
||||||
|
};
|
||||||
|
|