* more cancellation tokens and fixes * so much cancellation token * fix mysql playout builds
819 lines
35 KiB
Plaintext
819 lines
35 KiB
Plaintext
@page "/media/trash"
|
|
@using ErsatzTV.Application.Maintenance
|
|
@using ErsatzTV.Application.MediaCards
|
|
@using ErsatzTV.Application.Search
|
|
@using ErsatzTV.Core.Search
|
|
@using ErsatzTV.Extensions
|
|
@inherits MultiSelectBase<Search>
|
|
@inject NavigationManager NavigationManager
|
|
@inject PersistentComponentState ApplicationState
|
|
|
|
<MudForm Style="max-height: 100%">
|
|
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100;">
|
|
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%; align-items: center" class="ml-6 mr-6">
|
|
@if (IsSelectMode())
|
|
{
|
|
<div class="flex-grow-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.Error"
|
|
StartIcon="@Icons.Material.Filled.Delete"
|
|
OnClick="@(_ => DeleteFromDatabase())">
|
|
Delete From Database
|
|
</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.Delete" Label="Delete From Database" OnClick="DeleteFromDatabase"/>
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Check" Label="Clear Selection" OnClick="ClearSelection"/>
|
|
</MudMenu>
|
|
</div>
|
|
}
|
|
else if (IsNotEmpty)
|
|
{
|
|
<div style="align-items: center; display: flex; width: 100%" class="d-none d-md-flex">
|
|
@if (_movies?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#movies")" Style="margin-bottom: auto; margin-top: auto">@_movies.Count Movies</MudLink>
|
|
}
|
|
|
|
@if (_shows?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#shows")" Style="margin-bottom: auto; margin-top: auto">@_shows.Count Shows</MudLink>
|
|
}
|
|
|
|
@if (_seasons?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#seasons")" Style="margin-bottom: auto; margin-top: auto">@_seasons.Count Seasons</MudLink>
|
|
}
|
|
|
|
@if (_episodes?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#episodes")" Style="margin-bottom: auto; margin-top: auto">@_episodes.Count Episodes</MudLink>
|
|
}
|
|
|
|
@if (_artists?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#artists")" Style="margin-bottom: auto; margin-top: auto">@_artists.Count Artists</MudLink>
|
|
}
|
|
|
|
@if (_musicVideos?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#music_videos")" Style="margin-bottom: auto; margin-top: auto">@_musicVideos.Count Music Videos</MudLink>
|
|
}
|
|
|
|
@if (_otherVideos?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#other_videos")" Style="margin-bottom: auto; margin-top: auto">@_otherVideos.Count Other Videos</MudLink>
|
|
}
|
|
|
|
@if (_songs?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#songs")" Style="margin-bottom: auto; margin-top: auto">@_songs.Count Songs</MudLink>
|
|
}
|
|
|
|
@if (_images?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#images")" Style="margin-bottom: auto; margin-top: auto">@_images.Count Images</MudLink>
|
|
}
|
|
|
|
@if (_remoteStreams?.Cards.Count > 0)
|
|
{
|
|
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#remote_streams")" Style="margin-bottom: auto; margin-top: auto">@_remoteStreams.Count Remote Streams</MudLink>
|
|
}
|
|
<div class="flex-grow-1 d-none d-md-flex"></div>
|
|
<div>
|
|
<MudButton Variant="@Variant.Filled"
|
|
Color="@Color.Error"
|
|
StartIcon="@Icons.Material.Filled.DeleteForever"
|
|
OnClick="@(_ => EmptyTrash())">
|
|
Empty Trash
|
|
</MudButton>
|
|
</div>
|
|
</div>
|
|
<div style="align-items: center; display: flex; width: 100%" class="d-md-none">
|
|
<div class="flex-grow-1"></div>
|
|
<div>
|
|
<MudButton Variant="@Variant.Filled"
|
|
Color="@Color.Error"
|
|
StartIcon="@Icons.Material.Filled.DeleteForever"
|
|
OnClick="@(_ => EmptyTrash())">
|
|
Empty Trash
|
|
</MudButton>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<MudText>Nothing to see here...</MudText>
|
|
}
|
|
</div>
|
|
</MudPaper>
|
|
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
|
@if (_movies?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "movies" } })">
|
|
Movies
|
|
</MudText>
|
|
@if (_movies.Count > 50)
|
|
{
|
|
<MudLink Href="@GetMoviesLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (MovieCardViewModel card in _movies.Cards.OrderBy(m => m.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href="@($"media/movies/{card.MovieId}")"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_shows?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "shows" } })">
|
|
Shows
|
|
</MudText>
|
|
@if (_shows.Count > 50)
|
|
{
|
|
<MudLink Href="@GetShowsLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (TelevisionShowCardViewModel card in _shows.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href="@($"media/tv/shows/{card.TelevisionShowId}")"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_seasons?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "seasons" } })">
|
|
Seasons
|
|
</MudText>
|
|
@if (_seasons.Count > 50)
|
|
{
|
|
<MudLink Href="@GetSeasonsLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (TelevisionSeasonCardViewModel card in _seasons.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href="@($"media/tv/seasons/{card.TelevisionSeasonId}")"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_episodes?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "episodes" } })">
|
|
Episodes
|
|
</MudText>
|
|
@if (_episodes.Count > 50)
|
|
{
|
|
<MudLink Href="@GetEpisodesLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (TelevisionEpisodeCardViewModel card in _episodes.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
Href="@($"media/tv/seasons/{card.SeasonId}#episode-{card.EpisodeId}")"
|
|
Subtitle="@($"{card.ShowTitle} - S{card.Season} E{card.Episode}")"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_artists?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "artists" } })">
|
|
Artists
|
|
</MudText>
|
|
@if (_artists.Count > 50)
|
|
{
|
|
<MudLink Href="@GetArtistsLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (ArtistCardViewModel card in _artists.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href="@($"media/music/artists/{card.ArtistId}")"
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_musicVideos?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "music_videos" } })">
|
|
Music Videos
|
|
</MudText>
|
|
@if (_musicVideos.Count > 50)
|
|
{
|
|
<MudLink Href="@GetMusicVideosLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (MusicVideoCardViewModel card in _musicVideos.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href=""
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_otherVideos?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "other_videos" } })">
|
|
Other Videos
|
|
</MudText>
|
|
@if (_otherVideos.Count > 50)
|
|
{
|
|
<MudLink Href="@GetOtherVideosLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (OtherVideoCardViewModel card in _otherVideos.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href=""
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_songs?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "songs" } })">
|
|
Songs
|
|
</MudText>
|
|
@if (_songs.Count > 50)
|
|
{
|
|
<MudLink Href="@GetSongsLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (SongCardViewModel card in _songs.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href=""
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_images?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "images" } })">
|
|
Images
|
|
</MudText>
|
|
@if (_images.Count > 50)
|
|
{
|
|
<MudLink Href="@GetImagesLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (ImageCardViewModel card in _images.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href=""
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_remoteStreams?.Cards.Count > 0)
|
|
{
|
|
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
|
<MudText Typo="Typo.h4" UserAttributes="@(new Dictionary<string, object> { { "id", "remote_streams" } })">
|
|
Remote Streams
|
|
</MudText>
|
|
@if (_remoteStreams.Count > 50)
|
|
{
|
|
<MudLink Href="@GetRemoteStreamsLink()" Class="ml-4">See All >></MudLink>
|
|
}
|
|
</div>
|
|
<MudDivider Class="mb-6"/>
|
|
|
|
<MudStack Row="true" Wrap="Wrap.Wrap" Class="mb-10">
|
|
@foreach (RemoteStreamCardViewModel card in _remoteStreams.Cards.OrderBy(s => s.SortTitle))
|
|
{
|
|
<MediaCard Data="@card"
|
|
Href=""
|
|
ArtworkKind="ArtworkKind.Thumbnail"
|
|
DeleteClicked="@DeleteItemFromDatabase"
|
|
SelectColor="@Color.Error"
|
|
SelectClicked="@(e => SelectClicked(card, e))"
|
|
IsSelected="@IsSelected(card)"
|
|
IsSelectMode="@IsSelectMode()"/>
|
|
}
|
|
</MudStack>
|
|
}
|
|
</MudContainer>
|
|
</div>
|
|
</MudForm>
|
|
|
|
@code {
|
|
private CancellationTokenSource _cts;
|
|
|
|
private string _query;
|
|
private MovieCardResultsViewModel _movies = new(0, [], new SearchPageMap([]));
|
|
private TelevisionShowCardResultsViewModel _shows = new(0, [], new SearchPageMap([]));
|
|
private TelevisionSeasonCardResultsViewModel _seasons = new(0, [], new SearchPageMap([]));
|
|
private TelevisionEpisodeCardResultsViewModel _episodes = new(0, [], new SearchPageMap([]));
|
|
private MusicVideoCardResultsViewModel _musicVideos = new(0, [], new SearchPageMap([]));
|
|
private OtherVideoCardResultsViewModel _otherVideos = new(0, [], new SearchPageMap([]));
|
|
private SongCardResultsViewModel _songs = new(0, [], new SearchPageMap([]));
|
|
private ImageCardResultsViewModel _images = new(0, [], new SearchPageMap([]));
|
|
private RemoteStreamCardResultsViewModel _remoteStreams = new(0, [], new SearchPageMap([]));
|
|
private ArtistCardResultsViewModel _artists = new(0, [], new SearchPageMap([]));
|
|
|
|
private PersistingComponentStateSubscription _persistingSubscription;
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_persistingSubscription.Dispose();
|
|
|
|
_cts?.Cancel();
|
|
_cts?.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
protected override Task OnInitializedAsync()
|
|
{
|
|
_persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
|
|
return base.OnInitializedAsync();
|
|
}
|
|
|
|
private Task PersistData()
|
|
{
|
|
ApplicationState.PersistAsJson("_movies", _movies);
|
|
ApplicationState.PersistAsJson("_shows", _shows);
|
|
ApplicationState.PersistAsJson("_seasons", _seasons);
|
|
ApplicationState.PersistAsJson("_episodes", _episodes);
|
|
ApplicationState.PersistAsJson("_musicVideos", _musicVideos);
|
|
ApplicationState.PersistAsJson("_otherVideos", _otherVideos);
|
|
ApplicationState.PersistAsJson("_songs", _songs);
|
|
ApplicationState.PersistAsJson("_images", _images);
|
|
ApplicationState.PersistAsJson("_remoteStreams", _remoteStreams);
|
|
ApplicationState.PersistAsJson("_artists", _artists);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
_cts?.Cancel();
|
|
_cts?.Dispose();
|
|
_cts = new CancellationTokenSource();
|
|
var token = _cts.Token;
|
|
|
|
await RefreshData(token);
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
protected override async Task RefreshData(CancellationToken token)
|
|
{
|
|
try
|
|
{
|
|
_query = "state:FileNotFound";
|
|
if (string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var loadingTasks = new List<Task>
|
|
{
|
|
LoadDataAsync("_movies", () => Mediator.Send(new QuerySearchIndexMovies($"type:movie AND ({_query})", 1, 50), token), r => _movies = r),
|
|
LoadDataAsync("_shows", () => Mediator.Send(new QuerySearchIndexShows($"type:show AND ({_query})", 1, 50), token), r => _shows = r),
|
|
LoadDataAsync("_seasons", () => Mediator.Send(new QuerySearchIndexSeasons($"type:season AND ({_query})", 1, 50), token), r => _seasons = r),
|
|
LoadDataAsync("_episodes", () => Mediator.Send(new QuerySearchIndexEpisodes($"type:episode AND ({_query})", 1, 50), token), r => _episodes = r),
|
|
LoadDataAsync("_musicVideos", () => Mediator.Send(new QuerySearchIndexMusicVideos($"type:music_video AND ({_query})", 1, 50), token), r => _musicVideos = r),
|
|
LoadDataAsync("_otherVideos", () => Mediator.Send(new QuerySearchIndexOtherVideos($"type:other_video AND ({_query})", 1, 50), token), r => _otherVideos = r),
|
|
LoadDataAsync("_songs", () => Mediator.Send(new QuerySearchIndexSongs($"type:song AND ({_query})", 1, 50), token), r => _songs = r),
|
|
LoadDataAsync("_images", () => Mediator.Send(new QuerySearchIndexImages($"type:image AND ({_query})", 1, 50), token), r => _images = r),
|
|
LoadDataAsync("_remoteStreams", () => Mediator.Send(new QuerySearchIndexRemoteStreams($"type:remote_stream AND ({_query})", 1, 50), token), r => _remoteStreams = r),
|
|
LoadDataAsync("_artists", () => Mediator.Send(new QuerySearchIndexArtists($"type:artist AND ({_query})", 1, 50), token), r => _artists = r)
|
|
};
|
|
|
|
await Task.WhenAll(loadingTasks);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
private async Task LoadDataAsync<T>(string key, Func<Task<T>> fetch, Action<T> assign)
|
|
{
|
|
if (!ApplicationState.TryTakeFromJson(key, out T restored))
|
|
{
|
|
assign(await fetch());
|
|
}
|
|
else
|
|
{
|
|
assign(restored);
|
|
}
|
|
}
|
|
|
|
private bool IsNotEmpty =>
|
|
_movies?.Cards.Count > 0 || _shows?.Cards.Count > 0 || _seasons?.Cards.Count > 0 || _episodes?.Cards.Count > 0 || _musicVideos?.Cards.Count > 0 || _otherVideos?.Cards.Count > 0 || _songs?.Cards.Count > 0 || _artists?.Cards.Count > 0 || _images?.Cards.Count > 0;
|
|
|
|
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
|
|
{
|
|
List<MediaCardViewModel> GetSortedItems()
|
|
{
|
|
return _movies.Cards.OrderBy(m => m.SortTitle)
|
|
.Append<MediaCardViewModel>(_shows.Cards.OrderBy(s => s.SortTitle))
|
|
.Append(_seasons.Cards.OrderBy(s => s.SortTitle))
|
|
.Append(_episodes.Cards.OrderBy(ep => ep.SortTitle))
|
|
.Append(_artists.Cards.OrderBy(a => a.SortTitle))
|
|
.Append(_musicVideos.Cards.OrderBy(mv => mv.SortTitle))
|
|
.Append(_otherVideos.Cards.OrderBy(ov => ov.SortTitle))
|
|
.Append(_songs.Cards.OrderBy(ov => ov.SortTitle))
|
|
.Append(_images.Cards.OrderBy(i => i.SortTitle))
|
|
.Append(_remoteStreams.Cards.OrderBy(rs => rs.SortTitle))
|
|
.ToList();
|
|
}
|
|
|
|
SelectClicked(GetSortedItems, card, e);
|
|
}
|
|
|
|
private string GetMoviesLink()
|
|
{
|
|
var uri = "media/movies/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetShowsLink()
|
|
{
|
|
var uri = "media/tv/shows/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetSeasonsLink()
|
|
{
|
|
var uri = "media/tv/seasons/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetEpisodesLink()
|
|
{
|
|
var uri = "media/tv/episodes/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetArtistsLink()
|
|
{
|
|
var uri = "media/music/artists/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetMusicVideosLink()
|
|
{
|
|
var uri = "media/music/videos/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetOtherVideosLink()
|
|
{
|
|
var uri = "media/other/videos/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetSongsLink()
|
|
{
|
|
var uri = "media/music/songs/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetImagesLink()
|
|
{
|
|
var uri = "media/images/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private string GetRemoteStreamsLink()
|
|
{
|
|
var uri = "media/remote/streams/page/1";
|
|
if (!string.IsNullOrWhiteSpace(_query))
|
|
{
|
|
(string key, string value) = _query.EncodeQuery();
|
|
uri = $"{uri}?{key}={value}";
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
private Task DeleteFromDatabase() => DeleteItemsFromDatabase(
|
|
SelectedItems.OfType<MovieCardViewModel>().Map(m => m.MovieId).ToList(),
|
|
SelectedItems.OfType<TelevisionShowCardViewModel>().Map(s => s.TelevisionShowId).ToList(),
|
|
SelectedItems.OfType<TelevisionSeasonCardViewModel>().Map(s => s.TelevisionSeasonId).ToList(),
|
|
SelectedItems.OfType<TelevisionEpisodeCardViewModel>().Map(e => e.EpisodeId).ToList(),
|
|
SelectedItems.OfType<ArtistCardViewModel>().Map(a => a.ArtistId).ToList(),
|
|
SelectedItems.OfType<MusicVideoCardViewModel>().Map(mv => mv.MusicVideoId).ToList(),
|
|
SelectedItems.OfType<OtherVideoCardViewModel>().Map(ov => ov.OtherVideoId).ToList(),
|
|
SelectedItems.OfType<SongCardViewModel>().Map(s => s.SongId).ToList(),
|
|
SelectedItems.OfType<ImageCardViewModel>().Map(i => i.ImageId).ToList(),
|
|
SelectedItems.OfType<RemoteStreamCardViewModel>().Map(i => i.RemoteStreamId).ToList());
|
|
|
|
private async Task DeleteItemsFromDatabase(
|
|
List<int> movieIds,
|
|
List<int> showIds,
|
|
List<int> seasonIds,
|
|
List<int> episodeIds,
|
|
List<int> artistIds,
|
|
List<int> musicVideoIds,
|
|
List<int> otherVideoIds,
|
|
List<int> songIds,
|
|
List<int> imageIds,
|
|
List<int> remoteStreamIds,
|
|
string entityName = "selected items")
|
|
{
|
|
int count = movieIds.Count + showIds.Count + seasonIds.Count + episodeIds.Count + artistIds.Count +
|
|
musicVideoIds.Count + otherVideoIds.Count + songIds.Count + imageIds.Count;
|
|
|
|
var parameters = new DialogParameters
|
|
{ { "EntityType", count.ToString() }, { "EntityName", entityName } };
|
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
|
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteFromDatabaseDialog>("Delete From Database", parameters, options);
|
|
DialogResult result = await dialog.Result;
|
|
if (result is { Canceled: false })
|
|
{
|
|
var request = new DeleteItemsFromDatabase(
|
|
movieIds.Append(showIds)
|
|
.Append(seasonIds)
|
|
.Append(episodeIds)
|
|
.Append(artistIds)
|
|
.Append(musicVideoIds)
|
|
.Append(otherVideoIds)
|
|
.Append(songIds)
|
|
.Append(imageIds)
|
|
.Append(remoteStreamIds)
|
|
.ToList());
|
|
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
Either<BaseError, Unit> addResult = await Mediator.Send(request, cts.Token);
|
|
await addResult.Match(
|
|
Left: error =>
|
|
{
|
|
Snackbar.Add($"Unexpected error deleting items from database: {error.Value}");
|
|
Logger.LogError("Unexpected error deleting items from database: {Error}", error.Value);
|
|
return Task.CompletedTask;
|
|
},
|
|
Right: async _ =>
|
|
{
|
|
Snackbar.Add($"Deleted {count} items from the database", Severity.Success);
|
|
ClearSelection();
|
|
await RefreshData(cts.Token);
|
|
});
|
|
}
|
|
}
|
|
|
|
private async Task DeleteItemFromDatabase(MediaCardViewModel vm)
|
|
{
|
|
DeleteItemsFromDatabase request;
|
|
|
|
switch (vm)
|
|
{
|
|
case MovieCardViewModel movie:
|
|
request = new DeleteItemsFromDatabase([movie.MovieId]);
|
|
await DeleteItemsWithConfirmation("movie", $"{movie.Title} ({movie.Subtitle})", request);
|
|
break;
|
|
case TelevisionShowCardViewModel show:
|
|
request = new DeleteItemsFromDatabase([show.TelevisionShowId]);
|
|
await DeleteItemsWithConfirmation("show", $"{show.Title} ({show.Subtitle})", request);
|
|
break;
|
|
case TelevisionSeasonCardViewModel season:
|
|
request = new DeleteItemsFromDatabase([season.TelevisionSeasonId]);
|
|
await DeleteItemsWithConfirmation("season", $"{season.Title} ({season.Subtitle})", request);
|
|
break;
|
|
case TelevisionEpisodeCardViewModel episode:
|
|
request = new DeleteItemsFromDatabase([episode.EpisodeId]);
|
|
await DeleteItemsWithConfirmation("episode", $"{episode.Title} ({episode.Subtitle})", request);
|
|
break;
|
|
case ArtistCardViewModel artist:
|
|
request = new DeleteItemsFromDatabase([artist.ArtistId]);
|
|
await DeleteItemsWithConfirmation("artist", $"{artist.Title} ({artist.Subtitle})", request);
|
|
break;
|
|
case MusicVideoCardViewModel musicVideo:
|
|
request = new DeleteItemsFromDatabase([musicVideo.MusicVideoId]);
|
|
await DeleteItemsWithConfirmation("music video", $"{musicVideo.Title} ({musicVideo.Subtitle})", request);
|
|
break;
|
|
case OtherVideoCardViewModel otherVideo:
|
|
request = new DeleteItemsFromDatabase([otherVideo.OtherVideoId]);
|
|
await DeleteItemsWithConfirmation("other video", $"{otherVideo.Title} ({otherVideo.Subtitle})", request);
|
|
break;
|
|
case SongCardViewModel song:
|
|
request = new DeleteItemsFromDatabase([song.SongId]);
|
|
await DeleteItemsWithConfirmation("song", $"{song.Title} ({song.Subtitle})", request);
|
|
break;
|
|
case ImageCardViewModel image:
|
|
request = new DeleteItemsFromDatabase([image.ImageId]);
|
|
await DeleteItemsWithConfirmation("image", $"{image.Title} ({image.Subtitle})", request);
|
|
break;
|
|
case RemoteStreamCardViewModel remoteStream:
|
|
request = new DeleteItemsFromDatabase([remoteStream.RemoteStreamId]);
|
|
await DeleteItemsWithConfirmation("remote stream", $"{remoteStream.Title} ({remoteStream.Subtitle})", request);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task DeleteItemsWithConfirmation(
|
|
string entityType,
|
|
string entityName,
|
|
DeleteItemsFromDatabase request)
|
|
{
|
|
var parameters = new DialogParameters { { "EntityType", entityType }, { "EntityName", entityName } };
|
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
|
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteFromDatabaseDialog>("Delete From Database", parameters, options);
|
|
DialogResult result = await dialog.Result;
|
|
if (result is not null && !result.Canceled)
|
|
{
|
|
var token = _cts?.Token ?? CancellationToken.None;
|
|
|
|
await Mediator.Send(request, token);
|
|
await RefreshData(token);
|
|
}
|
|
}
|
|
|
|
private async Task EmptyTrash()
|
|
{
|
|
int count = _movies.Count + _shows.Count + _seasons.Count + _episodes.Count + _artists.Count +
|
|
_musicVideos.Count + _otherVideos.Count + _songs.Count + _images.Count;
|
|
|
|
var parameters = new DialogParameters { { "EntityType", count.ToString() }, { "EntityName", "missing items" } };
|
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
|
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<DeleteFromDatabaseDialog>("Delete From Database", parameters, options);
|
|
DialogResult result = await dialog.Result;
|
|
if (result is not null && !result.Canceled)
|
|
{
|
|
var token = _cts?.Token ?? CancellationToken.None;
|
|
|
|
Either<BaseError, Unit> emptyTrashResult = await Mediator.Send(new EmptyTrash(), token);
|
|
foreach (BaseError error in emptyTrashResult.LeftToSeq())
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error emptying trash: {Error}", error.Value);
|
|
}
|
|
|
|
if (emptyTrashResult.IsRight)
|
|
{
|
|
await RefreshData(token);
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|