Browse Source

rework many media pages (#2134)

* rework many list pages

* refactor

* rework movie details and season list
pull/2136/head
Jason Dove 1 month ago committed by GitHub
parent
commit
ffd3e3604c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 85
      ErsatzTV/Pages/ArtistList.razor
  2. 92
      ErsatzTV/Pages/EpisodeList.razor
  3. 25
      ErsatzTV/Pages/ImageBrowser.razor
  4. 92
      ErsatzTV/Pages/ImageList.razor
  5. 75
      ErsatzTV/Pages/Movie.razor
  6. 91
      ErsatzTV/Pages/MovieList.razor
  7. 7
      ErsatzTV/Pages/MultiSelectBase.cs
  8. 92
      ErsatzTV/Pages/MusicVideoList.razor
  9. 92
      ErsatzTV/Pages/OtherVideoList.razor
  10. 92
      ErsatzTV/Pages/SongList.razor
  11. 102
      ErsatzTV/Pages/TelevisionSeasonList.razor
  12. 90
      ErsatzTV/Pages/TelevisionShowList.razor
  13. 108
      ErsatzTV/Shared/MediaCardPager.razor
  14. 3
      ErsatzTV/wwwroot/css/site.css

85
ErsatzTV/Pages/ArtistList.razor

@ -7,61 +7,36 @@
@inherits MultiSelectBase<MusicVideoList> @inherits MultiSelectBase<MusicVideoList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Secondary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.Check" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => ClearSelection())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Clear Selection <FragmentLetterAnchor TCard="ArtistCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
</div> Href="@($"media/music/artists/{context.ArtistId}")"
} ArtworkKind="ArtworkKind.Thumbnail"
else AddToCollectionClicked="@AddToCollection"
{ SelectClicked="@(e => SelectClicked(context, e))"
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText> IsSelected="@IsSelected(context)"
<div style="max-width: 300px; width: 33%;"> IsSelectMode="@IsSelectMode()"/>
<MudPaper Style="align-items: center; display: flex; justify-content: center;"> </FragmentLetterAnchor>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft" </MudStack>
OnClick="@PrevPage" </MudContainer>
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="ArtistCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href="@($"media/music/artists/{context.ArtistId}")"
ArtworkKind="ArtworkKind.Thumbnail"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -139,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddArtistToCollection(collection.Id, artist.ArtistId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

92
ErsatzTV/Pages/EpisodeList.razor

@ -7,68 +7,36 @@
@inherits MultiSelectBase<EpisodeList> @inherits MultiSelectBase<EpisodeList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="TelevisionEpisodeCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href="@($"media/tv/seasons/{context.SeasonId}#episode-{context.EpisodeId}")"
Variant="Variant.Filled" Subtitle="@($"{context.ShowTitle} - S{context.Season} E{context.Episode}")"
Color="Color.Secondary" AddToCollectionClicked="@AddToCollection"
StartIcon="@Icons.Material.Filled.Check" SelectClicked="@(e => SelectClicked(context, e))"
OnClick="@(_ => ClearSelection())"> IsSelected="@IsSelected(context)"
Clear Selection IsSelectMode="@IsSelectMode()"/>
</MudButton> </FragmentLetterAnchor>
</div> </MudStack>
} </MudContainer>
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="TelevisionEpisodeCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href="@($"media/tv/seasons/{context.SeasonId}#episode-{context.EpisodeId}")"
Subtitle="@($"{context.ShowTitle} - S{context.Season} E{context.Episode}")"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -146,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddEpisodeToCollection(collection.Id, episode.EpisodeId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

25
ErsatzTV/Pages/ImageBrowser.razor

@ -5,20 +5,22 @@
@inject IDialogService Dialog @inject IDialogService Dialog
@inject IMediator Mediator @inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudForm Style="max-height: 100%">
<MudGrid> <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudItem xs="8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">Images</MudText>
<MudDivider Class="mb-6"/>
<MudCard> <MudCard>
<MudTreeView T="ImageTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true"> <MudTreeView T="ImageTreeItemViewModel" ServerData="LoadServerData" Items="@TreeItems" Hover="true" ExpandOnClick="true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
<MudTreeViewItem T="ImageTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value"> <MudTreeViewItem T="ImageTreeItemViewModel" Items="@item.Value!.TreeItems" Icon="@item.Value.Icon" CanExpand="@item.Value.CanExpand" Value="@item.Value">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: flex; align-items: center; width: 100%">
<div style="justify-self: start;"> <div style="justify-self: start; flex: 1">
<MudText>@item.Value.Text</MudText> <MudText>@item.Value.Text</MudText>
</div> </div>
<div style="justify-self: end;"> <div style="display: flex; align-items: center; justify-self: end;">
<span>@item.Value.EndText</span> <span class="d-none d-sm-flex">@item.Value.EndText</span>
<MudTooltip Text="Edit Image Folder Duration" ShowOnHover="true" ShowOnClick="false" ShowOnFocus="false"> <MudTooltip Text="Edit Image Folder Duration" ShowOnHover="true" ShowOnClick="false" ShowOnFocus="false">
<MudIconButton Icon="@Icons.Material.Filled.Edit" <MudIconButton Icon="@Icons.Material.Filled.Edit"
OnClick="@(_ => EditImageFolderDuration(item.Value))"> OnClick="@(_ => EditImageFolderDuration(item.Value))">
@ -29,6 +31,7 @@
if (!string.IsNullOrWhiteSpace(query)) if (!string.IsNullOrWhiteSpace(query))
{ {
<MudIconButton <MudIconButton
Class="d-none d-sm-flex"
Icon="@Icons.Material.Filled.Search" Icon="@Icons.Material.Filled.Search"
Href="@($"search?query={query}")"/> Href="@($"search?query={query}")"/>
} }
@ -40,9 +43,9 @@
</ItemTemplate> </ItemTemplate>
</MudTreeView> </MudTreeView>
</MudCard> </MudCard>
</MudItem> </MudContainer>
</MudGrid> </div>
</MudContainer> </MudForm>
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
@ -90,7 +93,7 @@
IDialogReference dialog = await Dialog.ShowAsync<EditImageFolderDurationDialog>("Edit Image Folder Duration", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<EditImageFolderDurationDialog>("Edit Image Folder Duration", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is { Canceled: false })
{ {
double? duration = await Mediator.Send(new UpdateImageFolderDuration(item.LibraryFolderId, result.Data as double?), _cts.Token); double? duration = await Mediator.Send(new UpdateImageFolderDuration(item.LibraryFolderId, result.Data as double?), _cts.Token);
item.UpdateDuration(duration); item.UpdateDuration(duration);

92
ErsatzTV/Pages/ImageList.razor

@ -7,68 +7,36 @@
@inherits MultiSelectBase<ImageList> @inherits MultiSelectBase<ImageList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="ImageCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href=""
Variant="Variant.Filled" ArtworkKind="ArtworkKind.Thumbnail"
Color="Color.Secondary" AddToCollectionClicked="@AddToCollection"
StartIcon="@Icons.Material.Filled.Check" SelectClicked="@(e => SelectClicked(context, e))"
OnClick="@(_ => ClearSelection())"> IsSelected="@IsSelected(context)"
Clear Selection IsSelectMode="@IsSelectMode()"/>
</MudButton> </FragmentLetterAnchor>
</div> </MudStack>
} </MudContainer>
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="ImageCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -146,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is MediaCollectionViewModel collection) if (result is { Canceled: false, Data: MediaCollectionViewModel collection })
{ {
var request = new AddImageToCollection(collection.Id, image.ImageId); var request = new AddImageToCollection(collection.Id, image.ImageId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

75
ErsatzTV/Pages/Movie.razor

@ -27,23 +27,12 @@
} }
} }
</MudContainer> </MudContainer>
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px"> <MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 100px">
<div style="display: flex; flex-direction: row;" class="mb-6"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Spacing="6">
@if (!string.IsNullOrWhiteSpace(_movie?.Poster)) @if (!string.IsNullOrWhiteSpace(_movie?.Poster))
{ {
<div class="mr-6" style="display: flex; flex-direction: column; max-height: 440px; position: relative"> <div style="display: flex; flex-direction: column; max-height: 325px; position: relative">
@if (_movie.Poster.StartsWith("http://") || _movie.Poster.StartsWith("https://")) <MudImage Src="@GetPosterUrl(_movie.Poster)" Class="rounded-lg" Style="max-height: 325px" ObjectFit="ObjectFit.Contain" />
{
<img class="mud-elevation-2"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@_movie.Poster" alt="movie poster"/>
}
else
{
<img class="mud-elevation-2"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"artwork/posters/{_movie.Poster}")" alt="movie poster"/>
}
@if (_movie.MediaItemState == MediaItemState.FileNotFound) @if (_movie.MediaItemState == MediaItemState.FileNotFound)
{ {
<div style="position: absolute; right: 10px; top: 8px;"> <div style="position: absolute; right: 10px; top: 8px;">
@ -59,8 +48,15 @@
</div> </div>
} }
<div style="display: flex; flex-direction: column; height: 100%"> <div style="display: flex; flex-direction: column; height: 100%">
<MudText Typo="Typo.h2" Class="media-item-title">@_movie?.Title</MudText> <MudStack Row="false">
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_movie?.Year</MudText> <MudHidden Invert="true" Breakpoint="Breakpoint.SmAndDown">
<MudText Typo="Typo.h4" Class="media-item-title">@_movie?.Title</MudText>
</MudHidden>
<MudHidden Invert="true" Breakpoint="Breakpoint.MdAndUp">
<MudText Typo="Typo.h2" Class="media-item-title">@_movie?.Title</MudText>
</MudHidden>
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_movie?.Year</MudText>
</MudStack>
@if (!string.IsNullOrWhiteSpace(_movie?.Plot)) @if (!string.IsNullOrWhiteSpace(_movie?.Plot))
{ {
<MudCard Elevation="2" Class="mb-6"> <MudCard Elevation="2" Class="mb-6">
@ -69,34 +65,28 @@
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
} }
<div> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="mb-6">
<MudButton Class="mb-6" <MudButton Variant="Variant.Filled"
Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add" StartIcon="@Icons.Material.Filled.Add"
OnClick="@AddToCollection"> OnClick="@AddToCollection">
Add To Collection Add To Collection
</MudButton> </MudButton>
</div> <MudButton Variant="Variant.Filled"
<div>
<MudButton Class="mb-6"
Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add" StartIcon="@Icons.Material.Filled.PlaylistAdd"
OnClick="@AddToPlaylist"> OnClick="@AddToPlaylist">
Add To Playlist Add To Playlist
</MudButton> </MudButton>
</div>
<div>
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled"
Color="Color.Secondary" Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Info" StartIcon="@Icons.Material.Filled.Info"
OnClick="@ShowInfo"> OnClick="@ShowInfo">
Show Media Info Show Media Info
</MudButton> </MudButton>
</div> </MudStack>
</div> </div>
</div> </MudStack>
@if (_movie?.MediaItemState == MediaItemState.FileNotFound) @if (_movie?.MediaItemState == MediaItemState.FileNotFound)
{ {
<MudCard Class="mb-6"> <MudCard Class="mb-6">
@ -121,7 +111,7 @@
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
} }
<MudCard Class="mb-6"> <MudCard Class="mt-6 mb-6">
<MudCardContent> <MudCardContent>
@if (_sortedContentRatings.Any()) @if (_sortedContentRatings.Any())
{ {
@ -214,14 +204,14 @@
{ {
<MudContainer MaxWidth="MaxWidth.Large"> <MudContainer MaxWidth="MaxWidth.Large">
<MudText Class="mb-4">Actors</MudText> <MudText Class="mb-4">Actors</MudText>
</MudContainer> <MudStack Row="true" Wrap="Wrap.Wrap">
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid"> @foreach (ActorCardViewModel actor in _movie.Actors)
@foreach (ActorCardViewModel actor in _movie.Actors) {
{ <MediaCard Data="@actor"
<MediaCard Data="@actor" Href="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())"
Href="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())" ArtworkKind="ArtworkKind.Thumbnail"/>
ArtworkKind="ArtworkKind.Thumbnail"/> }
} </MudStack>
</MudContainer> </MudContainer>
} }
@ -296,7 +286,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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 AddMovieToCollection(collection.Id, MovieId), _cts.Token); await Mediator.Send(new AddMovieToCollection(collection.Id, MovieId), _cts.Token);
NavigationManager.NavigateTo($"media/collections/{collection.Id}"); NavigationManager.NavigateTo($"media/collections/{collection.Id}");
@ -310,7 +300,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options);
DialogResult result = await dialog.Result; 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 AddMovieToPlaylist(playlist.Id, MovieId), _cts.Token); await Mediator.Send(new AddMovieToPlaylist(playlist.Id, MovieId), _cts.Token);
NavigationManager.NavigateTo($"media/playlists/{playlist.Id}"); NavigationManager.NavigateTo($"media/playlists/{playlist.Id}");
@ -336,4 +326,9 @@
} }
} }
private static string GetPosterUrl(string poster)
{
return poster.StartsWith("http://") || poster.StartsWith("https://") ? poster : $"artwork/posters/{poster}";
}
} }

91
ErsatzTV/Pages/MovieList.razor

@ -9,67 +9,36 @@
@using ErsatzTV.Extensions @using ErsatzTV.Extensions
@implements IDisposable @implements IDisposable
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="MovieCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href="@($"media/movies/{context.MovieId}")"
Variant="Variant.Filled" AddToCollectionClicked="@AddToCollection"
Color="Color.Secondary" SelectClicked="@(e => SelectClicked(context, e))"
StartIcon="@Icons.Material.Filled.Check" IsSelected="@IsSelected(context)"
OnClick="@(_ => ClearSelection())"> IsSelectMode="@IsSelectMode()"/>
Clear Selection </FragmentLetterAnchor>
</MudButton> </MudStack>
</div> </MudContainer>
}
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="MovieCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href="@($"media/movies/{context.MovieId}")"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -174,7 +143,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddMovieToCollection(collection.Id, movie.MovieId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

7
ErsatzTV/Pages/MultiSelectBase.cs

@ -48,6 +48,7 @@ public class MultiSelectBase<T> : FragmentNavigationBase
{ {
SelectedItems.Clear(); SelectedItems.Clear();
_recentlySelected = None; _recentlySelected = None;
StateHasChanged();
} }
protected virtual Task RefreshData() => Task.CompletedTask; protected virtual Task RefreshData() => Task.CompletedTask;
@ -125,7 +126,7 @@ public class MultiSelectBase<T> : FragmentNavigationBase
IDialogReference dialog = IDialogReference dialog =
await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is MediaCollectionViewModel collection) if (result is { Canceled: false, Data: MediaCollectionViewModel collection })
{ {
var request = new AddItemsToCollection( var request = new AddItemsToCollection(
collection.Id, collection.Id,
@ -170,7 +171,7 @@ public class MultiSelectBase<T> : FragmentNavigationBase
parameters, parameters,
options); options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is { Canceled: false })
{ {
var itemIds = SelectedItems.Map(vm => vm.MediaItemId).ToList(); var itemIds = SelectedItems.Map(vm => vm.MediaItemId).ToList();
@ -208,7 +209,7 @@ public class MultiSelectBase<T> : FragmentNavigationBase
IDialogReference dialog = IDialogReference dialog =
await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options); await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is PlaylistViewModel playlist) if (result is { Canceled: false, Data: PlaylistViewModel playlist })
{ {
var request = new AddItemsToPlaylist( var request = new AddItemsToPlaylist(
playlist.Id, playlist.Id,

92
ErsatzTV/Pages/MusicVideoList.razor

@ -7,68 +7,36 @@
@inherits MultiSelectBase<MusicVideoList> @inherits MultiSelectBase<MusicVideoList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="MusicVideoCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href=""
Variant="Variant.Filled" ArtworkKind="ArtworkKind.Thumbnail"
Color="Color.Secondary" AddToCollectionClicked="@AddToCollection"
StartIcon="@Icons.Material.Filled.Check" SelectClicked="@(e => SelectClicked(context, e))"
OnClick="@(_ => ClearSelection())"> IsSelected="@IsSelected(context)"
Clear Selection IsSelectMode="@IsSelectMode()"/>
</MudButton> </FragmentLetterAnchor>
</div> </MudStack>
} </MudContainer>
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="MusicVideoCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -146,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddMusicVideoToCollection(collection.Id, musicVideo.MusicVideoId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

92
ErsatzTV/Pages/OtherVideoList.razor

@ -7,68 +7,36 @@
@inherits MultiSelectBase<OtherVideoList> @inherits MultiSelectBase<OtherVideoList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="OtherVideoCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href=""
Variant="Variant.Filled" ArtworkKind="ArtworkKind.Thumbnail"
Color="Color.Secondary" AddToCollectionClicked="@AddToCollection"
StartIcon="@Icons.Material.Filled.Check" SelectClicked="@(e => SelectClicked(context, e))"
OnClick="@(_ => ClearSelection())"> IsSelected="@IsSelected(context)"
Clear Selection IsSelectMode="@IsSelectMode()"/>
</MudButton> </FragmentLetterAnchor>
</div> </MudStack>
} </MudContainer>
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="OtherVideoCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -146,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddOtherVideoToCollection(collection.Id, otherVideo.OtherVideoId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

92
ErsatzTV/Pages/SongList.razor

@ -7,68 +7,36 @@
@inherits MultiSelectBase<SongList> @inherits MultiSelectBase<SongList>
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="SongCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href=""
Variant="Variant.Filled" ArtworkKind="ArtworkKind.Thumbnail"
Color="Color.Secondary" AddToCollectionClicked="@AddToCollection"
StartIcon="@Icons.Material.Filled.Check" SelectClicked="@(e => SelectClicked(context, e))"
OnClick="@(_ => ClearSelection())"> IsSelected="@IsSelected(context)"
Clear Selection IsSelectMode="@IsSelectMode()"/>
</MudButton> </FragmentLetterAnchor>
</div> </MudStack>
} </MudContainer>
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="SongCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -146,7 +114,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddSongToCollection(collection.Id, song.SongId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

102
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -26,26 +26,19 @@
} }
} }
</MudContainer> </MudContainer>
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px"> <MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 100px">
<div style="display: flex; flex-direction: row;" class="mb-6"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Spacing="6">
@if (!string.IsNullOrWhiteSpace(_show?.Poster)) <MudImage Src="@GetPosterUrl(_show.Poster)" Class="rounded-lg" Style="max-height: 325px" ObjectFit="ObjectFit.Contain" />
{
if (_show.Poster.StartsWith("http://") || _show.Poster.StartsWith("https://"))
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@_show.Poster" alt="movie poster"/>
}
else
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"artwork/posters/{_show.Poster}")" alt="movie poster"/>
}
}
<div style="display: flex; flex-direction: column; height: 100%"> <div style="display: flex; flex-direction: column; height: 100%">
<MudText Typo="Typo.h2" Class="media-item-title">@_show?.Title</MudText> <MudStack Row="false">
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_show?.Year</MudText> <MudHidden Invert="true" Breakpoint="Breakpoint.SmAndDown">
<MudText Typo="Typo.h4" Class="media-item-title">@_show?.Title</MudText>
</MudHidden>
<MudHidden Invert="true" Breakpoint="Breakpoint.MdAndUp">
<MudText Typo="Typo.h2" Class="media-item-title">@_show?.Title</MudText>
</MudHidden>
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_show?.Year</MudText>
</MudStack>
@if (!string.IsNullOrWhiteSpace(_show?.Plot)) @if (!string.IsNullOrWhiteSpace(_show?.Plot))
{ {
<MudCard Elevation="2" Class="mb-6"> <MudCard Elevation="2" Class="mb-6">
@ -54,31 +47,29 @@
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
} }
<div> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="mb-6">
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add" StartIcon="@Icons.Material.Filled.Add"
OnClick="@AddToCollection"> OnClick="@AddToCollection">
Add To Collection Add To Collection
</MudButton> </MudButton>
<MudButton Class="ml-3" <MudButton Variant="Variant.Filled"
Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.PlaylistAdd" StartIcon="@Icons.Material.Filled.PlaylistAdd"
OnClick="@AddToPlaylist"> OnClick="@AddToPlaylist">
Add To Playlist Add To Playlist
</MudButton> </MudButton>
<MudButton Class="ml-3" <MudButton Variant="Variant.Filled"
Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Schedule" StartIcon="@Icons.Material.Filled.Schedule"
OnClick="@AddToSchedule"> OnClick="@AddToSchedule">
Add To Schedule Add To Schedule
</MudButton> </MudButton>
</div> </MudStack>
</div> </div>
</div> </MudStack>
<MudCard Class="mb-6"> <MudCard Class="mt-6 mb-6">
<MudCardContent> <MudCardContent>
@if (_sortedContentRatings.Any()) @if (_sortedContentRatings.Any())
{ {
@ -157,29 +148,27 @@
</MudContainer> </MudContainer>
<MudContainer MaxWidth="MaxWidth.Large"> <MudContainer MaxWidth="MaxWidth.Large">
<MudText Class="mb-4">Seasons</MudText> <MudText Class="mb-4">Seasons</MudText>
</MudContainer> <MudStack Row="true" Wrap="Wrap.Wrap">
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid"> @foreach (TelevisionSeasonCardViewModel card in _data.Cards)
@foreach (TelevisionSeasonCardViewModel card in _data.Cards) {
<MediaCard Data="@card" Placeholder="@card.Placeholder"
Href="@($"media/tv/seasons/{card.TelevisionSeasonId}")"
AddToCollectionClicked="@AddSeasonToCollection"/>
}
</MudStack>
@if ((_show?.Actors?.Count ?? 0) > 0)
{ {
<MediaCard Data="@card" Placeholder="@card.Placeholder" <MudText Class="mb-4">Actors</MudText>
Href="@($"media/tv/seasons/{card.TelevisionSeasonId}")" <MudStack Row="true" Wrap="Wrap.Wrap">
AddToCollectionClicked="@AddSeasonToCollection"/> @foreach (ActorCardViewModel actor in _show.Actors)
{
<MediaCard Data="@actor"
Href="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())"
ArtworkKind="ArtworkKind.Thumbnail"/>
}
</MudStack>
} }
</MudContainer> </MudContainer>
@if ((_show?.Actors?.Count ?? 0) > 0)
{
<MudContainer MaxWidth="MaxWidth.Large">
<MudText Class="mb-4">Actors</MudText>
</MudContainer>
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
@foreach (ActorCardViewModel actor in _show.Actors)
{
<MediaCard Data="@actor"
Href="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())"
ArtworkKind="ArtworkKind.Thumbnail"/>
}
</MudContainer>
}
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
@ -195,8 +184,8 @@
private List<string> _sortedGenres = []; private List<string> _sortedGenres = [];
private List<string> _sortedTags = []; private List<string> _sortedTags = [];
private int _pageSize => 100; private static int PageSize => 100;
private readonly int _pageNumber = 1; private const int PageNumber = 1;
private TelevisionSeasonCardResultsViewModel _data = new(0, new List<TelevisionSeasonCardViewModel>(), null); private TelevisionSeasonCardResultsViewModel _data = new(0, new List<TelevisionSeasonCardViewModel>(), null);
@ -222,7 +211,7 @@
_sortedNetworks = _show.Networks.OrderBy(n => n).ToList(); _sortedNetworks = _show.Networks.OrderBy(n => n).ToList();
} }
_data = await Mediator.Send(new GetTelevisionSeasonCards(ShowId, _pageNumber, _pageSize), _cts.Token); _data = await Mediator.Send(new GetTelevisionSeasonCards(ShowId, PageNumber, PageSize), _cts.Token);
} }
private async Task AddToCollection() private async Task AddToCollection()
@ -232,7 +221,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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 AddShowToCollection(collection.Id, ShowId), _cts.Token); await Mediator.Send(new AddShowToCollection(collection.Id, ShowId), _cts.Token);
NavigationManager.NavigateTo($"media/collections/{collection.Id}"); NavigationManager.NavigateTo($"media/collections/{collection.Id}");
@ -246,7 +235,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options);
DialogResult result = await dialog.Result; 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 AddShowToPlaylist(playlist.Id, ShowId), _cts.Token); await Mediator.Send(new AddShowToPlaylist(playlist.Id, ShowId), _cts.Token);
NavigationManager.NavigateTo($"media/playlists/{playlist.Id}"); NavigationManager.NavigateTo($"media/playlists/{playlist.Id}");
@ -260,7 +249,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToScheduleDialog>("Add To Schedule", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToScheduleDialog>("Add To Schedule", parameters, options);
DialogResult result = await dialog.Result; 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.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, 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");
@ -276,7 +265,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddSeasonToCollection(collection.Id, season.TelevisionSeasonId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, _cts.Token); Either<BaseError, Unit> addResult = await Mediator.Send(request, _cts.Token);
@ -291,4 +280,9 @@
} }
} }
private static string GetPosterUrl(string poster)
{
return poster.StartsWith("http://") || poster.StartsWith("https://") ? poster : $"artwork/posters/{poster}";
}
} }

90
ErsatzTV/Pages/TelevisionShowList.razor

@ -9,67 +9,35 @@
@using ErsatzTV.Extensions @using ErsatzTV.Extensions
@implements IDisposable @implements IDisposable
<MudPaper Square="true" Style="display: flex; height: 64px; width: 100%; z-index: 100;"> <MudForm Style="max-height: 100%">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
@if (IsSelectMode()) <MediaCardPager Query="@_query"
{ PageNumber="@PageNumber"
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> PageSize="@PageSize"
<div style="margin-left: auto"> TotalCount="@_data.Count"
<MudButton Variant="Variant.Filled" NextPage="@NextPage"
Color="Color.Primary" PrevPage="@PrevPage"
StartIcon="@Icons.Material.Filled.Add" AddSelectionToCollection="@AddSelectionToCollection"
OnClick="@(_ => AddSelectionToCollection())"> AddSelectionToPlaylist="@AddSelectionToPlaylist"
Add To Collection ClearSelection="@ClearSelection"
</MudButton> IsSelectMode="@IsSelectMode"
<MudButton Class="ml-3" SelectionLabel="@SelectionLabel"/>
Variant="Variant.Filled" </MudPaper>
Color="Color.Primary" <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
StartIcon="@Icons.Material.Filled.PlaylistAdd" <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
OnClick="@(_ => AddSelectionToPlaylist())"> <MudStack Row="true" Wrap="Wrap.Wrap">
Add To Playlist <FragmentLetterAnchor TCard="TelevisionShowCardViewModel" Cards="@_data.Cards">
</MudButton> <MediaCard Data="@context"
<MudButton Class="ml-3" Href="@($"media/tv/shows/{context.TelevisionShowId}")"
Variant="Variant.Filled" AddToCollectionClicked="@AddToCollection"
Color="Color.Secondary" SelectClicked="@(e => SelectClicked(context, e))"
StartIcon="@Icons.Material.Filled.Check" IsSelected="@IsSelected(context)"
OnClick="@(_ => ClearSelection())"> IsSelectMode="@IsSelectMode()"/>
Clear Selection </FragmentLetterAnchor>
</MudButton> </MudStack>
</div> </MudContainer>
}
else
{
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText>
<div style="max-width: 300px; width: 33%;">
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
</MudIconButton>
</MudPaper>
</div>
}
</div> </div>
</MudPaper> </MudForm>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
<FragmentLetterAnchor TCard="TelevisionShowCardViewModel" Cards="@_data.Cards">
<MediaCard Data="@context"
Href="@($"media/tv/shows/{context.TelevisionShowId}")"
AddToCollectionClicked="@AddToCollection"
SelectClicked="@(e => SelectClicked(context, e))"
IsSelected="@IsSelected(context)"
IsSelectMode="@IsSelectMode()"/>
</FragmentLetterAnchor>
</MudContainer>
</MudContainer>
@if (_data.PageMap is not null) @if (_data.PageMap is not null)
{ {
<LetterBar PageMap="@_data.PageMap" <LetterBar PageMap="@_data.PageMap"
@ -174,7 +142,7 @@
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options);
DialogResult result = await dialog.Result; 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); var request = new AddShowToCollection(collection.Id, show.TelevisionShowId);
Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken); Either<BaseError, Unit> addResult = await Mediator.Send(request, CancellationToken);

108
ErsatzTV/Shared/MediaCardPager.razor

@ -0,0 +1,108 @@
@using System.Globalization
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6">
@if (IsSelectMode())
{
<div style="align-items: center; display: flex; width: 100%;">
<div style="flex: 1;">
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText>
</div>
<div style="margin-left: auto" class="d-none d-md-flex">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="@(_ => AddSelectionToCollection())">
Add To Collection
</MudButton>
<MudButton Class="ml-3"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.PlaylistAdd"
OnClick="@(_ => AddSelectionToPlaylist())">
Add To Playlist
</MudButton>
<MudButton Class="ml-3"
Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Check"
OnClick="@(_ => ClearSelection())">
Clear Selection
</MudButton>
</div>
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Add To Collection" OnClick="@AddSelectionToCollection"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add To Playlist" OnClick="AddSelectionToPlaylist"/>
<MudMenuItem Icon="@Icons.Material.Filled.Check" Label="Clear Selection" OnClick="ClearSelection"/>
</MudMenu>
</div>
</div>
}
else
{
<div style="align-items: center; display: flex; width: 100%">
<div style="flex: 1">
<MudText Class="d-none d-md-flex">@Query</MudText>
</div>
<div>
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
OnClick="@PrevPage"
Disabled="@(PageNumber <= 1)">
</MudIconButton>
<MudText Style="flex-grow: 1"
Align="Align.Center">
@PaddedString(Math.Min((PageNumber - 1) * PageSize + 1, TotalCount), TotalCount) - @PaddedString(Math.Min(TotalCount, PageNumber * PageSize), TotalCount) of @TotalCount
</MudText>
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= TotalCount)">
</MudIconButton>
</MudPaper>
</div>
<div style="flex: 1"></div>
</div>
}
</div>
@code {
[Parameter]
public string Query { get; set; }
[Parameter]
public int PageNumber { get; set; }
[Parameter]
public int PageSize { get; set; }
[Parameter]
public int TotalCount { get; set; }
[Parameter]
public EventCallback PrevPage { get; set; }
[Parameter]
public EventCallback NextPage { get; set; }
[Parameter]
public Func<bool> IsSelectMode { get; set; }
[Parameter]
public Func<string> SelectionLabel { get; set; }
[Parameter]
public Func<Task> AddSelectionToCollection { get; set; }
[Parameter]
public Func<Task> AddSelectionToPlaylist { get; set; }
[Parameter]
public Action ClearSelection { get; set; }
private static MarkupString PaddedString(int value, int maximum)
{
int length = maximum.ToString(CultureInfo.InvariantCulture).Length;
return (MarkupString)value.ToString(CultureInfo.InvariantCulture).PadLeft(length, ' ').Replace(" ", "&nbsp;");
}
}

3
ErsatzTV/wwwroot/css/site.css

@ -92,9 +92,8 @@
} }
.fanart-container > .fanart-tint { .fanart-container > .fanart-tint {
background: linear-gradient(360deg, black, transparent); background: linear-gradient(rgba(39, 39, 39, 0.47) 0%, rgb(39, 39, 39) 100%);
height: 400px; height: 400px;
opacity: 0.85;
position: absolute; position: absolute;
width: 100%; width: 100%;
z-index: 1; z-index: 1;

Loading…
Cancel
Save