add media card overflow menu (#2180)
* add media card overflow menu * remove commented code
This commit is contained in:
@@ -128,6 +128,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- `Playlist Item Size`: will play all media items from the current playlist item before continuing to the next schedule item
|
- `Playlist Item Size`: will play all media items from the current playlist item before continuing to the next schedule item
|
||||||
- `Multi-Episode Group Size`: will play all media items from the current multi-part episode group, or one ungrouped media item
|
- `Multi-Episode Group Size`: will play all media items from the current multi-part episode group, or one ungrouped media item
|
||||||
- Change watermark width and margins to allow decimals
|
- Change watermark width and margins to allow decimals
|
||||||
|
- Move `Add To Collection` button to overflow menu on all media cards, and add `Show Media Info` to overflow menu
|
||||||
|
- This allows showing media info for all media kinds
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix QSV acceleration in docker with older Intel devices
|
- Fix QSV acceleration in docker with older Intel devices
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
namespace ErsatzTV.Application.MediaCards;
|
namespace ErsatzTV.Application.MediaCards;
|
||||||
|
|
||||||
public record ActorCardViewModel(int Id, string Name, string Role, string Thumb, MediaItemState State) :
|
public record ActorCardViewModel(int Id, string Name, string Role, string Thumb, MediaItemState State) :
|
||||||
MediaCardViewModel(Id, Name, Role, Name, Thumb, State);
|
MediaCardViewModel(Id, Name, Role, Name, Thumb, State, HasMediaInfo: false);
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ public record ArtistCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State);
|
State,
|
||||||
|
HasMediaInfo: false);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public record ImageCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ public record MediaCardViewModel(
|
|||||||
string Subtitle,
|
string Subtitle,
|
||||||
string SortTitle,
|
string SortTitle,
|
||||||
string Poster,
|
string Poster,
|
||||||
MediaItemState State);
|
MediaItemState State,
|
||||||
|
bool HasMediaInfo);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public record MovieCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ public record MusicVideoCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public record OtherVideoCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public record RemoteStreamCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public record SongCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State)
|
State,
|
||||||
|
HasMediaInfo: true)
|
||||||
{
|
{
|
||||||
public int CustomIndex { get; set; }
|
public int CustomIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,5 @@ public record TelevisionEpisodeCardViewModel(
|
|||||||
$"Episode {Episode}",
|
$"Episode {Episode}",
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State);
|
State,
|
||||||
|
HasMediaInfo: true);
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ public record TelevisionSeasonCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State);
|
State,
|
||||||
|
HasMediaInfo: false);
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ public record TelevisionShowCardViewModel(
|
|||||||
Subtitle,
|
Subtitle,
|
||||||
SortTitle,
|
SortTitle,
|
||||||
Poster,
|
Poster,
|
||||||
State);
|
State,
|
||||||
|
HasMediaInfo: false);
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ public record MediaCollectionViewModel(
|
|||||||
string.Empty,
|
string.Empty,
|
||||||
Name,
|
Name,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
State);
|
State,
|
||||||
|
HasMediaInfo: false);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class GetMediaItemInfoHandler : IRequestHandler<GetMediaItemInfo, Either<
|
|||||||
.ThenInclude(l => l.MediaSource)
|
.ThenInclude(l => l.MediaSource)
|
||||||
// TODO: support all media types here
|
// TODO: support all media types here
|
||||||
.Include(i => (i as Movie).MovieMetadata)
|
.Include(i => (i as Movie).MovieMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(mm => mm.Subtitles)
|
||||||
.Include(i => (i as Movie).MediaVersions)
|
.Include(i => (i as Movie).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as Movie).MediaVersions)
|
.Include(i => (i as Movie).MediaVersions)
|
||||||
@@ -40,40 +40,42 @@ public class GetMediaItemInfoHandler : IRequestHandler<GetMediaItemInfo, Either<
|
|||||||
.Include(i => (i as Episode).MediaVersions)
|
.Include(i => (i as Episode).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
.Include(i => (i as Episode).EpisodeMetadata)
|
.Include(i => (i as Episode).EpisodeMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(em => em.Subtitles)
|
||||||
.Include(i => (i as Episode).Season)
|
.Include(i => (i as Episode).Season)
|
||||||
.ThenInclude(s => s.Show)
|
.ThenInclude(s => s.Show)
|
||||||
.ThenInclude(s => s.ShowMetadata)
|
.ThenInclude(s => s.ShowMetadata)
|
||||||
.Include(i => (i as OtherVideo).OtherVideoMetadata)
|
.Include(i => (i as OtherVideo).OtherVideoMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(ovm => ovm.Subtitles)
|
||||||
.Include(i => (i as OtherVideo).MediaVersions)
|
.Include(i => (i as OtherVideo).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as OtherVideo).MediaVersions)
|
.Include(i => (i as OtherVideo).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
.Include(i => (i as Image).ImageMetadata)
|
.Include(i => (i as Image).ImageMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(im => im.Subtitles)
|
||||||
.Include(i => (i as Image).MediaVersions)
|
.Include(i => (i as Image).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as Image).MediaVersions)
|
.Include(i => (i as Image).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
.Include(i => (i as RemoteStream).RemoteStreamMetadata)
|
.Include(i => (i as RemoteStream).RemoteStreamMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(rsm => rsm.Subtitles)
|
||||||
.Include(i => (i as RemoteStream).MediaVersions)
|
.Include(i => (i as RemoteStream).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as RemoteStream).MediaVersions)
|
.Include(i => (i as RemoteStream).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
.Include(i => (i as Song).SongMetadata)
|
.Include(i => (i as Song).SongMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(sm => sm.Subtitles)
|
||||||
.Include(i => (i as Song).MediaVersions)
|
.Include(i => (i as Song).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as Song).MediaVersions)
|
.Include(i => (i as Song).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
.Include(i => (i as MusicVideo).MusicVideoMetadata)
|
.Include(i => (i as MusicVideo).MusicVideoMetadata)
|
||||||
.ThenInclude(mv => mv.Subtitles)
|
.ThenInclude(mvm => mvm.Subtitles)
|
||||||
.Include(i => (i as MusicVideo).MediaVersions)
|
.Include(i => (i as MusicVideo).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Chapters)
|
.ThenInclude(mv => mv.Chapters)
|
||||||
.Include(i => (i as MusicVideo).MediaVersions)
|
.Include(i => (i as MusicVideo).MediaVersions)
|
||||||
.ThenInclude(mv => mv.Streams)
|
.ThenInclude(mv => mv.Streams)
|
||||||
|
.Include(i => (i as MusicVideo).Artist)
|
||||||
|
.ThenInclude(a => a.ArtistMetadata)
|
||||||
.SelectOneAsync(i => i.Id, i => i.Id == request.Id)
|
.SelectOneAsync(i => i.Id, i => i.Id == request.Id)
|
||||||
.MapT(Project);
|
.MapT(Project);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
@using ErsatzTV.Application.MediaCards
|
@using ErsatzTV.Application.MediaCards
|
||||||
|
@using ErsatzTV.Application.MediaItems
|
||||||
@using static Prelude
|
@using static Prelude
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IDialogService Dialog
|
||||||
|
@inject IMediator Mediator
|
||||||
|
|
||||||
<div class="@((ContainerClass ?? "media-card-container mr-6") + " pb-3")" id="@($"item_{Data.MediaItemId}")">
|
<div class="@((ContainerClass ?? "media-card-container mr-6") + " pb-3")" id="@($"item_{Data.MediaItemId}")">
|
||||||
@if (SelectClicked.HasDelegate || !string.IsNullOrWhiteSpace(Href))
|
@if (SelectClicked.HasDelegate || !string.IsNullOrWhiteSpace(Href))
|
||||||
@@ -35,32 +38,53 @@
|
|||||||
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning"/>
|
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="media-card-overlay" style="">
|
<MudElement HtmlTag="div" Class="@_overlayClass" Style="">
|
||||||
<MudButton Style="height: 100%; width: 100%"
|
<MudButton Style="height: 100%; width: 100%" OnClick="@OnOverlayClick" />
|
||||||
OnClick="OnOverlayClick">
|
|
||||||
</MudButton>
|
|
||||||
@if (SelectClicked.HasDelegate)
|
@if (SelectClicked.HasDelegate)
|
||||||
{
|
{
|
||||||
<MudIconButton Color="@SelectColor"
|
<MudIconButton Color="@SelectColor"
|
||||||
Icon="@(IsSelected ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank)"
|
Icon="@(IsSelected ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank)"
|
||||||
Style="left: 0; position: absolute; top: 0;"
|
Style="left: 0; position: absolute; top: 0;"
|
||||||
|
Class="media-card-select-checkbox"
|
||||||
OnClick="@(e => SelectClicked.InvokeAsync(e))"/>
|
OnClick="@(e => SelectClicked.InvokeAsync(e))"/>
|
||||||
}
|
}
|
||||||
@if (AddToCollectionClicked.HasDelegate && !IsSelectMode)
|
@if (!IsSelectMode)
|
||||||
{
|
{
|
||||||
<MudIconButton Color="Color.Tertiary"
|
@if (AddToCollectionClicked.HasDelegate || AddToPlaylistClicked.HasDelegate || Data.HasMediaInfo)
|
||||||
Icon="@Icons.Material.Filled.PlaylistAdd"
|
{
|
||||||
Style="bottom: 0; left: 0; position: absolute;"
|
<MudMenu Icon="@Icons.Material.Filled.MoreVert"
|
||||||
OnClick="@(() => AddToCollectionClicked.InvokeAsync(Data))"/>
|
Style="bottom: 0; left: 0; position: absolute;"
|
||||||
|
OpenChanged="@OnMenuOpenChanged">
|
||||||
|
@if (AddToCollectionClicked.HasDelegate)
|
||||||
|
{
|
||||||
|
<MudMenuItem Icon="@Icons.Material.Filled.Add"
|
||||||
|
Label="Add To Collection"
|
||||||
|
OnClick="@(() => AddToCollectionClicked.InvokeAsync(Data))"/>
|
||||||
|
}
|
||||||
|
@if (AddToPlaylistClicked.HasDelegate)
|
||||||
|
{
|
||||||
|
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd"
|
||||||
|
Label="Add To Playlist"
|
||||||
|
OnClick="@(() => AddToPlaylistClicked.InvokeAsync(Data))"/>
|
||||||
|
}
|
||||||
|
@if (Data.HasMediaInfo)
|
||||||
|
{
|
||||||
|
<MudMenuItem Icon="@Icons.Material.Filled.Info"
|
||||||
|
Label="Show Media Info"
|
||||||
|
OnClick="@ShowMediaInfo"/>
|
||||||
|
}
|
||||||
|
</MudMenu>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (DeleteClicked.HasDelegate)
|
||||||
|
{
|
||||||
|
<MudIconButton Color="Color.Error"
|
||||||
|
Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Style="bottom: 0; position: absolute; right: 0;"
|
||||||
|
OnClick="@(() => DeleteClicked.InvokeAsync(Data))"/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@if (DeleteClicked.HasDelegate && !IsSelectMode)
|
</MudElement>
|
||||||
{
|
|
||||||
<MudIconButton Color="Color.Error"
|
|
||||||
Icon="@Icons.Material.Filled.Delete"
|
|
||||||
Style="bottom: 0; position: absolute; right: 0;"
|
|
||||||
OnClick="@(() => DeleteClicked.InvokeAsync(Data))"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -117,6 +141,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<MediaCardViewModel> AddToCollectionClicked { get; set; }
|
public EventCallback<MediaCardViewModel> AddToCollectionClicked { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<MediaCardViewModel> AddToPlaylistClicked { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<MouseEventArgs> SelectClicked { get; set; }
|
public EventCallback<MouseEventArgs> SelectClicked { get; set; }
|
||||||
|
|
||||||
@@ -132,6 +159,8 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool IsRemoteArtwork { get; set; }
|
public bool IsRemoteArtwork { get; set; }
|
||||||
|
|
||||||
|
private string _overlayClass = "media-card-overlay";
|
||||||
|
|
||||||
private string GetPlaceholder(string sortTitle)
|
private string GetPlaceholder(string sortTitle)
|
||||||
{
|
{
|
||||||
if (Placeholder != null)
|
if (Placeholder != null)
|
||||||
@@ -173,4 +202,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMenuOpenChanged(bool open)
|
||||||
|
{
|
||||||
|
_overlayClass = open
|
||||||
|
? "media-card-overlay media-card-overlay-menu-open"
|
||||||
|
: "media-card-overlay";
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowMediaInfo()
|
||||||
|
{
|
||||||
|
Either<BaseError, MediaItemInfo> maybeInfo = await Mediator.Send(new GetMediaItemInfo(Data.MediaItemId));
|
||||||
|
foreach (MediaItemInfo info in maybeInfo.RightToSeq())
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters { { "MediaItemInfo", info } };
|
||||||
|
var options = new DialogOptions { CloseButton = true, CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
|
||||||
|
IDialogReference dialog = await Dialog.ShowAsync<MediaItemInfoDialog>(Data.Title, parameters, options);
|
||||||
|
DialogResult _ = await dialog.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -31,15 +31,6 @@
|
|||||||
|
|
||||||
.media-card-poster-placeholder { font-weight: bold; }
|
.media-card-poster-placeholder { font-weight: bold; }
|
||||||
|
|
||||||
.media-card-menu {
|
|
||||||
bottom: 0;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-card:hover .media-card-menu { display: block; }
|
|
||||||
|
|
||||||
.media-card-overlay {
|
.media-card-overlay {
|
||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -54,6 +45,10 @@
|
|||||||
|
|
||||||
.media-card-overlay:hover { opacity: 1; }
|
.media-card-overlay:hover { opacity: 1; }
|
||||||
|
|
||||||
|
.media-card-overlay:not(:hover) button.media-card-select-checkbox { opacity: 0; }
|
||||||
|
|
||||||
|
.media-card-overlay-menu-open { opacity: 1; }
|
||||||
|
|
||||||
.search-bar .mud-input { height: 42px; }
|
.search-bar .mud-input { height: 42px; }
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
|
|||||||
Reference in New Issue
Block a user