Compare commits
125 Commits
release-10
...
release-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be195b0e24 | ||
|
|
32330e3126 | ||
|
|
af706726ef | ||
|
|
393bfd2559 | ||
|
|
575766b8e8 | ||
|
|
f2cbfbb549 | ||
|
|
b589363d31 | ||
|
|
4c1a301bdb | ||
|
|
dfcfaad39c | ||
|
|
f9109a8194 | ||
|
|
2192388b78 | ||
|
|
f6d50b0721 | ||
|
|
05816d5562 | ||
|
|
65f4e29d36 | ||
|
|
0f0593f260 | ||
|
|
7d920beff0 | ||
|
|
2dc95beb8f | ||
|
|
8449b94d16 | ||
|
|
4a17964e67 | ||
|
|
c2106ab86c | ||
|
|
591ee288bf | ||
|
|
87cae4336c | ||
|
|
7cf812df37 | ||
|
|
b5d9ff9279 | ||
|
|
b053cb9d79 | ||
|
|
ba0815e1c3 | ||
|
|
94ac2b22a2 | ||
|
|
7af38bb60c | ||
|
|
21fd1a96fe | ||
|
|
b50eb4fa2e | ||
|
|
458fad3cb0 | ||
|
|
b467e581b0 | ||
|
|
2d381bbdc6 | ||
|
|
bdfc09739e | ||
|
|
ff0a46a004 | ||
|
|
32d152150b | ||
|
|
b9e0dd4938 | ||
|
|
79d0ed3ee4 | ||
|
|
d73d00a344 | ||
|
|
d91d3120e9 | ||
|
|
74de218ff6 | ||
|
|
eb3f1a3ba3 | ||
|
|
8e63b38a08 | ||
|
|
def3532019 | ||
|
|
5ec953e344 | ||
|
|
85013363e8 | ||
|
|
79c8495a1b | ||
|
|
add2ec132f | ||
|
|
bcba45fc7d | ||
|
|
e404106f9c | ||
|
|
31b52f2c32 | ||
|
|
a6d9897d8f | ||
|
|
925c2c926d | ||
|
|
a1dddf4524 | ||
|
|
bad273ee3f | ||
|
|
e6f59a761b | ||
|
|
afdaf29dc6 | ||
|
|
48201581d6 | ||
|
|
8bb34c4266 | ||
|
|
3ad0bb9118 | ||
|
|
7724b5fedc | ||
|
|
2a7d708944 | ||
|
|
97461dabf2 | ||
|
|
0ccbae44d1 | ||
|
|
d85c91ce2e | ||
|
|
93042157f8 | ||
|
|
5e8c92a7a7 | ||
|
|
3a57471b69 | ||
|
|
fb17afad60 | ||
|
|
4e38caba0a | ||
|
|
1f97acc46e | ||
|
|
998df179a8 | ||
|
|
ff9fab5af1 | ||
|
|
84c5df8ea4 | ||
|
|
d4c8cceb7f | ||
|
|
d588fc42c6 | ||
|
|
0fa42cb7e6 | ||
|
|
ef4f5d1f08 | ||
|
|
d27f661b46 | ||
|
|
0f247fd9f4 | ||
|
|
ac34647e85 | ||
|
|
e8028aa1a1 | ||
|
|
42b0b01a4e | ||
|
|
cde21b3ff2 | ||
|
|
58dc73cf66 | ||
|
|
136983e026 | ||
|
|
f38fb6eb46 | ||
|
|
aba1d8655b | ||
|
|
7582206eb3 | ||
|
|
68edd48a0d | ||
|
|
e8d044fbb7 | ||
|
|
7379822c72 | ||
|
|
2c230388be | ||
|
|
32514307eb | ||
|
|
0f12bd64aa | ||
|
|
6ff1bdcaca | ||
|
|
88dc049a2f | ||
|
|
5d42ac19c7 | ||
|
|
efe4374153 | ||
|
|
cd256f7989 | ||
|
|
49fa4e0b65 | ||
|
|
3a4023ca40 | ||
|
|
982bbee9ea | ||
|
|
142c56bf6a | ||
|
|
1207ac98be | ||
|
|
c757c53e5c | ||
|
|
cd2d30eefc | ||
|
|
07a33779d8 | ||
|
|
4dedb8ae45 | ||
|
|
87d459f827 | ||
|
|
eeded17cbd | ||
|
|
95a995327d | ||
|
|
27896bcc84 | ||
|
|
4aeecfa043 | ||
|
|
88bb7adaba | ||
|
|
d768cf7970 | ||
|
|
22df1eb3a6 | ||
|
|
ac6ba3228f | ||
|
|
7e5fcd8374 | ||
|
|
ccef18ee5d | ||
|
|
5f63743ed0 | ||
|
|
972ecc4106 | ||
|
|
c4fd94b147 | ||
|
|
a0ef8a405c | ||
|
|
4b9ffb7b22 |
@@ -21,19 +21,19 @@ jobs:
|
||||
- task: Cache@2
|
||||
displayName: 'Cache node_modules'
|
||||
inputs:
|
||||
key: 'npm | package-lock.json'
|
||||
key: 'yarn | yarn.lock'
|
||||
path: 'node_modules'
|
||||
|
||||
- script: 'npm ci --no-audit'
|
||||
- script: 'yarn install --frozen-lockfile'
|
||||
displayName: 'Install Dependencies'
|
||||
env:
|
||||
SKIP_PREPARE: 'true'
|
||||
|
||||
- script: 'npm run build:development'
|
||||
- script: 'yarn build:development'
|
||||
displayName: 'Build Development'
|
||||
condition: eq(variables['BuildConfiguration'], 'development')
|
||||
|
||||
- script: 'npm run build:production'
|
||||
- script: 'yarn build:production'
|
||||
displayName: 'Build Production'
|
||||
condition: eq(variables['BuildConfiguration'], 'production')
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ jobs:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||
displayName: Set release version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
|
||||
displayName: 'Build Dockerfile'
|
||||
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
@@ -123,4 +119,4 @@ jobs:
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'inline'
|
||||
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch)'
|
||||
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'
|
||||
|
||||
1
.copr/Makefile
Symbolic link
1
.copr/Makefile
Symbolic link
@@ -0,0 +1 @@
|
||||
../fedora/Makefile
|
||||
222
.eslintrc.js
222
.eslintrc.js
@@ -4,7 +4,6 @@ module.exports = {
|
||||
root: true,
|
||||
plugins: [
|
||||
'@babel',
|
||||
'react',
|
||||
'promise',
|
||||
'import',
|
||||
'eslint-comments'
|
||||
@@ -19,31 +18,25 @@ module.exports = {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
jsx: true
|
||||
impliedStrict: true
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
// 'plugin:promise/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:eslint-comments/recommended',
|
||||
'plugin:compat/recommended'
|
||||
],
|
||||
rules: {
|
||||
'array-callback-return': ['error'],
|
||||
'block-spacing': ['error'],
|
||||
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'comma-spacing': ['error'],
|
||||
'default-case-last': ['error'],
|
||||
'eol-last': ['error'],
|
||||
'indent': ['error', 4, { 'SwitchCase': 1 }],
|
||||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
'keyword-spacing': ['error'],
|
||||
'max-statements-per-line': ['error'],
|
||||
'no-empty-function': ['error'],
|
||||
'no-floating-decimal': ['error'],
|
||||
'no-multi-spaces': ['error'],
|
||||
'no-multiple-empty-lines': ['error', { 'max': 1 }],
|
||||
@@ -60,110 +53,10 @@ module.exports = {
|
||||
'space-infix-ops': 'error',
|
||||
'yoda': 'error'
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect'
|
||||
},
|
||||
'import/extensions': [
|
||||
'.js',
|
||||
'.ts',
|
||||
'.jsx',
|
||||
'.tsx'
|
||||
],
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': [ '.ts', '.tsx' ]
|
||||
},
|
||||
polyfills: [
|
||||
// Native Promises Only
|
||||
'Promise',
|
||||
// whatwg-fetch
|
||||
'fetch',
|
||||
// document-register-element
|
||||
'document.registerElement',
|
||||
// resize-observer-polyfill
|
||||
'ResizeObserver',
|
||||
// fast-text-encoding
|
||||
'TextEncoder',
|
||||
// intersection-observer
|
||||
'IntersectionObserver',
|
||||
// Core-js
|
||||
'Object.assign',
|
||||
'Object.is',
|
||||
'Object.setPrototypeOf',
|
||||
'Object.toString',
|
||||
'Object.freeze',
|
||||
'Object.seal',
|
||||
'Object.preventExtensions',
|
||||
'Object.isFrozen',
|
||||
'Object.isSealed',
|
||||
'Object.isExtensible',
|
||||
'Object.getOwnPropertyDescriptor',
|
||||
'Object.getPrototypeOf',
|
||||
'Object.keys',
|
||||
'Object.entries',
|
||||
'Object.getOwnPropertyNames',
|
||||
'Function.name',
|
||||
'Function.hasInstance',
|
||||
'Array.from',
|
||||
'Array.arrayOf',
|
||||
'Array.copyWithin',
|
||||
'Array.fill',
|
||||
'Array.find',
|
||||
'Array.findIndex',
|
||||
'Array.iterator',
|
||||
'String.fromCodePoint',
|
||||
'String.raw',
|
||||
'String.iterator',
|
||||
'String.codePointAt',
|
||||
'String.endsWith',
|
||||
'String.includes',
|
||||
'String.repeat',
|
||||
'String.startsWith',
|
||||
'String.trim',
|
||||
'String.anchor',
|
||||
'String.big',
|
||||
'String.blink',
|
||||
'String.bold',
|
||||
'String.fixed',
|
||||
'String.fontcolor',
|
||||
'String.fontsize',
|
||||
'String.italics',
|
||||
'String.link',
|
||||
'String.small',
|
||||
'String.strike',
|
||||
'String.sub',
|
||||
'String.sup',
|
||||
'RegExp',
|
||||
'Number',
|
||||
'Math',
|
||||
'Date',
|
||||
'async',
|
||||
'Symbol',
|
||||
'Map',
|
||||
'Set',
|
||||
'WeakMap',
|
||||
'WeakSet',
|
||||
'ArrayBuffer',
|
||||
'DataView',
|
||||
'Int8Array',
|
||||
'Uint8Array',
|
||||
'Uint8ClampedArray',
|
||||
'Int16Array',
|
||||
'Uint16Array',
|
||||
'Int32Array',
|
||||
'Uint32Array',
|
||||
'Float32Array',
|
||||
'Float64Array',
|
||||
'Reflect',
|
||||
// Temporary while eslint-compat-plugin is buggy
|
||||
'document.querySelector'
|
||||
]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'./src/**/*.js',
|
||||
'./src/**/*.ts'
|
||||
'./src/**/*.js'
|
||||
],
|
||||
parser: '@babel/eslint-parser',
|
||||
env: {
|
||||
@@ -187,7 +80,6 @@ module.exports = {
|
||||
'jQuery': 'readonly',
|
||||
// Jellyfin globals
|
||||
'ApiClient': 'writable',
|
||||
'Events': 'writable',
|
||||
'chrome': 'writable',
|
||||
'DlnaProfilePage': 'writable',
|
||||
'DashboardPage': 'writable',
|
||||
@@ -203,30 +95,102 @@ module.exports = {
|
||||
'Loading': 'writable',
|
||||
'MetadataEditor': 'writable',
|
||||
'PlaylistViewer': 'writable',
|
||||
'ServerNotifications': 'writable',
|
||||
'TaskButton': 'writable',
|
||||
'UserParentalControlPage': 'writable',
|
||||
'Windows': 'readonly'
|
||||
},
|
||||
rules: {
|
||||
// TODO: Fix warnings and remove these rules
|
||||
'no-redeclare': ['warn'],
|
||||
'no-useless-escape': ['warn'],
|
||||
'no-unused-vars': ['warn']
|
||||
},
|
||||
settings: {
|
||||
polyfills: [
|
||||
// Native Promises Only
|
||||
'Promise',
|
||||
// whatwg-fetch
|
||||
'fetch',
|
||||
// document-register-element
|
||||
'document.registerElement',
|
||||
// resize-observer-polyfill
|
||||
'ResizeObserver',
|
||||
// fast-text-encoding
|
||||
'TextEncoder',
|
||||
// intersection-observer
|
||||
'IntersectionObserver',
|
||||
// Core-js
|
||||
'Object.assign',
|
||||
'Object.is',
|
||||
'Object.setPrototypeOf',
|
||||
'Object.toString',
|
||||
'Object.freeze',
|
||||
'Object.seal',
|
||||
'Object.preventExtensions',
|
||||
'Object.isFrozen',
|
||||
'Object.isSealed',
|
||||
'Object.isExtensible',
|
||||
'Object.getOwnPropertyDescriptor',
|
||||
'Object.getPrototypeOf',
|
||||
'Object.keys',
|
||||
'Object.entries',
|
||||
'Object.getOwnPropertyNames',
|
||||
'Function.name',
|
||||
'Function.hasInstance',
|
||||
'Array.from',
|
||||
'Array.arrayOf',
|
||||
'Array.copyWithin',
|
||||
'Array.fill',
|
||||
'Array.find',
|
||||
'Array.findIndex',
|
||||
'Array.iterator',
|
||||
'String.fromCodePoint',
|
||||
'String.raw',
|
||||
'String.iterator',
|
||||
'String.codePointAt',
|
||||
'String.endsWith',
|
||||
'String.includes',
|
||||
'String.repeat',
|
||||
'String.startsWith',
|
||||
'String.trim',
|
||||
'String.anchor',
|
||||
'String.big',
|
||||
'String.blink',
|
||||
'String.bold',
|
||||
'String.fixed',
|
||||
'String.fontcolor',
|
||||
'String.fontsize',
|
||||
'String.italics',
|
||||
'String.link',
|
||||
'String.small',
|
||||
'String.strike',
|
||||
'String.sub',
|
||||
'String.sup',
|
||||
'RegExp',
|
||||
'Number',
|
||||
'Math',
|
||||
'Date',
|
||||
'async',
|
||||
'Symbol',
|
||||
'Map',
|
||||
'Set',
|
||||
'WeakMap',
|
||||
'WeakSet',
|
||||
'ArrayBuffer',
|
||||
'DataView',
|
||||
'Int8Array',
|
||||
'Uint8Array',
|
||||
'Uint8ClampedArray',
|
||||
'Int16Array',
|
||||
'Uint16Array',
|
||||
'Int32Array',
|
||||
'Uint32Array',
|
||||
'Float32Array',
|
||||
'Float64Array',
|
||||
'Reflect',
|
||||
// Temporary while eslint-compat-plugin is buggy
|
||||
'document.querySelector'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'./src/**/*.ts',
|
||||
'./src/**/*.tsx'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:eslint-comments/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:jsx-a11y/recommended'
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
4
.github/SUPPORT.md
vendored
4
.github/SUPPORT.md
vendored
@@ -13,8 +13,8 @@ question in these venues:
|
||||
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](ircs://irc.libera.chat:6697/#jellyfin)
|
||||
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](ircs://irc.libera.chat:6697/#jellyfin-troubleshooting)
|
||||
* #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.
|
||||
|
||||
7
.github/dependabot.yaml
vendored
Normal file
7
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
51
.github/renovate.json
vendored
51
.github/renovate.json
vendored
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["npm"],
|
||||
"addLabels": ["javascript"]
|
||||
},
|
||||
{
|
||||
"description": "Adds label to dev dependency updates",
|
||||
"matchDepTypes": ["devDependencies"],
|
||||
"addLabels": ["dev-deps"]
|
||||
},
|
||||
{
|
||||
"description": "Collects and groups dev dependency updates",
|
||||
"matchDepTypes": ["devDependencies"],
|
||||
"groupName": "development dependencies",
|
||||
"groupSlug": "dev-deps"
|
||||
},
|
||||
{
|
||||
"description": "Collects and groups npm dependency updates",
|
||||
"matchDepTypes": ["dependencies"],
|
||||
"groupName": "dependencies",
|
||||
"groupSlug": "deps"
|
||||
},
|
||||
{
|
||||
"description": "Collects and groups GitHub Action dependency updates",
|
||||
"matchDepTypes": ["action"],
|
||||
"addLabels": ["github_actions"],
|
||||
"groupName": "CI dependencies",
|
||||
"groupSlug": "ci-deps"
|
||||
},
|
||||
{
|
||||
"description": "Disables HLS.js major updates",
|
||||
"matchPackageNames": ["hls.js"],
|
||||
"matchUpdateTypes": "major",
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"vulnerabilityAlerts": {
|
||||
"addLabels": ["security"]
|
||||
},
|
||||
"dependencyDashboard": false,
|
||||
"ignoreDeps": ["npm", "node"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": false
|
||||
},
|
||||
"enabledManagers": ["npm", "github-actions"],
|
||||
"labels": ["dependencies"],
|
||||
"prHourlyLimit": 2,
|
||||
"rebaseWhen": "conflicted",
|
||||
"rangeStrategy": "pin"
|
||||
}
|
||||
20
.github/stale.yml
vendored
Normal file
20
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 14
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- regression
|
||||
- future
|
||||
- feature
|
||||
- enhancement
|
||||
- confirmed
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
|
||||
If this issue is safe to close now please do so.
|
||||
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
20
.github/workflows/automation.yml
vendored
20
.github/workflows/automation.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 'Automation'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types:
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
name: 'Merge conflict labeling'
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
|
||||
steps:
|
||||
- uses: eps1lon/actions-label-merge-conflict@v2.0.1
|
||||
with:
|
||||
dirtyLabel: 'merge conflict'
|
||||
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
language: [ 'javascript' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
||||
28
.github/workflows/commands.yml
vendored
28
.github/workflows/commands.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Commands
|
||||
on:
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: '+1'
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
90
.github/workflows/lint.yml
vendored
90
.github/workflows/lint.yml
vendored
@@ -2,9 +2,9 @@ name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, release* ]
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master, release* ]
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
run-eslint:
|
||||
@@ -13,34 +13,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v3.0.0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
||||
- name: Get npm cache directory path
|
||||
id: npm-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v3.0.0
|
||||
id: npm-cache
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci --no-audit
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run eslint
|
||||
run: npm run lint
|
||||
run: yarn lint
|
||||
|
||||
run-stylelint-css:
|
||||
name: Run stylelint (css)
|
||||
@@ -48,37 +40,29 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v3.0.0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
||||
- name: Get npm cache directory path
|
||||
id: npm-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v3.0.0
|
||||
id: npm-cache
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
|
||||
- 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: npm ci --no-audit
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run stylelint
|
||||
run: npm run stylelint:css
|
||||
run: yarn stylelint:css
|
||||
|
||||
run-stylelint-scss:
|
||||
name: Run stylelint (scss)
|
||||
@@ -86,34 +70,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v3.0.0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
||||
- name: Get npm cache directory path
|
||||
id: npm-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v3.0.0
|
||||
id: npm-cache
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
|
||||
- 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: npm ci --no-audit
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run stylelint
|
||||
run: npm run stylelint:scss
|
||||
run: yarn stylelint:scss
|
||||
|
||||
15
.github/workflows/merge-conflicts.yml
vendored
Normal file
15
.github/workflows/merge-conflicts.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
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 }}
|
||||
27
.github/workflows/repo-stale.yaml
vendored
27
.github/workflows/repo-stale.yaml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Issue Stale Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@v5.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
days-before-stale: 120
|
||||
days-before-pr-stale: -1
|
||||
days-before-close: 21
|
||||
days-before-pr-close: -1
|
||||
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
||||
stale-issue-label: stale
|
||||
stale-issue-message: |-
|
||||
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
||||
|
||||
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
||||
|
||||
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -12,10 +12,3 @@ config.json
|
||||
|
||||
# log
|
||||
yarn-error.log
|
||||
|
||||
# vim
|
||||
*.sw?
|
||||
|
||||
# build artifacts
|
||||
fedora/jellyfin-web-*.src.rpm
|
||||
fedora/jellyfin-web-*.tar.gz
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"declaration-colon-space-after": "always-single-line",
|
||||
"declaration-colon-space-before": "never",
|
||||
"font-family-no-duplicate-names": true,
|
||||
"function-calc-no-invalid": true,
|
||||
"function-calc-no-unspaced-operator": true,
|
||||
"function-comma-newline-after": "always-multi-line",
|
||||
"function-comma-space-after": "always-single-line",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"extends": [ "./.stylelintrc.json" ],
|
||||
"customSyntax": "postcss-scss",
|
||||
"plugins": [ "stylelint-scss" ],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
|
||||
@@ -45,14 +45,6 @@
|
||||
- [Camc314](https://github.com/camc314)
|
||||
- [danieladov](https://github.com/danieladov)
|
||||
- [Stephane Senart](https://github.com/ssenart)
|
||||
- [imchasingshadows](https://github.com/imchasingshadows)
|
||||
- [Ömer Erdinç Yağmurlu](https://github.com/omeryagmurlu)
|
||||
- [Keegan Dahm](https://github.com/keegandahm)
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||
- [taku0](https://github.com/taku0)
|
||||
- [Peter Spenler](https://github.com/peterspenler)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
@@ -116,6 +108,3 @@
|
||||
- [tikuf](https://github.com/tikuf/)
|
||||
- [Tim Hobbs](https://github.com/timhobbs)
|
||||
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
|
||||
- [jomp16](https://github.com/jomp16)
|
||||
- [Leon de Klerk](https://github.com/leondeklerk)
|
||||
- [CrispyBaguette](https://github.com/CrispyBaguette)
|
||||
12
README.md
12
README.md
@@ -23,6 +23,9 @@
|
||||
<a href="https://features.jellyfin.org">
|
||||
<img alt="Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/>
|
||||
</a>
|
||||
<a href="https://forum.jellyfin.org">
|
||||
<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/>
|
||||
</a>
|
||||
<a href="https://matrix.to/#/+jellyfin:matrix.org">
|
||||
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
|
||||
</a>
|
||||
@@ -42,7 +45,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||
### Dependencies
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
- npm (included in Node.js)
|
||||
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
|
||||
- Gulp-cli
|
||||
|
||||
### Getting Started
|
||||
|
||||
@@ -56,17 +60,17 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||
2. Install build dependencies in the project directory.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. Run the web client with webpack for local development.
|
||||
|
||||
```sh
|
||||
npm start
|
||||
yarn serve
|
||||
```
|
||||
|
||||
4. Build the client with sourcemaps available.
|
||||
|
||||
```sh
|
||||
npm run build:development
|
||||
yarn build:development
|
||||
```
|
||||
|
||||
@@ -11,14 +11,6 @@ module.exports = {
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 3
|
||||
}
|
||||
],
|
||||
'@babel/preset-react',
|
||||
[
|
||||
'@babel/preset-typescript',
|
||||
{
|
||||
isTSX: true,
|
||||
allExtensions: true
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins: [
|
||||
|
||||
2
build.sh
2
build.sh
@@ -39,7 +39,7 @@ do_build_native() {
|
||||
}
|
||||
|
||||
do_build_docker() {
|
||||
if ! [ $(uname -m) = "x86_64" ]; then
|
||||
if ! dpkg --print-architecture | grep -q 'amd64'; then
|
||||
echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# We just wrap `build` so this is really it
|
||||
name: "jellyfin-web"
|
||||
version: "10.8.13"
|
||||
version: "10.7.7"
|
||||
packages:
|
||||
- debian.all
|
||||
- fedora.all
|
||||
|
||||
37
bump_version
37
bump_version
@@ -18,39 +18,38 @@ if [[ -z $1 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shared_version_file="src/components/apphost.js"
|
||||
build_file="./build.yaml"
|
||||
package_file="./package*.json"
|
||||
|
||||
new_version="$1"
|
||||
|
||||
old_version="$(
|
||||
grep "version:" ${build_file} \
|
||||
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
|
||||
)"
|
||||
echo "Old version: ${old_version}"
|
||||
# Parse the version from shared version file
|
||||
old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )"
|
||||
echo "Old version in appHost is: $old_version"
|
||||
|
||||
# Bump the NPM version
|
||||
# Set the shared version to the specified new_version
|
||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
|
||||
npm --no-git-tag-version --allow-same-version version v${new_version_sed}
|
||||
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
|
||||
|
||||
old_version="$( grep "version:" ${build_file} | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )"
|
||||
echo "Old version in ${build_file}: ${old_version}"
|
||||
|
||||
# Set the build.yaml version to the specified new_version
|
||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${build_file}
|
||||
|
||||
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
|
||||
|
||||
if [[ ${new_version} == *"-"* ]]; then
|
||||
new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )"
|
||||
new_version_deb_sup=""
|
||||
new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
|
||||
else
|
||||
new_version_pkg="${new_version}"
|
||||
new_version_deb_sup="-1"
|
||||
new_version_deb="${new_version}-1"
|
||||
fi
|
||||
|
||||
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
|
||||
debian_changelog_file="debian/changelog"
|
||||
debian_changelog_temp="$( mktemp )"
|
||||
# Create new temp file with our changelog
|
||||
echo -e "jellyfin-web (${new_version_pkg}${new_version_deb_sup}) unstable; urgency=medium
|
||||
echo -e "jellyfin-web (${new_version_deb}) unstable; urgency=medium
|
||||
|
||||
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}
|
||||
|
||||
@@ -71,7 +70,7 @@ pushd ${fedora_spec_temp_dir}
|
||||
# Split out the stuff before and after changelog
|
||||
csplit jellyfin-web.spec "/^%changelog/" # produces xx00 xx01
|
||||
# Update the version in xx00
|
||||
sed -i "s/${old_version_sed}/${new_version_pkg}/g" xx00
|
||||
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
|
||||
# Remove the header from xx01
|
||||
sed -i '/^%changelog/d' xx01
|
||||
# Create new temp file with our changelog
|
||||
@@ -85,8 +84,8 @@ popd
|
||||
# Move into place
|
||||
mv ${fedora_spec_temp} ${fedora_spec_file}
|
||||
# Clean up
|
||||
rm -rf ${fedora_spec_temp_dir}
|
||||
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
|
||||
|
||||
# Stage the changed files for commit
|
||||
git add .
|
||||
git status -v
|
||||
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
|
||||
git status
|
||||
|
||||
92
debian/changelog
vendored
92
debian/changelog
vendored
@@ -1,95 +1,45 @@
|
||||
jellyfin-web (10.8.13-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.7-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
|
||||
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 28 Nov 2023 22:21:29 -0500
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
|
||||
|
||||
jellyfin-web (10.8.12-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.6-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
|
||||
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 04 Nov 2023 14:42:41 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
|
||||
|
||||
jellyfin-web (10.8.11-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.5-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
|
||||
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 23 Sep 2023 21:41:40 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
|
||||
|
||||
jellyfin-web (10.8.10-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
|
||||
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 23 Apr 2023 11:01:33 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
|
||||
|
||||
jellyfin-web (10.8.9-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
|
||||
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 22 Jan 2023 14:09:13 -0500
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
|
||||
|
||||
jellyfin-web (10.8.8-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
|
||||
* 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> Tue, 29 Nov 2022 13:42:54 -0500
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
|
||||
|
||||
jellyfin-web (10.8.7-1) unstable; urgency=medium
|
||||
jellyfin-web (10.7.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
|
||||
* 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> Mon, 31 Oct 2022 23:06:34 -0400
|
||||
|
||||
jellyfin-web (10.8.6-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 28 Oct 2022 22:44:15 -0400
|
||||
|
||||
jellyfin-web (10.8.5-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 24 Sep 2022 22:02:26 -0400
|
||||
|
||||
jellyfin-web (10.8.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 13 Aug 2022 21:52:04 -0400
|
||||
|
||||
jellyfin-web (10.8.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 20:22:00 -0400
|
||||
|
||||
jellyfin-web (10.8.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 14:27:56 -0400
|
||||
|
||||
jellyfin-web (10.8.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 26 Jun 2022 20:59:39 -0400
|
||||
|
||||
jellyfin-web (10.8.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 10 Jun 2022 22:16:40 -0400
|
||||
-- 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, 27 Jul 2020 19:13:31 -0400
|
||||
|
||||
jellyfin-web (10.6.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400
|
||||
|
||||
2
debian/rules
vendored
2
debian/rules
vendored
@@ -11,7 +11,7 @@ override_dh_auto_test:
|
||||
override_dh_clistrip:
|
||||
|
||||
override_dh_auto_build:
|
||||
npm ci --no-audit --unsafe-perm
|
||||
npx yarn install
|
||||
mv $(CURDIR)/dist $(CURDIR)/web
|
||||
|
||||
override_dh_auto_clean:
|
||||
|
||||
@@ -12,9 +12,12 @@ ENV IS_DOCKER=YES
|
||||
# Prepare CentOS environment
|
||||
RUN yum update -y \
|
||||
&& yum install -y epel-release \
|
||||
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core autoconf automake glibc-devel gcc-c++ make \
|
||||
&& curl -fsSL https://rpm.nodesource.com/setup_12.x | bash - \
|
||||
&& yum install -y nodejs
|
||||
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
|
||||
|
||||
# Install recent NodeJS and Yarn
|
||||
RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
|
||||
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
|
||||
&& yum install -y yarn
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh
|
||||
|
||||
@@ -12,10 +12,10 @@ ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y debhelper mmv git curl \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_12.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
&& apt-get install -y debhelper mmv npm git
|
||||
|
||||
# Prepare Yarn
|
||||
RUN npm install -g yarn
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh
|
||||
|
||||
@@ -8,4 +8,4 @@ RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool ma
|
||||
WORKDIR ${SOURCE_DIR}
|
||||
COPY . .
|
||||
|
||||
RUN npm ci --no-audit --unsafe-perm && mv dist ${ARTIFACT_DIR}
|
||||
RUN yarn install && mv dist ${ARTIFACT_DIR}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:36
|
||||
FROM fedora:33
|
||||
|
||||
# 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 autoconf automake glibc-devel make
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs nodejs-yarn autoconf automake glibc-devel
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
|
||||
|
||||
@@ -11,9 +11,10 @@ ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y mmv curl git \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_12.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
&& apt-get install -y mmv npm git
|
||||
|
||||
# Prepare Yarn
|
||||
RUN npm install -g yarn
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
|
||||
|
||||
@@ -6,7 +6,7 @@ set -o xtrace
|
||||
# move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
cp -a package-lock.json /tmp/package-lock.json
|
||||
cp -a yarn.lock /tmp/yarn.lock
|
||||
|
||||
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
@@ -36,6 +36,6 @@ if [[ ${IS_DOCKER} == YES ]]; then
|
||||
fi
|
||||
|
||||
rm -f fedora/jellyfin*.tar.gz
|
||||
cp -a /tmp/package-lock.json package-lock.json
|
||||
cp -a /tmp/yarn.lock yarn.lock
|
||||
|
||||
popd
|
||||
|
||||
@@ -6,7 +6,7 @@ set -o xtrace
|
||||
# move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
cp -a package-lock.json /tmp/package-lock.json
|
||||
cp -a yarn.lock /tmp/yarn.lock
|
||||
|
||||
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
@@ -30,7 +30,7 @@ dpkg-buildpackage -us -uc --pre-clean --post-clean
|
||||
mkdir -p ${ARTIFACT_DIR}
|
||||
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}
|
||||
|
||||
cp -a /tmp/package-lock.json package-lock.json
|
||||
cp -a /tmp/yarn.lock yarn.lock
|
||||
|
||||
if [[ ${IS_DOCKER} == YES ]]; then
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -6,7 +6,7 @@ set -o xtrace
|
||||
# move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
cp -a package-lock.json /tmp/package-lock.json
|
||||
cp -a yarn.lock /tmp/yarn.lock
|
||||
|
||||
# modify changelog to unstable configuration if IS_UNSTABLE
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
@@ -36,6 +36,6 @@ if [[ ${IS_DOCKER} == YES ]]; then
|
||||
fi
|
||||
|
||||
rm -f fedora/jellyfin*.tar.gz
|
||||
cp -a /tmp/package-lock.json package-lock.json
|
||||
cp -a /tmp/yarn.lock yarn.lock
|
||||
|
||||
popd
|
||||
|
||||
@@ -14,7 +14,7 @@ else
|
||||
fi
|
||||
|
||||
# build archives
|
||||
npm ci --no-audit --unsafe-perm
|
||||
npx yarn install
|
||||
mv dist jellyfin-web_${version}
|
||||
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
|
||||
rm -rf dist
|
||||
|
||||
@@ -1,48 +1,21 @@
|
||||
DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||
# install git and npm
|
||||
$(info $(shell set -x; if [ "$$(id -u)" = "0" ]; then echo "Installing git"; dnf -y install git npm; fi))
|
||||
NAME := jellyfin-web
|
||||
VERSION := $(shell set -x; sed -ne '/^Version:/s/.* *//p' $(DIR)/$(NAME).spec)
|
||||
RELEASE := $(shell set -x; sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR)/$(NAME).spec)
|
||||
SRPM := jellyfin-web-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
|
||||
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
|
||||
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin-web.spec)
|
||||
|
||||
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
|
||||
|
||||
fed_ver := $(shell rpm -E %fedora)
|
||||
# fallback when not running on Fedora
|
||||
fed_ver ?= 36
|
||||
TARGET ?= fedora-$(fed_ver)-x86_64
|
||||
|
||||
outdir ?= $(PWD)/$(DIR)/
|
||||
|
||||
srpm: $(DIR)/$(SRPM)
|
||||
tarball: $(DIR)/$(TARBALL)
|
||||
|
||||
$(DIR)/$(TARBALL):
|
||||
cd $(DIR)/; \
|
||||
SOURCE_DIR=.. \
|
||||
WORKDIR="$${PWD}"; \
|
||||
version=$(VERSION); \
|
||||
tar \
|
||||
--transform "s,^\.,$(NAME)-$(subst -,~,$(VERSION))," \
|
||||
--exclude='.git*' \
|
||||
--exclude='**/.git' \
|
||||
--exclude='**/.hg' \
|
||||
--exclude=deployment \
|
||||
--exclude='*.deb' \
|
||||
--exclude='*.rpm' \
|
||||
--exclude=$(notdir $@) \
|
||||
-czf $(notdir $@) \
|
||||
srpm:
|
||||
cd fedora/; \
|
||||
SOURCE_DIR=.. \
|
||||
WORKDIR="$${PWD}"; \
|
||||
tar \
|
||||
--transform "s,^\.,jellyfin-web-$(VERSION)," \
|
||||
--exclude='.git*' \
|
||||
--exclude='**/.git' \
|
||||
--exclude='**/.hg' \
|
||||
--exclude='deployment' \
|
||||
--exclude='*.deb' \
|
||||
--exclude='*.rpm' \
|
||||
--exclude='jellyfin-web-$(VERSION).tar.gz' \
|
||||
-czf "jellyfin-web-$(VERSION).tar.gz" \
|
||||
-C $${SOURCE_DIR} ./
|
||||
|
||||
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin-web.spec
|
||||
cd $(DIR)/; \
|
||||
rpmbuild -bs $(NAME).spec \
|
||||
--define "_sourcedir $$PWD/" \
|
||||
cd fedora/; \
|
||||
rpmbuild -bs jellyfin-web.spec \
|
||||
--define "_sourcedir $$PWD/" \
|
||||
--define "_srcrpmdir $(outdir)"
|
||||
|
||||
rpms: $(DIR)/$(SRPM)
|
||||
mock $(addprefix --addrepo=, $($(TARGET)_repos)) \
|
||||
--enable-network \
|
||||
-r $(TARGET) $<
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: jellyfin-web
|
||||
Version: 10.8.13
|
||||
Version: 10.7.7
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media System web client
|
||||
License: GPLv2
|
||||
License: GPLv3
|
||||
URL: https://jellyfin.org
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
||||
Source0: jellyfin-web-%{version}.tar.gz
|
||||
|
||||
BuildArch: noarch
|
||||
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
|
||||
BuildRequires: nodejs
|
||||
%if 0%{?centos}
|
||||
BuildRequires: yarn
|
||||
%else
|
||||
BuildRequires: git
|
||||
BuildRequires: npm
|
||||
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
|
||||
|
||||
%description
|
||||
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
|
||||
@@ -24,53 +32,37 @@ Jellyfin is a free software media system that puts you in control of managing an
|
||||
%prep
|
||||
%autosetup -n jellyfin-web-%{version} -b 0
|
||||
|
||||
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
|
||||
# Required for CentOS build
|
||||
chown root:root -R .
|
||||
%endif
|
||||
|
||||
|
||||
%build
|
||||
npm ci --no-audit --unsafe-perm
|
||||
|
||||
|
||||
%install
|
||||
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin/jellyfin-web
|
||||
%{__cp} -r dist/* %{buildroot}%{_libdir}/jellyfin/jellyfin-web
|
||||
|
||||
yarn install
|
||||
%{__mkdir} -p %{buildroot}%{_datadir}
|
||||
mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%files
|
||||
%defattr(644,root,root,755)
|
||||
%{_libdir}/jellyfin/jellyfin-web
|
||||
%license LICENSE
|
||||
|
||||
%{_datadir}/jellyfin-web
|
||||
%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%changelog
|
||||
* Tue Nov 28 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
|
||||
* Sat Nov 04 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
|
||||
* Sat Sep 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
|
||||
* Sun Apr 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
|
||||
* Sun Jan 22 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
|
||||
* Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
|
||||
* Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
|
||||
* Fri Oct 28 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
|
||||
* Sat Sep 24 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
|
||||
* Sat Aug 13 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
|
||||
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
|
||||
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
|
||||
* Sun Jun 26 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
|
||||
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0
|
||||
* 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
|
||||
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- Forthcoming stable release
|
||||
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- Forthcoming stable release
|
||||
|
||||
15705
package-lock.json
generated
15705
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
178
package.json
178
package.json
@@ -1,109 +1,86 @@
|
||||
{
|
||||
"name": "jellyfin-web",
|
||||
"version": "10.8.13",
|
||||
"version": "0.0.0",
|
||||
"description": "Web interface for Jellyfin",
|
||||
"repository": "https://github.com/jellyfin/jellyfin-web",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.17.7",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"@babel/eslint-plugin": "7.17.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-proposal-private-methods": "7.16.11",
|
||||
"@babel/plugin-transform-modules-umd": "7.16.7",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/preset-typescript": "7.16.7",
|
||||
"@thornbill/jellyfin-sdk": "0.4.1",
|
||||
"@types/escape-html": "1.0.1",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/react": "17.0.40",
|
||||
"@types/react-dom": "17.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "5.15.0",
|
||||
"@typescript-eslint/parser": "5.15.0",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
"autoprefixer": "10.4.4",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-dynamic-import-polyfill": "1.0.0",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"confusing-browser-globals": "1.0.11",
|
||||
"copy-webpack-plugin": "10.2.4",
|
||||
"css-loader": "6.7.1",
|
||||
"cssnano": "5.1.4",
|
||||
"eslint": "8.11.0",
|
||||
"eslint-plugin-compat": "4.0.2",
|
||||
"eslint-plugin-eslint-comments": "3.2.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||
"eslint-plugin-promise": "6.0.0",
|
||||
"eslint-plugin-react": "7.29.4",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"expose-loader": "3.1.0",
|
||||
"html-loader": "3.1.0",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"postcss": "8.4.12",
|
||||
"postcss-loader": "6.2.1",
|
||||
"postcss-preset-env": "7.4.2",
|
||||
"postcss-scss": "4.0.3",
|
||||
"sass": "1.49.9",
|
||||
"sass-loader": "12.6.0",
|
||||
"source-map-loader": "3.0.1",
|
||||
"style-loader": "3.3.1",
|
||||
"stylelint": "14.6.0",
|
||||
"stylelint-config-rational-order": "0.1.2",
|
||||
"stylelint-no-browser-hacks": "1.2.1",
|
||||
"stylelint-order": "5.0.0",
|
||||
"stylelint-scss": "4.2.0",
|
||||
"ts-loader": "9.2.8",
|
||||
"typescript": "4.6.2",
|
||||
"webpack": "5.70.0",
|
||||
"webpack-cli": "4.9.2",
|
||||
"webpack-dev-server": "4.7.4",
|
||||
"webpack-merge": "5.8.0",
|
||||
"workbox-webpack-plugin": "6.5.1",
|
||||
"worker-loader": "3.0.8"
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/eslint-parser": "^7.12.1",
|
||||
"@babel/eslint-plugin": "^7.12.1",
|
||||
"@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",
|
||||
"cssnano": "^4.1.10",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-plugin-compat": "^3.5.1",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"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",
|
||||
"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",
|
||||
"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-merge": "^4.2.2",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"worker-plugin": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/noto-sans": "4.5.1",
|
||||
"@fontsource/noto-sans-hk": "4.5.2",
|
||||
"@fontsource/noto-sans-jp": "4.5.2",
|
||||
"@fontsource/noto-sans-kr": "4.5.2",
|
||||
"@fontsource/noto-sans-sc": "4.5.2",
|
||||
"@fontsource/noto-sans-tc": "4.5.2",
|
||||
"@jellyfin/libass-wasm": "4.1.1",
|
||||
"blurhash": "1.1.4",
|
||||
"blurhash": "^1.1.3",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"classnames": "2.3.1",
|
||||
"core-js": "3.20.2",
|
||||
"date-fns": "2.28.0",
|
||||
"dompurify": "2.3.4",
|
||||
"epubjs": "0.3.93",
|
||||
"escape-html": "1.0.3",
|
||||
"fast-text-encoding": "1.0.3",
|
||||
"flv.js": "1.6.2",
|
||||
"headroom.js": "0.12.0",
|
||||
"hls.js": "0.14.17",
|
||||
"intersection-observer": "0.12.0",
|
||||
"jellyfin-apiclient": "1.10.0",
|
||||
"jquery": "3.6.0",
|
||||
"jstree": "3.3.12",
|
||||
"libarchive.js": "1.3.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"marked": "4.0.10",
|
||||
"material-design-icons-iconfont": "6.1.1",
|
||||
"native-promise-only": "0.8.1",
|
||||
"page": "1.11.6",
|
||||
"pdfjs-dist": "2.12.313",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"screenfull": "6.0.0",
|
||||
"sortablejs": "1.14.0",
|
||||
"swiper": "6.8.4",
|
||||
"webcomponents.js": "0.7.24",
|
||||
"whatwg-fetch": "3.6.2",
|
||||
"workbox-core": "6.2.4",
|
||||
"workbox-precaching": "6.2.4"
|
||||
"core-js": "^3.8.0",
|
||||
"date-fns": "^2.16.1",
|
||||
"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",
|
||||
"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",
|
||||
"native-promise-only": "^0.8.0-a",
|
||||
"page": "^1.11.6",
|
||||
"pdfjs-dist": "2.5.207",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^5.0.2",
|
||||
"sortablejs": "^1.12.0",
|
||||
"swiper": "^6.3.5",
|
||||
"webcomponents.js": "^0.7.24",
|
||||
"whatwg-fetch": "^3.5.0",
|
||||
"workbox-core": "^5.1.4",
|
||||
"workbox-precaching": "^5.1.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Firefox versions",
|
||||
@@ -122,17 +99,14 @@
|
||||
"Firefox ESR"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "npm run serve",
|
||||
"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": "npm run stylelint:css && npm run stylelint:scss",
|
||||
"stylelint": "yarn stylelint:css && yarn stylelint:scss",
|
||||
"stylelint:css": "stylelint \"src/**/*.css\"",
|
||||
"stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\""
|
||||
},
|
||||
"engines": {
|
||||
"yarn": "YARN NO LONGER USED - use npm instead."
|
||||
}
|
||||
}
|
||||
|
||||
352
src/apiclient.d.ts
vendored
352
src/apiclient.d.ts
vendored
@@ -1,352 +0,0 @@
|
||||
// TODO: Move to jellyfin-apiclient
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
declare module 'jellyfin-apiclient' {
|
||||
import type {
|
||||
AllThemeMediaResult,
|
||||
AuthenticationResult,
|
||||
BaseItemDto,
|
||||
BaseItemDtoQueryResult,
|
||||
BufferRequestDto,
|
||||
ClientCapabilities,
|
||||
CountryInfo,
|
||||
CultureDto,
|
||||
DeviceOptions,
|
||||
DisplayPreferencesDto,
|
||||
EndPointInfo,
|
||||
FileSystemEntryInfo,
|
||||
GeneralCommand,
|
||||
GroupInfoDto,
|
||||
GuideInfo,
|
||||
IgnoreWaitRequestDto,
|
||||
ImageInfo,
|
||||
ImageProviderInfo,
|
||||
ImageType,
|
||||
ItemCounts,
|
||||
LiveTvInfo,
|
||||
MovePlaylistItemRequestDto,
|
||||
NewGroupRequestDto,
|
||||
NextItemRequestDto,
|
||||
NotificationResultDto,
|
||||
NotificationsSummaryDto,
|
||||
ParentalRating,
|
||||
PingRequestDto,
|
||||
PlaybackInfoResponse,
|
||||
PlaybackProgressInfo,
|
||||
PlaybackStartInfo,
|
||||
PlaybackStopInfo,
|
||||
PlayCommand,
|
||||
PlaystateCommand,
|
||||
PluginInfo,
|
||||
PluginSecurityInfo,
|
||||
PreviousItemRequestDto,
|
||||
QueryFiltersLegacy,
|
||||
QueueRequestDto,
|
||||
QuickConnectResult,
|
||||
QuickConnectState,
|
||||
ReadyRequestDto,
|
||||
RecommendationDto,
|
||||
RemoteImageResult,
|
||||
RemoveFromPlaylistRequestDto,
|
||||
SearchHintResult,
|
||||
SeekRequestDto,
|
||||
SeriesTimerInfoDto,
|
||||
SeriesTimerInfoDtoQueryResult,
|
||||
ServerConfiguration,
|
||||
SessionInfo,
|
||||
SetPlaylistItemRequestDto,
|
||||
SetRepeatModeRequestDto,
|
||||
SetShuffleModeRequestDto,
|
||||
SystemInfo,
|
||||
TaskInfo,
|
||||
TaskTriggerInfo,
|
||||
TimerInfoDto,
|
||||
TimerInfoDtoQueryResult,
|
||||
UserConfiguration,
|
||||
UserDto,
|
||||
UserItemDataDto,
|
||||
UserPolicy,
|
||||
UtcTimeResponse,
|
||||
VirtualFolderInfo
|
||||
} from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
|
||||
class ApiClient {
|
||||
constructor(serverAddress: string, appName: string, appVersion: string, deviceName: string, deviceId: string);
|
||||
|
||||
accessToken(): string;
|
||||
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
|
||||
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
|
||||
appName(): string;
|
||||
appVersion(): string;
|
||||
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;
|
||||
cancelLiveTvSeriesTimer(id: string): Promise<void>;
|
||||
cancelLiveTvTimer(id: string): Promise<void>;
|
||||
cancelSyncItems(itemIds: string[], targetId?: string): Promise<void>;
|
||||
clearAuthenticationInfo(): void;
|
||||
clearUserItemRating(userId: string, itemId: string): Promise<UserItemDataDto>;
|
||||
closeWebSocket(): void;
|
||||
createLiveTvSeriesTimer(item: string): Promise<void>;
|
||||
createLiveTvTimer(item: string): Promise<void>;
|
||||
createPackageReview(review: any): Promise<any>;
|
||||
createSyncPlayGroup(options?: NewGroupRequestDto): Promise<void>;
|
||||
createUser(user: UserDto): Promise<UserDto>;
|
||||
deleteDevice(deviceId: string): Promise<void>;
|
||||
deleteItemImage(itemId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
|
||||
deleteItem(itemId: string): Promise<void>;
|
||||
deleteLiveTvRecording(id: string): Promise<void>;
|
||||
deleteUserImage(userId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
|
||||
deleteUser(userId: string): Promise<void>;
|
||||
detectBitrate(force: boolean): Promise<number>;
|
||||
deviceId(): string;
|
||||
deviceName(): string;
|
||||
disablePlugin(id: string, version: string): Promise<void>;
|
||||
downloadRemoteImage(options: any): Promise<void>;
|
||||
enablePlugin(id: string, version: string): Promise<void>;
|
||||
encodeName(name: string): string;
|
||||
ensureWebSocket(): void;
|
||||
fetch(request: any, includeAuthorization?: boolean): Promise<any>;
|
||||
fetchWithFailover(request: any, enableReconnection?: boolean): Promise<any>;
|
||||
getAdditionalVideoParts(userId?: string, itemId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getAlbumArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getAncestorItems(itemId: string, userId?: string): Promise<BaseItemDto[]>;
|
||||
getArtist(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getAvailablePlugins(options?: any): Promise<PluginInfo[]>;
|
||||
getAvailableRemoteImages(options: any): Promise<RemoteImageResult>;
|
||||
getContentUploadHistory(): Promise<any>;
|
||||
getCountries(): Promise<CountryInfo[]>;
|
||||
getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getCultures(): Promise<CultureDto[]>;
|
||||
getCurrentUserId(): string;
|
||||
getDateParamValue(date: Date): string;
|
||||
getDefaultImageQuality(imageType: ImageType): number;
|
||||
getDevicesOptions(): Promise<DeviceOptions>;
|
||||
getDirectoryContents(path: string, options?: any): Promise<FileSystemEntryInfo[]>;
|
||||
getDisplayPreferences(id: string, userId: string, app: string): Promise<DisplayPreferencesDto>;
|
||||
getDownloadSpeed(byteSize: number): Promise<number>;
|
||||
getDrives(): Promise<FileSystemEntryInfo[]>;
|
||||
getEndpointInfo(): Promise<EndPointInfo>;
|
||||
getEpisodes(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getFilters(options?: any): Promise<QueryFiltersLegacy>;
|
||||
getGenre(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getImageUrl(itemId: string, options?: any): string;
|
||||
getInstalledPlugins(): Promise<PluginInfo[]>;
|
||||
getInstantMixFromItem(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getIntros(itemId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getItemCounts(userId?: string): Promise<ItemCounts>;
|
||||
getItemDownloadUrl(itemId: string): string;
|
||||
getItemImageInfos(itemId: string): Promise<ImageInfo[]>;
|
||||
getItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getItem(userId: string, itemId: string): Promise<BaseItemDto>;
|
||||
getJSON(url: string, includeAuthorization?: boolean): Promise<any>;
|
||||
getLatestItems(options?: any): Promise<BaseItemDto[]>;
|
||||
getLiveStreamMediaInfo(liveStreamId: string): Promise<any>;
|
||||
getLiveTvChannel(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvChannels(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvGuideInfo(userId: string): Promise<GuideInfo>;
|
||||
getLiveTvInfo(userId: string): Promise<LiveTvInfo>;
|
||||
getLiveTvProgram(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecommendedPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecordingGroup(id: string): Promise<BaseItemDto>;
|
||||
getLiveTvRecordingGroups(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecording(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvRecordingSeries(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecordings(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvSeriesTimer(id: string): Promise<SeriesTimerInfoDto>;
|
||||
getLiveTvSeriesTimers(options?: any): Promise<SeriesTimerInfoDtoQueryResult>;
|
||||
getLiveTvTimer(id: string): Promise<TimerInfoDto>;
|
||||
getLiveTvTimers(options?: any): Promise<TimerInfoDtoQueryResult>;
|
||||
getLocalTrailers(userId: string, itemId: string): Promise<BaseItemDto[]>;
|
||||
getMovieRecommendations(options?: any): Promise<RecommendationDto[]>;
|
||||
getMusicGenre(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getMusicGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getNamedConfiguration(name: string): Promise<any>;
|
||||
getNetworkDevices(): Promise<any>;
|
||||
getNetworkShares(path: string): Promise<FileSystemEntryInfo[]>;
|
||||
getNewLiveTvTimerDefaults(options?: any): Promise<SeriesTimerInfoDto>;
|
||||
getNextUpEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getNotificationSummary(userId: string): Promise<NotificationsSummaryDto>;
|
||||
getNotifications(userId: string, options?: any): Promise<NotificationResultDto>;
|
||||
getPackageInfo(name: string, guid: string): Promise<PackageInfo>;
|
||||
getPackageReviews(packageId: string, minRating?: string, maxRating?: string, limit?: string): Promise<any>;
|
||||
getParentalRatings(): Promise<ParentalRating[]>;
|
||||
getParentPath(path: string): Promise<string>;
|
||||
getPeople(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getPerson(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getPhysicalPaths(): Promise<string[]>;
|
||||
getPlaybackInfo(itemId: string, options: any, deviceProfile: any): Promise<PlaybackInfoResponse>;
|
||||
getPluginConfiguration(id: string): Promise<any>;
|
||||
getPublicSystemInfo(): Promise<PublicSystemInfo>;
|
||||
getPublicUsers(): Promise<UserDto[]>;
|
||||
getQuickConnect(verb: string): Promise<void|boolean|number|QuickConnectResult|QuickConnectState>;
|
||||
getReadySyncItems(deviceId: string): Promise<any>;
|
||||
getRecordingFolders(userId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getRegistrationInfo(feature: string): Promise<any>;
|
||||
getRemoteImageProviders(options: any): Promise<ImageProviderInfo[]>;
|
||||
getResumableItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getRootFolder(userId: string): Promise<BaseItemDto>;
|
||||
getSavedEndpointInfo(): EndPointInfo;
|
||||
getScaledImageUrl(itemId: string, options?: any): string;
|
||||
getScheduledTask(id: string): Promise<TaskInfo>;
|
||||
getScheduledTasks(options?: any): Promise<TaskInfo[]>;
|
||||
getSearchHints(options?: any): Promise<SearchHintResult>;
|
||||
getSeasons(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getServerConfiguration(): Promise<ServerConfiguration>;
|
||||
getServerTime(): Promise<UtcTimeResponse>;
|
||||
getSessions(options?: any): Promise<SessionInfo[]>;
|
||||
getSimilarItems(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getSpecialFeatures(userId: string, itemId: string): Promise<BaseItemDto[]>;
|
||||
getStudio(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getStudios(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getSyncPlayGroups(): Promise<GroupInfoDto[]>;
|
||||
getSyncStatus(itemId: string): Promise<any>;
|
||||
getSystemInfo(): Promise<SystemInfo>;
|
||||
getThemeMedia(userId?: string, itemId: string, inherit?: boolean): Promise<AllThemeMediaResult>;
|
||||
getThumbImageUrl(item: BaseItemDto, options?: any): string;
|
||||
getUpcomingEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getUrl(name: string, params?: any, serverAddress?: string): string;
|
||||
get(url: string): Promise<any>;
|
||||
getUserImageUrl(userId: string, options?: any): string;
|
||||
getUsers(options?: any): Promise<UserDto[]>;
|
||||
getUser(userId: string): Promise<UserDto>;
|
||||
getUserViews(options?: any, userId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getVirtualFolders(): Promise<VirtualFolderInfo[]>;
|
||||
handleMessageReceived(msg: any): void;
|
||||
installPlugin(name: string, guid: string, version?: string): Promise<void>;
|
||||
isLoggedIn(): boolean;
|
||||
isMessageChannelOpen(): boolean;
|
||||
isMinServerVersion(version: string): boolean;
|
||||
isWebSocketOpen(): boolean;
|
||||
isWebSocketOpenOrConnecting(): boolean;
|
||||
isWebSocketSupported(): boolean;
|
||||
joinSyncPlayGroup(options?: any): Promise<void>;
|
||||
leaveSyncPlayGroup(): Promise<void>;
|
||||
logout(): Promise<void>;
|
||||
markNotificationsRead(userId: string, idList: string[], isRead: boolean): Promise<void>;
|
||||
markPlayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
|
||||
markUnplayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
|
||||
openWebSocket(): void;
|
||||
quickConnect(secret: string): Promise<AuthenticationResult>;
|
||||
refreshItem(itemId: string, options?: any): Promise<void>;
|
||||
removeMediaPath(virtualFolderName: string, mediaPath: string, refreshLibrary?: boolean): Promise<void>;
|
||||
removeVirtualFolder(name: string, refreshLibrary?: boolean): Promise<void>;
|
||||
renameVirtualFolder(name: string, newName: string, refreshLibrary?: boolean): Promise<void>;
|
||||
reportCapabilities(capabilities: ClientCapabilities): Promise<void>;
|
||||
reportOfflineActions(actions: any): Promise<any>;
|
||||
reportPlaybackProgress(options: PlaybackProgressInfo): Promise<void>;
|
||||
reportPlaybackStart(options: PlaybackStartInfo): Promise<void>;
|
||||
reportPlaybackStopped(options: PlaybackStopInfo): Promise<void>;
|
||||
reportSyncJobItemTransferred(syncJobItemId: string): Promise<any>;
|
||||
requestSyncPlayBuffering(options?: BufferRequestDto): Promise<void>;
|
||||
requestSyncPlayMovePlaylistItem(options?: MovePlaylistItemRequestDto): Promise<void>;
|
||||
requestSyncPlayNextItem(options?: NextItemRequestDto): Promise<void>;
|
||||
requestSyncPlayPause(): Promise<void>;
|
||||
requestSyncPlayPreviousItem(options?: PreviousItemRequestDto): Promise<void>;
|
||||
requestSyncPlayQueue(options?: QueueRequestDto): Promise<void>;
|
||||
requestSyncPlayReady(options?: ReadyRequestDto): Promise<void>;
|
||||
requestSyncPlayRemoveFromPlaylist(options?: RemoveFromPlaylistRequestDto): Promise<void>;
|
||||
requestSyncPlaySeek(options?: SeekRequestDto): Promise<void>;
|
||||
requestSyncPlaySetIgnoreWait(options?: IgnoreWaitRequestDto): Promise<void>;
|
||||
requestSyncPlaySetNewQueue(options?: NewGroupRequestDto): Promise<void>;
|
||||
requestSyncPlaySetPlaylistItem(options?: SetPlaylistItemRequestDto): Promise<void>;
|
||||
requestSyncPlaySetRepeatMode(options?: SetRepeatModeRequestDto): Promise<void>;
|
||||
requestSyncPlaySetShuffleMode(options?: SetShuffleModeRequestDto): Promise<void>;
|
||||
requestSyncPlayUnpause(): Promise<void>;
|
||||
resetEasyPassword(userId: string): Promise<void>;
|
||||
resetLiveTvTuner(id: string): Promise<void>;
|
||||
resetUserPassword(userId: string): Promise<void>;
|
||||
restartServer(): Promise<void>;
|
||||
sendCommand(sessionId: string, command: any): Promise<void>;
|
||||
sendMessageCommand(sessionId: string, options: GeneralCommand): Promise<void>;
|
||||
sendMessage(name: string, data: any): void;
|
||||
sendPlayCommand(sessionId: string, options: PlayCommand): Promise<void>;
|
||||
sendPlayStateCommand(sessionId: string, command: PlaystateCommand, options?: any): Promise<void>;
|
||||
sendSyncPlayPing(options?: PingRequestDto): Promise<void>;
|
||||
sendWebSocketMessage(name: string, data: any): void;
|
||||
serverAddress(val?: string): string;
|
||||
serverId(): string;
|
||||
serverVersion(): string
|
||||
setAuthenticationInfo(accessKey?: string, userId?: string): void;
|
||||
setRequestHeaders(headers: any): void;
|
||||
setSystemInfo(info: SystemInfo): void;
|
||||
shutdownServer(): Promise<void>;
|
||||
startScheduledTask(id: string): Promise<void>;
|
||||
stopActiveEncodings(playSessionId: string): Promise<void>;
|
||||
stopScheduledTask(id: string): Promise<void>;
|
||||
syncData(data: any): Promise<any>;
|
||||
uninstallPluginByVersion(id: string, version: string): Promise<void>;
|
||||
uninstallPlugin(id: string): Promise<void>;
|
||||
updateDisplayPreferences(id: string, obj: DisplayPreferencesDto, userId: string, app: string): Promise<void>;
|
||||
updateEasyPassword(userId: string, newPassword: string): Promise<void>;
|
||||
updateFavoriteStatus(userId: string, itemId: string, isFavorite: boolean): Promise<UserItemDataDto>;
|
||||
updateItemImageIndex(itemId: string, imageType: ImageType, imageIndex: number, newIndex: number): Promise<any>;
|
||||
updateItem(item: BaseItemDto): Promise<void>;
|
||||
updateLiveTvSeriesTimer(item: SeriesTimerInfoDto): Promise<void>;
|
||||
updateLiveTvTimer(item: TimerInfoDto): Promise<void>;
|
||||
updateMediaPath(virtualFolderName: string, pathInfo: any): Promise<void>;
|
||||
updateNamedConfiguration(name: string, configuration: any): Promise<void>;
|
||||
updatePluginConfiguration(id: string, configuration: any): Promise<void>;
|
||||
updatePluginSecurityInfo(info: PluginSecurityInfo): Promise<void>;
|
||||
updateScheduledTaskTriggers(id: string, triggers: TaskTriggerInfo[]): Promise<void>;
|
||||
updateServerConfiguration(configuration: ServerConfiguration): Promise<void>;
|
||||
updateServerInfo(server: any, serverUrl: string): void;
|
||||
updateUserConfiguration(userId: string, configuration: UserConfiguration): Promise<void>;
|
||||
updateUserItemRating(userId: string, itemId: string, likes: boolean): Promise<UserItemDataDto>;
|
||||
updateUserPassword(userId: string, currentPassword: string, newPassword: string): Promise<void>;
|
||||
updateUserPolicy(userId: string, policy: UserPolicy): Promise<void>;
|
||||
updateUser(user: UserDto): Promise<void>;
|
||||
updateVirtualFolderOptions(id: string, libraryOptions?: any): Promise<void>;
|
||||
uploadItemImage(itemId: string, imageType: ImageType, file: File): Promise<void>;
|
||||
uploadItemSubtitle(itemId: string, language: string, isForced: boolean, file: File): Promise<void>;
|
||||
uploadUserImage(userId: string, imageType: ImageType, file: File): Promise<void>;
|
||||
}
|
||||
|
||||
class AppStore {
|
||||
constructor();
|
||||
|
||||
getItem(name: string): string|null;
|
||||
removeItem(name: string): void;
|
||||
setItem(name: string, value: string): void;
|
||||
}
|
||||
|
||||
class ConnectionManager {
|
||||
constructor(credentialProvider: Credentials, appName: string, appVersion: string, deviceName: string, deviceId: string, capabilities: ClientCapabilities);
|
||||
|
||||
addApiClient(apiClient: ApiClient): void;
|
||||
clearData(): void;
|
||||
connect(options?: any): Promise<any>;
|
||||
connectToAddress(address: string, options?: any): Promise<any>;
|
||||
connectToServer(server: any, options?: any): Promise<any>;
|
||||
connectToServers(servers: any[], options?: any): Promise<any>;
|
||||
deleteServer(serverId: string): Promise<void>;
|
||||
getApiClient(item: BaseItemDto|string): ApiClient;
|
||||
getApiClients(): ApiClient[];
|
||||
getAvailableServers(): any[];
|
||||
getOrCreateApiClient(serverId: string): ApiClient;
|
||||
getSavedServers(): any[];
|
||||
handleMessageReceived(msg: any): void;
|
||||
logout(): Promise<void>;
|
||||
minServerVersion(val?: string): string;
|
||||
user(apiClient: ApiClient): Promise<any>;
|
||||
}
|
||||
|
||||
class Credentials {
|
||||
constructor(key?: string);
|
||||
|
||||
addOrUpdateServer(list: any[], server: any): any;
|
||||
clear(): void;
|
||||
credentials(data?: any): any;
|
||||
}
|
||||
|
||||
interface Event {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const Events: {
|
||||
off(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
|
||||
on(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
|
||||
trigger(obj: any, eventName: string, ...args: any[]): void;
|
||||
};
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
@@ -214,7 +214,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
position: absolute;
|
||||
border-radius: 0.2em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@@ -346,15 +345,11 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-weight: 400;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.darkenContent {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.sessionAppName {
|
||||
vertical-align: top;
|
||||
max-width: 200px;
|
||||
@@ -1,38 +1,18 @@
|
||||
@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%);
|
||||
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
html[lang|="ja"] {
|
||||
font-family: "Noto Sans", "Noto Sans JP", "Noto Sans HK", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
|
||||
}
|
||||
|
||||
html[lang|="ko"] {
|
||||
font-family: "Noto Sans", "Noto Sans KR", "Noto Sans HK", "Noto Sans JP", "Noto Sans SC", "Noto Sans TC", sans-serif;
|
||||
}
|
||||
|
||||
html[lang|="zh-CN"] {
|
||||
font-family: "Noto Sans", "Noto Sans SC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", sans-serif;
|
||||
}
|
||||
|
||||
html[lang|="zh-TW"] {
|
||||
font-family: "Noto Sans", "Noto Sans TC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
html[lang|="zh-HK"] {
|
||||
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@include font(400, 1.8em);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,3 @@
|
||||
// The padding of the header content on mobile needs to be adjusted
|
||||
// based on the size of the poster card (values from card.scss)
|
||||
@mixin header-poster-padding() {
|
||||
padding-left: 37.5%;
|
||||
|
||||
@media all and (min-width: 43.75em) {
|
||||
padding-left: 25%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 50em) {
|
||||
padding-left: 20%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 75em) {
|
||||
padding-left: 16.666666666666666666666666666667%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 87.5em) {
|
||||
padding-left: 14.285714285714285714285714285714%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 100em) {
|
||||
padding-left: 12.5%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 120em) {
|
||||
padding-left: 11.111111111111111111111111111111%;
|
||||
}
|
||||
|
||||
@media all and (min-width: 131.25em) {
|
||||
padding-left: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
.headerUserImage,
|
||||
.navMenuOption,
|
||||
.pageTitle {
|
||||
@@ -86,6 +52,8 @@
|
||||
z-index: 1;
|
||||
margin: 0 !important;
|
||||
top: 6.9em !important;
|
||||
-webkit-transition: -webkit-transform 0.2s ease-out;
|
||||
-o-transition: transform 0.2s ease-out;
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
@@ -94,14 +62,17 @@
|
||||
}
|
||||
|
||||
.headerUserImage {
|
||||
-webkit-background-size: contain;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-webkit-border-radius: 100em;
|
||||
border-radius: 100em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.headerUserButtonRound div {
|
||||
-webkit-border-radius: 100em;
|
||||
border-radius: 100em;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
@@ -109,6 +80,7 @@
|
||||
}
|
||||
|
||||
.headerButton {
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -118,15 +90,23 @@
|
||||
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.headerRight {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -136,10 +116,15 @@
|
||||
}
|
||||
|
||||
.pageTitle {
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: inline-flex;
|
||||
margin: 0 0 0 0.5em;
|
||||
height: 1.7em;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-flex-shrink: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
@@ -149,16 +134,21 @@
|
||||
|
||||
.headerLeft,
|
||||
.skinHeader {
|
||||
display: flex;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
|
||||
.detailButton,
|
||||
.skinHeader {
|
||||
flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
}
|
||||
|
||||
.pageTitleWithLogo {
|
||||
background-position: left center;
|
||||
-webkit-background-size: contain;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
width: 13.2em;
|
||||
@@ -204,19 +194,27 @@
|
||||
}
|
||||
|
||||
.navMenuOption {
|
||||
display: -webkit-box !important;
|
||||
display: -webkit-flex !important;
|
||||
display: flex !important;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
padding: 0.9em 0 0.9em 2.4em !important;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
font-weight: 400 !important;
|
||||
margin: 0 !important;
|
||||
-webkit-border-radius: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.navMenuOptionIcon {
|
||||
margin-right: 1.2em;
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -231,6 +229,8 @@
|
||||
}
|
||||
|
||||
.dashboardDocument .skinBody {
|
||||
-webkit-transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
|
||||
-o-transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
|
||||
transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -250,6 +250,26 @@
|
||||
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 {
|
||||
@@ -260,7 +280,9 @@
|
||||
z-index: inherit !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
-webkit-transform: none !important;
|
||||
transform: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
width: 20.205em !important;
|
||||
font-size: 94%;
|
||||
@@ -295,9 +317,14 @@
|
||||
}
|
||||
|
||||
.headerTabs {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
width: auto;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
margin-top: -4.3em;
|
||||
@@ -354,6 +381,8 @@
|
||||
}
|
||||
|
||||
.flexPageTabContent.is-active {
|
||||
display: -webkit-box !important;
|
||||
display: -webkit-flex !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
@@ -374,17 +403,13 @@
|
||||
margin: 1.5em 0;
|
||||
background: #222;
|
||||
padding: 0.8em 0.8em 0.8em 3em;
|
||||
-webkit-border-radius: 0.3em;
|
||||
border-radius: 0.3em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detailLogo {
|
||||
width: 25vw;
|
||||
height: 16vh;
|
||||
position: absolute;
|
||||
top: 10vh;
|
||||
right: 25vw;
|
||||
background-size: contain;
|
||||
.detailLogo,
|
||||
.itemBackdrop {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
@@ -437,34 +462,30 @@
|
||||
}
|
||||
|
||||
.itemBackdrop {
|
||||
-webkit-background-size: cover;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
height: 40vh;
|
||||
position: relative;
|
||||
animation: backdrop-fadein 800ms ease-in normal both;
|
||||
|
||||
.layout-mobile & {
|
||||
background-attachment: initial;
|
||||
background-position: top center;
|
||||
margin-top: 3rem;
|
||||
|
||||
@media all and (orientation: portrait) and (max-width: 40em) {
|
||||
height: 30vh;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-desktop &::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-tv .itemBackdrop {
|
||||
.layout-mobile .itemBackdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-desktop .itemBackdrop::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-tv .itemBackdrop,
|
||||
.layout-desktop .noBackdrop .itemBackdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -473,18 +494,26 @@
|
||||
flex-direction: column;
|
||||
padding-left: 32.45vw;
|
||||
padding-right: 2%;
|
||||
}
|
||||
|
||||
.layout-mobile & {
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
}
|
||||
.layout-mobile .detailPageContent {
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
}
|
||||
|
||||
.layout-desktop &,
|
||||
.layout-tv & {
|
||||
.emby-scroller {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.layout-desktop .detailPageContent .emby-scroller,
|
||||
.layout-tv .detailPageContent .emby-scroller {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.layout-desktop .noBackdrop .detailPageContent,
|
||||
.layout-tv .noBackdrop .detailPageContent {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
|
||||
.layout-desktop .noBackdrop .detailImageContainer img,
|
||||
.layout-tv .noBackdrop .detailImageContainer img {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.detailSectionContent a {
|
||||
@@ -524,8 +553,14 @@
|
||||
margin: -0.25em 0 0.25em;
|
||||
}
|
||||
|
||||
.layout-mobile .itemExternalLinks {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mainDetailButtons {
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
@@ -533,19 +568,13 @@
|
||||
.detailButton,
|
||||
.mainDetailButtons {
|
||||
display: flex;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
|
||||
.itemName {
|
||||
margin: 0.5em 0;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
.layout-mobile & {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.itemName.originalTitle {
|
||||
@@ -584,19 +613,14 @@
|
||||
}
|
||||
|
||||
.itemMiscInfo {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
|
||||
.layout-mobile & {
|
||||
@media all and (orientation: portrait) and (max-width: 40em) {
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
.mediaInfoItem {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-mobile .parentName,
|
||||
@@ -609,30 +633,17 @@
|
||||
}
|
||||
|
||||
.layout-mobile .mainDetailButtons {
|
||||
margin-top: 1em;
|
||||
flex: 2 0 70%;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-left: 0;
|
||||
|
||||
@include header-poster-padding;
|
||||
|
||||
// The buttons row is full width on small screens
|
||||
@media all and (max-width: 32em) {
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0.15em 0 0.2em;
|
||||
|
||||
// Leave room for a focused button
|
||||
margin-left: -1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.layout-mobile .subtitle {
|
||||
margin: 0.2em 0 0.2em;
|
||||
padding-left: 0; // Reset padding for focused button since 'margin-left' is 0
|
||||
}
|
||||
|
||||
.detailPagePrimaryContainer {
|
||||
@@ -640,22 +651,23 @@
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.layout-mobile & {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 0.5rem 5%;
|
||||
}
|
||||
.layout-tv .detailPagePrimaryContainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-desktop & {
|
||||
position: relative;
|
||||
padding-left: 32.45vw;
|
||||
}
|
||||
.layout-mobile .detailPagePrimaryContainer {
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
padding: 4.5rem 3.3% 0.5rem;
|
||||
}
|
||||
|
||||
.layout-tv & {
|
||||
display: block;
|
||||
padding-left: 32.45vw;
|
||||
}
|
||||
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
||||
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
|
||||
position: relative;
|
||||
top: 0;
|
||||
padding-left: 32.45vw;
|
||||
}
|
||||
|
||||
.layout-desktop .detailRibbon {
|
||||
@@ -668,24 +680,30 @@
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.layout-desktop .noBackdrop .detailRibbon,
|
||||
.layout-tv .noBackdrop .detailRibbon {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.infoWrapper {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.layout-mobile & {
|
||||
@include header-poster-padding;
|
||||
|
||||
@media all and (max-width: 32em) {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.layout-mobile .infoWrapper {
|
||||
flex: 2 0 70%;
|
||||
}
|
||||
|
||||
.infoText {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-mobile .infoText {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.detailPageSecondaryContainer {
|
||||
@@ -696,65 +714,46 @@
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.layout-mobile .detailImageContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.detailImageContainer .card {
|
||||
// important is needed here to override :focus setting
|
||||
// the position to relative in the tv layout
|
||||
position: absolute !important;
|
||||
top: 20%;
|
||||
max-width: 25vw;
|
||||
max-height: 80vh;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
float: left;
|
||||
width: 25vw;
|
||||
z-index: 3;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.cardBox {
|
||||
margin: 0;
|
||||
}
|
||||
.detailImageContainer .card.backdropCard {
|
||||
top: 35%;
|
||||
}
|
||||
|
||||
&.backdropCard {
|
||||
top: 35%;
|
||||
}
|
||||
.detailImageContainer .card.squareCard {
|
||||
top: 40%;
|
||||
}
|
||||
|
||||
&.squareCard {
|
||||
top: 40%;
|
||||
}
|
||||
|
||||
.layout-mobile & {
|
||||
left: 5%;
|
||||
bottom: 1rem;
|
||||
max-width: 30vw;
|
||||
filter: drop-shadow(0 0 0.5rem #000);
|
||||
|
||||
@media all and (max-width: 32em) {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&,
|
||||
&.backdropCard,
|
||||
&.squareCard {
|
||||
top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-desktop & {
|
||||
left: 3.3%;
|
||||
top: -80%;
|
||||
width: 25vw;
|
||||
// FIXME: the fixed width + max height cause the card to be cropped this needs a proper fix
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.layout-tv & {
|
||||
left: 5%;
|
||||
top: 50%;
|
||||
width: 25vw;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.layout-desktop .noBackdrop .detailImageContainer,
|
||||
.layout-tv .noBackdrop .detailImageContainer {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.detailPagePrimaryContent {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detailLogo {
|
||||
width: 25vw;
|
||||
height: 16vh;
|
||||
position: absolute;
|
||||
top: 10vh;
|
||||
right: 25vw;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.noBackdrop .detailLogo,
|
||||
.layout-mobile .detailLogo {
|
||||
display: none;
|
||||
}
|
||||
@@ -767,6 +766,7 @@
|
||||
|
||||
.itemDetailImage {
|
||||
width: 100% !important;
|
||||
-webkit-box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
|
||||
box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
.recordingFields button {
|
||||
margin-left: 0;
|
||||
margin-right: 0.5em;
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -883,7 +884,11 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
.detailButton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
padding: 0.7em 0.7em !important;
|
||||
@@ -911,9 +916,18 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
}
|
||||
|
||||
.detailButton-content {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -970,6 +984,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
@media all and (max-width: 31.25em) {
|
||||
.mobileDetails .itemMiscInfo {
|
||||
text-align: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -991,8 +1007,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.mediaInfoContent .btnCopy .material-icons {
|
||||
font-size: inherit;
|
||||
.layout-desktop .noBackdrop .detailPageWrapperContainer,
|
||||
.layout-tv .noBackdrop .detailPageWrapperContainer {
|
||||
margin-top: 3.8em;
|
||||
}
|
||||
|
||||
.mediaInfoStream {
|
||||
@@ -1003,10 +1020,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
|
||||
.mediaInfoStreamType {
|
||||
display: block;
|
||||
margin: 0.622em 0; /* copy button height compensation */
|
||||
}
|
||||
|
||||
.layout-tv .mediaInfoStreamType {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
@@ -1058,9 +1071,14 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
}
|
||||
|
||||
.mediaInfoIcons {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin: 1em 0;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -1108,6 +1126,7 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||
|
||||
.sectionTitleButton {
|
||||
margin-left: 1.5em !important;
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1117,17 +1136,22 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||
|
||||
.sectionTitleIconButton {
|
||||
margin-left: 1.5em !important;
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
font-size: 84% !important;
|
||||
padding: 0.5em !important;
|
||||
}
|
||||
|
||||
.horizontalItemsContainer {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sectionTitleTextButton {
|
||||
margin: 0 !important;
|
||||
display: -webkit-inline-box !important;
|
||||
display: -webkit-inline-flex !important;
|
||||
display: inline-flex !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
@@ -1195,6 +1219,8 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||
}
|
||||
|
||||
.itemsViewSettingsContainer {
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -1219,7 +1245,7 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||
}
|
||||
|
||||
.itemDetailsGroup {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.trackSelections {
|
||||
@@ -7,8 +7,3 @@
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: background sizing for cards really needs revisited, but these are particularly terrible
|
||||
#channelsTab .cardImageContainer {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 663 B |
@@ -8,12 +8,8 @@ class ServerConnections extends ConnectionManager {
|
||||
super(...arguments);
|
||||
this.localApiClient = null;
|
||||
|
||||
Events.on(this, 'localusersignedout', function (eventName, logoutInfo) {
|
||||
Events.on(this, 'localusersignedout', function () {
|
||||
setUserInfo(null, null);
|
||||
|
||||
if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedOut === 'function') {
|
||||
window.NativeShell.onLocalUserSignedOut(logoutInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,12 +62,7 @@ class ServerConnections extends ConnectionManager {
|
||||
onLocalUserSignedIn(user) {
|
||||
const apiClient = this.getApiClient(user.ServerId);
|
||||
this.setLocalApiClient(apiClient);
|
||||
return setUserInfo(user.Id, apiClient).then(() => {
|
||||
if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedIn === 'function') {
|
||||
return window.NativeShell.onLocalUserSignedIn(user, apiClient.accessToken());
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
return setUserInfo(user.Id, apiClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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.scss';
|
||||
import '../formdialog.css';
|
||||
import template from './accessSchedule.template.html';
|
||||
|
||||
function getDisplayTime(hours) {
|
||||
|
||||
@@ -90,13 +90,12 @@
|
||||
|
||||
.actionSheetTitle {
|
||||
margin: 0.6em 0 0.7em !important;
|
||||
padding: 0 0.75rem;
|
||||
padding: 0 0.9em;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.actionSheetText {
|
||||
margin-top: 0;
|
||||
padding: 0 0.75rem;
|
||||
padding: 0 1em;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
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.scss';
|
||||
import './actionSheet.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../../assets/css/scrollstyles.scss';
|
||||
import '../../components/listview/listview.scss';
|
||||
import '../../assets/css/scrollstyles.css';
|
||||
import '../../components/listview/listview.css';
|
||||
|
||||
function getOffsets(elems) {
|
||||
const results = [];
|
||||
@@ -142,8 +141,8 @@ export function show(options) {
|
||||
}
|
||||
|
||||
if (layoutManager.tv) {
|
||||
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1" title="${globalize.translate('ButtonBack')}">
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1">
|
||||
<span class="material-icons arrow_back"></span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
@@ -157,10 +156,10 @@ export function show(options) {
|
||||
}
|
||||
|
||||
if (options.title) {
|
||||
html += '<h1 class="actionSheetTitle">' + escapeHtml(options.title) + '</h1>';
|
||||
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
|
||||
}
|
||||
if (options.text) {
|
||||
html += '<p class="actionSheetText">' + escapeHtml(options.text) + '</p>';
|
||||
html += '<p class="actionSheetText">' + options.text + '</p>';
|
||||
}
|
||||
|
||||
let scrollerClassName = 'actionSheetScroller';
|
||||
@@ -205,25 +204,25 @@ export function show(options) {
|
||||
itemIcon = icons[i];
|
||||
|
||||
if (itemIcon) {
|
||||
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}" aria-hidden="true"></span>`;
|
||||
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
|
||||
} else if (renderIcon && !center) {
|
||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" aria-hidden="true" style="visibility:hidden;"></span>';
|
||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody actionsheetListItemBody">';
|
||||
|
||||
html += '<div class="listItemBodyText actionSheetItemText">';
|
||||
html += escapeHtml(item.name || item.textContent || item.innerText);
|
||||
html += (item.name || item.textContent || item.innerText);
|
||||
html += '</div>';
|
||||
|
||||
if (item.secondaryText) {
|
||||
html += `<div class="listItemBodyText secondary">${escapeHtml(item.secondaryText)}</div>`;
|
||||
html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
if (item.asideText) {
|
||||
html += `<div class="listItemAside actionSheetItemAsideText">${escapeHtml(item.asideText)}</div>`;
|
||||
html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
|
||||
}
|
||||
|
||||
html += '</button>';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import globalize from '../scripts/globalize';
|
||||
import dom from '../scripts/dom';
|
||||
@@ -6,7 +5,7 @@ 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.scss';
|
||||
import './listview/listview.css';
|
||||
import ServerConnections from './ServerConnections';
|
||||
import alert from './alert';
|
||||
|
||||
@@ -24,29 +23,29 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
if (entry.UserId && entry.UserPrimaryImageTag) {
|
||||
html += '<span class="listItemIcon material-icons dvr" aria-hidden="true" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
html += '<span class="listItemIcon material-icons dvr" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
type: 'Primary',
|
||||
tag: entry.UserPrimaryImageTag
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\"></span>";
|
||||
} else {
|
||||
html += '<span class="listItemIcon material-icons ' + icon + '" aria-hidden="true" style="background-color:' + color + '"></span>';
|
||||
html += '<span class="listItemIcon material-icons ' + icon + '" style="background-color:' + color + '"></span>';
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody three-line">';
|
||||
html += '<div class="listItemBodyText">';
|
||||
html += escapeHtml(entry.Name);
|
||||
html += entry.Name;
|
||||
html += '</div>';
|
||||
html += '<div class="listItemBodyText secondary">';
|
||||
html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
|
||||
html += '</div>';
|
||||
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
|
||||
html += escapeHtml(entry.ShortOverview || '');
|
||||
html += entry.ShortOverview || '';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
if (entry.Overview) {
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
|
||||
<span class="material-icons info" aria-hidden="true"></span>
|
||||
<span class="material-icons info"></span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ import alert from './alert';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderList(elem, apiClient, result) {
|
||||
function renderList(elem, apiClient, result, startIndex, limit) {
|
||||
elem.innerHTML = result.Items.map(function (i) {
|
||||
return getEntryHtml(i, apiClient);
|
||||
}).join('');
|
||||
@@ -98,11 +97,11 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
instance.items = result.Items;
|
||||
renderList(elem, apiClient, result);
|
||||
renderList(elem, apiClient, result, startIndex, limit);
|
||||
});
|
||||
}
|
||||
|
||||
function onActivityLogUpdate(e, apiClient) {
|
||||
function onActivityLogUpdate(e, apiClient, data) {
|
||||
const options = this.options;
|
||||
|
||||
if (options && options.serverId === apiClient.serverId()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appRouter } from './appRouter';
|
||||
|
||||
import browser from '../scripts/browser';
|
||||
import dialog from './dialog/dialog';
|
||||
import globalize from '../scripts/globalize';
|
||||
@@ -10,16 +10,7 @@ import globalize from '../scripts/globalize';
|
||||
return originalString.replace(reg, strWith);
|
||||
}
|
||||
|
||||
function useNativeAlert() {
|
||||
// webOS seems to block modals
|
||||
// Tizen 2.x seems to block modals
|
||||
return !browser.web0s
|
||||
&& !(browser.tizenVersion && browser.tizenVersion < 3)
|
||||
&& browser.tv
|
||||
&& window.alert;
|
||||
}
|
||||
|
||||
export default async function (text, title) {
|
||||
export default function (text, title) {
|
||||
let options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
@@ -30,11 +21,8 @@ import globalize from '../scripts/globalize';
|
||||
options = text;
|
||||
}
|
||||
|
||||
await appRouter.ready();
|
||||
|
||||
if (useNativeAlert()) {
|
||||
if (browser.tv && window.alert) {
|
||||
alert(replaceAll(options.text || '', '<br/>', '\n'));
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
const items = [];
|
||||
|
||||
@@ -45,8 +33,17 @@ import globalize from '../scripts/globalize';
|
||||
});
|
||||
|
||||
options.buttons = items;
|
||||
return dialog.show(options);
|
||||
|
||||
return dialog.show(options).then(function (result) {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import AlphaPicker from './alphaPicker';
|
||||
|
||||
type AlphaPickerProps = {
|
||||
onAlphaPicked?: (e: Event) => void
|
||||
};
|
||||
|
||||
// React compatibility wrapper component for alphaPicker.js
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const AlphaPickerComponent: FunctionComponent<AlphaPickerProps> = ({ onAlphaPicked = () => {} }: AlphaPickerProps) => {
|
||||
const [ alphaPicker, setAlphaPicker ] = useState<AlphaPicker>();
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setAlphaPicker(new AlphaPicker({
|
||||
element: element.current,
|
||||
mode: 'keyboard'
|
||||
}));
|
||||
|
||||
element.current?.addEventListener('alphavalueclicked', onAlphaPicked);
|
||||
|
||||
return () => {
|
||||
alphaPicker?.destroy();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Disabled for wrapper components
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={element}
|
||||
className='alphaPicker align-items-center'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlphaPickerComponent;
|
||||
@@ -8,8 +8,7 @@
|
||||
import focusManager from '../focusManager';
|
||||
import layoutManager from '../layoutManager';
|
||||
import dom from '../../scripts/dom';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import './style.scss';
|
||||
import './style.css';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import 'material-design-icons-iconfont';
|
||||
|
||||
@@ -76,7 +75,7 @@ import 'material-design-icons-iconfont';
|
||||
|
||||
html += `<div class="${rowClassName}">`;
|
||||
if (options.mode === 'keyboard') {
|
||||
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonSpace')}"><span class="material-icons alphaPickerButtonIcon space_bar" aria-hidden="true"></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('');
|
||||
@@ -86,7 +85,7 @@ 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}" aria-label="${globalize.translate('ButtonBackspace')}"><span class="material-icons alphaPickerButtonIcon backspace" aria-hidden="true"></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'];
|
||||
@@ -281,16 +280,6 @@ import 'material-design-icons-iconfont';
|
||||
element.removeEventListener(name, fn);
|
||||
}
|
||||
|
||||
updateControls(query) {
|
||||
if (query.NameLessThan) {
|
||||
this.value('#');
|
||||
} else {
|
||||
this.value(query.NameStartsWith);
|
||||
}
|
||||
|
||||
this.visible(query.SortBy.indexOf('SortName') !== -1);
|
||||
}
|
||||
|
||||
visible(visible) {
|
||||
const element = this.options.element;
|
||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import './appFooter.scss';
|
||||
import './appFooter.css';
|
||||
|
||||
function render() {
|
||||
function render(options) {
|
||||
const elem = document.createElement('div');
|
||||
elem.classList.add('appfooter');
|
||||
|
||||
@@ -10,10 +10,10 @@ function render() {
|
||||
}
|
||||
|
||||
class appFooter {
|
||||
constructor() {
|
||||
constructor(options) {
|
||||
const self = this;
|
||||
|
||||
self.element = render();
|
||||
self.element = render(options);
|
||||
self.add = function (elem) {
|
||||
self.element.appendChild(elem);
|
||||
};
|
||||
@@ -33,4 +33,4 @@ class appFooter {
|
||||
}
|
||||
}
|
||||
|
||||
export default new appFooter();
|
||||
export default new appFooter({});
|
||||
|
||||
@@ -11,7 +11,6 @@ import viewManager from './viewManager/viewManager';
|
||||
import Dashboard from '../scripts/clientUtils';
|
||||
import ServerConnections from './ServerConnections';
|
||||
import alert from './alert';
|
||||
import reactControllerFactory from './reactControllerFactory';
|
||||
|
||||
class AppRouter {
|
||||
allRoutes = [];
|
||||
@@ -24,33 +23,24 @@ class AppRouter {
|
||||
isDummyBackToHome;
|
||||
msgTimeout;
|
||||
popstateOccurred = false;
|
||||
promiseShow;
|
||||
resolveOnNextShow;
|
||||
previousRoute = {};
|
||||
/**
|
||||
* Pages of "no return" (when "Go back" should behave differently, probably quitting the application).
|
||||
*/
|
||||
startPages = ['home', 'login', 'selectserver'];
|
||||
|
||||
constructor() {
|
||||
// WebKit fires a popstate event on document load
|
||||
// Skip it using boolean
|
||||
// For Tizen 2.x
|
||||
// See `page` node module
|
||||
let loaded = document.readyState === 'complete';
|
||||
if (!loaded) {
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(() => {
|
||||
loaded = true;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
window.addEventListener('popstate', () => {
|
||||
if (!loaded) return;
|
||||
this.popstateOccurred = true;
|
||||
});
|
||||
|
||||
document.addEventListener('viewshow', () => this.onViewShow());
|
||||
document.addEventListener('viewshow', () => {
|
||||
const resolve = this.resolveOnNextShow;
|
||||
if (resolve) {
|
||||
this.resolveOnNextShow = null;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
this.baseRoute = window.location.href.split('?')[0].replace(this.getRequestFile(), '');
|
||||
// support hashbang
|
||||
@@ -59,6 +49,8 @@ class AppRouter {
|
||||
this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1);
|
||||
}
|
||||
|
||||
this.setBaseRoute();
|
||||
|
||||
// paths that start with a hashbang (i.e. /#!/page.html) get transformed to starting with //
|
||||
// we need to strip one "/" for our routes to work
|
||||
page('//*', (ctx) => {
|
||||
@@ -66,6 +58,18 @@ class AppRouter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setBaseRoute() {
|
||||
let baseRoute = window.location.pathname.replace(this.getRequestFile(), '');
|
||||
if (baseRoute.lastIndexOf('/') === baseRoute.length - 1) {
|
||||
baseRoute = baseRoute.substring(0, baseRoute.length - 1);
|
||||
}
|
||||
console.debug('setting page base to ' + baseRoute);
|
||||
page.base(baseRoute);
|
||||
}
|
||||
|
||||
addRoute(path, newRoute) {
|
||||
page(path, this.getHandler(newRoute));
|
||||
this.allRoutes.push(newRoute);
|
||||
@@ -114,24 +118,11 @@ class AppRouter {
|
||||
}
|
||||
}
|
||||
|
||||
ready() {
|
||||
return this.promiseShow || Promise.resolve();
|
||||
back() {
|
||||
page.back();
|
||||
}
|
||||
|
||||
async back() {
|
||||
if (this.promiseShow) await this.promiseShow;
|
||||
|
||||
this.promiseShow = new Promise((resolve) => {
|
||||
this.resolveOnNextShow = resolve;
|
||||
page.back();
|
||||
});
|
||||
|
||||
return this.promiseShow;
|
||||
}
|
||||
|
||||
async show(path, options) {
|
||||
if (this.promiseShow) await this.promiseShow;
|
||||
|
||||
show(path, options) {
|
||||
// ensure the path does not start with '#!' since the router adds this
|
||||
if (path.startsWith('#!')) {
|
||||
path = path.substring(2);
|
||||
@@ -151,25 +142,17 @@ class AppRouter {
|
||||
}
|
||||
}
|
||||
|
||||
this.promiseShow = new Promise((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
this.resolveOnNextShow = resolve;
|
||||
// Schedule a call to return the promise
|
||||
setTimeout(() => page.show(path, options), 0);
|
||||
page.show(path, options);
|
||||
});
|
||||
|
||||
return this.promiseShow;
|
||||
}
|
||||
|
||||
async showDirect(path) {
|
||||
if (this.promiseShow) await this.promiseShow;
|
||||
|
||||
this.promiseShow = new Promise((resolve) => {
|
||||
showDirect(path) {
|
||||
return new Promise(function(resolve) {
|
||||
this.resolveOnNextShow = resolve;
|
||||
// Schedule a call to return the promise
|
||||
setTimeout(() => page.show(this.baseUrl() + path), 0);
|
||||
page.show(this.baseUrl() + path);
|
||||
});
|
||||
|
||||
return this.promiseShow;
|
||||
}
|
||||
|
||||
start(options) {
|
||||
@@ -358,9 +341,7 @@ class AppRouter {
|
||||
this.sendRouteToViewManager(ctx, next, route, controllerFactory);
|
||||
};
|
||||
|
||||
if (route.pageComponent) {
|
||||
onInitComplete(reactControllerFactory);
|
||||
} else if (route.controller) {
|
||||
if (route.controller) {
|
||||
import('../controllers/' + route.controller).then(onInitComplete);
|
||||
} else {
|
||||
onInitComplete();
|
||||
@@ -392,7 +373,6 @@ class AppRouter {
|
||||
fullscreen: route.fullscreen,
|
||||
controllerFactory: controllerFactory,
|
||||
options: {
|
||||
pageComponent: route.pageComponent,
|
||||
supportsThemeMedia: route.supportsThemeMedia || false,
|
||||
enableMediaControl: route.enableMediaControl !== false
|
||||
},
|
||||
@@ -424,15 +404,6 @@ class AppRouter {
|
||||
});
|
||||
}
|
||||
|
||||
onViewShow() {
|
||||
const resolve = this.resolveOnNextShow;
|
||||
if (resolve) {
|
||||
this.promiseShow = null;
|
||||
this.resolveOnNextShow = null;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
onForcedLogoutMessageTimeout() {
|
||||
const msg = this.forcedLogoutMsg;
|
||||
this.forcedLogoutMsg = null;
|
||||
@@ -650,17 +621,8 @@ class AppRouter {
|
||||
getHandler(route) {
|
||||
return (ctx, next) => {
|
||||
ctx.isBack = this.popstateOccurred;
|
||||
this.popstateOccurred = false;
|
||||
|
||||
const ignore = route.dummyRoute === true || this.previousRoute.dummyRoute === true;
|
||||
this.previousRoute = route;
|
||||
if (ignore) {
|
||||
// Resolve 'show' promise
|
||||
this.onViewShow();
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleRoute(ctx, next, route);
|
||||
this.popstateOccurred = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -788,10 +750,6 @@ class AppRouter {
|
||||
return '#!/list.html?type=Programs&IsAiring=true&serverId=' + options.serverId;
|
||||
}
|
||||
|
||||
if (options.section === 'channels') {
|
||||
return '#!/livetv.html?tab=2&serverId=' + options.serverId;
|
||||
}
|
||||
|
||||
if (options.section === 'dvrschedule') {
|
||||
return '#!/livetv.html?tab=4&serverId=' + options.serverId;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Package from '../../package.json';
|
||||
|
||||
import appSettings from '../scripts/settings/appSettings';
|
||||
import browser from '../scripts/browser';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
@@ -8,6 +8,7 @@ import globalize from '../scripts/globalize';
|
||||
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||
|
||||
const appName = 'Jellyfin Web';
|
||||
const appVersion = '10.7.6';
|
||||
|
||||
function getBaseProfileOptions(item) {
|
||||
const disableHlsVideoAudioCodecs = [];
|
||||
@@ -28,37 +29,17 @@ function getBaseProfileOptions(item) {
|
||||
};
|
||||
}
|
||||
|
||||
function getDeviceProfile(item) {
|
||||
function getDeviceProfile(item, options = {}) {
|
||||
return new Promise(function (resolve) {
|
||||
let profile;
|
||||
|
||||
if (window.NativeShell) {
|
||||
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, Package.version);
|
||||
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, appVersion);
|
||||
} else {
|
||||
const builderOpts = getBaseProfileOptions(item);
|
||||
profile = profileBuilder(builderOpts);
|
||||
}
|
||||
|
||||
const maxVideoWidth = appSettings.maxVideoWidth();
|
||||
const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth;
|
||||
|
||||
if (maxTranscodingVideoWidth) {
|
||||
profile.TranscodingProfiles.forEach((transcodingProfile) => {
|
||||
if (transcodingProfile.Type === 'Video') {
|
||||
transcodingProfile.Conditions = (transcodingProfile.Conditions || []).filter((condition) => {
|
||||
return condition.Property !== 'Width';
|
||||
});
|
||||
|
||||
transcodingProfile.Conditions.push({
|
||||
Condition: 'LessThanEqual',
|
||||
Property: 'Width',
|
||||
Value: maxTranscodingVideoWidth.toString(),
|
||||
IsRequired: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolve(profile);
|
||||
});
|
||||
}
|
||||
@@ -219,6 +200,7 @@ const supportedFeatures = function () {
|
||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||
features.push('exit');
|
||||
} else {
|
||||
features.push('exitmenu');
|
||||
features.push('plugins');
|
||||
}
|
||||
|
||||
@@ -294,7 +276,7 @@ const supportedFeatures = function () {
|
||||
*/
|
||||
function doExit() {
|
||||
try {
|
||||
if (window.NativeShell?.AppHost?.exit) {
|
||||
if (window.NativeShell) {
|
||||
window.NativeShell.AppHost.exit();
|
||||
} else if (browser.tizen) {
|
||||
tizen.application.getCurrentApplication().exit();
|
||||
@@ -379,20 +361,16 @@ export const appHost = {
|
||||
};
|
||||
},
|
||||
deviceName: function () {
|
||||
return window.NativeShell?.AppHost?.deviceName
|
||||
? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||
},
|
||||
deviceId: function () {
|
||||
return window.NativeShell?.AppHost?.deviceId
|
||||
? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||
},
|
||||
appName: function () {
|
||||
return window.NativeShell?.AppHost?.appName
|
||||
? window.NativeShell.AppHost.appName() : appName;
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
||||
},
|
||||
appVersion: function () {
|
||||
return window.NativeShell?.AppHost?.appVersion
|
||||
? window.NativeShell.AppHost.appVersion() : Package.version;
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
||||
},
|
||||
getPushTokenInfo: function () {
|
||||
return {};
|
||||
@@ -402,27 +380,6 @@ export const appHost = {
|
||||
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);
|
||||
}
|
||||
},
|
||||
screen: () => {
|
||||
let hostScreen = null;
|
||||
|
||||
const appHostImpl = window.NativeShell?.AppHost;
|
||||
|
||||
if (appHostImpl?.screen) {
|
||||
hostScreen = appHostImpl.screen();
|
||||
} else if (window.screen && !browser.tv) {
|
||||
hostScreen = {
|
||||
width: Math.floor(window.screen.width * window.devicePixelRatio),
|
||||
height: Math.floor(window.screen.height * window.devicePixelRatio)
|
||||
};
|
||||
}
|
||||
|
||||
if (hostScreen) {
|
||||
// Use larger dimension to account for screen orientation changes
|
||||
hostScreen.maxAllowedWidth = Math.max(hostScreen.width, hostScreen.height);
|
||||
}
|
||||
|
||||
return hostScreen;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ import layoutManager from './layoutManager';
|
||||
candidates.push(activeElement);
|
||||
}
|
||||
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnResume')));
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay')));
|
||||
|
||||
let focusedElement;
|
||||
|
||||
@@ -2,12 +2,12 @@ import browser from '../../scripts/browser';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import dom from '../../scripts/dom';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import './backdrop.scss';
|
||||
import './backdrop.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
function enableAnimation() {
|
||||
function enableAnimation(elem) {
|
||||
if (browser.slow) {
|
||||
return false;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import ServerConnections from '../ServerConnections';
|
||||
backdropImage.classList.add('backdropImageFadeIn');
|
||||
parent.appendChild(backdropImage);
|
||||
|
||||
if (!enableAnimation()) {
|
||||
if (!enableAnimation(backdropImage)) {
|
||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ button::-moz-focus-inner {
|
||||
.card.show-focus:not(.show-animation) .cardBox.visualCardBox,
|
||||
.card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable {
|
||||
border: 0.5em solid transparent;
|
||||
border-radius: 0.7em; /* card border + card border-radius */
|
||||
}
|
||||
|
||||
.card.show-animation:focus > .cardBox {
|
||||
@@ -150,21 +149,17 @@ button::-moz-focus-inner {
|
||||
left: 0.3em;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
font-size: 88%;
|
||||
font-weight: 500;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
background: rgb(51, 136, 204);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cardImageContainer {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
border-radius: 0.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -199,7 +194,6 @@ button::-moz-focus-inner {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0.2em;
|
||||
|
||||
/* Needed in case this is a button */
|
||||
display: block;
|
||||
@@ -227,17 +221,13 @@ button::-moz-focus-inner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.cardPadder {
|
||||
position: relative; // For centering the cardImageIcon
|
||||
}
|
||||
|
||||
.cardBox:not(.visualCardBox) .cardPadder {
|
||||
border-radius: 0.2em;
|
||||
background-color: #242424;
|
||||
}
|
||||
|
||||
.blurhash-canvas {
|
||||
border-radius: 0.2em;
|
||||
.visualCardBox .cardContent {
|
||||
border-top-left-radius: 0.2em;
|
||||
border-top-right-radius: 0.2em;
|
||||
}
|
||||
|
||||
.cardContent-shadow,
|
||||
@@ -273,7 +263,7 @@ button::-moz-focus-inner {
|
||||
|
||||
.visualCardBox {
|
||||
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
|
||||
border-radius: 0.2em;
|
||||
border-radius: 0.145em;
|
||||
}
|
||||
|
||||
.innerCardFooter {
|
||||
@@ -337,7 +327,6 @@ button::-moz-focus-inner {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.innerCardFooter > .cardText {
|
||||
@@ -360,8 +349,7 @@ button::-moz-focus-inner {
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.cardTextCentered,
|
||||
.cardTextCentered > .textActionButton {
|
||||
.cardTextCentered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -381,18 +369,6 @@ button::-moz-focus-inner {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cardPadder .cardImageIcon {
|
||||
color: #111;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.cardImageContainer .cardImageIcon {
|
||||
margin: auto; /* 'justify-content: center' doesn't work in Safari 10 */
|
||||
}
|
||||
|
||||
.cardIndicators {
|
||||
right: 0.225em;
|
||||
top: 0.225em;
|
||||
@@ -793,14 +769,6 @@ button::-moz-focus-inner {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
user-select: none;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.visualCardBox .blurhash-canvas,
|
||||
.visualCardBox .cardContent,
|
||||
.visualCardBox .cardOverlayContainer {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.card-hoverable:hover .cardOverlayContainer {
|
||||
@@ -5,7 +5,6 @@
|
||||
* @module components/cardBuilder/cardBuilder
|
||||
*/
|
||||
|
||||
import escapeHtml from 'escape-html';
|
||||
import datetime from '../../scripts/datetime';
|
||||
import imageLoader from '../images/imageLoader';
|
||||
import itemHelper from '../itemHelper';
|
||||
@@ -18,9 +17,9 @@ import browser from '../../scripts/browser';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import itemShortcuts from '../shortcuts';
|
||||
import imageHelper from '../../scripts/imagehelper';
|
||||
import './card.scss';
|
||||
import './card.css';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../guide/programs.scss';
|
||||
import '../guide/programs.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
@@ -146,7 +145,7 @@ import ServerConnections from '../ServerConnections';
|
||||
return 100 / 14.2857142857;
|
||||
}
|
||||
if (screenWidth >= 1200) {
|
||||
return 100 / 16.66666667;
|
||||
return 100 / 16.666666666666666666;
|
||||
}
|
||||
if (screenWidth >= 1000) {
|
||||
return 5;
|
||||
@@ -482,20 +481,12 @@ import ServerConnections from '../ServerConnections';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CardImageUrl
|
||||
* @property {string} imgUrl - Image URL.
|
||||
* @property {string} blurhash - Image blurhash.
|
||||
* @property {boolean} forceName - Force name.
|
||||
* @property {boolean} coverImage - Use cover style.
|
||||
*/
|
||||
|
||||
/** Get the URL of the card's image.
|
||||
* @param {Object} item - Item for which to generate a card.
|
||||
* @param {Object} apiClient - API client object.
|
||||
* @param {Object} options - Options of the card.
|
||||
* @param {string} shape - Shape of the desired image.
|
||||
* @returns {CardImageUrl} Object representing the URL of the card's image.
|
||||
* @returns {Object} Object representing the URL of the card's image.
|
||||
*/
|
||||
function getCardImageUrl(item, apiClient, options, shape) {
|
||||
item = item.ProgramInfo || item;
|
||||
@@ -648,7 +639,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
/**
|
||||
* Generates an index used to select the default color of a card based on a string.
|
||||
* @param {?string} [str] - String to use for generating the index.
|
||||
* @param {string} str - String to use for generating the index.
|
||||
* @returns {number} Index of the color.
|
||||
*/
|
||||
function getDefaultColorIndex(str) {
|
||||
@@ -735,8 +726,8 @@ import ServerConnections from '../ServerConnections';
|
||||
/**
|
||||
* Returns the air time text for the item based on the given times.
|
||||
* @param {object} item - Item used to generate the air time text.
|
||||
* @param {boolean} showAirDateTime - ISO8601 date for the start of the show.
|
||||
* @param {boolean} showAirEndTime - ISO8601 date for the end of the show.
|
||||
* @param {string} showAirDateTime - ISO8601 date for the start of the show.
|
||||
* @param {string} showAirEndTime - ISO8601 date for the end of the show.
|
||||
* @returns {string} The air time text for the item based on the given dates.
|
||||
*/
|
||||
function getAirTimeText(item, showAirDateTime, showAirEndTime) {
|
||||
@@ -780,7 +771,6 @@ import ServerConnections from '../ServerConnections';
|
||||
* @returns {string} HTML markup of the card's footer text element.
|
||||
*/
|
||||
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
|
||||
item = item.ProgramInfo || item;
|
||||
let html = '';
|
||||
|
||||
if (logoUrl) {
|
||||
@@ -791,7 +781,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
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" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`;
|
||||
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,20 +804,20 @@ import ServerConnections from '../ServerConnections';
|
||||
IsFolder: true
|
||||
}));
|
||||
} else {
|
||||
lines.push(escapeHtml(item.SeriesName));
|
||||
lines.push(item.SeriesName);
|
||||
}
|
||||
} else {
|
||||
if (isUsingLiveTvNaming(item)) {
|
||||
lines.push(escapeHtml(item.Name));
|
||||
lines.push(item.Name);
|
||||
|
||||
if (!item.EpisodeTitle && !item.IndexNumber) {
|
||||
if (!item.EpisodeTitle) {
|
||||
titleAdded = true;
|
||||
}
|
||||
} else {
|
||||
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
|
||||
|
||||
if (parentTitle || showTitle) {
|
||||
lines.push(escapeHtml(parentTitle));
|
||||
lines.push(parentTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -861,14 +851,10 @@ import ServerConnections from '../ServerConnections';
|
||||
item.AlbumArtists[0].IsFolder = true;
|
||||
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
|
||||
} else {
|
||||
lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')));
|
||||
lines.push(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''));
|
||||
}
|
||||
}
|
||||
|
||||
if (item.ExtraType && item.ExtraType !== 'Unknown') {
|
||||
lines.push(globalize.translate(item.ExtraType));
|
||||
}
|
||||
|
||||
if (options.showItemCounts) {
|
||||
lines.push(getItemCountsHtml(options, item));
|
||||
}
|
||||
@@ -949,13 +935,13 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
}, item.ChannelName));
|
||||
} else {
|
||||
lines.push(escapeHtml(item.ChannelName || '') || ' ');
|
||||
lines.push(item.ChannelName || ' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.showCurrentProgram && item.Type === 'TvChannel') {
|
||||
if (item.CurrentProgram) {
|
||||
lines.push(escapeHtml(item.CurrentProgram.Name));
|
||||
lines.push(item.CurrentProgram.Name);
|
||||
} else {
|
||||
lines.push('');
|
||||
}
|
||||
@@ -981,13 +967,13 @@ import ServerConnections from '../ServerConnections';
|
||||
if (item.RecordAnyChannel) {
|
||||
lines.push(globalize.translate('AllChannels'));
|
||||
} else {
|
||||
lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel'));
|
||||
lines.push(item.ChannelName || globalize.translate('OneChannel'));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.showPersonRoleOrType) {
|
||||
if (item.Role) {
|
||||
lines.push(globalize.translate('PersonRole', escapeHtml(item.Role)));
|
||||
lines.push(globalize.translate('PersonRole', item.Role));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -997,7 +983,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (overlayText && showTitle) {
|
||||
lines = [escapeHtml(item.Name)];
|
||||
lines = [item.Name];
|
||||
}
|
||||
|
||||
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
|
||||
@@ -1032,8 +1018,6 @@ import ServerConnections from '../ServerConnections';
|
||||
text = itemHelper.getDisplayName(item);
|
||||
}
|
||||
|
||||
text = escapeHtml(text);
|
||||
|
||||
if (layoutManager.tv) {
|
||||
return text;
|
||||
}
|
||||
@@ -1140,7 +1124,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
/**
|
||||
* Returns the default background class for a card based on a string.
|
||||
* @param {?string} [str] - Text used to generate the background class.
|
||||
* @param {string} str - Text used to generate the background class.
|
||||
* @returns {string} CSS classes for default card backgrounds.
|
||||
*/
|
||||
export function getDefaultBackgroundClass(str) {
|
||||
@@ -1312,15 +1296,15 @@ import ServerConnections from '../ServerConnections';
|
||||
const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction';
|
||||
|
||||
if (options.centerPlayButton) {
|
||||
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass} cardOverlayButton-centered" data-action="play" title="${globalize.translate('Play')}"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>`;
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayButton-centered" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
|
||||
}
|
||||
|
||||
if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') {
|
||||
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="play" title="${globalize.translate('Play')}"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>`;
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
|
||||
}
|
||||
|
||||
if (options.overlayMoreButton) {
|
||||
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons cardOverlayButtonIcon more_vert" aria-hidden="true"></span></button>`;
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_vert"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1347,28 +1331,15 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
cardImageContainerClose = '</div>';
|
||||
} else {
|
||||
const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
|
||||
|
||||
// Don't use the IMG tag with safari because it puts a white border around it
|
||||
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + cardImageContainerAriaLabelAttribute + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction"' + cardImageContainerAriaLabelAttribute + '>');
|
||||
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
|
||||
|
||||
cardImageContainerClose = '</button>';
|
||||
}
|
||||
|
||||
const cardScalableClass = 'cardScalable';
|
||||
|
||||
let cardPadderIcon = '';
|
||||
|
||||
// TV Channel logos are transparent so skip the placeholder to avoid overlapping
|
||||
if (imgUrl && item.Type !== 'TvChannel') {
|
||||
cardPadderIcon = getDefaultText(item, {
|
||||
// Always use an icon
|
||||
defaultCardImageIcon: 'folder',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
cardImageContainerOpen = `<div class="${cardBoxClass}"><div class="${cardScalableClass}"><div class="cardPadder cardPadder-${shape}">${cardPadderIcon}</div>${cardImageContainerOpen}`;
|
||||
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
|
||||
cardBoxClose = '</div>';
|
||||
cardScalableClose = '</div>';
|
||||
|
||||
@@ -1425,12 +1396,10 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
let actionAttribute;
|
||||
let ariaLabelAttribute = '';
|
||||
|
||||
if (tagName === 'button') {
|
||||
className += ' itemAction';
|
||||
actionAttribute = ' data-action="' + action + '"';
|
||||
ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
|
||||
} else {
|
||||
actionAttribute = '';
|
||||
}
|
||||
@@ -1445,7 +1414,7 @@ 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="' + escapeHtml(item.Path) + '"') : '';
|
||||
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() + '"') : '';
|
||||
@@ -1457,7 +1426,7 @@ import ServerConnections from '../ServerConnections';
|
||||
additionalCardContent += getHoverMenuHtml(item, action);
|
||||
}
|
||||
|
||||
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="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + 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 + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1474,7 +1443,7 @@ import ServerConnections from '../ServerConnections';
|
||||
const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light';
|
||||
|
||||
if (playbackManager.canPlay(item)) {
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow" aria-hidden="true"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow"></span></button>';
|
||||
}
|
||||
|
||||
html += '<div class="cardOverlayButton-br flex">';
|
||||
@@ -1484,7 +1453,7 @@ import ServerConnections from '../ServerConnections';
|
||||
if (itemHelper.canMarkPlayed(item)) {
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-playstatebutton/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" aria-hidden="true"></span></button>';
|
||||
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)) {
|
||||
@@ -1492,10 +1461,10 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-ratingbutton/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" aria-hidden="true"></span></button>';
|
||||
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>';
|
||||
}
|
||||
|
||||
html += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert" aria-hidden="true"></span></button>`;
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -1510,44 +1479,39 @@ import ServerConnections from '../ServerConnections';
|
||||
*/
|
||||
export function getDefaultText(item, options) {
|
||||
if (item.CollectionType) {
|
||||
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '"></span>';
|
||||
}
|
||||
|
||||
switch (item.Type) {
|
||||
case 'MusicAlbum':
|
||||
return '<span class="cardImageIcon material-icons album" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons album"></span>';
|
||||
case 'MusicArtist':
|
||||
case 'Person':
|
||||
return '<span class="cardImageIcon material-icons person" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons person"></span>';
|
||||
case 'Audio':
|
||||
return '<span class="cardImageIcon material-icons audiotrack" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons audiotrack"></span>';
|
||||
case 'Movie':
|
||||
return '<span class="cardImageIcon material-icons movie" aria-hidden="true"></span>';
|
||||
case 'Episode':
|
||||
return '<span class="cardImageIcon material-icons movie"></span>';
|
||||
case 'Series':
|
||||
return '<span class="cardImageIcon material-icons tv" aria-hidden="true"></span>';
|
||||
case 'Program':
|
||||
return '<span class="cardImageIcon material-icons live_tv" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons tv"></span>';
|
||||
case 'Book':
|
||||
return '<span class="cardImageIcon material-icons book" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons book"></span>';
|
||||
case 'Folder':
|
||||
return '<span class="cardImageIcon material-icons folder" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons folder"></span>';
|
||||
case 'BoxSet':
|
||||
return '<span class="cardImageIcon material-icons collections" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons collections"></span>';
|
||||
case 'Playlist':
|
||||
return '<span class="cardImageIcon material-icons view_list" aria-hidden="true"></span>';
|
||||
case 'Photo':
|
||||
return '<span class="cardImageIcon material-icons photo" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons view_list"></span>';
|
||||
case 'PhotoAlbum':
|
||||
return '<span class="cardImageIcon material-icons photo_album" aria-hidden="true"></span>';
|
||||
return '<span class="cardImageIcon material-icons photo_album"></span>';
|
||||
}
|
||||
|
||||
if (options?.defaultCardImageIcon) {
|
||||
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '" aria-hidden="true"></span>';
|
||||
if (options && options.defaultCardImageIcon) {
|
||||
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '"></span>';
|
||||
}
|
||||
|
||||
const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item);
|
||||
return '<div class="cardText cardDefaultText">' + escapeHtml(defaultName) + '</div>';
|
||||
return '<div class="cardText cardDefaultText">' + defaultName + '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1640,7 +1604,7 @@ import ServerConnections from '../ServerConnections';
|
||||
indicatorsElem = ensureIndicators(card, indicatorsElem);
|
||||
indicatorsElem.appendChild(playedIndicator);
|
||||
}
|
||||
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check" aria-hidden="true"></span>';
|
||||
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
|
||||
} else {
|
||||
playedIndicator = card.querySelector('.playedIndicator');
|
||||
if (playedIndicator) {
|
||||
@@ -1723,7 +1687,7 @@ import ServerConnections from '../ServerConnections';
|
||||
const icon = cell.querySelector('.timerIndicator');
|
||||
if (!icon) {
|
||||
const indicatorsElem = ensureIndicators(cell);
|
||||
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record" aria-hidden="true"></span>');
|
||||
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record"></span>');
|
||||
}
|
||||
cell.setAttribute('data-timerid', newTimerId);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @module components/cardBuilder/chaptercardbuilder
|
||||
*/
|
||||
|
||||
import escapeHtml from 'escape-html';
|
||||
import datetime from '../../scripts/datetime';
|
||||
import imageLoader from '../images/imageLoader';
|
||||
import layoutManager from '../layoutManager';
|
||||
@@ -95,11 +94,11 @@ import ServerConnections from '../ServerConnections';
|
||||
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
|
||||
|
||||
if (!imgUrl) {
|
||||
cardImageContainer += '<span class="material-icons cardImageIcon local_movies" aria-hidden="true"></span>';
|
||||
cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
|
||||
}
|
||||
|
||||
let nameHtml = '';
|
||||
nameHtml += `<div class="cardText">${escapeHtml(chapter.Name)}</div>`;
|
||||
nameHtml += `<div class="cardText">${chapter.Name}</div>`;
|
||||
nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
|
||||
|
||||
const cardBoxCssClass = 'cardBox';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import dom from '../../scripts/dom';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import loading from '../loading/loading';
|
||||
@@ -7,9 +6,9 @@ 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.scss';
|
||||
import '../listview/listview.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../formdialog.scss';
|
||||
import '../formdialog.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
|
||||
export default class channelMapper {
|
||||
@@ -30,7 +29,7 @@ export default class channelMapper {
|
||||
}).then(mapping => {
|
||||
const listItem = dom.parentWithClass(button, 'listItem');
|
||||
button.setAttribute('data-providerid', mapping.ProviderChannelId);
|
||||
listItem.querySelector('.secondary').innerText = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
|
||||
listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
@@ -73,20 +72,20 @@ export default class channelMapper {
|
||||
function getTunerChannelHtml(channel, providerName) {
|
||||
let html = '';
|
||||
html += '<div class="listItem">';
|
||||
html += '<span class="material-icons listItemIcon dvr" aria-hidden="true"></span>';
|
||||
html += '<span class="material-icons listItemIcon dvr"></span>';
|
||||
html += '<div class="listItemBody two-line">';
|
||||
html += '<h3 class="listItemBodyText">';
|
||||
html += escapeHtml(channel.Name);
|
||||
html += channel.Name;
|
||||
html += '</h3>';
|
||||
html += '<div class="secondary listItemBodyText">';
|
||||
|
||||
if (channel.ProviderChannelName) {
|
||||
html += escapeHtml(getMappingSecondaryName(channel, providerName));
|
||||
html += getMappingSecondaryName(channel, providerName);
|
||||
}
|
||||
|
||||
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" aria-hidden="true"></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>';
|
||||
}
|
||||
|
||||
@@ -128,7 +127,7 @@ export default class channelMapper {
|
||||
let html = '';
|
||||
const title = globalize.translate('MapChannels');
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import dom from '../../scripts/dom';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import loading from '../loading/loading';
|
||||
@@ -11,7 +10,7 @@ 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.scss';
|
||||
import '../formdialog.css';
|
||||
import '../../assets/css/flexstyles.scss';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import toast from '../toast/toast';
|
||||
@@ -113,7 +112,7 @@ import toast from '../toast/toast';
|
||||
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
|
||||
|
||||
html += result.Items.map(i => {
|
||||
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
|
||||
return `<option value="${i.Id}">${i.Name}</option>`;
|
||||
});
|
||||
|
||||
select.innerHTML = html;
|
||||
@@ -230,7 +229,7 @@ import toast from '../toast/toast';
|
||||
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" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { appRouter } from '../appRouter';
|
||||
import browser from '../../scripts/browser';
|
||||
import dialog from '../dialog/dialog';
|
||||
import globalize from '../../scripts/globalize';
|
||||
@@ -7,16 +6,7 @@ function replaceAll(str, find, replace) {
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
function useNativeConfirm() {
|
||||
// webOS seems to block modals
|
||||
// Tizen 2.x seems to block modals
|
||||
return !browser.web0s
|
||||
&& !(browser.tizenVersion && browser.tizenVersion < 3)
|
||||
&& browser.tv
|
||||
&& window.confirm;
|
||||
}
|
||||
|
||||
async function nativeConfirm(options) {
|
||||
function nativeConfirm(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
@@ -25,7 +15,6 @@ async function nativeConfirm(options) {
|
||||
}
|
||||
|
||||
const text = replaceAll(options.text || '', '<br/>', '\n');
|
||||
await appRouter.ready();
|
||||
const result = window.confirm(text);
|
||||
|
||||
if (result) {
|
||||
@@ -35,7 +24,7 @@ async function nativeConfirm(options) {
|
||||
}
|
||||
}
|
||||
|
||||
async function customConfirm(text, title) {
|
||||
function customConfirm(text, title) {
|
||||
let options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
@@ -62,8 +51,6 @@ async function customConfirm(text, title) {
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
await appRouter.ready();
|
||||
|
||||
return dialog.show(options).then(result => {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
@@ -73,6 +60,6 @@ async function customConfirm(text, title) {
|
||||
});
|
||||
}
|
||||
|
||||
const confirm = useNativeConfirm() ? nativeConfirm : customConfirm;
|
||||
const confirm = browser.tv && window.confirm ? nativeConfirm : customConfirm;
|
||||
|
||||
export default confirm;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import datetime from '../../../scripts/datetime';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createButtonElement = (index: number) => ({
|
||||
__html: `<button
|
||||
type='button'
|
||||
is='paper-icon-button-light'
|
||||
class='btnDelete listItemButton'
|
||||
data-index='${index}'
|
||||
>
|
||||
<span class='material-icons delete' aria-hidden='true' />
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
index: number;
|
||||
Id: number;
|
||||
DayOfWeek?: string;
|
||||
StartHour?: number ;
|
||||
EndHour?: number;
|
||||
}
|
||||
|
||||
function getDisplayTime(hours = 0) {
|
||||
let minutes = 0;
|
||||
const pct = hours % 1;
|
||||
|
||||
if (pct) {
|
||||
minutes = Math.floor(60 * pct);
|
||||
}
|
||||
|
||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
||||
}
|
||||
|
||||
const AccessScheduleList: FunctionComponent<IProps> = ({index, DayOfWeek, StartHour, EndHour}: IProps) => {
|
||||
return (
|
||||
<div
|
||||
className='liSchedule listItem'
|
||||
data-day={ DayOfWeek}
|
||||
data-start={ StartHour}
|
||||
data-end={ EndHour}
|
||||
>
|
||||
<div className='listItemBody two-line'>
|
||||
<h3 className='listItemBodyText'>
|
||||
{globalize.translate(DayOfWeek)}
|
||||
</h3>
|
||||
<div className='listItemBodyText secondary'>
|
||||
{getDisplayTime(StartHour) + ' - ' + getDisplayTime(EndHour)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessScheduleList;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
const createButtonElement = (tag?: string) => ({
|
||||
__html: `<button
|
||||
type='button'
|
||||
is='paper-icon-button-light'
|
||||
class='blockedTag btnDeleteTag listItemButton'
|
||||
data-tag='${tag}'
|
||||
>
|
||||
<span class='material-icons delete' aria-hidden='true' />
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
const BlockedTagList: FunctionComponent<IProps> = ({tag}: IProps) => {
|
||||
return (
|
||||
<div className='paperList'>
|
||||
<div className='listItem'>
|
||||
<div className='listItemBody'>
|
||||
<h3 className='listItemBodyText'>
|
||||
{tag}
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement(tag)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockedTagList;
|
||||
@@ -1,32 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createButtonElement = ({ type, className, title }: { type?: string, className?: string, title?: string }) => ({
|
||||
__html: `<button
|
||||
is="emby-button"
|
||||
type="${type}"
|
||||
class="${className}"
|
||||
>
|
||||
<span>${title}</span>
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
type?: string;
|
||||
className?: string;
|
||||
title?: string
|
||||
}
|
||||
|
||||
const ButtonElement: FunctionComponent<IProps> = ({ type, className, title }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement({
|
||||
type: type,
|
||||
className: className,
|
||||
title: globalize.translate(title)
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ButtonElement;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createCheckBoxElement = ({ labelClassName, type, className, title }: { labelClassName?: string, type?: string, className?: string, title?: string }) => ({
|
||||
__html: `<label class="${labelClassName}">
|
||||
<input
|
||||
is="emby-checkbox"
|
||||
type="${type}"
|
||||
class="${className}"
|
||||
/>
|
||||
<span>${title}</span>
|
||||
</label>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
labelClassName?: string;
|
||||
type?: string;
|
||||
className?: string;
|
||||
title?: string
|
||||
}
|
||||
|
||||
const CheckBoxElement: FunctionComponent<IProps> = ({ labelClassName, type, className, title }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
className='sectioncheckbox'
|
||||
dangerouslySetInnerHTML={createCheckBoxElement({
|
||||
labelClassName: labelClassName ? labelClassName : '',
|
||||
type: type,
|
||||
className: className,
|
||||
title: globalize.translate(title)
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckBoxElement;
|
||||
@@ -1,41 +0,0 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
Name?: string;
|
||||
Id?: string;
|
||||
ItemType?: string;
|
||||
AppName?: string;
|
||||
checkedAttribute?: string;
|
||||
}
|
||||
|
||||
const createCheckBoxElement = ({className, Name, dataAttributes, AppName, checkedAttribute}: {className?: string, Name?: string, dataAttributes?: string, AppName?: string, checkedAttribute?: string}) => ({
|
||||
__html: `<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
is="emby-checkbox"
|
||||
class="${className}"
|
||||
${dataAttributes} ${checkedAttribute}
|
||||
/>
|
||||
<span>${escapeHtml(Name || '')} ${AppName}</span>
|
||||
</label>`
|
||||
});
|
||||
|
||||
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id, ItemType, AppName, checkedAttribute}: IProps) => {
|
||||
return (
|
||||
<div
|
||||
className='sectioncheckbox'
|
||||
dangerouslySetInnerHTML={createCheckBoxElement({
|
||||
className: className,
|
||||
Name: Name,
|
||||
dataAttributes: ItemType ? `data-itemtype='${ItemType}'` : `data-id='${Id}'`,
|
||||
AppName: AppName ? `- ${AppName}` : '',
|
||||
checkedAttribute: checkedAttribute
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckBoxListItem;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createInputElement = ({ type, id, label, options }: { type?: string, id?: string, label?: string, options?: string }) => ({
|
||||
__html: `<input
|
||||
is="emby-input"
|
||||
type="${type}"
|
||||
id="${id}"
|
||||
label="${label}"
|
||||
${options}
|
||||
/>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
type?: string;
|
||||
id?: string;
|
||||
label?: string;
|
||||
options?: string
|
||||
}
|
||||
|
||||
const InputElement: FunctionComponent<IProps> = ({ type, id, label, options }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createInputElement({
|
||||
type: type,
|
||||
id: id,
|
||||
label: globalize.translate(label),
|
||||
options: options ? options : ''
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputElement;
|
||||
@@ -1,30 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
type IProps = {
|
||||
title?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const createLinkElement = ({ className, title }: IProps) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
class="${className}"
|
||||
href='#'
|
||||
>
|
||||
${title}
|
||||
</a>`
|
||||
});
|
||||
|
||||
const LinkEditUserPreferences: FunctionComponent<IProps> = ({ className, title }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createLinkElement({
|
||||
className: className,
|
||||
title: globalize.translate(title)
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinkEditUserPreferences;
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
type IProps = {
|
||||
activeTab: string;
|
||||
}
|
||||
|
||||
const createLinkElement = (activeTab: string) => ({
|
||||
__html: `<a href="#"
|
||||
is="emby-linkbutton"
|
||||
data-role="button"
|
||||
class="${activeTab === 'useredit' ? 'ui-btn-active' : ''}"
|
||||
onclick="Dashboard.navigate('useredit.html', true);">
|
||||
${globalize.translate('Profile')}
|
||||
</a>
|
||||
<a href="#"
|
||||
is="emby-linkbutton"
|
||||
data-role="button"
|
||||
class="${activeTab === 'userlibraryaccess' ? 'ui-btn-active' : ''}"
|
||||
onclick="Dashboard.navigate('userlibraryaccess.html', true);">
|
||||
${globalize.translate('TabAccess')}
|
||||
</a>
|
||||
<a href="#"
|
||||
is="emby-linkbutton"
|
||||
data-role="button"
|
||||
class="${activeTab === 'userparentalcontrol' ? 'ui-btn-active' : ''}"
|
||||
onclick="Dashboard.navigate('userparentalcontrol.html', true);">
|
||||
${globalize.translate('TabParentalControl')}
|
||||
</a>
|
||||
<a href="#"
|
||||
is="emby-linkbutton"
|
||||
data-role="button"
|
||||
class="${activeTab === 'userpassword' ? 'ui-btn-active' : ''}"
|
||||
onclick="Dashboard.navigate('userpassword.html', true);">
|
||||
${globalize.translate('HeaderPassword')}
|
||||
</a>`
|
||||
});
|
||||
|
||||
const SectionTabs: FunctionComponent<IProps> = ({activeTab}: IProps) => {
|
||||
return (
|
||||
<div
|
||||
data-role='controlgroup'
|
||||
data-type='horizontal'
|
||||
className='localnav'
|
||||
dangerouslySetInnerHTML={createLinkElement(activeTab)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionTabs;
|
||||
@@ -1,34 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
type IProps = {
|
||||
title: string;
|
||||
className?: string;
|
||||
icon: string,
|
||||
}
|
||||
|
||||
const createButtonElement = ({ className, title, icon }: { className?: string, title: string, icon: string }) => ({
|
||||
__html: `<button
|
||||
is="emby-button"
|
||||
type="button"
|
||||
class="${className}"
|
||||
style="margin-left:1em;"
|
||||
title="${title}"
|
||||
>
|
||||
<span class="material-icons ${icon}" aria-hidden="true"></span>
|
||||
</button>`
|
||||
});
|
||||
|
||||
const SectionTitleButtonElement: FunctionComponent<IProps> = ({ className, title, icon }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement({
|
||||
className: className,
|
||||
title: globalize.translate(title),
|
||||
icon: icon
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionTitleButtonElement;
|
||||
@@ -1,34 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createLinkElement = ({ className, title, href }: { className?: string, title?: string, href?: string }) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
rel="noopener noreferrer"
|
||||
class="${className}"
|
||||
target="_blank"
|
||||
href="${href}"
|
||||
>
|
||||
${title}
|
||||
</a>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
title?: string;
|
||||
className?: string;
|
||||
url?: string
|
||||
}
|
||||
|
||||
const SectionTitleLinkElement: FunctionComponent<IProps> = ({ className, title, url }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createLinkElement({
|
||||
className: className,
|
||||
title: globalize.translate(title),
|
||||
href: url
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionTitleLinkElement;
|
||||
@@ -1,44 +0,0 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string[] }) => ({
|
||||
__html: `<select
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
label="${label}"
|
||||
>
|
||||
${option}
|
||||
</select>`
|
||||
});
|
||||
|
||||
type ProvidersArr = {
|
||||
Name?: string;
|
||||
Id?: string;
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
label?: string;
|
||||
currentProviderId: string;
|
||||
providers: ProvidersArr[]
|
||||
}
|
||||
|
||||
const SelectElement: FunctionComponent<IProps> = ({ className, label, currentProviderId, providers }: IProps) => {
|
||||
const renderOption = providers.map((provider) => {
|
||||
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
|
||||
return '<option value="' + provider.Id + '"' + selected + '>' + escapeHtml(provider.Name) + '</option>';
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createSelectElement({
|
||||
className: className,
|
||||
label: globalize.translate(label),
|
||||
option: renderOption
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectElement;
|
||||
@@ -1,47 +0,0 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string }) => ({
|
||||
__html: `<select
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
label="${label}"
|
||||
>
|
||||
<option value=''></option>
|
||||
${option}
|
||||
</select>`
|
||||
});
|
||||
|
||||
type RatingsArr = {
|
||||
Name: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
label?: string;
|
||||
parentalRatings: RatingsArr[];
|
||||
}
|
||||
|
||||
const SelectMaxParentalRating: FunctionComponent<IProps> = ({ className, label, parentalRatings }: IProps) => {
|
||||
const renderOption = () => {
|
||||
let content = '';
|
||||
for (const rating of parentalRatings) {
|
||||
content += `<option value='${rating.Value}'>${escapeHtml(rating.Name)}</option>`;
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createSelectElement({
|
||||
className: className,
|
||||
label: globalize.translate(label),
|
||||
option: renderOption()
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectMaxParentalRating;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, id, label }: { className?: string, id?: string, label: string }) => ({
|
||||
__html: `<select
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
id="${id}"
|
||||
label="${label}"
|
||||
>
|
||||
<option value='CreateAndJoinGroups'>${globalize.translate('LabelSyncPlayAccessCreateAndJoinGroups')}</option>
|
||||
<option value='JoinGroups'>${globalize.translate('LabelSyncPlayAccessJoinGroups')}</option>
|
||||
<option value='None'>${globalize.translate('LabelSyncPlayAccessNone')}</option>
|
||||
</select>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
id?: string;
|
||||
label?: string
|
||||
}
|
||||
|
||||
const SelectSyncPlayAccessElement: FunctionComponent<IProps> = ({ className, id, label }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createSelectElement({
|
||||
className: className,
|
||||
id: id,
|
||||
label: globalize.translate(label)
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectSyncPlayAccessElement;
|
||||
@@ -1,101 +0,0 @@
|
||||
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { getLocaleWithSuffix } from '../../../scripts/dfnshelper';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import cardBuilder from '../../cardbuilder/cardBuilder';
|
||||
|
||||
const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl: string }) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
class="cardContent"
|
||||
href="#!/useredit.html?userId=${user.Id}"
|
||||
>
|
||||
${renderImgUrl}
|
||||
</a>`
|
||||
});
|
||||
|
||||
const createButtonElement = () => ({
|
||||
__html: `<button
|
||||
is="paper-icon-button-light"
|
||||
type="button"
|
||||
class="btnUserMenu flex-shrink-zero"
|
||||
>
|
||||
<span class="material-icons more_vert" aria-hidden="true"></span>
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
user?: UserDto;
|
||||
}
|
||||
|
||||
const getLastSeenText = (lastActivityDate?: string | null) => {
|
||||
if (lastActivityDate) {
|
||||
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), getLocaleWithSuffix()));
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
||||
let cssClass = 'card squareCard scalableCard squareCard-scalable';
|
||||
|
||||
if (user.Policy?.IsDisabled) {
|
||||
cssClass += ' grayscale';
|
||||
}
|
||||
|
||||
let imgUrl;
|
||||
|
||||
if (user.PrimaryImageTag && user.Id) {
|
||||
imgUrl = window.ApiClient.getUserImageUrl(user.Id, {
|
||||
width: 300,
|
||||
tag: user.PrimaryImageTag,
|
||||
type: 'Primary'
|
||||
});
|
||||
}
|
||||
|
||||
let imageClass = 'cardImage';
|
||||
|
||||
if (user.Policy?.IsDisabled) {
|
||||
imageClass += ' disabledUser';
|
||||
}
|
||||
|
||||
const lastSeen = getLastSeenText(user.LastActivityDate);
|
||||
|
||||
const renderImgUrl = imgUrl ?
|
||||
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
|
||||
`<div class='${imageClass} ${cardBuilder.getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
|
||||
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
|
||||
</div>`;
|
||||
|
||||
return (
|
||||
<div data-userid={user.Id} className={cssClass}>
|
||||
<div className='cardBox visualCardBox'>
|
||||
<div className='cardScalable visualCardBox-cardScalable'>
|
||||
<div className='cardPadder cardPadder-square'></div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createLinkElement({
|
||||
user: user,
|
||||
renderImgUrl: renderImgUrl
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className='cardFooter visualCardBox-cardFooter'>
|
||||
<div className='cardText flex align-items-center'>
|
||||
<div className='flex-grow' style={{overflow: 'hidden', textOverflow: 'ellipsis'}}>
|
||||
{user.Name}
|
||||
</div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement()}
|
||||
/>
|
||||
</div>
|
||||
<div className='cardText cardText-secondary'>
|
||||
{lastSeen != '' ? lastSeen : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserCardBox;
|
||||
@@ -1,310 +0,0 @@
|
||||
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react';
|
||||
import Dashboard from '../../../scripts/clientUtils';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import LibraryMenu from '../../../scripts/libraryMenu';
|
||||
import confirm from '../../confirm/confirm';
|
||||
import loading from '../../loading/loading';
|
||||
import toast from '../../toast/toast';
|
||||
import ButtonElement from './ButtonElement';
|
||||
import CheckBoxElement from './CheckBoxElement';
|
||||
import InputElement from './InputElement';
|
||||
|
||||
type IProps = {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const loadUser = useCallback(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
window.ApiClient.getUser(userId).then(function (user) {
|
||||
Dashboard.getCurrentUser().then(function (loggedInUser: UserDto) {
|
||||
if (!user.Policy) {
|
||||
throw new Error('Unexpected null user.Policy');
|
||||
}
|
||||
|
||||
if (!user.Configuration) {
|
||||
throw new Error('Unexpected null user.Configuration');
|
||||
}
|
||||
|
||||
LibraryMenu.setTitle(user.Name);
|
||||
|
||||
let showLocalAccessSection = false;
|
||||
|
||||
if (user.HasConfiguredPassword) {
|
||||
(page.querySelector('.btnResetPassword') as HTMLDivElement).classList.remove('hide');
|
||||
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.remove('hide');
|
||||
showLocalAccessSection = true;
|
||||
} else {
|
||||
(page.querySelector('.btnResetPassword') as HTMLDivElement).classList.add('hide');
|
||||
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.add('hide');
|
||||
}
|
||||
|
||||
if (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess) {
|
||||
(page.querySelector('.passwordSection') as HTMLDivElement).classList.remove('hide');
|
||||
} else {
|
||||
(page.querySelector('.passwordSection') as HTMLDivElement).classList.add('hide');
|
||||
}
|
||||
|
||||
if (showLocalAccessSection && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
(page.querySelector('.localAccessSection') as HTMLDivElement).classList.remove('hide');
|
||||
} else {
|
||||
(page.querySelector('.localAccessSection') as HTMLDivElement).classList.add('hide');
|
||||
}
|
||||
|
||||
const txtEasyPassword = page.querySelector('#txtEasyPassword') as HTMLInputElement;
|
||||
txtEasyPassword.value = '';
|
||||
|
||||
if (user.HasConfiguredEasyPassword) {
|
||||
txtEasyPassword.placeholder = '******';
|
||||
(page.querySelector('.btnResetEasyPassword') as HTMLDivElement).classList.remove('hide');
|
||||
} else {
|
||||
txtEasyPassword.removeAttribute('placeholder');
|
||||
txtEasyPassword.placeholder = '';
|
||||
(page.querySelector('.btnResetEasyPassword') as HTMLDivElement).classList.add('hide');
|
||||
}
|
||||
|
||||
const chkEnableLocalEasyPassword = page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement;
|
||||
|
||||
chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false;
|
||||
|
||||
import('../../autoFocuser').then(({default: autoFocuser}) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
(page.querySelector('#txtCurrentPassword') as HTMLInputElement).value = '';
|
||||
(page.querySelector('#txtNewPassword') as HTMLInputElement).value = '';
|
||||
(page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value = '';
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadUser();
|
||||
|
||||
const onSubmit = (e: Event) => {
|
||||
if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value != (page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value) {
|
||||
toast(globalize.translate('PasswordMatchError'));
|
||||
} else {
|
||||
loading.show();
|
||||
savePassword();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
const savePassword = () => {
|
||||
let currentPassword = (page.querySelector('#txtCurrentPassword') as HTMLInputElement).value;
|
||||
const newPassword = (page.querySelector('#txtNewPassword') as HTMLInputElement).value;
|
||||
|
||||
if ((page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.contains('hide')) {
|
||||
// Firefox does not respect autocomplete=off, so clear it if the field is supposed to be hidden (and blank)
|
||||
// This should only happen when user.HasConfiguredPassword is false, but this information is not passed on
|
||||
currentPassword = '';
|
||||
}
|
||||
|
||||
window.ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () {
|
||||
loading.hide();
|
||||
toast(globalize.translate('PasswordSaved'));
|
||||
|
||||
loadUser();
|
||||
}, function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
title: globalize.translate('HeaderLoginFailure'),
|
||||
message: globalize.translate('MessageInvalidUser')
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onLocalAccessSubmit = (e: Event) => {
|
||||
loading.show();
|
||||
saveEasyPassword();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
const saveEasyPassword = () => {
|
||||
const easyPassword = (page.querySelector('#txtEasyPassword') as HTMLInputElement).value;
|
||||
|
||||
if (easyPassword) {
|
||||
window.ApiClient.updateEasyPassword(userId, easyPassword).then(function () {
|
||||
onEasyPasswordSaved();
|
||||
});
|
||||
} else {
|
||||
onEasyPasswordSaved();
|
||||
}
|
||||
};
|
||||
|
||||
const onEasyPasswordSaved = () => {
|
||||
window.ApiClient.getUser(userId).then(function (user) {
|
||||
if (!user.Configuration) {
|
||||
throw new Error('Unexpected null user.Configuration');
|
||||
}
|
||||
|
||||
if (!user.Id) {
|
||||
throw new Error('Unexpected null user.Id');
|
||||
}
|
||||
|
||||
user.Configuration.EnableLocalPassword = (page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement).checked;
|
||||
window.ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () {
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
|
||||
loadUser();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const resetEasyPassword = () => {
|
||||
const msg = globalize.translate('PinCodeResetConfirmation');
|
||||
|
||||
confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () {
|
||||
loading.show();
|
||||
window.ApiClient.resetEasyPassword(userId).then(function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
message: globalize.translate('PinCodeResetComplete'),
|
||||
title: globalize.translate('HeaderPinCodeReset')
|
||||
});
|
||||
loadUser();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const resetPassword = () => {
|
||||
const msg = globalize.translate('PasswordResetConfirmation');
|
||||
confirm(msg, globalize.translate('ResetPassword')).then(function () {
|
||||
loading.show();
|
||||
window.ApiClient.resetUserPassword(userId).then(function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
message: globalize.translate('PasswordResetComplete'),
|
||||
title: globalize.translate('ResetPassword')
|
||||
});
|
||||
loadUser();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
(page.querySelector('.updatePasswordForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||
(page.querySelector('.localAccessForm') as HTMLFormElement).addEventListener('submit', onLocalAccessSubmit);
|
||||
|
||||
(page.querySelector('.btnResetEasyPassword') as HTMLButtonElement).addEventListener('click', resetEasyPassword);
|
||||
(page.querySelector('.btnResetPassword') as HTMLButtonElement).addEventListener('click', resetPassword);
|
||||
}, [loadUser, userId]);
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<form
|
||||
className='updatePasswordForm passwordSection hide'
|
||||
style={{margin: '0 auto 2em'}}
|
||||
>
|
||||
<div className='detailSection'>
|
||||
<div id='fldCurrentPassword' className='inputContainer hide'>
|
||||
<InputElement
|
||||
type='password'
|
||||
id='txtCurrentPassword'
|
||||
label='LabelCurrentPassword'
|
||||
options={'autoComplete="off"'}
|
||||
/>
|
||||
</div>
|
||||
<div className='inputContainer'>
|
||||
<InputElement
|
||||
type='password'
|
||||
id='txtNewPassword'
|
||||
label='LabelNewPassword'
|
||||
options={'autoComplete="off"'}
|
||||
/>
|
||||
</div>
|
||||
<div className='inputContainer'>
|
||||
<InputElement
|
||||
type='password'
|
||||
id='txtNewPasswordConfirm'
|
||||
label='LabelNewPasswordConfirm'
|
||||
options={'autoComplete="off"'}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<ButtonElement
|
||||
type='submit'
|
||||
className='raised button-submit block'
|
||||
title='Save'
|
||||
/>
|
||||
<ButtonElement
|
||||
type='button'
|
||||
className='raised btnResetPassword button-cancel block hide'
|
||||
title='ResetPassword'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<form
|
||||
className='localAccessForm localAccessSection'
|
||||
style={{margin: '0 auto'}}
|
||||
>
|
||||
<div className='detailSection'>
|
||||
<div className='detailSectionHeader'>
|
||||
{globalize.translate('HeaderEasyPinCode')}
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
{globalize.translate('EasyPasswordHelp')}
|
||||
</div>
|
||||
<br />
|
||||
<div className='inputContainer'>
|
||||
<InputElement
|
||||
type='number'
|
||||
id='txtEasyPassword'
|
||||
label='LabelEasyPinCode'
|
||||
options={'autoComplete="off" pattern="[0-9]*" step="1" maxlength="5"'}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div className='checkboxContainer checkboxContainer-withDescription'>
|
||||
<CheckBoxElement
|
||||
type='checkbox'
|
||||
className='chkEnableLocalEasyPassword'
|
||||
title='LabelInNetworkSignInWithEasyPassword'
|
||||
/>
|
||||
<div className='fieldDescription checkboxFieldDescription'>
|
||||
{globalize.translate('LabelInNetworkSignInWithEasyPasswordHelp')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ButtonElement
|
||||
type='submit'
|
||||
className='raised button-submit block'
|
||||
title='Save'
|
||||
/>
|
||||
<ButtonElement
|
||||
type='button'
|
||||
className='raised btnResetEasyPassword button-cancel block hide'
|
||||
title='ButtonResetEasyPassword'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPasswordForm;
|
||||
@@ -1,5 +1,3 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import escapeHtml from 'escape-html';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import dom from '../../scripts/dom';
|
||||
import layoutManager from '../layoutManager';
|
||||
@@ -9,7 +7,7 @@ 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.scss';
|
||||
import '../formdialog.css';
|
||||
import '../../assets/css/flexstyles.scss';
|
||||
import template from './dialog.template.html';
|
||||
|
||||
@@ -49,13 +47,13 @@ import template from './dialog.template.html';
|
||||
}
|
||||
|
||||
if (options.title) {
|
||||
dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || '';
|
||||
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
|
||||
} else {
|
||||
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
|
||||
}
|
||||
|
||||
const displayText = options.html || options.text || '';
|
||||
dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText);
|
||||
dlg.querySelector('.text').innerHTML = displayText;
|
||||
|
||||
if (!displayText) {
|
||||
dlg.querySelector('.dialogContentInner').classList.add('hide');
|
||||
@@ -84,7 +82,7 @@ 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}>${escapeHtml(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>`;
|
||||
|
||||
@@ -4,8 +4,8 @@ import browser from '../../scripts/browser';
|
||||
import layoutManager from '../layoutManager';
|
||||
import inputManager from '../../scripts/inputManager';
|
||||
import dom from '../../scripts/dom';
|
||||
import './dialoghelper.scss';
|
||||
import '../../assets/css/scrollstyles.scss';
|
||||
import './dialoghelper.css';
|
||||
import '../../assets/css/scrollstyles.css';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
@@ -48,7 +48,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
const activeElement = document.activeElement;
|
||||
let removeScrollLockOnClose = false;
|
||||
|
||||
function onHashChange() {
|
||||
function onHashChange(e) {
|
||||
const isBack = self.originalUrl === window.location.href;
|
||||
|
||||
if (isBack || !isOpened(dlg)) {
|
||||
@@ -87,7 +87,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
if (!self.closedByBack && isHistoryEnabled(dlg)) {
|
||||
const state = window.history.state || {};
|
||||
if (state.dialogId === hash) {
|
||||
appRouter.back();
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
animateDialogOpen(dlg);
|
||||
|
||||
if (isHistoryEnabled(dlg)) {
|
||||
appRouter.show(`/dialog?dlg=${hash}`, { dialogId: hash });
|
||||
appRouter.pushState({ dialogId: hash }, 'Dialog', `#${hash}`);
|
||||
|
||||
window.addEventListener('popstate', onHashChange);
|
||||
} else {
|
||||
@@ -200,7 +200,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
dlg.dialogContainer = dialogContainer;
|
||||
document.body.appendChild(dialogContainer);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
|
||||
});
|
||||
}
|
||||
@@ -213,7 +213,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
export function close(dlg) {
|
||||
if (isOpened(dlg)) {
|
||||
if (isHistoryEnabled(dlg)) {
|
||||
appRouter.back();
|
||||
window.history.back();
|
||||
} else {
|
||||
closeDialog(dlg);
|
||||
}
|
||||
@@ -265,7 +265,6 @@ import '../../assets/css/scrollstyles.scss';
|
||||
dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
|
||||
once: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -379,7 +378,7 @@ import '../../assets/css/scrollstyles.scss';
|
||||
dlg.setAttribute('data-lockscroll', 'true');
|
||||
}
|
||||
|
||||
if (options.enableHistory !== false) {
|
||||
if (options.enableHistory === true) {
|
||||
dlg.setAttribute('data-history', 'true');
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@
|
||||
right: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,301 +1,312 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import loading from '../loading/loading';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import dom from '../../scripts/dom';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import '../listview/listview.scss';
|
||||
import '../listview/listview.css';
|
||||
import '../../elements/emby-input/emby-input';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import './directorybrowser.scss';
|
||||
import '../formdialog.scss';
|
||||
import './directorybrowser.css';
|
||||
import '../formdialog.css';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import alert from '../alert';
|
||||
|
||||
function getSystemInfo() {
|
||||
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
|
||||
info => {
|
||||
systemInfo = info;
|
||||
return info;
|
||||
/* eslint-disable indent */
|
||||
|
||||
function getSystemInfo() {
|
||||
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
|
||||
info => {
|
||||
systemInfo = info;
|
||||
return info;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
||||
if (path && typeof path !== 'string') {
|
||||
throw new Error('invalid path');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
loading.hide();
|
||||
}
|
||||
loading.show();
|
||||
|
||||
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
||||
if (path && typeof path !== 'string') {
|
||||
throw new Error('invalid path');
|
||||
}
|
||||
|
||||
loading.show();
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (path) {
|
||||
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
||||
promises.push(ApiClient.getParentPath(path));
|
||||
} else {
|
||||
promises.push(ApiClient.getDrives());
|
||||
}
|
||||
|
||||
Promise.all(promises).then(
|
||||
responses => {
|
||||
const folders = responses[0];
|
||||
const parentPath = (responses[1] ? JSON.parse(responses[1]) : '') || '';
|
||||
let html = '';
|
||||
|
||||
page.querySelector('.results').scrollTop = 0;
|
||||
page.querySelector('#txtDirectoryPickerPath').value = path || '';
|
||||
const promises = [];
|
||||
|
||||
if (path === 'Network') {
|
||||
promises.push(ApiClient.getNetworkDevices());
|
||||
} else {
|
||||
if (path) {
|
||||
html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
|
||||
}
|
||||
for (let i = 0, length = folders.length; i < length; i++) {
|
||||
const folder = folders[i];
|
||||
const cssClass = folder.Type === 'File' ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
|
||||
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
|
||||
}
|
||||
|
||||
page.querySelector('.results').innerHTML = html;
|
||||
loading.hide();
|
||||
}, () => {
|
||||
if (updatePathOnError) {
|
||||
page.querySelector('#txtDirectoryPickerPath').value = '';
|
||||
page.querySelector('.results').innerHTML = '';
|
||||
loading.hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getItem(cssClass, type, path, name) {
|
||||
let html = '';
|
||||
html += `<div class="listItem listItem-border ${cssClass}" data-type="${type}" data-path="${escapeHtml(path)}">`;
|
||||
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
|
||||
html += '<div class="listItemBodyText">';
|
||||
html += escapeHtml(name);
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<span class="material-icons arrow_forward" aria-hidden="true" style="font-size:inherit;"></span>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function getEditorHtml(options, systemInfo) {
|
||||
let html = '';
|
||||
html += '<div class="formDialogContent scrollY">';
|
||||
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
|
||||
if (!options.pathReadOnly) {
|
||||
const instruction = options.instruction ? `${escapeHtml(options.instruction)}<br/><br/>` : '';
|
||||
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
|
||||
html += instruction;
|
||||
if (systemInfo.OperatingSystem.toLowerCase() === 'bsd') {
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
html += globalize.translate('MessageDirectoryPickerBSDInstruction');
|
||||
html += '<br/>';
|
||||
} else if (systemInfo.OperatingSystem.toLowerCase() === 'linux') {
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
html += globalize.translate('MessageDirectoryPickerLinuxInstruction');
|
||||
html += '<br/>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<form style="margin:auto;">';
|
||||
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
|
||||
html += '<div style="flex-grow:1;">';
|
||||
let labelKey;
|
||||
if (options.includeFiles !== true) {
|
||||
labelKey = 'LabelFolder';
|
||||
} else {
|
||||
labelKey = 'LabelPath';
|
||||
}
|
||||
const readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
|
||||
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search" aria-hidden="true"></span></button>`;
|
||||
}
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>';
|
||||
}
|
||||
if (options.enableNetworkSharePath) {
|
||||
html += '<div class="inputContainer" style="margin-top:2em;">';
|
||||
html += `<input is="emby-input" id="txtNetworkPath" type="text" label="${globalize.translate('LabelOptionalNetworkPath')}"/>`;
|
||||
html += '<div class="fieldDescription">';
|
||||
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<div class="formDialogFooter">';
|
||||
html += `<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">${globalize.translate('ButtonOk')}</button>`;
|
||||
html += '</div>';
|
||||
html += '</form>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function alertText(text) {
|
||||
alertTextWithOptions({
|
||||
text: text
|
||||
});
|
||||
}
|
||||
|
||||
function alertTextWithOptions(options) {
|
||||
alert(options);
|
||||
}
|
||||
|
||||
function validatePath(path, validateWriteable, apiClient) {
|
||||
return apiClient.ajax({
|
||||
type: 'POST',
|
||||
url: apiClient.getUrl('Environment/ValidatePath'),
|
||||
data: JSON.stringify({
|
||||
ValidateWriteable: validateWriteable,
|
||||
Path: path
|
||||
}),
|
||||
contentType: 'application/json'
|
||||
}).catch(response => {
|
||||
if (response) {
|
||||
if (response.status === 404) {
|
||||
alertText(globalize.translate('PathNotFound'));
|
||||
return Promise.reject();
|
||||
}
|
||||
if (response.status === 500) {
|
||||
if (validateWriteable) {
|
||||
alertText(globalize.translate('WriteAccessRequired'));
|
||||
} else {
|
||||
alertText(globalize.translate('PathNotFound'));
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function initEditor(content, options, fileOptions) {
|
||||
content.addEventListener('click', e => {
|
||||
const lnkPath = dom.parentWithClass(e.target, 'lnkPath');
|
||||
if (lnkPath) {
|
||||
const path = lnkPath.getAttribute('data-path');
|
||||
if (lnkPath.classList.contains('lnkFile')) {
|
||||
content.querySelector('#txtDirectoryPickerPath').value = path;
|
||||
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
||||
promises.push(ApiClient.getParentPath(path));
|
||||
} else {
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true);
|
||||
promises.push(ApiClient.getDrives());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.addEventListener('click', e => {
|
||||
if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
|
||||
const path = content.querySelector('#txtDirectoryPickerPath').value;
|
||||
refreshDirectoryBrowser(content, path, fileOptions);
|
||||
}
|
||||
});
|
||||
Promise.all(promises).then(
|
||||
responses => {
|
||||
const folders = responses[0];
|
||||
const parentPath = responses[1] || '';
|
||||
let html = '';
|
||||
|
||||
content.addEventListener('change', e => {
|
||||
const txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
|
||||
if (txtDirectoryPickerPath && txtDirectoryPickerPath.id === 'txtDirectoryPickerPath') {
|
||||
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
|
||||
}
|
||||
});
|
||||
page.querySelector('.results').scrollTop = 0;
|
||||
page.querySelector('#txtDirectoryPickerPath').value = path || '';
|
||||
|
||||
content.querySelector('form').addEventListener('submit', function(e) {
|
||||
if (options.callback) {
|
||||
let networkSharePath = this.querySelector('#txtNetworkPath');
|
||||
networkSharePath = networkSharePath ? networkSharePath.value : null;
|
||||
const path = this.querySelector('#txtDirectoryPickerPath').value;
|
||||
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (path) {
|
||||
html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
|
||||
}
|
||||
for (let i = 0, length = folders.length; i < length; i++) {
|
||||
const folder = folders[i];
|
||||
const cssClass = folder.Type === 'File' ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
|
||||
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
|
||||
}
|
||||
|
||||
function getDefaultPath(options) {
|
||||
if (options.path) {
|
||||
return Promise.resolve(options.path);
|
||||
} else {
|
||||
return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
|
||||
result => {
|
||||
return result.Path || '';
|
||||
if (!path) {
|
||||
html += getItem('lnkPath lnkDirectory', '', 'Network', globalize.translate('ButtonNetwork'));
|
||||
}
|
||||
|
||||
page.querySelector('.results').innerHTML = html;
|
||||
loading.hide();
|
||||
}, () => {
|
||||
return '';
|
||||
if (updatePathOnError) {
|
||||
page.querySelector('#txtDirectoryPickerPath').value = '';
|
||||
page.querySelector('.results').innerHTML = '';
|
||||
loading.hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let systemInfo;
|
||||
class DirectoryBrowser {
|
||||
currentDialog;
|
||||
function getItem(cssClass, type, path, name) {
|
||||
let html = '';
|
||||
html += `<div class="listItem listItem-border ${cssClass}" data-type="${type}" data-path="${path}">`;
|
||||
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
|
||||
html += '<div class="listItemBodyText">';
|
||||
html += name;
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<span class="material-icons arrow_forward" style="font-size:inherit;"></span>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
show = options => {
|
||||
options = options || {};
|
||||
const fileOptions = {
|
||||
includeDirectories: true
|
||||
};
|
||||
if (options.includeDirectories != null) {
|
||||
fileOptions.includeDirectories = options.includeDirectories;
|
||||
function getEditorHtml(options, systemInfo) {
|
||||
let html = '';
|
||||
html += '<div class="formDialogContent scrollY">';
|
||||
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
|
||||
if (!options.pathReadOnly) {
|
||||
const instruction = options.instruction ? `${options.instruction}<br/><br/>` : '';
|
||||
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
|
||||
html += instruction;
|
||||
if (systemInfo.OperatingSystem.toLowerCase() === 'bsd') {
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
html += globalize.translate('MessageDirectoryPickerBSDInstruction');
|
||||
html += '<br/>';
|
||||
} else if (systemInfo.OperatingSystem.toLowerCase() === 'linux') {
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
html += globalize.translate('MessageDirectoryPickerLinuxInstruction');
|
||||
html += '<br/>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
if (options.includeFiles != null) {
|
||||
fileOptions.includeFiles = options.includeFiles;
|
||||
html += '<form style="margin:auto;">';
|
||||
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
|
||||
html += '<div style="flex-grow:1;">';
|
||||
let labelKey;
|
||||
if (options.includeFiles !== true) {
|
||||
labelKey = 'LabelFolder';
|
||||
} else {
|
||||
labelKey = 'LabelPath';
|
||||
}
|
||||
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
|
||||
responses => {
|
||||
const systemInfo = responses[0];
|
||||
const initialPath = responses[1];
|
||||
const dlg = dialogHelper.createDialog({
|
||||
size: 'small',
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
});
|
||||
dlg.classList.add('ui-body-a');
|
||||
dlg.classList.add('background-theme-a');
|
||||
dlg.classList.add('directoryPicker');
|
||||
dlg.classList.add('formDialog');
|
||||
const readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
|
||||
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search"></span></button>`;
|
||||
}
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>';
|
||||
}
|
||||
if (options.enableNetworkSharePath) {
|
||||
html += '<div class="inputContainer" style="margin-top:2em;">';
|
||||
html += `<input is="emby-input" id="txtNetworkPath" type="text" label="${globalize.translate('LabelOptionalNetworkPath')}"/>`;
|
||||
html += '<div class="fieldDescription">';
|
||||
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<div class="formDialogFooter">';
|
||||
html += `<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">${globalize.translate('ButtonOk')}</button>`;
|
||||
html += '</div>';
|
||||
html += '</form>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
let html = '';
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += `<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += escapeHtml(options.header || '') || globalize.translate('HeaderSelectPath');
|
||||
html += '</h3>';
|
||||
html += '</div>';
|
||||
html += getEditorHtml(options, systemInfo);
|
||||
dlg.innerHTML = html;
|
||||
initEditor(dlg, options, fileOptions);
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.querySelector('.btnCloseDialog').addEventListener('click', () => {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
this.currentDialog = dlg;
|
||||
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
|
||||
const txtNetworkPath = dlg.querySelector('#txtNetworkPath');
|
||||
if (txtNetworkPath) {
|
||||
txtNetworkPath.value = options.networkSharePath || '';
|
||||
return html;
|
||||
}
|
||||
|
||||
function alertText(text) {
|
||||
alertTextWithOptions({
|
||||
text: text
|
||||
});
|
||||
}
|
||||
|
||||
function alertTextWithOptions(options) {
|
||||
alert(options);
|
||||
}
|
||||
|
||||
function validatePath(path, validateWriteable, apiClient) {
|
||||
return apiClient.ajax({
|
||||
type: 'POST',
|
||||
url: apiClient.getUrl('Environment/ValidatePath'),
|
||||
data: JSON.stringify({
|
||||
ValidateWriteable: validateWriteable,
|
||||
Path: path
|
||||
}),
|
||||
contentType: 'application/json'
|
||||
}).catch(response => {
|
||||
if (response) {
|
||||
if (response.status === 404) {
|
||||
alertText(globalize.translate('PathNotFound'));
|
||||
return Promise.reject();
|
||||
}
|
||||
if (!options.pathReadOnly) {
|
||||
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
|
||||
if (response.status === 500) {
|
||||
if (validateWriteable) {
|
||||
alertText(globalize.translate('WriteAccessRequired'));
|
||||
} else {
|
||||
alertText(globalize.translate('PathNotFound'));
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
close = () => {
|
||||
if (this.currentDialog) {
|
||||
dialogHelper.close(this.currentDialog);
|
||||
function initEditor(content, options, fileOptions) {
|
||||
content.addEventListener('click', e => {
|
||||
const lnkPath = dom.parentWithClass(e.target, 'lnkPath');
|
||||
if (lnkPath) {
|
||||
const path = lnkPath.getAttribute('data-path');
|
||||
if (lnkPath.classList.contains('lnkFile')) {
|
||||
content.querySelector('#txtDirectoryPickerPath').value = path;
|
||||
} else {
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.addEventListener('click', e => {
|
||||
if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
|
||||
const path = content.querySelector('#txtDirectoryPickerPath').value;
|
||||
refreshDirectoryBrowser(content, path, fileOptions);
|
||||
}
|
||||
});
|
||||
|
||||
content.addEventListener('change', e => {
|
||||
const txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
|
||||
if (txtDirectoryPickerPath && txtDirectoryPickerPath.id === 'txtDirectoryPickerPath') {
|
||||
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
|
||||
}
|
||||
});
|
||||
|
||||
content.querySelector('form').addEventListener('submit', function(e) {
|
||||
if (options.callback) {
|
||||
let networkSharePath = this.querySelector('#txtNetworkPath');
|
||||
networkSharePath = networkSharePath ? networkSharePath.value : null;
|
||||
const path = this.querySelector('#txtDirectoryPickerPath').value;
|
||||
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultPath(options) {
|
||||
if (options.path) {
|
||||
return Promise.resolve(options.path);
|
||||
} else {
|
||||
return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
|
||||
result => {
|
||||
return result.Path || '';
|
||||
}, () => {
|
||||
return '';
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default DirectoryBrowser;
|
||||
class directoryBrowser {
|
||||
constructor() {
|
||||
let currentDialog;
|
||||
this.show = options => {
|
||||
options = options || {};
|
||||
const fileOptions = {
|
||||
includeDirectories: true
|
||||
};
|
||||
if (options.includeDirectories != null) {
|
||||
fileOptions.includeDirectories = options.includeDirectories;
|
||||
}
|
||||
if (options.includeFiles != null) {
|
||||
fileOptions.includeFiles = options.includeFiles;
|
||||
}
|
||||
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
|
||||
responses => {
|
||||
const systemInfo = responses[0];
|
||||
const initialPath = responses[1];
|
||||
const dlg = dialogHelper.createDialog({
|
||||
size: 'small',
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
});
|
||||
dlg.classList.add('ui-body-a');
|
||||
dlg.classList.add('background-theme-a');
|
||||
dlg.classList.add('directoryPicker');
|
||||
dlg.classList.add('formDialog');
|
||||
|
||||
let html = '';
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += options.header || globalize.translate('HeaderSelectPath');
|
||||
html += '</h3>';
|
||||
html += '</div>';
|
||||
html += getEditorHtml(options, systemInfo);
|
||||
dlg.innerHTML = html;
|
||||
initEditor(dlg, options, fileOptions);
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.querySelector('.btnCloseDialog').addEventListener('click', () => {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
currentDialog = dlg;
|
||||
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
|
||||
const txtNetworkPath = dlg.querySelector('#txtNetworkPath');
|
||||
if (txtNetworkPath) {
|
||||
txtNetworkPath.value = options.networkSharePath || '';
|
||||
}
|
||||
if (!options.pathReadOnly) {
|
||||
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
this.close = () => {
|
||||
if (currentDialog) {
|
||||
dialogHelper.close(currentDialog);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let systemInfo;
|
||||
|
||||
/* eslint-enable indent */
|
||||
export default directoryBrowser;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import browser from '../../scripts/browser';
|
||||
import layoutManager from '../layoutManager';
|
||||
import { pluginManager } from '../pluginManager';
|
||||
@@ -12,7 +11,6 @@ import { Events } from 'jellyfin-apiclient';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
import '../../elements/emby-checkbox/emby-checkbox';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-textarea/emby-textarea';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import toast from '../toast/toast';
|
||||
import template from './displaySettings.template.html';
|
||||
@@ -22,7 +20,7 @@ import template from './displaySettings.template.html';
|
||||
function fillThemes(select, selectedTheme) {
|
||||
skinManager.getThemes().then(themes => {
|
||||
select.innerHTML = themes.map(t => {
|
||||
return `<option value="${t.id}">${escapeHtml(t.name)}</option>`;
|
||||
return `<option value="${t.id}">${t.name}</option>`;
|
||||
}).join('');
|
||||
|
||||
// get default theme
|
||||
@@ -48,7 +46,7 @@ import template from './displaySettings.template.html';
|
||||
});
|
||||
|
||||
selectScreensaver.innerHTML = options.map(o => {
|
||||
return `<option value="${o.value}">${escapeHtml(o.name)}</option>`;
|
||||
return `<option value="${o.value}">${o.name}</option>`;
|
||||
}).join('');
|
||||
|
||||
selectScreensaver.value = userSettings.screensaver();
|
||||
@@ -101,6 +99,16 @@ import template from './displaySettings.template.html';
|
||||
context.querySelector('.fldDateTimeLocale').classList.add('hide');
|
||||
}
|
||||
|
||||
if (!browser.tizen && !browser.web0s) {
|
||||
context.querySelector('.fldBackdrops').classList.remove('hide');
|
||||
context.querySelector('.fldThemeSong').classList.remove('hide');
|
||||
context.querySelector('.fldThemeVideo').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.fldBackdrops').classList.add('hide');
|
||||
context.querySelector('.fldThemeSong').classList.add('hide');
|
||||
context.querySelector('.fldThemeVideo').classList.add('hide');
|
||||
}
|
||||
|
||||
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
|
||||
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
|
||||
|
||||
@@ -115,18 +123,11 @@ import template from './displaySettings.template.html';
|
||||
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
|
||||
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
|
||||
|
||||
context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss();
|
||||
context.querySelector('#txtLocalCustomCss').value = userSettings.customCss();
|
||||
|
||||
context.querySelector('#selectLanguage').value = userSettings.language() || '';
|
||||
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
|
||||
|
||||
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
|
||||
|
||||
context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp();
|
||||
context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp();
|
||||
context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume();
|
||||
|
||||
context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || '';
|
||||
|
||||
showOrHideMissingEpisodesField(context);
|
||||
@@ -151,18 +152,11 @@ import template from './displaySettings.template.html';
|
||||
|
||||
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
|
||||
|
||||
userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value);
|
||||
userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked);
|
||||
userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked);
|
||||
|
||||
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
|
||||
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
|
||||
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
|
||||
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
|
||||
|
||||
userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked);
|
||||
userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value);
|
||||
|
||||
if (user.Id === apiClient.getCurrentUserId()) {
|
||||
skinManager.setTheme(userSettingsInstance.theme());
|
||||
}
|
||||
|
||||
@@ -1,82 +1,71 @@
|
||||
<form style="margin: 0 auto;">
|
||||
<h2 class="sectionTitle">
|
||||
${Localization}
|
||||
${Display}
|
||||
</h2>
|
||||
|
||||
<div class="selectContainer languageSection hide">
|
||||
<select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}">
|
||||
<option value="">${Auto}</option>
|
||||
<option value="af">Afrikaans</option>
|
||||
<option value="ar">العربية</option>
|
||||
<option value="be-BY">Беларуская</option>
|
||||
<option value="bg-BG">Български</option>
|
||||
<option value="bn_BD">বাংলা (বাংলাদেশ)</option>
|
||||
<option value="ca">Català</option>
|
||||
<option value="cs">Čeština</option>
|
||||
<option value="cy">Cymraeg</option>
|
||||
<option value="da">Dansk</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="el">Ελληνικά</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="sq">Albanian</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian</option>
|
||||
<option value="bn_BD">Bengali (Bangladesh)</option>
|
||||
<option value="bg-BG">Bulgarian</option>
|
||||
<option value="ca">Catalan</option>
|
||||
<option value="zh-HK">Chinese (Hong Kong)</option>
|
||||
<option value="zh-CN">Chinese (Simplified)</option>
|
||||
<option value="zh-TW">Chinese (Traditional)</option>
|
||||
<option value="hr">Croatian</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="da">Danish</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="en-US">English</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="es_419">Español americano</option>
|
||||
<option value="es-AR">Español (Argentina)</option>
|
||||
<option value="es_DO">Español (Dominicana)</option>
|
||||
<option value="es-MX">Español (México)</option>
|
||||
<option value="et">Eesti</option>
|
||||
<option value="eu">Euskara</option>
|
||||
<option value="fa">فارسی</option>
|
||||
<option value="fi">Suomi</option>
|
||||
<option value="fil">Filipino</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="fr-CA">Français (Canada)</option>
|
||||
<option value="gl">Galego</option>
|
||||
<option value="gsw">Schwiizerdütsch</option>
|
||||
<option value="he">עִבְרִית</option>
|
||||
<option value="hi-IN">हिन्दी</option>
|
||||
<option value="hr">Hrvatski </option>
|
||||
<option value="hu">Magyar</option>
|
||||
<option value="id">Bahasa Indonesia</option>
|
||||
<option value="is-IS">Íslenska</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="kk">Qazaqşa</option>
|
||||
<option value="ko">한국어</option>
|
||||
<option value="lt-LT">Lietuvių</option>
|
||||
<option value="lv">Latviešu</option>
|
||||
<option value="mk">Македонски</option>
|
||||
<option value="ml">മലയാളം</option>
|
||||
<option value="mr">मराठी</option>
|
||||
<option value="ms">Bahasa Melayu</option>
|
||||
<option value="nb">Norsk bokmål</option>
|
||||
<option value="ne">नेपाली</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="nn">Norsk nynorsk</option>
|
||||
<option value="pa">ਪੰਜਾਬੀ</option>
|
||||
<option value="pl">Polski</option>
|
||||
<option value="fi">Finnish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="fr-CA">French (Canada)</option>
|
||||
<option value="gl">Galician</option>
|
||||
<option value="de">German</option>
|
||||
<option value="gsw">German (Swiss)</option>
|
||||
<option value="el">Greek</option>
|
||||
<option value="he">Hebrew</option>
|
||||
<option value="hi-IN">Hindi</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="is">Icelandic</option>
|
||||
<option value="id">Indonesian</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="kk">Kazakh</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="lt-LT">Lithuanian</option>
|
||||
<option value="ms">Malay</option>
|
||||
<option value="mr">Marathi</option>
|
||||
<option value="nb">Norwegian Bokmål</option>
|
||||
<option value="fa">Persian</option>
|
||||
<option value="pr">Pirate</option>
|
||||
<option value="pt">Português</option>
|
||||
<option value="pt-BR">Português (Brasil)</option>
|
||||
<option value="pt-PT">Português (Portugal)</option>
|
||||
<option value="ro">Românește</option>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="sk">Slovenčina</option>
|
||||
<option value="sl-SI">Slovenščina</option>
|
||||
<option value="sq">Shqip</option>
|
||||
<option value="sr">Српски</option>
|
||||
<option value="sv">Svenska</option>
|
||||
<option value="ta">தமிழ்</option>
|
||||
<option value="te">తెలుగు</option>
|
||||
<option value="th">ภาษาไทย</option>
|
||||
<option value="tr">Türkçe</option>
|
||||
<option value="uk">Українська</option>
|
||||
<option value="ur_PK">اُردُو</option>
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
<option value="zh-CN">汉语 (简化字)</option>
|
||||
<option value="zh-TW">漢語 (繁体字)</option>
|
||||
<option value="zh-HK">廣東話 (香港)</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="pt-BR">Portuguese (Brazil)</option>
|
||||
<option value="pt-PT">Portuguese (Portugal)</option>
|
||||
<option value="ro">Romanian</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="sk">Slovak</option>
|
||||
<option value="sl-SI">Slovenian (Slovenia)</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="es_AR">Spanish (Argentina)</option>
|
||||
<option value="es_DO">Spanish (Dominican Republic)</option>
|
||||
<option value="es-419">Spanish (Latin America)</option>
|
||||
<option value="es-MX">Spanish (Mexico)</option>
|
||||
<option value="sv">Swedish</option>
|
||||
<option value="ta">Tamil</option>
|
||||
<option value="th">Thai</option>
|
||||
<option value="tr">Turkish</option>
|
||||
<option value="uk">Ukrainian</option>
|
||||
<option value="ur_PK">Urdu (Pakistan)</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
<div class="fieldDescription">
|
||||
<div>${LabelDisplayLanguageHelp}</div>
|
||||
@@ -90,82 +79,68 @@
|
||||
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
|
||||
<option value="">${Auto}</option>
|
||||
<option value="af">Afrikaans</option>
|
||||
<option value="ar">العربية</option>
|
||||
<option value="be-BY">Беларуская</option>
|
||||
<option value="bg-BG">Български</option>
|
||||
<option value="bn_BD">বাংলা (বাংলাদেশ)</option>
|
||||
<option value="ca">Català</option>
|
||||
<option value="cs">Čeština</option>
|
||||
<option value="cy">Cymraeg</option>
|
||||
<option value="da">Dansk</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="el">Ελληνικά</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="sq">Albanian</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian</option>
|
||||
<option value="bn_BD">Bengali (Bangladesh)</option>
|
||||
<option value="bg-BG">Bulgarian</option>
|
||||
<option value="ca">Catalan</option>
|
||||
<option value="zh-HK">Chinese (Hong Kong)</option>
|
||||
<option value="zh-CN">Chinese (Simplified)</option>
|
||||
<option value="zh-TW">Chinese (Traditional)</option>
|
||||
<option value="hr">Croatian</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="da">Danish</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="en-US">English</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="es_419">Español americano</option>
|
||||
<option value="es-AR">Español (Argentina)</option>
|
||||
<option value="es_DO">Español (Dominicana)</option>
|
||||
<option value="es-MX">Español (México)</option>
|
||||
<option value="et">Eesti</option>
|
||||
<option value="fa">فارسی</option>
|
||||
<option value="fi">Suomi</option>
|
||||
<option value="fil">Filipino</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="fr-CA">Français (Canada)</option>
|
||||
<option value="gl">Galego</option>
|
||||
<option value="gsw">Schwiizerdütsch</option>
|
||||
<option value="he">עִבְרִית</option>
|
||||
<option value="hi-IN">हिन्दी</option>
|
||||
<option value="hr">Hrvatski </option>
|
||||
<option value="hu">Magyar</option>
|
||||
<option value="id">Bahasa Indonesia</option>
|
||||
<option value="is-IS">Íslenska</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="kk">Qazaqşa</option>
|
||||
<option value="ko">한국어</option>
|
||||
<option value="lt-LT">Lietuvių</option>
|
||||
<option value="lv">Latviešu</option>
|
||||
<option value="mk">Македонски</option>
|
||||
<option value="ml">മലയാളം</option>
|
||||
<option value="mr">मराठी</option>
|
||||
<option value="ms">Bahasa Melayu</option>
|
||||
<option value="nb">Norsk bokmål</option>
|
||||
<option value="ne">नेपाली</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="nn">Norsk nynorsk</option>
|
||||
<option value="pa">ਪੰਜਾਬੀ</option>
|
||||
<option value="pl">Polski</option>
|
||||
<option value="fi">Finnish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="fr-CA">French (Canada)</option>
|
||||
<option value="gl">Galician</option>
|
||||
<option value="de">German</option>
|
||||
<option value="gsw">German (Swiss)</option>
|
||||
<option value="el">Greek</option>
|
||||
<option value="he">Hebrew</option>
|
||||
<option value="hi-IN">Hindi</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="is">Icelandic</option>
|
||||
<option value="id">Indonesian</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="kk">Kazakh</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="lt-LT">Lithuanian</option>
|
||||
<option value="ms">Malay</option>
|
||||
<option value="mr">Marathi</option>
|
||||
<option value="nb">Norwegian Bokmål</option>
|
||||
<option value="fa">Persian</option>
|
||||
<option value="pr">Pirate</option>
|
||||
<option value="pt">Português</option>
|
||||
<option value="pt-BR">Português (Brasil)</option>
|
||||
<option value="pt-PT">Português (Portugal)</option>
|
||||
<option value="ro">Românește</option>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="sk">Slovenčina</option>
|
||||
<option value="sl-SI">Slovenščina</option>
|
||||
<option value="sq">Shqip</option>
|
||||
<option value="sr">Српски</option>
|
||||
<option value="sv">Svenska</option>
|
||||
<option value="ta">தமிழ்</option>
|
||||
<option value="te">తెలుగు</option>
|
||||
<option value="th">ภาษาไทย</option>
|
||||
<option value="tr">Türkçe</option>
|
||||
<option value="uk">Українська</option>
|
||||
<option value="ur_PK">اُردُو</option>
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
<option value="zh-CN">汉语 (简化字)</option>
|
||||
<option value="zh-TW">漢語 (繁体字)</option>
|
||||
<option value="zh-HK">廣東話 (香港)</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="pt-BR">Portuguese (Brazil)</option>
|
||||
<option value="pt-PT">Portuguese (Portugal)</option>
|
||||
<option value="ro">Romanian</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="sk">Slovak</option>
|
||||
<option value="sl-SI">Slovenian (Slovenia)</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="es_AR">Spanish (Argentina)</option>
|
||||
<option value="es_DO">Spanish (Dominican Republic)</option>
|
||||
<option value="es-419">Spanish (Latin America)</option>
|
||||
<option value="es-MX">Spanish (Mexico)</option>
|
||||
<option value="sv">Swedish</option>
|
||||
<option value="ta">Tamil</option>
|
||||
<option value="th">Thai</option>
|
||||
<option value="tr">Turkish</option>
|
||||
<option value="uk">Ukrainian</option>
|
||||
<option value="ur_PK">Urdu (Pakistan)</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${Display}
|
||||
</h2>
|
||||
|
||||
<div class="selectContainer fldDisplayMode hide">
|
||||
<select is="emby-select" class="selectLayout" label="${LabelDisplayMode}">
|
||||
<option value="">${Auto}</option>
|
||||
@@ -181,19 +156,6 @@
|
||||
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkDisableCustomCss" />
|
||||
<span>${DisableCustomCss}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LabelDisableCustomCss}</div>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer customCssContainer">
|
||||
<textarea is="emby-textarea" id="txtLocalCustomCss" label="${LabelCustomCss}" class="textarea-mono"></textarea>
|
||||
<div class="fieldDescription">${LabelLocalCustomCss}</div>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer selectDashboardThemeContainer hide">
|
||||
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
|
||||
</div>
|
||||
@@ -202,6 +164,11 @@
|
||||
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer inputContainer-withDescription">
|
||||
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
|
||||
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
|
||||
@@ -218,16 +185,15 @@
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableBlurHashHelp}</div>
|
||||
</div>
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${HeaderLibraries}
|
||||
</h2>
|
||||
|
||||
<div class="inputContainer inputContainer-withDescription">
|
||||
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
|
||||
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
|
||||
<span>${EnableDetailsBanner}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableDetailsBannerHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
|
||||
<span>${Backdrops}</span>
|
||||
@@ -235,7 +201,7 @@
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
|
||||
<span>${ThemeSongs}</span>
|
||||
@@ -243,7 +209,7 @@
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />
|
||||
<span>${ThemeVideos}</span>
|
||||
@@ -259,43 +225,6 @@
|
||||
<div class="fieldDescription checkboxFieldDescription">${DisplayMissingEpisodesWithinSeasonsHelp}</div>
|
||||
</div>
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${NextUp}
|
||||
</h2>
|
||||
|
||||
<div class="inputContainer inputContainer-withDescription">
|
||||
<input is="emby-input" type="number" id="txtMaxDaysForNextUp" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelMaxDaysForNextUp}" />
|
||||
<div class="fieldDescription">${LabelMaxDaysForNextUpHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkRewatchingNextUp" />
|
||||
<span>${EnableRewatchingNextUp}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableRewatchingNextUpHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldUseEpisodeImagesInNextUp">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkUseEpisodeImagesInNextUp" />
|
||||
<span>${UseEpisodeImagesInNextUp}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${UseEpisodeImagesInNextUpHelp}</div>
|
||||
</div>
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${ItemDetails}
|
||||
</h2>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
|
||||
<span>${EnableDetailsBanner}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableDetailsBannerHelp}</div>
|
||||
</div>
|
||||
|
||||
<button is="emby-button" type="submit" class="raised button-submit block btnSave hide">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appHost } from './apphost';
|
||||
import imageLoader from './images/imageLoader';
|
||||
import globalize from '../scripts/globalize';
|
||||
import layoutManager from './layoutManager';
|
||||
import '../assets/css/scrollstyles.scss';
|
||||
import '../assets/css/scrollstyles.css';
|
||||
import '../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
/* eslint-disable indent */
|
||||
@@ -145,7 +145,7 @@ import '../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate(section.name);
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '<span class="material-icons chevron_right"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';
|
||||
|
||||
@@ -4,7 +4,7 @@ import globalize from '../../scripts/globalize';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import '../../elements/emby-checkbox/emby-checkbox';
|
||||
import '../../elements/emby-collapse/emby-collapse';
|
||||
import './style.scss';
|
||||
import './style.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import template from './filterdialog.template.html';
|
||||
|
||||
@@ -32,6 +32,9 @@ import template from './filterdialog.template.html';
|
||||
}
|
||||
|
||||
function renderFilters(context, result, query) {
|
||||
if (result.Tags) {
|
||||
result.Tags.length = Math.min(result.Tags.length, 50);
|
||||
}
|
||||
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
|
||||
const delimeter = '|';
|
||||
return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter);
|
||||
@@ -87,7 +90,7 @@ import template from './filterdialog.template.html';
|
||||
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
|
||||
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
|
||||
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
|
||||
context.querySelector('.chkSDFilter').checked = query.IsHD === false;
|
||||
context.querySelector('.chkSDFilter').checked = query.IsHD === true;
|
||||
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
|
||||
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
|
||||
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
|
||||
@@ -272,25 +275,15 @@ import template from './filterdialog.template.html';
|
||||
triggerChange(this);
|
||||
});
|
||||
const chkHDFilter = context.querySelector('.chkHDFilter');
|
||||
const chkSDFilter = context.querySelector('.chkSDFilter');
|
||||
chkHDFilter.addEventListener('change', () => {
|
||||
query.StartIndex = 0;
|
||||
if (chkHDFilter.checked) {
|
||||
chkSDFilter.checked = false;
|
||||
query.IsHD = true;
|
||||
} else {
|
||||
query.IsHD = null;
|
||||
}
|
||||
query.IsHD = chkHDFilter.checked ? true : null;
|
||||
triggerChange(this);
|
||||
});
|
||||
const chkSDFilter = context.querySelector('.chkSDFilter');
|
||||
chkSDFilter.addEventListener('change', () => {
|
||||
query.StartIndex = 0;
|
||||
if (chkSDFilter.checked) {
|
||||
chkHDFilter.checked = false;
|
||||
query.IsHD = false;
|
||||
} else {
|
||||
query.IsHD = null;
|
||||
}
|
||||
query.IsHD = chkSDFilter.checked ? false : null;
|
||||
triggerChange(this);
|
||||
});
|
||||
for (const elem of context.querySelectorAll('.chkStatus')) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
import dom from '../../scripts/dom';
|
||||
import focusManager from '../focusManager';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
@@ -12,7 +11,7 @@ import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../formdialog.scss';
|
||||
import '../formdialog.css';
|
||||
import '../../assets/css/flexstyles.scss';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import template from './filtermenu.template.html';
|
||||
@@ -38,7 +37,7 @@ function renderOptions(context, selector, cssClass, items, isCheckedFn) {
|
||||
const checkedHtml = isCheckedFn(filter) ? ' checked' : '';
|
||||
itemHtml += '<label>';
|
||||
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>';
|
||||
itemHtml += '<span>' + escapeHtml(filter.Name) + '</span>';
|
||||
itemHtml += '<span>' + filter.Name + '</span>';
|
||||
itemHtml += '</label>';
|
||||
|
||||
return itemHtml;
|
||||
@@ -211,7 +210,7 @@ function loadDynamicFilters(context, options) {
|
||||
}
|
||||
class FilterMenu {
|
||||
show(options) {
|
||||
return new Promise( (resolve) => {
|
||||
return new Promise( (resolve, reject) => {
|
||||
const dialogOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
@@ -229,7 +228,7 @@ class FilterMenu {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += `<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">${Filters}</h3>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
@@ -8,7 +8,7 @@ import scrollManager from './scrollManager';
|
||||
scopes.push(elem);
|
||||
}
|
||||
|
||||
function popScope() {
|
||||
function popScope(elem) {
|
||||
if (scopes.length) {
|
||||
scopes.length -= 1;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
}
|
||||
|
||||
.layout-tv .formDialogFooter {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -84,6 +83,11 @@
|
||||
flex-basis: 12em;
|
||||
}
|
||||
|
||||
.layout-tv .formDialogFooterItem {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.formDialogFooterItem-vertical {
|
||||
max-width: none !important;
|
||||
width: 100%;
|
||||
@@ -5,7 +5,7 @@ import layoutManager from '../layoutManager';
|
||||
import scrollHelper from '../../scripts/scrollHelper';
|
||||
import '../../elements/emby-checkbox/emby-checkbox';
|
||||
import '../../elements/emby-radio/emby-radio';
|
||||
import '../formdialog.scss';
|
||||
import '../formdialog.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import template from './guide-settings.template.html';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user