feat: enable collections.

This commit is contained in:
redxef 2025-01-19 22:28:32 +01:00
parent 6ac75835f0
commit aa6fed146d
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
8 changed files with 172 additions and 12 deletions

View file

@ -54,7 +54,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Api {
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult> SetCollection([FromRoute, Required] CollectionId collectionId, [FromBody] SmartCollectionDto smartCollection) {
public async Task<ActionResult> SetCollection([FromBody, Required] SmartCollectionDto smartCollection) {
await _store.SaveSmartCollectionAsync(smartCollection);
return Created();
}

View file

@ -279,7 +279,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
IList<Expression> r = new List<Expression>();
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_));

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SmartCollection</title>
</head>
<body>
<div id="SmartCollectionConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox" data-controller="__plugin/smartCollections.js">
<div data-role="content">
<div class="content-primary">
<form id="SmartCollectionConfigForm">
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartcollectionSelection">Choose a collection to edit</label>
<select id="SmartcollectionSelection" class="emby-select">
</select>
</div>
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartcollectionEditName">Name</label>
<input id="SmartcollectionEditName" type="text" class="emby-input"/>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartcollectionEditProgram">Program</label>
<div class="fieldDescription">A program which should return <code>t</code> or <code>nil</code> to include or exclude the provided <code>item</code>.</div>
<textarea id="SmartcollectionEditProgram" class="emby-input smartcollection-monospace" name="Program" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartcollectionEditSortProgram">Sort Program</label>
<div class="fieldDescription">A program which should return a list of items to include in the collection, sorted however you like.</div>
<textarea id="SmartcollectionEditSortProgram" class="emby-input smartcollection-monospace" name="SortProgram" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartcollectionEditEnabled">Enabled</label>
<div class="fieldDescription">Is the collection enabled.</div>
<input id="SmartcollectionEditEnabled" type="checkbox" class="emby-input"/>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
<style>
.smartcollection-monospace {
font-family: monospace;
}
</style>
</div>
</body>
</html>

View file

@ -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();
});

View file

@ -35,7 +35,7 @@
</select>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartPlaylistEditEnabled">Enabled</label>
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditEnabled">Enabled</label>
<div class="fieldDescription">Is the playlist enabled.</div>
<input id="SmartplaylistEditEnabled" type="checkbox" class="emby-input"/>
</div>

View file

@ -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();
})
});

View file

@ -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",

View file

@ -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);
}
}
}