mirror of https://github.com/ErsatzTV/ErsatzTV.git
5 changed files with 246 additions and 1 deletions
@ -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 @@ |
|||||||
|
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 @@ |
|||||||
|
@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