Stream custom live channels using your own media
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

345 lines
14 KiB

@page "/media/collections/{Id:int}"
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCards.Queries
@using ErsatzTV.Application.MediaCollections.Commands
@inherits MultiSelectBase<CollectionItems>
@inject NavigationManager NavigationManager
@inject ChannelWriter<IBackgroundServiceRequest> Channel
@inject IJSRuntime JsRuntime
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;">
<div style="align-items: center; display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%;" class="ml-6 mr-6">
@if (IsSelectMode())
{
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText>
<div style="margin-left: auto">
<MudButton Variant="Variant.Filled"
Color="Color.Error"
StartIcon="@Icons.Material.Filled.Remove"
OnClick="@(_ => RemoveSelectionFromCollection(Id))">
Remove From Collection
</MudButton>
<MudButton Class="ml-3"
Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Check"
OnClick="@(_ => ClearSelection())">
Clear Selection
</MudButton>
</div>
}
else
{
<div style="display: flex; flex-direction: row;">
<MudText Typo="Typo.h4">@_data.Name</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Link="@($"/media/collections/{Id}/edit")"
Style="margin-bottom: auto; margin-top: auto;"/>
</div>
@if (_data.MovieCards.Any())
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#movies")">@_data.MovieCards.Count Movies</MudLink>
}
@if (_data.ShowCards.Any())
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#shows")">@_data.ShowCards.Count Shows</MudLink>
}
@if (_data.SeasonCards.Any())
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#seasons")">@_data.SeasonCards.Count Seasons</MudLink>
}
@if (_data.EpisodeCards.Any())
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#episodes")">@_data.EpisodeCards.Count Episodes</MudLink>
}
@if (_data.MusicVideoCards.Any())
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#music_videos")">@_data.MusicVideoCards.Count Music Videos</MudLink>
}
@if (SupportsCustomOrdering())
{
<div style="margin-left: auto">
<MudSwitch T="bool"
Checked="@_data.UseCustomPlaybackOrder"
Color="Color.Primary"
CheckedChanged="@OnUseCustomOrderChanged"
Label="Use Custom Playback Order"/>
</div>
}
}
</div>
</MudPaper>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
@if (_data.MovieCards.Any())
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "movies" } })">
Movies
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid" UserAttributes="@(new Dictionary<string, object> { { "id", "sortable-collection" } })">
@foreach (MovieCardViewModel card in OrderMovies(_data.MovieCards))
{
<MediaCard Data="@card"
Link="@($"/media/movies/{card.MovieId}")"
DeleteClicked="@RemoveMovieFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data.ShowCards.Any())
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "shows" } })">
Television Shows
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle))
{
<MediaCard Data="@card"
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
DeleteClicked="@RemoveShowFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data.SeasonCards.Any())
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "seasons" } })">
Television Seasons
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle))
{
<MediaCard Data="@card"
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"
DeleteClicked="@RemoveSeasonFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data.EpisodeCards.Any())
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "episodes" } })">
Television Episodes
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired))
{
<MediaCard Data="@card"
Link="@($"/media/tv/seasons/{card.SeasonId}#episode-{card.EpisodeId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"
ContainerClass="media-card-episode-container mx-2"
CardClass="media-card-episode"
DeleteClicked="@(_ => RemoveEpisodeFromCollection(card))"
ArtworkKind="@ArtworkKind.Thumbnail"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data.MusicVideoCards.Any())
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "music_videos" } })">
Music Videos
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (MusicVideoCardViewModel card in _data.MusicVideoCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Link=""
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveMusicVideoFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
</MudContainer>
@code {
[Parameter]
public int Id { get; set; }
private CollectionCardResultsViewModel _data;
private bool SupportsCustomOrdering() =>
_data.MovieCards.Any() && !_data.ShowCards.Any() && !_data.SeasonCards.Any() && !_data.EpisodeCards.Any();
protected override async Task OnParametersSetAsync() => await RefreshData();
protected override async Task RefreshData()
{
Either<BaseError, CollectionCardResultsViewModel> maybeResult =
await Mediator.Send(new GetCollectionCards(Id));
maybeResult.Match(
result => _data = result,
error => NavigationManager.NavigateTo("404"));
}
private IOrderedEnumerable<MovieCardViewModel> OrderMovies(List<MovieCardViewModel> movies)
{
if (_data.UseCustomPlaybackOrder)
{
return movies.OrderBy(m => m.CustomIndex);
}
return movies.OrderBy(m => m.SortTitle);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JsRuntime.InvokeVoidAsync("sortableCollection", Id);
if (_data.UseCustomPlaybackOrder)
{
await JsRuntime.InvokeVoidAsync("enableSorting");
}
else
{
await JsRuntime.InvokeVoidAsync("disableSorting");
}
await base.OnAfterRenderAsync(firstRender);
}
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{
List<MediaCardViewModel> GetSortedItems()
{
return _data.MovieCards.OrderBy(m => m.SortTitle)
.Append<MediaCardViewModel>(_data.ShowCards.OrderBy(s => s.SortTitle))
.Append(_data.SeasonCards.OrderBy(s => s.SortTitle))
.Append(_data.EpisodeCards.OrderBy(ep => ep.Aired))
.Append(_data.MusicVideoCards.OrderBy(mv => mv.SortTitle))
.ToList();
}
SelectClicked(GetSortedItems, card, e);
}
private async Task RemoveMovieFromCollection(MediaCardViewModel vm)
{
if (vm is MovieCardViewModel movie)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = new List<int> { movie.MovieId }
};
await RemoveItemsWithConfirmation("movie", $"{movie.Title} ({movie.Subtitle})", request);
}
}
private async Task RemoveMusicVideoFromCollection(MediaCardViewModel vm)
{
if (vm is MusicVideoCardViewModel musicVideo)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = new List<int> { musicVideo.MusicVideoId }
};
await RemoveItemsWithConfirmation("music video", $"{musicVideo.Title} ({musicVideo.Subtitle})", request);
}
}
private async Task RemoveShowFromCollection(MediaCardViewModel vm)
{
if (vm is TelevisionShowCardViewModel show)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = new List<int> { show.TelevisionShowId }
};
await RemoveItemsWithConfirmation("show", $"{show.Title} ({show.Subtitle})", request);
}
}
private async Task RemoveSeasonFromCollection(MediaCardViewModel vm)
{
if (vm is TelevisionSeasonCardViewModel season)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = new List<int> { season.TelevisionSeasonId }
};
await RemoveItemsWithConfirmation("season", $"{season.ShowTitle} - {season.Title}", request);
}
}
private async Task RemoveEpisodeFromCollection(TelevisionEpisodeCardViewModel episode)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = new List<int> { episode.EpisodeId }
};
await RemoveItemsWithConfirmation("episode", $"{episode.ShowTitle} - {episode.Title}", request);
}
private async Task RemoveItemsWithConfirmation(
string entityType,
string entityName,
RemoveItemsFromCollection request)
{
var parameters = new DialogParameters { { "EntityType", entityType }, { "EntityName", entityName } };
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialog = Dialog.Show<RemoveFromCollectionDialog>("Remove From Collection", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Cancelled)
{
await Mediator.Send(request);
await RefreshData();
}
}
private async Task OnUseCustomOrderChanged()
{
_data.UseCustomPlaybackOrder = !_data.UseCustomPlaybackOrder;
var request = new UpdateCollection(Id, _data.Name) { UseCustomPlaybackOrder = _data.UseCustomPlaybackOrder };
await Mediator.Send(request);
}
}