filler rework (#449)
* add chapter statistics and new filler options * refactor playout builder * more refactor prep for filler * rewrite schedulers * refactor collectionkey * add tail filler kind * migrate tail filler to filler preset * optionally show filler * fix playout detail row count * remove duration tail filler options * implement tail and fallback in flood scheduler * implement tail and fallback in one scheduler * implement tail and fallback in multiple scheduler * implement looping fallback filler * more duration tests * start to add post-roll filler to flood * rework playoutitem filler tagging * rework scheduler logging * calculate whether configured filler will fit * implement pre-roll and post-roll duration and count filler * improve duration filler calculation * add minutes to search index * update channel guide to work with new filler * add mid-roll filler * don't clone enumerators for filler calculations * support pre-roll and post-roll pad filler * implement mid-roll pad filler * allow clearing filler selections in schedule editor * fix tests * filler config validation * use consistent time zone for tests
This commit is contained in:
@@ -42,7 +42,7 @@ namespace ErsatzTV.Controllers
|
||||
{
|
||||
Process process = processModel.Process;
|
||||
|
||||
_logger.LogDebug(
|
||||
_logger.LogInformation(
|
||||
"ffmpeg arguments {FFmpegArguments}",
|
||||
string.Join(" ", process.StartInfo.ArgumentList));
|
||||
process.Start();
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
DialogResult result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
|
||||
{
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, ProgramScheduleItemCollectionType.Collection, null, null, null, null, null, GuideMode.Normal));
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null));
|
||||
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
private readonly CollectionEditViewModel _model = new();
|
||||
private EditContext _editContext;
|
||||
private ValidationMessageStore _messageStore;
|
||||
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (IsEdit)
|
||||
@@ -59,7 +59,7 @@
|
||||
_editContext = new EditContext(_model);
|
||||
_messageStore = new ValidationMessageStore(_editContext);
|
||||
}
|
||||
|
||||
|
||||
private bool IsEdit => Id != 0;
|
||||
|
||||
private async Task HandleSubmitAsync()
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
@page "/media/filler/presets/{Id:int}/edit"
|
||||
@page "/media/filler/presets/add"
|
||||
@using ErsatzTV.Application.Filler
|
||||
@using ErsatzTV.Application.Filler.Queries
|
||||
@using ErsatzTV.Application.Television.Queries
|
||||
@using ErsatzTV.Application.Artists.Queries
|
||||
@using ErsatzTV.Application.MediaCollections
|
||||
@using ErsatzTV.Application.MediaCollections.Queries
|
||||
@using ErsatzTV.Application.MediaItems
|
||||
@using ErsatzTV.Core.Domain.Filler
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject ILogger<FillerPresetEditor> _logger
|
||||
@inject ISnackbar _snackbar
|
||||
@inject IMediator _mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Filler Preset" : "Add Filler Preset")</MudText>
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudSelect Class="mt-3" Label="Filler Kind" @bind-Value="_model.FillerKind" For="@(() => _model.FillerKind)" Disabled="@IsEdit">
|
||||
<MudSelectItem Value="@(FillerKind.PreRoll)">Pre-Roll</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerKind.MidRoll)">Mid-Roll</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerKind.PostRoll)">Post-Roll</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerKind.Tail)">Tail</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerKind.Fallback)">Fallback</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect Class="mt-3" Label="Filler Mode" @bind-Value="_model.FillerMode" For="@(() => _model.FillerMode)"
|
||||
Disabled="@(_model.FillerKind is FillerKind.Fallback or FillerKind.Tail)">
|
||||
<MudSelectItem Value="@(FillerMode.Duration)">Duration</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerMode.Count)">Count</MudSelectItem>
|
||||
<MudSelectItem Value="@(FillerMode.Pad)">Pad</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudTimePicker Class="mt-3" Label="Filler Duration" @bind-Time="@_model.Duration" For="@(() => _model.Duration)" Disabled="@(_model.FillerMode != FillerMode.Duration)"/>
|
||||
<MudTextField Class="mt-3" Label="Filler Count" @bind-Value="@_model.Count" For="@(() => _model.Count)" Disabled="@(_model.FillerMode != FillerMode.Count)"/>
|
||||
<MudSelect Class="mt-3" Label="Filler Pad To Nearest Minute" @bind-Value="_model.PadToNearestMinute" For="@(() => _model.PadToNearestMinute)" Disabled="@(_model.FillerMode != FillerMode.Pad)">
|
||||
<MudSelectItem T="int?" Value="5">5 (:00, :05, :10, :15, :20, etc)</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="10">10 (:00, :10, :20, :30, :40, :50)</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="15">15 (:00, :15, :30, :45)</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="30">30 (:00, :30)</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect Class="mt-3" Label="Filler Collection Type" @bind-Value="_model.CollectionType" For="@(() => _model.CollectionType)">
|
||||
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
|
||||
{
|
||||
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MediaCollectionViewModel"
|
||||
Label="Collection"
|
||||
@bind-value="_model.Collection"
|
||||
SearchFunc="@SearchMediaCollections"
|
||||
ToStringFunc="@(c => c?.Name)"/>
|
||||
}
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.MultiCollection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MultiCollectionViewModel"
|
||||
Label="Multi Collection"
|
||||
@bind-value="_model.MultiCollection"
|
||||
SearchFunc="@SearchMultiCollections"
|
||||
ToStringFunc="@(c => c?.Name)"/>
|
||||
}
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.SmartCollection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="SmartCollectionViewModel"
|
||||
Label="Smart Collection"
|
||||
@bind-value="_model.SmartCollection"
|
||||
SearchFunc="@SearchSmartCollections"
|
||||
ToStringFunc="@(c => c?.Name)"/>
|
||||
}
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Show"
|
||||
@bind-value="_model.MediaItem"
|
||||
SearchFunc="@SearchTelevisionShows"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Season"
|
||||
@bind-value="_model.MediaItem"
|
||||
SearchFunc="@SearchTelevisionSeasons"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
@if (_model.CollectionType == ProgramScheduleItemCollectionType.Artist)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Artist"
|
||||
@bind-value="_model.MediaItem"
|
||||
SearchFunc="@SearchArtists"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">
|
||||
@(IsEdit ? "Save Changes" : "Add Filler Preset")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private readonly FillerPresetEditViewModel _model = new();
|
||||
private EditContext _editContext;
|
||||
private ValidationMessageStore _messageStore;
|
||||
|
||||
private List<MediaCollectionViewModel> _mediaCollections;
|
||||
private List<MultiCollectionViewModel> _multiCollections;
|
||||
private List<SmartCollectionViewModel> _smartCollections;
|
||||
private List<NamedMediaItemViewModel> _televisionShows;
|
||||
private List<NamedMediaItemViewModel> _televisionSeasons;
|
||||
private List<NamedMediaItemViewModel> _artists;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_mediaCollections = await _mediator.Send(new GetAllCollections())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_multiCollections = await _mediator.Send(new GetAllMultiCollections())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_smartCollections = await _mediator.Send(new GetAllSmartCollections())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_televisionShows = await _mediator.Send(new GetAllTelevisionShows())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_televisionSeasons = await _mediator.Send(new GetAllTelevisionSeasons())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_artists = await _mediator.Send(new GetAllArtists())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
|
||||
if (IsEdit)
|
||||
{
|
||||
Option<FillerPresetViewModel> maybeFillerPreset = await _mediator.Send(new GetFillerPresetById(Id));
|
||||
maybeFillerPreset.IfSome(fillerPreset =>
|
||||
{
|
||||
_model.Id = fillerPreset.Id;
|
||||
_model.Name = fillerPreset.Name;
|
||||
_model.FillerKind = fillerPreset.FillerKind;
|
||||
_model.FillerMode = fillerPreset.FillerMode;
|
||||
_model.Duration = fillerPreset.Duration;
|
||||
_model.Count = fillerPreset.Count;
|
||||
_model.PadToNearestMinute = fillerPreset.PadToNearestMinute;
|
||||
_model.CollectionType = fillerPreset.CollectionType;
|
||||
_model.Collection = fillerPreset.CollectionId.HasValue
|
||||
? _mediaCollections.Find(c => c.Id == fillerPreset.CollectionId.Value)
|
||||
: null;
|
||||
_model.MultiCollection = fillerPreset.MultiCollectionId.HasValue
|
||||
? _multiCollections.Find(c => c.Id == fillerPreset.MultiCollectionId.Value)
|
||||
: null;
|
||||
_model.SmartCollection = fillerPreset.SmartCollectionId.HasValue
|
||||
? _smartCollections.Find(c => c.Id == fillerPreset.SmartCollectionId.Value)
|
||||
: null;
|
||||
_model.MediaItem = fillerPreset.MediaItemId.HasValue
|
||||
? _televisionShows.Append(_televisionSeasons).Append(_artists).ToList().Find(vm => vm.MediaItemId == fillerPreset.MediaItemId.Value)
|
||||
: null;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_model.Name = "New Filler Preset";
|
||||
_model.FillerKind = FillerKind.PreRoll;
|
||||
_model.FillerMode = FillerMode.Duration;
|
||||
}
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_editContext = new EditContext(_model);
|
||||
_messageStore = new ValidationMessageStore(_editContext);
|
||||
}
|
||||
|
||||
private bool IsEdit => Id != 0;
|
||||
|
||||
private async Task HandleSubmitAsync()
|
||||
{
|
||||
_messageStore.Clear();
|
||||
if (_editContext.Validate())
|
||||
{
|
||||
IRequest<Either<BaseError, LanguageExt.Unit>> request = IsEdit ? _model.ToEdit() : _model.ToUpdate();
|
||||
|
||||
Seq<BaseError> errorMessage = await _mediator.Send(request)
|
||||
.Map(result => result.LeftToSeq());
|
||||
|
||||
errorMessage.HeadOrNone().Match(
|
||||
error =>
|
||||
{
|
||||
_snackbar.Add(error.Value, Severity.Error);
|
||||
_logger.LogError("Error saving filler preset: {Error}", error.Value);
|
||||
},
|
||||
() => _navigationManager.NavigateTo("/media/filler/presets"));
|
||||
}
|
||||
}
|
||||
|
||||
private Task<IEnumerable<MediaCollectionViewModel>> SearchMediaCollections(string value) =>
|
||||
_mediaCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) =>
|
||||
_multiCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) =>
|
||||
_smartCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) =>
|
||||
_televisionShows.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) =>
|
||||
_televisionSeasons.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) =>
|
||||
_artists.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
@page "/media/filler/presets"
|
||||
@using ErsatzTV.Application.Filler
|
||||
@using ErsatzTV.Application.Configuration.Queries
|
||||
@using ErsatzTV.Application.Configuration.Commands
|
||||
@using ErsatzTV.Application.Filler.Commands
|
||||
@using ErsatzTV.Application.Filler.Queries
|
||||
@using ErsatzTV.Core.Domain.Filler
|
||||
@inject IDialogService _dialog
|
||||
@inject IMediator _mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/media/filler/presets/add">
|
||||
Add Filler Preset
|
||||
</MudButton>
|
||||
</div>
|
||||
<MudTable Class="mt-4"
|
||||
Hover="true"
|
||||
@bind-RowsPerPage="@_fillerPresetsRowsPerPage"
|
||||
ServerData="@(new Func<TableState, Task<TableData<FillerPresetViewModel>>>(ServerReloadFillerPresets))"
|
||||
Dense="true"
|
||||
@ref="_fillerPresetsTable">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Filler Presets</MudText>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 120px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Filler Kind</MudTh>
|
||||
<MudTh/>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Filler Kind">
|
||||
@(
|
||||
context.FillerKind switch {
|
||||
FillerKind.PreRoll => "Pre-Roll",
|
||||
FillerKind.MidRoll => "Mid-Roll",
|
||||
FillerKind.PostRoll => "Post-Roll",
|
||||
FillerKind.Fallback => "Fallback",
|
||||
FillerKind.Tail => "Tail",
|
||||
_ => "None"
|
||||
}
|
||||
)
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<div style="align-items: center; display: flex;">
|
||||
<MudTooltip Text="Edit Filler Preset">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Link="@($"/media/filler/presets/{context.Id}/edit")">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete Filler Preset">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteFillerPreset(context))">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private MudTable<FillerPresetViewModel> _fillerPresetsTable;
|
||||
|
||||
private int _fillerPresetsRowsPerPage;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_fillerPresetsRowsPerPage = await _mediator.Send(new GetConfigElementByKey(ConfigElementKey.FillerPresetsPageSize))
|
||||
.Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10));
|
||||
}
|
||||
|
||||
private async Task DeleteFillerPreset(FillerPresetViewModel fillerPreset)
|
||||
{
|
||||
var parameters = new DialogParameters { { "EntityType", "filler preset" }, { "EntityName", fillerPreset.Name } };
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
|
||||
|
||||
IDialogReference dialog = _dialog.Show<DeleteDialog>("Delete Filler Preset", parameters, options);
|
||||
DialogResult result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
await _mediator.Send(new DeleteFillerPreset(fillerPreset.Id));
|
||||
await _fillerPresetsTable.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<TableData<FillerPresetViewModel>> ServerReloadFillerPresets(TableState state)
|
||||
{
|
||||
await _mediator.Send(new SaveConfigElementByKey(ConfigElementKey.FillerPresetsPageSize, state.PageSize.ToString()));
|
||||
|
||||
PagedFillerPresetsViewModel data = await _mediator.Send(new GetPagedFillerPresets(state.Page, state.PageSize));
|
||||
return new TableData<FillerPresetViewModel> { TotalItems = data.TotalCount, Items = data.Page };
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,12 @@
|
||||
@inject IMediator _mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/playouts/add">
|
||||
Add Playout
|
||||
</MudButton>
|
||||
<MudTable Hover="true"
|
||||
Dense="true"
|
||||
Class="mt-4"
|
||||
SelectedItemChanged="@(async (PlayoutNameViewModel x) => await PlayoutSelected(x))"
|
||||
@bind-RowsPerPage="@_rowsPerPage"
|
||||
ServerData="@(new Func<TableState, Task<TableData<PlayoutNameViewModel>>>(ServerReload))"
|
||||
@@ -64,9 +68,6 @@
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/playouts/add" Class="mt-4">
|
||||
Add Playout
|
||||
</MudButton>
|
||||
|
||||
@if (_selectedPlayoutId != null)
|
||||
{
|
||||
@@ -78,6 +79,7 @@
|
||||
@ref="_detailTable">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Playout Detail</MudText>
|
||||
<MudSwitch T="bool" Class="ml-6" @bind-Checked="@ShowFiller" Color="Color.Secondary" Label="Show Filler"/>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Start</MudTh>
|
||||
@@ -102,6 +104,20 @@
|
||||
private int _rowsPerPage;
|
||||
private int _detailRowsPerPage;
|
||||
private int? _selectedPlayoutId;
|
||||
private bool _showFiller;
|
||||
|
||||
private bool ShowFiller
|
||||
{
|
||||
get => _showFiller;
|
||||
set
|
||||
{
|
||||
if (_showFiller != value)
|
||||
{
|
||||
_showFiller = value;
|
||||
_detailTable.ReloadServerData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -109,6 +125,8 @@
|
||||
.Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10));
|
||||
_detailRowsPerPage = await _mediator.Send(new GetConfigElementByKey(ConfigElementKey.PlayoutsDetailPageSize))
|
||||
.Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10));
|
||||
_showFiller = await _mediator.Send(new GetConfigElementByKey(ConfigElementKey.PlayoutsDetailShowFiller))
|
||||
.Map(maybeShow => maybeShow.Match(ce => bool.TryParse(ce.Value, out bool show) && show, () => false));
|
||||
}
|
||||
|
||||
private async Task PlayoutSelected(PlayoutNameViewModel playout)
|
||||
@@ -117,6 +135,14 @@
|
||||
await _detailTable.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task ShowFillerChanged()
|
||||
{
|
||||
if (_selectedPlayoutId != null)
|
||||
{
|
||||
await _detailTable.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeletePlayout(PlayoutNameViewModel playout)
|
||||
{
|
||||
var parameters = new DialogParameters { { "EntityType", "playout" }, { "EntityName", $"{playout.ScheduleName} on {playout.ChannelNumber} - {playout.ChannelName}" } };
|
||||
@@ -178,11 +204,12 @@
|
||||
private async Task<TableData<PlayoutItemViewModel>> DetailServerReload(TableState state)
|
||||
{
|
||||
await _mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailPageSize, state.PageSize.ToString()));
|
||||
await _mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailShowFiller, _showFiller.ToString()));
|
||||
|
||||
if (_selectedPlayoutId.HasValue)
|
||||
{
|
||||
PagedPlayoutItemsViewModel data =
|
||||
await _mediator.Send(new GetFuturePlayoutItemsById(_selectedPlayoutId.Value, state.Page, state.PageSize));
|
||||
await _mediator.Send(new GetFuturePlayoutItemsById(_selectedPlayoutId.Value, _showFiller, state.Page, state.PageSize));
|
||||
return new TableData<PlayoutItemViewModel>
|
||||
{
|
||||
TotalItems = data.TotalCount,
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
@using ErsatzTV.Application.ProgramSchedules.Queries
|
||||
@using ErsatzTV.Application.Television.Queries
|
||||
@using ErsatzTV.Application.Artists.Queries
|
||||
@using ErsatzTV.Application.Filler
|
||||
@using ErsatzTV.Application.Filler.Queries
|
||||
@using ErsatzTV.Core.Domain.Filler
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject ILogger<ScheduleItemsEditor> _logger
|
||||
@inject ISnackbar _snackbar
|
||||
@@ -36,7 +39,7 @@
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime == null ? string.Empty : DateTime.Today.Add(context.StartTime.Value).ToShortTimeString() : "Dynamic")
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">
|
||||
@@ -81,10 +84,10 @@
|
||||
|
||||
@if (_selectedItem is not null)
|
||||
{
|
||||
<div style="display: flex; flex-direction: row;" class="mt-6">
|
||||
<div style="flex-grow: 1; max-width: 400px;">
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<div style="display: flex; flex-direction: row;" class="mt-6">
|
||||
<div style="flex-grow: 1; max-width: 400px;" class="mr-6">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudSelect Label="Start Type" @bind-Value="_selectedItem.StartType" For="@(() => _selectedItem.StartType)">
|
||||
@@ -188,72 +191,6 @@
|
||||
<MudSelectItem Value="@TailMode.Offline">Offline</MudSelectItem>
|
||||
<MudSelectItem Value="@TailMode.Filler">Filler</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect Class="mt-3" Label="Tail Collection Type" @bind-Value="_selectedItem.TailCollectionType" For="@(() => _selectedItem.TailCollectionType)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)">
|
||||
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
|
||||
{
|
||||
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MediaCollectionViewModel"
|
||||
Label="Collection"
|
||||
@bind-value="_selectedItem.TailCollection"
|
||||
SearchFunc="@SearchMediaCollections"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.MultiCollection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MultiCollectionViewModel"
|
||||
Label="Multi Collection"
|
||||
@bind-value="_selectedItem.TailMultiCollection"
|
||||
SearchFunc="@SearchMultiCollections"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.SmartCollection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="SmartCollectionViewModel"
|
||||
Label="Smart Collection"
|
||||
@bind-value="_selectedItem.TailSmartCollection"
|
||||
SearchFunc="@SearchSmartCollections"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Show"
|
||||
@bind-value="_selectedItem.TailMediaItem"
|
||||
SearchFunc="@SearchTelevisionShows"
|
||||
ToStringFunc="@(s => s?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Season"
|
||||
@bind-value="_selectedItem.TailMediaItem"
|
||||
SearchFunc="@SearchTelevisionSeasons"
|
||||
ToStringFunc="@(s => s?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
@if (_selectedItem.TailCollectionType == ProgramScheduleItemCollectionType.Artist)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Artist"
|
||||
@bind-value="_selectedItem.TailMediaItem"
|
||||
SearchFunc="@SearchArtists"
|
||||
ToStringFunc="@(s => s?.Name)"
|
||||
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration || _selectedItem.TailMode != TailMode.Filler)"/>
|
||||
}
|
||||
<MudTextField Class="mt-3" Label="Custom Title" @bind-Value="@_selectedItem.CustomTitle" For="@(() => _selectedItem.CustomTitle)"/>
|
||||
<MudSelect Class="mt-3" Label="Guide Mode" @bind-Value="@_selectedItem.GuideMode" For="@(() => _selectedItem.GuideMode)">
|
||||
<MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem>
|
||||
@@ -261,28 +198,55 @@
|
||||
</MudSelect>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
<div style="flex-grow: 1; max-width: 400px;">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="FillerPresetViewModel"
|
||||
Label="Pre-Roll Filler"
|
||||
@bind-value="_selectedItem.PreRollFiller"
|
||||
SearchFunc="@(arg => SearchFillerPresets(FillerKind.PreRoll, arg))"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
ResetValueOnEmptyText="true"
|
||||
Clearable="true"/>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="FillerPresetViewModel"
|
||||
Label="Mid-Roll Filler"
|
||||
@bind-value="_selectedItem.MidRollFiller"
|
||||
SearchFunc="@(arg => SearchFillerPresets(FillerKind.MidRoll, arg))"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
ResetValueOnEmptyText="true"
|
||||
Clearable="true"/>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="FillerPresetViewModel"
|
||||
Label="Post-Roll Filler"
|
||||
@bind-value="_selectedItem.PostRollFiller"
|
||||
SearchFunc="@(arg => SearchFillerPresets(FillerKind.PostRoll, arg))"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
ResetValueOnEmptyText="true"
|
||||
Clearable="true"/>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="FillerPresetViewModel"
|
||||
Label="Tail Filler"
|
||||
@bind-value="_selectedItem.TailFiller"
|
||||
SearchFunc="@(arg => SearchFillerPresets(FillerKind.Tail, arg))"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
ResetValueOnEmptyText="true"
|
||||
Clearable="true"/>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="FillerPresetViewModel"
|
||||
Label="Fallback Filler"
|
||||
@bind-value="_selectedItem.FallbackFiller"
|
||||
SearchFunc="@(arg => SearchFillerPresets(FillerKind.Fallback, arg))"
|
||||
ToStringFunc="@(c => c?.Name)"
|
||||
ResetValueOnEmptyText="true"
|
||||
Clearable="true"/>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</div>
|
||||
</div>
|
||||
<div style="max-width: 600px;" class="ml-6">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudText Class="mt-3" Typo="Typo.h6">
|
||||
Start Type
|
||||
</MudText>
|
||||
<MudText Class="mt-3">
|
||||
A <b>fixed</b> start type requires a <b>start time</b>, while a <b>dynamic</b> start type means the schedule item will start immediately after the preceding schedule item.
|
||||
</MudText>
|
||||
<MudText Class="mt-3 mb-2" Typo="Typo.h6">Playout Mode</MudText>
|
||||
<ul class="mud-typography-body1">
|
||||
<li><b>One</b> - to play one media item from the collection before advancing to the next schedule item.</li>
|
||||
<li><b>Multiple</b> - to play a specified <b>count</b> of media items from the collection before advancing to the next schedule item. A count of <b>0</b> is a special case that will automatically and always equal the number of items in the collection, so each item from the collection will be played once before advancing.</li>
|
||||
<li><b>Duration</b> - to play the maximum number of complete media items that will fit in the specified <b>playout duration</b>, before either going offline for the remainder of the <b>playout duration</b> (an <b>offline tail</b>), or immediately advancing to the next schedule item.</li>
|
||||
<li><b>Flood</b> - to play media items from the collection forever, or until the next schedule item's <b>start time</b> if one exists.</li>
|
||||
</ul>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@@ -298,6 +262,7 @@
|
||||
private List<NamedMediaItemViewModel> _televisionShows;
|
||||
private List<NamedMediaItemViewModel> _televisionSeasons;
|
||||
private List<NamedMediaItemViewModel> _artists;
|
||||
private List<FillerPresetViewModel> _fillerPresets;
|
||||
|
||||
private ProgramScheduleItemEditViewModel _selectedItem;
|
||||
|
||||
@@ -318,17 +283,27 @@
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_artists = await _mediator.Send(new GetAllArtists())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
_fillerPresets = await _mediator.Send(new GetAllFillerPresets())
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
|
||||
string name = string.Empty;
|
||||
Option<ProgramScheduleViewModel> maybeSchedule = await _mediator.Send(new GetProgramScheduleById(Id));
|
||||
maybeSchedule.IfSome(vm => name = vm.Name);
|
||||
|
||||
Option<IEnumerable<ProgramScheduleItemViewModel>> maybeResults = await _mediator.Send(new GetProgramScheduleItems(Id));
|
||||
maybeResults.IfSome(items => _schedule = new ProgramScheduleItemsEditViewModel
|
||||
foreach (IEnumerable<ProgramScheduleItemViewModel> items in maybeResults)
|
||||
{
|
||||
Name = name,
|
||||
Items = items.Map(ProjectToEditViewModel).ToList()
|
||||
});
|
||||
_schedule = new ProgramScheduleItemsEditViewModel
|
||||
{
|
||||
Name = name,
|
||||
Items = items.Map(ProjectToEditViewModel).ToList()
|
||||
};
|
||||
|
||||
if (_schedule.Items.Count == 1)
|
||||
{
|
||||
_selectedItem = _schedule.Items.Head();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item)
|
||||
@@ -347,7 +322,12 @@
|
||||
MediaItem = item.MediaItem,
|
||||
PlaybackOrder = item.PlaybackOrder,
|
||||
CustomTitle = item.CustomTitle,
|
||||
GuideMode = item.GuideMode
|
||||
GuideMode = item.GuideMode,
|
||||
PreRollFiller = item.PreRollFiller,
|
||||
MidRollFiller = item.MidRollFiller,
|
||||
PostRollFiller = item.PostRollFiller,
|
||||
TailFiller = item.TailFiller,
|
||||
FallbackFiller = item.FallbackFiller
|
||||
};
|
||||
|
||||
switch (item)
|
||||
@@ -358,11 +338,6 @@
|
||||
case ProgramScheduleItemDurationViewModel duration:
|
||||
result.PlayoutDuration = duration.PlayoutDuration;
|
||||
result.TailMode = duration.TailMode;
|
||||
result.TailCollectionType = duration.TailCollectionType;
|
||||
result.TailCollection = duration.TailCollection;
|
||||
result.TailMultiCollection = duration.TailMultiCollection;
|
||||
result.TailSmartCollection = duration.TailSmartCollection;
|
||||
result.TailMediaItem = duration.TailMediaItem;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -422,6 +397,9 @@
|
||||
private Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) =>
|
||||
_artists.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private Task<IEnumerable<FillerPresetViewModel>> SearchFillerPresets(FillerKind fillerKind, string value) =>
|
||||
_fillerPresets.Filter(p => p.FillerKind == fillerKind && p.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||
|
||||
private async Task SaveChanges()
|
||||
{
|
||||
var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem(
|
||||
@@ -438,13 +416,13 @@
|
||||
item.MultipleCount,
|
||||
item.PlayoutDuration,
|
||||
item.TailMode,
|
||||
item.TailCollectionType,
|
||||
item.TailCollection?.Id,
|
||||
item.TailMultiCollection?.Id,
|
||||
item.TailSmartCollection?.Id,
|
||||
item.TailMediaItem?.MediaItemId,
|
||||
item.CustomTitle,
|
||||
item.GuideMode)).ToList();
|
||||
item.GuideMode,
|
||||
item.PreRollFiller?.Id,
|
||||
item.MidRollFiller?.Id,
|
||||
item.PostRollFiller?.Id,
|
||||
item.TailFiller?.Id,
|
||||
item.FallbackFiller?.Id)).ToList();
|
||||
|
||||
Seq<BaseError> errorMessages = await _mediator.Send(new ReplaceProgramScheduleItems(Id, items)).Map(e => e.LeftToSeq());
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime == null ? string.Empty : DateTime.Today.Add(context.StartTime.Value).ToShortTimeString() : "Dynamic")
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Playout Mode">@context.PlayoutMode</MudTd>
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
DialogResult result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
|
||||
{
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, ProgramScheduleItemCollectionType.Collection, null, null, null, null, null, GuideMode.Normal));
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null));
|
||||
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
DialogResult result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
|
||||
{
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, ProgramScheduleItemCollectionType.Collection, null, null, null, null, null, GuideMode.Normal));
|
||||
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null));
|
||||
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<MudNavGroup Title="Lists" Expanded="true">
|
||||
<MudNavLink Href="/media/collections">Collections</MudNavLink>
|
||||
<MudNavLink Href="/media/trakt/lists">Trakt Lists</MudNavLink>
|
||||
<MudNavLink Href="/media/filler/presets">Filler Presets</MudNavLink>
|
||||
</MudNavGroup>
|
||||
<MudNavLink Href="/schedules">Schedules</MudNavLink>
|
||||
<MudNavLink Href="/playouts">Playouts</MudNavLink>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Filler;
|
||||
using ErsatzTV.ViewModels;
|
||||
using FluentValidation;
|
||||
|
||||
namespace ErsatzTV.Validators
|
||||
{
|
||||
public class FillerPresetEditViewModelValidator : AbstractValidator<FillerPresetEditViewModel>
|
||||
{
|
||||
public FillerPresetEditViewModelValidator()
|
||||
{
|
||||
RuleFor(fp => fp.Name).NotEmpty();
|
||||
RuleFor(fp => fp.FillerKind).NotEqual(FillerKind.None);
|
||||
|
||||
When(
|
||||
fp => fp.FillerKind != FillerKind.Fallback && fp.FillerKind != FillerKind.Tail,
|
||||
() => RuleFor(fp => fp.FillerMode).NotEqual(FillerMode.None));
|
||||
When(
|
||||
fp => fp.FillerMode == FillerMode.Count,
|
||||
() => RuleFor(fp => fp.Count).NotNull().GreaterThanOrEqualTo(0));
|
||||
When(
|
||||
fp => fp.FillerMode == FillerMode.Duration,
|
||||
() => RuleFor(fp => fp.Duration).NotNull());
|
||||
|
||||
When(
|
||||
fp => fp.CollectionType == ProgramScheduleItemCollectionType.Collection,
|
||||
() => RuleFor(fp => fp.Collection).NotNull());
|
||||
When(
|
||||
fp => fp.CollectionType == ProgramScheduleItemCollectionType.MultiCollection,
|
||||
() => RuleFor(fp => fp.MultiCollection).NotNull());
|
||||
When(
|
||||
fp => fp.CollectionType == ProgramScheduleItemCollectionType.SmartCollection,
|
||||
() => RuleFor(fp => fp.SmartCollection).NotNull());
|
||||
When(
|
||||
fp => fp.CollectionType is ProgramScheduleItemCollectionType.Artist or ProgramScheduleItemCollectionType
|
||||
.TelevisionShow or ProgramScheduleItemCollectionType.TelevisionSeason,
|
||||
() => RuleFor(fp => fp.MediaItem).NotNull());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.Filler.Commands;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Filler;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using Unit = LanguageExt.Unit;
|
||||
|
||||
namespace ErsatzTV.ViewModels
|
||||
{
|
||||
public class FillerPresetEditViewModel
|
||||
{
|
||||
private FillerKind _fillerKind;
|
||||
private TimeSpan? _duration;
|
||||
private FillerMode _fillerMode;
|
||||
private int? _count;
|
||||
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public FillerKind FillerKind
|
||||
{
|
||||
get => _fillerKind;
|
||||
set
|
||||
{
|
||||
_fillerKind = value;
|
||||
if (_fillerKind is FillerKind.Fallback or FillerKind.Tail)
|
||||
{
|
||||
FillerMode = FillerMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FillerMode FillerMode
|
||||
{
|
||||
get => FillerKind is FillerKind.Fallback or FillerKind.Tail ? FillerMode.None : _fillerMode;
|
||||
set => _fillerMode = value;
|
||||
}
|
||||
|
||||
public TimeSpan? Duration
|
||||
{
|
||||
get => FillerMode == FillerMode.Duration ? _duration : null;
|
||||
set => _duration = value;
|
||||
}
|
||||
|
||||
public int? Count
|
||||
{
|
||||
get => FillerMode == FillerMode.Count ? _count : null;
|
||||
set => _count = value;
|
||||
}
|
||||
|
||||
public int? PadToNearestMinute { get; set; }
|
||||
public ProgramScheduleItemCollectionType CollectionType { get; set; }
|
||||
public MediaCollectionViewModel Collection { get; set; }
|
||||
public NamedMediaItemViewModel MediaItem { get; set; }
|
||||
public MultiCollectionViewModel MultiCollection { get; set; }
|
||||
public SmartCollectionViewModel SmartCollection { get; set; }
|
||||
|
||||
public IRequest<Either<BaseError, Unit>> ToEdit() =>
|
||||
new UpdateFillerPreset(
|
||||
Id,
|
||||
Name,
|
||||
FillerKind,
|
||||
FillerMode,
|
||||
Duration.Map(FixDuration),
|
||||
Count,
|
||||
PadToNearestMinute,
|
||||
CollectionType,
|
||||
Collection?.Id,
|
||||
MediaItem?.MediaItemId,
|
||||
MultiCollection?.Id,
|
||||
SmartCollection?.Id);
|
||||
|
||||
public IRequest<Either<BaseError, Unit>> ToUpdate() =>
|
||||
new CreateFillerPreset(
|
||||
Name,
|
||||
FillerKind,
|
||||
FillerMode,
|
||||
Duration.Map(FixDuration),
|
||||
Count,
|
||||
PadToNearestMinute,
|
||||
CollectionType,
|
||||
Collection?.Id,
|
||||
MediaItem?.MediaItemId,
|
||||
MultiCollection?.Id,
|
||||
SmartCollection?.Id);
|
||||
|
||||
private static TimeSpan FixDuration(TimeSpan duration) =>
|
||||
duration > TimeSpan.FromDays(1) ? duration.Subtract(TimeSpan.FromDays(1)) : duration;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ErsatzTV.Annotations;
|
||||
using ErsatzTV.Application.Filler;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Core.Domain;
|
||||
@@ -14,7 +15,6 @@ namespace ErsatzTV.ViewModels
|
||||
private int? _multipleCount;
|
||||
private TimeSpan? _playoutDuration;
|
||||
private TimeSpan? _startTime;
|
||||
private ProgramScheduleItemCollectionType _tailCollectionType;
|
||||
|
||||
public int Id { get; set; }
|
||||
public int Index { get; set; }
|
||||
@@ -59,6 +59,11 @@ namespace ErsatzTV.ViewModels
|
||||
public MultiCollectionViewModel MultiCollection { get; set; }
|
||||
public SmartCollectionViewModel SmartCollection { get; set; }
|
||||
public NamedMediaItemViewModel MediaItem { get; set; }
|
||||
public FillerPresetViewModel PreRollFiller { get; set; }
|
||||
public FillerPresetViewModel MidRollFiller { get; set; }
|
||||
public FillerPresetViewModel PostRollFiller { get; set; }
|
||||
public FillerPresetViewModel TailFiller { get; set; }
|
||||
public FillerPresetViewModel FallbackFiller { get; set; }
|
||||
|
||||
public string CollectionName => CollectionType switch
|
||||
{
|
||||
@@ -87,33 +92,6 @@ namespace ErsatzTV.ViewModels
|
||||
|
||||
public TailMode TailMode { get; set; }
|
||||
|
||||
public ProgramScheduleItemCollectionType TailCollectionType
|
||||
{
|
||||
get => _tailCollectionType;
|
||||
set
|
||||
{
|
||||
if (_tailCollectionType != value)
|
||||
{
|
||||
_tailCollectionType = value;
|
||||
|
||||
TailCollection = null;
|
||||
TailMultiCollection = null;
|
||||
TailMediaItem = null;
|
||||
TailSmartCollection = null;
|
||||
|
||||
OnPropertyChanged(nameof(TailCollection));
|
||||
OnPropertyChanged(nameof(TailMultiCollection));
|
||||
OnPropertyChanged(nameof(TailMediaItem));
|
||||
OnPropertyChanged(nameof(TailSmartCollection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MediaCollectionViewModel TailCollection { get; set; }
|
||||
public MultiCollectionViewModel TailMultiCollection { get; set; }
|
||||
public SmartCollectionViewModel TailSmartCollection { get; set; }
|
||||
public NamedMediaItemViewModel TailMediaItem { get; set; }
|
||||
|
||||
public string CustomTitle { get; set; }
|
||||
|
||||
public GuideMode GuideMode { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user