mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add deco groups and decos; set default deco for block playout * use block playout default deco for watermark * add deco templates, groups and deco template editor * associate deco template with playout template * use deco template item watermark for playback * update changelog for decospull/1666/head
91 changed files with 30033 additions and 26 deletions
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record CreateDeco(int DecoGroupId, string Name) : IRequest<Either<BaseError, DecoViewModel>>; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record CreateDecoGroup(string Name) : IRequest<Either<BaseError, DecoGroupViewModel>>; |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class CreateDecoGroupHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<CreateDecoGroup, Either<BaseError, DecoGroupViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, DecoGroupViewModel>> Handle( |
||||||
|
CreateDecoGroup request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, DecoGroup> validation = await Validate(request); |
||||||
|
return await validation.Apply(profile => PersistDecoGroup(dbContext, profile)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<DecoGroupViewModel> PersistDecoGroup(TvContext dbContext, DecoGroup decoGroup) |
||||||
|
{ |
||||||
|
await dbContext.DecoGroups.AddAsync(decoGroup); |
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
return Mapper.ProjectToViewModel(decoGroup); |
||||||
|
} |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, DecoGroup>> Validate(CreateDecoGroup request) => |
||||||
|
Task.FromResult(ValidateName(request).Map(name => new DecoGroup { Name = name, Decos = [] })); |
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateDecoGroup createDecoGroup) => |
||||||
|
createDecoGroup.NotEmpty(x => x.Name) |
||||||
|
.Bind(_ => createDecoGroup.NotLongerThan(50)(x => x.Name)); |
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class CreateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<CreateDeco, Either<BaseError, DecoViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, DecoViewModel>> Handle( |
||||||
|
CreateDeco request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, Deco> validation = await Validate(dbContext, request); |
||||||
|
return await validation.Apply(profile => PersistDeco(dbContext, profile)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<DecoViewModel> PersistDeco(TvContext dbContext, Deco deco) |
||||||
|
{ |
||||||
|
await dbContext.Decos.AddAsync(deco); |
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
return Mapper.ProjectToViewModel(deco); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, CreateDeco request) => |
||||||
|
await ValidateDecoName(dbContext, request).MapT( |
||||||
|
name => new Deco |
||||||
|
{ |
||||||
|
DecoGroupId = request.DecoGroupId, |
||||||
|
Name = name |
||||||
|
}); |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, string>> ValidateDecoName( |
||||||
|
TvContext dbContext, |
||||||
|
CreateDeco request) |
||||||
|
{ |
||||||
|
if (request.Name.Length > 50) |
||||||
|
{ |
||||||
|
return BaseError.New($"Deco name \"{request.Name}\" is invalid"); |
||||||
|
} |
||||||
|
|
||||||
|
Option<Deco> maybeExisting = await dbContext.Decos |
||||||
|
.FirstOrDefaultAsync(r => r.DecoGroupId == request.DecoGroupId && r.Name == request.Name) |
||||||
|
.Map(Optional); |
||||||
|
|
||||||
|
return maybeExisting.IsSome |
||||||
|
? BaseError.New($"A deco named \"{request.Name}\" already exists in that deco group") |
||||||
|
: Success<BaseError, string>(request.Name); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record CreateDecoTemplate(int DecoTemplateGroupId, string Name) : IRequest<Either<BaseError, DecoTemplateViewModel>>; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record CreateDecoTemplateGroup(string Name) : IRequest<Either<BaseError, DecoTemplateGroupViewModel>>; |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class CreateDecoTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<CreateDecoTemplateGroup, Either<BaseError, DecoTemplateGroupViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, DecoTemplateGroupViewModel>> Handle( |
||||||
|
CreateDecoTemplateGroup request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, DecoTemplateGroup> validation = await Validate(request); |
||||||
|
return await validation.Apply(profile => PersistDecoTemplateGroup(dbContext, profile)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<DecoTemplateGroupViewModel> PersistDecoTemplateGroup( |
||||||
|
TvContext dbContext, |
||||||
|
DecoTemplateGroup decoDecoTemplateGroup) |
||||||
|
{ |
||||||
|
await dbContext.DecoTemplateGroups.AddAsync(decoDecoTemplateGroup); |
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
return Mapper.ProjectToViewModel(decoDecoTemplateGroup); |
||||||
|
} |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, DecoTemplateGroup>> Validate(CreateDecoTemplateGroup request) => |
||||||
|
Task.FromResult(ValidateName(request).Map(name => new DecoTemplateGroup { Name = name, DecoTemplates = [] })); |
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateDecoTemplateGroup createDecoTemplateGroup) => |
||||||
|
createDecoTemplateGroup.NotEmpty(x => x.Name) |
||||||
|
.Bind(_ => createDecoTemplateGroup.NotLongerThan(50)(x => x.Name)); |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class CreateDecoTemplateHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<CreateDecoTemplate, Either<BaseError, DecoTemplateViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, DecoTemplateViewModel>> Handle( |
||||||
|
CreateDecoTemplate request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, DecoTemplate> validation = await Validate(request); |
||||||
|
return await validation.Apply(profile => PersistDecoTemplate(dbContext, profile)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<DecoTemplateViewModel> PersistDecoTemplate(TvContext dbContext, DecoTemplate decoTemplate) |
||||||
|
{ |
||||||
|
await dbContext.DecoTemplates.AddAsync(decoTemplate); |
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
return Mapper.ProjectToViewModel(decoTemplate); |
||||||
|
} |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, DecoTemplate>> Validate(CreateDecoTemplate request) => |
||||||
|
Task.FromResult( |
||||||
|
ValidateName(request).Map( |
||||||
|
name => new DecoTemplate |
||||||
|
{ |
||||||
|
DecoTemplateGroupId = request.DecoTemplateGroupId, |
||||||
|
Name = name |
||||||
|
})); |
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateDecoTemplate createDecoTemplate) => |
||||||
|
createDecoTemplate.NotEmpty(x => x.Name) |
||||||
|
.Bind(_ => createDecoTemplate.NotLongerThan(50)(x => x.Name)); |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DeleteDeco(int DecoId) : IRequest<Option<BaseError>>; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DeleteDecoGroup(int DecoGroupId) : IRequest<Option<BaseError>>; |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class DeleteDecoGroupHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<DeleteDecoGroup, Option<BaseError>> |
||||||
|
{ |
||||||
|
public async Task<Option<BaseError>> Handle(DeleteDecoGroup request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
Option<DecoGroup> maybeDecoGroup = await dbContext.DecoGroups |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.DecoGroupId); |
||||||
|
|
||||||
|
foreach (DecoGroup decoGroup in maybeDecoGroup) |
||||||
|
{ |
||||||
|
dbContext.DecoGroups.Remove(decoGroup); |
||||||
|
await dbContext.SaveChangesAsync(cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
return maybeDecoGroup.Match( |
||||||
|
_ => Option<BaseError>.None, |
||||||
|
() => BaseError.New($"DecoGroup {request.DecoGroupId} does not exist.")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class DeleteDecoHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<DeleteDeco, Option<BaseError>> |
||||||
|
{ |
||||||
|
public async Task<Option<BaseError>> Handle(DeleteDeco request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
Option<Deco> maybeDeco = await dbContext.Decos |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.DecoId); |
||||||
|
|
||||||
|
foreach (Deco deco in maybeDeco) |
||||||
|
{ |
||||||
|
dbContext.Decos.Remove(deco); |
||||||
|
await dbContext.SaveChangesAsync(cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
return maybeDeco.Match( |
||||||
|
_ => Option<BaseError>.None, |
||||||
|
() => BaseError.New($"Deco {request.DecoId} does not exist.")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DeleteDecoTemplate(int DecoTemplateId) : IRequest<Option<BaseError>>; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DeleteDecoTemplateGroup(int DecoTemplateGroupId) : IRequest<Option<BaseError>>; |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class DeleteDecoTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<DeleteDecoTemplateGroup, Option<BaseError>> |
||||||
|
{ |
||||||
|
public async Task<Option<BaseError>> Handle(DeleteDecoTemplateGroup request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
Option<DecoTemplateGroup> maybeDecoTemplateGroup = await dbContext.DecoTemplateGroups |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.DecoTemplateGroupId); |
||||||
|
|
||||||
|
foreach (DecoTemplateGroup decoTemplateGroup in maybeDecoTemplateGroup) |
||||||
|
{ |
||||||
|
dbContext.DecoTemplateGroups.Remove(decoTemplateGroup); |
||||||
|
await dbContext.SaveChangesAsync(cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
return maybeDecoTemplateGroup.Match( |
||||||
|
_ => Option<BaseError>.None, |
||||||
|
() => BaseError.New($"DecoTemplateGroup {request.DecoTemplateGroupId} does not exist.")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class DeleteDecoTemplateHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<DeleteDecoTemplate, Option<BaseError>> |
||||||
|
{ |
||||||
|
public async Task<Option<BaseError>> Handle(DeleteDecoTemplate request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
Option<DecoTemplate> maybeDecoTemplate = await dbContext.DecoTemplates |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.DecoTemplateId); |
||||||
|
|
||||||
|
foreach (DecoTemplate decoTemplate in maybeDecoTemplate) |
||||||
|
{ |
||||||
|
dbContext.DecoTemplates.Remove(decoTemplate); |
||||||
|
await dbContext.SaveChangesAsync(cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
return maybeDecoTemplate.Match( |
||||||
|
_ => Option<BaseError>.None, |
||||||
|
() => BaseError.New($"DecoTemplate {request.DecoTemplateId} does not exist.")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record ReplaceDecoTemplateItem(int DecoId, TimeSpan StartTime, TimeSpan EndTime); |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record ReplaceDecoTemplateItems(int DecoTemplateId, string Name, List<ReplaceDecoTemplateItem> Items) |
||||||
|
: IRequest<Either<BaseError, List<DecoTemplateItemViewModel>>>; |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class ReplaceDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<ReplaceDecoTemplateItems, Either<BaseError, List<DecoTemplateItemViewModel>>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, List<DecoTemplateItemViewModel>>> Handle( |
||||||
|
ReplaceDecoTemplateItems request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, DecoTemplate> validation = await Validate(dbContext, request); |
||||||
|
return await validation.Apply(ps => Persist(dbContext, request, ps)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<List<DecoTemplateItemViewModel>> Persist( |
||||||
|
TvContext dbContext, |
||||||
|
ReplaceDecoTemplateItems request, |
||||||
|
DecoTemplate decoTemplate) |
||||||
|
{ |
||||||
|
decoTemplate.Name = request.Name; |
||||||
|
decoTemplate.DateUpdated = DateTime.UtcNow; |
||||||
|
|
||||||
|
dbContext.RemoveRange(decoTemplate.Items); |
||||||
|
decoTemplate.Items = request.Items.Map(i => BuildItem(decoTemplate, i)).ToList(); |
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
|
||||||
|
// TODO: refresh any playouts that use this schedule
|
||||||
|
// foreach (Playout playout in programSchedule.Playouts)
|
||||||
|
// {
|
||||||
|
// await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Refresh));
|
||||||
|
// }
|
||||||
|
|
||||||
|
await dbContext.Entry(decoTemplate) |
||||||
|
.Collection(t => t.Items) |
||||||
|
.Query() |
||||||
|
.Include(i => i.Deco) |
||||||
|
.LoadAsync(); |
||||||
|
|
||||||
|
return decoTemplate.Items.Map(Mapper.ProjectToViewModel).ToList(); |
||||||
|
} |
||||||
|
|
||||||
|
private static DecoTemplateItem BuildItem(DecoTemplate decoTemplate, ReplaceDecoTemplateItem item) => |
||||||
|
new() |
||||||
|
{ |
||||||
|
DecoTemplateId = decoTemplate.Id, |
||||||
|
DecoId = item.DecoId, |
||||||
|
StartTime = item.StartTime, |
||||||
|
EndTime = item.EndTime |
||||||
|
}; |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, DecoTemplate>> Validate(TvContext dbContext, ReplaceDecoTemplateItems request) => |
||||||
|
DecoTemplateMustExist(dbContext, request.DecoTemplateId); |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, DecoTemplate>> DecoTemplateMustExist(TvContext dbContext, int decoTemplateId) => |
||||||
|
dbContext.DecoTemplates |
||||||
|
.Include(b => b.Items) |
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == decoTemplateId) |
||||||
|
.Map(o => o.ToValidation<BaseError>("[DecoTemplateId] does not exist.")); |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record UpdateDeco(int DecoId, int DecoGroupId, string Name, int? WatermarkId) : IRequest<Either<BaseError, DecoViewModel>>; |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<UpdateDeco, Either<BaseError, DecoViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, DecoViewModel>> Handle(UpdateDeco request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, Deco> validation = await Validate(dbContext, request); |
||||||
|
return await validation.Apply(ps => ApplyUpdateRequest(dbContext, ps, request)); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<DecoViewModel> ApplyUpdateRequest( |
||||||
|
TvContext dbContext, |
||||||
|
Deco existing, |
||||||
|
UpdateDeco request) |
||||||
|
{ |
||||||
|
existing.Name = request.Name; |
||||||
|
existing.WatermarkId = request.WatermarkId; |
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(); |
||||||
|
|
||||||
|
return Mapper.ProjectToViewModel(existing); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, UpdateDeco request) => |
||||||
|
(await DecoMustExist(dbContext, request), await ValidateDecoName(dbContext, request)) |
||||||
|
.Apply((deco, _) => deco); |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Deco>> DecoMustExist( |
||||||
|
TvContext dbContext, |
||||||
|
UpdateDeco request) => |
||||||
|
dbContext.Decos |
||||||
|
.SelectOneAsync(d => d.Id, d => d.Id == request.DecoId) |
||||||
|
.Map(o => o.ToValidation<BaseError>("Deco does not exist")); |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, string>> ValidateDecoName( |
||||||
|
TvContext dbContext, |
||||||
|
UpdateDeco request) |
||||||
|
{ |
||||||
|
if (request.Name.Length > 50) |
||||||
|
{ |
||||||
|
return BaseError.New($"Deco name \"{request.Name}\" is invalid"); |
||||||
|
} |
||||||
|
|
||||||
|
Option<Deco> maybeExisting = await dbContext.Decos |
||||||
|
.FirstOrDefaultAsync( |
||||||
|
d => d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name) |
||||||
|
.Map(Optional); |
||||||
|
|
||||||
|
return maybeExisting.IsSome |
||||||
|
? BaseError.New($"A deco named \"{request.Name}\" already exists in that deco group") |
||||||
|
: Success<BaseError, string>(request.Name); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record UpdateDefaultDeco(int PlayoutId, int? DecoId) : IRequest; |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class UpdateDefaultDecoHandler(IDbContextFactory<TvContext> dbContextFactory) : IRequestHandler<UpdateDefaultDeco> |
||||||
|
{ |
||||||
|
public async Task Handle(UpdateDefaultDeco request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
await dbContext.Playouts |
||||||
|
.Where(p => p.Id == request.PlayoutId) |
||||||
|
.ExecuteUpdateAsync(u => u.SetProperty(p => p.DecoId, p => request.DecoId), cancellationToken); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DecoGroupViewModel(int Id, string Name, int DecoCount); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DecoTemplateGroupViewModel(int Id, string Name, int DecoTemplateCount); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DecoTemplateItemViewModel(int DecoId, string DecoName, DateTime StartTime, DateTime EndTime); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DecoTemplateViewModel(int Id, int DecoTemplateGroupId, string Name); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record DecoViewModel(int Id, int DecoGroupId, string Name, int? WatermarkId); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetAllDecoGroups : IRequest<List<DecoGroupViewModel>>; |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetAllDecoGroupsHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetAllDecoGroups, List<DecoGroupViewModel>> |
||||||
|
{ |
||||||
|
public async Task<List<DecoGroupViewModel>> Handle(GetAllDecoGroups request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
List<DecoGroup> decoGroups = await dbContext.DecoGroups |
||||||
|
.AsNoTracking() |
||||||
|
.Include(g => g.Decos) |
||||||
|
.ToListAsync(cancellationToken); |
||||||
|
|
||||||
|
return decoGroups.Map(Mapper.ProjectToViewModel).ToList(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetAllDecoTemplateGroups : IRequest<List<DecoTemplateGroupViewModel>>; |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetAllDecoTemplateGroupsHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetAllDecoTemplateGroups, List<DecoTemplateGroupViewModel>> |
||||||
|
{ |
||||||
|
public async Task<List<DecoTemplateGroupViewModel>> Handle( |
||||||
|
GetAllDecoTemplateGroups request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
List<DecoTemplateGroup> blockGroups = await dbContext.DecoTemplateGroups |
||||||
|
.AsNoTracking() |
||||||
|
.Include(g => g.DecoTemplates) |
||||||
|
.ToListAsync(cancellationToken); |
||||||
|
|
||||||
|
return blockGroups.Map(Mapper.ProjectToViewModel).ToList(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecoById(int DecoId) : IRequest<Option<DecoViewModel>>; |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecoByIdHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecoById, Option<DecoViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Option<DecoViewModel>> Handle(GetDecoById request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
return await dbContext.Decos |
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == request.DecoId) |
||||||
|
.MapT(Mapper.ProjectToViewModel); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecoByPlayoutId(int PlayoutId) : IRequest<Option<DecoViewModel>>; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecoByPlayoutIdHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecoByPlayoutId, Option<DecoViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Option<DecoViewModel>> Handle(GetDecoByPlayoutId request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
return await dbContext.Playouts |
||||||
|
.Include(p => p.Deco) |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId && p.DecoId != null) |
||||||
|
.MapT(p => Mapper.ProjectToViewModel(p.Deco)); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecoTemplateById(int DecoTemplateId) : IRequest<Option<DecoTemplateViewModel>>; |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecoTemplateByIdHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecoTemplateById, Option<DecoTemplateViewModel>> |
||||||
|
{ |
||||||
|
public async Task<Option<DecoTemplateViewModel>> Handle(GetDecoTemplateById request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
return await dbContext.DecoTemplates |
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == request.DecoTemplateId) |
||||||
|
.MapT(Mapper.ProjectToViewModel); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecoTemplateItems(int DecoTemplateId) : IRequest<List<DecoTemplateItemViewModel>>; |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecoTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecoTemplateItems, List<DecoTemplateItemViewModel>> |
||||||
|
{ |
||||||
|
public async Task<List<DecoTemplateItemViewModel>> Handle(GetDecoTemplateItems request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
return await dbContext.DecoTemplateItems |
||||||
|
.AsNoTracking() |
||||||
|
.Filter(i => i.DecoTemplateId == request.DecoTemplateId) |
||||||
|
.Include(i => i.Deco) |
||||||
|
.ToListAsync(cancellationToken) |
||||||
|
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList()); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecoTemplatesByDecoTemplateGroupId(int DecoTemplateGroupId) : IRequest<List<DecoTemplateViewModel>>; |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecoTemplatesByDecoTemplateGroupIdHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecoTemplatesByDecoTemplateGroupId, List<DecoTemplateViewModel>> |
||||||
|
{ |
||||||
|
public async Task<List<DecoTemplateViewModel>> Handle( |
||||||
|
GetDecoTemplatesByDecoTemplateGroupId request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
return await dbContext.DecoTemplates |
||||||
|
.AsNoTracking() |
||||||
|
.Filter(i => i.DecoTemplateGroupId == request.DecoTemplateGroupId) |
||||||
|
.ToListAsync(cancellationToken) |
||||||
|
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList()); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public record GetDecosByDecoGroupId(int DecoGroupId) : IRequest<List<DecoViewModel>>; |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling; |
||||||
|
|
||||||
|
public class GetDecosByDecoGroupIdHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||||
|
: IRequestHandler<GetDecosByDecoGroupId, List<DecoViewModel>> |
||||||
|
{ |
||||||
|
public async Task<List<DecoViewModel>> Handle(GetDecosByDecoGroupId request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
|
||||||
|
List<Deco> decos = await dbContext.Decos |
||||||
|
.Filter(b => b.DecoGroupId == request.DecoGroupId) |
||||||
|
.AsNoTracking() |
||||||
|
.ToListAsync(cancellationToken); |
||||||
|
|
||||||
|
return decos.Map(Mapper.ProjectToViewModel).ToList(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling; |
||||||
|
|
||||||
|
public class Deco |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public int DecoGroupId { get; set; } |
||||||
|
public DecoGroup DecoGroup { get; set; } |
||||||
|
|
||||||
|
public string Name { get; set; } |
||||||
|
|
||||||
|
public int? WatermarkId { get; set; } |
||||||
|
public ChannelWatermark Watermark { get; set; } |
||||||
|
|
||||||
|
// can be added directly to playouts
|
||||||
|
public ICollection<Playout> Playouts { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling; |
||||||
|
|
||||||
|
public class DecoGroup |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public ICollection<Deco> Decos { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplate |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public int DecoTemplateGroupId { get; set; } |
||||||
|
public DecoTemplateGroup DecoTemplateGroup { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public ICollection<DecoTemplateItem> Items { get; set; } |
||||||
|
public ICollection<PlayoutTemplate> PlayoutTemplates { get; set; } |
||||||
|
public DateTime DateUpdated { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplateGroup |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public ICollection<DecoTemplate> DecoTemplates { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplateItem |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public int DecoTemplateId { get; set; } |
||||||
|
public DecoTemplate DecoTemplate { get; set; } |
||||||
|
public int DecoId { get; set; } |
||||||
|
public Deco Deco { get; set; } |
||||||
|
public TimeSpan StartTime { get; set; } |
||||||
|
public TimeSpan EndTime { get; set; } |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,117 @@ |
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata; |
||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
#nullable disable |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Deco_Group_Deco : Migration |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.AddColumn<int>( |
||||||
|
name: "DecoId", |
||||||
|
table: "Playout", |
||||||
|
type: "int", |
||||||
|
nullable: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoGroup", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "int", nullable: false) |
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4") |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoGroup", x => x.Id); |
||||||
|
}) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "Deco", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "int", nullable: false) |
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
||||||
|
DecoGroupId = table.Column<int>(type: "int", nullable: false), |
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"), |
||||||
|
WatermarkId = table.Column<int>(type: "int", nullable: true) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_Deco", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_Deco_ChannelWatermark_WatermarkId", |
||||||
|
column: x => x.WatermarkId, |
||||||
|
principalTable: "ChannelWatermark", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_Deco_DecoGroup_DecoGroupId", |
||||||
|
column: x => x.DecoGroupId, |
||||||
|
principalTable: "DecoGroup", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Playout_DecoId", |
||||||
|
table: "Playout", |
||||||
|
column: "DecoId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Deco_DecoGroupId_Name", |
||||||
|
table: "Deco", |
||||||
|
columns: new[] { "DecoGroupId", "Name" }, |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Deco_WatermarkId", |
||||||
|
table: "Deco", |
||||||
|
column: "WatermarkId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoGroup_Name", |
||||||
|
table: "DecoGroup", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_Playout_Deco_DecoId", |
||||||
|
table: "Playout", |
||||||
|
column: "DecoId", |
||||||
|
principalTable: "Deco", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_Playout_Deco_DecoId", |
||||||
|
table: "Playout"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "Deco"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoGroup"); |
||||||
|
|
||||||
|
migrationBuilder.DropIndex( |
||||||
|
name: "IX_Playout_DecoId", |
||||||
|
table: "Playout"); |
||||||
|
|
||||||
|
migrationBuilder.DropColumn( |
||||||
|
name: "DecoId", |
||||||
|
table: "Playout"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,154 @@ |
|||||||
|
using System; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata; |
||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
#nullable disable |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_DecoTemplate : Migration |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.AddColumn<int>( |
||||||
|
name: "DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
type: "int", |
||||||
|
nullable: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplateGroup", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "int", nullable: false) |
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4") |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplateGroup", x => x.Id); |
||||||
|
}) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplate", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "int", nullable: false) |
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
||||||
|
DecoTemplateGroupId = table.Column<int>(type: "int", nullable: false), |
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"), |
||||||
|
DateUpdated = table.Column<DateTime>(type: "datetime(6)", nullable: false) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplate", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplate_DecoTemplateGroup_DecoTemplateGroupId", |
||||||
|
column: x => x.DecoTemplateGroupId, |
||||||
|
principalTable: "DecoTemplateGroup", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplateItem", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "int", nullable: false) |
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
||||||
|
DecoTemplateId = table.Column<int>(type: "int", nullable: false), |
||||||
|
DecoId = table.Column<int>(type: "int", nullable: false), |
||||||
|
StartTime = table.Column<TimeSpan>(type: "time(6)", nullable: false), |
||||||
|
EndTime = table.Column<TimeSpan>(type: "time(6)", nullable: false) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplateItem", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplateItem_DecoTemplate_DecoTemplateId", |
||||||
|
column: x => x.DecoTemplateId, |
||||||
|
principalTable: "DecoTemplate", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplateItem_Deco_DecoId", |
||||||
|
column: x => x.DecoId, |
||||||
|
principalTable: "Deco", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}) |
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_PlayoutTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
column: "DecoTemplateId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplate_DecoTemplateGroupId", |
||||||
|
table: "DecoTemplate", |
||||||
|
column: "DecoTemplateGroupId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplate_Name", |
||||||
|
table: "DecoTemplate", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateGroup_Name", |
||||||
|
table: "DecoTemplateGroup", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateItem_DecoId", |
||||||
|
table: "DecoTemplateItem", |
||||||
|
column: "DecoId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateItem_DecoTemplateId", |
||||||
|
table: "DecoTemplateItem", |
||||||
|
column: "DecoTemplateId"); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_PlayoutTemplate_DecoTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
column: "DecoTemplateId", |
||||||
|
principalTable: "DecoTemplate", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_PlayoutTemplate_DecoTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplateItem"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplateGroup"); |
||||||
|
|
||||||
|
migrationBuilder.DropIndex( |
||||||
|
name: "IX_PlayoutTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropColumn( |
||||||
|
name: "DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,112 @@ |
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
#nullable disable |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Deco_Group_Deco : Migration |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.AddColumn<int>( |
||||||
|
name: "DecoId", |
||||||
|
table: "Playout", |
||||||
|
type: "INTEGER", |
||||||
|
nullable: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoGroup", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||||
|
.Annotation("Sqlite:Autoincrement", true), |
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoGroup", x => x.Id); |
||||||
|
}); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "Deco", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||||
|
.Annotation("Sqlite:Autoincrement", true), |
||||||
|
DecoGroupId = table.Column<int>(type: "INTEGER", nullable: false), |
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true), |
||||||
|
WatermarkId = table.Column<int>(type: "INTEGER", nullable: true) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_Deco", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_Deco_ChannelWatermark_WatermarkId", |
||||||
|
column: x => x.WatermarkId, |
||||||
|
principalTable: "ChannelWatermark", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_Deco_DecoGroup_DecoGroupId", |
||||||
|
column: x => x.DecoGroupId, |
||||||
|
principalTable: "DecoGroup", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Playout_DecoId", |
||||||
|
table: "Playout", |
||||||
|
column: "DecoId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Deco_DecoGroupId_Name", |
||||||
|
table: "Deco", |
||||||
|
columns: new[] { "DecoGroupId", "Name" }, |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_Deco_WatermarkId", |
||||||
|
table: "Deco", |
||||||
|
column: "WatermarkId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoGroup_Name", |
||||||
|
table: "DecoGroup", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_Playout_Deco_DecoId", |
||||||
|
table: "Playout", |
||||||
|
column: "DecoId", |
||||||
|
principalTable: "Deco", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_Playout_Deco_DecoId", |
||||||
|
table: "Playout"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "Deco"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoGroup"); |
||||||
|
|
||||||
|
migrationBuilder.DropIndex( |
||||||
|
name: "IX_Playout_DecoId", |
||||||
|
table: "Playout"); |
||||||
|
|
||||||
|
migrationBuilder.DropColumn( |
||||||
|
name: "DecoId", |
||||||
|
table: "Playout"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,147 @@ |
|||||||
|
using System; |
||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
#nullable disable |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_DecoTemplate : Migration |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.AddColumn<int>( |
||||||
|
name: "DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
type: "INTEGER", |
||||||
|
nullable: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplateGroup", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||||
|
.Annotation("Sqlite:Autoincrement", true), |
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplateGroup", x => x.Id); |
||||||
|
}); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplate", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||||
|
.Annotation("Sqlite:Autoincrement", true), |
||||||
|
DecoTemplateGroupId = table.Column<int>(type: "INTEGER", nullable: false), |
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true), |
||||||
|
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: false) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplate", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplate_DecoTemplateGroup_DecoTemplateGroupId", |
||||||
|
column: x => x.DecoTemplateGroupId, |
||||||
|
principalTable: "DecoTemplateGroup", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}); |
||||||
|
|
||||||
|
migrationBuilder.CreateTable( |
||||||
|
name: "DecoTemplateItem", |
||||||
|
columns: table => new |
||||||
|
{ |
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||||
|
.Annotation("Sqlite:Autoincrement", true), |
||||||
|
DecoTemplateId = table.Column<int>(type: "INTEGER", nullable: false), |
||||||
|
DecoId = table.Column<int>(type: "INTEGER", nullable: false), |
||||||
|
StartTime = table.Column<TimeSpan>(type: "TEXT", nullable: false) |
||||||
|
}, |
||||||
|
constraints: table => |
||||||
|
{ |
||||||
|
table.PrimaryKey("PK_DecoTemplateItem", x => x.Id); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplateItem_DecoTemplate_DecoTemplateId", |
||||||
|
column: x => x.DecoTemplateId, |
||||||
|
principalTable: "DecoTemplate", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
table.ForeignKey( |
||||||
|
name: "FK_DecoTemplateItem_Deco_DecoId", |
||||||
|
column: x => x.DecoId, |
||||||
|
principalTable: "Deco", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Cascade); |
||||||
|
}); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_PlayoutTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
column: "DecoTemplateId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplate_DecoTemplateGroupId", |
||||||
|
table: "DecoTemplate", |
||||||
|
column: "DecoTemplateGroupId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplate_Name", |
||||||
|
table: "DecoTemplate", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateGroup_Name", |
||||||
|
table: "DecoTemplateGroup", |
||||||
|
column: "Name", |
||||||
|
unique: true); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateItem_DecoId", |
||||||
|
table: "DecoTemplateItem", |
||||||
|
column: "DecoId"); |
||||||
|
|
||||||
|
migrationBuilder.CreateIndex( |
||||||
|
name: "IX_DecoTemplateItem_DecoTemplateId", |
||||||
|
table: "DecoTemplateItem", |
||||||
|
column: "DecoTemplateId"); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_PlayoutTemplate_DecoTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate", |
||||||
|
column: "DecoTemplateId", |
||||||
|
principalTable: "DecoTemplate", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_PlayoutTemplate_DecoTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplateItem"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropTable( |
||||||
|
name: "DecoTemplateGroup"); |
||||||
|
|
||||||
|
migrationBuilder.DropIndex( |
||||||
|
name: "IX_PlayoutTemplate_DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
|
||||||
|
migrationBuilder.DropColumn( |
||||||
|
name: "DecoTemplateId", |
||||||
|
table: "PlayoutTemplate"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
|||||||
|
using System; |
||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
#nullable disable |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_DecoTemplateItem_EndTime : Migration |
||||||
|
{ |
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.AddColumn<TimeSpan>( |
||||||
|
name: "EndTime", |
||||||
|
table: "DecoTemplateItem", |
||||||
|
type: "TEXT", |
||||||
|
nullable: false, |
||||||
|
defaultValue: new TimeSpan(0, 0, 0, 0, 0)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropColumn( |
||||||
|
name: "EndTime", |
||||||
|
table: "DecoTemplateItem"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling; |
||||||
|
|
||||||
|
public class DecoConfiguration : IEntityTypeConfiguration<Deco> |
||||||
|
{ |
||||||
|
public void Configure(EntityTypeBuilder<Deco> builder) |
||||||
|
{ |
||||||
|
builder.ToTable("Deco"); |
||||||
|
|
||||||
|
builder.HasIndex(d => new { d.DecoGroupId, d.Name }) |
||||||
|
.IsUnique(); |
||||||
|
|
||||||
|
builder.HasMany(d => d.Playouts) |
||||||
|
.WithOne(p => p.Deco) |
||||||
|
.HasForeignKey(p => p.DecoId) |
||||||
|
.OnDelete(DeleteBehavior.SetNull) |
||||||
|
.IsRequired(false); |
||||||
|
|
||||||
|
builder.HasOne(d => d.Watermark) |
||||||
|
.WithMany() |
||||||
|
.HasForeignKey(d => d.WatermarkId) |
||||||
|
.OnDelete(DeleteBehavior.SetNull) |
||||||
|
.IsRequired(false); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling; |
||||||
|
|
||||||
|
public class DecoGroupConfiguration : IEntityTypeConfiguration<DecoGroup> |
||||||
|
{ |
||||||
|
public void Configure(EntityTypeBuilder<DecoGroup> builder) |
||||||
|
{ |
||||||
|
builder.ToTable("DecoGroup"); |
||||||
|
|
||||||
|
builder.HasIndex(dg => dg.Name) |
||||||
|
.IsUnique(); |
||||||
|
|
||||||
|
builder.HasMany(dg => dg.Decos) |
||||||
|
.WithOne(d => d.DecoGroup) |
||||||
|
.HasForeignKey(d => d.DecoGroupId) |
||||||
|
.OnDelete(DeleteBehavior.Cascade); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplateConfiguration : IEntityTypeConfiguration<DecoTemplate> |
||||||
|
{ |
||||||
|
public void Configure(EntityTypeBuilder<DecoTemplate> builder) |
||||||
|
{ |
||||||
|
builder.ToTable("DecoTemplate"); |
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name) |
||||||
|
.IsUnique(); |
||||||
|
|
||||||
|
builder.HasMany(b => b.Items) |
||||||
|
.WithOne(i => i.DecoTemplate) |
||||||
|
.HasForeignKey(i => i.DecoTemplateId) |
||||||
|
.OnDelete(DeleteBehavior.Cascade); |
||||||
|
|
||||||
|
builder.HasMany(t => t.PlayoutTemplates) |
||||||
|
.WithOne(t => t.DecoTemplate) |
||||||
|
.HasForeignKey(t => t.DecoTemplateId) |
||||||
|
.OnDelete(DeleteBehavior.SetNull) |
||||||
|
.IsRequired(false); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplateGroupConfiguration : IEntityTypeConfiguration<DecoTemplateGroup> |
||||||
|
{ |
||||||
|
public void Configure(EntityTypeBuilder<DecoTemplateGroup> builder) |
||||||
|
{ |
||||||
|
builder.ToTable("DecoTemplateGroup"); |
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name) |
||||||
|
.IsUnique(); |
||||||
|
|
||||||
|
builder.HasMany(b => b.DecoTemplates) |
||||||
|
.WithOne(i => i.DecoTemplateGroup) |
||||||
|
.HasForeignKey(i => i.DecoTemplateGroupId) |
||||||
|
.OnDelete(DeleteBehavior.Cascade); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
using ErsatzTV.Core.Domain.Scheduling; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling; |
||||||
|
|
||||||
|
public class DecoTemplateItemConfiguration : IEntityTypeConfiguration<DecoTemplateItem> |
||||||
|
{ |
||||||
|
public void Configure(EntityTypeBuilder<DecoTemplateItem> builder) => builder.ToTable("DecoTemplateItem"); |
||||||
|
} |
||||||
@ -0,0 +1,157 @@ |
|||||||
|
@page "/playouts/block/{Id:int}" |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@using ErsatzTV.Application.Channels |
||||||
|
@implements IDisposable |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ILogger<BlockPlayoutEditor> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IEntityLocker EntityLocker; |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Edit Block Playout - @_channelName</MudText> |
||||||
|
<MudGrid Class="mt-4"> |
||||||
|
<MudCard Class="mr-6 mb-6" Style="width: 400px"> |
||||||
|
<MudCardHeader> |
||||||
|
<CardHeaderContent> |
||||||
|
<MudText Typo="Typo.h5">Playout Templates</MudText> |
||||||
|
</CardHeaderContent> |
||||||
|
</MudCardHeader> |
||||||
|
<MudCardContent> |
||||||
|
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Primary" Link="@($"playouts/{Id}/templates")" Class="mt-4"> |
||||||
|
Edit Templates |
||||||
|
</MudButton> |
||||||
|
</MudCardContent> |
||||||
|
</MudCard> |
||||||
|
<MudCard Class="mr-6 mb-6" Style="width: 400px"> |
||||||
|
<MudCardHeader> |
||||||
|
<CardHeaderContent> |
||||||
|
<MudText Typo="Typo.h5">Playout Items and History</MudText> |
||||||
|
</CardHeaderContent> |
||||||
|
</MudCardHeader> |
||||||
|
<MudCardContent> |
||||||
|
<div> |
||||||
|
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Warning" OnClick="@(_ => EraseItems(eraseHistory: false))" Class="mt-4"> |
||||||
|
Erase Items |
||||||
|
</MudButton> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<MudButton Disabled="@EntityLocker.IsPlayoutLocked(Id)" Variant="Variant.Filled" Color="Color.Error" OnClick="@(_ => EraseItems(eraseHistory: true))" Class="mt-4"> |
||||||
|
Erase Items and History |
||||||
|
</MudButton> |
||||||
|
</div> |
||||||
|
</MudCardContent> |
||||||
|
</MudCard> |
||||||
|
<MudCard Class="mr-6 mb-6" Style="width: 400px"> |
||||||
|
<MudCardHeader> |
||||||
|
<CardHeaderContent> |
||||||
|
<MudText Typo="Typo.h5">Default Deco</MudText> |
||||||
|
</CardHeaderContent> |
||||||
|
</MudCardHeader> |
||||||
|
<MudCardContent> |
||||||
|
<MudElement HtmlTag="div" Class="mt-3"> |
||||||
|
<MudSwitch T="bool" Label="Enable Default Deco" @bind-Value="_enableDefaultDeco" Color="Color.Primary"/> |
||||||
|
</MudElement> |
||||||
|
@if (_enableDefaultDeco) |
||||||
|
{ |
||||||
|
<MudElement HtmlTag="div" Class="mt-2"> |
||||||
|
<MudSelect T="DecoGroupViewModel" |
||||||
|
Label="Deco Group" |
||||||
|
Value="@_selectedDefaultDecoGroup" |
||||||
|
ValueChanged="@(vm => UpdateDefaultDecoTemplateGroupItems(vm))"> |
||||||
|
@foreach (DecoGroupViewModel decoGroup in _decoGroups) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@decoGroup">@decoGroup.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
</MudElement> |
||||||
|
<MudElement HtmlTag="div" Class="mt-2"> |
||||||
|
<MudSelect Label="Deco" |
||||||
|
@bind-Value="_defaultDeco" |
||||||
|
For="@(() => _defaultDeco)"> |
||||||
|
@foreach (DecoViewModel deco in _decos) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@deco">@deco.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
</MudElement> |
||||||
|
} |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveDefaultDeco())"> |
||||||
|
Save Changes |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</MudGrid> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
private readonly List<DecoGroupViewModel> _decoGroups = []; |
||||||
|
private readonly List<DecoViewModel> _decos = []; |
||||||
|
|
||||||
|
private string _channelName; |
||||||
|
private bool _enableDefaultDeco; |
||||||
|
private DecoGroupViewModel _selectedDefaultDecoGroup; |
||||||
|
private DecoViewModel _defaultDeco; |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
_decoGroups.Clear(); |
||||||
|
_decoGroups.AddRange(await Mediator.Send(new GetAllDecoGroups(), _cts.Token)); |
||||||
|
|
||||||
|
Option<DecoViewModel> maybeDefaultDeco = await Mediator.Send(new GetDecoByPlayoutId(Id), _cts.Token); |
||||||
|
foreach (DecoViewModel defaultDeco in maybeDefaultDeco) |
||||||
|
{ |
||||||
|
_enableDefaultDeco = true; |
||||||
|
_selectedDefaultDecoGroup = _decoGroups.SingleOrDefault(dg => dg.Id == defaultDeco.DecoGroupId); |
||||||
|
await UpdateDefaultDecoTemplateGroupItems(_selectedDefaultDecoGroup); |
||||||
|
_defaultDeco = defaultDeco; |
||||||
|
} |
||||||
|
} |
||||||
|
private async Task UpdateDefaultDecoTemplateGroupItems(DecoGroupViewModel decoGroup) |
||||||
|
{ |
||||||
|
_selectedDefaultDecoGroup = decoGroup; |
||||||
|
|
||||||
|
_decos.Clear(); |
||||||
|
_decos.AddRange(await Mediator.Send(new GetDecosByDecoGroupId(_selectedDefaultDecoGroup.Id), _cts.Token)); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task EraseItems(bool eraseHistory) |
||||||
|
{ |
||||||
|
IRequest request = eraseHistory ? new EraseBlockPlayoutHistory(Id) : new EraseBlockPlayoutItems(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 SaveDefaultDeco() |
||||||
|
{ |
||||||
|
int? decoId = _enableDefaultDeco ? _defaultDeco?.Id : null; |
||||||
|
await Mediator.Send(new UpdateDefaultDeco(Id, decoId), _cts.Token); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,90 @@ |
|||||||
|
@page "/decos/{Id:int}" |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@using ErsatzTV.Application.Watermarks |
||||||
|
@implements IDisposable |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ILogger<DecoEditor> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText> |
||||||
|
<div style="max-width: 400px"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudTextField Label="Name" @bind-Value="_deco.Name" For="@(() => _deco.Name)"/> |
||||||
|
<MudSelect Class="mt-3" Label="Watermark" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)" |
||||||
|
Clearable="true"> |
||||||
|
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem> |
||||||
|
@foreach (WatermarkViewModel watermark in _watermarks) |
||||||
|
{ |
||||||
|
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
</MudCardContent> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4"> |
||||||
|
Save Changes |
||||||
|
</MudButton> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
private DecoEditViewModel _deco = new(); |
||||||
|
private List<WatermarkViewModel> _watermarks = []; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
await LoadWatermarks(); |
||||||
|
await LoadDeco(); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task LoadWatermarks() => |
||||||
|
_watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token); |
||||||
|
|
||||||
|
private async Task LoadDeco() |
||||||
|
{ |
||||||
|
Option<DecoViewModel> maybeDeco = await Mediator.Send(new GetDecoById(Id), _cts.Token); |
||||||
|
if (maybeDeco.IsNone) |
||||||
|
{ |
||||||
|
NavigationManager.NavigateTo("decos"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoViewModel deco in maybeDeco) |
||||||
|
{ |
||||||
|
_deco = new DecoEditViewModel |
||||||
|
{ |
||||||
|
Name = deco.Name, |
||||||
|
DecoGroupId = deco.DecoGroupId, |
||||||
|
WatermarkId = deco.WatermarkId |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task SaveChanges() |
||||||
|
{ |
||||||
|
Seq<BaseError> errorMessages = await Mediator |
||||||
|
.Send(new UpdateDeco(Id, _deco.DecoGroupId, _deco.Name, _deco.WatermarkId), _cts.Token) |
||||||
|
.Map(e => e.LeftToSeq()); |
||||||
|
|
||||||
|
errorMessages.HeadOrNone().Match( |
||||||
|
error => |
||||||
|
{ |
||||||
|
Snackbar.Add($"Unexpected error saving deco: {error.Value}", Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving deco: {Error}", error.Value); |
||||||
|
}, |
||||||
|
() => NavigationManager.NavigateTo("/decos")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,314 @@ |
|||||||
|
@page "/deco-templates/{Id:int}" |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@using System.Globalization |
||||||
|
@implements IDisposable |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ILogger<DecoTemplateEditor> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco Template</MudText> |
||||||
|
<MudGrid> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudTextField Label="Name" @bind-Value="_decoTemplate.Name" For="@(() => _decoTemplate.Name)"/> |
||||||
|
</MudCardContent> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4"> |
||||||
|
Save Changes |
||||||
|
</MudButton> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudSelect T="DecoGroupViewModel" |
||||||
|
Label="Deco Group" |
||||||
|
ValueChanged="@(vm => UpdateDecoGroupItems(vm))"> |
||||||
|
@foreach (DecoGroupViewModel decoGroup in _decoGroups) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@decoGroup">@decoGroup.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudSelect Class="mt-3" |
||||||
|
T="DecoViewModel" |
||||||
|
Label="Deco" |
||||||
|
@bind-value="_selectedDeco"> |
||||||
|
@foreach (DecoViewModel deco in _decos) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@deco">@deco.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudSelect Class="mt-3" |
||||||
|
T="DateTime" |
||||||
|
Label="Start Time On Or After" |
||||||
|
@bind-value="_selectedDecoStart"> |
||||||
|
@foreach (DateTime startTime in _startTimes) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@startTime"> |
||||||
|
@startTime.ToString(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern) |
||||||
|
</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center"> |
||||||
|
<MudItem xs="6"> |
||||||
|
<MudTextField T="int" |
||||||
|
Label="Duration" |
||||||
|
@bind-Value="_durationHours" |
||||||
|
Adornment="Adornment.End" |
||||||
|
AdornmentText="hours"/> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="6"> |
||||||
|
<MudSelect T="int" @bind-Value="_durationMinutes" Adornment="Adornment.End" AdornmentText="minutes"> |
||||||
|
<MudSelectItem Value="0"/> |
||||||
|
<MudSelectItem Value="5"/> |
||||||
|
<MudSelectItem Value="10"/> |
||||||
|
<MudSelectItem Value="15"/> |
||||||
|
<MudSelectItem Value="20"/> |
||||||
|
<MudSelectItem Value="25"/> |
||||||
|
<MudSelectItem Value="30"/> |
||||||
|
<MudSelectItem Value="35"/> |
||||||
|
<MudSelectItem Value="40"/> |
||||||
|
<MudSelectItem Value="45"/> |
||||||
|
<MudSelectItem Value="50"/> |
||||||
|
<MudSelectItem Value="55"/> |
||||||
|
</MudSelect> |
||||||
|
</MudItem> |
||||||
|
</MudGrid> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddDecoToDecoTemplate())" Disabled="@(_selectedDeco is null)"> |
||||||
|
Add Deco To Deco Template |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudSelect T="DecoTemplateItemEditViewModel" |
||||||
|
Label="Deco To Remove" |
||||||
|
@bind-Value="_decoToRemove"> |
||||||
|
<MudSelectItem Value="@((DecoTemplateItemEditViewModel)null)">(none)</MudSelectItem> |
||||||
|
@foreach (DecoTemplateItemEditViewModel item in _decoTemplate.Items.OrderBy(i => i.Start)) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@item">@item.Start.ToShortTimeString() - @item.Text</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => RemoveDecoFromDecoTemplate())" Disabled="@(_decoToRemove is null)"> |
||||||
|
Remove Deco From Deco Template |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="8"> |
||||||
|
<MudCalendar Class="mt-4" |
||||||
|
Items="@_decoTemplate.Items" |
||||||
|
ShowMonth="false" |
||||||
|
ShowWeek="false" |
||||||
|
ShowPrevNextButtons="false" |
||||||
|
ShowDatePicker="false" |
||||||
|
ShowTodayButton="false" |
||||||
|
DayTimeInterval="CalendarTimeInterval.Minutes10" |
||||||
|
Use24HourClock="@(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern.Contains("H"))" |
||||||
|
EnableDragItems="true" |
||||||
|
EnableResizeItems="false" |
||||||
|
ItemChanged="@(ci => CalendarItemChanged(ci))"/> |
||||||
|
</MudItem> |
||||||
|
</MudGrid> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
private readonly List<DecoGroupViewModel> _decoGroups = []; |
||||||
|
private readonly List<DecoViewModel> _decos = []; |
||||||
|
private readonly List<DateTime> _startTimes = []; |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
private DecoTemplateItemsEditViewModel _decoTemplate = new(); |
||||||
|
private DecoTemplateItemEditViewModel _decoToRemove; |
||||||
|
private DecoGroupViewModel _selectedDecoGroup; |
||||||
|
private DecoViewModel _selectedDeco; |
||||||
|
private DateTime _selectedDecoStart; |
||||||
|
private int _durationHours; |
||||||
|
private int _durationMinutes = 15; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
await LoadDecoTemplateItems(); |
||||||
|
|
||||||
|
DateTime start = DateTime.Today; |
||||||
|
_selectedDecoStart = start; |
||||||
|
while (start.Date == DateTime.Today.Date) |
||||||
|
{ |
||||||
|
_startTimes.Add(start); |
||||||
|
start = start.AddMinutes(5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task LoadDecoTemplateItems() |
||||||
|
{ |
||||||
|
Option<DecoTemplateViewModel> maybeDecoTemplate = await Mediator.Send(new GetDecoTemplateById(Id), _cts.Token); |
||||||
|
if (maybeDecoTemplate.IsNone) |
||||||
|
{ |
||||||
|
NavigationManager.NavigateTo("deco-templates"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoTemplateViewModel template in maybeDecoTemplate) |
||||||
|
{ |
||||||
|
_decoTemplate = new DecoTemplateItemsEditViewModel |
||||||
|
{ |
||||||
|
Name = template.Name, |
||||||
|
Items = [] |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
Option<IEnumerable<DecoTemplateItemViewModel>> maybeResults = await Mediator.Send(new GetDecoTemplateItems(Id), _cts.Token); |
||||||
|
foreach (IEnumerable<DecoTemplateItemViewModel> items in maybeResults) |
||||||
|
{ |
||||||
|
_decoTemplate.Items.AddRange(items.Map(ProjectToEditViewModel)); |
||||||
|
} |
||||||
|
|
||||||
|
_decoGroups.AddRange(await Mediator.Send(new GetAllDecoGroups(), _cts.Token)); |
||||||
|
} |
||||||
|
|
||||||
|
private static DecoTemplateItemEditViewModel ProjectToEditViewModel(DecoTemplateItemViewModel item) => |
||||||
|
new() |
||||||
|
{ |
||||||
|
DecoId = item.DecoId, |
||||||
|
DecoName = item.DecoName, |
||||||
|
Start = item.StartTime, |
||||||
|
End = item.EndTime |
||||||
|
}; |
||||||
|
|
||||||
|
private async Task UpdateDecoGroupItems(DecoGroupViewModel decoGroup) |
||||||
|
{ |
||||||
|
_selectedDecoGroup = decoGroup; |
||||||
|
|
||||||
|
_decos.Clear(); |
||||||
|
_decos.AddRange(await Mediator.Send(new GetDecosByDecoGroupId(_selectedDecoGroup.Id), _cts.Token)); |
||||||
|
} |
||||||
|
|
||||||
|
private void AddDecoToDecoTemplate() |
||||||
|
{ |
||||||
|
// find first time where this deco will fit |
||||||
|
DateTime maybeStart = _selectedDecoStart; |
||||||
|
while (maybeStart.Date == DateTime.Today) |
||||||
|
{ |
||||||
|
DateTime maybeEnd = maybeStart.AddHours(_durationHours).AddMinutes(_durationMinutes); |
||||||
|
if (IntersectsOthers(null, maybeStart, maybeEnd) == false) |
||||||
|
{ |
||||||
|
var item = new DecoTemplateItemEditViewModel |
||||||
|
{ |
||||||
|
DecoId = _selectedDeco.Id, |
||||||
|
DecoName = _selectedDeco.Name, |
||||||
|
Start = maybeStart, |
||||||
|
End = maybeEnd, |
||||||
|
LastStart = maybeStart, |
||||||
|
LastEnd = maybeEnd |
||||||
|
}; |
||||||
|
|
||||||
|
_decoTemplate.Items.Add(item); |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
maybeStart = maybeStart.AddMinutes(5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task RemoveDecoFromDecoTemplate() |
||||||
|
{ |
||||||
|
if (_decoToRemove is not null) |
||||||
|
{ |
||||||
|
_decoTemplate.Items.Remove(_decoToRemove); |
||||||
|
_decoToRemove = null; |
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void CalendarItemChanged(CalendarItem calendarItem) |
||||||
|
{ |
||||||
|
// don't allow any overlap |
||||||
|
if (calendarItem is DecoTemplateItemEditViewModel item) |
||||||
|
{ |
||||||
|
if (item.End.HasValue && IntersectsOthers(item, item.Start, item.End.Value)) |
||||||
|
{ |
||||||
|
// roll back |
||||||
|
item.Start = item.LastStart; |
||||||
|
item.End = item.LastEnd; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// commit |
||||||
|
item.LastStart = item.Start; |
||||||
|
item.LastEnd = item.End; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private bool IntersectsOthers(DecoTemplateItemEditViewModel item, DateTime start, DateTime end) |
||||||
|
{ |
||||||
|
var willFit = true; |
||||||
|
|
||||||
|
foreach (DecoTemplateItemEditViewModel existing in _decoTemplate.Items) |
||||||
|
{ |
||||||
|
if (existing == item) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (start < existing.End && existing.Start < end) |
||||||
|
{ |
||||||
|
willFit = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return willFit == false; |
||||||
|
} |
||||||
|
|
||||||
|
// private void RemoveDecoTemplateItem(DecoTemplateItemEditViewModel item) |
||||||
|
// { |
||||||
|
// _selectedItem = null; |
||||||
|
// _decoTemplate.Items.Remove(item); |
||||||
|
// } |
||||||
|
|
||||||
|
private async Task SaveChanges() |
||||||
|
{ |
||||||
|
await Task.Delay(10); |
||||||
|
|
||||||
|
var items = _decoTemplate.Items.Map(item => new ReplaceDecoTemplateItem(item.DecoId, item.Start.TimeOfDay, item.End!.Value.TimeOfDay)).ToList(); |
||||||
|
|
||||||
|
Seq<BaseError> errorMessages = await Mediator.Send(new ReplaceDecoTemplateItems(Id, _decoTemplate.Name, items), _cts.Token) |
||||||
|
.Map(e => e.LeftToSeq()); |
||||||
|
|
||||||
|
errorMessages.HeadOrNone().Match( |
||||||
|
error => |
||||||
|
{ |
||||||
|
Snackbar.Add($"Unexpected error saving template: {error.Value}", Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving template: {Error}", error.Value); |
||||||
|
}, |
||||||
|
() => NavigationManager.NavigateTo("/deco-templates")); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,205 @@ |
|||||||
|
@page "/deco-templates" |
||||||
|
@using S = System.Collections.Generic |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@implements IDisposable |
||||||
|
@inject ILogger<DecoTemplates> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IDialogService Dialog |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Deco Templates</MudText> |
||||||
|
<MudGrid> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px;" class="mr-4"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudTextField Class="mt-3 mx-3" Label="Deco Template Group Name" @bind-Value="_decoTemplateGroupName" For="@(() => _decoTemplateGroupName)"/> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddDecoTemplateGroup())" Class="ml-4 mb-4"> |
||||||
|
Add Deco Template Group |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px;" class="mb-6"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<div class="mx-4"> |
||||||
|
<MudSelect Label="Deco Template Group" @bind-Value="_selectedDecoTemplateGroup" Class="mt-3"> |
||||||
|
@foreach (DecoTemplateGroupViewModel decoTemplateGroup in _decoTemplateGroups) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@decoTemplateGroup">@decoTemplateGroup.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudTextField Class="mt-3" Label="Deco Template Name" @bind-Value="_decoTemplateName" For="@(() => _decoTemplateName)"/> |
||||||
|
</div> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddDecoTemplate())" Class="ml-4 mb-4"> |
||||||
|
Add Deco Template |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="8"> |
||||||
|
<MudCard> |
||||||
|
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> |
||||||
|
<ItemTemplate Context="item"> |
||||||
|
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> |
||||||
|
<BodyContent> |
||||||
|
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> |
||||||
|
<MudGrid Justify="Justify.FlexStart"> |
||||||
|
<MudItem xs="8"> |
||||||
|
<MudText>@item.Text</MudText> |
||||||
|
</MudItem> |
||||||
|
</MudGrid> |
||||||
|
<div style="justify-self: end;"> |
||||||
|
@foreach (int decoTemplateId in Optional(item.DecoTemplateId)) |
||||||
|
{ |
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"deco-templates/{decoTemplateId}")"/> |
||||||
|
} |
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BodyContent> |
||||||
|
</MudTreeViewItem> |
||||||
|
</ItemTemplate> |
||||||
|
</MudTreeView> |
||||||
|
</MudCard> |
||||||
|
</MudItem> |
||||||
|
</MudGrid> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
private S.HashSet<DecoTemplateTreeItemViewModel> TreeItems { get; set; } = []; |
||||||
|
private List<DecoTemplateGroupViewModel> _decoTemplateGroups = []; |
||||||
|
private DecoTemplateGroupViewModel _selectedDecoTemplateGroup; |
||||||
|
private string _decoTemplateGroupName; |
||||||
|
private string _decoTemplateName; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
await ReloadDecoTemplateGroups(); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task ReloadDecoTemplateGroups() |
||||||
|
{ |
||||||
|
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); |
||||||
|
TreeItems = _decoTemplateGroups.Map(g => new DecoTemplateTreeItemViewModel(g)).ToHashSet(); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddDecoTemplateGroup() |
||||||
|
{ |
||||||
|
if (!string.IsNullOrWhiteSpace(_decoTemplateGroupName)) |
||||||
|
{ |
||||||
|
Either<BaseError, DecoTemplateGroupViewModel> result = await Mediator.Send(new CreateDecoTemplateGroup(_decoTemplateGroupName), _cts.Token); |
||||||
|
|
||||||
|
foreach (BaseError error in result.LeftToSeq()) |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error adding deco template group: {Error}", error.Value); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoTemplateGroupViewModel decoTemplateGroup in result.RightToSeq()) |
||||||
|
{ |
||||||
|
TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplateGroup)); |
||||||
|
_decoTemplateGroupName = null; |
||||||
|
|
||||||
|
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddDecoTemplate() |
||||||
|
{ |
||||||
|
if (_selectedDecoTemplateGroup is not null && !string.IsNullOrWhiteSpace(_decoTemplateName)) |
||||||
|
{ |
||||||
|
Either<BaseError, DecoTemplateViewModel> result = await Mediator.Send(new CreateDecoTemplate(_selectedDecoTemplateGroup.Id, _decoTemplateName), _cts.Token); |
||||||
|
|
||||||
|
foreach (BaseError error in result.LeftToSeq()) |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error adding deco template: {Error}", error.Value); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoTemplateViewModel decoTemplate in result.RightToSeq()) |
||||||
|
{ |
||||||
|
foreach (DecoTemplateTreeItemViewModel item in TreeItems.Where(item => item.DecoTemplateGroupId == _selectedDecoTemplateGroup.Id)) |
||||||
|
{ |
||||||
|
item.TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplate)); |
||||||
|
} |
||||||
|
|
||||||
|
_decoTemplateName = null; |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<S.HashSet<DecoTemplateTreeItemViewModel>> LoadServerData(DecoTemplateTreeItemViewModel parentNode) |
||||||
|
{ |
||||||
|
foreach (int decoTemplateGroupId in Optional(parentNode.DecoTemplateGroupId)) |
||||||
|
{ |
||||||
|
List<DecoTemplateViewModel> result = await Mediator.Send(new GetDecoTemplatesByDecoTemplateGroupId(decoTemplateGroupId), _cts.Token); |
||||||
|
foreach (DecoTemplateViewModel decoTemplate in result) |
||||||
|
{ |
||||||
|
parentNode.TreeItems.Add(new DecoTemplateTreeItemViewModel(decoTemplate)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return parentNode.TreeItems; |
||||||
|
} |
||||||
|
|
||||||
|
private async Task DeleteItem(DecoTemplateTreeItemViewModel treeItem) |
||||||
|
{ |
||||||
|
foreach (int decoTemplateGroupId in Optional(treeItem.DecoTemplateGroupId)) |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "Deco Template group" }, { "EntityName", treeItem.Text } }; |
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
||||||
|
|
||||||
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template Group", parameters, options); |
||||||
|
DialogResult result = await dialog.Result; |
||||||
|
if (!result.Canceled) |
||||||
|
{ |
||||||
|
await Mediator.Send(new DeleteDecoTemplateGroup(decoTemplateGroupId), _cts.Token); |
||||||
|
TreeItems.RemoveWhere(i => i.DecoTemplateGroupId == decoTemplateGroupId); |
||||||
|
|
||||||
|
_decoTemplateGroups = await Mediator.Send(new GetAllDecoTemplateGroups(), _cts.Token); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (int decoTemplateId in Optional(treeItem.DecoTemplateId)) |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "Deco Template" }, { "EntityName", treeItem.Text } }; |
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
||||||
|
|
||||||
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Template", parameters, options); |
||||||
|
DialogResult result = await dialog.Result; |
||||||
|
if (!result.Canceled) |
||||||
|
{ |
||||||
|
await Mediator.Send(new DeleteDecoTemplate(decoTemplateId), _cts.Token); |
||||||
|
foreach (DecoTemplateTreeItemViewModel parent in TreeItems) |
||||||
|
{ |
||||||
|
parent.TreeItems.Remove(treeItem); |
||||||
|
} |
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,211 @@ |
|||||||
|
@page "/decos" |
||||||
|
@using S = System.Collections.Generic |
||||||
|
@using ErsatzTV.Application.Scheduling |
||||||
|
@implements IDisposable |
||||||
|
@inject ILogger<Decos> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IDialogService Dialog |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Decos</MudText> |
||||||
|
<MudGrid> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px;" class="mr-4"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudTextField Class="mt-3 mx-3" Label="Deco Group Name" @bind-Value="_decoGroupName" For="@(() => _decoGroupName)"/> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddDecoGroup())" Class="ml-4 mb-4"> |
||||||
|
Add Deco Group |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="4"> |
||||||
|
<div style="max-width: 400px;" class="mb-6"> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<div class="mx-4"> |
||||||
|
<MudSelect Label="Deco Group" @bind-Value="_selectedDecoGroup" Class="mt-3"> |
||||||
|
@foreach (DecoGroupViewModel decoGroup in _decoGroups) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@decoGroup">@decoGroup.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudTextField Class="mt-3" Label="Deco Name" @bind-Value="_decoName" For="@(() => _decoName)"/> |
||||||
|
</div> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddDeco())" Class="ml-4 mb-4"> |
||||||
|
Add Deco |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</div> |
||||||
|
</MudItem> |
||||||
|
<MudItem xs="8"> |
||||||
|
<MudCard> |
||||||
|
<MudTreeView ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> |
||||||
|
<ItemTemplate Context="item"> |
||||||
|
<MudTreeViewItem Items="@item.TreeItems" Icon="@item.Icon" CanExpand="@item.CanExpand" Value="@item"> |
||||||
|
<BodyContent> |
||||||
|
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> |
||||||
|
<MudGrid Justify="Justify.FlexStart"> |
||||||
|
<MudItem xs="5"> |
||||||
|
<MudText>@item.Text</MudText> |
||||||
|
</MudItem> |
||||||
|
@if (!string.IsNullOrWhiteSpace(item.EndText)) |
||||||
|
{ |
||||||
|
<MudItem xs="6"> |
||||||
|
<MudText>@item.EndText</MudText> |
||||||
|
</MudItem> |
||||||
|
} |
||||||
|
</MudGrid> |
||||||
|
<div style="justify-self: end;"> |
||||||
|
@foreach (int decoId in Optional(item.DecoId)) |
||||||
|
{ |
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Href="@($"decos/{decoId}")"/> |
||||||
|
} |
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(_ => DeleteItem(item))"/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BodyContent> |
||||||
|
</MudTreeViewItem> |
||||||
|
</ItemTemplate> |
||||||
|
</MudTreeView> |
||||||
|
</MudCard> |
||||||
|
</MudItem> |
||||||
|
</MudGrid> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
private S.HashSet<DecoTreeItemViewModel> TreeItems { get; set; } = []; |
||||||
|
private List<DecoGroupViewModel> _decoGroups = []; |
||||||
|
private DecoGroupViewModel _selectedDecoGroup; |
||||||
|
private string _decoGroupName; |
||||||
|
private string _decoName; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
await ReloadDecoGroups(); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task ReloadDecoGroups() |
||||||
|
{ |
||||||
|
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); |
||||||
|
TreeItems = _decoGroups.Map(g => new DecoTreeItemViewModel(g)).ToHashSet(); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddDecoGroup() |
||||||
|
{ |
||||||
|
if (!string.IsNullOrWhiteSpace(_decoGroupName)) |
||||||
|
{ |
||||||
|
Either<BaseError, DecoGroupViewModel> result = await Mediator.Send(new CreateDecoGroup(_decoGroupName), _cts.Token); |
||||||
|
|
||||||
|
foreach (BaseError error in result.LeftToSeq()) |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error adding deco group: {Error}", error.Value); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoGroupViewModel decoGroup in result.RightToSeq()) |
||||||
|
{ |
||||||
|
TreeItems.Add(new DecoTreeItemViewModel(decoGroup)); |
||||||
|
_decoGroupName = null; |
||||||
|
|
||||||
|
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddDeco() |
||||||
|
{ |
||||||
|
if (_selectedDecoGroup is not null && !string.IsNullOrWhiteSpace(_decoName)) |
||||||
|
{ |
||||||
|
Either<BaseError, DecoViewModel> result = await Mediator.Send(new CreateDeco(_selectedDecoGroup.Id, _decoName), _cts.Token); |
||||||
|
|
||||||
|
foreach (BaseError error in result.LeftToSeq()) |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error adding deco: {Error}", error.Value); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (DecoViewModel deco in result.RightToSeq()) |
||||||
|
{ |
||||||
|
foreach (DecoTreeItemViewModel item in TreeItems.Where(item => item.DecoGroupId == _selectedDecoGroup.Id)) |
||||||
|
{ |
||||||
|
item.TreeItems.Add(new DecoTreeItemViewModel(deco)); |
||||||
|
} |
||||||
|
|
||||||
|
_decoName = null; |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<S.HashSet<DecoTreeItemViewModel>> LoadServerData(DecoTreeItemViewModel parentNode) |
||||||
|
{ |
||||||
|
foreach (int decoGroupId in Optional(parentNode.DecoGroupId)) |
||||||
|
{ |
||||||
|
List<DecoViewModel> result = await Mediator.Send(new GetDecosByDecoGroupId(decoGroupId), _cts.Token); |
||||||
|
foreach (DecoViewModel deco in result) |
||||||
|
{ |
||||||
|
parentNode.TreeItems.Add(new DecoTreeItemViewModel(deco)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return parentNode.TreeItems; |
||||||
|
} |
||||||
|
|
||||||
|
private async Task DeleteItem(DecoTreeItemViewModel treeItem) |
||||||
|
{ |
||||||
|
foreach (int decoGroupId in Optional(treeItem.DecoGroupId)) |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "deco group" }, { "EntityName", treeItem.Text } }; |
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
||||||
|
|
||||||
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco Group", parameters, options); |
||||||
|
DialogResult result = await dialog.Result; |
||||||
|
if (!result.Canceled) |
||||||
|
{ |
||||||
|
await Mediator.Send(new DeleteDecoGroup(decoGroupId), _cts.Token); |
||||||
|
TreeItems.RemoveWhere(i => i.DecoGroupId == decoGroupId); |
||||||
|
|
||||||
|
_decoGroups = await Mediator.Send(new GetAllDecoGroups(), _cts.Token); |
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (int decoId in Optional(treeItem.DecoId)) |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "deco" }, { "EntityName", treeItem.Text } }; |
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
||||||
|
|
||||||
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Deco", parameters, options); |
||||||
|
DialogResult result = await dialog.Result; |
||||||
|
if (!result.Canceled) |
||||||
|
{ |
||||||
|
await Mediator.Send(new DeleteDeco(decoId), _cts.Token); |
||||||
|
foreach (DecoTreeItemViewModel parent in TreeItems) |
||||||
|
{ |
||||||
|
parent.TreeItems.Remove(treeItem); |
||||||
|
} |
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.ViewModels; |
||||||
|
|
||||||
|
public class DecoEditViewModel |
||||||
|
{ |
||||||
|
public int DecoGroupId { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public int? WatermarkId { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
using Heron.MudCalendar; |
||||||
|
|
||||||
|
namespace ErsatzTV.ViewModels; |
||||||
|
|
||||||
|
public class DecoTemplateItemEditViewModel : CalendarItem |
||||||
|
{ |
||||||
|
private string _blockName; |
||||||
|
public int DecoId { get; set; } |
||||||
|
|
||||||
|
public string DecoName |
||||||
|
{ |
||||||
|
get => _blockName; |
||||||
|
set |
||||||
|
{ |
||||||
|
_blockName = value; |
||||||
|
Text = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public DateTime LastStart { get; set; } |
||||||
|
public DateTime? LastEnd { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
namespace ErsatzTV.ViewModels; |
||||||
|
|
||||||
|
public class DecoTemplateItemsEditViewModel |
||||||
|
{ |
||||||
|
public string Name { get; set; } |
||||||
|
public List<DecoTemplateItemEditViewModel> Items { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
using ErsatzTV.Application.Scheduling; |
||||||
|
using MudBlazor; |
||||||
|
using S = System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.ViewModels; |
||||||
|
|
||||||
|
public class DecoTemplateTreeItemViewModel |
||||||
|
{ |
||||||
|
public DecoTemplateTreeItemViewModel(DecoTemplateGroupViewModel decoTemplateGroup) |
||||||
|
{ |
||||||
|
Text = decoTemplateGroup.Name; |
||||||
|
TreeItems = []; |
||||||
|
CanExpand = decoTemplateGroup.DecoTemplateCount > 0; |
||||||
|
DecoTemplateGroupId = decoTemplateGroup.Id; |
||||||
|
Icon = Icons.Material.Filled.Folder; |
||||||
|
} |
||||||
|
|
||||||
|
public DecoTemplateTreeItemViewModel(DecoTemplateViewModel decoTemplate) |
||||||
|
{ |
||||||
|
Text = decoTemplate.Name; |
||||||
|
TreeItems = []; |
||||||
|
CanExpand = false; |
||||||
|
DecoTemplateId = decoTemplate.Id; |
||||||
|
} |
||||||
|
|
||||||
|
public string Text { get; } |
||||||
|
|
||||||
|
public string Icon { get; } |
||||||
|
|
||||||
|
public bool CanExpand { get; } |
||||||
|
|
||||||
|
public int? DecoTemplateId { get; } |
||||||
|
|
||||||
|
public int? DecoTemplateGroupId { get; } |
||||||
|
|
||||||
|
public S.HashSet<DecoTemplateTreeItemViewModel> TreeItems { get; } |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
using ErsatzTV.Application.Scheduling; |
||||||
|
using MudBlazor; |
||||||
|
using S = System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.ViewModels; |
||||||
|
|
||||||
|
public class DecoTreeItemViewModel |
||||||
|
{ |
||||||
|
public DecoTreeItemViewModel(DecoGroupViewModel decoGroup) |
||||||
|
{ |
||||||
|
Text = decoGroup.Name; |
||||||
|
EndText = string.Empty; |
||||||
|
TreeItems = []; |
||||||
|
CanExpand = decoGroup.DecoCount > 0; |
||||||
|
DecoGroupId = decoGroup.Id; |
||||||
|
Icon = Icons.Material.Filled.Folder; |
||||||
|
} |
||||||
|
|
||||||
|
public DecoTreeItemViewModel(DecoViewModel deco) |
||||||
|
{ |
||||||
|
Text = deco.Name; |
||||||
|
TreeItems = []; |
||||||
|
CanExpand = false; |
||||||
|
DecoId = deco.Id; |
||||||
|
} |
||||||
|
|
||||||
|
public string Text { get; } |
||||||
|
|
||||||
|
public string EndText { get; } |
||||||
|
|
||||||
|
public string Icon { get; } |
||||||
|
|
||||||
|
public bool CanExpand { get; } |
||||||
|
|
||||||
|
public int? DecoId { get; } |
||||||
|
|
||||||
|
public int? DecoGroupId { get; } |
||||||
|
|
||||||
|
public S.HashSet<DecoTreeItemViewModel> TreeItems { get; } |
||||||
|
} |
||||||
Loading…
Reference in new issue