mirror of https://github.com/ErsatzTV/ErsatzTV.git
5 changed files with 246 additions and 1 deletions
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record CopyBlock(int BlockId, int NewBlockGroupId, string NewBlockName) |
||||
: IRequest<Either<BaseError, BlockViewModel>>; |
||||
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Scheduling; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using ErsatzTV.Infrastructure.Extensions; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using static ErsatzTV.Application.Scheduling.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public class CopyBlockHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||
: IRequestHandler<CopyBlock, Either<BaseError, BlockViewModel>> |
||||
{ |
||||
public async Task<Either<BaseError, BlockViewModel>> Handle( |
||||
CopyBlock request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
try |
||||
{ |
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||
Validation<BaseError, Block> validation = await Validate(dbContext, request, cancellationToken); |
||||
return await validation.Apply(p => PerformCopy(dbContext, p, request, cancellationToken)); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
Console.WriteLine(ex.ToString()); |
||||
return BaseError.New(ex.Message); |
||||
} |
||||
} |
||||
|
||||
private static async Task<BlockViewModel> PerformCopy( |
||||
TvContext dbContext, |
||||
Block block, |
||||
CopyBlock request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
DetachEntity(dbContext, block); |
||||
block.Name = request.NewBlockName; |
||||
block.BlockGroup = null; |
||||
block.BlockGroupId = request.NewBlockGroupId; |
||||
|
||||
foreach (BlockItem item in block.Items) |
||||
{ |
||||
DetachEntity(dbContext, item); |
||||
item.BlockId = 0; |
||||
item.Block = block; |
||||
|
||||
foreach (BlockItemWatermark watermark in item.BlockItemWatermarks) |
||||
{ |
||||
DetachEntity(dbContext, watermark); |
||||
watermark.BlockItemId = 0; |
||||
watermark.BlockItem = item; |
||||
} |
||||
|
||||
foreach (BlockItemGraphicsElement graphicsElement in item.BlockItemGraphicsElements) |
||||
{ |
||||
DetachEntity(dbContext, graphicsElement); |
||||
graphicsElement.BlockItemId = 0; |
||||
graphicsElement.BlockItem = item; |
||||
} |
||||
} |
||||
|
||||
await dbContext.Blocks.AddAsync(block, cancellationToken); |
||||
await dbContext.BlockItems.AddRangeAsync(block.Items, cancellationToken); |
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken); |
||||
|
||||
await dbContext.Entry(block).Reference(b => b.BlockGroup).LoadAsync(cancellationToken); |
||||
|
||||
return ProjectToViewModel(block); |
||||
} |
||||
|
||||
private static async Task<Validation<BaseError, Block>> Validate( |
||||
TvContext dbContext, |
||||
CopyBlock request, |
||||
CancellationToken cancellationToken) => |
||||
(await BlockMustExist(dbContext, request, cancellationToken), await ValidateName(dbContext, request)) |
||||
.Apply((block, _) => block); |
||||
|
||||
private static Task<Validation<BaseError, Block>> BlockMustExist( |
||||
TvContext dbContext, |
||||
CopyBlock request, |
||||
CancellationToken cancellationToken) => |
||||
dbContext.Blocks |
||||
.AsNoTracking() |
||||
.Include(ps => ps.Items) |
||||
.ThenInclude(i => i.BlockItemWatermarks) |
||||
.Include(ps => ps.Items) |
||||
.ThenInclude(i => i.BlockItemGraphicsElements) |
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.BlockId, cancellationToken) |
||||
.Map(o => o.ToValidation<BaseError>("Block does not exist.")); |
||||
|
||||
private static async Task<Validation<BaseError, string>> ValidateName(TvContext dbContext, CopyBlock request) |
||||
{ |
||||
List<string> allNames = await dbContext.Blocks |
||||
.Where(b => b.BlockGroupId == request.NewBlockGroupId) |
||||
.Map(ps => ps.Name) |
||||
.ToListAsync(); |
||||
|
||||
Validation<BaseError, string> result1 = request.NotEmpty(c => c.NewBlockName) |
||||
.Bind(_ => request.NotLongerThan(50)(c => c.NewBlockName)); |
||||
|
||||
var result2 = Optional(request.NewBlockName) |
||||
.Where(name => !allNames.Contains(name)) |
||||
.ToValidation<BaseError>("Block name must be unique within the block group."); |
||||
|
||||
return (result1, result2).Apply((_, _) => request.NewBlockName); |
||||
} |
||||
|
||||
private static void DetachEntity<T>(TvContext db, T entity) where T : class |
||||
{ |
||||
db.Entry(entity).State = EntityState.Detached; |
||||
if (entity.GetType().GetProperty("Id") is not null) |
||||
{ |
||||
entity.GetType().GetProperty("Id")!.SetValue(entity, 0); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
@using ErsatzTV.Application.Scheduling |
||||
@implements IDisposable |
||||
@inject IMediator Mediator |
||||
@inject ISnackbar Snackbar |
||||
@inject ILogger<CopyScheduleDialog> Logger |
||||
|
||||
<MudDialog> |
||||
<DialogContent> |
||||
<EditForm Model="@_dummyModel" OnSubmit="@(_ => Submit())"> |
||||
<div class="d-flex mb-6"> |
||||
<MudText>Select a group for the new block</MudText> |
||||
</div> |
||||
<MudSelect @bind-Value="_selectedBlockGroup" Class="mb-6 mx-4" Label="New Block Group"> |
||||
@foreach (BlockGroupViewModel blockGroup in _blockGroups) |
||||
{ |
||||
<MudSelectItem Value="@blockGroup">@blockGroup.Name</MudSelectItem> |
||||
} |
||||
</MudSelect> |
||||
<div class="d-flex mb-6"> |
||||
<MudText>Enter a name for the new block</MudText> |
||||
</div> |
||||
<MudTextField T="string" Label="New Block Name" |
||||
@bind-Text="@_newName" |
||||
Class="mb-6 mx-4"> |
||||
</MudTextField> |
||||
</EditForm> |
||||
</DialogContent> |
||||
<DialogActions> |
||||
<MudButton OnClick="@Cancel" ButtonType="ButtonType.Reset">Cancel</MudButton> |
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="@Submit"> |
||||
Copy Block |
||||
</MudButton> |
||||
</DialogActions> |
||||
</MudDialog> |
||||
|
||||
@code { |
||||
private CancellationTokenSource _cts; |
||||
|
||||
[CascadingParameter] |
||||
IMudDialogInstance MudDialog { get; set; } |
||||
|
||||
[Parameter] |
||||
public int BlockId { get; set; } |
||||
|
||||
private record DummyModel; |
||||
|
||||
private readonly DummyModel _dummyModel = new(); |
||||
|
||||
private List<BlockGroupViewModel> _blockGroups = []; |
||||
private BlockGroupViewModel _selectedBlockGroup; |
||||
|
||||
private string _newName; |
||||
|
||||
public void Dispose() |
||||
{ |
||||
_cts?.Cancel(); |
||||
_cts?.Dispose(); |
||||
} |
||||
|
||||
protected override async Task OnParametersSetAsync() |
||||
{ |
||||
_cts?.Cancel(); |
||||
_cts?.Dispose(); |
||||
_cts = new CancellationTokenSource(); |
||||
var token = _cts.Token; |
||||
|
||||
try |
||||
{ |
||||
_blockGroups = await Mediator.Send(new GetAllBlockGroups(), token); |
||||
} |
||||
catch (OperationCanceledException) |
||||
{ |
||||
// do nothing |
||||
} |
||||
} |
||||
|
||||
private bool CanSubmit() => _selectedBlockGroup != null && !string.IsNullOrWhiteSpace(_newName); |
||||
|
||||
private async Task Submit() |
||||
{ |
||||
if (!CanSubmit()) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
Either<BaseError, BlockViewModel> maybeResult = |
||||
await Mediator.Send(new CopyBlock(BlockId, _selectedBlockGroup.Id, _newName), _cts.Token); |
||||
|
||||
maybeResult.Match( |
||||
schedule => { MudDialog.Close(DialogResult.Ok(schedule)); }, |
||||
error => |
||||
{ |
||||
Snackbar.Add(error.Value, Severity.Error); |
||||
Logger.LogError("Error copying block: {Error}", error.Value); |
||||
MudDialog.Close(DialogResult.Cancel()); |
||||
}); |
||||
} |
||||
|
||||
private void Cancel(MouseEventArgs e) => MudDialog.Cancel(); |
||||
} |
||||
Loading…
Reference in new issue