Browse Source

add option to keep multi-part episodes together when shuffling (#188)

* add setting to keep multi-part episodes together

* keep multi-part episodes together when shuffling
pull/189/head
Jason Dove 4 years ago committed by GitHub
parent
commit
47e9a319ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramSchedule.cs
  2. 6
      ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs
  3. 3
      ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramSchedule.cs
  4. 3
      ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramScheduleHandler.cs
  5. 6
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  6. 6
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleViewModel.cs
  7. 10
      ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs
  8. 2
      ErsatzTV.Core/Domain/MediaItem/Episode.cs
  9. 5
      ErsatzTV.Core/Domain/MediaItem/JellyfinEpisode.cs
  10. 1
      ErsatzTV.Core/Domain/ProgramSchedule.cs
  11. 4
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  12. 85
      ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs
  13. 86
      ErsatzTV.Core/Scheduling/ChronologicalMediaComparer.cs
  14. 7
      ErsatzTV.Core/Scheduling/GroupedMediaItem.cs
  15. 87
      ErsatzTV.Core/Scheduling/MultiPartEpisodeGrouper.cs
  16. 5
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  17. 26
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs
  18. 2474
      ErsatzTV.Infrastructure/Migrations/20210517232825_Add_ProgramSchedule_KeepMultiPartEpisodesTogether.Designer.cs
  19. 20
      ErsatzTV.Infrastructure/Migrations/20210517232825_Add_ProgramSchedule_KeepMultiPartEpisodesTogether.cs
  20. 3
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  21. 7
      ErsatzTV/Pages/ScheduleEditor.razor
  22. 9
      ErsatzTV/ViewModels/ProgramScheduleEditViewModel.cs

6
ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramSchedule.cs

@ -5,6 +5,8 @@ using MediatR; @@ -5,6 +5,8 @@ using MediatR;
namespace ErsatzTV.Application.ProgramSchedules.Commands
{
public record CreateProgramSchedule(string Name, PlaybackOrder MediaCollectionPlaybackOrder) :
IRequest<Either<BaseError, ProgramScheduleViewModel>>;
public record CreateProgramSchedule(
string Name,
PlaybackOrder MediaCollectionPlaybackOrder,
bool KeepMultiPartEpisodesTogether) : IRequest<Either<BaseError, ProgramScheduleViewModel>>;
}

6
ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs

@ -36,7 +36,11 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -36,7 +36,11 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
.MapT(
name => new ProgramSchedule
{
Name = name, MediaCollectionPlaybackOrder = request.MediaCollectionPlaybackOrder
Name = name,
MediaCollectionPlaybackOrder = request.MediaCollectionPlaybackOrder,
KeepMultiPartEpisodesTogether =
request.MediaCollectionPlaybackOrder == PlaybackOrder.Shuffle &&
request.KeepMultiPartEpisodesTogether
});
private async Task<Validation<BaseError, string>> ValidateName(CreateProgramSchedule createProgramSchedule)

3
ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramSchedule.cs

@ -9,5 +9,6 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -9,5 +9,6 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
(
int ProgramScheduleId,
string Name,
PlaybackOrder MediaCollectionPlaybackOrder) : IRequest<Either<BaseError, ProgramScheduleViewModel>>;
PlaybackOrder MediaCollectionPlaybackOrder,
bool KeepMultiPartEpisodesTogether) : IRequest<Either<BaseError, ProgramScheduleViewModel>>;
}

3
ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramScheduleHandler.cs

@ -43,6 +43,9 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -43,6 +43,9 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
programSchedule.Name = update.Name;
programSchedule.MediaCollectionPlaybackOrder = update.MediaCollectionPlaybackOrder;
programSchedule.KeepMultiPartEpisodesTogether =
update.MediaCollectionPlaybackOrder == PlaybackOrder.Shuffle &&
update.KeepMultiPartEpisodesTogether;
await _programScheduleRepository.Update(programSchedule);
if (needToRebuildPlayout)

6
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -6,7 +6,11 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -6,7 +6,11 @@ namespace ErsatzTV.Application.ProgramSchedules
internal static class Mapper
{
internal static ProgramScheduleViewModel ProjectToViewModel(ProgramSchedule programSchedule) =>
new(programSchedule.Id, programSchedule.Name, programSchedule.MediaCollectionPlaybackOrder);
new(
programSchedule.Id,
programSchedule.Name,
programSchedule.MediaCollectionPlaybackOrder,
programSchedule.KeepMultiPartEpisodesTogether);
internal static ProgramScheduleItemViewModel ProjectToViewModel(ProgramScheduleItem programScheduleItem) =>
programScheduleItem switch

6
ErsatzTV.Application/ProgramSchedules/ProgramScheduleViewModel.cs

@ -2,5 +2,9 @@ @@ -2,5 +2,9 @@
namespace ErsatzTV.Application.ProgramSchedules
{
public record ProgramScheduleViewModel(int Id, string Name, PlaybackOrder MediaCollectionPlaybackOrder);
public record ProgramScheduleViewModel(
int Id,
string Name,
PlaybackOrder MediaCollectionPlaybackOrder,
bool KeepMultiPartEpisodesTogether);
}

10
ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs

@ -23,7 +23,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -23,7 +23,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
// normally returns 10 5 7 4 3 6 2 8 9 1 1 (note duplicate 1 at end)
var state = new CollectionEnumeratorState { Seed = 8 };
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state, false);
var list = new List<int>();
for (var i = 1; i <= 1000; i++)
@ -50,7 +50,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -50,7 +50,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
var state = new CollectionEnumeratorState();
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state, false);
var list = new List<int>();
for (var i = 1; i <= 10; i++)
@ -70,7 +70,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -70,7 +70,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
var state = new CollectionEnumeratorState();
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state, false);
var list = new List<int>();
for (var i = 1; i <= 10; i++)
@ -90,7 +90,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -90,7 +90,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
List<MediaItem> contents = Episodes(10);
var state = new CollectionEnumeratorState();
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state, false);
for (var i = 0; i < 10; i++)
{
@ -105,7 +105,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -105,7 +105,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
List<MediaItem> contents = Episodes(10);
var state = new CollectionEnumeratorState { Index = 5, Seed = MagicSeed };
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state, false);
for (var i = 6; i <= 10; i++)
{

2
ErsatzTV.Core/Domain/MediaItem/Episode.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace ErsatzTV.Core.Domain
{
[DebuggerDisplay("{EpisodeMetadata[0].Title}")]
public class Episode : MediaItem
{
public int EpisodeNumber { get; set; }

5
ErsatzTV.Core/Domain/MediaItem/JellyfinEpisode.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
namespace ErsatzTV.Core.Domain
using System.Diagnostics;
namespace ErsatzTV.Core.Domain
{
[DebuggerDisplay("{EpisodeMetadata[0].Title}")]
public class JellyfinEpisode : Episode
{
public string ItemId { get; set; }

1
ErsatzTV.Core/Domain/ProgramSchedule.cs

@ -7,6 +7,7 @@ namespace ErsatzTV.Core.Domain @@ -7,6 +7,7 @@ namespace ErsatzTV.Core.Domain
public int Id { get; set; }
public string Name { get; set; }
public PlaybackOrder MediaCollectionPlaybackOrder { get; set; }
public bool KeepMultiPartEpisodesTogether { get; set; }
public List<ProgramScheduleItem> Items { get; set; }
public List<Playout> Playouts { get; set; }
}

4
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -79,8 +79,8 @@ namespace ErsatzTV.Core.FFmpeg @@ -79,8 +79,8 @@ namespace ErsatzTV.Core.FFmpeg
IDisplaySize scaledSize = CalculateScaledSize(ffmpegProfile, version);
if (!scaledSize.IsSameSizeAs(version))
{
int fixedHeight = scaledSize.Height + (scaledSize.Height % 2);
int fixedWidth = scaledSize.Width + (scaledSize.Width % 2);
int fixedHeight = scaledSize.Height + scaledSize.Height % 2;
int fixedWidth = scaledSize.Width + scaledSize.Width % 2;
result.ScaledSize = Some((IDisplaySize) new DisplaySize(fixedWidth, fixedHeight));
}
}

85
ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling;
@ -16,7 +15,7 @@ namespace ErsatzTV.Core.Scheduling @@ -16,7 +15,7 @@ namespace ErsatzTV.Core.Scheduling
IEnumerable<MediaItem> mediaItems,
CollectionEnumeratorState state)
{
_sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalComparer()).ToList();
_sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalMediaComparer()).ToList();
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
@ -30,85 +29,5 @@ namespace ErsatzTV.Core.Scheduling @@ -30,85 +29,5 @@ namespace ErsatzTV.Core.Scheduling
public Option<MediaItem> Current => _sortedMediaItems.Any() ? _sortedMediaItems[State.Index] : None;
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
private class ChronologicalComparer : IComparer<MediaItem>
{
public int Compare(MediaItem x, MediaItem y)
{
if (x == null || y == null)
{
return 0;
}
DateTime date1 = x switch
{
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match(
mvm => mvm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
_ => DateTime.MaxValue
};
DateTime date2 = y switch
{
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match(
mvm => mvm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
_ => DateTime.MaxValue
};
if (date1 != date2)
{
return date1.CompareTo(date2);
}
int season1 = x switch
{
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
int season2 = y switch
{
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
if (season1 != season2)
{
return season1.CompareTo(season2);
}
int episode1 = x switch
{
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};
int episode2 = y switch
{
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};
if (episode1 != episode2)
{
return episode1.CompareTo(episode2);
}
return x.Id.CompareTo(y.Id);
}
}
}
}

86
ErsatzTV.Core/Scheduling/ChronologicalMediaComparer.cs

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Scheduling
{
internal class ChronologicalMediaComparer : IComparer<MediaItem>
{
public int Compare(MediaItem x, MediaItem y)
{
if (x == null || y == null)
{
return 0;
}
DateTime date1 = x switch
{
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match(
mvm => mvm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
_ => DateTime.MaxValue
};
DateTime date2 = y switch
{
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match(
mvm => mvm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
_ => DateTime.MaxValue
};
if (date1 != date2)
{
return date1.CompareTo(date2);
}
int season1 = x switch
{
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
int season2 = y switch
{
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
if (season1 != season2)
{
return season1.CompareTo(season2);
}
int episode1 = x switch
{
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};
int episode2 = y switch
{
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};
if (episode1 != episode2)
{
return episode1.CompareTo(episode2);
}
return x.Id.CompareTo(y.Id);
}
}
}

7
ErsatzTV.Core/Scheduling/GroupedMediaItem.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using System.Collections.Generic;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Scheduling
{
public record GroupedMediaItem(MediaItem First, List<MediaItem> Additional);
}

87
ErsatzTV.Core/Scheduling/MultiPartEpisodeGrouper.cs

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using ErsatzTV.Core.Domain;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Scheduling
{
public static class MultiPartEpisodeGrouper
{
public static List<GroupedMediaItem> GroupMediaItems(IList<MediaItem> mediaItems)
{
var sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalMediaComparer()).ToList();
var groups = new List<GroupedMediaItem>();
GroupedMediaItem group = null;
var lastNumber = 0;
foreach (MediaItem item in sortedMediaItems)
{
if (item is Episode e)
{
const string PATTERN = @"^.*\((\d+)\)$";
Match match = Regex.Match(e.EpisodeMetadata.Head().Title, PATTERN);
if (match.Success)
{
var number = int.Parse(match.Groups[1].Value);
if (number == lastNumber + 1)
{
if (lastNumber == 0)
{
// start a new group
group = new GroupedMediaItem(item, null);
}
else if (group != null)
{
// add to current group
List<MediaItem> additional = group.Additional ?? new List<MediaItem>();
additional.Add(item);
group = group with { Additional = additional };
}
else
{
// this should never happen
throw new InvalidOperationException("Bad shuffle state");
}
lastNumber = number;
}
}
else
{
if (group != null && lastNumber != 0)
{
groups.Add(group);
group = null;
lastNumber = 0;
}
groups.Add(new GroupedMediaItem(item, null));
}
}
else
{
groups.Add(new GroupedMediaItem(item, null));
}
}
return groups;
}
public static IList<MediaItem> FlattenGroups(GroupedMediaItem[] copy, int mediaItemCount)
{
var result = new MediaItem[mediaItemCount];
var i = 0;
foreach (GroupedMediaItem group in copy)
{
result[i++] = group.First;
foreach (MediaItem additional in Optional(group.Additional).Flatten())
{
result[i++] = additional;
}
}
return result;
}
}
}

5
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -468,7 +468,10 @@ namespace ErsatzTV.Core.Scheduling @@ -468,7 +468,10 @@ namespace ErsatzTV.Core.Scheduling
case PlaybackOrder.Random:
return new RandomizedMediaCollectionEnumerator(mediaItems, state);
case PlaybackOrder.Shuffle:
return new ShuffledMediaCollectionEnumerator(mediaItems, state);
return new ShuffledMediaCollectionEnumerator(
mediaItems,
state,
playout.ProgramSchedule.KeepMultiPartEpisodesTogether);
default:
// TODO: handle this error case differently?
return new RandomizedMediaCollectionEnumerator(mediaItems, state);

26
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -10,14 +10,22 @@ namespace ErsatzTV.Core.Scheduling @@ -10,14 +10,22 @@ namespace ErsatzTV.Core.Scheduling
{
public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
{
private readonly IList<MediaItem> _mediaItems;
private readonly int _mediaItemCount;
private readonly IList<GroupedMediaItem> _mediaItems;
private Random _random;
private IList<MediaItem> _shuffled;
public ShuffledMediaCollectionEnumerator(IList<MediaItem> mediaItems, CollectionEnumeratorState state)
public ShuffledMediaCollectionEnumerator(
IList<MediaItem> mediaItems,
CollectionEnumeratorState state,
bool keepMultiPartEpisodesTogether)
{
_mediaItems = mediaItems;
_mediaItemCount = mediaItems.Count;
_mediaItems = keepMultiPartEpisodesTogether
? MultiPartEpisodeGrouper.GroupMediaItems(mediaItems)
: mediaItems.Map(mi => new GroupedMediaItem(mi, null)).ToList();
_random = new Random(state.Seed);
_shuffled = Shuffle(_mediaItems, _random);
@ -30,7 +38,7 @@ namespace ErsatzTV.Core.Scheduling @@ -30,7 +38,7 @@ namespace ErsatzTV.Core.Scheduling
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItems.Count] : None;
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItemCount] : None;
public void MoveNext()
{
@ -54,21 +62,21 @@ namespace ErsatzTV.Core.Scheduling @@ -54,21 +62,21 @@ namespace ErsatzTV.Core.Scheduling
State.Index %= _shuffled.Count;
}
private static IList<T> Shuffle<T>(IEnumerable<T> list, Random random)
private IList<MediaItem> Shuffle(IEnumerable<GroupedMediaItem> list, Random random)
{
T[] copy = list.ToArray();
GroupedMediaItem[] copy = list.ToArray();
int n = copy.Length;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
T value = copy[k];
GroupedMediaItem value = copy[k];
copy[k] = copy[n];
copy[n] = value;
}
return copy;
return MultiPartEpisodeGrouper.FlattenGroups(copy, _mediaItemCount);
}
}
}

2474
ErsatzTV.Infrastructure/Migrations/20210517232825_Add_ProgramSchedule_KeepMultiPartEpisodesTogether.Designer.cs generated

File diff suppressed because it is too large Load Diff

20
ErsatzTV.Infrastructure/Migrations/20210517232825_Add_ProgramSchedule_KeepMultiPartEpisodesTogether.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_ProgramSchedule_KeepMultiPartEpisodesTogether : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.AddColumn<bool>(
"KeepMultiPartEpisodesTogether",
"ProgramSchedule",
"INTEGER",
nullable: false,
defaultValue: false);
protected override void Down(MigrationBuilder migrationBuilder) =>
migrationBuilder.DropColumn(
"KeepMultiPartEpisodesTogether",
"ProgramSchedule");
}
}

3
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -969,6 +969,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -969,6 +969,9 @@ namespace ErsatzTV.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("KeepMultiPartEpisodesTogether")
.HasColumnType("INTEGER");
b.Property<int>("MediaCollectionPlaybackOrder")
.HasColumnType("INTEGER");

7
ErsatzTV/Pages/ScheduleEditor.razor

@ -22,6 +22,12 @@ @@ -22,6 +22,12 @@
<MudSelectItem Value="@playbackOrder">@playbackOrder</MudSelectItem>
}
</MudSelect>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Label="Keep Multi-Part Episodes Together"
@bind-Checked="@_model.KeepMultiPartEpisodesTogether"
Disabled="@(_model.MediaCollectionPlaybackOrder != PlaybackOrder.Shuffle)"
For="@(() => _model.KeepMultiPartEpisodesTogether)"/>
</MudElement>
</MudCardContent>
<MudCardActions>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
@ -53,6 +59,7 @@ @@ -53,6 +59,7 @@
_model.Id = viewModel.Id;
_model.Name = viewModel.Name;
_model.MediaCollectionPlaybackOrder = viewModel.MediaCollectionPlaybackOrder;
_model.KeepMultiPartEpisodesTogether = viewModel.KeepMultiPartEpisodesTogether;
},
() => NavigationManager.NavigateTo("404"));
}

9
ErsatzTV/ViewModels/ProgramScheduleEditViewModel.cs

@ -8,7 +8,12 @@ namespace ErsatzTV.ViewModels @@ -8,7 +8,12 @@ namespace ErsatzTV.ViewModels
public int Id { get; set; }
public string Name { get; set; }
public PlaybackOrder MediaCollectionPlaybackOrder { get; set; }
public UpdateProgramSchedule ToUpdate() => new(Id, Name, MediaCollectionPlaybackOrder);
public CreateProgramSchedule ToCreate() => new(Name, MediaCollectionPlaybackOrder);
public bool KeepMultiPartEpisodesTogether { get; set; }
public UpdateProgramSchedule ToUpdate() =>
new(Id, Name, MediaCollectionPlaybackOrder, KeepMultiPartEpisodesTogether);
public CreateProgramSchedule ToCreate() =>
new(Name, MediaCollectionPlaybackOrder, KeepMultiPartEpisodesTogether);
}
}

Loading…
Cancel
Save