diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 4ee827471a..02dfd18aac 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -2,4 +2,4 @@ version: 1 update_configs: - package_manager: "javascript" directory: "/" - update_schedule: "live" + update_schedule: "weekly" diff --git a/.eslintrc.js b/.eslintrc.js index 6fd4392e60..e5ee2dfe86 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,5 @@ +const restrictedGlobals = require('confusing-browser-globals'); + module.exports = { root: true, plugins: [ @@ -39,14 +41,15 @@ module.exports = { 'no-floating-decimal': ['error'], 'no-multi-spaces': ['error'], 'no-multiple-empty-lines': ['error', { 'max': 1 }], + 'no-restricted-globals': ['error'].concat(restrictedGlobals), 'no-trailing-spaces': ['error'], '@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], - //'no-unused-vars': ['error', { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': true }], 'one-var': ['error', 'never'], 'padded-blocks': ['error', 'never'], - //'prefer-const': ['error', {'destructuring': 'all'}], + 'prefer-const': ['error', {'destructuring': 'all'}], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], '@babel/semi': ['error'], + 'no-var': ['error'], 'space-before-blocks': ['error'], 'space-infix-ops': 'error', 'yoda': 'error' @@ -81,7 +84,6 @@ module.exports = { 'ApiClient': 'writable', 'AppInfo': 'writable', 'chrome': 'writable', - 'ConnectionManager': 'writable', 'DlnaProfilePage': 'writable', 'Dashboard': 'writable', 'DashboardPage': 'writable', diff --git a/.gitignore b/.gitignore index 36b843f022..98aa2d974b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ dist web node_modules +# config +config.json + # ide .idea .vscode diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 46c40b6c99..1bdf1cd903 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -34,10 +34,13 @@ - [Ryan Hartzell](https://github.com/ryan-hartzell) - [Thibault Nocchi](https://github.com/ThibaultNocchi) - [MrTimscampi](https://github.com/MrTimscampi) + - [ConfusedPolarBear](https://github.com/ConfusedPolarBear) - [Sarab Singh](https://github.com/sarab97) - [GuilhermeHideki](https://github.com/GuilhermeHideki) - [Andrei Oanca](https://github.com/OancaAndrei) - [Cromefire_](https://github.com/cromefire) + - [Orry Verducci](https://github.com/orryverducci) + - [Camc314](https://github.com/camc314) # Emby Contributors diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index b8c77f2a1f..c35a1caab2 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -14,6 +14,9 @@ BuildRequires: yarn %else BuildRequires: nodejs-yarn %endif +# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it +# ditto for Fedora's yarn RPM +BuildRequires: git BuildArch: noarch # Disable Automatic Dependency Processing diff --git a/package.json b/package.json index a6f3e8724d..399fb4700e 100644 --- a/package.json +++ b/package.json @@ -5,27 +5,28 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.11.1", - "@babel/eslint-parser": "^7.11.3", - "@babel/eslint-plugin": "^7.11.3", + "@babel/core": "^7.12.3", + "@babel/eslint-parser": "^7.12.1", + "@babel/eslint-plugin": "^7.12.1", "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.5", - "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/polyfill": "^7.12.1", + "@babel/preset-env": "^7.12.1", "autoprefixer": "^9.8.6", "babel-loader": "^8.0.6", - "browser-sync": "^2.26.12", + "browser-sync": "^2.26.13", + "confusing-browser-globals": "^1.0.10", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.2.1", + "css-loader": "^5.0.0", "cssnano": "^4.1.10", - "del": "^5.1.0", - "eslint": "^7.7.0", + "del": "^6.0.0", + "eslint": "^7.12.0", "eslint-plugin-compat": "^3.5.1", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.21.2", + "eslint-plugin-import": "^2.22.1", "eslint-plugin-promise": "^4.2.1", - "file-loader": "^6.0.0", + "file-loader": "^6.1.1", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-cli": "^2.3.0", @@ -38,49 +39,51 @@ "gulp-postcss": "^8.0.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-terser": "^1.3.2", - "html-webpack-plugin": "^4.3.0", + "gulp-terser": "^1.4.0", + "html-webpack-plugin": "^4.5.0", "lazypipe": "^1.0.2", "node-sass": "^4.13.1", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", - "style-loader": "^1.1.3", - "stylelint": "^13.6.1", + "style-loader": "^2.0.0", + "stylelint": "^13.7.2", "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.1.0", - "webpack": "^4.44.1", + "webpack": "^5.2.0", "webpack-merge": "^4.2.2", - "webpack-stream": "^5.2.1" + "webpack-stream": "^6.1.0", + "worker-plugin": "^5.0.0" }, "dependencies": { "alameda": "^1.4.0", "blurhash": "^1.1.3", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "core-js": "^3.6.5", - "date-fns": "^2.15.0", + "date-fns": "^2.16.1", "epubjs": "^0.3.85", "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", - "headroom.js": "^0.11.0", - "hls.js": "^0.14.8", - "howler": "^2.2.0", + "headroom.js": "^0.12.0", + "hls.js": "^0.14.16", + "howler": "^2.2.1", "intersection-observer": "^0.11.0", - "jellyfin-apiclient": "^1.4.1", + "jellyfin-apiclient": "^1.4.2", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", "jstree": "^3.3.10", + "libarchive.js": "^1.3.0", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", - "material-design-icons-iconfont": "^5.0.1", + "material-design-icons-iconfont": "^6.1.0", "native-promise-only": "^0.8.0-a", "page": "^1.11.6", - "query-string": "^6.13.1", + "query-string": "^6.13.6", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", - "sortablejs": "^1.10.2", - "swiper": "^5.4.5", + "sortablejs": "^1.12.0", + "swiper": "^6.3.4", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.4.0" + "whatwg-fetch": "^3.4.1" }, "babel": { "presets": [ @@ -163,6 +166,8 @@ "src/components/playmenu.js", "src/components/pluginManager.js", "src/components/prompt/prompt.js", + "src/components/qualityOptions.js", + "src/components/quickConnectSettings/quickConnectSettings.js", "src/components/recordingcreator/recordingbutton.js", "src/components/recordingcreator/recordingcreator.js", "src/components/recordingcreator/seriesrecordingeditor.js", @@ -170,13 +175,13 @@ "src/components/refreshdialog/refreshdialog.js", "src/components/recordingcreator/recordingeditor.js", "src/components/recordingcreator/recordingfields.js", - "src/components/qualityOptions.js", "src/components/remotecontrol/remotecontrol.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/plugins/experimentalWarnings/plugin.js", "src/plugins/sessionPlayer/plugin.js", "src/plugins/htmlAudioPlayer/plugin.js", + "src/plugins/comicsPlayer/plugin.js", "src/plugins/chromecastPlayer/plugin.js", "src/components/slideshow/slideshow.js", "src/components/sortmenu/sortmenu.js", @@ -209,7 +214,7 @@ "src/components/castSenderApi.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", - "src/controllers/session/redeemPassword/index.js", + "src/controllers/session/resetPassword/index.js", "src/controllers/session/login/index.js", "src/controllers/session/selectServer/index.js", "src/controllers/dashboard/apikeys.js", @@ -240,6 +245,7 @@ "src/controllers/dashboard/plugins/installed/index.js", "src/controllers/dashboard/plugins/available/index.js", "src/controllers/dashboard/plugins/repositories/index.js", + "src/controllers/dashboard/quickConnect.js", "src/controllers/dashboard/scheduledtasks/scheduledtask.js", "src/controllers/dashboard/scheduledtasks/scheduledtasks.js", "src/controllers/dashboard/serveractivity.js", @@ -288,6 +294,7 @@ "src/controllers/user/menu/index.js", "src/controllers/user/playback/index.js", "src/controllers/user/profile/index.js", + "src/controllers/user/quickConnect/index.js", "src/controllers/user/subtitles/index.js", "src/controllers/wizard/finish/index.js", "src/controllers/wizard/remote/index.js", @@ -320,10 +327,12 @@ "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", + "src/plugins/chromecastPlayer/chromecastHelper.js", "src/plugins/photoPlayer/plugin.js", "src/plugins/youtubePlayer/plugin.js", "src/scripts/alphanumericshortcuts.js", "src/scripts/autoBackdrops.js", + "src/scripts/autocast.js", "src/scripts/browser.js", "src/scripts/clientUtils.js", "src/scripts/datetime.js", diff --git a/scripts/unused.py b/scripts/unused.py index 12af27320a..abbc399cf6 100644 --- a/scripts/unused.py +++ b/scripts/unused.py @@ -16,7 +16,7 @@ langlst.append('en-us.json') dep = [] def grep(key): - command = 'grep -r -E "(\(\\\"|\(\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key + command = 'grep -r -E "(\\\"|\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = p.stdout.readlines() if output: diff --git a/src/assets/css/fonts.css b/src/assets/css/fonts.css index cb0da0f80f..6e87f11d9d 100644 --- a/src/assets/css/fonts.css +++ b/src/assets/css/fonts.css @@ -1,7 +1,5 @@ html { font-family: "Noto Sans", sans-serif; - font-size: 93%; - -webkit-text-size-adjust: 100%; text-size-adjust: 100%; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; @@ -29,7 +27,9 @@ h3 { } .layout-tv { - font-size: 130%; + /* Per WebOS and Tizen guidelines, fonts must be 20px minimum. + This takes the 16px baseline and multiplies it by 1.25 to get 20px. */ + font-size: 125%; } .layout-mobile { diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 643fb9ca97..c9ee82c8a0 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -28,6 +28,10 @@ padding-top: 0 !important; } +.layout-tv .itemDetailPage { + padding-top: 4.2em !important; +} + .standalonePage { padding-top: 4.5em !important; } @@ -163,6 +167,12 @@ transition: background ease-in-out 0.5s; } +.layout-tv .skinHeader { + /* In TV layout, it makes more sense to keep the top bar at the top of the page + Having it follow the view only makes us lose vertical space, while not being focusable */ + position: relative; +} + .hiddenViewMenuBar .skinHeader { display: none; } @@ -447,8 +457,7 @@ height: 26.5vh; } -.layout-desktop .itemBackdrop::after, -.layout-tv .itemBackdrop::after { +.layout-desktop .itemBackdrop::after { content: ""; width: 100%; height: 100%; @@ -456,8 +465,8 @@ display: block; } -.layout-desktop .noBackdrop .itemBackdrop, -.layout-tv .noBackdrop .itemBackdrop { +.layout-tv .itemBackdrop, +.layout-desktop .noBackdrop .itemBackdrop { display: none; } @@ -624,6 +633,10 @@ z-index: 2; } +.layout-tv .detailPagePrimaryContainer { + display: block; +} + .layout-mobile .detailPagePrimaryContainer { display: block; position: relative; @@ -637,12 +650,16 @@ padding-left: 32.45vw; } -.layout-desktop .detailRibbon, -.layout-tv .detailRibbon { +.layout-desktop .detailRibbon { margin-top: -7.2em; height: 7.2em; } +.layout-tv .detailRibbon { + margin-top: 0; + height: inherit; +} + .layout-desktop .noBackdrop .detailRibbon, .layout-tv .noBackdrop .detailRibbon { margin-top: 0; @@ -748,8 +765,7 @@ div.itemDetailGalleryLink.defaultCardBackground { position: relative; } - .layout-desktop .itemBackdrop, - .layout-tv .itemBackdrop { + .layout-desktop .itemBackdrop { height: 40vh; } @@ -775,13 +791,8 @@ div.itemDetailGalleryLink.defaultCardBackground { } .emby-button.detailFloatingButton { - position: absolute; - background-color: rgba(0, 0, 0, 0.5); - z-index: 3; - top: 100%; - left: 90%; - margin: -2.2em 0 0 -2.2em; - padding: 0.4em; + font-size: 1.4em; + margin-right: 0.5em !important; color: rgba(255, 255, 255, 0.76); } @@ -844,7 +855,7 @@ div.itemDetailGalleryLink.defaultCardBackground { -webkit-align-items: center; align-items: center; margin: 0 !important; - padding: 0.5em 0.7em !important; + padding: 0.7em 0.7em !important; } @media all and (min-width: 29em) { @@ -913,10 +924,6 @@ div.itemDetailGalleryLink.defaultCardBackground { } @media all and (min-width: 100em) { - .detailFloatingButton { - display: none !important; - } - .personBackdrop { display: none !important; } @@ -925,6 +932,11 @@ div.itemDetailGalleryLink.defaultCardBackground { font-size: 108%; margin: 1.25em 0; } + + .layout-tv .mainDetailButtons { + font-size: 108%; + margin: 1em 0 1.25em; + } } @media all and (max-width: 50em) { @@ -1140,13 +1152,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { } .layout-tv .padded-top-focusscale { - padding-top: 1em; - margin-top: -1em; + padding-top: 1.5em; + margin-top: -1.5em; } .layout-tv .padded-bottom-focusscale { - padding-bottom: 1em; - margin-bottom: -1em; + padding-bottom: 1.5em; + margin-bottom: -1.5em; } @media all and (min-height: 31.25em) { diff --git a/src/assets/css/videoosd.css b/src/assets/css/videoosd.css index 59a485468d..b2446d5d48 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -6,29 +6,42 @@ -ms-user-select: none; } -.osdPoster img, .videoOsdBottom { bottom: 0; left: 0; right: 0; + position: fixed; + background: linear-gradient(0deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%); + padding-top: 7.5em; + padding-bottom: 1.75em; + display: flex; + flex-direction: row; + justify-content: center; + will-change: opacity; + transition: opacity 0.3s ease-out; + color: #fff; + user-select: none; + -webkit-touch-callout: none; } -.osdHeader { - -webkit-transition: opacity 0.3s ease-out; - -o-transition: opacity 0.3s ease-out; +.skinHeader-withBackground.osdHeader { transition: opacity 0.3s ease-out; position: relative; z-index: 1; - background: rgba(0, 0, 0, 0.7) !important; - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; - color: #eee !important; + background: linear-gradient(180deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%); + backdrop-filter: none; + color: #eee; + height: 7.5em; } .osdHeader-hidden { opacity: 0; } +.osdHeader .headerTop { + max-height: 3.5em; +} + .osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) { display: none; } @@ -86,34 +99,17 @@ opacity: 0.6; } -.videoOsdBottom { - position: fixed; - background-color: rgba(0, 0, 0, 0.7); - padding: 1%; - display: -webkit-box; - display: -webkit-flex; - display: flex; - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -webkit-flex-direction: row; - flex-direction: row; - will-change: opacity; - -webkit-transition: opacity 0.3s ease-out; - -o-transition: opacity 0.3s ease-out; - transition: opacity 0.3s ease-out; - color: #fff; - user-select: none; - -webkit-touch-callout: none; -} - .videoOsdBottom-hidden { opacity: 0; } .osdControls { - -webkit-box-flex: 1; - -webkit-flex-grow: 1; flex-grow: 1; + padding: 0 0.8em; +} + +.layout-desktop .osdControls { + max-width: calc(100vh * 1.77 - 2vh); } .videoOsdBottom .buttons { @@ -145,7 +141,7 @@ } .volumeButtons { - margin: 0 0.5em 0 auto; + margin: 0 1em 0 0.29em; display: flex; -webkit-align-items: center; align-items: center; @@ -153,33 +149,13 @@ .osdTimeText { margin-left: 1em; + margin-right: auto; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } -.osdPoster { - width: 10%; - position: relative; - margin-right: 0.5em; -} - -.osdPoster img { - position: absolute; - height: auto; - width: 100%; - -webkit-box-shadow: 0 0 1.9vh #000; - box-shadow: 0 0 1.9vh #000; - border: 0.08em solid #222; - user-drag: none; - user-select: none; - -moz-user-select: none; - -webkit-user-drag: none; - -webkit-user-select: none; - -ms-user-select: none; -} - .osdTitle, .osdTitleSmall { margin: 0 1em 0 0; @@ -248,8 +224,6 @@ } @media all and (max-width: 30em) { - .btnFastForward, - .btnRewind, .osdMediaInfo, .osdPoster { display: none !important; diff --git a/src/assets/img/avatar.png b/src/assets/img/avatar.png index 0453f24897..4023d1c5c7 100644 Binary files a/src/assets/img/avatar.png and b/src/assets/img/avatar.png differ diff --git a/src/assets/img/icon-transparent.png b/src/assets/img/icon-transparent.png index 0d71b56067..76ef75baf9 100644 Binary files a/src/assets/img/icon-transparent.png and b/src/assets/img/icon-transparent.png differ diff --git a/src/assets/splash/ipad_splash.png b/src/assets/splash/ipad_splash.png index 0faca927b7..45ed7d398d 100755 Binary files a/src/assets/splash/ipad_splash.png and b/src/assets/splash/ipad_splash.png differ diff --git a/src/assets/splash/ipad_splash_l.png b/src/assets/splash/ipad_splash_l.png index 3ecf2d5adc..baf016e865 100644 Binary files a/src/assets/splash/ipad_splash_l.png and b/src/assets/splash/ipad_splash_l.png differ diff --git a/src/assets/splash/ipadpro1_splash.png b/src/assets/splash/ipadpro1_splash.png index 9e7fdb7f6c..c5afa50986 100755 Binary files a/src/assets/splash/ipadpro1_splash.png and b/src/assets/splash/ipadpro1_splash.png differ diff --git a/src/assets/splash/ipadpro1_splash_l.png b/src/assets/splash/ipadpro1_splash_l.png index cffad337c8..f5ef6f3b04 100644 Binary files a/src/assets/splash/ipadpro1_splash_l.png and b/src/assets/splash/ipadpro1_splash_l.png differ diff --git a/src/assets/splash/ipadpro2_splash.png b/src/assets/splash/ipadpro2_splash.png index a2e9624c65..500cad2b3d 100755 Binary files a/src/assets/splash/ipadpro2_splash.png and b/src/assets/splash/ipadpro2_splash.png differ diff --git a/src/assets/splash/ipadpro2_splash_l.png b/src/assets/splash/ipadpro2_splash_l.png index 588902a2b1..ec17e3e81b 100644 Binary files a/src/assets/splash/ipadpro2_splash_l.png and b/src/assets/splash/ipadpro2_splash_l.png differ diff --git a/src/assets/splash/ipadpro3_splash.png b/src/assets/splash/ipadpro3_splash.png index 89a04afe07..3a1c82e983 100755 Binary files a/src/assets/splash/ipadpro3_splash.png and b/src/assets/splash/ipadpro3_splash.png differ diff --git a/src/assets/splash/ipadpro3_splash_l.png b/src/assets/splash/ipadpro3_splash_l.png index a0b6c56904..df275b3448 100644 Binary files a/src/assets/splash/ipadpro3_splash_l.png and b/src/assets/splash/ipadpro3_splash_l.png differ diff --git a/src/assets/splash/iphone5_splash.png b/src/assets/splash/iphone5_splash.png index 3e073a9e1b..bdbc28ab0d 100755 Binary files a/src/assets/splash/iphone5_splash.png and b/src/assets/splash/iphone5_splash.png differ diff --git a/src/assets/splash/iphone5_splash_l.png b/src/assets/splash/iphone5_splash_l.png index 28fa8838e1..789f50f628 100644 Binary files a/src/assets/splash/iphone5_splash_l.png and b/src/assets/splash/iphone5_splash_l.png differ diff --git a/src/assets/splash/iphone6_splash.png b/src/assets/splash/iphone6_splash.png index afe42fa26a..879d5b716f 100755 Binary files a/src/assets/splash/iphone6_splash.png and b/src/assets/splash/iphone6_splash.png differ diff --git a/src/assets/splash/iphone6_splash_l.png b/src/assets/splash/iphone6_splash_l.png index c7de58510f..396224fcb2 100644 Binary files a/src/assets/splash/iphone6_splash_l.png and b/src/assets/splash/iphone6_splash_l.png differ diff --git a/src/assets/splash/iphoneplus_splash.png b/src/assets/splash/iphoneplus_splash.png index 2cc62163ac..a9e7de01ca 100755 Binary files a/src/assets/splash/iphoneplus_splash.png and b/src/assets/splash/iphoneplus_splash.png differ diff --git a/src/assets/splash/iphoneplus_splash_l.png b/src/assets/splash/iphoneplus_splash_l.png index c989f2237e..f14f0af1ff 100644 Binary files a/src/assets/splash/iphoneplus_splash_l.png and b/src/assets/splash/iphoneplus_splash_l.png differ diff --git a/src/assets/splash/iphonex_splash.png b/src/assets/splash/iphonex_splash.png index 2d9698c287..435bbe07a9 100755 Binary files a/src/assets/splash/iphonex_splash.png and b/src/assets/splash/iphonex_splash.png differ diff --git a/src/assets/splash/iphonex_splash_l.png b/src/assets/splash/iphonex_splash_l.png index 9eb9e037ce..cbadd0a239 100644 Binary files a/src/assets/splash/iphonex_splash_l.png and b/src/assets/splash/iphonex_splash_l.png differ diff --git a/src/assets/splash/iphonexr_splash.png b/src/assets/splash/iphonexr_splash.png index 0f911eb2e8..b6072df329 100755 Binary files a/src/assets/splash/iphonexr_splash.png and b/src/assets/splash/iphonexr_splash.png differ diff --git a/src/assets/splash/iphonexr_splash_l.png b/src/assets/splash/iphonexr_splash_l.png index 1522f372a9..f78e825192 100644 Binary files a/src/assets/splash/iphonexr_splash_l.png and b/src/assets/splash/iphonexr_splash_l.png differ diff --git a/src/assets/splash/iphonexsmax_splash.png b/src/assets/splash/iphonexsmax_splash.png index 3e9aaf56af..b4e9255b31 100755 Binary files a/src/assets/splash/iphonexsmax_splash.png and b/src/assets/splash/iphonexsmax_splash.png differ diff --git a/src/assets/splash/iphonexsmax_splash_l.png b/src/assets/splash/iphonexsmax_splash_l.png index e2961f512e..e94692de0e 100644 Binary files a/src/assets/splash/iphonexsmax_splash_l.png and b/src/assets/splash/iphonexsmax_splash_l.png differ diff --git a/src/bundle.js b/src/bundle.js index 5a7ffed075..3d1d600a9f 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -60,8 +60,8 @@ _define('resize-observer-polyfill', function() { }); // swiper -const swiper = require('swiper/js/swiper'); -require('swiper/css/swiper.min.css'); +const swiper = require('swiper/swiper-bundle'); +require('swiper/swiper-bundle.css'); _define('swiper', function() { return swiper; }); @@ -175,3 +175,9 @@ _define('connectionManagerFactory', function () { _define('appStorage', function () { return apiclient.AppStorage; }); + +// libarchive.js +const libarchive = require('libarchive.js'); +_define('libarchive', function () { + return libarchive; +}); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index ab489a3f31..3878d03355 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -4,7 +4,6 @@ import dom from 'dom'; import * as datefns from 'date-fns'; import dfnshelper from 'dfnshelper'; import serverNotifications from 'serverNotifications'; -import connectionManager from 'connectionManager'; import 'emby-button'; import 'listViewStyle'; @@ -141,7 +140,7 @@ class ActivityLog { const element = options.element; element.classList.add('activityLogListWidget'); element.addEventListener('click', onListClick.bind(this)); - const apiClient = connectionManager.getApiClient(options.serverId); + const apiClient = window.connectionManager.getApiClient(options.serverId); reloadData(this, element, apiClient); const onUpdate = onActivityLogUpdate.bind(this); this.updateFn = onUpdate; @@ -153,7 +152,7 @@ class ActivityLog { if (options) { options.element.classList.remove('activityLogListWidget'); - connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500'); + window.connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500'); } const onUpdate = this.updateFn; diff --git a/src/components/appFooter/appFooter.js b/src/components/appFooter/appFooter.js index c60aa1a27c..260aea8280 100644 --- a/src/components/appFooter/appFooter.js +++ b/src/components/appFooter/appFooter.js @@ -27,7 +27,7 @@ class appFooter { }; } destroy() { - var self = this; + const self = this; self.element = null; } diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 28826c55cc..a6bb6da618 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -2,7 +2,6 @@ import appHost from 'apphost'; import appSettings from 'appSettings'; import backdrop from 'backdrop'; import browser from 'browser'; -import connectionManager from 'connectionManager'; import events from 'events'; import globalize from 'globalize'; import itemHelper from 'itemHelper'; @@ -41,7 +40,7 @@ class AppRouter { } }); - this.baseRoute = self.location.href.split('?')[0].replace(this.getRequestFile(), ''); + this.baseRoute = window.location.href.split('?')[0].replace(this.getRequestFile(), ''); // support hashbang this.baseRoute = this.baseRoute.split('#')[0]; if (this.baseRoute.endsWith('/') && !this.baseRoute.endsWith('://')) { @@ -55,7 +54,7 @@ class AppRouter { * @private */ setBaseRoute() { - let baseRoute = self.location.pathname.replace(this.getRequestFile(), ''); + let baseRoute = window.location.pathname.replace(this.getRequestFile(), ''); if (baseRoute.lastIndexOf('/') === baseRoute.length - 1) { baseRoute = baseRoute.substring(0, baseRoute.length - 1); } @@ -95,7 +94,7 @@ class AppRouter { beginConnectionWizard() { backdrop.clearBackdrop(); loading.show(); - connectionManager.connect({ + window.connectionManager.connect({ enableAutoLogin: appSettings.enableAutoLogin() }).then((result) => { this.handleConnectionResult(result); @@ -154,7 +153,7 @@ class AppRouter { events.on(appHost, 'beforeexit', this.onBeforeExit); events.on(appHost, 'resume', this.onAppResume); - connectionManager.connect({ + window.connectionManager.connect({ enableAutoLogin: appSettings.enableAutoLogin() }).then((result) => { this.firstConnectionResult = result; @@ -182,7 +181,7 @@ class AppRouter { return false; } - return history.length > 1; + return window.history.length > 1; } current() { @@ -210,7 +209,7 @@ class AppRouter { showItem(item, serverId, options) { // TODO: Refactor this so it only gets items, not strings. if (typeof (item) === 'string') { - const apiClient = serverId ? connectionManager.getApiClient(serverId) : connectionManager.currentApiClient(); + const apiClient = serverId ? window.connectionManager.getApiClient(serverId) : window.connectionManager.currentApiClient(); apiClient.getItem(apiClient.getCurrentUserId(), item).then((itemObject) => { this.showItem(itemObject, options); }); @@ -258,7 +257,7 @@ class AppRouter { pushState(state, title, url) { state.navigate = false; - history.pushState(state, title, url); + window.history.pushState(state, title, url); } enableNativeHistory() { @@ -309,7 +308,9 @@ class AppRouter { url = route.contentPath || route.path; } - if (url.indexOf('://') === -1) { + if (url.includes('configurationpage')) { + url = ApiClient.getUrl('/web' + url); + } else if (url.indexOf('://') === -1) { // Put a slash at the beginning but make sure to avoid a double slash if (url.indexOf('/') !== 0) { url = '/' + url; @@ -492,15 +493,15 @@ class AppRouter { } initApiClients() { - connectionManager.getApiClients().forEach((apiClient) => { + window.connectionManager.getApiClients().forEach((apiClient) => { this.initApiClient(apiClient, this); }); - events.on(connectionManager, 'apiclientcreated', this.onApiClientCreated); + events.on(window.connectionManager, 'apiclientcreated', this.onApiClientCreated); } onAppResume() { - const apiClient = connectionManager.currentApiClient(); + const apiClient = window.connectionManager.currentApiClient(); if (apiClient) { apiClient.ensureWebSocket(); @@ -518,7 +519,7 @@ class AppRouter { } } - const apiClient = connectionManager.currentApiClient(); + const apiClient = window.connectionManager.currentApiClient(); const pathname = ctx.pathname.toLowerCase(); console.debug('appRouter - processing path request ' + pathname); @@ -594,7 +595,7 @@ class AppRouter { } getRequestFile() { - let path = self.location.pathname || ''; + let path = window.location.pathname || ''; const index = path.lastIndexOf('/'); if (index !== -1) { diff --git a/src/components/apphost.js b/src/components/apphost.js index c3e9342827..875c7907e6 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -55,7 +55,7 @@ function replaceAll(originalString, strReplace, strWith) { function generateDeviceId() { const keys = []; - if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { + if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), window.btoa) { const result = replaceAll(btoa(keys.join('|')), '=', '1'); return Promise.resolve(result); } @@ -78,7 +78,7 @@ function getDeviceId() { } function getDeviceName() { - var deviceName; + let deviceName; if (browser.tizen) { deviceName = 'Samsung Smart TV'; } else if (browser.web0s) { @@ -404,9 +404,9 @@ document.addEventListener(visibilityChange, function () { } }, false); -if (self.addEventListener) { - self.addEventListener('focus', onAppVisible); - self.addEventListener('blur', onAppHidden); +if (window.addEventListener) { + window.addEventListener('focus', onAppVisible); + window.addEventListener('blur', onAppHidden); } export default appHost; diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index d3c9e58b59..83888b81b9 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -1,5 +1,4 @@ import browser from 'browser'; -import connectionManager from 'connectionManager'; import playbackManager from 'playbackManager'; import dom from 'dom'; import * as userSettings from 'userSettings'; @@ -177,7 +176,7 @@ import 'css!./backdrop'; function getItemImageUrls(item, imageOptions) { imageOptions = imageOptions || {}; - const apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { return item.BackdropImageTags.map((imgTag, index) => { return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { diff --git a/src/components/cardbuilder/card.css b/src/components/cardbuilder/card.css index ef5ea6604c..4c046ce984 100644 --- a/src/components/cardbuilder/card.css +++ b/src/components/cardbuilder/card.css @@ -209,6 +209,10 @@ button::-moz-focus-inner { contain: strict; } +.defaultCardBackground { + display: flex; +} + .cardContent:not(.defaultCardBackground) { background-color: transparent; } @@ -239,33 +243,13 @@ button::-moz-focus-inner { border: none; } -.cardImage-img { - max-height: 100%; - max-width: 100%; - - /* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */ - min-height: 70%; - min-width: 70%; - margin: auto; -} - -.coveredImage-img { - width: 100%; - height: 100%; -} - -.coveredImage-noscale-img { - max-height: none; - max-width: none; -} - .coveredImage { background-size: cover; background-position: center center; } -.coveredImage-noScale { - background-size: cover; +.coveredImage.coveredImage-contain { + background-size: contain; } .cardFooter { @@ -372,6 +356,8 @@ button::-moz-focus-inner { .cardDefaultText { white-space: normal; text-align: center; + font-size: 2em; + font-weight: bold; } .cardImageContainer .cardImageIcon { diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index d0a8c67f94..63b2e26adb 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -7,7 +7,6 @@ import datetime from 'datetime'; import imageLoader from 'imageLoader'; -import connectionManager from 'connectionManager'; import itemHelper from 'itemHelper'; import focusManager from 'focusManager'; import indicators from 'indicators'; @@ -371,7 +370,7 @@ import 'programStyles'; if (serverId !== lastServerId) { lastServerId = serverId; - apiClient = connectionManager.getApiClient(lastServerId); + apiClient = window.connectionManager.getApiClient(lastServerId); } if (options.indexBy) { @@ -986,6 +985,10 @@ import 'programStyles'; lines = []; } + if (overlayText && showTitle) { + lines = [item.Name]; + } + const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); @@ -1212,8 +1215,8 @@ import 'programStyles'; if (coveredImage) { cardImageContainerClass += ' coveredImage'; - if (item.MediaType === 'Photo' || item.Type === 'PhotoAlbum' || item.Type === 'Folder' || item.ProgramInfo || item.Type === 'Program' || item.Type === 'Recording') { - cardImageContainerClass += ' coveredImage-noScale'; + if (item.Type === 'TvChannel') { + cardImageContainerClass += ' coveredImage-contain'; } } diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 642a87db2a..35ae2b0cdd 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -7,7 +7,6 @@ import datetime from 'datetime'; import imageLoader from 'imageLoader'; -import connectionManager from 'connectionManager'; import layoutManager from 'layoutManager'; import browser from 'browser'; @@ -48,7 +47,7 @@ import browser from 'browser'; let html = ''; let itemsInRow = 0; - const apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); for (let i = 0, length = chapters.length; i < length; i++) { if (options.rows && itemsInRow === 0) { diff --git a/src/components/channelMapper/channelMapper.js b/src/components/channelMapper/channelMapper.js index e747279693..294f9e223a 100644 --- a/src/components/channelMapper/channelMapper.js +++ b/src/components/channelMapper/channelMapper.js @@ -1,7 +1,6 @@ import dom from 'dom'; import dialogHelper from 'dialogHelper'; import loading from 'loading'; -import connectionManager from 'connectionManager'; import globalize from 'globalize'; import actionsheet from 'actionsheet'; import 'emby-input'; @@ -16,7 +15,7 @@ export default class channelMapper { function mapChannel(button, channelId, providerChannelId) { loading.show(); const providerId = options.providerId; - connectionManager.getApiClient(options.serverId).ajax({ + window.connectionManager.getApiClient(options.serverId).ajax({ type: 'POST', url: ApiClient.getUrl('LiveTv/ChannelMappings'), data: JSON.stringify({ @@ -24,6 +23,7 @@ export default class channelMapper { tunerChannelId: channelId, providerChannelId: providerChannelId }), + contentType: 'application/json', dataType: 'json' }).then(mapping => { const listItem = dom.parentWithClass(button, 'listItem'); @@ -58,7 +58,7 @@ export default class channelMapper { } function getChannelMappingOptions(serverId, providerId) { - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', { providerId: providerId })); diff --git a/src/components/collectionEditor/collectionEditor.js b/src/components/collectionEditor/collectionEditor.js index dd8b3d6837..2d0d025929 100644 --- a/src/components/collectionEditor/collectionEditor.js +++ b/src/components/collectionEditor/collectionEditor.js @@ -2,7 +2,6 @@ import dom from 'dom'; import dialogHelper from 'dialogHelper'; import loading from 'loading'; import layoutManager from 'layoutManager'; -import connectionManager from 'connectionManager'; import appRouter from 'appRouter'; import globalize from 'globalize'; import 'emby-checkbox'; @@ -25,7 +24,7 @@ import 'flexStyles'; const collectionId = panel.querySelector('#selectCollectionToAddTo').value; - const apiClient = connectionManager.getApiClient(currentServerId); + const apiClient = window.connectionManager.getApiClient(currentServerId); if (collectionId) { addToCollection(apiClient, panel, collectionId); @@ -106,7 +105,7 @@ import 'flexStyles'; EnableTotalRecordCount: false }; - const apiClient = connectionManager.getApiClient(currentServerId); + const apiClient = window.connectionManager.getApiClient(currentServerId); apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; diff --git a/src/components/confirm/confirm.js b/src/components/confirm/confirm.js index 0670816a53..eca612ccb8 100644 --- a/src/components/confirm/confirm.js +++ b/src/components/confirm/confirm.js @@ -19,7 +19,7 @@ export default (() => { } const text = replaceAll(options.text || '', '
', '\n'); - const result = confirm(text); + const result = window.confirm(text); if (result) { return Promise.resolve(); diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index b9256714fd..eb46d98b12 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -85,9 +85,9 @@ import 'scrollStyles'; } if (!self.closedByBack && isHistoryEnabled(dlg)) { - const state = history.state || {}; + const state = window.history.state || {}; if (state.dialogId === hash) { - history.back(); + window.history.back(); } } @@ -213,7 +213,7 @@ import 'scrollStyles'; export function close(dlg) { if (isOpened(dlg)) { if (isHistoryEnabled(dlg)) { - history.back(); + window.history.back(); } else { closeDialog(dlg); } @@ -375,7 +375,7 @@ import 'scrollStyles'; dlg.setAttribute('data-lockscroll', 'true'); } - if (options.enableHistory !== false) { + if (options.enableHistory === true) { dlg.setAttribute('data-history', 'true'); } diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index 4205e04a4f..3dd3302b28 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -169,7 +169,8 @@ import 'emby-button'; data: JSON.stringify({ ValidateWriteable: validateWriteable, Path: path - }) + }), + contentType: 'application/json' }).catch(response => { if (response) { if (response.status === 404) { diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index ae7647f98b..efaab16b3f 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -6,7 +6,6 @@ import focusManager from 'focusManager'; import datetime from 'datetime'; import globalize from 'globalize'; import loading from 'loading'; -import connectionManager from 'connectionManager'; import skinManager from 'skinManager'; import events from 'events'; import 'emby-select'; @@ -24,7 +23,7 @@ import 'emby-button'; }).join(''); // get default theme - var defaultTheme = themes.find(theme => { + const defaultTheme = themes.find(theme => { return theme.default; }); @@ -182,7 +181,7 @@ import 'emby-button'; function onSubmit(e) { const self = this; - const apiClient = connectionManager.getApiClient(self.options.serverId); + const apiClient = window.connectionManager.getApiClient(self.options.serverId); const userId = self.options.userId; const userSettings = self.options.userSettings; @@ -221,7 +220,7 @@ import 'emby-button'; loading.show(); const userId = self.options.userId; - const apiClient = connectionManager.getApiClient(self.options.serverId); + const apiClient = window.connectionManager.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; return apiClient.getUser(userId).then(user => { diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index 7a94b07454..86cd050216 100644 --- a/src/components/favoriteitems.js +++ b/src/components/favoriteitems.js @@ -28,21 +28,21 @@ import 'emby-itemscontainer'; function getSections() { return [{ - name: 'HeaderFavoriteMovies', + name: 'Movies', types: 'Movie', id: 'favoriteMovies', shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, { - name: 'HeaderFavoriteShows', + name: 'Shows', types: 'Series', id: 'favoriteShows', shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, { - name: 'HeaderFavoriteEpisodes', + name: 'Episodes', types: 'Episode', id: 'favoriteEpisode', shape: getThumbShape(), @@ -53,7 +53,7 @@ import 'emby-itemscontainer'; overlayText: false, centerText: true }, { - name: 'HeaderFavoriteVideos', + name: 'Videos', types: 'Video,MusicVideo', id: 'favoriteVideos', shape: getThumbShape(), @@ -63,7 +63,7 @@ import 'emby-itemscontainer'; overlayText: false, centerText: true }, { - name: 'HeaderFavoriteArtists', + name: 'Artists', types: 'MusicArtist', id: 'favoriteArtists', shape: getSquareShape(), @@ -75,7 +75,7 @@ import 'emby-itemscontainer'; overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoriteAlbums', + name: 'Albums', types: 'MusicAlbum', id: 'favoriteAlbums', shape: getSquareShape(), @@ -87,7 +87,7 @@ import 'emby-itemscontainer'; overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoriteSongs', + name: 'Songs', types: 'Audio', id: 'favoriteSongs', shape: getSquareShape(), diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js index df17c19dec..d11edb40a2 100644 --- a/src/components/filterdialog/filterdialog.js +++ b/src/components/filterdialog/filterdialog.js @@ -1,7 +1,6 @@ import dom from 'dom'; import dialogHelper from 'dialogHelper'; import globalize from 'globalize'; -import connectionManager from 'connectionManager'; import events from 'events'; import 'emby-checkbox'; import 'emby-collapse'; @@ -420,7 +419,7 @@ import 'css!./style.css'; this.bindEvents(dlg); if (enableDynamicFilters(this.options.mode)) { dlg.classList.add('dynamicFilterDialog'); - const apiClient = connectionManager.getApiClient(this.options.serverId); + const apiClient = window.connectionManager.getApiClient(this.options.serverId); loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); } }); diff --git a/src/components/filterdialog/filterdialog.template.html b/src/components/filterdialog/filterdialog.template.html index 1d61f3923e..03aba5ee35 100644 --- a/src/components/filterdialog/filterdialog.template.html +++ b/src/components/filterdialog/filterdialog.template.html @@ -6,12 +6,12 @@