@page "/schedules/{Id:int}/items" @using ErsatzTV.Application.Filler @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.ProgramSchedules @using ErsatzTV.Application.Search @using ErsatzTV.Application.Watermarks @using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Core.Scheduling @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @inject ISnackbar Snackbar @inject IMediator Mediator
@if (_selectedItem is not null) { string start = _selectedItem.StartType == StartType.Fixed ? _selectedItem.StartTime == null ? string.Empty : DateTime.Today.Add(_selectedItem.StartTime.Value).ToShortTimeString() : "Dynamic"; var mode = _selectedItem.PlayoutMode.ToString(); @if (_selectedItem.PlayoutMode is PlayoutMode.Multiple) { switch (_selectedItem.MultipleMode) { case MultipleMode.CollectionSize: mode = "Collection Size"; break; case MultipleMode.PlaylistItemSize: mode = "Playlist Item Size"; break; case MultipleMode.MultiEpisodeGroupSize: mode = "Multi-Episode Group Size"; break; case MultipleMode.Count: default: mode += $" ({_selectedItem.MultipleCount})"; break; } } var result = $"{_schedule.Name} ({start} - {_selectedItem.CollectionName} - {mode})"; @result } else { @_schedule.Name }
Save Schedule Items Add Schedule Item
Schedule Items Start Time Collection Playout Mode @(context.StartType == StartType.Fixed ? context.StartTime == null ? string.Empty : DateTime.Today.Add(context.StartTime.Value).ToShortTimeString() : "Dynamic") @context.CollectionName @if (context.PlayoutMode is PlayoutMode.Multiple) { switch (context.MultipleMode) { case MultipleMode.CollectionSize: @:Collection Size break; case MultipleMode.PlaylistItemSize: @:Playlist Item Size break; case MultipleMode.MultiEpisodeGroupSize: @:Multi-Episode Group Size break; case MultipleMode.Count: default: @($"Multiple ({context.MultipleCount})") break; } } else { @context.PlayoutMode }
@if (_selectedItem is not null) { Schedule Item
Start Type
Dynamic @if (!_schedule.ShuffleScheduleItems) { Fixed }
Start Time
Fixed Start Time Behavior
Inherit Strict - Always Wait For Exact Start Time Flexible - Start As Soon As Possible After Start Time
Collection Type
Collection Television Show Television Season Artist Multi Collection Smart Collection Playlist
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection) {
Collection
Only the first 10 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection) {
Multi Collection
Only the first 10 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection) {
Smart Collection
Only the first 10 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow) {
Television Show
Only the first 10 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) {
Television Season
Only the first 20 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist) {
Artist
Only the first 10 items are shown
} @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Playlist) {
Playlist Group
@foreach (PlaylistGroupViewModel playlistGroup in _playlistGroups) { @playlistGroup.Name }
Playlist
@foreach (PlaylistViewModel playlist in _playlists) { @playlist.Name }
}
Playback Order
@switch (_selectedItem.CollectionType) { case ProgramScheduleItemCollectionType.MultiCollection: Shuffle Shuffle In Order break; case ProgramScheduleItemCollectionType.Collection: case ProgramScheduleItemCollectionType.SmartCollection: Chronological Random Shuffle Shuffle In Order break; case ProgramScheduleItemCollectionType.TelevisionShow: Chronological Season, Episode Random Shuffle Multi-Episode Shuffle break; case ProgramScheduleItemCollectionType.Playlist: break; default: Chronological Random Shuffle break; }
Playout Mode
@if (!_schedule.ShuffleScheduleItems) { Flood } One Multiple Duration
Multiple Mode
Count @if (_selectedItem.CollectionType is not ProgramScheduleItemCollectionType.Playlist) { Collection Size } else { Playlist Item Size } @if (_selectedItem.PlaybackOrder is PlaybackOrder.Chronological) { Multi-Episode Group Size }
Multiple Count
Playout Duration
Fill With Group Mode (Show or Artist)
(none) @if (_selectedItem.CanFillWithGroups) { Ordered Groups Shuffled Groups } else { _selectedItem.FillWithGroupMode = FillWithGroupMode.None; }
Tail Mode
(none) Offline Filler
Discard To Fill Attempts
Custom Title
Guide Mode
Normal Filler
Schedule Item Filler
Pre-Roll Filler
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PreRoll)) { @filler.Name }
Mid-Roll Filler
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.MidRoll)) { @filler.Name }
Post-Roll Filler
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PostRoll)) { @filler.Name }
Tail Filler
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Tail)) { @filler.Name }
Fallback Filler
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Fallback)) { @filler.Name }
Schedule Item Overrides
Watermark
@foreach (WatermarkViewModel watermark in _watermarks) { @watermark.Name }
Preferred Audio Language
(none) @foreach (LanguageCodeViewModel culture in _availableCultures) { @culture.EnglishName }
Preferred Audio Title
Preferred Subtitle Language
(none) @foreach (LanguageCodeViewModel culture in _availableCultures) { @culture.EnglishName }
Subtitle Mode
None Forced Default Any
}
@code { private readonly CancellationTokenSource _cts = new(); [Parameter] public int Id { get; set; } private ProgramScheduleItemsEditViewModel _schedule = new(); private List _fillerPresets = new(); private List _watermarks = new(); private List _availableCultures = new(); private readonly List _playlistGroups = []; private readonly List _playlists = []; private PlaylistGroupViewModel _selectedPlaylistGroup; private ProgramScheduleItemEditViewModel _selectedItem; private MudForm _form; private bool _success; public void Dispose() { _cts.Cancel(); _cts.Dispose(); } protected override async Task OnParametersSetAsync() => await LoadScheduleItems(); private async Task LoadScheduleItems() { // TODO: fix performance _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token) .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); _watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token) .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); _availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token); _playlistGroups.AddRange(await Mediator.Send(new GetAllPlaylistGroups(), _cts.Token)); string name = string.Empty; var shuffleScheduleItems = false; Option maybeSchedule = await Mediator.Send(new GetProgramScheduleById(Id), _cts.Token); foreach (ProgramScheduleViewModel schedule in maybeSchedule) { name = schedule.Name; shuffleScheduleItems = schedule.ShuffleScheduleItems; } Option> maybeResults = await Mediator.Send(new GetProgramScheduleItems(Id), _cts.Token); foreach (IEnumerable items in maybeResults) { _schedule = new ProgramScheduleItemsEditViewModel { Name = name, ShuffleScheduleItems = shuffleScheduleItems, Items = items.Map(ProjectToEditViewModel).ToList() }; if (_schedule.Items.Count == 1) { await SelectedItemChanged(_schedule.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 async Task UpdatePlaylistGroupItems(PlaylistGroupViewModel playlistGroup) { _selectedPlaylistGroup = playlistGroup; _playlists.Clear(); _playlists.AddRange(await Mediator.Send(new GetPlaylistsByPlaylistGroupId(playlistGroup.Id), _cts.Token)); } private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item) { var result = new ProgramScheduleItemEditViewModel { Id = item.Id, Index = item.Index, StartType = item.StartType, StartTime = item.StartTime, FixedStartTimeBehavior = item.FixedStartTimeBehavior, PlayoutMode = item.PlayoutMode, CollectionType = item.CollectionType, Collection = item.Collection, MultiCollection = item.MultiCollection, SmartCollection = item.SmartCollection, Playlist = item.Playlist, MediaItem = item.MediaItem, PlaybackOrder = item.PlaybackOrder, FillWithGroupMode = item.FillWithGroupMode, CustomTitle = item.CustomTitle, GuideMode = item.GuideMode, PreRollFiller = item.PreRollFiller, MidRollFiller = item.MidRollFiller, PostRollFiller = item.PostRollFiller, TailFiller = item.TailFiller, FallbackFiller = item.FallbackFiller, Watermark = item.Watermark, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioTitle = item.PreferredAudioTitle, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, SubtitleMode = item.SubtitleMode }; switch (item) { case ProgramScheduleItemMultipleViewModel multiple: result.MultipleMode = multiple.MultipleMode; result.MultipleCount = multiple.Count; break; case ProgramScheduleItemDurationViewModel duration: result.PlayoutDuration = duration.PlayoutDuration; result.TailMode = duration.TailMode; result.DiscardToFillAttempts = duration.DiscardToFillAttempts; 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, PlaybackOrder = PlaybackOrder.Shuffle, CollectionType = ProgramScheduleItemCollectionType.Collection }; _schedule.Items.Add(item); _selectedItem = item; } private void RemoveScheduleItem(ProgramScheduleItemEditViewModel item) { _selectedItem = null; _schedule.Items.Remove(item); } private void CopyItem(ProgramScheduleItemEditViewModel item) { var newItem = new ProgramScheduleItemEditViewModel { Index = item.Index + 1, StartType = item.StartType, StartTime = item.StartTime, FixedStartTimeBehavior = item.FixedStartTimeBehavior, PlayoutMode = item.PlayoutMode, CollectionType = item.CollectionType, Collection = item.Collection, MultiCollection = item.MultiCollection, SmartCollection = item.SmartCollection, Playlist = item.Playlist, MediaItem = item.MediaItem, PlaybackOrder = item.PlaybackOrder, FillWithGroupMode = item.FillWithGroupMode, CustomTitle = item.CustomTitle, GuideMode = item.GuideMode, PreRollFiller = item.PreRollFiller, MidRollFiller = item.MidRollFiller, PostRollFiller = item.PostRollFiller, TailFiller = item.TailFiller, FallbackFiller = item.FallbackFiller, Watermark = item.Watermark, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioTitle = item.PreferredAudioTitle, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, SubtitleMode = item.SubtitleMode, MultipleMode = item.MultipleMode, MultipleCount = item.MultipleCount, PlayoutDuration = item.PlayoutDuration, TailMode = item.TailMode, DiscardToFillAttempts = item.DiscardToFillAttempts }; foreach (ProgramScheduleItemEditViewModel i in _schedule.Items.Filter(si => si.Index >= newItem.Index)) { i.Index += 1; } _schedule.Items.Add(newItem); if (_selectedItem is not null) { _selectedItem = newItem; } } private void MoveItemUp(ProgramScheduleItemEditViewModel item) { // swap with lower index ProgramScheduleItemEditViewModel toSwap = _schedule.Items.OrderByDescending(x => x.Index).First(x => x.Index < item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } private void MoveItemDown(ProgramScheduleItemEditViewModel item) { // swap with higher index ProgramScheduleItemEditViewModel toSwap = _schedule.Items.OrderBy(x => x.Index).First(x => x.Index > item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } private async Task SaveChanges() { await _form.Validate(); if (!_success) { return; } var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem( item.Index, item.StartType, item.StartTime, item.FixedStartTimeBehavior, item.PlayoutMode, item.CollectionType, item.Collection?.Id, item.MultiCollection?.Id, item.SmartCollection?.Id, item.MediaItem?.MediaItemId, item.Playlist?.Id, item.PlaybackOrder, item.FillWithGroupMode, item.MultipleMode, item.MultipleCount, item.PlayoutDuration, item.TailMode, item.DiscardToFillAttempts, item.CustomTitle, item.GuideMode, item.PreRollFiller?.Id, item.MidRollFiller?.Id, item.PostRollFiller?.Id, item.TailFiller?.Id, item.FallbackFiller?.Id, item.Watermark?.Id, item.PreferredAudioLanguageCode, item.PreferredAudioTitle, item.PreferredSubtitleLanguageCode, item.SubtitleMode)).ToList(); Seq errorMessages = await Mediator.Send(new ReplaceProgramScheduleItems(Id, items), _cts.Token).Map(e => e.LeftToSeq()); errorMessages.HeadOrNone().Match( error => { Snackbar.Add($"Unexpected error saving schedule: {error.Value}", Severity.Error); Logger.LogError("Unexpected error saving schedule: {Error}", error.Value); }, () => NavigationManager.NavigateTo("schedules")); } private async Task SelectedItemChanged(ProgramScheduleItemEditViewModel vm) { _selectedItem = vm; foreach (int playlistGroupId in Optional(_selectedItem.Playlist?.PlaylistGroupId)) { foreach (PlaylistGroupViewModel group in Optional(_playlistGroups.Find(g => g.Id == playlistGroupId))) { _selectedPlaylistGroup = group; await UpdatePlaylistGroupItems(group); } } } private string SelectedRowClassFunc(ProgramScheduleItemEditViewModel element, int rowNumber) => _selectedItem != null && _selectedItem == element ? "selected" : string.Empty; }