Browse Source

preview block playout in block editor (#1558)

* block editor cleanup

* preview block playout

* cleanup
pull/1562/head
Jason Dove 2 years ago committed by GitHub
parent
commit
a59f71039c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ErsatzTV.Application/Playouts/Mapper.cs
  2. 3
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayout.cs
  3. 118
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  4. 7
      ErsatzTV.Application/Scheduling/Mapper.cs
  5. 3
      ErsatzTV.Application/Scheduling/PlayoutItemPreviewViewModel.cs
  6. 9
      ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutBuilder.cs
  7. 48
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  8. 178
      ErsatzTV/Pages/BlockEditor.razor

4
ErsatzTV.Application/Playouts/Mapper.cs

@ -22,7 +22,7 @@ internal static class Mapper
programScheduleAlternate.DaysOfMonth, programScheduleAlternate.DaysOfMonth,
programScheduleAlternate.MonthsOfYear); programScheduleAlternate.MonthsOfYear);
private static string GetDisplayTitle(PlayoutItem playoutItem) internal static string GetDisplayTitle(PlayoutItem playoutItem)
{ {
switch (playoutItem.MediaItem) switch (playoutItem.MediaItem)
{ {
@ -80,7 +80,7 @@ internal static class Mapper
} }
} }
private static string GetDisplayDuration(TimeSpan duration) => internal static string GetDisplayDuration(TimeSpan duration) =>
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}", duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",

3
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayout.cs

@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Scheduling;
public record PreviewBlockPlayout(ReplaceBlockItems Data) : IRequest<List<PlayoutItemPreviewViewModel>>;

118
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs

@ -0,0 +1,118 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
namespace ErsatzTV.Application.Scheduling;
public class PreviewBlockPlayoutHandler(
IDbContextFactory<TvContext> dbContextFactory,
IBlockPlayoutBuilder blockPlayoutBuilder)
: IRequestHandler<PreviewBlockPlayout, List<PlayoutItemPreviewViewModel>>
{
public async Task<List<PlayoutItemPreviewViewModel>> Handle(
PreviewBlockPlayout request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
var template = new Template
{
Items = []
};
template.Items.Add(
new TemplateItem
{
Block = MapToBlock(request.Data),
StartTime = TimeSpan.Zero,
Template = template
});
var playout = new Playout
{
Channel = new Channel(Guid.NewGuid())
{
Number = "1",
Name = "Block Preview"
},
Items = [],
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Block,
PlayoutHistory = [],
Templates =
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
Template = template
}
]
};
await blockPlayoutBuilder.Build(
playout,
PlayoutBuildMode.Reset,
NullLogger.Instance,
1,
randomizeStartPoints: true,
cancellationToken);
// load playout item details for title
foreach (PlayoutItem playoutItem in playout.Items)
{
Option<MediaItem> maybeMediaItem = await dbContext.MediaItems
.AsNoTracking()
.Include(mi => (mi as Movie).MovieMetadata)
.Include(mi => (mi as Movie).MediaVersions)
.Include(mi => (mi as MusicVideo).MusicVideoMetadata)
.Include(mi => (mi as MusicVideo).MediaVersions)
.Include(mi => (mi as MusicVideo).Artist)
.ThenInclude(mm => mm.ArtistMetadata)
.Include(mi => (mi as Episode).EpisodeMetadata)
.Include(mi => (mi as Episode).MediaVersions)
.Include(mi => (mi as Episode).Season)
.ThenInclude(s => s.SeasonMetadata)
.Include(mi => (mi as Episode).Season.Show)
.ThenInclude(s => s.ShowMetadata)
.Include(mi => (mi as OtherVideo).OtherVideoMetadata)
.Include(mi => (mi as OtherVideo).MediaVersions)
.Include(mi => (mi as Song).SongMetadata)
.Include(mi => (mi as Song).MediaVersions)
.SelectOneAsync(mi => mi.Id, mi => mi.Id == playoutItem.MediaItemId);
foreach (MediaItem mediaItem in maybeMediaItem)
{
playoutItem.MediaItem = mediaItem;
}
}
return playout.Items.Map(Mapper.ProjectToViewModel).ToList();
}
private static Block MapToBlock(ReplaceBlockItems request) =>
new()
{
Minutes = request.Minutes,
Name = request.Name,
Items = request.Items.Map(MapToBlockItem).ToList(),
};
private static BlockItem MapToBlockItem(int id, ReplaceBlockItem request) =>
new()
{
Id = id,
Index = request.Index,
CollectionType = request.CollectionType,
CollectionId = request.CollectionId,
MultiCollectionId = request.MultiCollectionId,
SmartCollectionId = request.SmartCollectionId,
MediaItemId = request.MediaItemId,
PlaybackOrder = request.PlaybackOrder
};
}

7
ErsatzTV.Application/Scheduling/Mapper.cs

@ -53,4 +53,11 @@ internal static class Mapper
playoutTemplate.DaysOfWeek, playoutTemplate.DaysOfWeek,
playoutTemplate.DaysOfMonth, playoutTemplate.DaysOfMonth,
playoutTemplate.MonthsOfYear); playoutTemplate.MonthsOfYear);
internal static PlayoutItemPreviewViewModel ProjectToViewModel(PlayoutItem playoutItem) =>
new(
Playouts.Mapper.GetDisplayTitle(playoutItem),
playoutItem.StartOffset.TimeOfDay,
playoutItem.FinishOffset.TimeOfDay,
Playouts.Mapper.GetDisplayDuration(playoutItem.FinishOffset - playoutItem.StartOffset));
} }

3
ErsatzTV.Application/Scheduling/PlayoutItemPreviewViewModel.cs

@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Scheduling;
public record PlayoutItemPreviewViewModel(string Title, TimeSpan Start, TimeSpan Finish, string Duration);

9
ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutBuilder.cs

@ -1,9 +1,18 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.Interfaces.Scheduling; namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IBlockPlayoutBuilder public interface IBlockPlayoutBuilder
{ {
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken); Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
ILogger customLogger,
int daysToBuild,
bool randomizeStartPoints,
CancellationToken cancellationToken);
} }

48
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -19,7 +19,24 @@ public class BlockPlayoutBuilder(
{ {
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken) public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
{ {
logger.LogDebug( int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
.IfNoneAsync(2);
return await Build(playout, mode, logger, daysToBuild, randomizeStartPoints: false, cancellationToken);
}
public async Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
ILogger customLogger,
int daysToBuild,
bool randomizeStartPoints,
CancellationToken cancellationToken)
{
// ReSharper disable once LocalVariableHidesPrimaryConstructorParameter
ILogger log = customLogger ?? logger;
log.LogDebug(
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}", "Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
playout.Id, playout.Id,
playout.Channel.Number, playout.Channel.Number,
@ -34,8 +51,6 @@ public class BlockPlayoutBuilder(
DateTimeOffset start = DateTimeOffset.Now; DateTimeOffset start = DateTimeOffset.Now;
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
.IfNoneAsync(2);
// get blocks to schedule // get blocks to schedule
List<EffectiveBlock> blocksToSchedule = EffectiveBlock.GetEffectiveBlocks(playout, start, daysToBuild); List<EffectiveBlock> blocksToSchedule = EffectiveBlock.GetEffectiveBlocks(playout, start, daysToBuild);
@ -43,8 +58,9 @@ public class BlockPlayoutBuilder(
// get all collection items for the playout // get all collection items for the playout
Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(blocksToSchedule); Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(blocksToSchedule);
Dictionary<PlayoutItem, BlockKey> itemBlockKeys = BlockPlayoutChangeDetection.GetPlayoutItemToBlockKeyMap(playout); Dictionary<PlayoutItem, BlockKey> itemBlockKeys =
BlockPlayoutChangeDetection.GetPlayoutItemToBlockKeyMap(playout);
// remove items without a block key (shouldn't happen often, just upgrades) // remove items without a block key (shouldn't happen often, just upgrades)
playout.Items.RemoveAll(i => !itemBlockKeys.ContainsKey(i)); playout.Items.RemoveAll(i => !itemBlockKeys.ContainsKey(i));
@ -64,14 +80,14 @@ public class BlockPlayoutBuilder(
{ {
currentTime = effectiveBlock.Start; currentTime = effectiveBlock.Start;
logger.LogDebug( log.LogDebug(
"Will schedule block {Block} at {Start}", "Will schedule block {Block} at {Start}",
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
effectiveBlock.Start); effectiveBlock.Start);
} }
else else
{ {
logger.LogDebug( log.LogDebug(
"Will schedule block {Block} with start {Start} at {ActualStart}", "Will schedule block {Block} with start {Start} at {ActualStart}",
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
effectiveBlock.Start, effectiveBlock.Start,
@ -90,7 +106,7 @@ public class BlockPlayoutBuilder(
if (currentTime >= blockFinish) if (currentTime >= blockFinish)
{ {
logger.LogDebug( log.LogDebug(
"Current time {Time} for block {Block} is beyond block finish {Finish}; will stop with this block's items", "Current time {Time} for block {Block} is beyond block finish {Finish}; will stop with this block's items",
currentTime, currentTime,
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
@ -129,7 +145,7 @@ public class BlockPlayoutBuilder(
// seek to the appropriate place in the collection enumerator // seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory history in maybeHistory) foreach (PlayoutHistory history in maybeHistory)
{ {
logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details); log.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
HistoryDetails.MoveToNextItem( HistoryDetails.MoveToNextItem(
collectionItems, collectionItems,
@ -138,9 +154,19 @@ public class BlockPlayoutBuilder(
blockItem.PlaybackOrder); blockItem.PlaybackOrder);
} }
if (maybeHistory.IsNone && randomizeStartPoints)
{
enumerator.ResetState(
new CollectionEnumeratorState
{
Seed = new Random().Next(),
Index = new Random().Next(collectionItems.Count)
});
}
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
logger.LogDebug( log.LogDebug(
"current item: {Id} / {Title}", "current item: {Id} / {Title}",
mediaItem.Id, mediaItem.Id,
mediaItem is Episode e ? GetTitle(e) : string.Empty); mediaItem is Episode e ? GetTitle(e) : string.Empty);
@ -223,7 +249,7 @@ public class BlockPlayoutBuilder(
group.Add(history); group.Add(history);
} }
foreach ((string key, List<PlayoutHistory> group) in groups) foreach ((string _, List<PlayoutHistory> group) in groups)
{ {
//logger.LogDebug("History key {Key} has {Count} items in group", key, group.Count); //logger.LogDebug("History key {Key} has {Count} items in group", key, group.Count);

178
ErsatzTV/Pages/BlockEditor.razor

@ -25,67 +25,80 @@
</MudItem> </MudItem>
<MudItem xs="6"> <MudItem xs="6">
<MudSelect T="int" @bind-Value="_durationMinutes" Adornment="Adornment.End" AdornmentText="minutes"> <MudSelect T="int" @bind-Value="_durationMinutes" Adornment="Adornment.End" AdornmentText="minutes">
<MudSelectItem Value="0" /> <MudSelectItem Value="0"/>
<MudSelectItem Value="15" /> <MudSelectItem Value="15"/>
<MudSelectItem Value="30" /> <MudSelectItem Value="30"/>
<MudSelectItem Value="45" /> <MudSelectItem Value="45"/>
</MudSelect> </MudSelect>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</div> </div>
<MudTable Class="mt-6" Hover="true" Items="_block.Items.OrderBy(i => i.Index)" Dense="true" @bind-SelectedItem="_selectedItem">
<ColGroup>
<col/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Collection</MudTh>
<MudTh/>
<MudTh/>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Collection">
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
@context.CollectionName
</MudText>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
OnClick="@(_ => MoveItemUp(context))"
Disabled="@(_block.Items.All(x => x.Index >= context.Index))">
</MudIconButton>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
OnClick="@(_ => MoveItemDown(context))"
Disabled="@(_block.Items.All(x => x.Index <= context.Index))">
</MudIconButton>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemoveBlockItem(context))">
</MudIconButton>
</MudTd>
</RowTemplate>
</MudTable>
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddBlockItem())" Class="mt-4"> <MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddBlockItem())" Class="mt-4">
Add Block Item Add Block Item
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4"> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
Save Changes Save Changes
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => PreviewPlayout())" Class="mt-4 ml-4">
@if (_selectedItem is not null) Preview Block Playout
{ </MudButton>
<EditForm Model="_selectedItem"> <MudGrid>
<FluentValidationValidator/> <MudItem xs="8">
<div style="display: flex; flex-direction: row;" class="mt-6"> <MudTable Class="mt-6" Hover="true" Items="_block.Items.OrderBy(i => i.Index)" Dense="true" @bind-SelectedItem="_selectedItem">
<div style="flex-grow: 1; max-width: 400px;" class="mr-6"> <ColGroup>
<col/>
<col/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Collection</MudTh>
<MudTh>Playback Order</MudTh>
<MudTh/>
<MudTh/>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Collection">
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
@context.CollectionName
</MudText>
</MudTd>
<MudTd DataLabel="Playback Order">
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
@context.PlaybackOrder
</MudText>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
OnClick="@(_ => MoveItemUp(context))"
Disabled="@(_block.Items.All(x => x.Index >= context.Index))">
</MudIconButton>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
OnClick="@(_ => MoveItemDown(context))"
Disabled="@(_block.Items.All(x => x.Index <= context.Index))">
</MudIconButton>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemoveBlockItem(context))">
</MudIconButton>
</MudTd>
</RowTemplate>
</MudTable>
</MudItem>
</MudGrid>
<div class="mt-4">
@if (_selectedItem is not null)
{
<EditForm Model="_selectedItem">
<FluentValidationValidator/>
<div style="max-width: 400px;" class="mr-6">
<MudCard> <MudCard>
<MudCardContent> <MudCardContent>
<MudSelect Class="mt-3" Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)"> <MudSelect Class="mt-3" Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
@ -108,6 +121,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{ {
<MudAutocomplete Class="mt-3" T="MultiCollectionViewModel" Label="Multi Collection" <MudAutocomplete Class="mt-3" T="MultiCollectionViewModel" Label="Multi Collection"
@ -120,6 +134,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{ {
<MudAutocomplete Class="mt-3" T="SmartCollectionViewModel" Label="Smart Collection" <MudAutocomplete Class="mt-3" T="SmartCollectionViewModel" Label="Smart Collection"
@ -132,6 +147,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Show" <MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Show"
@ -144,6 +160,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Season" <MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Season"
@ -157,6 +174,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Artist" <MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Artist"
@ -170,6 +188,7 @@
</MoreItemsTemplate> </MoreItemsTemplate>
</MudAutocomplete> </MudAutocomplete>
} }
<MudSelect Class="mt-3" Label="Playback Order" @bind-Value="@_selectedItem.PlaybackOrder" For="@(() => _selectedItem.PlaybackOrder)"> <MudSelect Class="mt-3" Label="Playback Order" @bind-Value="@_selectedItem.PlaybackOrder" For="@(() => _selectedItem.PlaybackOrder)">
@switch (_selectedItem.CollectionType) @switch (_selectedItem.CollectionType)
{ {
@ -204,8 +223,31 @@
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</div> </div>
</div> </EditForm>
</EditForm> }
</div>
@if (_previewItems != null)
{
<MudTable Class="mt-8"
Hover="true"
Dense="true"
Items="_previewItems">
<ToolBarContent>
<MudText Typo="Typo.h6">Block Playout Preview</MudText>
</ToolBarContent>
<HeaderContent>
<MudTh>Start</MudTh>
<MudTh>Finish</MudTh>
<MudTh>Media Item</MudTh>
<MudTh>Duration</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Start">@context.Start.ToString(@"hh\:mm\:ss")</MudTd>
<MudTd DataLabel="Finish">@context.Finish.ToString(@"hh\:mm\:ss")</MudTd>
<MudTd DataLabel="Media Item">@context.Title</MudTd>
<MudTd DataLabel="Duration">@context.Duration</MudTd>
</RowTemplate>
</MudTable>
} }
</MudContainer> </MudContainer>
@ -217,6 +259,7 @@
private BlockItemsEditViewModel _block = new() { Items = [] }; private BlockItemsEditViewModel _block = new() { Items = [] };
private BlockItemEditViewModel _selectedItem; private BlockItemEditViewModel _selectedItem;
private List<PlayoutItemPreviewViewModel> _previewItems;
private int _durationHours = 0; private int _durationHours = 0;
private int _durationMinutes = 15; private int _durationMinutes = 15;
@ -368,6 +411,21 @@
} }
private async Task SaveChanges() private async Task SaveChanges()
{
Seq<BaseError> errorMessages = await Mediator
.Send(GenerateReplaceRequest(), _cts.Token)
.Map(e => e.LeftToSeq());
errorMessages.HeadOrNone().Match(
error =>
{
Snackbar.Add($"Unexpected error saving block: {error.Value}", Severity.Error);
Logger.LogError("Unexpected error saving block: {Error}", error.Value);
},
() => NavigationManager.NavigateTo("/blocks"));
}
private ReplaceBlockItems GenerateReplaceRequest()
{ {
var items = _block.Items.Map( var items = _block.Items.Map(
item => new ReplaceBlockItem( item => new ReplaceBlockItem(
@ -381,16 +439,12 @@
_block.Minutes = _durationHours * 60 + _durationMinutes; _block.Minutes = _durationHours * 60 + _durationMinutes;
Seq<BaseError> errorMessages = await Mediator return new ReplaceBlockItems(Id, _block.Name, _block.Minutes, items);
.Send(new ReplaceBlockItems(Id, _block.Name, _block.Minutes, items), _cts.Token) }
.Map(e => e.LeftToSeq());
errorMessages.HeadOrNone().Match( private async Task PreviewPlayout()
error => {
{ _selectedItem = null;
Snackbar.Add($"Unexpected error saving block: {error.Value}", Severity.Error); _previewItems = await Mediator.Send(new PreviewBlockPlayout(GenerateReplaceRequest()), _cts.Token);
Logger.LogError("Unexpected error saving block: {Error}", error.Value);
},
() => NavigationManager.NavigateTo("/blocks"));
} }
} }

Loading…
Cancel
Save