Compare commits

..

38 Commits

Author SHA1 Message Date
Joshua M. Boniface
fc461a830d Fix Release ref for Fedora 2020-08-30 16:45:54 -04:00
Joshua M. Boniface
e6e5f38a0d Bump version to 10.6.4 2020-08-30 16:45:40 -04:00
dkanada
756e322e1c Merge pull request #1820 from thornbill/fix-guide-margin
Remove horizontal margins on guide

(cherry picked from commit 9e3a27266c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-30 16:15:07 -04:00
Anthony Lavado
c9ed8c750e Merge pull request #1823 from thornbill/extra-blury
Set background color on blurhash image load

(cherry picked from commit 5a2c0beec5)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-30 16:14:54 -04:00
Joshua M. Boniface
5f1af65c2e Merge pull request #1849 from brianjmurrell/patch-1
Add BuildRequires: git to Fedora specfile
2020-08-30 16:05:29 -04:00
Brian J. Murrell
ff88922f19 Add BuildRequires: git to Fedora specfile
Sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't
add it as a BuildRequires:.

This really should be reported upstream at yarnpkg.com.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2020-08-27 12:29:07 -04:00
dkanada
47700dc29c Merge pull request #1818 from dmitrylyzo/fix-non-es6
Fix non-ES6 import [10.6.z]
2020-08-18 23:15:12 +09:00
Dmitry Lyzo
c2ebc35080 Fix non-ES6 import 2020-08-18 15:01:41 +03:00
Joshua M. Boniface
d040177525 Bump version to 10.6.3 2020-08-16 19:48:04 -04:00
dkanada
cac6b42dab Merge pull request #1799 from matjaz321/checkbox-space-ff-issue
Hitting space to check/uncheck checkboxes doesn't work on firefox

(cherry picked from commit fea28f7277)
2020-08-16 19:47:09 -04:00
dkanada
15e9b645f2 Merge pull request #1794 from bugfixin/master
Remove extraneous pageContainer element from videoOSD

(cherry picked from commit 926f1cbbcc)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-16 19:29:20 -04:00
dkanada
1833b348e9 Merge pull request #1748 from dmitrylyzo/fix-old-edge
Fix old Edge loading

(cherry picked from commit 73e3a94755)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-16 19:29:17 -04:00
dkanada
ccb74610aa Merge pull request #1542 from dmitrylyzo/fix-osd-lock
Fix OSD lock

(cherry picked from commit 04b2763447)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-16 19:21:38 -04:00
Joshua M. Boniface
5c37ee6276 Bump version to 10.6.2 2020-08-02 20:26:00 -04:00
dkanada
e25721cb34 Merge pull request #1734 from dmitrylyzo/fix-plugin-configuration
Fix injecting of view with embedded script

(cherry picked from commit 6607718edb)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:24:11 -04:00
dkanada
f11b27fc14 Merge pull request #1701 from MrTimscampi/mobile-logo
Use emblem when loading on mobile

(cherry picked from commit 60d9af0d75)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:48 -04:00
dkanada
26d29dc2cb Merge pull request #1686 from Larvitar/master
Fix issue with nowplaying page when item.Album or item.Artists are null.

(cherry picked from commit b1aa18e7e7)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:48 -04:00
Anthony Lavado
537b3a3351 Merge pull request #1684 from thornbill/fix-ios-fullscreeen
Fix fullscreen video in iOS Safari

(cherry picked from commit 9b6a90ffdd)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
ab58d3c3a1 Merge pull request #1681 from jellyfin/book-paging
Add paging in book player with touch events

(cherry picked from commit cefdde1848)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
e7be594b8f Merge pull request #1678 from jellyfin/banner
Fix banner height and use primary image as fallback

(cherry picked from commit 34d0b67f0e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
229294aecc Merge pull request #1672 from MrTimscampi/browsers
Adjust target browsers

(cherry picked from commit feab1aca89)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
e41c1854d0 Merge pull request #1669 from joshuaboniface/fix-ci-docker
Flip quoting in variable set command

(cherry picked from commit 40b98bb3d4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
Anthony Lavado
e355fcdac0 Merge pull request #1667 from joshuaboniface/fix-ci-docker
Get and tag with actual release version in CI

(cherry picked from commit 2c53a329e7)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
453d225c84 Merge pull request #1665 from joshuaboniface/fix-bump-version
Fix bump_version so it works properly

(cherry picked from commit c8966a7f7d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
dkanada
ec9ab39d26 Merge pull request #1656 from dmitrylyzo/fix-plugin-install
Fix CircleOfDeath on plugin install

(cherry picked from commit 89e584686e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-08-02 20:15:47 -04:00
Joshua M. Boniface
c2d9ca2af9 Fix bad Debian package name in changelog 2020-07-27 18:57:44 -04:00
Joshua M. Boniface
eba30bda75 Bump version to 10.6.1 2020-07-27 18:30:12 -04:00
Anthony Lavado
5c9847468f Merge pull request #1662 from dmitrylyzo/fix-ios-transcode
Add h264 codec profile for TS container

(cherry picked from commit 58198df5ce)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:19:52 -04:00
Joshua M. Boniface
027cb4f744 Merge pull request #1660 from nyanmisaka/edge-chromium
Fix mkv directplay on Edge chromium

(cherry picked from commit 0408ff0867)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:21 -04:00
dkanada
37cde45d12 Merge pull request #1641 from dmitrylyzo/fix-event-source-2
Fix event source for inputManager in case of multiple open dialogs

(cherry picked from commit f5e93a18de)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:21 -04:00
dkanada
875c4e0882 Merge pull request #1632 from dmitrylyzo/remove-leftovers-1
Remove debug leftovers

(cherry picked from commit 71646b6131)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:21 -04:00
dkanada
ee9d651246 Merge pull request #1628 from dmitrylyzo/fix-webos3
Use NodeList instead of HTMLCollection

(cherry picked from commit 5bc2d567ab)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
dkanada
7f133ff8cd Merge pull request #1627 from rom4nik/master
Fix required track count for subtitle selector to appear

(cherry picked from commit 120ce4f0ff)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
dkanada
76f9b2c741 Merge pull request #1625 from MrTimscampi/syncplay-version-check
Add version check for SyncPlay

(cherry picked from commit 03a9e73b3b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
Bond-009
51768eb119 Merge pull request #1624 from MrTimscampi/apiclient-update
Update apiclient to 1.4.1

(cherry picked from commit 930197ac9e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
dkanada
cdaf03a769 Merge pull request #1620 from MrTimscampi/metadata-more-fix
Restore More button in metadata editor

(cherry picked from commit 995cbdde16)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
dkanada
f1acfda7d2 Merge pull request #1612 from Maxr1998/master
Add support for seeking with milliseconds

(cherry picked from commit 6b45755480)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
dkanada
fef30a0be3 Merge pull request #1595 from jellyfin/error
Fix issue with sync menu and excessive logging

(cherry picked from commit 7c2472785b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-07-27 18:11:20 -04:00
589 changed files with 53965 additions and 59969 deletions

View File

@@ -8,6 +8,8 @@ jobs:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
pool:
vmImage: 'ubuntu-latest'
@@ -19,15 +21,15 @@ jobs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Cache node_modules'
displayName: 'Check Cache'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
env:
SKIP_PREPARE: 'true'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn build:development'
displayName: 'Build Development'
@@ -37,6 +39,10 @@ jobs:
displayName: 'Build Production'
condition: eq(variables['BuildConfiguration'], 'production')
- script: 'yarn build:standalone'
displayName: 'Build Standalone'
condition: eq(variables['BuildConfiguration'], 'standalone')
- script: 'test -d dist'
displayName: 'Check Build'

View 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: 'Check Cache'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn run lint --quiet'
displayName: 'Run ESLint'
- script: 'yarn run stylelint'
displayName: 'Run Stylelint'

View File

@@ -13,4 +13,5 @@ pr:
jobs:
- template: azure-pipelines-build.yml
- template: azure-pipelines-lint.yml
- template: azure-pipelines-package.yml

5
.dependabot/config.yml Normal file
View File

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

View File

@@ -8,5 +8,5 @@ trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
[*.json]
[json]
indent_size = 2

View File

@@ -2,3 +2,4 @@ node_modules
dist
.idea
.vscode
src/libraries

View File

@@ -1,9 +1,6 @@
const restrictedGlobals = require('confusing-browser-globals');
module.exports = {
root: true,
plugins: [
'@babel',
'promise',
'import',
'eslint-comments'
@@ -25,12 +22,13 @@ module.exports = {
'eslint:recommended',
// 'plugin:promise/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:eslint-comments/recommended',
'plugin:compat/recommended'
],
rules: {
'block-spacing': ['error'],
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'brace-style': ['error'],
'comma-dangle': ['error', 'never'],
'comma-spacing': ['error'],
'eol-last': ['error'],
@@ -40,25 +38,19 @@ module.exports = {
'no-floating-decimal': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-restricted-globals': ['error'].concat(restrictedGlobals),
'no-trailing-spaces': ['error'],
'@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'one-var': ['error', 'never'],
'padded-blocks': ['error', 'never'],
'prefer-const': ['error', {'destructuring': 'all'}],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'@babel/semi': ['error'],
'no-var': ['error'],
'semi': ['error'],
'space-before-blocks': ['error'],
'space-infix-ops': 'error',
'yoda': 'error'
'space-infix-ops': 'error'
},
overrides: [
{
files: [
'./src/**/*.js'
],
parser: '@babel/eslint-parser',
parser: 'babel-eslint',
env: {
node: false,
amd: true,
@@ -78,12 +70,17 @@ module.exports = {
// Dependency globals
'$': 'readonly',
'jQuery': 'readonly',
'requirejs': 'readonly',
// Jellyfin globals
'ApiClient': 'writable',
'AppInfo': 'writable',
'chrome': 'writable',
'ConnectionManager': 'writable',
'DlnaProfilePage': 'writable',
'Dashboard': 'writable',
'DashboardPage': 'writable',
'Emby': 'readonly',
'Events': 'writable',
'getParameterByName': 'writable',
'getWindowLocationSearch': 'writable',
'Globalize': 'writable',
@@ -92,8 +89,9 @@ module.exports = {
'LibraryMenu': 'writable',
'LinkParser': 'writable',
'LiveTvHelpers': 'writable',
'Loading': 'writable',
'MetadataEditor': 'writable',
'pageClassOn': 'writable',
'pageIdOn': 'writable',
'PlaylistViewer': 'writable',
'UserParentalControlPage': 'writable',
'Windows': 'readonly'
@@ -101,8 +99,10 @@ module.exports = {
rules: {
// TODO: Fix warnings and remove these rules
'no-redeclare': ['warn'],
'no-unused-vars': ['warn'],
'no-useless-escape': ['warn'],
'no-unused-vars': ['warn']
// TODO: Remove after ES6 migration is complete
'import/no-unresolved': ['off']
},
settings: {
polyfills: [
@@ -193,4 +193,4 @@ module.exports = {
}
}
]
};
}

4
.github/CODEOWNERS vendored
View File

@@ -1,6 +1,4 @@
.ci @dkanada @EraYaN
.github @jellyfin/core
fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
build.sh @joshuaboniface
deployment @joshuaboniface

View File

@@ -1,22 +0,0 @@
---
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. -->

View File

@@ -1,13 +0,0 @@
---
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. -->

View File

@@ -1,9 +0,0 @@
---
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]
* [ ] ...

View File

@@ -1,20 +1,23 @@
---
name: Bug Report
about: You have noticed a general issue or regression, and would like to report it
name: Bug report
about: Create a bug report
title: ''
labels: bug
assignees: ''
---
**Describe The Bug**
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Steps To Reproduce**
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected Behavior**
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
@@ -24,9 +27,9 @@ labels: bug
<!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):**
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
- OS: [e.g. Docker, Debian, Windows]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.6.0]
- Jellyfin Version: [e.g. 10.0.1]
**Additional Context**
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@@ -1,8 +0,0 @@
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
View File

@@ -1,24 +0,0 @@
# 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.

View File

@@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,95 +0,0 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
run-eslint:
name: Run eslint
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run eslint
run: yarn lint
run-stylelint-css:
name: Run stylelint (css)
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Set up stylelint matcher
uses: xt0rted/stylelint-problem-matcher@v1
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: yarn stylelint:css
run-stylelint-scss:
name: Run stylelint (scss)
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Set up stylelint matcher
uses: xt0rted/stylelint-problem-matcher@v1
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: yarn stylelint:scss

View File

@@ -1,15 +0,0 @@
name: "Merge Conflicts"
on:
push:
branches:
- master
jobs:
triage:
runs-on: ubuntu-latest
if: github.repository == 'jellyfin/jellyfin-web'
steps:
- uses: mschilde/auto-label-merge-conflicts@master
with:
CONFLICT_LABEL_NAME: "merge conflict"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -3,9 +3,6 @@ dist
web
node_modules
# config
config.json
# ide
.idea
.vscode

View File

@@ -1,14 +1,14 @@
{
"plugins": [
"stylelint-no-browser-hacks/lib"
"stylelint-no-browser-hacks/lib",
],
"rules": {
"at-rule-empty-line-before": [ "always", {
"except": [
except: [
"blockless-after-same-name-blockless",
"first-nested"
"first-nested",
],
"ignore": ["after-comment"]
ignore: ["after-comment"],
} ],
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
@@ -26,27 +26,27 @@
"color-hex-length": "short",
"color-no-invalid-hex": true,
"comment-empty-line-before": [ "always", {
"except": ["first-nested"],
"ignore": ["stylelint-commands"]
except: ["first-nested"],
ignore: ["stylelint-commands"],
} ],
"comment-no-empty": true,
"comment-whitespace-inside": "always",
"custom-property-empty-line-before": [ "always", {
"except": [
except: [
"after-custom-property",
"first-nested"
"first-nested",
],
"ignore": [
ignore: [
"after-comment",
"inside-single-line-block"
]
"inside-single-line-block",
],
} ],
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignore": ["consecutive-duplicates-with-different-values"]
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
@@ -105,8 +105,8 @@
}
],
"rule-empty-line-before": [ "always-multi-line", {
"except": ["first-nested"],
"ignore": ["after-comment"]
except: ["first-nested"],
ignore: ["after-comment"],
} ],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
@@ -138,6 +138,6 @@
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0
"value-list-max-empty-lines": 0,
}
}

View File

@@ -1,9 +0,0 @@
{
"extends": [ "./.stylelintrc.json" ],
"plugins": [ "stylelint-scss" ],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"plugin/no-browser-hacks": null
}
}

View File

@@ -34,17 +34,8 @@
- [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [artiume](https://github.com/Artiume)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [Sarab Singh](https://github.com/sarab97)
- [DesertCookie](https://github.com/desertcookie)
- [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)
- [danieladov](https://github.com/danieladov)
- [Stephane Senart](https://github.com/ssenart)
# Emby Contributors

View File

@@ -44,7 +44,6 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies
- [Node.js](https://nodejs.org/en/download)
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
- Gulp-cli
@@ -69,8 +68,14 @@ Jellyfin Web is the frontend used for most of the clients available for end user
yarn serve
```
4. Build the client with sourcemaps available.
4. Build the client with sourcemaps.
```sh
yarn build:development
```
You can build a nginx compatible version as well.
```sh
yarn build:standalone
```

View File

@@ -1,21 +0,0 @@
module.exports = {
babelrcRoots: [
// Keep the root as a root
'.'
],
sourceType: 'unambiguous',
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3
}
]
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-private-methods',
'babel-plugin-dynamic-import-polyfill'
]
};

View File

@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin-web"
version: "10.7.7"
version: "10.6.4"
packages:
- debian.all
- fedora.all

46
debian/changelog vendored
View File

@@ -1,45 +1,29 @@
jellyfin-web (10.7.7-1) unstable; urgency=medium
jellyfin-web (10.6.4-1) unstable; urgency=medium
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
* New upstream version 10.6.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 30 Aug 2020 16:45:08 -0400
jellyfin-web (10.7.6-1) unstable; urgency=medium
jellyfin-web (10.6.3-1) unstable; urgency=medium
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
* New upstream version 10.6.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 16 Aug 2020 19:48:03 -0400
jellyfin-web (10.7.5-1) unstable; urgency=medium
jellyfin-web (10.6.2-1) unstable; urgency=medium
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
* New upstream version 10.6.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.2
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 02 Aug 2020 20:25:58 -0400
jellyfin-web (10.7.4-1) unstable; urgency=medium
jellyfin-web (10.6.1-1) unstable; urgency=medium
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
* New upstream version 10.6.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.1
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 18:29:54 -0400
jellyfin-web (10.7.3-1) unstable; urgency=medium
jellyfin-web (10.6.0-1) unstable; urgency=medium
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
jellyfin-web (10.7.2-1) unstable; urgency=medium
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
jellyfin-web (10.7.1-1) unstable; urgency=medium
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 21 Mar 2021 19:23:29 -0400
jellyfin-web (10.7.0-1) unstable; urgency=medium
* New upstream version 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400

1
debian/conffiles vendored
View File

@@ -1 +0,0 @@
/usr/share/jellyfin/web/config.json

View File

@@ -1,9 +1,9 @@
FROM node:lts-alpine
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 python3
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python
WORKDIR ${SOURCE_DIR}
COPY . .

View File

@@ -1,4 +1,4 @@
FROM fedora:33
FROM fedora:31
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs 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
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh

View File

@@ -1,26 +1,22 @@
%global debug_package %{nil}
Name: jellyfin-web
Version: 10.7.7
Version: 10.6.4
Release: 1%{?dist}
Summary: The Free Software Media System web client
License: GPLv3
URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
Source0: jellyfin-web-%{version}.tar.gz
%if 0%{?centos}
BuildRequires: yarn
# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it
BuildRequires: git
%else
BuildRequires: nodejs-yarn
%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
%if 0%{?fedora} >= 33
BuildRequires: nodejs
%endif
# Disable Automatic Dependency Processing
AutoReqProv: no
@@ -41,28 +37,17 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%files
%defattr(644,root,root,755)
%{_datadir}/jellyfin-web
%attr(755,root,root) %{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE
%changelog
* Sun Sep 05 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
* Thu May 20 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New stable release 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
* Sun Aug 30 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.6.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.4
* Sun Aug 16 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.6.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.3
* Sun Aug 02 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.6.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.2
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
- New upstream version 10.6.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.1
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release

204
gulpfile.js Normal file
View File

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

View File

@@ -5,82 +5,146 @@
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.12.1",
"@babel/eslint-plugin": "^7.12.1",
"@babel/core": "^7.10.3",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-private-methods": "^7.12.1",
"@babel/plugin-transform-modules-umd": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@uupaa/dynamic-import-polyfill": "^1.0.2",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-polyfill": "^1.0.0",
"clean-webpack-plugin": "^3.0.0",
"confusing-browser-globals": "^1.0.10",
"copy-webpack-plugin": "^6.3.2",
"css-loader": "^5.0.1",
"@babel/plugin-proposal-private-methods": "^7.10.1",
"@babel/plugin-transform-modules-amd": "^7.9.6",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.10.3",
"autoprefixer": "^9.8.5",
"babel-eslint": "^11.0.0-beta.2",
"babel-loader": "^8.0.6",
"browser-sync": "^2.26.7",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.6.0",
"cssnano": "^4.1.10",
"eslint": "^7.14.0",
"del": "^5.1.0",
"eslint": "^6.8.0",
"eslint-plugin-compat": "^3.5.1",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-promise": "^4.2.1",
"expose-loader": "^1.0.3",
"file-loader": "^6.2.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.5.0",
"file-loader": "^6.0.0",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-cli": "^2.3.0",
"gulp-concat": "^2.6.1",
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-inject": "^5.0.5",
"gulp-mode": "^1.0.2",
"gulp-postcss": "^8.0.0",
"gulp-sass": "^4.0.2",
"gulp-sourcemaps": "^2.6.5",
"gulp-terser": "^1.2.0",
"html-webpack-plugin": "^4.3.0",
"lazypipe": "^1.0.2",
"node-sass": "^4.13.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"source-map-loader": "^1.1.1",
"style-loader": "^2.0.0",
"stylelint": "^13.8.0",
"style-loader": "^1.1.3",
"stylelint": "^13.6.1",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-no-browser-hacks": "^1.2.1",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.18.0",
"webpack": "^5.9.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^3.11.0",
"webpack": "^4.41.5",
"webpack-merge": "^4.2.2",
"workbox-webpack-plugin": "^6.1.5",
"worker-plugin": "^5.0.0"
"webpack-stream": "^5.2.1"
},
"dependencies": {
"alameda": "^1.4.0",
"blurhash": "^1.1.3",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.8.0",
"date-fns": "^2.16.1",
"core-js": "^3.6.5",
"date-fns": "^2.14.0",
"epubjs": "^0.3.85",
"fast-text-encoding": "^1.0.3",
"flv.js": "^1.5.0",
"fontsource-noto-sans": "^3.1.5",
"fontsource-noto-sans-hk": "^3.1.5",
"fontsource-noto-sans-jp": "^3.1.5",
"fontsource-noto-sans-kr": "^3.1.5",
"fontsource-noto-sans-sc": "^3.1.5",
"headroom.js": "^0.12.0",
"hls.js": "^0.14.17",
"intersection-observer": "^0.12.0",
"jellyfin-apiclient": "^1.8.0",
"headroom.js": "^0.11.0",
"hls.js": "^0.14.0",
"howler": "^2.2.0",
"intersection-observer": "^0.11.0",
"jellyfin-apiclient": "^1.4.1",
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.5.1",
"jstree": "^3.3.10",
"libarchive.js": "^1.3.0",
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
"material-design-icons-iconfont": "^6.1.0",
"material-design-icons-iconfont": "^5.0.1",
"native-promise-only": "^0.8.0-a",
"page": "^1.11.6",
"pdfjs-dist": "2.5.207",
"query-string": "^6.13.1",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.2",
"sortablejs": "^1.12.0",
"swiper": "^6.3.5",
"shaka-player": "^2.5.13",
"sortablejs": "^1.10.2",
"swiper": "^5.4.5",
"webcomponents.js": "^0.7.24",
"whatwg-fetch": "^3.5.0",
"workbox-core": "^5.1.4",
"workbox-precaching": "^5.1.4"
"whatwg-fetch": "^3.2.0"
},
"babel": {
"presets": [
"@babel/preset-env"
],
"overrides": [
{
"test": [
"src/components/accessSchedule/accessSchedule.js",
"src/components/actionSheet/actionSheet.js",
"src/components/autoFocuser.js",
"src/components/cardbuilder/cardBuilder.js",
"src/components/cardbuilder/chaptercardbuilder.js",
"src/components/cardbuilder/peoplecardbuilder.js",
"src/components/images/imageLoader.js",
"src/components/indicators/indicators.js",
"src/components/lazyLoader/lazyLoaderIntersectionObserver.js",
"src/components/playback/brightnessosd.js",
"src/components/playback/mediasession.js",
"src/components/playback/nowplayinghelper.js",
"src/components/playback/playbackorientation.js",
"src/components/playback/playerSelectionMenu.js",
"src/components/playback/playersettingsmenu.js",
"src/components/playback/playmethodhelper.js",
"src/components/playback/remotecontrolautoplay.js",
"src/components/playback/volumeosd.js",
"src/components/playmenu.js",
"src/components/sanatizefilename.js",
"src/components/scrollManager.js",
"src/components/syncPlay/groupSelectionMenu.js",
"src/components/syncPlay/playbackPermissionManager.js",
"src/components/syncPlay/syncPlayManager.js",
"src/components/syncPlay/timeSyncManager.js",
"src/controllers/dashboard/logs.js",
"src/controllers/dashboard/plugins/repositories.js",
"src/controllers/user/display.js",
"src/controllers/user/home.js",
"src/controllers/user/playback.js",
"src/controllers/user/subtitles.js",
"src/plugins/bookPlayer/plugin.js",
"src/plugins/bookPlayer/tableOfContents.js",
"src/plugins/photoPlayer/plugin.js",
"src/scripts/deleteHelper.js",
"src/scripts/dfnshelper.js",
"src/scripts/dom.js",
"src/scripts/fileDownloader.js",
"src/scripts/filesystem.js",
"src/scripts/imagehelper.js",
"src/scripts/inputManager.js",
"src/plugins/backdropScreensaver/plugin.js",
"src/components/filterdialog/filterdialog.js",
"src/components/fetchhelper.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",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
}
]
},
"browserslist": [
"last 2 Firefox versions",
@@ -99,14 +163,12 @@
"Firefox ESR"
],
"scripts": {
"start": "yarn serve",
"serve": "webpack serve --config webpack.dev.js",
"prepare": "node ./scripts/prepare.js",
"build:development": "webpack --config webpack.dev.js",
"build:production": "webpack --config webpack.prod.js",
"lint": "eslint \"src/\"",
"stylelint": "yarn stylelint:css && yarn stylelint:scss",
"stylelint:css": "stylelint \"src/**/*.css\"",
"stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\""
"serve": "gulp serve --development",
"prepare": "gulp --production",
"build:development": "gulp --development",
"build:production": "gulp --production",
"build:standalone": "gulp standalone --development",
"lint": "eslint \".\"",
"stylelint": "stylelint \"src/**/*.css\""
}
}

View File

@@ -1,33 +0,0 @@
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')

View File

@@ -1,12 +0,0 @@
const { execSync } = require('child_process');
/**
* The npm `prepare` script needs to run a build to support installing
* a package from git repositories (this is dumb but a limitation of how
* npm behaves). We don't want to run these in CI though because
* building is slow so this script will skip the build when the
* `SKIP_PREPARE` environment variable has been set.
*/
if (!process.env.SKIP_PREPARE) {
execSync('webpack --config webpack.prod.js', { stdio: 'inherit' });
}

View File

@@ -16,7 +16,7 @@ langlst.append('en-us.json')
dep = []
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)
output = p.stdout.readlines()
if output:

View File

@@ -11,7 +11,7 @@ langlst = os.listdir(langdir)
keys = []
with open('unused.txt', 'r') as f:
with open('scout.txt', 'r') as f:
for line in f:
keys.append(line.strip('\n'))

View File

@@ -8,7 +8,7 @@
</div>
<br />
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Connect}</span>
<span>${ButtonConnect}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel">
<span>${ButtonCancel}</span>

View File

@@ -3,7 +3,7 @@
<div class="content-primary">
<div class="detailSectionHeader">
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">${HeaderApiKeys}</h2>
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${Add}">
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${ButtonAdd}">
<span class="material-icons add" aria-hidden="true"></span>
</button>
</div>

View File

@@ -127,8 +127,8 @@ div[data-role=controlgroup] a.ui-btn-active {
}
.sessionAppInfo img {
max-width: 2.5em;
max-height: 2.5em;
max-width: 40px;
max-height: 40px;
margin-right: 8px;
}
@@ -204,10 +204,6 @@ div[data-role=controlgroup] a.ui-btn-active {
flex-grow: 1;
}
.dashboardActionsContainer {
margin: 1em -0.3em 0;
}
.sessionNowPlayingContent {
-webkit-background-size: cover;
background-size: cover;
@@ -235,13 +231,6 @@ div[data-role=controlgroup] a.ui-btn-active {
margin-bottom: 0.5em;
}
.dashboardSection .sectionTitleTextButton > .material-icons.material-icons {
font-size: 1.17em;
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-top: 0;
}
.activeRecordingItems > .card {
width: 50%;
}
@@ -257,12 +246,20 @@ div[data-role=controlgroup] a.ui-btn-active {
@media all and (min-width: 70em) {
.dashboardSections {
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
flex-direction: row;
}
.dashboardColumn-2-60 {
flex-grow: 2;
width: 46%;
}
.dashboardColumn-2-40 {
width: 27%;
}
.dashboardSection {
@@ -294,7 +291,6 @@ div[data-role=controlgroup] a.ui-btn-active {
}
.activeSession {
min-width: 20rem;
width: 100% !important;
}
@@ -308,24 +304,27 @@ div[data-role=controlgroup] a.ui-btn-active {
background-position: center;
}
@media all and (min-width: 40em) {
.activeSession {
width: 100% !important;
}
}
@media all and (min-width: 50em) {
.activeSession {
max-width: 25rem;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 50%;
width: 50% !important;
}
}
.sessionCardFooter {
padding-top: 0.5em !important;
padding-bottom: 1em !important;
border-top: 1px solid #eee;
text-align: center;
position: relative;
}
.sessionAppInfo {
flex-grow: 1;
padding: 0.5em;
overflow: hidden;
}
@@ -345,9 +344,11 @@ div[data-role=controlgroup] a.ui-btn-active {
right: 0;
bottom: 0;
font-weight: 400;
}
.sessionNowPlayingContent-withbackground + .sessionNowPlayingInnerContent {
color: #fff !important;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
}
.sessionAppName {
@@ -357,6 +358,9 @@ div[data-role=controlgroup] a.ui-btn-active {
.sessionNowPlayingDetails {
display: flex;
position: absolute;
bottom: 0;
width: 100%;
}
.sessionNowPlayingInfo {
@@ -372,6 +376,10 @@ div[data-role=controlgroup] a.ui-btn-active {
padding: 0.8em 0.5em;
}
.sessionNowPlayingStreamInfo {
white-space: nowrap;
}
.playbackProgress,
.transcodingProgress {
margin: 0;
@@ -379,12 +387,6 @@ div[data-role=controlgroup] a.ui-btn-active {
background: transparent !important;
}
.activeDevices.itemsContainer {
/* offset for cardBox margin */
margin: -0.6em;
}
.activeSession .backgroundProgress,
.activeSession .playbackProgress,
.activeSession .transcodingProgress {
position: absolute;
@@ -401,14 +403,9 @@ div[data-role=controlgroup] a.ui-btn-active {
}
.transcodingProgress > div {
z-index: 10;
background-color: #dd4919;
}
.backgroundProgress > div {
background-color: #303030;
}
@media all and (max-width: 34.375em) {
.sessionAppName {
max-width: 160px;

37
src/assets/css/fonts.css Normal file
View File

@@ -0,0 +1,37 @@
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%;
}

View File

@@ -1,36 +0,0 @@
@import "../../styles/noto-sans/index.scss";
@mixin font($weight: null, $size: null) {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
font-weight: $weight;
font-size: $size;
}
html {
@include font($size: 93%);
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%;
}

View File

@@ -0,0 +1,31 @@
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;
}
}

View File

@@ -1,31 +0,0 @@
@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);
}

View File

@@ -28,10 +28,6 @@
padding-top: 0 !important;
}
.layout-tv .itemDetailPage {
padding-top: 4.2em !important;
}
.standalonePage {
padding-top: 4.5em !important;
}
@@ -167,12 +163,6 @@
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 {
display: none;
}
@@ -246,30 +236,16 @@
text-align: center;
}
.layout-desktop .searchTabButton,
.layout-mobile .searchTabButton,
.layout-tv .headerSearchButton {
display: none !important;
}
.mainDrawer-scrollContainer {
padding-bottom: 10vh;
}
.primaryImageWrapper {
display: none;
}
.primaryImageWrapper > img {
display: block;
margin: 0 auto;
max-width: 80vw;
max-height: 50vh;
}
.primaryImageWrapper > img.aspect-square {
max-height: 45vh;
}
.layout-mobile .primaryImageWrapper {
display: block;
flex: 1 0 auto;
}
@media all and (min-width: 40em) {
.dashboardDocument .adminDrawerLogo,
.dashboardDocument .mainDrawerButton {
@@ -473,10 +449,12 @@
}
.layout-mobile .itemBackdrop {
display: none;
background-attachment: scroll;
height: 26.5vh;
}
.layout-desktop .itemBackdrop::after {
.layout-desktop .itemBackdrop::after,
.layout-tv .itemBackdrop::after {
content: "";
width: 100%;
height: 100%;
@@ -484,8 +462,8 @@
display: block;
}
.layout-tv .itemBackdrop,
.layout-desktop .noBackdrop .itemBackdrop {
.layout-desktop .noBackdrop .itemBackdrop,
.layout-tv .noBackdrop .itemBackdrop {
display: none;
}
@@ -633,8 +611,7 @@
}
.layout-mobile .mainDetailButtons {
flex: 2 0 70%;
margin-top: 0.5em;
margin-top: 1em;
margin-bottom: 0.5em;
}
@@ -653,14 +630,10 @@
z-index: 2;
}
.layout-tv .detailPagePrimaryContainer {
display: block;
}
.layout-mobile .detailPagePrimaryContainer {
flex-wrap: wrap;
display: block;
position: relative;
padding: 4.5rem 3.3% 0.5rem;
padding: 0.5em 3.3% 0.5em;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
@@ -670,16 +643,12 @@
padding-left: 32.45vw;
}
.layout-desktop .detailRibbon {
.layout-desktop .detailRibbon,
.layout-tv .detailRibbon {
margin-top: -7.2em;
height: 7.2em;
}
.layout-tv .detailRibbon {
margin-top: 0;
height: inherit;
}
.layout-desktop .noBackdrop .detailRibbon,
.layout-tv .noBackdrop .detailRibbon {
margin-top: 0;
@@ -689,10 +658,6 @@
flex: 1 0 0;
}
.layout-mobile .infoWrapper {
flex: 2 0 70%;
}
.infoText {
white-space: nowrap;
text-overflow: ellipsis;
@@ -753,8 +718,7 @@
background-size: contain;
}
.noBackdrop .detailLogo,
.layout-mobile .detailLogo {
.noBackdrop .detailLogo {
display: none;
}
@@ -779,17 +743,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
height: 23vw;
}
.sectionTitleTextButton > .material-icons {
font-size: 1.5em;
margin-bottom: 0.35em;
margin-top: 0;
}
.layout-mobile .sectionTitleTextButton > .material-icons {
margin-bottom: 0;
padding-top: 0.5em;
}
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
font-size: 15vw;
margin-top: 50%;
@@ -801,7 +754,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
position: relative;
}
.layout-desktop .itemBackdrop {
.layout-desktop .itemBackdrop,
.layout-tv .itemBackdrop {
height: 40vh;
}
@@ -827,8 +781,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.emby-button.detailFloatingButton {
font-size: 1.4em;
margin-right: 0.5em !important;
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
z-index: 3;
top: 100%;
left: 90%;
margin: -2.2em 0 0 -2.2em;
padding: 0.4em;
color: rgba(255, 255, 255, 0.76);
}
@@ -891,7 +850,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
-webkit-align-items: center;
align-items: center;
margin: 0 !important;
padding: 0.7em 0.7em !important;
padding: 0.5em 0.7em !important;
}
@media all and (min-width: 29em) {
@@ -960,6 +919,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
@media all and (min-width: 100em) {
.detailFloatingButton {
display: none !important;
}
.personBackdrop {
display: none !important;
}
@@ -968,11 +931,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
font-size: 108%;
margin: 1.25em 0;
}
.layout-tv .mainDetailButtons {
font-size: 108%;
margin: 1em 0 1.25em;
}
}
@media all and (max-width: 50em) {
@@ -1093,7 +1051,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
.sectionTitleButton,
.sectionTitleIconButton {
margin-right: 0 !important;
display: inline-flex;
display: inline-block;
vertical-align: middle;
}
@@ -1188,13 +1146,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
}
.layout-tv .padded-top-focusscale {
padding-top: 1.5em;
margin-top: -1.5em;
padding-top: 1em;
margin-top: -1em;
}
.layout-tv .padded-bottom-focusscale {
padding-bottom: 1.5em;
margin-bottom: -1.5em;
padding-bottom: 1em;
margin-bottom: -1em;
}
@media all and (min-height: 31.25em) {

View File

@@ -2,8 +2,8 @@
padding-bottom: 15em;
}
#guideTab {
@media all and (min-width: 62.5em) {
@media all and (min-width: 62.5em) {
#guideTab {
padding-left: 0.5em;
}
}

View File

@@ -1,19 +1,14 @@
@mixin fullpage {
body,
html {
margin: 0;
padding: 0;
height: 100%;
}
html {
@include fullpage;
line-height: 1.35;
}
body {
@include fullpage;
overflow-x: hidden;
background-color: transparent !important;
-webkit-font-smoothing: antialiased;
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
user-select: none;
}
.clipForScreenReader {
@@ -41,10 +36,14 @@ body {
contain: strict;
}
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
user-select: none;
html {
line-height: 1.35;
}
body {
overflow-x: hidden;
background-color: transparent !important;
-webkit-font-smoothing: antialiased;
}
.mainAnimatedPage {
@@ -59,7 +58,7 @@ body {
overflow-y: hidden !important;
}
div[data-role="page"] {
div[data-role=page] {
outline: 0;
}
@@ -72,10 +71,10 @@ div[data-role="page"] {
padding-left: 0.15em;
font-weight: 400;
white-space: normal !important;
}
+ .fieldDescription {
margin-top: 0.3em;
}
.fieldDescription + .fieldDescription {
margin-top: 0.3em;
}
.content-primary,
@@ -86,14 +85,9 @@ div[data-role="page"] {
padding-bottom: 5em !important;
}
.readOnlyContent {
@media all and (min-width: 50em) {
max-width: 54em;
}
}
form {
@media all and (min-width: 50em) {
@media all and (min-width: 50em) {
.readOnlyContent,
form {
max-width: 54em;
}
}
@@ -113,14 +107,14 @@ form {
.headroom {
will-change: transform;
transition: transform 200ms linear;
}
&--pinned {
transform: translateY(0%);
}
.headroom--pinned {
transform: translateY(0%);
}
&--unpinned {
transform: translateY(-100%);
}
.headroom--unpinned {
transform: translateY(-100%);
}
.drawerContent {
@@ -135,17 +129,3 @@ form {
.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;
}

View File

@@ -6,45 +6,29 @@
-ms-user-select: none;
}
.osdPoster img,
.videoOsdBottom {
bottom: 0;
left: 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;
pointer-events: none;
}
.skinHeader-withBackground.osdHeader {
.osdHeader {
-webkit-transition: opacity 0.3s ease-out;
-o-transition: opacity 0.3s ease-out;
transition: opacity 0.3s ease-out;
position: relative;
z-index: 1;
background: linear-gradient(180deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
backdrop-filter: none;
color: #eee;
height: 7.5em;
pointer-events: none;
background: rgba(0, 0, 0, 0.7) !important;
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
color: #eee !important;
}
.osdHeader-hidden {
opacity: 0;
}
.osdHeader .headerTop {
pointer-events: all;
max-height: 3.5em;
}
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) {
display: none;
}
@@ -102,18 +86,34 @@
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 {
opacity: 0;
}
.osdControls {
pointer-events: all;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
padding: 0 0.8em;
}
.layout-desktop .osdControls {
max-width: calc(100vh * 1.77 - 2vh);
}
.videoOsdBottom .buttons {
@@ -145,7 +145,7 @@
}
.volumeButtons {
margin: 0 1em 0 0.29em;
margin: 0 0.5em 0 auto;
display: flex;
-webkit-align-items: center;
align-items: center;
@@ -153,13 +153,33 @@
.osdTimeText {
margin-left: 1em;
margin-right: auto;
-webkit-user-select: none;
-moz-user-select: none;
-ms-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,
.osdTitleSmall {
margin: 0 1em 0 0;
@@ -228,6 +248,8 @@
}
@media all and (max-width: 30em) {
.btnFastForward,
.btnRewind,
.osdMediaInfo,
.osdPoster {
display: none !important;
@@ -259,119 +281,3 @@
display: none !important;
}
}
.syncPlayContainer {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
pointer-events: none;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.primary-icon {
position: absolute;
font-size: 64px;
align-self: center;
}
.primary-icon.spin {
font-size: 76px !important;
animation: spin 2s linear infinite;
}
.secondary-icon {
position: absolute;
font-size: 24px;
}
.secondary-icon.centered {
font-size: 28px !important;
align-self: center;
}
.secondary-icon.shifted {
right: 0;
bottom: 0;
font-size: 52px;
}
.syncPlayIconCircle {
position: relative;
visibility: hidden;
display: flex;
justify-content: center;
border-radius: 50%;
margin: 60px;
height: 96px;
width: 96px;
color: rgba(0, 164, 220, 0);
background: rgba(0, 164, 220, 0);
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
transform: scale(1);
}
.syncPlayIconCircle.oneShotPulse {
animation: pulse 1.5s 1;
}
.syncPlayIconCircle.infinitePulse {
animation: infinite-pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
color: rgba(0, 164, 220, 0.7);
background: rgba(0, 164, 220, 0.3);
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0.3);
}
70% {
transform: scale(1);
color: rgba(0, 164, 220, 0);
background: rgba(0, 164, 220, 0);
box-shadow: 0 0 0 60px rgba(0, 164, 220, 0);
}
100% {
transform: scale(0.95);
color: rgba(0, 164, 220, 0);
background: rgba(0, 164, 220, 0);
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
}
}
@keyframes infinite-pulse {
0% {
transform: scale(0.95);
color: rgba(0, 164, 220, 0.7);
background: rgba(0, 164, 220, 0.3);
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0.3);
}
70% {
transform: scale(1);
color: rgba(0, 164, 220, 0.6);
background: rgba(0, 164, 220, 0);
box-shadow: 0 0 0 60px rgba(0, 164, 220, 0);
}
100% {
transform: scale(0.95);
color: rgba(0, 164, 220, 0.7);
background: rgba(0, 164, 220, 0.3);
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
}
}
@keyframes spin {
100% {
transform: rotate(-360deg);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 43 KiB

183
src/bundle.js Normal file
View File

@@ -0,0 +1,183 @@
/**
* require.js module definitions bundled by webpack
*/
// Use define from require.js not webpack's define
var _define = window.define;
// fetch
var fetch = require('whatwg-fetch');
_define('fetch', function() {
return fetch;
});
// Blurhash
var blurhash = require('blurhash');
_define('blurhash', function() {
return blurhash;
});
// 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;
});
// 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;
});
// shaka
var shaka = require('shaka-player');
_define('shaka', function() {
return shaka;
});
// 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;
});
var epubjs = require('epubjs');
_define('epubjs', function () {
return epubjs;
});
// page.js
var page = require('page');
_define('page', function() {
return page;
});
// core-js
var polyfill = require('@babel/polyfill/dist/polyfill');
_define('polyfill', function () {
return polyfill;
});
// domtokenlist-shim
var classlist = require('classlist.js');
_define('classlist-polyfill', function () {
return classlist;
});
// Date-FNS
var dateFns = require('date-fns');
_define('date-fns', function () {
return dateFns;
});
var dateFnsLocale = require('date-fns/locale');
_define('date-fns/locale', function () {
return dateFnsLocale;
});
var fast_text_encoding = require('fast-text-encoding');
_define('fast-text-encoding', function () {
return fast_text_encoding;
});
// intersection-observer
var intersection_observer = require('intersection-observer');
_define('intersection-observer', function () {
return intersection_observer;
});
// screenfull
var screenfull = require('screenfull');
_define('screenfull', function () {
return screenfull;
});
// headroom.js
var headroom = require('headroom.js/dist/headroom');
_define('headroom', function () {
return headroom;
});
// apiclient
var apiclient = require('jellyfin-apiclient');
_define('apiclient', function () {
return apiclient.ApiClient;
});
_define('events', function () {
return apiclient.Events;
});
_define('credentialprovider', function () {
return apiclient.Credentials;
});
_define('connectionManagerFactory', function () {
return apiclient.ConnectionManager;
});
_define('appStorage', function () {
return apiclient.AppStorage;
});

View File

@@ -1,79 +0,0 @@
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
import { appHost } from './apphost';
import Dashboard from '../scripts/clientUtils';
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(server) {
console.debug('creating ApiClient singleton');
const apiClient = new ApiClient(
server,
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);

View File

@@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
@@ -6,13 +5,12 @@
* @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';
import template from './accessSchedule.template.html';
import dialogHelper from 'dialogHelper';
import datetime from 'datetime';
import globalize from 'globalize';
import 'emby-select';
import 'paper-icon-button-light';
import 'formDialogStyle';
function getDisplayTime(hours) {
let minutes = 0;
@@ -51,7 +49,7 @@ import template from './accessSchedule.template.html';
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
return void alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd'));
}
context.submitted = true;
@@ -61,31 +59,34 @@ import template from './accessSchedule.template.html';
export function show(options) {
return new Promise((resolve, reject) => {
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;
// TODO: remove require
require(['text!./components/accessSchedule/accessSchedule.template.html'], template => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateDocument(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;
});
});
});
}

View File

@@ -1,5 +1,5 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${Previous}" tabindex="-1">
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${LabelPrevious}" tabindex="-1">
<span class="material-icons arrow_back" aria-hidden="true"></span>
</button>
<h3 class="formDialogHeaderTitle">
@@ -12,13 +12,13 @@
<div class="selectContainer">
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
<option value="Sunday">${Sunday}</option>
<option value="Monday">${Monday}</option>
<option value="Tuesday">${Tuesday}</option>
<option value="Wednesday">${Wednesday}</option>
<option value="Thursday">${Thursday}</option>
<option value="Friday">${Friday}</option>
<option value="Saturday">${Saturday}</option>
<option value="Sunday">${OptionSunday}</option>
<option value="Monday">${OptionMonday}</option>
<option value="Tuesday">${OptionTuesday}</option>
<option value="Wednesday">${OptionWednesday}</option>
<option value="Thursday">${OptionThursday}</option>
<option value="Friday">${OptionFriday}</option>
<option value="Saturday">${OptionSaturday}</option>
<option value="Everyday">${OptionEveryday}</option>
<option value="Weekday">${OptionWeekdays}</option>
<option value="Weekend">${OptionWeekends}</option>
@@ -33,7 +33,7 @@
<div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${Add}</span>
<span>${ButtonAdd}</span>
</button>
</div>
</form>

View File

@@ -1,22 +1,23 @@
import dialogHelper from '../dialogHelper/dialogHelper';
import layoutManager from '../layoutManager';
import globalize from '../../scripts/globalize';
import dom from '../../scripts/dom';
import '../../elements/emby-button/emby-button';
import './actionSheet.css';
import 'material-design-icons-iconfont';
import '../../assets/css/scrollstyles.css';
import '../../components/listview/listview.css';
import dialogHelper from 'dialogHelper';
import layoutManager from 'layoutManager';
import globalize from 'globalize';
import dom from 'dom';
import 'emby-button';
import 'css!./actionSheet';
import 'material-icons';
import 'scrollStyles';
import 'listViewStyle';
function getOffsets(elems) {
const results = [];
let results = [];
if (!document) {
return results;
}
for (const elem of elems) {
const box = elem.getBoundingClientRect();
let box = elem.getBoundingClientRect();
results.push({
top: box.top,
@@ -30,11 +31,12 @@ function getOffsets(elems) {
}
function getPosition(options, dlg) {
const windowSize = dom.getWindowSize();
const windowHeight = windowSize.innerHeight;
const windowWidth = windowSize.innerWidth;
const pos = getOffsets([options.positionTo])[0];
let pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2;
@@ -71,18 +73,19 @@ function getPosition(options, dlg) {
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
require(['scrollHelper'], function (scrollHelper) {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export function show(options) {
// items
// positionTo
// showCancel
// title
const dialogOptions = {
let dialogOptions = {
removeOnClose: true,
enableHistory: options.enableHistory,
scrollY: false
@@ -95,6 +98,7 @@ export function show(options) {
isFullscreen = true;
dialogOptions.autoFocus = true;
} else {
dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation;
@@ -103,7 +107,7 @@ export function show(options) {
dialogOptions.autoFocus = false;
}
const dlg = dialogHelper.createDialog(dialogOptions);
let dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen');
@@ -129,9 +133,10 @@ export function show(options) {
}
let renderIcon = false;
const icons = [];
let icons = [];
let itemIcon;
for (const item of options.items) {
itemIcon = item.icon || (item.selected ? 'check' : null);
if (itemIcon) {
@@ -156,6 +161,7 @@ export function show(options) {
}
if (options.title) {
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
}
if (options.text) {
@@ -191,6 +197,7 @@ export function show(options) {
const item = options.items[i];
if (item.divider) {
html += '<div class="actionsheetDivider"></div>';
continue;
}
@@ -241,13 +248,15 @@ export function show(options) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
}
const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) {
btnCloseActionSheet.addEventListener('click', function () {
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 timeout;
@@ -258,20 +267,26 @@ export function show(options) {
}
return new Promise(function (resolve, reject) {
let isResolved;
dlg.addEventListener('click', function (e) {
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
if (actionSheetMenuItem) {
selectedId = actionSheetMenuItem.getAttribute('data-id');
if (options.resolveOnClick) {
if (options.resolveOnClick.indexOf) {
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
resolve(selectedId);
isResolved = true;
}
} else {
resolve(selectedId);
isResolved = true;
@@ -280,9 +295,11 @@ export function show(options) {
dialogHelper.close(dlg);
}
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
}

View File

@@ -1,23 +1,13 @@
import { Events } from 'jellyfin-apiclient';
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 */
define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', 'serverNotifications', 'connectionManager', 'emby-button', 'listViewStyle'], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) {
'use strict';
function getEntryHtml(entry, apiClient) {
let html = '';
var html = '';
html += '<div class="listItem listItem-border">';
let color = '#00a4dc';
let icon = 'notifications';
var color = '#00a4dc';
var icon = 'notifications';
if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') {
if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) {
color = '#cc0000';
icon = 'notification_important';
}
@@ -61,15 +51,14 @@ import alert from './alert';
}
function reloadData(instance, elem, apiClient, startIndex, limit) {
if (startIndex == null) {
if (null == startIndex) {
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0');
}
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7');
const minDate = new Date();
const hasUserId = elem.getAttribute('data-useractivity') !== 'false';
var minDate = new Date();
var hasUserId = 'false' !== elem.getAttribute('data-useractivity');
// TODO: Use date-fns
if (hasUserId) {
minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
} else {
@@ -85,7 +74,7 @@ import alert from './alert';
elem.setAttribute('data-activitystartindex', startIndex);
elem.setAttribute('data-activitylimit', limit);
if (!startIndex) {
const activityContainer = dom.parentWithClass(elem, 'activityContainer');
var activityContainer = dom.parentWithClass(elem, 'activityContainer');
if (activityContainer) {
if (result.Items.length) {
@@ -102,7 +91,7 @@ import alert from './alert';
}
function onActivityLogUpdate(e, apiClient, data) {
const options = this.options;
var options = this.options;
if (options && options.serverId === apiClient.serverId()) {
reloadData(this, options.element, apiClient);
@@ -110,14 +99,14 @@ import alert from './alert';
}
function onListClick(e) {
const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
if (btnEntryInfo) {
const id = btnEntryInfo.getAttribute('data-id');
const items = this.items;
var id = btnEntryInfo.getAttribute('data-id');
var items = this.items;
if (items) {
const item = items.filter(function (i) {
var item = items.filter(function (i) {
return i.Id.toString() === id;
})[0];
@@ -129,43 +118,43 @@ import alert from './alert';
}
function showItemOverview(item) {
alert({
text: item.Overview
require(['alert'], function (alert) {
alert({
text: item.Overview
});
});
}
class ActivityLog {
constructor(options) {
function ActivityLog(options) {
this.options = options;
const element = options.element;
var element = options.element;
element.classList.add('activityLogListWidget');
element.addEventListener('click', onListClick.bind(this));
const apiClient = ServerConnections.getApiClient(options.serverId);
var apiClient = connectionManager.getApiClient(options.serverId);
reloadData(this, element, apiClient);
const onUpdate = onActivityLogUpdate.bind(this);
var onUpdate = onActivityLogUpdate.bind(this);
this.updateFn = onUpdate;
Events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
apiClient.sendMessage('ActivityLogEntryStart', '0,1500');
}
destroy() {
const options = this.options;
ActivityLog.prototype.destroy = function () {
var options = this.options;
if (options) {
options.element.classList.remove('activityLogListWidget');
ServerConnections.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
}
const onUpdate = this.updateFn;
var onUpdate = this.updateFn;
if (onUpdate) {
Events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
}
this.items = null;
this.options = null;
}
}
};
export default ActivityLog;
/* eslint-enable indent */
return ActivityLog;
});

View File

@@ -1,17 +1,14 @@
import browser from '../scripts/browser';
import dialog from './dialog/dialog';
import globalize from '../scripts/globalize';
/* eslint-disable indent */
define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize) {
'use strict';
function replaceAll(originalString, strReplace, strWith) {
const reg = new RegExp(strReplace, 'ig');
var reg = new RegExp(strReplace, 'ig');
return originalString.replace(reg, strWith);
}
export default function (text, title) {
let options;
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
@@ -24,7 +21,7 @@ import globalize from '../scripts/globalize';
if (browser.tv && window.alert) {
alert(replaceAll(options.text || '', '<br/>', '\n'));
} else {
const items = [];
var items = [];
items.push({
name: globalize.translate('ButtonGotIt'),
@@ -34,7 +31,7 @@ import globalize from '../scripts/globalize';
options.buttons = items;
return dialog.show(options).then(function (result) {
return dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
@@ -44,6 +41,5 @@ import globalize from '../scripts/globalize';
}
return Promise.resolve();
}
/* eslint-enable indent */
};
});

View File

@@ -1,22 +1,11 @@
/* eslint-disable indent */
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
'use strict';
/**
* 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';
var selectedButtonClass = 'alphaPickerButton-selected';
function focus() {
const scope = this;
const selected = scope.querySelector(`.${selectedButtonClass}`);
var scope = this;
var selected = scope.querySelector('.' + selectedButtonClass);
if (selected) {
focusManager.focus(selected);
@@ -26,7 +15,8 @@ import 'material-design-icons-iconfont';
}
function getAlphaPickerButtonClassName(vertical) {
let alphaPickerButtonClassName = 'alphaPickerButton';
var alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
@@ -40,42 +30,44 @@ import 'material-design-icons-iconfont';
}
function getLetterButton(l, vertical) {
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + '</button>';
}
function mapLetters(letters, vertical) {
return letters.map(l => {
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');
}
const vertical = element.classList.contains('alphaPicker-vertical');
var vertical = element.classList.contains('alphaPicker-vertical');
if (!vertical) {
element.classList.add('focuscontainer-x');
}
let html = '';
let letters;
var html = '';
var letters;
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
let rowClassName = 'alphaPickerRow';
var rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += `<div class="${rowClassName}">`;
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>`;
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('');
@@ -85,11 +77,11 @@ import 'material-design-icons-iconfont';
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 += '<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 += '<div class="' + rowClassName + '">';
html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
@@ -103,211 +95,227 @@ import 'material-design-icons-iconfont';
element.focus = focus;
}
export class AlphaPicker {
constructor(options) {
const self = this;
function AlphaPicker(options) {
this.options = options;
var self = this;
this.options = options;
const element = options.element;
const itemsContainer = options.itemsContainer;
const itemClass = options.itemClass;
var element = options.element;
var itemsContainer = options.itemsContainer;
var itemClass = options.itemClass;
let itemFocusValue;
let itemFocusTimeout;
var itemFocusValue;
var 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);
function onItemFocusTimeout() {
itemFocusTimeout = null;
self.value(itemFocusValue);
}
value(value, applyValue) {
const element = this.options.element;
let btn;
let selected;
var alphaFocusedElement;
var alphaFocusTimeout;
if (value !== undefined) {
if (value != null) {
value = value.toUpperCase();
this._currentValue = value;
function onAlphaFocusTimeout() {
if (this.options.mode !== 'keyboard') {
selected = element.querySelector(`.${selectedButtonClass}`);
alphaFocusTimeout = null;
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 (document.activeElement === alphaFocusedElement) {
var value = alphaFocusedElement.getAttribute('data-value');
self.value(value, true);
}
}
if (applyValue) {
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
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: value
}
}));
}
return this._currentValue;
}
on(name, fn) {
const element = this.options.element;
element.addEventListener(name, fn);
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);
}
}
}
off(name, fn) {
const element = this.options.element;
element.removeEventListener(name, fn);
}
function onAlphaPickerFocusIn(e) {
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'));
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
return values;
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
alphaFocusedElement = alphaPickerButton;
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
}
}
focus() {
const element = this.options.element;
focusManager.autoFocus(element, true);
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);
}
}
}
destroy() {
const element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
}
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);
}
/* eslint-enable indent */
export default AlphaPicker;
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;
});

View File

@@ -12,6 +12,7 @@
.alphaPicker-fixed {
position: fixed;
bottom: 5.5em;
z-index: 999999;
}
.alphaPickerRow {

View File

@@ -1,17 +1,17 @@
import './appFooter.css';
define(['browser', 'css!./appFooter'], function (browser) {
'use strict';
function render(options) {
const elem = document.createElement('div');
elem.classList.add('appfooter');
function render(options) {
var elem = document.createElement('div');
elem.classList.add('appfooter');
document.body.appendChild(elem);
document.body.appendChild(elem);
return elem;
}
return elem;
}
class appFooter {
constructor(options) {
const self = this;
function appFooter(options) {
var self = this;
self.element = render(options);
self.add = function (elem) {
@@ -26,11 +26,12 @@ class appFooter {
}
};
}
destroy() {
const self = this;
appFooter.prototype.destroy = function () {
var self = this;
self.element = null;
}
}
};
export default new appFooter({});
return appFooter;
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,87 +1,98 @@
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
'use strict';
import appSettings from '../scripts/settings/appSettings';
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';
function getBaseProfileOptions(item) {
var disableHlsVideoAudioCodecs = [];
const appName = 'Jellyfin Web';
const appVersion = '10.7.6';
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge) {
disableHlsVideoAudioCodecs.push('mp3');
}
function getBaseProfileOptions(item) {
const disableHlsVideoAudioCodecs = [];
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge) {
disableHlsVideoAudioCodecs.push('mp3');
disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push('opus');
}
disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push('opus');
return {
enableMkvProgressive: false,
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
};
}
return {
enableMkvProgressive: false,
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
};
}
function getDeviceProfileForWindowsUwp(item) {
return new Promise(function (resolve, reject) {
require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) {
var profileOptions = getBaseProfileOptions(item);
profileOptions.supportsDts = uwpMediaCaps.supportsDTS();
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
profileOptions.audioChannels = uwpMediaCaps.getAudioChannels();
resolve(profileBuilder(profileOptions));
});
});
}
function getDeviceProfile(item, options = {}) {
return new Promise(function (resolve) {
let profile;
function getDeviceProfile(item, options) {
options = options || {};
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, appVersion);
} else {
const builderOpts = getBaseProfileOptions(item);
profile = profileBuilder(builderOpts);
if (self.Windows) {
return getDeviceProfileForWindowsUwp(item);
}
resolve(profile);
});
}
return new Promise(function (resolve) {
require(['browserdeviceprofile'], function (profileBuilder) {
var profile;
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
}
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else {
var builderOpts = getBaseProfileOptions(item);
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
profile = profileBuilder(builderOpts);
}
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;
resolve(profile);
});
});
}
return new Date().getTime();
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
function getDeviceId() {
if (!deviceId) {
const key = '_deviceId2';
function replaceAll(originalString, strReplace, strWith) {
var strReplace2 = escapeRegExp(strReplace);
var reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith);
}
deviceId = appSettings.get(key);
function generateDeviceId() {
var keys = [];
if (!deviceId) {
deviceId = generateDeviceId();
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) {
var result = replaceAll(btoa(keys.join('|')), '=', '1');
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);
}
return deviceId;
});
}
return deviceId;
}
function getDeviceName() {
if (!deviceName) {
function getDeviceName() {
var deviceName;
if (browser.tizen) {
deviceName = 'Samsung Smart TV';
} else if (browser.web0s) {
@@ -115,299 +126,333 @@ function getDeviceName() {
} else if (browser.android) {
deviceName += ' Android';
}
return deviceName;
}
return deviceName;
}
function supportsVoiceInput() {
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;
}
const element = document.documentElement;
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
}
function supportsFullscreen() {
if (browser.tv) {
return false;
}
function getDefaultLayout() {
return 'desktop';
}
var element = document.documentElement;
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
}
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;
}
if (browser.mobile) {
return false;
}
function supportsCue() {
try {
var video = document.createElement('video');
var style = document.createElement('style');
return true;
}
style.textContent = 'video::cue {background: inherit}';
document.body.appendChild(style);
document.body.appendChild(video);
function supportsCue() {
try {
const video = document.createElement('video');
const style = document.createElement('style');
var cue = window.getComputedStyle(video, '::cue').background;
document.body.removeChild(style);
document.body.removeChild(video);
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');
}
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('filedownload');
}
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push('exit');
} else {
features.push('exitmenu');
features.push('plugins');
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push('externallinks');
features.push('externalpremium');
}
if (!browser.operaTv) {
features.push('externallinkdisplay');
}
if (supportsVoiceInput()) {
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 {
window.close();
return !!cue.length;
} catch (err) {
console.error('error detecting cue support: ' + err);
return false;
}
} catch (err) {
console.error('error closing application: ' + err);
}
}
let exitPromise;
/**
* Ask user for exit
*/
function askForExit() {
if (exitPromise) {
return;
}
import('../components/actionSheet/actionSheet').then((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') {
function onAppVisible() {
if (isHidden) {
isHidden = false;
console.debug('triggering app resume event');
events.trigger(appHost, 'resume');
}
}
function onAppHidden() {
if (!isHidden) {
isHidden = true;
console.debug('app is hidden');
}
}
var supportedFeatures = function () {
var features = [];
if (navigator.share) {
features.push('sharing');
}
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('filedownload');
}
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push('exit');
} else {
features.push('exitmenu');
features.push('plugins');
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push('externallinks');
features.push('externalpremium');
}
if (!browser.operaTv) {
features.push('externallinkdisplay');
}
if (supportsVoiceInput()) {
features.push('voiceinput');
}
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.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 {
window.close();
}
} catch (err) {
console.error('error closing application: ' + err);
}
}
var exitPromise;
/**
* Ask user for exit
*/
function askForExit() {
if (exitPromise) {
return;
}
require(['actionsheet'], function (actionsheet) {
exitPromise = actionsheet.show({
title: globalize.translate('MessageConfirmAppExit'),
items: [
{id: 'yes', name: globalize.translate('Yes')},
{id: 'no', name: globalize.translate('No')}
]
}).then(function (value) {
if (value === 'yes') {
doExit();
}
}).finally(function () {
exitPromise = null;
});
});
}
var deviceId;
var deviceName;
var appName = 'Jellyfin Web';
var appVersion = '10.6.4';
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();
}
}).finally(function () {
exitPromise = null;
});
});
}
},
supports: function (command) {
if (window.NativeShell) {
return window.NativeShell.AppHost.supports(command);
}
let deviceId;
let deviceName;
return -1 !== supportedFeatures.indexOf(command.toLowerCase());
},
preferVisualCards: browser.android || browser.chrome,
getSyncProfile: getSyncProfile,
getDefaultLayout: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.getDefaultLayout();
}
export const appHost = {
getWindowState: function () {
return document.windowState || 'Normal';
},
setWindowState: function () {
alert('setWindowState is not supported and should not be called');
},
exit: function () {
if (!!window.appMode && browser.tizen) {
askForExit();
return getDefaultLayout();
},
getDeviceProfile: getDeviceProfile,
init: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.init();
}
deviceName = getDeviceName();
getDeviceId().then(function (id) {
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 {
doExit();
}
},
supports: function (command) {
if (window.NativeShell) {
return window.NativeShell.AppHost.supports(command);
onAppVisible();
}
}, false);
return supportedFeatures.indexOf(command.toLowerCase()) !== -1;
},
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);
}
if (self.addEventListener) {
self.addEventListener('focus', onAppVisible);
self.addEventListener('blur', onAppHidden);
}
};
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();
return appHost;
});

View File

@@ -5,8 +5,8 @@
* @module components/autoFocuser
*/
import focusManager from './focusManager';
import layoutManager from './layoutManager';
import focusManager from 'focusManager';
import layoutManager from 'layoutManager';
/**
* Previously selected element.

View File

@@ -1,11 +1,5 @@
import browser from '../../scripts/browser';
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 */
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) {
'use strict';
function enableAnimation(elem) {
if (browser.slow) {
@@ -28,70 +22,71 @@ import ServerConnections from '../ServerConnections';
return true;
}
class Backdrop {
load(url, parent, existingBackdropImage) {
const img = new Image();
const self = this;
img.onload = () => {
if (self.isDestroyed) {
return;
}
const 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;
}
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
});
internalBackdrop(true);
};
img.src = url;
}
cancelAnimation() {
const elem = this.currentAnimatingElement;
if (elem) {
elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
}
}
destroy() {
this.isDestroyed = true;
this.cancelAnimation();
}
function Backdrop() {
}
let backdropContainer;
Backdrop.prototype.load = function (url, parent, existingBackdropImage) {
var img = new Image();
var self = this;
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 () {
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
});
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 () {
this.isDestroyed = true;
this.cancelAnimation();
};
var backdropContainer;
function getBackdropContainer() {
if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer');
@@ -106,7 +101,7 @@ import ServerConnections from '../ServerConnections';
return backdropContainer;
}
export function clearBackdrop(clearAll) {
function clearBackdrop(clearAll) {
clearRotation();
if (currentLoadingBackdrop) {
@@ -114,7 +109,7 @@ import ServerConnections from '../ServerConnections';
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
var elem = getBackdropContainer();
elem.innerHTML = '';
if (clearAll) {
@@ -124,7 +119,7 @@ import ServerConnections from '../ServerConnections';
internalBackdrop(false);
}
let backgroundContainer;
var backgroundContainer;
function getBackgroundContainer() {
if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer');
@@ -140,27 +135,31 @@ import ServerConnections from '../ServerConnections';
}
}
let hasInternalBackdrop;
var hasInternalBackdrop;
function internalBackdrop(enabled) {
hasInternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
let hasExternalBackdrop;
export function externalBackdrop(enabled) {
var hasExternalBackdrop;
function externalBackdrop(enabled) {
hasExternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
let currentLoadingBackdrop;
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
var currentLoadingBackdrop;
function setBackdropImage(url) {
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
var elem = getBackdropContainer();
var existingBackdropImage = elem.querySelector('.displayingBackdropImage');
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
if (existingBackdropImage.getAttribute('data-url') === url) {
@@ -169,7 +168,7 @@ import ServerConnections from '../ServerConnections';
existingBackdropImage.classList.remove('displayingBackdropImage');
}
const instance = new Backdrop();
var instance = new Backdrop();
instance.load(url, elem, existingBackdropImage);
currentLoadingBackdrop = instance;
}
@@ -177,9 +176,9 @@ import ServerConnections from '../ServerConnections';
function getItemImageUrls(item, imageOptions) {
imageOptions = imageOptions || {};
const apiClient = ServerConnections.getApiClient(item.ServerId);
var apiClient = connectionManager.getApiClient(item.ServerId);
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
return item.BackdropImageTags.map((imgTag, index) => {
return item.BackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: 'Backdrop',
tag: imgTag,
@@ -190,7 +189,7 @@ import ServerConnections from '../ServerConnections';
}
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
return item.ParentBackdropImageTags.map((imgTag, index) => {
return item.ParentBackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: 'Backdrop',
tag: imgTag,
@@ -204,13 +203,13 @@ import ServerConnections from '../ServerConnections';
}
function getImageUrls(items, imageOptions) {
const list = [];
const onImg = img => {
var list = [];
var onImg = function (img) {
list.push(img);
};
for (let i = 0, length = items.length; i < length; i++) {
const itemImages = getItemImageUrls(items[i], imageOptions);
for (var i = 0, length = items.length; i < length; i++) {
var itemImages = getItemImageUrls(items[i], imageOptions);
itemImages.forEach(onImg);
}
@@ -230,7 +229,7 @@ import ServerConnections from '../ServerConnections';
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
for (let i = 0; i < a.length; ++i) {
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) {
return false;
}
@@ -243,12 +242,12 @@ import ServerConnections from '../ServerConnections';
return userSettings.enableBackdrops();
}
let rotationInterval;
let currentRotatingImages = [];
let currentRotationIndex = -1;
export function setBackdrops(items, imageOptions, enableImageRotation) {
var rotationInterval;
var currentRotatingImages = [];
var currentRotationIndex = -1;
function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) {
const images = getImageUrls(items, imageOptions);
var images = getImageUrls(items, imageOptions);
if (images.length) {
startRotation(images, enableImageRotation);
@@ -280,7 +279,7 @@ import ServerConnections from '../ServerConnections';
return;
}
let newIndex = currentRotationIndex + 1;
var newIndex = currentRotationIndex + 1;
if (newIndex >= currentRotatingImages.length) {
newIndex = 0;
}
@@ -290,7 +289,7 @@ import ServerConnections from '../ServerConnections';
}
function clearRotation() {
const interval = rotationInterval;
var interval = rotationInterval;
if (interval) {
clearInterval(interval);
}
@@ -300,7 +299,7 @@ import ServerConnections from '../ServerConnections';
currentRotationIndex = -1;
}
export function setBackdrop(url, imageOptions) {
function setBackdrop(url, imageOptions) {
if (url && typeof url !== 'string') {
url = getImageUrls([url], imageOptions)[0];
}
@@ -313,11 +312,10 @@ import ServerConnections from '../ServerConnections';
}
}
/* eslint-enable indent */
export default {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clearBackdrop: clearBackdrop,
externalBackdrop: externalBackdrop
};
return {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clear: clearBackdrop,
externalBackdrop: externalBackdrop
};
});

View File

@@ -160,6 +160,7 @@ button::-moz-focus-inner {
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
@@ -168,13 +169,8 @@ button::-moz-focus-inner {
color: inherit;
}
.cardContent.cardImageContainer {
display: flex;
}
.cardScalable .cardImageContainer {
height: 100%;
width: 100%;
contain: strict;
}
@@ -213,10 +209,6 @@ button::-moz-focus-inner {
contain: strict;
}
.defaultCardBackground {
display: flex;
}
.cardContent:not(.defaultCardBackground) {
background-color: transparent;
}
@@ -226,8 +218,8 @@ button::-moz-focus-inner {
}
.visualCardBox .cardContent {
border-top-left-radius: 0.2em;
border-top-right-radius: 0.2em;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.cardContent-shadow,
@@ -247,13 +239,33 @@ button::-moz-focus-inner {
border: none;
}
.cardImage-img {
max-height: 100%;
max-width: 100%;
/* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */
min-height: 70%;
min-width: 70%;
margin: auto;
}
.coveredImage-img {
width: 100%;
height: 100%;
}
.coveredImage-noscale-img {
max-height: none;
max-width: none;
}
.coveredImage {
background-size: cover;
background-position: center center;
}
.coveredImage.coveredImage-contain {
background-size: contain;
.coveredImage-noScale {
background-size: cover;
}
.cardFooter {
@@ -360,15 +372,18 @@ button::-moz-focus-inner {
.cardDefaultText {
white-space: normal;
text-align: center;
font-size: 2em;
font-weight: bold;
}
.cardImageIcon {
.cardImageContainer .cardImageIcon {
font-size: 5em;
color: inherit;
}
.cardImageIcon-small {
font-size: 3em !important;
margin-bottom: 0.1em;
}
.cardIndicators {
right: 0.225em;
top: 0.225em;

View File

@@ -5,22 +5,22 @@
* @module components/cardBuilder/cardBuilder
*/
import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import itemHelper from '../itemHelper';
import focusManager from '../focusManager';
import indicators from '../indicators/indicators';
import globalize from '../../scripts/globalize';
import layoutManager from '../layoutManager';
import dom from '../../scripts/dom';
import browser from '../../scripts/browser';
import { playbackManager } from '../playback/playbackmanager';
import itemShortcuts from '../shortcuts';
import imageHelper from '../../scripts/imagehelper';
import './card.css';
import '../../elements/emby-button/paper-icon-button-light';
import '../guide/programs.css';
import ServerConnections from '../ServerConnections';
import datetime from 'datetime';
import imageLoader from 'imageLoader';
import connectionManager from 'connectionManager';
import itemHelper from 'itemHelper';
import focusManager from 'focusManager';
import indicators from 'indicators';
import globalize from 'globalize';
import layoutManager from 'layoutManager';
import dom from 'dom';
import browser from 'browser';
import playbackManager from 'playbackManager';
import itemShortcuts from 'itemShortcuts';
import imageHelper from 'scripts/imagehelper';
import 'css!./card';
import 'paper-icon-button-light';
import 'programStyles';
const enableFocusTransform = !browser.slow && !browser.edge;
@@ -277,7 +277,7 @@ import ServerConnections from '../ServerConnections';
*/
function getImageWidth(shape, screenWidth, isOrientationLandscape) {
const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape);
return Math.round(screenWidth / imagesPerRow);
return Math.round(screenWidth / imagesPerRow) * 2;
}
/**
@@ -291,10 +291,12 @@ import ServerConnections from '../ServerConnections';
const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items);
if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) {
const requestedShape = options.shape;
options.shape = null;
if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 3) {
options.shape = 'banner';
options.coverImage = true;
@@ -362,16 +364,16 @@ import ServerConnections from '../ServerConnections';
let hasOpenRow;
let hasOpenSection;
const sectionTitleTagName = options.sectionTitleTagName || 'div';
let sectionTitleTagName = options.sectionTitleTagName || 'div';
let apiClient;
let lastServerId;
for (const [i, item] of items.entries()) {
const serverId = item.ServerId || options.serverId;
let serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId) {
lastServerId = serverId;
apiClient = ServerConnections.getApiClient(lastServerId);
apiClient = connectionManager.getApiClient(lastServerId);
}
if (options.indexBy) {
@@ -392,6 +394,7 @@ import ServerConnections from '../ServerConnections';
}
if (newIndexValue !== currentIndexValue) {
if (hasOpenRow) {
html += '</div>';
hasOpenRow = false;
@@ -399,6 +402,7 @@ import ServerConnections from '../ServerConnections';
}
if (hasOpenSection) {
html += '</div>';
if (isVertical) {
@@ -422,6 +426,7 @@ import ServerConnections from '../ServerConnections';
}
if (options.rows && itemsInRow === 0) {
if (hasOpenRow) {
html += '</div>';
hasOpenRow = false;
@@ -498,7 +503,7 @@ import ServerConnections from '../ServerConnections';
let imgUrl = null;
let imgTag = null;
let coverImage = false;
const uiAspect = getDesiredAspect(shape);
let uiAspect = null;
let imgType = null;
let itemId = null;
@@ -543,8 +548,11 @@ import ServerConnections from '../ServerConnections';
forceName = true;
}
if (primaryImageAspectRatio && uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape);
if (uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
@@ -560,8 +568,11 @@ import ServerConnections from '../ServerConnections';
forceName = true;
}
if (primaryImageAspectRatio && uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape);
if (uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.ParentPrimaryImageTag) {
imgType = 'Primary';
@@ -573,8 +584,11 @@ import ServerConnections from '../ServerConnections';
itemId = item.AlbumId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
if (primaryImageAspectRatio && uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape);
if (uiAspect) {
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
imgType = 'Thumb';
@@ -604,20 +618,15 @@ import ServerConnections from '../ServerConnections';
}
if (imgTag && imgType) {
// TODO: This place is a mess. Could do with a good spring cleaning.
if (!height && width && uiAspect) {
height = width / uiAspect;
}
imgUrl = apiClient.getScaledImageUrl(itemId, {
type: imgType,
fillHeight: height,
fillWidth: width,
quality: 96,
maxHeight: height,
maxWidth: width,
tag: imgTag
});
}
const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
return {
imgUrl: imgUrl,
@@ -652,7 +661,7 @@ import ServerConnections from '../ServerConnections';
for (let i = 0; i < character.length; i++) {
sum += parseInt(character.charAt(i));
}
const index = String(sum).substr(-1);
let index = String(sum).substr(-1);
return (index % numRandomColors) + 1;
} else {
@@ -677,8 +686,9 @@ import ServerConnections from '../ServerConnections';
let valid = 0;
for (let i = 0; i < lines.length; i++) {
let currentCssClass = cssClass;
const text = lines[i];
let text = lines[i];
if (valid > 0 && isOuterFooter) {
currentCssClass += ' cardText-secondary';
@@ -703,7 +713,8 @@ import ServerConnections from '../ServerConnections';
}
if (forceLines) {
const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
let linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
while (valid < linesLength) {
html += "<div class='" + cssClass + "'>&nbsp;</div>";
@@ -734,6 +745,7 @@ import ServerConnections from '../ServerConnections';
let airTimeText = '';
if (item.StartDate) {
try {
let date = datetime.parseISO8601Date(item.StartDate);
@@ -780,6 +792,7 @@ import ServerConnections from '../ServerConnections';
const showOtherText = isOuterFooter ? !overlayText : overlayText;
if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
if (options.cardFooterAside !== 'none') {
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
}
@@ -794,7 +807,9 @@ import ServerConnections from '../ServerConnections';
if (showOtherText) {
if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (item.SeriesId) {
lines.push(getTextActionButton({
Id: item.SeriesId,
@@ -807,12 +822,15 @@ import ServerConnections from '../ServerConnections';
lines.push(item.SeriesName);
}
} else {
if (isUsingLiveTvNaming(item)) {
lines.push(item.Name);
if (!item.EpisodeTitle) {
titleAdded = true;
}
} else {
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
@@ -830,6 +848,7 @@ import ServerConnections from '../ServerConnections';
}
if (showMediaTitle) {
const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle
});
@@ -846,6 +865,7 @@ import ServerConnections from '../ServerConnections';
if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) {
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true;
@@ -879,6 +899,7 @@ import ServerConnections from '../ServerConnections';
}
if (options.showPremiereDate) {
if (item.PremiereDate) {
try {
lines.push(datetime.toLocaleDateString(
@@ -887,6 +908,7 @@ import ServerConnections from '../ServerConnections';
));
} catch (err) {
lines.push('');
}
} else {
lines.push('');
@@ -894,10 +916,14 @@ import ServerConnections from '../ServerConnections';
}
if (options.showYear || options.showSeriesYear) {
if (item.Type === 'Series') {
if (item.Status === 'Continuing') {
lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || ''));
} else {
if (item.EndDate && item.ProductionYear) {
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear();
lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
@@ -911,7 +937,9 @@ import ServerConnections from '../ServerConnections';
}
if (options.showRuntime) {
if (item.RunTimeTicks) {
lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks));
} else {
lines.push('');
@@ -919,11 +947,14 @@ import ServerConnections from '../ServerConnections';
}
if (options.showAirTime) {
lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || '');
}
if (options.showChannelName) {
if (item.ChannelId) {
lines.push(getTextActionButton({
Id: item.ChannelId,
@@ -940,6 +971,7 @@ import ServerConnections from '../ServerConnections';
}
if (options.showCurrentProgram && item.Type === 'TvChannel') {
if (item.CurrentProgram) {
lines.push(item.CurrentProgram.Name);
} else {
@@ -948,6 +980,7 @@ import ServerConnections from '../ServerConnections';
}
if (options.showCurrentProgramTime && item.Type === 'TvChannel') {
if (item.CurrentProgram) {
lines.push(getAirTimeText(item.CurrentProgram, false, true) || '');
} else {
@@ -957,6 +990,7 @@ import ServerConnections from '../ServerConnections';
if (options.showSeriesTimerTime) {
if (item.RecordAnyTime) {
lines.push(globalize.translate('Anytime'));
} else {
lines.push(datetime.getDisplayTime(item.StartDate));
@@ -982,10 +1016,6 @@ import ServerConnections from '../ServerConnections';
lines = [];
}
if (overlayText && showTitle) {
lines = [item.Name];
}
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
@@ -995,6 +1025,7 @@ import ServerConnections from '../ServerConnections';
}
if (html) {
if (!isOuterFooter || logoUrl || options.cardLayout) {
html = '<div class="' + footerClass + '">' + html;
@@ -1036,25 +1067,31 @@ import ServerConnections from '../ServerConnections';
* @returns {string} HTML markup for the item count indicator.
*/
function getItemCountsHtml(options, item) {
const counts = [];
let counts = [];
let childText;
if (item.Type === 'Playlist') {
childText = '';
if (item.RunTimeTicks) {
let minutes = item.RunTimeTicks / 600000000;
minutes = minutes || 1;
childText += globalize.translate('ValueMinutes', Math.round(minutes));
} else {
childText += globalize.translate('ValueMinutes', 0);
}
counts.push(childText);
} else if (item.Type === 'Genre' || item.Type === 'Studio') {
if (item.MovieCount) {
childText = item.MovieCount === 1 ?
globalize.translate('ValueOneMovie') :
globalize.translate('ValueMovieCount', item.MovieCount);
@@ -1063,6 +1100,7 @@ import ServerConnections from '../ServerConnections';
}
if (item.SeriesCount) {
childText = item.SeriesCount === 1 ?
globalize.translate('ValueOneSeries') :
globalize.translate('ValueSeriesCount', item.SeriesCount);
@@ -1070,14 +1108,18 @@ import ServerConnections from '../ServerConnections';
counts.push(childText);
}
if (item.EpisodeCount) {
childText = item.EpisodeCount === 1 ?
globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.EpisodeCount);
counts.push(childText);
}
} else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') {
if (item.AlbumCount) {
childText = item.AlbumCount === 1 ?
globalize.translate('ValueOneAlbum') :
globalize.translate('ValueAlbumCount', item.AlbumCount);
@@ -1085,6 +1127,7 @@ import ServerConnections from '../ServerConnections';
counts.push(childText);
}
if (item.SongCount) {
childText = item.SongCount === 1 ?
globalize.translate('ValueOneSong') :
globalize.translate('ValueSongCount', item.SongCount);
@@ -1092,13 +1135,16 @@ import ServerConnections from '../ServerConnections';
counts.push(childText);
}
if (item.MusicVideoCount) {
childText = item.MusicVideoCount === 1 ?
globalize.translate('ValueOneMusicVideo') :
globalize.translate('ValueMusicVideoCount', item.MusicVideoCount);
counts.push(childText);
}
} else if (item.Type === 'Series') {
childText = item.RecursiveItemCount === 1 ?
globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.RecursiveItemCount);
@@ -1114,11 +1160,10 @@ import ServerConnections from '../ServerConnections';
/**
* Imports the refresh indicator element.
*/
function importRefreshIndicator() {
function requireRefreshIndicator() {
if (!refreshIndicatorLoaded) {
refreshIndicatorLoaded = true;
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator');
require(['emby-itemrefreshindicator']);
}
}
@@ -1152,11 +1197,13 @@ import ServerConnections from '../ServerConnections';
let shape = options.shape;
if (shape === 'mixed') {
shape = null;
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 1.33) {
shape = 'mixedBackdrop';
} else if (primaryImageAspectRatio > 0.71) {
@@ -1212,8 +1259,8 @@ import ServerConnections from '../ServerConnections';
if (coveredImage) {
cardImageContainerClass += ' coveredImage';
if (item.Type === 'TvChannel') {
cardImageContainerClass += ' coveredImage-contain';
if (item.MediaType === 'Photo' || item.Type === 'PhotoAlbum' || item.Type === 'Folder' || item.ProgramInfo || item.Type === 'Program' || item.Type === 'Recording') {
cardImageContainerClass += ' coveredImage-noScale';
}
}
@@ -1248,6 +1295,7 @@ import ServerConnections from '../ServerConnections';
}
if (overlayText) {
logoUrl = null;
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
@@ -1318,7 +1366,7 @@ import ServerConnections from '../ServerConnections';
let cardBoxClose = '';
let cardScalableClose = '';
const cardContentClass = 'cardContent';
let cardContentClass = 'cardContent';
let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
@@ -1337,7 +1385,7 @@ import ServerConnections from '../ServerConnections';
cardImageContainerClose = '</button>';
}
const cardScalableClass = 'cardScalable';
let cardScalableClass = 'cardScalable';
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>';
@@ -1356,6 +1404,7 @@ import ServerConnections from '../ServerConnections';
indicatorsHtml += indicators.getTypeIndicator(item);
if (options.showGroupCount) {
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
@@ -1366,7 +1415,7 @@ import ServerConnections from '../ServerConnections';
if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
importRefreshIndicator();
requireRefreshIndicator();
}
if (indicatorsHtml) {
@@ -1414,28 +1463,26 @@ import ServerConnections from '../ServerConnections';
const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : '';
const collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : '';
const channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : '';
const pathData = item.Path ? (' data-path="' + item.Path + '"') : '';
const contextData = options.context ? (' data-context="' + options.context + '"') : '';
const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : '';
const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : '';
const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : '';
let additionalCardContent = '';
if (layoutManager.desktop && !options.disableHoverMenu) {
additionalCardContent += getHoverMenuHtml(item, action);
additionalCardContent += getHoverMenuHtml(item, action, options);
}
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
}
/**
* Generates HTML markup for the card overlay.
* @param {object} item - Item used to generate the card overlay.
* @param {string} action - Action assigned to the overlay.
* @param {Array} options - Card builder options.
* @returns {string} HTML markup of the card overlay.
*/
function getHoverMenuHtml(item, action) {
function getHoverMenuHtml(item, action, options) {
let html = '';
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
@@ -1451,16 +1498,15 @@ import ServerConnections from '../ServerConnections';
const userData = item.UserData || {};
if (itemHelper.canMarkPlayed(item)) {
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../../elements/emby-playstatebutton/emby-playstatebutton');
require(['emby-playstatebutton']);
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check"></span></button>';
}
if (itemHelper.canRate(item)) {
const likes = userData.Likes == null ? '' : userData.Likes;
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../../elements/emby-ratingbutton/emby-ratingbutton');
require(['emby-ratingbutton']);
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite"></span></button>';
}
@@ -1537,6 +1583,7 @@ import ServerConnections from '../ServerConnections';
const html = buildCardsHtmlInternal(items, options);
if (html) {
if (options.itemsContainer.cardBuilderHtml !== html) {
options.itemsContainer.innerHTML = html;
@@ -1549,6 +1596,7 @@ import ServerConnections from '../ServerConnections';
imageLoader.lazyChildren(options.itemsContainer);
} else {
options.itemsContainer.innerHTML = html;
options.itemsContainer.cardBuilderHtml = null;
}
@@ -1572,6 +1620,7 @@ import ServerConnections from '../ServerConnections';
indicatorsElem = card.querySelector('.cardIndicators');
if (!indicatorsElem) {
const cardImageContainer = card.querySelector('.cardImageContainer');
indicatorsElem = document.createElement('div');
indicatorsElem.classList.add('cardIndicators');
@@ -1595,9 +1644,11 @@ import ServerConnections from '../ServerConnections';
let itemProgressBar = null;
if (userData.Played) {
playedIndicator = card.querySelector('.playedIndicator');
if (!playedIndicator) {
playedIndicator = document.createElement('div');
playedIndicator.classList.add('playedIndicator');
playedIndicator.classList.add('indicator');
@@ -1606,8 +1657,10 @@ import ServerConnections from '../ServerConnections';
}
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
} else {
playedIndicator = card.querySelector('.playedIndicator');
if (playedIndicator) {
playedIndicator.parentNode.removeChild(playedIndicator);
}
}
@@ -1615,6 +1668,7 @@ import ServerConnections from '../ServerConnections';
countIndicator = card.querySelector('.countIndicator');
if (!countIndicator) {
countIndicator = document.createElement('div');
countIndicator.classList.add('countIndicator');
indicatorsElem = ensureIndicators(card, indicatorsElem);
@@ -1622,8 +1676,10 @@ import ServerConnections from '../ServerConnections';
}
countIndicator.innerHTML = userData.UnplayedItemCount;
} else if (enableCountIndicator) {
countIndicator = card.querySelector('.countIndicator');
if (countIndicator) {
countIndicator.parentNode.removeChild(countIndicator);
}
}
@@ -1635,6 +1691,7 @@ import ServerConnections from '../ServerConnections';
});
if (progressHtml) {
itemProgressBar = card.querySelector('.itemProgressBar');
if (!itemProgressBar) {
@@ -1653,6 +1710,7 @@ import ServerConnections from '../ServerConnections';
itemProgressBar.innerHTML = progressHtml;
} else {
itemProgressBar = card.querySelector('.itemProgressBar');
if (itemProgressBar) {
itemProgressBar.parentNode.removeChild(itemProgressBar);
@@ -1683,7 +1741,7 @@ import ServerConnections from '../ServerConnections';
const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]');
for (let i = 0, length = cells.length; i < length; i++) {
const cell = cells[i];
let cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
if (!icon) {
const indicatorsElem = ensureIndicators(cell);
@@ -1702,8 +1760,8 @@ import ServerConnections from '../ServerConnections';
const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]');
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
let cell = cells[i];
let icon = cell.querySelector('.timerIndicator');
if (icon) {
icon.parentNode.removeChild(icon);
}
@@ -1720,8 +1778,8 @@ import ServerConnections from '../ServerConnections';
const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]');
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
let cell = cells[i];
let icon = cell.querySelector('.timerIndicator');
if (icon) {
icon.parentNode.removeChild(icon);
}

View File

@@ -5,15 +5,16 @@
* @module components/cardBuilder/chaptercardbuilder
*/
import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import layoutManager from '../layoutManager';
import browser from '../../scripts/browser';
import ServerConnections from '../ServerConnections';
import datetime from 'datetime';
import imageLoader from 'imageLoader';
import connectionManager from 'connectionManager';
import layoutManager from 'layoutManager';
import browser from 'browser';
const enableFocusTransform = !browser.slow && !browser.edge;
function buildChapterCardsHtml(item, chapters, options) {
// TODO move card creation code to Card component
let className = 'card itemAction chapterCard';
@@ -34,6 +35,7 @@ import ServerConnections from '../ServerConnections';
let shape = (options.backdropShape || 'backdrop');
if (videoStream.Width && videoStream.Height) {
if ((videoStream.Width / videoStream.Height) <= 1.2) {
shape = (options.squareShape || 'square');
}
@@ -48,9 +50,10 @@ import ServerConnections from '../ServerConnections';
let html = '';
let itemsInRow = 0;
const apiClient = ServerConnections.getApiClient(item.ServerId);
const apiClient = connectionManager.getApiClient(item.ServerId);
for (let i = 0, length = chapters.length; i < length; i++) {
if (options.rows && itemsInRow === 0) {
html += '<div class="cardColumn">';
}
@@ -70,10 +73,12 @@ import ServerConnections from '../ServerConnections';
}
function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) {
if (ImageTag) {
return apiClient.getScaledImageUrl(Id, {
maxWidth: maxWidth,
maxWidth: maxWidth * 2,
tag: ImageTag,
type: 'Chapter',
index
@@ -84,6 +89,7 @@ import ServerConnections from '../ServerConnections';
}
function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) {
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
@@ -104,10 +110,13 @@ import ServerConnections from '../ServerConnections';
const cardBoxCssClass = 'cardBox';
const cardScalableClass = 'cardScalable';
return `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
const html = `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
return html;
}
export function buildChapterCards(item, chapters, options) {
if (options.parentContainer) {
// Abort if the container has been disposed
if (!document.body.contains(options.parentContainer)) {

View File

@@ -5,9 +5,10 @@
* @module components/cardBuilder/peoplecardbuilder
*/
import cardBuilder from './cardBuilder';
import cardBuilder from 'cardBuilder';
export function buildPeopleCards(items, options) {
options = Object.assign(options || {}, {
cardLayout: false,
centerText: true,

View File

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

View File

@@ -1,33 +1,21 @@
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import globalize from '../../scripts/globalize';
import actionsheet from '../actionSheet/actionSheet';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-button/emby-button';
import '../listview/listview.css';
import 'material-design-icons-iconfont';
import '../formdialog.css';
import ServerConnections from '../ServerConnections';
define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'actionsheet', 'emby-input', 'paper-icon-button-light', 'emby-button', 'listViewStyle', 'material-icons', 'formDialogStyle'], function (dom, dialogHelper, loading, connectionManager, globalize, actionsheet) {
'use strict';
export default class channelMapper {
constructor(options) {
return function (options) {
function mapChannel(button, channelId, providerChannelId) {
loading.show();
const providerId = options.providerId;
ServerConnections.getApiClient(options.serverId).ajax({
var providerId = options.providerId;
connectionManager.getApiClient(options.serverId).ajax({
type: 'POST',
url: ApiClient.getUrl('LiveTv/ChannelMappings'),
data: JSON.stringify({
data: {
providerId: providerId,
tunerChannelId: channelId,
providerChannelId: providerChannelId
}),
contentType: 'application/json',
},
dataType: 'json'
}).then(mapping => {
const listItem = dom.parentWithClass(button, 'listItem');
}).then(function (mapping) {
var listItem = dom.parentWithClass(button, 'listItem');
button.setAttribute('data-providerid', mapping.ProviderChannelId);
listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
loading.hide();
@@ -35,42 +23,42 @@ export default class channelMapper {
}
function onChannelsElementClick(e) {
const btnMap = dom.parentWithClass(e.target, 'btnMap');
var btnMap = dom.parentWithClass(e.target, 'btnMap');
if (btnMap) {
const channelId = btnMap.getAttribute('data-id');
const providerChannelId = btnMap.getAttribute('data-providerid');
const menuItems = currentMappingOptions.ProviderChannels.map(m => {
var channelId = btnMap.getAttribute('data-id');
var providerChannelId = btnMap.getAttribute('data-providerid');
var menuItems = currentMappingOptions.ProviderChannels.map(function (m) {
return {
name: m.Name,
id: m.Id,
selected: m.Id.toLowerCase() === providerChannelId.toLowerCase()
};
}).sort((a, b) => {
}).sort(function (a, b) {
return a.name.localeCompare(b.name);
});
actionsheet.show({
positionTo: btnMap,
items: menuItems
}).then(newChannelId => {
}).then(function (newChannelId) {
mapChannel(btnMap, channelId, newChannelId);
});
}
}
function getChannelMappingOptions(serverId, providerId) {
const apiClient = ServerConnections.getApiClient(serverId);
var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', {
providerId: providerId
}));
}
function getMappingSecondaryName(mapping, providerName) {
return `${mapping.ProviderChannelName || ''} - ${providerName}`;
return (mapping.ProviderChannelName || '') + ' - ' + providerName;
}
function getTunerChannelHtml(channel, providerName) {
let html = '';
var html = '';
html += '<div class="listItem">';
html += '<span class="material-icons listItemIcon dvr"></span>';
html += '<div class="listItemBody two-line">';
@@ -85,16 +73,16 @@ export default class channelMapper {
html += '</div>';
html += '</div>';
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit"></span></button>`;
html += '<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="' + channel.Id + '" data-providerid="' + channel.ProviderChannelId + '"><span class="material-icons mode_edit"></span></button>';
return html += '</div>';
}
function getEditorHtml() {
let html = '';
var html = '';
html += '<div class="formDialogContent smoothScrollY">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">';
html += `<h1>${globalize.translate('Channels')}</h1>`;
html += '<h1>' + globalize.translate('HeaderChannels') + '</h1>';
html += '<div class="channels paperList">';
html += '</div>';
html += '</form>';
@@ -103,29 +91,30 @@ export default class channelMapper {
}
function initEditor(dlg, options) {
getChannelMappingOptions(options.serverId, options.providerId).then(result => {
getChannelMappingOptions(options.serverId, options.providerId).then(function (result) {
currentMappingOptions = result;
const channelsElement = dlg.querySelector('.channels');
channelsElement.innerHTML = result.TunerChannels.map(channel => {
var channelsElement = dlg.querySelector('.channels');
channelsElement.innerHTML = result.TunerChannels.map(function (channel) {
return getTunerChannelHtml(channel, result.ProviderName);
}).join('');
channelsElement.addEventListener('click', onChannelsElementClick);
});
}
let currentMappingOptions;
var currentMappingOptions;
var self = this;
this.show = () => {
const dialogOptions = {
self.show = function () {
var dialogOptions = {
removeOnClose: true
};
dialogOptions.size = 'small';
const dlg = dialogHelper.createDialog(dialogOptions);
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
let html = '';
const title = globalize.translate('MapChannels');
var html = '';
var title = globalize.translate('MapChannels');
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
@@ -135,13 +124,13 @@ export default class channelMapper {
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, options);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
return new Promise(resolve => {
return new Promise(function (resolve, reject) {
dlg.addEventListener('close', resolve);
dialogHelper.open(dlg);
});
};
}
}
};
});

View File

@@ -1,32 +1,16 @@
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import layoutManager from '../layoutManager';
import { appRouter } from '../appRouter';
import globalize from '../../scripts/globalize';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.css';
import '../../assets/css/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dom, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
'use strict';
/* eslint-disable indent */
let currentServerId;
var currentServerId;
function onSubmit(e) {
loading.show();
const panel = dom.parentWithClass(this, 'dialog');
var panel = dom.parentWithClass(this, 'dialog');
const collectionId = panel.querySelector('#selectCollectionToAddTo').value;
var collectionId = panel.querySelector('#selectCollectionToAddTo').value;
const apiClient = ServerConnections.getApiClient(currentServerId);
var apiClient = connectionManager.getApiClient(currentServerId);
if (collectionId) {
addToCollection(apiClient, panel, collectionId);
@@ -39,7 +23,8 @@ import toast from '../toast/toast';
}
function createCollection(apiClient, dlg) {
const url = apiClient.getUrl('Collections', {
var url = apiClient.getUrl('Collections', {
Name: dlg.querySelector('#txtNewCollectionName').value,
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
@@ -51,23 +36,27 @@ import toast from '../toast/toast';
url: url,
dataType: 'json'
}).then(result => {
}).then(function (result) {
loading.hide();
const id = result.Id;
var id = result.Id;
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToCollection(apiClient, id);
});
}
function redirectToCollection(apiClient, id) {
appRouter.showItem(id, apiClient.serverId());
}
function addToCollection(apiClient, dlg, id) {
const url = apiClient.getUrl(`Collections/${id}/Items`, {
var url = apiClient.getUrl('Collections/' + id + '/Items', {
Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
});
@@ -76,13 +65,16 @@ import toast from '../toast/toast';
type: 'POST',
url: url
}).then(() => {
}).then(function () {
loading.hide();
dlg.submitted = true;
dialogHelper.close(dlg);
toast(globalize.translate('MessageItemsAdded'));
require(['toast'], function (toast) {
toast(globalize.translate('MessageItemsAdded'));
});
});
}
@@ -91,13 +83,14 @@ import toast from '../toast/toast';
}
function populateCollections(panel) {
loading.show();
const select = panel.querySelector('#selectCollectionToAddTo');
var select = panel.querySelector('#selectCollectionToAddTo');
panel.querySelector('.newCollectionInfo').classList.add('hide');
const options = {
var options = {
Recursive: true,
IncludeItemTypes: 'BoxSet',
@@ -105,14 +98,16 @@ import toast from '../toast/toast';
EnableTotalRecordCount: false
};
const apiClient = ServerConnections.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
let html = '';
var apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
var html = '';
html += result.Items.map(i => {
return `<option value="${i.Id}">${i.Name}</option>`;
html += '<option value="">' + globalize.translate('OptionNew') + '</option>';
html += result.Items.map(function (i) {
return '<option value="' + i.Id + '">' + i.Name + '</option>';
});
select.innerHTML = html;
@@ -124,7 +119,8 @@ import toast from '../toast/toast';
}
function getEditorHtml() {
let html = '';
var html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
@@ -138,27 +134,27 @@ import toast from '../toast/toast';
html += '<br/>';
html += '<br/>';
html += '<div class="selectContainer">';
html += `<select is="emby-select" label="${globalize.translate('LabelCollection')}" id="selectCollectionToAddTo" autofocus></select>`;
html += '<select is="emby-select" label="' + globalize.translate('LabelCollection') + '" id="selectCollectionToAddTo" autofocus></select>';
html += '</div>';
html += '</div>';
html += '<div class="newCollectionInfo">';
html += '<div class="inputContainer">';
html += `<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="${globalize.translate('LabelName')}" />`;
html += `<div class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
html += '<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="' + globalize.translate('LabelName') + '" />';
html += '<div class="fieldDescription">' + globalize.translate('NewCollectionNameExample') + '</div>';
html += '</div>';
html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
html += '<span>' + globalize.translate('SearchForCollectionInternetMetadata') + '</span>';
html += '</label>';
// newCollectionInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('ButtonOk')}</button>`;
html += '<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">' + globalize.translate('ButtonOk') + '</button>';
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
@@ -171,6 +167,7 @@ import toast from '../toast/toast';
}
function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide');
@@ -191,7 +188,7 @@ import toast from '../toast/toast';
} else {
content.querySelector('.fldSelectCollection').classList.add('hide');
const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
var selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
selectCollectionToAddTo.innerHTML = '';
selectCollectionToAddTo.value = '';
triggerChange(selectCollectionToAddTo);
@@ -199,70 +196,79 @@ import toast from '../toast/toast';
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export class showEditor {
constructor(options) {
const items = options.items || {};
currentServerId = options.serverId;
function CollectionEditor() {
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
}
}
/* eslint-enable indent */
export default showEditor;
CollectionEditor.prototype.show = function (options) {
var items = options.items || {};
currentServerId = options.serverId;
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
if (appHost.supports('externallinks')) {
html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate('Help') + '"><span class="material-icons info"></span><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>';
}
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
};
return CollectionEditor;
});

View File

@@ -1,65 +1,65 @@
import browser from '../../scripts/browser';
import dialog from '../dialog/dialog';
import globalize from '../../scripts/globalize';
define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize) {
'use strict';
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
function nativeConfirm(options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
const text = replaceAll(options.text || '', '<br/>', '\n');
const result = window.confirm(text);
if (browser.tv && window.confirm) {
// Use the native confirm dialog
return function (options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
}
var text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text);
function customConfirm(text, title) {
let options;
if (typeof text === 'string') {
options = {
title: title,
text: text
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
};
} else {
options = text;
// Use our own dialog
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
var items = [];
items.push({
name: options.cancelText || globalize.translate('ButtonCancel'),
id: 'cancel',
type: 'cancel'
});
items.push({
name: options.confirmText || globalize.translate('ButtonOk'),
id: 'ok',
type: options.primary === 'delete' ? 'delete' : 'submit'
});
options.buttons = items;
return dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
return Promise.reject();
});
};
}
const items = [];
items.push({
name: options.cancelText || globalize.translate('ButtonCancel'),
id: 'cancel',
type: 'cancel'
});
items.push({
name: options.confirmText || globalize.translate('ButtonOk'),
id: 'ok',
type: options.primary === 'delete' ? 'delete' : 'submit'
});
options.buttons = items;
return dialog.show(options).then(result => {
if (result === 'ok') {
return Promise.resolve();
}
return Promise.reject();
});
}
const confirm = browser.tv && window.confirm ? nativeConfirm : customConfirm;
export default confirm;
});

View File

@@ -1,32 +1,20 @@
import dialogHelper from '../dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
import layoutManager from '../layoutManager';
import scrollHelper from '../../scripts/scrollHelper';
import globalize from '../../scripts/globalize';
import 'material-design-icons-iconfont';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-input/emby-input';
import '../formdialog.css';
import '../../assets/css/flexstyles.scss';
import template from './dialog.template.html';
define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
'use strict';
/* eslint-disable indent */
function showDialog(options, template) {
function showDialog(options = { dialogOptions: {}, buttons: [] }) {
const dialogOptions = {
var dialogOptions = {
removeOnClose: true,
scrollY: false,
...options.dialogOptions
scrollY: false
};
const enableTvLayout = layoutManager.tv;
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
const dlg = dialogHelper.createDialog(dialogOptions);
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
@@ -34,7 +22,7 @@ import template from './dialog.template.html';
dlg.classList.add('align-items-center');
dlg.classList.add('justify-content-center');
const formDialogContent = dlg.querySelector('.formDialogContent');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.classList.add('no-grow');
if (enableTvLayout) {
@@ -42,36 +30,41 @@ import template from './dialog.template.html';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = `${Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)}px`;
formDialogContent.style.maxWidth = (Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
dlg.classList.add('dialog-fullscreen-lowres');
}
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
// dialogHelper.close(dlg);
//});
if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
} else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
}
const displayText = options.html || options.text || '';
var displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = displayText;
if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide');
}
let i;
let length;
let html = '';
let hasDescriptions = false;
var i;
var length;
var html = '';
var hasDescriptions = false;
for (i = 0, length = options.buttons.length; i < length; i++) {
const item = options.buttons[i];
const autoFocus = i === 0 ? ' autofocus' : '';
let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
var item = options.buttons[i];
var autoFocus = i === 0 ? ' autofocus' : '';
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ` button-${item.type}`;
buttonClass += ' button-' + item.type;
}
if (item.description) {
@@ -82,10 +75,10 @@ import template from './dialog.template.html';
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
}
html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${item.name}</button>`;
html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + '>' + item.name + '</button>';
if (item.description) {
html += `<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">${item.description}</div>`;
html += '<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">' + item.description + '</div>';
}
}
@@ -95,18 +88,19 @@ import template from './dialog.template.html';
dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical');
}
let dialogResult;
var dialogResult;
function onButtonClick() {
dialogResult = this.getAttribute('data-id');
dialogHelper.close(dlg);
}
const buttons = dlg.querySelectorAll('.btnOption');
var buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
return dialogHelper.open(dlg).then(() => {
return dialogHelper.open(dlg).then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
@@ -119,8 +113,9 @@ import template from './dialog.template.html';
});
}
export function show(text, title) {
let options;
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
@@ -130,10 +125,10 @@ import template from './dialog.template.html';
options = text;
}
return showDialog(options);
}
/* eslint-enable indent */
export default {
show: show
};
return new Promise(function (resolve, reject) {
require(['text!./dialog.template.html'], function (template) {
showDialog(options, template).then(resolve, reject);
});
});
};
});

View File

@@ -1,17 +1,10 @@
import { appRouter } from '../appRouter';
import focusManager from '../focusManager';
import browser from '../../scripts/browser';
import layoutManager from '../layoutManager';
import inputManager from '../../scripts/inputManager';
import dom from '../../scripts/dom';
import './dialoghelper.css';
import '../../assets/css/scrollstyles.css';
define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 'dom', 'css!./dialoghelper.css', 'scrollStyles'], function (appRouter, focusManager, browser, layoutManager, inputManager, dom) {
'use strict';
/* eslint-disable indent */
let globalOnOpenCallback;
var globalOnOpenCallback;
function enableAnimation() {
// too slow
if (browser.tv) {
return false;
@@ -21,6 +14,7 @@ import '../../assets/css/scrollstyles.css';
}
function removeCenterFocus(dlg) {
if (layoutManager.tv) {
if (dlg.classList.contains('scrollX')) {
centerFocus(dlg, true, false);
@@ -31,8 +25,9 @@ import '../../assets/css/scrollstyles.css';
}
function tryRemoveElement(elem) {
const parentNode = elem.parentNode;
var parentNode = elem.parentNode;
if (parentNode) {
// Seeing crashes in edge webview
try {
parentNode.removeChild(elem);
@@ -43,13 +38,15 @@ import '../../assets/css/scrollstyles.css';
}
function DialogHashHandler(dlg, hash, resolve) {
const self = this;
var self = this;
self.originalUrl = window.location.href;
const activeElement = document.activeElement;
let removeScrollLockOnClose = false;
var activeElement = document.activeElement;
var removeScrollLockOnClose = false;
function onHashChange(e) {
const isBack = self.originalUrl === window.location.href;
var isBack = self.originalUrl === window.location.href;
if (isBack || !isOpened(dlg)) {
window.removeEventListener('popstate', onHashChange);
@@ -62,6 +59,7 @@ import '../../assets/css/scrollstyles.css';
}
function onBackCommand(e) {
if (e.detail.command === 'back') {
self.closedByBack = true;
e.preventDefault();
@@ -71,6 +69,7 @@ import '../../assets/css/scrollstyles.css';
}
function onDialogClosed() {
if (!isHistoryEnabled(dlg)) {
inputManager.off(dlg, onBackCommand);
}
@@ -85,9 +84,9 @@ import '../../assets/css/scrollstyles.css';
}
if (!self.closedByBack && isHistoryEnabled(dlg)) {
const state = window.history.state || {};
var state = history.state || {};
if (state.dialogId === hash) {
window.history.back();
history.back();
}
}
@@ -98,7 +97,7 @@ import '../../assets/css/scrollstyles.css';
if (dlg.getAttribute('data-removeonclose') !== 'false') {
removeCenterFocus(dlg);
const dialogContainer = dlg.dialogContainer;
var dialogContainer = dlg.dialogContainer;
if (dialogContainer) {
tryRemoveElement(dialogContainer);
dlg.dialogContainer = null;
@@ -109,7 +108,7 @@ import '../../assets/css/scrollstyles.css';
//resolve();
// if we just called history.back(), then use a timeout to allow the history events to fire first
setTimeout(() => {
setTimeout(function () {
resolve({
element: dlg,
closedByBack: self.closedByBack
@@ -119,7 +118,7 @@ import '../../assets/css/scrollstyles.css';
dlg.addEventListener('close', onDialogClosed);
const center = !dlg.classList.contains('dialog-fixedSize');
var center = !dlg.classList.contains('dialog-fixedSize');
if (center) {
dlg.classList.add('centeredDialog');
}
@@ -142,7 +141,7 @@ import '../../assets/css/scrollstyles.css';
animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) {
appRouter.pushState({ dialogId: hash }, 'Dialog', `#${hash}`);
appRouter.pushState({ dialogId: hash }, 'Dialog', '#' + hash);
window.addEventListener('popstate', onHashChange);
} else {
@@ -151,10 +150,11 @@ import '../../assets/css/scrollstyles.css';
}
function addBackdropOverlay(dlg) {
const backdrop = document.createElement('div');
var backdrop = document.createElement('div');
backdrop.classList.add('dialogBackdrop');
const backdropParent = dlg.dialogContainer || dlg;
var backdropParent = dlg.dialogContainer || dlg;
backdropParent.parentNode.insertBefore(backdrop, backdropParent);
dlg.backdrop = backdrop;
@@ -162,7 +162,7 @@ import '../../assets/css/scrollstyles.css';
void backdrop.offsetWidth;
backdrop.classList.add('dialogBackdropOpened');
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => {
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', function (e) {
if (e.target === dlg.dialogContainer) {
close(dlg);
}
@@ -170,7 +170,7 @@ import '../../assets/css/scrollstyles.css';
passive: true
});
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => {
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
if (e.target === dlg.dialogContainer) {
// Close the application dialog menu
close(dlg);
@@ -184,36 +184,40 @@ import '../../assets/css/scrollstyles.css';
return dlg.getAttribute('data-history') === 'true';
}
export function open(dlg) {
function open(dlg) {
if (globalOnOpenCallback) {
globalOnOpenCallback(dlg);
}
const parent = dlg.parentNode;
var parent = dlg.parentNode;
if (parent) {
parent.removeChild(dlg);
}
const dialogContainer = document.createElement('div');
var dialogContainer = document.createElement('div');
dialogContainer.classList.add('dialogContainer');
dialogContainer.appendChild(dlg);
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise((resolve, reject) => {
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
return new Promise(function (resolve, reject) {
new DialogHashHandler(dlg, 'dlg' + new Date().getTime(), resolve);
});
}
function isOpened(dlg) {
//return dlg.opened;
return !dlg.classList.contains('hide');
}
export function close(dlg) {
function close(dlg) {
if (isOpened(dlg)) {
if (isHistoryEnabled(dlg)) {
window.history.back();
history.back();
} else {
closeDialog(dlg);
}
@@ -221,13 +225,15 @@ import '../../assets/css/scrollstyles.css';
}
function closeDialog(dlg) {
if (!dlg.classList.contains('hide')) {
dlg.dispatchEvent(new CustomEvent('closing', {
bubbles: false,
cancelable: false
}));
const onAnimationFinish = () => {
var onAnimationFinish = function () {
focusManager.popScope(dlg);
dlg.classList.add('hide');
@@ -242,7 +248,8 @@ import '../../assets/css/scrollstyles.css';
}
function animateDialogOpen(dlg) {
const onAnimationFinish = () => {
var onAnimationFinish = function () {
focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') {
@@ -256,7 +263,8 @@ import '../../assets/css/scrollstyles.css';
};
if (enableAnimation()) {
const onFinish = () => {
var onFinish = function () {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
@@ -272,24 +280,27 @@ import '../../assets/css/scrollstyles.css';
}
function animateDialogClose(dlg, onAnimationFinish) {
if (enableAnimation()) {
let animated = true;
var animated = true;
switch (dlg.animationConfig.exit.name) {
case 'fadeout':
dlg.style.animation = `fadeout ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
dlg.style.animation = 'fadeout ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
break;
case 'scaledown':
dlg.style.animation = `scaledown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
dlg.style.animation = 'scaledown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
break;
case 'slidedown':
dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
dlg.style.animation = 'slidedown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
break;
default:
animated = false;
break;
}
const onFinish = () => {
var onFinish = function () {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
@@ -307,9 +318,14 @@ import '../../assets/css/scrollstyles.css';
onAnimationFinish();
}
const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
var supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
function shouldLockDocumentScroll(options) {
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
return false;
}
if (options.lockScroll != null) {
return options.lockScroll;
}
@@ -318,10 +334,6 @@ import '../../assets/css/scrollstyles.css';
return true;
}
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
return false;
}
if (options.size) {
return true;
}
@@ -330,7 +342,8 @@ import '../../assets/css/scrollstyles.css';
}
function removeBackdrop(dlg) {
const backdrop = dlg.backdrop;
var backdrop = dlg.backdrop;
if (!backdrop) {
return;
@@ -338,11 +351,12 @@ import '../../assets/css/scrollstyles.css';
dlg.backdrop = null;
const onAnimationFinish = () => {
var onAnimationFinish = function () {
tryRemoveElement(backdrop);
};
if (enableAnimation()) {
backdrop.classList.remove('dialogBackdropOpened');
// this is not firing animatonend
@@ -354,22 +368,20 @@ import '../../assets/css/scrollstyles.css';
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export function createDialog(options = {}) {
function createDialog(options) {
options = options || {};
// If there's no native dialog support, use a plain div
// Also not working well in samsung tizen browser, content inside not clickable
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
const dlg = document.createElement('div');
// Add an id so we can access the dialog element
if (options.id) {
dlg.id = options.id;
}
var dlg = document.createElement('div');
dlg.classList.add('focuscontainer');
dlg.classList.add('hide');
@@ -378,7 +390,7 @@ import '../../assets/css/scrollstyles.css';
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory === true) {
if (options.enableHistory !== false && appRouter.enableNativeHistory()) {
dlg.setAttribute('data-history', 'true');
}
@@ -394,14 +406,17 @@ import '../../assets/css/scrollstyles.css';
dlg.setAttribute('data-autofocus', 'true');
}
const defaultEntryAnimation = 'scaleup';
const defaultExitAnimation = 'scaledown';
const entryAnimation = options.entryAnimation || defaultEntryAnimation;
const exitAnimation = options.exitAnimation || defaultExitAnimation;
var defaultEntryAnimation;
var defaultExitAnimation;
defaultEntryAnimation = 'scaleup';
defaultExitAnimation = 'scaledown';
var entryAnimation = options.entryAnimation || defaultEntryAnimation;
var exitAnimation = options.exitAnimation || defaultExitAnimation;
// If it's not fullscreen then lower the default animation speed to make it open really fast
const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
var entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
var exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
dlg.animationConfig = {
// scale up
@@ -446,22 +461,24 @@ import '../../assets/css/scrollstyles.css';
if (options.size) {
dlg.classList.add('dialog-fixedSize');
dlg.classList.add(`dialog-${options.size}`);
dlg.classList.add('dialog-' + options.size);
}
if (enableAnimation()) {
switch (dlg.animationConfig.entry.name) {
case 'fadein':
dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`;
dlg.style.animation = 'fadein ' + entryAnimationDuration + 'ms ease-out normal';
break;
case 'scaleup':
dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`;
dlg.style.animation = 'scaleup ' + entryAnimationDuration + 'ms ease-out normal both';
break;
case 'slideup':
dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`;
dlg.style.animation = 'slideup ' + entryAnimationDuration + 'ms ease-out normal';
break;
case 'slidedown':
dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`;
dlg.style.animation = 'slidedown ' + entryAnimationDuration + 'ms ease-out normal';
break;
default:
break;
@@ -471,15 +488,12 @@ import '../../assets/css/scrollstyles.css';
return dlg;
}
export function setOnOpen(val) {
globalOnOpenCallback = val;
}
/* eslint-enable indent */
export default {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: setOnOpen
};
return {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: function (val) {
globalOnOpenCallback = val;
}
};
});

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