remove all sync and convert buttons

This commit is contained in:
dkanada 2019-03-18 22:22:48 -07:00
parent 1ae37fea1b
commit 886dec1174
18 changed files with 62 additions and 1150 deletions

View file

@ -1,31 +0,0 @@
define(["itemHelper", "libraryMenu", "apphost"], function(itemHelper, libraryMenu, appHost) {
"use strict";
function initSyncButtons(view) {
var apiClient = window.ApiClient;
apiClient && apiClient.getCurrentUserId() && apiClient.getCurrentUser().then(function(user) {
for (var item = {
SupportsSync: !0
}, categorySyncButtons = view.querySelectorAll(".categorySyncButton"), i = 0, length = categorySyncButtons.length; i < length; i++) categorySyncButtons[i].addEventListener("click", onCategorySyncButtonClick), itemHelper.canSync(user, item) ? categorySyncButtons[i].classList.remove("hide") : categorySyncButtons[i].classList.add("hide")
})
}
function onCategorySyncButtonClick(e) {
var button = this,
category = button.getAttribute("data-category"),
parentId = libraryMenu.getTopParentId();
require(["syncDialog"], function(syncDialog) {
syncDialog.showMenu({
ParentId: parentId,
Category: category,
serverId: ApiClient.serverId(),
mode: appHost.supports("sync") ? "download" : "sync"
})
})
}
return {
init: function(view) {
initSyncButtons(view)
}
}
});

View file

@ -118,13 +118,6 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
});
}
if (itemHelper.canConvert(item, user, connectionManager.getApiClient(item))) {
commands.push({
name: globalize.translate('Convert'),
id: 'convert'
});
}
if (item.CanDelete && options.deleteItem !== false) {
if (item.Type === 'Playlist' || item.Type === 'BoxSet') {
@ -147,15 +140,6 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
});
}
if (appHost.supports('sync') && options.syncLocal !== false) {
if (itemHelper.canSync(user, item)) {
commands.push({
name: globalize.translate('Download'),
id: 'synclocal'
});
}
}
var canEdit = itemHelper.canEdit(user, item);
if (canEdit) {
@ -328,17 +312,13 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
{
require(['fileDownloader'], function (fileDownloader) {
var downloadHref = apiClient.getItemDownloadUrl(itemId);
fileDownloader.download([
{
url: downloadHref,
itemId: itemId,
serverId: serverId
}]);
fileDownloader.download([{
url: downloadHref,
itemId: itemId,
serverId: serverId
}]);
getResolveFunction(getResolveFunction(resolve, id), id)();
});
break;
}
case 'editsubtitles':
@ -433,102 +413,48 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
break;
}
case 'share':
{
navigator.share({
title: item.Name,
text: item.Overview,
url: "https://github.com/jellyfin/jellyfin"
});
break;
}
case 'album':
{
appRouter.showItem(item.AlbumId, item.ServerId);
getResolveFunction(resolve, id)();
break;
}
appRouter.showItem(item.AlbumId, item.ServerId);
getResolveFunction(resolve, id)();
break;
case 'artist':
{
appRouter.showItem(item.ArtistItems[0].Id, item.ServerId);
getResolveFunction(resolve, id)();
appRouter.showItem(item.ArtistItems[0].Id, item.ServerId);
getResolveFunction(resolve, id)();
break;
}
case 'playallfromhere':
{
getResolveFunction(resolve, id)();
getResolveFunction(resolve, id)();
break;
}
case 'queueallfromhere':
{
getResolveFunction(resolve, id)();
break;
}
case 'convert':
{
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: [item],
serverId: serverId,
mode: 'convert'
});
});
getResolveFunction(resolve, id)();
break;
}
case 'sync':
{
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: [item],
serverId: serverId,
mode: 'sync'
});
});
getResolveFunction(resolve, id)();
break;
}
case 'synclocal':
{
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: [item],
serverId: serverId,
mode: 'download'
});
});
getResolveFunction(resolve, id)();
break;
}
case 'removefromplaylist':
apiClient.ajax({
url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', {
EntryIds: [item.PlaylistItemId].join(',')
}),
type: 'DELETE'
}).then(function () {
getResolveFunction(resolve, id, true)();
});
break;
case 'removefromcollection':
apiClient.ajax({
type: "DELETE",
url: apiClient.getUrl("Collections/" + options.collectionId + "/Items", {
Ids: [item.Id].join(',')
})
}).then(function () {
getResolveFunction(resolve, id, true)();
});
break;
case 'canceltimer':
deleteTimer(apiClient, item, resolve, id);

View file

@ -218,13 +218,6 @@ define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'glo
});
}
if (user.Policy.EnableContentDownloading && appHost.supports('sync')) {
menuItems.push({
name: globalize.translate('Download'),
id: 'synclocal'
});
}
menuItems.push({
name: globalize.translate('GroupVersions'),
id: 'groupvideos',
@ -254,20 +247,16 @@ define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'glo
}
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
items: menuItems,
positionTo: e.target,
callback: function (id) {
var items = selectedItems.slice(0);
var serverId = apiClient.serverInfo().Id;
switch (id) {
case 'addtocollection':
require(['collectionEditor'], function (collectionEditor) {
new collectionEditor().show({
items: items,
serverId: serverId
@ -318,35 +307,6 @@ define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'glo
hideSelections();
dispatchNeedsRefresh();
break;
case 'sync':
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: items.map(function (i) {
return {
Id: i
};
}),
serverId: serverId
});
});
hideSelections();
dispatchNeedsRefresh();
break;
case 'synclocal':
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: items.map(function (i) {
return {
Id: i
};
}),
isLocalSync: true,
serverId: serverId
});
});
hideSelections();
dispatchNeedsRefresh();
break;
default:
break;
}

View file

@ -4,20 +4,14 @@
var connectionManager;
function getApiClient(serverId) {
if (connectionManager) {
return Promise.resolve(connectionManager.getApiClient(serverId));
}
//importScripts('serviceworker-cache-polyfill.js');
return Promise.reject();
}
function executeAction(action, data, serverId) {
return getApiClient(serverId).then(function (apiClient) {
switch (action) {
case 'cancel-install':
var id = data.id;
@ -32,7 +26,6 @@
}
self.addEventListener('notificationclick', function (event) {
var notification = event.notification;
notification.close();
@ -47,6 +40,5 @@
}
event.waitUntil(executeAction(action, data, serverId));
}, false);
})();

View file

@ -1,6 +0,0 @@
self.addEventListener('sync', function (event) {
'use strict';
if (event.tag === 'emby-sync') {
}
});

View file

@ -1,188 +0,0 @@
define(['connectionManager', 'serverNotifications', 'events', 'globalize', 'emby-button'], function (connectionManager, serverNotifications, events, globalize, EmbyButtonPrototype) {
'use strict';
function onClick(e) {
var button = this;
var id = button.getAttribute('data-id');
var serverId = button.getAttribute('data-serverid');
var apiClient = connectionManager.getApiClient(serverId);
if (!button.classList.contains('downloadbutton-on')) {
require(['syncDialog'], function (syncDialog) {
syncDialog.showMenu({
items: [id],
mode: 'download',
serverId: serverId
}).then(function () {
button.dispatchEvent(new CustomEvent('download', {
cancelable: false
}));
});
});
} else {
require(['confirm'], function (confirm) {
confirm({
text: globalize.translate('ConfirmRemoveDownload'),
confirmText: globalize.translate('RemoveDownload'),
cancelText: globalize.translate('KeepDownload'),
primary: 'cancel'
}).then(function () {
apiClient.cancelSyncItems([id]);
button.dispatchEvent(new CustomEvent('download-cancel', {
cancelable: false
}));
});
});
}
}
function updateSyncStatus(button, syncPercent) {
var icon = button.iconElement;
if (!icon) {
button.iconElement = button.querySelector('i');
icon = button.iconElement;
}
if (syncPercent != null) {
button.classList.add('downloadbutton-on');
if (icon) {
icon.classList.add('downloadbutton-icon-on');
}
} else {
button.classList.remove('downloadbutton-on');
if (icon) {
icon.classList.remove('downloadbutton-icon-on');
}
}
if ((syncPercent || 0) >= 100) {
button.classList.add('downloadbutton-complete');
if (icon) {
icon.classList.add('downloadbutton-icon-complete');
}
} else {
button.classList.remove('downloadbutton-complete');
if (icon) {
icon.classList.remove('downloadbutton-icon-complete');
}
}
var text;
if ((syncPercent || 0) >= 100) {
text = globalize.translate('Downloaded');
} else if (syncPercent != null) {
text = globalize.translate('Downloading');
} else {
text = globalize.translate('Download');
}
var textElement = button.querySelector('.emby-downloadbutton-downloadtext');
if (textElement) {
textElement.innerHTML = text;
}
button.title = text;
}
function clearEvents(button) {
button.removeEventListener('click', onClick);
}
function bindEvents(button) {
clearEvents(button);
button.addEventListener('click', onClick);
}
var EmbyDownloadButtonPrototype = Object.create(EmbyButtonPrototype);
EmbyDownloadButtonPrototype.createdCallback = function () {
// base method
if (EmbyButtonPrototype.createdCallback) {
EmbyButtonPrototype.createdCallback.call(this);
}
};
EmbyDownloadButtonPrototype.attachedCallback = function () {
// base method
if (EmbyButtonPrototype.attachedCallback) {
EmbyButtonPrototype.attachedCallback.call(this);
}
var itemId = this.getAttribute('data-id');
var serverId = this.getAttribute('data-serverid');
if (itemId && serverId) {
bindEvents(this);
}
};
EmbyDownloadButtonPrototype.detachedCallback = function () {
// base method
if (EmbyButtonPrototype.detachedCallback) {
EmbyButtonPrototype.detachedCallback.call(this);
}
clearEvents(this);
this.iconElement = null;
};
function fetchAndUpdate(button, item) {
connectionManager.getApiClient(item.ServerId).getSyncStatus(item.Id).then(function (result) {
updateSyncStatus(button, result.Progress);
}, function () {
});
}
EmbyDownloadButtonPrototype.setItem = function (item) {
if (item) {
this.setAttribute('data-id', item.Id);
this.setAttribute('data-serverid', item.ServerId);
fetchAndUpdate(this, item);
bindEvents(this);
} else {
this.removeAttribute('data-id');
this.removeAttribute('data-serverid');
clearEvents(this);
}
};
document.registerElement('emby-downloadbutton', {
prototype: EmbyDownloadButtonPrototype,
extends: 'button'
});
});

View file

@ -1,736 +0,0 @@
define(['apphost', 'globalize', 'connectionManager', 'layoutManager', 'focusManager', 'scrollHelper', 'appSettings', 'registrationServices', 'dialogHelper', 'paper-icon-button-light', 'formDialogStyle'], function (appHost, globalize, connectionManager, layoutManager, focusManager, scrollHelper, appSettings, registrationServices, dialogHelper) {
'use strict';
var currentDialogOptions;
function submitJob(dlg, apiClient, userId, syncOptions, form) {
if (!userId) {
throw new Error('userId cannot be null');
}
if (!syncOptions) {
throw new Error('syncOptions cannot be null');
}
if (!form) {
throw new Error('form cannot be null');
}
var selectSyncTarget = form.querySelector('#selectSyncTarget');
var target = selectSyncTarget ? selectSyncTarget.value : null;
if (!target) {
require(['toast'], function (toast) {
toast(globalize.translate('PleaseSelectDeviceToSyncTo'));
});
return false;
}
var options = {
userId: userId,
TargetId: target,
ParentId: syncOptions.ParentId,
Category: syncOptions.Category
};
setJobValues(options, form);
if (syncOptions.items && syncOptions.items.length) {
options.ItemIds = (syncOptions.items || []).map(function (i) {
return i.Id || i;
}).join(',');
}
apiClient.ajax({
type: "POST",
url: apiClient.getUrl("Sync/Jobs"),
data: JSON.stringify(options),
contentType: "application/json",
dataType: 'json'
}).then(function () {
dialogHelper.close(dlg);
require(['toast'], function (toast) {
showSubmissionToast(target, apiClient);
if (syncOptions.mode === 'download') {
syncNow();
}
});
});
return true;
}
function showSubmissionToast(targetId, apiClient) {
require(['toast'], function (toast) {
var msg = targetId === apiClient.deviceId() ?
globalize.translate('DownloadingDots') :
globalize.translate('SyncingDots');
toast(msg);
});
}
function syncNow() {
require(['localsync'], function (localSync) {
localSync.sync();
});
}
function submitQuickSyncJob(apiClient, userId, targetId, syncOptions) {
if (!userId) {
throw new Error('userId cannot be null');
}
if (!syncOptions) {
throw new Error('syncOptions cannot be null');
}
if (!targetId) {
throw new Error('targetId cannot be null');
}
var options = {
userId: userId,
TargetId: targetId,
ParentId: syncOptions.ParentId,
Category: syncOptions.Category,
Quality: syncOptions.Quality,
Bitrate: syncOptions.Bitrate
};
if (syncOptions.items && syncOptions.items.length) {
options.ItemIds = (syncOptions.items || []).map(function (i) {
return i.Id || i;
}).join(',');
}
return apiClient.ajax({
type: "POST",
url: apiClient.getUrl("Sync/Jobs"),
data: JSON.stringify(options),
contentType: "application/json",
dataType: 'json'
}).then(function () {
require(['toast'], function (toast) {
showSubmissionToast(targetId, apiClient);
if (syncOptions.mode === 'download') {
syncNow();
}
});
});
}
function setJobValues(job, form) {
var txtBitrate = form.querySelector('#txtBitrate');
var bitrate = txtBitrate ? txtBitrate.value : null;
if (bitrate) {
bitrate = parseFloat(bitrate) * 1000000;
}
job.Bitrate = bitrate;
var selectQuality = form.querySelector('#selectQuality');
if (selectQuality) {
job.Quality = selectQuality.value;
appSettings.set('sync-lastquality', job.Quality || '');
}
var selectProfile = form.querySelector('#selectProfile');
if (selectProfile) {
job.Profile = selectProfile.value;
}
var txtItemLimit = form.querySelector('#txtItemLimit');
if (txtItemLimit) {
job.ItemLimit = txtItemLimit.value || null;
}
var chkSyncNewContent = form.querySelector('#chkSyncNewContent');
if (chkSyncNewContent) {
job.SyncNewContent = chkSyncNewContent.checked;
}
var chkUnwatchedOnly = form.querySelector('#chkUnwatchedOnly');
if (chkUnwatchedOnly) {
job.UnwatchedOnly = chkUnwatchedOnly.checked;
}
}
function renderForm(options) {
return new Promise(function (resolve, reject) {
require(['emby-checkbox', 'emby-input', 'emby-select'], function () {
renderFormInternal(options, connectionManager.deviceId(), resolve);
});
});
}
function renderFormInternal(options, defaultTargetId, resolve) {
var elem = options.elem;
var dialogOptions = options.dialogOptions;
var targets = dialogOptions.Targets;
var html = '';
var mode = options.mode;
var targetContainerClass = mode === 'download' ? ' hide' : '';
var syncTargetLabel = mode === 'convert' ? globalize.translate('LabelConvertTo') : globalize.translate('LabelSyncTo');
if (options.readOnlySyncTarget) {
html += '<div class="inputContainer' + targetContainerClass + '">';
html += '<input is="emby-input" type="text" id="selectSyncTarget" readonly label="' + syncTargetLabel + '"/>';
html += '</div>';
} else {
html += '<div class="selectContainer' + targetContainerClass + '">';
html += '<select is="emby-select" id="selectSyncTarget" required="required" label="' + syncTargetLabel + '">';
html += targets.map(function (t) {
var isSelected = defaultTargetId === t.Id;
var selectedHtml = isSelected ? ' selected="selected"' : '';
return '<option' + selectedHtml + ' value="' + t.Id + '">' + t.Name + '</option>';
}).join('');
html += '</select>';
if (!targets.length) {
html += '<div class="fieldDescription">' + globalize.translate('LabelSyncNoTargetsHelp') + '</div>';
}
if (appHost.supports('externallinks')) {
html += '<div class="fieldDescription"><a is="emby-linkbutton" class="button-link lnkLearnMore" href="https://github.com/MediaBrowser/Wiki/wiki/Sync" target="_blank">' + globalize.translate('LearnMore') + '</a></div>';
}
html += '</div>';
}
html += '<div class="fldProfile selectContainer hide">';
html += '<select is="emby-select" id="selectProfile" label="' + globalize.translate('LabelProfile') + '">';
html += '</select>';
html += '<div class="fieldDescription profileDescription"></div>';
html += '</div>';
html += '<div class="fldQuality selectContainer hide">';
html += '<select is="emby-select" id="selectQuality" required="required" label="' + globalize.translate('LabelQuality') + '">';
html += '</select>';
html += '<div class="fieldDescription qualityDescription"></div>';
html += '</div>';
html += '<div class="fldBitrate inputContainer hide">';
html += '<input is="emby-input" type="number" step=".1" min=".1" id="txtBitrate" label="' + globalize.translate('LabelBitrateMbps') + '"/>';
html += '</div>';
if (dialogOptions.Options.indexOf('UnwatchedOnly') !== -1) {
html += '<div class="checkboxContainer checkboxContainer-withDescription">';
html += '<label>';
html += '<input is="emby-checkbox" type="checkbox" id="chkUnwatchedOnly"/>';
if (mode === 'convert') {
html += '<span>' + globalize.translate('ConvertUnwatchedVideosOnly') + '</span>';
} else {
html += '<span>' + globalize.translate('SyncUnwatchedVideosOnly') + '</span>';
}
html += '</label>';
if (mode === 'convert') {
html += '<div class="fieldDescription checkboxFieldDescription">' + globalize.translate('ConvertUnwatchedVideosOnlyHelp') + '</div>';
} else {
html += '<div class="fieldDescription checkboxFieldDescription">' + globalize.translate('SyncUnwatchedVideosOnlyHelp') + '</div>';
}
html += '</div>';
}
if (dialogOptions.Options.indexOf('SyncNewContent') !== -1) {
html += '<div class="checkboxContainer checkboxContainer-withDescription">';
html += '<label>';
html += '<input is="emby-checkbox" type="checkbox" id="chkSyncNewContent"/>';
if (mode === 'convert') {
html += '<span>' + globalize.translate('AutomaticallyConvertNewContent') + '</span>';
} else {
html += '<span>' + globalize.translate('AutomaticallySyncNewContent') + '</span>';
}
html += '</label>';
if (mode === 'convert') {
html += '<div class="fieldDescription checkboxFieldDescription">' + globalize.translate('AutomaticallyConvertNewContentHelp') + '</div>';
} else {
html += '<div class="fieldDescription checkboxFieldDescription">' + globalize.translate('AutomaticallySyncNewContentHelp') + '</div>';
}
html += '</div>';
}
if (dialogOptions.Options.indexOf('ItemLimit') !== -1) {
html += '<div class="inputContainer">';
html += '<input is="emby-input" type="number" step="1" min="1" id="txtItemLimit" label="' + globalize.translate('LabelItemLimit') + '"/>';
if (mode === 'convert') {
html += '<div class="fieldDescription">' + globalize.translate('ConvertItemLimitHelp') + '</div>';
} else {
html += '<div class="fieldDescription">' + globalize.translate('DownloadItemLimitHelp') + '</div>';
}
html += '</div>';
}
//html += '</div>';
//html += '</div>';
elem.innerHTML = html;
var selectSyncTarget = elem.querySelector('#selectSyncTarget');
if (selectSyncTarget) {
selectSyncTarget.addEventListener('change', function () {
loadQualityOptions(elem, this.value, options.dialogOptionsFn).then(resolve);
});
selectSyncTarget.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
var selectProfile = elem.querySelector('#selectProfile');
if (selectProfile) {
selectProfile.addEventListener('change', function () {
onProfileChange(elem, this.value);
});
if (dialogOptions.ProfileOptions.length) {
selectProfile.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
}
var selectQuality = elem.querySelector('#selectQuality');
if (selectQuality) {
selectQuality.addEventListener('change', function () {
onQualityChange(elem, this.value);
});
selectQuality.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
// This isn't ideal, but allow time for the change handlers above to run
setTimeout(function () {
focusManager.autoFocus(elem);
}, 100);
}
function showWifiMessage() {
require(['dialog', 'appRouter'], function (dialog, appRouter) {
var options = {
title: globalize.translate('HeaderWaitingForWifi'),
text: globalize.translate('WifiRequiredToDownload')
};
var items = [];
items.push({
name: options.confirmText || globalize.translate('ButtonOk'),
id: 'ok',
type: 'submit'
});
items.push({
name: options.cancelText || globalize.translate('HeaderDownloadSettings'),
id: 'downloadsettings',
type: 'cancel'
});
options.buttons = items;
dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
if (result === 'downloadsettings') {
appRouter.show(appRouter.getRouteUrl('downloadsettings'));
return Promise.resolve();
}
return Promise.reject();
});
});
}
function validateNetwork() {
var network = navigator.connection ? navigator.connection.type : null;
switch (network) {
case 'cellular':
case 'bluetooth':
showWifiMessage();
return false;
default:
return true;
}
}
function showSyncMenu(options) {
if (options.mode === 'download' && appSettings.syncOnlyOnWifi() && !validateNetwork()) {
return Promise.reject();
}
return registrationServices.validateFeature('sync').then(function () {
return showSyncMenuInternal(options);
});
}
function enableAutoSync(options) {
if (options.mode !== 'download') {
return false;
}
var firstItem = (options.items || [])[0] || {};
if (firstItem.Type === 'Audio') {
return true;
}
if (firstItem.Type === 'MusicAlbum') {
return true;
}
if (firstItem.Type === 'MusicArtist') {
return true;
}
if (firstItem.Type === 'MusicGenre') {
return true;
}
if (firstItem.Type === 'Playlist' && firstItem.MediaType === 'Audio') {
return true;
}
return false;
}
function showSyncMenuInternal(options) {
var apiClient = connectionManager.getApiClient(options.serverId);
var userId = apiClient.getCurrentUserId();
if (enableAutoSync(options)) {
return submitQuickSyncJob(apiClient, userId, apiClient.deviceId(), {
items: options.items,
Quality: 'custom',
Bitrate: appSettings.maxStaticMusicBitrate()
});
}
var dialogOptionsFn = getTargetDialogOptionsFn(apiClient, {
UserId: userId,
ItemIds: (options.items || []).map(function (i) {
return i.Id || i;
}).join(','),
ParentId: options.ParentId,
Category: options.Category,
IncludeProviders: options.mode === 'convert' ? 'ConvertSyncProvider' : null,
ExcludeProviders: options.mode === 'convert' ? null : 'ConvertSyncProvider'
});
return dialogOptionsFn().then(function (dialogOptions) {
currentDialogOptions = dialogOptions;
var dlgElementOptions = {
removeOnClose: true,
scrollY: false,
autoFocus: false
};
if (layoutManager.tv) {
dlgElementOptions.size = 'fullscreen';
} else {
dlgElementOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dlgElementOptions);
dlg.classList.add('formDialog');
var html = '';
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>';
html += '<h3 class="formDialogHeaderTitle">';
var syncButtonLabel = options.mode === 'download' ?
globalize.translate('Download') :
(options.mode === 'convert' ? globalize.translate('Convert') : globalize.translate('Sync'));
html += syncButtonLabel;
html += '</h3>';
if (appHost.supports('externallinks')) {
html += '<a is="emby-linkbutton" href="https://github.com/MediaBrowser/Wiki/wiki/Sync" target="_blank" class="button-link lnkHelp" style="margin-top:0;display:inline-block;vertical-align:middle;margin-left:auto;"><i class="md-icon">info</i><span>' + globalize.translate('Help') + '</span></a>';
}
html += '</div>';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form class="formSubmitSyncRequest" style="margin: auto;">';
html += '<div class="formFields"></div>';
html += '<div class="formDialogFooter">';
html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem"><span>' + syncButtonLabel + '</span></button>';
html += '</div>';
html += '</form>';
html += '</div>';
html += '</div>';
dlg.innerHTML = html;
var submitted = false;
dlg.querySelector('form').addEventListener('submit', function (e) {
submitted = submitJob(dlg, apiClient, userId, options, this);
e.preventDefault();
return false;
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
}
var promise = dialogHelper.open(dlg);
renderForm({
elem: dlg.querySelector('.formFields'),
dialogOptions: dialogOptions,
dialogOptionsFn: dialogOptionsFn,
mode: options.mode
});
return promise.then(function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
if (submitted) {
return Promise.resolve();
}
return Promise.reject();
});
});
}
function getTargetDialogOptionsFn(apiClient, query) {
return function (targetId) {
query.TargetId = targetId;
return apiClient.getJSON(apiClient.getUrl('Sync/Options', query));
};
}
function setQualityFieldVisible(form, visible) {
var fldQuality = form.querySelector('.fldQuality');
var selectQuality = form.querySelector('#selectQuality');
if (visible) {
if (fldQuality) {
fldQuality.classList.remove('hide');
}
if (selectQuality) {
//selectQuality.setAttribute('required', 'required');
// This is a hack due to what appears to be a edge bug but it shoudln't matter as the list always has selectable items
selectQuality.removeAttribute('required');
}
} else {
if (fldQuality) {
fldQuality.classList.add('hide');
}
if (selectQuality) {
selectQuality.removeAttribute('required');
}
}
}
function onProfileChange(form, profileId) {
var options = currentDialogOptions || {};
var profileOptions = options.ProfileOptions || [];
if (!profileOptions.length) {
return;
}
var option = profileOptions.filter(function (o) {
return o.Id === profileId;
})[0];
var qualityOptions = options.QualityOptions || [];
if (option) {
form.querySelector('.profileDescription').innerHTML = option.Description || '';
setQualityFieldVisible(form, qualityOptions.length > 0 && option.EnableQualityOptions && options.Options.indexOf('Quality') !== -1);
} else {
form.querySelector('.profileDescription').innerHTML = '';
setQualityFieldVisible(form, qualityOptions.length > 0 && options.Options.indexOf('Quality') !== -1);
}
}
function onQualityChange(form, qualityId) {
var options = currentDialogOptions || {};
var option = (options.QualityOptions || []).filter(function (o) {
return o.Id === qualityId;
})[0];
var qualityDescription = form.querySelector('.qualityDescription');
if (option) {
qualityDescription.innerHTML = option.Description || '';
} else {
qualityDescription.innerHTML = '';
}
var fldBitrate = form.querySelector('.fldBitrate');
var txtBitrate = form.querySelector('#txtBitrate');
if (qualityId === 'custom') {
if (fldBitrate) {
fldBitrate.classList.remove('hide');
}
if (txtBitrate) {
txtBitrate.setAttribute('required', 'required');
}
} else {
if (fldBitrate) {
fldBitrate.classList.add('hide');
}
if (txtBitrate) {
txtBitrate.removeAttribute('required');
}
}
}
function renderTargetDialogOptions(form, options) {
currentDialogOptions = options;
var fldProfile = form.querySelector('.fldProfile');
var selectProfile = form.querySelector('#selectProfile');
if (options.ProfileOptions.length && options.Options.indexOf('Profile') !== -1) {
if (fldProfile) {
fldProfile.classList.remove('hide');
}
if (selectProfile) {
selectProfile.setAttribute('required', 'required');
}
} else {
if (fldProfile) {
fldProfile.classList.add('hide');
}
if (selectProfile) {
selectProfile.removeAttribute('required');
}
}
setQualityFieldVisible(form, options.QualityOptions.length > 0);
if (selectProfile) {
selectProfile.innerHTML = options.ProfileOptions.map(function (o) {
var selectedAttribute = o.IsDefault ? ' selected="selected"' : '';
return '<option value="' + o.Id + '"' + selectedAttribute + '>' + o.Name + '</option>';
}).join('');
selectProfile.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
var selectQuality = form.querySelector('#selectQuality');
if (selectQuality) {
selectQuality.innerHTML = options.QualityOptions.map(function (o) {
var selectedAttribute = o.IsDefault ? ' selected="selected"' : '';
return '<option value="' + o.Id + '"' + selectedAttribute + '>' + o.Name + '</option>';
}).join('');
var lastQuality = appSettings.get('sync-lastquality');
if (lastQuality && options.QualityOptions.filter(function (i) {
return i.Id === lastQuality;
}).length) {
selectQuality.value = lastQuality;
}
selectQuality.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
}
function loadQualityOptions(form, targetId, dialogOptionsFn) {
return dialogOptionsFn(targetId).then(function (options) {
return renderTargetDialogOptions(form, options);
});
}
return {
showMenu: showSyncMenu,
renderForm: renderForm,
setJobValues: setJobValues
};
});

View file

@ -104,18 +104,6 @@
<span>${ButtonShuffle}</span>
</button>
<button is="emby-downloadbutton" type="button" class="button-flat btnSyncDownload hide detailButton-mobile">
<div class="detailButton-mobile-content">
<i class="md-icon detailButton-mobile-icon">&#xE2C4;</i>
<div class="detailButton-mobile-text emby-downloadbutton-downloadtext">${ButtonDownload}</div>
</div>
</button>
<button is="emby-downloadbutton" type="button" class="raised btnSyncDownload hide detailButton">
<i class="md-icon">&#xE2C4;</i>
<span class="emby-downloadbutton-downloadtext">${ButtonDownload}</span>
</button>
<button is="emby-button" type="button" class="button-flat btnCancelSeriesTimer hide detailButton-mobile">
<div class="detailButton-mobile-content">
<i class="md-icon detailButton-mobile-icon">&#xE872;</i>

View file

@ -74,10 +74,6 @@
<h2 class="sectionTitle sectionTitle-cards">${HeaderLatestRecordings}</h2>
<i class="md-icon">&#xE5CC;</i>
</button>
<button is="emby-button" type="button" class="fab submit categorySyncButton button-submit sectionTitleButton" data-category="Latest" title="${ButtonDownload}">
<i class="md-icon">&#xE2C4;</i>
</button>
</div>
<div is="emby-itemscontainer" class="recordingItems itemsContainer padded-left padded-right"></div>
</div>

View file

@ -30,9 +30,6 @@
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderLatestMovies}</h2>
<button is="emby-button" type="button" class="fab submit categorySyncButton button-submit sectionTitleButton" data-category="Latest" title="${ButtonDownload}">
<i class="md-icon">&#xE2C4;</i>
</button>
</div>
<div is="emby-itemscontainer" id="recentlyAddedItems" class="itemsContainer padded-left padded-right">

View file

@ -1,4 +1,4 @@
define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "apphost", "imageLoader", "libraryMenu", "globalize", "browser", "events", "scrollHelper", "playbackManager", "libraryBrowser", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-linkbutton", "emby-playstatebutton", "emby-ratingbutton", "emby-downloadbutton", "emby-scroller", "emby-select"], function(loading, appRouter, layoutManager, connectionManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, appHost, imageLoader, libraryMenu, globalize, browser, events, scrollHelper, playbackManager, libraryBrowser) {
define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "apphost", "imageLoader", "libraryMenu", "globalize", "browser", "events", "scrollHelper", "playbackManager", "libraryBrowser", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-linkbutton", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function(loading, appRouter, layoutManager, connectionManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, appHost, imageLoader, libraryMenu, globalize, browser, events, scrollHelper, playbackManager, libraryBrowser) {
"use strict";
function getPromise(apiClient, params) {
@ -33,12 +33,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
user: user,
share: !0
};
return appHost.supports("sync") && (options.syncLocal = !1), options
}
function renderSyncLocalContainer(page, params, user, item) {
if (appHost.supports("sync"))
for (var canSync = itemHelper.canSync(user, item), buttons = page.querySelectorAll(".btnSyncDownload"), i = 0, length = buttons.length; i < length; i++) buttons[i].setItem(item), canSync ? buttons[i].classList.remove("hide") : buttons[i].classList.add("hide")
return options;
}
function getProgramScheduleHtml(items, options) {
@ -286,7 +281,11 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
var apiClient = connectionManager.getApiClient(item.ServerId);
renderSeriesTimerEditor(page, item, apiClient, user), renderTimerEditor(page, item, apiClient, user), renderImage(page, item, apiClient, user), renderLogo(page, item, apiClient), setTitle(item, apiClient), setInitialCollapsibleState(page, item, apiClient, context, user), renderDetails(page, item, apiClient, context), renderTrackSelections(page, instance, item), dom.getWindowSize().innerWidth >= 1e3 ? backdrop.setBackdrops([item]) : backdrop.clear(), renderDetailPageBackdrop(page, item, apiClient);
var canPlay = reloadPlayButtons(page, item);
(item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf("PlayTrailers") ? hideAll(page, "btnPlayTrailer", !0) : hideAll(page, "btnPlayTrailer"), setTrailerButtonVisibility(page, item), item.CanDelete && !item.IsFolder ? hideAll(page, "btnDeleteItem", !0) : hideAll(page, "btnDeleteItem"), renderSyncLocalContainer(page, params, user, item), "Program" !== item.Type || canPlay ? hideAll(page, "mainDetailButtons", !0) : hideAll(page, "mainDetailButtons"), showRecordingFields(instance, page, item, user);
(item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf("PlayTrailers") ? hideAll(page, "btnPlayTrailer", !0) : hideAll(page, "btnPlayTrailer");
setTrailerButtonVisibility(page, item);
item.CanDelete && !item.IsFolder ? hideAll(page, "btnDeleteItem", !0) : hideAll(page, "btnDeleteItem");
"Program" !== item.Type || canPlay ? hideAll(page, "mainDetailButtons", !0) : hideAll(page, "mainDetailButtons");
showRecordingFields(instance, page, item, user);
var groupedVersions = (item.MediaSources || []).filter(function(g) {
return "Grouping" == g.Type
});
@ -1166,30 +1165,55 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}))
}
}
var currentItem, self = this,
apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient;
var currentItem;
var self = this;
var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient;
view.querySelectorAll(".btnPlay");
bindAll(view, ".btnPlay", "click", onPlayClick), bindAll(view, ".btnResume", "click", onPlayClick), bindAll(view, ".btnInstantMix", "click", onInstantMixClick), bindAll(view, ".btnShuffle", "click", onShuffleClick), bindAll(view, ".btnPlayTrailer", "click", onPlayTrailerClick), bindAll(view, ".btnCancelSeriesTimer", "click", onCancelSeriesTimerClick), bindAll(view, ".btnCancelTimer", "click", onCancelTimerClick), bindAll(view, ".btnDeleteItem", "click", onDeleteClick), bindAll(view, ".btnSyncDownload", "download", onDownloadChange), bindAll(view, ".btnSyncDownload", "download-cancel", onDownloadChange), view.querySelector(".btnMoreCommands i").innerHTML = "&#xE5D3;", view.querySelector(".trackSelections").addEventListener("submit", onTrackSelectionsSubmit), view.querySelector(".btnSplitVersions").addEventListener("click", function() {
bindAll(view, ".btnPlay", "click", onPlayClick);
bindAll(view, ".btnResume", "click", onPlayClick);
bindAll(view, ".btnInstantMix", "click", onInstantMixClick);
bindAll(view, ".btnShuffle", "click", onShuffleClick);
bindAll(view, ".btnPlayTrailer", "click", onPlayTrailerClick);
bindAll(view, ".btnCancelSeriesTimer", "click", onCancelSeriesTimerClick);
bindAll(view, ".btnCancelTimer", "click", onCancelTimerClick);
bindAll(view, ".btnDeleteItem", "click", onDeleteClick);
view.querySelector(".btnMoreCommands i").innerHTML = "&#xE5D3;";
view.querySelector(".trackSelections").addEventListener("submit", onTrackSelectionsSubmit);
view.querySelector(".btnSplitVersions").addEventListener("click", function() {
splitVersions(self, view, apiClient, params)
}), bindAll(view, ".btnMoreCommands", "click", onMoreCommandsClick), view.querySelector(".selectSource").addEventListener("change", function() {
renderVideoSelections(view, self._currentPlaybackMediaSources), renderAudioSelections(view, self._currentPlaybackMediaSources), renderSubtitleSelections(view, self._currentPlaybackMediaSources)
}), view.addEventListener("click", function(e) {
});
bindAll(view, ".btnMoreCommands", "click", onMoreCommandsClick);
view.querySelector(".selectSource").addEventListener("change", function() {
renderVideoSelections(view, self._currentPlaybackMediaSources);
renderAudioSelections(view, self._currentPlaybackMediaSources);
renderSubtitleSelections(view, self._currentPlaybackMediaSources);
});
view.addEventListener("click", function(e) {
dom.parentWithClass(e.target, "moreScenes") ? apiClient.getCurrentUser().then(function(user) {
renderScenes(view, currentItem)
}) : dom.parentWithClass(e.target, "morePeople") ? renderCast(view, currentItem, params.context) : dom.parentWithClass(e.target, "moreSpecials") && apiClient.getCurrentUser().then(function(user) {
renderSpecials(view, currentItem, user)
})
}), view.querySelector(".detailImageContainer").addEventListener("click", function(e) {
});
view.querySelector(".detailImageContainer").addEventListener("click", function(e) {
dom.parentWithClass(e.target, "itemDetailGalleryLink") && editImages().then(function() {
reload(self, view, params)
})
}), view.addEventListener("viewshow", function(e) {
});
view.addEventListener("viewshow", function(e) {
var page = this;
libraryMenu.setTransparentMenu(!0), e.detail.isRestored ? currentItem && (setTitle(currentItem, connectionManager.getApiClient(currentItem.ServerId)), renderTrackSelections(page, self, currentItem, !0)) : reload(self, page, params), events.on(apiClient, "message", onWebSocketMessage), events.on(playbackManager, "playerchange", onPlayerChange)
}), view.addEventListener("viewbeforehide", function() {
events.off(apiClient, "message", onWebSocketMessage), events.off(playbackManager, "playerchange", onPlayerChange), libraryMenu.setTransparentMenu(!1)
}), view.addEventListener("viewdestroy", function() {
currentItem = null, self._currentPlaybackMediaSources = null, self.currentRecordingFields = null
});
view.addEventListener("viewbeforehide", function() {
events.off(apiClient, "message", onWebSocketMessage);
events.off(playbackManager, "playerchange", onPlayerChange);
libraryMenu.setTransparentMenu(!1);
});
view.addEventListener("viewdestroy", function() {
currentItem = null;
self._currentPlaybackMediaSources = null;
self.currentRecordingFields = null;
})
}
});

View file

@ -1,4 +1,4 @@
define(["layoutManager", "loading", "components/categorysyncbuttons", "cardBuilder", "apphost", "imageLoader", "scripts/livetvcomponents", "listViewStyle", "emby-itemscontainer"], function(layoutManager, loading, categorysyncbuttons, cardBuilder, appHost, imageLoader) {
define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "scripts/livetvcomponents", "listViewStyle", "emby-itemscontainer"], function(layoutManager, loading, cardBuilder, appHost, imageLoader) {
"use strict";
function renderRecordings(elem, recordings, cardOptions, scrollX) {
@ -54,7 +54,6 @@ define(["layoutManager", "loading", "components/categorysyncbuttons", "cardBuild
}
var foldersPromise, latestPromise, self = this,
lastFullRender = 0;
categorysyncbuttons.init(tabContent);
for (var moreButtons = tabContent.querySelectorAll(".more"), i = 0, length = moreButtons.length; i < length; i++) moreButtons[i].addEventListener("click", onMoreClick);
self.preRender = function() {
enableFullRender() && (latestPromise = ApiClient.getLiveTvRecordings({

View file

@ -1,4 +1,4 @@
define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu", "mainTabsManager", "components/categorysyncbuttons", "cardBuilder", "dom", "imageLoader", "playbackManager", "emby-itemscontainer", "emby-tabs", "emby-button"], function(events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, categorysyncbuttons, cardBuilder, dom, imageLoader, playbackManager) {
define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu", "mainTabsManager", "cardBuilder", "dom", "imageLoader", "playbackManager", "emby-itemscontainer", "emby-tabs", "emby-button"], function(events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager) {
"use strict";
function enableScrollX() {
@ -250,7 +250,7 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
suggestionsTabIndex = 1;
self.initTab = function() {
var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
categorysyncbuttons.init(tabContent), initSuggestedTab(view, tabContent)
initSuggestedTab(view, tabContent);
}, self.renderTab = function() {
var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
loadSuggestionsTab(view, params, tabContent)

View file

@ -1431,7 +1431,6 @@ var AppInfo = {};
define("programStyles", ["css!" + componentsPath + "/guide/programs"], returnFirstDependency);
define("guide-settings-dialog", [componentsPath + "/guide/guide-settings"], returnFirstDependency);
define("loadingDialog", [componentsPath + "/loadingdialog/loadingdialog"], returnFirstDependency);
define("syncDialog", [componentsPath + "/sync/sync"], returnFirstDependency);
define("viewManager", [componentsPath + "/viewmanager/viewmanager"], function (viewManager) {
window.ViewManager = viewManager;
viewManager.dispatchPageEvents(true);
@ -1470,7 +1469,6 @@ var AppInfo = {};
define("userdataButtons", [componentsPath + "/userdatabuttons/userdatabuttons"], returnFirstDependency);
define("emby-playstatebutton", [componentsPath + "/userdatabuttons/emby-playstatebutton"], returnFirstDependency);
define("emby-ratingbutton", [componentsPath + "/userdatabuttons/emby-ratingbutton"], returnFirstDependency);
define("emby-downloadbutton", [componentsPath + "/sync/emby-downloadbutton"], returnFirstDependency);
define("listView", [componentsPath + "/listview/listview"], returnFirstDependency);
define("listViewStyle", ["css!" + componentsPath + "/listview/listview"], returnFirstDependency);
define("formDialogStyle", ["css!" + componentsPath + "/formdialog"], returnFirstDependency);

View file

@ -1,4 +1,4 @@
define(["loading", "components/categorysyncbuttons", "components/groupedcards", "cardBuilder", "apphost", "imageLoader"], function(loading, categorysyncbuttons, groupedcards, cardBuilder, appHost, imageLoader) {
define(["loading", "components/groupedcards", "cardBuilder", "apphost", "imageLoader"], function(loading, groupedcards, cardBuilder, appHost, imageLoader) {
"use strict";
function getLatestPromise(context, params) {
@ -43,7 +43,6 @@ define(["loading", "components/categorysyncbuttons", "components/groupedcards",
}
return function(view, params, tabContent) {
var self = this;
categorysyncbuttons.init(tabContent);
var latestPromise;
self.preRender = function() {
latestPromise = getLatestPromise(view, params)

View file

@ -1,4 +1,4 @@
define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "dom", "components/categorysyncbuttons", "userSettings", "cardBuilder", "playbackManager", "mainTabsManager", "scrollStyles", "emby-itemscontainer", "emby-button"], function(events, inputManager, libraryMenu, layoutManager, loading, dom, categorysyncbuttons, userSettings, cardBuilder, playbackManager, mainTabsManager) {
define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "dom", "userSettings", "cardBuilder", "playbackManager", "mainTabsManager", "scrollStyles", "emby-itemscontainer", "emby-button"], function(events, inputManager, libraryMenu, layoutManager, loading, dom, userSettings, cardBuilder, playbackManager, mainTabsManager) {
"use strict";
function getTabs() {
@ -204,7 +204,7 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do
initialTabIndex = currentTabIndex;
self.initTab = function() {
var tabContent = self.tabContent;
setScrollClasses(tabContent.querySelector("#resumableItems"), enableScrollX()), categorysyncbuttons.init(tabContent)
setScrollClasses(tabContent.querySelector("#resumableItems"), enableScrollX())
}, self.renderTab = function() {
reload()
};

View file

@ -1 +1 @@
importScripts("components/serviceworker/notifications.js", "components/serviceworker/sync.js");
importScripts("components/serviceworker/notifications.js");

View file

@ -30,9 +30,6 @@
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left nextUpHeader">${HeaderNextUp}</h2>
<button is="emby-button" type="button" class="fab submit categorySyncButton button-submit sectionTitleButton" data-category="NextUp" title="${ButtonDownload}">
<i class="md-icon">&#xE2C4;</i>
</button>
</div>
<div is="emby-itemscontainer" id="nextUpItems" class="itemsContainer vertical-wrap padded-left padded-right">
</div>
@ -43,9 +40,6 @@
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderLatestEpisodes}</h2>
<button is="emby-button" type="button" class="fab submit categorySyncButton button-submit sectionTitleButton" data-category="Latest" title="${ButtonDownload}">
<i class="md-icon">&#xE2C4;</i>
</button>
</div>
<div is="emby-itemscontainer" id="latestEpisodes" class="itemsContainer vertical-wrap padded-left padded-right">
</div>