* 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
883 lines
40 KiB
Plaintext
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;
|
|
}
|