Browse Source

add multiple mode to schedule items (#2163)

pull/2164/head
Jason Dove 4 weeks ago committed by GitHub
parent
commit
54be3761dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  3. 1
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  4. 14
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  5. 1
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  6. 1
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  7. 8
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  8. 4
      ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
  9. 1
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
  10. 17
      ErsatzTV.Core/Domain/MultipleMode.cs
  11. 2
      ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs
  12. 33
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  13. 5920
      ErsatzTV.Infrastructure.MySql/Migrations/20250718133418_Add_ProgramScheduleItemMultipleMode.Designer.cs
  14. 31
      ErsatzTV.Infrastructure.MySql/Migrations/20250718133418_Add_ProgramScheduleItemMultipleMode.cs
  15. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  16. 5759
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250718133309_Add_ProgramScheduleItemMultipleMode.Designer.cs
  17. 31
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250718133309_Add_ProgramScheduleItemMultipleMode.cs
  18. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  19. 2
      ErsatzTV/Pages/Artist.razor
  20. 58
      ErsatzTV/Pages/ScheduleItemsEditor.razor
  21. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  22. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  23. 5
      ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs
  24. 2
      ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

7
CHANGELOG.md

@ -106,10 +106,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Split main `Settings` page into multiple pages - Split main `Settings` page into multiple pages
- Update UI layout on all pages to be less cramped and to work better on mobile - Update UI layout on all pages to be less cramped and to work better on mobile
- Add CPU and Video Controller info to `Troubleshooting` > `General` output - Add CPU and Video Controller info to `Troubleshooting` > `General` output
- Expand special zero-count case for `Multiple` playout mode with playlists
- This configuration will automatically maintain the multiple count so that it is equal to the number of items in each playlist item
- This configuration should be used if you want to play every media item in a playlist item exactly once before advancing
- Enable write-ahead logging (WAL) mode on SQLite databases - Enable write-ahead logging (WAL) mode on SQLite databases
- Add `Multiple Mode` option to schedule items editor and remove support for count values of zero
- `Count`: same behavior as before, requires a number of media items to play and will always schedule the same number
- `Collection Size`: similar to count of zero before, will play all media items from the collection before continuing to the next schedule item
- `Playlist Item Size`: will play all media items from the current playlist item before continuing to the next schedule item
### Fixed ### Fixed
- Fix QSV acceleration in docker with older Intel devices - Fix QSV acceleration in docker with older Intel devices

1
ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs

@ -18,6 +18,7 @@ public record AddProgramScheduleItem(
int? PlaylistId, int? PlaylistId,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
FillWithGroupMode FillWithGroupMode, FillWithGroupMode FillWithGroupMode,
MultipleMode MultipleMode,
int? MultipleCount, int? MultipleCount,
TimeSpan? PlayoutDuration, TimeSpan? PlayoutDuration,
TailMode TailMode, TailMode TailMode,

1
ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs

@ -16,6 +16,7 @@ public interface IProgramScheduleItemRequest
PlayoutMode PlayoutMode { get; } PlayoutMode PlayoutMode { get; }
PlaybackOrder PlaybackOrder { get; } PlaybackOrder PlaybackOrder { get; }
FillWithGroupMode FillWithGroupMode { get; } FillWithGroupMode FillWithGroupMode { get; }
MultipleMode MultipleMode { get; }
int? MultipleCount { get; } int? MultipleCount { get; }
TimeSpan? PlayoutDuration { get; } TimeSpan? PlayoutDuration { get; }
TailMode TailMode { get; } TailMode TailMode { get; }

14
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -71,10 +71,17 @@ public abstract class ProgramScheduleItemCommandBase
case PlayoutMode.One: case PlayoutMode.One:
break; break;
case PlayoutMode.Multiple: case PlayoutMode.Multiple:
if (item.MultipleCount.GetValueOrDefault() < 0) if (item.MultipleMode is MultipleMode.PlaylistItemSize &&
item.CollectionType is not ProgramScheduleItemCollectionType.Playlist)
{ {
return BaseError.New( return BaseError.New(
"[MultipleCount] must be greater than or equal to 0 for playout mode 'multiple'"); "[MultipleMode] cannot be [PlaylistItemSize] when collection is not a playlist");
}
if (item.MultipleMode is MultipleMode.Count && item.MultipleCount.GetValueOrDefault() < 1)
{
return BaseError.New(
"[MultipleCount] must be greater than 0 for playout mode 'multiple / count'");
} }
break; break;
@ -248,7 +255,8 @@ public abstract class ProgramScheduleItemCommandBase
PlaylistId = item.PlaylistId, PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder, PlaybackOrder = item.PlaybackOrder,
FillWithGroupMode = item.FillWithGroupMode, FillWithGroupMode = item.FillWithGroupMode,
Count = item.MultipleCount.GetValueOrDefault(), MultipleMode = item.MultipleMode,
Count = item.MultipleMode is MultipleMode.Count ? item.MultipleCount.GetValueOrDefault() : 0,
CustomTitle = item.CustomTitle, CustomTitle = item.CustomTitle,
GuideMode = item.GuideMode, GuideMode = item.GuideMode,
PreRollFillerId = item.PreRollFillerId, PreRollFillerId = item.PreRollFillerId,

1
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs

@ -18,6 +18,7 @@ public record ReplaceProgramScheduleItem(
int? PlaylistId, int? PlaylistId,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
FillWithGroupMode FillWithGroupMode, FillWithGroupMode FillWithGroupMode,
MultipleMode MultipleMode,
int? MultipleCount, int? MultipleCount,
TimeSpan? PlayoutDuration, TimeSpan? PlayoutDuration,
TailMode TailMode, TailMode TailMode,

1
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -155,6 +155,7 @@ internal static class Mapper
}, },
multiple.PlaybackOrder, multiple.PlaybackOrder,
multiple.FillWithGroupMode, multiple.FillWithGroupMode,
multiple.MultipleMode,
multiple.Count, multiple.Count,
multiple.CustomTitle, multiple.CustomTitle,
multiple.GuideMode, multiple.GuideMode,

8
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -23,6 +23,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
NamedMediaItemViewModel mediaItem, NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder, PlaybackOrder playbackOrder,
FillWithGroupMode fillWithGroupMode, FillWithGroupMode fillWithGroupMode,
MultipleMode multipleMode,
int count, int count,
string customTitle, string customTitle,
GuideMode guideMode, GuideMode guideMode,
@ -61,8 +62,13 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredAudioTitle, preferredAudioTitle,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
subtitleMode) => subtitleMode)
{
MultipleMode = multipleMode;
Count = count; Count = count;
}
public MultipleMode MultipleMode { get; set; }
public int Count { get; } public int Count { get; }
} }

4
ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs

@ -1503,7 +1503,7 @@ public class PlayoutBuilderTests
} }
[Test] [Test]
public async Task Auto_Zero_MultipleCount() public async Task Multiple_Mode_Collection_Size()
{ {
var collectionOne = new Collection var collectionOne = new Collection
{ {
@ -1543,6 +1543,7 @@ public class PlayoutBuilderTests
CollectionId = collectionOne.Id, CollectionId = collectionOne.Id,
StartTime = null, StartTime = null,
Count = 0, Count = 0,
MultipleMode = MultipleMode.CollectionSize,
PlaybackOrder = PlaybackOrder.Chronological PlaybackOrder = PlaybackOrder.Chronological
}, },
new ProgramScheduleItemMultiple new ProgramScheduleItemMultiple
@ -1553,6 +1554,7 @@ public class PlayoutBuilderTests
CollectionId = collectionTwo.Id, CollectionId = collectionTwo.Id,
StartTime = null, StartTime = null,
Count = 0, Count = 0,
MultipleMode = MultipleMode.CollectionSize,
PlaybackOrder = PlaybackOrder.Chronological PlaybackOrder = PlaybackOrder.Chronological
} }
}; };

1
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs

@ -33,6 +33,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
TailFiller = null, TailFiller = null,
FallbackFiller = null, FallbackFiller = null,
Count = 0, Count = 0,
MultipleMode = MultipleMode.CollectionSize,
CustomTitle = "CustomTitle" CustomTitle = "CustomTitle"
}; };

17
ErsatzTV.Core/Domain/MultipleMode.cs

@ -0,0 +1,17 @@
namespace ErsatzTV.Core.Domain;
public enum MultipleMode
{
// static integer count
Count = 0,
// current size of the collection
CollectionSize = 1,
// current size of the playlist item
PlaylistItemSize = 2,
// from one item (not a multi-episode) to however many multi-episodes are linked together
// is this limited to chronological and season/episode?
MultiEpisodeSize = 3
}

2
ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs

@ -2,5 +2,7 @@
public class ProgramScheduleItemMultiple : ProgramScheduleItem public class ProgramScheduleItemMultiple : ProgramScheduleItem
{ {
public MultipleMode MultipleMode { get; set; }
public int Count { get; set; } public int Count { get; set; }
} }

33
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -42,22 +42,25 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
if (nextState.MultipleRemaining == 0) if (nextState.MultipleRemaining == 0)
{ {
// playlist count of zero means play all media items in the current playlist item switch (scheduleItem.MultipleMode)
if (contentEnumerator is PlaylistEnumerator { CurrentEnumeratorPlayAll: true } playlistEnumerator)
{ {
nextState = nextState with case MultipleMode.CollectionSize:
{ nextState = nextState with
MultipleRemaining = playlistEnumerator {
.ChildEnumerators[playlistEnumerator.EnumeratorIndex] MultipleRemaining = _collectionItemCount[CollectionKey.ForScheduleItem(scheduleItem)]
.Enumerator.Count };
}; break;
} case MultipleMode.PlaylistItemSize:
else if (contentEnumerator is PlaylistEnumerator { CurrentEnumeratorPlayAll: true } playlistEnumerator)
{ {
nextState = nextState with nextState = nextState with
{ {
MultipleRemaining = _collectionItemCount[CollectionKey.ForScheduleItem(scheduleItem)] MultipleRemaining = playlistEnumerator
}; .ChildEnumerators[playlistEnumerator.EnumeratorIndex]
.Enumerator.Count
};
}
break;
} }
} }

5920
ErsatzTV.Infrastructure.MySql/Migrations/20250718133418_Add_ProgramScheduleItemMultipleMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure.MySql/Migrations/20250718133418_Add_ProgramScheduleItemMultipleMode.cs

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ProgramScheduleItemMultipleMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "MultipleMode",
table: "ProgramScheduleMultipleItem",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(@"UPDATE `ProgramScheduleMultipleItem` SET `MultipleMode` = 1 WHERE `Count` = 0");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MultipleMode",
table: "ProgramScheduleMultipleItem");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -3488,6 +3488,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("Count") b.Property<int>("Count")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("MultipleMode")
.HasColumnType("int");
b.ToTable("ProgramScheduleMultipleItem", (string)null); b.ToTable("ProgramScheduleMultipleItem", (string)null);
}); });

5759
ErsatzTV.Infrastructure.Sqlite/Migrations/20250718133309_Add_ProgramScheduleItemMultipleMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure.Sqlite/Migrations/20250718133309_Add_ProgramScheduleItemMultipleMode.cs

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_ProgramScheduleItemMultipleMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "MultipleMode",
table: "ProgramScheduleMultipleItem",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(@"UPDATE `ProgramScheduleMultipleItem` SET `MultipleMode` = 1 WHERE `Count` = 0");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MultipleMode",
table: "ProgramScheduleMultipleItem");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -3327,6 +3327,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("Count") b.Property<int>("Count")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("MultipleMode")
.HasColumnType("INTEGER");
b.ToTable("ProgramScheduleMultipleItem", (string)null); b.ToTable("ProgramScheduleMultipleItem", (string)null);
}); });

2
ErsatzTV/Pages/Artist.razor

@ -286,7 +286,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule }) if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{ {
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token); await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
} }
} }

58
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -21,9 +21,21 @@
{ {
string start = _selectedItem.StartType == StartType.Fixed ? _selectedItem.StartTime == null ? string.Empty : DateTime.Today.Add(_selectedItem.StartTime.Value).ToShortTimeString() : "Dynamic"; string start = _selectedItem.StartType == StartType.Fixed ? _selectedItem.StartTime == null ? string.Empty : DateTime.Today.Add(_selectedItem.StartTime.Value).ToShortTimeString() : "Dynamic";
var mode = _selectedItem.PlayoutMode.ToString(); var mode = _selectedItem.PlayoutMode.ToString();
@if (_selectedItem.PlayoutMode == PlayoutMode.Multiple && _selectedItem.MultipleCount.HasValue) @if (_selectedItem.PlayoutMode is PlayoutMode.Multiple)
{ {
mode += $" ({_selectedItem.MultipleCount})"; switch (_selectedItem.MultipleMode)
{
case MultipleMode.CollectionSize:
mode = "Collection Size";
break;
case MultipleMode.PlaylistItemSize:
mode = "Playlist Item Size";
break;
case MultipleMode.Count:
default:
mode += $" ({_selectedItem.MultipleCount})";
break;
}
} }
var result = $"{_schedule.Name} ({start} - {_selectedItem.CollectionName} - {mode})"; var result = $"{_schedule.Name} ({start} - {_selectedItem.CollectionName} - {mode})";
@ -88,10 +100,25 @@
</MudTd> </MudTd>
<MudTd DataLabel="Playout Mode"> <MudTd DataLabel="Playout Mode">
<MudText Typo="@Typo.body2"> <MudText Typo="@Typo.body2">
@context.PlayoutMode @if (context.PlayoutMode is PlayoutMode.Multiple)
@if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue) {
switch (context.MultipleMode)
{
case MultipleMode.CollectionSize:
@:Collection Size
break;
case MultipleMode.PlaylistItemSize:
@:Playlist Item Size
break;
case MultipleMode.Count:
default:
@($"Multiple ({context.MultipleCount})")
break;
}
}
else
{ {
@($" ({context.MultipleCount})") @context.PlayoutMode
} }
</MudText> </MudText>
</MudTd> </MudTd>
@ -346,13 +373,29 @@
<MudSelectItem Value="PlayoutMode.Duration">Duration</MudSelectItem> <MudSelectItem Value="PlayoutMode.Duration">Duration</MudSelectItem>
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Multiple Mode</MudText>
</div>
<MudSelect @bind-Value="@_selectedItem.MultipleMode" For="@(() => _selectedItem.MultipleMode)">
<MudSelectItem Value="MultipleMode.Count">Count</MudSelectItem>
@if (_selectedItem.CollectionType is not ProgramScheduleItemCollectionType.Playlist)
{
<MudSelectItem Value="MultipleMode.CollectionSize">Collection Size</MudSelectItem>
}
else
{
<MudSelectItem Value="MultipleMode.PlaylistItemSize">Playlist Item Size</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Multiple Count</MudText> <MudText>Multiple Count</MudText>
</div> </div>
<MudTextField @bind-Value="@_selectedItem.MultipleCount" <MudTextField @bind-Value="@_selectedItem.MultipleCount"
For="@(() => _selectedItem.MultipleCount)" For="@(() => _selectedItem.MultipleCount)"
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Multiple)"/> Disabled="@(_selectedItem.PlayoutMode is not PlayoutMode.Multiple || _selectedItem.MultipleMode is not MultipleMode.Count)"/>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
@ -723,6 +766,7 @@
switch (item) switch (item)
{ {
case ProgramScheduleItemMultipleViewModel multiple: case ProgramScheduleItemMultipleViewModel multiple:
result.MultipleMode = multiple.MultipleMode;
result.MultipleCount = multiple.Count; result.MultipleCount = multiple.Count;
break; break;
case ProgramScheduleItemDurationViewModel duration: case ProgramScheduleItemDurationViewModel duration:
@ -786,6 +830,7 @@
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode, SubtitleMode = item.SubtitleMode,
MultipleMode = item.MultipleMode,
MultipleCount = item.MultipleCount, MultipleCount = item.MultipleCount,
PlayoutDuration = item.PlayoutDuration, PlayoutDuration = item.PlayoutDuration,
TailMode = item.TailMode, TailMode = item.TailMode,
@ -841,6 +886,7 @@
item.Playlist?.Id, item.Playlist?.Id,
item.PlaybackOrder, item.PlaybackOrder,
item.FillWithGroupMode, item.FillWithGroupMode,
item.MultipleMode,
item.MultipleCount, item.MultipleCount,
item.PlayoutDuration, item.PlayoutDuration,
item.TailMode, item.TailMode,

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -259,7 +259,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule }) if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{ {
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token); await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
} }
} }

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -251,7 +251,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule }) if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{ {
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token); await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
} }
} }

5
ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs

@ -13,7 +13,10 @@ public class ProgramScheduleItemEditViewModelValidator : AbstractValidator<Progr
() => RuleFor(i => i.StartTime).NotNull()); () => RuleFor(i => i.StartTime).NotNull());
When( When(
i => i.PlayoutMode == PlayoutMode.Multiple, i => i.PlayoutMode == PlayoutMode.Multiple,
() => RuleFor(i => i.MultipleCount).NotNull().GreaterThanOrEqualTo(0)); () =>
{
When(i => i.MultipleMode is MultipleMode.Count, () => RuleFor(i => i.MultipleCount).NotNull().GreaterThan(0));
});
When( When(
i => i.PlayoutMode == PlayoutMode.Duration, i => i.PlayoutMode == PlayoutMode.Duration,
() => () =>

2
ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

@ -104,6 +104,8 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged
public PlaybackOrder PlaybackOrder { get; set; } public PlaybackOrder PlaybackOrder { get; set; }
public MultipleMode MultipleMode { get; set; }
public int? MultipleCount public int? MultipleCount
{ {
get => PlayoutMode == PlayoutMode.Multiple ? _multipleCount : null; get => PlayoutMode == PlayoutMode.Multiple ? _multipleCount : null;

Loading…
Cancel
Save