search overhaul (#87)
* add letter bar with no links * use lucene for search, add paged search results * add search index version * index library_name; rebuild index when folder is missing * maintain index as local movies change * fix tests * maintain index as local shows change * maintain index as plex movies change * maintain index as plex shows change * code cleanup * add duplicate filter to search * add links to letter bar * code cleanup
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
@page "/channels/{Id:int?}"
|
||||
@page "/channels/add"
|
||||
@using static LanguageExt.Prelude
|
||||
@using ErsatzTV.Application.FFmpegProfiles
|
||||
@using ErsatzTV.Application.FFmpegProfiles.Queries
|
||||
@using ErsatzTV.Application.Images.Commands
|
||||
@using ErsatzTV.Application.Channels
|
||||
@using ErsatzTV.Application.Channels.Queries
|
||||
@using static LanguageExt.Prelude
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILogger<ChannelEditor> Logger
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<MudSwitch T="bool"
|
||||
Label="Save troubleshooting reports to disk"
|
||||
Color="Color.Primary"
|
||||
@bind-Checked="@_ffmpegSettings.SaveReports" />
|
||||
@bind-Checked="@_ffmpegSettings.SaveReports"/>
|
||||
</MudElement>
|
||||
</MudForm>
|
||||
</MudCardContent>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="mb-2">
|
||||
@foreach (string genre in _movie.Genres.OrderBy(g => g))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a{genre}")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{genre.ToLowerInvariant()}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
<div>
|
||||
@foreach (string tag in _movie.Tags.OrderBy(t => t))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a{tag}")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{tag.ToLowerInvariant()}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
@page "/media/movies"
|
||||
@page "/media/movies/page/{PageNumber:int}"
|
||||
@using LanguageExt.UnsafeValueAccess
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using Microsoft.Extensions.Primitives
|
||||
@using ErsatzTV.Application.MediaCards
|
||||
@using ErsatzTV.Application.MediaCards.Queries
|
||||
@using ErsatzTV.Application.MediaCollections
|
||||
@using ErsatzTV.Application.MediaCollections.Commands
|
||||
@using ErsatzTV.Application.Search.Queries
|
||||
@using Unit = LanguageExt.Unit
|
||||
@inherits MultiSelectBase<MovieList>
|
||||
@inject NavigationManager NavigationManager
|
||||
@@ -32,7 +35,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="margin-left: auto; margin-right: auto; max-width: 300px;">
|
||||
<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"
|
||||
@@ -63,6 +67,12 @@
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
@if (_data.PageMap.IsSome)
|
||||
{
|
||||
<LetterBar PageMap="@_data.PageMap.ValueUnsafe()"
|
||||
BaseUri="/media/movies"
|
||||
Query="@_query"/>
|
||||
}
|
||||
|
||||
@code {
|
||||
private static int PageSize => 100;
|
||||
@@ -71,6 +81,7 @@
|
||||
public int PageNumber { get; set; }
|
||||
|
||||
private MovieCardResultsViewModel _data;
|
||||
private string _query;
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -79,15 +90,40 @@
|
||||
PageNumber = 1;
|
||||
}
|
||||
|
||||
string query = new Uri(NavigationManager.Uri).Query;
|
||||
if (QueryHelpers.ParseQuery(query).TryGetValue("query", out StringValues value))
|
||||
{
|
||||
_query = value;
|
||||
}
|
||||
|
||||
return RefreshData();
|
||||
}
|
||||
|
||||
protected override async Task RefreshData() =>
|
||||
_data = await Mediator.Send(new GetMovieCards(PageNumber, PageSize));
|
||||
protected override async Task RefreshData()
|
||||
{
|
||||
string searchQuery = string.IsNullOrWhiteSpace(_query) ? "type:movie" : $"type:movie AND ({_query})";
|
||||
_data = await Mediator.Send(new QuerySearchIndexMovies(searchQuery, PageNumber, PageSize));
|
||||
}
|
||||
|
||||
private void PrevPage() => NavigationManager.NavigateTo($"/media/movies/page/{PageNumber - 1}");
|
||||
private void PrevPage()
|
||||
{
|
||||
var uri = $"/media/movies/page/{PageNumber - 1}";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
NavigationManager.NavigateTo(uri);
|
||||
}
|
||||
|
||||
private void NextPage() => NavigationManager.NavigateTo($"/media/movies/page/{PageNumber + 1}");
|
||||
private void NextPage()
|
||||
{
|
||||
var uri = $"/media/movies/page/{PageNumber + 1}";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
NavigationManager.NavigateTo(uri);
|
||||
}
|
||||
|
||||
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
|
||||
{
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
@if (_selectedItem is not null)
|
||||
{
|
||||
<div style="display: flex; flex-direction: row;" class="mt-6">
|
||||
<div style="max-width: 400px; flex-grow: 1">
|
||||
<div style="flex-grow: 1; max-width: 400px;">
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
|
||||
+71
-24
@@ -1,8 +1,8 @@
|
||||
@page "/search"
|
||||
@using ErsatzTV.Application.MediaCards
|
||||
@using ErsatzTV.Application.MediaCards.Queries
|
||||
@using ErsatzTV.Application.MediaCollections
|
||||
@using ErsatzTV.Application.MediaCollections.Commands
|
||||
@using ErsatzTV.Application.Search.Queries
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using Microsoft.Extensions.Primitives
|
||||
@using Unit = LanguageExt.Unit
|
||||
@@ -34,23 +34,43 @@
|
||||
else
|
||||
{
|
||||
<MudText>@_query</MudText>
|
||||
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#movies")">@_data.MovieCards.Count Movies</MudLink>
|
||||
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#shows")">@_data.ShowCards.Count Shows</MudLink>
|
||||
if (_movies.Count > 0)
|
||||
{
|
||||
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#movies")">@_movies.Count Movies</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="ml-4">0 Movies</MudText>
|
||||
}
|
||||
|
||||
if (_shows.Count > 0)
|
||||
{
|
||||
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#shows")">@_shows.Count Shows</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="ml-4">0 Shows</MudText>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</MudPaper>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Style="margin-top: 96px">
|
||||
@if (_data?.MovieCards.Any() == true)
|
||||
@if (_movies.Count > 0)
|
||||
{
|
||||
<MudText GutterBottom="true"
|
||||
Typo="Typo.h4"
|
||||
Style="scroll-margin-top: 160px"
|
||||
UserAttributes="@(new Dictionary<string, object> { { "id", "movies" } })">
|
||||
Movies
|
||||
</MudText>
|
||||
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
||||
<MudText Typo="Typo.h4"
|
||||
Style="scroll-margin-top: 160px"
|
||||
UserAttributes="@(new Dictionary<string, object> { { "id", "movies" } })">
|
||||
Movies
|
||||
</MudText>
|
||||
@if (_movies.Count > 50)
|
||||
{
|
||||
<MudLink Href="@GetMoviesLink()" Class="ml-4">See All >></MudLink>
|
||||
}
|
||||
</div>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MovieCardViewModel card in _data.MovieCards.OrderBy(m => m.SortTitle))
|
||||
@foreach (MovieCardViewModel card in _movies.Cards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/movies/{card.MovieId}")"
|
||||
@@ -62,17 +82,22 @@
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@if (_data?.ShowCards.Any() == true)
|
||||
@if (_shows.Count > 0)
|
||||
{
|
||||
<MudText GutterBottom="true"
|
||||
Typo="Typo.h4"
|
||||
Style="scroll-margin-top: 160px"
|
||||
UserAttributes="@(new Dictionary<string, object> { { "id", "shows" } })">
|
||||
Shows
|
||||
</MudText>
|
||||
<div class="mb-4" style="align-items: baseline; display: flex; flex-direction: row;">
|
||||
<MudText Typo="Typo.h4"
|
||||
Style="scroll-margin-top: 160px"
|
||||
UserAttributes="@(new Dictionary<string, object> { { "id", "shows" } })">
|
||||
Shows
|
||||
</MudText>
|
||||
@if (_shows.Count > 50)
|
||||
{
|
||||
<MudLink Href="@GetShowsLink()" Class="ml-4">See All >></MudLink>
|
||||
}
|
||||
</div>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(s => s.SortTitle))
|
||||
@foreach (TelevisionShowCardViewModel card in _shows.Cards.OrderBy(s => s.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||
@@ -87,7 +112,8 @@
|
||||
|
||||
@code {
|
||||
private string _query;
|
||||
private SearchCardResultsViewModel _data;
|
||||
private MovieCardResultsViewModel _movies;
|
||||
private TelevisionShowCardResultsViewModel _shows;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -96,8 +122,9 @@
|
||||
if (QueryHelpers.ParseQuery(query).TryGetValue("query", out StringValues value))
|
||||
{
|
||||
_query = value;
|
||||
Either<BaseError, SearchCardResultsViewModel> maybeResults = await Mediator.Send(new GetSearchCards(_query));
|
||||
maybeResults.IfRight(results => _data = results);
|
||||
|
||||
_movies = await Mediator.Send(new QuerySearchIndexMovies($"type:movie AND ({_query})", 1, 50));
|
||||
_shows = await Mediator.Send(new QuerySearchIndexShows($"type:show AND ({_query})", 1, 50));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +132,8 @@
|
||||
{
|
||||
List<MediaCardViewModel> GetSortedItems()
|
||||
{
|
||||
return _data.MovieCards.OrderBy(m => m.SortTitle)
|
||||
.Append<MediaCardViewModel>(_data.ShowCards.OrderBy(s => s.SortTitle))
|
||||
return _movies.Cards.OrderBy(m => m.SortTitle)
|
||||
.Append<MediaCardViewModel>(_shows.Cards.OrderBy(s => s.SortTitle))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -158,4 +185,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMoviesLink()
|
||||
{
|
||||
var uri = "/media/movies";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
private string GetShowsLink()
|
||||
{
|
||||
var uri = "/media/tv/shows";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="mb-2">
|
||||
@foreach (string genre in _show.Genres.OrderBy(g => g))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a{genre}")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{genre.ToLowerInvariant()}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -74,7 +74,7 @@
|
||||
<div>
|
||||
@foreach (string tag in _show.Tags.OrderBy(t => t))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a{tag}")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{tag.ToLowerInvariant()}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
@page "/media/tv/shows"
|
||||
@page "/media/tv/shows/page/{PageNumber:int}"
|
||||
@using LanguageExt.UnsafeValueAccess
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using Microsoft.Extensions.Primitives
|
||||
@using ErsatzTV.Application.MediaCards
|
||||
@using ErsatzTV.Application.MediaCards.Queries
|
||||
@using ErsatzTV.Application.MediaCollections
|
||||
@using ErsatzTV.Application.MediaCollections.Commands
|
||||
@using ErsatzTV.Application.Search.Queries
|
||||
@using Unit = LanguageExt.Unit
|
||||
@inherits MultiSelectBase<TelevisionShowList>
|
||||
@inject NavigationManager NavigationManager
|
||||
@@ -32,7 +35,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="margin-left: auto; margin-right: auto; max-width: 300px;">
|
||||
<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"
|
||||
@@ -63,6 +67,12 @@
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
@if (_data.PageMap.IsSome)
|
||||
{
|
||||
<LetterBar PageMap="@_data.PageMap.ValueUnsafe()"
|
||||
BaseUri="/media/tv/shows"
|
||||
Query="@_query"/>
|
||||
}
|
||||
|
||||
@code {
|
||||
private static int PageSize => 100;
|
||||
@@ -71,6 +81,7 @@
|
||||
public int PageNumber { get; set; }
|
||||
|
||||
private TelevisionShowCardResultsViewModel _data;
|
||||
private string _query;
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -79,15 +90,40 @@
|
||||
PageNumber = 1;
|
||||
}
|
||||
|
||||
string query = new Uri(NavigationManager.Uri).Query;
|
||||
if (QueryHelpers.ParseQuery(query).TryGetValue("query", out StringValues value))
|
||||
{
|
||||
_query = value;
|
||||
}
|
||||
|
||||
return RefreshData();
|
||||
}
|
||||
|
||||
protected override async Task RefreshData() =>
|
||||
_data = await Mediator.Send(new GetTelevisionShowCards(PageNumber, PageSize));
|
||||
protected override async Task RefreshData()
|
||||
{
|
||||
string searchQuery = string.IsNullOrWhiteSpace(_query) ? "type:show" : $"type:show AND ({_query})";
|
||||
_data = await Mediator.Send(new QuerySearchIndexShows(searchQuery, PageNumber, PageSize));
|
||||
}
|
||||
|
||||
private void PrevPage() => NavigationManager.NavigateTo($"/media/tv/shows/page/{PageNumber - 1}");
|
||||
private void PrevPage()
|
||||
{
|
||||
var uri = $"/media/tv/shows/page/{PageNumber - 1}";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
NavigationManager.NavigateTo(uri);
|
||||
}
|
||||
|
||||
private void NextPage() => NavigationManager.NavigateTo($"/media/tv/shows/page/{PageNumber + 1}");
|
||||
private void NextPage()
|
||||
{
|
||||
var uri = $"/media/tv/shows/page/{PageNumber + 1}";
|
||||
if (!string.IsNullOrWhiteSpace(_query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", _query);
|
||||
}
|
||||
NavigationManager.NavigateTo(uri);
|
||||
}
|
||||
|
||||
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using ErsatzTV.Application;
|
||||
using ErsatzTV.Application.MediaSources.Commands;
|
||||
using ErsatzTV.Application.Playouts.Commands;
|
||||
using ErsatzTV.Application.Plex.Commands;
|
||||
using ErsatzTV.Application.Search.Commands;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
@@ -54,6 +55,7 @@ namespace ErsatzTV.Services
|
||||
|
||||
private async Task DoWork(CancellationToken cancellationToken)
|
||||
{
|
||||
await RebuildSearchIndex(cancellationToken);
|
||||
await BuildPlayouts(cancellationToken);
|
||||
await ScanLocalMediaSources(cancellationToken);
|
||||
await ScanPlexMediaSources(cancellationToken);
|
||||
@@ -111,5 +113,8 @@ namespace ErsatzTV.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RebuildSearchIndex(CancellationToken cancellationToken) =>
|
||||
await _channel.WriteAsync(new RebuildSearchIndex(), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using ErsatzTV.Application;
|
||||
using ErsatzTV.Application.MediaSources.Commands;
|
||||
using ErsatzTV.Application.Playouts.Commands;
|
||||
using ErsatzTV.Application.Plex.Commands;
|
||||
using ErsatzTV.Application.Search.Commands;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
@@ -82,6 +83,9 @@ namespace ErsatzTV.Services
|
||||
synchronizePlexLibraryById.PlexLibraryId,
|
||||
error.Value));
|
||||
break;
|
||||
case RebuildSearchIndex rebuildSearchIndex:
|
||||
await mediator.Send(rebuildSearchIndex, cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
@using ErsatzTV.Core.Search
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
<div class="letter-bar-container">
|
||||
<div class="letter-bar">
|
||||
<MudLink Href="@GetLinkForLetter('#')">#</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('a')">A</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('b')">B</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('c')">C</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('d')">D</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('e')">E</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('f')">F</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('g')">G</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('h')">H</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('i')">I</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('j')">J</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('k')">K</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('l')">L</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('m')">M</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('n')">N</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('o')">O</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('p')">P</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('q')">Q</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('r')">R</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('s')">S</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('t')">T</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('u')">U</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('v')">V</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('w')">W</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('x')">X</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('y')">Y</MudLink>
|
||||
<MudLink Href="@GetLinkForLetter('z')">Z</MudLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public SearchPageMap PageMap { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string BaseUri { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Query { get; set; }
|
||||
|
||||
private string GetLinkForLetter(char letter)
|
||||
{
|
||||
var uri = $"{BaseUri}/page/{PageMap.PageMap[letter]}";
|
||||
if (!string.IsNullOrWhiteSpace(Query))
|
||||
{
|
||||
uri = QueryHelpers.AddQueryString(uri, "query", Query);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using ErsatzTV.Core.Plex;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
@@ -24,6 +25,7 @@ using ErsatzTV.Infrastructure.Data.Repositories;
|
||||
using ErsatzTV.Infrastructure.Images;
|
||||
using ErsatzTV.Infrastructure.Locking;
|
||||
using ErsatzTV.Infrastructure.Plex;
|
||||
using ErsatzTV.Infrastructure.Search;
|
||||
using ErsatzTV.Serialization;
|
||||
using ErsatzTV.Services;
|
||||
using FluentValidation.AspNetCore;
|
||||
@@ -209,6 +211,7 @@ namespace ErsatzTV
|
||||
services.AddScoped<IPlexMovieLibraryScanner, PlexMovieLibraryScanner>();
|
||||
services.AddScoped<IPlexTelevisionLibraryScanner, PlexTelevisionLibraryScanner>();
|
||||
services.AddScoped<IPlexServerApiClient, PlexServerApiClient>();
|
||||
services.AddScoped<ISearchIndex, SearchIndex>();
|
||||
|
||||
services.AddHostedService<PlexService>();
|
||||
services.AddHostedService<FFmpegLocatorService>();
|
||||
|
||||
@@ -106,4 +106,18 @@
|
||||
.media-item-subtitle {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 5px #000;
|
||||
}
|
||||
|
||||
.letter-bar-container {
|
||||
height: calc(100vh - 128px);
|
||||
position: fixed;
|
||||
right: 1em;
|
||||
top: 128px;
|
||||
}
|
||||
|
||||
.letter-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
}
|
||||
Reference in New Issue
Block a user