From 0e1c7cbb7ac9b255df12959df0a1fb5fd8c99f8e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 15 Sep 2015 23:55:26 -0400 Subject: [PATCH] rework collection editor --- dashboard-ui/apiclient/fileupload.js | 2 +- dashboard-ui/apiclient/localassetmanager.js | 14 + .../apiclient/sync/contentuploader.js | 5 +- dashboard-ui/apiclient/sync/mediasync.js | 18 +- .../apiclient/sync/offlineusersync.js | 12 +- .../cordova/android/androidcredentials.js | 2 +- dashboard-ui/cordova/fileupload.js | 61 ++-- dashboard-ui/cordova/ios/vlcplayer.js | 7 +- dashboard-ui/cordova/localassetmanager.js | 316 ++++++++++++++++-- dashboard-ui/edititemmetadata.html | 50 +-- dashboard-ui/scripts/collectioneditor.js | 2 +- dashboard-ui/scripts/editcollectionitems.js | 267 --------------- dashboard-ui/scripts/edititemimages.js | 2 +- dashboard-ui/scripts/edititemmetadata.js | 6 - dashboard-ui/scripts/extensions.js | 1 - dashboard-ui/scripts/itemdetailpage.js | 49 ++- dashboard-ui/scripts/librarybrowser.js | 10 +- dashboard-ui/scripts/librarylist.js | 11 + dashboard-ui/scripts/mediacontroller.js | 1 - dashboard-ui/scripts/site.js | 1 - .../strings/javascript/javascript.json | 1 + 21 files changed, 436 insertions(+), 402 deletions(-) delete mode 100644 dashboard-ui/scripts/editcollectionitems.js diff --git a/dashboard-ui/apiclient/fileupload.js b/dashboard-ui/apiclient/fileupload.js index d8e77aa17b..36c2f995f6 100644 --- a/dashboard-ui/apiclient/fileupload.js +++ b/dashboard-ui/apiclient/fileupload.js @@ -4,7 +4,7 @@ var self = this; - self.upload = function (file, mimeType, name, url) { + self.upload = function (file, name, url) { var deferred = DeferredBuilder.Deferred(); diff --git a/dashboard-ui/apiclient/localassetmanager.js b/dashboard-ui/apiclient/localassetmanager.js index acb4e05502..de0641c045 100644 --- a/dashboard-ui/apiclient/localassetmanager.js +++ b/dashboard-ui/apiclient/localassetmanager.js @@ -12,6 +12,12 @@ return deferred.promise(); } + function deleteOfflineUser(id) { + var deferred = DeferredBuilder.Deferred(); + deferred.resolve(); + return deferred.promise(); + } + function getCameraPhotos() { var deferred = DeferredBuilder.Deferred(); deferred.resolveWith(null, [[]]); @@ -24,6 +30,12 @@ return deferred.promise(); } + function deleteOfflineActions(actions) { + var deferred = DeferredBuilder.Deferred(); + deferred.resolveWith(null, [[]]); + return deferred.promise(); + } + function getServerItemIds(serverId) { var deferred = DeferredBuilder.Deferred(); deferred.resolveWith(null, [[]]); @@ -86,8 +98,10 @@ window.LocalAssetManager = { getLocalMediaSource: getLocalMediaSource, saveOfflineUser: saveOfflineUser, + deleteOfflineUser: deleteOfflineUser, getCameraPhotos: getCameraPhotos, getOfflineActions: getOfflineActions, + deleteOfflineActions: deleteOfflineActions, getServerItemIds: getServerItemIds, removeLocalItem: removeLocalItem, getLocalItem: getLocalItem, diff --git a/dashboard-ui/apiclient/sync/contentuploader.js b/dashboard-ui/apiclient/sync/contentuploader.js index 80c9d63785..f11559e5c9 100644 --- a/dashboard-ui/apiclient/sync/contentuploader.js +++ b/dashboard-ui/apiclient/sync/contentuploader.js @@ -113,10 +113,7 @@ Logger.log('Uploading file to ' + url); - // TODO: Need to get this from cordova file api instead of trying to infer the content type from the path - var mimeType = file.toLowerCase().indexOf('mp4') != -1 || file.toLowerCase().indexOf('m4v') != -1 ? 'video/mp4' : (file.toLowerCase().indexOf('png') != -1 ? 'image/png' : 'image/jpg'); - - new MediaBrowser.FileUpload().upload(file, mimeType, name, url).done(function () { + new MediaBrowser.FileUpload().upload(file, name, url).done(function () { Logger.log('File upload succeeded'); deferred.resolve(); diff --git a/dashboard-ui/apiclient/sync/mediasync.js b/dashboard-ui/apiclient/sync/mediasync.js index 51c54f3bf0..1ba0b81b4d 100644 --- a/dashboard-ui/apiclient/sync/mediasync.js +++ b/dashboard-ui/apiclient/sync/mediasync.js @@ -49,7 +49,11 @@ apiClient.reportOfflineActions(actions).done(function () { - deferred.resolve(); + LocalAssetManager.deleteOfflineActions(actions).done(function () { + + deferred.resolve(); + + }).fail(getOnFail(deferred)); }).fail(getOnFail(deferred)); @@ -235,9 +239,9 @@ Logger.log('Downloading media. Url: ' + url + '. Local path: ' + localPath); - localAssetManager.downloadFile(url, localPath).done(function () { + LocalAssetManager.downloadFile(url, localPath).done(function () { - localAssetManager.addOrUpdateLocalItem(localItem).done(function () { + LocalAssetManager.addOrUpdateLocalItem(localItem).done(function () { deferred.resolve(); @@ -324,7 +328,7 @@ require(['localassetmanager'], function () { - localAssetManager.hasImage(serverId, itemId, imageTag).done(function (hasImage) { + LocalAssetManager.hasImage(serverId, itemId, imageTag).done(function (hasImage) { if (hasImage) { deferred.resolve(); @@ -336,7 +340,7 @@ ImageType: imageType }); - localAssetManager.downloadImage(imageUrl, serverId, itemId, imageTag).done(function () { + LocalAssetManager.downloadImage(imageUrl, serverId, itemId, imageTag).done(function () { deferred.resolve(); @@ -418,7 +422,7 @@ LocalAssetManager.downloadSubtitles(url, localItem, subtitleStream).done(function (subtitlePath) { subtitleStream.Path = subtitlePath; - localAssetManager.addOrUpdateLocalItem(localItem).done(function () { + LocalAssetManager.addOrUpdateLocalItem(localItem).done(function () { deferred.resolve(); }).fail(getOnFail(deferred)); @@ -479,7 +483,7 @@ else { localItem.UserIdsWithAccess = userIdsWithAccess; - localAssetManager.addOrUpdateLocalItem(localItem).done(function () { + LocalAssetManager.addOrUpdateLocalItem(localItem).done(function () { deferred.resolve(); }).fail(getOnFail(deferred)); } diff --git a/dashboard-ui/apiclient/sync/offlineusersync.js b/dashboard-ui/apiclient/sync/offlineusersync.js index 8102d6db93..63ee8e135e 100644 --- a/dashboard-ui/apiclient/sync/offlineusersync.js +++ b/dashboard-ui/apiclient/sync/offlineusersync.js @@ -48,7 +48,17 @@ }); }).fail(function () { - deferred.reject(); + + // TODO: We should only delete if there's a 401 response + + require(['localassetmanager'], function () { + + LocalAssetManager.deleteOfflineUser(user.Id).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.resolve(); + }); + }); }); return deferred.promise(); diff --git a/dashboard-ui/cordova/android/androidcredentials.js b/dashboard-ui/cordova/android/androidcredentials.js index 8c4233a24c..0310c2b558 100644 --- a/dashboard-ui/cordova/android/androidcredentials.js +++ b/dashboard-ui/cordova/android/androidcredentials.js @@ -27,7 +27,7 @@ ApiClientBridge.init(AppInfo.appName, AppInfo.appVersion, AppInfo.deviceId, AppInfo.deviceName, JSON.stringify(capabilities)); - initAjax(); + //initAjax(); } var baseAjaxMethod; diff --git a/dashboard-ui/cordova/fileupload.js b/dashboard-ui/cordova/fileupload.js index 8be134113a..772e85bf52 100644 --- a/dashboard-ui/cordova/fileupload.js +++ b/dashboard-ui/cordova/fileupload.js @@ -4,32 +4,51 @@ var self = this; - self.upload = function (file, mimeType, name, url) { + self.upload = function (path, name, url) { var deferred = DeferredBuilder.Deferred(); - var onSuccess = function (r) { - console.log("Code = " + r.responseCode); - console.log("Response = " + r.response); - console.log("Sent = " + r.bytesSent); - deferred.resolve(); - } + resolveLocalFileSystemURL(path, function (fileEntry) { - var onFail = function (error) { - console.log("upload error source " + error.source); - console.log("upload error target " + error.target); + fileEntry.file(function (file) { + + var mimeType = file.type; + + Logger.log('mimeType for file ' + path + ' is ' + file); + + var onSuccess = function (r) { + console.log("Code = " + r.responseCode); + console.log("Response = " + r.response); + console.log("Sent = " + r.bytesSent); + deferred.resolve(); + } + + var onFail = function (error) { + console.log("upload error source " + error.source); + console.log("upload error target " + error.target); + deferred.reject(); + } + + var options = new FileUploadOptions(); + options.fileKey = "file"; + options.fileName = name; + options.mimeType = mimeType; + + var params = {}; + options.params = params; + + new FileTransfer().upload(file, url, onSuccess, onFail, options); + + }, function () { + Logger.log('File upload failed. fileEntry.file returned an error'); + deferred.reject(); + }); + + }, function () { + + Logger.log('File upload failed. resolveLocalFileSystemURL returned an error'); deferred.reject(); - } - - var options = new FileUploadOptions(); - options.fileKey = "file"; - options.fileName = name; - options.mimeType = mimeType; - - var params = {}; - options.params = params; - - new FileTransfer().upload(file, url, onSuccess, onFail, options); + }); return deferred.promise(); }; diff --git a/dashboard-ui/cordova/ios/vlcplayer.js b/dashboard-ui/cordova/ios/vlcplayer.js index 898f4efd3c..adbeba4760 100644 --- a/dashboard-ui/cordova/ios/vlcplayer.js +++ b/dashboard-ui/cordova/ios/vlcplayer.js @@ -110,12 +110,13 @@ return self.playerState.volume; }; - self.setCurrentSrc = function (val, item, mediaSource, tracks) { + self.setCurrentSrc = function (streamInfo, item, mediaSource, tracks) { - if (!val) { + if (!streamInfo) { return; } + var val = streamInfo.url; var tIndex = val.indexOf('#t='); var startPosMs = 0; @@ -127,8 +128,6 @@ if (options.type == 'audio') { - // TODO - //AndroidVlcPlayer.playAudioVlc(val, JSON.stringify(item), JSON.stringify(mediaSource), options.poster); var artist = item.ArtistItems && item.ArtistItems.length ? item.ArtistItems[0].Name : null; window.audioplayer.playstream(successHandler, function () { diff --git a/dashboard-ui/cordova/localassetmanager.js b/dashboard-ui/cordova/localassetmanager.js index b12424f939..30ef01d0f2 100644 --- a/dashboard-ui/cordova/localassetmanager.js +++ b/dashboard-ui/cordova/localassetmanager.js @@ -68,46 +68,300 @@ return deferred.promise(); } + var offlineUserDatabase; + function getOfflineUserdb(callback) { + + if (offlineUserDatabase) { + callback(offlineUserDatabase); + return; + } + + // Create/open database + offlineUserDatabase = window.sqlitePlugin.openDatabase({ name: "offlineusers.db" }); + + offlineUserDatabase.transaction(function (tx) { + + tx.executeSql('CREATE TABLE IF NOT EXISTS users (id text primary key, data text)'); + tx.executeSql('create index if not exists idx_users on users(id)'); + + callback(offlineUserDatabase); + }); + } + function saveOfflineUser(user) { - // TODO var deferred = DeferredBuilder.Deferred(); - deferred.resolve(); + + getOfflineUserdb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("REPLACE INTO offlineusers (id, data) VALUES (?,?)", [user.Id, JSON.stringify(user)], function (tx, res) { + + deferred.resolve(); + }, function (e) { + deferred.reject(); + }); + }); + }); + return deferred.promise(); } + function deleteOfflineUser(id) { + + var deferred = DeferredBuilder.Deferred(); + + getOfflineUserdb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("DELETE from offlineusers where id=?", [user.Id], function (tx, res) { + + deferred.resolve(); + }, function (e) { + deferred.reject(); + }); + }); + }); + + return deferred.promise(); + } + + var offlineActionsDatabase; + function getOfflineActionsDb(callback) { + + if (offlineActionsDatabase) { + callback(offlineActionsDatabase); + return; + } + + // Create/open database + offlineActionsDatabase = window.sqlitePlugin.openDatabase({ name: "offlineactions.db" }); + + offlineActionsDatabase.transaction(function (tx) { + + tx.executeSql('CREATE TABLE IF NOT EXISTS offlineactions (Id text primary key, ServerId text not null, Json text not null)'); + tx.executeSql('create index if not exists idx_offlineactions on offlineactions(id)'); + + callback(offlineActionsDatabase); + }); + } + function getOfflineActions(serverId) { - // TODO + var deferred = DeferredBuilder.Deferred(); - deferred.resolveWith(null, [[]]); + + getOfflineActionsDb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("SELECT json from offlineactions where ServerId=?", [serverId], function (tx, res) { + + var actions = []; + for (var i = 0, length = res.rows.length; i < length; i++) { + actions.push(JSON.parse(res.rows.item(i).json)); + } + + deferred.resolveWith(null, [actions]); + + }, function (e) { + deferred.reject(); + }); + }); + }); + return deferred.promise(); } + function deleteOfflineActions(actions) { + + var ids = actions.map(function (a) { return "'" + a.Id + "'"; }).join(','); + + var deferred = DeferredBuilder.Deferred(); + + getOfflineActionsDb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("DELETE from offlineactions where Id in (" + ids + ")", [], function (tx, res) { + + deferred.resolve(); + + }, function (e) { + deferred.reject(); + }); + }); + }); + + return deferred.promise(); + } + + var offlineItemsDatabase; + function getOfflineItemsDb(callback) { + + if (offlineItemsDatabase) { + callback(offlineItemsDatabase); + return; + } + + // Create/open database + offlineItemsDatabase = window.sqlitePlugin.openDatabase({ name: "offlineitems.db" }); + + offlineItemsDatabase.transaction(function (tx) { + + tx.executeSql('CREATE TABLE IF NOT EXISTS Items ( Id text primary key, ItemId text not null, ItemType text not null, MediaType text, ServerId text not null, LocalPath text not null, UserIdsWithAccess text, AlbumId text, AlbumName text, SeriesId text, SeriesName text, Json text not null)'); + tx.executeSql('create index if not exists idx_items on Items(Id)'); + + tx.executeSql('CREATE TABLE IF NOT EXISTS AlbumArtists ( Id text not null, Name text not null, ItemId text not null)'); + tx.executeSql('create index if not exists idx_AlbumArtists on AlbumArtists(id)'); + + callback(offlineItemsDatabase); + }); + } + function getServerItemIds(serverId) { - // TODO - var deferred = DeferredBuilder.Deferred(); - deferred.resolveWith(null, [[]]); - return deferred.promise(); - } - function removeLocalItem(itemId, serverId) { - // TODO var deferred = DeferredBuilder.Deferred(); - deferred.resolveWith(null, []); + + getOfflineItemsDb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("SELECT ItemId from Items where ServerId=?", [serverId], function (tx, res) { + + var itemIds = []; + for (var i = 0, length = res.rows.length; i < length; i++) { + itemIds.push(res.rows.item(i).ItemId); + } + + deferred.resolveWith(null, [itemIds]); + + }, function (e) { + deferred.reject(); + }); + }); + }); + return deferred.promise(); } function getLocalItem(itemId, serverId) { - // TODO + var deferred = DeferredBuilder.Deferred(); - deferred.resolveWith(null, []); + + getOfflineItemsDb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("SELECT Json from Items where itemId=? AND serverId=?", [itemId, serverId], function (tx, res) { + + if (res.rows.length) { + + var localItem = JSON.parse(res.rows.item(0).Json); + + deferred.resolveWith(null, [item]); + } + else { + deferred.resolveWith(null, [null]); + } + + }, function (e) { + deferred.reject(); + }); + }); + }); + return deferred.promise(); } - function addOrUpdateLocalItem(localItem) { - // TODO + function addOrUpdateLocalItem(item) { + var deferred = DeferredBuilder.Deferred(); - deferred.resolveWith(null, [null]); + + getOfflineItemsDb(function (db) { + + db.transaction(function (tx) { + + var values = [item.Id, item.ItemId, item.Item.Type, item.Item.MediaType, item.ServerId, item.LocalPath, item.UserIdsWithAccess.join(','), item.Item.AlbumId, item.Item.AlbumName, item.Item.SeriesId, item.Item.SeriesName, JSON.stringify(item)]; + tx.executeSql("REPLACE INTO Items (Id, ItemId, ItemType, MediaType, ServerId, LocalPath, UserIdsWithAccess, AlbumId, AlbumName, SeriesId, SeriesName, Json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", values); + }); + }); + + return deferred.promise(); + } + + function removeLocalItem(itemId, serverId) { + + var deferred = DeferredBuilder.Deferred(); + + getLocalItem(itemId, serverId).done(function (item) { + + getOfflineItemsDb(function (db) { + + db.transaction(function (tx) { + + tx.executeSql("DELETE from Items where itemId=? AND serverId=?", [itemId, serverId]); + + var files = item.AdditionalFiles || []; + files.push(item.LocalPath); + + deleteFiles(files).done(function () { + + deferred.resolve(); + + }).fail(getOnFail(deferred)); + + }); + }); + + }).fail(getOnFail(deferred)); + + return deferred.promise(); + } + + function deleteFiles(files) { + var deferred = DeferredBuilder.Deferred(); + deleteNextFile(files, 0, deferred); + return deferred.promise(); + } + + function deleteNextFile(files, index, deferred) { + + if (index >= files.length) { + deferred.resolve(); + return; + } + + deleteFile(file).done(function () { + deleteNextFile(files, index + 1, deferred); + }).fail(function () { + deleteNextFile(files, index + 1, deferred); + }); + } + + function deleteFile(path) { + + var deferred = DeferredBuilder.Deferred(); + + Logger.log('Deleting ' + path); + resolveLocalFileSystemURL(path, function (fileEntry) { + + fileEntry.remove(function () { + Logger.log('Deleted ' + path); + deferred.resolve(); + }, function () { + + Logger.log('Error deleting ' + path); + deferred.reject(); + }); + + }, function () { + + Logger.log('Skipping deletion because file does not exist: ' + path); + deferred.resolve(); + }); + return deferred.promise(); } @@ -122,15 +376,15 @@ getFileSystem().done(function (fileSystem) { - var localPath = fileSystem.root.toURL() + "/" + path.join('/'); + var localPath = trimEnd(fileSystem.root.toURL()) + "/" + path.join('/'); item.LocalPath = localPath; for (var i = 0, length = libraryItem.MediaSources.length; i < length; i++) { var mediaSource = libraryItem.MediaSources[i]; - mediaSource.setPath(localPath); - mediaSource.setProtocol(MediaProtocol.File); + mediaSource.Path = localPath; + mediaSource.Protocol = 'File'; } item.ServerId = serverInfo.Id; @@ -143,9 +397,23 @@ return deferred.promise(); } - function getDirectoryPath(item, serverInfo) { + function trimEnd(str) { + + var charEnd = '/'; + + if (str.charAt(str.length - 1) == charEnd) { + + str = str.substring(0, str.length - 1); + } + + return str; + } + + function getDirectoryPath(item, server) { var parts = []; + parts.push("emby"); + parts.push("sync"); parts.push(server.Name); if (item.Type == "Episode") { @@ -186,7 +454,7 @@ var filename = originalFileName || libraryItem.Name; - return fileRepository.getValidFileName(filename); + return getValidFileName(filename); } function getValidFileName(filename) { @@ -306,7 +574,7 @@ var deferred = DeferredBuilder.Deferred(); getFileSystem().done(function (fileSystem) { - var path = fileSystem.root.toURL() + "/emby/images/" + serverId + "/" + itemId + "/" + imageTag; + var path = trimEnd(fileSystem.root.toURL()) + "/emby/images/" + serverId + "/" + itemId + "/" + imageTag; deferred.resolveWith(null, [path]); }); @@ -357,8 +625,10 @@ window.LocalAssetManager = { getLocalMediaSource: getLocalMediaSource, saveOfflineUser: saveOfflineUser, + deleteOfflineUser: deleteOfflineUser, getCameraPhotos: getCameraPhotos, getOfflineActions: getOfflineActions, + deleteOfflineActions: deleteOfflineActions, getServerItemIds: getServerItemIds, removeLocalItem: removeLocalItem, getLocalItem: getLocalItem, diff --git a/dashboard-ui/edititemmetadata.html b/dashboard-ui/edititemmetadata.html index a620a74078..6ce8eeafdc 100644 --- a/dashboard-ui/edititemmetadata.html +++ b/dashboard-ui/edititemmetadata.html @@ -4,7 +4,7 @@ Emby -
+
@@ -17,7 +17,6 @@ ${TabMetadata} ${TabSubtitles} - ${TabCollectionTitles} ${TabImages} @@ -367,18 +366,6 @@
-
-

- ${EditCollectionItemsHelp} -

- -
- - -
-
-
-
@@ -568,41 +555,6 @@
- -

${HeaderAddUpdateImage}

diff --git a/dashboard-ui/scripts/collectioneditor.js b/dashboard-ui/scripts/collectioneditor.js index aef5d0d272..f8c96bdd52 100644 --- a/dashboard-ui/scripts/collectioneditor.js +++ b/dashboard-ui/scripts/collectioneditor.js @@ -214,7 +214,7 @@ var invalidTypes = ['Person', 'Genre', 'MusicGenre', 'Studio', 'GameGenre', 'BoxSet', 'Playlist', 'UserView', 'CollectionFolder', 'Audio', 'Episode']; - return item.LocationType == 'FileSystem' && !item.CollectionType && invalidTypes.indexOf(item.Type) == -1 && item.MediaType != 'Photo'; + return !item.CollectionType && invalidTypes.indexOf(item.Type) == -1 && item.MediaType != 'Photo'; } }; diff --git a/dashboard-ui/scripts/editcollectionitems.js b/dashboard-ui/scripts/editcollectionitems.js deleted file mode 100644 index f34ae4f723..0000000000 --- a/dashboard-ui/scripts/editcollectionitems.js +++ /dev/null @@ -1,267 +0,0 @@ -(function ($, document, window, FileReader, escape) { - - var currentItem; - - function reload(page) { - - Dashboard.showLoadingMsg(); - - $('#btnRemoveItems', page).buttonEnabled(false); - - MetadataEditor.getItemPromise().done(function (item) { - - currentItem = item; - - LibraryBrowser.renderName(item, $('.itemName', page), true); - - reloadTitles(page, item); - }); - } - - function getTitleHtml(item) { - - var html = '
'; - - html += '
'; - if (item.ImageTags.Primary) { - - var imgUrl = ApiClient.getScaledImageUrl(item.Id, { - type: "Primary", - maxWidth: 120, - maxHeight: 180, - tag: item.ImageTags.Primary - }); - - html += ''; - - } - html += '
'; - - html += '
' + item.Name + '
'; - - if (item.ParentId != currentItem.Id) { - html += ''; - } - - html += '
'; - - return html; - } - - function getSearchResultHtml(item) { - - var html = '
'; - - html += '
'; - - if (item.PrimaryImageTag) { - - var imgUrl = ApiClient.getScaledImageUrl(item.ItemId, { - type: "Primary", - maxWidth: 100, - maxHeight: 150, - tag: item.PrimaryImageTag - }); - - html += ''; - - } - html += '
'; - - html += '
' + item.Name + '
'; - - html += ''; - - html += '
'; - - return html; - } - - function reloadTitles(page, item) { - - ApiClient.getItems(Dashboard.getCurrentUserId(), { - - ParentId: item.Id - - }).done(function (result) { - - // Scroll back up so they can see the results from the beginning - window.scrollTo(0, 0); - - var html = result.Items.map(getTitleHtml).join(''); - - var elem = $('.collectionItems', page).html(html).trigger('create'); - - $('.chkRemoveItem', elem).on('change', function () { - - if ($('.chkRemoveItem:checked', elem).length) { - $('#btnRemoveItems', page).buttonEnabled(true); - } else { - $('#btnRemoveItems', page).buttonEnabled(false); - } - - }); - - Dashboard.hideLoadingMsg(); - }); - - } - - function showSearchResults(page, searchTerm) { - - ApiClient.getSearchHints({ - - userId: Dashboard.getCurrentUserId(), - searchTerm: searchTerm, - limit: 30, - - includePeople: false, - includeGenres: false, - includeStudios: false, - includeArtists: false, - - IncludeItemTypes: "Movie,Series,Game,MusicAlbum,Book" - - }).done(function (result) { - - renderSearchResults(page, result.SearchHints); - - }); - - } - - function renderSearchResults(page, items) { - - var existingIds = $('.chkRemoveItem', page).get().map(function (c) { - return c.getAttribute('data-itemid'); - }); - - var html = items.filter(function (i) { - - return existingIds.indexOf(i.ItemId) == -1; - - }).map(getSearchResultHtml).join(''); - - var elem = $('.collectionItemSearchResults', page).html(html).trigger('create'); - - $('.chkAddItem', elem).on('change', function () { - - if ($('.chkAddItem:checked', elem).length) { - $('#btnAddItems', page).buttonEnabled(true); - } else { - $('#btnAddItems', page).buttonEnabled(false); - } - - }); - } - - function addItemsToCollection(page) { - - var items = $('.chkAddItem:checked', page).get().map(function (c) { - - return c.getAttribute('data-itemid'); - - }); - - if (!items.length) { - Dashboard.alert(Globalize.translate("MessagePleaseSelectOneItem")); - return; - } - - var url = ApiClient.getUrl("Collections/" + currentItem.Id + "/Items", { - - Ids: items.join(',') - - }); - - ApiClient.ajax({ - type: "POST", - url: url - - }).done(function () { - - Dashboard.hideLoadingMsg(); - - $('.popupIdentifyCollection', page).popup('close'); - - reload(page); - - }); - } - - function removeItemsFromCollection(page) { - var items = $('.chkRemoveItem:checked', page).get().map(function (c) { - - return c.getAttribute('data-itemid'); - - }); - - if (!items.length) { - Dashboard.alert(Globalize.translate("MessagePleaseSelectOneItem")); - return; - } - - var url = ApiClient.getUrl("Collections/" + currentItem.Id + "/Items", { - - Ids: items.join(',') - - }); - - ApiClient.ajax({ - type: "DELETE", - url: url - - }).done(function () { - - Dashboard.hideLoadingMsg(); - - reload(page); - - }); - } - - function onSearchFormSubmit() { - var page = $(this).parents('.page'); - - showSearchResults(page, $('#txtLookupName', this).val()); - return false; - } - - $(document).on('pageinit', "#editItemMetadataPage", function () { - - var page = this; - - $('#btnAddItem', page).on('click', function () { - - - var popup = $('.popupIdentifyCollection', page).popup('open'); - - $('#txtLookupName', popup).val(''); - $('.collectionItemSearchResults', popup).empty(); - $('#btnAddItems', popup).buttonEnabled(false); - }); - - $('#btnAddItems', page).on('click', function () { - - addItemsToCollection(page); - }); - - $('#btnRemoveItems', page).on('click', function () { - - - removeItemsFromCollection(page); - }); - - $('.collectionItemSearchForm').off('submit', onSearchFormSubmit).on('submit', onSearchFormSubmit); - - $(page.querySelector('paper-tabs')).on('tabchange', function () { - - if (parseInt(this.selected) == 2) { - var tabContent = page.querySelector('.collectionItemsTabContent'); - - reload(tabContent); - } - }); - }); - -})(jQuery, document, window, window.FileReader, escape); \ No newline at end of file diff --git a/dashboard-ui/scripts/edititemimages.js b/dashboard-ui/scripts/edititemimages.js index 06b49d9bce..45fce56bd6 100644 --- a/dashboard-ui/scripts/edititemimages.js +++ b/dashboard-ui/scripts/edititemimages.js @@ -566,7 +566,7 @@ $(page.querySelector('paper-tabs')).on('tabchange', function () { - if (parseInt(this.selected) == 3) { + if (parseInt(this.selected) == 2) { var tabContent = page.querySelector('.imageEditorTab'); reload(tabContent); diff --git a/dashboard-ui/scripts/edititemmetadata.js b/dashboard-ui/scripts/edititemmetadata.js index 39240069b8..500f57d6a2 100644 --- a/dashboard-ui/scripts/edititemmetadata.js +++ b/dashboard-ui/scripts/edititemmetadata.js @@ -46,12 +46,6 @@ setFieldVisibilities(page, item); fillItemInfo(page, item, metadataEditorInfo.ParentalRatingOptions); - if (item.Type == "BoxSet") { - page.querySelector('.collectionItemsTabButton').classList.remove('hide'); - } else { - page.querySelector('.collectionItemsTabButton').classList.add('hide'); - } - if (item.MediaType == "Video" && item.LocationType == "FileSystem" && item.Type !== 'TvChannel') { page.querySelector('.subtitleTabButton').classList.remove('hide'); } else { diff --git a/dashboard-ui/scripts/extensions.js b/dashboard-ui/scripts/extensions.js index 9003237ef4..33228956f6 100644 --- a/dashboard-ui/scripts/extensions.js +++ b/dashboard-ui/scripts/extensions.js @@ -90,7 +90,6 @@ if (!Array.prototype.filter) { }; } -// TODO: There should be notification services, where each defines what it supports and the best service is chosen based on params var WebNotifications = { show: function (data) { diff --git a/dashboard-ui/scripts/itemdetailpage.js b/dashboard-ui/scripts/itemdetailpage.js index 2ea6ff7f17..7d26bff602 100644 --- a/dashboard-ui/scripts/itemdetailpage.js +++ b/dashboard-ui/scripts/itemdetailpage.js @@ -893,7 +893,7 @@ { name: Globalize.translate('HeaderBooks'), type: 'Book' } ]; - renderCollectionItems(page, collectionItemTypes, result.Items, user, context); + renderCollectionItems(page, item, collectionItemTypes, result.Items, user, context); } }); @@ -947,7 +947,10 @@ }); } - function renderCollectionItems(page, types, items, user) { + function renderCollectionItems(page, parentItem, types, items, user) { + + // First empty out existing content + page.querySelector('.collectionItems').innerHTML = ''; for (var i = 0, length = types.length; i < length; i++) { @@ -960,7 +963,7 @@ }); if (typeItems.length) { - renderCollectionItemType(page, type, typeItems, user); + renderCollectionItemType(page, parentItem, type, typeItems, user); } } @@ -977,17 +980,17 @@ }); if (otherTypeItems.length) { - renderCollectionItemType(page, otherType, otherTypeItems, user); + renderCollectionItemType(page, parentItem, otherType, otherTypeItems, user); } if (!items.length) { - renderCollectionItemType(page, { name: Globalize.translate('HeaderItems') }, items, user); + renderCollectionItemType(page, parentItem, { name: Globalize.translate('HeaderItems') }, items, user); } $('.collectionItems', page).createCardMenus(); } - function renderCollectionItemType(page, type, items, user, context) { + function renderCollectionItemType(page, parentItem, type, items, user, context) { var html = ''; @@ -996,10 +999,6 @@ html += '

'; html += '' + type.name + ''; - if (user.Policy.IsAdministrator) { - html += ''; - } - html += '

'; html += '
'; @@ -1013,7 +1012,10 @@ centerText: true, context: context, lazy: true, - showDetailsMenu: true + showDetailsMenu: true, + overlayMoreButton: true, + showAddToCollection: false, + showRemoveFromCollection: true }); html += '
'; @@ -1022,6 +1024,31 @@ var collectionItems = page.querySelector('.collectionItems'); $(collectionItems).append(html); ImageLoader.lazyChildren(collectionItems); + + $(collectionItems).off('removefromcollection').on('removefromcollection', function (e, itemId) { + + removeFromCollection(page, parentItem, [itemId], user, context); + }); + } + + function removeFromCollection(page, parentItem, itemIds, user, context) { + + Dashboard.showLoadingMsg(); + + var url = ApiClient.getUrl("Collections/" + parentItem.Id + "/Items", { + + Ids: itemIds.join(',') + }); + + ApiClient.ajax({ + type: "DELETE", + url: url + + }).done(function () { + + renderChildren(page, parentItem, user, context); + Dashboard.hideLoadingMsg(); + }); } function renderUserDataIcons(page, item) { diff --git a/dashboard-ui/scripts/librarybrowser.js b/dashboard-ui/scripts/librarybrowser.js index 0238fdf3c5..8881aa83a5 100644 --- a/dashboard-ui/scripts/librarybrowser.js +++ b/dashboard-ui/scripts/librarybrowser.js @@ -1375,8 +1375,14 @@ } } - if (BoxSetEditor.supportsAddingToCollection(item)) { - itemCommands.push('addtocollection'); + if (options.showAddToCollection !== false) { + if (BoxSetEditor.supportsAddingToCollection(item)) { + itemCommands.push('addtocollection'); + } + } + + if (options.showRemoveFromCollection) { + itemCommands.push('removefromcollection'); } if (options.playFromHere) { diff --git a/dashboard-ui/scripts/librarylist.js b/dashboard-ui/scripts/librarylist.js index 56d33d8470..7e32b1c47b 100644 --- a/dashboard-ui/scripts/librarylist.js +++ b/dashboard-ui/scripts/librarylist.js @@ -327,6 +327,14 @@ }); } + if (commands.indexOf('removefromcollection') != -1) { + items.push({ + name: Globalize.translate('ButtonRemoveFromCollection'), + id: 'removefromcollection', + ironIcon: 'remove' + }); + } + if (commands.indexOf('removefromplaylist') != -1) { items.push({ name: Globalize.translate('ButtonRemoveFromPlaylist'), @@ -476,6 +484,9 @@ case 'removefromplaylist': $(card).parents('.itemsContainer').trigger('removefromplaylist', [playlistItemId]); break; + case 'removefromcollection': + $(card).parents('.collectionItems').trigger('removefromcollection', [itemId]); + break; default: break; } diff --git a/dashboard-ui/scripts/mediacontroller.js b/dashboard-ui/scripts/mediacontroller.js index 0fc2d3104e..9874e6ef94 100644 --- a/dashboard-ui/scripts/mediacontroller.js +++ b/dashboard-ui/scripts/mediacontroller.js @@ -860,7 +860,6 @@ return true; } - // TODO: Need to verify the host is going to be reachable return mediaSource.Path.toLowerCase().replace('https:', 'http').indexOf(ApiClient.serverAddress().toLowerCase().replace('https:', 'http').substring(0, 14)) == 0; } diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index b1275bedf0..92bdc22cdd 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -14,7 +14,6 @@ } })(); -// TODO: Deprecated in 1.9 $.support.cors = true; $(document).one('click', WebNotifications.requestPermission); diff --git a/dashboard-ui/strings/javascript/javascript.json b/dashboard-ui/strings/javascript/javascript.json index e5d911c7f5..4c0e2cd15d 100644 --- a/dashboard-ui/strings/javascript/javascript.json +++ b/dashboard-ui/strings/javascript/javascript.json @@ -45,6 +45,7 @@ "ButtonDonate": "Donate", "LabelRecurringDonationCanBeCancelledHelp": "Recurring donations can be cancelled at any time from within your PayPal account.", "HeaderMyMedia": "My Media", + "ButtonRemoveFromCollection": "Remove from Collection", "LabelAutomaticUpdateLevel": "Automatic update level:", "LabelAutomaticUpdateLevelForPlugins": "Automatic update level for plugins:", "TitleNotifications": "Notifications",