mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-30 16:17:41 +01:00
added theflix scraper and better search component
This commit is contained in:
parent
80cad8f8f2
commit
68e81e8bff
14 changed files with 308 additions and 110 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { GetProviderFromId, MWMedia, MWMediaType } from "scrapers";
|
import { getProviderFromId, MWMedia, MWMediaType } from "scrapers";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Icon, Icons } from "components/Icon";
|
import { Icon, Icons } from "components/Icon";
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ function MediaCardContent({
|
||||||
linkable,
|
linkable,
|
||||||
watchedPercentage,
|
watchedPercentage,
|
||||||
}: MediaCardProps) {
|
}: MediaCardProps) {
|
||||||
const provider = GetProviderFromId(media.providerId);
|
const provider = getProviderFromId(media.providerId);
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -62,7 +62,9 @@ function MediaCardContent({
|
||||||
{/* card content */}
|
{/* card content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h1 className="mb-1 font-bold text-white">{media.title}</h1>
|
<h1 className="mb-1 font-bold text-white">{media.title}</h1>
|
||||||
<MediaMeta content={[provider.displayName, provider.type]} />
|
<MediaMeta
|
||||||
|
content={[provider.displayName, media.mediaType, media.year]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* hoverable chevron */}
|
{/* hoverable chevron */}
|
||||||
|
@ -79,9 +81,8 @@ function MediaCardContent({
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MediaCard(props: MediaCardProps) {
|
export function MediaCard(props: MediaCardProps) {
|
||||||
const provider = GetProviderFromId(props.media.providerId);
|
|
||||||
let link = "movie";
|
let link = "movie";
|
||||||
if (provider?.type === MWMediaType.SERIES) link = "series";
|
if (props.media.mediaType === MWMediaType.MOVIE) link = "series";
|
||||||
|
|
||||||
const content = <MediaCardContent {...props} />;
|
const content = <MediaCardContent {...props} />;
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,5 @@ export interface WatchedMediaCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WatchedMediaCard(props: WatchedMediaCardProps) {
|
export function WatchedMediaCard(props: WatchedMediaCardProps) {
|
||||||
return <MediaCard watchedPercentage={72} media={props.media} linkable />;
|
return <MediaCard watchedPercentage={0} media={props.media} linkable />;
|
||||||
}
|
}
|
||||||
|
|
39
src/hooks/useLoading.ts
Normal file
39
src/hooks/useLoading.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
export function useLoading<T extends (...args: any) => Promise<any>>(
|
||||||
|
action: T
|
||||||
|
) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const [error, setError] = useState<any | undefined>(undefined);
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
isMounted = true;
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const doAction = async (...args: Parameters<T>) => {
|
||||||
|
setLoading(true);
|
||||||
|
setSuccess(false);
|
||||||
|
setError(undefined);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
action(...args)
|
||||||
|
.then((v) => {
|
||||||
|
if (!isMounted) return resolve(undefined);
|
||||||
|
setSuccess(true);
|
||||||
|
resolve(v);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (isMounted) {
|
||||||
|
setError(err);
|
||||||
|
setSuccess(false);
|
||||||
|
}
|
||||||
|
resolve(undefined);
|
||||||
|
});
|
||||||
|
}).finally(() => isMounted && setLoading(false));
|
||||||
|
};
|
||||||
|
return [doAction, loading, error, success];
|
||||||
|
}
|
2
src/mw_constants.ts
Normal file
2
src/mw_constants.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const CORS_PROXY_URL =
|
||||||
|
"https://proxy-1.movie-web.workers.dev/?destination=";
|
|
@ -1,3 +0,0 @@
|
||||||
# about scrapers
|
|
||||||
|
|
||||||
TODO - put stuff here later
|
|
|
@ -1,21 +1,58 @@
|
||||||
import { theFlixMovieScraper } from "./list/theflixmovie";
|
import { theFlixScraper } from "./list/theflix";
|
||||||
import { theFlixSeriesScraper } from "./list/theflixseries";
|
import { MWMedia, MWMediaType, MWPortableMedia, MWQuery } from "./types";
|
||||||
import { MWMediaProvider, MWQuery } from "./types";
|
import { MWWrappedMediaProvider, WrapProvider } from "./wrapper";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
|
||||||
const mediaProvidersUnchecked: MWMediaProvider[] = [
|
const mediaProvidersUnchecked: MWWrappedMediaProvider[] = [
|
||||||
theFlixMovieScraper,
|
WrapProvider(theFlixScraper),
|
||||||
theFlixSeriesScraper,
|
];
|
||||||
]
|
export const mediaProviders: MWWrappedMediaProvider[] =
|
||||||
export const mediaProviders: MWMediaProvider[] = mediaProvidersUnchecked.filter(v=>v.enabled);
|
mediaProvidersUnchecked.filter((v) => v.enabled);
|
||||||
|
|
||||||
export async function SearchProviders(query: MWQuery) {
|
/*
|
||||||
const allQueries = mediaProviders.map(provider => provider.searchForMedia(query));
|
** Fetch all enabled providers for a specific type
|
||||||
|
*/
|
||||||
|
export function GetProvidersForType(type: MWMediaType) {
|
||||||
|
return mediaProviders.filter((v) => v.type.includes(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Call search on all providers that matches query type
|
||||||
|
*/
|
||||||
|
export async function SearchProviders(query: MWQuery): Promise<MWMedia[]> {
|
||||||
|
const allQueries = GetProvidersForType(query.type).map((provider) =>
|
||||||
|
provider.searchForMedia(query)
|
||||||
|
);
|
||||||
const allResults = await Promise.all(allQueries);
|
const allResults = await Promise.all(allQueries);
|
||||||
|
return allResults.flatMap((results) => results);
|
||||||
return allResults.flatMap(results => results);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetProviderFromId(id: string) {
|
/*
|
||||||
return mediaProviders.find(v=>v.id===id);
|
** Get a provider by a id
|
||||||
|
*/
|
||||||
|
export function getProviderFromId(id: string) {
|
||||||
|
return mediaProviders.find((v) => v.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Turn media object into a portable media object
|
||||||
|
*/
|
||||||
|
export function convertMediaToPortable(media: MWMedia): MWPortableMedia {
|
||||||
|
return {
|
||||||
|
mediaId: media.mediaId,
|
||||||
|
providerId: media.providerId,
|
||||||
|
mediaType: media.mediaType,
|
||||||
|
episode: media.episode,
|
||||||
|
season: media.season,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Turn portable media into media object
|
||||||
|
*/
|
||||||
|
export async function convertPortableToMedia(
|
||||||
|
portable: MWPortableMedia
|
||||||
|
): Promise<MWMedia | undefined> {
|
||||||
|
const provider = getProviderFromId(portable.providerId);
|
||||||
|
return await provider?.getMediaFromPortable(portable);
|
||||||
}
|
}
|
||||||
|
|
46
src/scrapers/list/theflix/index.ts
Normal file
46
src/scrapers/list/theflix/index.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
MWMediaProvider,
|
||||||
|
MWMediaType,
|
||||||
|
MWPortableMedia,
|
||||||
|
MWQuery,
|
||||||
|
} from "scrapers/types";
|
||||||
|
|
||||||
|
import {
|
||||||
|
searchTheFlix,
|
||||||
|
getDataFromSearch,
|
||||||
|
turnDataIntoMedia,
|
||||||
|
} from "scrapers/list/theflix/search";
|
||||||
|
|
||||||
|
import { getDataFromPortableSearch } from "scrapers/list/theflix/portableToMedia";
|
||||||
|
import { MWProviderMediaResult } from "scrapers";
|
||||||
|
|
||||||
|
export const theFlixScraper: MWMediaProvider = {
|
||||||
|
id: "theflix",
|
||||||
|
enabled: true,
|
||||||
|
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
||||||
|
displayName: "theflix",
|
||||||
|
|
||||||
|
async getMediaFromPortable(
|
||||||
|
media: MWPortableMedia
|
||||||
|
): Promise<MWProviderMediaResult> {
|
||||||
|
const data: any = await getDataFromPortableSearch(media);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...media,
|
||||||
|
year: new Date(data.releaseDate).getFullYear().toString(),
|
||||||
|
title: data.name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
|
||||||
|
const searchRes = await searchTheFlix(query);
|
||||||
|
const searchData = await getDataFromSearch(searchRes, 10);
|
||||||
|
|
||||||
|
const results: MWProviderMediaResult[] = [];
|
||||||
|
for (let item of searchData) {
|
||||||
|
results.push(turnDataIntoMedia(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
};
|
35
src/scrapers/list/theflix/portableToMedia.ts
Normal file
35
src/scrapers/list/theflix/portableToMedia.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { CORS_PROXY_URL } from "mw_constants";
|
||||||
|
import { MWMediaType, MWPortableMedia } from "scrapers/types";
|
||||||
|
|
||||||
|
const getTheFlixUrl = (media: MWPortableMedia, params?: URLSearchParams) => {
|
||||||
|
if (media.mediaType === MWMediaType.MOVIE) {
|
||||||
|
return `https://theflix.to/movie/${media.mediaId}?${params}`;
|
||||||
|
} else if (media.mediaType === MWMediaType.SERIES) {
|
||||||
|
return `https://theflix.to/tv-show/${media.mediaId}/season-${media.season}/episode-${media.episode}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getDataFromPortableSearch(
|
||||||
|
media: MWPortableMedia
|
||||||
|
): Promise<any> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("movieInfo", media.mediaId);
|
||||||
|
|
||||||
|
const res = await fetch(CORS_PROXY_URL + getTheFlixUrl(media, params)).then(
|
||||||
|
(d) => d.text()
|
||||||
|
);
|
||||||
|
|
||||||
|
const node: Element = Array.from(
|
||||||
|
new DOMParser()
|
||||||
|
.parseFromString(res, "text/html")
|
||||||
|
.querySelectorAll(`script[id="__NEXT_DATA__"]`)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (media.mediaType === MWMediaType.MOVIE) {
|
||||||
|
return JSON.parse(node.innerHTML).props.pageProps.movie;
|
||||||
|
} else if (media.mediaType === MWMediaType.SERIES) {
|
||||||
|
return JSON.parse(node.innerHTML).props.pageProps.selectedTv;
|
||||||
|
}
|
||||||
|
}
|
44
src/scrapers/list/theflix/search.ts
Normal file
44
src/scrapers/list/theflix/search.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { CORS_PROXY_URL } from "mw_constants";
|
||||||
|
import { MWMediaType, MWProviderMediaResult, MWQuery } from "scrapers";
|
||||||
|
|
||||||
|
const getTheFlixUrl = (type: "tv-shows" | "movies", params: URLSearchParams) =>
|
||||||
|
`https://theflix.to/${type}/trending?${params}`;
|
||||||
|
|
||||||
|
export async function searchTheFlix(query: MWQuery): Promise<string> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", query.searchQuery);
|
||||||
|
return await fetch(
|
||||||
|
CORS_PROXY_URL +
|
||||||
|
getTheFlixUrl(
|
||||||
|
query.type === MWMediaType.MOVIE ? "movies" : "tv-shows",
|
||||||
|
params
|
||||||
|
)
|
||||||
|
).then((d) => d.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataFromSearch(page: string, limit: number = 10): any[] {
|
||||||
|
const node: Element = Array.from(
|
||||||
|
new DOMParser()
|
||||||
|
.parseFromString(page, "text/html")
|
||||||
|
.querySelectorAll(`script[id="__NEXT_DATA__"]`)
|
||||||
|
)[0];
|
||||||
|
const data = JSON.parse(node.innerHTML);
|
||||||
|
return data.props.pageProps.mainList.docs
|
||||||
|
.filter((d: any) => d.available)
|
||||||
|
.slice(0, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function turnDataIntoMedia(data: any): MWProviderMediaResult {
|
||||||
|
return {
|
||||||
|
mediaId:
|
||||||
|
data.id +
|
||||||
|
"-" +
|
||||||
|
data.name
|
||||||
|
.replace(/[^a-z0-9]+|\s+/gim, " ")
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.toLowerCase(),
|
||||||
|
title: data.name,
|
||||||
|
year: new Date(data.releaseDate).getFullYear().toString(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
import { MWMedia, MWMediaProvider, MWMediaType, MWPortableMedia, MWQuery } from "scrapers/types";
|
|
||||||
|
|
||||||
export const theFlixMovieScraper: MWMediaProvider = {
|
|
||||||
id: "theflixmovie",
|
|
||||||
enabled: true,
|
|
||||||
type: MWMediaType.MOVIE,
|
|
||||||
displayName: "TheFlix",
|
|
||||||
|
|
||||||
async getMediaFromPortable(media: MWPortableMedia): Promise<MWMedia> {
|
|
||||||
return {
|
|
||||||
...media,
|
|
||||||
title: "title is here"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async searchForMedia(query: MWQuery): Promise<MWMedia[]> {
|
|
||||||
return [{
|
|
||||||
mediaId: "a",
|
|
||||||
providerId: this.id,
|
|
||||||
title: `movie testing in progress`,
|
|
||||||
}];
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { MWMedia, MWMediaProvider, MWMediaType, MWPortableMedia, MWQuery } from "scrapers/types";
|
|
||||||
|
|
||||||
export const theFlixSeriesScraper: MWMediaProvider = {
|
|
||||||
id: "theflixseries",
|
|
||||||
enabled: true,
|
|
||||||
type: MWMediaType.SERIES,
|
|
||||||
displayName: "TheFlix",
|
|
||||||
|
|
||||||
async getMediaFromPortable(media: MWPortableMedia): Promise<MWMedia> {
|
|
||||||
return {
|
|
||||||
...media,
|
|
||||||
title: "title here"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async searchForMedia(query: MWQuery): Promise<MWMedia[]> {
|
|
||||||
return [{
|
|
||||||
mediaId: "b",
|
|
||||||
providerId: this.id,
|
|
||||||
title: `series test`,
|
|
||||||
}];
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -5,25 +5,31 @@ export enum MWMediaType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MWPortableMedia {
|
export interface MWPortableMedia {
|
||||||
mediaId: string,
|
mediaId: string;
|
||||||
providerId: string,
|
mediaType: MWMediaType;
|
||||||
|
providerId: string;
|
||||||
|
season?: number;
|
||||||
|
episode?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MWMedia extends MWPortableMedia {
|
export interface MWMedia extends MWPortableMedia {
|
||||||
title: string,
|
title: string;
|
||||||
|
year: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MWProviderMediaResult = Omit<MWMedia, "mediaType" | "providerId">;
|
||||||
|
|
||||||
export interface MWQuery {
|
export interface MWQuery {
|
||||||
searchQuery: string,
|
searchQuery: string;
|
||||||
type: MWMediaType,
|
type: MWMediaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MWMediaProvider {
|
export interface MWMediaProvider {
|
||||||
id: string, // id of provider, must be unique
|
id: string; // id of provider, must be unique
|
||||||
enabled: boolean,
|
enabled: boolean;
|
||||||
type: MWMediaType,
|
type: MWMediaType[];
|
||||||
displayName: string,
|
displayName: string;
|
||||||
|
|
||||||
getMediaFromPortable(media: MWPortableMedia): Promise<MWMedia>,
|
getMediaFromPortable(media: MWPortableMedia): Promise<MWProviderMediaResult>;
|
||||||
searchForMedia(query: MWQuery): Promise<MWMedia[]>,
|
searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
also type safety is important, this is all spaghetti with "any" everywhere
|
also type safety is important, this is all spaghetti with "any" everywhere
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
function buildStoreObject(d: any) {
|
function buildStoreObject(d: any) {
|
||||||
const data: any = {
|
const data: any = {
|
||||||
versions: d.versions,
|
versions: d.versions,
|
||||||
|
@ -23,7 +22,7 @@ function buildStoreObject(d: any) {
|
||||||
if (version.constructor !== Number || version < 0) version = -42;
|
if (version.constructor !== Number || version < 0) version = -42;
|
||||||
// invalid on purpose so it will reset
|
// invalid on purpose so it will reset
|
||||||
else {
|
else {
|
||||||
version = (version as number + 1).toString();
|
version = ((version as number) + 1).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if version exists
|
// check if version exists
|
||||||
|
@ -190,15 +189,19 @@ export function versionedStoreBuilder(): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// register helper
|
// register helper
|
||||||
if (type === "instance") this._data.instanceHelpers[name as string] = helper;
|
if (type === "instance")
|
||||||
else if (type === "static") this._data.staticHelpers[name as string] = helper;
|
this._data.instanceHelpers[name as string] = helper;
|
||||||
|
else if (type === "static")
|
||||||
|
this._data.staticHelpers[name as string] = helper;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
// check if version list doesnt skip versions
|
// check if version list doesnt skip versions
|
||||||
const versionListSorted = this._data.versionList.sort((a: number, b: number) => a - b);
|
const versionListSorted = this._data.versionList.sort(
|
||||||
|
(a: number, b: number) => a - b
|
||||||
|
);
|
||||||
versionListSorted.forEach((v: any, i: number, arr: any[]) => {
|
versionListSorted.forEach((v: any, i: number, arr: any[]) => {
|
||||||
if (i === 0) return;
|
if (i === 0) return;
|
||||||
if (v !== arr[i - 1] + 1)
|
if (v !== arr[i - 1] + 1)
|
||||||
|
|
|
@ -9,9 +9,57 @@ import { Loading } from "components/layout/Loading";
|
||||||
import { Tagline } from "components/Text/Tagline";
|
import { Tagline } from "components/Text/Tagline";
|
||||||
import { Title } from "components/Text/Title";
|
import { Title } from "components/Text/Title";
|
||||||
import { useDebounce } from "hooks/useDebounce";
|
import { useDebounce } from "hooks/useDebounce";
|
||||||
|
import { useLoading } from "hooks/useLoading";
|
||||||
|
|
||||||
|
function SearchLoading() {
|
||||||
|
return <Loading className="my-12" text="Fetching your favourite shows..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchResultsView({ searchQuery }: { searchQuery: MWQuery }) {
|
||||||
|
const [results, setResults] = useState<MWMedia[]>([]);
|
||||||
|
const [runSearchQuery, loading, error, success] = useLoading(
|
||||||
|
(query: MWQuery) => SearchProviders(query)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQuery.searchQuery !== "") runSearch(searchQuery);
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
async function runSearch(query: MWQuery) {
|
||||||
|
const results = await runSearchQuery(query);
|
||||||
|
if (!results) return;
|
||||||
|
setResults(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* results */}
|
||||||
|
{success && results.length > 0 ? (
|
||||||
|
<SectionHeading title="Search results" icon={Icons.SEARCH}>
|
||||||
|
{results.map((v) => (
|
||||||
|
<WatchedMediaCard
|
||||||
|
key={[v.mediaId, v.providerId].join("|")}
|
||||||
|
media={v}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SectionHeading>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* no results */}
|
||||||
|
{success && results.length === 0 ? <p>No results found</p> : null}
|
||||||
|
|
||||||
|
{/* error */}
|
||||||
|
{error ? <p>All scrapers failed</p> : null}
|
||||||
|
|
||||||
|
{/* Loading icon */}
|
||||||
|
{loading ? <SearchLoading /> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function SearchView() {
|
export function SearchView() {
|
||||||
const [results, setResults] = useState<MWMedia[]>([]);
|
const [searching, setSearching] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [search, setSearch] = useState<MWQuery>({
|
const [search, setSearch] = useState<MWQuery>({
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
type: MWMediaType.MOVIE,
|
type: MWMediaType.MOVIE,
|
||||||
|
@ -19,19 +67,12 @@ export function SearchView() {
|
||||||
|
|
||||||
const debouncedSearch = useDebounce<MWQuery>(search, 2000);
|
const debouncedSearch = useDebounce<MWQuery>(search, 2000);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedSearch.searchQuery !== "") runSearch(debouncedSearch);
|
setSearching(search.searchQuery !== "");
|
||||||
}, [debouncedSearch]);
|
setLoading(search.searchQuery !== "");
|
||||||
useEffect(() => {
|
|
||||||
setResults([]);
|
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
useEffect(() => {
|
||||||
async function runSearch(query: MWQuery) {
|
setLoading(false);
|
||||||
const results = await SearchProviders(query);
|
}, [debouncedSearch]);
|
||||||
setResults(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLoading = search.searchQuery !== "" && results.length === 0;
|
|
||||||
const hasResult = results.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThinContainer>
|
<ThinContainer>
|
||||||
|
@ -48,18 +89,11 @@ export function SearchView() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* results */}
|
{/* results view */}
|
||||||
{hasResult ? (
|
{loading ? (
|
||||||
<SectionHeading title="Search results" icon={Icons.SEARCH}>
|
<SearchLoading />
|
||||||
{results.map((v) => (
|
) : searching ? (
|
||||||
<WatchedMediaCard media={v} />
|
<SearchResultsView searchQuery={debouncedSearch} />
|
||||||
))}
|
|
||||||
</SectionHeading>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Loading icon */}
|
|
||||||
{isLoading ? (
|
|
||||||
<Loading className="my-12" text="Fetching your favourite shows..." />
|
|
||||||
) : null}
|
) : null}
|
||||||
</ThinContainer>
|
</ThinContainer>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue