mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-18 11:28:23 -07:00
Merge branch 'master' into disable-embedded-subs
This commit is contained in:
commit
7912daf0c6
@ -21,10 +21,6 @@ jobs:
|
|||||||
displayName: Set release version (stable)
|
displayName: Set release version (stable)
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
|
||||||
- script: './bump-version $(JellyfinVersion)'
|
|
||||||
displayName: Bump internal version (stable)
|
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
|
||||||
|
|
||||||
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
|
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
|
||||||
displayName: 'Build Dockerfile'
|
displayName: 'Build Dockerfile'
|
||||||
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
@ -76,10 +72,6 @@ jobs:
|
|||||||
displayName: Set release version (stable)
|
displayName: Set release version (stable)
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
|
|
||||||
- script: './bump-version $(JellyfinVersion)'
|
|
||||||
displayName: Bump internal version (stable)
|
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
|
||||||
|
|
||||||
- task: Docker@2
|
- task: Docker@2
|
||||||
displayName: 'Push Unstable Image'
|
displayName: 'Push Unstable Image'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
174
.eslintrc.js
174
.eslintrc.js
@ -70,7 +70,92 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
'import/parsers': {
|
'import/parsers': {
|
||||||
'@typescript-eslint/parser': [ '.ts', '.tsx' ]
|
'@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: [
|
overrides: [
|
||||||
{
|
{
|
||||||
@ -122,93 +207,6 @@ module.exports = {
|
|||||||
'Windows': 'readonly'
|
'Windows': 'readonly'
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
},
|
|
||||||
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'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
16
.github/dependabot.yaml
vendored
16
.github/dependabot.yaml
vendored
@ -1,16 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
ignore:
|
|
||||||
- dependency-name: hls.js
|
|
||||||
update-types: [ version-update:semver-major ]
|
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
open-pull-requests-limit: 10
|
|
33
.github/renovate.json
vendored
Normal file
33
.github/renovate.json
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchDepTypes": ["devDependencies"],
|
||||||
|
"groupName": "development dependencies",
|
||||||
|
"groupSlug": "dev-deps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchDepTypes": ["dependencies"],
|
||||||
|
"groupName": "dependencies",
|
||||||
|
"groupSlug": "deps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchDepTypes": ["action"],
|
||||||
|
"groupName": "CI dependencies",
|
||||||
|
"groupSlug": "ci-deps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchPackageNames": ["hls.js"],
|
||||||
|
"matchUpdateTypes": "major",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencyDashboard": false,
|
||||||
|
"ignoreDeps": ["npm", "node"],
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"enabledManagers": ["npm", "github-actions"],
|
||||||
|
"labels": ["dependencies"],
|
||||||
|
"rebaseWhen": "behind-base-branch",
|
||||||
|
"rangeStrategy": "pin"
|
||||||
|
}
|
27
.github/stale.yml
vendored
27
.github/stale.yml
vendored
@ -1,27 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 120
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 21
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- regression
|
|
||||||
- security
|
|
||||||
- roadmap
|
|
||||||
- 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: >
|
|
||||||
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 nightlies, 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).
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
||||||
|
|
||||||
# Disable automatic closing of pull requests
|
|
||||||
pulls:
|
|
||||||
daysUntilClose: false
|
|
2
.github/workflows/commands.yml
vendored
2
.github/workflows/commands.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup node environment
|
- name: Setup node environment
|
||||||
uses: actions/setup-node@v2.4.1
|
uses: actions/setup-node@v2.5.1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 12
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -26,7 +26,7 @@ jobs:
|
|||||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||||
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
id: npm-cache
|
id: npm-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||||
@ -51,7 +51,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup node environment
|
- name: Setup node environment
|
||||||
uses: actions/setup-node@v2.4.1
|
uses: actions/setup-node@v2.5.1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 12
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||||
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
id: npm-cache
|
id: npm-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||||
@ -89,7 +89,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup node environment
|
- name: Setup node environment
|
||||||
uses: actions/setup-node@v2.4.1
|
uses: actions/setup-node@v2.5.1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 12
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -99,7 +99,7 @@ jobs:
|
|||||||
run: echo "::set-output name=dir::$(npm config get cache)"
|
run: echo "::set-output name=dir::$(npm config get cache)"
|
||||||
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
id: npm-cache
|
id: npm-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||||
|
27
.github/workflows/repo-stale.yaml
vendored
Normal file
27
.github/workflows/repo-stale.yaml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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@v4.1.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).
|
@ -4,9 +4,8 @@ $(info $(shell set -x; if [ "$$(id -u)" = "0" ]; then echo "Installing git"; dnf
|
|||||||
NAME := jellyfin-web
|
NAME := jellyfin-web
|
||||||
VERSION := $(shell set -x; sed -ne '/^Version:/s/.* *//p' $(DIR)/$(NAME).spec)
|
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)
|
RELEASE := $(shell set -x; sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR)/$(NAME).spec)
|
||||||
GIT_VER := $(shell set -x; git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//')
|
SRPM := jellyfin-web-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
|
||||||
SRPM := jellyfin-web-$(subst -,~,$(GIT_VER))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
|
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
|
||||||
TARBALL :=$(NAME)-$(subst -,~,$(GIT_VER)).tar.gz
|
|
||||||
|
|
||||||
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
|
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
|
||||||
|
|
||||||
@ -20,9 +19,9 @@ $(DIR)/$(TARBALL):
|
|||||||
cd $(DIR)/; \
|
cd $(DIR)/; \
|
||||||
SOURCE_DIR=.. \
|
SOURCE_DIR=.. \
|
||||||
WORKDIR="$${PWD}"; \
|
WORKDIR="$${PWD}"; \
|
||||||
version=$(GIT_VER); \
|
version=$(VERSION); \
|
||||||
tar \
|
tar \
|
||||||
--transform "s,^\.,$(NAME)-$(subst -,~,$(GIT_VER))," \
|
--transform "s,^\.,$(NAME)-$(subst -,~,$(VERSION))," \
|
||||||
--exclude='.git*' \
|
--exclude='.git*' \
|
||||||
--exclude='**/.git' \
|
--exclude='**/.git' \
|
||||||
--exclude='**/.hg' \
|
--exclude='**/.hg' \
|
||||||
@ -34,7 +33,6 @@ $(DIR)/$(TARBALL):
|
|||||||
-C $${SOURCE_DIR} ./
|
-C $${SOURCE_DIR} ./
|
||||||
|
|
||||||
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin-web.spec
|
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin-web.spec
|
||||||
./bump_version $(GIT_VER)
|
|
||||||
cd $(DIR)/; \
|
cd $(DIR)/; \
|
||||||
rpmbuild -bs $(NAME).spec \
|
rpmbuild -bs $(NAME).spec \
|
||||||
--define "_sourcedir $$PWD/" \
|
--define "_sourcedir $$PWD/" \
|
||||||
|
@ -30,6 +30,10 @@ Jellyfin is a free software media system that puts you in control of managing an
|
|||||||
%build
|
%build
|
||||||
|
|
||||||
%install
|
%install
|
||||||
|
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
|
||||||
|
# Required for CentOS build
|
||||||
|
chown root:root -R .
|
||||||
|
%endif
|
||||||
npm ci --no-audit --unsafe-perm
|
npm ci --no-audit --unsafe-perm
|
||||||
%{__mkdir} -p %{buildroot}%{_datadir}
|
%{__mkdir} -p %{buildroot}%{_datadir}
|
||||||
mv dist %{buildroot}%{_datadir}/jellyfin-web
|
mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||||
|
5707
package-lock.json
generated
5707
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
176
package.json
176
package.json
@ -5,98 +5,98 @@
|
|||||||
"repository": "https://github.com/jellyfin/jellyfin-web",
|
"repository": "https://github.com/jellyfin/jellyfin-web",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.0",
|
"@babel/core": "7.16.7",
|
||||||
"@babel/eslint-parser": "^7.16.3",
|
"@babel/eslint-parser": "7.16.5",
|
||||||
"@babel/eslint-plugin": "^7.14.5",
|
"@babel/eslint-plugin": "7.16.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||||
"@babel/plugin-proposal-private-methods": "^7.16.0",
|
"@babel/plugin-proposal-private-methods": "7.16.7",
|
||||||
"@babel/plugin-transform-modules-umd": "^7.16.0",
|
"@babel/plugin-transform-modules-umd": "7.16.7",
|
||||||
"@babel/preset-env": "^7.16.0",
|
"@babel/preset-env": "7.16.7",
|
||||||
"@babel/preset-react": "^7.16.0",
|
"@babel/preset-react": "7.16.7",
|
||||||
"@babel/preset-typescript": "^7.16.0",
|
"@babel/preset-typescript": "7.16.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
"@typescript-eslint/eslint-plugin": "4.33.0",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@typescript-eslint/parser": "4.33.0",
|
||||||
"@uupaa/dynamic-import-polyfill": "^1.0.2",
|
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "10.4.1",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "8.2.3",
|
||||||
"babel-plugin-dynamic-import-polyfill": "^1.0.0",
|
"babel-plugin-dynamic-import-polyfill": "1.0.0",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "4.0.0",
|
||||||
"confusing-browser-globals": "^1.0.10",
|
"confusing-browser-globals": "1.0.11",
|
||||||
"copy-webpack-plugin": "^9.1.0",
|
"copy-webpack-plugin": "10.2.0",
|
||||||
"css-loader": "^6.5.1",
|
"css-loader": "6.5.1",
|
||||||
"cssnano": "^5.0.10",
|
"cssnano": "5.0.14",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-plugin-compat": "^3.13.0",
|
"eslint-plugin-compat": "4.0.0",
|
||||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
"eslint-plugin-eslint-comments": "3.2.0",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||||
"eslint-plugin-promise": "^5.1.1",
|
"eslint-plugin-promise": "6.0.0",
|
||||||
"eslint-plugin-react": "^7.27.0",
|
"eslint-plugin-react": "7.28.0",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "4.3.0",
|
||||||
"expose-loader": "^3.1.0",
|
"expose-loader": "3.1.0",
|
||||||
"html-loader": "^3.0.1",
|
"html-loader": "3.0.1",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"postcss": "^8.3.11",
|
"postcss": "8.4.5",
|
||||||
"postcss-loader": "^6.2.0",
|
"postcss-loader": "6.2.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "7.2.0",
|
||||||
"postcss-scss": "^4.0.2",
|
"postcss-scss": "4.0.2",
|
||||||
"sass": "^1.43.4",
|
"sass": "1.45.2",
|
||||||
"sass-loader": "^12.3.0",
|
"sass-loader": "12.4.0",
|
||||||
"source-map-loader": "^3.0.0",
|
"source-map-loader": "3.0.1",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "3.3.1",
|
||||||
"stylelint": "^14.1.0",
|
"stylelint": "14.2.0",
|
||||||
"stylelint-config-rational-order": "^0.1.2",
|
"stylelint-config-rational-order": "0.1.2",
|
||||||
"stylelint-no-browser-hacks": "^1.2.1",
|
"stylelint-no-browser-hacks": "1.2.1",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "5.0.0",
|
||||||
"stylelint-scss": "^4.0.0",
|
"stylelint-scss": "4.1.0",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "9.2.6",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "4.5.4",
|
||||||
"webpack": "^5.64.0",
|
"webpack": "5.65.0",
|
||||||
"webpack-cli": "^4.9.1",
|
"webpack-cli": "4.9.1",
|
||||||
"webpack-dev-server": "^4.5.0",
|
"webpack-dev-server": "4.7.2",
|
||||||
"webpack-merge": "^5.8.0",
|
"webpack-merge": "5.8.0",
|
||||||
"workbox-webpack-plugin": "^6.2.4",
|
"workbox-webpack-plugin": "6.2.4",
|
||||||
"worker-plugin": "^5.0.1"
|
"worker-loader": "3.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/noto-sans": "^4.5.1",
|
"@fontsource/noto-sans": "4.5.1",
|
||||||
"@fontsource/noto-sans-hk": "^4.5.2",
|
"@fontsource/noto-sans-hk": "4.5.2",
|
||||||
"@fontsource/noto-sans-jp": "^4.5.2",
|
"@fontsource/noto-sans-jp": "4.5.2",
|
||||||
"@fontsource/noto-sans-kr": "^4.5.2",
|
"@fontsource/noto-sans-kr": "4.5.2",
|
||||||
"@fontsource/noto-sans-sc": "^4.5.2",
|
"@fontsource/noto-sans-sc": "4.5.2",
|
||||||
"blurhash": "^1.1.4",
|
"blurhash": "1.1.4",
|
||||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "2.3.1",
|
||||||
"core-js": "^3.19.1",
|
"core-js": "3.20.2",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "2.28.0",
|
||||||
"dompurify": "^2.3.3",
|
"dompurify": "2.3.4",
|
||||||
"epubjs": "^0.3.90",
|
"epubjs": "0.3.90",
|
||||||
"fast-text-encoding": "^1.0.3",
|
"fast-text-encoding": "1.0.3",
|
||||||
"flv.js": "^1.6.2",
|
"flv.js": "1.6.2",
|
||||||
"headroom.js": "^0.12.0",
|
"headroom.js": "0.12.0",
|
||||||
"hls.js": "^0.14.17",
|
"hls.js": "0.14.17",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "0.12.0",
|
||||||
"jellyfin-apiclient": "^1.9.1",
|
"jellyfin-apiclient": "1.10.0",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "3.6.0",
|
||||||
"jstree": "^3.3.12",
|
"jstree": "3.3.12",
|
||||||
"libarchive.js": "^1.3.0",
|
"libarchive.js": "1.3.0",
|
||||||
"libass-wasm": "git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#4.0.0-jf-4",
|
"libass-wasm": "git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#4.0.0-jf-4",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"marked": "^4.0.3",
|
"marked": "4.0.10",
|
||||||
"material-design-icons-iconfont": "^6.1.1",
|
"material-design-icons-iconfont": "6.1.1",
|
||||||
"native-promise-only": "^0.8.0-a",
|
"native-promise-only": "0.8.1",
|
||||||
"page": "^1.11.6",
|
"page": "1.11.6",
|
||||||
"pdfjs-dist": "2.6.347",
|
"pdfjs-dist": "2.12.313",
|
||||||
"react": "^17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "1.5.1",
|
||||||
"screenfull": "^6.0.0",
|
"screenfull": "6.0.0",
|
||||||
"sortablejs": "^1.14.0",
|
"sortablejs": "1.14.0",
|
||||||
"swiper": "^6.8.4",
|
"swiper": "6.8.4",
|
||||||
"webcomponents.js": "^0.7.24",
|
"webcomponents.js": "0.7.24",
|
||||||
"whatwg-fetch": "^3.6.2",
|
"whatwg-fetch": "3.6.2",
|
||||||
"workbox-core": "^6.2.4",
|
"workbox-core": "6.2.4",
|
||||||
"workbox-precaching": "^6.2.4"
|
"workbox-precaching": "6.2.4"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 2 Firefox versions",
|
"last 2 Firefox versions",
|
||||||
|
1
src/assets/img/devices/apple.svg
Normal file
1
src/assets/img/devices/apple.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 663 B |
@ -280,6 +280,16 @@ import 'material-design-icons-iconfont';
|
|||||||
element.removeEventListener(name, fn);
|
element.removeEventListener(name, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateControls(query) {
|
||||||
|
if (query.NameLessThan) {
|
||||||
|
this.value('#');
|
||||||
|
} else {
|
||||||
|
this.value(query.NameStartsWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible(query.SortBy.indexOf('SortName') === 0);
|
||||||
|
}
|
||||||
|
|
||||||
visible(visible) {
|
visible(visible) {
|
||||||
const element = this.options.element;
|
const element = this.options.element;
|
||||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||||
|
@ -797,6 +797,10 @@ class AppRouter {
|
|||||||
return '#!/list.html?type=Programs&IsAiring=true&serverId=' + options.serverId;
|
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') {
|
if (options.section === 'dvrschedule') {
|
||||||
return '#!/livetv.html?tab=4&serverId=' + options.serverId;
|
return '#!/livetv.html?tab=4&serverId=' + options.serverId;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,6 @@ const supportedFeatures = function () {
|
|||||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||||
features.push('exit');
|
features.push('exit');
|
||||||
} else {
|
} else {
|
||||||
features.push('exitmenu');
|
|
||||||
features.push('plugins');
|
features.push('plugins');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,9 @@ import layoutManager from './layoutManager';
|
|||||||
candidates.push(container.querySelector('.btnPreviousPage'));
|
candidates.push(container.querySelector('.btnPreviousPage'));
|
||||||
} else if (activeElement.classList.contains('btnSelectView')) {
|
} else if (activeElement.classList.contains('btnSelectView')) {
|
||||||
candidates.push(container.querySelector('.btnSelectView'));
|
candidates.push(container.querySelector('.btnSelectView'));
|
||||||
|
} else if (activeElement.classList.contains('btnPlay')) {
|
||||||
|
// Resume has priority over Play
|
||||||
|
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnResume')));
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates.push(activeElement);
|
candidates.push(activeElement);
|
||||||
|
@ -333,6 +333,7 @@ button::-moz-focus-inner {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.innerCardFooter > .cardText {
|
.innerCardFooter > .cardText {
|
||||||
@ -355,7 +356,8 @@ button::-moz-focus-inner {
|
|||||||
background-position: center center;
|
background-position: center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardTextCentered {
|
.cardTextCentered,
|
||||||
|
.cardTextCentered > .textActionButton {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,6 +771,7 @@ import ServerConnections from '../ServerConnections';
|
|||||||
* @returns {string} HTML markup of the card's footer text element.
|
* @returns {string} HTML markup of the card's footer text element.
|
||||||
*/
|
*/
|
||||||
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
|
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
|
||||||
|
item = item.ProgramInfo || item;
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
if (logoUrl) {
|
if (logoUrl) {
|
||||||
@ -855,6 +856,10 @@ import ServerConnections from '../ServerConnections';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.ExtraType && item.ExtraType !== 'Unknown') {
|
||||||
|
lines.push(globalize.translate(item.ExtraType));
|
||||||
|
}
|
||||||
|
|
||||||
if (options.showItemCounts) {
|
if (options.showItemCounts) {
|
||||||
lines.push(getItemCountsHtml(options, item));
|
lines.push(getItemCountsHtml(options, item));
|
||||||
}
|
}
|
||||||
|
60
src/components/dashboard/users/AccessScheduleList.tsx
Normal file
60
src/components/dashboard/users/AccessScheduleList.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import datetime from '../../../scripts/datetime';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
const createButtonElement = ({index}) => ({
|
||||||
|
__html: `<button
|
||||||
|
type='button'
|
||||||
|
is='paper-icon-button-light'
|
||||||
|
class='btnDelete listItemButton'
|
||||||
|
data-index='${index}'
|
||||||
|
>
|
||||||
|
<span class='material-icons delete' />
|
||||||
|
</button>`
|
||||||
|
});
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
index: number;
|
||||||
|
Id: number;
|
||||||
|
DayOfWeek?: string;
|
||||||
|
StartHour?: number ;
|
||||||
|
EndHour?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayTime(hours) {
|
||||||
|
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: index
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessScheduleList;
|
38
src/components/dashboard/users/BlockedTagList.tsx
Normal file
38
src/components/dashboard/users/BlockedTagList.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
|
||||||
|
const createButtonElement = ({tag}) => ({
|
||||||
|
__html: `<button
|
||||||
|
type='button'
|
||||||
|
is='paper-icon-button-light'
|
||||||
|
class='blockedTag btnDeleteTag listItemButton'
|
||||||
|
data-tag='${tag}'
|
||||||
|
>
|
||||||
|
<span class='material-icons delete' />
|
||||||
|
</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: tag
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlockedTagList;
|
@ -1,8 +1,8 @@
|
|||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import globalize from '../../../scripts/globalize';
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
const createCheckBoxElement = ({ type, className, title }) => ({
|
const createCheckBoxElement = ({ labelClassName, type, className, title }) => ({
|
||||||
__html: `<label>
|
__html: `<label class="${labelClassName}">
|
||||||
<input
|
<input
|
||||||
is="emby-checkbox"
|
is="emby-checkbox"
|
||||||
type="${type}"
|
type="${type}"
|
||||||
@ -13,15 +13,18 @@ const createCheckBoxElement = ({ type, className, title }) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
|
labelClassName?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckBoxElement: FunctionComponent<IProps> = ({ type, className, title }: IProps) => {
|
const CheckBoxElement: FunctionComponent<IProps> = ({ labelClassName, type, className, title }: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className='sectioncheckbox'
|
||||||
dangerouslySetInnerHTML={createCheckBoxElement({
|
dangerouslySetInnerHTML={createCheckBoxElement({
|
||||||
|
labelClassName: labelClassName ? labelClassName : '',
|
||||||
type: type,
|
type: type,
|
||||||
className: className,
|
className: className,
|
||||||
title: globalize.translate(title)
|
title: globalize.translate(title)
|
||||||
|
@ -4,27 +4,33 @@ type IProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
Name?: string;
|
Name?: string;
|
||||||
Id?: string;
|
Id?: string;
|
||||||
|
ItemType?: string;
|
||||||
|
AppName?: string;
|
||||||
|
checkedAttribute?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createCheckBoxElement = ({className, Name, Id}) => ({
|
const createCheckBoxElement = ({className, Name, dataAttributes, AppName, checkedAttribute}) => ({
|
||||||
__html: `<label>
|
__html: `<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
is="emby-checkbox"
|
is="emby-checkbox"
|
||||||
class="${className}"
|
class="${className}"
|
||||||
data-id="${Id}"
|
${dataAttributes} ${checkedAttribute}
|
||||||
/>
|
/>
|
||||||
<span>${Name}</span>
|
<span>${Name} ${AppName}</span>
|
||||||
</label>`
|
</label>`
|
||||||
});
|
});
|
||||||
|
|
||||||
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id}: IProps) => {
|
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id, ItemType, AppName, checkedAttribute}: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className='sectioncheckbox'
|
||||||
dangerouslySetInnerHTML={createCheckBoxElement({
|
dangerouslySetInnerHTML={createCheckBoxElement({
|
||||||
className: className,
|
className: className,
|
||||||
Name: Name,
|
Name: Name,
|
||||||
Id: Id
|
dataAttributes: ItemType ? `data-itemtype='${ItemType}'` : `data-id='${Id}'`,
|
||||||
|
AppName: AppName ? `- ${AppName}` : '',
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
30
src/components/dashboard/users/LinkEditUserPreferences.tsx
Normal file
30
src/components/dashboard/users/LinkEditUserPreferences.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
title?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLinkElement = ({ className, title }) => ({
|
||||||
|
__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;
|
52
src/components/dashboard/users/SectionTabs.tsx
Normal file
52
src/components/dashboard/users/SectionTabs.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
activeTab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLinkElement = ({ activeTab }) => ({
|
||||||
|
__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: activeTab
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SectionTabs;
|
@ -1,23 +1,24 @@
|
|||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import globalize from '../../../scripts/globalize';
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
title: string;
|
||||||
|
className?: string;
|
||||||
|
icon: string,
|
||||||
|
}
|
||||||
|
|
||||||
const createButtonElement = ({ className, title, icon }) => ({
|
const createButtonElement = ({ className, title, icon }) => ({
|
||||||
__html: `<button
|
__html: `<button
|
||||||
is="emby-button"
|
is="emby-button"
|
||||||
type="button"
|
type="button"
|
||||||
class="${className}"
|
class="${className}"
|
||||||
style="margin-left:1em;"
|
style="margin-left:1em;"
|
||||||
title="${title}">
|
title="${title}"
|
||||||
|
>
|
||||||
<span class="material-icons ${icon}"></span>
|
<span class="material-icons ${icon}"></span>
|
||||||
</button>`
|
</button>`
|
||||||
});
|
});
|
||||||
|
|
||||||
type IProps = {
|
|
||||||
title?: string;
|
|
||||||
className?: string;
|
|
||||||
icon?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SectionTitleButtonElement: FunctionComponent<IProps> = ({ className, title, icon }: IProps) => {
|
const SectionTitleButtonElement: FunctionComponent<IProps> = ({ className, title, icon }: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
43
src/components/dashboard/users/SelectElement.tsx
Normal file
43
src/components/dashboard/users/SelectElement.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
const createSelectElement = ({ className, label, option }) => ({
|
||||||
|
__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 + '>' + provider.Name + '</option>';
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={createSelectElement({
|
||||||
|
className: className,
|
||||||
|
label: globalize.translate(label),
|
||||||
|
option: renderOption
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectElement;
|
46
src/components/dashboard/users/SelectMaxParentalRating.tsx
Normal file
46
src/components/dashboard/users/SelectMaxParentalRating.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
const createSelectElement = ({ className, label, option }) => ({
|
||||||
|
__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}'>${rating.Name}</option>`;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={createSelectElement({
|
||||||
|
className: className,
|
||||||
|
label: globalize.translate(label),
|
||||||
|
option: renderOption()
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectMaxParentalRating;
|
@ -0,0 +1,35 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import globalize from '../../../scripts/globalize';
|
||||||
|
|
||||||
|
const createSelectElement = ({ className, id, label }) => ({
|
||||||
|
__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;
|
@ -32,15 +32,11 @@ function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
|||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
if (path === 'Network') {
|
if (path) {
|
||||||
promises.push(ApiClient.getNetworkDevices());
|
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
||||||
|
promises.push(ApiClient.getParentPath(path));
|
||||||
} else {
|
} else {
|
||||||
if (path) {
|
promises.push(ApiClient.getDrives());
|
||||||
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
|
||||||
promises.push(ApiClient.getParentPath(path));
|
|
||||||
} else {
|
|
||||||
promises.push(ApiClient.getDrives());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(promises).then(
|
Promise.all(promises).then(
|
||||||
@ -61,10 +57,6 @@ function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
|||||||
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
|
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
html += getItem('lnkPath lnkDirectory', '', 'Network', globalize.translate('ButtonNetwork'));
|
|
||||||
}
|
|
||||||
|
|
||||||
page.querySelector('.results').innerHTML = html;
|
page.querySelector('.results').innerHTML = html;
|
||||||
loading.hide();
|
loading.hide();
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -532,6 +532,11 @@ import ServerConnections from '../ServerConnections';
|
|||||||
section: 'guide'
|
section: 'guide'
|
||||||
}) + '" class="raised"><span>' + globalize.translate('Guide') + '</span></a>';
|
}) + '" class="raised"><span>' + globalize.translate('Guide') + '</span></a>';
|
||||||
|
|
||||||
|
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||||
|
serverId: apiClient.serverId(),
|
||||||
|
section: 'channels'
|
||||||
|
}) + '" class="raised"><span>' + globalize.translate('Channels') + '</span></a>';
|
||||||
|
|
||||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('recordedtv', {
|
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('recordedtv', {
|
||||||
serverId: apiClient.serverId()
|
serverId: apiClient.serverId()
|
||||||
}) + '" class="raised"><span>' + globalize.translate('Recordings') + '</span></a>';
|
}) + '" class="raised"><span>' + globalize.translate('Recordings') + '</span></a>';
|
||||||
|
16
src/components/images/blurhash.worker.ts
Normal file
16
src/components/images/blurhash.worker.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
|
self.onmessage = ({ data: { hash, width, height } }): void => {
|
||||||
|
try {
|
||||||
|
self.postMessage({
|
||||||
|
pixels: decode(hash, width, height),
|
||||||
|
hsh: hash,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
throw new TypeError(`Blurhash ${hash} is not valid`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* eslint-enable no-restricted-globals */
|
@ -1,7 +1,22 @@
|
|||||||
|
import Worker from './blurhash.worker.ts'; // eslint-disable-line import/default
|
||||||
import * as lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver';
|
import * as lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver';
|
||||||
import * as userSettings from '../../scripts/settings/userSettings';
|
import * as userSettings from '../../scripts/settings/userSettings';
|
||||||
import { decode, isBlurhashValid } from 'blurhash';
|
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
const worker = new Worker();
|
||||||
|
const targetDic = {};
|
||||||
|
worker.addEventListener(
|
||||||
|
'message',
|
||||||
|
({ data: { pixels, hsh, width, height } }) => {
|
||||||
|
const elems = targetDic[hsh];
|
||||||
|
if (elems && elems.length) {
|
||||||
|
for (const elem of elems) {
|
||||||
|
drawBlurhash(elem, pixels, width, height);
|
||||||
|
}
|
||||||
|
delete targetDic[hsh];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
/* eslint-disable indent */
|
/* eslint-disable indent */
|
||||||
|
|
||||||
export function lazyImage(elem, source = elem.getAttribute('data-src')) {
|
export function lazyImage(elem, source = elem.getAttribute('data-src')) {
|
||||||
@ -12,42 +27,45 @@ import './style.scss';
|
|||||||
fillImageElement(elem, source);
|
fillImageElement(elem, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemBlurhashing(target, blurhashstr) {
|
function drawBlurhash(target, pixels, width, height) {
|
||||||
if (isBlurhashValid(blurhashstr)) {
|
const canvas = document.createElement('canvas');
|
||||||
// Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us,
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const imgData = ctx.createImageData(width, height);
|
||||||
|
|
||||||
|
imgData.data.set(pixels);
|
||||||
|
ctx.putImageData(imgData, 0, 0);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// This class is just an utility class, so users can customize the canvas using their own CSS.
|
||||||
|
canvas.classList.add('blurhash-canvas');
|
||||||
|
|
||||||
|
target.parentNode.insertBefore(canvas, target);
|
||||||
|
target.classList.add('blurhashed');
|
||||||
|
target.removeAttribute('data-blurhash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemBlurhashing(target, hash) {
|
||||||
|
try {
|
||||||
|
// Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us,
|
||||||
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
|
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
|
||||||
// Lower values had more visible pixelation
|
// Lower values had more visible pixelation
|
||||||
const width = 18;
|
const width = 20;
|
||||||
const height = 18;
|
const height = 20;
|
||||||
let pixels;
|
targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target);
|
||||||
try {
|
targetDic[hash].push(target);
|
||||||
pixels = decode(blurhashstr, width, height);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Blurhash decode error: ', err);
|
|
||||||
target.classList.add('non-blurhashable');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
const imgData = ctx.createImageData(width, height);
|
|
||||||
|
|
||||||
imgData.data.set(pixels);
|
worker.postMessage({
|
||||||
ctx.putImageData(imgData, 0, 0);
|
hash,
|
||||||
|
width,
|
||||||
requestAnimationFrame(() => {
|
height
|
||||||
canvas.classList.add('blurhash-canvas');
|
|
||||||
if (userSettings.enableFastFadein()) {
|
|
||||||
canvas.classList.add('lazy-blurhash-fadein-fast');
|
|
||||||
} else {
|
|
||||||
canvas.classList.add('lazy-blurhash-fadein');
|
|
||||||
}
|
|
||||||
|
|
||||||
target.parentNode.insertBefore(canvas, target);
|
|
||||||
target.classList.add('blurhashed');
|
|
||||||
target.removeAttribute('data-blurhash');
|
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
target.classList.add('non-blurhashable');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,14 +83,25 @@ import './style.scss';
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entry.intersectionRatio > 0) {
|
if (entry.intersectionRatio > 0) {
|
||||||
if (source) fillImageElement(target, source);
|
if (source) {
|
||||||
|
fillImageElement(target, source);
|
||||||
|
}
|
||||||
} else if (!source) {
|
} else if (!source) {
|
||||||
requestAnimationFrame(() => {
|
emptyImageElement(target);
|
||||||
emptyImageElement(target);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAnimationEnd(event) {
|
||||||
|
const elem = event.target;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const canvas = elem.previousSibling;
|
||||||
|
if (elem.classList.contains('blurhashed') && canvas && canvas.tagName === 'CANVAS') {
|
||||||
|
canvas.classList.add('lazy-hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
elem.removeEventListener('animationend', onAnimationEnd);
|
||||||
|
}
|
||||||
|
|
||||||
function fillImageElement(elem, url) {
|
function fillImageElement(elem, url) {
|
||||||
if (url === undefined) {
|
if (url === undefined) {
|
||||||
throw new TypeError('url cannot be undefined');
|
throw new TypeError('url cannot be undefined');
|
||||||
@ -82,6 +111,7 @@ import './style.scss';
|
|||||||
preloaderImg.src = url;
|
preloaderImg.src = url;
|
||||||
|
|
||||||
elem.classList.add('lazy-hidden');
|
elem.classList.add('lazy-hidden');
|
||||||
|
elem.addEventListener('animationend', onAnimationEnd);
|
||||||
|
|
||||||
preloaderImg.addEventListener('load', () => {
|
preloaderImg.addEventListener('load', () => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -92,25 +122,22 @@ import './style.scss';
|
|||||||
}
|
}
|
||||||
elem.removeAttribute('data-src');
|
elem.removeAttribute('data-src');
|
||||||
|
|
||||||
elem.classList.remove('lazy-hidden');
|
|
||||||
if (userSettings.enableFastFadein()) {
|
if (userSettings.enableFastFadein()) {
|
||||||
elem.classList.add('lazy-image-fadein-fast');
|
elem.classList.add('lazy-image-fadein-fast');
|
||||||
} else {
|
} else {
|
||||||
elem.classList.add('lazy-image-fadein');
|
elem.classList.add('lazy-image-fadein');
|
||||||
}
|
}
|
||||||
|
elem.classList.remove('lazy-hidden');
|
||||||
const canvas = elem.previousSibling;
|
|
||||||
if (elem.classList.contains('blurhashed') && canvas && canvas.tagName === 'CANVAS') {
|
|
||||||
canvas.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
|
|
||||||
canvas.classList.add('lazy-hidden');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyImageElement(elem) {
|
function emptyImageElement(elem) {
|
||||||
// block repeated call - requestAnimationFrame twice for one image
|
elem.removeEventListener('animationend', onAnimationEnd);
|
||||||
if (elem.getAttribute('data-src')) return;
|
const canvas = elem.previousSibling;
|
||||||
|
if (canvas && canvas.tagName === 'CANVAS') {
|
||||||
|
canvas.classList.remove('lazy-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
|
|
||||||
@ -125,16 +152,6 @@ import './style.scss';
|
|||||||
|
|
||||||
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
|
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
|
||||||
elem.classList.add('lazy-hidden');
|
elem.classList.add('lazy-hidden');
|
||||||
|
|
||||||
const canvas = elem.previousSibling;
|
|
||||||
if (canvas && canvas.tagName === 'CANVAS') {
|
|
||||||
canvas.classList.remove('lazy-hidden');
|
|
||||||
if (userSettings.enableFastFadein()) {
|
|
||||||
canvas.classList.add('lazy-image-fadein-fast');
|
|
||||||
} else {
|
|
||||||
canvas.classList.add('lazy-image-fadein');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lazyChildren(elem) {
|
export function lazyChildren(elem) {
|
||||||
|
@ -1,17 +1,3 @@
|
|||||||
.lazy-image-fadein {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lazy-image-fadein-fast {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lazy-hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@ -22,12 +8,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lazy-blurhash-fadein-fast {
|
.lazy-image-fadein {
|
||||||
|
opacity: 1;
|
||||||
|
animation: fadein 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lazy-image-fadein-fast {
|
||||||
|
opacity: 1;
|
||||||
animation: fadein 0.1s;
|
animation: fadein 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lazy-blurhash-fadein {
|
.lazy-hidden {
|
||||||
animation: fadein 0.4s;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blurhash-canvas {
|
.blurhash-canvas {
|
||||||
|
@ -20,7 +20,7 @@ export function getDisplayName(item, options = {}) {
|
|||||||
}
|
}
|
||||||
if (item.Type === 'Episode' && item.ParentIndexNumber === 0) {
|
if (item.Type === 'Episode' && item.ParentIndexNumber === 0) {
|
||||||
name = globalize.translate('ValueSpecialEpisodeName', name);
|
name = globalize.translate('ValueSpecialEpisodeName', name);
|
||||||
} else if ((item.Type === 'Episode' || item.Type === 'Program') && item.IndexNumber != null && item.ParentIndexNumber != null && options.includeIndexNumber !== false) {
|
} else if ((item.Type === 'Episode' || item.Type === 'Program' || item.Type === 'Recording') && item.IndexNumber != null && item.ParentIndexNumber != null && options.includeIndexNumber !== false) {
|
||||||
let displayIndexNumber = item.IndexNumber;
|
let displayIndexNumber = item.IndexNumber;
|
||||||
|
|
||||||
let number = displayIndexNumber;
|
let number = displayIndexNumber;
|
||||||
|
@ -143,10 +143,8 @@ import template from './itemMediaInfo.template.html';
|
|||||||
if (stream.NalLengthSize) {
|
if (stream.NalLengthSize) {
|
||||||
attributes.push(createAttribute('NAL', stream.NalLengthSize));
|
attributes.push(createAttribute('NAL', stream.NalLengthSize));
|
||||||
}
|
}
|
||||||
if (stream.Type !== 'Video') {
|
if (stream.Type === 'Subtitle' || stream.Type === 'Audio') {
|
||||||
attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No')));
|
attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No')));
|
||||||
}
|
|
||||||
if (stream.Type === 'Subtitle') {
|
|
||||||
attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No')));
|
attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No')));
|
||||||
attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No')));
|
attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No')));
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,10 @@
|
|||||||
callback(entry);
|
callback(entry);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{rootMargin: '25%'});
|
{
|
||||||
|
rootMargin: '50%',
|
||||||
|
threshold: 0
|
||||||
|
});
|
||||||
|
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,7 @@ import template from './libraryoptionseditor.template.html';
|
|||||||
parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide');
|
parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies');
|
parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed');
|
||||||
|
|
||||||
return populateMetadataSettings(parent, contentType);
|
return populateMetadataSettings(parent, contentType);
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,10 @@
|
|||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listItemImage .cardImageIcon {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (max-width: 64em) {
|
@media all and (max-width: 64em) {
|
||||||
.listItemImage-large {
|
.listItemImage-large {
|
||||||
width: 22vw;
|
width: 22vw;
|
||||||
|
@ -73,7 +73,7 @@ import template from './mediaLibraryCreator.template.html';
|
|||||||
$('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () {
|
$('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () {
|
||||||
const value = this.value;
|
const value = this.value;
|
||||||
const dlg = $(this).parents('.dialog')[0];
|
const dlg = $(this).parents('.dialog')[0];
|
||||||
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value == 'mixed' ? '' : value);
|
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
dlg.querySelector('.libraryOptions').classList.remove('hide');
|
dlg.querySelector('.libraryOptions').classList.remove('hide');
|
||||||
|
@ -131,7 +131,8 @@ import '../../elements/emby-button/emby-button';
|
|||||||
if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false) {
|
if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false) {
|
||||||
if (item.PremiereDate) {
|
if (item.PremiereDate) {
|
||||||
try {
|
try {
|
||||||
date = datetime.parseISO8601Date(item.PremiereDate);
|
//don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog
|
||||||
|
date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode');
|
||||||
|
|
||||||
text = datetime.toLocaleDateString(date);
|
text = datetime.toLocaleDateString(date);
|
||||||
miscInfo.push(text);
|
miscInfo.push(text);
|
||||||
|
@ -175,6 +175,12 @@ import itemHelper from '../itemHelper';
|
|||||||
apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => {
|
apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => {
|
||||||
const menuItems = [];
|
const menuItems = [];
|
||||||
|
|
||||||
|
menuItems.push({
|
||||||
|
name: globalize.translate('SelectAll'),
|
||||||
|
id: 'selectall',
|
||||||
|
icon: 'select_all'
|
||||||
|
});
|
||||||
|
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
name: globalize.translate('AddToCollection'),
|
name: globalize.translate('AddToCollection'),
|
||||||
id: 'addtocollection',
|
id: 'addtocollection',
|
||||||
@ -246,6 +252,19 @@ import itemHelper from '../itemHelper';
|
|||||||
const serverId = apiClient.serverInfo().Id;
|
const serverId = apiClient.serverInfo().Id;
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
case 'selectall':
|
||||||
|
{
|
||||||
|
const elems = document.querySelectorAll('.itemSelectionPanel');
|
||||||
|
for (let i = 0, length = elems.length; i < length; i++) {
|
||||||
|
const chkItemSelect = elems[i].querySelector('.chkItemSelect');
|
||||||
|
|
||||||
|
if (chkItemSelect && !chkItemSelect.classList.contains('checkedInitial') && !chkItemSelect.checked && chkItemSelect.getBoundingClientRect().width != 0) {
|
||||||
|
chkItemSelect.checked = true;
|
||||||
|
updateItemSelection(chkItemSelect, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'addtocollection':
|
case 'addtocollection':
|
||||||
import('../collectionEditor/collectionEditor').then(({default: collectionEditor}) => {
|
import('../collectionEditor/collectionEditor').then(({default: collectionEditor}) => {
|
||||||
new collectionEditor({
|
new collectionEditor({
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
@ -3,6 +3,8 @@ import { playbackManager } from '../playback/playbackmanager';
|
|||||||
import { Events } from 'jellyfin-apiclient';
|
import { Events } from 'jellyfin-apiclient';
|
||||||
import globalize from '../../scripts/globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
|
|
||||||
|
import NotificationIcon from './notificationicon.png';
|
||||||
|
|
||||||
function onOneDocumentClick() {
|
function onOneDocumentClick() {
|
||||||
document.removeEventListener('click', onOneDocumentClick);
|
document.removeEventListener('click', onOneDocumentClick);
|
||||||
document.removeEventListener('keydown', onOneDocumentClick);
|
document.removeEventListener('keydown', onOneDocumentClick);
|
||||||
@ -71,8 +73,8 @@ function showNotification(options, timeoutMs, apiClient) {
|
|||||||
|
|
||||||
options.data = options.data || {};
|
options.data = options.data || {};
|
||||||
options.data.serverId = apiClient.serverInfo().Id;
|
options.data.serverId = apiClient.serverInfo().Id;
|
||||||
options.icon = options.icon || getIconUrl();
|
options.icon = options.icon || NotificationIcon;
|
||||||
options.badge = options.badge || getIconUrl('badge.png');
|
options.badge = options.badge || NotificationIcon;
|
||||||
|
|
||||||
resetRegistration();
|
resetRegistration();
|
||||||
|
|
||||||
@ -148,11 +150,6 @@ function onLibraryChanged(data, apiClient) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIconUrl(name) {
|
|
||||||
name = name || 'notificationicon.png';
|
|
||||||
return './components/notifications/' + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPackageInstallNotification(apiClient, installation, status) {
|
function showPackageInstallNotification(apiClient, installation, status) {
|
||||||
apiClient.getCurrentUser().then(function (user) {
|
apiClient.getCurrentUser().then(function (user) {
|
||||||
if (!user.Policy.IsAdministrator) {
|
if (!user.Policy.IsAdministrator) {
|
||||||
@ -180,7 +177,7 @@ function showPackageInstallNotification(apiClient, installation, status) {
|
|||||||
{
|
{
|
||||||
action: 'cancel-install',
|
action: 'cancel-install',
|
||||||
title: globalize.translate('ButtonCancel'),
|
title: globalize.translate('ButtonCancel'),
|
||||||
icon: getIconUrl()
|
icon: NotificationIcon
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -249,7 +246,7 @@ Events.on(serverNotifications, 'RestartRequired', function (e, apiClient) {
|
|||||||
{
|
{
|
||||||
action: 'restart',
|
action: 'restart',
|
||||||
title: globalize.translate('Restart'),
|
title: globalize.translate('Restart'),
|
||||||
icon: getIconUrl()
|
icon: NotificationIcon
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ const NewUserPage: FunctionComponent = () => {
|
|||||||
IsHidden: false
|
IsHidden: false
|
||||||
}));
|
}));
|
||||||
const promiseChannels = window.ApiClient.getJSON(window.ApiClient.getUrl('Channels'));
|
const promiseChannels = window.ApiClient.getJSON(window.ApiClient.getUrl('Channels'));
|
||||||
// eslint-disable-next-line compat/compat
|
|
||||||
Promise.all([promiseFolders, promiseChannels]).then(function (responses) {
|
Promise.all([promiseFolders, promiseChannels]).then(function (responses) {
|
||||||
loadMediaFolders(responses[0].Items);
|
loadMediaFolders(responses[0].Items);
|
||||||
loadChannels(responses[1].Items);
|
loadChannels(responses[1].Items);
|
||||||
@ -191,6 +190,7 @@ const NewUserPage: FunctionComponent = () => {
|
|||||||
className='chkFolder'
|
className='chkFolder'
|
||||||
Id={Item.Id}
|
Id={Item.Id}
|
||||||
Name={Item.Name}
|
Name={Item.Name}
|
||||||
|
checkedAttribute=''
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -219,6 +219,7 @@ const NewUserPage: FunctionComponent = () => {
|
|||||||
className='chkChannel'
|
className='chkChannel'
|
||||||
Id={Item.Id}
|
Id={Item.Id}
|
||||||
Name={Item.Name}
|
Name={Item.Name}
|
||||||
|
checkedAttribute=''
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
526
src/components/pages/UserEditPage.tsx
Normal file
526
src/components/pages/UserEditPage.tsx
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||||
|
import Dashboard from '../../scripts/clientUtils';
|
||||||
|
import globalize from '../../scripts/globalize';
|
||||||
|
import LibraryMenu from '../../scripts/libraryMenu';
|
||||||
|
import { appRouter } from '../appRouter';
|
||||||
|
import ButtonElement from '../dashboard/users/ButtonElement';
|
||||||
|
import CheckBoxElement from '../dashboard/users/CheckBoxElement';
|
||||||
|
import CheckBoxListItem from '../dashboard/users/CheckBoxListItem';
|
||||||
|
import InputElement from '../dashboard/users/InputElement';
|
||||||
|
import LinkEditUserPreferences from '../dashboard/users/LinkEditUserPreferences';
|
||||||
|
import SectionTitleLinkElement from '../dashboard/users/SectionTitleLinkElement';
|
||||||
|
import SelectElement from '../dashboard/users/SelectElement';
|
||||||
|
import SelectSyncPlayAccessElement from '../dashboard/users/SelectSyncPlayAccessElement';
|
||||||
|
import SectionTabs from '../dashboard/users/SectionTabs';
|
||||||
|
import loading from '../loading/loading';
|
||||||
|
import toast from '../toast/toast';
|
||||||
|
|
||||||
|
type ItemsArr = {
|
||||||
|
Name?: string;
|
||||||
|
Id?: string;
|
||||||
|
checkedAttribute: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserEditPage: FunctionComponent = () => {
|
||||||
|
const [ userName, setUserName ] = useState('');
|
||||||
|
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState([]);
|
||||||
|
const [ authProviders, setAuthProviders ] = useState([]);
|
||||||
|
const [ passwordResetProviders, setPasswordResetProviders ] = useState([]);
|
||||||
|
|
||||||
|
const [ authenticationProviderId, setAuthenticationProviderId ] = useState('');
|
||||||
|
const [ passwordResetProviderId, setPasswordResetProviderId ] = useState('');
|
||||||
|
|
||||||
|
const element = useRef(null);
|
||||||
|
|
||||||
|
const triggerChange = (select) => {
|
||||||
|
const evt = document.createEvent('HTMLEvents');
|
||||||
|
evt.initEvent('change', false, true);
|
||||||
|
select.dispatchEvent(evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUser = () => {
|
||||||
|
const userId = appRouter.param('userId');
|
||||||
|
return window.ApiClient.getUser(userId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadAuthProviders = useCallback((user, providers) => {
|
||||||
|
const fldSelectLoginProvider = element?.current?.querySelector('.fldSelectLoginProvider');
|
||||||
|
providers.length > 1 ? fldSelectLoginProvider.classList.remove('hide') : fldSelectLoginProvider.classList.add('hide');
|
||||||
|
|
||||||
|
setAuthProviders(providers);
|
||||||
|
|
||||||
|
const currentProviderId = user.Policy.AuthenticationProviderId;
|
||||||
|
setAuthenticationProviderId(currentProviderId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadPasswordResetProviders = useCallback((user, providers) => {
|
||||||
|
const fldSelectPasswordResetProvider = element?.current?.querySelector('.fldSelectPasswordResetProvider');
|
||||||
|
providers.length > 1 ? fldSelectPasswordResetProvider.classList.remove('hide') : fldSelectPasswordResetProvider.classList.add('hide');
|
||||||
|
|
||||||
|
setPasswordResetProviders(providers);
|
||||||
|
|
||||||
|
const currentProviderId = user.Policy.PasswordResetProviderId;
|
||||||
|
setPasswordResetProviderId(currentProviderId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadDeleteFolders = useCallback((user, mediaFolders) => {
|
||||||
|
window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', {
|
||||||
|
SupportsMediaDeletion: true
|
||||||
|
})).then(function (channelsResult) {
|
||||||
|
let isChecked;
|
||||||
|
let checkedAttribute;
|
||||||
|
const itemsArr: ItemsArr[] = [];
|
||||||
|
|
||||||
|
for (const folder of mediaFolders) {
|
||||||
|
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
||||||
|
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
Id: folder.Id,
|
||||||
|
Name: folder.Name,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const folder of channelsResult.Items) {
|
||||||
|
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
||||||
|
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
Id: folder.Id,
|
||||||
|
Name: folder.Name,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteFoldersAccess(itemsArr);
|
||||||
|
|
||||||
|
const chkEnableDeleteAllFolders = element.current.querySelector('.chkEnableDeleteAllFolders');
|
||||||
|
chkEnableDeleteAllFolders.checked = user.Policy.EnableContentDeletion;
|
||||||
|
triggerChange(chkEnableDeleteAllFolders);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadUser = useCallback((user) => {
|
||||||
|
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) {
|
||||||
|
loadAuthProviders(user, providers);
|
||||||
|
});
|
||||||
|
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) {
|
||||||
|
loadPasswordResetProviders(user, providers);
|
||||||
|
});
|
||||||
|
window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
|
||||||
|
IsHidden: false
|
||||||
|
})).then(function (folders) {
|
||||||
|
loadDeleteFolders(user, folders.Items);
|
||||||
|
});
|
||||||
|
|
||||||
|
const disabledUserBanner = element?.current?.querySelector('.disabledUserBanner');
|
||||||
|
user.Policy.IsDisabled ? disabledUserBanner.classList.remove('hide') : disabledUserBanner.classList.add('hide');
|
||||||
|
|
||||||
|
const txtUserName = element?.current?.querySelector('#txtUserName');
|
||||||
|
txtUserName.disabled = '';
|
||||||
|
txtUserName.removeAttribute('disabled');
|
||||||
|
|
||||||
|
const lnkEditUserPreferences = element?.current?.querySelector('.lnkEditUserPreferences');
|
||||||
|
lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id);
|
||||||
|
LibraryMenu.setTitle(user.Name);
|
||||||
|
setUserName(user.Name);
|
||||||
|
element.current.querySelector('#txtUserName').value = user.Name;
|
||||||
|
element.current.querySelector('.chkIsAdmin').checked = user.Policy.IsAdministrator;
|
||||||
|
element.current.querySelector('.chkDisabled').checked = user.Policy.IsDisabled;
|
||||||
|
element.current.querySelector('.chkIsHidden').checked = user.Policy.IsHidden;
|
||||||
|
element.current.querySelector('.chkRemoteControlSharedDevices').checked = user.Policy.EnableSharedDeviceControl;
|
||||||
|
element.current.querySelector('.chkEnableRemoteControlOtherUsers').checked = user.Policy.EnableRemoteControlOfOtherUsers;
|
||||||
|
element.current.querySelector('.chkEnableDownloading').checked = user.Policy.EnableContentDownloading;
|
||||||
|
element.current.querySelector('.chkManageLiveTv').checked = user.Policy.EnableLiveTvManagement;
|
||||||
|
element.current.querySelector('.chkEnableLiveTvAccess').checked = user.Policy.EnableLiveTvAccess;
|
||||||
|
element.current.querySelector('.chkEnableMediaPlayback').checked = user.Policy.EnableMediaPlayback;
|
||||||
|
element.current.querySelector('.chkEnableAudioPlaybackTranscoding').checked = user.Policy.EnableAudioPlaybackTranscoding;
|
||||||
|
element.current.querySelector('.chkEnableVideoPlaybackTranscoding').checked = user.Policy.EnableVideoPlaybackTranscoding;
|
||||||
|
element.current.querySelector('.chkEnableVideoPlaybackRemuxing').checked = user.Policy.EnablePlaybackRemuxing;
|
||||||
|
element.current.querySelector('.chkForceRemoteSourceTranscoding').checked = user.Policy.ForceRemoteSourceTranscoding;
|
||||||
|
element.current.querySelector('.chkRemoteAccess').checked = user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess;
|
||||||
|
element.current.querySelector('#txtRemoteClientBitrateLimit').value = user.Policy.RemoteClientBitrateLimit / 1e6 || '';
|
||||||
|
element.current.querySelector('#txtLoginAttemptsBeforeLockout').value = user.Policy.LoginAttemptsBeforeLockout || '0';
|
||||||
|
element.current.querySelector('#txtMaxActiveSessions').value = user.Policy.MaxActiveSessions || '0';
|
||||||
|
if (window.ApiClient.isMinServerVersion('10.6.0')) {
|
||||||
|
element.current.querySelector('#selectSyncPlayAccess').value = user.Policy.SyncPlayAccess;
|
||||||
|
}
|
||||||
|
loading.hide();
|
||||||
|
}, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]);
|
||||||
|
|
||||||
|
const loadData = useCallback(() => {
|
||||||
|
loading.show();
|
||||||
|
getUser().then(function (user) {
|
||||||
|
loadUser(user);
|
||||||
|
});
|
||||||
|
}, [loadUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
function onSaveComplete() {
|
||||||
|
Dashboard.navigate('userprofiles.html');
|
||||||
|
loading.hide();
|
||||||
|
toast(globalize.translate('SettingsSaved'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveUser = (user) => {
|
||||||
|
user.Name = element?.current?.querySelector('#txtUserName').value;
|
||||||
|
user.Policy.IsAdministrator = element?.current?.querySelector('.chkIsAdmin').checked;
|
||||||
|
user.Policy.IsHidden = element?.current?.querySelector('.chkIsHidden').checked;
|
||||||
|
user.Policy.IsDisabled = element?.current?.querySelector('.chkDisabled').checked;
|
||||||
|
user.Policy.EnableRemoteControlOfOtherUsers = element?.current?.querySelector('.chkEnableRemoteControlOtherUsers').checked;
|
||||||
|
user.Policy.EnableLiveTvManagement = element?.current?.querySelector('.chkManageLiveTv').checked;
|
||||||
|
user.Policy.EnableLiveTvAccess = element?.current?.querySelector('.chkEnableLiveTvAccess').checked;
|
||||||
|
user.Policy.EnableSharedDeviceControl = element?.current?.querySelector('.chkRemoteControlSharedDevices').checked;
|
||||||
|
user.Policy.EnableMediaPlayback = element?.current?.querySelector('.chkEnableMediaPlayback').checked;
|
||||||
|
user.Policy.EnableAudioPlaybackTranscoding = element?.current?.querySelector('.chkEnableAudioPlaybackTranscoding').checked;
|
||||||
|
user.Policy.EnableVideoPlaybackTranscoding = element?.current?.querySelector('.chkEnableVideoPlaybackTranscoding').checked;
|
||||||
|
user.Policy.EnablePlaybackRemuxing = element?.current?.querySelector('.chkEnableVideoPlaybackRemuxing').checked;
|
||||||
|
user.Policy.ForceRemoteSourceTranscoding = element?.current?.querySelector('.chkForceRemoteSourceTranscoding').checked;
|
||||||
|
user.Policy.EnableContentDownloading = element?.current?.querySelector('.chkEnableDownloading').checked;
|
||||||
|
user.Policy.EnableRemoteAccess = element?.current?.querySelector('.chkRemoteAccess').checked;
|
||||||
|
user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat(element?.current?.querySelector('#txtRemoteClientBitrateLimit').value || '0'));
|
||||||
|
user.Policy.LoginAttemptsBeforeLockout = parseInt(element?.current?.querySelector('#txtLoginAttemptsBeforeLockout').value || '0');
|
||||||
|
user.Policy.MaxActiveSessions = parseInt(element?.current?.querySelector('#txtMaxActiveSessions').value || '0');
|
||||||
|
user.Policy.AuthenticationProviderId = element?.current?.querySelector('.selectLoginProvider').value;
|
||||||
|
user.Policy.PasswordResetProviderId = element?.current?.querySelector('.selectPasswordResetProvider').value;
|
||||||
|
user.Policy.EnableContentDeletion = element?.current?.querySelector('.chkEnableDeleteAllFolders').checked;
|
||||||
|
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkFolder'), function (c) {
|
||||||
|
return c.checked;
|
||||||
|
}).map(function (c) {
|
||||||
|
return c.getAttribute('data-id');
|
||||||
|
});
|
||||||
|
if (window.ApiClient.isMinServerVersion('10.6.0')) {
|
||||||
|
user.Policy.SyncPlayAccess = element?.current?.querySelector('#selectSyncPlayAccess').value;
|
||||||
|
}
|
||||||
|
window.ApiClient.updateUser(user).then(function () {
|
||||||
|
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||||
|
onSaveComplete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (e) => {
|
||||||
|
loading.show();
|
||||||
|
getUser().then(function (result) {
|
||||||
|
saveUser(result);
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
element?.current?.querySelector('.chkEnableDeleteAllFolders').addEventListener('change', function (this: HTMLInputElement) {
|
||||||
|
if (this.checked) {
|
||||||
|
element?.current?.querySelector('.deleteAccess').classList.add('hide');
|
||||||
|
} else {
|
||||||
|
element?.current?.querySelector('.deleteAccess').classList.remove('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ApiClient.getServerConfiguration().then(function (config) {
|
||||||
|
const fldRemoteAccess = element?.current?.querySelector('.fldRemoteAccess');
|
||||||
|
config.EnableRemoteAccess ? fldRemoteAccess.classList.remove('hide') : fldRemoteAccess.classList.add('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.editUserProfileForm').addEventListener('submit', onSubmit);
|
||||||
|
|
||||||
|
element?.current?.querySelector('.button-cancel').addEventListener('click', function() {
|
||||||
|
window.history.back();
|
||||||
|
});
|
||||||
|
}, [loadData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={element}>
|
||||||
|
<div className='content-primary'>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='sectionTitleContainer flex align-items-center'>
|
||||||
|
<h2 className='sectionTitle username'>
|
||||||
|
{userName}
|
||||||
|
</h2>
|
||||||
|
<SectionTitleLinkElement
|
||||||
|
className='raised button-alt headerHelpButton'
|
||||||
|
title='Help'
|
||||||
|
url='https://docs.jellyfin.org/general/server/users/'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SectionTabs activeTab='useredit'/>
|
||||||
|
<div
|
||||||
|
className='lnkEditUserPreferencesContainer'
|
||||||
|
style={{paddingBottom: '1em'}}
|
||||||
|
>
|
||||||
|
<LinkEditUserPreferences
|
||||||
|
className= 'lnkEditUserPreferences button-link'
|
||||||
|
title= 'ButtonEditOtherUserPreferences'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<form className='editUserProfileForm'>
|
||||||
|
<div className='disabledUserBanner hide'>
|
||||||
|
<div className='btn btnDarkAccent btnStatic'>
|
||||||
|
<div>
|
||||||
|
{globalize.translate('HeaderThisUserIsCurrentlyDisabled')}
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop: 5}}>
|
||||||
|
{globalize.translate('MessageReenableUser')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id='fldUserName' className='inputContainer'>
|
||||||
|
<InputElement
|
||||||
|
type='text'
|
||||||
|
id='txtUserName'
|
||||||
|
label='LabelName'
|
||||||
|
options={'required'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='selectContainer fldSelectLoginProvider hide'>
|
||||||
|
<SelectElement
|
||||||
|
className= 'selectLoginProvider'
|
||||||
|
label= 'LabelAuthProvider'
|
||||||
|
currentProviderId={authenticationProviderId}
|
||||||
|
providers={authProviders}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('AuthProviderHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='selectContainer fldSelectPasswordResetProvider hide'>
|
||||||
|
<SelectElement
|
||||||
|
className= 'selectPasswordResetProvider'
|
||||||
|
label= 'LabelPasswordResetProvider'
|
||||||
|
currentProviderId={passwordResetProviderId}
|
||||||
|
providers={passwordResetProviders}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('PasswordResetProviderHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='checkboxContainer checkboxContainer-withDescription fldRemoteAccess hide'>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkRemoteAccess'
|
||||||
|
title='AllowRemoteAccess'
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription checkboxFieldDescription'>
|
||||||
|
{globalize.translate('AllowRemoteAccessHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CheckBoxElement
|
||||||
|
labelClassName='checkboxContainer'
|
||||||
|
type='checkbox'
|
||||||
|
className='chkIsAdmin'
|
||||||
|
title='OptionAllowUserToManageServer'
|
||||||
|
/>
|
||||||
|
<div id='featureAccessFields' className='verticalSection'>
|
||||||
|
<h2 className='paperListLabel'>
|
||||||
|
{globalize.translate('HeaderFeatureAccess')}
|
||||||
|
</h2>
|
||||||
|
<div className='checkboxList paperList' style={{padding: '.5em 1em'}}>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableLiveTvAccess'
|
||||||
|
title='OptionAllowBrowsingLiveTv'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkManageLiveTv'
|
||||||
|
title='OptionAllowManageLiveTv'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<h2 className='paperListLabel'>
|
||||||
|
{globalize.translate('HeaderPlayback')}
|
||||||
|
</h2>
|
||||||
|
<div className='checkboxList paperList' style={{padding: '.5em 1em'}}>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableMediaPlayback'
|
||||||
|
title='OptionAllowMediaPlayback'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableAudioPlaybackTranscoding'
|
||||||
|
title='OptionAllowAudioPlaybackTranscoding'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableVideoPlaybackTranscoding'
|
||||||
|
title='OptionAllowVideoPlaybackTranscoding'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableVideoPlaybackRemuxing'
|
||||||
|
title='OptionAllowVideoPlaybackRemuxing'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkForceRemoteSourceTranscoding'
|
||||||
|
title='OptionForceRemoteSourceTranscoding'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionAllowMediaPlaybackTranscodingHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='inputContainer'>
|
||||||
|
<InputElement
|
||||||
|
type='number'
|
||||||
|
id='txtRemoteClientBitrateLimit'
|
||||||
|
label='LabelRemoteClientBitrateLimit'
|
||||||
|
options={'inputMode="decimal" pattern="[0-9]*(.[0-9]+)?" min="{0}" step=".25"'}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('LabelRemoteClientBitrateLimitHelp')}
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('LabelUserRemoteClientBitrateLimitHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='selectContainer fldSelectSyncPlayAccess'>
|
||||||
|
<SelectSyncPlayAccessElement
|
||||||
|
className='selectSyncPlayAccess'
|
||||||
|
id='selectSyncPlayAccess'
|
||||||
|
label='LabelSyncPlayAccess'
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('SyncPlayAccessHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<h2 className='checkboxListLabel' style={{marginBottom: '1em'}}>
|
||||||
|
{globalize.translate('HeaderAllowMediaDeletionFrom')}
|
||||||
|
</h2>
|
||||||
|
<div className='checkboxList paperList checkboxList-paperList'>
|
||||||
|
<CheckBoxElement
|
||||||
|
labelClassName='checkboxContainer'
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableDeleteAllFolders'
|
||||||
|
title='AllLibraries'
|
||||||
|
/>
|
||||||
|
<div className='deleteAccess'>
|
||||||
|
{deleteFoldersAccess.map(Item => (
|
||||||
|
<CheckBoxListItem
|
||||||
|
key={Item.Id}
|
||||||
|
className='chkFolder'
|
||||||
|
Id={Item.Id}
|
||||||
|
Name={Item.Name}
|
||||||
|
checkedAttribute={Item.checkedAttribute}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<h2 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('HeaderRemoteControl')}
|
||||||
|
</h2>
|
||||||
|
<div className='checkboxList paperList' style={{padding: '.5em 1em'}}>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableRemoteControlOtherUsers'
|
||||||
|
title='OptionAllowRemoteControlOthers'
|
||||||
|
/>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkRemoteControlSharedDevices'
|
||||||
|
title='OptionAllowRemoteSharedDevices'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionAllowRemoteSharedDevicesHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('Other')}
|
||||||
|
</h2>
|
||||||
|
<div className='checkboxContainer checkboxContainer-withDescription'>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableDownloading'
|
||||||
|
title='OptionAllowContentDownload'
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription checkboxFieldDescription'>
|
||||||
|
{globalize.translate('OptionAllowContentDownloadHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='checkboxContainer checkboxContainer-withDescription' id='fldIsEnabled'>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkDisabled'
|
||||||
|
title='OptionDisableUser'
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription checkboxFieldDescription'>
|
||||||
|
{globalize.translate('OptionDisableUserHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='checkboxContainer checkboxContainer-withDescription' id='fldIsHidden'>
|
||||||
|
<CheckBoxElement
|
||||||
|
type='checkbox'
|
||||||
|
className='chkIsHidden'
|
||||||
|
title='OptionHideUser'
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription checkboxFieldDescription'>
|
||||||
|
{globalize.translate('OptionHideUserFromLoginHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='inputContainer' id='fldLoginAttemptsBeforeLockout'>
|
||||||
|
<InputElement
|
||||||
|
type='number'
|
||||||
|
id='txtLoginAttemptsBeforeLockout'
|
||||||
|
label='LabelUserLoginAttemptsBeforeLockout'
|
||||||
|
options={'min={-1} step={1}'}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionLoginAttemptsBeforeLockout')}
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionLoginAttemptsBeforeLockoutHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='inputContainer' id='fldMaxActiveSessions'>
|
||||||
|
<InputElement
|
||||||
|
type='number'
|
||||||
|
id='txtMaxActiveSessions'
|
||||||
|
label='LabelUserMaxActiveSessions'
|
||||||
|
options={'min={0} step={1}'}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionMaxActiveSessions')}
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('OptionMaxActiveSessionsHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<ButtonElement
|
||||||
|
type='submit'
|
||||||
|
className='raised button-submit block'
|
||||||
|
title='Save'
|
||||||
|
/>
|
||||||
|
<ButtonElement
|
||||||
|
type='button'
|
||||||
|
className='raised button-cancel block btnCancel'
|
||||||
|
title='ButtonCancel'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserEditPage;
|
317
src/components/pages/UserLibraryAccessPage.tsx
Normal file
317
src/components/pages/UserLibraryAccessPage.tsx
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import loading from '../loading/loading';
|
||||||
|
import libraryMenu from '../../scripts/libraryMenu';
|
||||||
|
import globalize from '../../scripts/globalize';
|
||||||
|
import toast from '../toast/toast';
|
||||||
|
import { appRouter } from '../appRouter';
|
||||||
|
import SectionTitleLinkElement from '../dashboard/users/SectionTitleLinkElement';
|
||||||
|
import SectionTabs from '../dashboard/users/SectionTabs';
|
||||||
|
import CheckBoxElement from '../dashboard/users/CheckBoxElement';
|
||||||
|
import CheckBoxListItem from '../dashboard/users/CheckBoxListItem';
|
||||||
|
import ButtonElement from '../dashboard/users/ButtonElement';
|
||||||
|
|
||||||
|
type ItemsArr = {
|
||||||
|
Name?: string;
|
||||||
|
Id?: string;
|
||||||
|
AppName?: string;
|
||||||
|
checkedAttribute?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserLibraryAccessPage: FunctionComponent = () => {
|
||||||
|
const [ userName, setUserName ] = useState('');
|
||||||
|
const [channelsItems, setChannelsItems] = useState([]);
|
||||||
|
const [mediaFoldersItems, setMediaFoldersItems] = useState([]);
|
||||||
|
const [devicesItems, setDevicesItems] = useState([]);
|
||||||
|
|
||||||
|
const element = useRef(null);
|
||||||
|
|
||||||
|
const triggerChange = (select) => {
|
||||||
|
const evt = document.createEvent('HTMLEvents');
|
||||||
|
evt.initEvent('change', false, true);
|
||||||
|
select.dispatchEvent(evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadMediaFolders = useCallback((user, mediaFolders) => {
|
||||||
|
const itemsArr: ItemsArr[] = [];
|
||||||
|
|
||||||
|
for (const folder of mediaFolders) {
|
||||||
|
const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1;
|
||||||
|
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
Id: folder.Id,
|
||||||
|
Name: folder.Name,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMediaFoldersItems(itemsArr);
|
||||||
|
|
||||||
|
const chkEnableAllFolders = element.current.querySelector('.chkEnableAllFolders');
|
||||||
|
chkEnableAllFolders.checked = user.Policy.EnableAllFolders;
|
||||||
|
triggerChange(chkEnableAllFolders);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadChannels = useCallback((user, channels) => {
|
||||||
|
const itemsArr: ItemsArr[] = [];
|
||||||
|
|
||||||
|
for (const folder of channels) {
|
||||||
|
const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1;
|
||||||
|
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
Id: folder.Id,
|
||||||
|
Name: folder.Name,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setChannelsItems(itemsArr);
|
||||||
|
|
||||||
|
if (channels.length) {
|
||||||
|
element?.current?.querySelector('.channelAccessContainer').classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
element?.current?.querySelector('.channelAccessContainer').classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chkEnableAllChannels = element.current.querySelector('.chkEnableAllChannels');
|
||||||
|
chkEnableAllChannels.checked = user.Policy.EnableAllChannels;
|
||||||
|
triggerChange(chkEnableAllChannels);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadDevices = useCallback((user, devices) => {
|
||||||
|
const itemsArr: ItemsArr[] = [];
|
||||||
|
|
||||||
|
for (const device of devices) {
|
||||||
|
const isChecked = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1;
|
||||||
|
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
Id: device.Id,
|
||||||
|
Name: device.Name,
|
||||||
|
AppName : device.AppName,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setDevicesItems(itemsArr);
|
||||||
|
|
||||||
|
const chkEnableAllDevices = element.current.querySelector('.chkEnableAllDevices');
|
||||||
|
chkEnableAllDevices.checked = user.Policy.EnableAllDevices;
|
||||||
|
triggerChange(chkEnableAllDevices);
|
||||||
|
|
||||||
|
if (user.Policy.IsAdministrator) {
|
||||||
|
element?.current?.querySelector('.deviceAccessContainer').classList.add('hide');
|
||||||
|
} else {
|
||||||
|
element?.current?.querySelector('.deviceAccessContainer').classList.remove('hide');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadUser = useCallback((user, mediaFolders, channels, devices) => {
|
||||||
|
setUserName(user.Name);
|
||||||
|
libraryMenu.setTitle(user.Name);
|
||||||
|
loadChannels(user, channels);
|
||||||
|
loadMediaFolders(user, mediaFolders);
|
||||||
|
loadDevices(user, devices);
|
||||||
|
loading.hide();
|
||||||
|
}, [loadChannels, loadDevices, loadMediaFolders]);
|
||||||
|
|
||||||
|
const loadData = useCallback(() => {
|
||||||
|
loading.show();
|
||||||
|
const userId = appRouter.param('userId');
|
||||||
|
const promise1 = userId ? window.ApiClient.getUser(userId) : Promise.resolve({ Configuration: {} });
|
||||||
|
const promise2 = window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
|
||||||
|
IsHidden: false
|
||||||
|
}));
|
||||||
|
const promise3 = window.ApiClient.getJSON(window.ApiClient.getUrl('Channels'));
|
||||||
|
const promise4 = window.ApiClient.getJSON(window.ApiClient.getUrl('Devices'));
|
||||||
|
Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) {
|
||||||
|
loadUser(responses[0], responses[1].Items, responses[2].Items, responses[3].Items);
|
||||||
|
});
|
||||||
|
}, [loadUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
const onSubmit = (e) => {
|
||||||
|
loading.show();
|
||||||
|
const userId = appRouter.param('userId');
|
||||||
|
window.ApiClient.getUser(userId).then(function (result) {
|
||||||
|
saveUser(result);
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveUser = (user) => {
|
||||||
|
user.Policy.EnableAllFolders = element?.current?.querySelector('.chkEnableAllFolders').checked;
|
||||||
|
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkFolder'), function (c) {
|
||||||
|
return c.checked;
|
||||||
|
}).map(function (c) {
|
||||||
|
return c.getAttribute('data-id');
|
||||||
|
});
|
||||||
|
user.Policy.EnableAllChannels = element?.current?.querySelector('.chkEnableAllChannels').checked;
|
||||||
|
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkChannel'), function (c) {
|
||||||
|
return c.checked;
|
||||||
|
}).map(function (c) {
|
||||||
|
return c.getAttribute('data-id');
|
||||||
|
});
|
||||||
|
user.Policy.EnableAllDevices = element?.current?.querySelector('.chkEnableAllDevices').checked;
|
||||||
|
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkDevice'), function (c) {
|
||||||
|
return c.checked;
|
||||||
|
}).map(function (c) {
|
||||||
|
return c.getAttribute('data-id');
|
||||||
|
});
|
||||||
|
user.Policy.BlockedChannels = null;
|
||||||
|
user.Policy.BlockedMediaFolders = null;
|
||||||
|
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||||
|
onSaveComplete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveComplete = () => {
|
||||||
|
loading.hide();
|
||||||
|
toast(globalize.translate('SettingsSaved'));
|
||||||
|
};
|
||||||
|
|
||||||
|
element?.current?.querySelector('.chkEnableAllDevices').addEventListener('change', function (this: HTMLInputElement) {
|
||||||
|
element?.current?.querySelector('.deviceAccessListContainer').classList.toggle('hide', this.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.chkEnableAllChannels').addEventListener('change', function (this: HTMLInputElement) {
|
||||||
|
element?.current?.querySelector('.channelAccessListContainer').classList.toggle('hide', this.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.chkEnableAllFolders').addEventListener('change', function (this: HTMLInputElement) {
|
||||||
|
element?.current?.querySelector('.folderAccessListContainer').classList.toggle('hide', this.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.userLibraryAccessForm').addEventListener('submit', onSubmit);
|
||||||
|
}, [loadData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={element}>
|
||||||
|
<div className='content-primary'>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='sectionTitleContainer flex align-items-center'>
|
||||||
|
<h2 className='sectionTitle username'>
|
||||||
|
{userName}
|
||||||
|
</h2>
|
||||||
|
<SectionTitleLinkElement
|
||||||
|
className='raised button-alt headerHelpButton'
|
||||||
|
title='Help'
|
||||||
|
url='https://docs.jellyfin.org/general/server/users/'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SectionTabs activeTab='userlibraryaccess'/>
|
||||||
|
<form className='userLibraryAccessForm'>
|
||||||
|
<div className='folderAccessContainer'>
|
||||||
|
<h2>{globalize.translate('HeaderLibraryAccess')}</h2>
|
||||||
|
<CheckBoxElement
|
||||||
|
labelClassName='checkboxContainer'
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableAllFolders'
|
||||||
|
title='OptionEnableAccessToAllLibraries'
|
||||||
|
/>
|
||||||
|
<div className='folderAccessListContainer'>
|
||||||
|
<div className='folderAccess'>
|
||||||
|
<h3 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('HeaderLibraries')}
|
||||||
|
</h3>
|
||||||
|
<div className='checkboxList paperList checkboxList-paperList'>
|
||||||
|
{mediaFoldersItems.map(Item => {
|
||||||
|
return (
|
||||||
|
<CheckBoxListItem
|
||||||
|
key={Item.Id}
|
||||||
|
className='chkFolder'
|
||||||
|
Id={Item.Id}
|
||||||
|
Name={Item.Name}
|
||||||
|
checkedAttribute={Item.checkedAttribute}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('LibraryAccessHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='channelAccessContainer hide'>
|
||||||
|
<h2>{globalize.translate('HeaderChannelAccess')}</h2>
|
||||||
|
<CheckBoxElement
|
||||||
|
labelClassName='checkboxContainer'
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableAllChannels'
|
||||||
|
title='OptionEnableAccessToAllChannels'
|
||||||
|
/>
|
||||||
|
<div className='channelAccessListContainer'>
|
||||||
|
<div className='channelAccess'>
|
||||||
|
<h3 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('Channels')}
|
||||||
|
</h3>
|
||||||
|
<div className='checkboxList paperList' style={{padding: '.5em 1em'}}>
|
||||||
|
{channelsItems.map(Item => (
|
||||||
|
<CheckBoxListItem
|
||||||
|
key={Item.Id}
|
||||||
|
className='chkChannel'
|
||||||
|
Id={Item.Id}
|
||||||
|
Name={Item.Name}
|
||||||
|
checkedAttribute={Item.checkedAttribute}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('ChannelAccessHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className='deviceAccessContainer hide'>
|
||||||
|
<h2>{globalize.translate('HeaderDeviceAccess')}</h2>
|
||||||
|
<CheckBoxElement
|
||||||
|
labelClassName='checkboxContainer'
|
||||||
|
type='checkbox'
|
||||||
|
className='chkEnableAllDevices'
|
||||||
|
title='OptionEnableAccessFromAllDevices'
|
||||||
|
/>
|
||||||
|
<div className='deviceAccessListContainer'>
|
||||||
|
<div className='deviceAccess'>
|
||||||
|
<h3 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('HeaderDevices')}
|
||||||
|
</h3>
|
||||||
|
<div className='checkboxList paperList' style={{padding: '.5em 1em'}}>
|
||||||
|
{devicesItems.map(Item => (
|
||||||
|
<CheckBoxListItem
|
||||||
|
key={Item.Id}
|
||||||
|
className='chkDevice'
|
||||||
|
Id={Item.Id}
|
||||||
|
Name={Item.Name}
|
||||||
|
AppName={Item.AppName}
|
||||||
|
checkedAttribute={Item.checkedAttribute}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('DeviceAccessHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<ButtonElement
|
||||||
|
type='submit'
|
||||||
|
className='raised button-submit block'
|
||||||
|
title='Save'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserLibraryAccessPage;
|
378
src/components/pages/UserParentalControl.tsx
Normal file
378
src/components/pages/UserParentalControl.tsx
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||||
|
import globalize from '../../scripts/globalize';
|
||||||
|
import LibraryMenu from '../../scripts/libraryMenu';
|
||||||
|
import { appRouter } from '../appRouter';
|
||||||
|
import AccessScheduleList from '../dashboard/users/AccessScheduleList';
|
||||||
|
import BlockedTagList from '../dashboard/users/BlockedTagList';
|
||||||
|
import ButtonElement from '../dashboard/users/ButtonElement';
|
||||||
|
import CheckBoxListItem from '../dashboard/users/CheckBoxListItem';
|
||||||
|
import SectionTitleButtonElement from '../dashboard/users/SectionTitleButtonElement';
|
||||||
|
import SectionTitleLinkElement from '../dashboard/users/SectionTitleLinkElement';
|
||||||
|
import SelectMaxParentalRating from '../dashboard/users/SelectMaxParentalRating';
|
||||||
|
import SectionTabs from '../dashboard/users/SectionTabs';
|
||||||
|
import loading from '../loading/loading';
|
||||||
|
import toast from '../toast/toast';
|
||||||
|
|
||||||
|
type RatingsArr = {
|
||||||
|
Name: string;
|
||||||
|
Value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemsArr = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
checkedAttribute: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserParentalControl: FunctionComponent = () => {
|
||||||
|
const [ userName, setUserName ] = useState('');
|
||||||
|
const [ parentalRatings, setParentalRatings ] = useState([]);
|
||||||
|
const [ unratedItems, setUnratedItems ] = useState([]);
|
||||||
|
const [ accessSchedules, setAccessSchedules ] = useState([]);
|
||||||
|
const [ blockedTags, setBlockedTags ] = useState([]);
|
||||||
|
|
||||||
|
const element = useRef(null);
|
||||||
|
|
||||||
|
const populateRatings = useCallback((allParentalRatings) => {
|
||||||
|
let rating;
|
||||||
|
const ratings: RatingsArr[] = [];
|
||||||
|
|
||||||
|
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
||||||
|
rating = allParentalRatings[i];
|
||||||
|
|
||||||
|
if (ratings.length) {
|
||||||
|
const lastRating = ratings[ratings.length - 1];
|
||||||
|
|
||||||
|
if (lastRating.Value === rating.Value) {
|
||||||
|
lastRating.Name += '/' + rating.Name;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ratings.push({
|
||||||
|
Name: rating.Name,
|
||||||
|
Value: rating.Value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setParentalRatings(ratings);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadUnratedItems = useCallback((user) => {
|
||||||
|
const items = [{
|
||||||
|
name: globalize.translate('Books'),
|
||||||
|
value: 'Book'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('Channels'),
|
||||||
|
value: 'ChannelContent'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('LiveTV'),
|
||||||
|
value: 'LiveTvChannel'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('Movies'),
|
||||||
|
value: 'Movie'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('Music'),
|
||||||
|
value: 'Music'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('Trailers'),
|
||||||
|
value: 'Trailer'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('Shows'),
|
||||||
|
value: 'Series'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const itemsArr: ItemsArr[] = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const isChecked = user.Policy.BlockUnratedItems.indexOf(item.value) != -1;
|
||||||
|
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
|
itemsArr.push({
|
||||||
|
value: item.value,
|
||||||
|
name: item.name,
|
||||||
|
checkedAttribute: checkedAttribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setUnratedItems(itemsArr);
|
||||||
|
|
||||||
|
const blockUnratedItems = element?.current?.querySelector('.blockUnratedItems');
|
||||||
|
blockUnratedItems.dispatchEvent(new CustomEvent('create'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadBlockedTags = useCallback((tags) => {
|
||||||
|
setBlockedTags(tags);
|
||||||
|
|
||||||
|
const blockedTagsElem = element?.current?.querySelector('.blockedTags');
|
||||||
|
|
||||||
|
for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) {
|
||||||
|
btnDeleteTag.addEventListener('click', function () {
|
||||||
|
const tag = btnDeleteTag.getAttribute('data-tag');
|
||||||
|
const newTags = tags.filter(function (t) {
|
||||||
|
return t != tag;
|
||||||
|
});
|
||||||
|
loadBlockedTags(newTags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderAccessSchedule = useCallback((schedules) => {
|
||||||
|
setAccessSchedules(schedules);
|
||||||
|
|
||||||
|
const accessScheduleList = element?.current?.querySelector('.accessScheduleList');
|
||||||
|
|
||||||
|
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
||||||
|
btnDelete.addEventListener('click', function () {
|
||||||
|
const index = parseInt(btnDelete.getAttribute('data-index'));
|
||||||
|
schedules.splice(index, 1);
|
||||||
|
const newindex = schedules.filter(function (i) {
|
||||||
|
return i != index;
|
||||||
|
});
|
||||||
|
renderAccessSchedule(newindex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadUser = useCallback((user, allParentalRatings) => {
|
||||||
|
setUserName(user.Name);
|
||||||
|
LibraryMenu.setTitle(user.Name);
|
||||||
|
loadUnratedItems(user);
|
||||||
|
|
||||||
|
loadBlockedTags(user.Policy.BlockedTags);
|
||||||
|
populateRatings(allParentalRatings);
|
||||||
|
let ratingValue = '';
|
||||||
|
|
||||||
|
if (user.Policy.MaxParentalRating) {
|
||||||
|
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
||||||
|
const rating = allParentalRatings[i];
|
||||||
|
|
||||||
|
if (user.Policy.MaxParentalRating >= rating.Value) {
|
||||||
|
ratingValue = rating.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.current.querySelector('.selectMaxParentalRating').value = ratingValue;
|
||||||
|
|
||||||
|
if (user.Policy.IsAdministrator) {
|
||||||
|
element?.current?.querySelector('.accessScheduleSection').classList.add('hide');
|
||||||
|
} else {
|
||||||
|
element?.current?.querySelector('.accessScheduleSection').classList.remove('hide');
|
||||||
|
}
|
||||||
|
renderAccessSchedule(user.Policy.AccessSchedules || []);
|
||||||
|
loading.hide();
|
||||||
|
}, [loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]);
|
||||||
|
|
||||||
|
const loadData = useCallback(() => {
|
||||||
|
loading.show();
|
||||||
|
const userId = appRouter.param('userId');
|
||||||
|
const promise1 = window.ApiClient.getUser(userId);
|
||||||
|
const promise2 = window.ApiClient.getParentalRatings();
|
||||||
|
Promise.all([promise1, promise2]).then(function (responses) {
|
||||||
|
loadUser(responses[0], responses[1]);
|
||||||
|
});
|
||||||
|
}, [loadUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
const onSaveComplete = () => {
|
||||||
|
loading.hide();
|
||||||
|
toast(globalize.translate('SettingsSaved'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveUser = (user) => {
|
||||||
|
user.Policy.MaxParentalRating = element?.current?.querySelector('.selectMaxParentalRating').value || null;
|
||||||
|
user.Policy.BlockUnratedItems = Array.prototype.filter.call(element?.current?.querySelectorAll('.chkUnratedItem'), function (i) {
|
||||||
|
return i.checked;
|
||||||
|
}).map(function (i) {
|
||||||
|
return i.getAttribute('data-itemtype');
|
||||||
|
});
|
||||||
|
user.Policy.AccessSchedules = getSchedulesFromPage();
|
||||||
|
user.Policy.BlockedTags = getBlockedTagsFromPage();
|
||||||
|
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||||
|
onSaveComplete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSchedulePopup = (schedule, index) => {
|
||||||
|
schedule = schedule || {};
|
||||||
|
import('../../components/accessSchedule/accessSchedule').then(({default: accessschedule}) => {
|
||||||
|
accessschedule.show({
|
||||||
|
schedule: schedule
|
||||||
|
}).then(function (updatedSchedule) {
|
||||||
|
const schedules = getSchedulesFromPage();
|
||||||
|
|
||||||
|
if (index == -1) {
|
||||||
|
index = schedules.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
schedules[index] = updatedSchedule;
|
||||||
|
renderAccessSchedule(schedules);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSchedulesFromPage = () => {
|
||||||
|
return Array.prototype.map.call(element?.current?.querySelectorAll('.liSchedule'), function (elem) {
|
||||||
|
return {
|
||||||
|
DayOfWeek: elem.getAttribute('data-day'),
|
||||||
|
StartHour: elem.getAttribute('data-start'),
|
||||||
|
EndHour: elem.getAttribute('data-end')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBlockedTagsFromPage = () => {
|
||||||
|
return Array.prototype.map.call(element?.current?.querySelectorAll('.blockedTag'), function (elem) {
|
||||||
|
return elem.getAttribute('data-tag');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const showBlockedTagPopup = () => {
|
||||||
|
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
||||||
|
prompt({
|
||||||
|
label: globalize.translate('LabelTag')
|
||||||
|
}).then(function (value) {
|
||||||
|
const tags = getBlockedTagsFromPage();
|
||||||
|
|
||||||
|
if (tags.indexOf(value) == -1) {
|
||||||
|
tags.push(value);
|
||||||
|
loadBlockedTags(tags);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (e) => {
|
||||||
|
loading.show();
|
||||||
|
const userId = appRouter.param('userId');
|
||||||
|
window.ApiClient.getUser(userId).then(function (result) {
|
||||||
|
saveUser(result);
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
element?.current?.querySelector('.btnAddSchedule').addEventListener('click', function () {
|
||||||
|
showSchedulePopup({}, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.btnAddBlockedTag').addEventListener('click', function () {
|
||||||
|
showBlockedTagPopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
element?.current?.querySelector('.userParentalControlForm').addEventListener('submit', onSubmit);
|
||||||
|
}, [loadBlockedTags, loadData, renderAccessSchedule]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={element}>
|
||||||
|
<div className='content-primary'>
|
||||||
|
<div className='verticalSection'>
|
||||||
|
<div className='sectionTitleContainer flex align-items-center'>
|
||||||
|
<h2 className='sectionTitle username'>
|
||||||
|
{userName}
|
||||||
|
</h2>
|
||||||
|
<SectionTitleLinkElement
|
||||||
|
className='raised button-alt headerHelpButton'
|
||||||
|
title='Help'
|
||||||
|
url='https://docs.jellyfin.org/general/server/users/'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SectionTabs activeTab='userparentalcontrol'/>
|
||||||
|
<form className='userParentalControlForm'>
|
||||||
|
<div className='selectContainer'>
|
||||||
|
<SelectMaxParentalRating
|
||||||
|
className= 'selectMaxParentalRating'
|
||||||
|
label= 'LabelMaxParentalRating'
|
||||||
|
parentalRatings={parentalRatings}
|
||||||
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('MaxParentalRatingHelp')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='blockUnratedItems'>
|
||||||
|
<h3 className='checkboxListLabel'>
|
||||||
|
{globalize.translate('HeaderBlockItemsWithNoRating')}
|
||||||
|
</h3>
|
||||||
|
<div className='checkboxList paperList' style={{ padding: '.5em 1em' }}>
|
||||||
|
{unratedItems.map(Item => {
|
||||||
|
return <CheckBoxListItem
|
||||||
|
key={Item.value}
|
||||||
|
className='chkUnratedItem'
|
||||||
|
ItemType={Item.value}
|
||||||
|
Name={Item.name}
|
||||||
|
checkedAttribute={Item.checkedAttribute}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className='verticalSection' style={{marginBottom: '2em'}}>
|
||||||
|
<div
|
||||||
|
className='detailSectionHeader sectionTitleContainer'
|
||||||
|
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
||||||
|
>
|
||||||
|
<h2 className='sectionTitle'>
|
||||||
|
{globalize.translate('LabelBlockContentWithTags')}
|
||||||
|
</h2>
|
||||||
|
<SectionTitleButtonElement
|
||||||
|
className='fab btnAddBlockedTag submit'
|
||||||
|
title='Add'
|
||||||
|
icon='add'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='blockedTags' style={{marginTop: '.5em'}}>
|
||||||
|
{blockedTags.map((tag, index) => {
|
||||||
|
return <BlockedTagList
|
||||||
|
key={index}
|
||||||
|
tag={tag}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='accessScheduleSection verticalSection' style={{marginBottom: '2em'}}>
|
||||||
|
<div
|
||||||
|
className='sectionTitleContainer'
|
||||||
|
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
||||||
|
>
|
||||||
|
<h2 className='sectionTitle'>
|
||||||
|
{globalize.translate('HeaderAccessSchedule')}
|
||||||
|
</h2>
|
||||||
|
<SectionTitleButtonElement
|
||||||
|
className='fab btnAddSchedule submit'
|
||||||
|
title='Add'
|
||||||
|
icon='add'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>{globalize.translate('HeaderAccessScheduleHelp')}</p>
|
||||||
|
<div className='accessScheduleList paperList'>
|
||||||
|
{accessSchedules.map((accessSchedule, index) => {
|
||||||
|
return <AccessScheduleList
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
DayOfWeek={accessSchedule.DayOfWeek}
|
||||||
|
StartHour={accessSchedule.StartHour}
|
||||||
|
EndHour={accessSchedule.EndHour}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ButtonElement
|
||||||
|
type='submit'
|
||||||
|
className='raised button-submit block'
|
||||||
|
title='Save'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserParentalControl;
|
@ -3499,7 +3499,7 @@ class PlaybackManager {
|
|||||||
this.seek(ticks, player);
|
this.seek(ticks, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
playTrailers(item) {
|
async playTrailers(item) {
|
||||||
const player = this._currentPlayer;
|
const player = this._currentPlayer;
|
||||||
|
|
||||||
if (player && player.playTrailers) {
|
if (player && player.playTrailers) {
|
||||||
@ -3508,33 +3508,31 @@ class PlaybackManager {
|
|||||||
|
|
||||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||||
|
|
||||||
const instance = this;
|
let items;
|
||||||
|
|
||||||
if (item.LocalTrailerCount) {
|
if (item.LocalTrailerCount) {
|
||||||
return apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(function (result) {
|
items = await apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id);
|
||||||
return instance.play({
|
}
|
||||||
items: result
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const remoteTrailers = item.RemoteTrailers || [];
|
|
||||||
|
|
||||||
if (!remoteTrailers.length) {
|
if (!items || !items.length) {
|
||||||
return Promise.reject();
|
items = (item.RemoteTrailers || []).map((t) => {
|
||||||
}
|
return {
|
||||||
|
Name: t.Name || (item.Name + ' Trailer'),
|
||||||
return this.play({
|
Url: t.Url,
|
||||||
items: remoteTrailers.map(function (t) {
|
MediaType: 'Video',
|
||||||
return {
|
Type: 'Trailer',
|
||||||
Name: t.Name || (item.Name + ' Trailer'),
|
ServerId: apiClient.serverId()
|
||||||
Url: t.Url,
|
};
|
||||||
MediaType: 'Video',
|
|
||||||
Type: 'Trailer',
|
|
||||||
ServerId: apiClient.serverId()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (items.length) {
|
||||||
|
return this.play({
|
||||||
|
items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubtitleUrl(textStream, serverId) {
|
getSubtitleUrl(textStream, serverId) {
|
||||||
|
@ -20,6 +20,8 @@ import ServerConnections from '../ServerConnections';
|
|||||||
import { playbackManager } from '../playback/playbackmanager';
|
import { playbackManager } from '../playback/playbackmanager';
|
||||||
import template from './recordingcreator.template.html';
|
import template from './recordingcreator.template.html';
|
||||||
|
|
||||||
|
import PlaceholderImage from './empty.png';
|
||||||
|
|
||||||
let currentDialog;
|
let currentDialog;
|
||||||
let closeAction;
|
let closeAction;
|
||||||
let currentRecordingFields;
|
let currentRecordingFields;
|
||||||
@ -70,7 +72,7 @@ function renderRecording(context, defaultTimer, program, apiClient, refreshRecor
|
|||||||
const imageContainer = context.querySelector('.recordingDialog-imageContainer');
|
const imageContainer = context.querySelector('.recordingDialog-imageContainer');
|
||||||
|
|
||||||
if (imgUrl) {
|
if (imgUrl) {
|
||||||
imageContainer.innerHTML = '<img src="./empty.png" data-src="' + imgUrl + '" class="recordingDialog-img lazy" />';
|
imageContainer.innerHTML = `<img src="${PlaceholderImage}" data-src="${imgUrl}" class="recordingDialog-img lazy" />`;
|
||||||
imageContainer.classList.remove('hide');
|
imageContainer.classList.remove('hide');
|
||||||
|
|
||||||
imageLoader.lazyChildren(imageContainer);
|
imageLoader.lazyChildren(imageContainer);
|
||||||
|
@ -203,6 +203,7 @@
|
|||||||
.layout-desktop .playlistSectionButton,
|
.layout-desktop .playlistSectionButton,
|
||||||
.layout-tv .playlistSectionButton {
|
.layout-tv .playlistSectionButton {
|
||||||
background: none;
|
background: none;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop .nowPlayingPlaylist,
|
.layout-desktop .nowPlayingPlaylist,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||||
import loading from '../../../components/loading/loading';
|
import loading from '../../../components/loading/loading';
|
||||||
import dom from '../../../scripts/dom';
|
import dom from '../../../scripts/dom';
|
||||||
import globalize from '../../../scripts/globalize';
|
import globalize from '../../../scripts/globalize';
|
||||||
@ -96,7 +97,7 @@ import confirm from '../../../components/confirm/confirm';
|
|||||||
deviceHtml += '<div class="cardBox visualCardBox">';
|
deviceHtml += '<div class="cardBox visualCardBox">';
|
||||||
deviceHtml += '<div class="cardScalable">';
|
deviceHtml += '<div class="cardScalable">';
|
||||||
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
|
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||||
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#!/device.html?id=' + device.Id : '#'}" class="cardContent cardImageContainer">`;
|
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#!/device.html?id=' + device.Id : '#'}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||||
const iconUrl = imageHelper.getDeviceIcon(device);
|
const iconUrl = imageHelper.getDeviceIcon(device);
|
||||||
|
|
||||||
if (iconUrl) {
|
if (iconUrl) {
|
||||||
|
@ -13,13 +13,12 @@
|
|||||||
<select is="emby-select" id="selectVideoDecoder" label="${LabelHardwareAccelerationType}">
|
<select is="emby-select" id="selectVideoDecoder" label="${LabelHardwareAccelerationType}">
|
||||||
<option value="">${None}</option>
|
<option value="">${None}</option>
|
||||||
<option value="amf">AMD AMF</option>
|
<option value="amf">AMD AMF</option>
|
||||||
<option value="qsv">Intel Quick Sync</option>
|
|
||||||
<option value="mediacodec">MediaCodec Android</option>
|
|
||||||
<option value="omx">OpenMAX OMX</option>
|
|
||||||
<option value="nvenc">Nvidia NVENC</option>
|
<option value="nvenc">Nvidia NVENC</option>
|
||||||
|
<option value="qsv">Intel QuickSync (QSV)</option>
|
||||||
<option value="vaapi">Video Acceleration API (VAAPI)</option>
|
<option value="vaapi">Video Acceleration API (VAAPI)</option>
|
||||||
<option value="h264_v4l2m2m">Exynos V4L2 MFC</option>
|
<option value="videotoolbox">Apple VideoToolBox</option>
|
||||||
<option value="videotoolbox">Video ToolBox</option>
|
<option value="v4l2m2m">Video4Linux2 (V4L2)</option>
|
||||||
|
<option value="omx">OpenMAX OMX</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="fieldDescription">
|
<div class="fieldDescription">
|
||||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://docs.jellyfin.org/general/administration/hardware-acceleration.html" target="_blank">${LabelHardwareAccelerationTypeHelp}</a>
|
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://docs.jellyfin.org/general/administration/hardware-acceleration.html" target="_blank">${LabelHardwareAccelerationTypeHelp}</a>
|
||||||
@ -31,57 +30,53 @@
|
|||||||
<div class="fieldDescription">${LabelVaapiDeviceHelp}</div>
|
<div class="fieldDescription">${LabelVaapiDeviceHelp}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inputContainer hide fldOpenclDevice">
|
|
||||||
<input is="emby-input" type="text" id="txtOpenclDevice" label="${LabelOpenclDevice}" />
|
|
||||||
<div class="fieldDescription">${LabelOpenclDeviceHelp}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hardwareAccelerationOptions hide">
|
<div class="hardwareAccelerationOptions hide">
|
||||||
<div class="checkboxListContainer decodingCodecsList">
|
<div class="checkboxListContainer decodingCodecsList">
|
||||||
<h3 class="checkboxListLabel">${LabelEnableHardwareDecodingFor}</h3>
|
<h3 class="checkboxListLabel">${LabelEnableHardwareDecodingFor}</h3>
|
||||||
<div class="checkboxList">
|
<div class="checkboxList">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="h264" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="h264" data-types="amf,nvenc,qsv,vaapi,videotoolbox,v4l2m2m,omx" />
|
||||||
<span>H264</span>
|
<span>H264</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="hevc" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="hevc" data-types="amf,nvenc,qsv,vaapi,videotoolbox" />
|
||||||
<span>HEVC</span>
|
<span>HEVC</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,nvenc,qsv,vaapi,videotoolbox" />
|
||||||
<span>MPEG2</span>
|
<span>MPEG2</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="amf,nvenc,omx,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="amf,nvenc,videotoolbox" />
|
||||||
<span>MPEG4</span>
|
<span>MPEG4</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,qsv,nvenc,vaapi,omx,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,nvenc,qsv,vaapi,videotoolbox" />
|
||||||
<span>VC1</span>
|
<span>VC1</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp8" data-types="qsv,nvenc,vaapi,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp8" data-types="nvenc,qsv,vaapi,videotoolbox" />
|
||||||
<span>VP8</span>
|
<span>VP8</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp9" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp9" data-types="amf,nvenc,qsv,vaapi,videotoolbox" />
|
||||||
<span>VP9</span>
|
<span>VP9</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="av1" data-types="amf,nvenc,qsv,vaapi" />
|
||||||
|
<span>AV1</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkboxList hide fld10bitHevcVp9HwDecoding">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Hevc" />
|
||||||
|
<span>HEVC 10bit</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Vp9" />
|
||||||
|
<span>VP9 10bit</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkboxListContainer">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Hevc" />
|
|
||||||
<span>${EnableDecodingColorDepth10Hevc}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="checkboxListContainer">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Vp9" />
|
|
||||||
<span>${EnableDecodingColorDepth10Vp9}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkboxListContainer hide fldEnhancedNvdec">
|
<div class="checkboxListContainer hide fldEnhancedNvdec">
|
||||||
@ -91,13 +86,34 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxListContainer hide fldSysNativeHwDecoder">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkSystemNativeHwDecoder" />
|
||||||
|
<span>${PreferSystemNativeHwDecoder}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkboxListContainer">
|
<div class="checkboxListContainer">
|
||||||
|
<h3 class="checkboxListLabel">${LabelHardwareEncodingOptions}</h3>
|
||||||
<div class="checkboxList">
|
<div class="checkboxList">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkHardwareEncoding" />
|
<input type="checkbox" is="emby-checkbox" id="chkHardwareEncoding" />
|
||||||
<span>${EnableHardwareEncoding}</span>
|
<span>${EnableHardwareEncoding}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="checkboxList hide fldIntelLp">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkIntelLpH264HwEncoder" />
|
||||||
|
<span>${EnableIntelLowPowerH264HwEncoder}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkIntelLpHevcHwEncoder" />
|
||||||
|
<span>${EnableIntelLowPowerHevcHwEncoder}</span>
|
||||||
|
</label>
|
||||||
|
<div class="fieldDescription">
|
||||||
|
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://01.org/linuxgraphics/downloads/firmware" target="_blank">${IntelLowPowerEncHelp}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -177,6 +193,14 @@
|
|||||||
<option value="6">6</option>
|
<option value="6">6</option>
|
||||||
<option value="7">7</option>
|
<option value="7">7</option>
|
||||||
<option value="8">8</option>
|
<option value="8">8</option>
|
||||||
|
<option value="9">9</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="11">11</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="13">13</option>
|
||||||
|
<option value="14">14</option>
|
||||||
|
<option value="15">15</option>
|
||||||
|
<option value="16">16</option>
|
||||||
<option value="0">${OptionMax}</option>
|
<option value="0">${OptionMax}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="fieldDescription">${LabelTranscodingThreadCountHelp}</div>
|
<div class="fieldDescription">${LabelTranscodingThreadCountHelp}</div>
|
||||||
|
@ -15,6 +15,9 @@ import alert from '../../components/alert';
|
|||||||
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
|
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
|
||||||
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
|
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
|
||||||
page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder;
|
page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder;
|
||||||
|
page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder;
|
||||||
|
page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder;
|
||||||
|
page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder;
|
||||||
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
|
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
|
||||||
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
|
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
|
||||||
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
|
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
|
||||||
@ -28,7 +31,6 @@ import alert from '../../components/alert';
|
|||||||
$('#txtVaapiDevice', page).val(config.VaapiDevice || '');
|
$('#txtVaapiDevice', page).val(config.VaapiDevice || '');
|
||||||
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
|
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
|
||||||
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
|
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
|
||||||
page.querySelector('#txtOpenclDevice').value = config.OpenclDevice || '';
|
|
||||||
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
|
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
|
||||||
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
|
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
|
||||||
page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat;
|
page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat;
|
||||||
@ -81,7 +83,6 @@ import alert from '../../components/alert';
|
|||||||
config.EncodingThreadCount = $('#selectThreadCount', form).val();
|
config.EncodingThreadCount = $('#selectThreadCount', form).val();
|
||||||
config.HardwareAccelerationType = $('#selectVideoDecoder', form).val();
|
config.HardwareAccelerationType = $('#selectVideoDecoder', form).val();
|
||||||
config.VaapiDevice = $('#txtVaapiDevice', form).val();
|
config.VaapiDevice = $('#txtVaapiDevice', form).val();
|
||||||
config.OpenclDevice = form.querySelector('#txtOpenclDevice').value;
|
|
||||||
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
|
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
|
||||||
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
|
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
|
||||||
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
|
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
|
||||||
@ -105,6 +106,9 @@ import alert from '../../components/alert';
|
|||||||
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
|
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
|
||||||
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
|
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
|
||||||
config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked;
|
config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked;
|
||||||
|
config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked;
|
||||||
|
config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked;
|
||||||
|
config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked;
|
||||||
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
|
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
|
||||||
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
|
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
|
||||||
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
|
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
|
||||||
@ -182,32 +186,42 @@ import alert from '../../components/alert';
|
|||||||
page.querySelector('#txtVaapiDevice').removeAttribute('required');
|
page.querySelector('#txtVaapiDevice').removeAttribute('required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value == 'nvenc' || this.value == 'amf') {
|
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') {
|
||||||
page.querySelector('.fldOpenclDevice').classList.remove('hide');
|
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide');
|
||||||
page.querySelector('#txtOpenclDevice').setAttribute('required', 'required');
|
} else {
|
||||||
page.querySelector('.tonemappingOptions').classList.remove('hide');
|
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide');
|
||||||
} else if (this.value == 'vaapi') {
|
}
|
||||||
page.querySelector('.fldOpenclDevice').classList.add('hide');
|
|
||||||
page.querySelector('#txtOpenclDevice').removeAttribute('required');
|
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') {
|
||||||
page.querySelector('.tonemappingOptions').classList.remove('hide');
|
page.querySelector('.tonemappingOptions').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
page.querySelector('.fldOpenclDevice').classList.add('hide');
|
|
||||||
page.querySelector('#txtOpenclDevice').removeAttribute('required');
|
|
||||||
page.querySelector('.tonemappingOptions').classList.add('hide');
|
page.querySelector('.tonemappingOptions').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.value == 'qsv' || this.value == 'vaapi') {
|
||||||
|
page.querySelector('.fldIntelLp').classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
page.querySelector('.fldIntelLp').classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) {
|
||||||
|
page.querySelector('.fldVppTonemapping').classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
page.querySelector('.fldVppTonemapping').classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value == 'qsv') {
|
||||||
|
page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
page.querySelector('.fldSysNativeHwDecoder').classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
if (this.value == 'nvenc') {
|
if (this.value == 'nvenc') {
|
||||||
page.querySelector('.fldEnhancedNvdec').classList.remove('hide');
|
page.querySelector('.fldEnhancedNvdec').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
page.querySelector('.fldEnhancedNvdec').classList.add('hide');
|
page.querySelector('.fldEnhancedNvdec').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'vaapi' || this.value == 'qsv')) {
|
|
||||||
page.querySelector('.fldVppTonemapping').classList.remove('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.fldVppTonemapping').classList.add('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.value) {
|
if (this.value) {
|
||||||
page.querySelector('.hardwareAccelerationOptions').classList.remove('hide');
|
page.querySelector('.hardwareAccelerationOptions').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
<button is="emby-button" type="button" class="fab btnNewRepository submit" style="margin-left:1em;" title="${Add}">
|
<button is="emby-button" type="button" class="fab btnNewRepository submit" style="margin-left:1em;" title="${Add}">
|
||||||
<span class="material-icons add" aria-hidden="true"></span>
|
<span class="material-icons add" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
|
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/plugins/index.html#repositories">
|
||||||
|
${Help}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="repositories"></div>
|
<div id="repositories"></div>
|
||||||
|
@ -1,194 +1,3 @@
|
|||||||
<div id="editUserPage" data-role="page" class="page type-interior">
|
<div id="editUserPage" data-role="page" class="page type-interior">
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="content-primary">
|
|
||||||
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="sectionTitleContainer flex align-items-center">
|
|
||||||
<h2 class="sectionTitle username"></h2>
|
|
||||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" id="userProfileNavigation" data-mini="true">
|
|
||||||
<a href="#" is="emby-linkbutton" data-role="button" class="ui-btn-active">${Profile}</a>
|
|
||||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
|
|
||||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
|
|
||||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
|
||||||
</div>
|
|
||||||
<p class="lnkEditUserPreferencesContainer">
|
|
||||||
<a class="lnkEditUserPreferences button-link" href="#" is="emby-linkbutton">${ButtonEditOtherUserPreferences}</a>
|
|
||||||
</p>
|
|
||||||
<form class="editUserProfileForm">
|
|
||||||
|
|
||||||
<div class="disabledUserBanner" style="display: none;">
|
|
||||||
<div class="btn btnDarkAccent btnStatic">
|
|
||||||
<div>
|
|
||||||
${HeaderThisUserIsCurrentlyDisabled}
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 5px;">
|
|
||||||
${MessageReenableUser}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="fldUserName" class="inputContainer">
|
|
||||||
<input is="emby-input" id="txtUserName" required type="text" label="${LabelName}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="selectContainer fldSelectLoginProvider hide">
|
|
||||||
<select class="selectLoginProvider" is="emby-select" label="${LabelAuthProvider}"></select>
|
|
||||||
<div class="fieldDescription">${AuthProviderHelp}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="selectContainer fldSelectPasswordResetProvider hide">
|
|
||||||
<select class="selectPasswordResetProvider" is="emby-select" label="${LabelPasswordResetProvider}"></select>
|
|
||||||
<div class="fieldDescription">${PasswordResetProviderHelp}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription fldRemoteAccess hide">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkRemoteAccess" />
|
|
||||||
<span>${AllowRemoteAccess}</span>
|
|
||||||
</label>
|
|
||||||
<div class="fieldDescription checkboxFieldDescription">${AllowRemoteAccessHelp}</div>
|
|
||||||
</div>
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkIsAdmin" />
|
|
||||||
<span>${OptionAllowUserToManageServer}</span>
|
|
||||||
</label>
|
|
||||||
<div id="featureAccessFields" class="verticalSection">
|
|
||||||
<h2 class="paperListLabel">${HeaderFeatureAccess}</h2>
|
|
||||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableLiveTvAccess" />
|
|
||||||
<span>${OptionAllowBrowsingLiveTv}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkManageLiveTv" />
|
|
||||||
<span>${OptionAllowManageLiveTv}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="verticalSection">
|
|
||||||
<h2 class="paperListLabel">${HeaderPlayback}</h2>
|
|
||||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableMediaPlayback" />
|
|
||||||
<span>${OptionAllowMediaPlayback}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAudioPlaybackTranscoding" />
|
|
||||||
<span>${OptionAllowAudioPlaybackTranscoding}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackTranscoding" />
|
|
||||||
<span>${OptionAllowVideoPlaybackTranscoding}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackRemuxing" />
|
|
||||||
<span>${OptionAllowVideoPlaybackRemuxing}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkForceRemoteSourceTranscoding" />
|
|
||||||
<span>${OptionForceRemoteSourceTranscoding}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${OptionAllowMediaPlaybackTranscodingHelp}</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="inputContainer">
|
|
||||||
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" inputmode="decimal" pattern="[0-9]*(\.[0-9]+)?" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
|
|
||||||
<div class="fieldDescription">${LabelRemoteClientBitrateLimitHelp}</div>
|
|
||||||
<div class="fieldDescription">${LabelUserRemoteClientBitrateLimitHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="selectContainer fldSelectSyncPlayAccess">
|
|
||||||
<select class="selectSyncPlayAccess" is="emby-select" id="selectSyncPlayAccess" label="${LabelSyncPlayAccess}">
|
|
||||||
<option value="CreateAndJoinGroups">${LabelSyncPlayAccessCreateAndJoinGroups}</option>
|
|
||||||
<option value="JoinGroups">${LabelSyncPlayAccessJoinGroups}</option>
|
|
||||||
<option value="None">${LabelSyncPlayAccessNone}</option>
|
|
||||||
</select>
|
|
||||||
<div class="fieldDescription">${SyncPlayAccessHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="verticalSection">
|
|
||||||
<h2 class="checkboxListLabel" style="margin-bottom:1em;">${HeaderAllowMediaDeletionFrom}</h2>
|
|
||||||
<div class="checkboxList paperList checkboxList-paperList">
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableDeleteAllFolders" />
|
|
||||||
<span>${AllLibraries}</span>
|
|
||||||
</label>
|
|
||||||
<div class="deleteAccess">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="verticalSection">
|
|
||||||
<h2 class="checkboxListLabel">${HeaderRemoteControl}</h2>
|
|
||||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableRemoteControlOtherUsers" />
|
|
||||||
<span>${OptionAllowRemoteControlOthers}</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkRemoteControlSharedDevices" />
|
|
||||||
<span>${OptionAllowRemoteSharedDevices}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${OptionAllowRemoteSharedDevicesHelp}</div>
|
|
||||||
</div>
|
|
||||||
<h2 class="checkboxListLabel">${Other}</h2>
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableDownloading" />
|
|
||||||
<span>${OptionAllowContentDownload}</span>
|
|
||||||
</label>
|
|
||||||
<div class="fieldDescription checkboxFieldDescription">${OptionAllowContentDownloadHelp}</div>
|
|
||||||
</div>
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsEnabled">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkDisabled" />
|
|
||||||
<span>${OptionDisableUser}</span>
|
|
||||||
</label>
|
|
||||||
<div class="fieldDescription checkboxFieldDescription">${OptionDisableUserHelp}</div>
|
|
||||||
</div>
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsHidden">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkIsHidden" />
|
|
||||||
<span>${OptionHideUser}</span>
|
|
||||||
</label>
|
|
||||||
<div class="fieldDescription checkboxFieldDescription">${OptionHideUserFromLoginHelp}</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<div class=verticalSection>
|
|
||||||
<div class="inputContainer" id="fldLoginAttemptsBeforeLockout">
|
|
||||||
<input is="emby-input" type="number" id="txtLoginAttemptsBeforeLockout" min="-1" step="1" label="${LabelUserLoginAttemptsBeforeLockout}"/>
|
|
||||||
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockout}</div>
|
|
||||||
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockoutHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class=verticalSection>
|
|
||||||
<div class="inputContainer" id="fldMaxActiveSessions">
|
|
||||||
<input is="emby-input" type="number" id="txtMaxActiveSessions" min="0" step="1" label="${LabelUserMaxActiveSessions}"/>
|
|
||||||
<div class="fieldDescription">${OptionMaxActiveSessions}</div>
|
|
||||||
<div class="fieldDescription">${OptionMaxActiveSessionsHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
|
||||||
<span>${Save}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
|
|
||||||
<span>${ButtonCancel}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,196 +0,0 @@
|
|||||||
import 'jquery';
|
|
||||||
import loading from '../../../components/loading/loading';
|
|
||||||
import libraryMenu from '../../../scripts/libraryMenu';
|
|
||||||
import globalize from '../../../scripts/globalize';
|
|
||||||
import Dashboard from '../../../scripts/clientUtils';
|
|
||||||
import toast from '../../../components/toast/toast';
|
|
||||||
|
|
||||||
/* eslint-disable indent */
|
|
||||||
|
|
||||||
function loadDeleteFolders(page, user, mediaFolders) {
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Channels', {
|
|
||||||
SupportsMediaDeletion: true
|
|
||||||
})).then(function (channelsResult) {
|
|
||||||
let isChecked;
|
|
||||||
let checkedAttribute;
|
|
||||||
let html = '';
|
|
||||||
|
|
||||||
for (const folder of mediaFolders) {
|
|
||||||
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
|
||||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const folder of channelsResult.Items) {
|
|
||||||
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
|
||||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.deleteAccess', page).html(html).trigger('create');
|
|
||||||
$('#chkEnableDeleteAllFolders', page).prop('checked', user.Policy.EnableContentDeletion);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadAuthProviders(page, user, providers) {
|
|
||||||
if (providers.length > 1) {
|
|
||||||
page.querySelector('.fldSelectLoginProvider').classList.remove('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.fldSelectLoginProvider').classList.add('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentProviderId = user.Policy.AuthenticationProviderId;
|
|
||||||
page.querySelector('.selectLoginProvider').innerHTML = providers.map(function (provider) {
|
|
||||||
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
|
|
||||||
return '<option value="' + provider.Id + '"' + selected + '>' + provider.Name + '</option>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadPasswordResetProviders(page, user, providers) {
|
|
||||||
if (providers.length > 1) {
|
|
||||||
page.querySelector('.fldSelectPasswordResetProvider').classList.remove('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.fldSelectPasswordResetProvider').classList.add('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentProviderId = user.Policy.PasswordResetProviderId;
|
|
||||||
page.querySelector('.selectPasswordResetProvider').innerHTML = providers.map(function (provider) {
|
|
||||||
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
|
|
||||||
return '<option value="' + provider.Id + '"' + selected + '>' + provider.Name + '</option>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUser(page, user) {
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Auth/Providers')).then(function (providers) {
|
|
||||||
loadAuthProviders(page, user, providers);
|
|
||||||
});
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) {
|
|
||||||
loadPasswordResetProviders(page, user, providers);
|
|
||||||
});
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', {
|
|
||||||
IsHidden: false
|
|
||||||
})).then(function (folders) {
|
|
||||||
loadDeleteFolders(page, user, folders.Items);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user.Policy.IsDisabled) {
|
|
||||||
$('.disabledUserBanner', page).show();
|
|
||||||
} else {
|
|
||||||
$('.disabledUserBanner', page).hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#txtUserName', page).prop('disabled', '').removeAttr('disabled');
|
|
||||||
$('#fldConnectInfo', page).show();
|
|
||||||
$('.lnkEditUserPreferences', page).attr('href', 'mypreferencesmenu.html?userId=' + user.Id);
|
|
||||||
libraryMenu.setTitle(user.Name);
|
|
||||||
page.querySelector('.username').innerHTML = user.Name;
|
|
||||||
$('#txtUserName', page).val(user.Name);
|
|
||||||
$('#chkIsAdmin', page).prop('checked', user.Policy.IsAdministrator);
|
|
||||||
$('#chkDisabled', page).prop('checked', user.Policy.IsDisabled);
|
|
||||||
$('#chkIsHidden', page).prop('checked', user.Policy.IsHidden);
|
|
||||||
$('#chkRemoteControlSharedDevices', page).prop('checked', user.Policy.EnableSharedDeviceControl);
|
|
||||||
$('#chkEnableRemoteControlOtherUsers', page).prop('checked', user.Policy.EnableRemoteControlOfOtherUsers);
|
|
||||||
$('#chkEnableDownloading', page).prop('checked', user.Policy.EnableContentDownloading);
|
|
||||||
$('#chkManageLiveTv', page).prop('checked', user.Policy.EnableLiveTvManagement);
|
|
||||||
$('#chkEnableLiveTvAccess', page).prop('checked', user.Policy.EnableLiveTvAccess);
|
|
||||||
$('#chkEnableMediaPlayback', page).prop('checked', user.Policy.EnableMediaPlayback);
|
|
||||||
$('#chkEnableAudioPlaybackTranscoding', page).prop('checked', user.Policy.EnableAudioPlaybackTranscoding);
|
|
||||||
$('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding);
|
|
||||||
$('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing);
|
|
||||||
$('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding);
|
|
||||||
$('#chkRemoteAccess', page).prop('checked', user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess);
|
|
||||||
$('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || '');
|
|
||||||
$('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0');
|
|
||||||
$('#txtMaxActiveSessions', page).val(user.Policy.MaxActiveSessions || '0');
|
|
||||||
if (ApiClient.isMinServerVersion('10.6.0')) {
|
|
||||||
$('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess);
|
|
||||||
}
|
|
||||||
loading.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSaveComplete() {
|
|
||||||
Dashboard.navigate('userprofiles.html');
|
|
||||||
loading.hide();
|
|
||||||
toast(globalize.translate('SettingsSaved'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUser(user, page) {
|
|
||||||
user.Name = $('#txtUserName', page).val();
|
|
||||||
user.Policy.IsAdministrator = $('#chkIsAdmin', page).is(':checked');
|
|
||||||
user.Policy.IsHidden = $('#chkIsHidden', page).is(':checked');
|
|
||||||
user.Policy.IsDisabled = $('#chkDisabled', page).is(':checked');
|
|
||||||
user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).is(':checked');
|
|
||||||
user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).is(':checked');
|
|
||||||
user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).is(':checked');
|
|
||||||
user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).is(':checked');
|
|
||||||
user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).is(':checked');
|
|
||||||
user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).is(':checked');
|
|
||||||
user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).is(':checked');
|
|
||||||
user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).is(':checked');
|
|
||||||
user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked');
|
|
||||||
user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked');
|
|
||||||
user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked');
|
|
||||||
user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0'));
|
|
||||||
user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0');
|
|
||||||
user.Policy.MaxActiveSessions = parseInt($('#txtMaxActiveSessions', page).val() || '0');
|
|
||||||
user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value;
|
|
||||||
user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value;
|
|
||||||
user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).is(':checked');
|
|
||||||
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : $('.chkFolder', page).get().filter(function (c) {
|
|
||||||
return c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-id');
|
|
||||||
});
|
|
||||||
if (ApiClient.isMinServerVersion('10.6.0')) {
|
|
||||||
user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value;
|
|
||||||
}
|
|
||||||
ApiClient.updateUser(user).then(function () {
|
|
||||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
|
||||||
onSaveComplete();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSubmit() {
|
|
||||||
const page = $(this).parents('.page')[0];
|
|
||||||
loading.show();
|
|
||||||
getUser().then(function (result) {
|
|
||||||
saveUser(result, page);
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUser() {
|
|
||||||
const userId = getParameterByName('userId');
|
|
||||||
return ApiClient.getUser(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadData(page) {
|
|
||||||
loading.show();
|
|
||||||
getUser().then(function (user) {
|
|
||||||
loadUser(page, user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on('pageinit', '#editUserPage', function () {
|
|
||||||
$('.editUserProfileForm').off('submit', onSubmit).on('submit', onSubmit);
|
|
||||||
const page = this;
|
|
||||||
$('#chkEnableDeleteAllFolders', this).on('change', function () {
|
|
||||||
if (this.checked) {
|
|
||||||
$('.deleteAccess', page).hide();
|
|
||||||
} else {
|
|
||||||
$('.deleteAccess', page).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ApiClient.getServerConfiguration().then(function (config) {
|
|
||||||
if (config.EnableRemoteAccess) {
|
|
||||||
page.querySelector('.fldRemoteAccess').classList.remove('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.fldRemoteAccess').classList.add('hide');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).on('pagebeforeshow', '#editUserPage', function () {
|
|
||||||
loadData(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint-enable indent */
|
|
@ -1,68 +1,3 @@
|
|||||||
<div id="userLibraryAccessPage" data-role="page" class="page type-interior">
|
<div id="userLibraryAccessPage" data-role="page" class="page type-interior">
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="content-primary">
|
|
||||||
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="sectionTitleContainer flex align-items-center">
|
|
||||||
<h2 class="sectionTitle username"></h2>
|
|
||||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${Profile}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);" class="ui-btn-active">${TabAccess}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
|
||||||
</div>
|
|
||||||
<form class="userLibraryAccessForm">
|
|
||||||
|
|
||||||
<div class="folderAccessContainer">
|
|
||||||
<h2>${HeaderLibraryAccess}</h2>
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllFolders" />
|
|
||||||
<span>${OptionEnableAccessToAllLibraries}</span>
|
|
||||||
</label>
|
|
||||||
<div class="folderAccessListContainer">
|
|
||||||
<div class="folderAccess">
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${LibraryAccessHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="channelAccessContainer" style="display:none;">
|
|
||||||
<h2>${HeaderChannelAccess}</h2>
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllChannels" />
|
|
||||||
<span>${OptionEnableAccessToAllChannels}</span>
|
|
||||||
</label>
|
|
||||||
<div class="channelAccessListContainer">
|
|
||||||
<div class="channelAccess">
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${ChannelAccessHelp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="deviceAccessContainer hide">
|
|
||||||
<h2>${HeaderDeviceAccess}</h2>
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllDevices" />
|
|
||||||
<span>${OptionEnableAccessFromAllDevices}</span>
|
|
||||||
</label>
|
|
||||||
<div class="deviceAccessListContainer">
|
|
||||||
<div class="deviceAccess">
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${DeviceAccessHelp}</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
|
||||||
<span>${Save}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
import 'jquery';
|
|
||||||
import loading from '../../../components/loading/loading';
|
|
||||||
import libraryMenu from '../../../scripts/libraryMenu';
|
|
||||||
import globalize from '../../../scripts/globalize';
|
|
||||||
import Dashboard from '../../../scripts/clientUtils';
|
|
||||||
import toast from '../../../components/toast/toast';
|
|
||||||
|
|
||||||
/* eslint-disable indent */
|
|
||||||
|
|
||||||
function triggerChange(select) {
|
|
||||||
const evt = document.createEvent('HTMLEvents');
|
|
||||||
evt.initEvent('change', false, true);
|
|
||||||
select.dispatchEvent(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMediaFolders(page, user, mediaFolders) {
|
|
||||||
let html = '';
|
|
||||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderLibraries') + '</h3>';
|
|
||||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
|
||||||
|
|
||||||
for (let i = 0, length = mediaFolders.length; i < length; i++) {
|
|
||||||
const folder = mediaFolders[i];
|
|
||||||
const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1;
|
|
||||||
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
page.querySelector('.folderAccess').innerHTML = html;
|
|
||||||
const chkEnableAllFolders = page.querySelector('#chkEnableAllFolders');
|
|
||||||
chkEnableAllFolders.checked = user.Policy.EnableAllFolders;
|
|
||||||
triggerChange(chkEnableAllFolders);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadChannels(page, user, channels) {
|
|
||||||
let html = '';
|
|
||||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('Channels') + '</h3>';
|
|
||||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
|
||||||
|
|
||||||
for (let i = 0, length = channels.length; i < length; i++) {
|
|
||||||
const folder = channels[i];
|
|
||||||
const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1;
|
|
||||||
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkChannel" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
$('.channelAccess', page).show().html(html);
|
|
||||||
|
|
||||||
if (channels.length) {
|
|
||||||
$('.channelAccessContainer', page).show();
|
|
||||||
} else {
|
|
||||||
$('.channelAccessContainer', page).hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
const chkEnableAllChannels = page.querySelector('#chkEnableAllChannels');
|
|
||||||
chkEnableAllChannels.checked = user.Policy.EnableAllChannels;
|
|
||||||
triggerChange(chkEnableAllChannels);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDevices(page, user, devices) {
|
|
||||||
let html = '';
|
|
||||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderDevices') + '</h3>';
|
|
||||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
|
||||||
|
|
||||||
for (let i = 0, length = devices.length; i < length; i++) {
|
|
||||||
const device = devices[i];
|
|
||||||
const checkedAttribute = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1 ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkDevice" data-id="' + device.Id + '" ' + checkedAttribute + '><span>' + device.Name + ' - ' + device.AppName + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
$('.deviceAccess', page).show().html(html);
|
|
||||||
const chkEnableAllDevices = page.querySelector('#chkEnableAllDevices');
|
|
||||||
chkEnableAllDevices.checked = user.Policy.EnableAllDevices;
|
|
||||||
triggerChange(chkEnableAllDevices);
|
|
||||||
|
|
||||||
if (user.Policy.IsAdministrator) {
|
|
||||||
page.querySelector('.deviceAccessContainer').classList.add('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.deviceAccessContainer').classList.remove('hide');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUser(page, user, loggedInUser, mediaFolders, channels, devices) {
|
|
||||||
page.querySelector('.username').innerHTML = user.Name;
|
|
||||||
libraryMenu.setTitle(user.Name);
|
|
||||||
loadChannels(page, user, channels);
|
|
||||||
loadMediaFolders(page, user, mediaFolders);
|
|
||||||
loadDevices(page, user, devices);
|
|
||||||
loading.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSaveComplete() {
|
|
||||||
loading.hide();
|
|
||||||
toast(globalize.translate('SettingsSaved'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUser(user, page) {
|
|
||||||
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked');
|
|
||||||
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : $('.chkFolder', page).get().filter(function (c) {
|
|
||||||
return c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-id');
|
|
||||||
});
|
|
||||||
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked');
|
|
||||||
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : $('.chkChannel', page).get().filter(function (c) {
|
|
||||||
return c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-id');
|
|
||||||
});
|
|
||||||
user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).is(':checked');
|
|
||||||
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $('.chkDevice', page).get().filter(function (c) {
|
|
||||||
return c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-id');
|
|
||||||
});
|
|
||||||
user.Policy.BlockedChannels = null;
|
|
||||||
user.Policy.BlockedMediaFolders = null;
|
|
||||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
|
||||||
onSaveComplete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSubmit() {
|
|
||||||
const page = $(this).parents('.page');
|
|
||||||
loading.show();
|
|
||||||
const userId = getParameterByName('userId');
|
|
||||||
ApiClient.getUser(userId).then(function (result) {
|
|
||||||
saveUser(result, page);
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on('pageinit', '#userLibraryAccessPage', function () {
|
|
||||||
const page = this;
|
|
||||||
$('#chkEnableAllDevices', page).on('change', function () {
|
|
||||||
if (this.checked) {
|
|
||||||
$('.deviceAccessListContainer', page).hide();
|
|
||||||
} else {
|
|
||||||
$('.deviceAccessListContainer', page).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#chkEnableAllChannels', page).on('change', function () {
|
|
||||||
if (this.checked) {
|
|
||||||
$('.channelAccessListContainer', page).hide();
|
|
||||||
} else {
|
|
||||||
$('.channelAccessListContainer', page).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
page.querySelector('#chkEnableAllFolders').addEventListener('change', function () {
|
|
||||||
if (this.checked) {
|
|
||||||
page.querySelector('.folderAccessListContainer').classList.add('hide');
|
|
||||||
} else {
|
|
||||||
page.querySelector('.folderAccessListContainer').classList.remove('hide');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('.userLibraryAccessForm').off('submit', onSubmit).on('submit', onSubmit);
|
|
||||||
}).on('pageshow', '#userLibraryAccessPage', function () {
|
|
||||||
const page = this;
|
|
||||||
loading.show();
|
|
||||||
let promise1;
|
|
||||||
const userId = getParameterByName('userId');
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
promise1 = ApiClient.getUser(userId);
|
|
||||||
} else {
|
|
||||||
const deferred = $.Deferred();
|
|
||||||
deferred.resolveWith(null, [{
|
|
||||||
Configuration: {}
|
|
||||||
}]);
|
|
||||||
promise1 = deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise2 = Dashboard.getCurrentUser();
|
|
||||||
const promise4 = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', {
|
|
||||||
IsHidden: false
|
|
||||||
}));
|
|
||||||
const promise5 = ApiClient.getJSON(ApiClient.getUrl('Channels'));
|
|
||||||
const promise6 = ApiClient.getJSON(ApiClient.getUrl('Devices'));
|
|
||||||
Promise.all([promise1, promise2, promise4, promise5, promise6]).then(function (responses) {
|
|
||||||
loadUser(page, responses[0], responses[1], responses[2].Items, responses[3].Items, responses[4].Items);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint-enable indent */
|
|
@ -1,60 +1,3 @@
|
|||||||
<div id="userParentalControlPage" data-role="page" class="page type-interior">
|
<div id="userParentalControlPage" data-role="page" class="page type-interior">
|
||||||
<div>
|
|
||||||
<div class="content-primary">
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="sectionTitleContainer flex align-items-center">
|
|
||||||
<h2 class="sectionTitle username"></h2>
|
|
||||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${Profile}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);" class="ui-btn-active">${TabParentalControl}</a>
|
|
||||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="userParentalControlForm">
|
|
||||||
<div class="selectContainer">
|
|
||||||
<select is="emby-select" id="selectMaxParentalRating" label="${LabelMaxParentalRating}"></select>
|
|
||||||
<div class="fieldDescription">${MaxParentalRatingHelp}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="blockUnratedItems"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="verticalSection" style="margin-bottom:2em;">
|
|
||||||
<div class="detailSectionHeader sectionTitleContainer">
|
|
||||||
<h2 class="sectionTitle">${LabelBlockContentWithTags}</h2>
|
|
||||||
<button is="emby-button" type="button" class="fab btnAddBlockedTag submit" style="margin-left:1em;" title="${Add}">
|
|
||||||
<span class="material-icons add"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="blockedTags" style="margin-top:.5em;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="accessScheduleSection verticalSection" style="margin-bottom:2em;">
|
|
||||||
<div class="sectionTitleContainer">
|
|
||||||
<h2 class="sectionTitle">${HeaderAccessSchedule}</h2>
|
|
||||||
<button is="emby-button" type="button" class="fab btnAddSchedule submit" style="margin-left:1em;" title="${Add}">
|
|
||||||
<span class="material-icons add"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>${HeaderAccessScheduleHelp}</p>
|
|
||||||
<div class="accessScheduleList paperList"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
|
||||||
<span>${Save}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,274 +0,0 @@
|
|||||||
import 'jquery';
|
|
||||||
import datetime from '../../../scripts/datetime';
|
|
||||||
import loading from '../../../components/loading/loading';
|
|
||||||
import libraryMenu from '../../../scripts/libraryMenu';
|
|
||||||
import globalize from '../../../scripts/globalize';
|
|
||||||
import '../../../components/listview/listview.scss';
|
|
||||||
import '../../../elements/emby-button/paper-icon-button-light';
|
|
||||||
import toast from '../../../components/toast/toast';
|
|
||||||
|
|
||||||
/* eslint-disable indent */
|
|
||||||
|
|
||||||
function populateRatings(allParentalRatings, page) {
|
|
||||||
let html = '';
|
|
||||||
html += "<option value=''></option>";
|
|
||||||
let rating;
|
|
||||||
const ratings = [];
|
|
||||||
|
|
||||||
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
|
||||||
if (rating = allParentalRatings[i], ratings.length) {
|
|
||||||
const lastRating = ratings[ratings.length - 1];
|
|
||||||
|
|
||||||
if (lastRating.Value === rating.Value) {
|
|
||||||
lastRating.Name += '/' + rating.Name;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ratings.push({
|
|
||||||
Name: rating.Name,
|
|
||||||
Value: rating.Value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0, length = ratings.length; i < length; i++) {
|
|
||||||
rating = ratings[i];
|
|
||||||
html += "<option value='" + rating.Value + "'>" + rating.Name + '</option>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#selectMaxParentalRating', page).html(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUnratedItems(page, user) {
|
|
||||||
const items = [{
|
|
||||||
name: globalize.translate('Books'),
|
|
||||||
value: 'Book'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Channels'),
|
|
||||||
value: 'ChannelContent'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('LiveTV'),
|
|
||||||
value: 'LiveTvChannel'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Movies'),
|
|
||||||
value: 'Movie'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Music'),
|
|
||||||
value: 'Music'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Trailers'),
|
|
||||||
value: 'Trailer'
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Shows'),
|
|
||||||
value: 'Series'
|
|
||||||
}];
|
|
||||||
let html = '';
|
|
||||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderBlockItemsWithNoRating') + '</h3>';
|
|
||||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
|
||||||
|
|
||||||
for (let i = 0, length = items.length; i < length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const checkedAttribute = user.Policy.BlockUnratedItems.indexOf(item.value) != -1 ? ' checked="checked"' : '';
|
|
||||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkUnratedItem" data-itemtype="' + item.value + '" type="checkbox"' + checkedAttribute + '><span>' + item.name + '</span></label>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
$('.blockUnratedItems', page).html(html).trigger('create');
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUser(page, user, allParentalRatings) {
|
|
||||||
page.querySelector('.username').innerHTML = user.Name;
|
|
||||||
libraryMenu.setTitle(user.Name);
|
|
||||||
loadUnratedItems(page, user);
|
|
||||||
loadBlockedTags(page, user.Policy.BlockedTags);
|
|
||||||
populateRatings(allParentalRatings, page);
|
|
||||||
let ratingValue = '';
|
|
||||||
|
|
||||||
if (user.Policy.MaxParentalRating) {
|
|
||||||
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
|
||||||
const rating = allParentalRatings[i];
|
|
||||||
|
|
||||||
if (user.Policy.MaxParentalRating >= rating.Value) {
|
|
||||||
ratingValue = rating.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#selectMaxParentalRating', page).val(ratingValue);
|
|
||||||
|
|
||||||
if (user.Policy.IsAdministrator) {
|
|
||||||
$('.accessScheduleSection', page).hide();
|
|
||||||
} else {
|
|
||||||
$('.accessScheduleSection', page).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAccessSchedule(page, user.Policy.AccessSchedules || []);
|
|
||||||
loading.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadBlockedTags(page, tags) {
|
|
||||||
let html = tags.map(function (h) {
|
|
||||||
let li = '<div class="listItem">';
|
|
||||||
li += '<div class="listItemBody">';
|
|
||||||
li += '<h3 class="listItemBodyText">';
|
|
||||||
li += h;
|
|
||||||
li += '</h3>';
|
|
||||||
li += '</div>';
|
|
||||||
li += '<button type="button" is="paper-icon-button-light" class="blockedTag btnDeleteTag listItemButton" data-tag="' + h + '"><span class="material-icons delete"></span></button>';
|
|
||||||
return li += '</div>';
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
if (html) {
|
|
||||||
html = '<div class="paperList">' + html + '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const elem = $('.blockedTags', page).html(html).trigger('create');
|
|
||||||
$('.btnDeleteTag', elem).on('click', function () {
|
|
||||||
const tag = this.getAttribute('data-tag');
|
|
||||||
const newTags = tags.filter(function (t) {
|
|
||||||
return t != tag;
|
|
||||||
});
|
|
||||||
loadBlockedTags(page, newTags);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteAccessSchedule(page, schedules, index) {
|
|
||||||
schedules.splice(index, 1);
|
|
||||||
renderAccessSchedule(page, schedules);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAccessSchedule(page, schedules) {
|
|
||||||
let html = '';
|
|
||||||
let index = 0;
|
|
||||||
html += schedules.map(function (a) {
|
|
||||||
let itemHtml = '';
|
|
||||||
itemHtml += '<div class="liSchedule listItem" data-day="' + a.DayOfWeek + '" data-start="' + a.StartHour + '" data-end="' + a.EndHour + '">';
|
|
||||||
itemHtml += '<div class="listItemBody two-line">';
|
|
||||||
itemHtml += '<h3 class="listItemBodyText">';
|
|
||||||
itemHtml += globalize.translate('Option' + a.DayOfWeek);
|
|
||||||
itemHtml += '</h3>';
|
|
||||||
itemHtml += '<div class="listItemBodyText secondary">' + getDisplayTime(a.StartHour) + ' - ' + getDisplayTime(a.EndHour) + '</div>';
|
|
||||||
itemHtml += '</div>';
|
|
||||||
itemHtml += '<button type="button" is="paper-icon-button-light" class="btnDelete listItemButton" data-index="' + index + '"><span class="material-icons delete"></span></button>';
|
|
||||||
itemHtml += '</div>';
|
|
||||||
index++;
|
|
||||||
return itemHtml;
|
|
||||||
}).join('');
|
|
||||||
const accessScheduleList = page.querySelector('.accessScheduleList');
|
|
||||||
accessScheduleList.innerHTML = html;
|
|
||||||
$('.btnDelete', accessScheduleList).on('click', function () {
|
|
||||||
deleteAccessSchedule(page, schedules, parseInt(this.getAttribute('data-index')));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSaveComplete() {
|
|
||||||
loading.hide();
|
|
||||||
toast(globalize.translate('SettingsSaved'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUser(user, page) {
|
|
||||||
user.Policy.MaxParentalRating = $('#selectMaxParentalRating', page).val() || null;
|
|
||||||
user.Policy.BlockUnratedItems = $('.chkUnratedItem', page).get().filter(function (i) {
|
|
||||||
return i.checked;
|
|
||||||
}).map(function (i) {
|
|
||||||
return i.getAttribute('data-itemtype');
|
|
||||||
});
|
|
||||||
user.Policy.AccessSchedules = getSchedulesFromPage(page);
|
|
||||||
user.Policy.BlockedTags = getBlockedTagsFromPage(page);
|
|
||||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
|
||||||
onSaveComplete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDisplayTime(hours) {
|
|
||||||
let minutes = 0;
|
|
||||||
const pct = hours % 1;
|
|
||||||
|
|
||||||
if (pct) {
|
|
||||||
minutes = parseInt(60 * pct);
|
|
||||||
}
|
|
||||||
|
|
||||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSchedulePopup(page, schedule, index) {
|
|
||||||
schedule = schedule || {};
|
|
||||||
import('../../../components/accessSchedule/accessSchedule').then(({default: accessschedule}) => {
|
|
||||||
accessschedule.show({
|
|
||||||
schedule: schedule
|
|
||||||
}).then(function (updatedSchedule) {
|
|
||||||
const schedules = getSchedulesFromPage(page);
|
|
||||||
|
|
||||||
if (index == -1) {
|
|
||||||
index = schedules.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
schedules[index] = updatedSchedule;
|
|
||||||
renderAccessSchedule(page, schedules);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSchedulesFromPage(page) {
|
|
||||||
return $('.liSchedule', page).map(function () {
|
|
||||||
return {
|
|
||||||
DayOfWeek: this.getAttribute('data-day'),
|
|
||||||
StartHour: this.getAttribute('data-start'),
|
|
||||||
EndHour: this.getAttribute('data-end')
|
|
||||||
};
|
|
||||||
}).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBlockedTagsFromPage(page) {
|
|
||||||
return $('.blockedTag', page).map(function () {
|
|
||||||
return this.getAttribute('data-tag');
|
|
||||||
}).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showBlockedTagPopup(page) {
|
|
||||||
import('../../../components/prompt/prompt').then(({default: prompt}) => {
|
|
||||||
prompt({
|
|
||||||
label: globalize.translate('LabelTag')
|
|
||||||
}).then(function (value) {
|
|
||||||
const tags = getBlockedTagsFromPage(page);
|
|
||||||
|
|
||||||
if (tags.indexOf(value) == -1) {
|
|
||||||
tags.push(value);
|
|
||||||
loadBlockedTags(page, tags);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.UserParentalControlPage = {
|
|
||||||
onSubmit: function () {
|
|
||||||
const page = $(this).parents('.page');
|
|
||||||
loading.show();
|
|
||||||
const userId = getParameterByName('userId');
|
|
||||||
ApiClient.getUser(userId).then(function (result) {
|
|
||||||
saveUser(result, page);
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$(document).on('pageinit', '#userParentalControlPage', function () {
|
|
||||||
const page = this;
|
|
||||||
$('.btnAddSchedule', page).on('click', function () {
|
|
||||||
showSchedulePopup(page, {}, -1);
|
|
||||||
});
|
|
||||||
$('.btnAddBlockedTag', page).on('click', function () {
|
|
||||||
showBlockedTagPopup(page);
|
|
||||||
});
|
|
||||||
$('.userParentalControlForm').off('submit', UserParentalControlPage.onSubmit).on('submit', UserParentalControlPage.onSubmit);
|
|
||||||
}).on('pageshow', '#userParentalControlPage', function () {
|
|
||||||
const page = this;
|
|
||||||
loading.show();
|
|
||||||
const userId = getParameterByName('userId');
|
|
||||||
const promise1 = ApiClient.getUser(userId);
|
|
||||||
const promise2 = ApiClient.getParentalRatings();
|
|
||||||
Promise.all([promise1, promise2]).then(function (responses) {
|
|
||||||
loadUser(page, responses[0], responses[1]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint-enable indent */
|
|
@ -33,6 +33,12 @@ import ServerConnections from '../../components/ServerConnections';
|
|||||||
import confirm from '../../components/confirm/confirm';
|
import confirm from '../../components/confirm/confirm';
|
||||||
import { download } from '../../scripts/fileDownloader';
|
import { download } from '../../scripts/fileDownloader';
|
||||||
|
|
||||||
|
function autoFocus(container) {
|
||||||
|
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||||
|
autoFocuser.autoFocus(container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getPromise(apiClient, params) {
|
function getPromise(apiClient, params) {
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
|
||||||
@ -368,12 +374,12 @@ function reloadPlayButtons(page, item) {
|
|||||||
hideAll(page, 'btnShuffle');
|
hideAll(page, 'btnShuffle');
|
||||||
}
|
}
|
||||||
|
|
||||||
const btnResume = page.querySelector('.mainDetailButtons .btnResume');
|
if (layoutManager.tv) {
|
||||||
const btnPlay = page.querySelector('.mainDetailButtons .btnPlay');
|
const btnResume = page.querySelector('.mainDetailButtons .btnResume');
|
||||||
if (layoutManager.tv && !btnResume.classList.contains('hide')) {
|
const btnPlay = page.querySelector('.mainDetailButtons .btnPlay');
|
||||||
btnResume.classList.add('fab');
|
const resumeHidden = btnResume.classList.contains('hide');
|
||||||
} else if (layoutManager.tv && btnResume.classList.contains('hide')) {
|
btnResume.classList.toggle('raised', !resumeHidden);
|
||||||
btnPlay.classList.add('fab');
|
btnPlay.classList.toggle('raised', resumeHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canPlay;
|
return canPlay;
|
||||||
@ -634,12 +640,6 @@ function reloadFromItem(instance, page, params, item, user) {
|
|||||||
setInitialCollapsibleState(page, item, apiClient, params.context, user);
|
setInitialCollapsibleState(page, item, apiClient, params.context, user);
|
||||||
const canPlay = reloadPlayButtons(page, item);
|
const canPlay = reloadPlayButtons(page, item);
|
||||||
|
|
||||||
if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && playbackManager.getSupportedCommands().indexOf('PlayTrailers') !== -1) {
|
|
||||||
hideAll(page, 'btnPlayTrailer', true);
|
|
||||||
} else {
|
|
||||||
hideAll(page, 'btnPlayTrailer');
|
|
||||||
}
|
|
||||||
|
|
||||||
setTrailerButtonVisibility(page, item);
|
setTrailerButtonVisibility(page, item);
|
||||||
|
|
||||||
if (item.Type !== 'Program' || canPlay) {
|
if (item.Type !== 'Program' || canPlay) {
|
||||||
@ -727,9 +727,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
|||||||
hideAll(page, 'btnDownload', true);
|
hideAll(page, 'btnDownload', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
autoFocus(page);
|
||||||
autoFocuser.autoFocus(page);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function logoImageUrl(item, apiClient, options) {
|
function logoImageUrl(item, apiClient, options) {
|
||||||
@ -921,7 +919,7 @@ function setInitialCollapsibleState(page, item, apiClient, context, user) {
|
|||||||
|
|
||||||
renderScenes(page, item);
|
renderScenes(page, item);
|
||||||
|
|
||||||
if (item.SpecialFeatureCount && item.SpecialFeatureCount != 0 && item.Type != 'Series') {
|
if (item.SpecialFeatureCount > 0) {
|
||||||
page.querySelector('#specialsCollapsible').classList.remove('hide');
|
page.querySelector('#specialsCollapsible').classList.remove('hide');
|
||||||
renderSpecials(page, item, user);
|
renderSpecials(page, item, user);
|
||||||
} else {
|
} else {
|
||||||
@ -1756,9 +1754,7 @@ function renderCollectionItems(page, parentItem, types, items) {
|
|||||||
|
|
||||||
// HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem
|
// HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem
|
||||||
// FIXME: Sometimes focus does not move until all (?) sections are loaded
|
// FIXME: Sometimes focus does not move until all (?) sections are loaded
|
||||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
autoFocus(page);
|
||||||
autoFocuser.autoFocus(page);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCollectionItemType(page, parentItem, type, items) {
|
function renderCollectionItemType(page, parentItem, type, items) {
|
||||||
@ -2060,6 +2056,7 @@ export default function (view, params) {
|
|||||||
currentItem.UserData = userData;
|
currentItem.UserData = userData;
|
||||||
reloadPlayButtons(view, currentItem);
|
reloadPlayButtons(view, currentItem);
|
||||||
refreshImage(view, currentItem);
|
refreshImage(view, currentItem);
|
||||||
|
autoFocus(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow padded-left padded-right padded-bottom-page pageContainerTopPadding">
|
<div class="flex-grow padded-left padded-right padded-bottom-page pageContainerTopPadding">
|
||||||
<div class="flex align-items-center focuscontainer-x itemsViewSettingsContainer padded-top padded-bottom flex-wrap-wrap">
|
<div class="flex align-items-center focuscontainer-x itemsViewSettingsContainer padded-top padded-bottom flex-wrap-wrap">
|
||||||
|
<div class="paging"></div>
|
||||||
<button is="emby-button" class="btnPlay button-flat hide listTextButton-autohide">
|
<button is="emby-button" class="btnPlay button-flat hide listTextButton-autohide">
|
||||||
<span>${HeaderPlayAll}</span>
|
<span>${HeaderPlayAll}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -50,5 +51,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div is="emby-itemscontainer" class="vertical-wrap itemsContainer centered">
|
<div is="emby-itemscontainer" class="vertical-wrap itemsContainer centered">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||||
|
<div class="paging"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import globalize from '../scripts/globalize';
|
import globalize from '../scripts/globalize';
|
||||||
import listView from '../components/listview/listview';
|
import listView from '../components/listview/listview';
|
||||||
import layoutManager from '../components/layoutManager';
|
|
||||||
import * as userSettings from '../scripts/settings/userSettings';
|
import * as userSettings from '../scripts/settings/userSettings';
|
||||||
import focusManager from '../components/focusManager';
|
import focusManager from '../components/focusManager';
|
||||||
import cardBuilder from '../components/cardbuilder/cardBuilder';
|
import cardBuilder from '../components/cardbuilder/cardBuilder';
|
||||||
import loading from '../components/loading/loading';
|
import loading from '../components/loading/loading';
|
||||||
import AlphaNumericShortcuts from '../scripts/alphanumericshortcuts';
|
import AlphaNumericShortcuts from '../scripts/alphanumericshortcuts';
|
||||||
|
import libraryBrowser from '../scripts/libraryBrowser';
|
||||||
import { playbackManager } from '../components/playback/playbackmanager';
|
import { playbackManager } from '../components/playback/playbackmanager';
|
||||||
import AlphaPicker from '../components/alphaPicker/alphaPicker';
|
import AlphaPicker from '../components/alphaPicker/alphaPicker';
|
||||||
import '../elements/emby-itemscontainer/emby-itemscontainer';
|
import '../elements/emby-itemscontainer/emby-itemscontainer';
|
||||||
@ -15,12 +15,12 @@ import { appRouter } from '../components/appRouter';
|
|||||||
|
|
||||||
/* eslint-disable indent */
|
/* eslint-disable indent */
|
||||||
|
|
||||||
function getInitialLiveTvQuery(instance, params) {
|
function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) {
|
||||||
const query = {
|
const query = {
|
||||||
UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(),
|
UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(),
|
||||||
StartIndex: 0,
|
StartIndex: startIndex,
|
||||||
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
|
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
|
||||||
Limit: 300
|
Limit: limit
|
||||||
};
|
};
|
||||||
|
|
||||||
if (params.type === 'Recordings') {
|
if (params.type === 'Recordings') {
|
||||||
@ -165,7 +165,14 @@ import { appRouter } from '../components/appRouter';
|
|||||||
instance.setFilterStatus(hasFilters);
|
instance.setFilterStatus(hasFilters);
|
||||||
|
|
||||||
if (instance.alphaPicker) {
|
if (instance.alphaPicker) {
|
||||||
query.NameStartsWith = instance.alphaPicker.value();
|
const newValue = instance.alphaPicker.value();
|
||||||
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
@ -237,15 +244,15 @@ import { appRouter } from '../components/appRouter';
|
|||||||
|
|
||||||
instance.queryRecursive = false;
|
instance.queryRecursive = false;
|
||||||
if (params.type === 'Recordings') {
|
if (params.type === 'Recordings') {
|
||||||
return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params));
|
return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params, startIndex, limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.type === 'Programs') {
|
if (params.type === 'Programs') {
|
||||||
if (params.IsAiring === 'true') {
|
if (params.IsAiring === 'true') {
|
||||||
return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params));
|
return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params));
|
return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.type === 'nextup') {
|
if (params.type === 'nextup') {
|
||||||
@ -423,14 +430,75 @@ import { appRouter } from '../components/appRouter';
|
|||||||
|
|
||||||
class ItemsView {
|
class ItemsView {
|
||||||
constructor(view, params) {
|
constructor(view, params) {
|
||||||
|
const query = {
|
||||||
|
StartIndex: 0,
|
||||||
|
Limit: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
if (userSettings.libraryPageSize() > 0) {
|
||||||
|
query['Limit'] = userSettings.libraryPageSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
let isLoading = false;
|
||||||
|
|
||||||
|
function onNextPageClick() {
|
||||||
|
if (!isLoading && query.Limit > 0) {
|
||||||
|
query.StartIndex += query.Limit;
|
||||||
|
self.itemsContainer.refreshItems().then(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
autoFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPreviousPageClick() {
|
||||||
|
if (!isLoading && query.Limit > 0) {
|
||||||
|
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
||||||
|
self.itemsContainer.refreshItems().then(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
autoFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaging(startIndex, totalRecordCount, limit) {
|
||||||
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
|
startIndex,
|
||||||
|
limit,
|
||||||
|
totalRecordCount,
|
||||||
|
showLimit: false,
|
||||||
|
updatePageSizeSetting: false,
|
||||||
|
addLayoutButton: false,
|
||||||
|
sortButton: false,
|
||||||
|
filterButton: false
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const elem of view.querySelectorAll('.paging')) {
|
||||||
|
elem.innerHTML = pagingHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const elem of view.querySelectorAll('.btnNextPage')) {
|
||||||
|
elem.addEventListener('click', onNextPageClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const elem of view.querySelectorAll('.btnPreviousPage')) {
|
||||||
|
elem.addEventListener('click', onPreviousPageClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchData() {
|
function fetchData() {
|
||||||
return getItems(self, params, self.currentItem).then(function (result) {
|
isLoading = true;
|
||||||
|
|
||||||
|
return getItems(self, params, self.currentItem, null, query.StartIndex, query.Limit).then(function (result) {
|
||||||
if (self.totalItemCount == null) {
|
if (self.totalItemCount == null) {
|
||||||
self.totalItemCount = result.Items ? result.Items.length : result.length;
|
self.totalItemCount = result.Items ? result.Items.length : result.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAlphaPickerState(self, self.totalItemCount);
|
updateAlphaPickerState(self, self.totalItemCount);
|
||||||
|
updatePaging(result.StartIndex, result.TotalRecordCount, query.Limit);
|
||||||
return result;
|
return result;
|
||||||
|
}).finally(() => {
|
||||||
|
isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,15 +627,13 @@ class ItemsView {
|
|||||||
|
|
||||||
self.alphaPicker = new AlphaPicker({
|
self.alphaPicker = new AlphaPicker({
|
||||||
element: alphaPickerElement,
|
element: alphaPickerElement,
|
||||||
itemsContainer: layoutManager.tv ? self.itemsContainer : null,
|
valueChangeEvent: 'click'
|
||||||
itemClass: 'card',
|
|
||||||
valueChangeEvent: layoutManager.tv ? null : 'click'
|
|
||||||
});
|
});
|
||||||
self.alphaPicker.on('alphavaluechanged', onAlphaPickerValueChanged);
|
self.alphaPicker.on('alphavaluechanged', onAlphaPickerValueChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAlphaPickerValueChanged() {
|
function onAlphaPickerValueChanged() {
|
||||||
self.alphaPicker.value();
|
query.StartIndex = 0;
|
||||||
self.itemsContainer.refreshItems();
|
self.itemsContainer.refreshItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,7 +736,7 @@ class ItemsView {
|
|||||||
autoplay: true
|
autoplay: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
getItems(self, self.params, currentItem, null, null, 300).then(function (result) {
|
getItems(self, self.params, currentItem, null, 0, 300).then(function (result) {
|
||||||
playbackManager.play({
|
playbackManager.play({
|
||||||
items: result.Items,
|
items: result.Items,
|
||||||
autoplay: true
|
autoplay: true
|
||||||
@ -687,7 +753,7 @@ class ItemsView {
|
|||||||
items: [currentItem]
|
items: [currentItem]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
getItems(self, self.params, currentItem, null, null, 300).then(function (result) {
|
getItems(self, self.params, currentItem, null, 0, 300).then(function (result) {
|
||||||
playbackManager.queue({
|
playbackManager.queue({
|
||||||
items: result.Items
|
items: result.Items
|
||||||
});
|
});
|
||||||
@ -701,7 +767,7 @@ class ItemsView {
|
|||||||
if (currentItem && !self.hasFilters) {
|
if (currentItem && !self.hasFilters) {
|
||||||
playbackManager.shuffle(currentItem);
|
playbackManager.shuffle(currentItem);
|
||||||
} else {
|
} else {
|
||||||
getItems(self, self.params, currentItem, 'Random', null, 300).then(function (result) {
|
getItems(self, self.params, currentItem, 'Random', 0, 300).then(function (result) {
|
||||||
playbackManager.play({
|
playbackManager.play({
|
||||||
items: result.Items,
|
items: result.Items,
|
||||||
autoplay: true
|
autoplay: true
|
||||||
@ -710,6 +776,12 @@ class ItemsView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function autoFocus() {
|
||||||
|
import('../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||||
|
autoFocuser.autoFocus(view);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
self.params = params;
|
self.params = params;
|
||||||
this.itemsContainer = view.querySelector('.itemsContainer');
|
this.itemsContainer = view.querySelector('.itemsContainer');
|
||||||
|
@ -64,6 +64,9 @@
|
|||||||
<button is="paper-icon-button-light" class="btnFilter sectionTitleButton" title="${Filter}"><span class="material-icons filter_list"></span></button>
|
<button is="paper-icon-button-light" class="btnFilter sectionTitleButton" title="${Filter}"><span class="material-icons filter_list"></span></button>
|
||||||
</div>
|
</div>
|
||||||
<div is="emby-itemscontainer" id="items" class="itemsContainer vertical-wrap padded-left padded-right"></div>
|
<div is="emby-itemscontainer" id="items" class="itemsContainer vertical-wrap padded-left padded-right"></div>
|
||||||
|
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||||
|
<div class="paging"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pageTabContent" id="recordingsTab" data-index="3">
|
<div class="pageTabContent" id="recordingsTab" data-index="3">
|
||||||
<div id="latestRecordings" class="verticalSection hide">
|
<div id="latestRecordings" class="verticalSection hide">
|
||||||
|
@ -50,7 +50,9 @@ export default function (view, params, tabContent) {
|
|||||||
if (userSettings.libraryPageSize() > 0) {
|
if (userSettings.libraryPageSize() > 0) {
|
||||||
query.StartIndex += query.Limit;
|
query.StartIndex += query.Limit;
|
||||||
}
|
}
|
||||||
reloadItems(context);
|
reloadItems(context).then(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPreviousPageClick() {
|
function onPreviousPageClick() {
|
||||||
@ -61,18 +63,24 @@ export default function (view, params, tabContent) {
|
|||||||
if (userSettings.libraryPageSize() > 0) {
|
if (userSettings.libraryPageSize() > 0) {
|
||||||
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
||||||
}
|
}
|
||||||
reloadItems(context);
|
reloadItems(context).then(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = getQuery();
|
const query = getQuery();
|
||||||
context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({
|
|
||||||
startIndex: query.StartIndex,
|
for (const elem of context.querySelectorAll('.paging')) {
|
||||||
limit: query.Limit,
|
elem.innerHTML = libraryBrowser.getQueryPagingHtml({
|
||||||
totalRecordCount: result.TotalRecordCount,
|
startIndex: query.StartIndex,
|
||||||
showLimit: false,
|
limit: query.Limit,
|
||||||
updatePageSizeSetting: false,
|
totalRecordCount: result.TotalRecordCount,
|
||||||
filterButton: false
|
showLimit: false,
|
||||||
});
|
updatePageSizeSetting: false,
|
||||||
|
filterButton: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const html = getChannelsHtml(result.Items);
|
const html = getChannelsHtml(result.Items);
|
||||||
const elem = context.querySelector('#items');
|
const elem = context.querySelector('#items');
|
||||||
elem.innerHTML = html;
|
elem.innerHTML = html;
|
||||||
@ -110,13 +118,13 @@ export default function (view, params, tabContent) {
|
|||||||
const query = getQuery();
|
const query = getQuery();
|
||||||
const apiClient = ApiClient;
|
const apiClient = ApiClient;
|
||||||
query.UserId = apiClient.getCurrentUserId();
|
query.UserId = apiClient.getCurrentUserId();
|
||||||
apiClient.getLiveTvChannels(query).then(function (result) {
|
return apiClient.getLiveTvChannels(query).then(function (result) {
|
||||||
renderChannels(context, result);
|
renderChannels(context, result);
|
||||||
loading.hide();
|
loading.hide();
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||||
autoFocuser.autoFocus(view);
|
autoFocuser.autoFocus(context);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ function renderActiveRecordings(context, promise) {
|
|||||||
defaultShape: getBackdropShape(),
|
defaultShape: getBackdropShape(),
|
||||||
showParentTitle: false,
|
showParentTitle: false,
|
||||||
showParentTitleOrTitle: true,
|
showParentTitleOrTitle: true,
|
||||||
showTitle: false,
|
showTitle: true,
|
||||||
showAirTime: true,
|
showAirTime: true,
|
||||||
showAirEndTime: true,
|
showAirEndTime: true,
|
||||||
showChannelName: true,
|
showChannelName: true,
|
||||||
|
@ -2,6 +2,7 @@ import 'jquery';
|
|||||||
import globalize from '../scripts/globalize';
|
import globalize from '../scripts/globalize';
|
||||||
import taskButton from '../scripts/taskbutton';
|
import taskButton from '../scripts/taskbutton';
|
||||||
import dom from '../scripts/dom';
|
import dom from '../scripts/dom';
|
||||||
|
import cardBuilder from '../components/cardbuilder/cardBuilder';
|
||||||
import layoutManager from '../components/layoutManager';
|
import layoutManager from '../components/layoutManager';
|
||||||
import loading from '../components/loading/loading';
|
import loading from '../components/loading/loading';
|
||||||
import browser from '../scripts/browser';
|
import browser from '../scripts/browser';
|
||||||
@ -37,7 +38,7 @@ function getDeviceHtml(device) {
|
|||||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||||
html += '<div class="' + padderClass + '"></div>';
|
html += '<div class="' + padderClass + '"></div>';
|
||||||
html += '<div class="cardContent searchImage">';
|
html += '<div class="cardContent searchImage">';
|
||||||
html += '<div class="cardImageContainer coveredImage"><span class="cardImageIcon material-icons dvr"></span></div>';
|
html += `<div class="cardImageContainer coveredImage ${cardBuilder.getDefaultBackgroundClass()}"><span class="cardImageIcon material-icons dvr"></span></div>`;
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '<div class="cardFooter visualCardBox-cardFooter">';
|
html += '<div class="cardFooter visualCardBox-cardFooter">';
|
||||||
|
@ -17,7 +17,7 @@ import '../../elements/emby-button/emby-button';
|
|||||||
if (!pageData) {
|
if (!pageData) {
|
||||||
pageData = data[key] = {
|
pageData = data[key] = {
|
||||||
query: {
|
query: {
|
||||||
SortBy: 'Random',
|
SortBy: 'SortName',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
IncludeItemTypes: 'Movie',
|
IncludeItemTypes: 'Movie',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
@ -70,7 +70,7 @@ import '../../elements/emby-button/emby-button';
|
|||||||
|
|
||||||
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
|
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
|
||||||
const query = {
|
const query = {
|
||||||
SortBy: 'SortName',
|
SortBy: 'Random',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
IncludeItemTypes: 'Movie',
|
IncludeItemTypes: 'Movie',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
|
@ -23,24 +23,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
itemsContainer.innerHTML = '';
|
itemsContainer.innerHTML = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFilterControls = () => {
|
|
||||||
if (this.alphaPicker) {
|
|
||||||
this.alphaPicker.value(query.NameStartsWith);
|
|
||||||
if (query.SortBy.indexOf('SortName') === 0) {
|
|
||||||
this.alphaPicker.visible(true);
|
|
||||||
} else {
|
|
||||||
this.alphaPicker.visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function fetchData() {
|
function fetchData() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
loading.show();
|
loading.show();
|
||||||
return ApiClient.getItems(ApiClient.getCurrentUserId(), query);
|
return ApiClient.getItems(ApiClient.getCurrentUserId(), query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterRefresh(result) {
|
const afterRefresh = (result) => {
|
||||||
function onNextPageClick() {
|
function onNextPageClick() {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return;
|
return;
|
||||||
@ -64,7 +53,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
updateFilterControls();
|
this.alphaPicker?.updateControls(query);
|
||||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
startIndex: query.StartIndex,
|
startIndex: query.StartIndex,
|
||||||
limit: query.Limit,
|
limit: query.Limit,
|
||||||
@ -94,7 +83,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||||
autoFocuser.autoFocus(tabContent);
|
autoFocuser.autoFocus(tabContent);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const getItemsHtml = (items) => {
|
const getItemsHtml = (items) => {
|
||||||
let html;
|
let html;
|
||||||
@ -173,7 +162,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
if (alphaPickerElement) {
|
if (alphaPickerElement) {
|
||||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||||
const newValue = e.detail.value;
|
const newValue = e.detail.value;
|
||||||
query.NameStartsWith = newValue;
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
query.StartIndex = 0;
|
query.StartIndex = 0;
|
||||||
itemsContainer.refreshItems();
|
itemsContainer.refreshItems();
|
||||||
});
|
});
|
||||||
@ -301,9 +296,9 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
onViewStyleChange();
|
onViewStyleChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.renderTab = function () {
|
this.renderTab = () => {
|
||||||
itemsContainer.refreshItems();
|
itemsContainer.refreshItems();
|
||||||
updateFilterControls();
|
this.alphaPicker?.updateControls(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = function () {
|
this.destroy = function () {
|
||||||
|
@ -81,7 +81,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
updateFilterControls(tabContent);
|
this.alphaPicker?.updateControls(query);
|
||||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
startIndex: query.StartIndex,
|
startIndex: query.StartIndex,
|
||||||
limit: query.Limit,
|
limit: query.Limit,
|
||||||
@ -183,11 +183,6 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFilterControls = (tabContent) => {
|
|
||||||
const query = getQuery(tabContent);
|
|
||||||
this.alphaPicker.value(query.NameStartsWith);
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = {};
|
const data = {};
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
@ -216,7 +211,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||||
const newValue = e.detail.value;
|
const newValue = e.detail.value;
|
||||||
const query = getQuery(tabContent);
|
const query = getQuery(tabContent);
|
||||||
query.NameStartsWith = newValue;
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
query.StartIndex = 0;
|
query.StartIndex = 0;
|
||||||
reloadItems();
|
reloadItems();
|
||||||
});
|
});
|
||||||
@ -268,9 +269,9 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
|
|
||||||
initPage(tabContent);
|
initPage(tabContent);
|
||||||
|
|
||||||
this.renderTab = function () {
|
this.renderTab = () => {
|
||||||
reloadItems();
|
reloadItems();
|
||||||
updateFilterControls(tabContent);
|
this.alphaPicker?.updateControls(getQuery(tabContent));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = function () {};
|
this.destroy = function () {};
|
||||||
|
@ -112,7 +112,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
updateFilterControls();
|
this.alphaPicker?.updateControls(query);
|
||||||
let html;
|
let html;
|
||||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
startIndex: query.StartIndex,
|
startIndex: query.StartIndex,
|
||||||
@ -185,20 +185,6 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFilterControls = () => {
|
|
||||||
const query = getQuery();
|
|
||||||
|
|
||||||
if (this.alphaPicker) {
|
|
||||||
this.alphaPicker.value(query.NameStartsWith);
|
|
||||||
|
|
||||||
if (query.SortBy.indexOf('SortName') === 0) {
|
|
||||||
this.alphaPicker.visible(true);
|
|
||||||
} else {
|
|
||||||
this.alphaPicker.visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let savedQueryKey;
|
let savedQueryKey;
|
||||||
let pageData;
|
let pageData;
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
@ -230,7 +216,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||||
const newValue = e.detail.value;
|
const newValue = e.detail.value;
|
||||||
const query = getQuery();
|
const query = getQuery();
|
||||||
query.NameStartsWith = newValue;
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
query.StartIndex = 0;
|
query.StartIndex = 0;
|
||||||
reloadItems();
|
reloadItems();
|
||||||
});
|
});
|
||||||
@ -302,9 +294,9 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
initPage(tabContent);
|
initPage(tabContent);
|
||||||
onViewStyleChange();
|
onViewStyleChange();
|
||||||
|
|
||||||
this.renderTab = function () {
|
this.renderTab = () => {
|
||||||
reloadItems();
|
reloadItems();
|
||||||
updateFilterControls();
|
this.alphaPicker?.updateControls(getQuery());
|
||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = function () {};
|
this.destroy = function () {};
|
||||||
|
@ -99,7 +99,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
updateFilterControls(page);
|
this.alphaPicker?.updateControls(query);
|
||||||
let html;
|
let html;
|
||||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
startIndex: query.StartIndex,
|
startIndex: query.StartIndex,
|
||||||
@ -167,11 +167,6 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFilterControls = (tabContent) => {
|
|
||||||
const query = getQuery(tabContent);
|
|
||||||
this.alphaPicker.value(query.NameStartsWith);
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = {};
|
const data = {};
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
@ -201,7 +196,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||||
const newValue = e.detail.value;
|
const newValue = e.detail.value;
|
||||||
const query = getQuery(tabContent);
|
const query = getQuery(tabContent);
|
||||||
query.NameStartsWith = newValue;
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
query.StartIndex = 0;
|
query.StartIndex = 0;
|
||||||
reloadItems(tabContent);
|
reloadItems(tabContent);
|
||||||
});
|
});
|
||||||
@ -234,9 +235,9 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
initPage(tabContent);
|
initPage(tabContent);
|
||||||
onViewStyleChange();
|
onViewStyleChange();
|
||||||
|
|
||||||
this.renderTab = function () {
|
this.renderTab = () => {
|
||||||
reloadItems(tabContent);
|
reloadItems(tabContent);
|
||||||
updateFilterControls(tabContent);
|
this.alphaPicker?.updateControls(getQuery(tabContent));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = function () {};
|
this.destroy = function () {};
|
||||||
|
@ -27,7 +27,6 @@ import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
|||||||
|
|
||||||
onLoginSuccessful(user.Id, result.AccessToken, apiClient);
|
onLoginSuccessful(user.Id, result.AccessToken, apiClient);
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
page.querySelector('#txtManualName').value = '';
|
|
||||||
page.querySelector('#txtManualPassword').value = '';
|
page.querySelector('#txtManualPassword').value = '';
|
||||||
loading.hide();
|
loading.hide();
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import '../../elements/emby-button/emby-button';
|
|||||||
if (!pageData) {
|
if (!pageData) {
|
||||||
pageData = data[key] = {
|
pageData = data[key] = {
|
||||||
query: {
|
query: {
|
||||||
SortBy: 'Random',
|
SortBy: 'SortName',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
IncludeItemTypes: 'Series',
|
IncludeItemTypes: 'Series',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
@ -70,7 +70,7 @@ import '../../elements/emby-button/emby-button';
|
|||||||
|
|
||||||
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
|
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
|
||||||
const query = {
|
const query = {
|
||||||
SortBy: 'SortName',
|
SortBy: 'Random',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
IncludeItemTypes: 'Series',
|
IncludeItemTypes: 'Series',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
|
@ -97,7 +97,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
updateFilterControls(page);
|
this.alphaPicker?.updateControls(query);
|
||||||
let html;
|
let html;
|
||||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||||
startIndex: query.StartIndex,
|
startIndex: query.StartIndex,
|
||||||
@ -196,20 +196,6 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFilterControls = (tabContent) => {
|
|
||||||
const query = getQuery(tabContent);
|
|
||||||
|
|
||||||
if (this.alphaPicker) {
|
|
||||||
this.alphaPicker.value(query.NameStartsWith);
|
|
||||||
|
|
||||||
if (query.SortBy.indexOf('SortName') === 0) {
|
|
||||||
this.alphaPicker.visible(true);
|
|
||||||
} else {
|
|
||||||
this.alphaPicker.visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = {};
|
const data = {};
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
@ -239,7 +225,13 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||||
const newValue = e.detail.value;
|
const newValue = e.detail.value;
|
||||||
const query = getQuery(tabContent);
|
const query = getQuery(tabContent);
|
||||||
query.NameStartsWith = newValue;
|
if (newValue === '#') {
|
||||||
|
query.NameLessThan = 'A';
|
||||||
|
delete query.NameStartsWith;
|
||||||
|
} else {
|
||||||
|
query.NameStartsWith = newValue;
|
||||||
|
delete query.NameLessThan;
|
||||||
|
}
|
||||||
query.StartIndex = 0;
|
query.StartIndex = 0;
|
||||||
reloadItems(tabContent);
|
reloadItems(tabContent);
|
||||||
});
|
});
|
||||||
@ -301,9 +293,9 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||||||
initPage(tabContent);
|
initPage(tabContent);
|
||||||
onViewStyleChange();
|
onViewStyleChange();
|
||||||
|
|
||||||
this.renderTab = function () {
|
this.renderTab = () => {
|
||||||
reloadItems(tabContent);
|
reloadItems(tabContent);
|
||||||
updateFilterControls(tabContent);
|
this.alphaPicker?.updateControls(getQuery(tabContent));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = function () {};
|
this.destroy = function () {};
|
||||||
|
@ -112,6 +112,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="exitApp listItem-border">
|
||||||
|
<div class="listItem">
|
||||||
|
<span class="material-icons listItemIcon listItemIcon-transparent close"></span>
|
||||||
|
<div class="listItemBody">
|
||||||
|
<div class="listItemBodyText">${ButtonExitApp}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,10 @@ export default function (view, params) {
|
|||||||
window.NativeShell.openClientSettings();
|
window.NativeShell.openClientSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
view.querySelector('.exitApp').addEventListener('click', function () {
|
||||||
|
appHost.exit();
|
||||||
|
});
|
||||||
|
|
||||||
view.addEventListener('viewshow', function () {
|
view.addEventListener('viewshow', function () {
|
||||||
// this page can also be used by admins to change user preferences from the user edit page
|
// this page can also be used by admins to change user preferences from the user edit page
|
||||||
const userId = params.userId || Dashboard.getCurrentUserId();
|
const userId = params.userId || Dashboard.getCurrentUserId();
|
||||||
@ -33,6 +37,9 @@ export default function (view, params) {
|
|||||||
const supportsClientSettings = appHost.supports('clientsettings');
|
const supportsClientSettings = appHost.supports('clientsettings');
|
||||||
page.querySelector('.clientSettings').classList.toggle('hide', !supportsClientSettings);
|
page.querySelector('.clientSettings').classList.toggle('hide', !supportsClientSettings);
|
||||||
|
|
||||||
|
const supportsExitMenu = appHost.supports('exitmenu');
|
||||||
|
page.querySelector('.exitApp').classList.toggle('hide', !supportsExitMenu);
|
||||||
|
|
||||||
const supportsMultiServer = appHost.supports('multiserver');
|
const supportsMultiServer = appHost.supports('multiserver');
|
||||||
page.querySelector('.selectServer').classList.toggle('hide', !supportsMultiServer);
|
page.querySelector('.selectServer').classList.toggle('hide', !supportsMultiServer);
|
||||||
|
|
||||||
|
@ -108,6 +108,11 @@
|
|||||||
width: 12em;
|
width: 12em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkboxList > .sectioncheckbox > .emby-checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.checkboxList-paperList {
|
.checkboxList-paperList {
|
||||||
padding: 1em !important;
|
padding: 1em !important;
|
||||||
}
|
}
|
||||||
|
@ -138,15 +138,15 @@ import 'webcomponents.js/webcomponents-lite';
|
|||||||
label.innerHTML = this.getAttribute('label') || '';
|
label.innerHTML = this.getAttribute('label') || '';
|
||||||
label.classList.add('selectLabel');
|
label.classList.add('selectLabel');
|
||||||
label.htmlFor = this.id;
|
label.htmlFor = this.id;
|
||||||
this.parentNode.insertBefore(label, this);
|
this.parentNode?.insertBefore(label, this);
|
||||||
|
|
||||||
if (this.classList.contains('emby-select-withcolor')) {
|
if (this.classList.contains('emby-select-withcolor')) {
|
||||||
this.parentNode.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;display:none;">0</div><span class="selectArrow material-icons keyboard_arrow_down"></span></div>');
|
this.parentNode?.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;display:none;">0</div><span class="selectArrow material-icons keyboard_arrow_down"></span></div>');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbySelectPrototype.setLabel = function (text) {
|
EmbySelectPrototype.setLabel = function (text) {
|
||||||
const label = this.parentNode.querySelector('label');
|
const label = this.parentNode?.querySelector('label');
|
||||||
|
|
||||||
label.innerHTML = text;
|
label.innerHTML = text;
|
||||||
};
|
};
|
||||||
|
@ -44,6 +44,12 @@ export class BookPlayer {
|
|||||||
stop() {
|
stop() {
|
||||||
this.unbindEvents();
|
this.unbindEvents();
|
||||||
|
|
||||||
|
const stopInfo = {
|
||||||
|
src: this.item
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.trigger(this, 'stopped', [stopInfo]);
|
||||||
|
|
||||||
const elem = this.mediaElement;
|
const elem = this.mediaElement;
|
||||||
const tocElement = this.tocElement;
|
const tocElement = this.tocElement;
|
||||||
const rendition = this.rendition;
|
const rendition = this.rendition;
|
||||||
@ -67,6 +73,10 @@ export class BookPlayer {
|
|||||||
this.cancellationToken = true;
|
this.cancellationToken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
currentItem() {
|
currentItem() {
|
||||||
return this.item;
|
return this.item;
|
||||||
}
|
}
|
||||||
@ -149,8 +159,8 @@ export class BookPlayer {
|
|||||||
bindMediaElementEvents() {
|
bindMediaElementEvents() {
|
||||||
const elem = this.mediaElement;
|
const elem = this.mediaElement;
|
||||||
|
|
||||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
elem.addEventListener('close', this.onDialogClosed, { once: true });
|
||||||
elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
|
elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, { once: true });
|
||||||
elem.querySelector('#btnBookplayerToc').addEventListener('click', this.openTableOfContents);
|
elem.querySelector('#btnBookplayerToc').addEventListener('click', this.openTableOfContents);
|
||||||
elem.querySelector('#btnBookplayerFullscreen').addEventListener('click', this.toggleFullscreen);
|
elem.querySelector('#btnBookplayerFullscreen').addEventListener('click', this.toggleFullscreen);
|
||||||
elem.querySelector('#btnBookplayerPrev')?.addEventListener('click', this.previous);
|
elem.querySelector('#btnBookplayerPrev')?.addEventListener('click', this.previous);
|
||||||
@ -250,6 +260,7 @@ export class BookPlayer {
|
|||||||
this.streamInfo = {
|
this.streamInfo = {
|
||||||
started: true,
|
started: true,
|
||||||
ended: false,
|
ended: false,
|
||||||
|
item: this.item,
|
||||||
mediaSource: {
|
mediaSource: {
|
||||||
Id: item.Id
|
Id: item.Id
|
||||||
}
|
}
|
||||||
@ -263,9 +274,9 @@ export class BookPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
import('epubjs').then(({default: epubjs}) => {
|
import('epubjs').then(({ default: epubjs }) => {
|
||||||
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||||
const book = epubjs(downloadHref, {openAs: 'epub'});
|
const book = epubjs(downloadHref, { openAs: 'epub' });
|
||||||
|
|
||||||
// We need to calculate the height of the window beforehand because using 100% is not accurate when the dialog is opening.
|
// We need to calculate the height of the window beforehand because using 100% is not accurate when the dialog is opening.
|
||||||
// In addition we don't render to the full height so that we have space for the top buttons.
|
// In addition we don't render to the full height so that we have space for the top buttons.
|
||||||
@ -284,7 +295,7 @@ export class BookPlayer {
|
|||||||
|
|
||||||
return rendition.display().then(() => {
|
return rendition.display().then(() => {
|
||||||
const epubElem = document.querySelector('.epub-container');
|
const epubElem = document.querySelector('.epub-container');
|
||||||
epubElem.style.display = 'none';
|
epubElem.style.opacity = '0';
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
|
||||||
@ -298,10 +309,10 @@ export class BookPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
epubElem.style.display = 'block';
|
epubElem.style.opacity = '';
|
||||||
rendition.on('relocated', (locations) => {
|
rendition.on('relocated', (locations) => {
|
||||||
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
|
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
|
||||||
Events.trigger(this, 'timeupdate');
|
Events.trigger(this, 'pause');
|
||||||
});
|
});
|
||||||
|
|
||||||
loading.hide();
|
loading.hide();
|
||||||
|
@ -1048,12 +1048,16 @@ function tryRemoveElement(elem) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
renderSsaAss(videoElement, track, item) {
|
renderSsaAss(videoElement, track, item) {
|
||||||
|
const supportedFonts = ['application/x-truetype-font', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2'];
|
||||||
const avaliableFonts = [];
|
const avaliableFonts = [];
|
||||||
const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || [];
|
const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || [];
|
||||||
const apiClient = ServerConnections.getApiClient(item);
|
const apiClient = ServerConnections.getApiClient(item);
|
||||||
attachments.map(function (i) {
|
attachments.forEach(i => {
|
||||||
// embedded font url
|
// we only require font files and ignore embedded media attachments like covers as there are cases where ffmpeg fails to extract those
|
||||||
return avaliableFonts.push(apiClient.getUrl(i.DeliveryUrl));
|
if (supportedFonts.includes(i.MimeType)) {
|
||||||
|
// embedded font url
|
||||||
|
avaliableFonts.push(apiClient.getUrl(i.DeliveryUrl));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
||||||
api_key: apiClient.accessToken()
|
api_key: apiClient.accessToken()
|
||||||
@ -1865,9 +1869,10 @@ function tryRemoveElement(elem) {
|
|||||||
};
|
};
|
||||||
categories.push(videoCategory);
|
categories.push(videoCategory);
|
||||||
|
|
||||||
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
const rect = mediaElement.getBoundingClientRect ? mediaElement.getBoundingClientRect() : {};
|
const rect = mediaElement.getBoundingClientRect ? mediaElement.getBoundingClientRect() : {};
|
||||||
let height = parseInt(rect.height);
|
let height = Math.round(rect.height * devicePixelRatio);
|
||||||
let width = parseInt(rect.width);
|
let width = Math.round(rect.width * devicePixelRatio);
|
||||||
|
|
||||||
// Don't show player dimensions on smart TVs because the app UI could be lower resolution than the video and this causes users to think there is a problem
|
// Don't show player dimensions on smart TVs because the app UI could be lower resolution than the video and this causes users to think there is a problem
|
||||||
if (width && height && !browser.tv) {
|
if (width && height && !browser.tv) {
|
||||||
|
@ -36,6 +36,12 @@ export class PdfPlayer {
|
|||||||
stop() {
|
stop() {
|
||||||
this.unbindEvents();
|
this.unbindEvents();
|
||||||
|
|
||||||
|
const stopInfo = {
|
||||||
|
src: this.item
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.trigger(this, 'stopped', [stopInfo]);
|
||||||
|
|
||||||
const elem = this.mediaElement;
|
const elem = this.mediaElement;
|
||||||
if (elem) {
|
if (elem) {
|
||||||
dialogHelper.close(elem);
|
dialogHelper.close(elem);
|
||||||
@ -49,6 +55,10 @@ export class PdfPlayer {
|
|||||||
this.cancellationToken = true;
|
this.cancellationToken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
currentItem() {
|
currentItem() {
|
||||||
return this.item;
|
return this.item;
|
||||||
}
|
}
|
||||||
@ -114,8 +124,8 @@ export class PdfPlayer {
|
|||||||
bindMediaElementEvents() {
|
bindMediaElementEvents() {
|
||||||
const elem = this.mediaElement;
|
const elem = this.mediaElement;
|
||||||
|
|
||||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
elem.addEventListener('close', this.onDialogClosed, { once: true });
|
||||||
elem.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, {once: true});
|
elem.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
@ -181,6 +191,7 @@ export class PdfPlayer {
|
|||||||
this.streamInfo = {
|
this.streamInfo = {
|
||||||
started: true,
|
started: true,
|
||||||
ended: false,
|
ended: false,
|
||||||
|
item: this.item,
|
||||||
mediaSource: {
|
mediaSource: {
|
||||||
Id: item.Id
|
Id: item.Id
|
||||||
}
|
}
|
||||||
@ -218,12 +229,16 @@ export class PdfPlayer {
|
|||||||
if (this.progress === this.duration() - 1) return;
|
if (this.progress === this.duration() - 1) return;
|
||||||
this.loadPage(this.progress + 2);
|
this.loadPage(this.progress + 2);
|
||||||
this.progress = this.progress + 1;
|
this.progress = this.progress + 1;
|
||||||
|
|
||||||
|
Events.trigger(this, 'pause');
|
||||||
}
|
}
|
||||||
|
|
||||||
previous() {
|
previous() {
|
||||||
if (this.progress === 0) return;
|
if (this.progress === 0) return;
|
||||||
this.loadPage(this.progress);
|
this.loadPage(this.progress);
|
||||||
this.progress = this.progress - 1;
|
this.progress = this.progress - 1;
|
||||||
|
|
||||||
|
Events.trigger(this, 'pause');
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceCanvas(canvas) {
|
replaceCanvas(canvas) {
|
||||||
@ -265,8 +280,6 @@ export class PdfPlayer {
|
|||||||
|
|
||||||
renderPage(canvas, number) {
|
renderPage(canvas, number) {
|
||||||
this.book.getPage(number).then(page => {
|
this.book.getPage(number).then(page => {
|
||||||
Events.trigger(this, 'timeupdate');
|
|
||||||
|
|
||||||
const original = page.getViewport({ scale: 1 });
|
const original = page.getViewport({ scale: 1 });
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
@ -2,6 +2,11 @@ function isTv() {
|
|||||||
// This is going to be really difficult to get right
|
// This is going to be really difficult to get right
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
|
||||||
|
// The OculusBrowsers userAgent also has the samsungbrowser defined but is not a tv.
|
||||||
|
if (userAgent.indexOf('oculusbrowser') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (userAgent.indexOf('tv') !== -1) {
|
if (userAgent.indexOf('tv') !== -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ import browser from './browser';
|
|||||||
function testCanPlayAv1(videoTestElement) {
|
function testCanPlayAv1(videoTestElement) {
|
||||||
if (browser.tizenVersion >= 5.5) {
|
if (browser.tizenVersion >= 5.5) {
|
||||||
return true;
|
return true;
|
||||||
} else if (browser.web0sVersion >= 5 && window.outerHeight >= 2160) {
|
} else if (browser.web0sVersion >= 5) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,12 +294,55 @@ import browser from './browser';
|
|||||||
(browser.tizen && isTizenFhd ? 20000000 : null)));
|
(browser.tizen && isTizenFhd ? 20000000 : null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSpeakerCount() {
|
||||||
|
const AudioContext = window.AudioContext || window.webkitAudioContext || false; /* eslint-disable-line compat/compat */
|
||||||
|
|
||||||
|
if (AudioContext) {
|
||||||
|
const audioCtx = new AudioContext();
|
||||||
|
|
||||||
|
return audioCtx.destination.maxChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPhysicalAudioChannels(options) {
|
||||||
|
const allowedAudioChannels = parseInt(userSettings.allowedAudioChannels(), 10);
|
||||||
|
|
||||||
|
if (allowedAudioChannels > 0) {
|
||||||
|
return allowedAudioChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.audioChannels) {
|
||||||
|
return options.audioChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSurroundSoundSupportedBrowser = browser.safari || browser.chrome || browser.edgeChromium || browser.firefox || browser.tv || browser.ps4 || browser.xboxOne;
|
||||||
|
const speakerCount = getSpeakerCount();
|
||||||
|
|
||||||
|
if (speakerCount > 2) {
|
||||||
|
if (isSurroundSoundSupportedBrowser) {
|
||||||
|
return speakerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speakerCount > 0) {
|
||||||
|
return speakerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSurroundSoundSupportedBrowser) {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
export default function (options) {
|
export default function (options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
const isSurroundSoundSupportedBrowser = browser.safari || browser.chrome || browser.edgeChromium || browser.firefox;
|
const physicalAudioChannels = getPhysicalAudioChannels(options);
|
||||||
const allowedAudioChannels = parseInt(userSettings.allowedAudioChannels() || '-1');
|
|
||||||
const physicalAudioChannels = (allowedAudioChannels > 0 ? allowedAudioChannels : null) || options.audioChannels || (isSurroundSoundSupportedBrowser || browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2);
|
|
||||||
|
|
||||||
const bitrateSetting = getMaxBitrate();
|
const bitrateSetting = getMaxBitrate();
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
case 'AndroidTV':
|
case 'AndroidTV':
|
||||||
case 'Android TV':
|
case 'Android TV':
|
||||||
return baseUrl + 'android.svg';
|
return baseUrl + 'android.svg';
|
||||||
|
case 'Jellyfin Mobile (iOS)':
|
||||||
|
return baseUrl + 'apple.svg';
|
||||||
case 'Jellyfin Web':
|
case 'Jellyfin Web':
|
||||||
switch (device.Name || device.DeviceName) {
|
switch (device.Name || device.DeviceName) {
|
||||||
case 'Opera':
|
case 'Opera':
|
||||||
|
@ -302,6 +302,11 @@ import Headroom from 'headroom.js';
|
|||||||
|
|
||||||
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><span class="material-icons navMenuOptionIcon settings"></span><span class="navMenuOptionText">' + globalize.translate('Settings') + '</span></a>';
|
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><span class="material-icons navMenuOptionIcon settings"></span><span class="navMenuOptionText">' + globalize.translate('Settings') + '</span></a>';
|
||||||
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><span class="material-icons navMenuOptionIcon exit_to_app"></span><span class="navMenuOptionText">' + globalize.translate('ButtonSignOut') + '</span></a>';
|
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><span class="material-icons navMenuOptionIcon exit_to_app"></span><span class="navMenuOptionText">' + globalize.translate('ButtonSignOut') + '</span></a>';
|
||||||
|
|
||||||
|
if (appHost.supports('exitmenu')) {
|
||||||
|
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder exitApp" data-itemid="exitapp" href="#"><span class="material-icons navMenuOptionIcon close"></span><span class="navMenuOptionText">' + globalize.translate('ButtonExitApp') + '</span></a>';
|
||||||
|
}
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +323,11 @@ import Headroom from 'headroom.js';
|
|||||||
btnSettings.addEventListener('click', onSettingsClick);
|
btnSettings.addEventListener('click', onSettingsClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const btnExit = navDrawerScrollContainer.querySelector('.exitApp');
|
||||||
|
if (btnExit) {
|
||||||
|
btnExit.addEventListener('click', onExitAppClick);
|
||||||
|
}
|
||||||
|
|
||||||
const btnLogout = navDrawerScrollContainer.querySelector('.btnLogout');
|
const btnLogout = navDrawerScrollContainer.querySelector('.btnLogout');
|
||||||
if (btnLogout) {
|
if (btnLogout) {
|
||||||
btnLogout.addEventListener('click', onLogoutClick);
|
btnLogout.addEventListener('click', onLogoutClick);
|
||||||
@ -706,6 +716,10 @@ import Headroom from 'headroom.js';
|
|||||||
Dashboard.navigate('mypreferencesmenu.html');
|
Dashboard.navigate('mypreferencesmenu.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onExitAppClick() {
|
||||||
|
appHost.exit();
|
||||||
|
}
|
||||||
|
|
||||||
function onLogoutClick() {
|
function onLogoutClick() {
|
||||||
Dashboard.logout();
|
Dashboard.logout();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ function getTimersHtml(timers, options) {
|
|||||||
html += cardBuilder.getCardsHtml({
|
html += cardBuilder.getCardsHtml({
|
||||||
items: group.items,
|
items: group.items,
|
||||||
shape: getBackdropShape(),
|
shape: getBackdropShape(),
|
||||||
|
showTitle: true,
|
||||||
showParentTitleOrTitle: true,
|
showParentTitleOrTitle: true,
|
||||||
showAirTime: true,
|
showAirTime: true,
|
||||||
showAirEndTime: true,
|
showAirEndTime: true,
|
||||||
|
@ -440,7 +440,7 @@ import { appRouter } from '../components/appRouter';
|
|||||||
path: 'dashboard/users/useredit.html',
|
path: 'dashboard/users/useredit.html',
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
roles: 'admin',
|
roles: 'admin',
|
||||||
controller: 'dashboard/users/useredit'
|
pageComponent: 'UserEditPage'
|
||||||
});
|
});
|
||||||
|
|
||||||
defineRoute({
|
defineRoute({
|
||||||
@ -448,7 +448,7 @@ import { appRouter } from '../components/appRouter';
|
|||||||
path: 'dashboard/users/userlibraryaccess.html',
|
path: 'dashboard/users/userlibraryaccess.html',
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
roles: 'admin',
|
roles: 'admin',
|
||||||
controller: 'dashboard/users/userlibraryaccess'
|
pageComponent: 'UserLibraryAccessPage'
|
||||||
});
|
});
|
||||||
|
|
||||||
defineRoute({
|
defineRoute({
|
||||||
@ -464,7 +464,7 @@ import { appRouter } from '../components/appRouter';
|
|||||||
path: 'dashboard/users/userparentalcontrol.html',
|
path: 'dashboard/users/userparentalcontrol.html',
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
roles: 'admin',
|
roles: 'admin',
|
||||||
controller: 'dashboard/users/userparentalcontrol'
|
pageComponent: 'UserParentalControl'
|
||||||
});
|
});
|
||||||
|
|
||||||
defineRoute({
|
defineRoute({
|
||||||
|
@ -137,11 +137,11 @@ function loadPlugins() {
|
|||||||
return getPlugins().then(function (list) {
|
return getPlugins().then(function (list) {
|
||||||
if (!appHost.supports('remotecontrol')) {
|
if (!appHost.supports('remotecontrol')) {
|
||||||
// Disable remote player plugins if not supported
|
// Disable remote player plugins if not supported
|
||||||
list.splice(list.indexOf('sessionPlayer'), 1);
|
list = list.filter(plugin => !plugin.startsWith('sessionPlayer')
|
||||||
list.splice(list.indexOf('chromecastPlayer'), 1);
|
&& !plugin.startsWith('chromecastPlayer'));
|
||||||
} else if (!browser.chrome && !browser.edgeChromium && !browser.opera) {
|
} else if (!browser.chrome && !browser.edgeChromium && !browser.opera) {
|
||||||
// Disable chromecast player in unsupported browsers
|
// Disable chromecast player in unsupported browsers
|
||||||
list.splice(list.indexOf('chromecastPlayer'), 1);
|
list = list.filter(plugin => !plugin.startsWith('chromecastPlayer'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add any native plugins
|
// add any native plugins
|
||||||
|
@ -362,7 +362,7 @@
|
|||||||
"HeaderResponseProfile": "Terugvoer Profiel",
|
"HeaderResponseProfile": "Terugvoer Profiel",
|
||||||
"HeaderRemoveMediaLocation": "Verwyder Media Lokasie",
|
"HeaderRemoveMediaLocation": "Verwyder Media Lokasie",
|
||||||
"HeaderRemoveMediaFolder": "Verwyder Media Gidslêer",
|
"HeaderRemoveMediaFolder": "Verwyder Media Gidslêer",
|
||||||
"HeaderRemoteControl": "Afstandbeheer",
|
"HeaderRemoteControl": "Afstandbeheer:",
|
||||||
"HeaderRemoteAccessSettings": "Eksterne Toegang Verstellings",
|
"HeaderRemoteAccessSettings": "Eksterne Toegang Verstellings",
|
||||||
"HeaderRecordingPostProcessing": "Opname Naverwerking",
|
"HeaderRecordingPostProcessing": "Opname Naverwerking",
|
||||||
"HeaderRecordingOptions": "Opname Opsies",
|
"HeaderRecordingOptions": "Opname Opsies",
|
||||||
@ -373,7 +373,7 @@
|
|||||||
"HeaderPluginInstallation": "Inprop-toepassing Installasie",
|
"HeaderPluginInstallation": "Inprop-toepassing Installasie",
|
||||||
"HeaderPleaseSignIn": "Skakel asseblief in",
|
"HeaderPleaseSignIn": "Skakel asseblief in",
|
||||||
"HeaderPlaybackError": "Terugspeel Fout",
|
"HeaderPlaybackError": "Terugspeel Fout",
|
||||||
"HeaderPlayback": "Media Terugspeel",
|
"HeaderPlayback": "Media Terugspeel:",
|
||||||
"HeaderPlayOn": "Speel Aan",
|
"HeaderPlayOn": "Speel Aan",
|
||||||
"HeaderPlayAll": "Speel Alles",
|
"HeaderPlayAll": "Speel Alles",
|
||||||
"HeaderPinCodeReset": "Herstel PIN Kode",
|
"HeaderPinCodeReset": "Herstel PIN Kode",
|
||||||
@ -427,7 +427,7 @@
|
|||||||
"HeaderForKids": "Vir Kinders",
|
"HeaderForKids": "Vir Kinders",
|
||||||
"HeaderFetcherSettings": "Soeker Verstellings",
|
"HeaderFetcherSettings": "Soeker Verstellings",
|
||||||
"HeaderFetchImages": "Soek Beelde:",
|
"HeaderFetchImages": "Soek Beelde:",
|
||||||
"HeaderFeatureAccess": "Funksie Toegang",
|
"HeaderFeatureAccess": "Funksie Toegang:",
|
||||||
"HeaderExternalIds": "Eksterne IDs:",
|
"HeaderExternalIds": "Eksterne IDs:",
|
||||||
"HeaderError": "Fout",
|
"HeaderError": "Fout",
|
||||||
"HeaderEnabledFieldsHelp": "Ontkies 'n veld om dit te sluit en te verhoed dat die veld se data verander kan word.",
|
"HeaderEnabledFieldsHelp": "Ontkies 'n veld om dit te sluit en te verhoed dat die veld se data verander kan word.",
|
||||||
@ -476,7 +476,7 @@
|
|||||||
"HeaderApiKeysHelp": "Eksterne toepassings word vereis om 'n API sleutel te hê om te kommunikeer met die bediener. Sleutels word utigereik deur in te sluit in die normale verbruikers rekening of deur self 'n sleutel toe te staan aan die toepassing.",
|
"HeaderApiKeysHelp": "Eksterne toepassings word vereis om 'n API sleutel te hê om te kommunikeer met die bediener. Sleutels word utigereik deur in te sluit in die normale verbruikers rekening of deur self 'n sleutel toe te staan aan die toepassing.",
|
||||||
"HeaderApiKeys": "API Sleutels",
|
"HeaderApiKeys": "API Sleutels",
|
||||||
"HeaderApiKey": "API Sleutel",
|
"HeaderApiKey": "API Sleutel",
|
||||||
"HeaderAllowMediaDeletionFrom": "Laat Media Verwydering Toe Van",
|
"HeaderAllowMediaDeletionFrom": "Laat Media Verwydering Toe Van:",
|
||||||
"HeaderAlert": "Waarskuwing",
|
"HeaderAlert": "Waarskuwing",
|
||||||
"HeaderAdmin": "Administrasie",
|
"HeaderAdmin": "Administrasie",
|
||||||
"HeaderAdditionalParts": "Addisionele Dele",
|
"HeaderAdditionalParts": "Addisionele Dele",
|
||||||
@ -724,7 +724,7 @@
|
|||||||
"HeaderUploadImage": "Laai Foto",
|
"HeaderUploadImage": "Laai Foto",
|
||||||
"HeaderUpcomingOnTV": "Opkomend op TV",
|
"HeaderUpcomingOnTV": "Opkomend op TV",
|
||||||
"HeaderTypeText": "Voer teks",
|
"HeaderTypeText": "Voer teks",
|
||||||
"HeaderTypeImageFetchers": "Beeld gaanhaalers ({0})",
|
"HeaderTypeImageFetchers": "Beeld gaanhaalers ({0}):",
|
||||||
"HeaderTuners": "Ontvangers",
|
"HeaderTuners": "Ontvangers",
|
||||||
"HeaderTunerDevices": "Ontvanger Toestele",
|
"HeaderTunerDevices": "Ontvanger Toestele",
|
||||||
"HeaderTranscodingProfileHelp": "Las by transkodering profiele om aan te dui watter formate gebruik moet word as transkodering nodig is.",
|
"HeaderTranscodingProfileHelp": "Las by transkodering profiele om aan te dui watter formate gebruik moet word as transkodering nodig is.",
|
||||||
@ -793,5 +793,15 @@
|
|||||||
"OptionBluray": "BD",
|
"OptionBluray": "BD",
|
||||||
"TypeOptionPluralSeries": "TV Programme",
|
"TypeOptionPluralSeries": "TV Programme",
|
||||||
"LiveTV": "Lewendige TV",
|
"LiveTV": "Lewendige TV",
|
||||||
"OptionCriticRating": "Filkritiek Gradering"
|
"OptionCriticRating": "Filkritiek Gradering",
|
||||||
|
"HeaderSyncPlayTimeSyncSettings": "Tydsinkronisering",
|
||||||
|
"HeaderSyncPlayPlaybackSettings": "Afspeel",
|
||||||
|
"HeaderSyncPlaySettings": "SyncPlay Konfigurasie",
|
||||||
|
"ErrorPlayerNotFound": "Geen speler vir die gevraagde media gevind nie.",
|
||||||
|
"Engineer": "Klankingenieur",
|
||||||
|
"Cursive": "Kursief",
|
||||||
|
"Console": "Konsole",
|
||||||
|
"Conductor": "Kondukteur",
|
||||||
|
"Casual": "Informeel",
|
||||||
|
"Arranger": "Organiseerder"
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
"Fullscreen": "الشاشة كاملة",
|
"Fullscreen": "الشاشة كاملة",
|
||||||
"GuideProviderLogin": "تسجيل الدخول",
|
"GuideProviderLogin": "تسجيل الدخول",
|
||||||
"GuideProviderSelectListings": "إختر المبوبات",
|
"GuideProviderSelectListings": "إختر المبوبات",
|
||||||
"H264CrfHelp": "معامل المعدل الثابت CRF هو الجودة الافتراضية لإعدادات مشفر x264 و x265. بإمكانك إعطاء قيمة تتراوح بين 0 و 51، وكلما قلت القيمة فسينتج عن ذلك جودة أفضل (على حساب حجم تخزين أعلى). القيم المعقولة تتراوح بين 18 و 28. الافتراضي ل x264 هو 23، ول x265 هو 28, لذا فبإمكانك استخدام هذه القيمة كنقطة بداية.",
|
"H264CrfHelp": "معامل المعدل الثابت CRF هو الجودة الافتراضية لإعدادات مشفر x264 و x265. بإمكانك إعطاء قيمة تتراوح بين 0 و 51، وكلما قلة القيمة فسينتج عن ذلك جودة أفضل (على حساب حجم تخزين أعلى). القيم المعقولة تتراوح بين 18 و 28. الافتراضي ل x264 هو 23، ول x265 هو 28, لذا فبإمكانك استخدام هذه القيمة كنقطة بداية.",
|
||||||
"EncoderPresetHelp": "اختر قيمة أعلى لتحسين الصورة والأداء وقيمة أقل لتحسين الجودة.",
|
"EncoderPresetHelp": "اختر قيمة أعلى لتحسين الصورة والأداء وقيمة أقل لتحسين الجودة.",
|
||||||
"HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية بلا None.",
|
"HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية بلا None.",
|
||||||
"HeaderAccessSchedule": "جدول الدخولات",
|
"HeaderAccessSchedule": "جدول الدخولات",
|
||||||
@ -101,9 +101,9 @@
|
|||||||
"HeaderActivity": "الأنشطة",
|
"HeaderActivity": "الأنشطة",
|
||||||
"HeaderAddUpdateImage": "إضافة/تحديث صورة",
|
"HeaderAddUpdateImage": "إضافة/تحديث صورة",
|
||||||
"HeaderAdditionalParts": "أدوار إضافية",
|
"HeaderAdditionalParts": "أدوار إضافية",
|
||||||
"HeaderAdmin": "المدير",
|
"HeaderAdmin": "الادارة",
|
||||||
"HeaderAlert": "تنبيه",
|
"HeaderAlert": "تنبيه",
|
||||||
"HeaderAllowMediaDeletionFrom": "السماح بحذف الوسائط من قبل",
|
"HeaderAllowMediaDeletionFrom": "السماح بحذف الوسائط من قبل:",
|
||||||
"HeaderApiKey": "مفتاح API",
|
"HeaderApiKey": "مفتاح API",
|
||||||
"HeaderApiKeys": "مفاتيح API",
|
"HeaderApiKeys": "مفاتيح API",
|
||||||
"HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح API لكي تتصل بالخادم. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بمستخدم عادي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.",
|
"HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح API لكي تتصل بالخادم. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بمستخدم عادي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.",
|
||||||
@ -113,14 +113,14 @@
|
|||||||
"HeaderCastAndCrew": "الممثلين وطاقم العمل",
|
"HeaderCastAndCrew": "الممثلين وطاقم العمل",
|
||||||
"HeaderChannelAccess": "صلاحيات القنوات",
|
"HeaderChannelAccess": "صلاحيات القنوات",
|
||||||
"HeaderCodecProfile": "عريضة الكودك",
|
"HeaderCodecProfile": "عريضة الكودك",
|
||||||
"HeaderCodecProfileHelp": "عرائض الكودك تشير إلى محدودية جهاز ما عند تشغيل وسيطة مشفر بكودك معيّن. إن كان هناك أي محدودية مذكورة فستحال الوسيطة إلى التشغير البيني، حتى لو كانت الصيغة مضبوطة للعمل بتلقائية.",
|
"HeaderCodecProfileHelp": "تشير ملفات تعريف برنامج الترميز إلى قيود الجهاز عند تشغيل برامج ترميز معينة. إذا تم تطبيق قيود ، فسيتم تحويل ترميز الوسائط ، حتى إذا تم تكوين برنامج الترميز للتشغيل المباشر.",
|
||||||
"HeaderConfirmPluginInstallation": "أكد عملية تثبيت الملحق",
|
"HeaderConfirmPluginInstallation": "أكد عملية تثبيت الملحق",
|
||||||
"HeaderConfirmProfileDeletion": "أكّد حذف العريضة",
|
"HeaderConfirmProfileDeletion": "أكّد حذف العريضة",
|
||||||
"HeaderConfirmRevokeApiKey": "أرفض مفتاح API",
|
"HeaderConfirmRevokeApiKey": "أرفض مفتاح API",
|
||||||
"HeaderConnectToServer": "اتصل إلى الخادم",
|
"HeaderConnectToServer": "اتصل إلى الخادم",
|
||||||
"HeaderConnectionFailure": "فشل في الاتصال",
|
"HeaderConnectionFailure": "فشل في الاتصال",
|
||||||
"HeaderContainerProfile": "عريضة الحاوية",
|
"HeaderContainerProfile": "عريضة الحاوية",
|
||||||
"HeaderContainerProfileHelp": "عرائض الحاويات تشير إلى محدوديات جهاز ما عند تشغيل صيغ معينة. إن كان هناك أي محدودية مذكورة فستحال الوسيطة إلى التشغير البيني، حتى لو كانت الصيغة مضبوطة للعمل بتلقائية.",
|
"HeaderContainerProfileHelp": "تشير ملفات تعريف الحاوية إلى قيود الجهاز عند تشغيل تنسيقات معينة. إذا تم تطبيق قيود ، فسيتم تحويل ترميز الوسائط ، حتى إذا تم تكوين التنسيق للتشغيل المباشر.",
|
||||||
"HeaderContinueWatching": "استمر بالمشاهدة",
|
"HeaderContinueWatching": "استمر بالمشاهدة",
|
||||||
"HeaderCustomDlnaProfiles": "الحسابات المخصوصة",
|
"HeaderCustomDlnaProfiles": "الحسابات المخصوصة",
|
||||||
"HeaderDateIssued": "تاريخ الإصدار",
|
"HeaderDateIssued": "تاريخ الإصدار",
|
||||||
@ -132,11 +132,11 @@
|
|||||||
"HeaderDeveloperInfo": "معلومات المطور",
|
"HeaderDeveloperInfo": "معلومات المطور",
|
||||||
"HeaderDeviceAccess": "الدخول على جهاز",
|
"HeaderDeviceAccess": "الدخول على جهاز",
|
||||||
"HeaderDevices": "الأجهزة",
|
"HeaderDevices": "الأجهزة",
|
||||||
"HeaderDirectPlayProfile": "عريضة التشغيل المباشر",
|
"HeaderDirectPlayProfile": "ملف تعريف التشغيل المباشر",
|
||||||
"HeaderDirectPlayProfileHelp": "أضف مباشرةً عريضة تشغيل للإشارة لأي صيغة يتمكن الجهاز من التعامل معه بتلقائية.",
|
"HeaderDirectPlayProfileHelp": "أضف ملفات تعريف التشغيل المباشر للإشارة إلى التنسيقات التي يمكن للجهاز التعامل معها محليًا.",
|
||||||
"HeaderEasyPinCode": "الرمز الشخصي البسيط",
|
"HeaderEasyPinCode": "الرمز الشخصي البسيط",
|
||||||
"HeaderError": "حدث خطأ",
|
"HeaderError": "حدث خطأ",
|
||||||
"HeaderFeatureAccess": "صلاحية الخاصية",
|
"HeaderFeatureAccess": "صلاحية الخاصية:",
|
||||||
"HeaderFetchImages": "إطهار الصور:",
|
"HeaderFetchImages": "إطهار الصور:",
|
||||||
"HeaderForKids": "للأطفال",
|
"HeaderForKids": "للأطفال",
|
||||||
"HeaderFrequentlyPlayed": "تم تشغيله مراراً",
|
"HeaderFrequentlyPlayed": "تم تشغيله مراراً",
|
||||||
@ -168,9 +168,9 @@
|
|||||||
"HeaderPassword": "كلمة السر",
|
"HeaderPassword": "كلمة السر",
|
||||||
"HeaderPasswordReset": "إعادة تهيئة كلمة السر",
|
"HeaderPasswordReset": "إعادة تهيئة كلمة السر",
|
||||||
"HeaderPaths": "مسارات",
|
"HeaderPaths": "مسارات",
|
||||||
"HeaderPinCodeReset": "اعد تهيئة الرمز الشخصي",
|
"HeaderPinCodeReset": "إعادة تهيئة الرمز الشخصي البسيط",
|
||||||
"HeaderPlayAll": "تشغيل الكل",
|
"HeaderPlayAll": "تشغيل الكل",
|
||||||
"HeaderPlayback": "تشغيل الوسائط",
|
"HeaderPlayback": "تشغيل الوسائط:",
|
||||||
"HeaderPleaseSignIn": "الرجاء تسجيل الدخول",
|
"HeaderPleaseSignIn": "الرجاء تسجيل الدخول",
|
||||||
"HeaderPluginInstallation": "تثبيت الملحفات",
|
"HeaderPluginInstallation": "تثبيت الملحفات",
|
||||||
"HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات",
|
"HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات",
|
||||||
@ -178,7 +178,7 @@
|
|||||||
"HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل الخادم في للعملاء.",
|
"HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل الخادم في للعملاء.",
|
||||||
"HeaderRecentlyPlayed": "تم تشغيله مؤخراً",
|
"HeaderRecentlyPlayed": "تم تشغيله مؤخراً",
|
||||||
"HeaderRecordingPostProcessing": "تطبيق ما-بعد-المعالجة للتسجيل",
|
"HeaderRecordingPostProcessing": "تطبيق ما-بعد-المعالجة للتسجيل",
|
||||||
"HeaderRemoteControl": "التحكم عن بعد",
|
"HeaderRemoteControl": "التحكم عن بعد:",
|
||||||
"HeaderRemoveMediaFolder": "إحذف مجلد الوسائط",
|
"HeaderRemoveMediaFolder": "إحذف مجلد الوسائط",
|
||||||
"HeaderRemoveMediaLocation": "إحذف مكان الوسائط",
|
"HeaderRemoveMediaLocation": "إحذف مكان الوسائط",
|
||||||
"HeaderResponseProfile": "عريضة الرد",
|
"HeaderResponseProfile": "عريضة الرد",
|
||||||
@ -213,7 +213,7 @@
|
|||||||
"HeaderTranscodingProfileHelp": "أضف عرائض التشفير البيني للإشارة لأي صيغة يتعيّن استخدامها عندما توجد حاجة للتشفير البيني.",
|
"HeaderTranscodingProfileHelp": "أضف عرائض التشفير البيني للإشارة لأي صيغة يتعيّن استخدامها عندما توجد حاجة للتشفير البيني.",
|
||||||
"HeaderTunerDevices": "أجهزة التوليف",
|
"HeaderTunerDevices": "أجهزة التوليف",
|
||||||
"HeaderTuners": "المولفات",
|
"HeaderTuners": "المولفات",
|
||||||
"HeaderTypeImageFetchers": "جالبات الصور ({0})",
|
"HeaderTypeImageFetchers": "جالبات الصور ({0}):",
|
||||||
"HeaderTypeText": "أدخل النص",
|
"HeaderTypeText": "أدخل النص",
|
||||||
"HeaderUpcomingOnTV": "البرامج القادمة على التلفاز",
|
"HeaderUpcomingOnTV": "البرامج القادمة على التلفاز",
|
||||||
"HeaderUploadImage": "رفع الصور",
|
"HeaderUploadImage": "رفع الصور",
|
||||||
@ -233,7 +233,7 @@
|
|||||||
"LabelAirDays": "أيام البث:",
|
"LabelAirDays": "أيام البث:",
|
||||||
"LabelAirTime": "وقت البث:",
|
"LabelAirTime": "وقت البث:",
|
||||||
"LabelAlbum": "الألبوم:",
|
"LabelAlbum": "الألبوم:",
|
||||||
"LabelAlbumArtHelp": "PN المستخدمة في رسومات الألبوم، داخل سمة dlna:profileID في upnp:albumArtURI. بعض الأجهزة تحتاج قيمة محددة، مهما كان حجم الصورة.",
|
"LabelAlbumArtHelp": "يتم استخدام PN لصورة الألبوم ، داخل السمة \"dlna: profileID\" على \"upnp: AlbumArtURI\". تتطلب بعض الأجهزة قيمة معينة ، بغض النظر عن حجم الصورة.",
|
||||||
"LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم:",
|
"LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم:",
|
||||||
"LabelAlbumArtMaxWidth": "العرض الأقصى لرسوم الألبوم:",
|
"LabelAlbumArtMaxWidth": "العرض الأقصى لرسوم الألبوم:",
|
||||||
"LabelAlbumArtPN": "رسومات الألبوم PN:",
|
"LabelAlbumArtPN": "رسومات الألبوم PN:",
|
||||||
@ -259,12 +259,12 @@
|
|||||||
"LabelCurrentPassword": "كلمة السر الحالية:",
|
"LabelCurrentPassword": "كلمة السر الحالية:",
|
||||||
"LabelCustomCertificatePath": "مسار شهادة SSL المخصص:",
|
"LabelCustomCertificatePath": "مسار شهادة SSL المخصص:",
|
||||||
"LabelCustomCertificatePathHelp": "مسار ملف PKCS # 12 يحتوي على شهادة ومفتاح خاص لتمكين دعم TLS على مجال مخصص.",
|
"LabelCustomCertificatePathHelp": "مسار ملف PKCS # 12 يحتوي على شهادة ومفتاح خاص لتمكين دعم TLS على مجال مخصص.",
|
||||||
"LabelCustomCss": "تنيسق CSS مخصص:",
|
"LabelCustomCss": "كود CSS المخصص:",
|
||||||
"LabelCustomCssHelp": "طبق تنسيقك css المخصص لواجهة الويب.",
|
"LabelCustomCssHelp": "قم بتطبيق كود CSS المخصص الخاص بك من أجل السمات / العلامة التجارية على واجهة الويب.",
|
||||||
"LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام الاسم المبلغ من الجهاز.",
|
"LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام الاسم المبلغ من الجهاز.",
|
||||||
"LabelDateAddedBehavior": "كيف يتصرف المحتوى الجديد نحو \"تاريخ الإضافة\" الخاص به:",
|
"LabelDateAddedBehavior": "كيف يتصرف المحتوى الجديد نحو \"تاريخ الإضافة\" الخاص به:",
|
||||||
"LabelDateAddedBehaviorHelp": "إذا اخذت واصفات البيانات قيمة، فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.",
|
"LabelDateAddedBehaviorHelp": "إذا اخذت واصفات البيانات قيمة، فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.",
|
||||||
"LabelDay": "اليوم:",
|
"LabelDay": "يوم الأسبوع:",
|
||||||
"LabelDeathDate": "تاريخ الوفاة:",
|
"LabelDeathDate": "تاريخ الوفاة:",
|
||||||
"LabelDefaultUser": "المستخدم الافتراضي:",
|
"LabelDefaultUser": "المستخدم الافتراضي:",
|
||||||
"LabelDefaultUserHelp": "يحدد مكتبة المستخدم التي يجب عرضها على الأجهزة المتصلة. يمكن تجاوز هذا لكل جهاز باستخدام ملفات التعريف.",
|
"LabelDefaultUserHelp": "يحدد مكتبة المستخدم التي يجب عرضها على الأجهزة المتصلة. يمكن تجاوز هذا لكل جهاز باستخدام ملفات التعريف.",
|
||||||
@ -284,10 +284,10 @@
|
|||||||
"LabelEnableBlastAliveMessages": "بث رسائل قيد التشغيل",
|
"LabelEnableBlastAliveMessages": "بث رسائل قيد التشغيل",
|
||||||
"LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك.",
|
"LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك.",
|
||||||
"LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء:",
|
"LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء:",
|
||||||
"LabelEnableDlnaClientDiscoveryIntervalHelp": "يحدد الفترة بالثواني بين عمليات بحث SSDP.",
|
"LabelEnableDlnaClientDiscoveryIntervalHelp": "حدد المدة بالثواني بين عمليتي بحث SSDP.",
|
||||||
"LabelEnableDlnaDebugLogging": "تفعيل خاصية كشوفات أخطاء DLNA",
|
"LabelEnableDlnaDebugLogging": "تفعيل خاصية كشوفات أخطاء DLNA",
|
||||||
"LabelEnableDlnaDebugLoggingHelp": "انشاء سجلات كشفية ضخمة ولا ينبغي تفعيلها إلا عند الحاجة إليها بغرض استكشاف الأخطاء وحصرها.",
|
"LabelEnableDlnaDebugLoggingHelp": "انشاء سجلات كشفية ضخمة ولا ينبغي تفعيلها إلا عند الحاجة إليها بغرض استكشاف الأخطاء وحصرها.",
|
||||||
"LabelEnableDlnaPlayTo": "تفعيل خاصية DLNA Play To",
|
"LabelEnableDlnaPlayTo": "تفعيل خاصية 'تشغيل' تحالف الشبكة الرقمية الحية",
|
||||||
"LabelEnableDlnaPlayToHelp": "اكتشف الأجهزة على شبكتك ويقدم لك إمكانية التحكم بهم عن بعد.",
|
"LabelEnableDlnaPlayToHelp": "اكتشف الأجهزة على شبكتك ويقدم لك إمكانية التحكم بهم عن بعد.",
|
||||||
"LabelEnableDlnaServer": "تفعيل خادم DLNA",
|
"LabelEnableDlnaServer": "تفعيل خادم DLNA",
|
||||||
"LabelEnableDlnaServerHelp": "يسمح لأجهزة UPnP الموجودة على شبكتك بتصفح المحتوى وتشغيله.",
|
"LabelEnableDlnaServerHelp": "يسمح لأجهزة UPnP الموجودة على شبكتك بتصفح المحتوى وتشغيله.",
|
||||||
@ -461,10 +461,10 @@
|
|||||||
"LabelVaapiDeviceHelp": "هذه هي عقدة التصيير التي ستستخدم من قبل التسريع بعتاد الحاسوب.",
|
"LabelVaapiDeviceHelp": "هذه هي عقدة التصيير التي ستستخدم من قبل التسريع بعتاد الحاسوب.",
|
||||||
"LabelValue": "القيمة:",
|
"LabelValue": "القيمة:",
|
||||||
"LabelVersionInstalled": "{0} مثبتة",
|
"LabelVersionInstalled": "{0} مثبتة",
|
||||||
"LabelXDlnaCap": "سقف X-Dlna:",
|
"LabelXDlnaCap": "معرف قدرة الجهاز:",
|
||||||
"LabelXDlnaCapHelp": "تحدد محتوى عنصر X_DLNACAP في النطاق الاسمي ل urn:schemas-dlna-org:device-1-0 .",
|
"LabelXDlnaCapHelp": "حدد محتوى عنصر \"X_DLNACAP\" في مساحة الاسم \"urn: schemas-dlna-org: device-1-0\".",
|
||||||
"LabelXDlnaDoc": "وثيقة X-Dlna:",
|
"LabelXDlnaDoc": "معرف فئة الجهاز:",
|
||||||
"LabelXDlnaDocHelp": "تحدد محتوى عنصر X_DLNADOC في النطاق الاسمي ل urn:schemas-dlna-org:device-1-0 .",
|
"LabelXDlnaDocHelp": "حدد محتوى عنصر \"X_DLNADOC\" في مساحة الاسم \"urn: schemas-dlna-org: device-1-0\".",
|
||||||
"LabelYoureDone": "تم الانتهاء!",
|
"LabelYoureDone": "تم الانتهاء!",
|
||||||
"LabelZipCode": "الرمز البريدي:",
|
"LabelZipCode": "الرمز البريدي:",
|
||||||
"LabelffmpegPath": "مسار ffmpeg:",
|
"LabelffmpegPath": "مسار ffmpeg:",
|
||||||
@ -509,7 +509,7 @@
|
|||||||
"MessageContactAdminToResetPassword": "الرجاء التواصل مع مدير النظام لإعادة أعداد كملة سرّك.",
|
"MessageContactAdminToResetPassword": "الرجاء التواصل مع مدير النظام لإعادة أعداد كملة سرّك.",
|
||||||
"MessageCreateAccountAt": "أنشئ حساب في {0}",
|
"MessageCreateAccountAt": "أنشئ حساب في {0}",
|
||||||
"MessageDeleteTaskTrigger": "هل أنت متأكد أنك تريد حذف زناد المهمة؟",
|
"MessageDeleteTaskTrigger": "هل أنت متأكد أنك تريد حذف زناد المهمة؟",
|
||||||
"MessageDirectoryPickerBSDInstruction": "من أجل BSD، يمكنك أن تضبط إعدادات التخزين دخال حساب FreeNAS Jail الخاص بك لكي يتمكن Jellyfin أن يتصل به.",
|
"MessageDirectoryPickerBSDInstruction": "بالنسبة إلى BSD ، قد تحتاج إلى إعداد التخزين داخل \"FreeNAS Jail\" حتى يتمكن Jellyfin من الوصول إلى الوسائط الخاصة بك.",
|
||||||
"MessageDirectoryPickerLinuxInstruction": "من أجل أنظمة التشغيل التالية: Linux أو Arch Linux أو CentOS أو Debian أو Fedora أو openSUSE أو Ubuntu، يجب أن تمنح المستخدم النظامي صلاحية القراءة ليتمكن من الوصول إلى أماكن التخزين.",
|
"MessageDirectoryPickerLinuxInstruction": "من أجل أنظمة التشغيل التالية: Linux أو Arch Linux أو CentOS أو Debian أو Fedora أو openSUSE أو Ubuntu، يجب أن تمنح المستخدم النظامي صلاحية القراءة ليتمكن من الوصول إلى أماكن التخزين.",
|
||||||
"MessageEnablingOptionLongerScans": "قد يؤدي تمكين هذا الخيار إلى إبطاء البحث في المكتبات بشكل ملحوظ.",
|
"MessageEnablingOptionLongerScans": "قد يؤدي تمكين هذا الخيار إلى إبطاء البحث في المكتبات بشكل ملحوظ.",
|
||||||
"MessageFileReadError": "حصل خطأ أثناء قراءة الملف. الرجاء المحاولة مرة اخرى.",
|
"MessageFileReadError": "حصل خطأ أثناء قراءة الملف. الرجاء المحاولة مرة اخرى.",
|
||||||
@ -524,7 +524,7 @@
|
|||||||
"MessageNoPluginsInstalled": "ليس عندك أي ملحقات مثبتة.",
|
"MessageNoPluginsInstalled": "ليس عندك أي ملحقات مثبتة.",
|
||||||
"MessageNoTrailersFound": "قم بتثبيت قناة العروض الإعلانية لتحسين متعة المشاهدة بإضافة مكتبة عروض إعلانية من الإنترنت.",
|
"MessageNoTrailersFound": "قم بتثبيت قناة العروض الإعلانية لتحسين متعة المشاهدة بإضافة مكتبة عروض إعلانية من الإنترنت.",
|
||||||
"MessageNothingHere": "لا شىء هنا.",
|
"MessageNothingHere": "لا شىء هنا.",
|
||||||
"MessagePasswordResetForUsers": "تم إعادة تعيين كلمات المرور للمستخدمين التالين. يمكنهم الآن تسجيل الدخول باستخدام رموز PIN التي تم استخدامها لإجراء إعادة التعيين.",
|
"MessagePasswordResetForUsers": "تم إعادة تعيين كلمات المرور الخاصة بهم للمستخدمين التاليين. يمكنهم الآن تسجيل الدخول باستخدام رموز PIN سهلة الاستخدام التي تم استخدامها لإعادة الضبط.",
|
||||||
"MessagePleaseEnsureInternetMetadata": "الرجاء التأكد من أن إمكانية إنزال واصفات البيانات من الإنترنت ممكنة.",
|
"MessagePleaseEnsureInternetMetadata": "الرجاء التأكد من أن إمكانية إنزال واصفات البيانات من الإنترنت ممكنة.",
|
||||||
"MessagePluginConfigurationRequiresLocalAccess": "لضبط هذا البرنامج المساعد ، يرجى تسجيل الدخول إلى الخادم المحلي الخاص بك مباشرة.",
|
"MessagePluginConfigurationRequiresLocalAccess": "لضبط هذا البرنامج المساعد ، يرجى تسجيل الدخول إلى الخادم المحلي الخاص بك مباشرة.",
|
||||||
"MessagePluginInstallDisclaimer": "إن الملحقات التي بناها أعضاء مجتمع Jellyfin لهي طريقة رائعة لتحسين متعة استخدام Jellyfin وذلك بإضافة المزايا والخدمات الجديدة. قبل تثبيت الملحقات، نرجو أخذ العلم بالآثار التي قد تلحقها بخادم Jellyfin الخاص بك، مثل أوقات أطولة لتمشيط مكتبتك، والعمليات الخلفية الإضافية وتقليل استقرار نظامك.",
|
"MessagePluginInstallDisclaimer": "إن الملحقات التي بناها أعضاء مجتمع Jellyfin لهي طريقة رائعة لتحسين متعة استخدام Jellyfin وذلك بإضافة المزايا والخدمات الجديدة. قبل تثبيت الملحقات، نرجو أخذ العلم بالآثار التي قد تلحقها بخادم Jellyfin الخاص بك، مثل أوقات أطولة لتمشيط مكتبتك، والعمليات الخلفية الإضافية وتقليل استقرار نظامك.",
|
||||||
@ -534,7 +534,7 @@
|
|||||||
"MessageUnsetContentHelp": "المحتوى سيعرض كمجدات اعتيادية. لأفضل النتائج استخدم مدير واصفات البيانات لإعداد نوع محتوى المجلدات الفرعية.",
|
"MessageUnsetContentHelp": "المحتوى سيعرض كمجدات اعتيادية. لأفضل النتائج استخدم مدير واصفات البيانات لإعداد نوع محتوى المجلدات الفرعية.",
|
||||||
"MessageYouHaveVersionInstalled": "الإصدار المثبت حالياً هو {0}.",
|
"MessageYouHaveVersionInstalled": "الإصدار المثبت حالياً هو {0}.",
|
||||||
"MetadataManager": "مدير واصفات البيانات",
|
"MetadataManager": "مدير واصفات البيانات",
|
||||||
"MetadataSettingChangeHelp": "سيؤثر تغيير إعدادات البيانات الوصفية على المحتوى الجديد المُضاف من الآن فصاعدًا. لتحديث المحتوى الحالي ، افتح شاشة التفاصيل وانقر فوق زر التحديث ، أو قم بإجراء تحديثات مجمعة باستخدام مدير البيانات الوصفية.",
|
"MetadataSettingChangeHelp": "سيؤثر تغيير إعدادات البيانات الوصفية على المحتوى الجديد المُضاف من الآن فصاعدًا. لتحديث المحتوى الحالي ، افتح شاشة التفاصيل وانقر فوق الزر \"تحديث\" ، أو قم بإجراء تحديثات مجمعة باستخدام \"مدير البيانات الوصفية\".",
|
||||||
"MinutesAfter": "عدد الدقائق اللاحقة",
|
"MinutesAfter": "عدد الدقائق اللاحقة",
|
||||||
"MinutesBefore": "عدد الدقائق السابقة",
|
"MinutesBefore": "عدد الدقائق السابقة",
|
||||||
"Monday": "الاثنين",
|
"Monday": "الاثنين",
|
||||||
@ -608,7 +608,7 @@
|
|||||||
"OptionOnInterval": "بناء على فترة",
|
"OptionOnInterval": "بناء على فترة",
|
||||||
"OptionParentalRating": "التصنيف الأبوي",
|
"OptionParentalRating": "التصنيف الأبوي",
|
||||||
"OptionPlainStorageFolders": "غرض جميع المجلدات كمجلدات تخزين بسيطة",
|
"OptionPlainStorageFolders": "غرض جميع المجلدات كمجلدات تخزين بسيطة",
|
||||||
"OptionPlainStorageFoldersHelp": "يتم تمثيل كافة المجلدات في DIDL ك \"object.container.storageFolder\" بدلاً من نوع أكثر تحديدًا ، مثل \"object.container.person.musicArtist\".",
|
"OptionPlainStorageFoldersHelp": "يتم تمثيل جميع المجلدات في DIDL ك \"object.container.storageFolder\" بدلاً من نوع أكثر تحديدًا ، مثل \"object.container.person.musicArtist\".",
|
||||||
"OptionPlainVideoItems": "إظهار جميع الفيديوهات كعناصر فيديو بسيطة",
|
"OptionPlainVideoItems": "إظهار جميع الفيديوهات كعناصر فيديو بسيطة",
|
||||||
"OptionPlainVideoItemsHelp": "يتم تمثيل جميع مقاطع الفيديو في DIDL ك \"object.item.videoItem\" بدلاً من نوع أكثر تحديدًا ، مثل \"object.item.videoItem.movie\".",
|
"OptionPlainVideoItemsHelp": "يتم تمثيل جميع مقاطع الفيديو في DIDL ك \"object.item.videoItem\" بدلاً من نوع أكثر تحديدًا ، مثل \"object.item.videoItem.movie\".",
|
||||||
"OptionPlayCount": "مرات التشغيل",
|
"OptionPlayCount": "مرات التشغيل",
|
||||||
@ -617,8 +617,8 @@
|
|||||||
"OptionReleaseDate": "تاريخ الإنتاج",
|
"OptionReleaseDate": "تاريخ الإنتاج",
|
||||||
"OptionReportByteRangeSeekingWhenTranscoding": "قرّر ما إذا كان الخادم يدعم البحث عن البايت حال التشفير",
|
"OptionReportByteRangeSeekingWhenTranscoding": "قرّر ما إذا كان الخادم يدعم البحث عن البايت حال التشفير",
|
||||||
"OptionReportByteRangeSeekingWhenTranscodingHelp": "هذه مطلوبة لبعض الأجهزة التي لا تحسن البحث في الوقت.",
|
"OptionReportByteRangeSeekingWhenTranscodingHelp": "هذه مطلوبة لبعض الأجهزة التي لا تحسن البحث في الوقت.",
|
||||||
"OptionRequirePerfectSubtitleMatch": "نزّل فقط الترجمات التي توافق بدقة ملفات الفيديو الخاصة بي",
|
"OptionRequirePerfectSubtitleMatch": "قم بتنزيل الترجمات المطابقة تمامًا لملفات الفيديو فقط",
|
||||||
"OptionResElement": "عنصر الدقة",
|
"OptionResElement": "المورد",
|
||||||
"OptionResumable": "إمكانية التكملة",
|
"OptionResumable": "إمكانية التكملة",
|
||||||
"OptionSaveMetadataAsHidden": "حفظ واصفات البيانات والصور كملفات مخفية",
|
"OptionSaveMetadataAsHidden": "حفظ واصفات البيانات والصور كملفات مخفية",
|
||||||
"OptionSaveMetadataAsHiddenHelp": "سيؤدي تغيير هذا إلى تطبيق البيانات الوصفية الجديدة المحفوظة من الآن فصاعدًا. سيتم تحديث ملفات البيانات الوصفية الموجودة في المرة التالية التي يتم فيها حفظها بواسطة الخادم.",
|
"OptionSaveMetadataAsHiddenHelp": "سيؤدي تغيير هذا إلى تطبيق البيانات الوصفية الجديدة المحفوظة من الآن فصاعدًا. سيتم تحديث ملفات البيانات الوصفية الموجودة في المرة التالية التي يتم فيها حفظها بواسطة الخادم.",
|
||||||
@ -637,7 +637,7 @@
|
|||||||
"PasswordResetConfirmation": "هل انت متاكد من انك تريد اعادة تعيين كلمة السر؟",
|
"PasswordResetConfirmation": "هل انت متاكد من انك تريد اعادة تعيين كلمة السر؟",
|
||||||
"PasswordSaved": "تم حفظ كلمة السر.",
|
"PasswordSaved": "تم حفظ كلمة السر.",
|
||||||
"PictureInPicture": "صورة داخل صورة",
|
"PictureInPicture": "صورة داخل صورة",
|
||||||
"PinCodeResetComplete": "تمت إعادة تعيين رمز PIN.",
|
"PinCodeResetComplete": "تمت إعادة تعيين رمز PIN السهل.",
|
||||||
"PinCodeResetConfirmation": "هل أنت متأكد أنك تريد إعادة تهيئة الرمز الشخصي؟",
|
"PinCodeResetConfirmation": "هل أنت متأكد أنك تريد إعادة تهيئة الرمز الشخصي؟",
|
||||||
"PleaseAddAtLeastOneFolder": "الرجاء إضافة مجلد واحد على الأقل لهذه المكتبة بالضغط على زر \"إضافة\".",
|
"PleaseAddAtLeastOneFolder": "الرجاء إضافة مجلد واحد على الأقل لهذه المكتبة بالضغط على زر \"إضافة\".",
|
||||||
"PleaseConfirmPluginInstallation": "الرجاء الضغط على زر موافق لتأكيد قرائتك لما ورد أعلاه وأنك ترغب في الاستمرار في تثبيت الملحق.",
|
"PleaseConfirmPluginInstallation": "الرجاء الضغط على زر موافق لتأكيد قرائتك لما ورد أعلاه وأنك ترغب في الاستمرار في تثبيت الملحق.",
|
||||||
@ -673,7 +673,7 @@
|
|||||||
"TabLogs": "الكشوفات",
|
"TabLogs": "الكشوفات",
|
||||||
"TabMusic": "الموسيقى",
|
"TabMusic": "الموسيقى",
|
||||||
"TabMyPlugins": "ملحقاتي",
|
"TabMyPlugins": "ملحقاتي",
|
||||||
"TabNetworks": "الشبكات",
|
"TabNetworks": "شبكات التلفزيون",
|
||||||
"TabNfoSettings": "أعدادات Nfo",
|
"TabNfoSettings": "أعدادات Nfo",
|
||||||
"TabNotifications": "إشعارات",
|
"TabNotifications": "إشعارات",
|
||||||
"TabOther": "أخرى",
|
"TabOther": "أخرى",
|
||||||
@ -729,7 +729,7 @@
|
|||||||
"XmlTvSportsCategoriesHelp": "البرامج من هذه التصنيفات ستعرض كبرامج رياضية. إفصل الإدخالات المتعددة برمز \"|\".",
|
"XmlTvSportsCategoriesHelp": "البرامج من هذه التصنيفات ستعرض كبرامج رياضية. إفصل الإدخالات المتعددة برمز \"|\".",
|
||||||
"Yesterday": "البارحة",
|
"Yesterday": "البارحة",
|
||||||
"ConfirmDeleteImage": "حذف الصورة؟",
|
"ConfirmDeleteImage": "حذف الصورة؟",
|
||||||
"ConfigureDateAdded": "قم بتكوين كيفية تحديد \"تاريخ الإضافة\" في لوحة التحكم ضمن اعدادات المكتبة",
|
"ConfigureDateAdded": "قم بإعداد كيفية تحديد البيانات الوصفية ل \"تاريخ الإضافة\" في لوحة المعلومات> المكتبات> إعدادات NFO",
|
||||||
"Composer": "ألحان",
|
"Composer": "ألحان",
|
||||||
"CommunityRating": "تقييم الجمهور",
|
"CommunityRating": "تقييم الجمهور",
|
||||||
"ColorTransfer": "نقل اللون",
|
"ColorTransfer": "نقل اللون",
|
||||||
@ -753,7 +753,7 @@
|
|||||||
"CancelSeries": "إلغاء المسلسل",
|
"CancelSeries": "إلغاء المسلسل",
|
||||||
"CancelRecording": "إلغاء التسجيل",
|
"CancelRecording": "إلغاء التسجيل",
|
||||||
"ButtonScanAllLibraries": "فحص جميع المكتبات",
|
"ButtonScanAllLibraries": "فحص جميع المكتبات",
|
||||||
"ButtonGotIt": "وجدتها",
|
"ButtonGotIt": "حسنا",
|
||||||
"ButtonAddImage": "أضف صورة",
|
"ButtonAddImage": "أضف صورة",
|
||||||
"BurnSubtitlesHelp": "يحدد ما إذا كان يجب على الخادم نسخ الترجمات المصاحبة عند تحويل ترميز مقاطع الفيديو. سيؤدي تجنب ذلك إلى تحسين الأداء بشكل كبير. حدد تلقائي لنسخ التنسيقات القائمة على الصور (VOBSUB ، و PGS ، و SUB ، و IDX ، ...) وبعض ترجمات ASS أو SSA.",
|
"BurnSubtitlesHelp": "يحدد ما إذا كان يجب على الخادم نسخ الترجمات المصاحبة عند تحويل ترميز مقاطع الفيديو. سيؤدي تجنب ذلك إلى تحسين الأداء بشكل كبير. حدد تلقائي لنسخ التنسيقات القائمة على الصور (VOBSUB ، و PGS ، و SUB ، و IDX ، ...) وبعض ترجمات ASS أو SSA.",
|
||||||
"BoxRear": "العلبة (الجهة الخلفية)",
|
"BoxRear": "العلبة (الجهة الخلفية)",
|
||||||
@ -766,7 +766,7 @@
|
|||||||
"Backdrop": "خلفية الصفحة",
|
"Backdrop": "خلفية الصفحة",
|
||||||
"Auto": "تلقائي",
|
"Auto": "تلقائي",
|
||||||
"AuthProviderHelp": "إختر مقدم المصادقة ليتم إستخدامه لمصادقة كلمة مرور هذا المستخدم.",
|
"AuthProviderHelp": "إختر مقدم المصادقة ليتم إستخدامه لمصادقة كلمة مرور هذا المستخدم.",
|
||||||
"AroundTime": "حول",
|
"AroundTime": "حوالي {0}",
|
||||||
"AspectRatio": "نسبة العرض الى الارتفاع",
|
"AspectRatio": "نسبة العرض الى الارتفاع",
|
||||||
"Ascending": "تصاعدي",
|
"Ascending": "تصاعدي",
|
||||||
"AsManyAsPossible": "أكبر عدد ممكن",
|
"AsManyAsPossible": "أكبر عدد ممكن",
|
||||||
@ -789,10 +789,10 @@
|
|||||||
"Aired": "عرضت",
|
"Aired": "عرضت",
|
||||||
"AirDate": "تاريخ العرض",
|
"AirDate": "تاريخ العرض",
|
||||||
"AddedOnValue": "تم إضافة {0}",
|
"AddedOnValue": "تم إضافة {0}",
|
||||||
"AddToPlaylist": "إضافة لقائمة التشغيل",
|
"AddToPlaylist": "أضف إلى قائمة التشغيل",
|
||||||
"AddToPlayQueue": "إضافة لقائمة التشغيل المؤقتة",
|
"AddToPlayQueue": "أضف إلى قائمة التشغيل المؤقتة",
|
||||||
"AddToCollection": "إضافة للتجميعات",
|
"AddToCollection": "أضف إلى التجميعات",
|
||||||
"Add": "إضافة",
|
"Add": "اضف",
|
||||||
"Actor": "ممثل",
|
"Actor": "ممثل",
|
||||||
"AccessRestrictedTryAgainLater": "الوصول مقيد حاليًا. الرجاء المحاولة لاحقا.",
|
"AccessRestrictedTryAgainLater": "الوصول مقيد حاليًا. الرجاء المحاولة لاحقا.",
|
||||||
"Absolute": "مطلق",
|
"Absolute": "مطلق",
|
||||||
@ -849,7 +849,7 @@
|
|||||||
"HeaderLiveTvTunerSetup": "اعداد موالف التلفاز المباشر",
|
"HeaderLiveTvTunerSetup": "اعداد موالف التلفاز المباشر",
|
||||||
"HeaderLibrarySettings": "اعدادات المكتبة",
|
"HeaderLibrarySettings": "اعدادات المكتبة",
|
||||||
"HeaderLibraryOrder": "ترتيب المكتبة",
|
"HeaderLibraryOrder": "ترتيب المكتبة",
|
||||||
"HeaderKodiMetadataHelp": "لتشغيل او إطفاء البيانات الوصفية بصيغة NFO، عدل احد المكتبات في اعدادات المكتبات واوجد قسم حافظات البيانات الوصفية.",
|
"HeaderKodiMetadataHelp": "لتمكين أو تعطيل البيانات الوصفية ل NFO ، قم بتحرير مكتبة وابحث عن قسم \"حافظات البيانات الوصفية\".",
|
||||||
"EnableNextVideoInfoOverlay": "عرض معلومات الفيديو القادم أثناء التشغيل",
|
"EnableNextVideoInfoOverlay": "عرض معلومات الفيديو القادم أثناء التشغيل",
|
||||||
"DatePlayed": "تاريخ التشغيل",
|
"DatePlayed": "تاريخ التشغيل",
|
||||||
"DateAdded": "تاريخ الإضافة",
|
"DateAdded": "تاريخ الإضافة",
|
||||||
@ -859,7 +859,7 @@
|
|||||||
"Artist": "الفنان",
|
"Artist": "الفنان",
|
||||||
"AllowFfmpegThrottling": "إبطاء التحويل",
|
"AllowFfmpegThrottling": "إبطاء التحويل",
|
||||||
"AlbumArtist": "المؤدي",
|
"AlbumArtist": "المؤدي",
|
||||||
"Album": "الألبوم",
|
"Album": "ألبوم",
|
||||||
"Disconnect": "قطع الاتصال",
|
"Disconnect": "قطع الاتصال",
|
||||||
"Disc": "القرص",
|
"Disc": "القرص",
|
||||||
"Directors": "المخرجون",
|
"Directors": "المخرجون",
|
||||||
@ -1004,7 +1004,7 @@
|
|||||||
"EnableDecodingColorDepth10Hevc": "تمكين ترميز ال 10 بت عبر العتاد الصلب من اجل HEVC",
|
"EnableDecodingColorDepth10Hevc": "تمكين ترميز ال 10 بت عبر العتاد الصلب من اجل HEVC",
|
||||||
"LabelFont": "خط:",
|
"LabelFont": "خط:",
|
||||||
"LabelFolder": "مجلد:",
|
"LabelFolder": "مجلد:",
|
||||||
"LabelIconMaxResHelp": "اعلى دقه للايقونات المعروضة من خلال خاصيه upnp:icon.",
|
"LabelIconMaxResHelp": "عرض الحد الأقصى من دقة الرموز عبر خاصية \"upnp: icon\".",
|
||||||
"LabelHomeScreenSectionValue": "الشاشة الرئيسية جزء {0}:",
|
"LabelHomeScreenSectionValue": "الشاشة الرئيسية جزء {0}:",
|
||||||
"LabelHomeNetworkQuality": "جودة الشبكة المنزلية:",
|
"LabelHomeNetworkQuality": "جودة الشبكة المنزلية:",
|
||||||
"LabelBaseUrlHelp": "اضافه مجلد فرعي مخصص لعنوان الخادم. كمثال <code>http://example.com/<b><baseurl></b></code>",
|
"LabelBaseUrlHelp": "اضافه مجلد فرعي مخصص لعنوان الخادم. كمثال <code>http://example.com/<b><baseurl></b></code>",
|
||||||
@ -1013,8 +1013,8 @@
|
|||||||
"LabelEnableHttps": "تفعيل HTTPS",
|
"LabelEnableHttps": "تفعيل HTTPS",
|
||||||
"LabelEnableHardwareDecodingFor": "تفعيل فك الترميز عن طريق العتاد الصلب ل:",
|
"LabelEnableHardwareDecodingFor": "تفعيل فك الترميز عن طريق العتاد الصلب ل:",
|
||||||
"LabelCurrentStatus": "الحاله الحالية:",
|
"LabelCurrentStatus": "الحاله الحالية:",
|
||||||
"LabelAlbumArtMaxResHelp": "اقصي مستوي دقة لغطاء الالبوم المكشوف من قبل خاصية upnp:albumArtURI .",
|
"LabelAlbumArtMaxResHelp": "الدقة القصوى لصورة الألبوم المعروضة عبر خاصية \"upnp: AlbumArtURI\".",
|
||||||
"KnownProxiesHelp": "قائمه من عناوين الشبكه المفصولين بفصله للوكلاء المعرفين المستخدمين للاتصال بحاله Jellyfin. هذا الامر مطلوب لاستخدام رؤس صفحات X-Forwarded-For صحيح. يتطلب اعادة التشغيل بعد الحفظ.",
|
"KnownProxiesHelp": "قائمة مفصولة بفواصل لعناوين IP أو أسماء المضيفين للخوادم الوكيلة المعروفة المستخدمة عند الاتصال بمثيل Jellyfin الخاص بك. هذا مطلوب للاستفادة المناسبة من رؤوس \"X-Forwarded-For\". يتطلب إعادة التشغيل بعد الحفظ.",
|
||||||
"Image": "صورة",
|
"Image": "صورة",
|
||||||
"Other": "اخري",
|
"Other": "اخري",
|
||||||
"EnableQuickConnect": "تفعيل الاتصال السريع على هذا الخادم",
|
"EnableQuickConnect": "تفعيل الاتصال السريع على هذا الخادم",
|
||||||
@ -1113,7 +1113,7 @@
|
|||||||
"PlayNext": "قم بتشغيل التالي",
|
"PlayNext": "قم بتشغيل التالي",
|
||||||
"PlayFromBeginning": "التشغيل من البداية",
|
"PlayFromBeginning": "التشغيل من البداية",
|
||||||
"PlayCount": "عدد التشغيل",
|
"PlayCount": "عدد التشغيل",
|
||||||
"PlaybackRate": "معدل التشغيل",
|
"PlaybackRate": "سرعة التشغيل",
|
||||||
"PlaybackErrorNoCompatibleStream": "هذا العميل غير متوافق مع الوسائط ولا يرسل الخادم تنسيق وسائط متوافق.",
|
"PlaybackErrorNoCompatibleStream": "هذا العميل غير متوافق مع الوسائط ولا يرسل الخادم تنسيق وسائط متوافق.",
|
||||||
"PlaybackData": "معلومات التشغيل",
|
"PlaybackData": "معلومات التشغيل",
|
||||||
"PlayAllFromHere": "قم بتشغيل كلها من هنا",
|
"PlayAllFromHere": "قم بتشغيل كلها من هنا",
|
||||||
@ -1330,7 +1330,7 @@
|
|||||||
"Video": "فيديو",
|
"Video": "فيديو",
|
||||||
"Vertical": "عمودي",
|
"Vertical": "عمودي",
|
||||||
"ValueSeconds": "{0} ثانية",
|
"ValueSeconds": "{0} ثانية",
|
||||||
"UseEpisodeImagesInNextUpHelp": "ستستخدم أقسام المتابعة والمتابعة صور الحلقة كصور مصغرة بدلاً من الصورة المصغرة الأساسية للعرض.",
|
"UseEpisodeImagesInNextUpHelp": "سيستخدم قسمي \"التالي\" و \"متابعة المشاهدة\" صور الحلقة كصور مصغرة بدلاً من الصورة المصغرة الأساسية للعرض.",
|
||||||
"UseEpisodeImagesInNextUp": "استخدم صور الحلقة في قسمي \"التالي\" و \"متابعة المشاهدة\"",
|
"UseEpisodeImagesInNextUp": "استخدم صور الحلقة في قسمي \"التالي\" و \"متابعة المشاهدة\"",
|
||||||
"Upload": "تحميل",
|
"Upload": "تحميل",
|
||||||
"UnsupportedPlayback": "لا يمكن ل Jellyfin فك تشفير المحتوى المحمي بواسطة DRM ولكن سيتم تجربة كل المحتوى بغض النظر ، بما في ذلك العناوين المحمية. قد تظهر بعض الملفات سوداء بالكامل بسبب التشفير أو ميزات أخرى غير مدعومة ، مثل العناوين التفاعلية.",
|
"UnsupportedPlayback": "لا يمكن ل Jellyfin فك تشفير المحتوى المحمي بواسطة DRM ولكن سيتم تجربة كل المحتوى بغض النظر ، بما في ذلك العناوين المحمية. قد تظهر بعض الملفات سوداء بالكامل بسبب التشفير أو ميزات أخرى غير مدعومة ، مثل العناوين التفاعلية.",
|
||||||
@ -1450,7 +1450,7 @@
|
|||||||
"LabelKodiMetadataUserHelp": "احفظ بيانات الساعة في ملفات NFO لتستخدمها التطبيقات الأخرى.",
|
"LabelKodiMetadataUserHelp": "احفظ بيانات الساعة في ملفات NFO لتستخدمها التطبيقات الأخرى.",
|
||||||
"LabelKodiMetadataUser": "حفظ بيانات مشاهدة المستخدم في ملفات NFO من أجل:",
|
"LabelKodiMetadataUser": "حفظ بيانات مشاهدة المستخدم في ملفات NFO من أجل:",
|
||||||
"LabelInternetQuality": "جودة الإنترنت:",
|
"LabelInternetQuality": "جودة الإنترنت:",
|
||||||
"LabelDisableCustomCss": "تعطيل سمات / علامات CSS المخصصة المقدمة من الخادم.",
|
"LabelDisableCustomCss": "تعطيل كود CSS المخصص للتسمية / العلامة التجارية المقدمة من الخادم.",
|
||||||
"LabelCreateHttpPortMapHelp": "السماح بتعيين المنفذ التلقائي لإنشاء قاعدة لحركة مرور HTTP بالإضافة إلى حركة مرور HTTPS.",
|
"LabelCreateHttpPortMapHelp": "السماح بتعيين المنفذ التلقائي لإنشاء قاعدة لحركة مرور HTTP بالإضافة إلى حركة مرور HTTPS.",
|
||||||
"LabelCreateHttpPortMap": "قم بتمكين تعيين المنفذ التلقائي لحركة مرور HTTP و HTTPS.",
|
"LabelCreateHttpPortMap": "قم بتمكين تعيين المنفذ التلقائي لحركة مرور HTTP و HTTPS.",
|
||||||
"LabelAutomaticDiscoveryHelp": "اسمح للتطبيقات باكتشاف Jellyfin تلقائيًا باستخدام منفذ UDP 7359.",
|
"LabelAutomaticDiscoveryHelp": "اسمح للتطبيقات باكتشاف Jellyfin تلقائيًا باستخدام منفذ UDP 7359.",
|
||||||
@ -1502,7 +1502,7 @@
|
|||||||
"MediaInfoColorPrimaries": "الألوان الأساسية",
|
"MediaInfoColorPrimaries": "الألوان الأساسية",
|
||||||
"LanNetworksHelp": "قائمة مفصولة بفواصل لعناوين IP أو إدخالات IP / قناع الشبكة للشبكات التي سيتم أخذها في الاعتبار على الشبكة المحلية عند فرض قيود النطاق الترددي. في حالة الضبط ، سيتم اعتبار جميع عناوين IP الأخرى على الشبكة الخارجية وستخضع لقيود النطاق الترددي الخارجي. إذا تُركت فارغة ، فسيتم اعتبار الشبكة الفرعية للخادم فقط على الشبكة المحلية.",
|
"LanNetworksHelp": "قائمة مفصولة بفواصل لعناوين IP أو إدخالات IP / قناع الشبكة للشبكات التي سيتم أخذها في الاعتبار على الشبكة المحلية عند فرض قيود النطاق الترددي. في حالة الضبط ، سيتم اعتبار جميع عناوين IP الأخرى على الشبكة الخارجية وستخضع لقيود النطاق الترددي الخارجي. إذا تُركت فارغة ، فسيتم اعتبار الشبكة الفرعية للخادم فقط على الشبكة المحلية.",
|
||||||
"LabelVersion": "إصدار:",
|
"LabelVersion": "إصدار:",
|
||||||
"LabelUserRemoteClientBitrateLimitHelp": "تجاوز القيمة العامة الافتراضية المحددة في إعدادات تشغيل الخادم.",
|
"LabelUserRemoteClientBitrateLimitHelp": "تجاوز القيمة العامة الافتراضية المعينة في إعدادات الخادم ، راجع لوحة الاعدادت> التشغيل> تدفق.",
|
||||||
"LabelTVHomeScreen": "الشاشة الرئيسية الخاصة بوضع التلفزيون:",
|
"LabelTVHomeScreen": "الشاشة الرئيسية الخاصة بوضع التلفزيون:",
|
||||||
"LabelTranscodingProgress": "تقدم التحويل:",
|
"LabelTranscodingProgress": "تقدم التحويل:",
|
||||||
"LabelTranscodes": "تحويل الشفرات:",
|
"LabelTranscodes": "تحويل الشفرات:",
|
||||||
@ -1564,11 +1564,11 @@
|
|||||||
"LabelAutomaticallyAddToCollection": "إضافة إلى المجموعة تلقائيا",
|
"LabelAutomaticallyAddToCollection": "إضافة إلى المجموعة تلقائيا",
|
||||||
"Console": "وحدة التحكم",
|
"Console": "وحدة التحكم",
|
||||||
"Casual": "غير رسمي",
|
"Casual": "غير رسمي",
|
||||||
"AllowTonemappingHelp": "يمكن أن يؤدي تعيين النغمة إلى تحويل النطاق الديناميكي لمقطع فيديو من HDR إلى SDR مع الحفاظ على تفاصيل الصورة والألوان ، وهي معلومات مهمة جدًا لتمثيل المشهد الأصلي. يعمل حاليًا فقط عند تحويل ترميز مقاطع الفيديو باستخدام بيانات التعريف HDR10 أو HLG المضمنة. إذا لم يكن التشغيل سلسًا أو فشل ، فيرجى التفكير في إيقاف تشغيل وحدة فك ترميز الأجهزة المقابلة.",
|
"AllowTonemappingHelp": "يمكن أن يؤدي تعيين النغمة إلى تحويل النطاق الديناميكي لمقطع فيديو من HDR إلى SDR مع الحفاظ على تفاصيل الصورة والألوان ، وهي معلومات مهمة جدًا لتمثيل المشهد الأصلي. يعمل حاليًا فقط مع مقاطع فيديو HDR10 أو HLG. يتطلب هذا وقت تشغيل OpenCL أو CUDA المقابل.",
|
||||||
"RefFramesNotSupported": "الإطارات المرجعية غير مدعومة",
|
"RefFramesNotSupported": "الإطارات المرجعية غير مدعومة",
|
||||||
"InterlacedVideoNotSupported": "الفيديو المتشابك غير مدعوم",
|
"InterlacedVideoNotSupported": "الفيديو المتشابك غير مدعوم",
|
||||||
"AnamorphicVideoNotSupported": "لا يتم دعم الفيديو ذي الصورة المشوهة",
|
"AnamorphicVideoNotSupported": "لا يتم دعم الفيديو ذي الصورة المشوهة",
|
||||||
"AllowVppTonemappingHelp": "تعين النغمة الكامل للأجهزة دون استخدام مرشح OpenCL. يعمل حاليًا فقط عند تحويل ترميز مقاطع الفيديو باستخدام بيانات تعريف HDR10 المضمنة.",
|
"AllowVppTonemappingHelp": "تعين النغمة الكامل للأجهزة باستخدام Intel. يعمل حاليًا فقط على أجهزة معينة مع مقاطع فيديو HDR10. هذا له أولوية أعلى مقارنة بتطبيق OpenCL آخر.",
|
||||||
"EnableVppTonemapping": "تفعيل تعيين نغمة VPP",
|
"EnableVppTonemapping": "تفعيل تعيين نغمة VPP",
|
||||||
"Remuxing": "إعادة",
|
"Remuxing": "إعادة",
|
||||||
"AspectRatioCover": "غلاف",
|
"AspectRatioCover": "غلاف",
|
||||||
@ -1584,7 +1584,7 @@
|
|||||||
"TypeOptionPluralBoxSet": "مجموعات مربعه",
|
"TypeOptionPluralBoxSet": "مجموعات مربعه",
|
||||||
"TypeOptionPluralBook": "كتب",
|
"TypeOptionPluralBook": "كتب",
|
||||||
"TypeOptionPluralAudio": "صوتيات",
|
"TypeOptionPluralAudio": "صوتيات",
|
||||||
"TonemappingAlgorithmHelp": "يمكن ضبط النغمة بدقة. إذا لم تكن معتادًا على هذه الخيارات ، فما عليك سوى الاحتفاظ بالخيار الافتراضي. القيمة الموصى بها هي Hable.",
|
"TonemappingAlgorithmHelp": "يمكن ضبط النغمة بدقة. إذا لم تكن معتادًا على هذه الخيارات ، فما عليك سوى الاحتفاظ بالخيار الافتراضي. القيمة الموصى بها هي \"BT.2390\".",
|
||||||
"ThumbCard": "بطاقة مصغرة",
|
"ThumbCard": "بطاقة مصغرة",
|
||||||
"Thumb": "ابهام",
|
"Thumb": "ابهام",
|
||||||
"Smart": "ذكي",
|
"Smart": "ذكي",
|
||||||
@ -1598,5 +1598,27 @@
|
|||||||
"LabelTonemappingRange": "نطاق تعيين النغمة:",
|
"LabelTonemappingRange": "نطاق تعيين النغمة:",
|
||||||
"LabelTonemappingPeakHelp": "تجاوز الإشارة / الذروة الاسمية / المرجعية بهذه القيمة. يكون مفيدًا عندما تكون معلومات الذروة المضمنة في البيانات الوصفية للعرض غير موثوقة أو عند تعيين درجة اللون من نطاق أقل إلى نطاق أعلى. القيم الموصي بها والافتراضية هي 100 و 0.",
|
"LabelTonemappingPeakHelp": "تجاوز الإشارة / الذروة الاسمية / المرجعية بهذه القيمة. يكون مفيدًا عندما تكون معلومات الذروة المضمنة في البيانات الوصفية للعرض غير موثوقة أو عند تعيين درجة اللون من نطاق أقل إلى نطاق أعلى. القيم الموصي بها والافتراضية هي 100 و 0.",
|
||||||
"LabelTonemappingPeak": "ذروة رسم الخرائط:",
|
"LabelTonemappingPeak": "ذروة رسم الخرائط:",
|
||||||
"Cursive": "متّصل"
|
"Cursive": "متّصل",
|
||||||
|
"LabelHardwareEncodingOptions": "خيارات ترميز الأجهزة:",
|
||||||
|
"IntelLowPowerEncHelp": "يمكن أن يحافظ التشفير منخفض الطاقة على مزامنة وحدة المعالجة المركزية (CPU) ووحدة معالجة الرسومات (GPU) غير الضرورية. في Linux ، يجب تعطيلها إذا لم يتم تكوين البرنامج الثابت i915 HuC.",
|
||||||
|
"EnableIntelLowPowerHevcHwEncoder": "قم بتمكين ترميز أجهزة Intel Low-Power HEVC",
|
||||||
|
"EnableIntelLowPowerH264HwEncoder": "قم بتمكين برنامج تشفير الأجهزة Intel Low-Power H.264",
|
||||||
|
"PreferSystemNativeHwDecoder": "تفضل وحدات فك ترميز أجهزة DXVA أو VA-API الأصلية لنظام التشغيل",
|
||||||
|
"ContainerBitrateExceedsLimit": "معدل بت الفيديو تجاوز الحد",
|
||||||
|
"SelectAll": "اختر الكل",
|
||||||
|
"DirectPlayError": "حدث خطأ في بدء التشغيل المباشر",
|
||||||
|
"UnknownAudioStreamInfo": "معلومات دفق الصوت غير معروفة",
|
||||||
|
"UnknownVideoStreamInfo": "معلومات دفق الفيديو غير معروفة",
|
||||||
|
"VideoBitrateNotSupported": "معدل بت الفيديو غير مدعوم",
|
||||||
|
"AudioIsExternal": "دفق الصوت خارجي",
|
||||||
|
"ThemeVideo": "فيديو الشارة",
|
||||||
|
"ThemeSong": "أغنية الشارة",
|
||||||
|
"Sample": "عيّنة",
|
||||||
|
"Scene": "المشاهد",
|
||||||
|
"Interview": "مقابلة",
|
||||||
|
"DeletedScene": "مشاهد محذوفة",
|
||||||
|
"BehindTheScenes": "وراء الكواليس",
|
||||||
|
"Trailer": "العرض الإعلاني",
|
||||||
|
"Clip": "ميزة",
|
||||||
|
"ButtonExitApp": "أغلق التطبيق"
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,8 @@
|
|||||||
"Add": "Дадаць",
|
"Add": "Дадаць",
|
||||||
"Actor": "Акцёр",
|
"Actor": "Акцёр",
|
||||||
"AccessRestrictedTryAgainLater": "У цяперашні час доступ абмежаваны. Калі ласка паспрабуйце зноў пазней.",
|
"AccessRestrictedTryAgainLater": "У цяперашні час доступ абмежаваны. Калі ласка паспрабуйце зноў пазней.",
|
||||||
"Absolute": "Абсалютны"
|
"Absolute": "Абсалютны",
|
||||||
|
"Small": "Маленькі",
|
||||||
|
"Normal": "Нармальны",
|
||||||
|
"Large": "Вялікі"
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@
|
|||||||
"HeaderEditImages": "Редактиране на изображенията",
|
"HeaderEditImages": "Редактиране на изображенията",
|
||||||
"HeaderEnabledFields": "Включени полета",
|
"HeaderEnabledFields": "Включени полета",
|
||||||
"HeaderError": "Грешка",
|
"HeaderError": "Грешка",
|
||||||
"HeaderFeatureAccess": "Достъп до функции",
|
"HeaderFeatureAccess": "Достъп до функции:",
|
||||||
"HeaderFetchImages": "Свали изображения:",
|
"HeaderFetchImages": "Свали изображения:",
|
||||||
"HeaderForKids": "Детски",
|
"HeaderForKids": "Детски",
|
||||||
"HeaderFrequentlyPlayed": "Често пускани",
|
"HeaderFrequentlyPlayed": "Често пускани",
|
||||||
@ -176,7 +176,7 @@
|
|||||||
"HeaderProfileInformation": "Профил",
|
"HeaderProfileInformation": "Профил",
|
||||||
"HeaderProfileServerSettingsHelp": "Тези величини определят как Джелифин сървърът ще се представя на устройствата.",
|
"HeaderProfileServerSettingsHelp": "Тези величини определят как Джелифин сървърът ще се представя на устройствата.",
|
||||||
"HeaderRecentlyPlayed": "Скоро пускани",
|
"HeaderRecentlyPlayed": "Скоро пускани",
|
||||||
"HeaderRemoteControl": "Отдалечен контрол",
|
"HeaderRemoteControl": "Отдалечен контрол:",
|
||||||
"HeaderRemoveMediaFolder": "Премахване на медийна папка",
|
"HeaderRemoveMediaFolder": "Премахване на медийна папка",
|
||||||
"HeaderResponseProfile": "Профил на отговора",
|
"HeaderResponseProfile": "Профил на отговора",
|
||||||
"HeaderRevisionHistory": "Списък с промени",
|
"HeaderRevisionHistory": "Списък с промени",
|
||||||
@ -797,7 +797,7 @@
|
|||||||
"HeaderAppearsOn": "Фигурира в",
|
"HeaderAppearsOn": "Фигурира в",
|
||||||
"ApiKeysCaption": "Списък с работещите в момента API ключове",
|
"ApiKeysCaption": "Списък с работещите в момента API ключове",
|
||||||
"HeaderApiKeysHelp": "Външните програми се налага да имат API ключ ,за да комуникират правилно със сървъра.Такива се издават при вписването в сървъра или чрез ръчно предоставяне.",
|
"HeaderApiKeysHelp": "Външните програми се налага да имат API ключ ,за да комуникират правилно със сървъра.Такива се издават при вписването в сървъра или чрез ръчно предоставяне.",
|
||||||
"HeaderAllowMediaDeletionFrom": "Позволи изтриването на медия от",
|
"HeaderAllowMediaDeletionFrom": "Позволи изтриването на медия от:",
|
||||||
"HeaderAlert": "Предупреждение",
|
"HeaderAlert": "Предупреждение",
|
||||||
"HeaderAccessScheduleHelp": "Създай разписание за достъп ,за да го ограничиш до определени часове.",
|
"HeaderAccessScheduleHelp": "Създай разписание за достъп ,за да го ограничиш до определени часове.",
|
||||||
"HeaderAccessSchedule": "Разписание за достъп",
|
"HeaderAccessSchedule": "Разписание за достъп",
|
||||||
@ -820,7 +820,7 @@
|
|||||||
"HeaderXmlDocumentAttribute": "Атрибут на XML документа",
|
"HeaderXmlDocumentAttribute": "Атрибут на XML документа",
|
||||||
"HeaderUpcomingOnTV": "Скоро по ТВ",
|
"HeaderUpcomingOnTV": "Скоро по ТВ",
|
||||||
"HeaderTypeText": "Въведи текст",
|
"HeaderTypeText": "Въведи текст",
|
||||||
"HeaderTypeImageFetchers": "Извличане на картини ({0})",
|
"HeaderTypeImageFetchers": "Извличане на картини ({0}):",
|
||||||
"HeaderTuners": "Тунери",
|
"HeaderTuners": "Тунери",
|
||||||
"HeaderTranscodingProfileHelp": "Добави профили за транскодиране ,за да се види кои формати ще се използват ,когато е необходимо транскодиране.",
|
"HeaderTranscodingProfileHelp": "Добави профили за транскодиране ,за да се види кои формати ще се използват ,когато е необходимо транскодиране.",
|
||||||
"HeaderThisUserIsCurrentlyDisabled": "Този потребител в момента е блокиран",
|
"HeaderThisUserIsCurrentlyDisabled": "Този потребител в момента е блокиран",
|
||||||
@ -849,7 +849,7 @@
|
|||||||
"HeaderRecordingOptions": "Настройки за запис",
|
"HeaderRecordingOptions": "Настройки за запис",
|
||||||
"HeaderPluginInstallation": "Инсталиране на добавка",
|
"HeaderPluginInstallation": "Инсталиране на добавка",
|
||||||
"HeaderPlaybackError": "Грешка при възпроизвеждане",
|
"HeaderPlaybackError": "Грешка при възпроизвеждане",
|
||||||
"HeaderPlayback": "Възпроизвеждане на медия",
|
"HeaderPlayback": "Възпроизвеждане на медия:",
|
||||||
"HeaderPinCodeReset": "Зануляване на пин код",
|
"HeaderPinCodeReset": "Зануляване на пин код",
|
||||||
"HeaderPhotoAlbums": "Фото албум",
|
"HeaderPhotoAlbums": "Фото албум",
|
||||||
"HeaderPasswordReset": "Зануляване на парола",
|
"HeaderPasswordReset": "Зануляване на парола",
|
||||||
@ -1259,7 +1259,7 @@
|
|||||||
"OptionRegex": "Регуларен",
|
"OptionRegex": "Регуларен",
|
||||||
"OptionRandom": "Случаен",
|
"OptionRandom": "Случаен",
|
||||||
"OptionProtocolHttp": "HTTP",
|
"OptionProtocolHttp": "HTTP",
|
||||||
"OptionProtocolHls": "Директно предаване по HTTP",
|
"OptionProtocolHls": "Директно предаване по HTTP (HLS)",
|
||||||
"OptionPlainVideoItemsHelp": "Всички видеофайлове са представени в DIDL като \"object.item.videoItem\" вместо по-конкретен тип, като например \"object.item.videoItem.movie\".",
|
"OptionPlainVideoItemsHelp": "Всички видеофайлове са представени в DIDL като \"object.item.videoItem\" вместо по-конкретен тип, като например \"object.item.videoItem.movie\".",
|
||||||
"OptionPlainStorageFoldersHelp": "Всички папки са представени в DIDL като \"object.container.storageFolder\" вместо по-конкретен тип, като например \"object.container.person.musicArtist\".",
|
"OptionPlainStorageFoldersHelp": "Всички папки са представени в DIDL като \"object.container.storageFolder\" вместо по-конкретен тип, като например \"object.container.person.musicArtist\".",
|
||||||
"OptionMax": "Максимално",
|
"OptionMax": "Максимално",
|
||||||
|
@ -130,5 +130,6 @@
|
|||||||
"Depressed": "অবনমিত",
|
"Depressed": "অবনমিত",
|
||||||
"DeleteUserConfirmation": "আপনি কি নিশ্চিত যে আপনি এই ব্যবহারকারীকে মুছতে চান?",
|
"DeleteUserConfirmation": "আপনি কি নিশ্চিত যে আপনি এই ব্যবহারকারীকে মুছতে চান?",
|
||||||
"DeleteUser": "ব্যবহারকারী মুছুন",
|
"DeleteUser": "ব্যবহারকারী মুছুন",
|
||||||
"DeleteMedia": "মিডিয়া মুছুন"
|
"DeleteMedia": "মিডিয়া মুছুন",
|
||||||
|
"AllowedRemoteAddressesHelp": "নেটওয়ার্কের জন্য যেই আইপি এড্রেসগুলো অথবা আইপি/নেটমাস্ক গুলো কমা (,) দিয়ে আলাদা করা শুধু সেই এন্ট্রিগুলো রিমোটলি কানেক্ট হতে পারবে। যদি খালি রাখা হয় তাহলে সব আইপি থেকে কানেক্ট হতে পারবে।"
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
"HeaderEditImages": "Edita Imatges",
|
"HeaderEditImages": "Edita Imatges",
|
||||||
"HeaderEnabledFields": "Camps Habilitats",
|
"HeaderEnabledFields": "Camps Habilitats",
|
||||||
"HeaderExternalIds": "Identificadors externs:",
|
"HeaderExternalIds": "Identificadors externs:",
|
||||||
"HeaderFeatureAccess": "Accés a Funcions",
|
"HeaderFeatureAccess": "Accés a Funcions:",
|
||||||
"HeaderFetchImages": "Obtingues Imatges:",
|
"HeaderFetchImages": "Obtingues Imatges:",
|
||||||
"HeaderFrequentlyPlayed": "Reproduït Freqüentment",
|
"HeaderFrequentlyPlayed": "Reproduït Freqüentment",
|
||||||
"HeaderHttpHeaders": "Capçaleres HTTP",
|
"HeaderHttpHeaders": "Capçaleres HTTP",
|
||||||
@ -168,7 +168,7 @@
|
|||||||
"HeaderProfileServerSettingsHelp": "Aquests valors controlen com el servidor es presentarà als clients.",
|
"HeaderProfileServerSettingsHelp": "Aquests valors controlen com el servidor es presentarà als clients.",
|
||||||
"HeaderRecentlyPlayed": "Reproduït Recentment",
|
"HeaderRecentlyPlayed": "Reproduït Recentment",
|
||||||
"HeaderRecordingOptions": "Opcions d'Enregistrament",
|
"HeaderRecordingOptions": "Opcions d'Enregistrament",
|
||||||
"HeaderRemoteControl": "Control Remot",
|
"HeaderRemoteControl": "Control Remot:",
|
||||||
"HeaderRunningTasks": "Tasques Corrent",
|
"HeaderRunningTasks": "Tasques Corrent",
|
||||||
"HeaderScenes": "Escenes",
|
"HeaderScenes": "Escenes",
|
||||||
"HeaderSeasons": "Temporades",
|
"HeaderSeasons": "Temporades",
|
||||||
@ -592,7 +592,7 @@
|
|||||||
"Channels": "Canals",
|
"Channels": "Canals",
|
||||||
"Collections": "Col·leccions",
|
"Collections": "Col·leccions",
|
||||||
"Favorites": "Preferits",
|
"Favorites": "Preferits",
|
||||||
"HeaderAlbumArtists": "Àlbum de l'artista",
|
"HeaderAlbumArtists": "Artistes de l'àlbum",
|
||||||
"ChannelNumber": "Número de canal",
|
"ChannelNumber": "Número de canal",
|
||||||
"Categories": "Categories",
|
"Categories": "Categories",
|
||||||
"ButtonWebsite": "Lloc web",
|
"ButtonWebsite": "Lloc web",
|
||||||
@ -793,7 +793,7 @@
|
|||||||
"HeaderAudioBooks": "Llibres d’àudio",
|
"HeaderAudioBooks": "Llibres d’àudio",
|
||||||
"HeaderAppearsOn": "Apareix a",
|
"HeaderAppearsOn": "Apareix a",
|
||||||
"HeaderApp": "App",
|
"HeaderApp": "App",
|
||||||
"HeaderAllowMediaDeletionFrom": "Permetre la supressió de mitjans des de",
|
"HeaderAllowMediaDeletionFrom": "Permetre la supressió de mitjans des de:",
|
||||||
"HeaderAlert": "Alerta",
|
"HeaderAlert": "Alerta",
|
||||||
"HeaderAddUser": "Afegir usuari",
|
"HeaderAddUser": "Afegir usuari",
|
||||||
"HeaderAddUpdateSubtitle": "Afegir/ Actualitzar subtítols",
|
"HeaderAddUpdateSubtitle": "Afegir/ Actualitzar subtítols",
|
||||||
@ -1318,7 +1318,7 @@
|
|||||||
"LabelCertificatePassword": "Verificar contrasenya:",
|
"LabelCertificatePassword": "Verificar contrasenya:",
|
||||||
"LabelBurnSubtitles": "Gravar subtítols:",
|
"LabelBurnSubtitles": "Gravar subtítols:",
|
||||||
"LabelBlockContentWithTags": "Bloquejar elements amb etiquetes:",
|
"LabelBlockContentWithTags": "Bloquejar elements amb etiquetes:",
|
||||||
"LabelBlastMessageIntervalHelp": "Determina la durada en segons entre forns missatges vius.",
|
"LabelBlastMessageIntervalHelp": "Determina la durada en segons entre ràfegues de missatges en directe.",
|
||||||
"LabelBlastMessageInterval": "Interval entre missatges de vida:",
|
"LabelBlastMessageInterval": "Interval entre missatges de vida:",
|
||||||
"LabelBitrate": "Tassa de bits:",
|
"LabelBitrate": "Tassa de bits:",
|
||||||
"LabelBindToLocalNetworkAddressHelp": "Anul·lar l'adreça IP local per al servidor HTTP. Si és buit, el servidor s'unirà a totes les adreces disponibles. El canvi d'aquest valor, es requereix un reinici.",
|
"LabelBindToLocalNetworkAddressHelp": "Anul·lar l'adreça IP local per al servidor HTTP. Si és buit, el servidor s'unirà a totes les adreces disponibles. El canvi d'aquest valor, es requereix un reinici.",
|
||||||
@ -1362,7 +1362,7 @@
|
|||||||
"HeaderVideos": "vídeos",
|
"HeaderVideos": "vídeos",
|
||||||
"HeaderVideoQuality": "Qualitat de vídeo",
|
"HeaderVideoQuality": "Qualitat de vídeo",
|
||||||
"HeaderUploadSubtitle": "Pujar subtítols",
|
"HeaderUploadSubtitle": "Pujar subtítols",
|
||||||
"HeaderTypeImageFetchers": "Buscadors d'imatge ({0})",
|
"HeaderTypeImageFetchers": "Buscadors d'imatge ({0}):",
|
||||||
"HeaderTuners": "Sintonitzadors",
|
"HeaderTuners": "Sintonitzadors",
|
||||||
"HeaderTunerDevices": "Dispositius sintonitzadors",
|
"HeaderTunerDevices": "Dispositius sintonitzadors",
|
||||||
"HeaderTranscodingProfileHelp": "Afegir la transcodificació de perfils per indicar quins formats han de ser utilitzats quan es requereix la transcodificació.",
|
"HeaderTranscodingProfileHelp": "Afegir la transcodificació de perfils per indicar quins formats han de ser utilitzats quan es requereix la transcodificació.",
|
||||||
@ -1513,7 +1513,7 @@
|
|||||||
"HeaderPortRanges": "Firewall i configuració del Proxy",
|
"HeaderPortRanges": "Firewall i configuració del Proxy",
|
||||||
"HeaderPluginInstallation": "Instal·lació plug-in",
|
"HeaderPluginInstallation": "Instal·lació plug-in",
|
||||||
"HeaderPlayOn": "Reproduir a",
|
"HeaderPlayOn": "Reproduir a",
|
||||||
"HeaderPlayback": "Suport de reproducció",
|
"HeaderPlayback": "Suport de reproducció:",
|
||||||
"HeaderPinCodeReset": "Restablir PIN Code",
|
"HeaderPinCodeReset": "Restablir PIN Code",
|
||||||
"HeaderPhotoAlbums": "Àlbum de fotos",
|
"HeaderPhotoAlbums": "Àlbum de fotos",
|
||||||
"HeaderOtherItems": "Altres elements",
|
"HeaderOtherItems": "Altres elements",
|
||||||
@ -1548,5 +1548,37 @@
|
|||||||
"LabelSyncPlaySettingsDescription": "Canvia les preferències de SyncPlay",
|
"LabelSyncPlaySettingsDescription": "Canvia les preferències de SyncPlay",
|
||||||
"LabelMaxDaysForNextUp": "Màxims dies per \"A continuació\":",
|
"LabelMaxDaysForNextUp": "Màxims dies per \"A continuació\":",
|
||||||
"LabelHardwareEncoding": "Codificació per Hardware:",
|
"LabelHardwareEncoding": "Codificació per Hardware:",
|
||||||
"ErrorPlayerNotFound": "No s'ha trobat cap reproductor per al fitxer multimèdia sol·licitat."
|
"ErrorPlayerNotFound": "No s'ha trobat cap reproductor per al fitxer multimèdia sol·licitat.",
|
||||||
|
"SelectAll": "Selecciona-ho tot",
|
||||||
|
"DirectPlayError": "Hi ha hagut un error en iniciar la reproducció en directe",
|
||||||
|
"UnknownAudioStreamInfo": "La informació del flux d'àudio és desconeguda",
|
||||||
|
"UnknownVideoStreamInfo": "La informació del flux de vídeo és desconeguda",
|
||||||
|
"VideoBitrateNotSupported": "La velocitat de transmissió del vídeo no està suportada",
|
||||||
|
"LabelHardwareEncodingOptions": "Opcions de codificació per maquinari:",
|
||||||
|
"EnableIntelLowPowerHevcHwEncoder": "Habilita el còdec per maquinari Intel Low-Power HEVC",
|
||||||
|
"EnableIntelLowPowerH264HwEncoder": "Habilita el còdec per maquinari Intel Low-Power H.264",
|
||||||
|
"ContainerBitrateExceedsLimit": "El còdec de vídeo excedeix el límit",
|
||||||
|
"TypeOptionPluralVideo": "Vídeos",
|
||||||
|
"TypeOptionPluralSeason": "Temporades",
|
||||||
|
"TypeOptionPluralMusicVideo": "Vídeos Musicals",
|
||||||
|
"TypeOptionPluralMusicArtist": "Artistes musicals",
|
||||||
|
"TypeOptionPluralMusicAlbum": "Àlbums de música",
|
||||||
|
"TypeOptionPluralMovie": "Pel·lícules",
|
||||||
|
"TypeOptionPluralEpisode": "Episodis",
|
||||||
|
"TypeOptionPluralBook": "Llibres",
|
||||||
|
"Track": "Pista",
|
||||||
|
"Print": "Imprimeix",
|
||||||
|
"PreviousChapter": "Episodi anterior",
|
||||||
|
"PlaybackErrorPlaceHolder": "Això és un text variable per a mitjans físics que Jellyfin no pot reproduir. Si us plau insereix el disc per a reproduir.",
|
||||||
|
"OtherArtist": "Altres artistes",
|
||||||
|
"NextChapter": "Pròxim episodi",
|
||||||
|
"Mixer": "Mesclador",
|
||||||
|
"MediaInfoTitle": "Títol",
|
||||||
|
"LabelSyncPlaySettingsSyncCorrection": "Correcció de sincronisme",
|
||||||
|
"LabelSyncPlaySettingsExtraTimeOffset": "Temps afegit de decalatge:",
|
||||||
|
"LabelSortName": "Ordenar per nom:",
|
||||||
|
"LabelAutomaticallyAddToCollectionHelp": "Quan, com a mínim dues pel·lícules, tenen el mateix nom de col·lecció, seran afegides a la col·lecció.",
|
||||||
|
"LabelAutomaticallyAddToCollection": "Afegir a la col·lecció automàticament",
|
||||||
|
"Cursive": "Cursiva",
|
||||||
|
"Console": "Terminal"
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
"CancelRecording": "Zrušit nahrávání",
|
"CancelRecording": "Zrušit nahrávání",
|
||||||
"CancelSeries": "Ukončit Seriál",
|
"CancelSeries": "Ukončit Seriál",
|
||||||
"Categories": "Kategorie",
|
"Categories": "Kategorie",
|
||||||
"ChannelAccessHelp": "Vyberte kanály, které chcete sdílet s tímto uživatelem. Administrátoři budou moci upravovat všechny kanály pomocí správce metadat.",
|
"ChannelAccessHelp": "Vyberte kanály, které chcete sdílet s tímto uživatelem. Administrátoři budou moci upravovat všechny kanály pomocí Správce metadat.",
|
||||||
"ChannelNameOnly": "Kanál {0} jen",
|
"ChannelNameOnly": "Kanál {0} jen",
|
||||||
"ChannelNumber": "Číslo kanálu",
|
"ChannelNumber": "Číslo kanálu",
|
||||||
"CinemaModeConfigurationHelp": "Tento režim přibližuje domácí sledování filmů zážitku v kině díky možnosti přehrát upoutávky k filmům a vlastní úvodní video před hlavním pořadem.",
|
"CinemaModeConfigurationHelp": "Tento režim přibližuje domácí sledování filmů zážitku v kině díky možnosti přehrát upoutávky k filmům a vlastní úvodní video před hlavním pořadem.",
|
||||||
@ -227,7 +227,7 @@
|
|||||||
"HeaderEnabledFields": "Povolené pole",
|
"HeaderEnabledFields": "Povolené pole",
|
||||||
"HeaderEnabledFieldsHelp": "Zrušte zaškrtnutí, abyste zabránili změnám dat.",
|
"HeaderEnabledFieldsHelp": "Zrušte zaškrtnutí, abyste zabránili změnám dat.",
|
||||||
"HeaderError": "Chyba",
|
"HeaderError": "Chyba",
|
||||||
"HeaderFeatureAccess": "Přístup k funkcím",
|
"HeaderFeatureAccess": "Přístup k funkcím:",
|
||||||
"HeaderFetchImages": "Načíst obrázky:",
|
"HeaderFetchImages": "Načíst obrázky:",
|
||||||
"HeaderForKids": "Pro děti",
|
"HeaderForKids": "Pro děti",
|
||||||
"HeaderFrequentlyPlayed": "Nejčastěji přehráváno",
|
"HeaderFrequentlyPlayed": "Nejčastěji přehráváno",
|
||||||
@ -270,9 +270,9 @@
|
|||||||
"HeaderPassword": "Heslo",
|
"HeaderPassword": "Heslo",
|
||||||
"HeaderPasswordReset": "Obnova hesla",
|
"HeaderPasswordReset": "Obnova hesla",
|
||||||
"HeaderPaths": "Cesty",
|
"HeaderPaths": "Cesty",
|
||||||
"HeaderPinCodeReset": "Obnovit PIN kód",
|
"HeaderPinCodeReset": "Obnovit Easy PIN kód",
|
||||||
"HeaderPlayAll": "Přehrát vše",
|
"HeaderPlayAll": "Přehrát vše",
|
||||||
"HeaderPlayback": "Přehrání média",
|
"HeaderPlayback": "Přehrávání médií:",
|
||||||
"HeaderPlaybackError": "Chyba přehrávání",
|
"HeaderPlaybackError": "Chyba přehrávání",
|
||||||
"HeaderPleaseSignIn": "Prosíme, přihlaste se",
|
"HeaderPleaseSignIn": "Prosíme, přihlaste se",
|
||||||
"HeaderPluginInstallation": "Instalace zásuvných modulů",
|
"HeaderPluginInstallation": "Instalace zásuvných modulů",
|
||||||
@ -282,7 +282,7 @@
|
|||||||
"HeaderRecentlyPlayed": "Naposledy přehráváno",
|
"HeaderRecentlyPlayed": "Naposledy přehráváno",
|
||||||
"HeaderRecordingOptions": "Nastavení nahrávání",
|
"HeaderRecordingOptions": "Nastavení nahrávání",
|
||||||
"HeaderRecordingPostProcessing": "Následné zpracování nahrávek",
|
"HeaderRecordingPostProcessing": "Následné zpracování nahrávek",
|
||||||
"HeaderRemoteControl": "Dálkový ovladač",
|
"HeaderRemoteControl": "Dálkový ovladač:",
|
||||||
"HeaderRemoveMediaFolder": "Odebrat složku médií",
|
"HeaderRemoveMediaFolder": "Odebrat složku médií",
|
||||||
"HeaderRemoveMediaLocation": "Odebrat umístění media",
|
"HeaderRemoveMediaLocation": "Odebrat umístění media",
|
||||||
"HeaderResponseProfile": "Profil pro odezvy",
|
"HeaderResponseProfile": "Profil pro odezvy",
|
||||||
@ -355,7 +355,7 @@
|
|||||||
"LabelAirsAfterSeason": "Vysíláno po sezóně:",
|
"LabelAirsAfterSeason": "Vysíláno po sezóně:",
|
||||||
"LabelAirsBeforeEpisode": "Vysíláno před epizodou:",
|
"LabelAirsBeforeEpisode": "Vysíláno před epizodou:",
|
||||||
"LabelAirsBeforeSeason": "Vysíláno před sezónou:",
|
"LabelAirsBeforeSeason": "Vysíláno před sezónou:",
|
||||||
"LabelAlbumArtHelp": "PN používá obrázek alba v rámci technologie dlna:profileID atributu upnp:albumArtURI. Někteří klienti vyžadují konkrétní hodnoty, bez ohledu na velikost obrázku.",
|
"LabelAlbumArtHelp": "PN používá obrázek alba v rámci technologie 'dlna:profileID' atributu 'upnp:albumArtURI'. Někteří klienti vyžadují konkrétní hodnoty, bez ohledu na velikost obrázku.",
|
||||||
"LabelAlbumArtMaxHeight": "Maximální výška alba:",
|
"LabelAlbumArtMaxHeight": "Maximální výška alba:",
|
||||||
"LabelAlbumArtMaxWidth": "Maximální výška alba:",
|
"LabelAlbumArtMaxWidth": "Maximální výška alba:",
|
||||||
"LabelAlbumArtPN": "Alba PN:",
|
"LabelAlbumArtPN": "Alba PN:",
|
||||||
@ -383,7 +383,7 @@
|
|||||||
"LabelCriticRating": "Hodnocení kritiků:",
|
"LabelCriticRating": "Hodnocení kritiků:",
|
||||||
"LabelCurrentPassword": "Aktuální heslo:",
|
"LabelCurrentPassword": "Aktuální heslo:",
|
||||||
"LabelCustomCss": "Vlastní CSS:",
|
"LabelCustomCss": "Vlastní CSS:",
|
||||||
"LabelCustomCssHelp": "Aplikovat vaše vlastní styly webového rozhraní.",
|
"LabelCustomCssHelp": "Aplikovat vaše vlastní styly webového rozhraní pro změnu vzhledu či brandingu.",
|
||||||
"LabelCustomDeviceDisplayNameHelp": "Nahradit vlastním názvem zobrazení nebo ponechte prázdné, aby název byl určen zařízením.",
|
"LabelCustomDeviceDisplayNameHelp": "Nahradit vlastním názvem zobrazení nebo ponechte prázdné, aby název byl určen zařízením.",
|
||||||
"LabelCustomRating": "Vlastní hodnocení:",
|
"LabelCustomRating": "Vlastní hodnocení:",
|
||||||
"LabelDashboardTheme": "Motiv nástěnky serveru:",
|
"LabelDashboardTheme": "Motiv nástěnky serveru:",
|
||||||
@ -419,7 +419,7 @@
|
|||||||
"LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi dvěma vyhledáváními SSDP.",
|
"LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi dvěma vyhledáváními SSDP.",
|
||||||
"LabelEnableDlnaDebugLogging": "Povolit DLNA protokolování (pro ladění)",
|
"LabelEnableDlnaDebugLogging": "Povolit DLNA protokolování (pro ladění)",
|
||||||
"LabelEnableDlnaDebugLoggingHelp": "Vytváří velké soubory se záznamy a doporučuje se používat pouze pro potřeby odstraňování problémů.",
|
"LabelEnableDlnaDebugLoggingHelp": "Vytváří velké soubory se záznamy a doporučuje se používat pouze pro potřeby odstraňování problémů.",
|
||||||
"LabelEnableDlnaPlayTo": "Povolit DLNA přehrávání",
|
"LabelEnableDlnaPlayTo": "Povolit funkci DLNA 'Play To'",
|
||||||
"LabelEnableDlnaPlayToHelp": "Umí detekovat zařízení v rámci vaší sítě a nabízí možnost jejich dálkového ovládání.",
|
"LabelEnableDlnaPlayToHelp": "Umí detekovat zařízení v rámci vaší sítě a nabízí možnost jejich dálkového ovládání.",
|
||||||
"LabelEnableDlnaServer": "Povolit DLNA server",
|
"LabelEnableDlnaServer": "Povolit DLNA server",
|
||||||
"LabelEnableDlnaServerHelp": "Umožnit zařízením UPnP v síti procházet a přehrávat obsah.",
|
"LabelEnableDlnaServerHelp": "Umožnit zařízením UPnP v síti procházet a přehrávat obsah.",
|
||||||
@ -455,8 +455,8 @@
|
|||||||
"LabelImageFetchersHelp": "Povolí řazení stahovačů obrázků dle priority.",
|
"LabelImageFetchersHelp": "Povolí řazení stahovačů obrázků dle priority.",
|
||||||
"LabelImageType": "Typ obrázku:",
|
"LabelImageType": "Typ obrázku:",
|
||||||
"LabelImportOnlyFavoriteChannels": "Zamezit označení kanálů jako oblíbené",
|
"LabelImportOnlyFavoriteChannels": "Zamezit označení kanálů jako oblíbené",
|
||||||
"LabelInNetworkSignInWithEasyPassword": "Povolit přihlášení snadným PIN kódem uvnitř lokální sítě",
|
"LabelInNetworkSignInWithEasyPassword": "Povolit přihlášení Easy PIN kódem uvnitř lokální sítě",
|
||||||
"LabelInNetworkSignInWithEasyPasswordHelp": "Pomocí jednoduchého kódu PIN se přihlaste ke klientům v místní síti. Vaše běžné heslo bude potřeba pouze mimo domov. Pokud je kód PIN ponechán prázdný, nebudete potřebovat heslo v domácí síti.",
|
"LabelInNetworkSignInWithEasyPasswordHelp": "Pomocí Easy PIN kódu se přihlaste ke klientům v místní síti. Vaše běžné heslo bude potřeba pouze mimo domov. Pokud je kód PIN ponechán prázdný, nebudete potřebovat heslo v domácí síti.",
|
||||||
"LabelKeepUpTo": "Aktualizovat k:",
|
"LabelKeepUpTo": "Aktualizovat k:",
|
||||||
"LabelKidsCategories": "Dětské kategorie:",
|
"LabelKidsCategories": "Dětské kategorie:",
|
||||||
"LabelKodiMetadataDateFormat": "Formát data vydání:",
|
"LabelKodiMetadataDateFormat": "Formát data vydání:",
|
||||||
@ -493,7 +493,7 @@
|
|||||||
"LabelMetadataPathHelp": "Zadejte vlastní umístění pro stažení obrázků a metadat.",
|
"LabelMetadataPathHelp": "Zadejte vlastní umístění pro stažení obrázků a metadat.",
|
||||||
"LabelMetadataReaders": "Čtečky metadat:",
|
"LabelMetadataReaders": "Čtečky metadat:",
|
||||||
"LabelMetadataReadersHelp": "Seřaďte své preferované lokální zdroje metadat dle priority. První nalezená data budou načtena.",
|
"LabelMetadataReadersHelp": "Seřaďte své preferované lokální zdroje metadat dle priority. První nalezená data budou načtena.",
|
||||||
"LabelMetadataSavers": "Střadatelé metadat:",
|
"LabelMetadataSavers": "Ukládání metadat:",
|
||||||
"LabelMetadataSaversHelp": "Vyberte formáty souborů, které chcete použít pro ukládání metadat.",
|
"LabelMetadataSaversHelp": "Vyberte formáty souborů, které chcete použít pro ukládání metadat.",
|
||||||
"LabelMethod": "Metoda:",
|
"LabelMethod": "Metoda:",
|
||||||
"LabelMinBackdropDownloadWidth": "Maximální šířka pro stažení pozadí:",
|
"LabelMinBackdropDownloadWidth": "Maximální šířka pro stažení pozadí:",
|
||||||
@ -565,7 +565,7 @@
|
|||||||
"LabelScheduledTaskLastRan": "Poslední spuštění {0}, zabralo {1}.",
|
"LabelScheduledTaskLastRan": "Poslední spuštění {0}, zabralo {1}.",
|
||||||
"LabelScreensaver": "Šetřič obrazovky:",
|
"LabelScreensaver": "Šetřič obrazovky:",
|
||||||
"LabelSeasonNumber": "Číslo sezóny:",
|
"LabelSeasonNumber": "Číslo sezóny:",
|
||||||
"LabelSelectFolderGroups": "Automaticky seskupit obsah z následujících složek do zobrazení, jako jsou Filmy, Hudba a TV:",
|
"LabelSelectFolderGroups": "Automaticky seskupit obsah z následujících složek do zobrazení, jako jsou 'Filmy', 'Hudba' a 'Seriály':",
|
||||||
"LabelSelectFolderGroupsHelp": "Složky, které nejsou zaškrtnuty budou zobrazeny ve vlastním pohledu.",
|
"LabelSelectFolderGroupsHelp": "Složky, které nejsou zaškrtnuty budou zobrazeny ve vlastním pohledu.",
|
||||||
"LabelSelectUsers": "Vyberte uživatele:",
|
"LabelSelectUsers": "Vyberte uživatele:",
|
||||||
"LabelSelectVersionToInstall": "Vyber verzi k instalaci:",
|
"LabelSelectVersionToInstall": "Vyber verzi k instalaci:",
|
||||||
@ -580,7 +580,7 @@
|
|||||||
"LabelSkipIfGraphicalSubsPresent": "Přeskočit, jestliže video obsahuje vložené titulky",
|
"LabelSkipIfGraphicalSubsPresent": "Přeskočit, jestliže video obsahuje vložené titulky",
|
||||||
"LabelSkipIfGraphicalSubsPresentHelp": "Ponecháním textových titulků je možné dosáhnout efektivnějšího přenosu videa a snížení pravděpodobnosti, že bude video nutné překódovat.",
|
"LabelSkipIfGraphicalSubsPresentHelp": "Ponecháním textových titulků je možné dosáhnout efektivnějšího přenosu videa a snížení pravděpodobnosti, že bude video nutné překódovat.",
|
||||||
"LabelSonyAggregationFlags": "Agregační příznaky Sony:",
|
"LabelSonyAggregationFlags": "Agregační příznaky Sony:",
|
||||||
"LabelSonyAggregationFlagsHelp": "Určuje obsah prvku aggregationFlags ve jmenném prostoru urn:schemas-sonycom:av.",
|
"LabelSonyAggregationFlagsHelp": "Určuje obsah prvku 'aggregationFlags' ve jmenném prostoru 'urn:schemas-sonycom:av'.",
|
||||||
"LabelSortTitle": "Třídit dle názvu:",
|
"LabelSortTitle": "Třídit dle názvu:",
|
||||||
"LabelSource": "Zdroj:",
|
"LabelSource": "Zdroj:",
|
||||||
"LabelSportsCategories": "Sportovní kategorie:",
|
"LabelSportsCategories": "Sportovní kategorie:",
|
||||||
@ -617,10 +617,10 @@
|
|||||||
"LabelValue": "Hodnota:",
|
"LabelValue": "Hodnota:",
|
||||||
"LabelVersion": "Verze:",
|
"LabelVersion": "Verze:",
|
||||||
"LabelVersionInstalled": "{0} instalováno",
|
"LabelVersionInstalled": "{0} instalováno",
|
||||||
"LabelXDlnaCap": "Zachytávací zařízení X-DLNA:",
|
"LabelXDlnaCap": "ID schopnosti zařízení:",
|
||||||
"LabelXDlnaCapHelp": "Určuje obsah prvku X_DLNACAP ve jmenném prostoru urn:schemas-dlna-org:device-1-0.",
|
"LabelXDlnaCapHelp": "Určuje obsah prvku 'X_DLNACAP' ve jmenném prostoru 'urn:schemas-dlna-org:device-1-0'.",
|
||||||
"LabelXDlnaDoc": "Dokumentace X-DLNA:",
|
"LabelXDlnaDoc": "ID třídy zařízení:",
|
||||||
"LabelXDlnaDocHelp": "Určuje obsah prvku X_DLNADOC ve jmenném prostoru urn:schemas-dlna-org:device-1-0.",
|
"LabelXDlnaDocHelp": "Určuje obsah prvku 'X_DLNADOC' ve jmenném prostoru 'urn:schemas-dlna-org:device-1-0'.",
|
||||||
"LabelYear": "Rok:",
|
"LabelYear": "Rok:",
|
||||||
"LabelYoureDone": "Hotovo!",
|
"LabelYoureDone": "Hotovo!",
|
||||||
"LabelZipCode": "PSČ:",
|
"LabelZipCode": "PSČ:",
|
||||||
@ -629,7 +629,7 @@
|
|||||||
"Large": "Velký",
|
"Large": "Velký",
|
||||||
"LatestFromLibrary": "Nejnovější {0}",
|
"LatestFromLibrary": "Nejnovější {0}",
|
||||||
"LearnHowYouCanContribute": "Zjistěte, jak můžete přispět.",
|
"LearnHowYouCanContribute": "Zjistěte, jak můžete přispět.",
|
||||||
"LibraryAccessHelp": "Vyberte knihovny, které chcete sdílet s tímto uživatelem. Administrátoři budou moci editovat všechny složky pomocí správce metadat.",
|
"LibraryAccessHelp": "Vyberte knihovny, které chcete sdílet s tímto uživatelem. Administrátoři budou moci editovat všechny složky pomocí Správce metadat.",
|
||||||
"List": "Seznam",
|
"List": "Seznam",
|
||||||
"Live": "Živě",
|
"Live": "Živě",
|
||||||
"LiveBroadcasts": "Přímé přenosy",
|
"LiveBroadcasts": "Přímé přenosy",
|
||||||
@ -692,7 +692,7 @@
|
|||||||
"MessageNoPluginsInstalled": "Nemáte instalovány žádné zásuvné moduly.",
|
"MessageNoPluginsInstalled": "Nemáte instalovány žádné zásuvné moduly.",
|
||||||
"MessageNoTrailersFound": "Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.",
|
"MessageNoTrailersFound": "Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.",
|
||||||
"MessageNothingHere": "Tady nic není.",
|
"MessageNothingHere": "Tady nic není.",
|
||||||
"MessagePasswordResetForUsers": "Obnovení hesla bylo provedeno následujícími uživateli. Nyní se mohou přihlásit pomocí kódů PIN, které byly použity k provedení resetu.",
|
"MessagePasswordResetForUsers": "Následujícím uživatelům bylo resetováno heslo. Nyní se mohou přihlásit pomocí kódů Easy PIN, které byly použity k provedení resetu.",
|
||||||
"MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.",
|
"MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.",
|
||||||
"MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.",
|
"MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.",
|
||||||
"MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.",
|
"MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.",
|
||||||
@ -700,10 +700,10 @@
|
|||||||
"MessageReenableUser": "Viz níže pro znovuzapnutí",
|
"MessageReenableUser": "Viz níže pro znovuzapnutí",
|
||||||
"MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií:",
|
"MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií:",
|
||||||
"MessageUnableToConnectToServer": "Nejsme schopni se připojit k vybranému serveru právě teď. Prosím, ujistěte se, že je spuštěn a zkuste to znovu.",
|
"MessageUnableToConnectToServer": "Nejsme schopni se připojit k vybranému serveru právě teď. Prosím, ujistěte se, že je spuštěn a zkuste to znovu.",
|
||||||
"MessageUnsetContentHelp": "Obsah je zobrazen pomocí prostých složek. Pro dosažení nejlepších výsledků pomocí správce metadat nastavte typy obsahu pod-složek.",
|
"MessageUnsetContentHelp": "Obsah je zobrazen pomocí prostých složek. Pro dosažení nejlepších výsledků pomocí Správce metadat nastavte typy obsahu pod-složek.",
|
||||||
"MessageYouHaveVersionInstalled": "V současné době máte instalovánu verzi {0}.",
|
"MessageYouHaveVersionInstalled": "V současné době máte instalovánu verzi {0}.",
|
||||||
"MetadataManager": "Manažer metadat",
|
"MetadataManager": "Správce metadat",
|
||||||
"MetadataSettingChangeHelp": "Změna nastavení metadat bude mít vliv na obsah, který bude nově přidán v budoucnu. Chcete-li aktualizovat stávající obsah, otevřete obrazovku s podrobnostmi a klikněte na tlačítko Aktualizovat, nebo proveďte hromadnou aktualizaci pomocí správce metadat.",
|
"MetadataSettingChangeHelp": "Změna nastavení metadat bude mít vliv na obsah, který bude nově přidán v budoucnu. Chcete-li aktualizovat stávající obsah, otevřete obrazovku s podrobnostmi a klikněte na tlačítko 'Aktualizovat', nebo proveďte hromadnou aktualizaci pomocí Správce metadat.",
|
||||||
"MinutesAfter": "minut po",
|
"MinutesAfter": "minut po",
|
||||||
"MinutesBefore": "minut předem",
|
"MinutesBefore": "minut předem",
|
||||||
"Mobile": "Mobilní",
|
"Mobile": "Mobilní",
|
||||||
@ -782,7 +782,7 @@
|
|||||||
"OptionEveryday": "Každý den",
|
"OptionEveryday": "Každý den",
|
||||||
"OptionExternallyDownloaded": "Externí stažení",
|
"OptionExternallyDownloaded": "Externí stažení",
|
||||||
"OptionExtractChapterImage": "Povolit extrakci obrázků z videa",
|
"OptionExtractChapterImage": "Povolit extrakci obrázků z videa",
|
||||||
"OptionHasThemeSong": "Tematická hudba",
|
"OptionHasThemeSong": "Znělka",
|
||||||
"OptionHasThemeVideo": "Tematické video",
|
"OptionHasThemeVideo": "Tematické video",
|
||||||
"OptionHideUser": "Skrýt tohoto uživatele z přihlašovacích obrazovek",
|
"OptionHideUser": "Skrýt tohoto uživatele z přihlašovacích obrazovek",
|
||||||
"OptionHideUserFromLoginHelp": "Vhodné pro soukromé a administrátorské účty. Pro přihlášení musí uživatel manuálně zadat uživatelské jméno a heslo.",
|
"OptionHideUserFromLoginHelp": "Vhodné pro soukromé a administrátorské účty. Pro přihlášení musí uživatel manuálně zadat uživatelské jméno a heslo.",
|
||||||
@ -796,16 +796,16 @@
|
|||||||
"OptionOnInterval": "V intervalu",
|
"OptionOnInterval": "V intervalu",
|
||||||
"OptionParentalRating": "Rodičovské hodnocení",
|
"OptionParentalRating": "Rodičovské hodnocení",
|
||||||
"OptionPlainStorageFolders": "Zobrazit všechny složky jako obyčejné složky pro ukládání",
|
"OptionPlainStorageFolders": "Zobrazit všechny složky jako obyčejné složky pro ukládání",
|
||||||
"OptionPlainStorageFoldersHelp": "Všechny složky jsou prezentovány v DIDL jako \"object.container.storageFolder\" místo konkrétnějšího typu, například \"object.container.person.musicArtist\".",
|
"OptionPlainStorageFoldersHelp": "Všechny složky jsou prezentovány v DIDL jako 'object.container.storageFolder' místo konkrétnějšího typu, například 'object.container.person.musicArtist'.",
|
||||||
"OptionPlainVideoItems": "Zobrazit všechna videa jako s obyčejné video položky",
|
"OptionPlainVideoItems": "Zobrazit všechna videa jako s obyčejné video položky",
|
||||||
"OptionPlainVideoItemsHelp": "Všechna videa jsou prezentována v DIDL jako \"object.item.videoItem\" místo konkrétnějšího typu, například \"object.item.videoItem.movie\".",
|
"OptionPlainVideoItemsHelp": "Všechna videa jsou prezentována v DIDL jako 'object.item.videoItem' místo konkrétnějšího typu, například 'object.item.videoItem.movie'.",
|
||||||
"OptionPlayCount": "Počet přehrání",
|
"OptionPlayCount": "Počet přehrání",
|
||||||
"OptionPremiereDate": "Datum premiéry",
|
"OptionPremiereDate": "Datum premiéry",
|
||||||
"OptionRegex": "Regexp",
|
"OptionRegex": "Regexp",
|
||||||
"OptionReleaseDate": "Datum vydání",
|
"OptionReleaseDate": "Datum vydání",
|
||||||
"OptionReportByteRangeSeekingWhenTranscoding": "Hlásit, že server podporuje vyhledávání bajtů při překódování",
|
"OptionReportByteRangeSeekingWhenTranscoding": "Hlásit, že server podporuje vyhledávání bajtů při překódování",
|
||||||
"OptionReportByteRangeSeekingWhenTranscodingHelp": "Tento krok je nutný pro některá zařízení, které nemají moc dobrý time seek.",
|
"OptionReportByteRangeSeekingWhenTranscodingHelp": "Tento krok je nutný pro některá zařízení, které nemají moc dobrý time seek.",
|
||||||
"OptionRequirePerfectSubtitleMatch": "Stahovat jen titulky, které perfektně sedí k mým video souborům",
|
"OptionRequirePerfectSubtitleMatch": "Stahovat jen titulky, které perfektně sedí k video souborům",
|
||||||
"OptionResElement": "'res' element",
|
"OptionResElement": "'res' element",
|
||||||
"OptionResumable": "Pozastavavitelný",
|
"OptionResumable": "Pozastavavitelný",
|
||||||
"OptionSaveMetadataAsHidden": "Ukládat metadata a obrázky jako skryté soubory",
|
"OptionSaveMetadataAsHidden": "Ukládat metadata a obrázky jako skryté soubory",
|
||||||
@ -833,8 +833,8 @@
|
|||||||
"PerfectMatch": "Přesná shoda",
|
"PerfectMatch": "Přesná shoda",
|
||||||
"Photos": "Fotky",
|
"Photos": "Fotky",
|
||||||
"PictureInPicture": "Obraz v obraze",
|
"PictureInPicture": "Obraz v obraze",
|
||||||
"PinCodeResetComplete": "PIN kód byl obnoven.",
|
"PinCodeResetComplete": "Kód Easy PIN byl resetován.",
|
||||||
"PinCodeResetConfirmation": "Jsou si jist, že chcete resetovat PIN kód?",
|
"PinCodeResetConfirmation": "Opravdu chcete resetovat kód Easy PIN?",
|
||||||
"PlaceFavoriteChannelsAtBeginning": "Umístit oblíbené kanály na začátek",
|
"PlaceFavoriteChannelsAtBeginning": "Umístit oblíbené kanály na začátek",
|
||||||
"Play": "Přehrát",
|
"Play": "Přehrát",
|
||||||
"PlayAllFromHere": "Přehrát vše odsud",
|
"PlayAllFromHere": "Přehrát vše odsud",
|
||||||
@ -845,7 +845,7 @@
|
|||||||
"PlaybackErrorNoCompatibleStream": "Tento klient není kompatibilní s médiem a server neodesílá kompatibilní formát médií.",
|
"PlaybackErrorNoCompatibleStream": "Tento klient není kompatibilní s médiem a server neodesílá kompatibilní formát médií.",
|
||||||
"Played": "Přehráno",
|
"Played": "Přehráno",
|
||||||
"Playlists": "Seznamy skladeb",
|
"Playlists": "Seznamy skladeb",
|
||||||
"PleaseAddAtLeastOneFolder": "Přidejte prosím nejméně jednu složku do této knihovny pomocí tlačítka Přidat.",
|
"PleaseAddAtLeastOneFolder": "Přidejte prosím nejméně jednu složku do této knihovny pomocí tlačítka '+' v sekci 'Složky'.",
|
||||||
"PleaseConfirmPluginInstallation": "Pro potvrzení, že jste si přečetli text výše a chcete pokračovat v instalaci zásuvných modulů, klikněte na tlačítko OK.",
|
"PleaseConfirmPluginInstallation": "Pro potvrzení, že jste si přečetli text výše a chcete pokračovat v instalaci zásuvných modulů, klikněte na tlačítko OK.",
|
||||||
"PleaseEnterNameOrId": "Prosím, zadejte název nebo externí Id.",
|
"PleaseEnterNameOrId": "Prosím, zadejte název nebo externí Id.",
|
||||||
"PleaseRestartServerName": "Prosím restartuje Jellyfin na serveru {0}.",
|
"PleaseRestartServerName": "Prosím restartuje Jellyfin na serveru {0}.",
|
||||||
@ -1056,7 +1056,7 @@
|
|||||||
"General": "Obecné",
|
"General": "Obecné",
|
||||||
"Genre": "Žánr",
|
"Genre": "Žánr",
|
||||||
"GroupBySeries": "Seskupit podle série",
|
"GroupBySeries": "Seskupit podle série",
|
||||||
"HeaderAllowMediaDeletionFrom": "Povolit smazání médií z",
|
"HeaderAllowMediaDeletionFrom": "Povolit mazání médií z:",
|
||||||
"HeaderAppearsOn": "Objeví se",
|
"HeaderAppearsOn": "Objeví se",
|
||||||
"HeaderBlockItemsWithNoRating": "Blokovat položky s žádnými nebo nerozpoznanými informacemi o hodnocení:",
|
"HeaderBlockItemsWithNoRating": "Blokovat položky s žádnými nebo nerozpoznanými informacemi o hodnocení:",
|
||||||
"HeaderChapterImages": "Obrázky kapitol",
|
"HeaderChapterImages": "Obrázky kapitol",
|
||||||
@ -1066,7 +1066,7 @@
|
|||||||
"HeaderExternalIds": "Externí Id:",
|
"HeaderExternalIds": "Externí Id:",
|
||||||
"HeaderFetcherSettings": "Nastavení načítání",
|
"HeaderFetcherSettings": "Nastavení načítání",
|
||||||
"HeaderImageOptions": "Volby obrázku",
|
"HeaderImageOptions": "Volby obrázku",
|
||||||
"HeaderKodiMetadataHelp": "Chcete-li povolit nebo zakázat metadata v souborech NFO, upravte nastavení knihovny v sekci ukládání metadat.",
|
"HeaderKodiMetadataHelp": "Chcete-li povolit nebo zakázat metadata v souborech NFO, upravte nastavení knihovny v sekci 'Ukládání metadat'.",
|
||||||
"HeaderLiveTvTunerSetup": "Nastavení televizního tuneru",
|
"HeaderLiveTvTunerSetup": "Nastavení televizního tuneru",
|
||||||
"HeaderNewDevices": "Nové zařízení",
|
"HeaderNewDevices": "Nové zařízení",
|
||||||
"HeaderPhotoAlbums": "Fotoalba",
|
"HeaderPhotoAlbums": "Fotoalba",
|
||||||
@ -1074,7 +1074,7 @@
|
|||||||
"HeaderSeriesStatus": "Stav seriálu",
|
"HeaderSeriesStatus": "Stav seriálu",
|
||||||
"HeaderStopRecording": "Zastavit nahrávání",
|
"HeaderStopRecording": "Zastavit nahrávání",
|
||||||
"HeaderSubtitleDownloads": "Stahování titulků",
|
"HeaderSubtitleDownloads": "Stahování titulků",
|
||||||
"HeaderTypeImageFetchers": "Stahovače obrázků ({0})",
|
"HeaderTypeImageFetchers": "Stahovače obrázků ({0}):",
|
||||||
"HeaderVideoType": "Formát videa",
|
"HeaderVideoType": "Formát videa",
|
||||||
"Horizontal": "Vodorovně",
|
"Horizontal": "Vodorovně",
|
||||||
"HttpsRequiresCert": "Chcete-li povolit zabezpečená připojení, budete muset zadat důvěryhodný certifikát SSL, například Let's Encrypt. Zadejte prosím certifikát nebo zakažte zabezpečená připojení.",
|
"HttpsRequiresCert": "Chcete-li povolit zabezpečená připojení, budete muset zadat důvěryhodný certifikát SSL, například Let's Encrypt. Zadejte prosím certifikát nebo zakažte zabezpečená připojení.",
|
||||||
@ -1117,7 +1117,7 @@
|
|||||||
"LabelTypeMetadataDownloaders": "Stahovače metadat ({0}):",
|
"LabelTypeMetadataDownloaders": "Stahovače metadat ({0}):",
|
||||||
"LabelTypeText": "Text",
|
"LabelTypeText": "Text",
|
||||||
"LabelUserAgent": "User agent:",
|
"LabelUserAgent": "User agent:",
|
||||||
"LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení přehrávání serveru.",
|
"LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení serveru, viz Nástěnka > Přehrávání > Streamování.",
|
||||||
"LabelVideoCodec": "Video kodek:",
|
"LabelVideoCodec": "Video kodek:",
|
||||||
"LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.",
|
"LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.",
|
||||||
"LiveTV": "Televize",
|
"LiveTV": "Televize",
|
||||||
@ -1145,7 +1145,7 @@
|
|||||||
"Metadata": "Metadata",
|
"Metadata": "Metadata",
|
||||||
"MovieLibraryHelp": "Podívejte se na {0}průvodce pojmenováním filmů{1}.",
|
"MovieLibraryHelp": "Podívejte se na {0}průvodce pojmenováním filmů{1}.",
|
||||||
"Never": "Nikdy",
|
"Never": "Nikdy",
|
||||||
"NextUp": "Další",
|
"NextUp": "Další díly",
|
||||||
"NoNewDevicesFound": "Nebyla nalezena žádná nová zařízení. Chcete-li přidat nový tuner, zavřete tento dialog a zadejte informace o zařízení ručně.",
|
"NoNewDevicesFound": "Nebyla nalezena žádná nová zařízení. Chcete-li přidat nový tuner, zavřete tento dialog a zadejte informace o zařízení ručně.",
|
||||||
"OnlyImageFormats": "Pouze obrazové formáty (VobSub, PGS, SUB, atd.)",
|
"OnlyImageFormats": "Pouze obrazové formáty (VobSub, PGS, SUB, atd.)",
|
||||||
"Option3D": "3D",
|
"Option3D": "3D",
|
||||||
@ -1158,7 +1158,7 @@
|
|||||||
"OptionLoginAttemptsBeforeLockout": "Počet chybných pokusů o přihlášení, který lze provést před zablokováním.",
|
"OptionLoginAttemptsBeforeLockout": "Počet chybných pokusů o přihlášení, který lze provést před zablokováním.",
|
||||||
"OptionLoginAttemptsBeforeLockoutHelp": "0 znamená zdědění výchozí hodnoty 3 pokusů pro běžné uživatele a 5 pro administrátory. Nastavení na -1 deaktivuje funkci.",
|
"OptionLoginAttemptsBeforeLockoutHelp": "0 znamená zdědění výchozí hodnoty 3 pokusů pro běžné uživatele a 5 pro administrátory. Nastavení na -1 deaktivuje funkci.",
|
||||||
"OptionMax": "Max",
|
"OptionMax": "Max",
|
||||||
"OptionProtocolHls": "Přímý přenos z internetu (HLS)",
|
"OptionProtocolHls": "HTTP Live Streaming (HLS)",
|
||||||
"OptionProtocolHttp": "HTTP",
|
"OptionProtocolHttp": "HTTP",
|
||||||
"OptionRequirePerfectSubtitleMatchHelp": "Vyžadování dokonalé shody filtruje titulky tak, aby obsahovaly pouze ty, které byly testovány a ověřeny s vaším přesným videosouborem. Zrušení zaškrtnutí tohoto políčka zvýší pravděpodobnost stahování titulků, ale zvýší pravděpodobnost chybného nebo nesprávného textu titulků.",
|
"OptionRequirePerfectSubtitleMatchHelp": "Vyžadování dokonalé shody filtruje titulky tak, aby obsahovaly pouze ty, které byly testovány a ověřeny s vaším přesným videosouborem. Zrušení zaškrtnutí tohoto políčka zvýší pravděpodobnost stahování titulků, ale zvýší pravděpodobnost chybného nebo nesprávného textu titulků.",
|
||||||
"PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit při žádosti tohoto uživatele o resetování hesla.",
|
"PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit při žádosti tohoto uživatele o resetování hesla.",
|
||||||
@ -1186,13 +1186,13 @@
|
|||||||
"TabDirectPlay": "Přímé přehrávání",
|
"TabDirectPlay": "Přímé přehrávání",
|
||||||
"TabServer": "Server",
|
"TabServer": "Server",
|
||||||
"TagsValue": "Tagy: {0}",
|
"TagsValue": "Tagy: {0}",
|
||||||
"ThemeSongs": "Tematická hudba",
|
"ThemeSongs": "Znělky",
|
||||||
"ThemeVideos": "Tematická videa",
|
"ThemeVideos": "Tematická videa",
|
||||||
"Trailers": "Upoutávky",
|
"Trailers": "Upoutávky",
|
||||||
"TvLibraryHelp": "Podívejte se na {0}průvodce pojmenováním TV pořadů{1}.",
|
"TvLibraryHelp": "Podívejte se na {0}průvodce pojmenováním TV pořadů{1}.",
|
||||||
"Uniform": "Obrys",
|
"Uniform": "Obrys",
|
||||||
"Unplayed": "Nepřehrané",
|
"Unplayed": "Nepřehrané",
|
||||||
"UserAgentHelp": "Zadejte vlastní HTTP hlavičku user agenta.",
|
"UserAgentHelp": "Zadejte vlastní HTTP hlavičku 'User-Agent'.",
|
||||||
"ValueMinutes": "{0} min",
|
"ValueMinutes": "{0} min",
|
||||||
"ValueOneAlbum": "1 album",
|
"ValueOneAlbum": "1 album",
|
||||||
"ValueOneSong": "1 skladba",
|
"ValueOneSong": "1 skladba",
|
||||||
@ -1368,8 +1368,8 @@
|
|||||||
"Data": "Datumy",
|
"Data": "Datumy",
|
||||||
"VideoAudio": "Video audio",
|
"VideoAudio": "Video audio",
|
||||||
"Photo": "Fotka",
|
"Photo": "Fotka",
|
||||||
"LabelIconMaxResHelp": "Maximální rozlišení ikon daných vlastností upnp:icon.",
|
"LabelIconMaxResHelp": "Maximální rozlišení ikon daných vlastností 'upnp:icon'.",
|
||||||
"LabelAlbumArtMaxResHelp": "Maximální rozlišení obrázku v souboru dané vlastností upnp:albumArtURI.",
|
"LabelAlbumArtMaxResHelp": "Maximální rozlišení obrázku v souboru dané vlastností 'upnp:albumArtURI'.",
|
||||||
"Other": "Ostatní",
|
"Other": "Ostatní",
|
||||||
"Bwdif": "BWDIF",
|
"Bwdif": "BWDIF",
|
||||||
"UseDoubleRateDeinterlacingHelp": "Toto nastavení při odstranění prokládání zdvojnásobuje snímkovou frekvenci, aby výsledné video vypadalo stejně plynule, jako při přehrávání prokládaného obsahu v televizi.",
|
"UseDoubleRateDeinterlacingHelp": "Toto nastavení při odstranění prokládání zdvojnásobuje snímkovou frekvenci, aby výsledné video vypadalo stejně plynule, jako při přehrávání prokládaného obsahu v televizi.",
|
||||||
@ -1384,9 +1384,9 @@
|
|||||||
"LabelTonemappingDesat": "Snížení barevnosti při mapování tónů:",
|
"LabelTonemappingDesat": "Snížení barevnosti při mapování tónů:",
|
||||||
"TonemappingRangeHelp": "Výstupní rozsah barev. Automaticky znamená stejný jako vstupní.",
|
"TonemappingRangeHelp": "Výstupní rozsah barev. Automaticky znamená stejný jako vstupní.",
|
||||||
"LabelTonemappingRange": "Rozsah mapování tónů:",
|
"LabelTonemappingRange": "Rozsah mapování tónů:",
|
||||||
"TonemappingAlgorithmHelp": "Mapování tonů je možné dále ladit. Pokud možnostem zde nerozumíte, je možné ponechat vše ve výchozím nastavení. Doporučená hodnota je Hable.",
|
"TonemappingAlgorithmHelp": "Mapování tonů je možné dále ladit. Pokud možnostem zde nerozumíte, je možné ponechat vše ve výchozím nastavení. Doporučená hodnota je 'BT.2390'.",
|
||||||
"LabelTonemappingAlgorithm": "Algoritmus mapování tónů:",
|
"LabelTonemappingAlgorithm": "Algoritmus mapování tónů:",
|
||||||
"AllowTonemappingHelp": "Mapování tónů umožňuje změnit dynamický rozsah videa z HDR na SDR bez ztráty důležitých informací původního obrazu, např. detailů a barev. Tato funkce momentálně funguje pouze při překódování videí, které obsahují informace o HDR10 nebo HLG. Pokud je přehrávání trhané nebo vůbec nefunguje, zkuste vypnout příslušný hardwarový dekodér.",
|
"AllowTonemappingHelp": "Mapování tónů umožňuje změnit dynamický rozsah videa z HDR na SDR bez ztráty detailů a barev, tj. důležitých informací původního obrazu. Tato funkce momentálně funguje pouze u videí, které obsahují HDR10 nebo HLG, a vyžaduje buď OpenCL nebo CUDA.",
|
||||||
"EnableTonemapping": "Zapnout mapování tónů",
|
"EnableTonemapping": "Zapnout mapování tónů",
|
||||||
"LabelOpenclDeviceHelp": "Zařízení OpenCL použité pro mapování tónů. Nalevo od tečky je číslo platformy, napravo pak číslo zařízení na této platformě. Výchozí hodnota je 0.0. Soubor aplikace FFmpeg, který obsahuje metodu pro hardwarovou akceleraci OpenCL, je povinný.",
|
"LabelOpenclDeviceHelp": "Zařízení OpenCL použité pro mapování tónů. Nalevo od tečky je číslo platformy, napravo pak číslo zařízení na této platformě. Výchozí hodnota je 0.0. Soubor aplikace FFmpeg, který obsahuje metodu pro hardwarovou akceleraci OpenCL, je povinný.",
|
||||||
"LabelOpenclDevice": "Zařízení OpenCL:",
|
"LabelOpenclDevice": "Zařízení OpenCL:",
|
||||||
@ -1401,7 +1401,7 @@
|
|||||||
"LabelMaxMuxingQueueSizeHelp": "Maximální počet paketů, které je možné napřed načíst při čekání na spuštění všech proudů. Pokud se stále zobrazuje chyba \"Příliš mnoho paketů načtených napřed ve výstupním proudu\" v protokolech FFmpeg, zkuste hodnotu zvýšit. Doporučená hodnota je 2048.",
|
"LabelMaxMuxingQueueSizeHelp": "Maximální počet paketů, které je možné napřed načíst při čekání na spuštění všech proudů. Pokud se stále zobrazuje chyba \"Příliš mnoho paketů načtených napřed ve výstupním proudu\" v protokolech FFmpeg, zkuste hodnotu zvýšit. Doporučená hodnota je 2048.",
|
||||||
"LabelMaxMuxingQueueSize": "Maximální velikost muxovací fronty:",
|
"LabelMaxMuxingQueueSize": "Maximální velikost muxovací fronty:",
|
||||||
"LabelKnownProxies": "Známé proxy servery:",
|
"LabelKnownProxies": "Známé proxy servery:",
|
||||||
"KnownProxiesHelp": "Čárkami oddělený seznam IP adres nebo jmen hostitelů známých proxy serverů pro připojení k instanci Jellyfin. Vyžadováno pro správné využití HTTP hlavičky X-Forwarded-For. Vyžaduje restart.",
|
"KnownProxiesHelp": "Čárkami oddělený seznam IP adres nebo jmen hostitelů známých proxy serverů pro připojení k instanci Jellyfin. Vyžadováno pro správné využití HTTP hlavičky 'X-Forwarded-For'. Vyžaduje restart.",
|
||||||
"QuickConnectNotActive": "Rychlé připojení k tomuto serveru není povoleno",
|
"QuickConnectNotActive": "Rychlé připojení k tomuto serveru není povoleno",
|
||||||
"QuickConnectNotAvailable": "Požádejte správce serveru, aby rychlé připojení povolil",
|
"QuickConnectNotAvailable": "Požádejte správce serveru, aby rychlé připojení povolil",
|
||||||
"QuickConnectInvalidCode": "Neplatný kód pro rychlé připojení",
|
"QuickConnectInvalidCode": "Neplatný kód pro rychlé připojení",
|
||||||
@ -1502,7 +1502,7 @@
|
|||||||
"LabelMaxAudiobookResume": "Zbývající délka v minutách pro pokračování u audioknih:",
|
"LabelMaxAudiobookResume": "Zbývající délka v minutách pro pokračování u audioknih:",
|
||||||
"MessagePlaybackError": "Přehrání tohoto souboru na vašem zařízení Google Cast se nezdařilo.",
|
"MessagePlaybackError": "Přehrání tohoto souboru na vašem zařízení Google Cast se nezdařilo.",
|
||||||
"MessageChromecastConnectionError": "Vašemu zařízení Google Cast se nezdařilo kontaktovat server Jellyfin. Zkontrolujte připojení a zkuste to znovu.",
|
"MessageChromecastConnectionError": "Vašemu zařízení Google Cast se nezdařilo kontaktovat server Jellyfin. Zkontrolujte připojení a zkuste to znovu.",
|
||||||
"AllowVppTonemappingHelp": "Plně hardwarové mapování tónů bez použití filtru OpenCL. Momentálně funguje pouze při překódování videí, které obsahují metadata HDR10.",
|
"AllowVppTonemappingHelp": "Plné mapování tónů pomocí ovladače Intel. Momentálně funguje pouze na určitém hardwaru u videí s HDR10. Má vyšší prioritu než jiné implementace OpenCL.",
|
||||||
"EnableVppTonemapping": "Povolit mapování tónů VPP",
|
"EnableVppTonemapping": "Povolit mapování tónů VPP",
|
||||||
"EnableEnhancedNvdecDecoder": "Povolit vylepšený dekodér NVDEC",
|
"EnableEnhancedNvdecDecoder": "Povolit vylepšený dekodér NVDEC",
|
||||||
"Framerate": "Snímková frekvence",
|
"Framerate": "Snímková frekvence",
|
||||||
@ -1517,10 +1517,10 @@
|
|||||||
"MessageSent": "Zpráva odeslána.",
|
"MessageSent": "Zpráva odeslána.",
|
||||||
"LabelSlowResponseTime": "Čas v milisekundách, nad který je odezva považována za pomalou:",
|
"LabelSlowResponseTime": "Čas v milisekundách, nad který je odezva považována za pomalou:",
|
||||||
"LabelSlowResponseEnabled": "Zaznamenat varovnou zprávu, pokud byla odezva serveru pomalá",
|
"LabelSlowResponseEnabled": "Zaznamenat varovnou zprávu, pokud byla odezva serveru pomalá",
|
||||||
"UseEpisodeImagesInNextUpHelp": "Sekce Další a Pokračovat ve sledování použijí obrázky dílů jako náhledy místo primárního náhledu seriálů.",
|
"UseEpisodeImagesInNextUpHelp": "Sekce 'Další' a 'Pokračovat ve sledování' použijí obrázky dílů jako náhledy místo primárního náhledu seriálů.",
|
||||||
"UseEpisodeImagesInNextUp": "Použít obrázky dílů v sekcích 'Další' a 'Pokračovat ve sledování'",
|
"UseEpisodeImagesInNextUp": "Použít obrázky dílů v sekcích 'Další' a 'Pokračovat ve sledování'",
|
||||||
"LabelLocalCustomCss": "Úprava vzhledu prostřednictvím CSS, která se vztahuje pouze na tento klient. Možná bude potřeba vypnout CSS nabízené serverem.",
|
"LabelLocalCustomCss": "Úprava vzhledu prostřednictvím kódu CSS, který se vztahuje pouze na tento klient. Možná bude potřeba vypnout kód CSS nabízený serverem.",
|
||||||
"LabelDisableCustomCss": "Vypnout úpravy vzhledu prostřednictvím CSS nabízené serverem.",
|
"LabelDisableCustomCss": "Vypnout úpravy vzhledu prostřednictvím kódu CSS nabízeného serverem.",
|
||||||
"DisableCustomCss": "Vypnout CSS nabízené serverem",
|
"DisableCustomCss": "Vypnout CSS nabízené serverem",
|
||||||
"AudioBitDepthNotSupported": "Bitová hloubka zvuku není podporována",
|
"AudioBitDepthNotSupported": "Bitová hloubka zvuku není podporována",
|
||||||
"VideoProfileNotSupported": "Profil video kodeku není podporován",
|
"VideoProfileNotSupported": "Profil video kodeku není podporován",
|
||||||
@ -1600,5 +1600,27 @@
|
|||||||
"TypeOptionPluralBoxSet": "Kolekce",
|
"TypeOptionPluralBoxSet": "Kolekce",
|
||||||
"LabelAutomaticallyAddToCollectionHelp": "Pokud mají alespoň 2 filmy stejný název kolekce, budou automaticky přidány do kolekce.",
|
"LabelAutomaticallyAddToCollectionHelp": "Pokud mají alespoň 2 filmy stejný název kolekce, budou automaticky přidány do kolekce.",
|
||||||
"LabelAutomaticallyAddToCollection": "Automaticky přidat to kolekce",
|
"LabelAutomaticallyAddToCollection": "Automaticky přidat to kolekce",
|
||||||
"Cursive": "Kurzíva"
|
"Cursive": "Kurzíva",
|
||||||
|
"LabelHardwareEncodingOptions": "Možnosti hardwarového kódování:",
|
||||||
|
"IntelLowPowerEncHelp": "Nízkoenergetické kódování může zbytečně synchronizovat CPU s GPU. Na Linuxu musí být vypnuto, pokud není nakonfigurován firmware i915 HuC.",
|
||||||
|
"EnableIntelLowPowerHevcHwEncoder": "Povolit nízkoenergetický hardwarový dekodér Intel HEVC",
|
||||||
|
"EnableIntelLowPowerH264HwEncoder": "Povolit nízkoenergetický hardwarový dekodér Intel H.264",
|
||||||
|
"PreferSystemNativeHwDecoder": "Preferovat hardwarové dekodéry DXVA nebo VA-API nativní pro daný OS",
|
||||||
|
"ContainerBitrateExceedsLimit": "Bitový tok videa překračuje nastavenou mez",
|
||||||
|
"DirectPlayError": "Při spuštění přímého přehrávání došlo k chybě",
|
||||||
|
"UnknownAudioStreamInfo": "Informace o zvukové stopě jsou neznámé",
|
||||||
|
"UnknownVideoStreamInfo": "Informace o video stopě jsou neznámé",
|
||||||
|
"VideoBitrateNotSupported": "Bitový tok videa není podporovaný",
|
||||||
|
"AudioIsExternal": "Zvuková stopa je externí",
|
||||||
|
"SelectAll": "Vybrat vše",
|
||||||
|
"ButtonExitApp": "Ukončit aplikaci",
|
||||||
|
"ThemeVideo": "Úvodní video",
|
||||||
|
"ThemeSong": "Znělka",
|
||||||
|
"Sample": "Ukázka",
|
||||||
|
"Scene": "Scéna",
|
||||||
|
"Interview": "Rozhovor",
|
||||||
|
"DeletedScene": "Vymazaná scéna",
|
||||||
|
"BehindTheScenes": "Z natáčení",
|
||||||
|
"Trailer": "Upoutávka",
|
||||||
|
"Clip": "Krátký film"
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@
|
|||||||
"TypeOptionPluralSeries": "Rhaglenni teledu",
|
"TypeOptionPluralSeries": "Rhaglenni teledu",
|
||||||
"OptionTvdbRating": "Sgôr TheTVDB",
|
"OptionTvdbRating": "Sgôr TheTVDB",
|
||||||
"LiveTV": "Teledu Byw",
|
"LiveTV": "Teledu Byw",
|
||||||
"TV": "Teledu",
|
"TV": "Set teledu",
|
||||||
"LabelTitle": "Teitl:",
|
"LabelTitle": "Teitl:",
|
||||||
"LabelTime": "Amser:",
|
"LabelTime": "Amser:",
|
||||||
"LabelTheme": "Thema:",
|
"LabelTheme": "Thema:",
|
||||||
@ -447,5 +447,29 @@
|
|||||||
"ColorTransfer": "Trosglwyddo lliw",
|
"ColorTransfer": "Trosglwyddo lliw",
|
||||||
"ColorSpace": "Gofod lliw",
|
"ColorSpace": "Gofod lliw",
|
||||||
"ColorPrimaries": "Sylfaenol lliwiau",
|
"ColorPrimaries": "Sylfaenol lliwiau",
|
||||||
"LabelAccessDay": "Diwrnod yr wythnos:"
|
"LabelAccessDay": "Diwrnod yr wythnos:",
|
||||||
|
"HeaderCancelSeries": "Canslo Cyfres",
|
||||||
|
"HeaderCancelRecording": "Canslo Recordiad",
|
||||||
|
"HeaderAutoDiscovery": "Canfod Rhwydwaith",
|
||||||
|
"HeaderAudioSettings": "Gosodiadau Sain",
|
||||||
|
"HeaderAudioBooks": "Llyfrau Sain",
|
||||||
|
"HeaderAddUser": "Ychwanegu Defnyddiwr",
|
||||||
|
"HeaderAdditionalParts": "Rhannau Ychwanegol",
|
||||||
|
"HeaderActiveRecordings": "Gweithredol Recordiadau",
|
||||||
|
"HeaderActiveDevices": "Gweithredol Dyfeisiau",
|
||||||
|
"HeaderAccessSchedule": "Amserlen Mynediad",
|
||||||
|
"GuideProviderSelectListings": "Dewis Rhestriadau",
|
||||||
|
"GuestStar": "Seren wadd",
|
||||||
|
"GroupVersions": "Fersiynau grŵp",
|
||||||
|
"Fullscreen": "Sgrin lawn",
|
||||||
|
"FormatValue": "Fformat: {0}",
|
||||||
|
"ExtraLarge": "Mawr Ychwanegol",
|
||||||
|
"EveryHour": "Bob awr",
|
||||||
|
"Engineer": "Peiriannydd sain",
|
||||||
|
"CancelSeries": "Canslo cyfres",
|
||||||
|
"CancelRecording": "Canslo recordiad",
|
||||||
|
"ButtonSelectDirectory": "Dewis Cyfeiriadur",
|
||||||
|
"TypeOptionPluralAudio": "Synau",
|
||||||
|
"OptionSpecialEpisode": "Penodau arbennig",
|
||||||
|
"LabelProfileCodecs": "Codeciaid:"
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@
|
|||||||
"HeaderAddUpdateImage": "Tilføj/opdater billede",
|
"HeaderAddUpdateImage": "Tilføj/opdater billede",
|
||||||
"HeaderAdditionalParts": "Andre stier",
|
"HeaderAdditionalParts": "Andre stier",
|
||||||
"HeaderAlert": "Advarsel",
|
"HeaderAlert": "Advarsel",
|
||||||
"HeaderAllowMediaDeletionFrom": "Tillad Media Sletning Fra",
|
"HeaderAllowMediaDeletionFrom": "Tillad Media Sletning Fra:",
|
||||||
"HeaderApiKey": "API-nøgle",
|
"HeaderApiKey": "API-nøgle",
|
||||||
"HeaderApiKeys": "API-nøgler",
|
"HeaderApiKeys": "API-nøgler",
|
||||||
"HeaderApiKeysHelp": "Eksterne applikationer skal have en API-nøgle for at kunne kommunikere med serveren. Nøgler udstedes ved at logge ind med en normal bruger konto, eller ved manuelt at tildele applikationen en nøgle.",
|
"HeaderApiKeysHelp": "Eksterne applikationer skal have en API-nøgle for at kunne kommunikere med serveren. Nøgler udstedes ved at logge ind med en normal bruger konto, eller ved manuelt at tildele applikationen en nøgle.",
|
||||||
@ -197,7 +197,7 @@
|
|||||||
"HeaderEnabledFieldsHelp": "Fjern fluebenet fra et felt for at låse det og forhindre dets data fra at blive ændret.",
|
"HeaderEnabledFieldsHelp": "Fjern fluebenet fra et felt for at låse det og forhindre dets data fra at blive ændret.",
|
||||||
"HeaderError": "Fejl",
|
"HeaderError": "Fejl",
|
||||||
"HeaderExternalIds": "Eksterne ID'er:",
|
"HeaderExternalIds": "Eksterne ID'er:",
|
||||||
"HeaderFeatureAccess": "Adgang til funktioner",
|
"HeaderFeatureAccess": "Adgang til funktioner:",
|
||||||
"HeaderFetchImages": "Hent billeder:",
|
"HeaderFetchImages": "Hent billeder:",
|
||||||
"HeaderFetcherSettings": "Henter indstillinger",
|
"HeaderFetcherSettings": "Henter indstillinger",
|
||||||
"HeaderForKids": "For Børn",
|
"HeaderForKids": "For Børn",
|
||||||
@ -242,7 +242,7 @@
|
|||||||
"HeaderPaths": "Stier",
|
"HeaderPaths": "Stier",
|
||||||
"HeaderPinCodeReset": "Nulstil pinkode",
|
"HeaderPinCodeReset": "Nulstil pinkode",
|
||||||
"HeaderPlayAll": "Afspil Alle",
|
"HeaderPlayAll": "Afspil Alle",
|
||||||
"HeaderPlayback": "Medieafspilning",
|
"HeaderPlayback": "Medieafspilning:",
|
||||||
"HeaderPlaybackError": "Fejl i afspilning",
|
"HeaderPlaybackError": "Fejl i afspilning",
|
||||||
"HeaderPleaseSignIn": "Log venligst ind",
|
"HeaderPleaseSignIn": "Log venligst ind",
|
||||||
"HeaderPluginInstallation": "Plugin installation",
|
"HeaderPluginInstallation": "Plugin installation",
|
||||||
@ -252,7 +252,7 @@
|
|||||||
"HeaderRecentlyPlayed": "Afspillet for nyligt",
|
"HeaderRecentlyPlayed": "Afspillet for nyligt",
|
||||||
"HeaderRecordingOptions": "Optagelsesindstillinger",
|
"HeaderRecordingOptions": "Optagelsesindstillinger",
|
||||||
"HeaderRecordingPostProcessing": "Efterbehandling af Optagelse",
|
"HeaderRecordingPostProcessing": "Efterbehandling af Optagelse",
|
||||||
"HeaderRemoteControl": "Fjernbetjening",
|
"HeaderRemoteControl": "Fjernbetjening:",
|
||||||
"HeaderRemoveMediaFolder": "Fjern mediemappe",
|
"HeaderRemoveMediaFolder": "Fjern mediemappe",
|
||||||
"HeaderRemoveMediaLocation": "Fjern medielokalisation",
|
"HeaderRemoveMediaLocation": "Fjern medielokalisation",
|
||||||
"HeaderResponseProfile": "Svarprofil",
|
"HeaderResponseProfile": "Svarprofil",
|
||||||
@ -288,7 +288,7 @@
|
|||||||
"HeaderTranscodingProfileHelp": "Tilføj profiler for transkodning foe at angive hvilke formater der skal anvendes når transkodning er nødvendig.",
|
"HeaderTranscodingProfileHelp": "Tilføj profiler for transkodning foe at angive hvilke formater der skal anvendes når transkodning er nødvendig.",
|
||||||
"HeaderTunerDevices": "Tuner-Enheder",
|
"HeaderTunerDevices": "Tuner-Enheder",
|
||||||
"HeaderTuners": "Tunere",
|
"HeaderTuners": "Tunere",
|
||||||
"HeaderTypeImageFetchers": "Billede Hentere ({0})",
|
"HeaderTypeImageFetchers": "Billede Hentere ({0}):",
|
||||||
"HeaderTypeText": "Indtast tekst",
|
"HeaderTypeText": "Indtast tekst",
|
||||||
"HeaderUpcomingOnTV": "Kommende I TV",
|
"HeaderUpcomingOnTV": "Kommende I TV",
|
||||||
"HeaderUploadImage": "Upload Billede",
|
"HeaderUploadImage": "Upload Billede",
|
||||||
@ -1528,5 +1528,8 @@
|
|||||||
"Engineer": "Ingeniør",
|
"Engineer": "Ingeniør",
|
||||||
"Conductor": "Dirigent",
|
"Conductor": "Dirigent",
|
||||||
"Arranger": "Arrangør",
|
"Arranger": "Arrangør",
|
||||||
"AgeValue": "({0} år gammel)"
|
"AgeValue": "({0} år gammel)",
|
||||||
|
"LabelAutomaticallyAddToCollection": "Automatisk tilføj til samling",
|
||||||
|
"Cursive": "Kursiv",
|
||||||
|
"Console": "Konsol"
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user