diff --git a/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs b/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs index 13cc47031..b298d63cb 100644 --- a/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs +++ b/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs @@ -17,7 +17,7 @@ namespace ErsatzTV.Application.ProgramSchedules index, startType, startTime, - PlayoutMode.Duration, + PlayoutMode.Multiple, mediaCollection) => Count = count; diff --git a/ErsatzTV.Core/FileSystemLayout.cs b/ErsatzTV.Core/FileSystemLayout.cs index f683d9eec..5e2f17cf9 100644 --- a/ErsatzTV.Core/FileSystemLayout.cs +++ b/ErsatzTV.Core/FileSystemLayout.cs @@ -6,7 +6,9 @@ namespace ErsatzTV.Core public static class FileSystemLayout { public static readonly string AppDataFolder = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create), + Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData, + Environment.SpecialFolderOption.Create), "ersatztv"); public static readonly string DatabasePath = Path.Combine(AppDataFolder, "ersatztv.sqlite3"); diff --git a/ErsatzTV/Pages/ScheduleItemsEditor.razor b/ErsatzTV/Pages/ScheduleItemsEditor.razor index 1307c8f08..e9ade974b 100644 --- a/ErsatzTV/Pages/ScheduleItemsEditor.razor +++ b/ErsatzTV/Pages/ScheduleItemsEditor.razor @@ -1,51 +1,96 @@ @page "/schedules/{Id:int}/items" +@using ErsatzTV.Application.MediaCollections +@using ErsatzTV.Application.MediaCollections.Queries @using ErsatzTV.Application.ProgramSchedules @using ErsatzTV.Application.ProgramSchedules.Commands @using ErsatzTV.Application.ProgramSchedules.Queries -@using ErsatzTV.Application.MediaCollections -@using ErsatzTV.Application.MediaCollections.Queries +@inject NavigationManager NavigationManager +@inject ILogger Logger +@inject ISnackbar Snackbar @inject IMediator Mediator - + @_schedule.Name Items + + + + + + Start Time Media Collection Playout Mode + - @(context.StartType == StartType.Fixed ? context.StartTime : "Dynamic") + + @(context.StartType == StartType.Fixed ? context.StartTime : "Dynamic") + + + + + @context.MediaCollection.Name + + + + + @context.PlayoutMode + @if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue) + { + @($" ({context.MultipleCount})") + } + + + + - @context.MediaCollectionName - @context.PlayoutMode - @* *@ - @* *@ - @* Start Time *@ - @* *@ - @* *@ - @* *@ - @* *@ - @* *@ - @* *@ - @* @foreach (PlayoutMode playoutMode in Enum.GetValues()) *@ - @* { *@ - @* @playoutMode *@ - @* } *@ - @* *@ - @* *@ - @* *@ -@* TODO: only enable this button when media collections exist *@ - + Add Schedule Item + + Save Changes + + +@if (_selectedItem is not null) +{ +
+ + + + + + @foreach (StartType startType in Enum.GetValues()) + { + @startType + } + + + + + @foreach (PlayoutMode playoutMode in Enum.GetValues()) + { + @playoutMode + } + + + + + + + + + +
+} @code { @@ -53,11 +98,18 @@ public int Id { get; set; } private ProgramScheduleItemsEditViewModel _schedule; + private List _mediaCollections; + private Option _defaultCollection; + + private ProgramScheduleItemEditViewModel _selectedItem; protected override Task OnParametersSetAsync() => LoadScheduleItems(); private async Task LoadScheduleItems() { + _mediaCollections = await Mediator.Send(new GetAllMediaCollections()); + _defaultCollection = _mediaCollections.HeadOrNone(); + string name = string.Empty; Option maybeSchedule = await Mediator.Send(new GetProgramScheduleById(Id)); maybeSchedule.IfSome(vm => name = vm.Name); @@ -66,33 +118,80 @@ maybeResults.IfSome(items => _schedule = new ProgramScheduleItemsEditViewModel { Name = name, - Items = items.Map(i => new ProgramScheduleItemEditViewModel - { - Id = i.Id, - Index = i.Index, - StartType = i.StartType, - StartTime = i.StartTime, - PlayoutMode = i.PlayoutMode, - MediaCollectionName = i.MediaCollection.Name - }).ToList() + Items = items.Map(ProjectToEditViewModel).ToList() }); } - private async Task AddScheduleItem() + private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item) { - try + var result = new ProgramScheduleItemEditViewModel { - // TODO: don't bother going to the database for this, we probably want a "save changes" button - // to save all added, deleted, modified items + Id = item.Id, + Index = item.Index, + StartType = item.StartType, + StartTime = item.StartTime, + PlayoutMode = item.PlayoutMode, + MediaCollection = item.MediaCollection + }; - MediaCollectionViewModel mediaCollection = await Mediator.Send(new GetAllMediaCollections()).Map(list => list.Head()); - await Mediator.Send(new AddProgramScheduleItem(Id, StartType.Dynamic, null, PlayoutMode.One, mediaCollection.Id, null, null, null)); - await LoadScheduleItems(); - } - catch (Exception ex) + switch (item) { - // TODO: something? + case ProgramScheduleItemMultipleViewModel multiple: + result.MultipleCount = multiple.Count; + break; + case ProgramScheduleItemDurationViewModel duration: + result.PlayoutDuration = duration.PlayoutDuration; + result.OfflineTail = duration.OfflineTail; + break; } + + return result; + } + + private void AddScheduleItem() + { + var item = new ProgramScheduleItemEditViewModel + { + Index = _schedule.Items.Map(i => i.Index).DefaultIfEmpty().Max() + 1, + StartType = StartType.Dynamic, + PlayoutMode = PlayoutMode.One + }; + + _defaultCollection.IfSome(c => item.MediaCollection = c); + + _schedule.Items.Add(item); + _selectedItem = item; + } + + private void RemoveScheduleItem(ProgramScheduleItemEditViewModel item) + { + _selectedItem = null; + _schedule.Items.Remove(item); + } + + private async Task> SearchMediaCollections(string value) => _mediaCollections.Filter(c => c.Name.StartsWith(value)); + + private async Task SaveChanges() + { + var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem( + item.Index, + item.StartType, + item.StartTime, + item.PlayoutMode, + item.MediaCollection.Id, + item.MultipleCount, + item.PlayoutDuration, + item.PlayoutMode == PlayoutMode.Duration ? item.OfflineTail.IfNone(false) : null)).ToList(); + + Seq errorMessages = await Mediator.Send(new ReplaceProgramScheduleItems(Id, items)).Map(e => e.LeftToSeq()); + + errorMessages.HeadOrNone().Match( + error => + { + Snackbar.Add($"Unexpected error saving schedule: {error.Value}"); + Logger.LogError("Unexpected error saving schedule: {Error}", error.Value); + }, + () => NavigationManager.NavigateTo("/schedules")); } } \ No newline at end of file diff --git a/ErsatzTV/Pages/Schedules.razor b/ErsatzTV/Pages/Schedules.razor index 890131b00..5d3482581 100644 --- a/ErsatzTV/Pages/Schedules.razor +++ b/ErsatzTV/Pages/Schedules.razor @@ -5,7 +5,7 @@ @inject IDialogService Dialog @inject IMediator Mediator - + Schedules diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 6e98cc750..88413cd7e 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -86,7 +86,7 @@ namespace ErsatzTV "Server will listen on port {Port} - try UI at {UI}", 8989, "http://localhost:8989"); - + if (!Directory.Exists(FileSystemLayout.AppDataFolder)) { Directory.CreateDirectory(FileSystemLayout.AppDataFolder); diff --git a/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs b/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs new file mode 100644 index 000000000..19ccd4834 --- /dev/null +++ b/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs @@ -0,0 +1,22 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.ViewModels; +using FluentValidation; + +namespace ErsatzTV.Validators +{ + public class ProgramScheduleItemEditViewModelValidator : AbstractValidator + { + public ProgramScheduleItemEditViewModelValidator() + { + When( + i => i.StartType == StartType.Fixed, + () => RuleFor(i => i.StartTime).NotNull()); + When( + i => i.PlayoutMode == PlayoutMode.Multiple, + () => RuleFor(i => i.MultipleCount).NotNull().GreaterThan(0)); + When( + i => i.PlayoutMode == PlayoutMode.Duration, + () => RuleFor(i => i.PlayoutDuration).NotNull()); + } + } +} diff --git a/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs b/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs index 8694dbe0e..c9f04651c 100644 --- a/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs +++ b/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs @@ -1,15 +1,45 @@ using System; +using ErsatzTV.Application.MediaCollections; using ErsatzTV.Core.Domain; namespace ErsatzTV.ViewModels { public class ProgramScheduleItemEditViewModel { + private int? _multipleCount; + private bool? _offlineTail; + private TimeSpan? _playoutDuration; + private TimeSpan? _startTime; + public int Id { get; set; } public int Index { get; set; } public StartType StartType { get; set; } - public TimeSpan? StartTime { get; set; } + + public TimeSpan? StartTime + { + get => StartType == StartType.Fixed ? _startTime : null; + set => _startTime = value; + } + public PlayoutMode PlayoutMode { get; set; } - public string MediaCollectionName { get; set; } + public MediaCollectionViewModel MediaCollection { get; set; } + + public int? MultipleCount + { + get => PlayoutMode == PlayoutMode.Multiple ? _multipleCount : null; + set => _multipleCount = value; + } + + public TimeSpan? PlayoutDuration + { + get => PlayoutMode == PlayoutMode.Duration ? _playoutDuration : null; + set => _playoutDuration = value; + } + + public bool? OfflineTail + { + get => PlayoutMode == PlayoutMode.Duration ? _offlineTail : null; + set => _offlineTail = value; + } } }