mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
563 lines
22 KiB
563 lines
22 KiB
@page "/playouts/{Id:int}/templates" |
|
@using System.Globalization |
|
@using ErsatzTV.Application.Scheduling |
|
@using ErsatzTV.Application.Channels |
|
@using ErsatzTV.Core.Domain.Scheduling |
|
@using System.Text |
|
@implements IDisposable |
|
@inject NavigationManager NavigationManager |
|
@inject ILogger<PlayoutTemplatesEditor> Logger |
|
@inject ISnackbar Snackbar |
|
@inject IMediator Mediator |
|
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
|
<MudTable T="PlayoutTemplateEditViewModel" Hover="true" Items="_items.OrderBy(i => i.Index)" Dense="true" SelectedItem="@_selectedItem" SelectedItemChanged="@(vm => SelectedItemChanged(vm))"> |
|
<ToolBarContent> |
|
<MudText Typo="Typo.h6">@_channelName Templates</MudText> |
|
<MudSpacer/> |
|
<MudText Typo="Typo.subtitle1" Class="mr-3">In priority order from top to bottom</MudText> |
|
</ToolBarContent> |
|
<ColGroup> |
|
<col/> |
|
<col/> |
|
<col/> |
|
<col/> |
|
<col style="width: 60px;"/> |
|
<col style="width: 60px;"/> |
|
<col style="width: 60px;"/> |
|
</ColGroup> |
|
<HeaderContent> |
|
<MudTh>Template</MudTh> |
|
<MudTh>Days of the Week</MudTh> |
|
<MudTh>Days of the Month</MudTh> |
|
<MudTh>Months</MudTh> |
|
<MudTh/> |
|
<MudTh/> |
|
<MudTh/> |
|
</HeaderContent> |
|
<RowTemplate> |
|
<MudTd DataLabel="Template"> |
|
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)"> |
|
@context.Template?.Name |
|
</MudText> |
|
</MudTd> |
|
<MudTd DataLabel="Days of the Week"> |
|
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)"> |
|
@ToDaysOfWeekString(context.DaysOfWeek) |
|
</MudText> |
|
</MudTd> |
|
<MudTd DataLabel="Days of the Month"> |
|
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)"> |
|
@ToDaysOfMonthString(context.DaysOfMonth) |
|
</MudText> |
|
</MudTd> |
|
<MudTd DataLabel="Months"> |
|
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)"> |
|
@ToMonthsOfYearString(context.MonthsOfYear) |
|
</MudText> |
|
</MudTd> |
|
<MudTd> |
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward" |
|
OnClick="@(_ => MoveItemUp(context))" |
|
Disabled="@(_items.All(x => x.Index >= context.Index))"> |
|
</MudIconButton> |
|
</MudTd> |
|
<MudTd> |
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward" |
|
OnClick="@(_ => MoveItemDown(context))" |
|
Disabled="@(_items.All(x => x.Index <= context.Index))"> |
|
</MudIconButton> |
|
</MudTd> |
|
<MudTd> |
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" |
|
OnClick="@(_ => DeleteTemplate(context))"> |
|
</MudIconButton> |
|
</MudTd> |
|
</RowTemplate> |
|
</MudTable> |
|
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddTemplate())" Class="mt-4"> |
|
Add Template |
|
</MudButton> |
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4"> |
|
Save Changes |
|
</MudButton> |
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => PreviewCalendar())" Class="mt-4 ml-4"> |
|
Preview Calendar |
|
</MudButton> |
|
|
|
@if (_selectedItem is not null) |
|
{ |
|
<EditForm Model="_selectedItem"> |
|
<FluentValidationValidator/> |
|
<div style="display: flex; flex-direction: row;" class="mt-6"> |
|
<div style="flex-grow: 1; max-width: 400px;" class="mr-6"> |
|
<MudCard> |
|
<MudCardContent> |
|
<MudSelect T="TemplateGroupViewModel" Label="Template Group" Value="@_selectedGroup" ValueChanged="@(vm => UpdateTemplateGroupItems(vm))"> |
|
@foreach (TemplateGroupViewModel templateGroup in _templateGroups) |
|
{ |
|
<MudSelectItem Value="@templateGroup"> |
|
@templateGroup.Name |
|
</MudSelectItem> |
|
} |
|
</MudSelect> |
|
<MudSelect Label="Template" @bind-Value="_selectedItem.Template" For="@(() => _selectedItem.Template)"> |
|
@foreach (TemplateViewModel template in _templates) |
|
{ |
|
<MudSelectItem Value="@template">@template.Name</MudSelectItem> |
|
} |
|
</MudSelect> |
|
</MudCardContent> |
|
</MudCard> |
|
<MudCard Class="mt-4"> |
|
<MudCardContent> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Monday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Monday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Monday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Tuesday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Tuesday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Tuesday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Wednesday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Wednesday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Wednesday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Thursday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Thursday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Thursday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Friday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Friday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Friday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Saturday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Saturday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Saturday, c))"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Sunday)" |
|
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Sunday))" |
|
CheckedChanged="@((bool c) => DayOfWeekChanged(DayOfWeek.Sunday, c))"/> |
|
</MudElement> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectWeekdays())"> |
|
Weekdays |
|
</MudButton> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectWeekends())"> |
|
Weekends |
|
</MudButton> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllDaysOfWeek())"> |
|
All |
|
</MudButton> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoDaysOfWeek())"> |
|
None |
|
</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
</div> |
|
<div style="flex-grow: 1; max-width: 400px;" class="mr-6"> |
|
<MudCard> |
|
<MudCardContent> |
|
<MudGrid Justify="Justify.FlexStart" Class="mt-3"> |
|
@foreach (int day in Enumerable.Range(1, 31)) |
|
{ |
|
<MudItem xs="3"> |
|
<MudCheckBox T="bool" Label="@day.ToString()" |
|
Checked="@(_selectedItem.DaysOfMonth.Contains(day))" |
|
CheckedChanged="@((bool c) => DayOfMonthChanged(day, c))"/> |
|
</MudItem> |
|
} |
|
</MudGrid> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllDaysOfMonth())"> |
|
All |
|
</MudButton> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoDaysOfMonth())"> |
|
None |
|
</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
</div> |
|
<div style="flex-grow: 1; max-width: 400px;"> |
|
<MudCard> |
|
<MudCardContent> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetMonthName(1)" |
|
Checked="@(_selectedItem.MonthsOfYear.Contains(1))" |
|
CheckedChanged="@((bool c) => MonthOfYearChanged(1, c))"/> |
|
</MudElement> |
|
@foreach (int month in Enumerable.Range(2, 11)) |
|
{ |
|
<MudElement HtmlTag="div" Class="mt-2"> |
|
<MudCheckBox T="bool" Label="@_dtf.GetMonthName(month)" |
|
Checked="@(_selectedItem.MonthsOfYear.Contains(month))" |
|
CheckedChanged="@((bool c) => MonthOfYearChanged(month, c))"/> |
|
</MudElement> |
|
} |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllMonthsOfYear())"> |
|
All |
|
</MudButton> |
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoMonthsOfYear())"> |
|
None |
|
</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
</div> |
|
</div> |
|
</EditForm> |
|
} |
|
else |
|
{ |
|
<MudCard Class="mt-4"> |
|
<MudCalendar ShowWeek="false" |
|
ShowDay="false" |
|
DateRangeChanged="@(range => DateRangeChanged(range))" |
|
MonthCellMinHeight="115" |
|
Items="_previewItems"/> |
|
</MudCard> |
|
} |
|
</MudContainer> |
|
|
|
@code { |
|
private readonly CancellationTokenSource _cts = new(); |
|
|
|
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; |
|
|
|
[Parameter] |
|
public int Id { get; set; } |
|
|
|
private readonly List<TemplateGroupViewModel> _templateGroups = []; |
|
private readonly List<TemplateViewModel> _templates = []; |
|
|
|
private string _channelName; |
|
private List<PlayoutTemplateEditViewModel> _items = []; |
|
private TemplateGroupViewModel _selectedGroup; |
|
private PlayoutTemplateEditViewModel _selectedItem; |
|
private readonly List<CalendarItem> _previewItems = []; |
|
|
|
public void Dispose() |
|
{ |
|
_cts.Cancel(); |
|
_cts.Dispose(); |
|
} |
|
|
|
protected override async Task OnParametersSetAsync() => await LoadTemplates(); |
|
|
|
private async Task LoadTemplates() |
|
{ |
|
_channelName = (await Mediator.Send(new GetChannelNameByPlayoutId(Id), _cts.Token)).IfNone(string.Empty); |
|
|
|
_templateGroups.Clear(); |
|
_templateGroups.AddRange(await Mediator.Send(new GetAllTemplateGroups(), _cts.Token)); |
|
|
|
List<PlayoutTemplateViewModel> results = await Mediator.Send(new GetPlayoutTemplates(Id), _cts.Token); |
|
_items = results.Map(ProjectToEditViewModel).ToList(); |
|
if (_items.Count == 1) |
|
{ |
|
await SelectedItemChanged(_items.Head()); |
|
} |
|
} |
|
|
|
private PlayoutTemplateEditViewModel ProjectToEditViewModel(PlayoutTemplateViewModel item) => |
|
new() |
|
{ |
|
Id = item.Id, |
|
Template = item.Template, |
|
Index = item.Index, |
|
DaysOfWeek = item.DaysOfWeek.ToList(), |
|
DaysOfMonth = item.DaysOfMonth.ToList(), |
|
MonthsOfYear = item.MonthsOfYear.ToList() |
|
}; |
|
|
|
private async Task UpdateTemplateGroupItems(TemplateGroupViewModel templateGroup) |
|
{ |
|
_selectedGroup = templateGroup; |
|
|
|
_templates.Clear(); |
|
_templates.AddRange(await Mediator.Send(new GetTemplatesByTemplateGroupId(_selectedGroup.Id), _cts.Token)); |
|
} |
|
|
|
private async Task SelectedItemChanged(PlayoutTemplateEditViewModel template) |
|
{ |
|
_selectedItem = template; |
|
|
|
foreach (TemplateGroupViewModel group in Optional(_templateGroups.Find(group => group.Id == _selectedItem.Template.TemplateGroupId))) |
|
{ |
|
await UpdateTemplateGroupItems(group); |
|
} |
|
} |
|
|
|
private void DayOfWeekChanged(DayOfWeek dayOfWeek, bool isChecked) |
|
{ |
|
if (isChecked && !_selectedItem.DaysOfWeek.Contains(dayOfWeek)) |
|
{ |
|
_selectedItem.DaysOfWeek.Add(dayOfWeek); |
|
_selectedItem.DaysOfWeek = _selectedItem.DaysOfWeek.OrderBy(x => ((int)x + 6) % 7).ToList(); |
|
} |
|
|
|
if (!isChecked) |
|
{ |
|
_selectedItem.DaysOfWeek.Remove(dayOfWeek); |
|
} |
|
} |
|
|
|
private void SelectWeekdays() |
|
{ |
|
_selectedItem.DaysOfWeek.Clear(); |
|
_selectedItem.DaysOfWeek.AddRange( |
|
new[] |
|
{ |
|
DayOfWeek.Monday, |
|
DayOfWeek.Tuesday, |
|
DayOfWeek.Wednesday, |
|
DayOfWeek.Thursday, |
|
DayOfWeek.Friday |
|
}); |
|
} |
|
|
|
private void SelectWeekends() |
|
{ |
|
_selectedItem.DaysOfWeek.Clear(); |
|
_selectedItem.DaysOfWeek.AddRange( |
|
new[] |
|
{ |
|
DayOfWeek.Saturday, |
|
DayOfWeek.Sunday |
|
}); |
|
} |
|
|
|
private void SelectAllDaysOfWeek() |
|
{ |
|
_selectedItem.DaysOfWeek.Clear(); |
|
_selectedItem.DaysOfWeek.AddRange(PlayoutTemplate.AllDaysOfWeek()); |
|
} |
|
|
|
private void SelectNoDaysOfWeek() => _selectedItem.DaysOfWeek.Clear(); |
|
|
|
private void DayOfMonthChanged(int dayOfMonth, bool isChecked) |
|
{ |
|
if (isChecked && !_selectedItem.DaysOfMonth.Contains(dayOfMonth)) |
|
{ |
|
_selectedItem.DaysOfMonth.Add(dayOfMonth); |
|
_selectedItem.DaysOfMonth.Sort(); |
|
} |
|
|
|
if (!isChecked) |
|
{ |
|
_selectedItem.DaysOfMonth.Remove(dayOfMonth); |
|
} |
|
} |
|
|
|
private void SelectAllDaysOfMonth() |
|
{ |
|
_selectedItem.DaysOfMonth.Clear(); |
|
_selectedItem.DaysOfMonth.AddRange(PlayoutTemplate.AllDaysOfMonth()); |
|
} |
|
|
|
private void SelectNoDaysOfMonth() => _selectedItem.DaysOfMonth.Clear(); |
|
|
|
private void MonthOfYearChanged(int monthOfYear, bool isChecked) |
|
{ |
|
if (isChecked && !_selectedItem.MonthsOfYear.Contains(monthOfYear)) |
|
{ |
|
_selectedItem.MonthsOfYear.Add(monthOfYear); |
|
_selectedItem.MonthsOfYear.Sort(); |
|
} |
|
|
|
if (!isChecked) |
|
{ |
|
_selectedItem.MonthsOfYear.Remove(monthOfYear); |
|
} |
|
} |
|
|
|
private void SelectAllMonthsOfYear() |
|
{ |
|
_selectedItem.MonthsOfYear.Clear(); |
|
_selectedItem.MonthsOfYear.AddRange(PlayoutTemplate.AllMonthsOfYear()); |
|
} |
|
|
|
private void SelectNoMonthsOfYear() => _selectedItem.MonthsOfYear.Clear(); |
|
|
|
private void AddTemplate() |
|
{ |
|
var item = new PlayoutTemplateEditViewModel |
|
{ |
|
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1, |
|
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(), |
|
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(), |
|
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear() |
|
}; |
|
|
|
_items.Add(item); |
|
_selectedItem = item; |
|
} |
|
|
|
private void DeleteTemplate(PlayoutTemplateEditViewModel item) |
|
{ |
|
_selectedItem = null; |
|
_items.Remove(item); |
|
} |
|
|
|
private void MoveItemUp(PlayoutTemplateEditViewModel item) |
|
{ |
|
// swap with lower index |
|
PlayoutTemplateEditViewModel toSwap = _items.OrderByDescending(x => x.Index).First(x => x.Index < item.Index); |
|
(toSwap.Index, item.Index) = (item.Index, toSwap.Index); |
|
} |
|
|
|
private void MoveItemDown(PlayoutTemplateEditViewModel item) |
|
{ |
|
// swap with higher index |
|
PlayoutTemplateEditViewModel toSwap = _items.OrderBy(x => x.Index).First(x => x.Index > item.Index); |
|
(toSwap.Index, item.Index) = (item.Index, toSwap.Index); |
|
} |
|
|
|
private async Task SaveChanges() |
|
{ |
|
if (_items.Any(i => i.Template is null)) |
|
{ |
|
Snackbar.Add("Unable to save; item has no template selected", Severity.Error); |
|
return; |
|
} |
|
|
|
var items = _items.Map( |
|
item => new ReplacePlayoutTemplate( |
|
item.Id, |
|
item.Index, |
|
item.Template.Id, |
|
item.DaysOfWeek, |
|
item.DaysOfMonth, |
|
item.MonthsOfYear)).ToList(); |
|
|
|
Option<BaseError> maybeError = await Mediator.Send(new ReplacePlayoutTemplateItems(Id, items), _cts.Token); |
|
|
|
maybeError.Match( |
|
error => |
|
{ |
|
Snackbar.Add($"Unexpected error saving playout templates: {error.Value}", Severity.Error); |
|
Logger.LogError("Unexpected error saving playout templates: {Error}", error.Value); |
|
}, |
|
() => NavigationManager.NavigateTo("/playouts")); |
|
} |
|
|
|
private async Task PreviewCalendar() |
|
{ |
|
_selectedItem = null; |
|
await InvokeAsync(StateHasChanged); |
|
} |
|
|
|
private async Task DateRangeChanged(DateRange dateRange) |
|
{ |
|
await Task.Delay(10); |
|
|
|
_previewItems.Clear(); |
|
|
|
var prioritized = _items.Filter(i => i.Template is not null).OrderBy(t => t.Index).ToList(); |
|
|
|
if (dateRange.Start.HasValue && dateRange.End.HasValue) |
|
{ |
|
DateTime current = dateRange.Start.Value.Date; |
|
while (current <= dateRange.End.Value.Date) |
|
{ |
|
foreach (PlayoutTemplateEditViewModel template in prioritized) |
|
{ |
|
if (template.AppliesToDate(current)) |
|
{ |
|
_previewItems.Add( |
|
new CalendarItem |
|
{ |
|
AllDay = true, |
|
Start = current, |
|
Text = template.Template.Name |
|
}); |
|
|
|
break; |
|
} |
|
} |
|
|
|
current = current.AddDays(1); |
|
} |
|
} |
|
} |
|
|
|
private string ToDaysOfWeekString(List<DayOfWeek> daysOfWeek) |
|
{ |
|
if (daysOfWeek.Count is 0 or 7) |
|
{ |
|
return "*any*"; |
|
} |
|
|
|
daysOfWeek.Sort(); |
|
|
|
return string.Join(", ", daysOfWeek.Map(_dtf.GetAbbreviatedDayName)); |
|
} |
|
|
|
private string ToDaysOfMonthString(List<int> daysOfMonth) |
|
{ |
|
if (daysOfMonth.Count is 0 or 31) |
|
{ |
|
return "*any*"; |
|
} |
|
|
|
return ToRangeString(daysOfMonth); |
|
} |
|
|
|
private string ToMonthsOfYearString(List<int> monthsOfYear) |
|
{ |
|
if (monthsOfYear.Count is 0 or 12) |
|
{ |
|
return "*any*"; |
|
} |
|
|
|
monthsOfYear.Sort(); |
|
|
|
return string.Join(", ", monthsOfYear.Map(_dtf.GetAbbreviatedMonthName)); |
|
} |
|
|
|
private static string ToRangeString(List<int> list) |
|
{ |
|
list = list.Distinct().ToList(); |
|
list.Sort(); |
|
|
|
var result = new StringBuilder(); |
|
for (var i = 0; i < list.Count; i++) |
|
{ |
|
int temp = list[i]; |
|
|
|
//add a number |
|
result.Append(list[i]); |
|
|
|
//skip number(s) between a range |
|
while (i < list.Count - 1 && list[i + 1] == list[i] + 1) |
|
{ |
|
i++; |
|
} |
|
|
|
//add the range |
|
if (temp != list[i]) |
|
{ |
|
result.Append("-").Append(list[i]); |
|
} |
|
|
|
//add comma |
|
if (i != list.Count - 1) |
|
{ |
|
result.Append(", "); |
|
} |
|
} |
|
|
|
return result.ToString(); |
|
} |
|
|
|
} |