Browse Source

upgrade to mudblazor 7 (#1825)

pull/1826/head
Jason Dove 1 year ago committed by GitHub
parent
commit
694f25f8b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      Directory.Build.props
  2. 2
      ErsatzTV.Application/ErsatzTV.Application.csproj
  3. 4
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  4. 4
      ErsatzTV.Core/ErsatzTV.Core.csproj
  5. 2
      ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj
  6. 2
      ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj
  7. 2
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  8. 2
      ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
  9. 4
      ErsatzTV.Scanner/ErsatzTV.Scanner.csproj
  10. 8
      ErsatzTV/ErsatzTV.csproj
  11. 12
      ErsatzTV/Pages/BlockEditor.razor
  12. 39
      ErsatzTV/Pages/Blocks.razor
  13. 4
      ErsatzTV/Pages/Channels.razor
  14. 12
      ErsatzTV/Pages/Collections.razor
  15. 35
      ErsatzTV/Pages/DecoTemplates.razor
  16. 39
      ErsatzTV/Pages/Decos.razor
  17. 4
      ErsatzTV/Pages/FillerPresets.razor
  18. 21
      ErsatzTV/Pages/ImageBrowser.razor
  19. 4
      ErsatzTV/Pages/Index.razor
  20. 4
      ErsatzTV/Pages/Logs.razor
  21. 14
      ErsatzTV/Pages/PlaylistEditor.razor
  22. 39
      ErsatzTV/Pages/Playlists.razor
  23. 8
      ErsatzTV/Pages/Playouts.razor
  24. 2
      ErsatzTV/Pages/PlexMediaSources.razor
  25. 12
      ErsatzTV/Pages/ScheduleItemsEditor.razor
  26. 8
      ErsatzTV/Pages/Schedules.razor
  27. 35
      ErsatzTV/Pages/Templates.razor
  28. 4
      ErsatzTV/Pages/TraktLists.razor
  29. 8
      ErsatzTV/Shared/MainLayout.razor
  30. 5
      ErsatzTV/Shared/SaveAsSmartCollectionDialog.razor
  31. 2
      ErsatzTV/ViewModels/BlockTreeItemViewModel.cs
  32. 2
      ErsatzTV/ViewModels/DecoTemplateTreeItemViewModel.cs
  33. 2
      ErsatzTV/ViewModels/DecoTreeItemViewModel.cs
  34. 2
      ErsatzTV/ViewModels/ImageTreeItemViewModel.cs
  35. 2
      ErsatzTV/ViewModels/PlaylistTreeItemViewModel.cs
  36. 2
      ErsatzTV/ViewModels/TemplateTreeItemViewModel.cs
  37. 1
      ErsatzTV/_Imports.razor
  38. 2
      global.json

5
Directory.Build.props

@ -3,4 +3,9 @@
<InformationalVersion>develop</InformationalVersion> <InformationalVersion>develop</InformationalVersion>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup> </PropertyGroup>
<!-- Remove when https://github.com/dotnet/sdk/issues/13808 is resolved -->
<PropertyGroup>
<IntermediateOutputPath>/tmp/$(USER)/project/obj/</IntermediateOutputPath>
</PropertyGroup>
</Project> </Project>

2
ErsatzTV.Application/ErsatzTV.Application.csproj

@ -12,7 +12,7 @@
<PackageReference Include="Bugsnag" Version="3.1.0" /> <PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="CliWrap" Version="3.6.6" /> <PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MediatR" Version="12.3.0" /> <PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">

4
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -23,8 +23,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" /> <PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" /> <PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
</ItemGroup> </ItemGroup>

4
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -14,7 +14,7 @@
<PackageReference Include="Flurl" Version="4.0.0" /> <PackageReference Include="Flurl" Version="4.0.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" /> <PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="LanguageExt.Transformers" Version="4.4.8" /> <PackageReference Include="LanguageExt.Transformers" Version="4.4.8" />
<PackageReference Include="MediatR" Version="12.3.0" /> <PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
@ -25,7 +25,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.8" /> <PackageReference Include="SkiaSharp" Version="2.88.8" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />

2
ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj

@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" /> <PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" /> <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" /> <PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" /> <PackageReference Include="System.Runtime.Handles" Version="4.3.0" />

2
ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj

@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" /> <PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0"> <PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

2
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -14,7 +14,7 @@
<PackageReference Include="CliWrap" Version="3.6.6" /> <PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Dapper" Version="2.1.35" /> <PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.14.6" /> <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.14.6" />
<PackageReference Include="Jint" Version="3.1.5" /> <PackageReference Include="Jint" Version="4.0.0" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />

2
ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj

@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.1.0" /> <PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0"> <PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

4
ErsatzTV.Scanner/ErsatzTV.Scanner.csproj

@ -23,11 +23,11 @@
<PackageReference Include="CliWrap" Version="3.6.6" /> <PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" /> <PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="MediatR" Version="12.3.0" /> <PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" /> <PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />

8
ErsatzTV/ErsatzTV.csproj

@ -20,8 +20,8 @@
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" /> <PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
<PackageReference Include="FluentValidation" Version="11.9.2" /> <PackageReference Include="FluentValidation" Version="11.9.2" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Heron.MudCalendar" Version="1.1.2" /> <PackageReference Include="Heron.MudCalendar" Version="2.0.0" />
<PackageReference Include="HtmlSanitizer" Version="8.0.865" /> <PackageReference Include="HtmlSanitizer" Version="8.1.870" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" /> <PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="Markdig" Version="0.37.0" /> <PackageReference Include="Markdig" Version="0.37.0" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" /> <PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
@ -37,10 +37,10 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MudBlazor" Version="6.21.0" /> <PackageReference Include="MudBlazor" Version="7.4.0" />
<PackageReference Include="NaturalSort.Extension" Version="4.3.0" /> <PackageReference Include="NaturalSort.Extension" Version="4.3.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="7.1.2" /> <PackageReference Include="Refit.HttpClientFactory" Version="7.1.2" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" /> <PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" /> <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />

12
ErsatzTV/Pages/BlockEditor.razor

@ -334,7 +334,7 @@
} }
} }
private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -344,7 +344,7 @@
return await Mediator.Send(new SearchCollections(value), _cts.Token); return await Mediator.Send(new SearchCollections(value), _cts.Token);
} }
private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -354,7 +354,7 @@
return await Mediator.Send(new SearchMultiCollections(value), _cts.Token); return await Mediator.Send(new SearchMultiCollections(value), _cts.Token);
} }
private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -364,7 +364,7 @@
return await Mediator.Send(new SearchSmartCollections(value), _cts.Token); return await Mediator.Send(new SearchSmartCollections(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -374,7 +374,7 @@
return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token); return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -384,7 +384,7 @@
return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token); return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {

39
ErsatzTV/Pages/Blocks.razor

@ -1,6 +1,5 @@
@page "/blocks" @page "/blocks"
@using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject ILogger<Blocks> Logger @inject ILogger<Blocks> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@ -48,28 +47,28 @@
</MudItem> </MudItem>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="BlockTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="BlockTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="5"> <MudItem xs="5">
<MudText>@item.Text</MudText> <MudText>@item.Value.Text</MudText>
</MudItem> </MudItem>
@if (!string.IsNullOrWhiteSpace(item.EndText)) @if (!string.IsNullOrWhiteSpace(item.Value.EndText))
{ {
<MudItem xs="6"> <MudItem xs="6">
<MudText>@item.EndText</MudText> <MudText>@item.Value.EndText</MudText>
</MudItem> </MudItem>
} }
</MudGrid> </MudGrid>
<div style="justify-self: end;"> <div style="justify-self: end;">
@foreach (int blockId in Optional(item.BlockId)) @foreach (int blockId in Optional(item.Value.BlockId))
{ {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"blocks/{blockId}")"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"blocks/{blockId}")"/>
} }
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
</div> </div>
</div> </div>
</BodyContent> </BodyContent>
@ -83,7 +82,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<BlockTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<BlockTreeItemViewModel>> TreeItems { get; set; } = [];
private List<BlockGroupViewModel> _blockGroups = []; private List<BlockGroupViewModel> _blockGroups = [];
private BlockGroupViewModel _selectedBlockGroup; private BlockGroupViewModel _selectedBlockGroup;
private string _blockGroupName; private string _blockGroupName;
@ -104,7 +103,7 @@
private async Task ReloadBlockGroups() private async Task ReloadBlockGroups()
{ {
_blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token); _blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token);
TreeItems = _blockGroups.Map(g => new BlockTreeItemViewModel(g)).ToHashSet(); TreeItems = _blockGroups.Map(g => new TreeItemData<BlockTreeItemViewModel> { Value = new BlockTreeItemViewModel(g) }).ToList();
} }
private async Task AddBlockGroup() private async Task AddBlockGroup()
@ -121,7 +120,7 @@
foreach (BlockGroupViewModel blockGroup in result.RightToSeq()) foreach (BlockGroupViewModel blockGroup in result.RightToSeq())
{ {
TreeItems.Add(new BlockTreeItemViewModel(blockGroup)); TreeItems.Add(new TreeItemData<BlockTreeItemViewModel> { Value = new BlockTreeItemViewModel(blockGroup) });
_blockGroupName = null; _blockGroupName = null;
_blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token); _blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token);
@ -144,9 +143,9 @@
foreach (BlockViewModel block in result.RightToSeq()) foreach (BlockViewModel block in result.RightToSeq())
{ {
foreach (BlockTreeItemViewModel item in TreeItems.Where(item => item.BlockGroupId == _selectedBlockGroup.Id)) foreach (BlockTreeItemViewModel item in TreeItems.Map(i => i.Value).Where(item => item.BlockGroupId == _selectedBlockGroup.Id))
{ {
item.TreeItems.Add(new BlockTreeItemViewModel(block)); item.TreeItems.Add(new TreeItemData<BlockTreeItemViewModel> { Value = new BlockTreeItemViewModel(block) });
} }
_blockName = null; _blockName = null;
@ -155,14 +154,14 @@
} }
} }
private async Task<S.HashSet<BlockTreeItemViewModel>> LoadServerData(BlockTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<BlockTreeItemViewModel>>> LoadServerData(BlockTreeItemViewModel parentNode)
{ {
foreach (int blockGroupId in Optional(parentNode.BlockGroupId)) foreach (int blockGroupId in Optional(parentNode.BlockGroupId))
{ {
List<BlockViewModel> result = await Mediator.Send(new GetBlocksByBlockGroupId(blockGroupId), _cts.Token); List<BlockViewModel> result = await Mediator.Send(new GetBlocksByBlockGroupId(blockGroupId), _cts.Token);
foreach (BlockViewModel block in result) foreach (BlockViewModel block in result)
{ {
parentNode.TreeItems.Add(new BlockTreeItemViewModel(block)); parentNode.TreeItems.Add(new TreeItemData<BlockTreeItemViewModel> { Value = new BlockTreeItemViewModel(block) });
} }
} }
@ -178,10 +177,10 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Block Group", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Block Group", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteBlockGroup(blockGroupId), _cts.Token); await Mediator.Send(new DeleteBlockGroup(blockGroupId), _cts.Token);
TreeItems.RemoveWhere(i => i.BlockGroupId == blockGroupId); TreeItems.RemoveAll(i => i.Value?.BlockGroupId == blockGroupId);
_blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token); _blockGroups = await Mediator.Send(new GetAllBlockGroups(), _cts.Token);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@ -195,12 +194,12 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Block", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Block", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteBlock(blockId), _cts.Token); await Mediator.Send(new DeleteBlock(blockId), _cts.Token);
foreach (BlockTreeItemViewModel parent in TreeItems) foreach (BlockTreeItemViewModel parent in TreeItems.Map(i => i.Value))
{ {
parent.TreeItems.Remove(treeItem); parent.TreeItems.RemoveAll(i => i.Value == treeItem);
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

4
ErsatzTV/Pages/Channels.razor

@ -13,7 +13,7 @@
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudTable Hover="true" <MudTable Hover="true"
@bind-RowsPerPage="@_rowsPerPage" @bind-RowsPerPage="@_rowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<ChannelViewModel>>>(ServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<ChannelViewModel>>>(ServerReload))"
@ref="_table"> @ref="_table">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Channels</MudText> <MudText Typo="Typo.h6">Channels</MudText>
@ -214,7 +214,7 @@
} }
} }
private async Task<TableData<ChannelViewModel>> ServerReload(TableState state) private async Task<TableData<ChannelViewModel>> ServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.ChannelsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.ChannelsPageSize, state.PageSize.ToString()), _cts.Token);

12
ErsatzTV/Pages/Collections.razor

@ -18,7 +18,7 @@
<MudTable Class="mt-4" <MudTable Class="mt-4"
Hover="true" Hover="true"
@bind-RowsPerPage="@_collectionsRowsPerPage" @bind-RowsPerPage="@_collectionsRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<MediaCollectionViewModel>>>(ServerReloadCollections))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<MediaCollectionViewModel>>>(ServerReloadCollections))"
Dense="true" Dense="true"
@ref="_collectionsTable"> @ref="_collectionsTable">
<ToolBarContent> <ToolBarContent>
@ -56,7 +56,7 @@
<MudTable Class="mt-4" <MudTable Class="mt-4"
Hover="true" Hover="true"
@bind-RowsPerPage="@_multiCollectionsRowsPerPage" @bind-RowsPerPage="@_multiCollectionsRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<MultiCollectionViewModel>>>(ServerReloadMultiCollections))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<MultiCollectionViewModel>>>(ServerReloadMultiCollections))"
Dense="true" Dense="true"
@ref="_multiCollectionsTable"> @ref="_multiCollectionsTable">
<ToolBarContent> <ToolBarContent>
@ -94,7 +94,7 @@
<MudTable Class="mt-4" <MudTable Class="mt-4"
Hover="true" Hover="true"
@bind-RowsPerPage="@_smartCollectionsRowsPerPage" @bind-RowsPerPage="@_smartCollectionsRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<SmartCollectionViewModel>>>(ServerReloadSmartCollections))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<SmartCollectionViewModel>>>(ServerReloadSmartCollections))"
Dense="true" Dense="true"
@ref="_smartCollectionsTable"> @ref="_smartCollectionsTable">
<ToolBarContent> <ToolBarContent>
@ -211,7 +211,7 @@
} }
} }
private async Task<TableData<MediaCollectionViewModel>> ServerReloadCollections(TableState state) private async Task<TableData<MediaCollectionViewModel>> ServerReloadCollections(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.CollectionsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.CollectionsPageSize, state.PageSize.ToString()), _cts.Token);
@ -219,7 +219,7 @@
return new TableData<MediaCollectionViewModel> { TotalItems = data.TotalCount, Items = data.Page }; return new TableData<MediaCollectionViewModel> { TotalItems = data.TotalCount, Items = data.Page };
} }
private async Task<TableData<MultiCollectionViewModel>> ServerReloadMultiCollections(TableState state) private async Task<TableData<MultiCollectionViewModel>> ServerReloadMultiCollections(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.MultiCollectionsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.MultiCollectionsPageSize, state.PageSize.ToString()), _cts.Token);
@ -227,7 +227,7 @@
return new TableData<MultiCollectionViewModel> { TotalItems = data.TotalCount, Items = data.Page }; return new TableData<MultiCollectionViewModel> { TotalItems = data.TotalCount, Items = data.Page };
} }
private async Task<TableData<SmartCollectionViewModel>> ServerReloadSmartCollections(TableState state) private async Task<TableData<SmartCollectionViewModel>> ServerReloadSmartCollections(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SmartCollectionsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SmartCollectionsPageSize, state.PageSize.ToString()), _cts.Token);

35
ErsatzTV/Pages/DecoTemplates.razor

@ -1,6 +1,5 @@
@page "/deco-templates" @page "/deco-templates"
@using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject ILogger<DecoTemplates> Logger @inject ILogger<DecoTemplates> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@ -48,22 +47,22 @@
</MudItem> </MudItem>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="DecoTemplateTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="DecoTemplateTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="8"> <MudItem xs="8">
<MudText>@item.Text</MudText>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
<div style="justify-self: end;"> <div style="justify-self: end;">
@foreach (int decoTemplateId in Optional(item.DecoTemplateId)) @foreach (int decoTemplateId in Optional(item.Value.DecoTemplateId))
{ {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"deco-templates/{decoTemplateId}")"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"deco-templates/{decoTemplateId}")"/>
} }
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
</div> </div>
</div> </div>
</BodyContent> </BodyContent>
@ -77,7 +76,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<DecoTemplateTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<DecoTemplateTreeItemViewModel>> TreeItems { get; set; } = [];
private List<DecoTemplateGroupViewModel> _decoTemplateGroups = []; private List<DecoTemplateGroupViewModel> _decoTemplateGroups = [];
private DecoTemplateGroupViewModel _selectedDecoTemplateGroup; private DecoTemplateGroupViewModel _selectedDecoTemplateGroup;
private string _decoTemplateGroupName; private string _decoTemplateGroupName;
@ -98,7 +97,7 @@
private async Task ReloadDecoTemplateGroups() private async Task ReloadDecoTemplateGroups()
{ {
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); _decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token);
TreeItems = _decoTemplateGroups.Map(g => new DecoTemplateTreeItemViewModel(g)).ToHashSet(); TreeItems = _decoTemplateGroups.Map(g => new TreeItemData<DecoTemplateTreeItemViewModel> { Value = new DecoTemplateTreeItemViewModel(g) }).ToList();
} }
private async Task AddDecoTemplateGroup() private async Task AddDecoTemplateGroup()
@ -115,7 +114,7 @@
foreach (DecoTemplateGroupViewModel decoTemplateGroup in result.RightToSeq()) foreach (DecoTemplateGroupViewModel decoTemplateGroup in result.RightToSeq())
{ {
TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplateGroup)); TreeItems.Add(new TreeItemData<DecoTemplateTreeItemViewModel> { Value = new DecoTemplateTreeItemViewModel(decoTemplateGroup) });
_decoTemplateGroupName = null; _decoTemplateGroupName = null;
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); _decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token);
@ -138,9 +137,9 @@
foreach (DecoTemplateViewModel decoTemplate in result.RightToSeq()) foreach (DecoTemplateViewModel decoTemplate in result.RightToSeq())
{ {
foreach (DecoTemplateTreeItemViewModel item in TreeItems.Where(item => item.DecoTemplateGroupId == _selectedDecoTemplateGroup.Id)) foreach (DecoTemplateTreeItemViewModel item in TreeItems.Map(i => i.Value).Where(item => item.DecoTemplateGroupId == _selectedDecoTemplateGroup.Id))
{ {
item.TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplate)); item.TreeItems.Add(new TreeItemData<DecoTemplateTreeItemViewModel> { Value = new DecoTemplateTreeItemViewModel(decoTemplate) });
} }
_decoTemplateName = null; _decoTemplateName = null;
@ -149,14 +148,14 @@
} }
} }
private async Task<S.HashSet<DecoTemplateTreeItemViewModel>> LoadServerData(DecoTemplateTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<DecoTemplateTreeItemViewModel>>> LoadServerData(DecoTemplateTreeItemViewModel parentNode)
{ {
foreach (int decoTemplateGroupId in Optional(parentNode.DecoTemplateGroupId)) foreach (int decoTemplateGroupId in Optional(parentNode.DecoTemplateGroupId))
{ {
List<DecoTemplateViewModel> result = await Mediator.Send(new GetDecoTemplatesByDecoTemplateGroupId(decoTemplateGroupId), _cts.Token); List<DecoTemplateViewModel> result = await Mediator.Send(new GetDecoTemplatesByDecoTemplateGroupId(decoTemplateGroupId), _cts.Token);
foreach (DecoTemplateViewModel decoTemplate in result) foreach (DecoTemplateViewModel decoTemplate in result)
{ {
parentNode.TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplate)); parentNode.TreeItems.Add(new TreeItemData<DecoTemplateTreeItemViewModel> { Value = new DecoTemplateTreeItemViewModel(decoTemplate) });
} }
} }
@ -172,10 +171,10 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template Group", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template Group", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteDecoTemplateGroup(decoTemplateGroupId), _cts.Token); await Mediator.Send(new DeleteDecoTemplateGroup(decoTemplateGroupId), _cts.Token);
TreeItems.RemoveWhere(i => i.DecoTemplateGroupId == decoTemplateGroupId); TreeItems.RemoveAll(i => i.Value?.DecoTemplateGroupId == decoTemplateGroupId);
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); _decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@ -189,12 +188,12 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteDecoTemplate(decoTemplateId), _cts.Token); await Mediator.Send(new DeleteDecoTemplate(decoTemplateId), _cts.Token);
foreach (DecoTemplateTreeItemViewModel parent in TreeItems) foreach (DecoTemplateTreeItemViewModel parent in TreeItems.Map(i => i.Value))
{ {
parent.TreeItems.Remove(treeItem); parent.TreeItems.RemoveAll(i => i.Value == treeItem);
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

39
ErsatzTV/Pages/Decos.razor

@ -1,6 +1,5 @@
@page "/decos" @page "/decos"
@using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject ILogger<Decos> Logger @inject ILogger<Decos> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@ -48,28 +47,28 @@
</MudItem> </MudItem>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="DecoTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="DecoTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="5"> <MudItem xs="5">
<MudText>@item.Text</MudText> <MudText>@item.Value.Text</MudText>
</MudItem> </MudItem>
@if (!string.IsNullOrWhiteSpace(item.EndText)) @if (!string.IsNullOrWhiteSpace(item.Value.EndText))
{ {
<MudItem xs="6"> <MudItem xs="6">
<MudText>@item.EndText</MudText> <MudText>@item.Value.EndText</MudText>
</MudItem> </MudItem>
} }
</MudGrid> </MudGrid>
<div style="justify-self: end;"> <div style="justify-self: end;">
@foreach (int decoId in Optional(item.DecoId)) @foreach (int decoId in Optional(item.Value.DecoId))
{ {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"decos/{decoId}")"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"decos/{decoId}")"/>
} }
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
</div> </div>
</div> </div>
</BodyContent> </BodyContent>
@ -83,7 +82,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<DecoTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<DecoTreeItemViewModel>> TreeItems { get; set; } = [];
private List<DecoGroupViewModel> _decoGroups = []; private List<DecoGroupViewModel> _decoGroups = [];
private DecoGroupViewModel _selectedDecoGroup; private DecoGroupViewModel _selectedDecoGroup;
private string _decoGroupName; private string _decoGroupName;
@ -104,7 +103,7 @@
private async Task ReloadDecoGroups() private async Task ReloadDecoGroups()
{ {
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); _decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token);
TreeItems = _decoGroups.Map(g => new DecoTreeItemViewModel(g)).ToHashSet(); TreeItems = _decoGroups.Map(g => new TreeItemData<DecoTreeItemViewModel> { Value = new DecoTreeItemViewModel(g) }).ToList();
} }
private async Task AddDecoGroup() private async Task AddDecoGroup()
@ -121,7 +120,7 @@
foreach (DecoGroupViewModel decoGroup in result.RightToSeq()) foreach (DecoGroupViewModel decoGroup in result.RightToSeq())
{ {
TreeItems.Add(new DecoTreeItemViewModel(decoGroup)); TreeItems.Add(new TreeItemData<DecoTreeItemViewModel> { Value = new DecoTreeItemViewModel(decoGroup) });
_decoGroupName = null; _decoGroupName = null;
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); _decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token);
@ -144,9 +143,9 @@
foreach (DecoViewModel deco in result.RightToSeq()) foreach (DecoViewModel deco in result.RightToSeq())
{ {
foreach (DecoTreeItemViewModel item in TreeItems.Where(item => item.DecoGroupId == _selectedDecoGroup.Id)) foreach (DecoTreeItemViewModel item in TreeItems.Where(item => item.Value!.DecoGroupId == _selectedDecoGroup.Id).Map(i => i.Value))
{ {
item.TreeItems.Add(new DecoTreeItemViewModel(deco)); item.TreeItems.Add(new TreeItemData<DecoTreeItemViewModel> { Value = new DecoTreeItemViewModel(deco) });
} }
_decoName = null; _decoName = null;
@ -155,14 +154,14 @@
} }
} }
private async Task<S.HashSet<DecoTreeItemViewModel>> LoadServerData(DecoTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<DecoTreeItemViewModel>>> LoadServerData(DecoTreeItemViewModel parentNode)
{ {
foreach (int decoGroupId in Optional(parentNode.DecoGroupId)) foreach (int decoGroupId in Optional(parentNode.DecoGroupId))
{ {
List<DecoViewModel> result = await Mediator.Send(new GetDecosByDecoGroupId(decoGroupId), _cts.Token); List<DecoViewModel> result = await Mediator.Send(new GetDecosByDecoGroupId(decoGroupId), _cts.Token);
foreach (DecoViewModel deco in result) foreach (DecoViewModel deco in result)
{ {
parentNode.TreeItems.Add(new DecoTreeItemViewModel(deco)); parentNode.TreeItems.Add(new TreeItemData<DecoTreeItemViewModel> { Value = new DecoTreeItemViewModel(deco) });
} }
} }
@ -178,10 +177,10 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Group", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Group", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteDecoGroup(decoGroupId), _cts.Token); await Mediator.Send(new DeleteDecoGroup(decoGroupId), _cts.Token);
TreeItems.RemoveWhere(i => i.DecoGroupId == decoGroupId); TreeItems.RemoveAll(i => i.Value?.DecoGroupId == decoGroupId);
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); _decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@ -195,12 +194,12 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteDeco(decoId), _cts.Token); await Mediator.Send(new DeleteDeco(decoId), _cts.Token);
foreach (DecoTreeItemViewModel parent in TreeItems) foreach (DecoTreeItemViewModel parent in TreeItems.Map(i => i.Value))
{ {
parent.TreeItems.Remove(treeItem); parent.TreeItems.RemoveAll(ti => ti.Value == treeItem);
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

4
ErsatzTV/Pages/FillerPresets.razor

@ -15,7 +15,7 @@
<MudTable Class="mt-4" <MudTable Class="mt-4"
Hover="true" Hover="true"
@bind-RowsPerPage="@_fillerPresetsRowsPerPage" @bind-RowsPerPage="@_fillerPresetsRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<FillerPresetViewModel>>>(ServerReloadFillerPresets))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<FillerPresetViewModel>>>(ServerReloadFillerPresets))"
Dense="true" Dense="true"
@ref="_fillerPresetsTable"> @ref="_fillerPresetsTable">
<ToolBarContent> <ToolBarContent>
@ -100,7 +100,7 @@
} }
} }
private async Task<TableData<FillerPresetViewModel>> ServerReloadFillerPresets(TableState state) private async Task<TableData<FillerPresetViewModel>> ServerReloadFillerPresets(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.FillerPresetsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.FillerPresetsPageSize, state.PageSize.ToString()), _cts.Token);

21
ErsatzTV/Pages/ImageBrowser.razor

@ -1,7 +1,6 @@
@page "/media/browser/images" @page "/media/browser/images"
@using System.Net @using System.Net
@using ErsatzTV.Application.Images @using ErsatzTV.Application.Images
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject IDialogService Dialog @inject IDialogService Dialog
@inject IMediator Mediator @inject IMediator Mediator
@ -10,23 +9,23 @@
<MudGrid> <MudGrid>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="ImageTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="ImageTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<div style="justify-self: start;"> <div style="justify-self: start;">
<MudText>@item.Text</MudText> <MudText>@item.Value.Text</MudText>
</div> </div>
<div style="justify-self: end;"> <div style="justify-self: end;">
<span>@item.EndText</span> <span>@item.Value.EndText</span>
<MudTooltip Text="Edit Image Folder Duration" ShowOnHover="true" ShowOnClick="false" ShowOnFocus="false"> <MudTooltip Text="Edit Image Folder Duration" ShowOnHover="true" ShowOnClick="false" ShowOnFocus="false">
<MudIconButton Icon="@Icons.Material.Filled.Edit" <MudIconButton Icon="@Icons.Material.Filled.Edit"
OnClick="@(_ => EditImageFolderDuration(item))"> OnClick="@(_ => EditImageFolderDuration(item.Value))">
</MudIconButton> </MudIconButton>
</MudTooltip> </MudTooltip>
@{ @{
string query = GetSearchQuery(item); string query = GetSearchQuery(item.Value);
if (!string.IsNullOrWhiteSpace(query)) if (!string.IsNullOrWhiteSpace(query))
{ {
<MudIconButton <MudIconButton
@ -47,7 +46,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<ImageTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<ImageTreeItemViewModel>> TreeItems { get; set; } = [];
public void Dispose() public void Dispose()
{ {
@ -64,15 +63,15 @@
private async Task ReloadImageFolders() private async Task ReloadImageFolders()
{ {
List<ImageFolderViewModel> imageFolders = await Mediator.Send(new GetImageFolders(Option<int>.None), _cts.Token); List<ImageFolderViewModel> imageFolders = await Mediator.Send(new GetImageFolders(Option<int>.None), _cts.Token);
TreeItems = imageFolders.Map(g => new ImageTreeItemViewModel(g)).ToHashSet(); TreeItems = imageFolders.Map(g => new TreeItemData<ImageTreeItemViewModel> { Value = new ImageTreeItemViewModel(g) }).ToList();
} }
private async Task<S.HashSet<ImageTreeItemViewModel>> LoadServerData(ImageTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<ImageTreeItemViewModel>>> LoadServerData(ImageTreeItemViewModel parentNode)
{ {
List<ImageFolderViewModel> result = await Mediator.Send(new GetImageFolders(parentNode.LibraryFolderId), _cts.Token); List<ImageFolderViewModel> result = await Mediator.Send(new GetImageFolders(parentNode.LibraryFolderId), _cts.Token);
foreach (ImageFolderViewModel imageFolder in result) foreach (ImageFolderViewModel imageFolder in result)
{ {
parentNode.TreeItems.Add(new ImageTreeItemViewModel(imageFolder)); parentNode.TreeItems.Add(new TreeItemData<ImageTreeItemViewModel> { Value = new ImageTreeItemViewModel(imageFolder) });
} }
return parentNode.TreeItems; return parentNode.TreeItems;

4
ErsatzTV/Pages/Index.razor

@ -49,7 +49,7 @@
{ {
<MudTable Hover="true" <MudTable Hover="true"
Dense="true" Dense="true"
ServerData="@(new Func<TableState, Task<TableData<HealthCheckResult>>>(ServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<HealthCheckResult>>>(ServerReload))"
@ref="_table"> @ref="_table">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Health Checks</MudText> <MudText Typo="Typo.h6">Health Checks</MudText>
@ -196,7 +196,7 @@
} }
} }
private async Task<TableData<HealthCheckResult>> ServerReload(TableState state) private async Task<TableData<HealthCheckResult>> ServerReload(TableState state, CancellationToken cancellationToken)
{ {
List<HealthCheckResult> healthCheckResults = await Mediator.Send(new GetAllHealthCheckResults(), _cts.Token); List<HealthCheckResult> healthCheckResults = await Mediator.Send(new GetAllHealthCheckResults(), _cts.Token);

4
ErsatzTV/Pages/Logs.razor

@ -7,7 +7,7 @@
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudTable FixedHeader="true" <MudTable FixedHeader="true"
@bind-RowsPerPage="@_rowsPerPage" @bind-RowsPerPage="@_rowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<LogEntryViewModel>>>(ServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<LogEntryViewModel>>>(ServerReload))"
Dense="true" Dense="true"
@ref="_table"> @ref="_table">
<ToolBarContent> <ToolBarContent>
@ -64,7 +64,7 @@
await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.LogsPageSize), _cts.Token) await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.LogsPageSize), _cts.Token)
.Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10)); .Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10));
private async Task<TableData<LogEntryViewModel>> ServerReload(TableState state) private async Task<TableData<LogEntryViewModel>> ServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.LogsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.LogsPageSize, state.PageSize.ToString()), _cts.Token);

14
ErsatzTV/Pages/PlaylistEditor.razor

@ -329,7 +329,7 @@
} }
} }
private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -339,7 +339,7 @@
return await Mediator.Send(new SearchCollections(value), _cts.Token); return await Mediator.Send(new SearchCollections(value), _cts.Token);
} }
private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -349,7 +349,7 @@
return await Mediator.Send(new SearchMultiCollections(value), _cts.Token); return await Mediator.Send(new SearchMultiCollections(value), _cts.Token);
} }
private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -359,7 +359,7 @@
return await Mediator.Send(new SearchSmartCollections(value), _cts.Token); return await Mediator.Send(new SearchSmartCollections(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -369,7 +369,7 @@
return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token); return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -379,7 +379,7 @@
return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token); return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -389,7 +389,7 @@
return await Mediator.Send(new SearchArtists(value), _cts.Token); return await Mediator.Send(new SearchArtists(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchMovies(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchMovies(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {

39
ErsatzTV/Pages/Playlists.razor

@ -1,6 +1,5 @@
@page "/media/playlists" @page "/media/playlists"
@using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaCollections
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject ILogger<Playlists> Logger @inject ILogger<Playlists> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@ -48,28 +47,28 @@
</MudItem> </MudItem>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="PlaylistTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="PlaylistTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="5"> <MudItem xs="5">
<MudText>@item.Text</MudText> <MudText>@item.Value.Text</MudText>
</MudItem> </MudItem>
@if (!string.IsNullOrWhiteSpace(item.EndText)) @if (!string.IsNullOrWhiteSpace(item.Value.EndText))
{ {
<MudItem xs="6"> <MudItem xs="6">
<MudText>@item.EndText</MudText> <MudText>@item.Value.EndText</MudText>
</MudItem> </MudItem>
} }
</MudGrid> </MudGrid>
<div style="justify-self: end;"> <div style="justify-self: end;">
@foreach (int playlistId in Optional(item.PlaylistId)) @foreach (int playlistId in Optional(item.Value.PlaylistId))
{ {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"media/playlists/{playlistId}")"/> <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))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
</div> </div>
</div> </div>
</BodyContent> </BodyContent>
@ -83,7 +82,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<PlaylistTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<PlaylistTreeItemViewModel>> TreeItems { get; set; } = [];
private List<PlaylistGroupViewModel> _playlistGroups = []; private List<PlaylistGroupViewModel> _playlistGroups = [];
private PlaylistGroupViewModel _selectedPlaylistGroup; private PlaylistGroupViewModel _selectedPlaylistGroup;
private string _playlistGroupName; private string _playlistGroupName;
@ -104,7 +103,7 @@
private async Task ReloadPlaylistGroups() private async Task ReloadPlaylistGroups()
{ {
_playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token); _playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token);
TreeItems = _playlistGroups.Map(g => new PlaylistTreeItemViewModel(g)).ToHashSet(); TreeItems = _playlistGroups.Map(g => new TreeItemData<PlaylistTreeItemViewModel> { Value = new PlaylistTreeItemViewModel(g) }).ToList();
} }
private async Task AddPlaylistGroup() private async Task AddPlaylistGroup()
@ -121,7 +120,7 @@
foreach (PlaylistGroupViewModel playlistGroup in result.RightToSeq()) foreach (PlaylistGroupViewModel playlistGroup in result.RightToSeq())
{ {
TreeItems.Add(new PlaylistTreeItemViewModel(playlistGroup)); TreeItems.Add(new TreeItemData<PlaylistTreeItemViewModel> { Value = new PlaylistTreeItemViewModel(playlistGroup) });
_playlistGroupName = null; _playlistGroupName = null;
_playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token); _playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token);
@ -144,9 +143,9 @@
foreach (PlaylistViewModel playlist in result.RightToSeq()) foreach (PlaylistViewModel playlist in result.RightToSeq())
{ {
foreach (PlaylistTreeItemViewModel item in TreeItems.Where(item => item.PlaylistGroupId == _selectedPlaylistGroup.Id)) foreach (PlaylistTreeItemViewModel item in TreeItems.Map(i => i.Value).Where(item => item.PlaylistGroupId == _selectedPlaylistGroup.Id))
{ {
item.TreeItems.Add(new PlaylistTreeItemViewModel(playlist)); item.TreeItems.Add(new TreeItemData<PlaylistTreeItemViewModel> { Value = new PlaylistTreeItemViewModel(playlist) });
} }
_playlistName = null; _playlistName = null;
@ -155,14 +154,14 @@
} }
} }
private async Task<S.HashSet<PlaylistTreeItemViewModel>> LoadServerData(PlaylistTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<PlaylistTreeItemViewModel>>> LoadServerData(PlaylistTreeItemViewModel parentNode)
{ {
foreach (int playlistGroupId in Optional(parentNode.PlaylistGroupId)) foreach (int playlistGroupId in Optional(parentNode.PlaylistGroupId))
{ {
List<PlaylistViewModel> result = await Mediator.Send(new GetPlaylistsByPlaylistGroupId(playlistGroupId), _cts.Token); List<PlaylistViewModel> result = await Mediator.Send(new GetPlaylistsByPlaylistGroupId(playlistGroupId), _cts.Token);
foreach (PlaylistViewModel playlist in result) foreach (PlaylistViewModel playlist in result)
{ {
parentNode.TreeItems.Add(new PlaylistTreeItemViewModel(playlist)); parentNode.TreeItems.Add(new TreeItemData<PlaylistTreeItemViewModel> { Value = new PlaylistTreeItemViewModel(playlist) });
} }
} }
@ -178,10 +177,10 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Playlist Group", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Playlist Group", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeletePlaylistGroup(playlistGroupId), _cts.Token); await Mediator.Send(new DeletePlaylistGroup(playlistGroupId), _cts.Token);
TreeItems.RemoveWhere(i => i.PlaylistGroupId == playlistGroupId); TreeItems.RemoveAll(i => i.Value?.PlaylistGroupId == playlistGroupId);
_playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token); _playlistGroups = await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@ -195,12 +194,12 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Playlist", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Playlist", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeletePlaylist(playlistId), _cts.Token); await Mediator.Send(new DeletePlaylist(playlistId), _cts.Token);
foreach (PlaylistTreeItemViewModel parent in TreeItems) foreach (PlaylistTreeItemViewModel parent in TreeItems.Map(i => i.Value))
{ {
parent.TreeItems.Remove(treeItem); parent.TreeItems.RemoveAll(i => i.Value == treeItem);
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

8
ErsatzTV/Pages/Playouts.razor

@ -34,7 +34,7 @@
Class="mt-4" Class="mt-4"
SelectedItemChanged="@(async (PlayoutNameViewModel x) => await PlayoutSelected(x))" SelectedItemChanged="@(async (PlayoutNameViewModel x) => await PlayoutSelected(x))"
@bind-RowsPerPage="@_rowsPerPage" @bind-RowsPerPage="@_rowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<PlayoutNameViewModel>>>(ServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<PlayoutNameViewModel>>>(ServerReload))"
@ref="_table"> @ref="_table">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Playouts</MudText> <MudText Typo="Typo.h6">Playouts</MudText>
@ -183,7 +183,7 @@
Hover="true" Hover="true"
Dense="true" Dense="true"
@bind-RowsPerPage="@_detailRowsPerPage" @bind-RowsPerPage="@_detailRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<PlayoutItemViewModel>>>(DetailServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<PlayoutItemViewModel>>>(DetailServerReload))"
@ref="_detailTable"> @ref="_detailTable">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Playout Detail</MudText> <MudText Typo="Typo.h6">Playout Detail</MudText>
@ -361,7 +361,7 @@
} }
} }
private async Task<TableData<PlayoutNameViewModel>> ServerReload(TableState state) private async Task<TableData<PlayoutNameViewModel>> ServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsPageSize, state.PageSize.ToString()), _cts.Token);
@ -386,7 +386,7 @@
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }
private async Task<TableData<PlayoutItemViewModel>> DetailServerReload(TableState state) private async Task<TableData<PlayoutItemViewModel>> DetailServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailPageSize, state.PageSize.ToString()), _cts.Token);
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailShowFiller, _showFiller.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailShowFiller, _showFiller.ToString()), _cts.Token);

2
ErsatzTV/Pages/PlexMediaSources.razor

@ -12,7 +12,7 @@
@inject ChannelWriter<IScannerBackgroundServiceRequest> ScannerWorkerChannel @inject ChannelWriter<IScannerBackgroundServiceRequest> ScannerWorkerChannel
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudTable Hover="true" Dense="true" Items="_mediaSources"> <MudTable T="PlexMediaSourceViewModel" Hover="true" Dense="true" Items="_mediaSources">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Plex Media Sources</MudText> <MudText Typo="Typo.h6">Plex Media Sources</MudText>
</ToolBarContent> </ToolBarContent>

12
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -450,7 +450,7 @@
} }
} }
private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -460,7 +460,7 @@
return await Mediator.Send(new SearchCollections(value), _cts.Token); return await Mediator.Send(new SearchCollections(value), _cts.Token);
} }
private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -470,7 +470,7 @@
return await Mediator.Send(new SearchMultiCollections(value), _cts.Token); return await Mediator.Send(new SearchMultiCollections(value), _cts.Token);
} }
private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -480,7 +480,7 @@
return await Mediator.Send(new SearchSmartCollections(value), _cts.Token); return await Mediator.Send(new SearchSmartCollections(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -490,7 +490,7 @@
return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token); return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
@ -500,7 +500,7 @@
return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token); return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token);
} }
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {

8
ErsatzTV/Pages/Schedules.razor

@ -12,7 +12,7 @@
Dense="true" Dense="true"
SelectedItemChanged="@(async (ProgramScheduleViewModel x) => await ScheduleSelected(x))" SelectedItemChanged="@(async (ProgramScheduleViewModel x) => await ScheduleSelected(x))"
@bind-RowsPerPage="@_rowsPerPage" @bind-RowsPerPage="@_rowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<ProgramScheduleViewModel>>>(ServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<ProgramScheduleViewModel>>>(ServerReload))"
@ref="_table"> @ref="_table">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">Schedules</MudText> <MudText Typo="Typo.h6">Schedules</MudText>
@ -69,7 +69,7 @@
<MudTable Hover="true" <MudTable Hover="true"
Class="mt-8" Class="mt-8"
@bind-RowsPerPage="@_detailRowsPerPage" @bind-RowsPerPage="@_detailRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<ProgramScheduleItemViewModel>>>(DetailServerReload))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<ProgramScheduleItemViewModel>>>(DetailServerReload))"
@ref="_detailTable"> @ref="_detailTable">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">@_selectedSchedule.Name Items</MudText> <MudText Typo="Typo.h6">@_selectedSchedule.Name Items</MudText>
@ -160,7 +160,7 @@
} }
} }
private async Task<TableData<ProgramScheduleViewModel>> ServerReload(TableState state) private async Task<TableData<ProgramScheduleViewModel>> ServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SchedulesPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SchedulesPageSize, state.PageSize.ToString()), _cts.Token);
@ -175,7 +175,7 @@
}; };
} }
private async Task<TableData<ProgramScheduleItemViewModel>> DetailServerReload(TableState state) private async Task<TableData<ProgramScheduleItemViewModel>> DetailServerReload(TableState state, CancellationToken cancellationToken)
{ {
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SchedulesDetailPageSize, state.PageSize.ToString()), _cts.Token); await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SchedulesDetailPageSize, state.PageSize.ToString()), _cts.Token);

35
ErsatzTV/Pages/Templates.razor

@ -1,6 +1,5 @@
@page "/templates" @page "/templates"
@using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Scheduling
@using S = System.Collections.Generic
@implements IDisposable @implements IDisposable
@inject ILogger<Templates> Logger @inject ILogger<Templates> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@ -48,22 +47,22 @@
</MudItem> </MudItem>
<MudItem xs="8"> <MudItem xs="8">
<MudCard> <MudCard>
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="TemplateTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> <MudTreeViewItem T="TemplateTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="8"> <MudItem xs="8">
<MudText>@item.Text</MudText> <MudText>@item.Value.Text</MudText>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
<div style="justify-self: end;"> <div style="justify-self: end;">
@foreach (int templateId in Optional(item.TemplateId)) @foreach (int templateId in Optional(item.Value.TemplateId))
{ {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"templates/{templateId}")"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"templates/{templateId}")"/>
} }
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item.Value))"/>
</div> </div>
</div> </div>
</BodyContent> </BodyContent>
@ -77,7 +76,7 @@
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private S.HashSet<TemplateTreeItemViewModel> TreeItems { get; set; } = []; private List<TreeItemData<TemplateTreeItemViewModel>> TreeItems { get; set; } = [];
private List<TemplateGroupViewModel> _templateGroups = []; private List<TemplateGroupViewModel> _templateGroups = [];
private TemplateGroupViewModel _selectedTemplateGroup; private TemplateGroupViewModel _selectedTemplateGroup;
private string _templateGroupName; private string _templateGroupName;
@ -98,7 +97,7 @@
private async Task ReloadTemplateGroups() private async Task ReloadTemplateGroups()
{ {
_templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token); _templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token);
TreeItems = _templateGroups.Map(g => new TemplateTreeItemViewModel(g)).ToHashSet(); TreeItems = _templateGroups.Map(g => new TreeItemData<TemplateTreeItemViewModel> { Value = new TemplateTreeItemViewModel(g) }).ToList();
} }
private async Task AddTemplateGroup() private async Task AddTemplateGroup()
@ -115,7 +114,7 @@
foreach (TemplateGroupViewModel templateGroup in result.RightToSeq()) foreach (TemplateGroupViewModel templateGroup in result.RightToSeq())
{ {
TreeItems.Add(new TemplateTreeItemViewModel(templateGroup)); TreeItems.Add(new TreeItemData<TemplateTreeItemViewModel> { Value = new TemplateTreeItemViewModel(templateGroup) });
_templateGroupName = null; _templateGroupName = null;
_templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token); _templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token);
@ -138,9 +137,9 @@
foreach (TemplateViewModel template in result.RightToSeq()) foreach (TemplateViewModel template in result.RightToSeq())
{ {
foreach (TemplateTreeItemViewModel item in TreeItems.Where(item => item.TemplateGroupId == _selectedTemplateGroup.Id)) foreach (TemplateTreeItemViewModel item in TreeItems.Map(i => i.Value).Where(item => item.TemplateGroupId == _selectedTemplateGroup.Id))
{ {
item.TreeItems.Add(new TemplateTreeItemViewModel(template)); item.TreeItems.Add(new TreeItemData<TemplateTreeItemViewModel> { Value = new TemplateTreeItemViewModel(template) });
} }
_templateName = null; _templateName = null;
@ -149,14 +148,14 @@
} }
} }
private async Task<S.HashSet<TemplateTreeItemViewModel>> LoadServerData(TemplateTreeItemViewModel parentNode) private async Task<IReadOnlyCollection<TreeItemData<TemplateTreeItemViewModel>>> LoadServerData(TemplateTreeItemViewModel parentNode)
{ {
foreach (int templateGroupId in Optional(parentNode.TemplateGroupId)) foreach (int templateGroupId in Optional(parentNode.TemplateGroupId))
{ {
List<TemplateViewModel> result = await Mediator.Send(new GetTemplatesByTemplateGroupId(templateGroupId), _cts.Token); List<TemplateViewModel> result = await Mediator.Send(new GetTemplatesByTemplateGroupId(templateGroupId), _cts.Token);
foreach (TemplateViewModel template in result) foreach (TemplateViewModel template in result)
{ {
parentNode.TreeItems.Add(new TemplateTreeItemViewModel(template)); parentNode.TreeItems.Add(new TreeItemData<TemplateTreeItemViewModel> { Value = new TemplateTreeItemViewModel(template) });
} }
} }
@ -172,10 +171,10 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Template Group", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Template Group", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteTemplateGroup(templateGroupId), _cts.Token); await Mediator.Send(new DeleteTemplateGroup(templateGroupId), _cts.Token);
TreeItems.RemoveWhere(i => i.TemplateGroupId == templateGroupId); TreeItems.RemoveAll(i => i.Value?.TemplateGroupId == templateGroupId);
_templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token); _templateGroups = await Mediator.Send(new GetAllTemplateGroups(), _cts.Token);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@ -189,12 +188,12 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Template", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Template", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
{ {
await Mediator.Send(new DeleteTemplate(templateId), _cts.Token); await Mediator.Send(new DeleteTemplate(templateId), _cts.Token);
foreach (TemplateTreeItemViewModel parent in TreeItems) foreach (TemplateTreeItemViewModel parent in TreeItems.Map(i => i.Value))
{ {
parent.TreeItems.Remove(treeItem); parent.TreeItems.RemoveAll(i => i.Value == treeItem);
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

4
ErsatzTV/Pages/TraktLists.razor

@ -19,7 +19,7 @@
<MudTable Class="mt-4" <MudTable Class="mt-4"
Hover="true" Hover="true"
@bind-RowsPerPage="@_traktListsRowsPerPage" @bind-RowsPerPage="@_traktListsRowsPerPage"
ServerData="@(new Func<TableState, Task<TableData<TraktListViewModel>>>(ServerReloadTraktLists))" ServerData="@(new Func<TableState, CancellationToken, Task<TableData<TraktListViewModel>>>(ServerReloadTraktLists))"
Dense="true" Dense="true"
@ref="_traktListsTable"> @ref="_traktListsTable">
<ToolBarContent> <ToolBarContent>
@ -127,7 +127,7 @@
} }
} }
private async Task<TableData<TraktListViewModel>> ServerReloadTraktLists(TableState state) private async Task<TableData<TraktListViewModel>> ServerReloadTraktLists(TableState state, CancellationToken cancellationToken)
{ {
try try
{ {

8
ErsatzTV/Shared/MainLayout.razor

@ -10,8 +10,9 @@
@inject ISearchTargets SearchTargets; @inject ISearchTargets SearchTargets;
<MudThemeProvider Theme="ErsatzTvTheme"/> <MudThemeProvider Theme="ErsatzTvTheme"/>
<MudDialogProvider DisableBackdropClick="true"/> <MudDialogProvider BackdropClick="false"/>
<MudSnackbarProvider/> <MudSnackbarProvider/>
<MudPopoverProvider />
<MudLayout @onclick="@(() => _isOpen = false)"> <MudLayout @onclick="@(() => _isOpen = false)">
<MudAppBar Elevation="1" Class="app-bar"> <MudAppBar Elevation="1" Class="app-bar">
@ -40,7 +41,7 @@
var matches = _searchTargets.Where(s => s.Name.Contains(_query, StringComparison.CurrentCultureIgnoreCase)).ToList(); var matches = _searchTargets.Where(s => s.Name.Contains(_query, StringComparison.CurrentCultureIgnoreCase)).ToList();
if (matches.Any()) if (matches.Any())
{ {
<MudList Clickable="true" Dense="true"> <MudList T="SearchTargetViewModel" ReadOnly="false" Dense="true">
@foreach (SearchTargetViewModel searchTarget in matches) @foreach (SearchTargetViewModel searchTarget in matches)
{ {
<MudListItem @key="@searchTarget" OnClick="@(() => NavigateTo(searchTarget))"> <MudListItem @key="@searchTarget" OnClick="@(() => NavigateTo(searchTarget))">
@ -182,7 +183,7 @@
private static MudTheme ErsatzTvTheme => new() private static MudTheme ErsatzTvTheme => new()
{ {
Palette = new PaletteDark PaletteLight = new PaletteLight
{ {
ActionDefault = "rgba(255,255,255, 0.80)", ActionDefault = "rgba(255,255,255, 0.80)",
Primary = "#009000", Primary = "#009000",
@ -197,6 +198,7 @@
TextDisabled = "rgba(255,255,255, 0.40)", TextDisabled = "rgba(255,255,255, 0.40)",
ActionDisabled = "rgba(255,255,255, 0.40)", ActionDisabled = "rgba(255,255,255, 0.40)",
TableHover = "rgba(255,255,255, 0.10)", TableHover = "rgba(255,255,255, 0.10)",
TableLines = "rgba(255,255,255,0.11)",
Info = "#00c0c0", Info = "#00c0c0",
Tertiary = "#00c000", Tertiary = "#00c000",
White = Colors.Shades.White White = Colors.Shades.White

5
ErsatzTV/Shared/SaveAsSmartCollectionDialog.razor

@ -11,8 +11,9 @@
<EditForm Model="@_dummyModel" OnSubmit="@(_ => Submit())"> <EditForm Model="@_dummyModel" OnSubmit="@(_ => Submit())">
<MudContainer Class="mb-6"> <MudContainer Class="mb-6">
<MudText Class="mud-primary-text" <MudText Class="mud-primary-text"
Style="background-color: transparent; font-weight: bold" Style="background-color: transparent; font-weight: bold">
Text="Select the desired smart collection"/> Select the desired smart collection
</MudText>
</MudContainer> </MudContainer>
<MudSelect Label="Collection" @bind-Value="_selectedCollection" Class="mb-6 mx-4"> <MudSelect Label="Collection" @bind-Value="_selectedCollection" Class="mb-6 mx-4">
@foreach (SmartCollectionViewModel collection in _collections) @foreach (SmartCollectionViewModel collection in _collections)

2
ErsatzTV/ViewModels/BlockTreeItemViewModel.cs

@ -50,5 +50,5 @@ public class BlockTreeItemViewModel
public int? BlockGroupId { get; } public int? BlockGroupId { get; }
public S.HashSet<BlockTreeItemViewModel> TreeItems { get; } public List<TreeItemData<BlockTreeItemViewModel>> TreeItems { get; }
} }

2
ErsatzTV/ViewModels/DecoTemplateTreeItemViewModel.cs

@ -33,5 +33,5 @@ public class DecoTemplateTreeItemViewModel
public int? DecoTemplateGroupId { get; } public int? DecoTemplateGroupId { get; }
public S.HashSet<DecoTemplateTreeItemViewModel> TreeItems { get; } public List<TreeItemData<DecoTemplateTreeItemViewModel>> TreeItems { get; }
} }

2
ErsatzTV/ViewModels/DecoTreeItemViewModel.cs

@ -36,5 +36,5 @@ public class DecoTreeItemViewModel
public int? DecoGroupId { get; } public int? DecoGroupId { get; }
public S.HashSet<DecoTreeItemViewModel> TreeItems { get; } public List<TreeItemData<DecoTreeItemViewModel>> TreeItems { get; }
} }

2
ErsatzTV/ViewModels/ImageTreeItemViewModel.cs

@ -46,7 +46,7 @@ public class ImageTreeItemViewModel
public bool CanExpand { get; } public bool CanExpand { get; }
public S.HashSet<ImageTreeItemViewModel> TreeItems { get; } public S.HashSet<TreeItemData<ImageTreeItemViewModel>> TreeItems { get; }
public void UpdateDuration(double? imageFolderDuration) public void UpdateDuration(double? imageFolderDuration)
{ {

2
ErsatzTV/ViewModels/PlaylistTreeItemViewModel.cs

@ -36,5 +36,5 @@ public class PlaylistTreeItemViewModel
public int? PlaylistGroupId { get; } public int? PlaylistGroupId { get; }
public S.HashSet<PlaylistTreeItemViewModel> TreeItems { get; } public List<TreeItemData<PlaylistTreeItemViewModel>> TreeItems { get; }
} }

2
ErsatzTV/ViewModels/TemplateTreeItemViewModel.cs

@ -33,5 +33,5 @@ public class TemplateTreeItemViewModel
public int? TemplateGroupId { get; } public int? TemplateGroupId { get; }
public S.HashSet<TemplateTreeItemViewModel> TreeItems { get; } public List<TreeItemData<TemplateTreeItemViewModel>> TreeItems { get; }
} }

1
ErsatzTV/_Imports.razor

@ -18,7 +18,6 @@
@using LanguageExt @using LanguageExt
@using MediatR @using MediatR
@using MudBlazor @using MudBlazor
@using MudBlazor.Dialog
@using ErsatzTV @using ErsatzTV
@using ErsatzTV.Application @using ErsatzTV.Application
@using ErsatzTV.Core @using ErsatzTV.Core

2
global.json

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "8.0.0", "version": "8.0.303",
"rollForward": "latestFeature", "rollForward": "latestFeature",
"allowPrerelease": false "allowPrerelease": false
} }

Loading…
Cancel
Save