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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record CreateDecoGroup(string Name) : IRequest<Either<BaseError, DecoGroupViewModel>>; |
||||
@ -0,0 +1,33 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record CreateDecoTemplateGroup(string Name) : IRequest<Either<BaseError, DecoTemplateGroupViewModel>>; |
||||
@ -0,0 +1,35 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DeleteDeco(int DecoId) : IRequest<Option<BaseError>>; |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DeleteDecoGroup(int DecoGroupId) : IRequest<Option<BaseError>>; |
||||
@ -0,0 +1,29 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DeleteDecoTemplate(int DecoTemplateId) : IRequest<Option<BaseError>>; |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core; |
||||
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DeleteDecoTemplateGroup(int DecoTemplateGroupId) : IRequest<Option<BaseError>>; |
||||
@ -0,0 +1,29 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record ReplaceDecoTemplateItem(int DecoId, TimeSpan StartTime, TimeSpan EndTime); |
||||
@ -0,0 +1,6 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record UpdateDefaultDeco(int PlayoutId, int? DecoId) : IRequest; |
||||
@ -0,0 +1,16 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DecoGroupViewModel(int Id, string Name, int DecoCount); |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DecoTemplateGroupViewModel(int Id, string Name, int DecoTemplateCount); |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DecoTemplateItemViewModel(int DecoId, string DecoName, DateTime StartTime, DateTime EndTime); |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DecoTemplateViewModel(int Id, int DecoTemplateGroupId, string Name); |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record DecoViewModel(int Id, int DecoGroupId, string Name, int? WatermarkId); |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetAllDecoGroups : IRequest<List<DecoGroupViewModel>>; |
||||
@ -0,0 +1,21 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetAllDecoTemplateGroups : IRequest<List<DecoTemplateGroupViewModel>>; |
||||
@ -0,0 +1,23 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecoById(int DecoId) : IRequest<Option<DecoViewModel>>; |
||||
@ -0,0 +1,17 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecoByPlayoutId(int PlayoutId) : IRequest<Option<DecoViewModel>>; |
||||
@ -0,0 +1,18 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecoTemplateById(int DecoTemplateId) : IRequest<Option<DecoTemplateViewModel>>; |
||||
@ -0,0 +1,17 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecoTemplateItems(int DecoTemplateId) : IRequest<List<DecoTemplateItemViewModel>>; |
||||
@ -0,0 +1,20 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecoTemplatesByDecoTemplateGroupId(int DecoTemplateGroupId) : IRequest<List<DecoTemplateViewModel>>; |
||||
@ -0,0 +1,21 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Scheduling; |
||||
|
||||
public record GetDecosByDecoGroupId(int DecoGroupId) : IRequest<List<DecoViewModel>>; |
||||
@ -0,0 +1,21 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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