mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-28 01:41:50 +01:00
refactor search
This commit is contained in:
parent
7c890443e0
commit
95f03db5b2
@ -19,6 +19,7 @@ import {
|
|||||||
} from "./types/justwatch";
|
} from "./types/justwatch";
|
||||||
import { MWMediaMeta, MWMediaType } from "./types/mw";
|
import { MWMediaMeta, MWMediaType } from "./types/mw";
|
||||||
import {
|
import {
|
||||||
|
TMDBContentTypes,
|
||||||
TMDBMediaResult,
|
TMDBMediaResult,
|
||||||
TMDBMovieData,
|
TMDBMovieData,
|
||||||
TMDBSeasonMetaResult,
|
TMDBSeasonMetaResult,
|
||||||
@ -177,7 +178,7 @@ export async function convertLegacyUrl(
|
|||||||
const urlParts = url.split("/").slice(2);
|
const urlParts = url.split("/").slice(2);
|
||||||
const [, type, id] = urlParts[0].split("-", 3);
|
const [, type, id] = urlParts[0].split("-", 3);
|
||||||
|
|
||||||
const mediaType = TMDBMediaToMediaType(type);
|
const mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
|
||||||
const meta = await getLegacyMetaFromId(mediaType, id);
|
const meta = await getLegacyMetaFromId(mediaType, id);
|
||||||
|
|
||||||
if (!meta) return undefined;
|
if (!meta) return undefined;
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { SimpleCache } from "@/utils/cache";
|
import { SimpleCache } from "@/utils/cache";
|
||||||
|
|
||||||
import {
|
import { formatTMDBMeta, formatTMDBSearchResult, multiSearch } from "./tmdb";
|
||||||
formatTMDBMeta,
|
|
||||||
formatTMDBSearchResult,
|
|
||||||
mediaTypeToTMDB,
|
|
||||||
searchMedia,
|
|
||||||
} from "./tmdb";
|
|
||||||
import { MWMediaMeta, MWQuery } from "./types/mw";
|
import { MWMediaMeta, MWQuery } from "./types/mw";
|
||||||
|
|
||||||
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
|
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
|
||||||
@ -16,11 +11,11 @@ cache.initialize();
|
|||||||
|
|
||||||
export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
|
export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
|
||||||
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
|
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
|
||||||
const { searchQuery, type } = query;
|
const { searchQuery } = query;
|
||||||
|
|
||||||
const data = await searchMedia(searchQuery, mediaTypeToTMDB(type));
|
const data = await multiSearch(searchQuery);
|
||||||
const results = data.results.map((v) => {
|
const results = data.map((v) => {
|
||||||
const formattedResult = formatTMDBSearchResult(v, mediaTypeToTMDB(type));
|
const formattedResult = formatTMDBSearchResult(v, v.media_type);
|
||||||
return formatTMDBMeta(formattedResult);
|
return formatTMDBMeta(formattedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,29 +11,25 @@ import {
|
|||||||
TMDBMediaResult,
|
TMDBMediaResult,
|
||||||
TMDBMovieData,
|
TMDBMovieData,
|
||||||
TMDBMovieExternalIds,
|
TMDBMovieExternalIds,
|
||||||
TMDBMovieResponse,
|
|
||||||
TMDBMovieResult,
|
|
||||||
TMDBMovieSearchResult,
|
TMDBMovieSearchResult,
|
||||||
TMDBSearchResult,
|
TMDBSearchResult,
|
||||||
TMDBSeason,
|
TMDBSeason,
|
||||||
TMDBSeasonMetaResult,
|
TMDBSeasonMetaResult,
|
||||||
TMDBShowData,
|
TMDBShowData,
|
||||||
TMDBShowExternalIds,
|
TMDBShowExternalIds,
|
||||||
TMDBShowResponse,
|
|
||||||
TMDBShowResult,
|
|
||||||
TMDBShowSearchResult,
|
TMDBShowSearchResult,
|
||||||
} from "./types/tmdb";
|
} from "./types/tmdb";
|
||||||
import { mwFetch } from "../helpers/fetch";
|
import { mwFetch } from "../helpers/fetch";
|
||||||
|
|
||||||
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes {
|
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes {
|
||||||
if (type === MWMediaType.MOVIE) return "movie";
|
if (type === MWMediaType.MOVIE) return TMDBContentTypes.MOVIE;
|
||||||
if (type === MWMediaType.SERIES) return "show";
|
if (type === MWMediaType.SERIES) return TMDBContentTypes.TV;
|
||||||
throw new Error("unsupported type");
|
throw new Error("unsupported type");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TMDBMediaToMediaType(type: string): MWMediaType {
|
export function TMDBMediaToMediaType(type: TMDBContentTypes): MWMediaType {
|
||||||
if (type === "movie") return MWMediaType.MOVIE;
|
if (type === TMDBContentTypes.MOVIE) return MWMediaType.MOVIE;
|
||||||
if (type === "show") return MWMediaType.SERIES;
|
if (type === TMDBContentTypes.TV) return MWMediaType.SERIES;
|
||||||
throw new Error("unsupported type");
|
throw new Error("unsupported type");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ export function decodeTMDBId(
|
|||||||
if (prefix !== "tmdb") return null;
|
if (prefix !== "tmdb") return null;
|
||||||
let mediaType;
|
let mediaType;
|
||||||
try {
|
try {
|
||||||
mediaType = TMDBMediaToMediaType(type);
|
mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -131,36 +127,6 @@ async function get<T>(url: string, params?: object): Promise<T> {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchMedia(
|
|
||||||
query: string,
|
|
||||||
type: TMDBContentTypes
|
|
||||||
): Promise<TMDBMovieResponse | TMDBShowResponse> {
|
|
||||||
let data;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "movie":
|
|
||||||
data = await get<TMDBMovieResponse>("search/movie", {
|
|
||||||
query,
|
|
||||||
include_adult: false,
|
|
||||||
language: "en-US",
|
|
||||||
page: 1,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "show":
|
|
||||||
data = await get<TMDBShowResponse>("search/tv", {
|
|
||||||
query,
|
|
||||||
include_adult: false,
|
|
||||||
language: "en-US",
|
|
||||||
page: 1,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Invalid media type");
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function multiSearch(
|
export async function multiSearch(
|
||||||
query: string
|
query: string
|
||||||
): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> {
|
): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> {
|
||||||
@ -172,7 +138,9 @@ export async function multiSearch(
|
|||||||
});
|
});
|
||||||
// filter out results that aren't movies or shows
|
// filter out results that aren't movies or shows
|
||||||
const results = data.results.filter(
|
const results = data.results.filter(
|
||||||
(r) => r.media_type === "movie" || r.media_type === "tv"
|
(r) =>
|
||||||
|
r.media_type === TMDBContentTypes.MOVIE ||
|
||||||
|
r.media_type === TMDBContentTypes.TV
|
||||||
);
|
);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@ -183,20 +151,21 @@ export async function generateQuickSearchMediaUrl(
|
|||||||
const data = await multiSearch(query);
|
const data = await multiSearch(query);
|
||||||
if (data.length === 0) return undefined;
|
if (data.length === 0) return undefined;
|
||||||
const result = data[0];
|
const result = data[0];
|
||||||
const type = result.media_type === "movie" ? "movie" : "show";
|
const title =
|
||||||
const title = result.media_type === "movie" ? result.title : result.name;
|
result.media_type === TMDBContentTypes.MOVIE ? result.title : result.name;
|
||||||
|
|
||||||
return `/media/${TMDBIdToUrlId(
|
return `/media/${TMDBIdToUrlId(
|
||||||
TMDBMediaToMediaType(type),
|
TMDBMediaToMediaType(result.media_type),
|
||||||
result.id.toString(),
|
result.id.toString(),
|
||||||
title
|
title
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conditional type which for inferring the return type based on the content type
|
// Conditional type which for inferring the return type based on the content type
|
||||||
type MediaDetailReturn<T extends TMDBContentTypes> = T extends "movie"
|
type MediaDetailReturn<T extends TMDBContentTypes> =
|
||||||
|
T extends TMDBContentTypes.MOVIE
|
||||||
? TMDBMovieData
|
? TMDBMovieData
|
||||||
: T extends "show"
|
: T extends TMDBContentTypes.TV
|
||||||
? TMDBShowData
|
? TMDBShowData
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
@ -204,10 +173,10 @@ export function getMediaDetails<
|
|||||||
T extends TMDBContentTypes,
|
T extends TMDBContentTypes,
|
||||||
TReturn = MediaDetailReturn<T>
|
TReturn = MediaDetailReturn<T>
|
||||||
>(id: string, type: T): Promise<TReturn> {
|
>(id: string, type: T): Promise<TReturn> {
|
||||||
if (type === "movie") {
|
if (type === TMDBContentTypes.MOVIE) {
|
||||||
return get<TReturn>(`/movie/${id}`);
|
return get<TReturn>(`/movie/${id}`);
|
||||||
}
|
}
|
||||||
if (type === "show") {
|
if (type === TMDBContentTypes.TV) {
|
||||||
return get<TReturn>(`/tv/${id}`);
|
return get<TReturn>(`/tv/${id}`);
|
||||||
}
|
}
|
||||||
throw new Error("Invalid media type");
|
throw new Error("Invalid media type");
|
||||||
@ -236,10 +205,10 @@ export async function getExternalIds(
|
|||||||
let data;
|
let data;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "movie":
|
case TMDBContentTypes.MOVIE:
|
||||||
data = await get<TMDBMovieExternalIds>(`/movie/${id}/external_ids`);
|
data = await get<TMDBMovieExternalIds>(`/movie/${id}/external_ids`);
|
||||||
break;
|
break;
|
||||||
case "show":
|
case TMDBContentTypes.TV:
|
||||||
data = await get<TMDBShowExternalIds>(`/tv/${id}/external_ids`);
|
data = await get<TMDBShowExternalIds>(`/tv/${id}/external_ids`);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -263,12 +232,12 @@ export async function getMovieFromExternalId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatTMDBSearchResult(
|
export function formatTMDBSearchResult(
|
||||||
result: TMDBShowResult | TMDBMovieResult,
|
result: TMDBMovieSearchResult | TMDBShowSearchResult,
|
||||||
mediatype: TMDBContentTypes
|
mediatype: TMDBContentTypes
|
||||||
): TMDBMediaResult {
|
): TMDBMediaResult {
|
||||||
const type = TMDBMediaToMediaType(mediatype);
|
const type = TMDBMediaToMediaType(mediatype);
|
||||||
if (type === MWMediaType.SERIES) {
|
if (type === MWMediaType.SERIES) {
|
||||||
const show = result as TMDBShowResult;
|
const show = result as TMDBShowSearchResult;
|
||||||
return {
|
return {
|
||||||
title: show.name,
|
title: show.name,
|
||||||
poster: getMediaPoster(show.poster_path),
|
poster: getMediaPoster(show.poster_path),
|
||||||
@ -277,7 +246,8 @@ export function formatTMDBSearchResult(
|
|||||||
object_type: mediatype,
|
object_type: mediatype,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const movie = result as TMDBMovieResult;
|
|
||||||
|
const movie = result as TMDBMovieSearchResult;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: movie.title,
|
title: movie.title,
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export type TMDBContentTypes = "movie" | "show";
|
export enum TMDBContentTypes {
|
||||||
|
MOVIE = "movie",
|
||||||
|
TV = "tv",
|
||||||
|
}
|
||||||
|
|
||||||
export type TMDBSeasonShort = {
|
export type TMDBSeasonShort = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -183,54 +186,6 @@ export interface TMDBEpisodeResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TMDBShowResult {
|
|
||||||
adult: boolean;
|
|
||||||
backdrop_path: string | null;
|
|
||||||
genre_ids: number[];
|
|
||||||
id: number;
|
|
||||||
origin_country: string[];
|
|
||||||
original_language: string;
|
|
||||||
original_name: string;
|
|
||||||
overview: string;
|
|
||||||
popularity: number;
|
|
||||||
poster_path: string | null;
|
|
||||||
first_air_date: string;
|
|
||||||
name: string;
|
|
||||||
vote_average: number;
|
|
||||||
vote_count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TMDBShowResponse {
|
|
||||||
page: number;
|
|
||||||
results: TMDBShowResult[];
|
|
||||||
total_pages: number;
|
|
||||||
total_results: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TMDBMovieResult {
|
|
||||||
adult: boolean;
|
|
||||||
backdrop_path: string | null;
|
|
||||||
genre_ids: number[];
|
|
||||||
id: number;
|
|
||||||
original_language: string;
|
|
||||||
original_title: string;
|
|
||||||
overview: string;
|
|
||||||
popularity: number;
|
|
||||||
poster_path: string | null;
|
|
||||||
release_date: string;
|
|
||||||
title: string;
|
|
||||||
video: boolean;
|
|
||||||
vote_average: number;
|
|
||||||
vote_count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TMDBMovieResponse {
|
|
||||||
page: number;
|
|
||||||
results: TMDBMovieResult[];
|
|
||||||
total_pages: number;
|
|
||||||
total_results: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TMDBEpisode {
|
export interface TMDBEpisode {
|
||||||
air_date: string;
|
air_date: string;
|
||||||
episode_number: number;
|
episode_number: number;
|
||||||
@ -316,7 +271,7 @@ export interface TMDBMovieSearchResult {
|
|||||||
original_title: string;
|
original_title: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
poster_path: string;
|
poster_path: string;
|
||||||
media_type: "movie";
|
media_type: TMDBContentTypes.MOVIE;
|
||||||
genre_ids: number[];
|
genre_ids: number[];
|
||||||
popularity: number;
|
popularity: number;
|
||||||
release_date: string;
|
release_date: string;
|
||||||
@ -334,7 +289,7 @@ export interface TMDBShowSearchResult {
|
|||||||
original_name: string;
|
original_name: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
poster_path: string;
|
poster_path: string;
|
||||||
media_type: "tv";
|
media_type: TMDBContentTypes.TV;
|
||||||
genre_ids: number[];
|
genre_ids: number[];
|
||||||
popularity: number;
|
popularity: number;
|
||||||
first_air_date: string;
|
first_air_date: string;
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { MWQuery } from "@/backend/metadata/types/mw";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
|
|
||||||
|
|
||||||
import { DropdownButton } from "./buttons/DropdownButton";
|
|
||||||
import { Icon, Icons } from "./Icon";
|
import { Icon, Icons } from "./Icon";
|
||||||
import { TextInputControl } from "./text-inputs/TextInputControl";
|
import { TextInputControl } from "./text-inputs/TextInputControl";
|
||||||
|
|
||||||
export interface SearchBarProps {
|
export interface SearchBarProps {
|
||||||
buttonText?: string;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange: (value: MWQuery, force: boolean) => void;
|
onChange: (value: MWQuery, force: boolean) => void;
|
||||||
onUnFocus: () => void;
|
onUnFocus: () => void;
|
||||||
@ -16,9 +11,6 @@ export interface SearchBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SearchBarInput(props: SearchBarProps) {
|
export function SearchBarInput(props: SearchBarProps) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
function setSearch(value: string) {
|
function setSearch(value: string) {
|
||||||
props.onChange(
|
props.onChange(
|
||||||
{
|
{
|
||||||
@ -28,15 +20,6 @@ export function SearchBarInput(props: SearchBarProps) {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function setType(type: string) {
|
|
||||||
props.onChange(
|
|
||||||
{
|
|
||||||
...props.value,
|
|
||||||
type: type as MWMediaType,
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col rounded-[28px] bg-denim-400 transition-colors focus-within:bg-denim-400 hover:bg-denim-500 sm:flex-row sm:items-center">
|
<div className="relative flex flex-col rounded-[28px] bg-denim-400 transition-colors focus-within:bg-denim-400 hover:bg-denim-500 sm:flex-row sm:items-center">
|
||||||
@ -51,31 +34,6 @@ export function SearchBarInput(props: SearchBarProps) {
|
|||||||
className="w-full flex-1 bg-transparent px-4 py-4 pl-12 text-white placeholder-denim-700 focus:outline-none sm:py-4 sm:pr-2"
|
className="w-full flex-1 bg-transparent px-4 py-4 pl-12 text-white placeholder-denim-700 focus:outline-none sm:py-4 sm:pr-2"
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="px-4 py-4 pt-0 sm:px-2 sm:py-2">
|
|
||||||
<DropdownButton
|
|
||||||
icon={Icons.SEARCH}
|
|
||||||
open={dropdownOpen}
|
|
||||||
setOpen={(val) => setDropdownOpen(val)}
|
|
||||||
selectedItem={props.value.type}
|
|
||||||
setSelectedItem={(val) => setType(val)}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
id: MWMediaType.MOVIE,
|
|
||||||
name: t("searchBar.movie"),
|
|
||||||
icon: Icons.FILM,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: MWMediaType.SERIES,
|
|
||||||
name: t("searchBar.series"),
|
|
||||||
icon: Icons.CLAPPER_BOARD,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onClick={() => setDropdownOpen((old) => !old)}
|
|
||||||
>
|
|
||||||
{props.buttonText || t("searchBar.search")}
|
|
||||||
</DropdownButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
getMovieFromExternalId,
|
getMovieFromExternalId,
|
||||||
} from "@/backend/metadata/tmdb";
|
} from "@/backend/metadata/tmdb";
|
||||||
import { MWMediaType } from "@/backend/metadata/types/mw";
|
import { MWMediaType } from "@/backend/metadata/types/mw";
|
||||||
|
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
|
||||||
import { BookmarkStoreData } from "@/state/bookmark/types";
|
import { BookmarkStoreData } from "@/state/bookmark/types";
|
||||||
import { isNotNull } from "@/utils/typeguard";
|
import { isNotNull } from "@/utils/typeguard";
|
||||||
|
|
||||||
@ -59,7 +60,10 @@ export async function migrateV3Videos(
|
|||||||
clone.item.meta.id = migratedId;
|
clone.item.meta.id = migratedId;
|
||||||
if (clone.item.series) {
|
if (clone.item.series) {
|
||||||
const series = clone.item.series;
|
const series = clone.item.series;
|
||||||
const details = await getMediaDetails(migratedId, "show");
|
const details = await getMediaDetails(
|
||||||
|
migratedId,
|
||||||
|
TMDBContentTypes.TV
|
||||||
|
);
|
||||||
|
|
||||||
const season = details.seasons.find(
|
const season = details.seasons.find(
|
||||||
(v) => v.season_number === series.season
|
(v) => v.season_number === series.season
|
||||||
|
Loading…
Reference in New Issue
Block a user