mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add sorts to search items
This commit is contained in:
parent
68a3af0b52
commit
377d85c7f1
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
@ -33,7 +32,7 @@ namespace Kyoo.Core.Api
|
||||
/// An endpoint to search for every resources of kyoo. Searching for only a specific type of resource
|
||||
/// is available on the said endpoint.
|
||||
/// </summary>
|
||||
[Route("search/{query?}")]
|
||||
[Route("search")]
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
[ApiDefinition("Search", Group = ResourcesGroup)]
|
||||
@ -54,7 +53,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for collections
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -64,12 +63,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Collection), Kind.Read)]
|
||||
[ApiDefinition("Collections")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<Collection>> SearchCollections(string? query,
|
||||
public async Task<SearchPage<Collection>> SearchCollections(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<Collection> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<Collection> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchCollections(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchCollections(q, sortBy, pagination, fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -78,7 +78,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for shows
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -88,12 +88,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Show), Kind.Read)]
|
||||
[ApiDefinition("Show")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<Show>> SearchShows(string? query,
|
||||
public async Task<SearchPage<Show>> SearchShows(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<Show> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<Show> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchShows(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchShows(q, sortBy, pagination, fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -102,7 +103,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for movie
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -112,12 +113,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Movie), Kind.Read)]
|
||||
[ApiDefinition("Movie")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<Movie>> SearchMovies(string? query,
|
||||
public async Task<SearchPage<Movie>> SearchMovies(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<Movie> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<Movie> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchMovies(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchMovies(q, sortBy, pagination, fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -126,7 +128,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for items
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -136,12 +138,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(LibraryItem), Kind.Read)]
|
||||
[ApiDefinition("Item")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<LibraryItem>> SearchItems(string? query,
|
||||
public async Task<SearchPage<LibraryItem>> SearchItems(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<LibraryItem> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<LibraryItem> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchItems(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchItems(q, sortBy, pagination, fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -150,7 +153,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for episodes
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -160,12 +163,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Episode), Kind.Read)]
|
||||
[ApiDefinition("Episodes")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<Episode>> SearchEpisodes(string? query,
|
||||
public async Task<SearchPage<Episode>> SearchEpisodes(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<Episode> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<Episode> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchEpisodes(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchEpisodes(q, sortBy, pagination, fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -174,7 +178,7 @@ namespace Kyoo.Core.Api
|
||||
/// <remarks>
|
||||
/// Search for studios
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <param name="q">The query to search for.</param>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
|
||||
/// <param name="fields">The aditional fields to include in the result.</param>
|
||||
@ -184,12 +188,13 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Studio), Kind.Read)]
|
||||
[ApiDefinition("Studios")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<SearchPage<Studio>> SearchStudios(string? query,
|
||||
public async Task<SearchPage<Studio>> SearchStudios(
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] Sort<Studio> sortBy,
|
||||
[FromQuery] SearchPagination pagination,
|
||||
[FromQuery] Include<Studio> fields)
|
||||
{
|
||||
return SearchPage(await _searchManager.SearchStudios(query, sortBy, pagination, fields));
|
||||
return SearchPage(await _searchManager.SearchStudios(q, sortBy, pagination, fields));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,93 @@ namespace Kyoo.Meiliseach
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public static Dictionary<string, Settings> IndexSettings => new()
|
||||
{
|
||||
{
|
||||
"items",
|
||||
new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Name)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Slug)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Aliases)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Path)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Tags)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Overview)),
|
||||
},
|
||||
FilterableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Genres)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Status)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AirDate)),
|
||||
CamelCase.ConvertName(nameof(Movie.StudioID)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Kind)),
|
||||
},
|
||||
SortableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AirDate)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AddedDate)),
|
||||
},
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Id)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Kind)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
// TODO: Extend default ranking to add ratings.
|
||||
}
|
||||
},
|
||||
{
|
||||
nameof(Episode),
|
||||
new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.Name)),
|
||||
CamelCase.ConvertName(nameof(Episode.Overview)),
|
||||
CamelCase.ConvertName(nameof(Episode.Slug)),
|
||||
CamelCase.ConvertName(nameof(Episode.Path)),
|
||||
},
|
||||
FilterableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.SeasonNumber)),
|
||||
},
|
||||
SortableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.ReleaseDate)),
|
||||
CamelCase.ConvertName(nameof(Episode.AddedDate)),
|
||||
CamelCase.ConvertName(nameof(Episode.SeasonNumber)),
|
||||
CamelCase.ConvertName(nameof(Episode.EpisodeNumber)),
|
||||
CamelCase.ConvertName(nameof(Episode.AbsoluteNumber)),
|
||||
},
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.Id)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
}
|
||||
},
|
||||
{
|
||||
nameof(Studio),
|
||||
new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Studio.Name)),
|
||||
CamelCase.ConvertName(nameof(Studio.Slug)),
|
||||
},
|
||||
FilterableAttributes = Array.Empty<string>(),
|
||||
SortableAttributes = Array.Empty<string>(),
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Studio.Id)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public MeilisearchModule(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
@ -47,89 +134,16 @@ namespace Kyoo.Meiliseach
|
||||
{
|
||||
MeilisearchClient client = provider.GetRequiredService<MeilisearchClient>();
|
||||
|
||||
await _CreateIndex(client, "items", true, new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Name)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Slug)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Aliases)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Path)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Tags)),
|
||||
// Overview could be included as well but I think it would be better without.
|
||||
},
|
||||
FilterableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Genres)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Status)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AirDate)),
|
||||
CamelCase.ConvertName(nameof(Movie.StudioID)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Kind)),
|
||||
},
|
||||
SortableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AirDate)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.AddedDate)),
|
||||
},
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Id)),
|
||||
CamelCase.ConvertName(nameof(LibraryItem.Kind)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
// TODO: Extend default ranking to add ratings.
|
||||
});
|
||||
|
||||
await _CreateIndex(client, nameof(Episode), false, new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.Name)),
|
||||
CamelCase.ConvertName(nameof(Episode.Overview)),
|
||||
CamelCase.ConvertName(nameof(Episode.Slug)),
|
||||
CamelCase.ConvertName(nameof(Episode.Path)),
|
||||
},
|
||||
FilterableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.SeasonNumber)),
|
||||
},
|
||||
SortableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.ReleaseDate)),
|
||||
CamelCase.ConvertName(nameof(Episode.AddedDate)),
|
||||
CamelCase.ConvertName(nameof(Episode.SeasonNumber)),
|
||||
CamelCase.ConvertName(nameof(Episode.EpisodeNumber)),
|
||||
CamelCase.ConvertName(nameof(Episode.AbsoluteNumber)),
|
||||
},
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Episode.Id)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
});
|
||||
|
||||
await _CreateIndex(client, nameof(Studio), false, new Settings()
|
||||
{
|
||||
SearchableAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Studio.Name)),
|
||||
CamelCase.ConvertName(nameof(Studio.Slug)),
|
||||
},
|
||||
FilterableAttributes = Array.Empty<string>(),
|
||||
SortableAttributes = Array.Empty<string>(),
|
||||
DisplayedAttributes = new[]
|
||||
{
|
||||
CamelCase.ConvertName(nameof(Studio.Id)),
|
||||
},
|
||||
// TODO: Add stopwords
|
||||
});
|
||||
await _CreateIndex(client, "items", true);
|
||||
await _CreateIndex(client, nameof(Episode), false);
|
||||
await _CreateIndex(client, nameof(Studio), false);
|
||||
}
|
||||
|
||||
private static async Task _CreateIndex(MeilisearchClient client, string index, bool hasKind, Settings opts)
|
||||
private static async Task _CreateIndex(MeilisearchClient client, string index, bool hasKind)
|
||||
{
|
||||
TaskInfo task = await client.CreateIndexAsync(index, hasKind ? "ref" : CamelCase.ConvertName(nameof(IResource.Id)));
|
||||
await client.WaitForTaskAsync(task.TaskUid);
|
||||
await client.Index(index).UpdateSettingsAsync(opts);
|
||||
await client.Index(index).UpdateSettingsAsync(IndexSettings[index]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -32,13 +32,15 @@ public class SearchManager : ISearchManager
|
||||
private readonly MeilisearchClient _client;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
private static IEnumerable<string> _GetSortsBy<T>(Sort<T>? sort)
|
||||
private static IEnumerable<string> _GetSortsBy<T>(string index, Sort<T>? sort)
|
||||
{
|
||||
return sort switch
|
||||
{
|
||||
Sort<T>.Default => Array.Empty<string>(),
|
||||
Sort<T>.By @sortBy => new[] { $"{sortBy.Key}:{(sortBy.Desendant ? "desc" : "asc")}" },
|
||||
Sort<T>.Conglomerate(var list) => list.SelectMany(_GetSortsBy),
|
||||
Sort<T>.By @sortBy => MeilisearchModule.IndexSettings[index].SortableAttributes.Contains(sortBy.Key, StringComparer.InvariantCultureIgnoreCase)
|
||||
? new[] { $"{CamelCase.ConvertName(sortBy.Key)}:{(sortBy.Desendant ? "desc" : "asc")}" }
|
||||
: throw new ValidationException($"Invalid sorting mode: {sortBy.Key}"),
|
||||
Sort<T>.Conglomerate(var list) => list.SelectMany(x => _GetSortsBy(index, x)),
|
||||
Sort<T>.Random => throw new ValidationException("Random sorting is not supported while searching."),
|
||||
_ => Array.Empty<string>(),
|
||||
};
|
||||
@ -106,7 +108,7 @@ public class SearchManager : ISearchManager
|
||||
ISearchable<IdResource> res = await _client.Index(index).SearchAsync<IdResource>(query, new SearchQuery()
|
||||
{
|
||||
Filter = where,
|
||||
Sort = _GetSortsBy(sortBy),
|
||||
Sort = _GetSortsBy(index, sortBy),
|
||||
Limit = pagination?.Limit ?? 50,
|
||||
Offset = pagination?.Skip ?? 0,
|
||||
});
|
||||
@ -126,7 +128,7 @@ public class SearchManager : ISearchManager
|
||||
// TODO: add filters and facets
|
||||
ISearchable<IdResource> res = await _client.Index("items").SearchAsync<IdResource>(query, new SearchQuery()
|
||||
{
|
||||
Sort = _GetSortsBy(sortBy),
|
||||
Sort = _GetSortsBy("items", sortBy),
|
||||
Limit = pagination?.Limit ?? 50,
|
||||
Offset = pagination?.Skip ?? 0,
|
||||
});
|
||||
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Kyoo - A portable and vast media library solution.
|
||||
* Copyright (c) Kyoo.
|
||||
*
|
||||
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
*
|
||||
* Kyoo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Kyoo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { BrowsePage } from "@kyoo/ui";
|
||||
import { withRoute } from "~/router";
|
||||
|
||||
export default withRoute(BrowsePage);
|
@ -37,11 +37,11 @@ import ViewList from "@material-symbols/svg-400/rounded/view_list.svg";
|
||||
import Sort from "@material-symbols/svg-400/rounded/sort.svg";
|
||||
import ArrowUpward from "@material-symbols/svg-400/rounded/arrow_upward.svg";
|
||||
import ArrowDownward from "@material-symbols/svg-400/rounded/arrow_downward.svg";
|
||||
import { Layout, SortBy, SortOrd } from "./types";
|
||||
import { Layout, SearchSort, SortOrd } from "./types";
|
||||
import { forwardRef } from "react";
|
||||
import { View, PressableProps } from "react-native";
|
||||
|
||||
const SortTrigger = forwardRef<View, PressableProps & { sortKey: SortBy }>(function SortTrigger(
|
||||
const SortTrigger = forwardRef<View, PressableProps & { sortKey: string }>(function SortTrigger(
|
||||
{ sortKey, ...props },
|
||||
ref,
|
||||
) {
|
||||
@ -61,15 +61,17 @@ const SortTrigger = forwardRef<View, PressableProps & { sortKey: SortBy }>(funct
|
||||
});
|
||||
|
||||
export const BrowseSettings = ({
|
||||
availableSorts,
|
||||
sortKey,
|
||||
sortOrd,
|
||||
setSort,
|
||||
layout,
|
||||
setLayout,
|
||||
}: {
|
||||
sortKey: SortBy;
|
||||
availableSorts: string[];
|
||||
sortKey: string;
|
||||
sortOrd: SortOrd;
|
||||
setSort: (sort: SortBy, ord: SortOrd) => void;
|
||||
setSort: (sort: string, ord: SortOrd) => void;
|
||||
layout: Layout;
|
||||
setLayout: (layout: Layout) => void;
|
||||
}) => {
|
||||
@ -97,12 +99,18 @@ export const BrowseSettings = ({
|
||||
)}
|
||||
<View {...css({ flexDirection: "row" })}>
|
||||
<Menu Trigger={SortTrigger} sortKey={sortKey}>
|
||||
{Object.values(SortBy).map((x) => (
|
||||
{availableSorts.map((x) => (
|
||||
<Menu.Item
|
||||
key={x}
|
||||
label={t(`browse.sortkey.${x}`)}
|
||||
selected={sortKey === x}
|
||||
icon={sortOrd === SortOrd.Asc ? ArrowUpward : ArrowDownward}
|
||||
icon={
|
||||
x !== SearchSort.Relevance
|
||||
? sortOrd === SortOrd.Asc
|
||||
? ArrowUpward
|
||||
: ArrowDownward
|
||||
: undefined
|
||||
}
|
||||
onSelect={() =>
|
||||
setSort(x, sortKey === x && sortOrd === SortOrd.Asc ? SortOrd.Desc : SortOrd.Asc)
|
||||
}
|
||||
|
@ -53,20 +53,16 @@ export const itemMap = (
|
||||
};
|
||||
};
|
||||
|
||||
const query = (
|
||||
slug?: string,
|
||||
sortKey?: SortBy,
|
||||
sortOrd?: SortOrd,
|
||||
): QueryIdentifier<LibraryItem> => ({
|
||||
const query = (sortKey?: SortBy, sortOrd?: SortOrd): QueryIdentifier<LibraryItem> => ({
|
||||
parser: LibraryItemP,
|
||||
path: slug ? ["library", slug, "items"] : ["items"],
|
||||
path: ["items"],
|
||||
infinite: true,
|
||||
params: {
|
||||
sortBy: sortKey ? `${sortKey}:${sortOrd ?? "asc"}` : "name:asc",
|
||||
},
|
||||
});
|
||||
|
||||
export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
||||
export const BrowsePage: QueryPage = () => {
|
||||
const [sort, setSort] = useParam("sortBy");
|
||||
const sortKey = (sort?.split(":")[0] as SortBy) || SortBy.Name;
|
||||
const sortOrd = (sort?.split(":")[1] as SortOrd) || SortOrd.Asc;
|
||||
@ -76,11 +72,12 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
||||
|
||||
return (
|
||||
<InfiniteFetch
|
||||
query={query(slug, sortKey, sortOrd)}
|
||||
query={query(sortKey, sortOrd)}
|
||||
placeholderCount={15}
|
||||
layout={LayoutComponent.layout}
|
||||
Header={
|
||||
<BrowseSettings
|
||||
availableSorts={Object.values(SortBy)}
|
||||
sortKey={sortKey}
|
||||
sortOrd={sortOrd}
|
||||
setSort={(key, ord) => {
|
||||
@ -98,6 +95,6 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
||||
|
||||
BrowsePage.getLayout = DefaultLayout;
|
||||
|
||||
BrowsePage.getFetchUrls = ({ slug, sortBy }) => [
|
||||
query(slug, sortBy?.split("-")[0] as SortBy, sortBy?.split("-")[1] as SortOrd),
|
||||
BrowsePage.getFetchUrls = ({ sortBy }) => [
|
||||
query(sortBy?.split("-")[0] as SortBy, sortBy?.split("-")[1] as SortOrd),
|
||||
];
|
||||
|
@ -25,6 +25,12 @@ export enum SortBy {
|
||||
AddedDate = "addedDate",
|
||||
}
|
||||
|
||||
export enum SearchSort {
|
||||
Relevance = "relevance",
|
||||
AirDate = "airDate",
|
||||
AddedDate = "addedDate",
|
||||
}
|
||||
|
||||
export enum SortOrd {
|
||||
Asc = "asc",
|
||||
Desc = "desc",
|
||||
|
@ -19,38 +19,67 @@
|
||||
*/
|
||||
|
||||
import { LibraryItem, LibraryItemP, QueryIdentifier, QueryPage } from "@kyoo/models";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ItemGrid } from "../browse/grid";
|
||||
import { itemMap } from "../browse/index";
|
||||
import { EmptyView } from "../fetch";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { createParam } from "solito";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { itemMap } from "../browse";
|
||||
import { SearchSort, SortOrd, SortBy, Layout } from "../browse/types";
|
||||
import { BrowseSettings } from "../browse/header";
|
||||
import { ItemGrid } from "../browse/grid";
|
||||
import { ItemList } from "../browse/list";
|
||||
|
||||
const query = (query: string): QueryIdentifier<LibraryItem> => ({
|
||||
const { useParam } = createParam<{ sortBy?: string }>();
|
||||
|
||||
const query = (
|
||||
query?: string,
|
||||
sortKey?: SearchSort,
|
||||
sortOrd?: SortOrd,
|
||||
): QueryIdentifier<LibraryItem> => ({
|
||||
parser: LibraryItemP,
|
||||
path: ["search", query, "items"],
|
||||
path: ["search", "items"],
|
||||
infinite: true,
|
||||
getNext: () => undefined,
|
||||
params: {
|
||||
q: query,
|
||||
sortBy:
|
||||
sortKey && sortKey != SearchSort.Relevance ? `${sortKey}:${sortOrd ?? "asc"}` : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
export const SearchPage: QueryPage<{ q?: string }> = ({ q }) => {
|
||||
const { t } = useTranslation();
|
||||
const [sort, setSort] = useParam("sortBy");
|
||||
const sortKey = (sort?.split(":")[0] as SearchSort) || SearchSort.Relevance;
|
||||
const sortOrd = (sort?.split(":")[1] as SortOrd) || SortOrd.Asc;
|
||||
const [layout, setLayout] = useState(Layout.Grid);
|
||||
|
||||
const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList;
|
||||
|
||||
const empty = <EmptyView message={t("search.empty")} />;
|
||||
if (!q) return empty;
|
||||
return (
|
||||
<InfiniteFetch
|
||||
query={query(q)}
|
||||
// TODO: Understand why it does not work.
|
||||
incremental={true}
|
||||
layout={ItemGrid.layout}
|
||||
query={query(q, sortKey, sortOrd)}
|
||||
placeholderCount={15}
|
||||
empty={empty}
|
||||
layout={LayoutComponent.layout}
|
||||
Header={
|
||||
<BrowseSettings
|
||||
availableSorts={Object.values(SearchSort)}
|
||||
sortKey={sortKey}
|
||||
sortOrd={sortOrd}
|
||||
setSort={(key, ord) => {
|
||||
setSort(`${key}:${ord}`);
|
||||
}}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{(item) => <ItemGrid {...itemMap(item)} />}
|
||||
{(item) => <LayoutComponent {...itemMap(item)} />}
|
||||
</InfiniteFetch>
|
||||
);
|
||||
};
|
||||
|
||||
SearchPage.getLayout = DefaultLayout;
|
||||
SearchPage.getFetchUrls = ({ q }) => (q ? [query(q)] : []);
|
||||
SearchPage.getFetchUrls = ({ q, sortBy }) => [
|
||||
query(q, sortBy?.split("-")[0] as SearchSort, sortBy?.split("-")[1] as SortOrd),
|
||||
];
|
||||
|
@ -23,7 +23,9 @@
|
||||
"sortby": "Sort by {{key}}",
|
||||
"sortby-tt": "Sort by",
|
||||
"sortkey": {
|
||||
"relevance": "Relevance",
|
||||
"name": "Name",
|
||||
"airDate": "Air Date",
|
||||
"startAir": "Start air",
|
||||
"endAir": "End air",
|
||||
"addedDate": "Added date"
|
||||
|
Loading…
x
Reference in New Issue
Block a user