diff --git a/.eslintrc.yml b/.eslintrc.yml index 4bc22fc1d4..377716d53c 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -3,6 +3,12 @@ env: browser: true amd: true +parserOptions: + ecmaVersion: 6 + sourceType: module + ecmaFeatures: + impliedStrict: true + globals: # New browser globals DataView: readonly diff --git a/gulpfile.js b/gulpfile.js index ca6cf36dd2..f42376e24a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,14 +18,14 @@ const stream = require('webpack-stream'); const inject = require('gulp-inject'); const postcss = require('gulp-postcss'); const sass = require('gulp-sass'); - -sass.compiler = require('node-sass') +sass.compiler = require('node-sass'); +let config; if (mode.production()) { - var config = require('./webpack.prod.js'); + config = require('./webpack.prod.js'); } else { - var config = require('./webpack.dev.js'); + config = require('./webpack.dev.js'); } function serve() { @@ -36,20 +36,20 @@ function serve() { port: 8080 }); - watch(['src/**/*.js', '!src/bundle.js'], javascript); + watch(['src/**/*.js', '!src/bundle.js'], series(javascript, standalone)); watch('src/bundle.js', webpack); watch('src/**/*.css', css); watch(['src/**/*.html', '!src/index.html'], html); watch(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], images); watch(['src/**/*.json', 'src/**/*.ico'], copy); watch('src/index.html', injectBundle); - watch(['src/standalone.js', 'src/scripts/apploader.js'], standalone); } function standalone() { return src(['src/standalone.js', 'src/scripts/apploader.js'], { base: './src/' }) .pipe(concat('scripts/apploader.js')) - .pipe(dest('dist/')); + .pipe(dest('dist/')) + .pipe(browserSync.stream()); } function clean() { @@ -70,7 +70,6 @@ function javascript() { })) .pipe(mode.development(sourcemaps.write('.'))) .pipe(dest('dist/')) - .pipe(browserSync.stream()); } function webpack() { @@ -118,6 +117,6 @@ function injectBundle() { .pipe(browserSync.stream()); } -exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle) -exports.standalone = series(exports.default, standalone) -exports.serve = series(exports.standalone, serve) +exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle); +exports.standalone = series(exports.default, standalone); +exports.serve = series(exports.standalone, serve); diff --git a/package.json b/package.json index ae50f65b93..dc05ee0248 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@babel/core": "^7.8.6", "@babel/polyfill": "^7.8.7", + "@babel/plugin-transform-modules-amd": "^7.8.3", "@babel/preset-env": "^7.8.6", "autoprefixer": "^9.7.4", "babel-loader": "^8.0.6", @@ -58,10 +59,10 @@ "jquery": "^3.4.1", "jstree": "^3.3.7", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus", - "libjass": "^0.11.0", "material-design-icons-iconfont": "^5.0.1", "native-promise-only": "^0.8.0-a", "page": "^1.11.5", + "query-string": "^6.11.1", "resize-observer-polyfill": "^1.5.1", "shaka-player": "^2.5.9", "sortablejs": "^1.10.2", @@ -70,9 +71,11 @@ "whatwg-fetch": "^3.0.0" }, "babel": { - "presets": [ - "@babel/preset-env" - ] + "presets": ["@babel/preset-env"], + "overrides": [{ + "test": ["src/components/cardbuilder/cardBuilder.js"], + "plugins": ["@babel/plugin-transform-modules-amd"] + }] }, "browserslist": [ "last 2 Firefox versions", diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 67afef2376..13265e40d1 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -21,7 +21,7 @@ } .libraryPage { - padding-top: 7em !important; + padding-top: 7em; } .itemDetailPage { @@ -115,7 +115,7 @@ display: -webkit-inline-box; display: -webkit-inline-flex; display: inline-flex; - margin: 0.3em 0 0 0.5em; + margin: 0 0 0 0.5em; height: 1.7em; -webkit-box-align: center; -webkit-align-items: center; @@ -128,6 +128,10 @@ margin-top: 0; } +.layout-mobile .pageTitleWithDefaultLogo { + background-image: url(../img/icon-transparent.png); +} + .headerLeft, .skinHeader { display: -webkit-box; @@ -242,7 +246,6 @@ } @media all and (min-width: 40em) { - .dashboardDocument .adminDrawerLogo, .dashboardDocument .mainDrawerButton { display: none !important; } @@ -268,12 +271,6 @@ } } -@media all and (max-width: 60em) { - .libraryDocument .mainDrawerButton { - display: none; - } -} - @media all and (max-width: 84em) { .withSectionTabs .headerTop { padding-bottom: 0.55em; @@ -1122,3 +1119,7 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { .itemsViewSettingsContainer > .button-flat { margin: 0; } + +.layout-mobile #myPreferencesMenuPage { + padding-top: 3.75em; +} diff --git a/src/bundle.js b/src/bundle.js index dc2402d13d..316f42c94a 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -16,6 +16,12 @@ _define("fetch", function() { return fetch }); +// query-string +var query = require("query-string"); +_define("queryString", function() { + return query; +}); + // flvjs var flvjs = require("flv.js/dist/flv").default; _define("flvjs", function() { @@ -75,14 +81,7 @@ _define("sortable", function() { // webcomponents var webcomponents = require("webcomponents.js/webcomponents-lite"); _define("webcomponents", function() { - return webcomponents -}); - -// libjass -var libjass = require("libjass"); -require("libjass/libjass.css"); -_define("libjass", function() { - return libjass; + return webcomponents; }); // libass-wasm @@ -97,11 +96,10 @@ _define("material-icons", function() { return material_icons; }); -// Noto Sans - -var jellyfin_noto = require("jellyfin-noto"); +// noto font +var noto = require("jellyfin-noto"); _define("jellyfin-noto", function () { - return jellyfin_noto; + return noto; }); // page.js diff --git a/src/components/appRouter.js b/src/components/appRouter.js index efb58a089f..62bfb3cf40 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -511,9 +511,16 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM return baseRoute; } + var popstateOccurred = false; + window.addEventListener('popstate', function () { + popstateOccurred = true; + }); + function getHandler(route) { return function (ctx, next) { + ctx.isBack = popstateOccurred; handleRoute(ctx, next, route); + popstateOccurred = false; }; } diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index a6ee8fab6d..563405348b 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1,10 +1,36 @@ -define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusManager', 'indicators', 'globalize', 'layoutManager', 'apphost', 'dom', 'browser', 'playbackManager', 'itemShortcuts', 'scripts/imagehelper', 'css!./card', 'paper-icon-button-light', 'programStyles'], - function (datetime, imageLoader, connectionManager, itemHelper, focusManager, indicators, globalize, layoutManager, appHost, dom, browser, playbackManager, itemShortcuts, imageHelper) { - 'use strict'; +/* eslint-disable indent */ - var enableFocusTransform = !browser.slow && !browser.edge; +/** + * Module for building cards from item data. + * @module components/cardBuilder/cardBuilder + */ - function getCardsHtml(items, options) { +import datetime from 'datetime'; +import imageLoader from 'imageLoader'; +import connectionManager from 'connectionManager'; +import itemHelper from 'itemHelper'; +import focusManager from 'focusManager'; +import indicators from 'indicators'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import dom from 'dom'; +import browser from 'browser'; +import playbackManager from 'playbackManager'; +import itemShortcuts from 'itemShortcuts'; +import imageHelper from 'scripts/imagehelper'; +import 'css!./card'; +import 'paper-icon-button-light'; +import 'programStyles'; + + const enableFocusTransform = !browser.slow && !browser.edge; + + /** + * Generate the HTML markup for cards for a set of items. + * @param items - The items used to generate cards. + * @param options - The options of the cards. + * @returns {string} The HTML markup for the cards. + */ + export function getCardsHtml(items, options) { if (arguments.length === 1) { options = arguments[0]; items = options.items; @@ -13,6 +39,13 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana return buildCardsHtmlInternal(items, options); } + /** + * Computes the number of posters per row. + * @param {string} shape - Shape of the cards. + * @param {number} screenWidth - Width of the screen. + * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. + * @returns {number} Number of cards per row for an itemsContainer. + */ function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { switch (shape) { case 'portrait': @@ -217,10 +250,15 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } } + /** + * Checks if the window is resizable. + * @param {number} windowWidth - Width of the device's screen. + * @returns {boolean} - Result of the check. + */ function isResizable(windowWidth) { - var screen = window.screen; + const screen = window.screen; if (screen) { - var screenWidth = screen.availWidth; + const screenWidth = screen.availWidth; if ((screenWidth - windowWidth) > 20) { return true; @@ -230,22 +268,31 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana return false; } + /** + * Gets the width of a card's image according to the shape and amount of cards per row. + * @param {string} shape - Shape of the card. + * @param {number} screenWidth - Width of the screen. + * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. + * @returns {number} Width of the image for a card. + */ function getImageWidth(shape, screenWidth, isOrientationLandscape) { - var imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); - var shapeWidth = Math.round(screenWidth / imagesPerRow) * 2; - - return shapeWidth; + const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); + return Math.round(screenWidth / imagesPerRow) * 2; } + /** + * Normalizes the options for a card. + * @param {Object} items - A set of items. + * @param {Object} options - Options for handling the items. + */ function setCardData(items, options) { - options.shape = options.shape || "auto"; - var primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); + const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); - if (options.shape === 'auto' || options.shape === 'autohome' || options.shape === 'autooverflow' || options.shape === 'autoVertical') { + if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { - var requestedShape = options.shape; + const requestedShape = options.shape; options.shape = null; if (primaryImageAspectRatio) { @@ -283,11 +330,11 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } if (!options.width) { - var screenWidth = dom.getWindowSize().innerWidth; - var screenHeight = dom.getWindowSize().innerHeight; + let screenWidth = dom.getWindowSize().innerWidth; + const screenHeight = dom.getWindowSize().innerHeight; if (isResizable(screenWidth)) { - var roundScreenTo = 100; + const roundScreenTo = 100; screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; } @@ -295,9 +342,14 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } } + /** + * Generates the internal HTML markup for cards. + * @param {Object} items - Items for which to generate the markup. + * @param {Object} options - Options for generating the markup. + * @returns {string} The internal HTML markup of the cards. + */ function buildCardsHtmlInternal(items, options) { - - var isVertical; + let isVertical = false; if (options.shape === 'autoVertical') { isVertical = true; @@ -305,24 +357,21 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana setCardData(items, options); - var html = ''; - var itemsInRow = 0; + let html = ''; + let itemsInRow = 0; - var currentIndexValue; - var hasOpenRow; - var hasOpenSection; + let currentIndexValue; + let hasOpenRow; + let hasOpenSection; - var sectionTitleTagName = options.sectionTitleTagName || 'div'; - var apiClient; - var lastServerId; + let sectionTitleTagName = options.sectionTitleTagName || 'div'; + let apiClient; + let lastServerId; - var i; - var length; + for (let i = 0; i < items.length; i++) { - for (i = 0, length = items.length; i < length; i++) { - - var item = items[i]; - var serverId = item.ServerId || options.serverId; + let item = items[i]; + let serverId = item.ServerId || options.serverId; if (serverId !== lastServerId) { lastServerId = serverId; @@ -330,14 +379,14 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } if (options.indexBy) { - var newIndexValue = ''; + let newIndexValue = ''; if (options.indexBy === 'PremiereDate') { if (item.PremiereDate) { try { newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), { weekday: 'long', month: 'long', day: 'numeric' }); - } catch (err) { - console.error('error parsing timestamp for premiere date'); + } catch (error) { + console.error('error parsing timestamp for premiere date', error); } } } else if (options.indexBy === 'ProductionYear') { @@ -412,21 +461,15 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } } - var cardFooterHtml = ''; - for (i = 0, length = (options.lines || 0); i < length; i++) { - - if (i === 0) { - cardFooterHtml += '