add trakt playlist option (#2171)

* add generate playlist option; add system playlists

* fix official lists; sync items to playlist
This commit is contained in:
Jason Dove
2025-07-19 16:56:25 +00:00
committed by GitHub
parent 70fbd4c746
commit 867c88d8fc
38 changed files with 23963 additions and 51 deletions
+17 -14
View File
@@ -13,22 +13,22 @@
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%; align-items: center" class="ml-6 mr-6">
<div class="d-none d-md-flex">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveChanges" StartIcon="@Icons.Material.Filled.Save">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@SaveChanges" StartIcon="@Icons.Material.Filled.Save" Disabled="@(_playlist.IsSystem)">
Save Playlist
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="AddPlaylistItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="@AddPlaylistItem" StartIcon="@Icons.Material.Filled.PlaylistAdd" Disabled="@(_playlist.IsSystem)">
Add Playlist Item
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Secondary" OnClick="PreviewPlayout" StartIcon="@Icons.Material.Filled.Preview">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Secondary" OnClick="@PreviewPlayout" StartIcon="@Icons.Material.Filled.Preview">
Preview Playlist Playout
</MudButton>
</div>
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Playlist" OnClick="SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Playlist Item" OnClick="AddPlaylistItem"/>
<MudMenuItem Icon="@Icons.Material.Filled.Preview" Label="Preview Playlist Playout" OnClick="PreviewPlayout"/>
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Playlist" OnClick="@SaveChanges" Disabled="@(_playlist.IsSystem)"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Playlist Item" OnClick="@AddPlaylistItem" Disabled="@(_playlist.IsSystem)"/>
<MudMenuItem Icon="@Icons.Material.Filled.Preview" Label="Preview Playlist Playout" OnClick="@PreviewPlayout"/>
</MudMenu>
</div>
</div>
@@ -41,7 +41,7 @@
<div class="d-flex">
<MudText>Name</MudText>
</div>
<MudTextField @bind-Value="_playlist.Name" For="@(() => _playlist.Name)"/>
<MudTextField @bind-Value="_playlist.Name" For="@(() => _playlist.Name)" Disabled="@(_playlist.IsSystem)"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Playlist Items</MudText>
<MudDivider Class="mb-6"/>
@@ -81,32 +81,34 @@
</MudText>
</MudTd>
<MudTd DataLabel="Play All">
<MudCheckBox T="bool" Value="@context.PlayAll" ValueChanged="@(e => UpdatePlayAll(context, e))"/>
<MudCheckBox T="bool" Value="@context.PlayAll" ValueChanged="@(e => UpdatePlayAll(context, e))" Disabled="@(_playlist.IsSystem)"/>
</MudTd>
<MudTd DataLabel="Show In EPG">
<MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))"/>
<MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))" Disabled="@(_playlist.IsSystem)"/>
</MudTd>
<MudTd>
<div class="d-flex">
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy"
OnClick="@(_ => CopyItem(context))">
OnClick="@(_ => CopyItem(context))"
Disabled="@(_playlist.IsSystem)">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
OnClick="@(_ => MoveItemUp(context))"
Disabled="@(_playlist.Items.All(x => x.Index >= context.Index))">
Disabled="@(_playlist.IsSystem || _playlist.Items.All(x => x.Index >= context.Index))">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
OnClick="@(_ => MoveItemDown(context))"
Disabled="@(_playlist.Items.All(x => x.Index <= context.Index))">
Disabled="@(_playlist.IsSystem || _playlist.Items.All(x => x.Index <= context.Index))">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemovePlaylistItem(context))">
OnClick="@(_ => RemovePlaylistItem(context))"
Disabled="@(_playlist.IsSystem)">
</MudIconButton>
</div>
</MudTd>
</RowTemplate>
</MudTable>
@if (_selectedItem is not null)
@if (!_playlist.IsSystem && _selectedItem is not null)
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Playlist Item</MudText>
<MudDivider Class="mb-6"/>
@@ -350,6 +352,7 @@
_playlist = new PlaylistItemsEditViewModel
{
Name = playlist.Name,
IsSystem = playlist.IsSystem,
Items = []
};
}
+21 -8
View File
@@ -72,7 +72,11 @@
{
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"media/playlists/{playlistId}")"/>
}
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="Size.Medium"
Color="Color.Inherit"
Disabled="@item.Value.IsSystem"
OnClick="@(_ => DeleteItem(item.Value))"/>
</div>
</div>
</BodyContent>
@@ -128,12 +132,9 @@
Logger.LogError("Unexpected error adding playlist group: {Error}", error.Value);
}
foreach (PlaylistGroupViewModel playlistGroup in result.RightToSeq())
foreach (PlaylistGroupViewModel _ in result.RightToSeq())
{
_treeItems.Add(new TreeItemData<PlaylistTreeItemViewModel> { Value = new PlaylistTreeItemViewModel(playlistGroup) });
_playlistGroupName = null;
_playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token);
await ReloadPlaylistTree();
await InvokeAsync(StateHasChanged);
}
}
@@ -175,7 +176,13 @@
DialogResult result = await dialog.Result;
if (result is not null && !result.Canceled)
{
await Mediator.Send(new DeletePlaylistGroup(playlistGroupId), _cts.Token);
Option<BaseError> deleteResult = await Mediator.Send(new DeletePlaylistGroup(playlistGroupId), _cts.Token);
foreach (BaseError error in deleteResult)
{
Snackbar.Add(error.ToString(), Severity.Error);
return;
}
_treeItems.RemoveAll(i => i.Value?.PlaylistGroupId == playlistGroupId);
if (_selectedPlaylistGroup?.Id == playlistGroupId)
{
@@ -196,7 +203,13 @@
DialogResult result = await dialog.Result;
if (result is not null && !result.Canceled)
{
await Mediator.Send(new DeletePlaylist(playlistId), _cts.Token);
Option<BaseError> deleteResult = await Mediator.Send(new DeletePlaylist(playlistId), _cts.Token);
foreach (BaseError error in deleteResult)
{
Snackbar.Add(error.ToString(), Severity.Error);
return;
}
foreach (PlaylistTreeItemViewModel parent in _treeItems.Map(i => i.Value))
{
parent.TreeItems.RemoveAll(i => i.Value == treeItem);
+10 -1
View File
@@ -30,6 +30,14 @@
<MudText Typo="Typo.caption" Style="font-weight: normal">Update list from trakt.tv once each day</MudText>
</MudCheckBox>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Generate Playlist</MudText>
</div>
<MudCheckBox @bind-Value="@_model.GeneratePlaylist" For="@(() => _model.GeneratePlaylist)" Dense="true">
<MudText Typo="Typo.caption" Style="font-weight: normal">Generate a playlist from the sorted trakt list items</MudText>
</MudCheckBox>
</MudStack>
</MudContainer>
</div>
</MudForm>
@@ -59,6 +67,7 @@
_model.Id = viewModel.Id;
_model.Slug = viewModel.Slug;
_model.AutoRefresh = viewModel.AutoRefresh;
_model.GeneratePlaylist = viewModel.GeneratePlaylist;
},
() => NavigationManager.NavigateTo("404"));
}
@@ -68,7 +77,7 @@
await _form.Validate();
if (_success)
{
var request = new UpdateTraktList(_model.Id, _model.AutoRefresh);
var request = new UpdateTraktList(_model.Id, _model.AutoRefresh, _model.GeneratePlaylist);
Option<BaseError> result = await Mediator.Send(request, _cts.Token);
foreach (BaseError error in result)
{
@@ -3,5 +3,6 @@
public class PlaylistItemsEditViewModel
{
public string Name { get; set; }
public bool IsSystem { get; set; }
public List<PlaylistItemEditViewModel> Items { get; set; }
}
@@ -13,6 +13,7 @@ public class PlaylistTreeItemViewModel
TreeItems = [];
PlaylistGroupId = playlistGroup.Id;
Icon = Icons.Material.Filled.Folder;
IsSystem = playlistGroup.IsSystem;
}
public PlaylistTreeItemViewModel(TreeGroupViewModel playlistGroup)
@@ -23,6 +24,7 @@ public class PlaylistTreeItemViewModel
{ Value = new PlaylistTreeItemViewModel(p) }).ToList();
PlaylistGroupId = playlistGroup.Id;
Icon = Icons.Material.Filled.Folder;
IsSystem = playlistGroup.IsSystem;
}
public PlaylistTreeItemViewModel(PlaylistViewModel playlist)
@@ -31,6 +33,7 @@ public class PlaylistTreeItemViewModel
TreeItems = [];
CanExpand = false;
PlaylistId = playlist.Id;
IsSystem = playlist.IsSystem;
}
public PlaylistTreeItemViewModel(TreeItemViewModel playlist)
@@ -39,6 +42,7 @@ public class PlaylistTreeItemViewModel
TreeItems = [];
CanExpand = false;
PlaylistId = playlist.Id;
IsSystem = playlist.IsSystem;
}
public string Text { get; }
@@ -53,5 +57,7 @@ public class PlaylistTreeItemViewModel
public int? PlaylistGroupId { get; }
public bool IsSystem { get; }
public List<TreeItemData<PlaylistTreeItemViewModel>> TreeItems { get; }
}
@@ -5,4 +5,5 @@ public class TraktListEditViewModel
public int Id { get; set; }
public string Slug { get; set; }
public bool AutoRefresh { get; set; }
public bool GeneratePlaylist { get; set; }
}