Files
ersatztv/ErsatzTV/Pages/DecoEditor.razor
T
Jason DoveandGitHub 788a1ecdc4 add deco break content (#2446)
* add initial models

* migrations

* edit break content mode

* add and remove break content from ui

* use autocompletes in deco editor

* save break content to db

* allow break content playlists

* refactor default filler build

* fix slow startup

* start to implement adding break content

* use clone; try to fix block breaks

* fix updating history

* use consistent removebefore values

* cleanup logging

* only allow playlist break content

* update changelog
2025-09-19 18:48:51 +00:00

883 lines
40 KiB
Plaintext

@page "/decos/{Id:int}"
@using ErsatzTV.Application.Graphics
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Search
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Core.Domain.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<DecoEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
<MudForm Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="ml-6" StartIcon="@Icons.Material.Filled.Save">
Save Changes
</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">Deco</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Deco Group Name</MudText>
</div>
<MudTextField @bind-Value="_deco.GroupName" Disabled="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Deco Name</MudText>
</div>
<MudTextField @bind-Value="_deco.Name" For="@(() => _deco.Name)"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Watermark</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Watermark Mode</MudText>
</div>
<MudSelect @bind-Value="_deco.WatermarkMode" For="@(() => _deco.WatermarkMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Replace</MudSelectItem>
<MudSelectItem Value="DecoMode.Merge">Merge</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Watermark</MudText>
</div>
<MudSelect T="WatermarkViewModel"
@bind-SelectedValues="_deco.Watermarks"
Disabled="@(_deco.WatermarkMode is not (DecoMode.Override or DecoMode.Merge))"
ToStringFunc="@(wm => wm?.Name)"
Clearable="true"
MultiSelection="true">
@foreach (WatermarkViewModel watermark in _watermarks)
{
<MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Use Watermark During Filler</MudText>
</div>
<MudCheckBox T="bool"
Disabled="@(_deco.WatermarkMode != DecoMode.Override)"
@bind-Value="_deco.UseWatermarkDuringFiller"
Dense="true"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Graphics Elements</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Graphics Elements Mode</MudText>
</div>
<MudSelect @bind-Value="_deco.GraphicsElementsMode" For="@(() => _deco.GraphicsElementsMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Replace</MudSelectItem>
<MudSelectItem Value="DecoMode.Merge">Merge</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Graphics Elements</MudText>
</div>
<MudSelect T="GraphicsElementViewModel"
@bind-SelectedValues="_deco.GraphicsElements"
Disabled="@(_deco.GraphicsElementsMode is not (DecoMode.Override or DecoMode.Merge))"
ToStringFunc="@(ge => ge?.Name)"
Clearable="true"
MultiSelection="true">
@foreach (GraphicsElementViewModel graphicsElement in _graphicsElements)
{
<MudSelectItem Value="@graphicsElement">@graphicsElement.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Use Graphics Elements During Filler</MudText>
</div>
<MudCheckBox T="bool"
Disabled="@(_deco.GraphicsElementsMode != DecoMode.Override)"
@bind-Value="_deco.UseGraphicsElementsDuringFiller"
Dense="true"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Break Content</MudText>
<MudDivider Class="mb-6"/>
<MudText Typo="Typo.body2" Class="mb-6">Break content can be used to play specific items (using playlists), and to pad blocks to their full configured duration.</MudText>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Break Content Mode</MudText>
</div>
<MudSelect @bind-Value="_deco.BreakContentMode" For="@(() => _deco.BreakContentMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
</MudStack>
@if (_deco.BreakContentMode is DecoMode.Override)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddBreakContent())" StartIcon="@Icons.Material.Filled.Add">
Add Break Content
</MudButton>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Break Content Items</MudText>
<MudDivider Class="mb-6"/>
<MudTable T="DecoBreakContentEditViewModel" Class="mt-6" Hover="true" Items="_deco.BreakContent" Dense="true" SelectedItemChanged="@(vm => SelectedBreakContentChanged(vm))" RowClassFunc="@SelectedRowClassFunc">
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
<col/>
<col/>
<col/>
<col style="width: 60px;"/>
</MudHidden>
</ColGroup>
<HeaderContent>
<MudTh>Type</MudTh>
<MudTh>Name</MudTh>
<MudTh>Placement</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Type">@context.CollectionType.ToString()</MudTd>
<MudTd DataLabel="Name">@context.CollectionName</MudTd>
<MudTd DataLabel="Placement">
@(context.Placement switch {
DecoBreakPlacement.BlockStart => "Block Start",
DecoBreakPlacement.BlockFinish => "Block Finish",
DecoBreakPlacement.BetweenBlockItems => "Between Block Items",
DecoBreakPlacement.ChapterMarkers => "At Chapter Markers",
_ => string.Empty
})
</MudTd>
<MudTd>
<div class="d-flex">
@* <MudIconButton Icon="@Icons.Material.Filled.ContentCopy" *@
@* OnClick="@(_ => CopyItem(context))"> *@
@* </MudIconButton> *@
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemoveBreakContent(context))">
</MudIconButton>
</div>
</MudTd>
</RowTemplate>
</MudTable>
@if (_selectedBreakContent is not null)
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Break Content Item</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Placement</MudText>
</div>
<MudSelect @bind-Value="_selectedBreakContent.Placement" For="@(() => _selectedBreakContent.Placement)" Disabled="true">
<MudSelectItem Value="DecoBreakPlacement.BlockStart">Block Start</MudSelectItem>
<MudSelectItem Value="DecoBreakPlacement.BetweenBlockItems">Between Block Items</MudSelectItem>
<MudSelectItem Value="DecoBreakPlacement.ChapterMarkers">At Chapter Markers</MudSelectItem>
<MudSelectItem Value="DecoBreakPlacement.BlockFinish">Block Finish</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Collection Type</MudText>
</div>
<MudSelect @bind-Value="_selectedBreakContent.CollectionType" For="@(() => _selectedBreakContent.CollectionType)" Disabled="true">
@* <MudSelectItem Value="CollectionType.Collection">Collection</MudSelectItem> *@
@* <MudSelectItem Value="CollectionType.TelevisionShow">Television Show</MudSelectItem> *@
@* <MudSelectItem Value="CollectionType.TelevisionSeason">Television Season</MudSelectItem> *@
@* <MudSelectItem Value="CollectionType.Artist">Artist</MudSelectItem> *@
@* <MudSelectItem Value="CollectionType.MultiCollection">Multi Collection</MudSelectItem> *@
@* <MudSelectItem Value="CollectionType.SmartCollection">Smart Collection</MudSelectItem> *@
<MudSelectItem Value="CollectionType.Playlist">Playlist</MudSelectItem>
</MudSelect>
</MudStack>
@if (_selectedBreakContent.CollectionType == CollectionType.Collection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Collection</MudText>
</div>
<MudAutocomplete T="MediaCollectionViewModel"
@bind-Value="_selectedBreakContent.Collection"
SearchFunc="@SearchCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_selectedBreakContent.CollectionType == CollectionType.MultiCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Multi Collection</MudText>
</div>
<MudAutocomplete T="MultiCollectionViewModel"
@bind-Value="_selectedBreakContent.MultiCollection"
SearchFunc="@SearchMultiCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_selectedBreakContent.CollectionType == CollectionType.SmartCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Smart Collection</MudText>
</div>
<MudAutocomplete T="SmartCollectionViewModel"
@bind-Value="_selectedBreakContent.SmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_selectedBreakContent.CollectionType == CollectionType.TelevisionShow)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Show</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
@bind-Value="_selectedBreakContent.MediaItem"
SearchFunc="@SearchTelevisionShows"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_selectedBreakContent.CollectionType == CollectionType.TelevisionSeason)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Season</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
@bind-Value="_selectedBreakContent.MediaItem"
SearchFunc="@SearchTelevisionSeasons"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
MaxItems="20">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_selectedBreakContent.CollectionType == CollectionType.Playlist)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Playlist Group</MudText>
</div>
<MudSelect T="PlaylistGroupViewModel"
Value="@_selectedPlaylistGroup"
ValueChanged="@(vm => UpdatePlaylistGroupItems(vm))">
@foreach (PlaylistGroupViewModel playlistGroup in _playlistGroups)
{
<MudSelectItem Value="@playlistGroup">@playlistGroup.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Playlist</MudText>
</div>
<MudSelect T="PlaylistViewModel" @bind-value="_selectedBreakContent.Playlist">
@foreach (PlaylistViewModel playlist in _playlists)
{
<MudSelectItem Value="@playlist">@playlist.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
}
}
}
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Default Filler</MudText>
<MudDivider Class="mb-6"/>
<MudText Typo="Typo.body2" Class="mb-6">After all blocks have been scheduled, a second pass will be made to fill unscheduled time using random items from this collection.</MudText>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Default Filler Mode</MudText>
</div>
<MudSelect @bind-Value="_deco.DefaultFillerMode" For="@(() => _deco.DefaultFillerMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Default Filler Collection Type</MudText>
</div>
<MudSelect Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-Value="_deco.DefaultFillerCollectionType"
For="@(() => _deco.DefaultFillerCollectionType)">
<MudSelectItem Value="CollectionType.Collection">Collection</MudSelectItem>
<MudSelectItem Value="CollectionType.TelevisionShow">Television Show</MudSelectItem>
<MudSelectItem Value="CollectionType.TelevisionSeason">Television Season</MudSelectItem>
<MudSelectItem Value="CollectionType.Artist">Artist</MudSelectItem>
<MudSelectItem Value="CollectionType.MultiCollection">Multi Collection</MudSelectItem>
<MudSelectItem Value="CollectionType.SmartCollection">Smart Collection</MudSelectItem>
</MudSelect>
</MudStack>
@if (_deco.DefaultFillerCollectionType == CollectionType.Collection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Collection</MudText>
</div>
<MudAutocomplete T="MediaCollectionViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-value="_deco.DefaultFillerCollection"
SearchFunc="@SearchCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DefaultFillerCollectionType == CollectionType.MultiCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Multi Collection</MudText>
</div>
<MudAutocomplete T="MultiCollectionViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-value="_deco.DefaultFillerMultiCollection"
SearchFunc="@SearchMultiCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DefaultFillerCollectionType == CollectionType.SmartCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Smart Collection</MudText>
</div>
<MudAutocomplete T="SmartCollectionViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-value="_deco.DefaultFillerSmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DefaultFillerCollectionType == CollectionType.TelevisionShow)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Show</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-value="_deco.DefaultFillerMediaItem"
SearchFunc="@SearchTelevisionShows"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DefaultFillerCollectionType == CollectionType.TelevisionSeason)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Season</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-Value="_deco.DefaultFillerMediaItem"
SearchFunc="@SearchTelevisionSeasons"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
MaxItems="20">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DefaultFillerCollectionType == CollectionType.Artist)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Artist</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-Value="_deco.DefaultFillerMediaItem"
SearchFunc="@SearchArtists"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
MaxItems="20">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Trim To Fit</MudText>
</div>
<MudCheckBox T="bool"
Disabled="@(_deco.DefaultFillerMode != DecoMode.Override)"
@bind-Value="_deco.DefaultFillerTrimToFit"
Color="Color.Primary"
Dense="true"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Dead Air Fallback</MudText>
<MudDivider Class="mb-6"/>
<MudText Typo="Typo.body2">When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and trimmed to exactly fit until the start of the next playout item.</MudText>
<MudText Typo="Typo.body2" Class="mb-6 mt-3">This replaces the "Channel is Offline" image that would otherwise display.</MudText>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Dead Air Fallback Mode</MudText>
</div>
<MudSelect @bind-Value="_deco.DeadAirFallbackMode" For="@(() => _deco.DeadAirFallbackMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Dead Air Fallback Collection Type</MudText>
</div>
<MudSelect Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-Value="_deco.DeadAirFallbackCollectionType"
For="@(() => _deco.DeadAirFallbackCollectionType)">
<MudSelectItem Value="CollectionType.Collection">Collection</MudSelectItem>
<MudSelectItem Value="CollectionType.TelevisionShow">Television Show</MudSelectItem>
<MudSelectItem Value="CollectionType.TelevisionSeason">Television Season</MudSelectItem>
<MudSelectItem Value="CollectionType.Artist">Artist</MudSelectItem>
<MudSelectItem Value="CollectionType.MultiCollection">Multi Collection</MudSelectItem>
<MudSelectItem Value="CollectionType.SmartCollection">Smart Collection</MudSelectItem>
</MudSelect>
</MudStack>
@if (_deco.DeadAirFallbackCollectionType == CollectionType.Collection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Collection</MudText>
</div>
<MudAutocomplete T="MediaCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackCollection"
SearchFunc="@SearchCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DeadAirFallbackCollectionType == CollectionType.MultiCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Multi Collection</MudText>
</div>
<MudAutocomplete T="MultiCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackMultiCollection"
SearchFunc="@SearchMultiCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DeadAirFallbackCollectionType == CollectionType.SmartCollection)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Smart Collection</MudText>
</div>
<MudAutocomplete T="SmartCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackSmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DeadAirFallbackCollectionType == CollectionType.TelevisionShow)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Show</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackMediaItem"
SearchFunc="@SearchTelevisionShows"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DeadAirFallbackCollectionType == CollectionType.TelevisionSeason)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Television Season</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackMediaItem"
SearchFunc="@SearchTelevisionSeasons"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
MaxItems="20">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
@if (_deco.DeadAirFallbackCollectionType == CollectionType.Artist)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Artist</MudText>
</div>
<MudAutocomplete T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
@bind-value="_deco.DeadAirFallbackMediaItem"
SearchFunc="@SearchArtists"
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
MaxItems="20">
<MoreItemsTemplate>
<MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
}
</MudContainer>
</div>
</MudForm>
@code {
private CancellationTokenSource _cts;
[Parameter]
public int Id { get; set; }
private DecoEditViewModel _deco = new();
private List<WatermarkViewModel> _watermarks = [];
private List<GraphicsElementViewModel> _graphicsElements = [];
private readonly List<PlaylistGroupViewModel> _playlistGroups = [];
private readonly List<PlaylistViewModel> _playlists = [];
private DecoBreakContentEditViewModel _selectedBreakContent;
private PlaylistGroupViewModel _selectedPlaylistGroup;
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
protected override async Task OnParametersSetAsync()
{
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
var token = _cts.Token;
try
{
_watermarks = await Mediator.Send(new GetAllWatermarks(), token);
_graphicsElements = await Mediator.Send(new GetAllGraphicsElements(), token);
_playlistGroups.AddRange(await Mediator.Send(new GetAllPlaylistGroups(), token));
Option<DecoViewModel> maybeDeco = await Mediator.Send(new GetDecoById(Id), token);
if (maybeDeco.IsNone)
{
NavigationManager.NavigateTo("decos");
return;
}
foreach (DecoViewModel deco in maybeDeco)
{
_deco = new DecoEditViewModel
{
Name = deco.Name,
DecoGroupId = deco.DecoGroupId,
GroupName = deco.DecoGroupName,
WatermarkMode = deco.WatermarkMode,
Watermarks = deco.Watermarks,
UseWatermarkDuringFiller = deco.UseWatermarkDuringFiller,
GraphicsElementsMode = deco.GraphicsElementsMode,
GraphicsElements = deco.GraphicsElements,
UseGraphicsElementsDuringFiller = deco.UseGraphicsElementsDuringFiller,
BreakContentMode = deco.BreakContentMode,
BreakContent = deco.BreakContent.Map(ProjectToEditViewModel).ToList(),
DefaultFillerMode = deco.DefaultFillerMode,
DefaultFillerCollectionType = deco.DefaultFillerCollectionType,
DefaultFillerCollection = deco.DefaultFillerCollection,
DefaultFillerMediaItem = deco.DefaultFillerMediaItem,
DefaultFillerMultiCollection = deco.DefaultFillerMultiCollection,
DefaultFillerSmartCollection = deco.DefaultFillerSmartCollection,
DefaultFillerTrimToFit = deco.DefaultFillerTrimToFit,
DeadAirFallbackMode = deco.DeadAirFallbackMode,
DeadAirFallbackCollectionType = deco.DeadAirFallbackCollectionType,
DeadAirFallbackCollection = deco.DeadAirFallbackCollection,
DeadAirFallbackMediaItem = deco.DeadAirFallbackMediaItem,
DeadAirFallbackMultiCollection = deco.DeadAirFallbackMultiCollection,
DeadAirFallbackSmartCollection = deco.DeadAirFallbackSmartCollection
};
}
}
catch (OperationCanceledException)
{
// do nothing
}
}
private static DecoBreakContentEditViewModel ProjectToEditViewModel(DecoBreakContentViewModel vm) =>
new()
{
Id = vm.Id,
CollectionType = vm.CollectionType,
Collection = vm.Collection,
MediaItem = vm.MediaItem,
MultiCollection = vm.MultiCollection,
SmartCollection = vm.SmartCollection,
Playlist = vm.Playlist,
Placement = vm.Placement
};
private async Task SaveChanges()
{
var breakContent = _deco.BreakContent
.Map(vm => new UpdateDecoBreakContent(vm.Id, vm.CollectionType, vm.Collection?.Id, vm.MediaItem?.MediaItemId, vm.MultiCollection?.Id, vm.SmartCollection?.Id, vm.Playlist?.Id, vm.Placement))
.ToList();
var request = new UpdateDeco(
Id,
_deco.DecoGroupId,
_deco.Name,
_deco.WatermarkMode,
_deco.Watermarks.Map(wm => wm.Id).ToList(),
_deco.UseWatermarkDuringFiller,
_deco.GraphicsElementsMode,
_deco.GraphicsElements.Map(ge => ge.Id).ToList(),
_deco.UseGraphicsElementsDuringFiller,
_deco.BreakContentMode,
breakContent,
_deco.DefaultFillerMode,
_deco.DefaultFillerCollectionType,
_deco.DefaultFillerCollection?.Id,
_deco.DefaultFillerMediaItem?.MediaItemId,
_deco.DefaultFillerMultiCollection?.Id,
_deco.DefaultFillerSmartCollection?.Id,
_deco.DefaultFillerTrimToFit,
_deco.DeadAirFallbackMode,
_deco.DeadAirFallbackCollectionType,
_deco.DeadAirFallbackCollection?.Id,
_deco.DeadAirFallbackMediaItem?.MediaItemId,
_deco.DeadAirFallbackMultiCollection?.Id,
_deco.DeadAirFallbackSmartCollection?.Id);
Seq<BaseError> errorMessages = await Mediator
.Send(request, _cts.Token)
.Map(e => e.LeftToSeq());
errorMessages.HeadOrNone().Match(
error =>
{
Snackbar.Add($"Unexpected error saving deco: {error.Value}", Severity.Error);
Logger.LogError("Unexpected error saving deco: {Error}", error.Value);
},
() => NavigationManager.NavigateTo("decos"));
}
private async Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<MediaCollectionViewModel>();
}
return await Mediator.Send(new SearchCollections(value), _cts.Token);
}
private async Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<MultiCollectionViewModel>();
}
return await Mediator.Send(new SearchMultiCollections(value), _cts.Token);
}
private async Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<SmartCollectionViewModel>();
}
return await Mediator.Send(new SearchSmartCollections(value), _cts.Token);
}
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<NamedMediaItemViewModel>();
}
return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token);
}
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<NamedMediaItemViewModel>();
}
return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token);
}
private async Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(value))
{
return new List<NamedMediaItemViewModel>();
}
return await Mediator.Send(new SearchArtists(value), _cts.Token);
}
private async Task SelectedBreakContentChanged(DecoBreakContentEditViewModel vm)
{
_selectedBreakContent = vm;
foreach (int playlistGroupId in Optional(_selectedBreakContent.Playlist?.PlaylistGroupId))
{
foreach (PlaylistGroupViewModel group in Optional(_playlistGroups.Find(g => g.Id == playlistGroupId)))
{
_selectedPlaylistGroup = group;
await UpdatePlaylistGroupItems(group);
}
}
}
private async Task UpdatePlaylistGroupItems(PlaylistGroupViewModel playlistGroup)
{
_selectedPlaylistGroup = playlistGroup;
_playlists.Clear();
_playlists.AddRange(await Mediator.Send(new GetPlaylistsByPlaylistGroupId(playlistGroup.Id), _cts.Token));
}
private void AddBreakContent()
{
var item = new DecoBreakContentEditViewModel
{
//CollectionType = CollectionType.Collection,
//Collection = null,
CollectionType = CollectionType.Playlist,
Playlist = null,
Placement = DecoBreakPlacement.BlockStart
};
_deco.BreakContent.Add(item);
}
private void RemoveBreakContent(DecoBreakContentEditViewModel item)
{
_deco.BreakContent.Remove(item);
if (_selectedBreakContent == item)
{
_selectedBreakContent = null;
}
}
private string SelectedRowClassFunc(DecoBreakContentEditViewModel element, int rowNumber) => _selectedBreakContent != null && _selectedBreakContent == element ? "selected" : string.Empty;
}