@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
Watermarks
@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 CancellationTokenSource _cts; [Parameter] public int Id { get; set; } private ProgramScheduleItemsEditViewModel _schedule = new(); private List _fillerPresets = []; private List _watermarks = []; private List _availableCultures = []; 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() { _cts?.Cancel(); _cts?.Dispose(); _cts = new CancellationTokenSource(); var token = _cts.Token; try { // TODO: fix performance _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), token) .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); _watermarks = await Mediator.Send(new GetAllWatermarks(), token) .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); _availableCultures = await Mediator.Send(new GetAllLanguageCodes(), token); _playlistGroups.AddRange(await Mediator.Send(new GetAllPlaylistGroups(), token)); string name = string.Empty; var shuffleScheduleItems = false; Option maybeSchedule = await Mediator.Send(new GetProgramScheduleById(Id), token); foreach (ProgramScheduleViewModel schedule in maybeSchedule) { name = schedule.Name; shuffleScheduleItems = schedule.ShuffleScheduleItems; } Option> maybeResults = await Mediator.Send(new GetProgramScheduleItems(Id), 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()); } } } catch (OperationCanceledException) { // do nothing } } 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, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioTitle = item.PreferredAudioTitle, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, SubtitleMode = item.SubtitleMode, Watermarks = item.Watermarks }; 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, Watermarks = [] }; _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, Watermarks = item.Watermarks.ToList(), 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.Watermarks.Map(wm => wm.Id).ToList(), 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; }