diff --git a/dashboard-ui/bower_components/emby-webcomponents/.bower.json b/dashboard-ui/bower_components/emby-webcomponents/.bower.json index b9667f76de..e81163fce7 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/.bower.json +++ b/dashboard-ui/bower_components/emby-webcomponents/.bower.json @@ -16,12 +16,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.2.26", - "_release": "1.2.26", + "version": "1.2.31", + "_release": "1.2.31", "_resolution": { "type": "version", - "tag": "1.2.26", - "commit": "7e13c8d9c17a4946681b3485c5fbf3e62f39cd2f" + "tag": "1.2.31", + "commit": "87a2ef738364e9c40e0b97326f5861b6edfc5b3e" }, "_source": "https://github.com/MediaBrowser/emby-webcomponents.git", "_target": "^1.2.0", diff --git a/dashboard-ui/bower_components/emby-webcomponents/alert/alert.js b/dashboard-ui/bower_components/emby-webcomponents/alert/alert.js index dd0c56759b..b2e009531b 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/alert/alert.js +++ b/dashboard-ui/bower_components/emby-webcomponents/alert/alert.js @@ -62,7 +62,7 @@ define(['dialogHelper', 'layoutManager', 'dialogText', 'html!./../prompt/icons.h if (raisedButtons) { html += '' + dialogText.get(buttonText) + ''; } else { - html += '
'; + html += '
'; html += '' + dialogText.get(buttonText) + ''; html += '
'; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/datetime.js b/dashboard-ui/bower_components/emby-webcomponents/datetime.js index 022de2e9e8..0e6f9540d8 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/datetime.js +++ b/dashboard-ui/bower_components/emby-webcomponents/datetime.js @@ -1,4 +1,4 @@ -define([], function () { +define(['globalize'], function (globalize) { function parseISO8601Date(s, toLocal) { @@ -94,11 +94,28 @@ define([], function () { return parts.join(':'); } + var toLocaleTimeStringSupportsLocales = function toLocaleTimeStringSupportsLocales() { + try { + new Date().toLocaleTimeString('i'); + } catch (e) { + return e.name === 'RangeError'; + } + return false; + }(); + function getDisplayTime(date) { - var time = date.toLocaleTimeString().toLowerCase(); - if (time.indexOf('am') != -1 || time.indexOf('pm') != -1) { + var currentLocale = globalize.getCurrentLocale(); + var time = currentLocale && toLocaleTimeStringSupportsLocales ? + date.toLocaleTimeString(currentLocale) : + date.toLocaleTimeString(); + + var timeLower = time.toLowerCase(); + + if (timeLower.indexOf('am') != -1 || timeLower.indexOf('pm') != -1) { + + time = timeLower; var hour = date.getHours() % 12; var suffix = date.getHours() > 11 ? 'pm' : 'am'; if (!hour) { diff --git a/dashboard-ui/bower_components/emby-webcomponents/filedownloader.js b/dashboard-ui/bower_components/emby-webcomponents/filedownloader.js new file mode 100644 index 0000000000..d814ed616c --- /dev/null +++ b/dashboard-ui/bower_components/emby-webcomponents/filedownloader.js @@ -0,0 +1,11 @@ +define(['multi-download'], function (multiDownload) { + + return { + download: function (items) { + + multiDownload(items.map(function (item) { + return item.url; + })); + } + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/prompt/style.css b/dashboard-ui/bower_components/emby-webcomponents/prompt/style.css index 6ed558e1d6..ff22c039ee 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/prompt/style.css +++ b/dashboard-ui/bower_components/emby-webcomponents/prompt/style.css @@ -12,7 +12,7 @@ .promptDialogContent { text-align: left; - padding: 2em; + padding: 1em 2em; margin: 0 !important; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/slideshow/icons.html b/dashboard-ui/bower_components/emby-webcomponents/slideshow/icons.html index e3862c5f9d..edee1f7b44 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/slideshow/icons.html +++ b/dashboard-ui/bower_components/emby-webcomponents/slideshow/icons.html @@ -34,11 +34,13 @@ See [iron-iconset](#iron-iconset) and [iron-iconset-svg](#iron-iconset-svg) for - + - - + + + + diff --git a/dashboard-ui/bower_components/emby-webcomponents/slideshow/slideshow.js b/dashboard-ui/bower_components/emby-webcomponents/slideshow/slideshow.js index 37484262c9..f79631f837 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/slideshow/slideshow.js +++ b/dashboard-ui/bower_components/emby-webcomponents/slideshow/slideshow.js @@ -1,16 +1,117 @@ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'css!./style', 'html!./icons', 'iron-icon-set', 'paper-fab', 'paper-icon-button', 'paper-spinner'], function (dialogHelper, inputmanager, connectionManager, layoutManager) { +define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'apphost', 'css!./style', 'html!./icons', 'iron-icon-set', 'paper-icon-button', 'paper-spinner'], function (dialogHelper, inputmanager, connectionManager, layoutManager, focusManager, appHost) { + + function getImageUrl(item, options, apiClient) { + + options = options || {}; + options.type = options.type || "Primary"; + + if (typeof (item) === 'string') { + return apiClient.getScaledImageUrl(item, options); + } + + if (item.ImageTags && item.ImageTags[options.type]) { + + options.tag = item.ImageTags[options.type]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + if (options.type == 'Primary') { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + + options.tag = item.AlbumPrimaryImageTag; + return apiClient.getScaledImageUrl(item.AlbumId, options); + } + + //else if (item.AlbumId && item.SeriesPrimaryImageTag) { + + // imgUrl = ApiClient.getScaledImageUrl(item.SeriesId, { + // type: "Primary", + // width: downloadWidth, + // tag: item.SeriesPrimaryImageTag, + // minScale: minScale + // }); + + //} + //else if (item.ParentPrimaryImageTag) { + + // imgUrl = ApiClient.getImageUrl(item.ParentPrimaryImageItemId, { + // type: "Primary", + // width: downloadWidth, + // tag: item.ParentPrimaryImageTag, + // minScale: minScale + // }); + //} + } + + return null; + } + + function getBackdropImageUrl(item, options, apiClient) { + + options = options || {}; + options.type = options.type || "Backdrop"; + + options.width = null; + delete options.width; + options.maxWidth = null; + delete options.maxWidth; + options.maxHeight = null; + delete options.maxHeight; + options.height = null; + delete options.height; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; + } + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + return null; + } + + function getImgUrl(item, original) { + + 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) { + return apiClient.getUrl("Items/" + item.Id + "/Download", { + api_key: apiClient.accessToken() + }); + } + imageOptions.type = "Primary"; + return getImageUrl(item, imageOptions, apiClient); + } + } return function (options) { var self = this; var swiperInstance; var dlg; + var currentTimeout; + var currentIntervalMs; + var currentOptions; + var currentIndex; function createElements(options) { dlg = dialogHelper.createDialog({ exitAnimationDuration: options.interactive ? 400 : 800, - size: 'fullscreen' + size: 'fullscreen', + autoFocus: false }); dlg.classList.add('slideshowDialog'); @@ -22,13 +123,21 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c html += '
'; html += '
'; - html += ''; + html += ''; + html += ''; - html += '
'; - html += ''; + html += ''; + + html += '
'; + + //html += ''; html += ''; - html += ''; + if (appHost.supports('filedownload')) { + html += ''; + } + html += '
'; + html += '
'; } else { @@ -44,7 +153,21 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c }); dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage); dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage); - dlg.querySelector('.btnSlideshowPause').addEventListener('click', playPause); + + var btnPause = dlg.querySelector('.btnSlideshowPause'); + if (btnPause) { + btnPause.addEventListener('click', playPause); + } + + var btnDownload = dlg.querySelector('.btnDownload'); + if (btnDownload) { + btnDownload.addEventListener('click', download); + } + + var btnShare = dlg.querySelector('.btnShare'); + if (btnShare) { + btnShare.addEventListener('click', share); + } } document.body.appendChild(dlg); @@ -56,6 +179,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c }); inputmanager.on(window, onInputCommand); + document.addEventListener('mousemove', onMouseMove); dlg.addEventListener('close', onDialogClosed); @@ -101,7 +225,8 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c function getSwiperSlideHtmlFromItem(item) { return getSwiperSlideHtmlFromSlide({ - imageUrl: getImgUrl(item) + imageUrl: getImgUrl(item), + originalImage: getImgUrl(item, true) //title: item.Name, //description: item.Overview }); @@ -128,7 +253,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c function getSwiperSlideHtmlFromSlide(item) { var html = ''; - html += '
'; + html += '
'; html += ''; html += ''; if (item.title || item.subtitle) { @@ -179,6 +304,32 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c } } + function getCurrentImageUrl() { + + + if (swiperInstance) { + return document.querySelector('.swiper-slide-active').getAttribute('data-original'); + } else { + return null; + } + } + + function download() { + + var url = getCurrentImageUrl(); + + require(['fileDownloader'], function (fileDownloader) { + fileDownloader.download([ + { + url: url + }]); + }); + } + + function share() { + + } + function play() { dlg.querySelector('.btnSlideshowPause').icon = "slideshow:pause"; @@ -212,13 +363,9 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c } inputmanager.off(window, onInputCommand); + document.removeEventListener('mousemove', onMouseMove); } - var currentTimeout; - var currentIntervalMs; - var currentOptions; - var currentIndex; - function startInterval(options) { currentOptions = options; @@ -232,93 +379,137 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c } } - function getImgUrl(item) { + var _osdOpen = false; + function isOsdOpen() { + return _osdOpen; + } - var apiClient = connectionManager.getApiClient(item.ServerId); - if (item.BackdropImageTags && item.BackdropImageTags.length) { - return getBackdropImageUrl(item, { - maxWidth: screen.availWidth - }, apiClient); - } else { - return getImageUrl(item, { - type: "Primary", - maxWidth: screen.availWidth - }, apiClient); + function getOsdBottom() { + return dlg.querySelector('.slideshowBottomBar'); + } + + function showOsd() { + + slideUpToShow(getOsdBottom()); + startHideTimer(); + } + + function hideOsd() { + + slideDownToHide(getOsdBottom()); + } + + var hideTimeout; + function startHideTimer() { + stopHideTimer(); + hideTimeout = setTimeout(hideOsd, 4000); + } + function stopHideTimer() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; } } - function getBackdropImageUrl(item, options, apiClient) { + function slideUpToShow(elem) { - options = options || {}; - options.type = options.type || "Backdrop"; - - options.width = null; - delete options.width; - options.maxWidth = null; - delete options.maxWidth; - options.maxHeight = null; - delete options.maxHeight; - options.height = null; - delete options.height; - - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; + if (!elem.classList.contains('hide')) { + return; } - if (item.BackdropImageTags && item.BackdropImageTags.length) { + _osdOpen = true; + elem.classList.remove('hide'); - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } + requestAnimationFrame(function () { - return null; + var keyframes = [ + { transform: 'translate3d(0,' + elem.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 = function () { + focusManager.focus(elem.querySelector('.btnSlideshowPause')); + }; + }); } - function getImageUrl(item, options, apiClient) { + function slideDownToHide(elem) { - options = options || {}; - options.type = options.type || "Primary"; - - if (typeof (item) === 'string') { - return apiClient.getScaledImageUrl(item, options); + if (elem.classList.contains('hide')) { + return; } - if (item.ImageTags && item.ImageTags[options.type]) { + requestAnimationFrame(function () { - options.tag = item.ImageTags[options.type]; - return apiClient.getScaledImageUrl(item.Id, options); + var keyframes = [ + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, + { transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 1 }]; + var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + elem.animate(keyframes, timing).onfinish = function () { + elem.classList.add('hide'); + _osdOpen = false; + }; + }); + } + + var lastMouseMoveData; + function onMouseMove(e) { + + var eventX = e.screenX || 0; + var eventY = e.screenY || 0; + + var obj = lastMouseMoveData; + if (!obj) { + lastMouseMoveData = { + x: eventX, + y: eventY + }; + return; } - if (options.type == 'Primary') { - if (item.AlbumId && item.AlbumPrimaryImageTag) { - - options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); - } - - //else if (item.AlbumId && item.SeriesPrimaryImageTag) { - - // imgUrl = ApiClient.getScaledImageUrl(item.SeriesId, { - // type: "Primary", - // width: downloadWidth, - // tag: item.SeriesPrimaryImageTag, - // minScale: minScale - // }); - - //} - //else if (item.ParentPrimaryImageTag) { - - // imgUrl = ApiClient.getImageUrl(item.ParentPrimaryImageItemId, { - // type: "Primary", - // width: downloadWidth, - // tag: item.ParentPrimaryImageTag, - // minScale: minScale - // }); - //} + // if coord are same, it didn't move + if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { + return; } - return null; + obj.x = eventX; + obj.y = eventY; + + showOsd(); + } + + function onInputCommand(e) { + + switch (e.detail.command) { + + case 'left': + if (!isOsdOpen()) { + e.preventDefault(); + previousImage(); + } + break; + case 'right': + if (!isOsdOpen()) { + e.preventDefault(); + nextImage(); + } + break; + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + case 'play': + case 'playpause': + case 'pause': + case 'fastforward': + case 'rewind': + case 'next': + case 'previous': + showOsd(); + break; + default: + break; + } } function showNextImage(index, skipPreload) { @@ -397,33 +588,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c } } - function onInputCommand(e) { - - switch (e.detail.command) { - - case 'left': - previousImage(); - break; - case 'right': - nextImage(); - break; - case 'play': - play(); - break; - case 'pause': - pause(); - break; - case 'playpause': - playPause(); - break; - default: - return - break; - } - - e.preventDefault(); - } - self.show = function () { startInterval(options); }; diff --git a/dashboard-ui/bower_components/emby-webcomponents/slideshow/style.css b/dashboard-ui/bower_components/emby-webcomponents/slideshow/style.css index b22fc8c2c6..d447f163a8 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/slideshow/style.css +++ b/dashboard-ui/bower_components/emby-webcomponents/slideshow/style.css @@ -8,7 +8,6 @@ right: 0; left: 0; bottom: 0; - z-index: 1001; background-position: center center; background-size: contain; background-repeat: no-repeat; @@ -46,43 +45,84 @@ top: 50%; } -.btnSlideshowExit { +.slideshowDialog paper-icon-button { + width: 5.2vh; + height: 5.2vh; + color: #fff; + opacity: .7; + min-width: 40px; + min-height: 40px; +} + +.layout-tv .slideshowDialog paper-icon-button { + width: 7vh; + height: 7vh; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 120dpi) { + .slideshowDialog paper-icon-button { + width: 11vmin; + height: 11vmin; + } +} + +.btnSlideshowPrevious { + left: .5vh; + top: 45vh; z-index: 1002; position: absolute; - top: 1.5vh; - left: 1.5vh; - width: 6vh; - height: 6vh; - color: #eee; } -paper-fab.btnSlideshowExit { - background-color: #444; +.btnSlideshowNext { + right: .5vh; + top: 45vh; + z-index: 1002; + position: absolute; } -.slideshowControlBar { +.btnSlideshowExit { + right: .5vh; + top: .5vh; + z-index: 1002; + position: absolute; +} + +.slideshowBottomBar { position: fixed; left: 0; bottom: 0; right: 0; - z-index: 1002; - background: rgba(0,0,0,.5); - text-align: center; - color: #eee; + background-color: rgba(0, 0, 0, .7); + color: #fff; + padding: .5%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; } -.mouseIdle .btnSlideshowExit { +.slideshowTopBar { + position: fixed; + left: 0; + top: 0; + right: 0; + background-color: rgba(0, 0, 0, .7); + color: #fff; + padding: .5%; + display: flex; + flex-direction: row; + align-items: center; + text-align: right; + justify-content: flex-end; +} + +.mouseIdle .btnSlideshowPrevious, .mouseIdle .btnSlideshowNext, .mouseIdle .btnSlideshowExit { display: none; } -.mouseIdle .slideshowControlBar { - transform: translateY(100%); - transition: transform 600ms ease-out; -} - -.slideshowButton { - width: 8vh; - height: 8vh; +.slideshowExtraButtons { + margin-left: auto; + text-align: right; } .slideText { diff --git a/dashboard-ui/bower_components/iron-a11y-announcer/.bower.json b/dashboard-ui/bower_components/iron-a11y-announcer/.bower.json index d0996a0614..1baafa9707 100644 --- a/dashboard-ui/bower_components/iron-a11y-announcer/.bower.json +++ b/dashboard-ui/bower_components/iron-a11y-announcer/.bower.json @@ -30,14 +30,14 @@ "web-component-tester": "polymer/web-component-tester#^3.4.0" }, "ignore": [], - "homepage": "https://github.com/PolymerElements/iron-a11y-announcer", + "homepage": "https://github.com/polymerelements/iron-a11y-announcer", "_release": "1.0.4", "_resolution": { "type": "version", "tag": "v1.0.4", "commit": "5ce3eb8c4282bb53cd72e348858dc6be6b4c50b9" }, - "_source": "git://github.com/PolymerElements/iron-a11y-announcer.git", + "_source": "git://github.com/polymerelements/iron-a11y-announcer.git", "_target": "^1.0.0", - "_originalSource": "PolymerElements/iron-a11y-announcer" + "_originalSource": "polymerelements/iron-a11y-announcer" } \ No newline at end of file diff --git a/dashboard-ui/bower_components/iron-behaviors/.bower.json b/dashboard-ui/bower_components/iron-behaviors/.bower.json index 0b0736d6a6..85e8252b32 100644 --- a/dashboard-ui/bower_components/iron-behaviors/.bower.json +++ b/dashboard-ui/bower_components/iron-behaviors/.bower.json @@ -29,14 +29,14 @@ "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" }, "ignore": [], - "homepage": "https://github.com/polymerelements/iron-behaviors", + "homepage": "https://github.com/PolymerElements/iron-behaviors", "_release": "1.0.13", "_resolution": { "type": "version", "tag": "v1.0.13", "commit": "a7bc3428a6da2beed21987b3a8028206826a12bc" }, - "_source": "git://github.com/polymerelements/iron-behaviors.git", + "_source": "git://github.com/PolymerElements/iron-behaviors.git", "_target": "^1.0.0", - "_originalSource": "polymerelements/iron-behaviors" + "_originalSource": "PolymerElements/iron-behaviors" } \ No newline at end of file diff --git a/dashboard-ui/bower_components/iron-icon/.bower.json b/dashboard-ui/bower_components/iron-icon/.bower.json index 9784e3a3b7..f0167baf13 100644 --- a/dashboard-ui/bower_components/iron-icon/.bower.json +++ b/dashboard-ui/bower_components/iron-icon/.bower.json @@ -32,14 +32,14 @@ "web-component-tester": "^4.0.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" }, - "homepage": "https://github.com/PolymerElements/iron-icon", + "homepage": "https://github.com/polymerelements/iron-icon", "_release": "1.0.8", "_resolution": { "type": "version", "tag": "v1.0.8", "commit": "f36b38928849ef3853db727faa8c9ef104d611eb" }, - "_source": "git://github.com/PolymerElements/iron-icon.git", + "_source": "git://github.com/polymerelements/iron-icon.git", "_target": "^1.0.0", - "_originalSource": "PolymerElements/iron-icon" + "_originalSource": "polymerelements/iron-icon" } \ No newline at end of file diff --git a/dashboard-ui/bower_components/iron-selector/.bower.json b/dashboard-ui/bower_components/iron-selector/.bower.json index c88ed41aaf..43e942ccc6 100644 --- a/dashboard-ui/bower_components/iron-selector/.bower.json +++ b/dashboard-ui/bower_components/iron-selector/.bower.json @@ -36,7 +36,7 @@ "tag": "v1.4.0", "commit": "554f7418fdbd97688eb21518b5f8172167d53a95" }, - "_source": "git://github.com/polymerelements/iron-selector.git", + "_source": "git://github.com/PolymerElements/iron-selector.git", "_target": "^1.0.0", - "_originalSource": "polymerelements/iron-selector" + "_originalSource": "PolymerElements/iron-selector" } \ No newline at end of file diff --git a/dashboard-ui/bower_components/multi-download/.bower.json b/dashboard-ui/bower_components/multi-download/.bower.json new file mode 100644 index 0000000000..85b7ddcfae --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/.bower.json @@ -0,0 +1,15 @@ +{ + "name": "multi-download", + "homepage": "https://github.com/sindresorhus/multi-download", + "version": "2.0.0", + "_release": "2.0.0", + "_resolution": { + "type": "version", + "tag": "v2.0.0", + "commit": "181dc2d27de96f81a951b8cc8b50106d13219bd8" + }, + "_source": "https://github.com/sindresorhus/multi-download.git", + "_target": "^2.0.0", + "_originalSource": "multi-download", + "_direct": true +} \ No newline at end of file diff --git a/dashboard-ui/bower_components/multi-download/.editorconfig b/dashboard-ui/bower_components/multi-download/.editorconfig new file mode 100644 index 0000000000..8f9d77e2dc --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{package.json,*.yml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/dashboard-ui/bower_components/multi-download/.gitattributes b/dashboard-ui/bower_components/multi-download/.gitattributes new file mode 100644 index 0000000000..176a458f94 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/dashboard-ui/bower_components/multi-download/.gitignore b/dashboard-ui/bower_components/multi-download/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/dashboard-ui/bower_components/multi-download/.jshintrc b/dashboard-ui/bower_components/multi-download/.jshintrc new file mode 100644 index 0000000000..9fe1be2b6d --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/.jshintrc @@ -0,0 +1,12 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "curly": true, + "immed": true, + "newcap": true, + "noarg": true, + "undef": true, + "unused": "vars", + "strict": true +} diff --git a/dashboard-ui/bower_components/multi-download/browser.js b/dashboard-ui/bower_components/multi-download/browser.js new file mode 100644 index 0000000000..94d1171586 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/browser.js @@ -0,0 +1,73 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.multiDownload = f()}})(function(){var define,module,exports;return (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 + + + + + + multi-download + + + + + + + + + diff --git a/dashboard-ui/bower_components/multi-download/index.js b/dashboard-ui/bower_components/multi-download/index.js new file mode 100644 index 0000000000..fe6a245528 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/index.js @@ -0,0 +1,69 @@ +'use strict'; + +function fallback(urls) { + var i = 0; + + (function createIframe() { + var frame = document.createElement('iframe'); + frame.style.display = 'none'; + frame.src = urls[i++]; + document.documentElement.appendChild(frame); + + // the download init has to be sequential otherwise IE only use the first + var interval = setInterval(function () { + if (frame.contentWindow.document.readyState === 'complete') { + clearInterval(interval); + + // Safari needs a timeout + setTimeout(function () { + frame.parentNode.removeChild(frame); + }, 1000); + + if (i < urls.length) { + createIframe(); + } + } + }, 100); + })(); +} + +function isFirefox() { + // sad panda :( + return /Firefox\//i.test(navigator.userAgent); +} + +function sameDomain(url) { + var a = document.createElement('a'); + a.href = url; + + return location.hostname === a.hostname && location.protocol === a.protocol; +} + +function download(url) { + var a = document.createElement('a'); + a.download = ''; + a.href = url; + // firefox doesn't support `a.click()`... + a.dispatchEvent(new MouseEvent('click')); +} + +module.exports = function (urls) { + if (!urls) { + throw new Error('`urls` required'); + } + + if (typeof document.createElement('a').download === 'undefined') { + return fallback(urls); + } + + var delay = 0; + + urls.forEach(function (url) { + // the download init has to be sequential for firefox if the urls are not on the same domain + if (isFirefox() && !sameDomain(url)) { + return setTimeout(download.bind(null, url), 100 * ++delay); + } + + download(url); + }); +} diff --git a/dashboard-ui/bower_components/multi-download/license b/dashboard-ui/bower_components/multi-download/license new file mode 100644 index 0000000000..654d0bfe94 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/dashboard-ui/bower_components/multi-download/package.json b/dashboard-ui/bower_components/multi-download/package.json new file mode 100644 index 0000000000..eac49088bd --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/package.json @@ -0,0 +1,28 @@ +{ + "name": "multi-download", + "version": "2.0.0", + "description": "Download multiple files at once", + "license": "MIT", + "repository": "sindresorhus/multi-download", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "scripts": { + "build": "a=$npm_package_name; browserify -r ./index:$a -s $a index.js -o browser.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "browser", + "download", + "multiple", + "parallel", + "files" + ], + "devDependencies": { + "browserify": "^10.0.0" + } +} diff --git a/dashboard-ui/bower_components/multi-download/readme.md b/dashboard-ui/bower_components/multi-download/readme.md new file mode 100644 index 0000000000..474fe3b1c1 --- /dev/null +++ b/dashboard-ui/bower_components/multi-download/readme.md @@ -0,0 +1,62 @@ +# multi-download + +> Download multiple files at once + +![](screenshot.gif) + +It works by abusing the `a`-tag [`download` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download) and falling back to iframes on older browsers. + + +## [Demo](http://sindresorhus.com/multi-download) + + +## Install + +``` +$ npm install --save multi-download +``` + + +## Usage + +```html + +``` + +```js +document.querySelector('#download-btn').addEventListener('click', function (e) { + var files = e.target.dataset.files.split(' '); + multiDownload(files); +}); +``` + +```js +// with jQuery +$('#download-btn').on('click', function () { + var files = $(this).data('files').split(' '); + multiDownload(files); +}); +``` + + +## API + +### multiDownload(urls) + +#### urls + +Type: `array` + +URLs to files you want to download. + + +## Caveats + +Chrome will ask the user before downloading multiple files (once per domain). + +For the fallback to work you need to make sure the server sends the correct header for the browser to download the file rather than displaying it. This is usually achieved with the header `Content-Disposition: attachment; filename="" `. + + +## License + +MIT © [Sindre Sorhus](http://sindresorhus.com) diff --git a/dashboard-ui/bower_components/multi-download/screenshot.gif b/dashboard-ui/bower_components/multi-download/screenshot.gif new file mode 100644 index 0000000000..46576c0b6c Binary files /dev/null and b/dashboard-ui/bower_components/multi-download/screenshot.gif differ diff --git a/dashboard-ui/components/apphost.js b/dashboard-ui/components/apphost.js new file mode 100644 index 0000000000..7118ba4fee --- /dev/null +++ b/dashboard-ui/components/apphost.js @@ -0,0 +1,103 @@ +define(['appStorage', 'browser'], function (appStorage, browser) { + + function getDeviceProfile() { + + // TODO + return null; + } + + function getCapabilities() { + + var caps = { + PlayableMediaTypes: ['Audio', 'Video'], + + SupportsPersistentIdentifier: false, + DeviceProfile: getDeviceProfile() + }; + + return caps; + } + + function generateDeviceId() { + return new Promise(function (resolve, reject) { + + require(['fingerprintjs2'], function (Fingerprint2) { + + new Fingerprint2().get(function (result, components) { + console.log('Generated device id: ' + result); //a hash, representing your device fingerprint + resolve(result); + }); + }); + }); + } + + return { + getWindowState: function () { + return document.windowState || 'Normal'; + }, + setWindowState: function (state) { + alert('setWindowState is not supported and should not be called'); + }, + exit: function () { + alert('exit is not supported and should not be called'); + }, + supports: function (command) { + + var features = [ + 'filedownload' + ]; + + return features.indexOf(command.toLowerCase()) != -1; + }, + appName: function () { + return 'Emby Mobile'; + }, + appVersion: function () { + return '2.0.1'; + }, + deviceName: function () { + var deviceName; + + if (browser.chrome) { + deviceName = "Chrome"; + } else if (browser.edge) { + deviceName = "Edge"; + } else if (browser.firefox) { + deviceName = "Firefox"; + } else if (browser.msie) { + deviceName = "Internet Explorer"; + } else { + deviceName = "Web Browser"; + } + + if (browser.version) { + deviceName += " " + browser.version; + } + + if (browser.ipad) { + deviceName += " Ipad"; + } else if (browser.iphone) { + deviceName += " Iphone"; + } else if (browser.android) { + deviceName += " Android"; + } + + return deviceName; + }, + deviceId: function () { + + var key = '_deviceId2'; + var deviceId = appStorage.getItem(key); + + if (deviceId) { + return Promise.resolve(deviceId); + } else { + return generateDeviceId().then(function (deviceId) { + appStorage.setItem(key, deviceId); + return deviceId; + }); + } + }, + capabilities: getCapabilities + }; +}); \ No newline at end of file diff --git a/dashboard-ui/components/filedownloader.js b/dashboard-ui/components/filedownloader.js deleted file mode 100644 index 9460a31e61..0000000000 --- a/dashboard-ui/components/filedownloader.js +++ /dev/null @@ -1,9 +0,0 @@ -define([], function () { - - return function (items) { - - items.forEach(function (item) { - window.location.href = item.url; - }); - }; -}); \ No newline at end of file diff --git a/dashboard-ui/scripts/librarybrowser.js b/dashboard-ui/scripts/librarybrowser.js index 9c3d019891..741eca49a4 100644 --- a/dashboard-ui/scripts/librarybrowser.js +++ b/dashboard-ui/scripts/librarybrowser.js @@ -769,7 +769,7 @@ } if (item.CanDownload) { - if (AppInfo.supportsDownloading) { + if (appHost.supports('filedownload')) { commands.push('download'); } } @@ -987,7 +987,8 @@ api_key: ApiClient.accessToken() }); - fileDownloader([{ + fileDownloader.download([ + { url: downloadHref, itemId: itemId }]); diff --git a/dashboard-ui/scripts/librarylist.js b/dashboard-ui/scripts/librarylist.js index 4f64726f15..f73ec1142c 100644 --- a/dashboard-ui/scripts/librarylist.js +++ b/dashboard-ui/scripts/librarylist.js @@ -1,4 +1,4 @@ -define(['appSettings', 'appStorage', 'libraryBrowser', 'jQuery'], function (appSettings, appStorage, LibraryBrowser, $) { +define(['appSettings', 'appStorage', 'libraryBrowser', 'apphost', 'jQuery'], function (appSettings, appStorage, LibraryBrowser, appHost, $) { var showOverlayTimeout; @@ -270,7 +270,7 @@ }); } - if (user.Policy.EnableContentDownloading && AppInfo.supportsDownloading) { + if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { if (mediaType) { items.push({ name: Globalize.translate('ButtonDownload'), @@ -491,7 +491,8 @@ api_key: ApiClient.accessToken() }); - fileDownloader([{ + fileDownloader.download([ + { url: downloadHref, itemId: itemId }]); @@ -1143,7 +1144,7 @@ }); } - if (user.Policy.EnableContentDownloading && AppInfo.supportsDownloading) { + if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { //items.push({ // name: Globalize.translate('ButtonDownload'), // id: 'download', @@ -1214,7 +1215,7 @@ combineVersions($.mobile.activePage, items); break; case 'markplayed': - items.forEach(function(itemId) { + items.forEach(function (itemId) { ApiClient.markPlayed(Dashboard.getCurrentUserId(), itemId); }); hideSelections(); diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index c1d91036d4..907927f207 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1383,8 +1383,6 @@ var AppInfo = {}; // This doesn't perform well on iOS AppInfo.enableHeadRoom = !isIOS; - AppInfo.supportsDownloading = !(AppInfo.isNativeApp && isIOS); - // This currently isn't working on android, unfortunately AppInfo.supportsFileInput = !(AppInfo.isNativeApp && isAndroid); @@ -1696,8 +1694,10 @@ var AppInfo = {}; // hack for an android test before browserInfo is loaded if (Dashboard.isRunningInCordova() && window.MainActivity) { paths.appStorage = "cordova/android/appstorage"; + paths.apphost = "cordova/apphost"; } else { paths.appStorage = apiClientBowerPath + "/appstorage"; + paths.apphost = "components/apphost"; } paths.playlistManager = "scripts/playlistmanager"; @@ -1974,10 +1974,12 @@ var AppInfo = {}; define("loading", [embyWebComponentsBowerPath + "/loading/loading-lite"], returnFirstDependency); } + define("multi-download", [bowerPath + '/multi-download/browser'], returnFirstDependency); + if (Dashboard.isRunningInCordova() && browser.android) { define("fileDownloader", ['cordova/android/filedownloader'], returnFirstDependency); } else { - define("fileDownloader", ['components/filedownloader'], returnFirstDependency); + define("fileDownloader", [embyWebComponentsBowerPath + '/filedownloader'], returnFirstDependency); } }