diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 4ee827471..02dfd18aa 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 a4e972c83..dfb65cb4d 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'], - 'no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], - 'no-unused-vars': ['error', { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': true }], + '@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'}], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], - 'semi': ['error'], + '@babel/semi': ['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', @@ -106,6 +108,7 @@ module.exports = { // TODO: Fix warnings and remove these rules 'no-redeclare': ['off'], 'no-useless-escape': ['off'], + 'no-unused-vars': ['off'], // TODO: Remove after ES6 migration is complete 'import/no-unresolved': ['off'] }, diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index b8c77f2a1..1d85e5ae6 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -11,6 +11,8 @@ Source0: jellyfin-web-%{version}.tar.gz %if 0%{?centos} BuildRequires: yarn +# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it +BuildRequires: git %else BuildRequires: nodejs-yarn %endif diff --git a/package.json b/package.json index 27508faa1..5ca5c1515 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.11.5", + "@babel/eslint-parser": "^7.11.5", + "@babel/eslint-plugin": "^7.11.5", "@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/polyfill": "^7.11.5", + "@babel/preset-env": "^7.11.5", "autoprefixer": "^9.8.6", "babel-loader": "^8.0.6", "browser-sync": "^2.26.12", + "confusing-browser-globals": "^1.0.9", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.2.1", + "css-loader": "^4.2.2", "cssnano": "^4.1.10", "del": "^5.1.0", - "eslint": "^7.6.0", + "eslint": "^7.8.1", "eslint-plugin-compat": "^3.5.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.21.2", "eslint-plugin-promise": "^4.2.1", - "file-loader": "^6.0.0", + "file-loader": "^6.1.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-cli": "^2.3.0", @@ -38,47 +39,49 @@ "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.4.1", "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", + "stylelint": "^13.7.0", "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.1.0", "webpack": "^4.44.1", "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", + "hls.js": "^0.14.11", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", "jstree": "^3.3.10", + "libarchive.js": "^1.3.0", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", - "material-design-icons-iconfont": "^5.0.1", + "material-design-icons-iconfont": "^6.0.1", "native-promise-only": "^0.8.0-a", "page": "^1.11.6", "query-string": "^6.13.1", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", "sortablejs": "^1.10.2", - "swiper": "^5.4.5", + "swiper": "^6.1.1", "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.4.0" }, @@ -96,6 +99,7 @@ "src/components/alphaPicker/alphaPicker.js", "src/components/appFooter/appFooter.js", "src/components/apphost.js", + "src/components/appRouter.js", "src/components/autoFocuser.js", "src/components/backdrop/backdrop.js", "src/components/cardbuilder/cardBuilder.js", @@ -144,6 +148,7 @@ "src/components/multiSelect/multiSelect.js", "src/components/notifications/notifications.js", "src/components/nowPlayingBar/nowPlayingBar.js", + "src/components/packageManager.js", "src/components/playback/brightnessosd.js", "src/components/playback/mediasession.js", "src/components/playback/nowplayinghelper.js", @@ -159,17 +164,23 @@ "src/components/playerstats/playerstats.js", "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", + "src/components/pluginManager.js", "src/components/prompt/prompt.js", "src/components/recordingcreator/recordingbutton.js", "src/components/recordingcreator/recordingcreator.js", "src/components/recordingcreator/seriesrecordingeditor.js", "src/components/recordingcreator/recordinghelper.js", "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", @@ -313,11 +324,13 @@ "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/browser.js", + "src/scripts/clientUtils.js", "src/scripts/datetime.js", "src/scripts/deleteHelper.js", "src/scripts/dfnshelper.js", diff --git a/scripts/unused.py b/scripts/unused.py index 12af27320..abbc399cf 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 cb0da0f80..6e87f11d9 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 643fb9ca9..c9ee82c8a 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 59a485468..808915e58 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -248,8 +248,6 @@ } @media all and (max-width: 30em) { - .btnFastForward, - .btnRewind, .osdMediaInfo, .osdPoster { display: none !important; diff --git a/src/bundle.js b/src/bundle.js index 5a7ffed07..25810f58e 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 +var libarchive = require('libarchive.js'); +_define('libarchive', function () { + return libarchive; +}); diff --git a/src/components/accessSchedule/accessSchedule.template.html b/src/components/accessSchedule/accessSchedule.template.html index d89b69d9b..c0f83ccec 100644 --- a/src/components/accessSchedule/accessSchedule.template.html +++ b/src/components/accessSchedule/accessSchedule.template.html @@ -1,5 +1,5 @@
-

@@ -12,13 +12,13 @@
`; html += '
'; if (!readOnlyAttribute) { - html += ``; + html += ``; } html += '

'; if (!readOnlyAttribute) { @@ -166,10 +166,11 @@ import 'emby-button'; return apiClient.ajax({ type: 'POST', url: apiClient.getUrl('Environment/ValidatePath'), - data: { + 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 ae7647f98..9def27dc8 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'; @@ -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/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index fdaf8d70f..1b9bf0037 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -162,7 +162,7 @@
${EnableBackdropsHelp}
@@ -170,7 +170,7 @@
${EnableThemeSongsHelp}
@@ -178,7 +178,7 @@
${EnableThemeVideosHelp}
diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index 7a94b0745..86cd05021 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 df17c19de..d11edb40a 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 f4bbfe739..03aba5ee3 100644 --- a/src/components/filterdialog/filterdialog.template.html +++ b/src/components/filterdialog/filterdialog.template.html @@ -6,12 +6,12 @@