Compare commits
125 Commits
sortmedias
...
release-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be195b0e24 | ||
|
|
32330e3126 | ||
|
|
af706726ef | ||
|
|
393bfd2559 | ||
|
|
575766b8e8 | ||
|
|
f2cbfbb549 | ||
|
|
b589363d31 | ||
|
|
4c1a301bdb | ||
|
|
dfcfaad39c | ||
|
|
f9109a8194 | ||
|
|
2192388b78 | ||
|
|
f6d50b0721 | ||
|
|
05816d5562 | ||
|
|
65f4e29d36 | ||
|
|
0f0593f260 | ||
|
|
7d920beff0 | ||
|
|
2dc95beb8f | ||
|
|
8449b94d16 | ||
|
|
4a17964e67 | ||
|
|
c2106ab86c | ||
|
|
591ee288bf | ||
|
|
87cae4336c | ||
|
|
7cf812df37 | ||
|
|
b5d9ff9279 | ||
|
|
b053cb9d79 | ||
|
|
ba0815e1c3 | ||
|
|
94ac2b22a2 | ||
|
|
7af38bb60c | ||
|
|
21fd1a96fe | ||
|
|
b50eb4fa2e | ||
|
|
458fad3cb0 | ||
|
|
b467e581b0 | ||
|
|
2d381bbdc6 | ||
|
|
bdfc09739e | ||
|
|
ff0a46a004 | ||
|
|
32d152150b | ||
|
|
b9e0dd4938 | ||
|
|
79d0ed3ee4 | ||
|
|
d73d00a344 | ||
|
|
d91d3120e9 | ||
|
|
74de218ff6 | ||
|
|
eb3f1a3ba3 | ||
|
|
8e63b38a08 | ||
|
|
def3532019 | ||
|
|
5ec953e344 | ||
|
|
85013363e8 | ||
|
|
79c8495a1b | ||
|
|
add2ec132f | ||
|
|
bcba45fc7d | ||
|
|
e404106f9c | ||
|
|
31b52f2c32 | ||
|
|
a6d9897d8f | ||
|
|
925c2c926d | ||
|
|
a1dddf4524 | ||
|
|
bad273ee3f | ||
|
|
e6f59a761b | ||
|
|
afdaf29dc6 | ||
|
|
48201581d6 | ||
|
|
8bb34c4266 | ||
|
|
3ad0bb9118 | ||
|
|
7724b5fedc | ||
|
|
2a7d708944 | ||
|
|
97461dabf2 | ||
|
|
0ccbae44d1 | ||
|
|
d85c91ce2e | ||
|
|
93042157f8 | ||
|
|
5e8c92a7a7 | ||
|
|
3a57471b69 | ||
|
|
fb17afad60 | ||
|
|
4e38caba0a | ||
|
|
1f97acc46e | ||
|
|
998df179a8 | ||
|
|
ff9fab5af1 | ||
|
|
84c5df8ea4 | ||
|
|
d4c8cceb7f | ||
|
|
d588fc42c6 | ||
|
|
0fa42cb7e6 | ||
|
|
ef4f5d1f08 | ||
|
|
d27f661b46 | ||
|
|
0f247fd9f4 | ||
|
|
ac34647e85 | ||
|
|
e8028aa1a1 | ||
|
|
42b0b01a4e | ||
|
|
cde21b3ff2 | ||
|
|
58dc73cf66 | ||
|
|
136983e026 | ||
|
|
f38fb6eb46 | ||
|
|
aba1d8655b | ||
|
|
7582206eb3 | ||
|
|
68edd48a0d | ||
|
|
e8d044fbb7 | ||
|
|
7379822c72 | ||
|
|
2c230388be | ||
|
|
32514307eb | ||
|
|
0f12bd64aa | ||
|
|
6ff1bdcaca | ||
|
|
88dc049a2f | ||
|
|
5d42ac19c7 | ||
|
|
efe4374153 | ||
|
|
cd256f7989 | ||
|
|
49fa4e0b65 | ||
|
|
3a4023ca40 | ||
|
|
982bbee9ea | ||
|
|
142c56bf6a | ||
|
|
1207ac98be | ||
|
|
c757c53e5c | ||
|
|
cd2d30eefc | ||
|
|
07a33779d8 | ||
|
|
4dedb8ae45 | ||
|
|
87d459f827 | ||
|
|
eeded17cbd | ||
|
|
95a995327d | ||
|
|
27896bcc84 | ||
|
|
4aeecfa043 | ||
|
|
88bb7adaba | ||
|
|
d768cf7970 | ||
|
|
22df1eb3a6 | ||
|
|
ac6ba3228f | ||
|
|
7e5fcd8374 | ||
|
|
ccef18ee5d | ||
|
|
5f63743ed0 | ||
|
|
972ecc4106 | ||
|
|
c4fd94b147 | ||
|
|
a0ef8a405c | ||
|
|
4b9ffb7b22 |
@@ -92,6 +92,7 @@ module.exports = {
|
||||
'LibraryMenu': 'writable',
|
||||
'LinkParser': 'writable',
|
||||
'LiveTvHelpers': 'writable',
|
||||
'Loading': 'writable',
|
||||
'MetadataEditor': 'writable',
|
||||
'PlaylistViewer': 'writable',
|
||||
'UserParentalControlPage': 'writable',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"plugins": [ "stylelint-scss" ],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"plugin/no-browser-hacks": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
- [Ryan Hartzell](https://github.com/ryan-hartzell)
|
||||
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [artiume](https://github.com/Artiume)
|
||||
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||
- [Sarab Singh](https://github.com/sarab97)
|
||||
- [DesertCookie](https://github.com/desertcookie)
|
||||
@@ -43,6 +44,7 @@
|
||||
- [Orry Verducci](https://github.com/orryverducci)
|
||||
- [Camc314](https://github.com/camc314)
|
||||
- [danieladov](https://github.com/danieladov)
|
||||
- [Stephane Senart](https://github.com/ssenart)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = {
|
||||
// Keep the root as a root
|
||||
'.'
|
||||
],
|
||||
sourceType: 'unambiguous',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# We just wrap `build` so this is really it
|
||||
name: "jellyfin-web"
|
||||
version: "10.7.0"
|
||||
version: "10.7.7"
|
||||
packages:
|
||||
- debian.all
|
||||
- fedora.all
|
||||
|
||||
52
debian/changelog
vendored
52
debian/changelog
vendored
@@ -1,11 +1,45 @@
|
||||
jellyfin-web (10.7.7-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
|
||||
|
||||
jellyfin-web (10.7.6-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
|
||||
|
||||
jellyfin-web (10.7.5-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
|
||||
|
||||
jellyfin-web (10.7.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
|
||||
|
||||
jellyfin-web (10.7.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
|
||||
|
||||
jellyfin-web (10.7.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
|
||||
|
||||
jellyfin-web (10.7.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 21 Mar 2021 19:23:29 -0400
|
||||
|
||||
jellyfin-web (10.7.0-1) unstable; urgency=medium
|
||||
|
||||
* Forthcoming stable release
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:13:31 -0400
|
||||
|
||||
jellyfin-web (10.6.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400
|
||||
* New upstream version 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM node:alpine
|
||||
FROM node:lts-alpine
|
||||
|
||||
ARG SOURCE_DIR=/src
|
||||
ARG ARTIFACT_DIR=/jellyfin-web
|
||||
|
||||
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python
|
||||
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3
|
||||
|
||||
WORKDIR ${SOURCE_DIR}
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:31
|
||||
FROM fedora:33
|
||||
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Fedora environment
|
||||
RUN dnf update -y \
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs nodejs-yarn autoconf automake glibc-devel
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: jellyfin-web
|
||||
Version: 10.7.0
|
||||
Version: 10.7.7
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media System web client
|
||||
License: GPLv3
|
||||
URL: https://jellyfin.org
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
||||
Source0: jellyfin-web-%{version}.tar.gz
|
||||
|
||||
%if 0%{?centos}
|
||||
@@ -18,6 +18,9 @@ BuildRequires: nodejs-yarn
|
||||
# ditto for Fedora's yarn RPM
|
||||
BuildRequires: git
|
||||
BuildArch: noarch
|
||||
%if 0%{?fedora} >= 33
|
||||
BuildRequires: nodejs
|
||||
%endif
|
||||
|
||||
# Disable Automatic Dependency Processing
|
||||
AutoReqProv: no
|
||||
@@ -38,10 +41,27 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%files
|
||||
%attr(755,root,root) %{_datadir}/jellyfin-web
|
||||
%defattr(644,root,root,755)
|
||||
%{_datadir}/jellyfin-web
|
||||
%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%changelog
|
||||
* Sun Sep 05 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
|
||||
* Thu May 20 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
|
||||
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
|
||||
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
|
||||
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New stable release 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
|
||||
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- Forthcoming stable release
|
||||
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
|
||||
16
package.json
16
package.json
@@ -45,7 +45,7 @@
|
||||
"webpack-cli": "^4.0.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"workbox-webpack-plugin": "^5.1.4",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"worker-plugin": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -56,11 +56,15 @@
|
||||
"epubjs": "^0.3.85",
|
||||
"fast-text-encoding": "^1.0.3",
|
||||
"flv.js": "^1.5.0",
|
||||
"fontsource-noto-sans": "^3.1.5",
|
||||
"fontsource-noto-sans-hk": "^3.1.5",
|
||||
"fontsource-noto-sans-jp": "^3.1.5",
|
||||
"fontsource-noto-sans-kr": "^3.1.5",
|
||||
"fontsource-noto-sans-sc": "^3.1.5",
|
||||
"headroom.js": "^0.12.0",
|
||||
"hls.js": "^0.14.16",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"jellyfin-apiclient": "^1.4.2",
|
||||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||
"hls.js": "^0.14.17",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"jellyfin-apiclient": "^1.8.0",
|
||||
"jquery": "^3.5.1",
|
||||
"jstree": "^3.3.10",
|
||||
"libarchive.js": "^1.3.0",
|
||||
@@ -97,7 +101,7 @@
|
||||
"scripts": {
|
||||
"start": "yarn serve",
|
||||
"serve": "webpack serve --config webpack.dev.js",
|
||||
"prepare": "./scripts/prepare.sh",
|
||||
"prepare": "node ./scripts/prepare.js",
|
||||
"build:development": "webpack --config webpack.dev.js",
|
||||
"build:production": "webpack --config webpack.prod.js",
|
||||
"lint": "eslint \"src/\"",
|
||||
|
||||
12
scripts/prepare.js
Executable file
12
scripts/prepare.js
Executable file
@@ -0,0 +1,12 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* The npm `prepare` script needs to run a build to support installing
|
||||
* a package from git repositories (this is dumb but a limitation of how
|
||||
* npm behaves). We don't want to run these in CI though because
|
||||
* building is slow so this script will skip the build when the
|
||||
* `SKIP_PREPARE` environment variable has been set.
|
||||
*/
|
||||
if (!process.env.SKIP_PREPARE) {
|
||||
execSync('webpack --config webpack.prod.js', { stdio: 'inherit' });
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "${SKIP_PREPARE}" ]; then
|
||||
webpack --config webpack.prod.js
|
||||
fi
|
||||
@@ -127,8 +127,8 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.sessionAppInfo img {
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
max-width: 2.5em;
|
||||
max-height: 2.5em;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,10 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dashboardActionsContainer {
|
||||
margin: 1em -0.3em 0;
|
||||
}
|
||||
|
||||
.sessionNowPlayingContent {
|
||||
-webkit-background-size: cover;
|
||||
background-size: cover;
|
||||
@@ -231,6 +235,13 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.dashboardSection .sectionTitleTextButton > .material-icons.material-icons {
|
||||
font-size: 1.17em;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.activeRecordingItems > .card {
|
||||
width: 50%;
|
||||
}
|
||||
@@ -246,20 +257,12 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
|
||||
@media all and (min-width: 70em) {
|
||||
.dashboardSections {
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dashboardColumn-2-60 {
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.dashboardColumn-2-40 {
|
||||
width: 27%;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.dashboardSection {
|
||||
@@ -291,6 +294,7 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.activeSession {
|
||||
min-width: 20rem;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@@ -304,27 +308,24 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
.activeSession {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 50em) {
|
||||
.activeSession {
|
||||
width: 50% !important;
|
||||
max-width: 25rem;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.sessionCardFooter {
|
||||
padding-top: 0.5em !important;
|
||||
padding-bottom: 1em !important;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sessionAppInfo {
|
||||
flex-grow: 1;
|
||||
padding: 0.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -344,11 +345,9 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sessionNowPlayingContent-withbackground + .sessionNowPlayingInnerContent {
|
||||
color: #fff !important;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sessionAppName {
|
||||
@@ -358,9 +357,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
|
||||
.sessionNowPlayingDetails {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sessionNowPlayingInfo {
|
||||
@@ -376,10 +372,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
padding: 0.8em 0.5em;
|
||||
}
|
||||
|
||||
.sessionNowPlayingStreamInfo {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.playbackProgress,
|
||||
.transcodingProgress {
|
||||
margin: 0;
|
||||
@@ -387,6 +379,12 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.activeDevices.itemsContainer {
|
||||
/* offset for cardBox margin */
|
||||
margin: -0.6em;
|
||||
}
|
||||
|
||||
.activeSession .backgroundProgress,
|
||||
.activeSession .playbackProgress,
|
||||
.activeSession .transcodingProgress {
|
||||
position: absolute;
|
||||
@@ -403,9 +401,14 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.transcodingProgress > div {
|
||||
z-index: 10;
|
||||
background-color: #dd4919;
|
||||
}
|
||||
|
||||
.backgroundProgress > div {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
@media all and (max-width: 34.375em) {
|
||||
.sessionAppName {
|
||||
max-width: 160px;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@import "../../styles/noto-sans/index.scss";
|
||||
|
||||
@mixin font($weight: null, $size: null) {
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
|
||||
font-weight: $weight;
|
||||
font-size: $size;
|
||||
}
|
||||
|
||||
@@ -250,6 +250,26 @@
|
||||
padding-bottom: 10vh;
|
||||
}
|
||||
|
||||
.primaryImageWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.primaryImageWrapper > img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 80vw;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.primaryImageWrapper > img.aspect-square {
|
||||
max-height: 45vh;
|
||||
}
|
||||
|
||||
.layout-mobile .primaryImageWrapper {
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
.dashboardDocument .adminDrawerLogo,
|
||||
.dashboardDocument .mainDrawerButton {
|
||||
@@ -453,8 +473,7 @@
|
||||
}
|
||||
|
||||
.layout-mobile .itemBackdrop {
|
||||
background-attachment: scroll;
|
||||
height: 26.5vh;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-desktop .itemBackdrop::after {
|
||||
@@ -614,7 +633,8 @@
|
||||
}
|
||||
|
||||
.layout-mobile .mainDetailButtons {
|
||||
margin-top: 1em;
|
||||
flex: 2 0 70%;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
@@ -638,9 +658,9 @@
|
||||
}
|
||||
|
||||
.layout-mobile .detailPagePrimaryContainer {
|
||||
display: block;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
padding: 0.5em 3.3% 0.5em;
|
||||
padding: 4.5rem 3.3% 0.5rem;
|
||||
}
|
||||
|
||||
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
||||
@@ -669,6 +689,10 @@
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.layout-mobile .infoWrapper {
|
||||
flex: 2 0 70%;
|
||||
}
|
||||
|
||||
.infoText {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@@ -729,7 +753,8 @@
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.noBackdrop .detailLogo {
|
||||
.noBackdrop .detailLogo,
|
||||
.layout-mobile .detailLogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -754,6 +779,17 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
height: 23vw;
|
||||
}
|
||||
|
||||
.sectionTitleTextButton > .material-icons {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 0.35em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.layout-mobile .sectionTitleTextButton > .material-icons {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
|
||||
font-size: 15vw;
|
||||
margin-top: 50%;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.skinHeader-withBackground.osdHeader {
|
||||
@@ -32,6 +33,7 @@
|
||||
backdrop-filter: none;
|
||||
color: #eee;
|
||||
height: 7.5em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.osdHeader-hidden {
|
||||
@@ -39,6 +41,7 @@
|
||||
}
|
||||
|
||||
.osdHeader .headerTop {
|
||||
pointer-events: all;
|
||||
max-height: 3.5em;
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@
|
||||
}
|
||||
|
||||
.osdControls {
|
||||
pointer-events: all;
|
||||
flex-grow: 1;
|
||||
padding: 0 0.8em;
|
||||
}
|
||||
@@ -261,6 +265,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
@@ -427,12 +427,12 @@ class AppRouter {
|
||||
|
||||
if (data.status === 403) {
|
||||
if (data.errorCode === 'ParentalControl') {
|
||||
const isCurrentAllowed = this.currentRouteInfo ? (this.currentRouteInfo.route.anonymous || this.currentRouteInfo.route.startup) : true;
|
||||
const isCurrentAllowed = appRouter.currentRouteInfo ? (appRouter.currentRouteInfo.route.anonymous || appRouter.currentRouteInfo.route.startup) : true;
|
||||
|
||||
// Bounce to the login screen, but not if a password entry fails, obviously
|
||||
if (!isCurrentAllowed) {
|
||||
this.showForcedLogoutMessage(globalize.translate('AccessRestrictedTryAgainLater'));
|
||||
this.showLocalLogin(apiClient.serverId());
|
||||
appRouter.showForcedLogoutMessage(globalize.translate('AccessRestrictedTryAgainLater'));
|
||||
appRouter.showLocalLogin(apiClient.serverId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +446,7 @@ class AppRouter {
|
||||
|
||||
normalizeImageOptions(options) {
|
||||
let setQuality;
|
||||
if (options.maxWidth || options.width || options.maxHeight || options.height) {
|
||||
if (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight) {
|
||||
setQuality = true;
|
||||
}
|
||||
|
||||
@@ -471,18 +471,9 @@ class AppRouter {
|
||||
return null;
|
||||
}
|
||||
|
||||
getMaxBandwidthIOS() {
|
||||
return 800000;
|
||||
}
|
||||
|
||||
onApiClientCreated(e, newApiClient) {
|
||||
newApiClient.normalizeImageOptions = this.normalizeImageOptions;
|
||||
|
||||
if (browser.iOS) {
|
||||
newApiClient.getMaxBandwidth = this.getMaxBandwidthIOS;
|
||||
} else {
|
||||
newApiClient.getMaxBandwidth = this.getMaxBandwidth;
|
||||
}
|
||||
newApiClient.getMaxBandwidth = this.getMaxBandwidth;
|
||||
|
||||
Events.off(newApiClient, 'requestfail', this.onRequestFail);
|
||||
Events.on(newApiClient, 'requestfail', this.onRequestFail);
|
||||
@@ -512,22 +503,28 @@ class AppRouter {
|
||||
const firstResult = this.firstConnectionResult;
|
||||
|
||||
this.firstConnectionResult = null;
|
||||
if (firstResult && firstResult.State === 'ServerSignIn') {
|
||||
const url = firstResult.ApiClient.serverAddress() + '/System/Info/Public';
|
||||
fetch(url).then(response => {
|
||||
if (!response.ok) return Promise.reject('fetch failed');
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
if (data !== null && data.StartupWizardCompleted === false) {
|
||||
Dashboard.navigate('wizardstart.html');
|
||||
} else {
|
||||
this.handleConnectionResult(firstResult);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
if (firstResult) {
|
||||
if (firstResult.State === 'ServerSignIn') {
|
||||
const url = firstResult.ApiClient.serverAddress() + '/System/Info/Public';
|
||||
fetch(url).then(response => {
|
||||
if (!response.ok) return Promise.reject('fetch failed');
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
if (data !== null && data.StartupWizardCompleted === false) {
|
||||
ServerConnections.setLocalApiClient(firstResult.ApiClient);
|
||||
Dashboard.navigate('wizardstart.html');
|
||||
} else {
|
||||
this.handleConnectionResult(firstResult);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
return;
|
||||
return;
|
||||
} else if (firstResult.State !== 'SignedIn') {
|
||||
this.handleConnectionResult(firstResult);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
@@ -821,14 +818,20 @@ class AppRouter {
|
||||
url = '#!/tv.html?topParentId=' + item.Id;
|
||||
|
||||
if (options && options.section === 'latest') {
|
||||
url += '&tab=2';
|
||||
url += '&tab=1';
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
if (item.CollectionType == 'music') {
|
||||
return '#!/music.html?topParentId=' + item.Id;
|
||||
url = '#!/music.html?topParentId=' + item.Id;
|
||||
|
||||
if (options?.section === 'latest') {
|
||||
url += '&tab=1';
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import globalize from '../scripts/globalize';
|
||||
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||
|
||||
const appName = 'Jellyfin Web';
|
||||
const appVersion = '10.7.0';
|
||||
const appVersion = '10.7.6';
|
||||
|
||||
function getBaseProfileOptions(item) {
|
||||
const disableHlsVideoAudioCodecs = [];
|
||||
|
||||
@@ -160,7 +160,6 @@ button::-moz-focus-inner {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -169,8 +168,13 @@ button::-moz-focus-inner {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cardContent.cardImageContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cardScalable .cardImageContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
@@ -222,8 +226,8 @@ button::-moz-focus-inner {
|
||||
}
|
||||
|
||||
.visualCardBox .cardContent {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: 0.2em;
|
||||
border-top-right-radius: 0.2em;
|
||||
}
|
||||
|
||||
.cardContent-shadow,
|
||||
@@ -360,16 +364,11 @@ button::-moz-focus-inner {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cardImageContainer .cardImageIcon {
|
||||
.cardImageIcon {
|
||||
font-size: 5em;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cardImageIcon-small {
|
||||
font-size: 3em !important;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.cardIndicators {
|
||||
right: 0.225em;
|
||||
top: 0.225em;
|
||||
|
||||
@@ -498,7 +498,7 @@ import ServerConnections from '../ServerConnections';
|
||||
let imgUrl = null;
|
||||
let imgTag = null;
|
||||
let coverImage = false;
|
||||
let uiAspect = null;
|
||||
const uiAspect = getDesiredAspect(shape);
|
||||
let imgType = null;
|
||||
let itemId = null;
|
||||
|
||||
@@ -543,11 +543,8 @@ import ServerConnections from '../ServerConnections';
|
||||
forceName = true;
|
||||
}
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
} else if (item.SeriesPrimaryImageTag) {
|
||||
imgType = 'Primary';
|
||||
@@ -563,11 +560,8 @@ import ServerConnections from '../ServerConnections';
|
||||
forceName = true;
|
||||
}
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
} else if (item.ParentPrimaryImageTag) {
|
||||
imgType = 'Primary';
|
||||
@@ -579,11 +573,8 @@ import ServerConnections from '../ServerConnections';
|
||||
itemId = item.AlbumId;
|
||||
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
|
||||
imgType = 'Thumb';
|
||||
@@ -613,10 +604,15 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (imgTag && imgType) {
|
||||
// TODO: This place is a mess. Could do with a good spring cleaning.
|
||||
if (!height && width && uiAspect) {
|
||||
height = width / uiAspect;
|
||||
}
|
||||
imgUrl = apiClient.getScaledImageUrl(itemId, {
|
||||
type: imgType,
|
||||
maxHeight: height,
|
||||
maxWidth: width,
|
||||
fillHeight: height,
|
||||
fillWidth: width,
|
||||
quality: 96,
|
||||
tag: imgTag
|
||||
});
|
||||
}
|
||||
@@ -1421,6 +1417,8 @@ import ServerConnections from '../ServerConnections';
|
||||
const pathData = item.Path ? (' data-path="' + item.Path + '"') : '';
|
||||
const contextData = options.context ? (' data-context="' + options.context + '"') : '';
|
||||
const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : '';
|
||||
const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : '';
|
||||
const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : '';
|
||||
|
||||
let additionalCardContent = '';
|
||||
|
||||
@@ -1428,7 +1426,7 @@ import ServerConnections from '../ServerConnections';
|
||||
additionalCardContent += getHoverMenuHtml(item, action);
|
||||
}
|
||||
|
||||
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
|
||||
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,21 +17,17 @@ import template from './displaySettings.template.html';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
function fillThemes(context, userSettings) {
|
||||
const select = context.querySelector('#selectTheme');
|
||||
|
||||
function fillThemes(select, selectedTheme) {
|
||||
skinManager.getThemes().then(themes => {
|
||||
select.innerHTML = themes.map(t => {
|
||||
return `<option value="${t.id}">${t.name}</option>`;
|
||||
}).join('');
|
||||
|
||||
// get default theme
|
||||
const defaultTheme = themes.find(theme => {
|
||||
return theme.default;
|
||||
});
|
||||
const defaultTheme = themes.find(theme => theme.default);
|
||||
|
||||
// set the current theme
|
||||
select.value = userSettings.theme() || defaultTheme.id;
|
||||
select.value = selectedTheme || defaultTheme.id;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,6 +85,8 @@ import template from './displaySettings.template.html';
|
||||
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
|
||||
}
|
||||
|
||||
context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator);
|
||||
|
||||
if (appHost.supports('screensaver')) {
|
||||
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
|
||||
} else {
|
||||
@@ -111,7 +109,9 @@ import template from './displaySettings.template.html';
|
||||
context.querySelector('.fldThemeVideo').classList.add('hide');
|
||||
}
|
||||
|
||||
fillThemes(context, userSettings);
|
||||
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
|
||||
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
|
||||
|
||||
loadScreensavers(context, userSettings);
|
||||
|
||||
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
|
||||
@@ -147,6 +147,7 @@ import template from './displaySettings.template.html';
|
||||
userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked);
|
||||
userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked);
|
||||
userSettingsInstance.theme(context.querySelector('#selectTheme').value);
|
||||
userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value);
|
||||
userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value);
|
||||
|
||||
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
|
||||
|
||||
@@ -6,27 +6,35 @@
|
||||
<div class="selectContainer languageSection hide">
|
||||
<select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}">
|
||||
<option value="">${Auto}</option>
|
||||
<option value="af">Afrikaans</option>
|
||||
<option value="sq">Albanian</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian (Belarus)</option>
|
||||
<option value="bg-BG">Bulgarian (Bulgaria)</option>
|
||||
<option value="be-BY">Belarusian</option>
|
||||
<option value="bn_BD">Bengali (Bangladesh)</option>
|
||||
<option value="bg-BG">Bulgarian</option>
|
||||
<option value="ca">Catalan</option>
|
||||
<option value="zh-CN">Chinese Simplified</option>
|
||||
<option value="zh-TW">Chinese Traditional</option>
|
||||
<option value="zh-HK">Chinese Traditional (Hong Kong)</option>
|
||||
<option value="zh-HK">Chinese (Hong Kong)</option>
|
||||
<option value="zh-CN">Chinese (Simplified)</option>
|
||||
<option value="zh-TW">Chinese (Traditional)</option>
|
||||
<option value="hr">Croatian</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="da">Danish</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="en-US">English</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="en-US">English (United States)</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="fil">Filipino</option>
|
||||
<option value="fi">Finnish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="fr-CA">French (Canada)</option>
|
||||
<option value="gl">Galician</option>
|
||||
<option value="de">German</option>
|
||||
<option value="gsw">German (Swiss)</option>
|
||||
<option value="el">Greek</option>
|
||||
<option value="he">Hebrew</option>
|
||||
<option value="hi-IN">Hindi (India)</option>
|
||||
<option value="hi-IN">Hindi</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="is">Icelandic</option>
|
||||
<option value="id">Indonesian</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
@@ -34,9 +42,12 @@
|
||||
<option value="ko">Korean</option>
|
||||
<option value="lt-LT">Lithuanian</option>
|
||||
<option value="ms">Malay</option>
|
||||
<option value="mr">Marathi</option>
|
||||
<option value="nb">Norwegian Bokmål</option>
|
||||
<option value="fa">Persian</option>
|
||||
<option value="pr">Pirate</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="pt-BR">Portuguese (Brazil)</option>
|
||||
<option value="pt-PT">Portuguese (Portugal)</option>
|
||||
<option value="ro">Romanian</option>
|
||||
@@ -44,12 +55,16 @@
|
||||
<option value="sk">Slovak</option>
|
||||
<option value="sl-SI">Slovenian (Slovenia)</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="es_AR">Spanish (Argentina)</option>
|
||||
<option value="es_DO">Spanish (Dominican Republic)</option>
|
||||
<option value="es-419">Spanish (Latin America)</option>
|
||||
<option value="es-MX">Spanish (Mexico)</option>
|
||||
<option value="sv">Swedish</option>
|
||||
<option value="gsw">Swiss German</option>
|
||||
<option value="ta">Tamil</option>
|
||||
<option value="th">Thai</option>
|
||||
<option value="tr">Turkish</option>
|
||||
<option value="uk">Ukrainian</option>
|
||||
<option value="ur_PK">Urdu (Pakistan)</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
<div class="fieldDescription">
|
||||
@@ -63,27 +78,35 @@
|
||||
<div class="selectContainer fldDateTimeLocale hide">
|
||||
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
|
||||
<option value="">${Auto}</option>
|
||||
<option value="af">Afrikaans</option>
|
||||
<option value="sq">Albanian</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian (Belarus)</option>
|
||||
<option value="bg-BG">Bulgarian (Bulgaria)</option>
|
||||
<option value="be-BY">Belarusian</option>
|
||||
<option value="bn_BD">Bengali (Bangladesh)</option>
|
||||
<option value="bg-BG">Bulgarian</option>
|
||||
<option value="ca">Catalan</option>
|
||||
<option value="zh-CN">Chinese Simplified</option>
|
||||
<option value="zh-TW">Chinese Traditional</option>
|
||||
<option value="zh-HK">Chinese Traditional (Hong Kong)</option>
|
||||
<option value="zh-HK">Chinese (Hong Kong)</option>
|
||||
<option value="zh-CN">Chinese (Simplified)</option>
|
||||
<option value="zh-TW">Chinese (Traditional)</option>
|
||||
<option value="hr">Croatian</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="da">Danish</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="en-US">English</option>
|
||||
<option value="en-GB">English (United Kingdom)</option>
|
||||
<option value="en-US">English (United States)</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="fil">Filipino</option>
|
||||
<option value="fi">Finnish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="fr-CA">French (Canada)</option>
|
||||
<option value="gl">Galician</option>
|
||||
<option value="de">German</option>
|
||||
<option value="gsw">German (Swiss)</option>
|
||||
<option value="el">Greek</option>
|
||||
<option value="he">Hebrew</option>
|
||||
<option value="hi-IN">Hindi (India)</option>
|
||||
<option value="hi-IN">Hindi</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="is">Icelandic</option>
|
||||
<option value="id">Indonesian</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
@@ -91,9 +114,12 @@
|
||||
<option value="ko">Korean</option>
|
||||
<option value="lt-LT">Lithuanian</option>
|
||||
<option value="ms">Malay</option>
|
||||
<option value="mr">Marathi</option>
|
||||
<option value="nb">Norwegian Bokmål</option>
|
||||
<option value="fa">Persian</option>
|
||||
<option value="pr">Pirate</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="pt-BR">Portuguese (Brazil)</option>
|
||||
<option value="pt-PT">Portuguese (Portugal)</option>
|
||||
<option value="ro">Romanian</option>
|
||||
@@ -101,12 +127,16 @@
|
||||
<option value="sk">Slovak</option>
|
||||
<option value="sl-SI">Slovenian (Slovenia)</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="es_AR">Spanish (Argentina)</option>
|
||||
<option value="es_DO">Spanish (Dominican Republic)</option>
|
||||
<option value="es-419">Spanish (Latin America)</option>
|
||||
<option value="es-MX">Spanish (Mexico)</option>
|
||||
<option value="sv">Swedish</option>
|
||||
<option value="gsw">Swiss German</option>
|
||||
<option value="ta">Tamil</option>
|
||||
<option value="th">Thai</option>
|
||||
<option value="tr">Turkish</option>
|
||||
<option value="uk">Ukrainian</option>
|
||||
<option value="ur_PK">Urdu (Pakistan)</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -126,6 +156,10 @@
|
||||
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer selectDashboardThemeContainer hide">
|
||||
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer hide selectScreensaverContainer">
|
||||
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
|
||||
</div>
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
<div is="emby-collapse" title="${Filters}">
|
||||
<div class="collapseContent">
|
||||
<div class="checkboxList">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard"
|
||||
<label class="videoStandard">
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter"
|
||||
data-filter="IsPlayed" />
|
||||
<span>${Played}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard"
|
||||
<label class="videoStandard">
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter"
|
||||
data-filter="IsUnPlayed" />
|
||||
<span>${Unplayed}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard"
|
||||
<label class="videoStandard">
|
||||
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter"
|
||||
data-filter="IsResumable" />
|
||||
<span>${OptionResumable}</span>
|
||||
</label>
|
||||
|
||||
@@ -57,8 +57,8 @@ import template from './homeScreenSettings.template.html';
|
||||
value: 'suggestions'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Genres'),
|
||||
value: 'genres'
|
||||
name: globalize.translate('Trailers'),
|
||||
value: 'trailers'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Favorites'),
|
||||
@@ -68,6 +68,10 @@ import template from './homeScreenSettings.template.html';
|
||||
name: globalize.translate('Collections'),
|
||||
value: 'collections'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Genres'),
|
||||
value: 'genres'
|
||||
});
|
||||
} else if (type === 'tvshows') {
|
||||
list.push({
|
||||
name: globalize.translate('Shows'),
|
||||
@@ -79,7 +83,7 @@ import template from './homeScreenSettings.template.html';
|
||||
value: 'suggestions'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Upcoming'),
|
||||
name: globalize.translate('TabUpcoming'),
|
||||
value: 'upcoming'
|
||||
});
|
||||
list.push({
|
||||
@@ -87,7 +91,7 @@ import template from './homeScreenSettings.template.html';
|
||||
value: 'genres'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Networks'),
|
||||
name: globalize.translate('TabNetworks'),
|
||||
value: 'networks'
|
||||
});
|
||||
list.push({
|
||||
@@ -116,20 +120,40 @@ import template from './homeScreenSettings.template.html';
|
||||
name: globalize.translate('Playlists'),
|
||||
value: 'playlists'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Songs'),
|
||||
value: 'songs'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Genres'),
|
||||
value: 'genres'
|
||||
});
|
||||
} else if (type === 'livetv') {
|
||||
list.push({
|
||||
name: globalize.translate('Suggestions'),
|
||||
value: 'suggestions',
|
||||
name: globalize.translate('Programs'),
|
||||
value: 'programs',
|
||||
isDefault: true
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Guide'),
|
||||
value: 'guide'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Channels'),
|
||||
value: 'channels'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Recordings'),
|
||||
value: 'recordings'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Schedule'),
|
||||
value: 'schedule'
|
||||
});
|
||||
list.push({
|
||||
name: globalize.translate('Series'),
|
||||
value: 'series'
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
|
||||
@@ -491,7 +491,7 @@ import ServerConnections from '../ServerConnections';
|
||||
function loadResumeAudio(elem, apiClient, userId) {
|
||||
let html = '';
|
||||
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderContinueWatching') + '</h2>';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderContinueListening') + '</h2>';
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x" data-monitor="audioplayback,markplayed">';
|
||||
@@ -665,7 +665,8 @@ import ServerConnections from '../ServerConnections';
|
||||
UserId: apiClient.getCurrentUserId(),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false
|
||||
EnableTotalRecordCount: false,
|
||||
DisableFirstEpisode: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,15 +158,11 @@ import { Events } from 'jellyfin-apiclient';
|
||||
// (but rewinding cannot happen as the first event with media of non-empty duration)
|
||||
console.debug(`seeking to ${seconds} on ${e.type} event`);
|
||||
setCurrentTimeIfNeeded(element, seconds);
|
||||
events.map(function(name) {
|
||||
element.removeEventListener(name, onMediaChange);
|
||||
});
|
||||
events.forEach(name => element.removeEventListener(name, onMediaChange));
|
||||
if (onMediaReady) onMediaReady();
|
||||
}
|
||||
};
|
||||
events.map(function (name) {
|
||||
return element.addEventListener(name, onMediaChange);
|
||||
});
|
||||
events.forEach(name => element.addEventListener(name, onMediaChange));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ import template from './imageDownloader.template.html';
|
||||
let html = '';
|
||||
|
||||
for (let i = 0, length = imagesResult.Images.length; i < length; i++) {
|
||||
html += getRemoteImageHtml(imagesResult.Images[i], imageType, apiClient);
|
||||
html += getRemoteImageHtml(imagesResult.Images[i], imageType);
|
||||
}
|
||||
|
||||
const availableImagesList = page.querySelector('.availableImagesList');
|
||||
@@ -150,11 +150,7 @@ import template from './imageDownloader.template.html';
|
||||
});
|
||||
}
|
||||
|
||||
function getDisplayUrl(url, apiClient) {
|
||||
return apiClient.getUrl('Images/Remote', { imageUrl: url });
|
||||
}
|
||||
|
||||
function getRemoteImageHtml(image, imageType, apiClient) {
|
||||
function getRemoteImageHtml(image, imageType) {
|
||||
const tagName = layoutManager.tv ? 'button' : 'div';
|
||||
const enableFooterButtons = !layoutManager.tv;
|
||||
|
||||
@@ -209,9 +205,9 @@ import template from './imageDownloader.template.html';
|
||||
html += '<div class="cardContent">';
|
||||
|
||||
if (layoutManager.tv || !appHost.supports('externallinks')) {
|
||||
html += '<div class="cardImageContainer lazy" data-src="' + getDisplayUrl(image.Url, apiClient) + '" style="background-position:center center;background-size:contain;"></div>';
|
||||
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
|
||||
} else {
|
||||
html += '<a is="emby-linkbutton" target="_blank" href="' + getDisplayUrl(image.Url, apiClient) + '" class="button-link cardImageContainer lazy" data-src="' + getDisplayUrl(image.Url, apiClient) + '" style="background-position:center center;background-size:contain"></a>';
|
||||
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
@@ -87,9 +87,6 @@ import './style.css';
|
||||
requestAnimationFrame(() => {
|
||||
if (elem.tagName !== 'IMG') {
|
||||
elem.style.backgroundImage = "url('" + url + "')";
|
||||
if (elem.classList.contains('blurhashed')) {
|
||||
elem.style.backgroundColor = '#fff';
|
||||
}
|
||||
} else {
|
||||
elem.setAttribute('src', url);
|
||||
}
|
||||
@@ -101,6 +98,12 @@ import './style.css';
|
||||
} else {
|
||||
elem.classList.add('lazy-image-fadein');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -111,7 +114,6 @@ import './style.css';
|
||||
if (elem.tagName !== 'IMG') {
|
||||
url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, '');
|
||||
elem.style.backgroundImage = 'none';
|
||||
elem.style.backgroundColor = null;
|
||||
} else {
|
||||
url = elem.getAttribute('src');
|
||||
elem.setAttribute('src', '');
|
||||
@@ -120,6 +122,16 @@ import './style.css';
|
||||
|
||||
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
|
||||
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) {
|
||||
|
||||
@@ -471,7 +471,7 @@ import toast from './toast/toast';
|
||||
navigator.share({
|
||||
title: item.Name,
|
||||
text: item.Overview,
|
||||
url: `${apiClient.serverAddress()}/web/index.html#!/${appRouter.getRouteUrl(item)}`
|
||||
url: `${apiClient.serverAddress()}/web/index.html${appRouter.getRouteUrl(item)}`
|
||||
});
|
||||
break;
|
||||
case 'album':
|
||||
|
||||
@@ -171,9 +171,7 @@ import template from './itemidentifier.template.html';
|
||||
let resultHtml = lines.join('<br/>');
|
||||
|
||||
if (identifyResult.ImageUrl) {
|
||||
const displayUrl = getSearchImageDisplayUrl(identifyResult.ImageUrl, identifyResult.SearchProviderName);
|
||||
|
||||
resultHtml = `<div style="display:flex;align-items:center;"><img src="${displayUrl}" style="max-height:240px;" /><div style="margin-left:1em;">${resultHtml}</div>`;
|
||||
resultHtml = `<div style="display:flex;align-items:center;"><img src="${identifyResult.ImageUrl}" style="max-height:240px;" /><div style="margin-left:1em;">${resultHtml}</div>`;
|
||||
}
|
||||
|
||||
page.querySelector('.selectedSearchResult').innerHTML = resultHtml;
|
||||
@@ -218,9 +216,7 @@ import template from './itemidentifier.template.html';
|
||||
html += '<div class="cardContent searchImage">';
|
||||
|
||||
if (result.ImageUrl) {
|
||||
const displayUrl = getSearchImageDisplayUrl(result.ImageUrl, result.SearchProviderName);
|
||||
|
||||
html += `<div class="cardImageContainer coveredImage" style="background-image:url('${displayUrl}');"></div>`;
|
||||
html += `<div class="cardImageContainer coveredImage" style="background-image:url('${result.ImageUrl}');"></div>`;
|
||||
} else {
|
||||
html += `<div class="cardImageContainer coveredImage defaultCardBackground defaultCardBackground1"><div class="cardText cardCenteredText">${result.Name}</div></div>`;
|
||||
}
|
||||
@@ -258,16 +254,6 @@ import template from './itemidentifier.template.html';
|
||||
return html;
|
||||
}
|
||||
|
||||
function getSearchImageDisplayUrl(url, provider) {
|
||||
const apiClient = getApiClient();
|
||||
|
||||
return apiClient.getUrl('Items/RemoteSearch/Image', {
|
||||
imageUrl: url,
|
||||
ProviderName: provider,
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
}
|
||||
|
||||
function submitIdentficationResult(page) {
|
||||
loading.show();
|
||||
|
||||
|
||||
@@ -76,12 +76,13 @@ import ServerConnections from '../ServerConnections';
|
||||
return '';
|
||||
}
|
||||
|
||||
function getImageUrl(item, width) {
|
||||
function getImageUrl(item, size) {
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
let itemId;
|
||||
|
||||
const options = {
|
||||
maxWidth: width,
|
||||
fillWidth: size,
|
||||
fillHeight: size,
|
||||
type: 'Primary'
|
||||
};
|
||||
|
||||
@@ -105,10 +106,11 @@ import ServerConnections from '../ServerConnections';
|
||||
return null;
|
||||
}
|
||||
|
||||
function getChannelImageUrl(item, width) {
|
||||
function getChannelImageUrl(item, size) {
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
const options = {
|
||||
maxWidth: width,
|
||||
fillWidth: size,
|
||||
fillHeight: size,
|
||||
type: 'Primary'
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,11 @@ export function hide() {
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
show: show,
|
||||
hide: hide
|
||||
const loading = {
|
||||
show,
|
||||
hide
|
||||
};
|
||||
|
||||
window.Loading = loading;
|
||||
|
||||
export default loading;
|
||||
|
||||
@@ -196,11 +196,14 @@ import confirm from '../confirm/confirm';
|
||||
}
|
||||
|
||||
if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) {
|
||||
// Disabled because there is no callback for this item
|
||||
/*
|
||||
menuItems.push({
|
||||
name: globalize.translate('Download'),
|
||||
id: 'download',
|
||||
icon: 'file_download'
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
|
||||
@@ -556,6 +556,7 @@ import { appRouter } from '../appRouter';
|
||||
const options = {
|
||||
play: false,
|
||||
queue: false,
|
||||
stopPlayback: true,
|
||||
clearQueue: true,
|
||||
positionTo: contextButton
|
||||
};
|
||||
|
||||
@@ -156,7 +156,7 @@ function backdropImageUrl(apiClient, item, options) {
|
||||
options.type = options.type || 'Backdrop';
|
||||
|
||||
// If not resizing, get the original image
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) {
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height && !options.fillWidth && !options.fillHeight) {
|
||||
options.quality = 100;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,14 +142,13 @@ import ServerConnections from '../ServerConnections';
|
||||
});
|
||||
}
|
||||
|
||||
if (audioChannels) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelAudioChannels'),
|
||||
value: audioChannels
|
||||
});
|
||||
}
|
||||
|
||||
if (displayPlayMethod === 'Transcode') {
|
||||
if (audioChannels) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelAudioChannels'),
|
||||
value: audioChannels
|
||||
});
|
||||
}
|
||||
if (totalBitrate) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelBitrate'),
|
||||
|
||||
@@ -49,7 +49,8 @@ import ServerConnections from '../ServerConnections';
|
||||
apiClient.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
dataType: 'json'
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}).then(result => {
|
||||
loading.hide();
|
||||
|
||||
|
||||
@@ -74,14 +74,19 @@ import { playbackManager } from './playback/playbackmanager';
|
||||
if (typeof pluginSpec === 'string') {
|
||||
if (pluginSpec in window) {
|
||||
console.log(`Loading plugin (via window): ${pluginSpec}`);
|
||||
let pluginInstance = await window[pluginSpec];
|
||||
|
||||
if (typeof pluginInstance === 'function') {
|
||||
pluginInstance = await new pluginInstance();
|
||||
const pluginDefinition = await window[pluginSpec];
|
||||
if (typeof pluginDefinition !== 'function') {
|
||||
throw new TypeError('Plugin definitions in window have to be an (async) function returning the plugin class');
|
||||
}
|
||||
|
||||
const pluginClass = await pluginDefinition();
|
||||
if (typeof pluginClass !== 'function') {
|
||||
throw new TypeError(`Plugin definition doesn't return a class for '${pluginSpec}'`);
|
||||
}
|
||||
|
||||
// init plugin and pass basic dependencies
|
||||
plugin = new pluginInstance({
|
||||
plugin = new pluginClass({
|
||||
events: Events,
|
||||
loading,
|
||||
appSettings,
|
||||
@@ -98,9 +103,7 @@ import { playbackManager } from './playback/playbackmanager';
|
||||
const pluginResult = await pluginSpec;
|
||||
plugin = new pluginResult.default;
|
||||
} else {
|
||||
const err = new TypeError('Plugins have to be a Promise that resolves to a plugin builder function');
|
||||
console.error(err);
|
||||
throw err;
|
||||
throw new TypeError('Plugins have to be a Promise that resolves to a plugin builder function');
|
||||
}
|
||||
|
||||
return this.#preparePlugin(pluginSpec, plugin);
|
||||
|
||||
@@ -209,11 +209,10 @@ function updateNowPlayingInfo(context, state, serverId) {
|
||||
if (autoFocusContextButton) {
|
||||
contextButton.focus();
|
||||
}
|
||||
const stopPlayback = !!layoutManager.mobile;
|
||||
const options = {
|
||||
play: false,
|
||||
queue: false,
|
||||
stopPlayback: stopPlayback,
|
||||
stopPlayback: true,
|
||||
clearQueue: true,
|
||||
openAlbum: false,
|
||||
positionTo: contextButton
|
||||
|
||||
@@ -62,19 +62,13 @@ import template from './searchfields.template.html';
|
||||
}
|
||||
|
||||
function embed(elem, instance, options) {
|
||||
let html = globalize.translateHtml(template, 'core');
|
||||
|
||||
if (browser.tizen || browser.orsay) {
|
||||
html = html.replace('<input ', '<input readonly ');
|
||||
}
|
||||
|
||||
elem.innerHTML = html;
|
||||
elem.innerHTML = globalize.translateHtml(template, 'core');
|
||||
|
||||
elem.classList.add('searchFields');
|
||||
|
||||
const txtSearch = elem.querySelector('.searchfields-txtSearch');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
if (layoutManager.tv && !browser.tv) {
|
||||
const alphaPickerElement = elem.querySelector('.alphaPicker');
|
||||
|
||||
elem.querySelector('.alphaPicker').classList.remove('hide');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="searchFieldsInner flex align-items-center justify-content-center">
|
||||
<span class="searchfields-icon material-icons search"></span>
|
||||
<div class="inputContainer flex-grow" style="margin-bottom: 0;">
|
||||
<input is="emby-input" class="searchfields-txtSearch" type="text" data-keyboard="false" placeholder="${Search}" autocomplete="off" maxlength="40" autofocus />
|
||||
<input is="emby-input" class="searchfields-txtSearch" type="text" data-keyboard="true" placeholder="${Search}" autocomplete="off" maxlength="40" autofocus />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alphaPicker align-items-center hide"></div>
|
||||
|
||||
@@ -147,6 +147,8 @@ import toast from './toast/toast';
|
||||
MediaType: card.getAttribute('data-mediatype'),
|
||||
Path: card.getAttribute('data-path'),
|
||||
IsFolder: card.getAttribute('data-isfolder') === 'true',
|
||||
StartDate: card.getAttribute('data-startdate'),
|
||||
EndDate: card.getAttribute('data-enddate'),
|
||||
UserData: {
|
||||
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0')
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ function getBackdropImageUrl(item, options, apiClient) {
|
||||
options.type = options.type || 'Backdrop';
|
||||
|
||||
// If not resizing, get the original image
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) {
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height && !options.fillWidth && !options.fillHeight) {
|
||||
options.quality = 100;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ function init(instance) {
|
||||
let inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent);
|
||||
if (inputOffset) {
|
||||
inputOffset = inputOffset[0];
|
||||
inputOffset = parseFloat(inputOffset);
|
||||
inputOffset = Math.min(30, Math.max(-30, inputOffset));
|
||||
|
||||
// replace current text by considered offset
|
||||
this.textContent = inputOffset + 's';
|
||||
|
||||
inputOffset = parseFloat(inputOffset);
|
||||
// set new offset
|
||||
playbackManager.setSubtitleOffset(inputOffset, player);
|
||||
// synchronize with slider value
|
||||
@@ -121,7 +121,7 @@ function getPercentageFromOffset(value) {
|
||||
// convert fraction to percent
|
||||
percentValue *= 50;
|
||||
percentValue += 50;
|
||||
return Math.min(100, Math.max(0, percentValue.toFixed()));
|
||||
return Math.min(100, Math.max(0, percentValue.toFixed(1)));
|
||||
}
|
||||
|
||||
class SubtitleSync {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<button type="button" is="paper-icon-button-light" class="subtitleSync-closeButton"><span class="material-icons close"></span></button>
|
||||
<div class="subtitleSyncTextField" contenteditable="true" spellcheck="false">0s</div>
|
||||
<div class="sliderContainer subtitleSyncSliderContainer">
|
||||
<input is="emby-slider" type="range" step="1" min="0" max="100" value="50" class="subtitleSyncSlider" data-slider-keep-progress="true" />
|
||||
<input is="emby-slider" type="range" step=".1" min="0" max="100" value="50" class="subtitleSyncSlider" data-slider-keep-progress="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,6 @@ class PlaybackCore {
|
||||
onPlaybackStop(stopInfo) {
|
||||
this.lastCommand = null;
|
||||
Events.trigger(this.manager, 'playbackstop', [stopInfo]);
|
||||
this.manager.releaseCurrentPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function (options) {
|
||||
|
||||
const elem = document.createElement('div');
|
||||
elem.classList.add('toast');
|
||||
elem.innerHTML = options.text;
|
||||
elem.textContent = options.text;
|
||||
|
||||
document.body.appendChild(elem);
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
<p id="architecture"></p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1em;">
|
||||
<div class="dashboardActionsContainer">
|
||||
<button is="emby-button" type="button" class="raised btnRefresh">
|
||||
<span>${ButtonScanAllLibraries}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnRestartServer" class="raised" onclick="DashboardPage.restart(this);" style="margin-left:0;">
|
||||
<button is="emby-button" type="button" id="btnRestartServer" class="raised hide" onclick="DashboardPage.restart(this);">
|
||||
<span>${Restart}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnShutdown" class="raised" onclick="DashboardPage.shutdown(this);">
|
||||
|
||||
@@ -40,9 +40,13 @@ import confirm from '../../components/confirm/confirm';
|
||||
text.push(globalize.translate('DirectStreamHelp1'));
|
||||
text.push('<br/>');
|
||||
text.push(globalize.translate('DirectStreamHelp2'));
|
||||
} else if (displayPlayMethod === 'DirectPlay') {
|
||||
title = globalize.translate('DirectPlaying');
|
||||
text.push(globalize.translate('DirectPlayHelp'));
|
||||
} else if (displayPlayMethod === 'Transcode') {
|
||||
title = globalize.translate('Transcoding');
|
||||
text.push(globalize.translate('MediaIsBeingConverted'));
|
||||
text.push(DashboardPage.getSessionNowPlayingStreamInfo(session));
|
||||
|
||||
if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) {
|
||||
text.push('<br/>');
|
||||
@@ -200,6 +204,12 @@ import confirm from '../../components/confirm/confirm';
|
||||
view.querySelector('#operatingSystem').innerHTML = globalize.translate('DashboardOperatingSystem', systemInfo.OperatingSystem);
|
||||
view.querySelector('#architecture').innerHTML = globalize.translate('DashboardArchitecture', systemInfo.SystemArchitecture);
|
||||
|
||||
if (systemInfo.CanSelfRestart) {
|
||||
view.querySelector('#btnRestartServer').classList.remove('hide');
|
||||
} else {
|
||||
view.querySelector('#btnRestartServer').classList.add('hide');
|
||||
}
|
||||
|
||||
view.querySelector('#cachePath').innerHTML = systemInfo.CachePath;
|
||||
view.querySelector('#logPath').innerHTML = systemInfo.LogPath;
|
||||
view.querySelector('#transcodePath').innerHTML = systemInfo.TranscodingTempPath;
|
||||
@@ -245,12 +255,13 @@ import confirm from '../../components/confirm/confirm';
|
||||
} else {
|
||||
const nowPlayingItem = session.NowPlayingItem;
|
||||
const className = 'scalableCard card activeSession backdropCard backdropCard-scalable';
|
||||
const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem);
|
||||
|
||||
html += '<div class="' + className + '" id="' + rowId + '">';
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||
html += '<div class="cardContent">';
|
||||
const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem);
|
||||
html += `<div class="cardContent ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<div class="sessionNowPlayingContent sessionNowPlayingContent-withbackground"';
|
||||
@@ -281,29 +292,19 @@ import confirm from '../../components/confirm/confirm';
|
||||
html += '<div class="sessionNowPlayingTime">' + DashboardPage.getSessionNowPlayingTime(session) + '</div>';
|
||||
html += '</div>';
|
||||
|
||||
if (nowPlayingItem && nowPlayingItem.RunTimeTicks) {
|
||||
const percent = 100 * (session.PlayState.PositionTicks || 0) / nowPlayingItem.RunTimeTicks;
|
||||
html += indicators.getProgressHtml(percent, {
|
||||
containerClass: 'playbackProgress'
|
||||
});
|
||||
} else {
|
||||
// need to leave the element in just in case the device starts playback
|
||||
html += indicators.getProgressHtml(0, {
|
||||
containerClass: 'playbackProgress hide'
|
||||
});
|
||||
}
|
||||
let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks;
|
||||
html += indicators.getProgressHtml(percent || 0, {
|
||||
containerClass: 'playbackProgress'
|
||||
});
|
||||
|
||||
if (session.TranscodingInfo && session.TranscodingInfo.CompletionPercentage) {
|
||||
const percent = session.TranscodingInfo.CompletionPercentage.toFixed(1);
|
||||
html += indicators.getProgressHtml(percent, {
|
||||
containerClass: 'transcodingProgress'
|
||||
});
|
||||
} else {
|
||||
// same issue as playbackProgress element above
|
||||
html += indicators.getProgressHtml(0, {
|
||||
containerClass: 'transcodingProgress hide'
|
||||
});
|
||||
}
|
||||
percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1);
|
||||
html += indicators.getProgressHtml(percent || 0, {
|
||||
containerClass: 'transcodingProgress'
|
||||
});
|
||||
|
||||
html += indicators.getProgressHtml(100, {
|
||||
containerClass: 'backgroundProgress'
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
@@ -316,18 +317,12 @@ import confirm from '../../components/confirm/confirm';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionPlayPause paper-icon-button-light ' + btnCssClass + '"><span class="material-icons ' + playIcon + '"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionStop paper-icon-button-light ' + btnCssClass + '"><span class="material-icons stop"></span></button>';
|
||||
|
||||
btnCssClass = session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length ? '' : ' hide';
|
||||
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionInfo paper-icon-button-light ' + btnCssClass + '" title="' + globalize.translate('ViewPlaybackInfo') + '"><span class="material-icons info"></span></button>';
|
||||
|
||||
btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== ServerConnections.deviceId() ? '' : ' hide';
|
||||
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionSendMessage paper-icon-button-light ' + btnCssClass + '" title="' + globalize.translate('SendMessage') + '"><span class="material-icons message"></span></button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="sessionNowPlayingStreamInfo" style="padding:.5em 0 1em;">';
|
||||
html += DashboardPage.getSessionNowPlayingStreamInfo(session);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="flex align-items-center justify-content-center">';
|
||||
const userImage = DashboardPage.getUserImage(session);
|
||||
html += userImage ? '<div class="activitylogUserPhoto" style="background-image:url(\'' + userImage + "');\"></div>" : '<div style="height:1.71em;"></div>';
|
||||
@@ -409,10 +404,8 @@ import confirm from '../../components/confirm/confirm';
|
||||
} else if (displayPlayMethod === 'DirectStream') {
|
||||
html += globalize.translate('DirectStreaming');
|
||||
} else if (displayPlayMethod === 'Transcode') {
|
||||
html += globalize.translate('Transcoding');
|
||||
|
||||
if (session.TranscodingInfo && session.TranscodingInfo.Framerate) {
|
||||
html += ' (' + session.TranscodingInfo.Framerate + ' fps)';
|
||||
html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`;
|
||||
}
|
||||
|
||||
showTranscodingInfo = true;
|
||||
@@ -556,8 +549,10 @@ import confirm from '../../components/confirm/confirm';
|
||||
|
||||
if (nowPlayingItem) {
|
||||
row.classList.add('playingSession');
|
||||
row.querySelector('.btnSessionInfo').classList.remove('hide');
|
||||
} else {
|
||||
row.classList.remove('playingSession');
|
||||
row.querySelector('.btnSessionInfo').classList.add('hide');
|
||||
}
|
||||
|
||||
if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1) {
|
||||
@@ -566,12 +561,6 @@ import confirm from '../../components/confirm/confirm';
|
||||
row.querySelector('.btnSessionSendMessage').classList.add('hide');
|
||||
}
|
||||
|
||||
if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo) {
|
||||
row.querySelector('.btnSessionInfo').classList.remove('hide');
|
||||
} else {
|
||||
row.querySelector('.btnSessionInfo').classList.add('hide');
|
||||
}
|
||||
|
||||
const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause');
|
||||
|
||||
if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl) {
|
||||
@@ -586,7 +575,6 @@ import confirm from '../../components/confirm/confirm';
|
||||
btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause');
|
||||
btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause');
|
||||
|
||||
row.querySelector('.sessionNowPlayingStreamInfo').innerHTML = DashboardPage.getSessionNowPlayingStreamInfo(session);
|
||||
row.querySelector('.sessionNowPlayingTime').innerHTML = DashboardPage.getSessionNowPlayingTime(session);
|
||||
row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session);
|
||||
row.querySelector('.sessionAppSecondaryText').innerHTML = DashboardPage.getAppSecondaryText(session);
|
||||
@@ -599,30 +587,17 @@ import confirm from '../../components/confirm/confirm';
|
||||
}
|
||||
|
||||
const playbackProgressElem = row.querySelector('.playbackProgress');
|
||||
|
||||
if (nowPlayingItem && nowPlayingItem.RunTimeTicks) {
|
||||
const percent = 100 * (session.PlayState.PositionTicks || 0) / nowPlayingItem.RunTimeTicks;
|
||||
playbackProgressElem.outerHTML = indicators.getProgressHtml(percent, {
|
||||
containerClass: 'playbackProgress'
|
||||
});
|
||||
} else {
|
||||
playbackProgressElem.outerHTML = indicators.getProgressHtml(0, {
|
||||
containerClass: 'playbackProgress hide'
|
||||
});
|
||||
}
|
||||
|
||||
const transcodingProgress = row.querySelector('.transcodingProgress');
|
||||
|
||||
if (session.TranscodingInfo && session.TranscodingInfo.CompletionPercentage) {
|
||||
const percent = session.TranscodingInfo.CompletionPercentage.toFixed(1);
|
||||
transcodingProgress.outerHTML = indicators.getProgressHtml(percent, {
|
||||
containerClass: 'transcodingProgress'
|
||||
});
|
||||
} else {
|
||||
transcodingProgress.outerHTML = indicators.getProgressHtml(0, {
|
||||
containerClass: 'transcodingProgress hide'
|
||||
});
|
||||
}
|
||||
let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks;
|
||||
playbackProgressElem.outerHTML = indicators.getProgressHtml(percent || 0, {
|
||||
containerClass: 'playbackProgress'
|
||||
});
|
||||
|
||||
percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1);
|
||||
transcodingProgress.outerHTML = indicators.getProgressHtml(percent || 0, {
|
||||
containerClass: 'transcodingProgress'
|
||||
});
|
||||
|
||||
const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || '';
|
||||
const imgElem = row.querySelector('.sessionNowPlayingContent');
|
||||
|
||||
@@ -25,7 +25,7 @@ import confirm from '../../../components/confirm/confirm';
|
||||
confirm({
|
||||
text: msg,
|
||||
title: globalize.translate('HeaderDeleteDevices'),
|
||||
confirmText: globalize.translate('ButtonDelete'),
|
||||
confirmText: globalize.translate('Delete'),
|
||||
primary: 'delete'
|
||||
}).then(async () => {
|
||||
loading.show();
|
||||
@@ -96,7 +96,7 @@ import confirm from '../../../components/confirm/confirm';
|
||||
deviceHtml += '<div class="cardBox visualCardBox">';
|
||||
deviceHtml += '<div class="cardScalable">';
|
||||
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">`;
|
||||
const iconUrl = imageHelper.getDeviceIcon(device);
|
||||
|
||||
if (iconUrl) {
|
||||
|
||||
@@ -41,7 +41,7 @@ import confirm from '../../../components/confirm/confirm';
|
||||
html += '<div class="listItem listItem-border">';
|
||||
html += '<span class="listItemIcon material-icons live_tv"></span>';
|
||||
html += '<div class="listItemBody two-line">';
|
||||
html += "<a is='emby-linkbutton' style='padding:0;margin:0;' data-ripple='false' class='clearLink' href='dlnaprofile.html?id=" + profile.Id + "'>";
|
||||
html += "<a is='emby-linkbutton' style='padding:0;margin:0;' data-ripple='false' class='clearLink' href='#!/dlnaprofile.html?id=" + profile.Id + "'>";
|
||||
html += '<div>' + profile.Name + '</div>';
|
||||
html += '</a>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -84,6 +84,13 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkboxListContainer hide fldEnhancedNvdec">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnhancedNvdecDecoder" />
|
||||
<span>${EnableEnhancedNvdecDecoder}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkboxListContainer">
|
||||
<div class="checkboxList">
|
||||
<label>
|
||||
@@ -103,6 +110,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxListContainer checkboxContainer-withDescription fldVppTonemapping hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkVppTonemapping" />
|
||||
<span>${EnableVppTonemapping}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${AllowVppTonemappingHelp}</div>
|
||||
</div>
|
||||
<div class="tonemappingOptions hide">
|
||||
<div class="checkboxListContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
|
||||
@@ -14,6 +14,7 @@ import alert from '../../components/alert';
|
||||
});
|
||||
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
|
||||
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
|
||||
page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder;
|
||||
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
|
||||
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
|
||||
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
|
||||
@@ -26,6 +27,7 @@ import alert from '../../components/alert';
|
||||
page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont;
|
||||
$('#txtVaapiDevice', page).val(config.VaapiDevice || '');
|
||||
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
|
||||
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
|
||||
page.querySelector('#txtOpenclDevice').value = config.OpenclDevice || '';
|
||||
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
|
||||
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
|
||||
@@ -81,6 +83,7 @@ import alert from '../../components/alert';
|
||||
config.VaapiDevice = $('#txtVaapiDevice', form).val();
|
||||
config.OpenclDevice = form.querySelector('#txtOpenclDevice').value;
|
||||
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
|
||||
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
|
||||
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
|
||||
config.TonemappingRange = form.querySelector('#selectTonemappingRange').value;
|
||||
config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value;
|
||||
@@ -101,6 +104,7 @@ import alert from '../../components/alert';
|
||||
});
|
||||
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
|
||||
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
|
||||
config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked;
|
||||
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
|
||||
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
|
||||
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
|
||||
@@ -156,8 +160,19 @@ import alert from '../../components/alert';
|
||||
}];
|
||||
}
|
||||
|
||||
let systemInfo;
|
||||
function getSystemInfo() {
|
||||
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
|
||||
info => {
|
||||
systemInfo = info;
|
||||
return info;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$(document).on('pageinit', '#encodingSettingsPage', function () {
|
||||
const page = this;
|
||||
getSystemInfo();
|
||||
page.querySelector('#selectVideoDecoder').addEventListener('change', function () {
|
||||
if (this.value == 'vaapi') {
|
||||
page.querySelector('.fldVaapiDevice').classList.remove('hide');
|
||||
@@ -181,6 +196,18 @@ import alert from '../../components/alert';
|
||||
page.querySelector('.tonemappingOptions').classList.add('hide');
|
||||
}
|
||||
|
||||
if (this.value == 'nvenc') {
|
||||
page.querySelector('.fldEnhancedNvdec').classList.remove('hide');
|
||||
} else {
|
||||
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) {
|
||||
page.querySelector('.hardwareAccelerationOptions').classList.remove('hide');
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../components/cardbuilder/card.css';
|
||||
import '../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator';
|
||||
import Dashboard, { pageClassOn, pageIdOn } from '../../scripts/clientUtils';
|
||||
import confirm from '../../components/confirm/confirm';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
@@ -168,7 +169,8 @@ import confirm from '../../components/confirm/confirm';
|
||||
showType: false,
|
||||
showLocations: false,
|
||||
showMenu: false,
|
||||
showNameWithIcon: false
|
||||
showNameWithIcon: false,
|
||||
elementId: 'addLibrary'
|
||||
});
|
||||
|
||||
for (let i = 0; i < virtualFolders.length; i++) {
|
||||
@@ -254,11 +256,8 @@ import confirm from '../../components/confirm/confirm';
|
||||
style += 'min-width:33.3%;';
|
||||
}
|
||||
|
||||
if (virtualFolder.Locations.length == 0) {
|
||||
html += '<div id="addLibrary" class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
|
||||
} else {
|
||||
html += '<div class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
|
||||
}
|
||||
const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : '';
|
||||
html += '<div ' + elementId + 'class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
|
||||
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
@@ -276,11 +275,12 @@ import confirm from '../../components/confirm/confirm';
|
||||
let hasCardImageContainer;
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<div class="cardImageContainer editLibrary" style="cursor:pointer;background-image:url(\'' + imgUrl + "');\">";
|
||||
html += `<div class="cardImageContainer editLibrary ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer">`;
|
||||
html += `<img src="${imgUrl}" style="width:100%" />`;
|
||||
hasCardImageContainer = true;
|
||||
} else if (!virtualFolder.showNameWithIcon) {
|
||||
html += '<div class="cardImageContainer editLibrary" style="cursor:pointer;">';
|
||||
html += '<span class="cardImageIcon-small material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '"></span>';
|
||||
html += `<div class="cardImageContainer editLibrary ${cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer;">`;
|
||||
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '"></span>';
|
||||
hasCardImageContainer = true;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ import confirm from '../../components/confirm/confirm';
|
||||
|
||||
if (!imgUrl && virtualFolder.showNameWithIcon) {
|
||||
html += '<h3 class="cardImageContainer addLibrary" style="position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer;flex-direction:column;">';
|
||||
html += '<span class="cardImageIcon-small material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '"></span>';
|
||||
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '"></span>';
|
||||
|
||||
if (virtualFolder.showNameWithIcon) {
|
||||
html += '<div style="margin:1em 0;position:width:100%;">';
|
||||
|
||||
@@ -17,6 +17,18 @@
|
||||
${LabelMaxResumePercentageHelp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtMinAudiobookResume" name="txtMinAudiobookResume" pattern="[0-9]*" required min="0" max="100" label="${LabelMinAudiobookResume}"></input>
|
||||
<div class="fieldDescription">
|
||||
${LabelMinAudiobookResumeHelp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtMaxAudiobookResume" name="txtMaxAudiobookResume" pattern="[0-9]*" required min="1" max="100" label="${LabelMaxAudiobookResume}"></input>
|
||||
<div class="fieldDescription">
|
||||
${LabelMaxAudiobookResumeHelp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtMinResumeDuration" name="txtMinResumeDuration" pattern="[0-9]*" required min="0" label="${LabelMinResumeDuration}"></input>
|
||||
<div class="fieldDescription">
|
||||
|
||||
@@ -9,6 +9,8 @@ import Dashboard from '../../scripts/clientUtils';
|
||||
function loadPage(page, config) {
|
||||
$('#txtMinResumePct', page).val(config.MinResumePct);
|
||||
$('#txtMaxResumePct', page).val(config.MaxResumePct);
|
||||
$('#txtMinAudiobookResume', page).val(config.MinAudiobookResume);
|
||||
$('#txtMaxAudiobookResume', page).val(config.MaxAudiobookResume);
|
||||
$('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds);
|
||||
loading.hide();
|
||||
}
|
||||
@@ -19,6 +21,8 @@ import Dashboard from '../../scripts/clientUtils';
|
||||
ApiClient.getServerConfiguration().then(function (config) {
|
||||
config.MinResumePct = $('#txtMinResumePct', form).val();
|
||||
config.MaxResumePct = $('#txtMaxResumePct', form).val();
|
||||
config.MinAudiobookResume = $('#txtMinAudiobookResume', form).val();
|
||||
config.MaxAudiobookResume = $('#txtMaxAudiobookResume', form).val();
|
||||
config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val();
|
||||
|
||||
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
|
||||
|
||||
@@ -22,6 +22,10 @@ function populateHistory(packageInfo, page) {
|
||||
function populateVersions(packageInfo, page, installedPlugin) {
|
||||
let html = '';
|
||||
|
||||
packageInfo.versions.sort((a, b) => {
|
||||
return b.timestamp < a.timestamp ? -1 : 1;
|
||||
});
|
||||
|
||||
for (let i = 0; i < packageInfo.versions.length; i++) {
|
||||
const version = packageInfo.versions[i];
|
||||
html += '<option value="' + version.version + '">' + globalize.translate('PluginFromRepo', version.version, version.repositoryName) + '</option>';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import loading from '../../../../components/loading/loading';
|
||||
import libraryMenu from '../../../../scripts/libraryMenu';
|
||||
import globalize from '../../../../scripts/globalize';
|
||||
import * as cardBuilder from '../../../../components/cardbuilder/cardBuilder.js';
|
||||
import '../../../../components/cardbuilder/card.css';
|
||||
import '../../../../elements/emby-button/emby-button';
|
||||
import '../../../../elements/emby-checkbox/emby-checkbox';
|
||||
@@ -102,10 +103,20 @@ function getPluginHtml(plugin, options, installedPlugins) {
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||
html += '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + href + '"' + target + '>';
|
||||
html += '<span class="cardImageIcon material-icons folder"></span>';
|
||||
html += '<div class="cardContent">';
|
||||
html += `<a class="cardImageContainer" is="emby-linkbutton" style="margin:0;padding:0" href="${href}" ${target}>`;
|
||||
|
||||
if (plugin.imageUrl) {
|
||||
html += `<img src="${plugin.imageUrl}" style="width:100%" />`;
|
||||
} else {
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="cardImageIcon material-icons extension"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</a>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="cardFooter">';
|
||||
html += "<div class='cardText'>";
|
||||
html += plugin.name;
|
||||
|
||||
@@ -2,12 +2,13 @@ import loading from '../../../../components/loading/loading';
|
||||
import libraryMenu from '../../../../scripts/libraryMenu';
|
||||
import dom from '../../../../scripts/dom';
|
||||
import globalize from '../../../../scripts/globalize';
|
||||
import * as cardBuilder from '../../../../components/cardbuilder/cardBuilder.js';
|
||||
import '../../../../components/cardbuilder/card.css';
|
||||
import '../../../../elements/emby-button/emby-button';
|
||||
import Dashboard, { pageIdOn } from '../../../../scripts/clientUtils';
|
||||
import confirm from '../../../../components/confirm/confirm';
|
||||
|
||||
function deletePlugin(page, uniqueid, name) {
|
||||
function deletePlugin(page, uniqueid, version, name) {
|
||||
const msg = globalize.translate('UninstallPluginConfirmation', name);
|
||||
|
||||
confirm({
|
||||
@@ -17,12 +18,26 @@ function deletePlugin(page, uniqueid, name) {
|
||||
confirmText: globalize.translate('HeaderUninstallPlugin')
|
||||
}).then(function () {
|
||||
loading.show();
|
||||
ApiClient.uninstallPlugin(uniqueid).then(function () {
|
||||
ApiClient.uninstallPluginByVersion(uniqueid, version).then(function () {
|
||||
reloadList(page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function enablePlugin(page, uniqueid, version) {
|
||||
loading.show();
|
||||
ApiClient.enablePlugin(uniqueid, version).then(function () {
|
||||
reloadList(page);
|
||||
});
|
||||
}
|
||||
|
||||
function disablePlugin(page, uniqueid, version) {
|
||||
loading.show();
|
||||
ApiClient.disablePlugin(uniqueid, version).then(function () {
|
||||
reloadList(page);
|
||||
});
|
||||
}
|
||||
|
||||
function showNoConfigurationMessage() {
|
||||
Dashboard.alert({
|
||||
message: globalize.translate('MessageNoPluginConfiguration')
|
||||
@@ -39,16 +54,31 @@ function getPluginCardHtml(plugin, pluginConfigurationPages) {
|
||||
const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) {
|
||||
return pluginConfigurationPage.PluginId == plugin.Id;
|
||||
})[0];
|
||||
|
||||
const configPageUrl = configPage ? Dashboard.getPluginUrl(configPage.Name) : null;
|
||||
let html = '';
|
||||
html += "<div data-id='" + plugin.Id + "' data-name='" + plugin.Name + "' data-removable='" + plugin.CanUninstall + "' class='card backdropCard'>";
|
||||
|
||||
html += `<div data-id='${plugin.Id}' data-version='${plugin.Version}' data-name='${plugin.Name}' data-removable='${plugin.CanUninstall}' data-status='${plugin.Status}' class='card backdropCard'>`;
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||
html += configPageUrl ? '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + configPageUrl + '">' : '<div class="cardContent noConfigPluginCard noHoverEffect cardImageContainer emby-button">';
|
||||
html += '<span class="cardImageIcon material-icons folder"></span>';
|
||||
html += '<div class="cardContent">';
|
||||
if (configPageUrl) {
|
||||
html += `<a class="cardImageContainer" is="emby-linkbutton" style="margin:0;padding:0" href="${configPageUrl}">`;
|
||||
} else {
|
||||
html += '<div class="cardImageContainer noConfigPluginCard noHoverEffect emby-button" style="margin:0;padding:0">';
|
||||
}
|
||||
|
||||
if (plugin.HasImage) {
|
||||
html += `<img src="/Plugins/${plugin.Id}/${plugin.Version}/Image" style="width:100%" />`;
|
||||
} else {
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="cardImageIcon material-icons extension"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += configPageUrl ? '</a>' : '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="cardFooter">';
|
||||
|
||||
if (configPage || plugin.CanUninstall) {
|
||||
@@ -57,12 +87,10 @@ function getPluginCardHtml(plugin, pluginConfigurationPages) {
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += "<div class='cardText'>";
|
||||
html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name;
|
||||
html += '</div>';
|
||||
html += "<div class='cardText cardText-secondary'>";
|
||||
html += plugin.Version;
|
||||
html += '<div class="cardText">';
|
||||
html += `${plugin.Name}<span class='cardText cardText-secondary'>${plugin.Version}</span>`;
|
||||
html += '</div>';
|
||||
html += `<div class="cardText">${globalize.translate('LabelStatus')} ${plugin.Status}</div>`;
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
@@ -113,7 +141,9 @@ function showPluginMenu(page, elem) {
|
||||
const id = card.getAttribute('data-id');
|
||||
const name = card.getAttribute('data-name');
|
||||
const removable = card.getAttribute('data-removable');
|
||||
const configHref = card.querySelector('.cardContent').getAttribute('href');
|
||||
const configHref = card.querySelector('.cardImageContainer').getAttribute('href');
|
||||
const status = card.getAttribute('data-status');
|
||||
const version = card.getAttribute('data-version');
|
||||
const menuItems = [];
|
||||
|
||||
if (configHref) {
|
||||
@@ -125,6 +155,22 @@ function showPluginMenu(page, elem) {
|
||||
}
|
||||
|
||||
if (removable === 'true') {
|
||||
if (status === 'Disabled') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('EnablePlugin'),
|
||||
id: 'enable',
|
||||
icon: 'check_circle_outline'
|
||||
});
|
||||
}
|
||||
|
||||
if (status === 'Active') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('DisablePlugin'),
|
||||
id: 'disable',
|
||||
icon: 'do_not_disturb'
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
name: globalize.translate('ButtonUninstall'),
|
||||
id: 'delete',
|
||||
@@ -142,7 +188,13 @@ function showPluginMenu(page, elem) {
|
||||
Dashboard.navigate(configHref);
|
||||
break;
|
||||
case 'delete':
|
||||
deletePlugin(page, id, name);
|
||||
deletePlugin(page, id, version, name);
|
||||
break;
|
||||
case 'enable':
|
||||
enablePlugin(page, id, version);
|
||||
break;
|
||||
case 'disable':
|
||||
disablePlugin(page, id, version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ function populateList(options) {
|
||||
html += '</div>';
|
||||
if (!options.repositories.length) {
|
||||
options.noneElement.classList.remove('hide');
|
||||
} else {
|
||||
options.noneElement.classList.add('hide');
|
||||
}
|
||||
|
||||
options.listElement.innerHTML = html;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<h2 class="sectionTitle">${TabStreaming}</h2>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" pattern="[0-9]*" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<br />
|
||||
<div class="verticalSection">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" pattern="[0-9]*" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
|
||||
<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>
|
||||
|
||||
@@ -174,7 +174,6 @@ import toast from '../../../components/toast/toast';
|
||||
|
||||
$(document).on('pageinit', '#editUserPage', function () {
|
||||
$('.editUserProfileForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
this.querySelector('.sharingHelp').innerHTML = globalize.translate('OptionAllowLinkSharingHelp', 30);
|
||||
const page = this;
|
||||
$('#chkEnableDeleteAllFolders', this).on('change', function () {
|
||||
if (this.checked) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../../components/indicators/indicators.css';
|
||||
import '../../../assets/css/flexstyles.scss';
|
||||
import Dashboard, { pageIdOn } from '../../../scripts/clientUtils';
|
||||
import confirm from '../../../components/confirm/confirm';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
@@ -93,7 +94,7 @@ import confirm from '../../../components/confirm/confirm';
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-square"></div>';
|
||||
html += '<a is="emby-linkbutton" class="cardContent" href="#!/useredit.html?userId=' + user.Id + '">';
|
||||
html += `<a is="emby-linkbutton" class="cardContent ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" href="#!/useredit.html?userId=${user.Id}">`;
|
||||
let imgUrl;
|
||||
|
||||
if (user.PrimaryImageTag) {
|
||||
@@ -113,7 +114,7 @@ import confirm from '../../../components/confirm/confirm';
|
||||
if (imgUrl) {
|
||||
html += '<div class="' + imageClass + '" style="background-image:url(\'' + imgUrl + "');\">";
|
||||
} else {
|
||||
html += '<div class="' + imageClass + ' flex align-items-center justify-content-center">';
|
||||
html += `<div class="${imageClass} ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()} flex align-items-center justify-content-center">`;
|
||||
html += '<span class="material-icons cardImageIcon person"></span>';
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,8 @@ import ServerConnections from '../components/ServerConnections';
|
||||
}
|
||||
|
||||
elem.innerHTML = html;
|
||||
window.CustomElements.upgradeSubtree(elem);
|
||||
|
||||
const elems = elem.querySelectorAll('.itemsContainer');
|
||||
|
||||
for (let i = 0, length = elems.length; i < length; i++) {
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<div class="detailLogo"></div>
|
||||
<div class="detailPageWrapperContainer">
|
||||
<div class="detailPagePrimaryContainer padded-left padded-right">
|
||||
<div class="primaryImageWrapper hide">
|
||||
<img id="primaryImage" />
|
||||
</div>
|
||||
|
||||
<div class="infoWrapper infoText">
|
||||
<div class="nameContainer"></div>
|
||||
<div class="itemMiscInfo itemMiscInfo-primary" style="margin-bottom: 0.6em;"></div>
|
||||
|
||||
@@ -351,9 +351,13 @@ function reloadPlayButtons(page, item) {
|
||||
const isResumable = item.UserData && item.UserData.PlaybackPositionTicks > 0;
|
||||
hideAll(page, 'btnResume', isResumable);
|
||||
|
||||
if (isResumable) {
|
||||
for (const elem of page.querySelectorAll('.btnPlay')) {
|
||||
elem.querySelector('.detailButton-icon').classList.replace('play_arrow', 'replay');
|
||||
for (const elem of page.querySelectorAll('.btnPlay')) {
|
||||
const btnPlay = elem.querySelector('.detailButton-icon');
|
||||
|
||||
if (isResumable) {
|
||||
btnPlay.classList.replace('play_arrow', 'replay');
|
||||
} else {
|
||||
btnPlay.classList.replace('replay', 'play_arrow');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -552,7 +556,7 @@ function renderDetailPageBackdrop(page, item, apiClient) {
|
||||
let hasbackdrop = false;
|
||||
const itemBackdropElement = page.querySelector('#itemBackdrop');
|
||||
|
||||
if (!layoutManager.mobile && !userSettings.detailsBanner()) {
|
||||
if (layoutManager.mobile || !userSettings.detailsBanner()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -589,6 +593,24 @@ function renderDetailPageBackdrop(page, item, apiClient) {
|
||||
return hasbackdrop;
|
||||
}
|
||||
|
||||
function renderPrimaryImage(page, item, apiClient) {
|
||||
if (item?.ImageTags?.Primary) {
|
||||
const imageUrl = apiClient.getScaledImageUrl(item.Id, {
|
||||
type: 'Primary',
|
||||
maxWidth: dom.getScreenWidth(),
|
||||
tag: item.ImageTags.Primary
|
||||
});
|
||||
|
||||
const imageElem = page.querySelector('#primaryImage');
|
||||
imageElem.src = imageUrl;
|
||||
imageElem.alt = item.Name;
|
||||
if (item.PrimaryImageAspectRatio === 1) {
|
||||
imageElem.classList.add('aspect-square');
|
||||
}
|
||||
page.querySelector('.primaryImageWrapper')?.classList.remove('hide');
|
||||
}
|
||||
}
|
||||
|
||||
function reloadFromItem(instance, page, params, item, user) {
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
|
||||
@@ -601,6 +623,9 @@ function reloadFromItem(instance, page, params, item, user) {
|
||||
renderLogo(page, item, apiClient);
|
||||
renderDetailPageBackdrop(page, item, apiClient);
|
||||
}
|
||||
if (layoutManager.mobile) {
|
||||
renderPrimaryImage(page, item, apiClient);
|
||||
}
|
||||
renderBackdrop(item);
|
||||
|
||||
// Render the main information for the item
|
||||
@@ -720,9 +745,7 @@ function renderLogo(page, item, apiClient) {
|
||||
|
||||
const url = logoImageUrl(item, apiClient, {});
|
||||
|
||||
if (!layoutManager.mobile && !userSettings.enableBackdrops()) {
|
||||
detailLogo.classList.add('hide');
|
||||
} else if (url) {
|
||||
if (url) {
|
||||
detailLogo.classList.remove('hide');
|
||||
imageLoader.setLazyImage(detailLogo, url);
|
||||
} else {
|
||||
@@ -1188,7 +1211,7 @@ function renderMoreFromArtist(view, item, apiClient) {
|
||||
IncludeItemTypes: 'MusicAlbum',
|
||||
Recursive: true,
|
||||
ExcludeItemIds: item.Id,
|
||||
SortBy: 'ProductionYear,SortName',
|
||||
SortBy: 'PremiereDate,ProductionYear,SortName',
|
||||
SortOrder: 'Descending'
|
||||
};
|
||||
|
||||
@@ -1244,7 +1267,7 @@ function renderSimilarItems(page, item, context) {
|
||||
const options = {
|
||||
userId: apiClient.getCurrentUserId(),
|
||||
limit: 12,
|
||||
fields: 'PrimaryImageAspectRatio,UserData,CanDelete'
|
||||
fields: 'PrimaryImageAspectRatio,CanDelete'
|
||||
};
|
||||
|
||||
if (item.Type == 'MusicAlbum' && item.AlbumArtists && item.AlbumArtists.length) {
|
||||
@@ -1370,7 +1393,7 @@ function renderChildren(page, item) {
|
||||
Fields: fields
|
||||
});
|
||||
} else if (item.Type == 'MusicArtist') {
|
||||
query.SortBy = 'ProductionYear,SortName';
|
||||
query.SortBy = 'PremiereDate,ProductionYear,SortName';
|
||||
}
|
||||
|
||||
promise = promise || apiClient.getItems(apiClient.getCurrentUserId(), query);
|
||||
@@ -1772,7 +1795,7 @@ function renderMusicVideos(page, item, user) {
|
||||
}).then(function (result) {
|
||||
if (result.Items.length) {
|
||||
page.querySelector('#musicVideosCollapsible').classList.remove('hide');
|
||||
const musicVideosContent = page.querySelector('.musicVideosContent');
|
||||
const musicVideosContent = page.querySelector('#musicVideosContent');
|
||||
musicVideosContent.innerHTML = getVideosHtml(result.Items);
|
||||
imageLoader.lazyChildren(musicVideosContent);
|
||||
} else {
|
||||
@@ -2073,7 +2096,7 @@ export default function (view, params) {
|
||||
view.addEventListener('viewshow', function (e) {
|
||||
const page = this;
|
||||
|
||||
libraryMenu.setTransparentMenu(true);
|
||||
libraryMenu.setTransparentMenu(!layoutManager.mobile);
|
||||
|
||||
if (e.detail.isRestored) {
|
||||
if (currentItem) {
|
||||
|
||||
@@ -338,7 +338,7 @@ export default function (view, params) {
|
||||
let initialTabIndex = currentTabIndex;
|
||||
let lastFullRender = 0;
|
||||
[].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) {
|
||||
const href = link.href;
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
if (href) {
|
||||
link.href = href + '&serverId=' + ApiClient.serverId();
|
||||
|
||||
@@ -56,75 +56,73 @@ function showSaveMessage(recordingPathChanged) {
|
||||
}
|
||||
}
|
||||
|
||||
export default function () {
|
||||
$(document).on('pageinit', '#liveTvSettingsPage', function () {
|
||||
const page = this;
|
||||
$('.liveTvSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
$('#btnSelectRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtRecordingPath', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#btnSelectMovieRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtMovieRecordingPath', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#btnSelectSeriesRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtSeriesRecordingPath', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#btnSelectPostProcessorPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtPostProcessor', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
$(document).on('pageinit', '#liveTvSettingsPage', function () {
|
||||
const page = this;
|
||||
$('.liveTvSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
$('#btnSelectRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtRecordingPath', page).val(path);
|
||||
}
|
||||
});
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
}).on('pageshow', '#liveTvSettingsPage', function () {
|
||||
loading.show();
|
||||
const page = this;
|
||||
ApiClient.getNamedConfiguration('livetv').then(function (config) {
|
||||
loadPage(page, config);
|
||||
});
|
||||
});
|
||||
}
|
||||
$('#btnSelectMovieRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtMovieRecordingPath', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#btnSelectSeriesRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtSeriesRecordingPath', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
},
|
||||
validateWriteable: true
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#btnSelectPostProcessorPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: directoryBrowser}) => {
|
||||
const picker = new directoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
callback: function (path) {
|
||||
if (path) {
|
||||
$('#txtPostProcessor', page).val(path);
|
||||
}
|
||||
|
||||
picker.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}).on('pageshow', '#liveTvSettingsPage', function () {
|
||||
loading.show();
|
||||
const page = this;
|
||||
ApiClient.getNamedConfiguration('livetv').then(function (config) {
|
||||
loadPage(page, config);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -293,36 +293,34 @@ function onDevicesListClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
export default function () {
|
||||
$(document).on('pageinit', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
$('.btnAddDevice', page).on('click', function () {
|
||||
addDevice(this);
|
||||
});
|
||||
$('.formAddDevice', page).on('submit', function () {
|
||||
submitAddDeviceForm(page);
|
||||
return false;
|
||||
});
|
||||
$('.btnAddProvider', page).on('click', function () {
|
||||
addProvider(this);
|
||||
});
|
||||
page.querySelector('.devicesList').addEventListener('click', onDevicesListClick);
|
||||
}).on('pageshow', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
reload(page);
|
||||
taskButton({
|
||||
mode: 'on',
|
||||
progressElem: page.querySelector('.refreshGuideProgress'),
|
||||
taskKey: 'RefreshGuide',
|
||||
button: page.querySelector('.btnRefresh')
|
||||
});
|
||||
}).on('pagehide', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
taskButton({
|
||||
mode: 'off',
|
||||
progressElem: page.querySelector('.refreshGuideProgress'),
|
||||
taskKey: 'RefreshGuide',
|
||||
button: page.querySelector('.btnRefresh')
|
||||
});
|
||||
$(document).on('pageinit', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
$('.btnAddDevice', page).on('click', function () {
|
||||
addDevice(this);
|
||||
});
|
||||
}
|
||||
$('.formAddDevice', page).on('submit', function () {
|
||||
submitAddDeviceForm(page);
|
||||
return false;
|
||||
});
|
||||
$('.btnAddProvider', page).on('click', function () {
|
||||
addProvider(this);
|
||||
});
|
||||
page.querySelector('.devicesList').addEventListener('click', onDevicesListClick);
|
||||
}).on('pageshow', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
reload(page);
|
||||
taskButton({
|
||||
mode: 'on',
|
||||
progressElem: page.querySelector('.refreshGuideProgress'),
|
||||
taskKey: 'RefreshGuide',
|
||||
button: page.querySelector('.btnRefresh')
|
||||
});
|
||||
}).on('pagehide', '#liveTvStatusPage', function () {
|
||||
const page = this;
|
||||
taskButton({
|
||||
mode: 'off',
|
||||
progressElem: page.querySelector('.refreshGuideProgress'),
|
||||
taskKey: 'RefreshGuide',
|
||||
button: page.querySelector('.btnRefresh')
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
});
|
||||
});
|
||||
const btnSelectView = tabContent.querySelector('.btnSelectView');
|
||||
btnSelectView.addEventListener('click', function (e) {
|
||||
btnSelectView.addEventListener('click', (e) => {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
|
||||
});
|
||||
btnSelectView.addEventListener('layoutchange', function (e) {
|
||||
|
||||
@@ -238,8 +238,8 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
});
|
||||
}
|
||||
const btnSelectView = tabContent.querySelector('.btnSelectView');
|
||||
btnSelectView.addEventListener('click', function (e) {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle, 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
|
||||
btnSelectView.addEventListener('click', (e) => {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
|
||||
});
|
||||
btnSelectView.addEventListener('layoutchange', function (e) {
|
||||
const viewStyle = e.detail.viewStyle;
|
||||
|
||||
@@ -229,7 +229,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
alphaPickerElement.classList.add('alphaPicker-fixed-right');
|
||||
itemsContainer.classList.add('padded-right-withalphapicker');
|
||||
|
||||
tabContent.querySelector('.btnFilter').addEventListener('click', function () {
|
||||
tabContent.querySelector('.btnFilter').addEventListener('click', () => {
|
||||
this.showFilterMenu();
|
||||
});
|
||||
tabContent.querySelector('.btnSort').addEventListener('click', function (e) {
|
||||
|
||||
@@ -24,7 +24,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
function shuffle() {
|
||||
ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) {
|
||||
getQuery();
|
||||
playbackManager.shuffle(item, null);
|
||||
playbackManager.shuffle(item);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
this.showFilterMenu();
|
||||
});
|
||||
const btnSelectView = tabContent.querySelector('.btnSelectView');
|
||||
btnSelectView.addEventListener('click', function (e) {
|
||||
btnSelectView.addEventListener('click', (e) => {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(','));
|
||||
});
|
||||
btnSelectView.addEventListener('layoutchange', function (e) {
|
||||
|
||||
@@ -64,7 +64,7 @@ import ServerConnections from '../../../components/ServerConnections';
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
import('../../../components/appRouter').then(({default: appRouter}) => {
|
||||
import('../../../components/appRouter').then(({appRouter}) => {
|
||||
appRouter.back();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import ServerConnections from '../../../components/ServerConnections';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
|
||||
import baseAlert from '../../../components/alert';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
@@ -131,29 +132,6 @@ import baseAlert from '../../../components/alert';
|
||||
}
|
||||
}
|
||||
|
||||
const metroColors = ['#6FBD45', '#4BB3DD', '#4164A5', '#E12026', '#800080', '#E1B222', '#008040', '#0094FF', '#FF00C7', '#FF870F', '#7F0037'];
|
||||
|
||||
function getRandomMetroColor() {
|
||||
const index = Math.floor(Math.random() * (metroColors.length - 1));
|
||||
return metroColors[index];
|
||||
}
|
||||
|
||||
function getMetroColor(str) {
|
||||
if (str) {
|
||||
const character = String(str.substr(0, 1).charCodeAt());
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < character.length; i++) {
|
||||
sum += parseInt(character.charAt(i));
|
||||
}
|
||||
|
||||
const index = String(sum).substr(-1);
|
||||
return metroColors[index];
|
||||
}
|
||||
|
||||
return getRandomMetroColor();
|
||||
}
|
||||
|
||||
function loadUserList(context, apiClient, users) {
|
||||
let html = '';
|
||||
|
||||
@@ -176,7 +154,7 @@ import baseAlert from '../../../components/alert';
|
||||
html += '<div class="' + cardBoxCssClass + '">';
|
||||
html += '<div class="cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-square"></div>';
|
||||
html += '<div class="cardContent" data-haspw="' + user.HasPassword + '" data-username="' + user.Name + '" data-userid="' + user.Id + '">';
|
||||
html += `<div class="cardContent" style="border-radius:0.2em" data-haspw="${user.HasPassword}" data-username="${user.Name}" data-userid="${user.Id}">`;
|
||||
let imgUrl;
|
||||
|
||||
if (user.PrimaryImageTag) {
|
||||
@@ -185,11 +163,12 @@ import baseAlert from '../../../components/alert';
|
||||
tag: user.PrimaryImageTag,
|
||||
type: 'Primary'
|
||||
});
|
||||
|
||||
html += '<div class="cardImageContainer coveredImage" style="background-image:url(\'' + imgUrl + "');\"></div>";
|
||||
} else {
|
||||
const background = getMetroColor(user.Id);
|
||||
imgUrl = 'assets/img/avatar.png';
|
||||
html += '<div class="cardImageContainer coveredImage" style="background-image:url(\'' + imgUrl + "');background-color:" + background + ';"></div>';
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="material-icons cardImageIcon person"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../../elements/emby-button/emby-button';
|
||||
import Dashboard from '../../../scripts/clientUtils';
|
||||
import ServerConnections from '../../../components/ServerConnections';
|
||||
import alert from '../../../components/alert';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
@@ -64,7 +65,7 @@ import alert from '../../../components/alert';
|
||||
cardContainer += '<div class="cardPadder cardPadder-square">';
|
||||
cardContainer += '</div>';
|
||||
cardContainer += '<div class="cardContent">';
|
||||
cardContainer += '<div class="cardImageContainer coveredImage" style="background:#0288D1;border-radius:.15em;">';
|
||||
cardContainer += `<div class="cardImageContainer coveredImage ${cardBuilder.getDefaultBackgroundClass()}" style="border-radius:0.2em">`;
|
||||
cardContainer += cardImageContainer;
|
||||
cardContainer += '</div>';
|
||||
cardContainer += '</div>';
|
||||
|
||||
@@ -23,7 +23,7 @@ import Dashboard from '../../scripts/clientUtils';
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'Episode',
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,UserData',
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount',
|
||||
IsMissing: false,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Thumb',
|
||||
|
||||
@@ -13,7 +13,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
loading.show();
|
||||
const query = {
|
||||
Limit: 48,
|
||||
Fields: 'AirTime,UserData',
|
||||
Fields: 'AirTime',
|
||||
UserId: ApiClient.getCurrentUserId(),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
margin: 0 0.3em;
|
||||
margin: 0.3em;
|
||||
text-align: center;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
|
||||
@@ -18,6 +18,7 @@ function onAnchorClick(e) {
|
||||
shell.openUrl(href);
|
||||
}
|
||||
} else {
|
||||
e.preventDefault();
|
||||
appRouter.show(href);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html class="preload">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
@@ -11,6 +11,7 @@
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Jellyfin">
|
||||
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta property="og:title" content="Jellyfin">
|
||||
<meta property="og:site_name" content="Jellyfin">
|
||||
<meta property="og:url" content="http://jellyfin.org">
|
||||
|
||||
@@ -6,7 +6,7 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
|
||||
import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
import TableOfContents from './tableOfContents';
|
||||
import browser from '../../scripts/browser';
|
||||
import dom from '../../scripts/dom';
|
||||
import { translateHtml } from '../../scripts/globalize';
|
||||
|
||||
import '../../scripts/dom';
|
||||
@@ -22,18 +22,12 @@ export class BookPlayer {
|
||||
this.id = 'bookplayer';
|
||||
this.priority = 1;
|
||||
|
||||
this.epubOptions = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
// TODO: Add option for scrolled-doc
|
||||
flow: 'paginated'
|
||||
};
|
||||
|
||||
this.onDialogClosed = this.onDialogClosed.bind(this);
|
||||
this.openTableOfContents = this.openTableOfContents.bind(this);
|
||||
this.prevChapter = this.prevChapter.bind(this);
|
||||
this.nextChapter = this.nextChapter.bind(this);
|
||||
this.previous = this.previous.bind(this);
|
||||
this.next = this.next.bind(this);
|
||||
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
}
|
||||
|
||||
play(options) {
|
||||
@@ -109,20 +103,18 @@ export class BookPlayer {
|
||||
|
||||
onWindowKeyUp(e) {
|
||||
const key = keyboardnavigation.getKeyName(e);
|
||||
const rendition = this.rendition;
|
||||
const book = rendition.book;
|
||||
|
||||
if (!this.loaded) return;
|
||||
switch (key) {
|
||||
case 'l':
|
||||
case 'ArrowRight':
|
||||
case 'Right':
|
||||
book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next();
|
||||
this.next();
|
||||
break;
|
||||
case 'j':
|
||||
case 'ArrowLeft':
|
||||
case 'Left':
|
||||
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
||||
this.previous();
|
||||
break;
|
||||
case 'Escape':
|
||||
if (this.tocElement) {
|
||||
@@ -136,6 +128,19 @@ export class BookPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
onTouchStart(e) {
|
||||
if (!this.loaded || !e.touches || e.touches.length === 0) return;
|
||||
|
||||
// epubjs stores pages off the screen or something for preloading
|
||||
// get the modulus of the touch event to account for the increased width
|
||||
const touchX = e.touches[0].clientX % dom.getWindowSize().innerWidth;
|
||||
if (touchX < dom.getWindowSize().innerWidth / 2) {
|
||||
this.previous();
|
||||
} else {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
|
||||
onDialogClosed() {
|
||||
this.stop();
|
||||
}
|
||||
@@ -146,8 +151,8 @@ export class BookPlayer {
|
||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||
elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
|
||||
elem.querySelector('#btnBookplayerToc').addEventListener('click', this.openTableOfContents);
|
||||
elem.querySelector('#btnBookplayerPrev')?.addEventListener('click', this.prevChapter);
|
||||
elem.querySelector('#btnBookplayerNext')?.addEventListener('click', this.nextChapter);
|
||||
elem.querySelector('#btnBookplayerPrev')?.addEventListener('click', this.previous);
|
||||
elem.querySelector('#btnBookplayerNext')?.addEventListener('click', this.next);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
@@ -155,7 +160,7 @@ export class BookPlayer {
|
||||
|
||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||
|
||||
// FIXME: I don't really get why document keyup event is not triggered when epub is in focus
|
||||
this.rendition.on('touchstart', this.onTouchStart);
|
||||
this.rendition.on('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
@@ -165,8 +170,8 @@ export class BookPlayer {
|
||||
elem.removeEventListener('close', this.onDialogClosed);
|
||||
elem.querySelector('#btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
|
||||
elem.querySelector('#btnBookplayerToc').removeEventListener('click', this.openTableOfContents);
|
||||
elem.querySelector('#btnBookplayerPrev')?.removeEventListener('click', this.prevChapter);
|
||||
elem.querySelector('#btnBookplayerNext')?.removeEventListener('click', this.nextChapter);
|
||||
elem.querySelector('#btnBookplayerPrev')?.removeEventListener('click', this.previous);
|
||||
elem.querySelector('#btnBookplayerNext')?.removeEventListener('click', this.next);
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
@@ -176,9 +181,8 @@ export class BookPlayer {
|
||||
|
||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
|
||||
if (this.rendition) {
|
||||
this.rendition.off('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
this.rendition?.off('touchstart', this.onTouchStart);
|
||||
this.rendition?.off('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
openTableOfContents() {
|
||||
@@ -187,14 +191,18 @@ export class BookPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
prevChapter(e) {
|
||||
this.rendition.prev();
|
||||
e.preventDefault();
|
||||
previous(e) {
|
||||
e?.preventDefault();
|
||||
if (this.rendition) {
|
||||
this.rendition.book.package.metadata.direction === 'rtl' ? this.rendition.next() : this.rendition.prev();
|
||||
}
|
||||
}
|
||||
|
||||
nextChapter(e) {
|
||||
this.rendition.next();
|
||||
e.preventDefault();
|
||||
next(e) {
|
||||
e?.preventDefault();
|
||||
if (this.rendition) {
|
||||
this.rendition.book.package.metadata.direction === 'rtl' ? this.rendition.prev() : this.rendition.next();
|
||||
}
|
||||
}
|
||||
|
||||
createMediaElement() {
|
||||
@@ -242,7 +250,14 @@ export class BookPlayer {
|
||||
import('epubjs').then(({default: epubjs}) => {
|
||||
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||
const book = epubjs(downloadHref, {openAs: 'epub'});
|
||||
const rendition = book.renderTo('bookPlayerContainer', this.epubOptions);
|
||||
|
||||
const rendition = book.renderTo('bookPlayerContainer', {
|
||||
width: '100%',
|
||||
// Calculate the height of the window because using 100% is not accurate when the dialog is opening
|
||||
height: document.body.clientHeight,
|
||||
// TODO: Add option for scrolled-doc
|
||||
flow: 'paginated'
|
||||
});
|
||||
|
||||
this.currentSrc = downloadHref;
|
||||
this.rendition = rendition;
|
||||
|
||||
@@ -9,6 +9,8 @@ import ServerConnections from '../../components/ServerConnections';
|
||||
import { Swiper } from 'swiper/swiper-bundle.esm';
|
||||
import 'swiper/swiper-bundle.css';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
export class ComicsPlayer {
|
||||
constructor() {
|
||||
this.name = 'Comics Player';
|
||||
@@ -55,11 +57,29 @@ export class ComicsPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
bindMediaElementEvents() {
|
||||
const elem = this.mediaElement;
|
||||
|
||||
elem?.addEventListener('close', this.onDialogClosed, {once: true});
|
||||
elem?.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, {once: true});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.bindMediaElementEvents();
|
||||
|
||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
unbindMediaElementEvents() {
|
||||
const elem = this.mediaElement;
|
||||
|
||||
elem?.removeEventListener('close', this.onDialogClosed);
|
||||
elem?.querySelector('.btnExit').removeEventListener('click', this.onDialogClosed);
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
this.unbindMediaElementEvents();
|
||||
|
||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
@@ -83,13 +103,16 @@ export class ComicsPlayer {
|
||||
elem.id = 'comicsPlayer';
|
||||
elem.classList.add('slideshowDialog');
|
||||
|
||||
elem.innerHTML = '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
elem.innerHTML = `<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>
|
||||
<div class="actionButtons">
|
||||
<button is="paper-icon-button-light" class="autoSize btnExit" tabindex="-1"><i class="material-icons actionButtonIcon close"></i></button>
|
||||
</div>`;
|
||||
|
||||
this.bindEvents();
|
||||
dialogHelper.open(elem);
|
||||
}
|
||||
|
||||
this.mediaElement = elem;
|
||||
this.bindEvents();
|
||||
return elem;
|
||||
}
|
||||
|
||||
|
||||
16
src/plugins/comicsPlayer/style.scss
Normal file
16
src/plugins/comicsPlayer/style.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
#comicsPlayer {
|
||||
background: #fff;
|
||||
|
||||
.slideshowSwiperContainer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slider-zoom-container {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-slide-img {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -387,11 +387,11 @@ function tryRemoveElement(elem) {
|
||||
let maxBufferLength = 30;
|
||||
let maxMaxBufferLength = 600;
|
||||
|
||||
// chromium based browsers cannot handle huge fragments in high bitrate.
|
||||
// Some browsers cannot handle huge fragments in high bitrate.
|
||||
// This issue usually happens when using HWA encoders with a high bitrate setting.
|
||||
// Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome.
|
||||
// https://github.com/video-dev/hls.js/issues/876
|
||||
if ((browser.chrome || browser.edgeChromium) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) {
|
||||
if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) {
|
||||
maxBufferLength = 6;
|
||||
maxMaxBufferLength = 6;
|
||||
}
|
||||
@@ -1049,11 +1049,11 @@ function tryRemoveElement(elem) {
|
||||
renderSsaAss(videoElement, track, item) {
|
||||
const avaliableFonts = [];
|
||||
const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || [];
|
||||
const apiClient = ServerConnections.getApiClient(item);
|
||||
attachments.map(function (i) {
|
||||
// embedded font url
|
||||
return avaliableFonts.push(i.DeliveryUrl);
|
||||
return avaliableFonts.push(apiClient.getUrl(i.DeliveryUrl));
|
||||
});
|
||||
const apiClient = ServerConnections.getApiClient(item);
|
||||
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ class PlayAccessValidation {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return showErrorMessage();
|
||||
return showErrorMessage().finally(Promise.reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as userSettings from './settings/userSettings';
|
||||
import * as webSettings from './settings/webSettings';
|
||||
import skinManager from './themeManager';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import ServerConnections from '../components/ServerConnections';
|
||||
import { pageClassOn } from '../scripts/clientUtils';
|
||||
|
||||
// Set the default theme when loading
|
||||
skinManager.setTheme(userSettings.theme())
|
||||
@@ -12,6 +12,14 @@ skinManager.setTheme(userSettings.theme())
|
||||
.then(() => document.body.classList.add('force-scroll'));
|
||||
|
||||
// set the saved theme once a user authenticates
|
||||
Events.on(ServerConnections, 'localusersignedin', function (e, user) {
|
||||
Events.on(ServerConnections, 'localusersignedin', () => {
|
||||
skinManager.setTheme(userSettings.theme());
|
||||
});
|
||||
|
||||
pageClassOn('viewbeforeshow', 'page', function () {
|
||||
if (this.classList.contains('type-interior')) {
|
||||
skinManager.setTheme(userSettings.dashboardTheme());
|
||||
} else {
|
||||
skinManager.setTheme(userSettings.theme());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -210,7 +210,7 @@ if (!browser.chrome && !browser.edgeChromium && !browser.edge && !browser.opera
|
||||
browser.safari = true;
|
||||
}
|
||||
|
||||
browser.osx = userAgent.toLowerCase().indexOf('os x') !== -1;
|
||||
browser.osx = userAgent.toLowerCase().indexOf('mac os x') !== -1;
|
||||
|
||||
// This is a workaround to detect iPads on iOS 13+ that report as desktop Safari
|
||||
// This may break in the future if Apple releases a touchscreen Mac
|
||||
@@ -240,6 +240,9 @@ browser.edgeUwp = browser.edge && (userAgent.toLowerCase().indexOf('msapphost')
|
||||
if (!browser.tizen) {
|
||||
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
|
||||
} else {
|
||||
// UserAgent string contains 'Safari' and 'safari' is set by matched browser, but we only want 'tizen' to be true
|
||||
delete browser.safari;
|
||||
|
||||
const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/);
|
||||
browser.tizenVersion = parseInt(v[1]);
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ import browser from './browser';
|
||||
supported = browser.tizen;
|
||||
break;
|
||||
case 'mov':
|
||||
supported = browser.tizen || browser.web0s || browser.chrome || browser.edgeChromium || browser.edgeUwp;
|
||||
supported = browser.safari || browser.tizen || browser.web0s || browser.chrome || browser.edgeChromium || browser.edgeUwp;
|
||||
videoCodecs.push('h264');
|
||||
break;
|
||||
case 'm2ts':
|
||||
@@ -414,8 +414,10 @@ import browser from './browser';
|
||||
|
||||
if (canPlayAudioFormat('opus')) {
|
||||
videoAudioCodecs.push('opus');
|
||||
hlsInTsVideoAudioCodecs.push('opus');
|
||||
webmAudioCodecs.push('opus');
|
||||
if (browser.tizen) {
|
||||
hlsInTsVideoAudioCodecs.push('opus');
|
||||
}
|
||||
}
|
||||
|
||||
if (canPlayAudioFormat('flac')) {
|
||||
@@ -463,6 +465,10 @@ import browser from './browser';
|
||||
if (!browser.safari) {
|
||||
mp4VideoCodecs.push('hevc');
|
||||
}
|
||||
|
||||
if (browser.tizen || browser.web0s) {
|
||||
hlsInTsVideoCodecs.push('hevc');
|
||||
}
|
||||
}
|
||||
|
||||
if (supportsMpeg2Video()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export function logout() {
|
||||
}
|
||||
|
||||
export function getPluginUrl(name) {
|
||||
return '#!/configurationpage?name=' + encodeURIComponent(name);
|
||||
return 'configurationpage?name=' + encodeURIComponent(name);
|
||||
}
|
||||
|
||||
export function navigate(url, preserveQueryString) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { arDZ, be, bg, ca, cs, da, de, el, enGB, enUS, es, faIR, fi, fr, frCA, he, hi, hr, hu, id, it, ja, kk, ko, lt, ms, nb,
|
||||
nl, pl, ptBR, pt, ro, ru, sk, sl, sv, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
|
||||
import { af, arDZ, be, bg, bn, ca, cs, da, de, el, enGB, enUS, eo, es, faIR, fi, fr, frCA, he, hi, hr, hu, gl, id, is, it, ja, kk, ko, lt, ms, nb,
|
||||
nl, pl, ptBR, pt, ro, ru, sk, sl, sv, ta, th, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
|
||||
import globalize from './globalize';
|
||||
|
||||
const dateLocales = (locale) => ({
|
||||
'af': af,
|
||||
'ar': arDZ,
|
||||
'be-by': be,
|
||||
'bg-bg': bg,
|
||||
'bn': bn,
|
||||
'ca': ca,
|
||||
'cs': cs,
|
||||
'da': da,
|
||||
@@ -13,19 +15,23 @@ const dateLocales = (locale) => ({
|
||||
'el': el,
|
||||
'en-gb': enGB,
|
||||
'en-us': enUS,
|
||||
'eo': eo,
|
||||
'es': es,
|
||||
'es-ar': es,
|
||||
'es-do': es,
|
||||
'es-mx': es,
|
||||
'fa': faIR,
|
||||
'fi': fi,
|
||||
'fr': fr,
|
||||
'fr-ca': frCA,
|
||||
'gl': gl,
|
||||
'gsw': de,
|
||||
'he': he,
|
||||
'hi-in': hi,
|
||||
'hr': hr,
|
||||
'hu': hu,
|
||||
'id': id,
|
||||
'is': is,
|
||||
'it': it,
|
||||
'ja': ja,
|
||||
'kk': kk,
|
||||
@@ -35,6 +41,7 @@ const dateLocales = (locale) => ({
|
||||
'nb': nb,
|
||||
'nl': nl,
|
||||
'pl': pl,
|
||||
'pt': pt,
|
||||
'pt-br': ptBR,
|
||||
'pt-pt': pt,
|
||||
'ro': ro,
|
||||
@@ -42,6 +49,8 @@ const dateLocales = (locale) => ({
|
||||
'sk': sk,
|
||||
'sl-si': sl,
|
||||
'sv': sv,
|
||||
'ta': ta,
|
||||
'th': th,
|
||||
'tr': tr,
|
||||
'uk': uk,
|
||||
'vi': vi,
|
||||
|
||||
@@ -235,6 +235,9 @@ import { appHost } from '../components/apphost';
|
||||
},
|
||||
'repeatone': () => {
|
||||
playbackManager.setRepeatMode('RepeatOne');
|
||||
},
|
||||
'unknown': () => {
|
||||
// This is the command given by 'notify', it's a no-op
|
||||
}
|
||||
})[command];
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ function renderSection(page, item, element, type) {
|
||||
Limit: 10,
|
||||
SortBy: 'SortName'
|
||||
}, {
|
||||
shape: 'overflowPortrait',
|
||||
shape: 'overflowBackdrop',
|
||||
showTitle: true,
|
||||
centerText: true,
|
||||
overlayPlayButton: true
|
||||
@@ -195,7 +195,7 @@ function renderSection(page, item, element, type) {
|
||||
ArtistIds: '',
|
||||
AlbumArtistIds: '',
|
||||
SortOrder: 'Descending',
|
||||
SortBy: 'ProductionYear,Sortname'
|
||||
SortBy: 'PremiereDate,ProductionYear,Sortname'
|
||||
}, {
|
||||
shape: 'overflowSquare',
|
||||
playFromHere: true,
|
||||
@@ -329,7 +329,7 @@ function addCurrentItemToQuery(query, item) {
|
||||
} else if (item.Type === 'Studio') {
|
||||
query.StudioIds = item.Id;
|
||||
} else if (item.Type === 'MusicArtist') {
|
||||
query.AlbumArtistIds = item.Id;
|
||||
query.ArtistIds = item.Id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -464,6 +464,7 @@ import Headroom from 'headroom.js';
|
||||
pageIds: ['liveTvSettingsPage'],
|
||||
icon: 'dvr'
|
||||
});
|
||||
addPluginPagesToMainMenu(links, pluginItems, 'livetv');
|
||||
links.push({
|
||||
divider: true,
|
||||
name: globalize.translate('TabAdvanced')
|
||||
@@ -504,14 +505,27 @@ import Headroom from 'headroom.js';
|
||||
pageIds: ['scheduledTasksPage', 'scheduledTaskPage'],
|
||||
icon: 'schedule'
|
||||
});
|
||||
addPluginPagesToMainMenu(links, pluginItems);
|
||||
if (hasUnsortedPlugins(pluginItems)) {
|
||||
links.push({
|
||||
divider: true,
|
||||
name: globalize.translate('TabPlugins')
|
||||
});
|
||||
addPluginPagesToMainMenu(links, pluginItems);
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function addPluginPagesToMainMenu(links, pluginItems, section) {
|
||||
for (let i = 0, length = pluginItems.length; i < length; i++) {
|
||||
const pluginItem = pluginItems[i];
|
||||
function hasUnsortedPlugins(pluginItems) {
|
||||
for (const pluginItem of pluginItems) {
|
||||
if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addPluginPagesToMainMenu(links, pluginItems, section) {
|
||||
for (const pluginItem of pluginItems) {
|
||||
if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) {
|
||||
links.push({
|
||||
name: pluginItem.DisplayName,
|
||||
|
||||
@@ -3,7 +3,7 @@ import listView from '../components/listview/listview';
|
||||
function getFetchPlaylistItemsFn(itemId) {
|
||||
return function () {
|
||||
const query = {
|
||||
Fields: 'PrimaryImageAspectRatio,UserData',
|
||||
Fields: 'PrimaryImageAspectRatio',
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
UserId: ApiClient.getCurrentUserId()
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
let data;
|
||||
import DefaultConfig from '../../config.json';
|
||||
|
||||
let data;
|
||||
const urlResolver = document.createElement('a');
|
||||
|
||||
// `fetch` with `file:` support
|
||||
@@ -55,30 +56,14 @@ async function getConfig() {
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.warn('failed to fetch the web config file:', error);
|
||||
return getDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async function getDefaultConfig() {
|
||||
try {
|
||||
const response = await fetchLocal('config.template.json', {
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('network response was not ok');
|
||||
}
|
||||
|
||||
data = await response.json();
|
||||
data = DefaultConfig;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('failed to fetch the default web config file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export function getIncludeCorsCredentials() {
|
||||
return getConfig()
|
||||
.then(config => config.includeCorsCredentials)
|
||||
.then(config => !!config.includeCorsCredentials)
|
||||
.catch(error => {
|
||||
console.log('cannot get web config:', error);
|
||||
return false;
|
||||
@@ -87,7 +72,7 @@ export function getIncludeCorsCredentials() {
|
||||
|
||||
export function getMultiServer() {
|
||||
return getConfig().then(config => {
|
||||
return config.multiserver;
|
||||
return !!config.multiserver;
|
||||
}).catch(error => {
|
||||
console.log('cannot get web config:', error);
|
||||
return false;
|
||||
@@ -126,13 +111,16 @@ const checkDefaultTheme = (themes) => {
|
||||
|
||||
export function getThemes() {
|
||||
return getConfig().then(config => {
|
||||
const themes = Array.isArray(config.themes) ? config.themes : [];
|
||||
if (!Array.isArray(config.themes)) {
|
||||
console.error('web config is invalid, missing themes:', config);
|
||||
}
|
||||
const themes = Array.isArray(config.themes) ? config.themes : DefaultConfig.themes;
|
||||
checkDefaultTheme(themes);
|
||||
return themes;
|
||||
}).catch(error => {
|
||||
console.log('cannot get web config:', error);
|
||||
checkDefaultTheme();
|
||||
return [];
|
||||
return DefaultConfig.themes;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,9 +128,12 @@ export const getDefaultTheme = () => internalDefaultTheme;
|
||||
|
||||
export function getPlugins() {
|
||||
return getConfig().then(config => {
|
||||
return config.plugins;
|
||||
if (!config.plugins) {
|
||||
console.error('web config is invalid, missing plugins:', config);
|
||||
}
|
||||
return config.plugins || DefaultConfig.plugins;
|
||||
}).catch(error => {
|
||||
console.log('cannot get web config:', error);
|
||||
return [];
|
||||
return DefaultConfig.plugins;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'intersection-observer';
|
||||
import 'classlist.js';
|
||||
import 'whatwg-fetch';
|
||||
import 'resize-observer-polyfill';
|
||||
import 'jellyfin-noto/font-faces.css';
|
||||
import '../assets/css/site.scss';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import ServerConnections from '../components/ServerConnections';
|
||||
@@ -69,7 +68,7 @@ window.getParameterByName = function(name, url) {
|
||||
};
|
||||
|
||||
function loadCoreDictionary() {
|
||||
const languages = ['ar', 'be-by', 'bg-bg', 'ca', 'cs', 'da', 'de', 'el', 'en-gb', 'en-us', 'es', 'es-ar', 'es-mx', 'fa', 'fi', 'fr', 'fr-ca', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'ms', 'nb', 'nl', 'pl', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sv', 'tr', 'uk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
|
||||
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'mr', 'ms', 'nb', 'nl', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
|
||||
const translations = languages.map(function (language) {
|
||||
return {
|
||||
lang: language,
|
||||
@@ -169,8 +168,11 @@ function initSyncPlay() {
|
||||
|
||||
// Start SyncPlay.
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
SyncPlay.Manager.init(apiClient);
|
||||
if (apiClient) SyncPlay.Manager.init(apiClient);
|
||||
SyncPlayToasts.init();
|
||||
|
||||
// FIXME: Multiple apiClients?
|
||||
Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient));
|
||||
}
|
||||
|
||||
function onAppReady() {
|
||||
@@ -243,15 +245,11 @@ function onAppReady() {
|
||||
function registerServiceWorker() {
|
||||
/* eslint-disable compat/compat */
|
||||
if (navigator.serviceWorker && window.appMode !== 'cordova' && window.appMode !== 'android') {
|
||||
try {
|
||||
navigator.serviceWorker.register('/serviceworker.js').then(() =>
|
||||
console.log('serviceWorker registered')
|
||||
).catch(error =>
|
||||
console.log('error registering serviceWorker: ' + error)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('error registering serviceWorker: ' + err);
|
||||
}
|
||||
navigator.serviceWorker.register('serviceworker.js').then(() =>
|
||||
console.log('serviceWorker registered')
|
||||
).catch(error =>
|
||||
console.log('error registering serviceWorker: ' + error)
|
||||
);
|
||||
} else {
|
||||
console.warn('serviceWorker unsupported');
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@
|
||||
"LabelCustomCss": "CSS по избор:",
|
||||
"LabelCustomCssHelp": "Добавете собствен стил към уеб-интерфейса.",
|
||||
"LabelCustomRating": "Оценка по избор:",
|
||||
"LabelDashboardTheme": "Облик на сървърното табло:",
|
||||
"LabelDateAdded": "Дата на добавяне:",
|
||||
"LabelDateTimeLocale": "Местоположение за дата и час:",
|
||||
"LabelDay": "Ден:",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user