diff --git a/dashboard-ui/bower_components/emby-webcomponents/.bower.json b/dashboard-ui/bower_components/emby-webcomponents/.bower.json index e15a31299f..9a98379bd3 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/.bower.json +++ b/dashboard-ui/bower_components/emby-webcomponents/.bower.json @@ -14,12 +14,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.4.296", - "_release": "1.4.296", + "version": "1.4.299", + "_release": "1.4.299", "_resolution": { "type": "version", - "tag": "1.4.296", - "commit": "b1299a1ac50def9b8cdd03ec54b6170eb6c983f4" + "tag": "1.4.299", + "commit": "7e708cf27aa74f7f0d0aaa30d95d3e1f09b71ce3" }, "_source": "https://github.com/MediaBrowser/emby-webcomponents.git", "_target": "^1.2.1", diff --git a/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js b/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js index 88986eb210..5f1293615d 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js +++ b/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js @@ -191,7 +191,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'mediaInfo if (isResizable(screenWidth)) { var roundScreenTo = 100; - screenWidth = Math.ceil(screenWidth / roundScreenTo) * roundScreenTo; + screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; } if (window.screen) { @@ -1340,7 +1340,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'mediaInfo } if (!imgUrl) { - var defaultName = item.Type === 'Program' || item.Type == 'Timer' || item.EpisodeTitle ? item.Name : itemHelper.getDisplayName(item); + var defaultName = item.Type === 'Program' || item.Type === 'Timer' || item.EpisodeTitle ? item.Name : itemHelper.getDisplayName(item); cardImageContainerOpen += '
' + defaultName + '
'; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css b/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css index 24ec5c7692..bc0fe4cf4e 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css @@ -42,7 +42,7 @@ .emby-button > i { /* For non-fab buttons that have icons */ - font-size: 1.4em; + font-size: 1.36em; width: auto; height: auto; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/guide/guide-categories.js b/dashboard-ui/bower_components/emby-webcomponents/guide/guide-categories.js index bfff804e82..6cdf23599d 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/guide/guide-categories.js +++ b/dashboard-ui/bower_components/emby-webcomponents/guide/guide-categories.js @@ -1,4 +1,5 @@ define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) { + 'use strict'; function save(context, options) { @@ -32,7 +33,7 @@ var type = chkCategorys[i].getAttribute('data-type'); - chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) != -1; + chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; } } diff --git a/dashboard-ui/bower_components/emby-webcomponents/guide/guide-settings.js b/dashboard-ui/bower_components/emby-webcomponents/guide/guide-settings.js index aa91ae943d..b2e9780d25 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/guide/guide-settings.js +++ b/dashboard-ui/bower_components/emby-webcomponents/guide/guide-settings.js @@ -1,4 +1,5 @@ define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'emby-radio', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) { + 'use strict'; function save(context) { @@ -32,28 +33,28 @@ var type = chkIndicators[i].getAttribute('data-type'); - if (chkIndicators[i].getAttribute('data-default') == 'true') { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) != 'false'; + if (chkIndicators[i].getAttribute('data-default') === 'true') { + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; } else { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) == 'true'; + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; } } - context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') == 'true'; - context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') != 'false'; + context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; var sortByValue = userSettings.get('livetv-channelorder') || 'DatePlayed'; var sortBys = context.querySelectorAll('.chkSortOrder'); for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].checked = sortBys[i].value == sortByValue; + sortBys[i].checked = sortBys[i].value === sortByValue; } } function onSortByChange() { var newValue = this.value; if (this.checked) { - var changed = options.query.SortBy != newValue; + var changed = options.query.SortBy !== newValue; options.query.SortBy = newValue.replace('_', ','); options.query.StartIndex = 0; diff --git a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js index 26080439bb..36cddd0499 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js +++ b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js @@ -1,4 +1,5 @@ define(['require', 'browser', 'globalize', 'connectionManager', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'registrationservices', 'dom', 'clearButtonStyle', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-button', 'paper-icon-button-light'], function (require, browser, globalize, connectionManager, serverNotifications, loading, datetime, focusManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, registrationServices, dom) { + 'use strict'; function showViewSettings(instance) { @@ -176,11 +177,11 @@ channelQuery.EnableImageTypes = "Primary"; var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') != -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') != -1; - var displayNewsContent = !categories.length || categories.indexOf('news') != -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') != -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') != -1; + var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { channelQuery.IsMovie = null; @@ -206,7 +207,7 @@ } } - if (userSettings.get('livetv-channelorder') == 'Number') { + if (userSettings.get('livetv-channelorder') === 'Number') { channelQuery.SortBy = "SortName"; channelQuery.SortOrder = "Ascending"; } else { @@ -350,14 +351,14 @@ var status; - if (item.Type == 'SeriesTimer') { + if (item.Type === 'SeriesTimer') { return ''; } else if (item.TimerId || item.SeriesTimerId) { status = item.Status || 'Cancelled'; } - else if (item.Type == 'Timer') { + else if (item.Type === 'Timer') { status = item.Status; } @@ -367,7 +368,7 @@ if (item.SeriesTimerId) { - if (status != 'Cancelled') { + if (status !== 'Cancelled') { return ''; } @@ -385,7 +386,7 @@ var endMs = startMs + msPerDay - 1; programs = programs.filter(function (curr) { - return curr.ChannelId == channel.Id; + return curr.ChannelId === channel.Id; }); var outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; @@ -395,18 +396,18 @@ var clickAction = layoutManager.tv ? 'link' : 'programdialog'; var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') != -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') != -1; - var displayNewsContent = !categories.length || categories.indexOf('news') != -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') != -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') != -1; - var enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') == 'true'; + var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + var enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; for (var i = 0, length = programs.length; i < length; i++) { var program = programs[i]; - if (program.ChannelId != channel.Id) { + if (program.ChannelId !== channel.Id) { continue; } @@ -533,11 +534,11 @@ var allowIndicators = dom.getWindowSize().innerWidth >= 600; var options = { - showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') == 'true', - showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') != 'false', - showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') != 'false', - showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') == 'true', - showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') == 'true' + showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', + showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', + showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', + showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') === 'true', + showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true' }; for (var i = 0, length = channels.length; i < length; i++) { @@ -661,7 +662,7 @@ var focusElem; if (itemId) { - focusElem = context.querySelector('[data-id="' + itemId + '"]') + focusElem = context.querySelector('[data-id="' + itemId + '"]'); } if (focusElem) { @@ -671,7 +672,7 @@ var autoFocusParent; if (channelRowId) { - autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]') + autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); } if (!autoFocusParent) { @@ -788,7 +789,7 @@ var selectedDate = currentDate || new Date(); dateOptions.forEach(function (d) { - d.selected = new Date(d.id).getDate() == selectedDate.getDate(); + d.selected = new Date(d.id).getDate() === selectedDate.getDate(); }); require(['actionsheet'], function (actionsheet) { @@ -961,7 +962,7 @@ self.refresh(); }); - }; + } return Guide; }); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/imageeditor/imageeditor.js b/dashboard-ui/bower_components/emby-webcomponents/imageeditor/imageeditor.js index fef5b179fe..b6cac87ffb 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/imageeditor/imageeditor.js +++ b/dashboard-ui/bower_components/emby-webcomponents/imageeditor/imageeditor.js @@ -1,4 +1,5 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', 'focusManager', 'globalize', 'scrollHelper', 'imageLoader', 'require', 'cardStyle', 'formDialogStyle', 'emby-button', 'paper-icon-button-light'], function (dialogHelper, connectionManager, loading, dom, layoutManager, focusManager, globalize, scrollHelper, imageLoader, require) { + 'use strict'; var currentItem; var hasChanges = false; @@ -78,11 +79,11 @@ options.type = type; options.index = index; - if (type == 'Backdrop') { + if (type === 'Backdrop') { options.tag = item.BackdropImageTags[index]; - } else if (type == 'Screenshot') { + } else if (type === 'Screenshot') { options.tag = item.ScreenshotImageTags[index]; - } else if (type == 'Primary') { + } else if (type === 'Primary') { options.tag = item.PrimaryImageTag || item.ImageTags[type]; } else { options.tag = item.ImageTags[type]; @@ -101,7 +102,7 @@ cssClass += " backdropCard backdropCard-scalable"; - if (tagName == 'button') { + if (tagName === 'button') { cssClass += ' card-focusscale btnImageCard'; cardBoxCssClass += ' cardBox-focustransform cardBox-focustransform-transition'; @@ -142,7 +143,7 @@ if (enableFooterButtons) { html += '
'; - if (image.ImageType == "Backdrop" || image.ImageType == "Screenshot") { + if (image.ImageType === "Backdrop" || image.ImageType === "Screenshot") { if (index > 0) { html += ''; @@ -243,7 +244,7 @@ function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType != "Screenshot" && i.ImageType != "Backdrop" && i.ImageType != "Chapter"; + return i.ImageType !== "Screenshot" && i.ImageType !== "Backdrop" && i.ImageType !== "Chapter"; }); renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); @@ -252,7 +253,7 @@ function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType == "Backdrop"; + return i.ImageType === "Backdrop"; }).sort(function (a, b) { return a.ImageIndex - b.ImageIndex; @@ -269,7 +270,7 @@ function renderScreenshots(page, apiClient, item, imageInfos, imageProviders) { var images = imageInfos.filter(function (i) { - return i.ImageType == "Screenshot"; + return i.ImageType === "Screenshot"; }).sort(function (a, b) { return a.ImageIndex - b.ImageIndex; @@ -318,7 +319,7 @@ id: 'delete' }); - if (type == 'Backdrop' || type == 'Screenshot') { + if (type === 'Backdrop' || type === 'Screenshot') { if (index > 0) { commands.push({ name: globalize.translate('sharedcomponents#MoveLeft'), @@ -411,7 +412,7 @@ addListeners(context, 'btnDeleteImage', 'click', function () { var type = this.getAttribute('data-imagetype'); var index = this.getAttribute('data-index'); - index = index == "null" ? null : parseInt(index); + index = index === "null" ? null : parseInt(index); var apiClient = connectionManager.getApiClient(currentItem.ServerId); deleteImage(context, currentItem.Id, type, index, apiClient, true); }); diff --git a/dashboard-ui/bower_components/emby-webcomponents/images/basicimagefetcher.js b/dashboard-ui/bower_components/emby-webcomponents/images/basicimagefetcher.js index 751e3a4620..d73cc6f5ba 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/images/basicimagefetcher.js +++ b/dashboard-ui/bower_components/emby-webcomponents/images/basicimagefetcher.js @@ -1,4 +1,5 @@ define(['dom'], function (dom) { + 'use strict'; function loadImage(elem, url) { diff --git a/dashboard-ui/bower_components/emby-webcomponents/images/imagehelper.js b/dashboard-ui/bower_components/emby-webcomponents/images/imagehelper.js index accdea5740..004aa12a71 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/images/imagehelper.js +++ b/dashboard-ui/bower_components/emby-webcomponents/images/imagehelper.js @@ -1,8 +1,15 @@ -define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser', 'dom', 'appSettings'], function (visibleinviewport, imageFetcher, layoutManager, events, browser, dom, appSettings) { +define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser', 'dom', 'appSettings', 'require'], function (visibleinviewport, imageFetcher, layoutManager, events, browser, dom, appSettings, require) { + 'use strict'; var thresholdX; var thresholdY; + var requestIdleCallback = window.requestIdleCallback || function (fn) { + fn(); + }; + + //var imagesWorker = new Worker(require.toUrl('.').split('?')[0] + '/imagesworker.js'); + var supportsIntersectionObserver = function () { if (window.IntersectionObserver) { @@ -19,8 +26,8 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser var y = screen.availHeight; if (browser.touch) { - x *= 2; - y *= 2; + x *= 1.5; + y *= 1.5; } thresholdX = x; @@ -48,26 +55,72 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser source = elem.getAttribute('data-src'); } - if (source) { - - imageFetcher.loadImage(elem, source).then(function () { - - var fillingVibrant = fillVibrant(elem, source); - - if (enableFade && !layoutManager.tv && enableEffects !== false && !fillingVibrant) { - fadeIn(elem); - } - - elem.removeAttribute("data-src"); - }); + if (!source) { + return; } + + fillImageElement(elem, source, enableEffects); } - function fillVibrant(img, url) { + function fillImageElement(elem, source, enableEffects) { + imageFetcher.loadImage(elem, source).then(function () { - if (img.tagName != 'IMG') { - return false; - } + var fillingVibrant = elem.tagName !== 'IMG' ? false : fillVibrant(elem, source); + + if (enableFade && !layoutManager.tv && enableEffects !== false && !fillingVibrant) { + fadeIn(elem); + } + + elem.removeAttribute("data-src"); + }); + } + + //var placeholder = document.createElement('div'); + //imagesWorker.onmessage = function (evt) { + // placeholder.dispatchEvent(new CustomEvent('decoded', { + // detail: evt.data, + // bubbles: false, + // cancellable: false + // })); + //}; + + //var uniqueId = 0; + + //function fillCanvas(elem, source) { + + // var newUniqueId = (++uniqueId); + + // imagesWorker.postMessage({ + // url: source, + // id: newUniqueId + // }); + + // placeholder.addEventListener('decoded', function (e) { + + // if (e.detail.id == newUniqueId) { + + // var imageBitmap = e.detail.imageBitmap; + // var canvas = document.createElement('canvas'); + // var canvasContext = canvas.getContext('2d'); + + // //drawWidth *= ratio; + // //drawHeight *= ratio; + + // // https://stackoverflow.com/questions/21961839/simulation-background-size-cover-in-canvas/21961894#21961894 + // canvasContext.imageSmoothingEnabled = false; + // var width = canvas.width = elem.offsetWidth; + // var height = canvas.height = elem.offsetHeight; + // canvasContext.drawImage(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, 0, 0, width, height); + + // fillVibrant(elem, source, canvas, canvasContext); + + // elem.insertBefore(canvas, elem.firstChild); + // elem.removeAttribute("data-src"); + // } + // }); + //} + + function fillVibrant(img, url, canvas, canvasContext) { var vibrantElement = img.getAttribute('data-vibrant'); if (!vibrantElement) { @@ -75,30 +128,35 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser } if (window.Vibrant) { - fillVibrantOnLoaded(img, url, vibrantElement); + fillVibrantOnLoaded(img, url, vibrantElement, canvas, canvasContext); return true; } require(['vibrant'], function () { - fillVibrantOnLoaded(img, url, vibrantElement); + fillVibrantOnLoaded(img, url, vibrantElement, canvas, canvasContext); }); return true; } - function fillVibrantOnLoaded(img, url, vibrantElement) { + function fillVibrantOnLoaded(img, url, vibrantElement, canvas, canvasContext) { vibrantElement = document.getElementById(vibrantElement); if (!vibrantElement) { return; } - var swatch = getVibrantInfo(img, url).split('|'); - if (swatch.length) { + requestIdleCallback(function () { - var index = 0; - vibrantElement.style['backgroundColor'] = swatch[index]; - vibrantElement.style['color'] = swatch[index + 1]; - } + //var now = new Date().getTime(); + var swatch = getVibrantInfo(canvas || img, url, canvasContext).split('|'); + //console.log('vibrant took ' + (new Date().getTime() - now) + 'ms'); + if (swatch.length) { + + var index = 0; + vibrantElement.style.backgroundColor = swatch[index]; + vibrantElement.style.color = swatch[index + 1]; + } + }); /* * Results into: * Vibrant #7a4426 @@ -118,7 +176,8 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser url = url.split('?')[0]; - return 'vibrant5-' + url; + var cacheKey = 'vibrant8'; + return cacheKey + url; } function getCachedVibrantInfo(url) { @@ -126,43 +185,41 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser return appSettings.get(getSettingsKey(url)); } - function getVibrantInfo(img, url) { + function getVibrantInfo(img, url, canvasContext) { var value = getCachedVibrantInfo(url); if (value) { return value; } - var vibrant = new Vibrant(img); + var vibrant = new Vibrant(img, canvasContext); var swatches = vibrant.swatches(); value = ''; - var swatch = swatches['DarkVibrant']; + var swatch = swatches.DarkVibrant; if (swatch) { value += swatch.getHex() + '|' + swatch.getBodyTextColor(); } - swatch = swatches['DarkMuted']; + swatch = swatches.DarkMuted; if (swatch) { value += '|' + swatch.getHex() + '|' + swatch.getBodyTextColor(); } else { value += '||'; } - swatch = swatches['Vibrant']; + swatch = swatches.Vibrant; if (swatch) { value += '|' + swatch.getHex() + '|' + swatch.getBodyTextColor(); } else { value += '||'; } - swatch = swatches['Muted']; + swatch = swatches.Muted; if (swatch) { value += '|' + swatch.getHex() + '|' + swatch.getBodyTextColor(); } else { value += '||'; } - if (value) { - appSettings.set(getSettingsKey(url), value); - } + appSettings.set(getSettingsKey(url), value); return value; } @@ -335,31 +392,33 @@ define(['visibleinviewport', 'imageFetcher', 'layoutManager', 'events', 'browser var result; - if (values.length % 2) + if (values.length % 2) { result = values[half]; - else + } + else { result = (values[half - 1] + values[half]) / 2.0; + } // If really close to 2:3 (poster image), just return 2:3 var aspect2x3 = 2 / 3; - if (Math.abs(aspect2x3 - result) <= .15) { + if (Math.abs(aspect2x3 - result) <= 0.15) { return aspect2x3; } // If really close to 16:9 (episode image), just return 16:9 var aspect16x9 = 16 / 9; - if (Math.abs(aspect16x9 - result) <= .2) { + if (Math.abs(aspect16x9 - result) <= 0.2) { return aspect16x9; } // If really close to 1 (square image), just return 1 - if (Math.abs(1 - result) <= .15) { + if (Math.abs(1 - result) <= 0.15) { return 1; } // If really close to 4:3 (poster image), just return 2:3 var aspect4x3 = 4 / 3; - if (Math.abs(aspect4x3 - result) <= .15) { + if (Math.abs(aspect4x3 - result) <= 0.15) { return aspect4x3; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/images/indexeddbimagefetcher.js b/dashboard-ui/bower_components/emby-webcomponents/images/indexeddbimagefetcher.js index 302e715f88..79ca120dc0 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/images/indexeddbimagefetcher.js +++ b/dashboard-ui/bower_components/emby-webcomponents/images/indexeddbimagefetcher.js @@ -1,4 +1,5 @@ define(['cryptojs-md5'], function () { + 'use strict'; var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB; var dbVersion = 1; @@ -30,20 +31,18 @@ define(['cryptojs-md5'], function () { // Interim solution for Google Chrome to create an objectStore. Will be deprecated if (localDb.setVersion) { - if (localDb.version != dbVersion) { + if (localDb.version !== dbVersion) { var setVersion = localDb.setVersion(dbVersion); setVersion.onsuccess = function () { createObjectStore(localDb); }; - } - else { + } else { db = localDb; } - } - else { + } else { db = localDb; } - } + }; function revoke(url) { @@ -71,12 +70,12 @@ define(['cryptojs-md5'], function () { // Try to strip off the domain to share the cache between local and remote connections var index = url.indexOf('://'); - if (index != -1) { + if (index !== -1) { url = url.substring(index + 3); index = url.indexOf('/'); - if (index != -1) { + if (index !== -1) { url = url.substring(index + 1); } diff --git a/dashboard-ui/bower_components/emby-webcomponents/images/persistentimagefetcher.js b/dashboard-ui/bower_components/emby-webcomponents/images/persistentimagefetcher.js index bc1ebc769a..0d97fc9cc0 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/images/persistentimagefetcher.js +++ b/dashboard-ui/bower_components/emby-webcomponents/images/persistentimagefetcher.js @@ -1,4 +1,5 @@ define(['cryptojs-md5'], function () { + 'use strict'; function loadImage(elem, url) { @@ -20,7 +21,7 @@ define(['cryptojs-md5'], function () { function createDir(rootDirEntry, folders, callback, errorCallback) { // Throw out './' or '/' and move on to prevent something like '/foo/.//bar'. - if (folders[0] == '.' || folders[0] == '') { + if (folders[0] === '.' || folders[0] === '') { folders = folders.slice(1); } rootDirEntry.getDirectory(folders[0], { create: true }, function (dirEntry) { @@ -140,12 +141,12 @@ define(['cryptojs-md5'], function () { // Try to strip off the domain to share the cache between local and remote connections var index = url.indexOf('://'); - if (index != -1) { + if (index !== -1) { url = url.substring(index + 3); index = url.indexOf('/'); - if (index != -1) { + if (index !== -1) { url = url.substring(index + 1); } @@ -163,12 +164,12 @@ define(['cryptojs-md5'], function () { xhr.responseType = "arraybuffer"; xhr.onload = function (e) { - if (this.status == 200) { + if (this.status === 200) { writeData(dir, filename, this.getResponseHeader('Content-Type'), this.response, callback, errorCallback); } else { errorCallback(); } - } + }; xhr.send(); } @@ -200,7 +201,7 @@ define(['cryptojs-md5'], function () { return new Promise(function (resolve, reject) { - if (originalUrl.indexOf('tag=') != -1) { + if (originalUrl.indexOf('tag=') !== -1) { originalUrl += "&accept=webp"; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/indicators/indicators.js b/dashboard-ui/bower_components/emby-webcomponents/indicators/indicators.js index fc68ddff39..bba4ec1480 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/indicators/indicators.js +++ b/dashboard-ui/bower_components/emby-webcomponents/indicators/indicators.js @@ -1,9 +1,10 @@ define(['css!./indicators.css', 'material-icons'], function () { + 'use strict'; function enableProgressIndicator(item) { - if (item.MediaType == 'Video') { - if (item.Type != 'TvChannel') { + if (item.MediaType === 'Video') { + if (item.Type !== 'TvChannel') { return true; } } @@ -27,7 +28,7 @@ define(['css!./indicators.css', 'material-icons'], function () { function getProgressBarHtml(item, options) { if (enableProgressIndicator(item)) { - if (item.Type == "Recording" && item.CompletionPercentage) { + if (item.Type === "Recording" && item.CompletionPercentage) { return getProgressHtml(item.CompletionPercentage, options); } @@ -48,9 +49,9 @@ define(['css!./indicators.css', 'material-icons'], function () { function enablePlayedIndicator(item) { - if (item.Type == "Series" || item.Type == "Season" || item.Type == "BoxSet" || item.MediaType == "Video" || item.MediaType == "Game" || item.MediaType == "Book") { + if (item.Type === "Series" || item.Type === "Season" || item.Type === "BoxSet" || item.MediaType === "Video" || item.MediaType === "Game" || item.MediaType === "Book") { - if (item.Type != 'TvChannel') { + if (item.Type !== 'TvChannel') { return true; } } @@ -100,14 +101,14 @@ define(['css!./indicators.css', 'material-icons'], function () { var status; - if (item.Type == 'SeriesTimer') { + if (item.Type === 'SeriesTimer') { return ''; } else if (item.TimerId || item.SeriesTimerId) { status = item.Status || 'Cancelled'; } - else if (item.Type == 'Timer') { + else if (item.Type === 'Timer') { status = item.Status; } @@ -117,7 +118,7 @@ define(['css!./indicators.css', 'material-icons'], function () { if (item.SeriesTimerId) { - if (status != 'Cancelled') { + if (status !== 'Cancelled') { return ''; } @@ -129,7 +130,7 @@ define(['css!./indicators.css', 'material-icons'], function () { function getSyncIndicator(item) { - if (item.SyncPercent == 100) { + if (item.SyncPercent === 100) { return '
file_download
'; } else if (item.SyncPercent != null) { return '
file_download
'; diff --git a/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json b/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json index 98c4f80e99..02aed2edb9 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json +++ b/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json @@ -10,7 +10,6 @@ "Repeat": "Repeat", "TrackCount": "{0} tracks", "ItemCount": "{0} items", - "ValueSeriesYearToPresent": "{0} - Present", "ReleaseYearValue": "Release year: {0}", "OriginalAirDateValue": "Original air date: {0}", "EndsAtValue": "Ends at {0}", diff --git a/dashboard-ui/bower_components/vibrant/dist/Vibrant.js b/dashboard-ui/bower_components/vibrant/dist/Vibrant.js new file mode 100644 index 0000000000..a71e4cce80 --- /dev/null +++ b/dashboard-ui/bower_components/vibrant/dist/Vibrant.js @@ -0,0 +1,876 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o b) ? 1 : 0); + }, + sum: function(array, f) { + var o = {}; + return array.reduce(f ? function(p, d, i) { + o.index = i; + return p + f.call(o, d); + } : function(p, d) { + return p + d; + }, 0); + }, + max: function(array, f) { + return Math.max.apply(null, f ? pv.map(array, f) : array); + } + } +} + +/** + * Basic Javascript port of the MMCQ (modified median cut quantization) + * algorithm from the Leptonica library (http://www.leptonica.com/). + * Returns a color map you can use to map original pixels to the reduced + * palette. Still a work in progress. + * + * @author Nick Rabinowitz + * @example + +// array of pixels as [R,G,B] arrays +var myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207] + // etc + ]; +var maxColors = 4; + +var cmap = MMCQ.quantize(myPixels, maxColors); +var newPalette = cmap.palette(); +var newPixels = myPixels.map(function(p) { + return cmap.map(p); +}); + + */ +var MMCQ = (function() { + // private constants + var sigbits = 5, + rshift = 8 - sigbits, + maxIterations = 1000, + fractByPopulations = 0.75; + + // get reduced-space color index for a pixel + + function getColorIndex(r, g, b) { + return (r << (2 * sigbits)) + (g << sigbits) + b; + } + + // Simple priority queue + + function PQueue(comparator) { + var contents = [], + sorted = false; + + function sort() { + contents.sort(comparator); + sorted = true; + } + + return { + push: function(o) { + contents.push(o); + sorted = false; + }, + peek: function(index) { + if (!sorted) sort(); + if (index === undefined) index = contents.length - 1; + return contents[index]; + }, + pop: function() { + if (!sorted) sort(); + return contents.pop(); + }, + size: function() { + return contents.length; + }, + map: function(f) { + return contents.map(f); + }, + debug: function() { + if (!sorted) sort(); + return contents; + } + }; + } + + // 3d color space box + + function VBox(r1, r2, g1, g2, b1, b2, histo) { + var vbox = this; + vbox.r1 = r1; + vbox.r2 = r2; + vbox.g1 = g1; + vbox.g2 = g2; + vbox.b1 = b1; + vbox.b2 = b2; + vbox.histo = histo; + } + VBox.prototype = { + volume: function(force) { + var vbox = this; + if (!vbox._volume || force) { + vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1)); + } + return vbox._volume; + }, + count: function(force) { + var vbox = this, + histo = vbox.histo; + if (!vbox._count_set || force) { + var npix = 0, + i, j, k, index; + for (i = vbox.r1; i <= vbox.r2; i++) { + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(i, j, k); + npix += (histo[index] || 0); + } + } + } + vbox._count = npix; + vbox._count_set = true; + } + return vbox._count; + }, + copy: function() { + var vbox = this; + return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo); + }, + avg: function(force) { + var vbox = this, + histo = vbox.histo; + if (!vbox._avg || force) { + var ntot = 0, + mult = 1 << (8 - sigbits), + rsum = 0, + gsum = 0, + bsum = 0, + hval, + i, j, k, histoindex; + for (i = vbox.r1; i <= vbox.r2; i++) { + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + histoindex = getColorIndex(i, j, k); + hval = histo[histoindex] || 0; + ntot += hval; + rsum += (hval * (i + 0.5) * mult); + gsum += (hval * (j + 0.5) * mult); + bsum += (hval * (k + 0.5) * mult); + } + } + } + if (ntot) { + vbox._avg = [~~(rsum / ntot), ~~ (gsum / ntot), ~~ (bsum / ntot)]; + } else { + //console.log('empty box'); + vbox._avg = [~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), ~~ (mult * (vbox.g1 + vbox.g2 + 1) / 2), ~~ (mult * (vbox.b1 + vbox.b2 + 1) / 2)]; + } + } + return vbox._avg; + }, + contains: function(pixel) { + var vbox = this, + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + return (rval >= vbox.r1 && rval <= vbox.r2 && + gval >= vbox.g1 && gval <= vbox.g2 && + bval >= vbox.b1 && bval <= vbox.b2); + } + }; + + // Color map + + function CMap() { + this.vboxes = new PQueue(function(a, b) { + return pv.naturalOrder( + a.vbox.count() * a.vbox.volume(), + b.vbox.count() * b.vbox.volume() + ) + });; + } + CMap.prototype = { + push: function(vbox) { + this.vboxes.push({ + vbox: vbox, + color: vbox.avg() + }); + }, + palette: function() { + return this.vboxes.map(function(vb) { + return vb.color + }); + }, + size: function() { + return this.vboxes.size(); + }, + map: function(color) { + var vboxes = this.vboxes; + for (var i = 0; i < vboxes.size(); i++) { + if (vboxes.peek(i).vbox.contains(color)) { + return vboxes.peek(i).color; + } + } + return this.nearest(color); + }, + nearest: function(color) { + var vboxes = this.vboxes, + d1, d2, pColor; + for (var i = 0; i < vboxes.size(); i++) { + d2 = Math.sqrt( + Math.pow(color[0] - vboxes.peek(i).color[0], 2) + + Math.pow(color[1] - vboxes.peek(i).color[1], 2) + + Math.pow(color[2] - vboxes.peek(i).color[2], 2) + ); + if (d2 < d1 || d1 === undefined) { + d1 = d2; + pColor = vboxes.peek(i).color; + } + } + return pColor; + }, + forcebw: function() { + // XXX: won't work yet + var vboxes = this.vboxes; + vboxes.sort(function(a, b) { + return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color)) + }); + + // force darkest color to black if everything < 5 + var lowest = vboxes[0].color; + if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5) + vboxes[0].color = [0, 0, 0]; + + // force lightest color to white if everything > 251 + var idx = vboxes.length - 1, + highest = vboxes[idx].color; + if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251) + vboxes[idx].color = [255, 255, 255]; + } + }; + + // histo (1-d array, giving the number of pixels in + // each quantized region of color space), or null on error + + function getHisto(pixels) { + var histosize = 1 << (3 * sigbits), + histo = new Array(histosize), + index, rval, gval, bval; + pixels.forEach(function(pixel) { + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + index = getColorIndex(rval, gval, bval); + histo[index] = (histo[index] || 0) + 1; + }); + return histo; + } + + function vboxFromPixels(pixels, histo) { + var rmin = 1000000, + rmax = 0, + gmin = 1000000, + gmax = 0, + bmin = 1000000, + bmax = 0, + rval, gval, bval; + // find min/max + pixels.forEach(function(pixel) { + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + if (rval < rmin) rmin = rval; + else if (rval > rmax) rmax = rval; + if (gval < gmin) gmin = gval; + else if (gval > gmax) gmax = gval; + if (bval < bmin) bmin = bval; + else if (bval > bmax) bmax = bval; + }); + return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo); + } + + function medianCutApply(histo, vbox) { + if (!vbox.count()) return; + + var rw = vbox.r2 - vbox.r1 + 1, + gw = vbox.g2 - vbox.g1 + 1, + bw = vbox.b2 - vbox.b1 + 1, + maxw = pv.max([rw, gw, bw]); + // only one pixel, no split + if (vbox.count() == 1) { + return [vbox.copy()] + } + /* Find the partial sum arrays along the selected axis. */ + var total = 0, + partialsum = [], + lookaheadsum = [], + i, j, k, sum, index; + if (maxw == rw) { + for (i = vbox.r1; i <= vbox.r2; i++) { + sum = 0; + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(i, j, k); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } else if (maxw == gw) { + for (i = vbox.g1; i <= vbox.g2; i++) { + sum = 0; + for (j = vbox.r1; j <= vbox.r2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(j, i, k); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } else { /* maxw == bw */ + for (i = vbox.b1; i <= vbox.b2; i++) { + sum = 0; + for (j = vbox.r1; j <= vbox.r2; j++) { + for (k = vbox.g1; k <= vbox.g2; k++) { + index = getColorIndex(j, k, i); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } + partialsum.forEach(function(d, i) { + lookaheadsum[i] = total - d + }); + + function doCut(color) { + var dim1 = color + '1', + dim2 = color + '2', + left, right, vbox1, vbox2, d2, count2 = 0; + for (i = vbox[dim1]; i <= vbox[dim2]; i++) { + if (partialsum[i] > total / 2) { + vbox1 = vbox.copy(); + vbox2 = vbox.copy(); + left = i - vbox[dim1]; + right = vbox[dim2] - i; + if (left <= right) + d2 = Math.min(vbox[dim2] - 1, ~~ (i + right / 2)); + else d2 = Math.max(vbox[dim1], ~~ (i - 1 - left / 2)); + // avoid 0-count boxes + while (!partialsum[d2]) d2++; + count2 = lookaheadsum[d2]; + while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2]; + // set dimensions + vbox1[dim2] = d2; + vbox2[dim1] = vbox1[dim2] + 1; + // console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count()); + return [vbox1, vbox2]; + } + } + + } + // determine the cut planes + return maxw == rw ? doCut('r') : + maxw == gw ? doCut('g') : + doCut('b'); + } + + function quantize(pixels, maxcolors) { + // short-circuit + if (!pixels.length || maxcolors < 2 || maxcolors > 256) { + // console.log('wrong number of maxcolors'); + return false; + } + + // XXX: check color content and convert to grayscale if insufficient + + var histo = getHisto(pixels), + histosize = 1 << (3 * sigbits); + + // check that we aren't below maxcolors already + var nColors = 0; + histo.forEach(function() { + nColors++ + }); + if (nColors <= maxcolors) { + // XXX: generate the new colors from the histo and return + } + + // get the beginning vbox from the colors + var vbox = vboxFromPixels(pixels, histo), + pq = new PQueue(function(a, b) { + return pv.naturalOrder(a.count(), b.count()) + }); + pq.push(vbox); + + // inner function to do the iteration + + function iter(lh, target) { + var ncolors = 1, + niters = 0, + vbox; + while (niters < maxIterations) { + vbox = lh.pop(); + if (!vbox.count()) { /* just put it back */ + lh.push(vbox); + niters++; + continue; + } + // do the cut + var vboxes = medianCutApply(histo, vbox), + vbox1 = vboxes[0], + vbox2 = vboxes[1]; + + if (!vbox1) { + // console.log("vbox1 not defined; shouldn't happen!"); + return; + } + lh.push(vbox1); + if (vbox2) { /* vbox2 can be null */ + lh.push(vbox2); + ncolors++; + } + if (ncolors >= target) return; + if (niters++ > maxIterations) { + // console.log("infinite loop; perhaps too few pixels!"); + return; + } + } + } + + // first set of colors, sorted by population + iter(pq, fractByPopulations * maxcolors); + // console.log(pq.size(), pq.debug().length, pq.debug().slice()); + + // Re-sort by the product of pixel occupancy times the size in color space. + var pq2 = new PQueue(function(a, b) { + return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume()) + }); + while (pq.size()) { + pq2.push(pq.pop()); + } + + // next set - generate the median cuts using the (npix * vol) sorting. + iter(pq2, maxcolors - pq2.size()); + + // calculate the actual colors + var cmap = new CMap(); + while (pq2.size()) { + cmap.push(pq2.pop()); + } + + return cmap; + } + + return { + quantize: quantize + } +})(); + +module.exports = MMCQ.quantize + +},{}],2:[function(require,module,exports){ + +/* + Vibrant.js + by Jari Zwarts + + Color algorithm class that finds variations on colors in an image. + + Credits + -------- + Lokesh Dhakar (http://www.lokeshdhakar.com) - Created ColorThief + Google - Palette support library in Android + */ + +(function() { + var CanvasImage, Swatch, Vibrant, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + slice = [].slice; + + window.Swatch = Swatch = (function() { + Swatch.prototype.hsl = void 0; + + Swatch.prototype.rgb = void 0; + + Swatch.prototype.population = 1; + + Swatch.yiq = 0; + + function Swatch(rgb, population) { + this.rgb = rgb; + this.population = population; + } + + Swatch.prototype.getHsl = function() { + if (!this.hsl) { + return this.hsl = Vibrant.rgbToHsl(this.rgb[0], this.rgb[1], this.rgb[2]); + } else { + return this.hsl; + } + }; + + Swatch.prototype.getPopulation = function() { + return this.population; + }; + + Swatch.prototype.getRgb = function() { + return this.rgb; + }; + + Swatch.prototype.getHex = function() { + return "#" + ((1 << 24) + (this.rgb[0] << 16) + (this.rgb[1] << 8) + this.rgb[2]).toString(16).slice(1, 7); + }; + + Swatch.prototype.getTitleTextColor = function() { + this._ensureTextColors(); + if (this.yiq < 200) { + return "#fff"; + } else { + return "#000"; + } + }; + + Swatch.prototype.getBodyTextColor = function() { + this._ensureTextColors(); + if (this.yiq < 150) { + return "#fff"; + } else { + return "#000"; + } + }; + + Swatch.prototype._ensureTextColors = function() { + if (!this.yiq) { + return this.yiq = (this.rgb[0] * 299 + this.rgb[1] * 587 + this.rgb[2] * 114) / 1000; + } + }; + + return Swatch; + + })(); + + window.Vibrant = Vibrant = (function() { + Vibrant.prototype.quantize = require('quantize'); + + Vibrant.prototype._swatches = []; + + Vibrant.prototype.TARGET_DARK_LUMA = 0.26; + + Vibrant.prototype.MAX_DARK_LUMA = 0.45; + + Vibrant.prototype.MIN_LIGHT_LUMA = 0.55; + + Vibrant.prototype.TARGET_LIGHT_LUMA = 0.74; + + Vibrant.prototype.MIN_NORMAL_LUMA = 0.3; + + Vibrant.prototype.TARGET_NORMAL_LUMA = 0.5; + + Vibrant.prototype.MAX_NORMAL_LUMA = 0.7; + + Vibrant.prototype.TARGET_MUTED_SATURATION = 0.3; + + Vibrant.prototype.MAX_MUTED_SATURATION = 0.4; + + Vibrant.prototype.TARGET_VIBRANT_SATURATION = 1; + + Vibrant.prototype.MIN_VIBRANT_SATURATION = 0.35; + + Vibrant.prototype.WEIGHT_SATURATION = 3; + + Vibrant.prototype.WEIGHT_LUMA = 6; + + Vibrant.prototype.WEIGHT_POPULATION = 1; + + Vibrant.prototype.VibrantSwatch = void 0; + + Vibrant.prototype.MutedSwatch = void 0; + + Vibrant.prototype.DarkVibrantSwatch = void 0; + + Vibrant.prototype.DarkMutedSwatch = void 0; + + Vibrant.prototype.LightVibrantSwatch = void 0; + + Vibrant.prototype.LightMutedSwatch = void 0; + + Vibrant.prototype.HighestPopulation = 0; + + function Vibrant(sourceImage, colorCount, quality) { + this.swatches = bind(this.swatches, this); + var a, allPixels, b, cmap, g, i, image, imageData, offset, pixelCount, pixels, r; + if (typeof colorCount === 'undefined') { + colorCount = 64; + } + if (typeof quality === 'undefined') { + quality = 5; + } + image = new CanvasImage(sourceImage); + try { + imageData = image.getImageData(); + pixels = imageData.data; + pixelCount = image.getPixelCount(); + allPixels = []; + i = 0; + while (i < pixelCount) { + offset = i * 4; + r = pixels[offset + 0]; + g = pixels[offset + 1]; + b = pixels[offset + 2]; + a = pixels[offset + 3]; + if (a >= 125) { + if (!(r > 250 && g > 250 && b > 250)) { + allPixels.push([r, g, b]); + } + } + i = i + quality; + } + cmap = this.quantize(allPixels, colorCount); + this._swatches = cmap.vboxes.map((function(_this) { + return function(vbox) { + return new Swatch(vbox.color, vbox.vbox.count()); + }; + })(this)); + this.maxPopulation = this.findMaxPopulation; + this.generateVarationColors(); + this.generateEmptySwatches(); + } finally { + image.removeCanvas(); + } + } + + Vibrant.prototype.generateVarationColors = function() { + this.VibrantSwatch = this.findColorVariation(this.TARGET_NORMAL_LUMA, this.MIN_NORMAL_LUMA, this.MAX_NORMAL_LUMA, this.TARGET_VIBRANT_SATURATION, this.MIN_VIBRANT_SATURATION, 1); + this.LightVibrantSwatch = this.findColorVariation(this.TARGET_LIGHT_LUMA, this.MIN_LIGHT_LUMA, 1, this.TARGET_VIBRANT_SATURATION, this.MIN_VIBRANT_SATURATION, 1); + this.DarkVibrantSwatch = this.findColorVariation(this.TARGET_DARK_LUMA, 0, this.MAX_DARK_LUMA, this.TARGET_VIBRANT_SATURATION, this.MIN_VIBRANT_SATURATION, 1); + this.MutedSwatch = this.findColorVariation(this.TARGET_NORMAL_LUMA, this.MIN_NORMAL_LUMA, this.MAX_NORMAL_LUMA, this.TARGET_MUTED_SATURATION, 0, this.MAX_MUTED_SATURATION); + this.LightMutedSwatch = this.findColorVariation(this.TARGET_LIGHT_LUMA, this.MIN_LIGHT_LUMA, 1, this.TARGET_MUTED_SATURATION, 0, this.MAX_MUTED_SATURATION); + return this.DarkMutedSwatch = this.findColorVariation(this.TARGET_DARK_LUMA, 0, this.MAX_DARK_LUMA, this.TARGET_MUTED_SATURATION, 0, this.MAX_MUTED_SATURATION); + }; + + Vibrant.prototype.generateEmptySwatches = function() { + var hsl; + if (this.VibrantSwatch === void 0) { + if (this.DarkVibrantSwatch !== void 0) { + hsl = this.DarkVibrantSwatch.getHsl(); + hsl[2] = this.TARGET_NORMAL_LUMA; + this.VibrantSwatch = new Swatch(Vibrant.hslToRgb(hsl[0], hsl[1], hsl[2]), 0); + } + } + if (this.DarkVibrantSwatch === void 0) { + if (this.VibrantSwatch !== void 0) { + hsl = this.VibrantSwatch.getHsl(); + hsl[2] = this.TARGET_DARK_LUMA; + return this.DarkVibrantSwatch = new Swatch(Vibrant.hslToRgb(hsl[0], hsl[1], hsl[2]), 0); + } + } + }; + + Vibrant.prototype.findMaxPopulation = function() { + var j, len, population, ref, swatch; + population = 0; + ref = this._swatches; + for (j = 0, len = ref.length; j < len; j++) { + swatch = ref[j]; + population = Math.max(population, swatch.getPopulation()); + } + return population; + }; + + Vibrant.prototype.findColorVariation = function(targetLuma, minLuma, maxLuma, targetSaturation, minSaturation, maxSaturation) { + var j, len, luma, max, maxValue, ref, sat, swatch, value; + max = void 0; + maxValue = 0; + ref = this._swatches; + for (j = 0, len = ref.length; j < len; j++) { + swatch = ref[j]; + sat = swatch.getHsl()[1]; + luma = swatch.getHsl()[2]; + if (sat >= minSaturation && sat <= maxSaturation && luma >= minLuma && luma <= maxLuma && !this.isAlreadySelected(swatch)) { + value = this.createComparisonValue(sat, targetSaturation, luma, targetLuma, swatch.getPopulation(), this.HighestPopulation); + if (max === void 0 || value > maxValue) { + max = swatch; + maxValue = value; + } + } + } + return max; + }; + + Vibrant.prototype.createComparisonValue = function(saturation, targetSaturation, luma, targetLuma, population, maxPopulation) { + return this.weightedMean(this.invertDiff(saturation, targetSaturation), this.WEIGHT_SATURATION, this.invertDiff(luma, targetLuma), this.WEIGHT_LUMA, population / maxPopulation, this.WEIGHT_POPULATION); + }; + + Vibrant.prototype.invertDiff = function(value, targetValue) { + return 1 - Math.abs(value - targetValue); + }; + + Vibrant.prototype.weightedMean = function() { + var i, sum, sumWeight, value, values, weight; + values = 1 <= arguments.length ? slice.call(arguments, 0) : []; + sum = 0; + sumWeight = 0; + i = 0; + while (i < values.length) { + value = values[i]; + weight = values[i + 1]; + sum += value * weight; + sumWeight += weight; + i += 2; + } + return sum / sumWeight; + }; + + Vibrant.prototype.swatches = function() { + return { + Vibrant: this.VibrantSwatch, + Muted: this.MutedSwatch, + DarkVibrant: this.DarkVibrantSwatch, + DarkMuted: this.DarkMutedSwatch, + LightVibrant: this.LightVibrantSwatch, + LightMuted: this.LightMuted + }; + }; + + Vibrant.prototype.isAlreadySelected = function(swatch) { + return this.VibrantSwatch === swatch || this.DarkVibrantSwatch === swatch || this.LightVibrantSwatch === swatch || this.MutedSwatch === swatch || this.DarkMutedSwatch === swatch || this.LightMutedSwatch === swatch; + }; + + Vibrant.rgbToHsl = function(r, g, b) { + var d, h, l, max, min, s; + r /= 255; + g /= 255; + b /= 255; + max = Math.max(r, g, b); + min = Math.min(r, g, b); + h = void 0; + s = void 0; + l = (max + min) / 2; + if (max === min) { + h = s = 0; + } else { + d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + } + h /= 6; + } + return [h, s, l]; + }; + + Vibrant.hslToRgb = function(h, s, l) { + var b, g, hue2rgb, p, q, r; + r = void 0; + g = void 0; + b = void 0; + hue2rgb = function(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; + }; + if (s === 0) { + r = g = b = l; + } else { + q = l < 0.5 ? l * (1 + s) : l + s - (l * s); + p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - (1 / 3)); + } + return [r * 255, g * 255, b * 255]; + }; + + return Vibrant; + + })(); + + + /* + CanvasImage Class + Class that wraps the html image element and canvas. + It also simplifies some of the canvas context manipulation + with a set of helper functions. + Stolen from https://github.com/lokesh/color-thief + */ + + window.CanvasImage = CanvasImage = (function() { + function CanvasImage(image, context) { + + if (context) { + this.width = image.width; + this.height = image.height; + + this.context = context; + } else { + this.canvas = document.createElement('canvas'); + this.width = this.canvas.width = image.width; + this.height = this.canvas.height = image.height; + + this.context = this.canvas.getContext('2d'); + this.context.drawImage(image, 0, 0, this.width, this.height); + } + } + + CanvasImage.prototype.getPixelCount = function() { + return this.width * this.height; + }; + + CanvasImage.prototype.getImageData = function () { + return this.context.getImageData(0, 0, this.width, this.height); + }; + + CanvasImage.prototype.removeCanvas = function() { + this.context = null; + this.canvas = null; + }; + + return CanvasImage; + + })(); + +}).call(this); + +},{"quantize":1}]},{},[2]); diff --git a/dashboard-ui/components/apphost.js b/dashboard-ui/components/apphost.js index 40104dd8f8..209353656d 100644 --- a/dashboard-ui/components/apphost.js +++ b/dashboard-ui/components/apphost.js @@ -148,7 +148,7 @@ define(['appStorage', 'browser'], function (appStorage, browser) { features.push('fullscreen'); } - if (!browser.slow) { + if (browser.chrome || (browser.safari && !browser.mobile)) { features.push('imageanalysis'); } @@ -211,7 +211,7 @@ define(['appStorage', 'browser'], function (appStorage, browser) { }); }, capabilities: getCapabilities, - + preferVisualCards: browser.android || browser.chrome, moreIcon: browser.safari || browser.edge ? 'dots-horiz' : 'dots-vert' }; }); \ No newline at end of file diff --git a/dashboard-ui/components/favoriteitems.js b/dashboard-ui/components/favoriteitems.js index 7541bb83a5..a5f41c36d8 100644 --- a/dashboard-ui/components/favoriteitems.js +++ b/dashboard-ui/components/favoriteitems.js @@ -1,4 +1,4 @@ -define(['libraryBrowser', 'cardBuilder', 'dom', 'scrollStyles', 'emby-itemscontainer'], function (libraryBrowser, cardBuilder, dom) { +define(['libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'scrollStyles', 'emby-itemscontainer'], function (libraryBrowser, cardBuilder, dom, appHost) { function enableScrollX() { return browserInfo.mobile && AppInfo.enableAppLayouts; @@ -21,7 +21,7 @@ return [ { name: 'HeaderFavoriteMovies', types: "Movie", id: "favoriteMovies", shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, { name: 'HeaderFavoriteShows', types: "Series", id: "favoriteShows", shape: getPosterShape(), showTitle: false, overlayPlayButton: true }, - { name: 'HeaderFavoriteEpisodes', types: "Episode", id: "favoriteEpisode", shape: getThumbShape(), preferThumb: false, showTitle: true, showParentTitle: true, overlayPlayButton: true }, + { name: 'HeaderFavoriteEpisodes', types: "Episode", id: "favoriteEpisode", shape: getThumbShape(), preferThumb: false, showTitle: true, showParentTitle: true, overlayPlayButton: true, overlayText: false, centerText: true }, { name: 'HeaderFavoriteGames', types: "Game", id: "favoriteGames", shape: getSquareShape(), preferThumb: false, showTitle: true }, { name: 'HeaderFavoriteArtists', types: "MusicArtist", id: "favoriteArtists", shape: getSquareShape(), preferThumb: false, showTitle: true, overlayText: false, showParentTitle: true, centerText: true, overlayPlayButton: true }, { name: 'HeaderFavoriteAlbums', types: "MusicAlbum", id: "favoriteAlbums", shape: getSquareShape(), preferThumb: false, showTitle: true, overlayText: false, showParentTitle: true, centerText: true, overlayPlayButton: true }, @@ -88,10 +88,13 @@ html += '
'; } + var supportsImageAnalysis = appHost.supports('imageanalysis'); + var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.showTitle; + html += cardBuilder.getCardsHtml(result.Items, { preferThumb: section.preferThumb, shape: section.shape, - centerText: section.centerText, + centerText: section.centerText && !cardLayout, overlayText: section.overlayText !== false, showTitle: section.showTitle, showParentTitle: section.showParentTitle, @@ -99,7 +102,9 @@ overlayPlayButton: section.overlayPlayButton, overlayMoreButton: section.overlayMoreButton, action: section.action, - allowBottomPadding: !enableScrollX() + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout, + vibrant: supportsImageAnalysis && cardLayout }); html += '
'; diff --git a/dashboard-ui/scripts/livetvcomponents.js b/dashboard-ui/scripts/livetvcomponents.js index 6a2d62e801..9f5c0b627a 100644 --- a/dashboard-ui/scripts/livetvcomponents.js +++ b/dashboard-ui/scripts/livetvcomponents.js @@ -1,4 +1,4 @@ -define(['datetime', 'cardBuilder', 'listView'], function (datetime, cardBuilder, listView) { +define(['datetime', 'cardBuilder', 'apphost'], function (datetime, cardBuilder, appHost) { function enableScrollX() { return browserInfo.mobile && AppInfo.enableAppLayouts; @@ -81,6 +81,9 @@ html += '
'; } + var supportsImageAnalysis = appHost.supports('imageanalysis'); + var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; + html += cardBuilder.getCardsHtml({ items: group.items, shape: getBackdropShape(), @@ -88,8 +91,9 @@ showAirTime: true, showAirEndTime: true, showChannelName: true, - cardLayout: true, - vibrant: true, + cardLayout: cardLayout, + centerText: !cardLayout, + vibrant: supportsImageAnalysis, action: 'edit', cardFooterAside: 'none', preferThumb: true, diff --git a/dashboard-ui/scripts/livetvrecordings.js b/dashboard-ui/scripts/livetvrecordings.js index 298d05c8a4..2f0abb52e2 100644 --- a/dashboard-ui/scripts/livetvrecordings.js +++ b/dashboard-ui/scripts/livetvrecordings.js @@ -1,4 +1,4 @@ -define(['components/categorysyncbuttons', 'cardBuilder', 'scripts/livetvcomponents', 'emby-button', 'listViewStyle', 'emby-itemscontainer'], function (categorysyncbuttons, cardBuilder) { +define(['components/categorysyncbuttons', 'cardBuilder', 'apphost', 'scripts/livetvcomponents', 'emby-button', 'listViewStyle', 'emby-itemscontainer'], function (categorysyncbuttons, cardBuilder, appHost) { function getRecordingGroupHtml(group) { @@ -76,6 +76,9 @@ recordingItems.classList.add('vertical-wrap'); } + var supportsImageAnalysis = appHost.supports('imageanalysis'); + var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ items: recordings, shape: (enableScrollX() ? 'autooverflow' : 'auto'), @@ -83,8 +86,9 @@ showParentTitle: true, coverImage: true, lazy: true, - cardLayout: true, - vibrant: true, + cardLayout: cardLayout, + centerText: !cardLayout, + vibrant: supportsImageAnalysis, allowBottomPadding: !enableScrollX(), preferThumb: 'auto' diff --git a/dashboard-ui/scripts/livetvschedule.js b/dashboard-ui/scripts/livetvschedule.js index 718a7f0646..85442376e6 100644 --- a/dashboard-ui/scripts/livetvschedule.js +++ b/dashboard-ui/scripts/livetvschedule.js @@ -1,4 +1,4 @@ -define(['cardBuilder', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (cardBuilder) { +define(['cardBuilder', 'apphost', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (cardBuilder, appHost) { function enableScrollX() { return browserInfo.mobile && AppInfo.enableAppLayouts; @@ -22,15 +22,18 @@ recordingItems.classList.add('vertical-wrap'); } + var supportsImageAnalysis = appHost.supports('imageanalysis'); + var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ items: recordings, shape: (enableScrollX() ? 'autooverflow' : 'auto'), showTitle: true, showParentTitle: true, coverImage: true, - lazy: true, - cardLayout: true, - vibrant: true, + cardLayout: cardLayout, + centerText: !cardLayout, + vibrant: supportsImageAnalysis, allowBottomPadding: !enableScrollX(), preferThumb: 'auto' @@ -59,8 +62,6 @@ showAirTime: true, showAirEndTime: true, showChannelName: true, - cardLayout: true, - vibrant: true, preferThumb: true, coverImage: true, overlayText: false diff --git a/dashboard-ui/scripts/livetvseriestimers.js b/dashboard-ui/scripts/livetvseriestimers.js index e7c05b8fef..8584c64184 100644 --- a/dashboard-ui/scripts/livetvseriestimers.js +++ b/dashboard-ui/scripts/livetvseriestimers.js @@ -1,4 +1,4 @@ -define(['datetime', 'cardBuilder', 'imageLoader', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader) { +define(['datetime', 'cardBuilder', 'imageLoader', 'apphost', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader, appHost) { var query = { @@ -10,17 +10,22 @@ var html = ''; + var supportsImageAnalysis = appHost.supports('imageanalysis'); + var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; + html += cardBuilder.getCardsHtml({ items: timers, shape: 'backdrop', showTitle: true, - cardLayout: true, - vibrant: true, + cardLayout: cardLayout, + vibrant: supportsImageAnalysis, preferThumb: true, coverImage: true, overlayText: false, showSeriesTimerTime: true, - showSeriesTimerChannel: true + showSeriesTimerChannel: true, + centerText: !cardLayout, + overlayMoreButton: !cardLayout }); var elem = context.querySelector('#items'); diff --git a/dashboard-ui/scripts/playlists.js b/dashboard-ui/scripts/playlists.js index 9df97c2f2f..4019b9d569 100644 --- a/dashboard-ui/scripts/playlists.js +++ b/dashboard-ui/scripts/playlists.js @@ -1,4 +1,4 @@ -define(['listView', 'cardBuilder', 'libraryBrowser', 'emby-itemscontainer'], function (listView, cardBuilder, libraryBrowser) { +define(['listView', 'cardBuilder', 'libraryBrowser', 'apphost', 'emby-itemscontainer'], function (listView, cardBuilder, libraryBrowser, appHost) { return function (view, params) { @@ -18,7 +18,7 @@ StartIndex: 0, Limit: LibraryBrowser.getDefaultPageSize() }, - view: LibraryBrowser.getSavedView(key) || 'PosterCard' + view: LibraryBrowser.getSavedView(key) || appHost.preferVisualCards ? 'PosterCard' : 'Poster' }; pageData.query.ParentId = LibraryMenu.getTopParentId(); @@ -117,7 +117,8 @@ lazy: true, coverImage: true, showItemCounts: true, - cardLayout: true + cardLayout: true, + vibrant: true }); } else if (viewStyle == "Thumb") { @@ -139,7 +140,8 @@ lazy: true, preferThumb: true, cardLayout: true, - showItemCounts: true + showItemCounts: true, + vibrant: true }); } else { diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index a4c7c8206d..18e3b5c955 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1155,7 +1155,7 @@ var AppInfo = {}; var paths = { velocity: bowerPath + "/velocity/velocity.min", - vibrant: bowerPath + "/vibrant/dist/vibrant.min", + vibrant: bowerPath + "/vibrant/dist/vibrant", ironCardList: 'components/ironcardlist/ironcardlist', scrollThreshold: 'components/scrollthreshold', playlisteditor: 'components/playlisteditor/playlisteditor', @@ -1185,7 +1185,6 @@ var AppInfo = {}; globalize: embyWebComponentsBowerPath + "/globalize", itemHelper: embyWebComponentsBowerPath + '/itemhelper', itemShortcuts: embyWebComponentsBowerPath + "/shortcuts", - imageLoader: embyWebComponentsBowerPath + "/images/imagehelper", serverNotifications: embyWebComponentsBowerPath + '/servernotifications', webAnimations: bowerPath + '/web-animations-js/web-animations-next-lite.min' }; @@ -1213,6 +1212,7 @@ var AppInfo = {}; define("libjass", [bowerPath + "/libjass/libjass.min", "css!" + bowerPath + "/libjass/libjass"], returnFirstDependency); + define("imageLoader", [embyWebComponentsBowerPath + "/images/imagehelper"], returnFirstDependency); define("syncJobList", ["components/syncjoblist/syncjoblist"], returnFirstDependency); define("appfooter", ["components/appfooter/appfooter"], returnFirstDependency); define("dockedtabs", ["components/dockedtabs/dockedtabs"], returnFirstDependency); diff --git a/dashboard-ui/scripts/tvrecommended.js b/dashboard-ui/scripts/tvrecommended.js index 72021fd976..402bfd756e 100644 --- a/dashboard-ui/scripts/tvrecommended.js +++ b/dashboard-ui/scripts/tvrecommended.js @@ -98,6 +98,8 @@ var container = view.querySelector('#resumableItems'); + var cardLayout = appHost.preferVisualCards; + cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, @@ -106,10 +108,10 @@ showTitle: true, showParentTitle: true, overlayText: false, - centerText: false, + centerText: !cardLayout, overlayPlayButton: true, allowBottomPadding: allowBottomPadding, - cardLayout: true + cardLayout: cardLayout }); }); } diff --git a/dashboard-ui/scripts/tvstudios.js b/dashboard-ui/scripts/tvstudios.js index 6c4a193d23..e2cb90bc48 100644 --- a/dashboard-ui/scripts/tvstudios.js +++ b/dashboard-ui/scripts/tvstudios.js @@ -44,19 +44,15 @@ var elem = context.querySelector('#items'); - var supportsImageAnalysis = appHost.supports('imageanalysis'); - cardBuilder.buildCards(result.Items, { itemsContainer: elem, shape: "backdrop", preferThumb: true, - showTitle: supportsImageAnalysis, + showTitle: false, scalable: true, showItemCounts: true, - centerText: !supportsImageAnalysis, - overlayMoreButton: !supportsImageAnalysis, - cardLayout: supportsImageAnalysis, - vibrant: supportsImageAnalysis + centerText: true, + overlayMoreButton: true }); Dashboard.hideLoadingMsg();