From aa6fed146db1f71e4c584a7c957781d7ccc39b8a Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 19 Jan 2025 22:28:32 +0100 Subject: [PATCH] feat: enable collections. --- .../Api/SmartCollectionController.cs | 2 +- .../Lisp/Interpreter.cs | 2 +- .../Pages/smartCollections.html | 51 ++++++++ .../Pages/smartCollections.js | 111 ++++++++++++++++++ .../Pages/smartPlaylists.html | 2 +- .../Pages/smartPlaylists.js | 8 +- Jellyfin.Plugin.SmartPlaylist/Plugin.cs | 4 +- .../ScheduledTasks/GenerateCollection.cs | 4 +- 8 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.html create mode 100644 Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.js diff --git a/Jellyfin.Plugin.SmartPlaylist/Api/SmartCollectionController.cs b/Jellyfin.Plugin.SmartPlaylist/Api/SmartCollectionController.cs index 4a597a1..da0f62d 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Api/SmartCollectionController.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Api/SmartCollectionController.cs @@ -54,7 +54,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Api { [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] - public async Task SetCollection([FromRoute, Required] CollectionId collectionId, [FromBody] SmartCollectionDto smartCollection) { + public async Task SetCollection([FromBody, Required] SmartCollectionDto smartCollection) { await _store.SaveSmartCollectionAsync(smartCollection); return Created(); } diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs index 7027c09..c713d79 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs @@ -279,7 +279,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { IList r = new List(); MethodInfo? mi = o.Value().GetType().GetMethod(s.Value(), l_types); if (mi == null) { - throw new ApplicationException($"{o.Value()} has no method {s.Value()}"); + throw new ApplicationException($"{o.Value()} ({o.Value().GetType()}) has no method {s.Value()}"); } return Object.FromBase(mi.Invoke(o.Value(), l_)); diff --git a/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.html b/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.html new file mode 100644 index 0000000..15c50c6 --- /dev/null +++ b/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.html @@ -0,0 +1,51 @@ + + + + + SmartCollection + + +
+
+
+
+
+ + +
+
+ + +
+
+ +
A program which should return t or nil to include or exclude the provided item.
+ +
+
+ +
A program which should return a list of items to include in the collection, sorted however you like.
+ +
+
+ +
Is the collection enabled.
+ +
+
+ +
+
+
+
+ +
+ + diff --git a/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.js b/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.js new file mode 100644 index 0000000..8b9aadb --- /dev/null +++ b/Jellyfin.Plugin.SmartPlaylist/Pages/smartCollections.js @@ -0,0 +1,111 @@ + +var COLLECTIONS = [ + { + Id: 'My New Smartcollection', + Name: 'My New Smartcollection', + Program: '(is-favourite)', + SortProgram: '(begin *items*)', + CollectionId: '00000000000000000000000000000000', + Enabled: true, + } +]; + +function fillForm(collection) { + const editName = document.querySelector('#SmartcollectionEditName'); + const editProgram = document.querySelector('#SmartcollectionEditProgram'); + const editSortProgram = document.querySelector('#SmartcollectionEditSortProgram'); + const editEnabled = document.querySelector('#SmartcollectionEditEnabled'); + editName.value = collection.Name; + editProgram.value = collection.Program; + editSortProgram.value = collection.SortProgram; + editEnabled.checked = collection.Enabled; +} + +function fillCollectionSelect(collections) { + const selection = document.querySelector('#SmartcollectionSelection'); + selection.innerHTML = ''; + var o = document.createElement('option'); + o.value = null; + o.innerHTML = 'Create new collection ...'; + selection.appendChild(o); + for (const i of collections.slice(1)) { + var o = document.createElement('option'); + o.value = i.Id; + o.innerHTML = i.Name; + selection.appendChild(o); + } +} + +function jsonFromForm(collectionId) { + const editName = document.querySelector('#SmartcollectionEditName'); + const editProgram = document.querySelector('#SmartcollectionEditProgram'); + const editSortProgram = document.querySelector('#SmartcollectionEditSortProgram'); + const editEnabled = document.querySelector('#SmartcollectionEditEnabled'); + if (collectionId === null) { + collectionId = '00000000000000000000000000000000' + } + return { + Id: editName.value, + Name: editName.value, + Program: editProgram.value, + SortProgram: editSortProgram.value, + CollectionId: collectionId, + Enabled: editEnabled.checked, + }; +} + +ApiClient.getSmartCollections = function () { + const url = ApiClient.getUrl('SmartCollection'); + return this.ajax({ + type: 'GET', + url: url, + dataType: 'json', + }); +} + +ApiClient.setSmartCollection = function (c) { + const url = ApiClient.getUrl('SmartCollection'); + return this.ajax({ + type: 'POST', + url: url, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + data: JSON.stringify(c), + }); +} + +document.querySelector('#SmartCollectionConfigPage') + .addEventListener('viewshow', function() { + Dashboard.showLoadingMsg(); + ApiClient.getSmartCollections().then(function (collections) { + COLLECTIONS = [COLLECTIONS[0]].concat(collections); + const selection = document.querySelector('#SmartcollectionSelection'); + fillCollectionSelect(COLLECTIONS); + fillForm(COLLECTIONS[selection.selectedIndex]); + Dashboard.hideLoadingMsg(); + }); + }); + +document.querySelector('#SmartcollectionSelection') + .addEventListener('change', function() { + const selection = document.querySelector('#SmartcollectionSelection'); + fillForm(COLLECTIONS[selection.selectedIndex]); + }); + +document.querySelector('#SmartCollectionConfigForm') + .addEventListener('submit', function (e) { + Dashboard.showLoadingMsg(); + const selection = document.querySelector('#SmartcollectionSelection'); + const selectedid = COLLECTIONS[selection.selectedIndex].Id; + ApiClient.setSmartCollection(jsonFromForm(COLLECTIONS[selection.selectedIndex].CollectionId)).then(function () { + ApiClient.getSmartCollections().then(function (collections) { + COLLECTIONS = [COLLECTIONS[0]].concat(collections); + fillCollectionSelect(COLLECTIONS); + const idx = COLLECTIONS.map(x => x.Id).indexOf(selectedid); + selection.selectedIndex = idx; + fillForm(COLLECTIONS[selection.selectedIndex]); + Dashboard.hideLoadingMsg(); + }); + }); + e.preventDefault(); + }); diff --git a/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.html b/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.html index 2d93b39..5720218 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.html +++ b/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.html @@ -35,7 +35,7 @@
- +
Is the playlist enabled.
diff --git a/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.js b/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.js index a0fb77b..7a0d677 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.js +++ b/Jellyfin.Plugin.SmartPlaylist/Pages/smartPlaylists.js @@ -98,7 +98,7 @@ ApiClient.setSmartPlaylist = function (p) { } document.querySelector('#SmartPlaylistConfigPage') - .addEventListener('pageshow', function() { + .addEventListener('viewshow', function() { Dashboard.showLoadingMsg(); ApiClient.getSmartPlaylists().then(function (playlists) { PLAYLISTS = [PLAYLISTS[0]].concat(playlists); @@ -106,8 +106,6 @@ document.querySelector('#SmartPlaylistConfigPage') USERS = users; const selection = document.querySelector('#SmartplaylistSelection'); fillPlaylistSelect(PLAYLISTS); - console.log('selectedIndex =', selection.selectedIndex); - console.log('selectedIndex =', PLAYLISTS[selection.selectedIndex]); fillForm(PLAYLISTS[selection.selectedIndex], USERS); Dashboard.hideLoadingMsg(); }); @@ -117,7 +115,6 @@ document.querySelector('#SmartPlaylistConfigPage') document.querySelector('#SmartplaylistSelection') .addEventListener('change', function() { const selection = document.querySelector('#SmartplaylistSelection'); - console.log('p =', PLAYLISTS[selection.selectedIndex]); fillForm(PLAYLISTS[selection.selectedIndex], USERS); }); @@ -140,5 +137,4 @@ document.querySelector('#SmartPlaylistConfigForm') }); }); e.preventDefault(); - }) - + }); diff --git a/Jellyfin.Plugin.SmartPlaylist/Plugin.cs b/Jellyfin.Plugin.SmartPlaylist/Plugin.cs index 12046e3..0d962f6 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Plugin.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Plugin.cs @@ -35,6 +35,7 @@ namespace Jellyfin.Plugin.SmartPlaylist { }, new PluginPageInfo { Name = "Smart Playlists", + DisplayName = "Smart Playlists", EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartPlaylists.html", GetType().Namespace), EnableInMainMenu = true, }, @@ -44,8 +45,9 @@ namespace Jellyfin.Plugin.SmartPlaylist { }, new PluginPageInfo { Name = "Smart Collections", + DisplayName = "Smart Collections", EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartCollections.html", GetType().Namespace), - //EnableInMainMenu = true, + EnableInMainMenu = true, }, new PluginPageInfo { Name = "smartCollections.js", diff --git a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GenerateCollection.cs b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GenerateCollection.cs index fb672a5..d450d5b 100644 --- a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GenerateCollection.cs +++ b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GenerateCollection.cs @@ -101,8 +101,8 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) { throw new ArgumentException(""); } - var existingItems = collection.Children; - await _collectionManager.RemoveFromCollectionAsync(collectionId, existingItems.Select(x => x.Id)); + var existingItems = collection.LinkedChildren.Select(x => x.ItemId).Where(x => x != null).Select(x => x.Value); + await _collectionManager.RemoveFromCollectionAsync(collectionId, existingItems); } } }