mirror of https://github.com/ErsatzTV/ErsatzTV.git
6 changed files with 730 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts; |
||||||
|
|
||||||
|
public record UpdateScriptedPlayout(int PlayoutId, string ScheduleFile) |
||||||
|
: IRequest<Either<BaseError, PlayoutNameViewModel>>; |
||||||
@ -0,0 +1,62 @@ |
|||||||
|
using System.Threading.Channels; |
||||||
|
using ErsatzTV.Application.Channels; |
||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts; |
||||||
|
|
||||||
|
public class |
||||||
|
UpdateScriptedPlayoutHandler( |
||||||
|
IDbContextFactory<TvContext> dbContextFactory, |
||||||
|
ChannelWriter<IBackgroundServiceRequest> workerChannel) |
||||||
|
: IRequestHandler<UpdateScriptedPlayout, |
||||||
|
Either<BaseError, PlayoutNameViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, PlayoutNameViewModel>> Handle( |
||||||
|
UpdateScriptedPlayout request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, Playout> validation = await Validate(dbContext, request); |
||||||
|
return await validation.Apply(playout => ApplyUpdateRequest(dbContext, request, playout)); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<PlayoutNameViewModel> ApplyUpdateRequest( |
||||||
|
TvContext dbContext, |
||||||
|
UpdateScriptedPlayout request, |
||||||
|
Playout playout) |
||||||
|
{ |
||||||
|
playout.ScheduleFile = request.ScheduleFile; |
||||||
|
|
||||||
|
if (await dbContext.SaveChangesAsync() > 0) |
||||||
|
{ |
||||||
|
await workerChannel.WriteAsync(new RefreshChannelData(playout.Channel.Number)); |
||||||
|
} |
||||||
|
|
||||||
|
return new PlayoutNameViewModel( |
||||||
|
playout.Id, |
||||||
|
playout.ScheduleKind, |
||||||
|
playout.Channel.Name, |
||||||
|
playout.Channel.Number, |
||||||
|
playout.Channel.PlayoutMode, |
||||||
|
playout.ProgramSchedule?.Name ?? string.Empty, |
||||||
|
playout.ScheduleFile, |
||||||
|
playout.DailyRebuildTime); |
||||||
|
} |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Playout>> Validate( |
||||||
|
TvContext dbContext, |
||||||
|
UpdateScriptedPlayout request) => |
||||||
|
PlayoutMustExist(dbContext, request); |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Playout>> PlayoutMustExist( |
||||||
|
TvContext dbContext, |
||||||
|
UpdateScriptedPlayout updatePlayout) => |
||||||
|
dbContext.Playouts |
||||||
|
.Include(p => p.Channel) |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == updatePlayout.PlayoutId) |
||||||
|
.Map(o => o.ToValidation<BaseError>("Playout does not exist.")); |
||||||
|
} |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
@page "/playouts/scripted/{Id:int}" |
||||||
|
@using ErsatzTV.Application.Channels |
||||||
|
@using ErsatzTV.Application.Playouts |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@implements IDisposable |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IEntityLocker EntityLocker; |
||||||
|
@inject ILogger<ScriptedPlayoutEditor> Logger |
||||||
|
|
||||||
|
<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" Class="ml-6" OnClick="@(_ => SaveChanges())" StartIcon="@Icons.Material.Filled.Save"> |
||||||
|
Save Scripted Schedule |
||||||
|
</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">@_channelName - Scripted Schedule</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>Scripted Schedule</MudText> |
||||||
|
</div> |
||||||
|
<MudTextField @bind-Value="@_playout.ScheduleFile" For="@(() => _playout.ScheduleFile)"/> |
||||||
|
</MudStack> |
||||||
|
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Maintenance</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>Playout Items and History</MudText> |
||||||
|
</div> |
||||||
|
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Error" OnClick="@(_ => EraseItems(eraseHistory: true))" StartIcon="@Icons.Material.Filled.Delete"> |
||||||
|
Erase Items and History |
||||||
|
</MudButton> |
||||||
|
</MudStack> |
||||||
|
</MudContainer> |
||||||
|
</div> |
||||||
|
</MudForm> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
private PlayoutNameViewModel _playout; |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
private string _channelName; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
Option<string> maybeName = await Mediator.Send(new GetChannelNameByPlayoutId(Id), _cts.Token); |
||||||
|
if (maybeName.IsNone) |
||||||
|
{ |
||||||
|
NavigationManager.NavigateTo("playouts"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (string name in maybeName) |
||||||
|
{ |
||||||
|
_channelName = name; |
||||||
|
} |
||||||
|
|
||||||
|
Option<PlayoutNameViewModel> maybePlayout = await Mediator.Send(new GetPlayoutById(Id), _cts.Token); |
||||||
|
foreach (PlayoutNameViewModel playout in maybePlayout) |
||||||
|
{ |
||||||
|
_playout = playout; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task EraseItems(bool eraseHistory) |
||||||
|
{ |
||||||
|
IRequest request = eraseHistory ? new ErasePlayoutHistory(Id) : new ErasePlayoutItems(Id); |
||||||
|
await Mediator.Send(request, _cts.Token); |
||||||
|
|
||||||
|
string message = eraseHistory ? "Erased playout items and history" : "Erased playout items"; |
||||||
|
Snackbar.Add(message, Severity.Info); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task SaveChanges() |
||||||
|
{ |
||||||
|
if (_playout is null) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Either<BaseError, PlayoutNameViewModel> result = |
||||||
|
await Mediator.Send(new UpdateScriptedPlayout(_playout.PlayoutId, _playout.ScheduleFile), _cts.Token); |
||||||
|
|
||||||
|
result.Match( |
||||||
|
_ => { Snackbar.Add($"Saved scripted schedule for playout {_channelName}", Severity.Success); }, |
||||||
|
error => |
||||||
|
{ |
||||||
|
Snackbar.Add($"Unexpected error saving scripted schedule: {error.Value}", Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving scripted schedule: {Error}", error.Value); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue