diff --git a/src/backend/embeds/testEmbedScraper.ts b/src/backend/embeds/testEmbedScraper.ts index b97c7f05..8da3786f 100644 --- a/src/backend/embeds/testEmbedScraper.ts +++ b/src/backend/embeds/testEmbedScraper.ts @@ -2,16 +2,25 @@ import { MWEmbedType } from "../helpers/embed"; import { registerEmbedScraper } from "../helpers/register"; import { MWStreamQuality, MWStreamType } from "../helpers/streams"; +const timeout = (time: number) => + new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); + registerEmbedScraper({ id: "testembed", rank: 23, for: MWEmbedType.OPENLOAD, - async getStream({ progress, url }) { - console.log("scraping url: ", url); + async getStream({ progress }) { + await timeout(1000); progress(25); + await timeout(1000); progress(50); + await timeout(1000); progress(75); + throw new Error("failed to load or something"); + await timeout(1000); return { streamUrl: "hello-world", type: MWStreamType.MP4, diff --git a/src/backend/embeds/testEmbedScraperTwo.ts b/src/backend/embeds/testEmbedScraperTwo.ts new file mode 100644 index 00000000..7d409d98 --- /dev/null +++ b/src/backend/embeds/testEmbedScraperTwo.ts @@ -0,0 +1,24 @@ +import { MWEmbedType } from "../helpers/embed"; +import { registerEmbedScraper } from "../helpers/register"; +import { MWStreamQuality, MWStreamType } from "../helpers/streams"; + +const timeout = (time: number) => + new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); + +registerEmbedScraper({ + id: "testembedtwo", + rank: 19, + for: MWEmbedType.ANOTHER, + + async getStream({ progress }) { + progress(75); + await timeout(1000); + return { + streamUrl: "hello-world-5", + type: MWStreamType.MP4, + quality: MWStreamQuality.Q1080P, + }; + }, +}); diff --git a/src/backend/helpers/embed.ts b/src/backend/helpers/embed.ts index 20cd3b29..88c0420d 100644 --- a/src/backend/helpers/embed.ts +++ b/src/backend/helpers/embed.ts @@ -2,6 +2,7 @@ import { MWStream } from "./streams"; export enum MWEmbedType { OPENLOAD = "openload", + ANOTHER = "another", } export type MWEmbed = { diff --git a/src/backend/helpers/register.ts b/src/backend/helpers/register.ts index 001aed16..9d3f76c2 100644 --- a/src/backend/helpers/register.ts +++ b/src/backend/helpers/register.ts @@ -1,4 +1,4 @@ -import { MWEmbedScraper } from "./embed"; +import { MWEmbedScraper, MWEmbedType } from "./embed"; import { MWProvider } from "./provider"; let providers: MWProvider[] = []; @@ -15,8 +15,8 @@ export function registerEmbedScraper(embed: MWEmbedScraper) { export function initializeScraperStore() { // sort by ranking - providers = providers.sort((a, b) => a.rank - b.rank); - embeds = embeds.sort((a, b) => a.rank - b.rank); + providers = providers.sort((a, b) => b.rank - a.rank); + embeds = embeds.sort((a, b) => b.rank - a.rank); // check for invalid ranks let lastRank: null | number = null; @@ -50,6 +50,11 @@ export function initializeScraperStore() { const embedIds = embeds.map((v) => v.id); if (embedIds.length > 0 && new Set(embedIds).size !== embedIds.length) throw new Error("Duplicate IDS in embed scrapers"); + + // check for duplicate embed types + const embedTypes = embeds.map((v) => v.for); + if (embedTypes.length > 0 && new Set(embedTypes).size !== embedTypes.length) + throw new Error("Duplicate types in embed scrapers"); } export function getProviders(): MWProvider[] { @@ -59,3 +64,9 @@ export function getProviders(): MWProvider[] { export function getEmbeds(): MWEmbedScraper[] { return embeds; } + +export function getEmbedScraperByType( + type: MWEmbedType +): MWEmbedScraper | null { + return getEmbeds().find((v) => v.for === type) ?? null; +} diff --git a/src/backend/helpers/run.ts b/src/backend/helpers/run.ts new file mode 100644 index 00000000..6b3a548d --- /dev/null +++ b/src/backend/helpers/run.ts @@ -0,0 +1,52 @@ +import { MWEmbed, MWEmbedContext, MWEmbedScraper } from "./embed"; +import { + MWProvider, + MWProviderContext, + MWProviderScrapeResult, +} from "./provider"; +import { getEmbedScraperByType } from "./register"; +import { MWStream } from "./streams"; + +function sortProviderResult( + ctx: MWProviderScrapeResult +): MWProviderScrapeResult { + ctx.embeds = ctx.embeds + .map<[MWEmbed, MWEmbedScraper | null]>((v) => [ + v, + v.type ? getEmbedScraperByType(v.type) : null, + ]) + .sort(([, a], [, b]) => (b?.rank ?? 0) - (a?.rank ?? 0)) + .map((v) => v[0]); + return ctx; +} + +export async function runProvider( + provider: MWProvider, + ctx: MWProviderContext +): Promise { + try { + const data = await provider.scrape(ctx); + return sortProviderResult(data); + } catch (err) { + console.error("Failed to run provider", { + id: provider.id, + ctx: { ...ctx }, + }); + throw err; + } +} + +export async function runEmbedScraper( + scraper: MWEmbedScraper, + ctx: MWEmbedContext +): Promise { + try { + return await scraper.getStream(ctx); + } catch (err) { + console.error("Failed to run embed scraper", { + id: scraper.id, + ctx: { ...ctx }, + }); + throw err; + } +} diff --git a/src/backend/helpers/scrape.ts b/src/backend/helpers/scrape.ts new file mode 100644 index 00000000..fe0dc61b --- /dev/null +++ b/src/backend/helpers/scrape.ts @@ -0,0 +1,117 @@ +import { MWProviderScrapeResult } from "./provider"; +import { getEmbedScraperByType, getProviders } from "./register"; +import { runEmbedScraper, runProvider } from "./run"; +import { MWStream } from "./streams"; + +interface MWProgressData { + type: "embed" | "provider"; + id: string; + percentage: number; + errored: boolean; +} +interface MWNextData { + id: string; + type: "embed" | "provider"; +} + +export interface MWProviderRunContext { + tmdb: string; + imdb: string; + onProgress?: (data: MWProgressData) => void; + onNext?: (data: MWNextData) => void; +} + +async function findBestEmbedStream( + result: MWProviderScrapeResult, + ctx: MWProviderRunContext +): Promise { + if (result.stream) return result.stream; + + for (const embed of result.embeds) { + if (!embed.type) continue; + const scraper = getEmbedScraperByType(embed.type); + if (!scraper) throw new Error("Type for embed not found"); + + ctx.onNext?.({ id: scraper.id, type: "embed" }); + + let stream: MWStream; + try { + stream = await runEmbedScraper(scraper, { + url: embed.url, + progress(num) { + ctx.onProgress?.({ + errored: false, + id: scraper.id, + percentage: num, + type: "embed", + }); + }, + }); + } catch { + ctx.onProgress?.({ + errored: true, + id: scraper.id, + percentage: 100, + type: "embed", + }); + continue; + } + + ctx.onProgress?.({ + errored: false, + id: scraper.id, + percentage: 100, + type: "embed", + }); + + return stream; + } + + return null; +} + +export async function findBestStream( + ctx: MWProviderRunContext +): Promise { + const providers = getProviders(); + + for (const provider of providers) { + ctx.onNext?.({ id: provider.id, type: "provider" }); + let result: MWProviderScrapeResult; + try { + result = await runProvider(provider, { + imdbId: ctx.imdb, + tmdbId: ctx.tmdb, + progress(num) { + ctx.onProgress?.({ + percentage: num, + errored: false, + id: provider.id, + type: "provider", + }); + }, + }); + } catch (err) { + ctx.onProgress?.({ + percentage: 100, + errored: true, + id: provider.id, + type: "provider", + }); + continue; + } + + ctx.onProgress?.({ + errored: false, + id: provider.id, + percentage: 100, + type: "provider", + }); + + const stream = await findBestEmbedStream(result, ctx); + if (!stream) continue; + return stream; + } + + return null; +} diff --git a/src/backend/index.ts b/src/backend/index.ts index 34983fad..1384e109 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,7 +1,6 @@ import { initializeScraperStore } from "./helpers/register"; // TODO backend system: -// - run providers/embedscrapers in webworkers for multithreading and isolation // - caption support // - hooks to run all providers one by one // - move over old providers to new system @@ -10,8 +9,11 @@ import { initializeScraperStore } from "./helpers/register"; // providers // -- nothing here yet import "./providers/testProvider"; +import "./providers/testProviderTwo"; // embeds // -- nothing here yet +import "./embeds/testEmbedScraper"; +import "./embeds/testEmbedScraperTwo"; initializeScraperStore(); diff --git a/src/backend/metadata/search.ts b/src/backend/metadata/search.ts index fa26b35d..0b0964a7 100644 --- a/src/backend/metadata/search.ts +++ b/src/backend/metadata/search.ts @@ -1,5 +1,4 @@ -import { MWMediaType, MWQuery } from "@/providers"; -import { MWMediaMeta } from "./types"; +import { MWMediaMeta, MWMediaType, MWQuery } from "./types"; const JW_API_BASE = "https://apis.justwatch.com"; diff --git a/src/backend/metadata/types.ts b/src/backend/metadata/types.ts index a74e0520..afabb970 100644 --- a/src/backend/metadata/types.ts +++ b/src/backend/metadata/types.ts @@ -11,3 +11,8 @@ export type MWMediaMeta = { poster?: string; type: MWMediaType; }; + +export interface MWQuery { + searchQuery: string; + type: MWMediaType; +} diff --git a/src/backend/providers/testProvider.ts b/src/backend/providers/testProvider.ts index 21376d29..c00615c8 100644 --- a/src/backend/providers/testProvider.ts +++ b/src/backend/providers/testProvider.ts @@ -1,32 +1,35 @@ -import { MWEmbedType } from "../helpers/embed"; import { registerProvider } from "../helpers/register"; -import { MWStreamQuality, MWStreamType } from "../helpers/streams"; import { MWMediaType } from "../metadata/types"; +const timeout = (time: number) => + new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); + registerProvider({ id: "testprov", rank: 42, type: [MWMediaType.MOVIE], - async scrape({ progress, imdbId, tmdbId }) { - console.log("scraping provider for: ", imdbId, tmdbId); + async scrape({ progress }) { + await timeout(1000); progress(25); + await timeout(1000); progress(50); + await timeout(1000); progress(75); + await timeout(1000); - // providers can optionally provide a stream themselves, - // incase they host their own streams instead of using embeds return { - stream: { - streamUrl: "hello-world", - type: MWStreamType.HLS, - quality: MWStreamQuality.Q1080P, - }, embeds: [ - { - type: MWEmbedType.OPENLOAD, - url: "https://google.com", - }, + // { + // type: MWEmbedType.OPENLOAD, + // url: "https://google.com", + // }, + // { + // type: MWEmbedType.ANOTHER, + // url: "https://google.com", + // }, ], }; }, diff --git a/src/backend/providers/testProviderTwo.ts b/src/backend/providers/testProviderTwo.ts new file mode 100644 index 00000000..f3a39fbf --- /dev/null +++ b/src/backend/providers/testProviderTwo.ts @@ -0,0 +1,37 @@ +import { MWEmbedType } from "../helpers/embed"; +import { registerProvider } from "../helpers/register"; +import { MWMediaType } from "../metadata/types"; + +const timeout = (time: number) => + new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); + +registerProvider({ + id: "testprov2", + rank: 40, + type: [MWMediaType.MOVIE], + + async scrape({ progress }) { + await timeout(1000); + progress(25); + await timeout(1000); + progress(50); + await timeout(1000); + progress(75); + await timeout(1000); + + return { + embeds: [ + { + type: MWEmbedType.OPENLOAD, + url: "https://google.com", + }, + { + type: MWEmbedType.ANOTHER, + url: "https://google.com", + }, + ], + }; + }, +}); diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index df844c83..8e97138d 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,6 +1,6 @@ +import { MWMediaType, MWQuery } from "@/backend/metadata/types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { MWMediaType, MWQuery } from "@/providers"; import { DropdownButton } from "./buttons/DropdownButton"; import { Icon, Icons } from "./Icon"; import { TextInputControl } from "./text-inputs/TextInputControl"; diff --git a/src/components/layout/Seasons.tsx b/src/components/layout/Seasons.tsx index f2416372..1f31a1b9 100644 --- a/src/components/layout/Seasons.tsx +++ b/src/components/layout/Seasons.tsx @@ -7,17 +7,9 @@ import { Icons } from "@/components/Icon"; import { WatchedEpisode } from "@/components/media/WatchedEpisodeButton"; import { useLoading } from "@/hooks/useLoading"; import { serializePortableMedia } from "@/hooks/usePortableMedia"; -import { - convertMediaToPortable, - MWMedia, - MWMediaSeasons, - MWMediaSeason, - MWPortableMedia, -} from "@/providers"; -import { getSeasonDataFromMedia } from "@/providers/methods/seasons"; export interface SeasonsProps { - media: MWMedia; + media: any; } export function LoadingSeasons(props: { error?: boolean }) { @@ -45,80 +37,73 @@ export function LoadingSeasons(props: { error?: boolean }) { } export function Seasons(props: SeasonsProps) { - const { t } = useTranslation(); - - const [searchSeasons, loading, error, success] = useLoading( - (portableMedia: MWPortableMedia) => getSeasonDataFromMedia(portableMedia) - ); - const history = useHistory(); - const [seasons, setSeasons] = useState({ seasons: [] }); - const seasonSelected = props.media.seasonId as string; - const episodeSelected = props.media.episodeId as string; - - useEffect(() => { - (async () => { - const seasonData = await searchSeasons(props.media); - setSeasons(seasonData); - })(); - }, [searchSeasons, props.media]); - - function navigateToSeasonAndEpisode(seasonId: string, episodeId: string) { - const newMedia: MWMedia = { ...props.media }; - newMedia.episodeId = episodeId; - newMedia.seasonId = seasonId; - history.replace( - `/media/${newMedia.mediaType}/${serializePortableMedia( - convertMediaToPortable(newMedia) - )}` - ); - } - - const mapSeason = (season: MWMediaSeason) => ({ - id: season.id, - name: season.title || `${t("seasons.season", { season: season.sort })}`, - }); - - const options = seasons.seasons.map(mapSeason); - - const foundSeason = seasons.seasons.find( - (season) => season.id === seasonSelected - ); - const selectedItem = foundSeason ? mapSeason(foundSeason) : null; - - return ( - <> - {loading ? : null} - {error ? : null} - {success && seasons.seasons.length ? ( - <> - - navigateToSeasonAndEpisode( - seasonItem.id, - seasons.seasons.find((s) => s.id === seasonItem.id)?.episodes[0] - .id as string - ) - } - /> - {seasons.seasons - .find((s) => s.id === seasonSelected) - ?.episodes.map((v) => ( - navigateToSeasonAndEpisode(seasonSelected, v.id)} - /> - ))} - - ) : null} - - ); + // const { t } = useTranslation(); + // const [searchSeasons, loading, error, success] = useLoading( + // (portableMedia: MWPortableMedia) => getSeasonDataFromMedia(portableMedia) + // ); + // const history = useHistory(); + // const [seasons, setSeasons] = useState({ seasons: [] }); + // const seasonSelected = props.media.seasonId as string; + // const episodeSelected = props.media.episodeId as string; + // useEffect(() => { + // (async () => { + // const seasonData = await searchSeasons(props.media); + // setSeasons(seasonData); + // })(); + // }, [searchSeasons, props.media]); + // function navigateToSeasonAndEpisode(seasonId: string, episodeId: string) { + // const newMedia: MWMedia = { ...props.media }; + // newMedia.episodeId = episodeId; + // newMedia.seasonId = seasonId; + // history.replace( + // `/media/${newMedia.mediaType}/${serializePortableMedia( + // convertMediaToPortable(newMedia) + // )}` + // ); + // } + // const mapSeason = (season: MWMediaSeason) => ({ + // id: season.id, + // name: season.title || `${t("seasons.season", { season: season.sort })}`, + // }); + // const options = seasons.seasons.map(mapSeason); + // const foundSeason = seasons.seasons.find( + // (season) => season.id === seasonSelected + // ); + // const selectedItem = foundSeason ? mapSeason(foundSeason) : null; + // return ( + // <> + // {loading ? : null} + // {error ? : null} + // {success && seasons.seasons.length ? ( + // <> + // + // navigateToSeasonAndEpisode( + // seasonItem.id, + // seasons.seasons.find((s) => s.id === seasonItem.id)?.episodes[0] + // .id as string + // ) + // } + // /> + // {seasons.seasons + // .find((s) => s.id === seasonSelected) + // ?.episodes.map((v) => ( + // navigateToSeasonAndEpisode(seasonSelected, v.id)} + // /> + // ))} + // + // ) : null} + // + // ); } diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index 34fb09b4..ffc7a49b 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -1,10 +1,9 @@ import { Link } from "react-router-dom"; import { DotList } from "@/components/text/DotList"; -import { MWSearchResult } from "@/backend/metadata/search"; -import { MWMediaType } from "@/providers"; +import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types"; export interface MediaCardProps { - media: MWSearchResult; + media: MWMediaMeta; linkable?: boolean; } diff --git a/src/components/media/WatchedEpisodeButton.tsx b/src/components/media/WatchedEpisodeButton.tsx index aa699859..779f2aef 100644 --- a/src/components/media/WatchedEpisodeButton.tsx +++ b/src/components/media/WatchedEpisodeButton.tsx @@ -1,25 +1,24 @@ -import { getEpisodeFromMedia, MWMedia } from "@/providers"; +import { MWMediaMeta } from "@/backend/metadata/types"; import { useWatchedContext, getWatchedFromPortable } from "@/state/watched"; import { Episode } from "./EpisodeButton"; export interface WatchedEpisodeProps { - media: MWMedia; + media: MWMediaMeta; onClick?: () => void; active?: boolean; } export function WatchedEpisode(props: WatchedEpisodeProps) { - const { watched } = useWatchedContext(); - const foundWatched = getWatchedFromPortable(watched.items, props.media); - const episode = getEpisodeFromMedia(props.media); - const watchedPercentage = (foundWatched && foundWatched.percentage) || 0; - - return ( - - ); + // const { watched } = useWatchedContext(); + // const foundWatched = getWatchedFromPortable(watched.items, props.media); + // // const episode = getEpisodeFromMedia(props.media); + // const watchedPercentage = (foundWatched && foundWatched.percentage) || 0; + // return ( + // + // ); } diff --git a/src/components/media/WatchedMediaCard.tsx b/src/components/media/WatchedMediaCard.tsx index f1d37374..6dcf7064 100644 --- a/src/components/media/WatchedMediaCard.tsx +++ b/src/components/media/WatchedMediaCard.tsx @@ -1,8 +1,8 @@ -import { MWSearchResult } from "@/backend/metadata/search"; +import { MWMediaMeta } from "@/backend/metadata/types"; import { MediaCard } from "./MediaCard"; export interface WatchedMediaCardProps { - media: MWSearchResult; + media: MWMediaMeta; } export function WatchedMediaCard(props: WatchedMediaCardProps) { diff --git a/src/hooks/usePortableMedia.ts b/src/hooks/usePortableMedia.ts deleted file mode 100644 index 81744fb9..00000000 --- a/src/hooks/usePortableMedia.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; -import { MWPortableMedia } from "@/providers"; - -export function deserializePortableMedia(media: string): MWPortableMedia { - return JSON.parse(atob(decodeURIComponent(media))); -} - -export function serializePortableMedia(media: MWPortableMedia): string { - const data = encodeURIComponent(btoa(JSON.stringify(media))); - return data; -} - -export function usePortableMedia(): MWPortableMedia | undefined { - const { media } = useParams<{ media: string }>(); - const [mediaObject, setMediaObject] = useState( - undefined - ); - - useEffect(() => { - try { - setMediaObject(deserializePortableMedia(media)); - } catch (err) { - console.error("Failed to deserialize portable media", err); - setMediaObject(undefined); - } - }, [media, setMediaObject]); - - return mediaObject; -} diff --git a/src/hooks/useScrape.ts b/src/hooks/useScrape.ts new file mode 100644 index 00000000..2104e5f6 --- /dev/null +++ b/src/hooks/useScrape.ts @@ -0,0 +1,59 @@ +import { findBestStream } from "@/backend/helpers/scrape"; +import { MWStream } from "@/backend/helpers/streams"; +import { useEffect, useState } from "react"; + +interface ScrapeEventLog { + type: "provider" | "embed"; + errored: boolean; + percentage: number; + id: string; +} + +export function useScrape() { + const [eventLog, setEventLog] = useState([]); + const [stream, setStream] = useState(null); + const [pending, setPending] = useState(true); + + useEffect(() => { + setPending(true); + setStream(null); + setEventLog([]); + (async () => { + // TODO has test inputs + const scrapedStream = await findBestStream({ + imdb: "test1", + tmdb: "test2", + onNext(ctx) { + setEventLog((arr) => [ + ...arr, + { + errored: false, + id: ctx.id, + type: ctx.type, + percentage: 0, + }, + ]); + }, + onProgress(ctx) { + setEventLog((arr) => { + const item = arr.reverse().find((v) => v.id === ctx.id); + if (item) { + item.errored = ctx.errored; + item.percentage = ctx.percentage; + } + return [...arr]; + }); + }, + }); + + setPending(false); + setStream(scrapedStream); + })(); + }, []); + + return { + stream, + pending, + eventLog, + }; +} diff --git a/src/hooks/useSearchQuery.ts b/src/hooks/useSearchQuery.ts index 7f83f6aa..b20c78ab 100644 --- a/src/hooks/useSearchQuery.ts +++ b/src/hooks/useSearchQuery.ts @@ -1,6 +1,6 @@ +import { MWMediaType, MWQuery } from "@/backend/metadata/types"; import React, { useRef, useState } from "react"; import { generatePath, useHistory, useRouteMatch } from "react-router-dom"; -import { MWMediaType, MWQuery } from "@/providers"; export function useSearchQuery(): [ MWQuery, diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 091aa967..ebe4f016 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -1,5 +1,4 @@ import { Redirect, Route, Switch } from "react-router-dom"; -import { MWMediaType } from "@/providers"; import { BookmarkContextProvider } from "@/state/bookmark"; import { WatchedContextProvider } from "@/state/watched"; @@ -7,6 +6,7 @@ import { NotFoundPage } from "@/views/notfound/NotFoundView"; import { MediaView } from "@/views/MediaView"; import { SearchView } from "@/views/search/SearchView"; import { TestView } from "@/views/TestView"; +import { MWMediaType } from "@/backend/metadata/types"; function App() { return ( diff --git a/src/state/bookmark/context.tsx b/src/state/bookmark/context.tsx index c8eb2dca..2217908f 100644 --- a/src/state/bookmark/context.tsx +++ b/src/state/bookmark/context.tsx @@ -1,3 +1,4 @@ +import { MWMediaMeta } from "@/backend/metadata/types"; import { createContext, ReactNode, @@ -6,7 +7,6 @@ import { useMemo, useState, } from "react"; -import { getProviderMetadata, MWMediaMeta } from "@/providers"; import { BookmarkStore } from "./store"; interface BookmarkStoreData { @@ -64,7 +64,7 @@ export function BookmarkContextProvider(props: { children: ReactNode }) { const contextValue = useMemo( () => ({ - setItemBookmark(media: MWMediaMeta, bookmarked: boolean) { + setItemBookmark(media: any, bookmarked: boolean) { setBookmarked((data: BookmarkStoreData) => { if (bookmarked) { const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media); @@ -90,9 +90,7 @@ export function BookmarkContextProvider(props: { children: ReactNode }) { }); }, getFilteredBookmarks() { - return bookmarkStorage.bookmarks.filter( - (bookmark) => getProviderMetadata(bookmark.providerId)?.enabled - ); + return []; }, bookmarkStore: bookmarkStorage, }), diff --git a/src/state/watched/context.tsx b/src/state/watched/context.tsx index 789d2308..4fa68f3e 100644 --- a/src/state/watched/context.tsx +++ b/src/state/watched/context.tsx @@ -1,3 +1,4 @@ +import { MWMediaMeta } from "@/backend/metadata/types"; import React, { createContext, ReactNode, @@ -6,7 +7,6 @@ import React, { useMemo, useState, } from "react"; -import { MWMediaMeta, getProviderMetadata, MWMediaType } from "@/providers"; import { VideoProgressStore } from "./store"; interface WatchedStoreItem extends MWMediaMeta { @@ -28,13 +28,7 @@ export function getWatchedFromPortable( items: WatchedStoreItem[], media: MWMediaMeta ): WatchedStoreItem | undefined { - return items.find( - (v) => - v.mediaId === media.mediaId && - v.providerId === media.providerId && - v.episodeId === media.episodeId && - v.seasonId === media.seasonId - ); + return undefined; } const WatchedContext = createContext({ @@ -73,76 +67,73 @@ export function WatchedContextProvider(props: { children: ReactNode }) { progress: number, total: number ): void { - setWatched((data: WatchedStoreData) => { - let item = getWatchedFromPortable(data.items, media); - if (!item) { - item = { - mediaId: media.mediaId, - mediaType: media.mediaType, - providerId: media.providerId, - title: media.title, - year: media.year, - percentage: 0, - progress: 0, - episodeId: media.episodeId, - seasonId: media.seasonId, - }; - data.items.push(item); - } - - // update actual item - item.progress = progress; - item.percentage = Math.round((progress / total) * 100); - - return data; - }); + // setWatched((data: WatchedStoreData) => { + // let item = getWatchedFromPortable(data.items, media); + // if (!item) { + // item = { + // mediaId: media.mediaId, + // mediaType: media.mediaType, + // providerId: media.providerId, + // title: media.title, + // year: media.year, + // percentage: 0, + // progress: 0, + // episodeId: media.episodeId, + // seasonId: media.seasonId, + // }; + // data.items.push(item); + // } + // // update actual item + // item.progress = progress; + // item.percentage = Math.round((progress / total) * 100); + // return data; + // }); }, getFilteredWatched() { // remove disabled providers - let filtered = watched.items.filter( - (item) => getProviderMetadata(item.providerId)?.enabled - ); - - // get highest episode number for every anime/season - const highestEpisode: Record = {}; - const highestWatchedItem: Record = {}; - filtered = filtered.filter((item) => { - if ( - [MWMediaType.ANIME, MWMediaType.SERIES].includes(item.mediaType) - ) { - const key = `${item.mediaType}-${item.mediaId}`; - const current: [number, number] = [ - item.episodeId ? parseInt(item.episodeId, 10) : -1, - item.seasonId ? parseInt(item.seasonId, 10) : -1, - ]; - let existing = highestEpisode[key]; - if (!existing) { - existing = current; - highestEpisode[key] = current; - highestWatchedItem[key] = item; - } - - if ( - current[0] > existing[0] || - (current[0] === existing[0] && current[1] > existing[1]) - ) { - highestEpisode[key] = current; - highestWatchedItem[key] = item; - } - return false; - } - return true; - }); - - return [...filtered, ...Object.values(highestWatchedItem)]; + // let filtered = watched.items.filter( + // (item) => getProviderMetadata(item.providerId)?.enabled + // ); + // // get highest episode number for every anime/season + // const highestEpisode: Record = {}; + // const highestWatchedItem: Record = {}; + // filtered = filtered.filter((item) => { + // if ( + // [MWMediaType.ANIME, MWMediaType.SERIES].includes(item.mediaType) + // ) { + // const key = `${item.mediaType}-${item.mediaId}`; + // const current: [number, number] = [ + // item.episodeId ? parseInt(item.episodeId, 10) : -1, + // item.seasonId ? parseInt(item.seasonId, 10) : -1, + // ]; + // let existing = highestEpisode[key]; + // if (!existing) { + // existing = current; + // highestEpisode[key] = current; + // highestWatchedItem[key] = item; + // } + // if ( + // current[0] > existing[0] || + // (current[0] === existing[0] && current[1] > existing[1]) + // ) { + // highestEpisode[key] = current; + // highestWatchedItem[key] = item; + // } + // return false; + // } + // return true; + // }); + // return [...filtered, ...Object.values(highestWatchedItem)]; }, watched, }), - [watched, setWatched] + [ + /*watched, setWatched*/ + ] ); return ( - + {props.children} ); diff --git a/src/state/watched/store.ts b/src/state/watched/store.ts index 065de4ec..eab57080 100644 --- a/src/state/watched/store.ts +++ b/src/state/watched/store.ts @@ -1,6 +1,4 @@ -import { MWMediaType } from "@/providers"; import { versionedStoreBuilder } from "@/utils/storage"; -import { WatchedStoreData } from "./context"; export const VideoProgressStore = versionedStoreBuilder() .setKey("video-progress") @@ -9,79 +7,79 @@ export const VideoProgressStore = versionedStoreBuilder() }) .addVersion({ version: 1, - migrate(data: any) { - const output: WatchedStoreData = { items: [] }; + migrate() { + // const output: WatchedStoreData = { items: [] }; - if (!data || data.constructor !== Object) return output; + // if (!data || data.constructor !== Object) return output; - Object.keys(data).forEach((scraperId) => { - if (scraperId === "--version") return; - if (scraperId === "save") return; + // Object.keys(data).forEach((scraperId) => { + // if (scraperId === "--version") return; + // if (scraperId === "save") return; - if ( - data[scraperId].movie && - data[scraperId].movie.constructor === Object - ) { - Object.keys(data[scraperId].movie).forEach((movieId) => { - try { - output.items.push({ - mediaId: movieId.includes("player.php") - ? movieId.split("player.php%3Fimdb%3D")[1] - : movieId, - mediaType: MWMediaType.MOVIE, - providerId: scraperId, - title: data[scraperId].movie[movieId].full.meta.title, - year: data[scraperId].movie[movieId].full.meta.year, - progress: data[scraperId].movie[movieId].full.currentlyAt, - percentage: Math.round( - (data[scraperId].movie[movieId].full.currentlyAt / - data[scraperId].movie[movieId].full.totalDuration) * - 100 - ), - }); - } catch (err) { - console.error( - `Failed to migrate movie: ${scraperId}/${movieId}`, - data[scraperId].movie[movieId] - ); - } - }); - } + // if ( + // data[scraperId].movie && + // data[scraperId].movie.constructor === Object + // ) { + // Object.keys(data[scraperId].movie).forEach((movieId) => { + // try { + // output.items.push({ + // mediaId: movieId.includes("player.php") + // ? movieId.split("player.php%3Fimdb%3D")[1] + // : movieId, + // mediaType: MWMediaType.MOVIE, + // providerId: scraperId, + // title: data[scraperId].movie[movieId].full.meta.title, + // year: data[scraperId].movie[movieId].full.meta.year, + // progress: data[scraperId].movie[movieId].full.currentlyAt, + // percentage: Math.round( + // (data[scraperId].movie[movieId].full.currentlyAt / + // data[scraperId].movie[movieId].full.totalDuration) * + // 100 + // ), + // }); + // } catch (err) { + // console.error( + // `Failed to migrate movie: ${scraperId}/${movieId}`, + // data[scraperId].movie[movieId] + // ); + // } + // }); + // } - if ( - data[scraperId].show && - data[scraperId].show.constructor === Object - ) { - Object.keys(data[scraperId].show).forEach((showId) => { - if (data[scraperId].show[showId].constructor !== Object) return; - Object.keys(data[scraperId].show[showId]).forEach((episodeId) => { - try { - output.items.push({ - mediaId: showId, - mediaType: MWMediaType.SERIES, - providerId: scraperId, - title: data[scraperId].show[showId][episodeId].meta.title, - year: data[scraperId].show[showId][episodeId].meta.year, - percentage: Math.round( - (data[scraperId].show[showId][episodeId].currentlyAt / - data[scraperId].show[showId][episodeId].totalDuration) * - 100 - ), - progress: data[scraperId].show[showId][episodeId].currentlyAt, - episodeId: - data[scraperId].show[showId][episodeId].show.episode, - seasonId: data[scraperId].show[showId][episodeId].show.season, - }); - } catch (err) { - console.error( - `Failed to migrate series: ${scraperId}/${showId}/${episodeId}`, - data[scraperId].show[showId][episodeId] - ); - } - }); - }); - } - }); + // if ( + // data[scraperId].show && + // data[scraperId].show.constructor === Object + // ) { + // Object.keys(data[scraperId].show).forEach((showId) => { + // if (data[scraperId].show[showId].constructor !== Object) return; + // Object.keys(data[scraperId].show[showId]).forEach((episodeId) => { + // try { + // output.items.push({ + // mediaId: showId, + // mediaType: MWMediaType.SERIES, + // providerId: scraperId, + // title: data[scraperId].show[showId][episodeId].meta.title, + // year: data[scraperId].show[showId][episodeId].meta.year, + // percentage: Math.round( + // (data[scraperId].show[showId][episodeId].currentlyAt / + // data[scraperId].show[showId][episodeId].totalDuration) * + // 100 + // ), + // progress: data[scraperId].show[showId][episodeId].currentlyAt, + // episodeId: + // data[scraperId].show[showId][episodeId].show.episode, + // seasonId: data[scraperId].show[showId][episodeId].show.season, + // }); + // } catch (err) { + // console.error( + // `Failed to migrate series: ${scraperId}/${showId}/${episodeId}`, + // data[scraperId].show[showId][episodeId] + // ); + // } + // }); + // }); + // } + // }); return output; }, diff --git a/src/views/MediaView.tsx b/src/views/MediaView.tsx index 77d54d2c..5cea1a35 100644 --- a/src/views/MediaView.tsx +++ b/src/views/MediaView.tsx @@ -1,220 +1,215 @@ -import { ReactElement, useCallback, useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; +// import { ReactElement, useCallback, useEffect, useState } from "react"; +// import { useHistory } from "react-router-dom"; +// import { useTranslation } from "react-i18next"; +// import { IconPatch } from "@/components/buttons/IconPatch"; +// import { Icons } from "@/components/Icon"; +// import { Navigation } from "@/components/layout/Navigation"; +// import { Paper } from "@/components/layout/Paper"; +// import { LoadingSeasons, Seasons } from "@/components/layout/Seasons"; +// import { DecoratedVideoPlayer } from "@/components/video/DecoratedVideoPlayer"; +// import { ArrowLink } from "@/components/text/ArrowLink"; +// import { DotList } from "@/components/text/DotList"; +// import { Title } from "@/components/text/Title"; +// import { useLoading } from "@/hooks/useLoading"; +// import { usePortableMedia } from "@/hooks/usePortableMedia"; +// import { +// getIfBookmarkedFromPortable, +// useBookmarkContext, +// } from "@/state/bookmark"; +// import { getWatchedFromPortable, useWatchedContext } from "@/state/watched"; +// import { SourceControl } from "@/components/video/controls/SourceControl"; +// import { ProgressListenerControl } from "@/components/video/controls/ProgressListenerControl"; +// import { Loading } from "@/components/layout/Loading"; +// import { NotFoundChecks } from "./notfound/NotFoundChecks"; + import { useTranslation } from "react-i18next"; -import { IconPatch } from "@/components/buttons/IconPatch"; -import { Icons } from "@/components/Icon"; +import { useHistory } from "react-router-dom"; import { Navigation } from "@/components/layout/Navigation"; -import { Paper } from "@/components/layout/Paper"; -import { LoadingSeasons, Seasons } from "@/components/layout/Seasons"; -import { DecoratedVideoPlayer } from "@/components/video/DecoratedVideoPlayer"; import { ArrowLink } from "@/components/text/ArrowLink"; -import { DotList } from "@/components/text/DotList"; -import { Title } from "@/components/text/Title"; -import { useLoading } from "@/hooks/useLoading"; -import { usePortableMedia } from "@/hooks/usePortableMedia"; -import { - MWPortableMedia, - getStream, - MWMediaStream, - MWMedia, - convertPortableToMedia, - getProviderFromId, - MWMediaProvider, - MWMediaType, -} from "@/providers"; -import { - getIfBookmarkedFromPortable, - useBookmarkContext, -} from "@/state/bookmark"; -import { getWatchedFromPortable, useWatchedContext } from "@/state/watched"; -import { SourceControl } from "@/components/video/controls/SourceControl"; -import { ProgressListenerControl } from "@/components/video/controls/ProgressListenerControl"; -import { Loading } from "@/components/layout/Loading"; -import { NotFoundChecks } from "./notfound/NotFoundChecks"; -interface StyledMediaViewProps { - media: MWMedia; - stream: MWMediaStream; -} +// interface StyledMediaViewProps { +// media: MWMedia; +// stream: MWMediaStream; +// } -export function SkeletonVideoPlayer(props: { error?: boolean }) { - return ( -
- {props.error ? ( -
- -

Couldn't get your stream

-
- ) : ( -
- -

Getting your stream...

-
- )} -
- ); -} +// export function SkeletonVideoPlayer(props: { error?: boolean }) { +// return ( +//
+// {props.error ? ( +//
+// +//

Couldn't get your stream

+//
+// ) : ( +//
+// +//

Getting your stream...

+//
+// )} +//
+// ); +// } -function StyledMediaView(props: StyledMediaViewProps) { - const reactHistory = useHistory(); - const watchedStore = useWatchedContext(); - const startAtTime: number | undefined = getWatchedFromPortable( - watchedStore.watched.items, - props.media - )?.progress; +// function StyledMediaView(props: StyledMediaViewProps) { +// const reactHistory = useHistory(); +// const watchedStore = useWatchedContext(); +// const startAtTime: number | undefined = getWatchedFromPortable( +// watchedStore.watched.items, +// props.media +// )?.progress; - const updateProgress = useCallback( - (time: number, duration: number) => { - // Don't update stored progress if less than 30s into the video - if (time <= 30) return; - watchedStore.updateProgress(props.media, time, duration); - }, - [props, watchedStore] - ); +// const updateProgress = useCallback( +// (time: number, duration: number) => { +// // Don't update stored progress if less than 30s into the video +// if (time <= 30) return; +// watchedStore.updateProgress(props.media, time, duration); +// }, +// [props, watchedStore] +// ); - const goBack = useCallback(() => { - if (reactHistory.action !== "POP") reactHistory.goBack(); - else reactHistory.push("/"); - }, [reactHistory]); +// const goBack = useCallback(() => { +// if (reactHistory.action !== "POP") reactHistory.goBack(); +// else reactHistory.push("/"); +// }, [reactHistory]); - return ( -
- - - - -
- ); -} +// return ( +//
+// +// +// +// +//
+// ); +// } -interface StyledMediaFooterProps { - media: MWMedia; - provider: MWMediaProvider; -} +// interface StyledMediaFooterProps { +// media: MWMedia; +// provider: MWMediaProvider; +// } -function StyledMediaFooter(props: StyledMediaFooterProps) { - const { setItemBookmark, getFilteredBookmarks } = useBookmarkContext(); - const isBookmarked = getIfBookmarkedFromPortable( - getFilteredBookmarks(), - props.media - ); +// function StyledMediaFooter(props: StyledMediaFooterProps) { +// const { setItemBookmark, getFilteredBookmarks } = useBookmarkContext(); +// const isBookmarked = getIfBookmarkedFromPortable( +// getFilteredBookmarks(), +// props.media +// ); - return ( - -
-
- {props.media.title} - -
-
- setItemBookmark(props.media, !isBookmarked)} - clickable - /> -
-
- {props.media.mediaType !== MWMediaType.MOVIE ? ( - - ) : null} -
- ); -} +// return ( +// +//
+//
+// {props.media.title} +// +//
+//
+// setItemBookmark(props.media, !isBookmarked)} +// clickable +// /> +//
+//
+// {props.media.mediaType !== MWMediaType.MOVIE ? ( +// +// ) : null} +//
+// ); +// } -function LoadingMediaFooter(props: { error?: boolean }) { - const { t } = useTranslation(); +// function LoadingMediaFooter(props: { error?: boolean }) { +// const { t } = useTranslation(); - return ( - -
-
-
-
- - -
- {props.error ? ( -
- -

{t("media.invalidUrl")}

-
- ) : ( - - )} -
-
- - ); -} +// return ( +// +//
+//
+//
+//
+// +// +//
+// {props.error ? ( +//
+// +//

{t("media.invalidUrl")}

+//
+// ) : ( +// +// )} +//
+//
+// +// ); +// } -function MediaViewContent(props: { portable: MWPortableMedia }) { - const mediaPortable = props.portable; - const [streamUrl, setStreamUrl] = useState(); - const [media, setMedia] = useState(); - const [fetchMedia, loadingPortable, errorPortable] = useLoading( - (portable: MWPortableMedia) => convertPortableToMedia(portable) - ); - const [fetchStream, loadingStream, errorStream] = useLoading( - (portable: MWPortableMedia) => getStream(portable) - ); +// function MediaViewContent(props: { portable: MWPortableMedia }) { +// const mediaPortable = props.portable; +// const [streamUrl, setStreamUrl] = useState(); +// const [media, setMedia] = useState(); +// const [fetchMedia, loadingPortable, errorPortable] = useLoading( +// (portable: MWPortableMedia) => convertPortableToMedia(portable) +// ); +// const [fetchStream, loadingStream, errorStream] = useLoading( +// (portable: MWPortableMedia) => getStream(portable) +// ); - useEffect(() => { - (async () => { - if (mediaPortable) { - setMedia(await fetchMedia(mediaPortable)); - } - })(); - }, [mediaPortable, setMedia, fetchMedia]); +// useEffect(() => { +// (async () => { +// if (mediaPortable) { +// setMedia(await fetchMedia(mediaPortable)); +// } +// })(); +// }, [mediaPortable, setMedia, fetchMedia]); - useEffect(() => { - (async () => { - if (mediaPortable) { - setStreamUrl(await fetchStream(mediaPortable)); - } - })(); - }, [mediaPortable, setStreamUrl, fetchStream]); +// useEffect(() => { +// (async () => { +// if (mediaPortable) { +// setStreamUrl(await fetchStream(mediaPortable)); +// } +// })(); +// }, [mediaPortable, setStreamUrl, fetchStream]); - let playerContent: ReactElement | null = null; - if (loadingStream) playerContent = ; - else if (errorStream) playerContent = ; - else if (media && streamUrl) - playerContent = ; +// let playerContent: ReactElement | null = null; +// if (loadingStream) playerContent = ; +// else if (errorStream) playerContent = ; +// else if (media && streamUrl) +// playerContent = ; - let footerContent: ReactElement | null = null; - if (loadingPortable) footerContent = ; - else if (errorPortable) footerContent = ; - else if (mediaPortable && media) - footerContent = ( - - ); +// let footerContent: ReactElement | null = null; +// if (loadingPortable) footerContent = ; +// else if (errorPortable) footerContent = ; +// else if (mediaPortable && media) +// footerContent = ( +// +// ); - return ( - <> - {playerContent} - {footerContent} - - ); -} +// return ( +// <> +// {playerContent} +// {footerContent} +// +// ); +// } export function MediaView() { const { t } = useTranslation(); - const mediaPortable: MWPortableMedia | undefined = usePortableMedia(); + // const mediaPortable: MWPortableMedia | undefined = usePortableMedia(); const reactHistory = useHistory(); return ( @@ -230,11 +225,11 @@ export function MediaView() { linkText={t("media.arrowText")} /> - + {/*
-
+
*/}
); } diff --git a/src/views/TestView.tsx b/src/views/TestView.tsx index 5314996d..dd45fdb0 100644 --- a/src/views/TestView.tsx +++ b/src/views/TestView.tsx @@ -1,9 +1,10 @@ -import { searchForMedia } from "@/backend/metadata/search"; -import { ProgressListenerControl } from "@/components/video/controls/ProgressListenerControl"; -import { SourceControl } from "@/components/video/controls/SourceControl"; -import { DecoratedVideoPlayer } from "@/components/video/DecoratedVideoPlayer"; -import { MWMediaType } from "@/providers"; -import { useCallback, useState } from "react"; +// import { searchForMedia } from "@/backend/metadata/search"; +// import { ProgressListenerControl } from "@/components/video/controls/ProgressListenerControl"; +// import { SourceControl } from "@/components/video/controls/SourceControl"; +// import { DecoratedVideoPlayer } from "@/components/video/DecoratedVideoPlayer"; +import { useScrape } from "@/hooks/useScrape"; +// import { MWMediaType } from "@/providers"; +// import { useCallback, useState } from "react"; // test videos: https://gist.github.com/jsturgis/3b19447b304616f18657 @@ -24,37 +25,58 @@ import { useCallback, useState } from "react"; // - devices: ipadOS // - features: HLS, error handling, preload interactions +// export function TestView() { +// const [show, setShow] = useState(true); +// const handleClick = useCallback(() => { +// setShow((v) => !v); +// }, [setShow]); + +// if (!show) { +// return

Click me to show

; +// } + +// async function search() { +// const test = await searchForMedia({ +// searchQuery: "tron", +// type: MWMediaType.MOVIE, +// }); +// console.log(test); +// } + +// return ( +//
+// +// +// console.log(a, b)} +// /> +// +//

search()}>click me to search

+//
+// ); +// } + export function TestView() { - const [show, setShow] = useState(true); - const handleClick = useCallback(() => { - setShow((v) => !v); - }, [setShow]); - - if (!show) { - return

Click me to show

; - } - - async function search() { - const test = await searchForMedia({ - searchQuery: "tron", - type: MWMediaType.MOVIE, - }); - console.log(test); - } + const { eventLog, pending, stream } = useScrape(); return ( -
- - - console.log(a, b)} - /> - -

search()}>click me to search

+
+

pending: {pending}

+

+ stream: {stream?.streamUrl} - {stream?.type} - {stream?.quality} +

+
+ {eventLog.map((v) => ( +
+

+ {v.percentage}% - {v.type} - {v.errored ? "ERROR" : "pending"} +

+
+ ))}
); } diff --git a/src/views/notfound/NotFoundChecks.tsx b/src/views/notfound/NotFoundChecks.tsx index 0981e180..a8fe9e4a 100644 --- a/src/views/notfound/NotFoundChecks.tsx +++ b/src/views/notfound/NotFoundChecks.tsx @@ -1,9 +1,8 @@ import { ReactElement } from "react"; -import { getProviderMetadata, MWPortableMedia } from "@/providers"; -import { NotFoundMedia, NotFoundProvider } from "./NotFoundView"; +// import { NotFoundMedia, NotFoundProvider } from "./NotFoundView"; export interface NotFoundChecksProps { - portable: MWPortableMedia | undefined; + // portable: MWPortableMedia | undefined; children?: ReactElement; } @@ -13,17 +12,17 @@ export interface NotFoundChecksProps { export function NotFoundChecks( props: NotFoundChecksProps ): ReactElement | null { - const providerMeta = props.portable - ? getProviderMetadata(props.portable.providerId) - : undefined; + // const providerMeta = props.portable + // ? getProviderMetadata(props.portable.providerId) + // : undefined; - if (!providerMeta || !providerMeta.exists) { - return ; - } + // if (!providerMeta || !providerMeta.exists) { + // return ; + // } - if (!providerMeta.enabled) { - return ; - } + // if (!providerMeta.enabled) { + // return ; + // } return props.children || null; } diff --git a/src/views/search/HomeView.tsx b/src/views/search/HomeView.tsx index 16f68e7c..7a94cdcb 100644 --- a/src/views/search/HomeView.tsx +++ b/src/views/search/HomeView.tsx @@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next"; import { Icons } from "@/components/Icon"; import { SectionHeading } from "@/components/layout/SectionHeading"; import { MediaGrid } from "@/components/media/MediaGrid"; -import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; import { getIfBookmarkedFromPortable, useBookmarkContext, @@ -22,12 +21,12 @@ function Bookmarks() { icon={Icons.BOOKMARK} > - {bookmarks.map((v) => ( + {/* {bookmarks.map((v) => ( - ))} + ))} */} ); @@ -51,13 +50,13 @@ function Watched() { icon={Icons.CLOCK} > - {watchedItems.map((v) => ( + {/* {watchedItems.map((v) => ( - ))} + ))} */} ); diff --git a/src/views/search/SearchResultsPartial.tsx b/src/views/search/SearchResultsPartial.tsx index 59281093..d7859612 100644 --- a/src/views/search/SearchResultsPartial.tsx +++ b/src/views/search/SearchResultsPartial.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { useDebounce } from "@/hooks/useDebounce"; -import { MWQuery } from "@/providers"; +import { MWQuery } from "@/backend/metadata/types"; import { HomeView } from "./HomeView"; import { SearchLoadingView } from "./SearchLoadingView"; import { SearchResultsView } from "./SearchResultsView"; diff --git a/src/views/search/SearchResultsView.tsx b/src/views/search/SearchResultsView.tsx index ad611516..60a61115 100644 --- a/src/views/search/SearchResultsView.tsx +++ b/src/views/search/SearchResultsView.tsx @@ -6,8 +6,8 @@ import { SectionHeading } from "@/components/layout/SectionHeading"; import { MediaGrid } from "@/components/media/MediaGrid"; import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; import { useLoading } from "@/hooks/useLoading"; -import { MWQuery } from "@/providers"; -import { MWSearchResult, searchForMedia } from "@/backend/metadata/search"; +import { searchForMedia } from "@/backend/metadata/search"; +import { MWMediaMeta, MWQuery } from "@/backend/metadata/types"; import { SearchLoadingView } from "./SearchLoadingView"; function SearchSuffix(props: { failed?: boolean; results?: number }) { @@ -46,7 +46,7 @@ function SearchSuffix(props: { failed?: boolean; results?: number }) { export function SearchResultsView({ searchQuery }: { searchQuery: MWQuery }) { const { t } = useTranslation(); - const [results, setResults] = useState([]); + const [results, setResults] = useState([]); const [runSearchQuery, loading, error] = useLoading((query: MWQuery) => searchForMedia(query) );