feat: move smart playlist configuration into own menu.

This commit is contained in:
redxef 2025-01-19 21:49:23 +01:00
parent f5448e8a51
commit 6ac75835f0
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
12 changed files with 422 additions and 258 deletions

View file

@ -0,0 +1,62 @@
using System.Net.Mime;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Providers;
using System.ComponentModel.DataAnnotations;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Common.Api;
namespace Jellyfin.Plugin.SmartPlaylist.Api {
[ApiController]
[Authorize(Policy = Policies.RequiresElevation)]
[Route("SmartCollection")]
[Produces(MediaTypeNames.Application.Json)]
public class SmartCollectionController : ControllerBase {
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
private readonly ICollectionManager _collectionManager;
private readonly IStore _store;
public SmartCollectionController(
ILogger<SmartCollectionController> logger,
ILibraryManager libraryManager,
IUserManager userManager,
IProviderManager providerManager,
IFileSystem fileSystem,
ICollectionManager collectionManager,
IServerApplicationPaths serverApplicationPaths
) {
_logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
_collectionManager = collectionManager;
_store = new Store(new SmartFileSystem(serverApplicationPaths));
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IReadOnlyList<SmartCollectionDto>>> GetCollections() {
return Ok(await _store.GetAllSmartCollectionsAsync());
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult> SetCollection([FromRoute, Required] CollectionId collectionId, [FromBody] SmartCollectionDto smartCollection) {
await _store.SaveSmartCollectionAsync(smartCollection);
return Created();
}
}
}

View file

@ -0,0 +1,61 @@
using System.Net.Mime;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using System.ComponentModel.DataAnnotations;
using MediaBrowser.Common.Api;
namespace Jellyfin.Plugin.SmartPlaylist.Api {
[ApiController]
[Authorize(Policy = Policies.RequiresElevation)]
[Route("SmartPlaylist")]
[Produces(MediaTypeNames.Application.Json)]
public class SmartPlaylistController : ControllerBase {
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
private readonly IPlaylistManager _playlistManager;
private readonly IStore _store;
public SmartPlaylistController(
ILogger<SmartPlaylistController> logger,
ILibraryManager libraryManager,
IUserManager userManager,
IProviderManager providerManager,
IFileSystem fileSystem,
IPlaylistManager playlistManager,
IServerApplicationPaths serverApplicationPaths
) {
_logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
_playlistManager = playlistManager;
_store = new Store(new SmartFileSystem(serverApplicationPaths));
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IReadOnlyList<SmartPlaylistDto>>> GetPlaylists() {
return Ok(await _store.GetAllSmartPlaylistsAsync());
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult> SetPlaylist([FromBody, Required] SmartPlaylistDto smartPlaylist) {
await _store.SaveSmartPlaylistAsync(smartPlaylist);
return Created();
}
}
}

View file

@ -42,45 +42,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
(define find-parent (lambda (typename) (invoke-generic *item* "FindParent" nil (list typename)))) (define find-parent (lambda (typename) (invoke-generic *item* "FindParent" nil (list typename))))
(define find-artist (lambda nil (find-parent "MediaBrowser.Controller.Entities.Audio.MusicArtist, MediaBrowser.Controller")))) (define find-artist (lambda nil (find-parent "MediaBrowser.Controller.Entities.Audio.MusicArtist, MediaBrowser.Controller"))))
"""; """;
store = new Store(new SmartFileSystem(Plugin.Instance.ServerApplicationPaths));
} }
private Store store { get; set; }
public string InitialProgram { get; set; } public string InitialProgram { get; set; }
public SmartPlaylistDto[] Playlists {
get {
return store.GetAllSmartPlaylistsAsync().GetAwaiter().GetResult();
}
set {
var existing = store.GetAllSmartPlaylistsAsync().GetAwaiter().GetResult().Select(x => x.Id).ToList();
foreach (var p in value) {
existing.Remove(p.Id);
store.SaveSmartPlaylistAsync(p).GetAwaiter().GetResult();
}
foreach (var p in existing) {
store.DeleteSmartPlaylistById(p);
}
}
}
public SmartCollectionDto[] Collections {
get {
return store.GetAllSmartCollectionsAsync().GetAwaiter().GetResult();
}
set {
var existing = store.GetAllSmartCollectionsAsync().GetAwaiter().GetResult().Select(x => x.Id).ToList();
foreach (var p in value) {
existing.Remove(p.Id);
store.SaveSmartCollectionAsync(p).GetAwaiter().GetResult();
}
foreach (var p in existing) {
store.DeleteSmartCollectionById(p);
}
}
}
public object[][] Users {
get {
return Plugin.Instance.UserManager.Users.Select(x => new object[]{x.Id, x.Username}).ToArray();
}
set { }
}
} }
} }

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SmartPlaylist</title>
</head>
<body>
<div id="SmartPlaylistConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox" data-controller="__plugin/configPage.js">
<div data-role="content">
<div class="content-primary">
<form id="SmartPlaylistConfigForm">
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="InitialProgram">Initial Program</label>
<div class="fieldDescription">A program which can set up the environment</div>
<textarea id="InitialProgram" class="emby-input smartplaylist-monospace" name="InitialProgram" rows="16" cols="120"></textarea>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
<style>
.smartplaylist-monospace {
font-family: monospace;
}
</style>
</div>
</body>
</html>

View file

@ -0,0 +1,23 @@
var SmartPlaylistConfig = {
pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df'
};
document.querySelector('#SmartPlaylistConfigPage')
.addEventListener('pageshow', function() {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
document.querySelector('#InitialProgram').value = config.InitialProgram;
Dashboard.hideLoadingMsg();
});
});
document.querySelector('#SmartPlaylistConfigForm')
.addEventListener('submit', function(e) {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
config.InitialProgram = document.querySelector('#InitialProgram').value;
});
e.preventDefault();
return false;
});

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SmartPlaylist</title>
</head>
<body>
<div id="SmartPlaylistConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox" data-controller="__plugin/smartPlaylists.js">
<div data-role="content">
<div class="content-primary">
<form id="SmartPlaylistConfigForm">
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistSelection">Choose a playlist to edit</label>
<select id="SmartplaylistSelection" class="emby-select">
</select>
</div>
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditName">Name</label>
<input id="SmartplaylistEditName" type="text" class="emby-input"/>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditProgram">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="SmartplaylistEditProgram" class="emby-input smartplaylist-monospace" name="Program" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditSortProgram">Sort Program</label>
<div class="fieldDescription">A program which should return a list of items to include in the playlist, sorted however you like.</div>
<textarea id="SmartplaylistEditSortProgram" class="emby-input smartplaylist-monospace" name="SortProgram" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditUsers">Users</label>
<div class="fieldDescription">Which users should get access to the playlist.</div>
<select multiple id="SmartplaylistEditUsers" class="emby-select">
</select>
</div>
<div class="inputContainer">
<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>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
<style>
.smartplaylist-monospace {
font-family: monospace;
}
</style>
</div>
</body>
</html>

View file

@ -0,0 +1,144 @@
var PLAYLISTS = [
{
Id: 'My New Smartplaylist',
Name: 'My New Smartplaylist',
Program: '(is-favourite)',
SortProgram: '(begin *items*)',
Playlists: [],
Enabled: true,
}
];
var USERS = [];
function fillForm(playlist, users) {
const editName = document.querySelector('#SmartplaylistEditName');
const editProgram = document.querySelector('#SmartplaylistEditProgram');
const editSortProgram = document.querySelector('#SmartplaylistEditSortProgram');
const editUsers = document.querySelector('#SmartplaylistEditUsers');
const editEnabled = document.querySelector('#SmartplaylistEditEnabled');
editName.value = playlist.Name;
editProgram.value = playlist.Program;
editSortProgram.value = playlist.SortProgram;
editUsers.innerHTML = '';
var added_users = []
for (const p of playlist.Playlists) {
var o = document.createElement('option');
o.value = btoa(JSON.stringify(p));
o.innerHTML = users.filter(u => u.Id == p.UserId).map(u => u.Name)[0];
o.setAttribute('selected', 'selected');
editUsers.appendChild(o);
added_users.push(p.UserId);
}
for (const u of users) {
if (added_users.includes(u.Id)) {
continue;
}
var o = document.createElement('option');
o.value = btoa(JSON.stringify({
'PlaylistId': '00000000000000000000000000000000',
'UserId': u.Id,
}));
o.innerHTML = u.Name;
editUsers.appendChild(o);
}
editEnabled.checked = playlist.Enabled;
}
function fillPlaylistSelect(playlists) {
const selection = document.querySelector('#SmartplaylistSelection');
selection.innerHTML = '';
var o = document.createElement('option');
o.value = null;
o.innerHTML = 'Create new playlist ...';
selection.appendChild(o);
for (const i of playlists.slice(1)) {
var o = document.createElement('option');
o.value = i.Id;
o.innerHTML = i.Name;
selection.appendChild(o);
}
}
function jsonFromForm() {
const editName = document.querySelector('#SmartplaylistEditName');
const editProgram = document.querySelector('#SmartplaylistEditProgram');
const editSortProgram = document.querySelector('#SmartplaylistEditSortProgram');
const editUsers = document.querySelector('#SmartplaylistEditUsers');
const editEnabled = document.querySelector('#SmartplaylistEditEnabled');
return {
Id: editName.value,
Name: editName.value,
Program: editProgram.value,
SortProgram: editSortProgram.value,
Playlists: Array.from(editUsers.options).filter((x) => x.selected).map(x => JSON.parse(atob(x.value))),
Enabled: editEnabled.checked,
};
}
ApiClient.getSmartPlaylists = function () {
const url = ApiClient.getUrl('SmartPlaylist');
return this.ajax({
type: 'GET',
url: url,
dataType: 'json',
});
}
ApiClient.setSmartPlaylist = function (p) {
const url = ApiClient.getUrl('SmartPlaylist');
return this.ajax({
type: 'POST',
url: url,
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify(p),
});
}
document.querySelector('#SmartPlaylistConfigPage')
.addEventListener('pageshow', function() {
Dashboard.showLoadingMsg();
ApiClient.getSmartPlaylists().then(function (playlists) {
PLAYLISTS = [PLAYLISTS[0]].concat(playlists);
ApiClient.getUsers().then(function (users) {
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();
});
});
});
document.querySelector('#SmartplaylistSelection')
.addEventListener('change', function() {
const selection = document.querySelector('#SmartplaylistSelection');
console.log('p =', PLAYLISTS[selection.selectedIndex]);
fillForm(PLAYLISTS[selection.selectedIndex], USERS);
});
document.querySelector('#SmartPlaylistConfigForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg();
const selection = document.querySelector('#SmartplaylistSelection');
const selectedid = PLAYLISTS[selection.selectedIndex].Id;
ApiClient.setSmartPlaylist(jsonFromForm()).then(function () {
ApiClient.getSmartPlaylists().then(function (playlists) {
PLAYLISTS = [PLAYLISTS[0]].concat(playlists);
ApiClient.getUsers().then(function (users) {
USERS = users;
fillPlaylistSelect(PLAYLISTS);
const idx = PLAYLISTS.map(x => x.Id).indexOf(selectedid);
selection.selectedIndex = idx;
fillForm(PLAYLISTS[selection.selectedIndex], USERS);
Dashboard.hideLoadingMsg();
});
});
});
e.preventDefault();
})

View file

@ -27,8 +27,30 @@ namespace Jellyfin.Plugin.SmartPlaylist {
return new[] { return new[] {
new PluginPageInfo { new PluginPageInfo {
Name = this.Name, Name = this.Name,
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.configPage.html", GetType().Namespace) EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace),
} },
new PluginPageInfo {
Name = "configPage.js",
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.js", GetType().Namespace),
},
new PluginPageInfo {
Name = "Smart Playlists",
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartPlaylists.html", GetType().Namespace),
EnableInMainMenu = true,
},
new PluginPageInfo {
Name = "smartPlaylists.js",
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartPlaylists.js", GetType().Namespace),
},
new PluginPageInfo {
Name = "Smart Collections",
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartCollections.html", GetType().Namespace),
//EnableInMainMenu = true,
},
new PluginPageInfo {
Name = "smartCollections.js",
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartCollections.js", GetType().Namespace),
},
}; };
} }
} }

View file

@ -1,4 +1,3 @@
using System.Globalization;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@ -6,12 +5,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.Collections;
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {

View file

@ -7,15 +7,12 @@ using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Playlists;
using Jellyfin.Plugin.SmartPlaylist.Lisp; using Jellyfin.Plugin.SmartPlaylist.Lisp;
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object;
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
@ -78,30 +75,30 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); // parse here, so that we don't repeat the work for each item Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); // parse here, so that we don't repeat the work for each item
Executor executor = SetupExecutor(); Executor executor = SetupExecutor();
executor.environment.Set("*user*", Lisp_Object.FromBase(user)); executor.environment.Set("*user*", Lisp.Object.FromBase(user));
foreach (var i in items) { foreach (var i in items) {
executor.environment.Set("*item*", Lisp_Object.FromBase(i)); executor.environment.Set("*item*", Lisp.Object.FromBase(i));
var r = executor.eval(expression); var r = executor.eval(expression);
_logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString()); _logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString());
if ((r is not Lisp_Boolean r_bool) || (r_bool.Value())) { if ((r is not Lisp.Boolean r_bool) || (r_bool.Value())) {
_logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name); _logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name);
results.Add(i); results.Add(i);
} }
} }
executor = SetupExecutor(); executor = SetupExecutor();
executor.environment.Set("*user*", Lisp_Object.FromBase(user)); executor.environment.Set("*user*", Lisp.Object.FromBase(user));
executor.environment.Set("*items*", Lisp_Object.FromBase(results)); executor.environment.Set("*items*", Lisp.Object.FromBase(results));
results = new List<BaseItem>(); results = new List<BaseItem>();
var sort_result = executor.eval(smartPlaylist.SortProgram); var sort_result = executor.eval(smartPlaylist.SortProgram);
if (sort_result is Cons sorted_items) { if (sort_result is Cons sorted_items) {
foreach (var i in sorted_items.ToList()) { foreach (var i in sorted_items.ToList()) {
if (i is Lisp_Object iObject && iObject.Value() is BaseItem iBaseItem) { if (i is Lisp.Object iObject && iObject.Value() is BaseItem iBaseItem) {
results.Add(iBaseItem); results.Add(iBaseItem);
continue; continue;
} }
throw new ApplicationException($"Returned sorted list does contain unexpected items, got {i}"); throw new ApplicationException($"Returned sorted list does contain unexpected items, got {i}");
} }
} else if (sort_result == Lisp_Boolean.FALSE) { } else if (sort_result == Lisp.Boolean.FALSE) {
} else { } else {
throw new ApplicationException($"Did not return a list of items, returned {sort_result}"); throw new ApplicationException($"Did not return a list of items, returned {sort_result}");
} }

View file

@ -1,201 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SmartPlaylist</title>
</head>
<body>
<div id="SmartPlaylistConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<div data-role="content">
<div class="content-primary">
<form id="SmartPlaylistConfigForm">
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="InitialProgram">Initial Program</label>
<div class="fieldDescription">A program which can set up the environment</div>
<textarea id="InitialProgram" class="emby-input smartplaylist-monospace" name="InitialProgram" rows="16" cols="120"></textarea>
</div>
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistSelection">Choose a playlist to edit</label>
<select id="SmartplaylistSelection" class="emby-select">
</select>
</div>
<div>
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditName">Name</label>
<input id="SmartplaylistEditName" type="text" class="emby-input"/>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditProgram">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="SmartplaylistEditProgram" class="emby-input smartplaylist-monospace" name="Program" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditSortProgram">Sort Program</label>
<div class="fieldDescription">A program which should return a list of items to include in the playlist, sorted however you like.</div>
<textarea id="SmartplaylistEditSortProgram" class="emby-input smartplaylist-monospace" name="SortProgram" rows="16" cols="120"></textarea>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="SmartplaylistEditUsers">Users</label>
<div class="fieldDescription">Which users should get access to the playlist.</div>
<select multiple id="SmartplaylistEditUsers" class="emby-select">
</select>
</div>
<div class="inputContainer">
<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>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
<style>
.smartplaylist-monospace {
font-family: monospace;
}
</style>
<script type="text/javascript">
var SmartPlaylistConfig = {
pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df'
};
function changeEditBox(config, id) {
const selection = document.querySelector('#SmartplaylistSelection');
const editName = document.querySelector('#SmartplaylistEditName');
const editProgram = document.querySelector('#SmartplaylistEditProgram');
const editSortProgram = document.querySelector('#SmartplaylistEditSortProgram');
const editUsers = document.querySelector('#SmartplaylistEditUsers');
const editEnabled = document.querySelector('#SmartplaylistEditEnabled');
if (id === null) {
selection.selectedIndex = 0;
editName.value = 'My New Smartplaylist';
editProgram.value = '(is-favourite)';
editSortProgram.value = '(begin *items*)';
editUsers.innerHTML = '';
for (const u of config.Users) {
var o = document.createElement('option');
o.value = u[0];
o.innerHTML = u[1];
o.setAttribute('selected', 'selected');
editUsers.appendChild(o);
}
editEnabled.checked = true;
return;
}
function matchId(p) {
return p.Id == id;
}
const index = config.Playlists.map(function (x) { return x.Id }).indexOf(id);
selection.selectedIndex = index + 1;
const p = config.Playlists[index];
editName.value = p.Name;
editProgram.value = p.Program;
editSortProgram.value = p.SortProgram;
editUsers.innerHTML = '';
for (const u of config.Users) {
var o = document.createElement('option');
o.value = u[0];
o.innerHTML = u[1];
if (p.Playlists.map((x) => x.UserId).includes(u[0])) {
o.setAttribute('selected', 'selected');
}
editUsers.appendChild(o);
}
editEnabled.checked = p.Enabled;
}
function fillPlaylistSelect(config) {
const selection = document.querySelector('#SmartplaylistSelection');
selection.innerHTML = '';
var o = document.createElement('option');
o.value = null;
o.innerHTML = 'Create new playlist ...';
selection.appendChild(o);
for (const i of config.Playlists) {
var o = document.createElement('option');
o.value = i.Id;
o.innerHTML = i.Name;
selection.appendChild(o);
}
}
document.querySelector('#SmartPlaylistConfigPage')
.addEventListener('pageshow', function() {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
document.querySelector('#InitialProgram').value = config.InitialProgram;
fillPlaylistSelect(config);
changeEditBox(config, (config.Playlists.length > 0) ? config.Playlists[0].Id : null);
Dashboard.hideLoadingMsg();
});
});
document.querySelector('#SmartplaylistSelection')
.addEventListener('change', function() {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
const selection = document.querySelector('#SmartplaylistSelection');
changeEditBox(config, (selection.selectedIndex > 0) ? config.Playlists[selection.selectedIndex - 1].Id : null);
Dashboard.hideLoadingMsg();
});
});
document.querySelector('#SmartPlaylistConfigForm')
.addEventListener('submit', function(e) {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
config.InitialProgram = document.querySelector('#InitialProgram').value;
const selection = document.querySelector('#SmartplaylistSelection');
const editName = document.querySelector('#SmartplaylistEditName');
const editProgram = document.querySelector('#SmartplaylistEditProgram');
const editSortProgram = document.querySelector('#SmartplaylistEditSortProgram');
const editUsers = document.querySelector('#SmartplaylistEditUsers');
const editEnabled = document.querySelector('#SmartplaylistEditEnabled');
var index = selection.selectedIndex;
if (index === 0) {
const o = {
Id: editName.value,
Name: editName.value,
Program: editProgram.value,
SortProgram: editSortProgram.value,
Playlists: Array.from(editUsers.options).filter((x) => x.selected).map((x) => {
const m = {UserId: x.value, PlaylistId: "00000000-0000-0000-0000-000000000000"};
return m;
}),
Enabled: editEnabled.checked,
};
config.Playlists.push(o);
} else {
config.Playlists[index-1].Id = editName.value;
config.Playlists[index-1].Name = editName.value;
config.Playlists[index-1].Program = editProgram.value;
config.Playlists[index-1].SortProgram = editSortProgram.value;
config.Playlists[index-1].Playlists = Array.from(editUsers.options).filter((x) => x.selected).map((x) => {
const existing = config.Playlists[index-1].Playlists.filter((x_) => x_.UserId === x.value).map((x_) => x_.PlaylistId);
const m = {UserId: x.value, PlaylistId: ((existing.length > 0) ? existing[0] : "00000000-0000-0000-0000-000000000000")};
return m;
}),
config.Playlists[index-1].Enabled = editEnabled.checked;
}
ApiClient.updatePluginConfiguration(SmartPlaylistConfig.pluginUniqueId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result);
ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
document.querySelector('#InitialProgram').value = config.InitialProgram;
fillPlaylistSelect(config);
changeEditBox(config, (config.Playlists.length > 0) ? config.Playlists[0].Id : null);
Dashboard.hideLoadingMsg();
});
});
});
e.preventDefault();
return false;
});
</script>
</div>
</body>
</html>

View file

@ -15,8 +15,18 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="configPage.html"/> <None Remove="Configuration\configPage.html"/>
<EmbeddedResource Include="configPage.html"/> <None Remove="Configuration\configPage.js"/>
<None Remove="Pages\smartPlaylists.html"/>
<None Remove="Pages\smartPlaylists.js"/>
<None Remove="Pages\smartCollections.html"/>
<None Remove="Pages\smartCollections.js"/>
<EmbeddedResource Include="Configuration\configPage.html"/>
<EmbeddedResource Include="Configuration\configPage.js"/>
<EmbeddedResource Include="Pages\smartPlaylists.html"/>
<EmbeddedResource Include="Pages\smartPlaylists.js"/>
<EmbeddedResource Include="Pages\smartCollections.html"/>
<EmbeddedResource Include="Pages\smartCollections.js"/>
</ItemGroup> </ItemGroup>
</Project> </Project>