mirror of
https://github.com/morpheus65535/bazarr.git
synced 2025-04-19 12:24:59 -04:00
Added search poster (#2722)
This commit is contained in:
parent
65968dcd44
commit
e8077d64df
4 changed files with 67 additions and 33 deletions
|
@ -3,7 +3,7 @@
|
|||
from flask_restx import Resource, Namespace, reqparse
|
||||
from unidecode import unidecode
|
||||
|
||||
from app.config import settings
|
||||
from app.config import base_url, settings
|
||||
from app.database import TableShows, TableMovies, database, select
|
||||
|
||||
from ..utils import authenticate
|
||||
|
@ -34,6 +34,7 @@ class Searches(Resource):
|
|||
search_list += database.execute(
|
||||
select(TableShows.title,
|
||||
TableShows.sonarrSeriesId,
|
||||
TableShows.poster,
|
||||
TableShows.year)
|
||||
.order_by(TableShows.title)) \
|
||||
.all()
|
||||
|
@ -43,6 +44,7 @@ class Searches(Resource):
|
|||
search_list += database.execute(
|
||||
select(TableMovies.title,
|
||||
TableMovies.radarrId,
|
||||
TableMovies.poster,
|
||||
TableMovies.year)
|
||||
.order_by(TableMovies.title)) \
|
||||
.all()
|
||||
|
@ -58,8 +60,11 @@ class Searches(Resource):
|
|||
|
||||
if hasattr(x, 'sonarrSeriesId'):
|
||||
result['sonarrSeriesId'] = x.sonarrSeriesId
|
||||
result['poster'] = f"{base_url}/images/series{x.poster}" if x.poster else None
|
||||
|
||||
else:
|
||||
result['radarrId'] = x.radarrId
|
||||
result['poster'] = f"{base_url}/images/movies{x.poster}" if x.poster else None
|
||||
|
||||
results.append(result)
|
||||
|
||||
|
|
9
frontend/src/components/Search.test.tsx
Normal file
9
frontend/src/components/Search.test.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { describe, it } from "vitest";
|
||||
import { Search } from "@/components/index";
|
||||
import { render } from "@/tests";
|
||||
|
||||
describe("Search Bar", () => {
|
||||
it.skip("should render the closed empty state", () => {
|
||||
render(<Search />);
|
||||
});
|
||||
});
|
|
@ -1,48 +1,50 @@
|
|||
import { FunctionComponent, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Autocomplete, ComboboxItem, OptionsFilter, Text } from "@mantine/core";
|
||||
import {
|
||||
ComboboxItem,
|
||||
Image,
|
||||
OptionsFilter,
|
||||
Select,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { chain, includes } from "lodash";
|
||||
import { useServerSearch } from "@/apis/hooks";
|
||||
import { useDebouncedValue } from "@/utilities";
|
||||
|
||||
type SearchResultItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
link: string;
|
||||
poster: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
function useSearch(query: string) {
|
||||
const debouncedQuery = useDebouncedValue(query, 500);
|
||||
const { data } = useServerSearch(debouncedQuery, debouncedQuery.length >= 0);
|
||||
|
||||
const duplicates = chain(data)
|
||||
.groupBy((item) => `${item.title} (${item.year})`)
|
||||
.filter((group) => group.length > 1)
|
||||
.map((group) => `${group[0].title} (${group[0].year})`)
|
||||
.value();
|
||||
|
||||
return useMemo<SearchResultItem[]>(
|
||||
() =>
|
||||
data?.map((v) => {
|
||||
const { link, displayName } = (() => {
|
||||
const hasDuplicate = includes(duplicates, `${v.title} (${v.year})`);
|
||||
|
||||
const { link, label, poster, type, value } = (() => {
|
||||
if (v.sonarrSeriesId) {
|
||||
return {
|
||||
poster: v.poster,
|
||||
link: `/series/${v.sonarrSeriesId}`,
|
||||
displayName: hasDuplicate
|
||||
? `${v.title} (${v.year}) (S)`
|
||||
: `${v.title} (${v.year})`,
|
||||
type: "show",
|
||||
label: `${v.title} (${v.year})`,
|
||||
value: `s-${v.sonarrSeriesId}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (v.radarrId) {
|
||||
return {
|
||||
poster: v.poster,
|
||||
link: `/movies/${v.radarrId}`,
|
||||
displayName: hasDuplicate
|
||||
? `${v.title} (${v.year}) (M)`
|
||||
: `${v.title} (${v.year})`,
|
||||
type: "movie",
|
||||
value: `m-${v.radarrId}`,
|
||||
label: `${v.title} (${v.year})`,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,11 +52,14 @@ function useSearch(query: string) {
|
|||
})();
|
||||
|
||||
return {
|
||||
value: displayName,
|
||||
link,
|
||||
value: value,
|
||||
poster: poster,
|
||||
label: label,
|
||||
type: type,
|
||||
link: link,
|
||||
};
|
||||
}) ?? [],
|
||||
[data, duplicates],
|
||||
[data],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -64,8 +69,8 @@ const optionsFilter: OptionsFilter = ({ options, search }) => {
|
|||
|
||||
return (options as ComboboxItem[]).filter((option) => {
|
||||
return (
|
||||
option.value.toLowerCase().includes(lowercaseSearch) ||
|
||||
option.value
|
||||
option.label.toLowerCase().includes(lowercaseSearch) ||
|
||||
option.label
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLowerCase()
|
||||
|
@ -81,22 +86,36 @@ const Search: FunctionComponent = () => {
|
|||
const results = useSearch(query);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
leftSection={<FontAwesomeIcon icon={faSearch} />}
|
||||
renderOption={(input) => <Text p="xs">{input.option.value}</Text>}
|
||||
<Select
|
||||
placeholder="Search"
|
||||
withCheckIcon={false}
|
||||
leftSection={<FontAwesomeIcon icon={faSearch} />}
|
||||
rightSection={<></>}
|
||||
size="sm"
|
||||
data={results}
|
||||
value={query}
|
||||
searchable
|
||||
scrollAreaProps={{ type: "auto" }}
|
||||
maxDropdownHeight={400}
|
||||
onChange={setQuery}
|
||||
data={results}
|
||||
value={query}
|
||||
onSearchChange={(a) => {
|
||||
setQuery(a);
|
||||
}}
|
||||
onBlur={() => setQuery("")}
|
||||
filter={optionsFilter}
|
||||
onOptionSubmit={(option) =>
|
||||
navigate(results.find((a) => a.value === option)?.link || "/")
|
||||
}
|
||||
></Autocomplete>
|
||||
onOptionSubmit={(option) => {
|
||||
navigate(results.find((a) => a.value === option)?.link || "/");
|
||||
}}
|
||||
renderOption={(input) => {
|
||||
const result = results.find((r) => r.value === input.option.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Image src={result?.poster} w={35} h={50} />
|
||||
<Text p="xs">{result?.label}</Text>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
1
frontend/src/types/api.d.ts
vendored
1
frontend/src/types/api.d.ts
vendored
|
@ -295,6 +295,7 @@ type ItemSearchResult = Partial<SeriesIdType> &
|
|||
Partial<MovieIdType> & {
|
||||
title: string;
|
||||
year: string;
|
||||
poster: string;
|
||||
};
|
||||
|
||||
type BackendError = {
|
||||
|
|
Loading…
Add table
Reference in a new issue