@page "/blocks/{Id:int}" @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Search @using ErsatzTV.Core.Domain.Scheduling @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @inject ISnackbar Snackbar @inject IMediator Mediator Edit Block
Before Duration End After Duration End
Add Block Item Save Changes Preview Block Playout Collection Playback Order Show In EPG @context.CollectionName @context.PlaybackOrder
@if (_selectedItem is not null) {
Collection Television Show Television Season @* Artist *@ @* Multi Collection *@ Smart Collection @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection) { Only the first 10 items are shown } @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection) { Only the first 10 items are shown } @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection) { Only the first 10 items are shown } @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow) { Only the first 10 items are shown } @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) { Only the first 20 items are shown } @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist) { Only the first 10 items are shown } @switch (_selectedItem.CollectionType) { case ProgramScheduleItemCollectionType.MultiCollection: Shuffle @* Shuffle In Order *@ break; case ProgramScheduleItemCollectionType.Collection: case ProgramScheduleItemCollectionType.SmartCollection: Chronological Shuffle Random Random Rotation @* Shuffle In Order *@ break; case ProgramScheduleItemCollectionType.TelevisionShow: Season, Episode Chronological Shuffle Random @* Multi-Episode Shuffle *@ break; case ProgramScheduleItemCollectionType.TelevisionSeason: case ProgramScheduleItemCollectionType.Artist: case ProgramScheduleItemCollectionType.FakeCollection: default: Chronological Shuffle Random break; }
}
@if (_previewItems != null) { Block Playout Preview Start Finish Media Item Duration @context.Start.ToString(@"hh\:mm\:ss") @context.Finish.ToString(@"hh\:mm\:ss") @context.Title @context.Duration }
@code { private readonly CancellationTokenSource _cts = new(); [Parameter] public int Id { get; set; } private BlockItemsEditViewModel _block = new() { Items = [] }; private BlockItemEditViewModel _selectedItem; private List _previewItems; private int _durationHours; private int _durationMinutes = 15; public void Dispose() { _cts.Cancel(); _cts.Dispose(); } protected override async Task OnParametersSetAsync() => await LoadBlockItems(); private async Task LoadBlockItems() { Option maybeBlock = await Mediator.Send(new GetBlockById(Id), _cts.Token); if (maybeBlock.IsNone) { NavigationManager.NavigateTo("blocks"); return; } foreach (BlockViewModel block in maybeBlock) { _block = new BlockItemsEditViewModel { Name = block.Name, Minutes = block.Minutes, StopScheduling = block.StopScheduling, Items = [] }; _durationHours = _block.Minutes / 60; _durationMinutes = _block.Minutes % 60; } Option> maybeResults = await Mediator.Send(new GetBlockItems(Id), _cts.Token); foreach (IEnumerable items in maybeResults) { _block.Items.AddRange(items.Map(ProjectToEditViewModel)); if (_block.Items.Count == 1) { _selectedItem = _block.Items.Head(); } } } private async Task> SearchCollections(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchCollections(value), _cts.Token); } private async Task> SearchMultiCollections(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchMultiCollections(value), _cts.Token); } private async Task> SearchSmartCollections(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchSmartCollections(value), _cts.Token); } private async Task> SearchTelevisionShows(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token); } private async Task> SearchTelevisionSeasons(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token); } private async Task> SearchArtists(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return await Mediator.Send(new SearchArtists(value), _cts.Token); } private static BlockItemEditViewModel ProjectToEditViewModel(BlockItemViewModel item) => new() { Id = item.Id, Index = item.Index, CollectionType = item.CollectionType, Collection = item.Collection, MultiCollection = item.MultiCollection, SmartCollection = item.SmartCollection, MediaItem = item.MediaItem, PlaybackOrder = item.PlaybackOrder, IncludeInProgramGuide = item.IncludeInProgramGuide }; private void AddBlockItem() { var item = new BlockItemEditViewModel { Index = _block.Items.Map(i => i.Index).DefaultIfEmpty().Max() + 1, PlaybackOrder = PlaybackOrder.Chronological, CollectionType = ProgramScheduleItemCollectionType.Collection }; _block.Items.Add(item); _selectedItem = item; } private void CopyItem(BlockItemEditViewModel item) { var newItem = new BlockItemEditViewModel { Index = item.Index + 1, PlaybackOrder = item.PlaybackOrder, CollectionType = item.CollectionType, Collection = item.Collection, MultiCollection = item.MultiCollection, SmartCollection = item.SmartCollection, MediaItem = item.MediaItem, IncludeInProgramGuide = item.IncludeInProgramGuide }; foreach (BlockItemEditViewModel i in _block.Items.Filter(bi => bi.Index >= newItem.Index)) { i.Index += 1; } _block.Items.Add(newItem); _selectedItem = newItem; } private void RemoveBlockItem(BlockItemEditViewModel item) { _selectedItem = null; _block.Items.Remove(item); } private void MoveItemUp(BlockItemEditViewModel item) { // swap with lower index BlockItemEditViewModel toSwap = _block.Items.OrderByDescending(x => x.Index).First(x => x.Index < item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } private void MoveItemDown(BlockItemEditViewModel item) { // swap with higher index BlockItemEditViewModel toSwap = _block.Items.OrderBy(x => x.Index).First(x => x.Index > item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } private async Task SaveChanges() { Seq errorMessages = await Mediator .Send(GenerateReplaceRequest(), _cts.Token) .Map(e => e.LeftToSeq()); errorMessages.HeadOrNone().Match( error => { Snackbar.Add($"Unexpected error saving block: {error.Value}", Severity.Error); Logger.LogError("Unexpected error saving block: {Error}", error.Value); }, () => NavigationManager.NavigateTo("blocks")); } private ReplaceBlockItems GenerateReplaceRequest() { var items = _block.Items.Map( item => new ReplaceBlockItem( item.Index, item.CollectionType, item.Collection?.Id, item.MultiCollection?.Id, item.SmartCollection?.Id, item.MediaItem?.MediaItemId, item.PlaybackOrder, item.IncludeInProgramGuide)).ToList(); _block.Minutes = _durationHours * 60 + _durationMinutes; return new ReplaceBlockItems(Id, _block.Name, _block.Minutes, _block.StopScheduling, items); } private async Task PreviewPlayout() { _selectedItem = null; _previewItems = await Mediator.Send(new PreviewBlockPlayout(GenerateReplaceRequest()), _cts.Token); } private static void UpdateEPG(BlockItemEditViewModel context, bool includeInProgramGuide) => context.IncludeInProgramGuide = includeInProgramGuide; }