@page "/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 Template
General
Template Group Name
Template Name
Add Content
Block Group
@foreach (BlockGroupViewModel blockGroup in _blockGroups) { @blockGroup.Name }
Block
@foreach (BlockViewModel block in _blocks) { @block.Name }
Start Time On Or After
@foreach (DateTime startTime in _startTimes) { @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern) }
Add Block To Template
Remove Content
Block To Remove
(none) @foreach (TemplateItemEditViewModel item in _template.Items.OrderBy(i => i.Start)) { @item.Start.ToShortTimeString() - @item.Text }
Remove Block From Template
Content
@code { private CancellationTokenSource _cts; private readonly List _blockGroups = []; private readonly List _blocks = []; private readonly List _startTimes = []; [Parameter] public int Id { get; set; } private TemplateItemsEditViewModel _template = new(); private TemplateItemEditViewModel _blockToRemove; private BlockGroupViewModel _selectedBlockGroup; private BlockViewModel _selectedBlock; private DateTime _selectedBlockStart; private MudCalendar _calendar; 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 LoadTemplateItems(token); DateTime start = DateTime.Today; _selectedBlockStart = start; while (start.Date == DateTime.Today.Date) { _startTimes.Add(start); start = start.AddMinutes(5); } } catch (OperationCanceledException) { // do nothing } } private async Task LoadTemplateItems(CancellationToken cancellationToken) { Option maybeTemplate = await Mediator.Send(new GetTemplateById(Id), cancellationToken); if (maybeTemplate.IsNone) { NavigationManager.NavigateTo("templates"); return; } foreach (TemplateViewModel template in maybeTemplate) { _template = new TemplateItemsEditViewModel { GroupId = template.TemplateGroupId, GroupName = template.GroupName, Name = template.Name }; } Option> maybeResults = await Mediator.Send(new GetTemplateItems(Id), cancellationToken); foreach (IEnumerable items in maybeResults) { _template.Items.AddRange(items.Map(ProjectToEditViewModel)); } _blockGroups.AddRange(await Mediator.Send(new GetAllBlockGroups(), cancellationToken)); } private static TemplateItemEditViewModel ProjectToEditViewModel(TemplateItemViewModel item) => new() { BlockId = item.BlockId, BlockName = item.BlockName, Start = item.StartTime, End = item.EndTime }; private async Task UpdateBlockGroupItems(string blockGroupName) { Option maybeBlockGroup = Optional(_blockGroups.Find(bg => bg.Name == blockGroupName)); foreach (var blockGroup in maybeBlockGroup) { _selectedBlockGroup = blockGroup; _blocks.Clear(); _blocks.AddRange(await Mediator.Send(new GetBlocksByBlockGroupId(_selectedBlockGroup.Id), _cts.Token)); } } private void UpdateSelectedBlock(string blockName) { _selectedBlock = _blocks.Find(bg => bg.Name == blockName); InvokeAsync(StateHasChanged); } private void AddBlockToTemplate() { // find first time where this block will fit DateTime maybeStart = _selectedBlockStart; while (maybeStart.Date == DateTime.Today) { DateTime maybeEnd = maybeStart.AddMinutes(_selectedBlock.Minutes); if (!IntersectsOthers(null, maybeStart, maybeEnd)) { var item = new TemplateItemEditViewModel { BlockId = _selectedBlock.Id, BlockName = _selectedBlock.Name, Start = maybeStart, End = maybeEnd, LastStart = maybeStart, LastEnd = maybeEnd }; _template.Items.Add(item); InvokeAsync(() => { _calendar.ScrollToTime(TimeOnly.FromDateTime(item.Start)); }); break; } maybeStart = maybeStart.AddMinutes(5); } } private async Task RemoveBlockFromTemplate() { if (_blockToRemove is not null) { _template.Items.Remove(_blockToRemove); await InvokeAsync(() => { _calendar.ScrollToTime(TimeOnly.FromDateTime(_blockToRemove.Start)); }); _blockToRemove = null; await InvokeAsync(StateHasChanged); } } private void CalendarItemChanged(CalendarItem calendarItem) { // don't allow any overlap if (calendarItem is TemplateItemEditViewModel 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(TemplateItemEditViewModel item, DateTime start, DateTime end) { var willFit = true; foreach (TemplateItemEditViewModel existing in _template.Items) { if (existing == item) { continue; } if (start < existing.End && existing.Start < end) { willFit = false; break; } } return !willFit; } // private void RemoveTemplateItem(TemplateItemEditViewModel item) // { // _selectedItem = null; // _template.Items.Remove(item); // } private async Task SaveChanges() { var items = _template.Items.Map(item => new ReplaceTemplateItem(item.BlockId, item.Start.TimeOfDay)).ToList(); Seq errorMessages = await Mediator.Send(new ReplaceTemplateItems(_template.GroupId, Id, _template.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("templates")); } }