From 1a09bb26d781ea6c92568345ebefb8c3ac9ba25e Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:21:39 +0000 Subject: [PATCH] lots of mobile updates including detail pages (#2135) * update artist page layout * update season page layout * rework collection view * cleanup * update collection editor --- ErsatzTV.Application/MediaCards/Mapper.cs | 2 +- .../Queries/GetCollectionCardsHandler.cs | 2 + ErsatzTV/Pages/Artist.razor | 214 ++++--- ErsatzTV/Pages/CollectionEditor.razor | 48 +- ErsatzTV/Pages/CollectionItems.razor | 562 +++++++++--------- ErsatzTV/Pages/Collections.razor | 268 +++++---- ErsatzTV/Pages/Movie.razor | 2 +- ErsatzTV/Pages/Search.razor | 61 +- ErsatzTV/Pages/TelevisionEpisodeList.razor | 277 +++++---- ErsatzTV/Pages/TelevisionSeasonList.razor | 2 +- .../Pages/TelevisionSeasonSearchResults.razor | 2 +- ErsatzTV/Pages/TraktLists.razor | 2 +- ErsatzTV/Shared/MediaCard.razor | 3 +- .../CollectionEditViewModelValidator.cs | 9 - ErsatzTV/wwwroot/css/site.css | 4 - 15 files changed, 750 insertions(+), 708 deletions(-) delete mode 100644 ErsatzTV/Validators/CollectionEditViewModelValidator.cs diff --git a/ErsatzTV.Application/MediaCards/Mapper.cs b/ErsatzTV.Application/MediaCards/Mapper.cs index e30e4d54f..edc98c522 100644 --- a/ErsatzTV.Application/MediaCards/Mapper.cs +++ b/ErsatzTV.Application/MediaCards/Mapper.cs @@ -187,7 +187,7 @@ internal static class Mapper e.EpisodeMetadata.Head(), maybeJellyfin, maybeEmby, - false, + true, string.Empty)) .ToList(), collection.MediaItems.OfType().Map(a => ProjectToViewModel(a.ArtistMetadata.Head())).ToList(), diff --git a/ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs b/ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs index 7c579dee2..3f09be715 100644 --- a/ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs +++ b/ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs @@ -79,9 +79,11 @@ public class GetCollectionCardsHandler : .ThenInclude(i => (i as Episode).Season) .ThenInclude(s => s.Show) .ThenInclude(s => s.ShowMetadata) + .ThenInclude(sm => sm.Artwork) .Include(c => c.MediaItems) .ThenInclude(i => (i as Episode).Season) .ThenInclude(s => s.SeasonMetadata) + .ThenInclude(sm => sm.Artwork) .Include(c => c.MediaItems) .ThenInclude(i => (i as Episode).MediaVersions) .ThenInclude(mv => mv.MediaFiles) diff --git a/ErsatzTV/Pages/Artist.razor b/ErsatzTV/Pages/Artist.razor index ec7b1f7f1..008827d5b 100644 --- a/ErsatzTV/Pages/Artist.razor +++ b/ErsatzTV/Pages/Artist.razor @@ -19,17 +19,22 @@ fan art } - -
+ + @if (!string.IsNullOrWhiteSpace(_artist.Thumbnail)) { - artist thumbnail + }
- @_artist.Name - @_artist.Disambiguation + + + @_artist?.Name + + + @_artist?.Name + + @_artist.Disambiguation + @if (!string.IsNullOrWhiteSpace(_artist.Biography)) { @@ -47,23 +52,22 @@ } -
+ Add To Collection - Add To Schedule -
+
-
+ @if (_sortedLanguages.Any()) @@ -118,86 +122,110 @@
- @foreach (MusicVideoCardViewModel musicVideo in _musicVideos.Cards) - { - -
- @if (!string.IsNullOrWhiteSpace(musicVideo.Poster)) - { - - - @if (musicVideo.State == MediaItemState.FileNotFound) - { -
- -
- } - else if (musicVideo.State == MediaItemState.Unavailable) - { -
- -
- } -
- } - else - { -
- - @if (musicVideo.State == MediaItemState.FileNotFound) - { -
- -
- } - else if (musicVideo.State == MediaItemState.Unavailable) - { -
- -
- } -
- } - -
- @musicVideo.Title - @if (!string.IsNullOrWhiteSpace(musicVideo.Album)) - { -
- Album:  - @musicVideo.Album -
- } - @musicVideo.Plot -
- - Add To Collection - + + @foreach (MusicVideoCardViewModel musicVideo in _musicVideos.Cards) + { + + + @if (!string.IsNullOrWhiteSpace(musicVideo.Poster)) + { +
+ + @if (musicVideo.State == MediaItemState.FileNotFound) + { +
+ +
+ } + else if (musicVideo.State == MediaItemState.Unavailable) + { +
+ +
+ }
+ } + else + { + + @if (musicVideo.State is not MediaItemState.Normal and not MediaItemState.RemoteOnly) + { +
+ + @if (musicVideo.State == MediaItemState.FileNotFound) + { +
+ +
+ } + else if (musicVideo.State == MediaItemState.Unavailable) + { +
+ +
+ } +
+ } +
+ +
+ + @if (musicVideo.State == MediaItemState.FileNotFound) + { +
+ +
+ } + else if (musicVideo.State == MediaItemState.Unavailable) + { +
+ +
+ } +
+
+ } + +
+ @musicVideo.Title + @if (!string.IsNullOrWhiteSpace(musicVideo.Album)) + { +
+ Album:  + @musicVideo.Album +
+ } + @musicVideo.Plot +
+ + Add To Collection + +
+
+
+
+ @if (musicVideo.State == MediaItemState.FileNotFound) + { +
+ + File Not Found:  + @musicVideo.Path
- -
- @if (musicVideo.State == MediaItemState.FileNotFound) - { -
- - File Not Found:  - @musicVideo.Path -
- } - else if (musicVideo.State == MediaItemState.Unavailable) - { -
- - Unavailable:  - @musicVideo.LocalPath -
- } - - } + } + else if (musicVideo.State == MediaItemState.Unavailable) + { +
+ + Unavailable:  + @musicVideo.LocalPath +
+ } + + } + @code { @@ -242,7 +270,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is MediaCollectionViewModel collection) + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) { await Mediator.Send(new AddArtistToCollection(collection.Id, ArtistId), _cts.Token); NavigationManager.NavigateTo($"media/collections/{collection.Id}"); @@ -256,7 +284,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Schedule", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is 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); NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); @@ -270,7 +298,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is MediaCollectionViewModel collection) + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) { var request = new AddMusicVideoToCollection(collection.Id, musicVideo.MusicVideoId); Either addResult = await Mediator.Send(request, _cts.Token); diff --git a/ErsatzTV/Pages/CollectionEditor.razor b/ErsatzTV/Pages/CollectionEditor.razor index ff9e70ce7..b5a1bcedc 100644 --- a/ErsatzTV/Pages/CollectionEditor.razor +++ b/ErsatzTV/Pages/CollectionEditor.razor @@ -7,25 +7,23 @@ @inject ISnackbar Snackbar @inject IMediator Mediator - -
- @(IsEdit ? "Edit Collection" : "Add Collection") - - - - - - - - - - @(IsEdit ? "Save Changes" : "Add Collection") - - - - + + + @(IsEdit ? "Save Collection" : "Add Collection") + +
+ + Collection + + +
+ Name +
+ +
+
- +
@code { private readonly CancellationTokenSource _cts = new(); @@ -34,8 +32,8 @@ public int Id { get; set; } private readonly CollectionEditViewModel _model = new(); - private EditContext _editContext; - private ValidationMessageStore _messageStore; + private MudForm _form; + private bool _success; public void Dispose() { @@ -60,18 +58,12 @@ } } - protected override void OnInitialized() - { - _editContext = new EditContext(_model); - _messageStore = new ValidationMessageStore(_editContext); - } - private bool IsEdit => Id != 0; private async Task HandleSubmitAsync() { - _messageStore.Clear(); - if (_editContext.Validate()) + await _form.Validate(); + if (_success) { Seq errorMessage = IsEdit ? (await Mediator.Send(new UpdateCollection(Id, _model.Name), _cts.Token)).LeftToSeq() : (await Mediator.Send(new CreateCollection(_model.Name), _cts.Token)).LeftToSeq(); diff --git a/ErsatzTV/Pages/CollectionItems.razor b/ErsatzTV/Pages/CollectionItems.razor index a7e249b1e..2a54d7866 100644 --- a/ErsatzTV/Pages/CollectionItems.razor +++ b/ErsatzTV/Pages/CollectionItems.razor @@ -5,313 +5,333 @@ @inject NavigationManager NavigationManager @inject IJSRuntime JsRuntime - -
- @if (IsSelectMode()) - { - @SelectionLabel() -
- - Remove From Collection - - - Clear Selection - -
- } - else - { -
- @_data?.Name - -
+ + +
+ @if (IsSelectMode()) + { +
+ @SelectionLabel() +
+
+ + Remove From Collection + + + Clear Selection + +
+
+
+ + + + +
+ } + else + { +
+ @_data?.Name +
+ + @if (_data?.MovieCards.Count > 0) + { + @_data.MovieCards.Count Movies + } + + @if (_data?.ShowCards.Count > 0) + { + @_data.ShowCards.Count Shows + } + + @if (_data?.SeasonCards.Count > 0) + { + @_data.SeasonCards.Count Seasons + } + + @if (_data?.EpisodeCards.Count > 0) + { + @_data.EpisodeCards.Count Episodes + } + + @if (_data?.ArtistCards.Count > 0) + { + @_data.ArtistCards.Count Artists + } + + @if (_data?.MusicVideoCards.Count > 0) + { + @_data.MusicVideoCards.Count Music Videos + } + + @if (_data?.OtherVideoCards.Count > 0) + { + @_data.OtherVideoCards.Count Other Videos + } + + @if (_data?.SongCards.Count > 0) + { + @_data.SongCards.Count Songs + } + + @if (_data?.ImageCards.Count > 0) + { + @_data.ImageCards.Count Images + } +
+ @if (SupportsCustomOrdering()) + { +
+ +
+ } +
+ +
+
+ } +
+
+
+ @if (_data?.MovieCards.Count > 0) { - @_data.MovieCards.Count Movies + + Movies + + + + + @foreach (MovieCardViewModel card in OrderMovies(_data.MovieCards)) + { + + } + } @if (_data?.ShowCards.Count > 0) { - @_data.ShowCards.Count Shows + + Shows + + + + + @foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle)) + { + + } + } @if (_data?.SeasonCards.Count > 0) { - @_data.SeasonCards.Count Seasons + + Seasons + + + + + @foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle)) + { + + } + } @if (_data?.EpisodeCards.Count > 0) { - @_data.EpisodeCards.Count Episodes + + Episodes + + + + + @foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired)) + { + + } + } @if (_data?.ArtistCards.Count > 0) { - @_data.ArtistCards.Count Artists + + Artists + + + + + @foreach (ArtistCardViewModel card in _data.ArtistCards.OrderBy(e => e.SortTitle)) + { + + } + } @if (_data?.MusicVideoCards.Count > 0) { - @_data.MusicVideoCards.Count Music Videos + + Music Videos + + + + + @foreach (MusicVideoCardViewModel card in _data.MusicVideoCards.OrderBy(e => e.SortTitle)) + { + + } + } @if (_data?.OtherVideoCards.Count > 0) { - @_data.OtherVideoCards.Count Other Videos + + Other Videos + + + + + @foreach (OtherVideoCardViewModel card in _data.OtherVideoCards.OrderBy(e => e.SortTitle)) + { + + } + } @if (_data?.SongCards.Count > 0) { - @_data.SongCards.Count Songs + + Songs + + + + + @foreach (SongCardViewModel card in _data.SongCards.OrderBy(e => e.SortTitle)) + { + + } + } @if (_data?.ImageCards.Count > 0) { - @_data.ImageCards.Count Images - } + + Images + + - @if (SupportsCustomOrdering()) - { -
- -
+ + @foreach (ImageCardViewModel card in _data.ImageCards.OrderBy(e => e.SortTitle)) + { + + } + } - } +
- - - - @if (_data?.MovieCards.Count > 0) - { - - Movies - - - - @foreach (MovieCardViewModel card in OrderMovies(_data.MovieCards)) - { - - } - - } - - @if (_data?.ShowCards.Count > 0) - { - - Television Shows - - - - @foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle)) - { - - } - - } - - @if (_data?.SeasonCards.Count > 0) - { - - Television Seasons - - - - @foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle)) - { - - } - - } - - @if (_data?.EpisodeCards.Count > 0) - { - - Television Episodes - - - - @foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired)) - { - - } - - } - - @if (_data?.ArtistCards.Count > 0) - { - - Artists - - - - @foreach (ArtistCardViewModel card in _data.ArtistCards.OrderBy(e => e.SortTitle)) - { - - } - - } - - @if (_data?.MusicVideoCards.Count > 0) - { - - Music Videos - - - - @foreach (MusicVideoCardViewModel card in _data.MusicVideoCards.OrderBy(e => e.SortTitle)) - { - - } - - } - - @if (_data?.OtherVideoCards.Count > 0) - { - - Other Videos - - - - @foreach (OtherVideoCardViewModel card in _data.OtherVideoCards.OrderBy(e => e.SortTitle)) - { - - } - - } - - @if (_data?.SongCards.Count > 0) - { - - Songs - - - - @foreach (SongCardViewModel card in _data.SongCards.OrderBy(e => e.SortTitle)) - { - - } - - } - - @if (_data?.ImageCards.Count > 0) - { - - Images - - - - @foreach (ImageCardViewModel card in _data.ImageCards.OrderBy(e => e.SortTitle)) - { - - } - - } - +
@code { @@ -512,7 +532,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Remove From Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled) + if (result is { Canceled: false }) { await Mediator.Send(request, CancellationToken); await RefreshData(); diff --git a/ErsatzTV/Pages/Collections.razor b/ErsatzTV/Pages/Collections.razor index 3b9f088b1..4c3eb6c72 100644 --- a/ErsatzTV/Pages/Collections.razor +++ b/ErsatzTV/Pages/Collections.razor @@ -6,130 +6,146 @@ @inject IDialogService Dialog @inject IMediator Mediator - -
- - Add Collection - - - Add Multi Collection - + + +
+
+ + Add Collection + + + Add Multi Collection + +
+
+
+ + + + +
+
+
+
+ + Collections + + + + + + + + + + Name + + + + @context.Name + +
+ + + + + + + + +
+
+
+ + + +
+ Multi Collections + + + + + + + + + + Name + + + + @context.Name + +
+ + + + + + + + +
+
+
+ + + +
+ Smart Collections + + + + + + + + + + Name + + + + @context.Name + +
+ + + + + + + + +
+
+
+ + + +
+
+
- - - Collections - - - - - - - Name - - - - @context.Name - -
- - - - - - - - -
-
-
- - - -
- - - Multi Collections - - - - - - - Name - - - - @context.Name - -
- - - - - - - - -
-
-
- - - -
- - - Smart Collections - - - - - - - Name - - - - @context.Name - -
- - - - - - - - -
-
-
- - - -
- +
@code { private readonly CancellationTokenSource _cts = new(); @@ -167,7 +183,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Delete Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled) + if (result is { Canceled: false }) { await Mediator.Send(new DeleteCollection(collection.Id), _cts.Token); if (_collectionsTable != null) @@ -184,7 +200,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Delete Multi Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled) + if (result is { Canceled: false }) { await Mediator.Send(new DeleteMultiCollection(collection.Id), _cts.Token); if (_multiCollectionsTable != null) @@ -201,7 +217,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Delete Smart Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled) + if (result is { Canceled: false }) { await Mediator.Send(new DeleteSmartCollection(collection.Id), _cts.Token); if (_smartCollectionsTable != null) diff --git a/ErsatzTV/Pages/Movie.razor b/ErsatzTV/Pages/Movie.razor index bd3393f2f..678ebd918 100644 --- a/ErsatzTV/Pages/Movie.razor +++ b/ErsatzTV/Pages/Movie.razor @@ -32,7 +32,7 @@ @if (!string.IsNullOrWhiteSpace(_movie?.Poster)) {
- + @if (_movie.MediaItemState == MediaItemState.FileNotFound) {
diff --git a/ErsatzTV/Pages/Search.razor b/ErsatzTV/Pages/Search.razor index 2f290b231..ebaf4c7e2 100644 --- a/ErsatzTV/Pages/Search.razor +++ b/ErsatzTV/Pages/Search.razor @@ -10,10 +10,10 @@ -
+
@if (IsSelectMode()) { -
+
@SelectionLabel()
@@ -140,9 +140,7 @@ @if (_movies?.Count > 0) {
- + Movies @if (_movies.Count > 50) @@ -168,9 +166,7 @@ @if (_shows?.Count > 0) {
- + Shows @if (_shows.Count > 50) @@ -196,9 +192,7 @@ @if (_seasons?.Count > 0) {
- + Seasons @if (_seasons.Count > 50) @@ -224,9 +218,7 @@ @if (_episodes?.Count > 0) {
- + Episodes @if (_episodes.Count > 50) @@ -253,9 +245,7 @@ @if (_artists?.Count > 0) {
- + Artists @if (_artists.Count > 50) @@ -282,9 +272,7 @@ @if (_musicVideos?.Count > 0) {
- + Music Videos @if (_musicVideos.Count > 50) @@ -311,9 +299,7 @@ @if (_otherVideos?.Count > 0) {
- + Other Videos @if (_otherVideos.Count > 50) @@ -340,9 +326,7 @@ @if (_songs?.Count > 0) {
- + Songs @if (_songs.Count > 50) @@ -369,9 +353,7 @@ @if (_images?.Count > 0) {
- + Images @if (_images.Count > 50) @@ -604,6 +586,27 @@ } } + if (card is TelevisionSeasonCardViewModel season) + { + var parameters = new DialogParameters { { "EntityType", "season" }, { "EntityName", $"{season.Title} Season {season.TelevisionSeasonNumber}" } }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; + + IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); + DialogResult result = await dialog.Result; + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) + { + var request = new AddSeasonToCollection(collection.Id, season.TelevisionSeasonId); + Either addResult = await Mediator.Send(request, CancellationToken); + addResult.Match( + Left: error => + { + Snackbar.Add($"Unexpected error adding season to collection: {error.Value}"); + Logger.LogError("Unexpected error adding season to collection: {Error}", error.Value); + }, + Right: _ => Snackbar.Add($"Added {season.Title} Season {season.TelevisionSeasonNumber} to collection {collection.Name}", Severity.Success)); + } + } + if (card is ArtistCardViewModel artist) { var parameters = new DialogParameters { { "EntityType", "artist" }, { "EntityName", artist.Title } }; diff --git a/ErsatzTV/Pages/TelevisionEpisodeList.razor b/ErsatzTV/Pages/TelevisionEpisodeList.razor index 3509d0dac..846f23b01 100644 --- a/ErsatzTV/Pages/TelevisionEpisodeList.razor +++ b/ErsatzTV/Pages/TelevisionEpisodeList.razor @@ -27,165 +27,155 @@ } } - -
+ + @if (!string.IsNullOrWhiteSpace(_season?.Poster)) { - if (_season.Poster.StartsWith("http://") || _season.Poster.StartsWith("https://")) - { - season poster - } - else - { - season poster - } + } -
- - @_season?.Title - - @_season?.Name -
+ + + + + @_season?.Title + + + @_season?.Title + + + @_season?.Name + + Add To Collection - Add To Playlist - Add To Schedule -
-
-
+ + +
- @foreach (TelevisionEpisodeCardViewModel episode in _data.Cards) - { - -
- @if (!string.IsNullOrWhiteSpace(episode.Poster)) - { - - @if (episode.Poster.StartsWith("http://") || episode.Poster.StartsWith("https://")) - { - - } - else - { - - } - @if (episode.State == MediaItemState.FileNotFound) - { -
- -
- } - else if (episode.State == MediaItemState.Unavailable) - { -
- -
- } -
- } - -
- @episode.Episode. @episode.Title - @episode.Plot -
- - Add To Collection - + + @foreach (TelevisionEpisodeCardViewModel episode in _data.Cards) + { + + + @if (!string.IsNullOrWhiteSpace(episode.Poster)) + { +
+ + + + + + + @if (episode.State == MediaItemState.FileNotFound) + { +
+ +
+ } + else if (episode.State == MediaItemState.Unavailable) + { +
+ +
+ }
-
- - Add To Playlist - + } + +
+ @episode.Episode. @episode.Title + @episode.Plot + + + Add To Collection + + + Add To Playlist + + + Show Media Info + +
-
- - Show Media Info - + + +
+ @if (episode.State == MediaItemState.FileNotFound) + { +
+ + File Not Found:  + @episode.Path
+ } + else if (episode.State == MediaItemState.Unavailable) + { +
+ + Unavailable:  + @episode.LocalPath +
+ } +
+ Released: @episode.Aired.ToShortDateString()
- -
-
- @if (episode.State == MediaItemState.FileNotFound) - { -
- - File Not Found:  - @episode.Path -
- } - else if (episode.State == MediaItemState.Unavailable) - { -
- - Unavailable:  - @episode.LocalPath -
- } -
- Released: @episode.Aired.ToShortDateString() + @if (episode.Directors.Any()) + { + var sorted = episode.Directors.OrderBy(w => w).ToList(); + +
+ Directors:  + @sorted.Head() + @foreach (string director in sorted.Skip(1)) + { + + @director + } +
+ } + @if (episode.Writers.Any()) + { + var sorted = episode.Writers.OrderBy(w => w).ToList(); + +
+ Writers:  + @sorted.Head() + @foreach (string writer in sorted.Skip(1)) + { + + @writer + } +
+ }
- @if (episode.Directors.Any()) - { - var sorted = episode.Directors.OrderBy(w => w).ToList(); - -
- Directors:  - @sorted.Head() - @foreach (string director in sorted.Skip(1)) - { - - @director - } -
- } - @if (episode.Writers.Any()) - { - var sorted = episode.Writers.OrderBy(w => w).ToList(); - -
- Writers:  - @sorted.Head() - @foreach (string writer in sorted.Skip(1)) - { - - @writer - } -
- } -
- - } + + } + @code { @@ -196,8 +186,8 @@ private TelevisionSeasonViewModel _season; - private int _pageSize => 100; - private readonly int _pageNumber = 1; + private static int PageSize => 100; + private const int PageNumber = 1; private TelevisionEpisodeCardResultsViewModel _data = new(0, new List(), null); @@ -229,7 +219,7 @@ await Mediator.Send(new GetTelevisionSeasonById(SeasonId), _cts.Token) .IfSomeAsync(vm => _season = vm); - _data = await Mediator.Send(new GetTelevisionEpisodeCards(SeasonId, _pageNumber, _pageSize), _cts.Token); + _data = await Mediator.Send(new GetTelevisionEpisodeCards(SeasonId, PageNumber, PageSize), _cts.Token); } private async Task AddToCollection() @@ -239,7 +229,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is MediaCollectionViewModel collection) + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) { await Mediator.Send(new AddSeasonToCollection(collection.Id, SeasonId), _cts.Token); NavigationManager.NavigateTo($"media/collections/{collection.Id}"); @@ -253,7 +243,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Playlist", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is PlaylistViewModel playlist) + if (result is { Canceled: false, Data: PlaylistViewModel playlist }) { await Mediator.Send(new AddSeasonToPlaylist(playlist.Id, SeasonId), _cts.Token); NavigationManager.NavigateTo($"media/playlists/{playlist.Id}"); @@ -267,7 +257,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Schedule", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is 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); NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); @@ -281,7 +271,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is MediaCollectionViewModel collection) + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) { var request = new AddEpisodeToCollection(collection.Id, episode.EpisodeId); Either addResult = await Mediator.Send(request, _cts.Token); @@ -302,7 +292,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Playlist", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is PlaylistViewModel playlist) + if (result is { Canceled: false, Data: PlaylistViewModel playlist }) { var request = new AddEpisodeToPlaylist(playlist.Id, episode.EpisodeId); Either addResult = await Mediator.Send(request, _cts.Token); @@ -338,4 +328,9 @@ } } + private static string GetPosterUrl(string poster) + { + return poster.StartsWith("http://") || poster.StartsWith("https://") ? poster : $"artwork/posters/{poster}"; + } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/TelevisionSeasonList.razor b/ErsatzTV/Pages/TelevisionSeasonList.razor index 2611557bf..82719c8c6 100644 --- a/ErsatzTV/Pages/TelevisionSeasonList.razor +++ b/ErsatzTV/Pages/TelevisionSeasonList.razor @@ -28,7 +28,7 @@ - +
diff --git a/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor b/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor index 128495e1e..60d637ad7 100644 --- a/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor +++ b/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor @@ -138,7 +138,7 @@ IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is MediaCollectionViewModel collection) + if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) { var request = new AddSeasonToCollection(collection.Id, season.TelevisionSeasonId); Either addResult = await Mediator.Send(request, CancellationToken); diff --git a/ErsatzTV/Pages/TraktLists.razor b/ErsatzTV/Pages/TraktLists.razor index 85b27bb5b..277e0cd4b 100644 --- a/ErsatzTV/Pages/TraktLists.razor +++ b/ErsatzTV/Pages/TraktLists.razor @@ -152,7 +152,7 @@ var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small }; IDialogReference dialog = await Dialog.ShowAsync("Add Trakt List", options); DialogResult result = await dialog.Result; - if (!result.Canceled && result.Data is string url) + if (result is { Canceled: false, Data: string url }) { await WorkerChannel.WriteAsync(new AddTraktList(url), _cts.Token); } diff --git a/ErsatzTV/Shared/MediaCard.razor b/ErsatzTV/Shared/MediaCard.razor index 62a7ec814..1a1134e6a 100644 --- a/ErsatzTV/Shared/MediaCard.razor +++ b/ErsatzTV/Shared/MediaCard.razor @@ -1,6 +1,5 @@ @using ErsatzTV.Application.MediaCards @using static Prelude -@inject IMediator Mediator @inject NavigationManager NavigationManager
@@ -78,7 +77,7 @@ @(Title ?? Data.Title) - + @(Subtitle ?? Data.Subtitle)
diff --git a/ErsatzTV/Validators/CollectionEditViewModelValidator.cs b/ErsatzTV/Validators/CollectionEditViewModelValidator.cs deleted file mode 100644 index d6920b5f0..000000000 --- a/ErsatzTV/Validators/CollectionEditViewModelValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ErsatzTV.ViewModels; -using FluentValidation; - -namespace ErsatzTV.Validators; - -public class CollectionEditViewModelValidator : AbstractValidator -{ - public CollectionEditViewModelValidator() => RuleFor(c => c.Name).NotEmpty(); -} diff --git a/ErsatzTV/wwwroot/css/site.css b/ErsatzTV/wwwroot/css/site.css index 2c4671bd2..a50ae5214 100644 --- a/ErsatzTV/wwwroot/css/site.css +++ b/ErsatzTV/wwwroot/css/site.css @@ -6,8 +6,6 @@ .media-card-container { width: 152px; } -.media-card-episode-container { width: 392px; } - .media-card { display: flex; /*filter: brightness(100%);*/ @@ -22,8 +20,6 @@ .media-card-selected-delete { box-shadow: 0 0 0 3px #f44336, 0 0 4px rgba(0, 0, 0, 0.3); } -.media-card-episode { width: 392px; } - .media-card:hover { /*filter: brightness(75%);*/ } .media-card-title {