diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 7c7801b866..31f00754f5 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -2,8 +2,7 @@ trigger: batch: true branches: include: - - master - - release-* + - '*' tags: include: - '*' @@ -13,90 +12,94 @@ pr: - '*' jobs: - - job: build - displayName: 'Build' +- job: Build + displayName: 'Build' - pool: - vmImage: 'ubuntu-latest' + strategy: + matrix: + Development: + BuildConfiguration: development + Production: + BuildConfiguration: production + Standalone: + BuildConfiguration: standalone - strategy: - matrix: - Development: - BuildConfiguration: development - Production: - BuildConfiguration: production - Standalone: - BuildConfiguration: standalone - maxParallel: 3 + pool: + vmImage: 'ubuntu-latest' - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '12.x' + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') - - script: 'yarn build:development' - displayName: 'Build Development' - condition: eq(variables['BuildConfiguration'], 'development') + - script: 'yarn build:development' + displayName: 'Build Development' + condition: eq(variables['BuildConfiguration'], 'development') - - script: 'yarn build:production' - displayName: 'Build Bundle' - condition: eq(variables['BuildConfiguration'], 'production') + - script: 'yarn build:production' + displayName: 'Build Bundle' + condition: eq(variables['BuildConfiguration'], 'production') - - script: 'yarn build:standalone' - displayName: 'Build Standalone' - condition: eq(variables['BuildConfiguration'], 'standalone') + - script: 'yarn build:standalone' + displayName: 'Build Standalone' + condition: eq(variables['BuildConfiguration'], 'standalone') - - script: 'test -d dist' - displayName: 'Check Build' + - script: 'test -d dist' + displayName: 'Check Build' - - script: 'mv dist jellyfin-web' - displayName: 'Rename Directory' - condition: succeeded() + - script: 'mv dist jellyfin-web' + displayName: 'Rename Directory' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Release' - condition: succeeded() - inputs: - targetPath: '$(Build.SourcesDirectory)/jellyfin-web' - artifactName: 'jellyfin-web-$(BuildConfiguration)' + - task: ArchiveFiles@2 + displayName: 'Archive Directory' + inputs: + rootFolderOrFile: 'jellyfin-web' + includeRootFolder: true + archiveFile: 'jellyfin-web-$(BuildConfiguration)' - - job: lint - displayName: 'Lint' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Release' + inputs: + targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip' + artifactName: 'jellyfin-web-$(BuildConfiguration)' - pool: - vmImage: 'ubuntu-latest' +- job: Lint + displayName: 'Lint' - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '12.x' + pool: + vmImage: 'ubuntu-latest' - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED - - script: 'yarn run lint' - displayName: 'Run ESLint' + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') - - script: 'yarn run stylelint' - displayName: 'Run Stylelint' + - script: 'yarn run lint --quiet' + displayName: 'Run ESLint' + + - script: 'yarn run stylelint' + displayName: 'Run Stylelint' diff --git a/.eslintrc.yml b/.eslintrc.yml index 377716d53c..0b92c0c9b0 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,21 +1,32 @@ env: - es6: true - browser: true amd: true + browser: true + es6: true + es2017: true + es2020: true parserOptions: - ecmaVersion: 6 + ecmaVersion: 2020 sourceType: module ecmaFeatures: impliedStrict: true +plugins: + - promise + - import + - eslint-comments + +extends: + - eslint:recommended + - plugin:promise/recommended + - plugin:import/errors + - plugin:import/warnings + - plugin:eslint-comments/recommended + - plugin:compat/recommended + globals: - # New browser globals - DataView: readonly + # Browser globals MediaMetadata: readonly - Promise: readonly - # Deprecated browser globals - DocumentTouch: readonly # Tizen globals tizen: readonly webapis: readonly @@ -24,7 +35,6 @@ globals: # Dependency globals $: readonly jQuery: readonly - queryString: readonly requirejs: readonly # Jellyfin globals ApiClient: writable @@ -40,8 +50,7 @@ globals: getWindowLocationSearch: writable Globalize: writable Hls: writable - humaneDate: writable - humaneElapsed: writable + dfnshelper: writable LibraryMenu: writable LinkParser: writable LiveTvHelpers: writable @@ -52,9 +61,6 @@ globals: UserParentalControlPage: writable Windows: readonly -extends: - - eslint:recommended - rules: block-spacing: ["error"] brace-style: ["error"] @@ -69,9 +75,97 @@ rules: no-multiple-empty-lines: ["error", { "max": 1 }] no-trailing-spaces: ["error"] one-var: ["error", "never"] - semi: ["warn"] + semi: ["error"] space-before-blocks: ["error"] # TODO: Fix warnings and remove these rules no-redeclare: ["warn"] no-unused-vars: ["warn"] no-useless-escape: ["warn"] + promise/catch-or-return: ["warn"] + promise/always-return: ["warn"] + promise/no-return-wrap: ["warn"] + # TODO: Remove after ES6 migration is complete + import/no-unresolved: ["warn"] + +settings: + polyfills: + # Native Promises Only + - Promise + # whatwg-fetch + - fetch + # document-register-element + - document.registerElement + # resize-observer-polyfill + - ResizeObserver + # fast-text-encoding + - TextEncoder + # intersection-observer + - IntersectionObserver + # Core-js + - Object.assign + - Object.is + - Object.setPrototypeOf + - Object.toString + - Object.freeze + - Object.seal + - Object.preventExtensions + - Object.isFrozen + - Object.isSealed + - Object.isExtensible + - Object.getOwnPropertyDescriptor + - Object.getPrototypeOf + - Object.keys + - Object.getOwnPropertyNames + - Function.name + - Function.hasInstance + - Array.from + - Array.arrayOf + - Array.copyWithin + - Array.fill + - Array.find + - Array.findIndex + - Array.iterator + - String.fromCodePoint + - String.raw + - String.iterator + - String.codePointAt + - String.endsWith + - String.includes + - String.repeat + - String.startsWith + - String.trim + - String.anchor + - String.big + - String.blink + - String.bold + - String.fixed + - String.fontcolor + - String.fontsize + - String.italics + - String.link + - String.small + - String.strike + - String.sub + - String.sup + - RegExp + - Number + - Math + - Date + - async + - Symbol + - Map + - Set + - WeakMap + - WeakSet + - ArrayBuffer + - DataView + - Int8Array + - Uint8Array + - Uint8ClampedArray + - Int16Array + - Uint16Array + - Int32Array + - Uint32Array + - Float32Array + - Float64Array + - Reflect diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000000..1320b9a327 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} diff --git a/gulpfile.js b/gulpfile.js index 0eb5593541..4556e71bc8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -62,7 +62,7 @@ function serve() { port: 8080 }); - let events = ['add', 'change']; + const events = ['add', 'change']; watch(options.javascript.query).on('all', function (event, path) { if (events.includes(event)) { @@ -105,7 +105,7 @@ function clean() { return del(['dist/']); } -let pipelineJavascript = lazypipe() +const pipelineJavascript = lazypipe() .pipe(function () { return mode.development(sourcemaps.init({ loadMaps: true })); }) @@ -140,7 +140,7 @@ function apploader(standalone) { .pipe(pipelineJavascript()) .pipe(dest('dist/')) .pipe(browserSync.stream()); - }; + } task.displayName = 'apploader'; @@ -183,6 +183,12 @@ function copy(query) { .pipe(browserSync.stream()); } +function copyIndex() { + return src(options.injectBundle.query, { base: './src/' }) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + function injectBundle() { return src(options.injectBundle.query, { base: './src/' }) .pipe(inject( @@ -193,9 +199,9 @@ function injectBundle() { } function build(standalone) { - return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy), injectBundle); + return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); } -exports.default = build(false); -exports.standalone = build(true); +exports.default = series(build(false), copyIndex); +exports.standalone = series(build(true), injectBundle); exports.serve = series(exports.standalone, serve); diff --git a/package.json b/package.json index c0ff158ec9..4e79ea3c39 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,10 @@ "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", + "eslint-plugin-compat": "^3.5.1", + "eslint-plugin-eslint-comments": "^3.1.2", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-promise": "^4.2.1", "file-loader": "^6.0.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", @@ -51,23 +55,29 @@ }, "dependencies": { "alameda": "^1.4.0", + "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "core-js": "^3.6.4", + "date-fns": "^2.11.1", "document-register-element": "^1.14.3", + "fast-text-encoding": "^1.0.1", "flv.js": "^1.5.0", + "headroom.js": "^0.11.0", "hls.js": "^0.13.1", "howler": "^2.1.3", + "intersection-observer": "^0.7.0", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.4.1", "jstree": "^3.3.7", - "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf", + "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova", "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", + "screenfull": "^5.0.2", "shaka-player": "^2.5.10", "sortablejs": "^1.10.2", - "swiper": "^5.3.1", + "swiper": "^5.3.7", "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.0.0" }, @@ -85,8 +95,13 @@ "src/components/filesystem.js", "src/components/input/keyboardnavigation.js", "src/components/sanatizefilename.js", + "src/components/scrollManager.js", + "src/scripts/settings/appSettings.js", + "src/scripts/settings/userSettings.js", "src/scripts/settings/webSettings.js", - "src/components/scrollManager.js" + "src/scripts/dfnshelper.js", + "src/scripts/imagehelper.js", + "src/scripts/inputManager.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" @@ -110,7 +125,7 @@ "Firefox ESR" ], "scripts": { - "serve": "gulp serve", + "serve": "gulp serve --development", "prepare": "gulp --production", "build:development": "gulp --development", "build:production": "gulp --production", diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index e4b5bcf8d6..f7a1160469 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -21,7 +21,11 @@ } .libraryPage { - padding-top: 7em; + padding-top: 7em !important; +} + +.layout-mobile .libraryPage { + padding-top: 4em !important; } .itemDetailPage { @@ -128,10 +132,6 @@ margin-top: 0; } -.layout-mobile .pageTitleWithDefaultLogo { - background-image: url(../img/icon-transparent.png); -} - .headerLeft, .skinHeader { display: -webkit-box; @@ -246,6 +246,7 @@ } @media all and (min-width: 40em) { + .dashboardDocument .adminDrawerLogo, .dashboardDocument .mainDrawerButton { display: none !important; } @@ -313,7 +314,7 @@ } .dashboardDocument .mainDrawer-scrollContainer { - margin-top: 4.6em !important; + margin-top: 4.65em !important; } } @@ -516,6 +517,13 @@ .itemName { margin: 0.5em 0; + font-weight: 600; +} + +.nameContainer { + display: flex; + flex-direction: column; + flex-wrap: wrap; } .itemMiscInfo { @@ -533,7 +541,6 @@ .layout-mobile .itemName, .layout-mobile .itemMiscInfo, .layout-mobile .mainDetailButtons { - display: flex; align-items: center; justify-content: center; text-align: center; @@ -575,7 +582,6 @@ .infoText { white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; text-align: left; } @@ -606,12 +612,11 @@ } .detailLogo { - width: 67.25vw; - height: 14.5vh; + width: 30vw; + height: 25vh; position: absolute; - top: 15vh; - right: 0; - -webkit-background-size: contain; + top: 10vh; + right: 20vw; background-size: contain; } @@ -619,26 +624,8 @@ display: none; } -@media all and (max-width: 87.5em) { - .detailLogo { - right: 5%; - } -} - -@media all and (max-width: 75em) { - .detailLogo { - right: 2%; - } -} - @media all and (max-width: 68.75em) { .detailLogo { - width: 14.91em; - height: 3.5em; - right: 5%; - bottom: 5%; - top: auto; - background-position: center right; display: none; } } diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 67416663e7..e59b639f45 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -96,3 +96,16 @@ div[data-role=page] { margin-right: auto; width: 85%; } + +.headroom { + will-change: transform; + transition: transform 200ms linear; +} + +.headroom--pinned { + transform: translateY(0%); +} + +.headroom--unpinned { + transform: translateY(-100%); +} diff --git a/src/bundle.js b/src/bundle.js index ba5f74b163..8a829103fa 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -13,7 +13,7 @@ _define("document-register-element", function() { // fetch var fetch = require("whatwg-fetch"); _define("fetch", function() { - return fetch + return fetch; }); // query-string @@ -85,15 +85,15 @@ _define("webcomponents", function() { }); // libass-wasm -var libass_wasm = require("libass-wasm"); +var libassWasm = require("libass-wasm"); _define("JavascriptSubtitlesOctopus", function() { - return libass_wasm; + return libassWasm; }); // material-icons -var material_icons = require("material-design-icons-iconfont/dist/material-design-icons.css"); +var materialIcons = require("material-design-icons-iconfont/dist/material-design-icons.css"); _define("material-icons", function() { - return material_icons; + return materialIcons; }); // noto font @@ -112,3 +112,43 @@ var polyfill = require("@babel/polyfill/dist/polyfill"); _define("polyfill", function () { return polyfill; }); + +// domtokenlist-shim +var classlist = require("classlist.js"); +_define("classlist-polyfill", function () { + return classlist; +}); + +// Date-FNS +var dateFns = require("date-fns"); +_define("date-fns", function () { + return dateFns; +}); + +var dateFnsLocale = require("date-fns/locale"); +_define("date-fns/locale", function () { + return dateFnsLocale; +}); + +var fast_text_encoding = require("fast-text-encoding"); +_define("fast-text-encoding", function () { + return fast_text_encoding; +}); + +// intersection-observer +var intersection_observer = require("intersection-observer"); +_define("intersection-observer", function () { + return intersection_observer; +}); + +// screenfull +var screenfull = require("screenfull"); +_define("screenfull", function () { + return screenfull; +}); + +// headroom.js +var headroom = require("headroom.js/dist/headroom"); +_define("headroom", function () { + return headroom; +}); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 05971f01b8..62eda74d5f 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -1,4 +1,4 @@ -define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) { +define(["events", "globalize", "dom", "date-fns", "dfnshelper", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) { "use strict"; function getEntryHtml(entry, apiClient) { @@ -16,7 +16,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific html += 'dvr" + }) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr"; } else { html += '' + icon + ''; } @@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific html += entry.Name; html += ""; html += '
'; - var date = datetime.parseISO8601Date(entry.Date, true); - html += datetime.toLocaleString(date).toLowerCase(); + html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() }); html += "
"; html += '
'; html += entry.ShortOverview || ""; diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 62bfb3cf40..17b51b376d 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -200,7 +200,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM var apiClient = this; - if (data.status === 401) { + if (data.status === 403) { if (data.errorCode === "ParentalControl") { var isCurrentAllowed = currentRouteInfo ? (currentRouteInfo.route.anonymous || currentRouteInfo.route.startup) : true; @@ -268,6 +268,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } function getMaxBandwidth() { + /* eslint-disable compat/compat */ if (navigator.connection) { var max = navigator.connection.downlinkMax; if (max && max > 0 && max < Number.POSITIVE_INFINITY) { @@ -279,6 +280,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM return max; } } + /* eslint-enable compat/compat */ return null; } @@ -577,8 +579,9 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM function showDirect(path) { return new Promise(function(resolve, reject) { - resolveOnNextShow = resolve, page.show(baseUrl()+path) - }) + resolveOnNextShow = resolve; + page.show(baseUrl() + path); + }); } function show(path, options) { diff --git a/src/components/apphost.js b/src/components/apphost.js index b9567ada94..75a6156b05 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -237,10 +237,6 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings", "g features.push("voiceinput"); } - if (!browser.tv && !browser.xboxOne) { - browser.ps4; - } - if (supportsHtmlMediaAutoplay()) { features.push("htmlaudioautoplay"); features.push("htmlvideoautoplay"); @@ -279,8 +275,8 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings", "g features.push("screensaver"); webSettings.enableMultiServer().then(enabled => { - if (enabled) features.push("multiserver") - }) + if (enabled) features.push("multiserver"); + }); if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { features.push("subtitleappearancesettings"); @@ -351,8 +347,6 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings", "g var deviceName; var appName = "Jellyfin Web"; var appVersion = "10.5.0"; - var visibilityChange; - var visibilityState; var appHost = { getWindowState: function () { @@ -383,7 +377,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings", "g return window.NativeShell.AppHost.getDefaultLayout(); } - return getDefaultLayout() + return getDefaultLayout(); }, getDeviceProfile: getDeviceProfile, init: function () { @@ -426,40 +420,26 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings", "g } }; - var doc = self.document; var isHidden = false; + var hidden; + var visibilityChange; - if (doc) { - if (void 0 !== doc.visibilityState) { - visibilityChange = "visibilitychange"; - visibilityState = "hidden"; + if (typeof document.hidden !== "undefined") { /* eslint-disable-line compat/compat */ + hidden = "hidden"; + visibilityChange = "visibilitychange"; + } else if (typeof document.webkitHidden !== "undefined") { + hidden = "webkitHidden"; + visibilityChange = "webkitvisibilitychange"; + } + + document.addEventListener(visibilityChange, function () { + /* eslint-disable-next-line compat/compat */ + if (document[hidden]) { + onAppHidden(); } else { - if (void 0 !== doc.mozHidden) { - visibilityChange = "mozvisibilitychange"; - visibilityState = "mozVisibilityState"; - } else { - if (void 0 !== doc.msHidden) { - visibilityChange = "msvisibilitychange"; - visibilityState = "msVisibilityState"; - } else { - if (void 0 !== doc.webkitHidden) { - visibilityChange = "webkitvisibilitychange"; - visibilityState = "webkitVisibilityState"; - } - } - } + onAppVisible(); } - } - - if (doc) { - doc.addEventListener(visibilityChange, function () { - if (document[visibilityState]) { - onAppHidden(); - } else { - onAppVisible(); - } - }); - } + }, false); if (self.addEventListener) { self.addEventListener("focus", onAppVisible); diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index a469eb8854..43c341bfdf 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -95,8 +95,10 @@ import layoutManager from "layoutManager"; return focusedElement; } - export default { - isEnabled: isEnabled, - enable: enable, - autoFocus: autoFocus - }; +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + enable: enable, + autoFocus: autoFocus +}; diff --git a/src/components/backdropscreensaver/plugin.js b/src/components/backdropscreensaver/plugin.js index c0bd31fb86..55de27a138 100644 --- a/src/components/backdropscreensaver/plugin.js +++ b/src/components/backdropscreensaver/plugin.js @@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) { currentSlideshow = null; } }; - } + }; }); diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 1249f802af..a4cf6edadc 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1082,11 +1082,7 @@ import 'programStyles'; if (options.showPersonRoleOrType) { if (item.Role) { - lines.push('as ' + item.Role); - } else if (item.Type) { - lines.push(globalize.translate('' + item.Type)); - } else { - lines.push(''); + lines.push(globalize.translate('PersonRole', item.Role)); } } } @@ -1854,6 +1850,8 @@ import 'programStyles'; } } +/* eslint-enable indent */ + export default { getCardsHtml: getCardsHtml, getDefaultBackgroundClass: getDefaultBackgroundClass, diff --git a/src/components/chromecast/chromecasthelpers.js b/src/components/chromecast/chromecasthelpers.js index 2fef0c68b3..9967a4d96c 100644 --- a/src/components/chromecast/chromecasthelpers.js +++ b/src/components/chromecast/chromecasthelpers.js @@ -188,9 +188,9 @@ define(['events'], function (events) { return apiClient.getEndpointInfo().then(function (endpoint) { if (endpoint.IsInNetwork) { return apiClient.getPublicSystemInfo().then(function (info) { - var localAddress = info.LocalAddress + var localAddress = info.LocalAddress; if (!localAddress) { - console.debug("No valid local address returned, defaulting to external one") + console.debug("No valid local address returned, defaulting to external one"); localAddress = serverAddress; } addToCache(serverAddress, localAddress); diff --git a/src/components/chromecast/chromecastplayer.js b/src/components/chromecast/chromecastplayer.js index 18103e433f..71058cf01b 100644 --- a/src/components/chromecast/chromecastplayer.js +++ b/src/components/chromecast/chromecastplayer.js @@ -5,7 +5,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' var currentResolve; var currentReject; - var PlayerName = 'Chromecast'; + var PlayerName = 'Google Cast'; function sendConnectionResult(isOk) { diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index 3969c8bedd..9a43ee8ad1 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -7,11 +7,11 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in systemInfo = info; return info; } - ) + ); } function onDialogClosed() { - loading.hide() + loading.hide(); } function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) { @@ -24,7 +24,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in var promises = []; if ("Network" === path) { - promises.push(ApiClient.getNetworkDevices()) + promises.push(ApiClient.getNetworkDevices()); } else { if (path) { promises.push(ApiClient.getDirectoryContents(path, fileOptions)); @@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in var instruction = options.instruction ? options.instruction + "

" : ""; html += '
'; html += instruction; - html += globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "\\\\server").replace("{1}", "\\\\192.168.1.101"); + html += globalize.translate("MessageDirectoryPickerInstruction", "\\\\server", "\\\\192.168.1.101"); if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) { html += "
"; html += "
"; @@ -101,7 +101,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in html += globalize.translate("MessageDirectoryPickerLinuxInstruction"); html += "
"; } - html += "
" + html += "
"; } html += '
'; html += '
'; @@ -144,13 +144,13 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in function alertText(text) { alertTextWithOptions({ text: text - }) + }); } function alertTextWithOptions(options) { require(["alert"], function(alert) { - alert(options) - }) + alert(options); + }); } function validatePath(path, validateWriteable, apiClient) { @@ -163,21 +163,20 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in } }).catch(function(response) { if (response) { - // TODO All alerts (across the project), should use Globalize.translate() if (response.status === 404) { - alertText("The path could not be found. Please ensure the path is valid and try again."); + alertText(Globalize.translate("PathNotFound")); return Promise.reject(); } if (response.status === 500) { if (validateWriteable) { - alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again."); + alertText(Globalize.translate("WriteAccessRequired")); } else { - alertText("The path could not be found. Please ensure the path is valid and try again.") + alertText(Globalize.translate("PathNotFound")); } - return Promise.reject() + return Promise.reject(); } } - return Promise.resolve() + return Promise.resolve(); }); } @@ -189,7 +188,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in if (lnkPath.classList.contains("lnkFile")) { content.querySelector("#txtDirectoryPickerPath").value = path; } else { - refreshDirectoryBrowser(content, path, fileOptions, true) + refreshDirectoryBrowser(content, path, fileOptions, true); } } }); @@ -276,7 +275,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in dlg.addEventListener("close", onDialogClosed); dialogHelper.open(dlg); dlg.querySelector(".btnCloseDialog").addEventListener("click", function() { - dialogHelper.close(dlg) + dialogHelper.close(dlg); }); currentDialog = dlg; dlg.querySelector("#txtDirectoryPickerPath").value = initialPath; @@ -294,9 +293,9 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in if (currentDialog) { dialogHelper.close(currentDialog); } - } + }; } var systemInfo; - return directoryBrowser + return directoryBrowser; }); diff --git a/src/components/displaysettings/displaysettings.js b/src/components/displaysettings/displaysettings.js index da407c11f1..682e60f604 100644 --- a/src/components/displaysettings/displaysettings.js +++ b/src/components/displaysettings/displaysettings.js @@ -186,6 +186,8 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' context.querySelector('#selectLanguage').value = userSettings.language() || ''; context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; + context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); + selectDashboardTheme.value = userSettings.dashboardTheme() || ''; selectTheme.value = userSettings.theme() || ''; @@ -215,6 +217,8 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value); userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); + userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); + userSettingsInstance.skin(context.querySelector('.selectSkin').value); userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); diff --git a/src/components/displaysettings/displaysettings.template.html b/src/components/displaysettings/displaysettings.template.html index 4ef8c8b1ca..62cb493e82 100644 --- a/src/components/displaysettings/displaysettings.template.html +++ b/src/components/displaysettings/displaysettings.template.html @@ -143,6 +143,11 @@
+
+ +
${LabelLibraryPageSizeHelp}
+
+
+
+ +
+
- ${HideWatchedContentFromLatestMedia} - -
-

${HeaderLibraryFolders}

diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index d08ab5bae7..ff4bfd6851 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -64,18 +64,18 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } else { var noLibDescription; if (user['Policy'] && user['Policy']['IsAdministrator']) { - noLibDescription = globalize.translate("NoCreatedLibraries", '', '') + noLibDescription = globalize.translate("NoCreatedLibraries", '', ''); } else { noLibDescription = globalize.translate("AskAdminToCreateLibrary"); } html += '
'; html += '

' + globalize.translate("MessageNothingHere") + '

'; - html += '

' + noLibDescription + '

' + html += '

' + noLibDescription + '

'; html += '
'; elem.innerHTML = html; - var createNowLink = elem.querySelector("#button-createLibrary") + var createNowLink = elem.querySelector("#button-createLibrary"); if (createNowLink) { createNowLink.addEventListener("click", function () { Dashboard.navigate("library.html"); @@ -131,7 +131,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews, allSections); } else if (section === 'librarybuttons') { - loadlibraryButtons(elem, apiClient, user, userSettings, userViews, allSections); + loadlibraryButtons(elem, apiClient, user, userSettings, userViews); } else if (section === 'resume') { loadResumeVideo(elem, apiClient, userId); } else if (section === 'resumeaudio') { @@ -640,7 +640,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } @@ -714,7 +714,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } @@ -786,7 +786,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la if (enableScrollX()) { html += '
'; - html += '
' + html += '
'; } else { html += '
'; } diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 338b8e6fef..8026110e21 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -31,7 +31,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve } function enableHlsShakaPlayer(item, mediaSource, mediaType) { - + /* eslint-disable-next-line compat/compat */ if (!!window.MediaSource && !!MediaSource.isTypeSupported) { if (canPlayNativeHls()) { @@ -162,7 +162,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve } } - function seekOnPlaybackStart(instance, element, ticks) { + function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { var seconds = (ticks || 0) / 10000000; @@ -171,13 +171,31 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve // Appending #t=xxx to the query string doesn't seem to work with HLS // For plain video files, not all browsers support it either - var delay = browser.safari ? 2500 : 0; - if (delay) { - setTimeout(function () { - setCurrentTimeIfNeeded(element, seconds); - }, delay); - } else { + + if (element.duration >= seconds) { + // media is ready, seek immediately setCurrentTimeIfNeeded(element, seconds); + if (onMediaReady) onMediaReady(); + } else { + // update video player position when media is ready to be sought + var events = ["durationchange", "loadeddata", "play", "loadedmetadata"]; + var onMediaChange = function(e) { + if (element.currentTime === 0 && element.duration >= seconds) { + // seek only when video position is exactly zero, + // as this is true only if video hasn't started yet or + // user rewound to the very beginning + // (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); + }); + if (onMediaReady) onMediaReady(); + } + }; + events.map(function (name) { + element.addEventListener(name, onMediaChange); + }); } } } diff --git a/src/components/htmlaudioplayer/plugin.js b/src/components/htmlaudioplayer/plugin.js index 8cae76bbee..90f954d503 100644 --- a/src/components/htmlaudioplayer/plugin.js +++ b/src/components/htmlaudioplayer/plugin.js @@ -101,7 +101,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._timeUpdated = false; self._currentTime = null; - var elem = createMediaElement(options); + var elem = createMediaElement(); return setCurrentSrc(elem, options); }; diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index 97056f80ff..fa0c022372 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -1,4 +1,4 @@ -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'fullscreenManager', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, fullscreenManager, globalize) { +define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'screenfull', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, screenfull, globalize) { "use strict"; /* globals cast */ @@ -116,8 +116,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa }); } - function normalizeTrackEventText(text) { - return text.replace(/\\N/gi, '\n'); + function normalizeTrackEventText(text, useHtml) { + var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + return useHtml ? result.replace(/\n/gi, '
') : result; } function setTracks(elem, tracks, item, mediaSource) { @@ -567,19 +568,19 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa self.resetSubtitleOffset = function() { currentTrackOffset = 0; showTrackOffset = false; - } + }; self.enableShowingSubtitleOffset = function() { showTrackOffset = true; - } + }; self.disableShowingSubtitleOffset = function() { showTrackOffset = false; - } + }; self.isShowingSubtitleOffsetEnabled = function() { return showTrackOffset; - } + }; function getTextTrack() { var videoElement = self._mediaElement; @@ -599,8 +600,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var offsetValue = parseFloat(offset); // if .ass currently rendering - if (currentAssRenderer) { + if (currentSubtitlesOctopus) { updateCurrentTrackOffset(offsetValue); + currentSubtitlesOctopus.timeOffset = offsetValue; } else { var trackElement = getTextTrack(); // if .vtt currently rendering @@ -651,7 +653,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa self.getSubtitleOffset = function() { return currentTrackOffset; - } + }; function isAudioStreamSupported(stream, deviceProfile) { @@ -793,7 +795,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa dlg.parentNode.removeChild(dlg); } - fullscreenManager.exitFullscreen(); + screenfull.exit(); }; function onEnded() { @@ -855,7 +857,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa loading.hide(); - htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks); + htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks, function () { + if (currentSubtitlesOctopus) currentSubtitlesOctopus.resize(); + }); if (self._currentPlayOptions.fullscreen) { @@ -1019,7 +1023,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa xhr.onerror = function (e) { reject(e); decrementFetchQueue(); - } + }; xhr.send(); }); @@ -1048,11 +1052,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function renderSsaAss(videoElement, track, item) { var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || []; + var apiClient = connectionManager.getApiClient(item); var options = { video: videoElement, subUrl: getTextTrackUrl(track, item), fonts: attachments.map(function (i) { - return i.DeliveryUrl; + return apiClient.getUrl(i.DeliveryUrl); }), workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js", legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js", @@ -1208,7 +1213,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa data.TrackEvents.forEach(function (trackEvent) { var trackCueObject = window.VTTCue || window.TextTrackCue; - var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text)); + var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false)); trackElement.addCue(cue); }); @@ -1218,11 +1223,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function updateSubtitleText(timeMs) { - // handle offset for ass tracks - if (currentTrackOffset) { - timeMs += (currentTrackOffset * 1000); - } - var clock = currentClock; if (clock) { try { @@ -1249,8 +1249,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (selectedTrackEvent && selectedTrackEvent.Text) { - - subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text); + subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); subtitleTextElement.classList.remove('hide'); } else { @@ -1427,11 +1426,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (browser.safari || browser.iOS || browser.iPad) { - list.push('AirPlay') + list.push('AirPlay'); } list.push('SetBrightness'); - list.push("SetAspectRatio") + list.push("SetAspectRatio"); return list; } @@ -1554,11 +1553,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa if (video) { if (isEnabled) { video.requestAirPlay().catch(function(err) { - console.error("Error requesting AirPlay", err) + console.error("Error requesting AirPlay", err); }); } else { document.exitAirPLay().catch(function(err) { - console.error("Error exiting AirPlay", err) + console.error("Error exiting AirPlay", err); }); } } @@ -1691,12 +1690,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var mediaElement = this._mediaElement; if (mediaElement) { if ("auto" === val) { - mediaElement.style.removeProperty("object-fit") + mediaElement.style.removeProperty("object-fit"); } else { - mediaElement.style["object-fit"] = val + mediaElement.style["object-fit"] = val; } } - this._currentAspectRatio = val + this._currentAspectRatio = val; }; HtmlVideoPlayer.prototype.getAspectRatio = function () { @@ -1713,7 +1712,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa }, { name: "Fill", id: "fill" - }] + }]; }; HtmlVideoPlayer.prototype.togglePictureInPicture = function () { diff --git a/src/components/humanedate.js b/src/components/humanedate.js deleted file mode 100644 index 26ce26d942..0000000000 --- a/src/components/humanedate.js +++ /dev/null @@ -1,74 +0,0 @@ -define(["datetime"], function (datetime) { - "use strict"; - - function humaneDate(date_str) { - var format; - var time_formats = [ - [90, "a minute"], - [3600, "minutes", 60], - [5400, "an hour"], - [86400, "hours", 3600], - [129600, "a day"], - [604800, "days", 86400], - [907200, "a week"], - [2628e3, "weeks", 604800], - [3942e3, "a month"], - [31536e3, "months", 2628e3], - [47304e3, "a year"], - [31536e5, "years", 31536e3] - ]; - var dt = new Date(); - var date = datetime.parseISO8601Date(date_str, true); - var seconds = (dt - date) / 1000.0; - var i = 0; - - if (seconds < 0) { - seconds = Math.abs(seconds); - } - // eslint-disable-next-line no-cond-assign - for (; format = time_formats[i++];) { - if (seconds < format[0]) { - if (2 == format.length) { - return format[1] + " ago"; - } - - return Math.round(seconds / format[2]) + " " + format[1] + " ago"; - } - } - - if (seconds > 47304e5) { - return Math.round(seconds / 47304e5) + " centuries ago"; - } - - return date_str; - } - - function humaneElapsed(firstDateStr, secondDateStr) { - // TODO replace this whole script with a library or something - var dateOne = new Date(firstDateStr); - var dateTwo = new Date(secondDateStr); - var delta = (dateTwo.getTime() - dateOne.getTime()) / 1e3; - var days = Math.floor(delta % 31536e3 / 86400); - var hours = Math.floor(delta % 31536e3 % 86400 / 3600); - var minutes = Math.floor(delta % 31536e3 % 86400 % 3600 / 60); - var seconds = Math.round(delta % 31536e3 % 86400 % 3600 % 60); - var elapsed = ""; - elapsed += 1 == days ? days + " day " : ""; - elapsed += days > 1 ? days + " days " : ""; - elapsed += 1 == hours ? hours + " hour " : ""; - elapsed += hours > 1 ? hours + " hours " : ""; - elapsed += 1 == minutes ? minutes + " minute " : ""; - elapsed += minutes > 1 ? minutes + " minutes " : ""; - elapsed += elapsed.length > 0 ? "and " : ""; - elapsed += 1 == seconds ? seconds + " second" : ""; - elapsed += 0 == seconds || seconds > 1 ? seconds + " seconds" : ""; - return elapsed; - } - - window.humaneDate = humaneDate; - window.humaneElapsed = humaneElapsed; - return { - humaneDate: humaneDate, - humaneElapsed: humaneElapsed - }; -}); diff --git a/src/components/imagedownloader/imagedownloader.js b/src/components/imagedownloader/imagedownloader.js index f4fcd7091f..9df083aea2 100644 --- a/src/components/imagedownloader/imagedownloader.js +++ b/src/components/imagedownloader/imagedownloader.js @@ -109,7 +109,7 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image html += ''; var startAtDisplay = totalRecordCount ? startIndex + 1 : 0; - html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount; + html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount); html += ''; diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index 764be06fd1..28b6923c33 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -1,10 +1,6 @@ define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', 'userSettings', 'require', 'css!./style'], function (lazyLoader, imageFetcher, layoutManager, browser, appSettings, userSettings, require) { 'use strict'; - var requestIdleCallback = window.requestIdleCallback || function (fn) { - fn(); - }; - var self = {}; function fillImage(elem, source, enableEffects) { diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index e41ccb9775..633706d209 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -1,4 +1,4 @@ -define(['datetime', 'itemHelper', 'css!./indicators.css', 'material-icons'], function (datetime, itemHelper) { +define(['datetime', 'itemHelper', 'emby-progressbar', 'css!./indicators.css', 'material-icons'], function (datetime, itemHelper) { 'use strict'; function enableProgressIndicator(item) { @@ -183,45 +183,6 @@ define(['datetime', 'itemHelper', 'css!./indicators.css', 'material-icons'], fun return ''; } - var ProgressBarPrototype = Object.create(HTMLDivElement.prototype); - - function onAutoTimeProgress() { - var start = parseInt(this.getAttribute('data-starttime')); - var end = parseInt(this.getAttribute('data-endtime')); - - var now = new Date().getTime(); - var total = end - start; - var pct = 100 * ((now - start) / total); - - pct = Math.min(100, pct); - pct = Math.max(0, pct); - - var itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); - itemProgressBarForeground.style.width = pct + '%'; - } - - ProgressBarPrototype.attachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - } - - if (this.getAttribute('data-automode') === 'time') { - this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); - } - }; - - ProgressBarPrototype.detachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - this.timeInterval = null; - } - }; - - document.registerElement('emby-progressbar', { - prototype: ProgressBarPrototype, - extends: 'div' - }); - return { getProgressHtml: getProgressHtml, getProgressBarHtml: getProgressBarHtml, diff --git a/src/components/input/gamepadtokey.js b/src/components/input/gamepadtokey.js index 5356bcbb45..c2cf5005f1 100644 --- a/src/components/input/gamepadtokey.js +++ b/src/components/input/gamepadtokey.js @@ -184,7 +184,7 @@ require(['apphost'], function (appHost) { function allowInput() { // This would be nice but always seems to return true with electron - if (!isElectron && document.hidden) { + if (!isElectron && document.hidden) { /* eslint-disable-line compat/compat */ return false; } @@ -254,7 +254,7 @@ require(['apphost'], function (appHost) { var inputLoopTimer; function runInputLoop() { // Get the latest gamepad state. - var gamepads = navigator.getGamepads(); + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (!gamepad) { @@ -362,7 +362,7 @@ require(['apphost'], function (appHost) { } function isGamepadConnected() { - var gamepads = navigator.getGamepads(); + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (gamepad && gamepad.connected) { @@ -373,6 +373,7 @@ require(['apphost'], function (appHost) { } function onFocusOrGamepadAttach(e) { + /* eslint-disable-next-line compat/compat */ if (isGamepadConnected() && document.hasFocus()) { console.log("Gamepad connected! Starting input loop"); startInputLoop(); @@ -380,6 +381,7 @@ require(['apphost'], function (appHost) { } function onFocusOrGamepadDetach(e) { + /* eslint-disable-next-line compat/compat */ if (!isGamepadConnected() || !document.hasFocus()) { console.log("Gamepad disconnected! No other gamepads are connected, stopping input loop"); stopInputLoop(); diff --git a/src/components/input/keyboardnavigation.js b/src/components/input/keyboardnavigation.js index d356854a3e..3c80063f4f 100644 --- a/src/components/input/keyboardnavigation.js +++ b/src/components/input/keyboardnavigation.js @@ -159,7 +159,9 @@ function attachGamepadScript(e) { } // No need to check for gamepads manually at load time, the eventhandler will be fired for that -window.addEventListener("gamepadconnected", attachGamepadScript); +if (navigator.getGamepads) { /* eslint-disable-line compat/compat */ + window.addEventListener("gamepadconnected", attachGamepadScript); +} export default { enable: enable, diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index e33b1273f4..ff2ec626d6 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -116,7 +116,7 @@ define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings", } function createAttribute(label, value) { - return '' + label + '' + value + "" + return '' + label + '' + value + ""; } function showMediaInfoMore(itemId, serverId, template) { diff --git a/src/components/itemcontextmenu.js b/src/components/itemcontextmenu.js index ddb1905d89..eec46899a7 100644 --- a/src/components/itemcontextmenu.js +++ b/src/components/itemcontextmenu.js @@ -90,7 +90,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", }); } - if (itemHelper.supportsAddingToPlaylist(item)) { + if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { commands.push({ name: globalize.translate("AddToPlaylist"), id: "addtoplaylist", @@ -339,7 +339,9 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", fileDownloader.download([{ url: downloadHref, itemId: itemId, - serverId: serverId + serverId: serverId, + title: item.Name, + filename: item.Path.replace(/^.*[\\\/]/, '') }]); getResolveFunction(getResolveFunction(resolve, id), id)(); }); @@ -352,6 +354,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", document.body.appendChild(textArea); textArea.focus(); textArea.select(); + if (document.execCommand("copy")) { require(["toast"], function (toast) { toast(globalize.translate("CopyStreamURLSuccess")); @@ -361,14 +364,19 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter", } document.body.removeChild(textArea); }; + + /* eslint-disable-next-line compat/compat */ if (navigator.clipboard === undefined) { textAreaCopy(); } else { + /* eslint-disable-next-line compat/compat */ navigator.clipboard.writeText(downloadHref).then(function () { require(["toast"], function (toast) { toast(globalize.translate("CopyStreamURLSuccess")); }); - }, textAreaCopy); + }).catch(function () { + textAreaCopy(); + }); } getResolveFunction(resolve, id)(); break; diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index 2a779618f2..9f89aef947 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -309,7 +309,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize", fullName = idInfo.Name + " " + globalize.translate(idInfo.Type); } - var idLabel = globalize.translate("LabelDynamicExternalId").replace("{0}", fullName); + var idLabel = globalize.translate("LabelDynamicExternalId", fullName); html += ''; diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index 21bcdf5933..1428f3468d 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -45,7 +45,7 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve // Take a guess at initial layout. The consuming app can override if (browser.mobile) { this.setLayout('mobile', false); - } else if (browser.tv || browser.xboxOne) { + } else if (browser.tv || browser.xboxOne || browser.ps4) { this.setLayout('tv', false); } else { this.setLayout(this.defaultLayout || 'tv', false); diff --git a/src/components/lazyloader/lazyloader-scroll.js b/src/components/lazyloader/lazyloader-scroll.js index d5120146ce..4930f6376c 100644 --- a/src/components/lazyloader/lazyloader-scroll.js +++ b/src/components/lazyloader/lazyloader-scroll.js @@ -4,10 +4,6 @@ define(['visibleinviewport', 'dom', 'browser'], function (visibleinviewport, dom var thresholdX; var thresholdY; - var requestIdleCallback = window.requestIdleCallback || function (fn) { - fn(); - }; - function resetThresholds() { var threshold = 0.3; diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index a398d7043a..08197299e0 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -36,7 +36,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct html += ""; } select.innerHTML = html; - }) + }); } function populateRefreshInterval(select) { @@ -120,7 +120,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct html += plugin.Name; html += ""; html += "
"; - i > 0 ? html += '' : plugins.length > 1 && (html += ''), html += "
" + i > 0 ? html += '' : plugins.length > 1 && (html += ''), html += "
"; } html += "
"; html += '
' + globalize.translate("LabelMetadataDownloadersHelp") + "
"; @@ -265,10 +265,10 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct renderMetadataFetchers(parent, availableOptions, {}); renderSubtitleFetchers(parent, availableOptions, {}); renderImageFetchers(parent, availableOptions, {}); - availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide") + availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide"); }).catch(function() { return Promise.resolve(); - }) + }); } function adjustSortableListElement(elem) { @@ -296,8 +296,8 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct Type: type }, currentLibraryOptions.TypeOptions.push(typeOptions)); var availableOptions = getTypeOptions(currentAvailableOptions || {}, type); - (new ImageOptionsEditor).show(type, typeOptions, availableOptions) - }) + (new ImageOptionsEditor).show(type, typeOptions, availableOptions); + }); } function onImageFetchersContainerClick(e) { @@ -315,12 +315,12 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct var list = dom.parentWithClass(li, "paperList"); if (btnSortable.classList.contains("btnSortableMoveDown")) { var next = li.nextSibling; - next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling)) + next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling)); } else { var prev = li.previousSibling; - prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev)) + prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev)); } - Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement) + Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement); } } @@ -407,13 +407,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct function setSubtitleFetchersIntoOptions(parent, options) { options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) { - return !elem.checked + return !elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }); options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }); } @@ -455,13 +455,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct } typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) { - return elem.checked + return elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }); typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }); } } @@ -505,20 +505,20 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked, RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked, MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { - return elem.checked + return elem.checked; }), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }), TypeOptions: [] }; options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll(".localReaderOption"), function(elem) { - return elem.getAttribute("data-pluginname") + return elem.getAttribute("data-pluginname"); }); options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) { - return elem.checked + return elem.checked; }), function(elem) { - return elem.getAttribute("data-lang") + return elem.getAttribute("data-lang"); }); setSubtitleFetchersIntoOptions(parent, options); setMetadataFetchersIntoOptions(parent, options); @@ -531,7 +531,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct function getOrderedPlugins(plugins, configuredOrder) { plugins = plugins.slice(0); plugins.sort(function(a, b) { - return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0 + return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0; }); return plugins; } @@ -558,10 +558,10 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches; parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch; Array.prototype.forEach.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) { - elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled") + elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled"); }); Array.prototype.forEach.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) { - elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang")) + elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang")); }); renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); renderMetadataFetchers(parent, parent.availableOptions, options); @@ -578,5 +578,5 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct getLibraryOptions: getLibraryOptions, setLibraryOptions: setLibraryOptions, setAdvancedVisible: setAdvancedVisible - } + }; }); diff --git a/src/components/logoscreensaver/plugin.js b/src/components/logoscreensaver/plugin.js index 7716bbf6e5..2becfad0c3 100644 --- a/src/components/logoscreensaver/plugin.js +++ b/src/components/logoscreensaver/plugin.js @@ -188,5 +188,5 @@ define(["pluginManager"], function (pluginManager) { } } }; - } + }; }); diff --git a/src/components/metadataeditor/metadataeditor.js b/src/components/metadataeditor/metadataeditor.js index 84b60b32a3..8a64cac7ef 100644 --- a/src/components/metadataeditor/metadataeditor.js +++ b/src/components/metadataeditor/metadataeditor.js @@ -470,7 +470,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi fullName = idInfo.Name + " " + globalize.translate(idInfo.Type); } - var labelText = globalize.translate("LabelDynamicExternalId").replace("{0}", fullName); + var labelText = globalize.translate('LabelDynamicExternalId', fullName); html += '
'; html += '
'; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 2c3e45b630..8ba870613b 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -6,6 +6,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir document.removeEventListener('keydown', onOneDocumentClick); if (window.Notification) { + /* eslint-disable-next-line compat/compat */ Notification.requestPermission(); } } @@ -26,6 +27,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } function resetRegistration() { + /* eslint-disable-next-line compat/compat */ var serviceWorker = navigator.serviceWorker; if (serviceWorker) { serviceWorker.ready.then(function (registration) { @@ -173,15 +175,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir }; if (status === 'completed') { - notification.title = globalize.translate('PackageInstallCompleted').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); notification.vibrate = true; } else if (status === 'cancelled') { - notification.title = globalize.translate('PackageInstallCancelled').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); } else if (status === 'failed') { - notification.title = globalize.translate('PackageInstallFailed').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); notification.vibrate = true; } else if (status === 'progress') { - notification.title = globalize.translate('InstallingPackage').replace('{0}', installation.Name + ' ' + installation.Version); + notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); notification.actions = [ diff --git a/src/components/nowplayingbar/nowplayingbar.js b/src/components/nowplayingbar/nowplayingbar.js index 8da9b9c053..a3839a9342 100644 --- a/src/components/nowplayingbar/nowplayingbar.js +++ b/src/components/nowplayingbar/nowplayingbar.js @@ -1,4 +1,4 @@ -define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 'layoutManager', 'playbackManager', 'nowPlayingHelper', 'apphost', 'dom', 'connectionManager', 'paper-icon-button-light', 'emby-ratingbutton'], function (require, datetime, itemHelper, events, browser, imageLoader, layoutManager, playbackManager, nowPlayingHelper, appHost, dom, connectionManager) { +define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', 'layoutManager', 'playbackManager', 'nowPlayingHelper', 'apphost', 'dom', 'connectionManager', 'itemContextMenu', 'paper-icon-button-light', 'emby-ratingbutton'], function (require, datetime, itemHelper, events, browser, imageLoader, layoutManager, playbackManager, nowPlayingHelper, appHost, dom, connectionManager, itemContextMenu) { 'use strict'; var currentPlayer; @@ -66,7 +66,7 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', html += '
'; html += ''; - html += ''; + html += ''; html += '
'; html += '
'; @@ -155,8 +155,6 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } }); - elem.querySelector('.remoteControlButton').addEventListener('click', showRemoteControl); - toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton.addEventListener('click', function () { @@ -187,29 +185,15 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', volumeSliderContainer.classList.remove('hide'); } - var volumeSliderTimer; - function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; - if (currentPlayer) { currentPlayer.setVolume(this.value); } } - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); - } - } - volumeSlider.addEventListener('change', setVolume); - volumeSlider.addEventListener('mousemove', setVolumeDelayed); - volumeSlider.addEventListener('touchmove', setVolumeDelayed); + volumeSlider.addEventListener('mousemove', setVolume); + volumeSlider.addEventListener('touchmove', setVolume); positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); positionSlider.addEventListener('change', function () { @@ -240,8 +224,8 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', elem.addEventListener('click', function (e) { - if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT', 'A'])) { - showRemoteControl(0); + if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { + showRemoteControl(); } }); } @@ -449,17 +433,13 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', } } - function getTextActionButton(item, text, serverId) { + function getTextActionButton(item, text) { if (!text) { text = itemHelper.getDisplayName(item); } - var html = ''; - - return html; + return `${text}`; } function seriesImageUrl(item, options) { @@ -537,16 +517,16 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', if (textLines.length > 1) { textLines[1].secondary = true; } - var serverId = nowPlayingItem ? nowPlayingItem.ServerId : null; nowPlayingTextElement.innerHTML = textLines.map(function (nowPlayingName) { var cssClass = nowPlayingName.secondary ? ' class="nowPlayingBarSecondaryText"' : ''; if (nowPlayingName.item) { - return '' + getTextActionButton(nowPlayingName.item, nowPlayingName.text, serverId) + '
'; + var nowPlayingText = getTextActionButton(nowPlayingName.item, nowPlayingName.text); + return `
${nowPlayingText}
`; } - return '' + nowPlayingName.text + '
'; + return `
${nowPlayingText}
`; }).join(''); @@ -575,15 +555,25 @@ define(['require', 'datetime', 'itemHelper', 'events', 'browser', 'imageLoader', if (isRefreshing) { var apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { - var userData = item.UserData || {}; var likes = userData.Likes == null ? '' : userData.Likes; - + var contextButton = document.querySelector('.btnToggleContextMenu'); + var options = { + play: false, + queue: false, + positionTo: contextButton + }; nowPlayingUserData.innerHTML = ''; + apiClient.getCurrentUser().then(function(user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: item, + user: user + }, options )); + }); + }); }); - } } else { nowPlayingUserData.innerHTML = ''; diff --git a/src/components/playback/experimentalwarnings.js b/src/components/playback/experimentalwarnings.js index 2d1ef53c19..02a7b82caf 100644 --- a/src/components/playback/experimentalwarnings.js +++ b/src/components/playback/experimentalwarnings.js @@ -44,24 +44,15 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function ( } function showBlurayMessage() { - - var message = - 'Playback of Bluray folders in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native Bluray folder support.'; - return showMessage(message, 'blurayexpirementalinfo', 'nativeblurayplayback'); + return showMessage(globalize.translate("UnsupportedPlayback"), 'blurayexpirementalinfo', 'nativeblurayplayback'); } function showDvdMessage() { - - var message = - 'Playback of Dvd folders in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native Dvd folder support.'; - return showMessage(message, 'dvdexpirementalinfo', 'nativedvdplayback'); + return showMessage(globalize.translate("UnsupportedPlayback"), 'dvdexpirementalinfo', 'nativedvdplayback'); } function showIsoMessage() { - - var message = - 'Playback of ISO files in this app is experimental. Some titles may not work at all. For a better experience, consider converting to mkv video files, or use an Jellyfin app with native ISO support.'; - return showMessage(message, 'isoexpirementalinfo', 'nativeisoplayback'); + return showMessage(globalize.translate("UnsupportedPlayback"), 'isoexpirementalinfo', 'nativeisoplayback'); } function ExpirementalPlaybackWarnings() { diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 8de1ffc190..f323d3609c 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1,6 +1,9 @@ -define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'fullscreenManager'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, fullscreenManager) { +define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'screenfull'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, screenfull) { 'use strict'; + /** Delay time in ms for reportPlayback logging */ + const reportPlaybackLogDelay = 1e3; + function enableLocalPlaylistManagement(player) { if (player.getPlaylist) { @@ -17,7 +20,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function bindToFullscreenChange(player) { - events.on(fullscreenManager, 'fullscreenchange', function () { + screenfull.on('change', function () { events.trigger(player, 'fullscreenchange'); }); } @@ -38,6 +41,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); } + /** Last invoked method */ + let reportPlaybackLastMethod; + + /** Last invoke time of method */ + let reportPlaybackLastTime; + function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { if (!serverId) { @@ -57,7 +66,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); } - console.debug(method + '-' + JSON.stringify(info)); + const now = (new Date).getTime(); + + if (method !== reportPlaybackLastMethod || now - (reportPlaybackLastTime || 0) >= reportPlaybackLogDelay) { + console.debug(method + '-' + JSON.stringify(info)); + reportPlaybackLastMethod = method; + reportPlaybackLastTime = now; + } + var apiClient = connectionManager.getApiClient(serverId); apiClient[method](info); } @@ -1518,7 +1534,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.isFullscreen(); } - return fullscreenManager.isFullScreen(); + return screenfull.isFullscreen; }; self.toggleFullscreen = function (player) { @@ -1528,10 +1544,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.toggleFulscreen(); } - if (fullscreenManager.isFullScreen()) { - fullscreenManager.exitFullscreen(); - } else { - fullscreenManager.requestFullscreen(); + if (screenfull.isEnabled) { + screenfull.toggle(); } }; @@ -1633,29 +1647,29 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.supportSubtitleOffset = function(player) { player = player || self._currentPlayer; return player && 'setSubtitleOffset' in player; - } + }; self.enableShowingSubtitleOffset = function(player) { player = player || self._currentPlayer; player.enableShowingSubtitleOffset(); - } + }; self.disableShowingSubtitleOffset = function(player) { player = player || self._currentPlayer; if (player.disableShowingSubtitleOffset) { player.disableShowingSubtitleOffset(); } - } + }; self.isShowingSubtitleOffsetEnabled = function(player) { player = player || self._currentPlayer; return player.isShowingSubtitleOffsetEnabled(); - } + }; self.isSubtitleStreamExternal = function(index, player) { var stream = getSubtitleStream(player, index); return stream ? getDeliveryMethod(stream) === 'External' : false; - } + }; self.setSubtitleOffset = function (value, player) { player = player || self._currentPlayer; @@ -1669,12 +1683,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (player.getSubtitleOffset) { return player.getSubtitleOffset(); } - } + }; self.canHandleOffsetOnCurrentSubtitle = function(player) { var index = self.getSubtitleStreamIndex(player); return index !== -1 && self.isSubtitleStreamExternal(index, player); - } + }; self.seek = function (ticks, player) { @@ -3140,7 +3154,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla AllowVideoStreamCopy: false, AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null - }, true); + }); return; } @@ -3378,7 +3392,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla pluginManager.ofType('mediaplayer').map(initMediaPlayer); function sendProgressUpdate(player, progressEventName, reportPlaylist) { - if (!player) { throw new Error('player cannot be null'); } diff --git a/src/components/playback/playbackorientation.js b/src/components/playback/playbackorientation.js index 5b178dbf08..5836298ead 100644 --- a/src/components/playback/playbackorientation.js +++ b/src/components/playback/playbackorientation.js @@ -17,6 +17,7 @@ define(['playbackManager', 'layoutManager', 'events'], function (playbackManager var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player); if (isLocalVideo && layoutManager.mobile) { + /* eslint-disable-next-line compat/compat */ var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock); if (lockOrientation) { @@ -40,6 +41,7 @@ define(['playbackManager', 'layoutManager', 'events'], function (playbackManager if (orientationLocked && !playbackStopInfo.nextMediaType) { + /* eslint-disable-next-line compat/compat */ var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock); if (unlockOrientation) { diff --git a/src/components/remotecontrol/remotecontrol.css b/src/components/remotecontrol/remotecontrol.css index 83a1c48e5f..9cdfcde151 100644 --- a/src/components/remotecontrol/remotecontrol.css +++ b/src/components/remotecontrol/remotecontrol.css @@ -1,3 +1,7 @@ +.nowPlayingPage { + padding: 5em 0 0 0 !important; +} + .nowPlayingInfoContainer { display: -webkit-box; display: -webkit-flex; @@ -36,8 +40,30 @@ margin: 0 0 0.5em 0.5em; } +.nowPlayingAlbum a, +.nowPlayingArtist a { + font-weight: normal; + text-align: left !important; + color: inherit !important; +} + +.nowPlayingButtonsContainer { + display: flex; +} + +.nowPlayingInfoContainerMedia { + text-align: left; + margin-bottom: 1em; +} + +.nowPlayingPositionSlider { + width: stretch; +} + .nowPlayingPositionSliderContainer { - margin: 0.7em 0 0.7em 1em; + margin: 0.2em 1em 0.2em 1em; + width: 100%; + z-index: 0; } .nowPlayingInfoButtons { @@ -59,17 +85,32 @@ } .nowPlayingPageImageContainer { - width: 20%; - margin-right: 0.25em; + width: 16%; + margin-right: 1em; position: relative; -webkit-flex-shrink: 0; flex-shrink: 0; } -@media all and (min-width: 50em) { - .nowPlayingPageImageContainer { - width: 16%; - } +.nowPlayingPageImageContainerNoAlbum { + width: 100%; + position: relative; +} + +.nowPlayingPageImageContainerNoAlbum button { + cursor: default; +} + +.nowPlayingPageImageContainerNoAlbum::after { + content: ""; + display: block; + padding-bottom: 100%; +} + +.btnPlayPause { + font-size: xx-large; + padding: 0; + margin: 0; } .nowPlayingInfoControls { @@ -87,14 +128,15 @@ } .nowPlayingPageImage { + display: block; bottom: 0; left: 0; right: 0; + margin: 0 auto; width: 100%; -webkit-box-shadow: 0 0 1.9vh #000; box-shadow: 0 0 1.9vh #000; border: 0.1em solid #222; - user-drag: none; user-select: none; -moz-user-select: none; -webkit-user-drag: none; @@ -102,60 +144,16 @@ -ms-user-select: none; } -@media all and (orientation: portrait) and (max-width: 50em) { - .nowPlayingInfoContainer { - -webkit-box-orient: vertical !important; - -webkit-box-direction: normal !important; - -webkit-flex-direction: column !important; - flex-direction: column !important; - -webkit-box-align: center; - -webkit-align-items: center; - align-items: center; - } - - .nowPlayingPageTitle { - text-align: center; - margin: 0.5em 0 0.75em; - } - - .nowPlayingPositionSliderContainer { - margin: 0.7em 1em; - } - - .nowPlayingInfoButtons { - -webkit-box-pack: center; - -webkit-justify-content: center; - justify-content: center; - } - - .nowPlayingPageImageContainer { - width: auto; - margin-right: 0; - } - - .nowPlayingInfoControls { - margin-top: 1em; - max-width: 100%; - } - - .nowPlayingPageImage { - width: auto; - height: 36vh; - } +.contextMenuList { + padding: 1.5em 0; } -@media all and (orientation: portrait) and (max-width: 40em) { - .nowPlayingPageImage { - height: 30vh; - } +.contextMenuList a { + color: inherit !important; } -.nowPlayingTime { - display: flex; - -webkit-box-align: center; - -webkit-align-items: center; - align-items: center; - margin: 0 1em; +.contextMenuList i.listItemIcon { + font-size: x-large; } .nowPlayingSecondaryButtons { @@ -167,12 +165,17 @@ align-items: center; -webkit-flex-wrap: wrap; flex-wrap: wrap; - -webkit-box-pack: center; - -webkit-justify-content: center; - justify-content: center; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + z-index: 0; } -@media all and (min-width: 50em) { +@media all and (min-width: 63em) { + .nowPlayingPage { + padding: 8em 0 0 0 !important; + } + .nowPlayingSecondaryButtons { -webkit-box-flex: 1; -webkit-flex-grow: 1; @@ -181,6 +184,16 @@ -webkit-justify-content: flex-end; justify-content: flex-end; } + + .nowPlayingPageUserDataButtonsTitle { + display: none !important; + } + + .playlistSectionButton, + .nowPlayingPlaylist, + .nowPlayingContextMenu { + background: unset !important; + } } @media all and (min-width: 80em) { @@ -189,6 +202,414 @@ } } +@media all and (orientation: portrait) and (max-width: 47em) { + .remoteControlContent { + padding-left: 7.3% !important; + padding-right: 7.3% !important; + display: flex; + height: 100%; + flex-direction: column; + } + + .nowPlayingInfoContainer { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -webkit-flex-direction: column !important; + flex-direction: column !important; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + width: 100%; + height: calc(100% - 4.2em); + } + + .nowPlayingPageTitle { + /* text-align: center; */ + margin: 0; + } + + .nowPlayingAlbum, + .nowPlayingArtist { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .nowPlayingInfoContainerMedia { + text-align: left !important; + width: 80%; + } + + .nowPlayingPositionSliderContainer { + margin: 0.2em 1em 0.2em 1em; + } + + .nowPlayingInfoButtons { + /* margin: 1.5em 0 0 0; */ + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + font-size: x-large; + height: 100%; + } + + .nowPlayingPageImageContainer { + width: 100%; + margin: auto auto 0.5em; + } + + .nowPlayingPageImageContainerNoAlbum .cardImageContainer .cardImageIcon { + font-size: 15em; + color: inherit; + } + + .nowPlayingInfoControls { + margin: 0.5em 0 1em 0; + width: 100%; + -webkit-box-pack: start !important; + -webkit-justify-content: start !important; + justify-content: start !important; + } + + .nowPlayingSecondaryButtons { + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle { + width: 20%; + font-size: large; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { + padding-top: 0; + padding-right: 0; + margin-right: 0; + float: right; + border-radius: 0; + } + + .nowPlayingInfoButtons .btnRewind { + position: absolute; + left: 0; + margin-left: 0; + padding-left: 7.3%; + font-size: smaller; + } + + .nowPlayingInfoButtons .btnFastForward { + position: absolute; + right: 0; + margin-right: 0; + padding-right: 7.3%; + font-size: smaller; + } + + .paper-icon-button-light:hover { + color: #fff !important; + background-color: transparent !important; + } + + .btnPlayPause { + padding: 0; + margin: 0; + font-size: 1.7em; + } + + .btnPlayPause:hover { + background-color: transparent !important; + } + + .nowPlayingPageImage { + /* width: inherit; */ + overflow-y: hidden; + overflow: hidden; + margin: 0 auto; + } + + .nowPlayingPageImage.nowPlayingPageImageAudio { + width: 100%; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster { + height: 50%; + overflow: hidden; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster img { + height: 100%; + width: auto; + } + + #nowPlayingPage .playlistSection .playlist, + #nowPlayingPage .playlistSection .contextMenu { + position: absolute; + top: 12.2em; + bottom: 4.2em; + overflow: scroll; + padding: 0 1em; + display: inline-block; + left: 0; + right: 0; + z-index: 1000; + } + + .playlistSectionButton { + position: fixed; + bottom: 0; + left: 0; + height: 4.2em; + right: 0; + padding-left: 7.3%; + padding-right: 7.3%; + } + + .playlistSectionButton .btnTogglePlaylist { + font-size: larger; + margin: 0; + padding-left: 0; + } + + .playlistSectionButton .btnSavePlaylist { + margin: 0; + padding-right: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + border-radius: 0; + } + + .playlistSectionButton .btnToggleContextMenu { + font-size: larger; + margin: 0; + padding-right: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + border-radius: 0; + } + + .playlistSectionButton .volumecontrol { + width: 100%; + } + + .remoteControlSection { + margin: 0; + padding: 0 0 4.2em 0; + } + + .nowPlayingButtonsContainer { + display: flex; + height: 100%; + flex-direction: column; + } +} + +@media all and (orientation: landscape) and (max-width: 63em) { + .remoteControlContent { + padding-left: 4.3% !important; + padding-right: 4.3% !important; + display: flex; + height: 100%; + flex-direction: column; + } + + .nowPlayingInfoContainer { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -webkit-flex-direction: row !important; + flex-direction: row !important; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + width: 100%; + height: calc(100% - 4.2em); + } + + .nowPlayingPageTitle { + /* text-align: center; */ + margin: 0; + } + + .nowPlayingInfoContainerMedia { + text-align: left !important; + width: 80%; + } + + .nowPlayingPositionSliderContainer { + margin: 0.2em 1em 0.2em 1em; + } + + .nowPlayingInfoButtons { + /* margin: 1.5em 0 0 0; */ + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + font-size: x-large; + height: 100%; + } + + .nowPlayingPageImageContainer { + width: 30%; + margin: auto 1em auto auto; + } + + .nowPlayingPageImageContainerNoAlbum .cardImageContainer .cardImageIcon { + font-size: 12em; + color: inherit; + } + + .nowPlayingInfoControls { + margin: 0.5em 0 1em 0; + width: 100%; + -webkit-box-pack: start !important; + -webkit-justify-content: start !important; + justify-content: start !important; + } + + .nowPlayingSecondaryButtons { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle { + width: 20%; + font-size: large; + } + + .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { + padding-top: 0; + padding-right: 0; + margin-right: 0; + float: right; + border-radius: 0; + } + + .paper-icon-button-light:hover { + color: #fff !important; + background-color: transparent !important; + } + + .btnPlayPause { + padding: 0; + margin: 0; + font-size: 1.7em; + } + + .btnPlayPause:hover { + background-color: transparent !important; + } + + .nowPlayingPageImage { + /* width: inherit; */ + overflow-y: hidden; + overflow: hidden; + margin: 0 auto; + } + + .nowPlayingPageImage.nowPlayingPageImageAudio { + width: 100%; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster { + height: 100%; + overflow: hidden; + } + + .nowPlayingPageImageContainer.nowPlayingPageImagePoster img { + height: 100%; + width: auto; + } + + #nowPlayingPage .playlistSection .playlist, + #nowPlayingPage .playlistSection .contextMenu { + position: absolute; + top: 7.2em; + bottom: 4.2em; + overflow: scroll; + padding: 0 1em; + display: inline-block; + left: 0; + right: 0; + z-index: 1000; + } + + .playlistSectionButton { + position: fixed; + bottom: 0; + left: 0; + height: 4.2em; + right: 0; + padding-left: 4.3%; + padding-right: 4.3%; + } + + .playlistSectionButton .btnTogglePlaylist { + font-size: larger; + margin: 0; + padding-left: 0; + } + + .playlistSectionButton .btnSavePlaylist { + margin: 0; + padding-right: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + border-radius: 0; + } + + .playlistSectionButton .btnToggleContextMenu { + font-size: larger; + margin: 0; + padding-right: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + border-radius: 0; + } + + .playlistSectionButton .volumecontrol { + width: 100%; + } + + .remoteControlSection { + margin: 4.2em 0 0 0; + padding: 0 0 4.2em 0; + } + + .nowPlayingButtonsContainer { + display: flex; + height: 100%; + flex-direction: column; + } +} + +.nowPlayingTime { + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + margin: 0 1em; +} + .nowPlayingNavButtonContainer { width: 30em; } @@ -214,8 +635,11 @@ width: 9em; } -@media all and (max-width: 50em) { - .nowPlayingInfoButtons .nowPlayingPageUserDataButtons { +@media all and (max-width: 63em) { + .nowPlayingSecondaryButtons .nowPlayingPageUserDataButtons, + .nowPlayingSecondaryButtons .repeatToggleButton, + .nowPlayingInfoButtons .playlist .listItemMediaInfo, + .nowPlayingInfoButtons .btnStop { display: none !important; } @@ -223,17 +647,3 @@ font-size: 4em; } } - -@media all and (max-width: 47em) { - .nowPlayingInfoButtons .repeatToggleButton { - display: none !important; - } -} - -@media all and (max-width: 34em) { - .nowPlayingInfoButtons .btnNowPlayingFastForward, - .nowPlayingInfoButtons .btnNowPlayingRewind, - .nowPlayingInfoButtons .playlist .listItemMediaInfo { - display: none !important; - } -} diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 7b620d536a..8e2a382d1d 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,4 +1,4 @@ -define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageLoader", "playbackManager", "nowPlayingHelper", "events", "connectionManager", "apphost", "globalize", "layoutManager", "userSettings", "cardStyle", "emby-itemscontainer", "css!./remotecontrol.css", "emby-ratingbutton"], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings) { +define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageLoader", "playbackManager", "nowPlayingHelper", "events", "connectionManager", "apphost", "globalize", "layoutManager", "userSettings", "cardBuilder", "cardStyle", "emby-itemscontainer", "css!./remotecontrol.css", "emby-ratingbutton"], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder) { "use strict"; function showAudioMenu(context, player, button, item) { @@ -110,49 +110,93 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL return null; } - function updateNowPlayingInfo(context, state) { + function updateNowPlayingInfo(context, state, serverId) { var item = state.NowPlayingItem; var displayName = item ? getNowPlayingNameHtml(item).replace("
", " - ") : ""; - context.querySelector(".nowPlayingPageTitle").innerHTML = displayName; + if (typeof item !== 'undefined') { + var nowPlayingServerId = (item.ServerId || serverId); + if (item.Type == "Audio" || item.MediaStreams[0].Type == "Audio") { + var songName = item.Name; + if (item.Album != null && item.Artists != null) { + var albumName = item.Album; + var artistName; + if (item.ArtistItems != null) { + artistName = item.ArtistItems[0].Name; + context.querySelector(".nowPlayingAlbum").innerHTML = '${albumName}`; + context.querySelector(".nowPlayingArtist").innerHTML = '${artistName}`; + context.querySelector(".contextMenuAlbum").innerHTML = ' ` + globalize.translate("ViewAlbum") + ''; + context.querySelector(".contextMenuArtist").innerHTML = ' ` + globalize.translate("ViewArtist") + ''; + } else { + artistName = item.Artists; + context.querySelector(".nowPlayingAlbum").innerHTML = albumName; + context.querySelector(".nowPlayingArtist").innerHTML = artistName; + } + } + context.querySelector(".nowPlayingSongName").innerHTML = songName; + } else if (item.Type == "Episode") { + if (item.SeasonName != null) { + var seasonName = item.SeasonName; + context.querySelector(".nowPlayingSeason").innerHTML = '${seasonName}`; + } + if (item.SeriesName != null) { + var seriesName = item.SeriesName; + if (item.SeriesId !=null) { + context.querySelector(".nowPlayingSerie").innerHTML = '${seriesName}`; + } else { + context.querySelector(".nowPlayingSerie").innerHTML = seriesName; + } + } + context.querySelector(".nowPlayingEpisode").innerHTML = item.Name; + } else { + context.querySelector(".nowPlayingPageTitle").innerHTML = displayName; + } - if (displayName.length > 0) { - context.querySelector(".nowPlayingPageTitle").classList.remove("hide"); - } else { - context.querySelector(".nowPlayingPageTitle").classList.add("hide"); - } + if (displayName.length > 0 && item.Type != "Audio" && item.Type != "Episode") { + context.querySelector(".nowPlayingPageTitle").classList.remove("hide"); + } else { + context.querySelector(".nowPlayingPageTitle").classList.add("hide"); + } - var url = item ? seriesImageUrl(item, { - maxHeight: 300 * 2 - }) || imageUrl(item, { - maxHeight: 300 * 2 - }) : null; + var url = item ? seriesImageUrl(item, { + maxHeight: 300 * 2 + }) || imageUrl(item, { + maxHeight: 300 * 2 + }) : null; - console.debug("updateNowPlayingInfo"); - setImageUrl(context, url); - if (item) { - backdrop.setBackdrops([item]); - var apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - var userData = fullItem.UserData || {}; - var likes = null == userData.Likes ? "" : userData.Likes; - context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ''; - }); - } else { - backdrop.clear(); - context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ""; + console.debug("updateNowPlayingInfo"); + setImageUrl(context, state, url); + if (item) { + backdrop.setBackdrops([item]); + var apiClient = connectionManager.getApiClient(item.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + var userData = fullItem.UserData || {}; + var likes = null == userData.Likes ? "" : userData.Likes; + context.querySelector(".nowPlayingPageUserDataButtonsTitle").innerHTML = ''; + context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ''; + }); + } else { + backdrop.clear(); + context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ""; + } } } - function setImageUrl(context, url) { + function setImageUrl(context, state, url) { currentImgUrl = url; + var item = state.NowPlayingItem; var imgContainer = context.querySelector(".nowPlayingPageImageContainer"); if (url) { imgContainer.innerHTML = ''; - imgContainer.classList.remove("hide"); + if (item.Type == "Audio") { + context.querySelector(".nowPlayingPageImage").classList.add("nowPlayingPageImageAudio"); + context.querySelector(".nowPlayingPageImageContainer").classList.remove("nowPlayingPageImageAudio"); + } else { + context.querySelector(".nowPlayingPageImageContainer").classList.add("nowPlayingPageImagePoster"); + context.querySelector(".nowPlayingPageImage").classList.remove("nowPlayingPageImageAudio"); + } } else { - imgContainer.classList.add("hide"); - imgContainer.innerHTML = ""; + imgContainer.innerHTML = '
'; } } @@ -199,28 +243,35 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL var supportedCommands = playerInfo.supportedCommands; currentPlayerSupportedCommands = supportedCommands; var playState = state.PlayState || {}; - buttonVisible(context.querySelector(".btnToggleFullscreen"), item && "Video" == item.MediaType && -1 != supportedCommands.indexOf("ToggleFullscreen")); + var isSupportedCommands = supportedCommands.includes("DisplayMessage") || supportedCommands.includes("SendString") || supportedCommands.includes("Select"); + buttonVisible(context.querySelector(".btnToggleFullscreen"), item && "Video" == item.MediaType && supportedCommands.includes("ToggleFullscreen")); updateAudioTracksDisplay(player, context); updateSubtitleTracksDisplay(player, context); - if (-1 != supportedCommands.indexOf("DisplayMessage") && !currentPlayer.isLocalPlayer) { + if (supportedCommands.includes("DisplayMessage") && !currentPlayer.isLocalPlayer) { context.querySelector(".sendMessageSection").classList.remove("hide"); } else { context.querySelector(".sendMessageSection").classList.add("hide"); } - if (-1 != supportedCommands.indexOf("SendString") && !currentPlayer.isLocalPlayer) { + if (supportedCommands.includes("SendString") && !currentPlayer.isLocalPlayer) { context.querySelector(".sendTextSection").classList.remove("hide"); } else { context.querySelector(".sendTextSection").classList.add("hide"); } - if (-1 != supportedCommands.indexOf("Select") && !currentPlayer.isLocalPlayer) { + if (supportedCommands.includes("Select") && !currentPlayer.isLocalPlayer) { context.querySelector(".navigationSection").classList.remove("hide"); } else { context.querySelector(".navigationSection").classList.add("hide"); } + if (isSupportedCommands && !currentPlayer.isLocalPlayer) { + context.querySelector(".remoteControlSection").classList.remove("hide"); + } else { + context.querySelector(".remoteControlSection").classList.add("hide"); + } + buttonVisible(context.querySelector(".btnStop"), null != item); buttonVisible(context.querySelector(".btnNextTrack"), null != item); buttonVisible(context.querySelector(".btnPreviousTrack"), null != item); @@ -331,7 +382,7 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL function updatePlayPauseState(isPaused, isActive) { var context = dlg; var btnPlayPause = context.querySelector(".btnPlayPause"); - btnPlayPause.querySelector("i").innerHTML = isPaused ? "" : "pause"; + btnPlayPause.querySelector("i").innerHTML = isPaused ? "" : ""; buttonVisible(btnPlayPause, isActive); } @@ -374,9 +425,9 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL }); if (items.length) { - context.querySelector(".playlistSection").classList.remove("hide"); + context.querySelector(".btnTogglePlaylist").classList.remove("hide"); } else { - context.querySelector(".playlistSection").classList.add("hide"); + context.querySelector(".btnTogglePlaylist").classList.add("hide"); } var itemsContainer = context.querySelector(".playlist"); @@ -393,6 +444,9 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL } imageLoader.lazyChildren(itemsContainer); + context.querySelector(".playlist").classList.add("hide"); + context.querySelector(".contextMenu").classList.add("hide"); + context.querySelector(".btnSavePlaylist").classList.add("hide"); }); } @@ -614,27 +668,25 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL return datetime.getDisplayRunningTime(ticks); }; - var volumeSliderTimer; - function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; - playbackManager.setVolume(this.value, currentPlayer); } - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); - } + var contextmenuHtml = ''; + var volumecontrolHtml = '
'; + volumecontrolHtml += ''; + volumecontrolHtml += '
'; + volumecontrolHtml += '
'; + if (!layoutManager.mobile) { + context.querySelector(".nowPlayingSecondaryButtons").innerHTML += volumecontrolHtml; + context.querySelector(".playlistSectionButton").innerHTML += contextmenuHtml; + } else { + context.querySelector(".playlistSectionButton").innerHTML += volumecontrolHtml + contextmenuHtml; } context.querySelector(".nowPlayingVolumeSlider").addEventListener("change", setVolume); - context.querySelector(".nowPlayingVolumeSlider").addEventListener("mousemove", setVolumeDelayed); - context.querySelector(".nowPlayingVolumeSlider").addEventListener("touchmove", setVolumeDelayed); + context.querySelector(".nowPlayingVolumeSlider").addEventListener("mousemove", setVolume); + context.querySelector(".nowPlayingVolumeSlider").addEventListener("touchmove", setVolume); context.querySelector(".buttonMute").addEventListener("click", function () { playbackManager.toggleMute(currentPlayer); }); @@ -648,6 +700,27 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); }); context.querySelector(".btnSavePlaylist").addEventListener("click", savePlaylist); + context.querySelector(".btnTogglePlaylist").addEventListener("click", function () { + if (context.querySelector(".playlist").classList.contains("hide")) { + context.querySelector(".playlist").classList.remove("hide"); + context.querySelector(".btnSavePlaylist").classList.remove("hide"); + context.querySelector(".contextMenu").classList.add("hide"); + context.querySelector(".volumecontrol").classList.add("hide"); + } else { + context.querySelector(".playlist").classList.add("hide"); + context.querySelector(".btnSavePlaylist").classList.add("hide"); + context.querySelector(".volumecontrol").classList.remove("hide"); + } + }); + context.querySelector(".btnToggleContextMenu").addEventListener("click", function () { + if (context.querySelector(".contextMenu").classList.contains("hide")) { + context.querySelector(".contextMenu").classList.remove("hide"); + context.querySelector(".btnSavePlaylist").classList.add("hide"); + context.querySelector(".playlist").classList.add("hide"); + } else { + context.querySelector(".contextMenu").classList.add("hide"); + } + }); } function onPlayerChange() { diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 037ca5b059..6a626cd254 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -544,8 +544,10 @@ import layoutManager from "layoutManager"; }, {capture: true}); } - export default { - isEnabled: isEnabled, - scrollTo: scrollTo, - scrollToElement: scrollToElement - }; +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + scrollTo: scrollTo, + scrollToElement: scrollToElement +}; diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 4d426f2484..48ccee21c8 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -1,8 +1,18 @@ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'loading', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, loading) { +/** + * Image viewer component + * @module components/slideshow/slideshow + */ +define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) { 'use strict'; + /** + * Retrieves an item's image URL from the API. + * @param {object|string} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's image. + */ function getImageUrl(item, options, apiClient) { - options = options || {}; options.type = options.type || "Primary"; @@ -11,7 +21,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; return apiClient.getScaledImageUrl(item.Id, options); } @@ -27,8 +36,14 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return null; } + /** + * Retrieves a backdrop's image URL from the API. + * @param {object} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's backdrop. + */ function getBackdropImageUrl(item, options, apiClient) { - options = options || {}; options.type = options.type || "Backdrop"; @@ -46,19 +61,19 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return null; } - function getImgUrl(item, original) { - + /** + * Dispatches a request for an item's image to its respective handler. + * @param {object} item - Item used to generate the image URL. + * @returns {string} URL of the item's image. + */ + function getImgUrl(item) { var apiClient = connectionManager.getApiClient(item.ServerId); var imageOptions = {}; - if (!original) { - imageOptions.maxWidth = screen.availWidth; - } if (item.BackdropImageTags && item.BackdropImageTags.length) { return getBackdropImageUrl(item, imageOptions, apiClient); } else { - - if (item.MediaType === 'Photo' && original) { + if (item.MediaType === 'Photo') { return apiClient.getItemDownloadUrl(item.Id); } imageOptions.type = "Primary"; @@ -66,15 +81,25 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Generates a button using the specified icon, classes and properties. + * @param {string} icon - Name of the material icon on the button + * @param {string} cssClass - CSS classes to assign to the button + * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. + * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. + * @returns {string} The HTML markup of the button. + */ function getIcon(icon, cssClass, canFocus, autoFocus) { - var tabIndex = canFocus ? '' : ' tabindex="-1"'; autoFocus = autoFocus ? ' autofocus' : ''; return ''; } + /** + * Sets the viewport meta tag to enable or disable scaling by the user. + * @param {boolean} scalable - Flag to set the scalability of the viewport. + */ function setUserScalable(scalable) { - try { appHost.setUserScalable(scalable); } catch (err) { @@ -83,23 +108,31 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } return function (options) { - var self = this; + /** Initialized instance of Swiper. */ var swiperInstance; - var dlg; - var currentTimeout; - var currentIntervalMs; + /** Initialized instance of the dialog containing the Swiper instance. */ + var dialog; + /** Options of the slideshow components */ var currentOptions; - var currentIndex; + /** ID of the timeout used to hide the OSD. */ + var hideTimeout; + /** Last coordinates of the mouse pointer. */ + var lastMouseMoveData; + /** Visibility status of the OSD. */ + var _osdOpen = false; - // small hack since this is not possible anyway - if (browser.chromecast) { - options.interactive = false; - } + // Use autoplay on Chromecast since it is non-interactive. + options.interactive = !browser.chromecast; + /** + * Creates the HTML markup for the dialog and the OSD. + * @param {Object} options - Options used to create the dialog and slideshow. + */ function createElements(options) { + currentOptions = options; - dlg = dialogHelper.createDialog({ + dialog = dialogHelper.createDialog({ exitAnimationDuration: options.interactive ? 400 : 800, size: 'fullscreen', autoFocus: false, @@ -108,17 +141,15 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f removeOnClose: true }); - dlg.classList.add('slideshowDialog'); + dialog.classList.add('slideshowDialog'); var html = ''; - if (options.interactive) { + html += '
'; + if (options.interactive && !layoutManager.tv) { var actionButtonsOnTop = layoutManager.mobile; - html += '
'; - html += '
'; - html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); @@ -137,7 +168,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f if (!actionButtonsOnTop) { html += '
'; - html += getIcon('pause', 'btnSlideshowPause slideshowButton', true, true); + html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); if (appHost.supports('filedownload')) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } @@ -148,33 +179,28 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f html += '
'; } - html += '
'; - } else { html += '

'; } - dlg.innerHTML = html; + dialog.innerHTML = html; - if (options.interactive) { - dlg.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { - - dialogHelper.close(dlg); + if (options.interactive && !layoutManager.tv) { + dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { + dialogHelper.close(dialog); }); - dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage); - dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage); - var btnPause = dlg.querySelector('.btnSlideshowPause'); + var btnPause = dialog.querySelector('.btnSlideshowPause'); if (btnPause) { btnPause.addEventListener('click', playPause); } - var btnDownload = dlg.querySelector('.btnDownload'); + var btnDownload = dialog.querySelector('.btnDownload'); if (btnDownload) { btnDownload.addEventListener('click', download); } - var btnShare = dlg.querySelector('.btnShare'); + var btnShare = dialog.querySelector('.btnShare'); if (btnShare) { btnShare.addEventListener('click', share); } @@ -182,81 +208,111 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f setUserScalable(true); - dialogHelper.open(dlg).then(function () { - + dialogHelper.open(dialog).then(function () { setUserScalable(false); - stopInterval(); }); inputManager.on(window, onInputCommand); document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - dlg.addEventListener('close', onDialogClosed); + dialog.addEventListener('close', onDialogClosed); - if (options.interactive) { - loadSwiper(dlg); - } + loadSwiper(dialog, options); } + /** + * Handles OSD changes when the autoplay is started. + */ function onAutoplayStart() { - var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); + var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i'); if (btnSlideshowPause) { - btnSlideshowPause.classList.remove("play_arrow"); - btnSlideshowPause.classList.add("pause"); + btnSlideshowPause.classList.replace("play_arrow", "pause"); } } + /** + * Handles OSD changes when the autoplay is stopped. + */ function onAutoplayStop() { - var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i'); + var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i'); if (btnSlideshowPause) { - btnSlideshowPause.classList.remove("pause"); - btnSlideshowPause.classList.add("play_arrow"); + btnSlideshowPause.classList.replace("pause", "play_arrow"); } } - function loadSwiper(dlg) { - + /** + * Initializes the Swiper instance and binds the relevant events. + * @param {HTMLElement} dialog - Element containing the dialog. + * @param {Object} options - Options used to initialize the Swiper instance. + */ + function loadSwiper(dialog, options) { + var slides; if (currentOptions.slides) { - dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.slides.map(getSwiperSlideHtmlFromSlide).join(''); + slides = currentOptions.slides; } else { - dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.items.map(getSwiperSlideHtmlFromItem).join(''); + slides = currentOptions.items; } require(['swiper'], function (Swiper) { - - swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), { - // Optional parameters + swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { direction: 'horizontal', - loop: options.loop !== false, - autoplay: { - delay: options.interval || 8000 + // Loop is disabled due to the virtual slides option not supporting it. + loop: false, + zoom: { + minRatio: 1, + toggle: true, + containerClass: 'slider-zoom-container' }, - // Disable preloading of all images - preloadImages: false, - // Enable lazy loading - lazy: true, - loadPrevNext: true, - disableOnInteraction: false, + autoplay: !options.interactive, + keyboard: { + enabled: true + }, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, initialSlide: options.startIndex || 0, - speed: 240 + speed: 240, + navigation: { + nextEl: '.btnSlideshowNext', + prevEl: '.btnSlideshowPrevious' + }, + // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; + virtual: { + slides: slides, + cache: true, + renderSlide: getSwiperSlideHtml, + addSlidesBefore: 1, + addSlidesAfter: 1 + } }); swiperInstance.on('autoplayStart', onAutoplayStart); swiperInstance.on('autoplayStop', onAutoplayStop); - - if (layoutManager.mobile) { - pause(); - } else { - play(); - } }); } - function getSwiperSlideHtmlFromItem(item) { + /** + * Renders the HTML markup of a slide for an item or a slide. + * @param {Object} item - The item used to render the slide. + * @param {number} index - The index of the item in the Swiper instance. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtml(item, index) { + if (currentOptions.slides) { + return getSwiperSlideHtmlFromSlide(item); + } else { + return getSwiperSlideHtmlFromItem(item); + } + } + /** + * Renders the HTML markup of a slide for an item. + * @param {Object} item - Item used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromItem(item) { return getSwiperSlideHtmlFromSlide({ - imageUrl: getImgUrl(item), - originalImage: getImgUrl(item, true), + originalImage: getImgUrl(item), //title: item.Name, //description: item.Overview Id: item.Id, @@ -264,11 +320,17 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Renders the HTML markup of a slide for a slide object. + * @param {Object} item - Slide object used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ function getSwiperSlideHtmlFromSlide(item) { - var html = ''; - html += '
'; - html += ''; + html += '
'; + html += '
'; + html += ''; + html += '
'; if (item.title || item.subtitle) { html += '
'; html += '
'; @@ -290,42 +352,18 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f return html; } - function previousImage() { - if (swiperInstance) { - swiperInstance.slidePrev(); - } else { - stopInterval(); - showNextImage(currentIndex - 1); - } - } - - function nextImage() { - if (swiperInstance) { - - if (options.loop === false) { - - if (swiperInstance.activeIndex >= swiperInstance.slides.length - 1) { - dialogHelper.close(dlg); - return; - } - } - - swiperInstance.slideNext(); - } else { - stopInterval(); - showNextImage(currentIndex + 1); - } - } - + /** + * Fetches the information of the currently displayed slide. + * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. + */ function getCurrentImageInfo() { - if (swiperInstance) { var slide = document.querySelector('.swiper-slide-active'); if (slide) { return { url: slide.getAttribute('data-original'), - shareUrl: slide.getAttribute('data-imageurl'), + shareUrl: slide.getAttribute('data-original'), itemId: slide.getAttribute('data-itemid'), serverId: slide.getAttribute('data-serverid') }; @@ -336,8 +374,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Starts a download for the currently displayed slide. + */ function download() { - var imageInfo = getCurrentImageInfo(); require(['fileDownloader'], function (fileDownloader) { @@ -345,8 +385,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Shares the currently displayed slide using the browser's built-in sharing feature. + */ function share() { - var imageInfo = getCurrentImageInfo(); navigator.share({ @@ -354,20 +396,29 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f }); } + /** + * Starts the autoplay feature of the Swiper instance. + */ function play() { if (swiperInstance.autoplay) { swiperInstance.autoplay.start(); } } + /** + * Pauses the autoplay feature of the Swiper instance; + */ function pause() { if (swiperInstance.autoplay) { swiperInstance.autoplay.stop(); } } + /** + * Toggles the autoplay feature of the Swiper instance. + */ function playPause() { - var paused = !dlg.querySelector('.btnSlideshowPause i').classList.contains("pause"); + var paused = !dialog.querySelector('.btnSlideshowPause i').classList.contains("pause"); if (paused) { play(); } else { @@ -375,8 +426,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Closes the dialog and destroys the Swiper instance. + */ function onDialogClosed() { - var swiper = swiperInstance; if (swiper) { swiper.destroy(true, true); @@ -387,53 +440,38 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); } - function startInterval(options) { - - currentOptions = options; - - stopInterval(); - createElements(options); - - if (!options.interactive) { - currentIntervalMs = options.interval || 11000; - showNextImage(options.startIndex || 0, true); - } - } - - var _osdOpen = false; - - function isOsdOpen() { - return _osdOpen; - } - - function getOsdBottom() { - return dlg.querySelector('.slideshowBottomBar'); - } - + /** + * Shows the OSD. + */ function showOsd() { - - var bottom = getOsdBottom(); + var bottom = dialog.querySelector('.slideshowBottomBar'); if (bottom) { slideUpToShow(bottom); startHideTimer(); } } + /** + * Hides the OSD. + */ function hideOsd() { - - var bottom = getOsdBottom(); + var bottom = dialog.querySelector('.slideshowBottomBar'); if (bottom) { slideDownToHide(bottom); } } - var hideTimeout; - + /** + * Starts the timer used to automatically hide the OSD. + */ function startHideTimer() { stopHideTimer(); - hideTimeout = setTimeout(hideOsd, 4000); + hideTimeout = setTimeout(hideOsd, 3000); } + /** + * Stops the timer used to automatically hide the OSD. + */ function stopHideTimer() { if (hideTimeout) { clearTimeout(hideTimeout); @@ -441,71 +479,76 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } - function slideUpToShow(elem) { - - if (!elem.classList.contains('hide')) { + /** + * Shows the OSD by sliding it into view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideUpToShow(element) { + if (!element.classList.contains('hide')) { return; } _osdOpen = true; - elem.classList.remove('hide'); + element.classList.remove('hide'); var onFinish = function () { - focusManager.focus(elem.querySelector('.btnSlideshowPause')); + focusManager.focus(element.querySelector('.btnSlideshowPause')); }; - if (!elem.animate) { + if (!element.animate) { onFinish(); return; } requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } ]; var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - elem.animate(keyframes, timing).onfinish = onFinish; + element.animate(keyframes, timing).onfinish = onFinish; }); } - function slideDownToHide(elem) { - - if (elem.classList.contains('hide')) { + /** + * Hides the OSD by sliding it out of view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideDownToHide(element) { + if (element.classList.contains('hide')) { return; } var onFinish = function () { - elem.classList.add('hide'); + element.classList.add('hide'); _osdOpen = false; }; - if (!elem.animate) { + if (!element.animate) { onFinish(); return; } requestAnimationFrame(function () { - var keyframes = [ { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, - { transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } ]; var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - elem.animate(keyframes, timing).onfinish = onFinish; + element.animate(keyframes, timing).onfinish = onFinish; }); } - var lastMouseMoveData; - - function onPointerMove(e) { - - var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + /** + * Shows the OSD when moving the mouse pointer or touching the screen. + * @param {Event} event - Pointer movement event. + */ + function onPointerMove(event) { + var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); if (pointerType === 'mouse') { - var eventX = e.screenX || 0; - var eventY = e.screenY || 0; + var eventX = event.screenX || 0; + var eventY = event.screenY || 0; var obj = lastMouseMoveData; if (!obj) { @@ -528,125 +571,46 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } - function onInputCommand(e) { - - switch (e.detail.command) { - - case 'left': - if (!isOsdOpen()) { - e.preventDefault(); - e.stopPropagation(); - previousImage(); - } - break; - case 'right': - if (!isOsdOpen()) { - e.preventDefault(); - e.stopPropagation(); - nextImage(); - } - break; + /** + * Dispatches keyboard inputs to their proper handlers. + * @param {Event} event - Keyboard input event. + */ + function onInputCommand(event) { + switch (event.detail.command) { case 'up': case 'down': case 'select': case 'menu': case 'info': - case 'play': - case 'playpause': - case 'pause': showOsd(); break; + case 'play': + play(); + break; + case 'pause': + pause(); + break; + case 'playpause': + playPause(); + break; default: break; } } - function showNextImage(index, skipPreload) { - - index = Math.max(0, index); - if (index >= currentOptions.items.length) { - index = 0; - } - currentIndex = index; - - var options = currentOptions; - var items = options.items; - var item = items[index]; - var imgUrl = getImgUrl(item); - - var onSrcLoaded = function () { - var cardImageContainer = dlg.querySelector('.slideshowImage'); - - var newCardImageContainer = document.createElement('div'); - newCardImageContainer.className = cardImageContainer.className; - - if (options.cover) { - newCardImageContainer.classList.add('slideshowImage-cover'); - } - - newCardImageContainer.style.backgroundImage = "url('" + imgUrl + "')"; - newCardImageContainer.classList.add('hide'); - cardImageContainer.parentNode.appendChild(newCardImageContainer); - - if (options.showTitle) { - dlg.querySelector('.slideshowImageText').innerHTML = item.Name; - } else { - dlg.querySelector('.slideshowImageText').innerHTML = ''; - } - - newCardImageContainer.classList.remove('hide'); - var onAnimationFinished = function () { - - var parentNode = cardImageContainer.parentNode; - if (parentNode) { - parentNode.removeChild(cardImageContainer); - } - }; - - if (newCardImageContainer.animate) { - - var keyframes = [ - { opacity: '0', offset: 0 }, - { opacity: '1', offset: 1 } - ]; - var timing = { duration: 1200, iterations: 1 }; - newCardImageContainer.animate(keyframes, timing).onfinish = onAnimationFinished; - } else { - onAnimationFinished(); - } - - stopInterval(); - currentTimeout = setTimeout(function () { - showNextImage(index + 1, true); - - }, currentIntervalMs); - }; - - if (!skipPreload) { - var img = new Image(); - img.onload = onSrcLoaded; - img.src = imgUrl; - } else { - onSrcLoaded(); - } - } - - function stopInterval() { - if (currentTimeout) { - clearTimeout(currentTimeout); - currentTimeout = null; - } - } - + /** + * Shows the slideshow component. + */ self.show = function () { - startInterval(options); + createElements(options); }; + /** + * Hides the slideshow element. + */ self.hide = function () { - - var dialog = dlg; + var dialog = dialog; if (dialog) { - dialogHelper.close(dialog); } }; diff --git a/src/components/slideshow/style.css b/src/components/slideshow/style.css index 2bea7c9696..fc747cf371 100644 --- a/src/components/slideshow/style.css +++ b/src/components/slideshow/style.css @@ -41,17 +41,12 @@ } .swiper-slide-img { - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; - -ms-transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - -moz-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - position: absolute; - left: 50%; - top: 50%; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + text-align: center; } .slideshowButtonIcon { @@ -138,3 +133,12 @@ .slideSubtitle { color: #ccc; } + +.swiper-slide { + display: flex; + flex-direction: column; +} + +.slider-zoom-container { + margin: auto; +} diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 23d0d07a04..29d110f12e 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -30,7 +30,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', subtitleSyncTextField.updateOffset = function(offset) { this.textContent = offset + "s"; - } + }; subtitleSyncTextField.addEventListener("keypress", function(event) { @@ -66,7 +66,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', subtitleSyncSlider.updateOffset = function(percent) { // default value is 0s = 50% this.value = percent === undefined ? 50 : percent; - } + }; subtitleSyncSlider.addEventListener("change", function () { // set new offset @@ -132,7 +132,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', elem.parentNode.removeChild(elem); this.element = null; } - } + }; SubtitleSync.prototype.toggle = function(action) { @@ -166,7 +166,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', } /* eslint-enable no-fallthrough */ } - } + }; return SubtitleSync; }); diff --git a/src/controllers/auth/login.js b/src/controllers/auth/login.js index 6ce7c1c773..05075efe52 100644 --- a/src/controllers/auth/login.js +++ b/src/controllers/auth/login.js @@ -24,9 +24,11 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout page.querySelector("#txtManualPassword").value = ""; loading.hide(); - if (response.status === 401) { + const UnauthorizedOrForbidden = [401, 403]; + if (UnauthorizedOrForbidden.includes(response.status)) { require(["toast"], function (toast) { - toast(globalize.translate("MessageInvalidUser")); + const messageKey = response.status === 401 ? "MessageInvalidUser" : "MessageUnauthorizedUser"; + toast(globalize.translate(messageKey)); }); } else { Dashboard.alert({ diff --git a/src/controllers/auth/selectserver.js b/src/controllers/auth/selectserver.js index e766dbdb5c..2d27742119 100644 --- a/src/controllers/auth/selectserver.js +++ b/src/controllers/auth/selectserver.js @@ -95,7 +95,7 @@ define(["loading", "appRouter", "layoutManager", "appSettings", "apphost", "focu } function showServerConnectionFailure() { - alertText(globalize.translate("MessageUnableToConnectToServer"), globalize.translate("HeaderConnectionFailure")); + alertText(globalize.translate("MessageUnableToConnectToServer")); } return function (view, params) { diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 2057deaf6f..78f5cdca01 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -1,4 +1,4 @@ -define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "humanedate", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) { +define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "date-fns", "dfnshelper", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, datefns, dfnshelper, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) { "use strict"; function showPlaybackInfo(btn, session) { @@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa getNowPlayingName: function (session) { var imgUrl = ""; var nowPlayingItem = session.NowPlayingItem; - + // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix + // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences if (!nowPlayingItem) { return { - html: "Last seen " + humaneDate(session.LastActivityDate), + html: globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)), image: imgUrl }; } diff --git a/src/controllers/dashboard/logs.js b/src/controllers/dashboard/logs.js index 144e21fc92..46b5e1ac2e 100644 --- a/src/controllers/dashboard/logs.js +++ b/src/controllers/dashboard/logs.js @@ -29,5 +29,5 @@ define(["datetime", "loading", "apphost", "listViewStyle", "emby-button", "flexS loading.hide(); }); }); - } + }; }); diff --git a/src/controllers/dashboard/notifications/notifications.js b/src/controllers/dashboard/notifications/notifications.js index 8f63753bf3..3c0b5e9b36 100644 --- a/src/controllers/dashboard/notifications/notifications.js +++ b/src/controllers/dashboard/notifications/notifications.js @@ -49,12 +49,12 @@ define(["loading", "libraryMenu", "globalize", "listViewStyle", "emby-button"], } page.querySelector(".notificationList").innerHTML = html; loading.hide(); - }) + }); } return function(view, params) { view.addEventListener("viewshow", function() { reload(view); }); - } + }; }); diff --git a/src/controllers/dashboard/plugins/add.js b/src/controllers/dashboard/plugins/add.js index 72a7134fac..a05cac461b 100644 --- a/src/controllers/dashboard/plugins/add.js +++ b/src/controllers/dashboard/plugins/add.js @@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e } if (installedPlugin) { - var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "" + installedPlugin.Version + ""); + var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled", "" + installedPlugin.Version + ""); $("#pCurrentVersion", page).show().html(currentVersionText); } else { $("#pCurrentVersion", page).hide().html(""); diff --git a/src/controllers/dashboard/plugins/available.js b/src/controllers/dashboard/plugins/available.js index 5526bd9ade..adccfa3935 100644 --- a/src/controllers/dashboard/plugins/available.js +++ b/src/controllers/dashboard/plugins/available.js @@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby return ip.Id == plugin.guid; })[0]; html += "
"; - html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : " "; + html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : " "; html += "
"; html += "
"; html += "
"; diff --git a/src/controllers/dashboard/plugins/installed.js b/src/controllers/dashboard/plugins/installed.js index 026b58ce67..c381b2409e 100644 --- a/src/controllers/dashboard/plugins/installed.js +++ b/src/controllers/dashboard/plugins/installed.js @@ -2,7 +2,7 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button" "use strict"; function deletePlugin(page, uniqueid, name) { - var msg = globalize.translate("UninstallPluginConfirmation").replace("{0}", name); + var msg = globalize.translate("UninstallPluginConfirmation", name); require(["confirm"], function (confirm) { confirm({ diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 03eeeeb870..8a3cdf5d69 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby html += "
"; context.querySelector(".taskTriggers").innerHTML = html; }, + // TODO: Replace this mess with date-fns and remove datetime completely getTriggerFriendlyName: function (trigger) { if ("DailyTrigger" == trigger.Type) { - return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); + return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } if ("WeeklyTrigger" == trigger.Type) { - return trigger.DayOfWeek + "s at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); + // TODO: The day of week isn't localised as well + return globalize.translate("WeeklyAt", trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) { - return "On wake from sleep"; + return globalize.translate("OnWakeFromSleep"); } if (trigger.Type == "IntervalTrigger") { @@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby var hours = trigger.IntervalTicks / 36e9; if (hours == 0.25) { - return "Every 15 minutes"; + return globalize.translate("EveryXMinutes", "15"); } if (hours == 0.5) { - return "Every 30 minutes"; + return globalize.translate("EveryXMinutes", "30"); } if (hours == 0.75) { - return "Every 45 minutes"; + return globalize.translate("EveryXMinutes", "45"); } if (hours == 1) { - return "Every hour"; + return globalize.translate("EveryHour"); } - return "Every " + hours + " hours"; + return globalize.translate("EveryXHours", hours); } if (trigger.Type == "StartupTrigger") { - return "On application startup"; + return globalize.translate("OnApplicationStartup"); } return trigger.Type; diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 390fd17353..b9c7ba413c 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -1,4 +1,4 @@ -define(["jQuery", "loading", "events", "globalize", "serverNotifications", "humanedate", "listViewStyle", "emby-button"], function($, loading, events, globalize, serverNotifications) { +define(["jQuery", "loading", "events", "globalize", "serverNotifications", "date-fns", "dfnshelper", "listViewStyle", "emby-button"], function ($, loading, events, globalize, serverNotifications, datefns, dfnshelper) { "use strict"; function reloadList(page) { @@ -7,7 +7,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma }).then(function(tasks) { populateList(page, tasks); loading.hide(); - }) + }); } function populateList(page, tasks) { @@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma var html = ""; if (task.State === "Idle") { if (task.LastExecutionResult) { - html += globalize.translate("LabelScheduledTaskLastRan").replace("{0}", humaneDate(task.LastExecutionResult.EndTimeUtc)).replace("{1}", humaneElapsed(task.LastExecutionResult.StartTimeUtc, task.LastExecutionResult.EndTimeUtc)); + var endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); + var starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); + html += globalize.translate("LabelScheduledTaskLastRan", datefns.formatDistanceToNow(endtime, dfnshelper.localeWithSuffix), + datefns.formatDistance(starttime, endtime, { locale: dfnshelper.getLocale() })); if (task.LastExecutionResult.Status === "Failed") { html += " (" + globalize.translate("LabelFailed") + ")"; } else if (task.LastExecutionResult.Status === "Cancelled") { @@ -152,7 +155,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma ApiClient.startScheduledTask(id).then(function() { updateTaskButton(button, "Running"); reloadList(view); - }) + }); }); $(".divScheduledTasks", view).on("click", ".btnStopTask", function() { @@ -161,7 +164,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma ApiClient.stopScheduledTask(id).then(function() { updateTaskButton(button, ""); reloadList(view); - }) + }); }); view.addEventListener("viewbeforehide", function() { @@ -175,5 +178,5 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma reloadList(view); events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate); }); - } + }; }); diff --git a/src/controllers/devices.js b/src/controllers/devices.js index 3fd2be983e..8dd665f7fa 100644 --- a/src/controllers/devices.js +++ b/src/controllers/devices.js @@ -1,4 +1,4 @@ -define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "humanedate", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper) { +define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "date-fns", "dfnshelper", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper, datefns, dfnshelper) { "use strict"; function canDelete(deviceId) { @@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu if (device.LastUserName) { deviceHtml += device.LastUserName; - deviceHtml += ", " + humaneDate(device.DateLastActivity); + deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix); } deviceHtml += " "; diff --git a/src/controllers/dlnaprofile.js b/src/controllers/dlnaprofile.js index 9b9fd5b5cb..f4d5704a27 100644 --- a/src/controllers/dlnaprofile.js +++ b/src/controllers/dlnaprofile.js @@ -258,14 +258,14 @@ define(["jQuery", "loading", "globalize", "fnchecked", "emby-select", "emby-butt html += "
"; html += ''; - html += "

" + globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } @@ -319,14 +319,14 @@ define(["jQuery", "loading", "globalize", "fnchecked", "emby-select", "emby-butt html += "
"; html += ''; html += "

Protocol: " + (profile.Protocol || "Http") + "

"; - html += "

" + globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } @@ -404,11 +404,11 @@ define(["jQuery", "loading", "globalize", "fnchecked", "emby-select", "emby-butt html += "
"; html += ''; - html += "

" + globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + globalize.translate("ValueContainer", profile.Container || allText) + "

"; if (profile.Conditions && profile.Conditions.length) { html += "

"; - html += globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { + html += globalize.translate("ValueConditions", profile.Conditions.map(function (c) { return c.Property; }).join(", ")); html += "

"; @@ -476,11 +476,11 @@ define(["jQuery", "loading", "globalize", "fnchecked", "emby-select", "emby-butt html += "
"; html += ''; - html += "

" + globalize.translate("ValueCodec").replace("{0}", profile.Codec || allText) + "

"; + html += "

" + globalize.translate("ValueCodec", profile.Codec || allText) + "

"; if (profile.Conditions && profile.Conditions.length) { html += "

"; - html += globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { + html += globalize.translate("ValueConditions", profile.Conditions.map(function (c) { return c.Property; }).join(", ")); html += "

"; @@ -547,20 +547,20 @@ define(["jQuery", "loading", "globalize", "fnchecked", "emby-select", "emby-butt html += "
"; html += ''; - html += "

" + globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } if (profile.Conditions && profile.Conditions.length) { html += "

"; - html += globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { + html += globalize.translate("ValueConditions", profile.Conditions.map(function (c) { return c.Property; }).join(", ")); html += "

"; diff --git a/src/controllers/encodingsettings.js b/src/controllers/encodingsettings.js index a108f74bbd..9820d7401f 100644 --- a/src/controllers/encodingsettings.js +++ b/src/controllers/encodingsettings.js @@ -14,6 +14,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo $("#txtVaapiDevice", page).val(config.VaapiDevice || ""); page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || ""; page.querySelector("#txtH264Crf").value = config.H264Crf || ""; + page.querySelector("#selectDeinterlaceMethod").value = config.DeinterlaceMethod || ""; page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false; page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false; page.querySelector("#selectVideoDecoder").dispatchEvent(new CustomEvent("change", { @@ -58,6 +59,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo config.VaapiDevice = $("#txtVaapiDevice", form).val(); config.EncoderPreset = form.querySelector("#selectEncoderPreset").value; config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0"); + config.DeinterlaceMethod = form.querySelector("#selectDeinterlaceMethod").value; config.EnableSubtitleExtraction = form.querySelector("#chkEnableSubtitleExtraction").checked; config.EnableThrottling = form.querySelector("#chkEnableThrottling").checked; config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll(".chkDecodeCodec"), function (c) { diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemdetailpage.js index 23a672751c..e9242c5119 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemdetailpage.js @@ -336,7 +336,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti return html = html.join(" / "); } - function renderName(item, container, isStatic, context) { var parentRoute; var parentNameHtml = []; @@ -401,14 +400,25 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti } else if (item.Album) { parentNameHtml.push(item.Album); } - + // FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022 var html = ""; + var tvShowHtml = parentNameHtml[0]; + var tvSeasonHtml = parentNameHtml[1]; if (parentNameHtml.length) { if (parentNameLast) { - html = '

' + parentNameHtml.join(" - ") + "

"; + // Music + if (layoutManager.mobile) { + html = '

' + parentNameHtml.join("
") + "

"; + } else { + html = '

' + parentNameHtml.join(" - ") + "

"; + } } else { - html = '

' + parentNameHtml.join(" - ") + "

"; + if (layoutManager.mobile) { + html = '

' + parentNameHtml.join("
") + "

"; + } else { + html = '

' + tvShowHtml + "

"; + } } } @@ -418,13 +428,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti var offset = parentNameLast ? ".25em" : ".5em"; if (html && !parentNameLast) { - html += '

' + name + '

'; + if (!layoutManager.mobile && tvSeasonHtml) { + html += '

' + tvSeasonHtml + ' - ' + name + '

'; + } else { + html += '

' + name + '

'; + } } else { html = '

' + name + "

" + html; } if (item.OriginalTitle && item.OriginalTitle != item.Name) { - html += '

' + item.OriginalTitle + '

'; + html += '

' + item.OriginalTitle + '

'; } container.innerHTML = html; @@ -591,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti try { var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); itemBirthday.classList.remove("hide"); - itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday); + itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday); } catch (err) { itemBirthday.classList.add("hide"); } @@ -605,7 +619,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti try { var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); itemDeathDate.classList.remove("hide"); - itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday); + itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday); } catch (err) { itemDeathDate.classList.add("hide"); } @@ -618,7 +632,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) { var gmap = '
' + item.ProductionLocations[0] + ""; itemBirthLocation.classList.remove("hide"); - itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap); + itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap); } else { itemBirthLocation.classList.add("hide"); } diff --git a/src/controllers/librarydisplay.js b/src/controllers/librarydisplay.js index f3b5275d32..603ab1ee67 100644 --- a/src/controllers/librarydisplay.js +++ b/src/controllers/librarydisplay.js @@ -14,7 +14,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", " }, { href: "metadatanfo.html", name: globalize.translate("TabNfoSettings") - }] + }]; } return function(view, params) { @@ -27,14 +27,10 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", " view.querySelector("#chkSaveMetadataHidden").checked = config.SaveMetadataHidden; }); ApiClient.getNamedConfiguration("metadata").then(function(metadata) { - loadMetadataConfig(this, metadata) + view.querySelector("#selectDateAdded").selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; }); } - function loadMetadataConfig(page, config) { - $("#selectDateAdded", page).val(config.UseFileCreationTimeForDateAdded ? "1" : "0"); - } - view.querySelector("form").addEventListener("submit", function(e) { loading.show(); var form = this; @@ -67,5 +63,5 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", " } }); }); - } + }; }); diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 2de7e81843..63e4b47288 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -1,4 +1,4 @@ -define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "emby-itemscontainer"], function (cardBuilder, imageLoader, libraryBrowser, loading, events) { +define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "userSettings", "emby-itemscontainer"], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) { "use strict"; return function (view, params, tabContent) { @@ -7,12 +7,15 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em pageData = { query: { StartIndex: 0, - Limit: 100, Fields: "PrimaryImageAspectRatio" } }; } + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + return pageData; } @@ -39,7 +42,9 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(context); } @@ -48,7 +53,9 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(context); } diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index 3ee56a2a95..3bb85598cb 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -48,7 +48,7 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc } function getBackdropShape() { - return enableScrollX() ? "overflowBackdrop" : "backdrop" + return enableScrollX() ? "overflowBackdrop" : "backdrop"; } function renderActiveRecordings(context, promise) { diff --git a/src/controllers/metadataimagespage.js b/src/controllers/metadataimagespage.js index 656314a200..42d751a248 100644 --- a/src/controllers/metadataimagespage.js +++ b/src/controllers/metadataimagespage.js @@ -7,10 +7,10 @@ define(["jQuery", "dom", "loading", "libraryMenu", "globalize", "listViewStyle"] html += ""; for (var i = 0, length = languages.length; i < length; i++) { var culture = languages[i]; - html += "" + html += ""; } - select.innerHTML = html - }) + select.innerHTML = html; + }); } function populateCountries(select) { @@ -19,25 +19,25 @@ define(["jQuery", "dom", "loading", "libraryMenu", "globalize", "listViewStyle"] html += ""; for (var i = 0, length = allCountries.length; i < length; i++) { var culture = allCountries[i]; - html += "" + html += ""; } - select.innerHTML = html - }) + select.innerHTML = html; + }); } function loadPage(page) { var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector("#selectLanguage")), populateCountries(page.querySelector("#selectCountry"))]; Promise.all(promises).then(function(responses) { var config = responses[0]; - page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage || "", page.querySelector("#selectCountry").value = config.MetadataCountryCode || "", loading.hide() - }) + page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage || "", page.querySelector("#selectCountry").value = config.MetadataCountryCode || "", loading.hide(); + }); } function onSubmit() { var form = this; return loading.show(), ApiClient.getServerConfiguration().then(function(config) { - config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult) - }), !1 + config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }), !1; } function getTabs() { @@ -53,12 +53,12 @@ define(["jQuery", "dom", "loading", "libraryMenu", "globalize", "listViewStyle"] }, { href: "metadatanfo.html", name: globalize.translate("TabNfoSettings") - }] + }]; } $(document).on("pageinit", "#metadataImagesConfigurationPage", function() { - $(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit) + $(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit); }).on("pageshow", "#metadataImagesConfigurationPage", function() { - libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this) - }) + libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this); + }); }); diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index e4e750059a..4dfe23e7a6 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -1,4 +1,4 @@ -define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "globalize", "apphost", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, globalize, appHost) { +define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "userSettings", "globalize", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -16,11 +16,15 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB Fields: "PrimaryImageAspectRatio,SortName", ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + StartIndex: 0 }, view: libraryBrowser.getSavedView(key) || "Poster" }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -65,7 +69,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -74,7 +80,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -180,7 +188,6 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index d51a2e4789..bb395f337c 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -184,12 +184,12 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 0b541dfba2..9748da62dd 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -32,7 +32,9 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } itemsContainer.refreshItems(); } @@ -41,7 +43,9 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } itemsContainer.refreshItems(); } @@ -250,9 +254,13 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Banner,Thumb", StartIndex: 0, - Limit: 100, ParentId: params.topParentId }; + + if (userSettings.libraryPageSize() > 0) { + query['Limit'] = userSettings.libraryPageSize(); + } + var isLoading = false; if (options.mode === "favorites") { diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 760f6e2f8c..5f341d3e3a 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" switch (recommendation.RecommendationType) { case "SimilarToRecentlyPlayed": - title = globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName); + title = globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName); break; case "SimilarToLikedItem": - title = globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName); + title = globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName); break; case "HasDirectorFromRecentlyPlayed": case "HasLikedDirector": - title = globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName); + title = globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName); break; case "HasActorFromRecentlyPlayed": case "HasLikedActor": - title = globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName); + title = globalize.translate("RecommendationStarring", recommendation.BaselineItemName); break; } diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 249acf0e53..590b204b22 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -1,4 +1,4 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "globalize", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost, globalize) { +define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "userSettings", "globalize", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -16,11 +16,15 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + StartIndex: 0 }, view: libraryBrowser.getSavedView(key) || "Poster" }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -49,7 +53,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(); } @@ -58,7 +64,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(); } @@ -168,7 +176,6 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 74c439e361..580f30bce9 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -1,4 +1,4 @@ -define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "globalize", "emby-itemscontainer"], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost, globalize) { +define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "userSettings", "globalize", "emby-itemscontainer"], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -30,11 +30,15 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + StartIndex: 0 }, view: libraryBrowser.getSavedView(key) || "Poster" }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -79,7 +83,9 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -88,7 +94,9 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -175,7 +183,6 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser var savedQueryKey; var pageData; var self = this; - var pageSize = 100; var isLoading = false; self.showFilterMenu = function () { diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index ceed448a06..6048b9920e 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -1,4 +1,4 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost) { +define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "alphaPicker", "listView", "cardBuilder", "apphost", "userSettings", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, alphaPicker, listView, cardBuilder, appHost, userSettings) { "use strict"; return function (view, params, tabContent) { @@ -7,17 +7,22 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " var pageData = data[key]; if (!pageData) { + var queryValues = { + SortBy: "SortName", + SortOrder: "Ascending", + Recursive: true, + Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", + StartIndex: 0, + ImageTypeLimit: 1, + EnableImageTypes: "Primary,Backdrop,Banner,Thumb" + }; + + if (userSettings.libraryPageSize() > 0) { + queryValues['Limit'] = userSettings.libraryPageSize(); + } + pageData = data[key] = { - query: { - SortBy: "SortName", - SortOrder: "Ascending", - Recursive: true, - Fields: "PrimaryImageAspectRatio,SortName,BasicSyncInfo", - StartIndex: 0, - ImageTypeLimit: 1, - EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - Limit: 100 - }, + query: queryValues, view: libraryBrowser.getSavedView(key) || "Poster" }; pageData.query.ParentId = params.topParentId; @@ -67,7 +72,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -76,7 +83,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index b465a4d350..c1338bc222 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -107,12 +107,12 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index fd458c88ac..795eaba795 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -69,7 +69,7 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f var data = {}; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; var promise; diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 6c4454bdf0..29d21b0774 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -1,4 +1,4 @@ -define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "globalize", "emby-itemscontainer"], function (events, libraryBrowser, imageLoader, listView, loading, globalize) { +define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "userSettings", "globalize", "emby-itemscontainer"], function (events, libraryBrowser, imageLoader, listView, loading, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -14,12 +14,16 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "globa IncludeItemTypes: "Audio", Recursive: true, Fields: "AudioInfo,ParentId", - Limit: 100, StartIndex: 0, ImageTypeLimit: 1, EnableImageTypes: "Primary" } }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -49,7 +53,9 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "globa return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -58,7 +64,9 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "globa return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } diff --git a/src/controllers/playback/videoosd.js b/src/controllers/playback/videoosd.js index c61fd14a7a..555e34c5b0 100644 --- a/src/controllers/playback/videoosd.js +++ b/src/controllers/playback/videoosd.js @@ -1152,7 +1152,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med case "GamepadDPadLeft": case "GamepadLeftThumbstickLeft": // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.rewind(currentPlayer); showOsd(); } @@ -1161,7 +1161,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med case "GamepadDPadRight": case "GamepadLeftThumbstickRight": // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.fastForward(currentPlayer); showOsd(); } @@ -1272,7 +1272,6 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med var programEndDateMs = 0; var playbackStartTimeTicks = 0; var subtitleSyncOverlay; - var volumeSliderTimer; var nowPlayingVolumeSlider = view.querySelector(".osdVolumeSlider"); var nowPlayingVolumeSliderContainer = view.querySelector(".osdVolumeSliderContainer"); var nowPlayingPositionSlider = view.querySelector(".osdPositionSlider"); @@ -1423,27 +1422,15 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med } function setVolume() { - clearTimeout(volumeSliderTimer); - volumeSliderTimer = null; - playbackManager.setVolume(this.value, currentPlayer); } - function setVolumeDelayed() { - if (!volumeSliderTimer) { - var that = this; - volumeSliderTimer = setTimeout(function () { - setVolume.call(that); - }, 700); - } - } - view.querySelector(".buttonMute").addEventListener("click", function () { playbackManager.toggleMute(currentPlayer); }); nowPlayingVolumeSlider.addEventListener("change", setVolume); - nowPlayingVolumeSlider.addEventListener("mousemove", setVolumeDelayed); - nowPlayingVolumeSlider.addEventListener("touchmove", setVolumeDelayed); + nowPlayingVolumeSlider.addEventListener("mousemove", setVolume); + nowPlayingVolumeSlider.addEventListener("touchmove", setVolume); nowPlayingPositionSlider.addEventListener("change", function () { var player = currentPlayer; diff --git a/src/controllers/playbackconfiguration.js b/src/controllers/playbackconfiguration.js index c7bda1966f..a4ba6bb625 100644 --- a/src/controllers/playbackconfiguration.js +++ b/src/controllers/playbackconfiguration.js @@ -36,7 +36,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize"], function ($, loading, } $(document).on("pageinit", "#playbackConfigurationPage", function () { - $(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit) + $(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit); }).on("pageshow", "#playbackConfigurationPage", function () { loading.show(); libraryMenu.setTabs("playback", 1, getTabs); diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 2ad146ae4a..bd7823d401 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -1,4 +1,4 @@ -define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "globalize", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, globalize) { +define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "userSettings", "globalize", "emby-itemscontainer"], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -17,11 +17,15 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB IsMissing: false, ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Thumb", - StartIndex: 0, - Limit: pageSize + StartIndex: 0 }, view: libraryBrowser.getSavedView(key) || "Poster" }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -66,7 +70,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -75,7 +81,9 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -152,7 +160,6 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 9c37e04e7c..80e2f646b6 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -113,6 +113,9 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader itemsContainer: elem, shape: getPortraitShape(), scalable: true, + showTitle: true, + centerText: true, + showYear: true, overlayMoreButton: true, allowBottomPadding: false }); @@ -177,12 +180,12 @@ define(["layoutManager", "loading", "libraryBrowser", "cardBuilder", "lazyLoader }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; self.setCurrentViewStyle = function (viewStyle) { - getPageData(tabContent).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index e3a70f88b9..0f992fe8d6 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -1,4 +1,4 @@ -define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "alphaPicker", "globalize", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, listView, cardBuilder, alphaPicker, globalize) { +define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", "listView", "cardBuilder", "alphaPicker", "userSettings", "globalize", "emby-itemscontainer"], function (layoutManager, loading, events, libraryBrowser, imageLoader, listView, cardBuilder, alphaPicker, userSettings, globalize) { "use strict"; return function (view, params, tabContent) { @@ -16,11 +16,15 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " Fields: "PrimaryImageAspectRatio,BasicSyncInfo", ImageTypeLimit: 1, EnableImageTypes: "Primary,Backdrop,Banner,Thumb", - StartIndex: 0, - Limit: pageSize + StartIndex: 0 }, view: libraryBrowser.getSavedView(key) || "Poster" }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + pageData.query.ParentId = params.topParentId; libraryBrowser.loadSavedQueryValues(key, pageData.query); } @@ -65,7 +69,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex += query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } reloadItems(tabContent); } @@ -74,7 +80,9 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " return; } - query.StartIndex -= query.Limit; + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } reloadItems(tabContent); } @@ -185,7 +193,6 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " } var self = this; - var pageSize = 100; var data = {}; var isLoading = false; diff --git a/src/controllers/user/display.js b/src/controllers/user/display.js index f348f28750..28d39b7fde 100644 --- a/src/controllers/user/display.js +++ b/src/controllers/user/display.js @@ -1,4 +1,4 @@ -define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) { +define(["displaySettings", "userSettings", "autoFocuser"], function (DisplaySettings, userSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -11,7 +11,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"] var settingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); view.addEventListener("viewshow", function () { window.addEventListener("beforeunload", onBeforeUnload); @@ -22,7 +22,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"] serverId: ApiClient.serverId(), userId: userId, element: view.querySelector(".settingsContainer"), - userSettings: userSettings, + userSettings: currentSettings, enableSaveButton: false, enableSaveConfirmation: false, autoFocus: autoFocuser.isEnabled() diff --git a/src/controllers/user/home.js b/src/controllers/user/home.js index 7f12efc7fb..dccf6e5060 100644 --- a/src/controllers/user/home.js +++ b/src/controllers/user/home.js @@ -1,4 +1,4 @@ -define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { +define(["homescreenSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -11,7 +11,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin var homescreenSettingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); view.addEventListener("viewshow", function () { window.addEventListener("beforeunload", onBeforeUnload); @@ -22,7 +22,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin serverId: ApiClient.serverId(), userId: userId, element: view.querySelector(".homeScreenSettingsContainer"), - userSettings: userSettings, + userSettings: currentSettings, enableSaveButton: false, enableSaveConfirmation: false, autoFocus: autoFocuser.isEnabled() diff --git a/src/controllers/user/playback.js b/src/controllers/user/playback.js index 3def9d1931..8f48e0264b 100644 --- a/src/controllers/user/playback.js +++ b/src/controllers/user/playback.js @@ -1,4 +1,4 @@ -define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { +define(["playbackSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -11,7 +11,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading" var settingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); view.addEventListener("viewshow", function () { window.addEventListener("beforeunload", onBeforeUnload); @@ -22,7 +22,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading" serverId: ApiClient.serverId(), userId: userId, element: view.querySelector(".settingsContainer"), - userSettings: userSettings, + userSettings: currentSettings, enableSaveButton: false, enableSaveConfirmation: false, autoFocus: autoFocuser.isEnabled() diff --git a/src/controllers/user/subtitles.js b/src/controllers/user/subtitles.js index 1f1102194e..e2b98dc2dd 100644 --- a/src/controllers/user/subtitles.js +++ b/src/controllers/user/subtitles.js @@ -1,4 +1,4 @@ -define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) { +define(["subtitleSettings", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -11,7 +11,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser" var subtitleSettingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); view.addEventListener("viewshow", function () { window.addEventListener("beforeunload", onBeforeUnload); @@ -22,7 +22,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser" serverId: ApiClient.serverId(), userId: userId, element: view.querySelector(".settingsContainer"), - userSettings: userSettings, + userSettings: currentSettings, enableSaveButton: false, enableSaveConfirmation: false, autoFocus: autoFocuser.isEnabled() diff --git a/src/controllers/userprofilespage.js b/src/controllers/userprofilespage.js index 2a2387ab60..180d0e62ae 100644 --- a/src/controllers/userprofilespage.js +++ b/src/controllers/userprofilespage.js @@ -1,4 +1,4 @@ -define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize) { +define(["loading", "dom", "globalize", "date-fns", "dfnshelper", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize, datefns, dfnshelper) { "use strict"; function deleteUser(page, id) { @@ -125,10 +125,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", html += "
"; return html + "
"; } - + // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix + // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences function getLastSeenText(lastActivityDate) { if (lastActivityDate) { - return "Last seen " + humaneDate(lastActivityDate); + return globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix)); } return ""; diff --git a/src/components/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js similarity index 100% rename from src/components/emby-itemrefreshindicator/emby-itemrefreshindicator.js rename to src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js diff --git a/src/components/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js similarity index 100% rename from src/components/emby-itemscontainer/emby-itemscontainer.js rename to src/elements/emby-itemscontainer/emby-itemscontainer.js diff --git a/src/components/userdatabuttons/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js similarity index 100% rename from src/components/userdatabuttons/emby-playstatebutton.js rename to src/elements/emby-playstatebutton/emby-playstatebutton.js diff --git a/src/elements/emby-programcell/emby-programcell.js b/src/elements/emby-programcell/emby-programcell.js new file mode 100644 index 0000000000..a959033186 --- /dev/null +++ b/src/elements/emby-programcell/emby-programcell.js @@ -0,0 +1,16 @@ +define([], function() { + 'use strict'; + + var ProgramCellPrototype = Object.create(HTMLButtonElement.prototype); + + ProgramCellPrototype.detachedCallback = function () { + this.posLeft = null; + this.posWidth = null; + this.guideProgramName = null; + }; + + document.registerElement('emby-programcell', { + prototype: ProgramCellPrototype, + extends: 'button' + }); +}); diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js new file mode 100644 index 0000000000..a799f82bdd --- /dev/null +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -0,0 +1,42 @@ +define([], function() { + 'use strict'; + + var ProgressBarPrototype = Object.create(HTMLDivElement.prototype); + + function onAutoTimeProgress() { + var start = parseInt(this.getAttribute('data-starttime')); + var end = parseInt(this.getAttribute('data-endtime')); + + var now = new Date().getTime(); + var total = end - start; + var pct = 100 * ((now - start) / total); + + pct = Math.min(100, pct); + pct = Math.max(0, pct); + + var itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); + itemProgressBarForeground.style.width = pct + '%'; + } + + ProgressBarPrototype.attachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); + } + + if (this.getAttribute('data-automode') === 'time') { + this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); + } + }; + + ProgressBarPrototype.detachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); + this.timeInterval = null; + } + }; + + document.registerElement('emby-progressbar', { + prototype: ProgressBarPrototype, + extends: 'div' + }); +}); diff --git a/src/components/userdatabuttons/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js similarity index 100% rename from src/components/userdatabuttons/emby-ratingbutton.js rename to src/elements/emby-ratingbutton/emby-ratingbutton.js diff --git a/src/components/emby-scrollbuttons/emby-scrollbuttons.css b/src/elements/emby-scrollbuttons/emby-scrollbuttons.css similarity index 100% rename from src/components/emby-scrollbuttons/emby-scrollbuttons.css rename to src/elements/emby-scrollbuttons/emby-scrollbuttons.css diff --git a/src/components/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js similarity index 100% rename from src/components/emby-scrollbuttons/emby-scrollbuttons.js rename to src/elements/emby-scrollbuttons/emby-scrollbuttons.js diff --git a/src/components/emby-scroller/emby-scroller.css b/src/elements/emby-scroller/emby-scroller.css similarity index 100% rename from src/components/emby-scroller/emby-scroller.css rename to src/elements/emby-scroller/emby-scroller.css diff --git a/src/components/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js similarity index 91% rename from src/components/emby-scroller/emby-scroller.js rename to src/elements/emby-scroller/emby-scroller.js index cb5bae818f..3df40fa6c2 100644 --- a/src/components/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -96,17 +96,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro } } - function initHeadroom(elem) { - require(['headroom'], function (Headroom) { - var headroom = new Headroom([], { - scroller: elem - }); - - headroom.add(document.querySelector('.skinHeader')); - elem.headroom = headroom; - }); - } - ScrollerPrototype.attachedCallback = function () { if (this.getAttribute('data-navcommands')) { inputManager.on(this, onInputCommand); @@ -120,8 +109,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro slider.style['white-space'] = 'nowrap'; } - var bindHeader = this.getAttribute('data-bindheader') === 'true'; - var scrollFrame = this; var enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; @@ -137,7 +124,7 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro dragHandle: 1, autoImmediate: true, skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', - dispatchScrollEvent: enableScrollButtons || bindHeader || this.getAttribute('data-scrollevent') === 'true', + dispatchScrollEvent: enableScrollButtons || this.getAttribute('data-scrollevent') === 'true', hideScrollbar: enableScrollButtons || this.getAttribute('data-hidescrollbar') === 'true', allowNativeSmoothScroll: this.getAttribute('data-allownativesmoothscroll') === 'true' && !enableScrollButtons, allowNativeScroll: !enableScrollButtons, @@ -155,10 +142,6 @@ define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'bro initCenterFocus(this, this.scroller); } - if (bindHeader && layoutManager.mobile) { - initHeadroom(this); - } - if (enableScrollButtons) { loadScrollButtons(this); } diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 12177fb60b..03d64719e2 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -402,7 +402,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli this.addEventListener('keydown', onKeyDown); this.keyboardDraggingEnabled = true; } - } + }; /** * Set steps for keyboard input. @@ -413,7 +413,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { this.keyboardStepDown = stepDown || stepUp || 1; this.keyboardStepUp = stepUp || stepDown || 1; - } + }; function setRange(elem, startPercent, endPercent) { diff --git a/src/components/emby-tabs/emby-tabs.css b/src/elements/emby-tabs/emby-tabs.css similarity index 100% rename from src/components/emby-tabs/emby-tabs.css rename to src/elements/emby-tabs/emby-tabs.css diff --git a/src/components/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js similarity index 100% rename from src/components/emby-tabs/emby-tabs.js rename to src/elements/emby-tabs/emby-tabs.js diff --git a/src/encodingsettings.html b/src/encodingsettings.html index bcdd86544e..5a005d8001 100644 --- a/src/encodingsettings.html +++ b/src/encodingsettings.html @@ -136,6 +136,14 @@
${H264CrfHelp}
+
+ +
${DeinterlaceMethodHelp}
+
+