diff --git a/package.json b/package.json index 64b878a853..1f1f2df723 100644 --- a/package.json +++ b/package.json @@ -90,18 +90,20 @@ "test": [ "src/components/autoFocuser.js", "src/components/cardbuilder/cardBuilder.js", - "src/scripts/dom.js", "src/components/filedownloader.js", - "src/scripts/filesystem.js", - "src/scripts/keyboardnavigation.js", + "src/components/images/imageLoader.js", + "src/components/lazyloader/lazyloader-intersectionobserver.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/scripts/dfnshelper.js", + "src/scripts/dom.js", + "src/scripts/filesystem.js", + "src/scripts/imagehelper.js", + "src/scripts/inputManager.js", + "src/scripts/keyboardnavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", - "src/scripts/settings/webSettings.js", - "src/scripts/dfnshelper.js", - "src/scripts/imagehelper.js", - "src/scripts/inputManager.js" + "src/scripts/settings/webSettings.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index 74e07c7669..f23b407def 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -1,50 +1,82 @@ -define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', 'userSettings', 'require', 'css!./style'], function (lazyLoader, imageFetcher, layoutManager, browser, appSettings, userSettings, require) { - 'use strict'; - - var self = {}; - - function fillImage(elem, source, enableEffects) { - - if (!elem) { - throw new Error('elem cannot be null'); - } - - if (!source) { - source = elem.getAttribute('data-src'); - } +import * as lazyLoader from 'lazyLoader'; +import * as userSettings from 'userSettings'; +import 'css!./style'; +/* eslint-disable indent */ + export function lazyImage(elem, source = elem.getAttribute('data-src')) { if (!source) { return; } - fillImageElement(elem, source, enableEffects); + fillImageElement(elem, source); } - function fillImageElement(elem, source, enableEffects) { - imageFetcher.loadImage(elem, source).then(function () { + export function fillImage(entry) { + if (!entry) { + throw new Error('entry cannot be null'); + } - if (enableEffects !== false) { - fadeIn(elem); + var source = undefined; + if (entry.target) { + source = entry.target.getAttribute('data-src'); + } else { + source = entry; + } + + if (entry.intersectionRatio > 0) { + if (source) fillImageElement(entry.target, source); + } else if (!source) { + emptyImageElement(entry.target); + } + } + + function fillImageElement(elem, url) { + if (url === undefined) { + throw new Error('url cannot be undefined'); + } + + let preloaderImg = new Image(); + preloaderImg.src = url; + + preloaderImg.addEventListener('load', () => { + if (elem.tagName !== 'IMG') { + elem.style.backgroundImage = "url('" + url + "')"; + } else { + elem.setAttribute('src', url); + } + + if (userSettings.enableFastFadein()) { + elem.classList.add('lazy-image-fadein-fast'); + } else { + elem.classList.add('lazy-image-fadein'); } elem.removeAttribute('data-src'); }); } - function fadeIn(elem) { - if (userSettings.enableFastFadein()) { - elem.classList.add('lazy-image-fadein-fast'); + function emptyImageElement(elem) { + var url; + + if (elem.tagName !== 'IMG') { + url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); + elem.style.backgroundImage = 'none'; } else { - elem.classList.add('lazy-image-fadein'); + url = elem.getAttribute('src'); + elem.setAttribute('src', ''); } + + elem.setAttribute('data-src', url); + + elem.classList.remove('lazy-image-fadein-fast'); + elem.classList.remove('lazy-image-fadein'); } - function lazyChildren(elem) { - + export function lazyChildren(elem) { lazyLoader.lazyChildren(elem, fillImage); } - function getPrimaryImageAspectRatio(items) { + export function getPrimaryImageAspectRatio(items) { var values = []; @@ -104,7 +136,7 @@ define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', return result; } - function fillImages(elems) { + export function fillImages(elems) { for (var i = 0, length = elems.length; i < length; i++) { var elem = elems[0]; @@ -112,10 +144,11 @@ define(['lazyLoader', 'imageFetcher', 'layoutManager', 'browser', 'appSettings', } } - self.fillImages = fillImages; - self.lazyImage = fillImage; - self.lazyChildren = lazyChildren; - self.getPrimaryImageAspectRatio = getPrimaryImageAspectRatio; - - return self; -}); +/* eslint-enable indent */ +export default { + fillImages: fillImages, + fillImage: fillImage, + lazyImage: lazyImage, + lazyChildren: lazyChildren, + getPrimaryImageAspectRatio: getPrimaryImageAspectRatio +}; diff --git a/src/components/images/style.css b/src/components/images/style.css index 2836dd0159..2b9422d55b 100644 --- a/src/components/images/style.css +++ b/src/components/images/style.css @@ -1,44 +1,13 @@ -.lazy-image-fadein { +.cardImageContainer.lazy { opacity: 0; - animation: lazy-image-fadein 330ms ease-in normal both; - -webkit-animation-duration: 0.8s; - -moz-animation-duration: 0.8s; - -o-animation-duration: 0.8s; - animation-duration: 0.8s; - -webkit-animation-name: popInAnimation; - -moz-animation-name: popInAnimation; - -o-animation-name: popInAnimation; - animation-name: popInAnimation; - -webkit-animation-fill-mode: forwards; - -moz-animation-fill-mode: forwards; - -o-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - -moz-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - -o-animation-timing-function: cubic-bezier(0, 0, 0.5, 1); - animation-timing-function: cubic-bezier(0, 0, 0.5, 1); } -.lazy-image-fadein-fast { - animation: lazy-image-fadein 160ms ease-in normal both; +.cardImageContainer.lazy.lazy-image-fadein { + opacity: 1; + transition: opacity 0.7s; } -@keyframes lazy-image-fadein { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes popInAnimation { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } +.cardImageContainer.lazy.lazy-image-fadein-fast { + opacity: 1; + transition: opacity 0.2s; } diff --git a/src/components/lazyloader/lazyedgehack.css b/src/components/lazyloader/lazyedgehack.css deleted file mode 100644 index e358872f16..0000000000 --- a/src/components/lazyloader/lazyedgehack.css +++ /dev/null @@ -1,5 +0,0 @@ -.lazy { - /* In edge, intersection observer will not fire on 0px sized elements */ - min-width: 0.1em; - min-height: 0.1em; -} diff --git a/src/components/lazyloader/lazyloader-intersectionobserver.js b/src/components/lazyloader/lazyloader-intersectionobserver.js index 8fe0d377d0..8cf5b9cd0a 100644 --- a/src/components/lazyloader/lazyloader-intersectionobserver.js +++ b/src/components/lazyloader/lazyloader-intersectionobserver.js @@ -1,103 +1,67 @@ -define(['require', 'browser'], function (require, browser) { - 'use strict'; +/* eslint-disable indent */ + export class LazyLoader { + constructor(options) { + this.options = options; + } - function LazyLoader(options) { + createObserver() { + const callback = this.options.callback; - this.options = options; - } + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + callback(entry); + }, + {rootMargin: '50%'}); + }); - if (browser.edge) { - require(['css!./lazyedgehack']); - } + this.observer = observer; + } - LazyLoader.prototype.createObserver = function () { + addElements(elements) { + let observer = this.observer; - var observerOptions = {}; - var options = this.options; - var loadedCount = 0; - var callback = options.callback; - - observerOptions.rootMargin = '50%'; - - var observerId = 'obs' + new Date().getTime(); - - var self = this; - var observer = new IntersectionObserver(function (entries) { - for (var j = 0, length2 = entries.length; j < length2; j++) { - var entry = entries[j]; - - if (entry.intersectionRatio > 0) { - - // Stop watching and load the image - var target = entry.target; - - observer.unobserve(target); - - if (!target[observerId]) { - target[observerId] = 1; - callback(target); - loadedCount++; - - if (loadedCount >= self.elementCount) { - self.destroyObserver(); - } - } - } + if (!observer) { + this.createObserver(); + observer = this.observer; } - }, - observerOptions - ); - this.observer = observer; - }; - - LazyLoader.prototype.addElements = function (elements) { - - var observer = this.observer; - - if (!observer) { - this.createObserver(); - observer = this.observer; + Array.from(elements).forEach(element => { + observer.observe(element); + }); } - this.elementCount = (this.elementCount || 0) + elements.length; + destroyObserver() { + const observer = this.observer; - for (var i = 0, length = elements.length; i < length; i++) { - observer.observe(elements[i]); + if (observer) { + observer.disconnect(); + this.observer = null; + } } - }; - LazyLoader.prototype.destroyObserver = function (elements) { - - var observer = this.observer; - - if (observer) { - observer.disconnect(); - this.observer = null; + destroy() { + this.destroyObserver(); + this.options = null; } - }; - - LazyLoader.prototype.destroy = function (elements) { - - this.destroyObserver(); - this.options = null; - }; + } function unveilElements(elements, root, callback) { - if (!elements.length) { return; } - var lazyLoader = new LazyLoader({ + const lazyLoader = new LazyLoader({ callback: callback }); lazyLoader.addElements(elements); } - LazyLoader.lazyChildren = function (elem, callback) { - + export function lazyChildren(elem, callback) { unveilElements(elem.getElementsByClassName('lazy'), elem, callback); - }; + } - return LazyLoader; -}); +/* eslint-enable indent */ +export default { + LazyLoader: LazyLoader, + lazyChildren: lazyChildren +}; diff --git a/src/components/lazyloader/lazyloader-scroll.js b/src/components/lazyloader/lazyloader-scroll.js deleted file mode 100644 index 2704c0f7be..0000000000 --- a/src/components/lazyloader/lazyloader-scroll.js +++ /dev/null @@ -1,185 +0,0 @@ -define(['visibleinviewport', 'dom', 'browser'], function (visibleinviewport, dom, browser) { - 'use strict'; - - var thresholdX; - var thresholdY; - - function resetThresholds() { - - var threshold = 0.3; - - thresholdX = screen.availWidth * threshold; - thresholdY = screen.availHeight * threshold; - } - - function resetThresholdsOnTimer() { - - setTimeout(resetThresholds, 500); - } - - if (browser.iOS) { - dom.addEventListener(window, 'orientationchange', resetThresholdsOnTimer, { passive: true }); - dom.addEventListener(window, 'resize', resetThresholdsOnTimer, { passive: true }); - } else { - dom.addEventListener(window, 'orientationchange', resetThresholds, { passive: true }); - dom.addEventListener(window, 'resize', resetThresholds, { passive: true }); - } - resetThresholds(); - - function isVisible(elem) { - return visibleinviewport(elem, true, thresholdX, thresholdY); - } - - var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); - var self = {}; - - function cancelAll(tokens) { - for (var i = 0, length = tokens.length; i < length; i++) { - - tokens[i] = true; - } - } - - function unveilElementsInternal(instance, callback) { - - var unveiledElements = []; - var cancellationTokens = []; - var loadedCount = 0; - - function unveilInternal(tokenIndex) { - - var anyFound = false; - var out = false; - - var elements = instance.elements; - // TODO: This out construct assumes left to right, top to bottom - - for (var i = 0, length = elements.length; i < length; i++) { - - if (cancellationTokens[tokenIndex]) { - return; - } - if (unveiledElements[i]) { - continue; - } - var elem = elements[i]; - if (!out && isVisible(elem)) { - anyFound = true; - unveiledElements[i] = true; - callback(elem); - loadedCount++; - } else { - - if (anyFound) { - out = true; - } - } - } - - if (loadedCount >= elements.length) { - dom.removeEventListener(document, 'focus', unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(document, 'scroll', unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(document, wheelEvent, unveil, { - capture: true, - passive: true - }); - dom.removeEventListener(window, 'resize', unveil, { - capture: true, - passive: true - }); - } - } - - function unveil() { - - cancelAll(cancellationTokens); - - var index = cancellationTokens.length; - cancellationTokens.length++; - - setTimeout(function () { - unveilInternal(index); - }, 1); - } - - dom.addEventListener(document, 'focus', unveil, { - capture: true, - passive: true - }); - dom.addEventListener(document, 'scroll', unveil, { - capture: true, - passive: true - }); - dom.addEventListener(document, wheelEvent, unveil, { - capture: true, - passive: true - }); - dom.addEventListener(window, 'resize', unveil, { - capture: true, - passive: true - }); - - unveil(); - } - - function LazyLoader(options) { - - this.options = options; - } - - LazyLoader.prototype.createObserver = function () { - - unveilElementsInternal(this, this.options.callback); - this.observer = 1; - }; - - LazyLoader.prototype.addElements = function (elements) { - - this.elements = this.elements || []; - - for (var i = 0, length = elements.length; i < length; i++) { - this.elements.push(elements[i]); - } - - var observer = this.observer; - - if (!observer) { - this.createObserver(); - } - - }; - - LazyLoader.prototype.destroyObserver = function (elements) { - - }; - - LazyLoader.prototype.destroy = function (elements) { - - this.destroyObserver(); - this.options = null; - }; - - function unveilElements(elements, root, callback) { - - if (!elements.length) { - return; - } - var lazyLoader = new LazyLoader({ - callback: callback - }); - lazyLoader.addElements(elements); - } - - LazyLoader.lazyChildren = function (elem, callback) { - - unveilElements(elem.getElementsByClassName('lazy'), elem, callback); - }; - - return LazyLoader; -}); diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemdetailpage.js index ade68234a2..45342208ea 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemdetailpage.js @@ -487,7 +487,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti tag: item.ImageTags.Thumb }); page.classList.remove('noBackdrop'); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) { imgUrl = apiClient.getScaledImageUrl(item.Id, { @@ -497,7 +497,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti tag: item.ImageTags.Primary }); page.classList.remove('noBackdrop'); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else if (item.BackdropImageTags && item.BackdropImageTags.length) { imgUrl = apiClient.getScaledImageUrl(item.Id, { @@ -507,7 +507,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti tag: item.BackdropImageTags[0] }); page.classList.remove('noBackdrop'); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { @@ -517,7 +517,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti tag: item.ParentBackdropImageTags[0] }); page.classList.remove('noBackdrop'); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else if (item.ImageTags && item.ImageTags.Thumb) { imgUrl = apiClient.getScaledImageUrl(item.Id, { @@ -527,7 +527,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti tag: item.ImageTags.Thumb }); page.classList.remove('noBackdrop'); - imageLoader.lazyImage(itemBackdropElement, imgUrl, false); + imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else { itemBackdropElement.style.backgroundImage = ''; diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 073697625c..e8e49ff9da 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -50,7 +50,8 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(elem) { + function fillItemsContainer(entry) { + var elem = entry.target; var id = elem.getAttribute('data-id'); var viewStyle = self.getCurrentViewStyle(); var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index f6fa1e62ae..de38763e99 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -50,7 +50,8 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(elem) { + function fillItemsContainer(entry) { + var elem = entry.target; var id = elem.getAttribute('data-id'); var viewStyle = self.getCurrentViewStyle(); var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; diff --git a/src/libraries/visibleinviewport.js b/src/libraries/visibleinviewport.js deleted file mode 100644 index a48dbd3a1d..0000000000 --- a/src/libraries/visibleinviewport.js +++ /dev/null @@ -1,41 +0,0 @@ -define(['dom'], function (dom) { - 'use strict'; - - /** - * Copyright 2012, Digital Fusion - * Licensed under the MIT license. - * http://teamdf.com/jquery-plugins/license/ - * - * @author Sam Sehnert - * @desc A small plugin that checks whether elements are within - * the user visible viewport of a web browser. - * only accounts for vertical position, not horizontal. - */ - function visibleInViewport(elem, partial, thresholdX, thresholdY) { - - thresholdX = thresholdX || 0; - thresholdY = thresholdY || 0; - - if (!elem.getBoundingClientRect) { - return true; - } - - var windowSize = dom.getWindowSize(); - - var vpWidth = windowSize.innerWidth; - var vpHeight = windowSize.innerHeight; - - // Use this native browser method, if available. - var rec = elem.getBoundingClientRect(); - var tViz = rec.top >= 0 && rec.top < vpHeight + thresholdY; - var bViz = rec.bottom > 0 && rec.bottom <= vpHeight + thresholdY; - var lViz = rec.left >= 0 && rec.left < vpWidth + thresholdX; - var rViz = rec.right > 0 && rec.right <= vpWidth + thresholdX; - var vVisible = partial ? tViz || bViz : tViz && bViz; - var hVisible = partial ? lViz || rViz : lViz && rViz; - - return vVisible && hVisible; - } - - return visibleInViewport; -}); diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 12748eac20..072e3f3062 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -265,3 +265,33 @@ import events from 'events'; } /* eslint-enable indent */ +export default { + setUserInfo: setUserInfo, + getData: getData, + importFrom: importFrom, + set: set, + get: get, + serverConfig: serverConfig, + enableCinemaMode: enableCinemaMode, + enableNextVideoInfoOverlay: enableNextVideoInfoOverlay, + enableThemeSongs: enableThemeSongs, + enableThemeVideos: enableThemeVideos, + enableFastFadein: enableFastFadein, + enableBackdrops: enableBackdrops, + language: language, + dateTimeLocale: dateTimeLocale, + skipBackLength: skipBackLength, + skipForwardLength: skipForwardLength, + dashboardTheme: dashboardTheme, + skin: skin, + theme: theme, + screensaver: screensaver, + libraryPageSize: libraryPageSize, + soundEffects: soundEffects, + loadQuerySettings: loadQuerySettings, + saveQuerySettings: saveQuerySettings, + getSubtitleAppearanceSettings: getSubtitleAppearanceSettings, + setSubtitleAppearanceSettings: setSubtitleAppearanceSettings, + setFilter: setFilter, + getFilter: getFilter +}; diff --git a/src/scripts/site.js b/src/scripts/site.js index b6b904c75c..9bb15f4fea 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -375,12 +375,7 @@ var AppInfo = {}; define('filesystem', [scriptsPath + '/filesystem'], returnFirstDependency); - if (window.IntersectionObserver && !browser.edge) { - define('lazyLoader', [componentsPath + '/lazyloader/lazyloader-intersectionobserver'], returnFirstDependency); - } else { - define('lazyLoader', [componentsPath + '/lazyloader/lazyloader-scroll'], returnFirstDependency); - } - + define('lazyLoader', [componentsPath + '/lazyloader/lazyloader-intersectionobserver'], returnFirstDependency); define('shell', [componentsPath + '/shell'], returnFirstDependency); define('apiclient', [bowerPath + '/apiclient/apiclient'], returnFirstDependency); @@ -393,13 +388,10 @@ var AppInfo = {}; define('registerElement', ['document-register-element'], returnFirstDependency); } - define('imageFetcher', [componentsPath + '/images/imageFetcher'], returnFirstDependency); - - var preferNativeAlerts = browser.tv; - define('alert', [componentsPath + '/alert'], returnFirstDependency); defineResizeObserver(); + define('dialog', [componentsPath + '/dialog/dialog'], returnFirstDependency); define('confirm', [componentsPath + '/confirm/confirm'], returnFirstDependency);