@page "/deco-templates/{Id:int}" @using System.Globalization @using ErsatzTV.Application.Scheduling @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @inject ISnackbar Snackbar @inject IMediator Mediator Save Deco Template
General
Deco Template Group Name
Deco Template Name
Add Content
Deco Group
@foreach (DecoGroupViewModel decoGroup in _decoGroups) { @decoGroup.Name }
Deco
@foreach (DecoViewModel deco in _decos) { @deco.Name }
Start Time On Or After
@foreach (DateTime startTime in _startTimes) { @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern) }
Duration
Add Deco To Template
Remove Content
Deco To Remove
(none) @foreach (DecoTemplateItemEditViewModel item in _decoTemplate.Items.OrderBy(i => i.Start)) { @item.Start.ToShortTimeString() - @item.Text }
Remove Deco From Template
Content
@code { private CancellationTokenSource _cts; private readonly List _decoGroups = []; private readonly List _decos = []; private readonly List _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() { _cts?.Cancel(); _cts?.Dispose(); _cts = new CancellationTokenSource(); var token = _cts.Token; try { await LoadDecoTemplateItems(token); DateTime start = DateTime.Today; _selectedDecoStart = start; while (start.Date == DateTime.Today.Date) { _startTimes.Add(start); start = start.AddMinutes(5); } } catch (OperationCanceledException) { // do nothing } } private async Task LoadDecoTemplateItems(CancellationToken cancellationToken) { Option maybeDecoTemplate = await Mediator.Send(new GetDecoTemplateById(Id), cancellationToken); if (maybeDecoTemplate.IsNone) { NavigationManager.NavigateTo("deco-templates"); return; } foreach (DecoTemplateViewModel template in maybeDecoTemplate) { _decoTemplate = new DecoTemplateItemsEditViewModel { GroupId = template.DecoTemplateGroupId, GroupName = template.GroupName, Name = template.Name }; } Option> maybeResults = await Mediator.Send(new GetDecoTemplateItems(Id), cancellationToken); foreach (IEnumerable items in maybeResults) { _decoTemplate.Items.AddRange(items.Map(ProjectToEditViewModel)); } _decoGroups.AddRange(await Mediator.Send(new GetAllDecoGroups(), cancellationToken)); } 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)) { 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) { bool intersects = item.End.HasValue && IntersectsOthers(item, item.Start, item.End.Value); bool crossesMidnight = item.End.HasValue && item.End.Value.TimeOfDay > TimeSpan.Zero && item.End.Value.TimeOfDay < item.Start.TimeOfDay; if (intersects || crossesMidnight) { // 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; } // private void RemoveDecoTemplateItem(DecoTemplateItemEditViewModel item) // { // _selectedItem = null; // _decoTemplate.Items.Remove(item); // } private async Task SaveChanges() { if (_decoTemplate.Items.Any(i => i.End is null)) { Snackbar.Add("Template item cannot end after midnight", Severity.Error); return; } var items = _decoTemplate.Items.Map(item => new ReplaceDecoTemplateItem(item.DecoId, item.Start.TimeOfDay, item.End!.Value.TimeOfDay)).ToList(); Seq errorMessages = await Mediator.Send(new ReplaceDecoTemplateItems(Id, _decoTemplate.GroupId, _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")); } }