From 7e40a809ff507f32514bb978016249375641e981 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:19:34 +0000 Subject: [PATCH] improve search results ui on mobile (#2133) * show brief health check messages on mobile * update libraries layout * improve search results ui on mobile --- ErsatzTV.Core/Health/HealthCheckResult.cs | 2 +- .../Health/Checks/BaseHealthCheck.cs | 24 +- .../Checks/EpisodeMetadataHealthCheck.cs | 3 +- .../Health/Checks/ErrorReportsHealthCheck.cs | 5 +- .../Health/Checks/FFmpegReportsHealthCheck.cs | 3 +- .../Health/Checks/FFmpegVersionHealthCheck.cs | 14 +- .../Checks/HardwareAccelerationHealthCheck.cs | 9 +- .../Checks/MacOsConfigFolderHealthCheck.cs | 2 +- .../Health/Checks/MovieMetadataHealthCheck.cs | 3 +- .../Health/Checks/UnifiedDockerHealthCheck.cs | 3 +- .../Health/Checks/VaapiDriverHealthCheck.cs | 6 +- .../Health/Checks/ZeroDurationHealthCheck.cs | 4 +- .../Health/HealthCheckService.cs | 1 + ErsatzTV/Pages/Index.razor | 29 +- ErsatzTV/Pages/Libraries.razor | 241 +++--- ErsatzTV/Pages/Search.razor | 723 +++++++++--------- 16 files changed, 558 insertions(+), 514 deletions(-) diff --git a/ErsatzTV.Core/Health/HealthCheckResult.cs b/ErsatzTV.Core/Health/HealthCheckResult.cs index 4d04952c2..13cebdd50 100644 --- a/ErsatzTV.Core/Health/HealthCheckResult.cs +++ b/ErsatzTV.Core/Health/HealthCheckResult.cs @@ -1,3 +1,3 @@ namespace ErsatzTV.Core.Health; -public record HealthCheckResult(string Title, HealthCheckStatus Status, string Message, Option Link); +public record HealthCheckResult(string Title, HealthCheckStatus Status, string Message, string BriefMessage, Option Link); diff --git a/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs index f40c12fb2..c3e4cc9cf 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs @@ -9,26 +9,26 @@ public abstract class BaseHealthCheck { public abstract string Title { get; } - protected HealthCheckResult Result(HealthCheckStatus status, string message) => - new(Title, status, message, None); + protected HealthCheckResult Result(HealthCheckStatus status, string message, string briefMessage) => + new(Title, status, message, briefMessage, None); protected HealthCheckResult NotApplicableResult() => - new(Title, HealthCheckStatus.NotApplicable, string.Empty, None); + new(Title, HealthCheckStatus.NotApplicable, string.Empty, string.Empty, None); protected HealthCheckResult OkResult() => - new(Title, HealthCheckStatus.Pass, string.Empty, None); + new(Title, HealthCheckStatus.Pass, string.Empty, string.Empty, None); - protected HealthCheckResult FailResult(string message) => - new(Title, HealthCheckStatus.Fail, message, None); + protected HealthCheckResult FailResult(string message, string briefMessage) => + new(Title, HealthCheckStatus.Fail, message, briefMessage, None); - protected HealthCheckResult WarningResult(string message) => - new(Title, HealthCheckStatus.Warning, message, None); + protected HealthCheckResult WarningResult(string message, string briefMessage) => + new(Title, HealthCheckStatus.Warning, message, briefMessage, None); - protected HealthCheckResult WarningResult(string message, string link) => - new(Title, HealthCheckStatus.Warning, message, link); + protected HealthCheckResult WarningResult(string message, string briefMessage, string link) => + new(Title, HealthCheckStatus.Warning, message, briefMessage, link); - protected HealthCheckResult InfoResult(string message) => - new(Title, HealthCheckStatus.Info, message, None); + protected HealthCheckResult InfoResult(string message, string briefMessage) => + new(Title, HealthCheckStatus.Info, message, briefMessage, None); protected static async Task GetProcessOutput( string path, diff --git a/ErsatzTV.Infrastructure/Health/Checks/EpisodeMetadataHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/EpisodeMetadataHealthCheck.cs index bc414770d..454024d8f 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/EpisodeMetadataHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/EpisodeMetadataHealthCheck.cs @@ -38,7 +38,8 @@ public class EpisodeMetadataHealthCheck : BaseHealthCheck, IEpisodeMetadataHealt var folders = string.Join(", ", paths); return WarningResult( - $"There are {episodes.Count} episodes with missing metadata, including in the following folders: {folders}"); + $"There are {episodes.Count} episodes with missing metadata, including in the following folders: {folders}", + $"There are {episodes.Count} episodes with missing metadata"); } return OkResult(); diff --git a/ErsatzTV.Infrastructure/Health/Checks/ErrorReportsHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/ErrorReportsHealthCheck.cs index 0b8d52f67..3fe74bf28 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/ErrorReportsHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/ErrorReportsHealthCheck.cs @@ -20,11 +20,12 @@ public class ErrorReportsHealthCheck : BaseHealthCheck, IErrorReportsHealthCheck { return Result( HealthCheckStatus.Pass, - "Automated error reporting is enabled, thank you! To disable, edit the file appsettings.json or set the Bugsnag:Enable environment variable to false") + "Automated error reporting is enabled, thank you! To disable, edit the file appsettings.json or set the Bugsnag:Enable environment variable to false", + "Automated error reporting is enabled, thank you!") .AsTask(); } - return InfoResult("Automated error reporting is disabled. Please enable to support bug fixing efforts!") + return InfoResult("Automated error reporting is disabled. Please enable to support bug fixing efforts!", "Automated error reporting is disabled") .AsTask(); } } diff --git a/ErsatzTV.Infrastructure/Health/Checks/FFmpegReportsHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/FFmpegReportsHealthCheck.cs index 2f721b383..f6682c963 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/FFmpegReportsHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/FFmpegReportsHealthCheck.cs @@ -25,7 +25,8 @@ public class FFmpegReportsHealthCheck : BaseHealthCheck, IFFmpegReportsHealthChe { return Result( HealthCheckStatus.Warning, - "FFmpeg troubleshooting reports are enabled and may use a lot of disk space"); + "FFmpeg troubleshooting reports are enabled and may use a lot of disk space", + "FFmpeg troubleshooting reports are enabled"); } } diff --git a/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs index 4ae97ec24..be5414709 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs @@ -27,14 +27,14 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath); if (maybeFFmpegPath.IsNone) { - return FailResult("Unable to locate ffmpeg"); + return FailResult("Unable to locate ffmpeg", "Unable to locate ffmpeg"); } Option maybeFFprobePath = await _configElementRepository.GetConfigElement(ConfigElementKey.FFprobePath); if (maybeFFprobePath.IsNone) { - return FailResult("Unable to locate ffprobe"); + return FailResult("Unable to locate ffprobe", "Unable to locate ffprobe"); } foreach (ConfigElement ffmpegPath in maybeFFmpegPath) @@ -42,7 +42,7 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe Option maybeVersion = await GetVersion(ffmpegPath.Value, cancellationToken); if (maybeVersion.IsNone) { - return WarningResult("Unable to determine ffmpeg version"); + return WarningResult("Unable to determine ffmpeg version", "Unable to determine ffmpeg version"); } foreach (string version in maybeVersion) @@ -59,7 +59,7 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe Option maybeVersion = await GetVersion(ffprobePath.Value, cancellationToken); if (maybeVersion.IsNone) { - return WarningResult("Unable to determine ffprobe version"); + return WarningResult("Unable to determine ffprobe version", "Unable to determine ffprobe version"); } foreach (string version in maybeVersion) @@ -71,7 +71,7 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe } } - return new HealthCheckResult("FFmpeg Version", HealthCheckStatus.Pass, string.Empty, None); + return new HealthCheckResult("FFmpeg Version", HealthCheckStatus.Pass, string.Empty, string.Empty, None); } private Option ValidateVersion(string version, string app) @@ -81,7 +81,7 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe version.StartsWith("5.", StringComparison.OrdinalIgnoreCase) || version.StartsWith("6.", StringComparison.OrdinalIgnoreCase)) { - return FailResult($"{app} version {version} is too old; please install 7.1.1!"); + return FailResult($"{app} version {version} is too old; please install 7.1.1!", $"{app} version is too old"); } if (!version.StartsWith("7.1.1", StringComparison.OrdinalIgnoreCase) && @@ -90,7 +90,7 @@ public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthChe version != BundledVersionVaapi) { return WarningResult( - $"{app} version {version} is unexpected and may have problems; please install 7.1.1!"); + $"{app} version {version} is unexpected and may have problems; please install 7.1.1!", $"{app} version is unexpected"); } return None; diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index bcdcd1f3c..4658fefde 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -38,7 +38,7 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath); if (maybeFFmpegPath.IsNone) { - return FailResult("Unable to locate ffmpeg"); + return FailResult("Unable to locate ffmpeg", "Unable to locate ffmpeg"); } string version = Assembly.GetEntryAssembly()?.GetCustomAttribute() @@ -67,7 +67,9 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler if (accelerationKinds.Count == 0) { - return InfoResult("No compatible hardware acceleration kinds are supported by ffmpeg"); + return InfoResult( + "No compatible hardware acceleration kinds are supported by ffmpeg", + "FFmpeg does not support hardware acceleration"); } Option maybeResult = await VerifyProfilesUseAcceleration(accelerationKinds); @@ -94,7 +96,8 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler var accel = string.Join(", ", accelerationKinds); var channels = string.Join(", ", badChannels.Map(c => $"{c.Number} - {c.Name}")); return WarningResult( - $"The following channels use ffmpeg profiles that are not configured for hardware acceleration ({accel}): {channels}"); + $"The following channels use ffmpeg profiles that are not configured for hardware acceleration ({accel}): {channels}", + $"{channels.Length} channels are not configured for hardware acceleration"); } return None; diff --git a/ErsatzTV.Infrastructure/Health/Checks/MacOsConfigFolderHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/MacOsConfigFolderHealthCheck.cs index 182bf4140..ad8f60792 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/MacOsConfigFolderHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/MacOsConfigFolderHealthCheck.cs @@ -21,7 +21,7 @@ public class MacOsConfigFolderHealthCheck : BaseHealthCheck, IMacOsConfigFolderH { var message = $"Old config data exists; to migrate: exit ETV, backup the folder {FileSystemLayout.AppDataFolder} to another location, and restart ETV. Otherwise, move the old folder {FileSystemLayout.MacOsOldAppDataFolder} to another location to remove this message"; - return FailResult(message).AsTask(); + return FailResult(message, "Old config data exists").AsTask(); } return NotApplicableResult().AsTask(); diff --git a/ErsatzTV.Infrastructure/Health/Checks/MovieMetadataHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/MovieMetadataHealthCheck.cs index 87d655c59..010697643 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/MovieMetadataHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/MovieMetadataHealthCheck.cs @@ -38,7 +38,8 @@ public class MovieMetadataHealthCheck : BaseHealthCheck, IMovieMetadataHealthChe var folders = string.Join(", ", paths); return WarningResult( - $"There are {movies.Count} movies with missing metadata, including in the following folders: {folders}"); + $"There are {movies.Count} movies with missing metadata, including in the following folders: {folders}", + $"There are {movies.Count} movies with missing metadata"); } return OkResult(); diff --git a/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs index 4060b02b1..18f29a0f8 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs @@ -17,7 +17,8 @@ public class UnifiedDockerHealthCheck : BaseHealthCheck, IUnifiedDockerHealthChe if (InfoVersion.Contains("docker-vaapi") || InfoVersion.Contains("docker-nvidia")) { return WarningResult( - "VAAPI and NVIDIA docker tag suffixes are deprecated; please remove `-vaapi` or `-nvidia` and pull the default image.") + "VAAPI and NVIDIA docker tag suffixes are deprecated; please remove `-vaapi` or `-nvidia` and pull the default image.", + "docker tag is deprecated") .AsTask(); } diff --git a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs index d7593b657..1640a0635 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs @@ -71,7 +71,8 @@ public class VaapiDriverHealthCheck( if (capabilities is VaapiHardwareCapabilities { EntrypointCount: 0 } or NoHardwareCapabilities) { return FailResult( - $"FFmpeg Profile {profile.Name} is using device and driver combination ({profile.VaapiDevice} and {profile.VaapiDriver}) that reports no capabilities. Hardware Acceleration WILL NOT WORK as configured."); + $"FFmpeg Profile {profile.Name} is using device and driver combination ({profile.VaapiDevice} and {profile.VaapiDriver}) that reports no capabilities. Hardware Acceleration WILL NOT WORK as configured.", + "Hardware acceleration WILL NOT WORK..."); } } } @@ -83,7 +84,8 @@ public class VaapiDriverHealthCheck( return defaultProfiles.Count != 0 ? InfoResult( - $"{defaultProfiles.Count} FFmpeg Profile{(defaultProfiles.Count > 1 ? "s are" : " is")} set to use Default VAAPI Driver; selecting iHD (Gen 8+) or i965 (up to Gen 9) may offer better performance with Intel iGPU") + $"{defaultProfiles.Count} FFmpeg Profile{(defaultProfiles.Count > 1 ? "s are" : " is")} set to use Default VAAPI Driver; selecting iHD (Gen 8+) or i965 (up to Gen 9) may offer better performance with Intel iGPU", + "Default VAAPI driver is not optimal") : OkResult(); } diff --git a/ErsatzTV.Infrastructure/Health/Checks/ZeroDurationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/ZeroDurationHealthCheck.cs index 64125524d..2709d26c5 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/ZeroDurationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/ZeroDurationHealthCheck.cs @@ -63,7 +63,9 @@ public class ZeroDurationHealthCheck : BaseHealthCheck, IZeroDurationHealthCheck var files = string.Join(", ", paths); - return WarningResult($"There are {all.Count} files with zero duration, including the following: {files}"); + return WarningResult( + $"There are {all.Count} files with zero duration, including the following: {files}", + $"There are {all.Count} files with zero duration"); } return OkResult(); diff --git a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs index 6a562ef60..336c37999 100644 --- a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs +++ b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs @@ -60,6 +60,7 @@ public class HealthCheckService : IHealthCheckService c.Title, HealthCheckStatus.Fail, "Health check failure; see logs", + "Health check failure", None); return TryAsync(() => c.Check(cancellationToken)).IfFail(ex => LogAndReturn(ex, failedResult)); }) diff --git a/ErsatzTV/Pages/Index.razor b/ErsatzTV/Pages/Index.razor index 9a3c46b01..04d9ad9dc 100644 --- a/ErsatzTV/Pages/Index.razor +++ b/ErsatzTV/Pages/Index.razor @@ -84,21 +84,26 @@ - @if (context.Link.IsSome) - { - foreach (string link in context.Link) + + @if (context.Link.IsSome) + { + foreach (string link in context.Link) + { + + @context.Message + + } + } + else { - + @context.Message - + } - } - else - { - - @context.Message - - } + + + @context.BriefMessage + diff --git a/ErsatzTV/Pages/Libraries.razor b/ErsatzTV/Pages/Libraries.razor index 581391ea3..ccfdfafdb 100644 --- a/ErsatzTV/Pages/Libraries.razor +++ b/ErsatzTV/Pages/Libraries.razor @@ -13,132 +13,135 @@ @inject ChannelWriter ScannerWorkerChannel; @inject ICourier Courier - - - - Libraries - - - - @if (_showServerNames) - { - - } - - - - - - Library Kind - @if (_showServerNames) - { - Server Name - } - Library Name - Media Kind - - - - @context.LibraryKind - @if (_showServerNames) - { - @context.MediaSourceName - } - @context.Name - @context.MediaKind - -
- @if (Locker.IsLibraryLocked(context.Id)) + +
+ + Libraries + + + + + + @if (_showServerNames) + { + + } + + + + + + + Library Kind + @if (_showServerNames) { -
- @if (_progressByLibrary[context.Id] > 0) - { - - @($"{_progressByLibrary[context.Id]} %") - - } -
-
- -
+ Server Name } - else + Library Name + Media Kind + +
+ + @context.LibraryKind + @if (_showServerNames) { - if (context is PlexLibraryViewModel or EmbyLibraryViewModel or JellyfinLibraryViewModel) - { - - - - - } - else - { -
- } - - - - - + @context.MediaSourceName } - - - - -
- - - - + @context.Name + @context.MediaKind + +
+ @if (Locker.IsLibraryLocked(context.Id)) + { +
+ @if (_progressByLibrary[context.Id] > 0) + { + + @($"{_progressByLibrary[context.Id]} %") + + } +
+
+ +
+ } + else + { + if (context is PlexLibraryViewModel or EmbyLibraryViewModel or JellyfinLibraryViewModel) + { + + + + + } + else + { +
+ } -@if (_externalCollections.Any()) -{ - - - - External Collections - - - - - - - Library Kind - - - - @context.LibraryKind - -
-
- @if (AreCollectionsLocked(context.LibraryKind)) - { -
- -
- } - else - { -
- - + + + + + } + + - } -
-
-
-
-
-
-} +
+
+ + + External Collections + + @if (_externalCollections.Any()) + { + + + + + + + + + Library Kind + + + + @context.LibraryKind + +
+
+ @if (AreCollectionsLocked(context.LibraryKind)) + { +
+ +
+ } + else + { +
+ + + + + } +
+
+
+
+
+ } + +
+ @code { private readonly CancellationTokenSource _cts = new(); diff --git a/ErsatzTV/Pages/Search.razor b/ErsatzTV/Pages/Search.razor index 9b8058fb6..2f290b231 100644 --- a/ErsatzTV/Pages/Search.razor +++ b/ErsatzTV/Pages/Search.razor @@ -8,372 +8,395 @@ @using ErsatzTV.Extensions @implements IDisposable - -
- @if (IsSelectMode()) - { - @SelectionLabel() -
- - Add To Collection - - - Add To Playlist - - - Clear Selection - -
- } - else - { -
- @_query - @if (_movies?.Count > 0) - { - @_movies.Count Movies - } - - @if (_shows?.Count > 0) - { - @_shows.Count Shows - } - - @if (_seasons?.Count > 0) - { - @_seasons.Count Seasons - } - - @if (_episodes?.Count > 0) - { - @_episodes.Count Episodes - } - - @if (_artists?.Count > 0) - { - @_artists.Count Artists - } - - @if (_musicVideos?.Count > 0) - { - @_musicVideos.Count Music Videos - } - - @if (_otherVideos?.Count > 0) - { - @_otherVideos.Count Other Videos - } - - @if (_songs?.Count > 0) - { - @_songs.Count Songs - } - - @if (_images?.Count > 0) - { - @_images.Count Images - } -
-
- - - Add All - - - - - Add All - - - - - Save As - - -
-
-
-
- - - - - -
- } -
-
- - @if (_movies?.Count > 0) - { -
- - Movies - - @if (_movies.Count > 50) + + +
+ @if (IsSelectMode()) { - See All >> +
+ @SelectionLabel() +
+
+ + Add To Collection + + + Add To Playlist + + + Clear Selection + +
+
+
+ + + + + +
} -
- - - @foreach (MovieCardViewModel card in _movies.Cards.OrderBy(m => m.SortTitle)) + else { - - } - - } +
+ @_query + @if (_movies?.Count > 0) + { + @_movies.Count Movies + } - @if (_shows?.Count > 0) - { -
- - Shows - - @if (_shows.Count > 50) - { - See All >> - } -
+ @if (_shows?.Count > 0) + { + @_shows.Count Shows + } - - @foreach (TelevisionShowCardViewModel card in _shows.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + @if (_seasons?.Count > 0) + { + @_seasons.Count Seasons + } - @if (_seasons?.Count > 0) - { -
- - Seasons - - @if (_seasons.Count > 50) - { - See All >> - } -
+ @if (_episodes?.Count > 0) + { + @_episodes.Count Episodes + } - - @foreach (TelevisionSeasonCardViewModel card in _seasons.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + @if (_artists?.Count > 0) + { + @_artists.Count Artists + } - @if (_episodes?.Count > 0) - { -
- - Episodes - - @if (_episodes.Count > 50) - { - See All >> - } -
+ @if (_musicVideos?.Count > 0) + { + @_musicVideos.Count Music Videos + } - - @foreach (TelevisionEpisodeCardViewModel card in _episodes.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + @if (_otherVideos?.Count > 0) + { + @_otherVideos.Count Other Videos + } - @if (_artists?.Count > 0) - { -
- - Artists - - @if (_artists.Count > 50) - { - See All >> + @if (_songs?.Count > 0) + { + @_songs.Count Songs + } + + @if (_images?.Count > 0) + { + @_images.Count Images + } +
+
+ + + Add All + + + + + Add All + + + + + Save As + + +
+
+
+
+ + + + + +
}
+
+
+ + @if (_movies?.Count > 0) + { +
+ + Movies + + @if (_movies.Count > 50) + { + See All >> + } +
+ - - @foreach (ArtistCardViewModel card in _artists.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + + @foreach (MovieCardViewModel card in _movies.Cards.OrderBy(m => m.SortTitle)) + { + + } + + } + + @if (_shows?.Count > 0) + { +
+ + Shows + + @if (_shows.Count > 50) + { + See All >> + } +
+ - @if (_musicVideos?.Count > 0) - { -
- - Music Videos - - @if (_musicVideos.Count > 50) - { - See All >> - } -
+ + @foreach (TelevisionShowCardViewModel card in _shows.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_seasons?.Count > 0) + { +
+ + Seasons + + @if (_seasons.Count > 50) + { + See All >> + } +
+ - - @foreach (MusicVideoCardViewModel card in _musicVideos.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + + @foreach (TelevisionSeasonCardViewModel card in _seasons.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_episodes?.Count > 0) + { +
+ + Episodes + + @if (_episodes.Count > 50) + { + See All >> + } +
+ - @if (_otherVideos?.Count > 0) - { -
- - Other Videos - - @if (_otherVideos.Count > 50) - { - See All >> - } -
+ + @foreach (TelevisionEpisodeCardViewModel card in _episodes.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_artists?.Count > 0) + { +
+ + Artists + + @if (_artists.Count > 50) + { + See All >> + } +
+ - - @foreach (OtherVideoCardViewModel card in _otherVideos.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + + @foreach (ArtistCardViewModel card in _artists.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_musicVideos?.Count > 0) + { +
+ + Music Videos + + @if (_musicVideos.Count > 50) + { + See All >> + } +
+ - @if (_songs?.Count > 0) - { -
- - Songs - - @if (_songs.Count > 50) - { - See All >> - } -
+ + @foreach (MusicVideoCardViewModel card in _musicVideos.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_otherVideos?.Count > 0) + { +
+ + Other Videos + + @if (_otherVideos.Count > 50) + { + See All >> + } +
+ - - @foreach (SongCardViewModel card in _songs.Cards.OrderBy(s => s.SortTitle)) - { - - } - - } + + @foreach (OtherVideoCardViewModel card in _otherVideos.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_songs?.Count > 0) + { +
+ + Songs + + @if (_songs.Count > 50) + { + See All >> + } +
+ - @if (_images?.Count > 0) - { -
- - Images - - @if (_images.Count > 50) - { - See All >> - } -
+ + @foreach (SongCardViewModel card in _songs.Cards.OrderBy(s => s.SortTitle)) + { + + } + + } + + @if (_images?.Count > 0) + { +
+ + Images + + @if (_images.Count > 50) + { + See All >> + } +
+ - - @foreach (ImageCardViewModel card in _images.Cards.OrderBy(s => s.SortTitle)) - { - + + @foreach (ImageCardViewModel card in _images.Cards.OrderBy(s => s.SortTitle)) + { + + } + } - } -
+
+
@code { private string _query; @@ -525,7 +548,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 AddMovieToCollection(collection.Id, movie.MovieId); Either addResult = await Mediator.Send(request, CancellationToken); @@ -546,7 +569,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 AddShowToCollection(collection.Id, show.TelevisionShowId); Either addResult = await Mediator.Send(request, CancellationToken); @@ -567,7 +590,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, CancellationToken); @@ -588,7 +611,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 AddArtistToCollection(collection.Id, artist.ArtistId); Either addResult = await Mediator.Send(request, CancellationToken); @@ -609,7 +632,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, CancellationToken); @@ -630,7 +653,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 AddOtherVideoToCollection(collection.Id, otherVideo.OtherVideoId); Either addResult = await Mediator.Send(request, CancellationToken); @@ -651,7 +674,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 AddSongToCollection(collection.Id, song.SongId); Either addResult = await Mediator.Send(request, CancellationToken);