fix all eslint issues

Co-authored-by: William Oldham <wegg7250@gmail.com>
This commit is contained in:
mrjvs
2022-03-06 14:41:51 +01:00
parent 069c5271df
commit 0287bdad57
22 changed files with 384 additions and 323 deletions

View File

@@ -4,6 +4,14 @@ export interface ButtonControlProps {
className?: string;
}
export function ButtonControl({ onClick, children, className }: ButtonControlProps) {
return <button onClick={onClick} className={className}>{children}</button>
export function ButtonControl({
onClick,
children,
className,
}: ButtonControlProps) {
return (
<button onClick={onClick} className={className} type="button">
{children}
</button>
);
}

View File

@@ -9,7 +9,13 @@ import React, {
import { Backdrop, useBackdrop } from "components/layout/Backdrop";
import { ButtonControlProps, ButtonControl } from "./ButtonControl";
export interface DropdownButtonProps extends ButtonControlProps {
export interface OptionItem {
id: string;
name: string;
icon: Icons;
}
interface DropdownButtonProps extends ButtonControlProps {
icon: Icons;
open: boolean;
setOpen: (open: boolean) => void;
@@ -24,12 +30,6 @@ export interface OptionProps {
tabIndex?: number;
}
export interface OptionItem {
id: string;
name: string;
icon: Icons;
}
function Option({ option, onClick, tabIndex }: OptionProps) {
return (
<div
@@ -49,7 +49,7 @@ function Option({ option, onClick, tabIndex }: OptionProps) {
export const DropdownButton = React.forwardRef<
HTMLDivElement,
DropdownButtonProps
>((props, ref) => {
>((props: DropdownButtonProps, ref) => {
const [setBackdrop, backdropProps, highlightedProps] = useBackdrop();
const [delayedSelectedId, setDelayedSelectedId] = useState(
props.selectedItem

View File

@@ -42,7 +42,7 @@ const iconList: Record<Icons, string> = {
export function Icon(props: IconProps) {
return (
<span
dangerouslySetInnerHTML={{ __html: iconList[props.icon] }}
dangerouslySetInnerHTML={{ __html: iconList[props.icon] }} // eslint-disable-line react/no-danger
className={props.className}
/>
);

View File

@@ -4,7 +4,6 @@ import { DropdownButton } from "./buttons/DropdownButton";
import { Icons } from "./Icon";
import { TextInputControl } from "./text-inputs/TextInputControl";
export interface SearchBarProps {
buttonText?: string;
placeholder?: string;
@@ -30,7 +29,7 @@ export function SearchBarInput(props: SearchBarProps) {
return (
<div className="bg-denim-300 hover:bg-denim-400 focus-within:bg-denim-400 flex flex-col items-center gap-4 rounded-[28px] px-4 py-4 transition-colors sm:flex-row sm:py-2 sm:pl-8 sm:pr-2">
<TextInputControl
onChange={setSearch}
onChange={(val) => setSearch(val)}
value={props.value.searchQuery}
className="placeholder-denim-700 w-full flex-1 bg-transparent text-white focus:outline-none"
placeholder={props.placeholder}
@@ -39,9 +38,9 @@ export function SearchBarInput(props: SearchBarProps) {
<DropdownButton
icon={Icons.SEARCH}
open={dropdownOpen}
setOpen={setDropdownOpen}
setOpen={(val) => setDropdownOpen(val)}
selectedItem={props.value.type}
setSelectedItem={setType}
setSelectedItem={(val) => setType(val)}
options={[
{
id: MWMediaType.MOVIE,

View File

@@ -40,7 +40,7 @@ export function useBackdrop(): [
}
export function Backdrop(props: BackdropProps) {
const clickEvent = props.onClick || ((e: MouseEvent) => {});
const clickEvent = props.onClick || (() => {});
const animationEvent = props.onBackdropHide || (() => {});
const [isVisible, setVisible, fadeProps] = useFade();
@@ -63,6 +63,6 @@ export function Backdrop(props: BackdropProps) {
}`}
{...fadeProps}
onClick={(e) => clickEvent(e.nativeEvent)}
/>
/>
);
}

View File

@@ -14,10 +14,16 @@ interface ErrorBoundaryState {
};
}
export class ErrorBoundary extends Component<{}, ErrorBoundaryState> {
state: ErrorBoundaryState = {
hasError: false,
};
export class ErrorBoundary extends Component<
Record<string, unknown>,
ErrorBoundaryState
> {
constructor() {
super({});
this.state = {
hasError: false,
};
}
static getDerivedStateFromError() {
return {
@@ -50,8 +56,16 @@ export class ErrorBoundary extends Component<{}, ErrorBoundaryState> {
<IconPatch icon={Icons.WARNING} className="mb-6 text-red-400" />
<Title>Whoops, it broke</Title>
<p className="my-6 max-w-lg">
The app encountered an error and wasn't able to recover, please
report it to the <Link url={DISCORD_LINK} newTab>Discord server</Link> or on <Link url={GITHUB_LINK} newTab>GitHub</Link>.
The app encountered an error and wasn&apos;t able to recover, please
report it to the{" "}
<Link url={DISCORD_LINK} newTab>
Discord server
</Link>{" "}
or on{" "}
<Link url={GITHUB_LINK} newTab>
GitHub
</Link>
.
</p>
</div>
{this.state.error ? (

View File

@@ -2,7 +2,7 @@ import { IconPatch } from "components/buttons/IconPatch";
import { Icons } from "components/Icon";
import { Loading } from "components/layout/Loading";
import { MWMediaStream } from "providers";
import { useEffect, useRef, useState } from "react";
import { ReactElement, useEffect, useRef, useState } from "react";
export interface VideoPlayerProps {
source: MWMediaStream;
@@ -16,7 +16,7 @@ export function SkeletonVideoPlayer(props: { error?: boolean }) {
{props.error ? (
<div className="flex flex-col items-center">
<IconPatch icon={Icons.WARNING} className="text-red-400" />
<p className="mt-5 text-white">Couldn't get your stream</p>
<p className="mt-5 text-white">Couldn&apos;t get your stream</p>
</div>
) : (
<div className="flex flex-col items-center">
@@ -41,13 +41,16 @@ export function VideoPlayer(props: VideoPlayerProps) {
setErrored(false);
}, [props.source.url]);
let skeletonUi: null | ReactElement = null;
if (hasErrored) {
skeletonUi = <SkeletonVideoPlayer error />;
} else if (isLoading) {
skeletonUi = <SkeletonVideoPlayer />;
}
return (
<>
{hasErrored ? (
<SkeletonVideoPlayer error />
) : isLoading ? (
<SkeletonVideoPlayer />
) : null}
{skeletonUi}
<video
className={`bg-denim-500 w-full rounded-xl ${
!showVideo ? "hidden" : ""

View File

@@ -30,6 +30,7 @@ export function useLoading<T extends (...args: any) => Promise<any>>(
if (!isMounted.current) return resolve(undefined);
setSuccess(true);
resolve(v);
return null;
})
.catch((err) => {
if (isMounted) {

View File

@@ -2,6 +2,15 @@ import { MWPortableMedia } from "providers";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
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<MWPortableMedia | undefined>(
@@ -19,12 +28,3 @@ export function usePortableMedia(): MWPortableMedia | undefined {
return mediaObject;
}
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;
}

View File

@@ -1,9 +1,5 @@
import { getProviderFromId } from "./methods/helpers";
import {
MWMedia,
MWPortableMedia,
MWMediaStream,
} from "./types";
import { MWMedia, MWPortableMedia, MWMediaStream } from "./types";
import contentCache from "./methods/contentCache";
export * from "./types";
@@ -35,7 +31,7 @@ export async function convertPortableToMedia(
if (output) return output;
const provider = getProviderFromId(portable.providerId);
return await provider?.getMediaFromPortable(portable);
return provider?.getMediaFromPortable(portable);
}
/*
@@ -47,5 +43,5 @@ export async function getStream(
const provider = getProviderFromId(media.providerId);
if (!provider) return undefined;
return await provider.getStream(media);
return provider.getStream(media);
}

View File

@@ -4,7 +4,8 @@ import { MWMediaType, MWPortableMedia } from "providers/types";
const getTheFlixUrl = (media: MWPortableMedia, params?: URLSearchParams) => {
if (media.mediaType === MWMediaType.MOVIE) {
return `https://theflix.to/movie/${media.mediaId}?${params}`;
} if (media.mediaType === MWMediaType.SERIES) {
}
if (media.mediaType === MWMediaType.SERIES) {
return `https://theflix.to/tv-show/${media.mediaId}/season-${media.season}/episode-${media.episode}`;
}
@@ -29,7 +30,7 @@ export async function getDataFromPortableSearch(
if (media.mediaType === MWMediaType.MOVIE) {
return JSON.parse(node.innerHTML).props.pageProps.movie;
} if (media.mediaType === MWMediaType.SERIES) {
return JSON.parse(node.innerHTML).props.pageProps.selectedTv;
}
// must be series here
return JSON.parse(node.innerHTML).props.pageProps.selectedTv;
}

View File

@@ -4,10 +4,10 @@ import { MWMediaType, MWProviderMediaResult, MWQuery } from "providers";
const getTheFlixUrl = (type: "tv-shows" | "movies", params: URLSearchParams) =>
`https://theflix.to/${type}/trending?${params}`;
export async function searchTheFlix(query: MWQuery): Promise<string> {
export function searchTheFlix(query: MWQuery): Promise<string> {
const params = new URLSearchParams();
params.append("search", query.searchQuery);
return await fetch(
return fetch(
CORS_PROXY_URL +
getTheFlixUrl(
query.type === MWMediaType.MOVIE ? "movies" : "tv-shows",
@@ -30,14 +30,11 @@ export function getDataFromSearch(page: string, limit = 10): any[] {
export function turnDataIntoMedia(data: any): MWProviderMediaResult {
return {
mediaId:
`${data.id
}-${
data.name
.replace(/[^a-z0-9]+|\s+/gim, " ")
.trim()
.replace(/\s+/g, "-")
.toLowerCase()}`,
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(),
};

View File

@@ -1,20 +1,25 @@
import Fuse from "fuse.js";
import { MWMassProviderOutput, MWMedia, MWQuery, convertMediaToPortable } from "providers";
import {
MWMassProviderOutput,
MWMedia,
MWQuery,
convertMediaToPortable,
} from "providers";
import { SimpleCache } from "utils/cache";
import { GetProvidersForType } from "./helpers";
import contentCache from "./contentCache";
// cache
const resultCache = new SimpleCache<MWQuery, MWMassProviderOutput>();
resultCache.setCompare((a,b) => a.searchQuery === b.searchQuery && a.type === b.type);
resultCache.setCompare(
(a, b) => a.searchQuery === b.searchQuery && a.type === b.type
);
resultCache.initialize();
/*
** actually call all providers with the search query
*/
async function callProviders(
query: MWQuery
): Promise<MWMassProviderOutput> {
** actually call all providers with the search query
*/
async function callProviders(query: MWQuery): Promise<MWMassProviderOutput> {
const allQueries = GetProvidersForType(query.type).map<
Promise<{ media: MWMedia[]; success: boolean; id: string }>
>(async (provider) => {
@@ -55,33 +60,37 @@ async function callProviders(
output.results.forEach((result: MWMedia) => {
contentCache.set(convertMediaToPortable(result), result, 60 * 60);
})
});
return output;
}
/*
** sort results based on query
*/
function sortResults(query: MWQuery, providerResults: MWMassProviderOutput): MWMassProviderOutput {
const fuse = new Fuse(providerResults.results, { threshold: 0.3, keys: ["title"] });
providerResults.results = fuse.search(query.searchQuery).map((v) => v.item);
return providerResults;
** sort results based on query
*/
function sortResults(
query: MWQuery,
providerResults: MWMassProviderOutput
): MWMassProviderOutput {
const results: MWMassProviderOutput = { ...providerResults };
const fuse = new Fuse(results.results, { threshold: 0.3, keys: ["title"] });
results.results = fuse.search(query.searchQuery).map((v) => v.item);
return results;
}
/*
** Call search on all providers that matches query type
*/
export async function SearchProviders(
query: MWQuery
inputQuery: MWQuery
): Promise<MWMassProviderOutput> {
// input normalisation
const query = { ...inputQuery };
query.searchQuery = query.searchQuery.toLowerCase().trim();
// consult cache first
let output = resultCache.get(query);
if (!output)
output = await callProviders(query);
if (!output) output = await callProviders(query);
// sort results
output = sortResults(query, output);

View File

@@ -23,7 +23,7 @@ export interface MWMediaMeta extends MWPortableMedia {
year: string;
}
export type MWMedia = MWMediaMeta
export type MWMedia = MWMediaMeta;
export type MWProviderMediaResult = Omit<MWMedia, "mediaType" | "providerId">;

View File

@@ -1,5 +1,12 @@
import { getProviderMetadata, MWMediaMeta } from "providers";
import { createContext, ReactNode, useContext, useState } from "react";
import {
createContext,
ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from "react";
import { BookmarkStore } from "./store";
interface BookmarkStoreData {
@@ -20,55 +27,77 @@ const BookmarkedContext = createContext<BookmarkStoreDataWrapper>({
},
});
function getBookmarkIndexFromMedia(
bookmarks: MWMediaMeta[],
media: MWMediaMeta
): number {
const a = bookmarks.findIndex(
(v) =>
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
);
return a;
}
export function BookmarkContextProvider(props: { children: ReactNode }) {
const bookmarkLocalstorage = BookmarkStore.get();
const [bookmarkStorage, setBookmarkStore] = useState<BookmarkStoreData>(
bookmarkLocalstorage as BookmarkStoreData
);
function setBookmarked(data: any) {
setBookmarkStore((old) => {
const old2 = JSON.parse(JSON.stringify(old));
let newData = data;
if (data.constructor === Function) {
newData = data(old2);
}
bookmarkLocalstorage.save(newData);
return newData;
});
}
const contextValue = {
setItemBookmark(media: MWMediaMeta, bookmarked: boolean) {
setBookmarked((data: BookmarkStoreData) => {
if (bookmarked) {
const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media);
if (itemIndex === -1) {
const item = {
mediaId: media.mediaId,
mediaType: media.mediaType,
providerId: media.providerId,
title: media.title,
year: media.year,
episode: media.episode,
season: media.season,
};
data.bookmarks.push(item);
}
} else {
const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media);
if (itemIndex !== -1) {
data.bookmarks.splice(itemIndex);
}
const setBookmarked = useCallback(
(data: any) => {
setBookmarkStore((old) => {
const old2 = JSON.parse(JSON.stringify(old));
let newData = data;
if (data.constructor === Function) {
newData = data(old2);
}
return data;
bookmarkLocalstorage.save(newData);
return newData;
});
},
getFilteredBookmarks() {
return bookmarkStorage.bookmarks.filter((bookmark) => getProviderMetadata(bookmark.providerId)?.enabled);
},
bookmarkStore: bookmarkStorage,
};
[bookmarkLocalstorage, setBookmarkStore]
);
const contextValue = useMemo(
() => ({
setItemBookmark(media: MWMediaMeta, bookmarked: boolean) {
setBookmarked((data: BookmarkStoreData) => {
if (bookmarked) {
const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media);
if (itemIndex === -1) {
const item = {
mediaId: media.mediaId,
mediaType: media.mediaType,
providerId: media.providerId,
title: media.title,
year: media.year,
episode: media.episode,
season: media.season,
};
data.bookmarks.push(item);
}
} else {
const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media);
if (itemIndex !== -1) {
data.bookmarks.splice(itemIndex);
}
}
return data;
});
},
getFilteredBookmarks() {
return bookmarkStorage.bookmarks.filter(
(bookmark) => getProviderMetadata(bookmark.providerId)?.enabled
);
},
bookmarkStore: bookmarkStorage,
}),
[bookmarkStorage, setBookmarked]
);
return (
<BookmarkedContext.Provider value={contextValue}>
@@ -81,19 +110,6 @@ export function useBookmarkContext() {
return useContext(BookmarkedContext);
}
function getBookmarkIndexFromMedia(
bookmarks: MWMediaMeta[],
media: MWMediaMeta
): number {
const a = bookmarks.findIndex((v) => (
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
));
return a;
}
export function getIfBookmarkedFromPortable(
bookmarks: MWMediaMeta[],
media: MWMediaMeta

View File

@@ -1,5 +1,12 @@
import { MWMediaMeta, getProviderMetadata } from "providers";
import React, { createContext, ReactNode, useContext, useState } from "react";
import React, {
createContext,
ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from "react";
import { VideoProgressStore } from "./store";
interface WatchedStoreItem extends MWMediaMeta {
@@ -17,6 +24,19 @@ interface WatchedStoreDataWrapper {
watched: WatchedStoreData;
}
export function getWatchedFromPortable(
items: WatchedStoreItem[],
media: MWMediaMeta
): WatchedStoreItem | undefined {
return items.find(
(v) =>
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
);
}
const WatchedContext = createContext<WatchedStoreDataWrapper>({
updateProgress: () => {},
getFilteredWatched: () => [],
@@ -32,48 +52,60 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
watchedLocalstorage as WatchedStoreData
);
function setWatched(data: any) {
setWatchedReal((old) => {
let newData = data;
if (data.constructor === Function) {
newData = data(old);
}
watchedLocalstorage.save(newData);
return newData;
});
}
const contextValue = {
updateProgress(media: MWMediaMeta, 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,
episode: media.episode,
season: media.season,
};
data.items.push(item);
const setWatched = useCallback(
(data: any) => {
setWatchedReal((old) => {
let newData = data;
if (data.constructor === Function) {
newData = data(old);
}
// update actual item
item.progress = progress;
item.percentage = Math.round((progress / total) * 100);
return data;
watchedLocalstorage.save(newData);
return newData;
});
},
getFilteredWatched() {
return watched.items.filter((item) => getProviderMetadata(item.providerId)?.enabled);
},
watched,
};
[setWatchedReal, watchedLocalstorage]
);
const contextValue = useMemo(
() => ({
updateProgress(
media: MWMediaMeta,
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,
episode: media.episode,
season: media.season,
};
data.items.push(item);
}
// update actual item
item.progress = progress;
item.percentage = Math.round((progress / total) * 100);
return data;
});
},
getFilteredWatched() {
return watched.items.filter(
(item) => getProviderMetadata(item.providerId)?.enabled
);
},
watched,
}),
[watched, setWatched]
);
return (
<WatchedContext.Provider value={contextValue}>
@@ -85,15 +117,3 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
export function useWatchedContext() {
return useContext(WatchedContext);
}
export function getWatchedFromPortable(
items: WatchedStoreItem[],
media: MWMediaMeta
): WatchedStoreItem | undefined {
return items.find((v) => (
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
));
}

View File

@@ -11,7 +11,8 @@ function buildStoreObject(d: any) {
id: d.storageString,
};
function update(this: any, obj: any) {
function update(this: any, obj2: any) {
let obj = obj2;
if (!obj) throw new Error("object to update is not an object");
// repeat until object fully updated
@@ -53,54 +54,54 @@ function buildStoreObject(d: any) {
function get(this: any) {
// get from storage api
const store = this;
let data: any = localStorage.getItem(this.id);
let gottenData: any = localStorage.getItem(this.id);
// parse json if item exists
if (data) {
if (gottenData) {
try {
data = JSON.parse(data);
if (!data.constructor) {
gottenData = JSON.parse(gottenData);
if (!gottenData.constructor) {
console.error(
`Storage item for store ${this.id} has not constructor`
);
throw new Error("storage item has no constructor");
}
if (data.constructor !== Object) {
if (gottenData.constructor !== Object) {
console.error(`Storage item for store ${this.id} is not an object`);
throw new Error("storage item is not an object");
}
} catch (_) {
// if errored, set to null so it generates new one, see below
console.error(`Failed to parse storage item for store ${this.id}`);
data = null;
gottenData = null;
}
}
// if item doesnt exist, generate from version init
if (!data) {
data = this.versions[this.currentVersion.toString()].init();
if (!gottenData) {
gottenData = this.versions[this.currentVersion.toString()].init();
}
// update the data if needed
data = this.update(data);
gottenData = this.update(gottenData);
// add a save object to return value
data.save = function save(newData: any) {
const dataToStore = newData || data;
gottenData.save = function save(newData: any) {
const dataToStore = newData || gottenData;
localStorage.setItem(store.id, JSON.stringify(dataToStore));
};
// add instance helpers
Object.entries(d.instanceHelpers).forEach(([name, helper]: any) => {
if (data[name] !== undefined)
if (gottenData[name] !== undefined)
throw new Error(
`helper name: ${name} on instance of store ${this.id} is reserved`
);
data[name] = helper.bind(data);
gottenData[name] = helper.bind(gottenData);
});
// return data
return data;
return gottenData;
}
// add functions to store
@@ -158,7 +159,7 @@ export function versionedStoreBuilder(): any {
? (data: any) => {
// update function, and increment version
migrate(data);
data["--version"] = version;
data["--version"] = version; // eslint-disable-line no-param-reassign
return data;
}
: undefined,
@@ -176,7 +177,8 @@ export function versionedStoreBuilder(): any {
registerHelper({ name, helper, type }: any) {
// type
if (!type) type = "instance";
let helperType: string = type;
if (!helperType) helperType = "instance";
// input checking
if (!name || name.constructor !== String) {
@@ -185,14 +187,14 @@ export function versionedStoreBuilder(): any {
if (!helper || helper.constructor !== Function) {
throw new Error("helper function is not a function");
}
if (!["instance", "static"].includes(type)) {
if (!["instance", "static"].includes(helperType)) {
throw new Error("helper type must be either 'instance' or 'static'");
}
// register helper
if (type === "instance")
if (helperType === "instance")
this._data.instanceHelpers[name as string] = helper;
else if (type === "static")
else if (helperType === "static")
this._data.staticHelpers[name as string] = helper;
return this;

View File

@@ -17,7 +17,7 @@ import {
getProviderFromId,
MWMediaProvider,
} from "providers";
import { ReactNode, useEffect, useState } from "react";
import { ReactElement, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import {
getIfBookmarkedFromPortable,
@@ -57,7 +57,7 @@ function StyledMediaView(props: StyledMediaViewProps) {
<>
<VideoPlayer
source={props.stream}
onProgress={updateProgress}
onProgress={(e) => updateProgress(e)}
startAt={startAtTime}
/>
<Paper className="mt-5">
@@ -110,11 +110,13 @@ function MediaViewContent(props: { portable: MWPortableMedia }) {
const mediaPortable = props.portable;
const [streamUrl, setStreamUrl] = useState<MWMediaStream | undefined>();
const [media, setMedia] = useState<MWMedia | undefined>();
const [fetchAllData, loading, error] = useLoading((mediaPortable) => {
const streamPromise = getStream(mediaPortable);
const mediaPromise = convertPortableToMedia(mediaPortable);
return Promise.all([streamPromise, mediaPromise]);
});
const [fetchAllData, loading, error] = useLoading(
(portable: MWPortableMedia) => {
const streamPromise = getStream(portable);
const mediaPromise = convertPortableToMedia(portable);
return Promise.all([streamPromise, mediaPromise]);
}
);
useEffect(() => {
(async () => {
@@ -127,7 +129,7 @@ function MediaViewContent(props: { portable: MWPortableMedia }) {
})();
}, [mediaPortable, setStreamUrl, fetchAllData]);
let content: ReactNode;
let content: ReactElement | null = null;
if (loading) content = <LoadingMediaView />;
else if (error) content = <LoadingMediaView error />;
else if (mediaPortable && media && streamUrl)
@@ -141,7 +143,7 @@ function MediaViewContent(props: { portable: MWPortableMedia }) {
/>
);
return <>{content}</>;
return content;
}
export function MediaView() {
@@ -152,11 +154,11 @@ export function MediaView() {
<div className="flex min-h-screen w-full">
<Navigation>
<ArrowLink
onClick={() => {
onClick={() =>
reactHistory.action !== "POP"
? reactHistory.goBack()
: reactHistory.push("/");
}}
: reactHistory.push("/")
}
direction="left"
linkText="Go back"
/>

View File

@@ -1,7 +1,7 @@
import { WatchedMediaCard } from "components/media/WatchedMediaCard";
import { SearchBarInput } from "components/SearchBar";
import { MWMassProviderOutput, MWQuery, SearchProviders } from "providers";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { ThinContainer } from "components/layout/ThinContainer";
import { SectionHeading } from "components/layout/SectionHeading";
import { Icons } from "components/Icon";
@@ -78,9 +78,9 @@ function SearchResultsView({
useEffect(() => {
async function runSearch(query: MWQuery) {
const results = await runSearchQuery(query);
if (!results) return;
setResults(results);
const searchResults = await runSearchQuery(query);
if (!searchResults) return;
setResults(searchResults);
}
if (searchQuery.searchQuery !== "") runSearch(searchQuery);
@@ -123,53 +123,6 @@ function SearchResultsView({
);
}
export function SearchView() {
const [searching, setSearching] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [search, setSearch] = useSearchQuery();
const debouncedSearch = useDebounce<MWQuery>(search, 2000);
useEffect(() => {
setSearching(search.searchQuery !== "");
setLoading(search.searchQuery !== "");
}, [search]);
useEffect(() => {
setLoading(false);
}, [debouncedSearch]);
return (
<>
<Navigation />
<ThinContainer>
{/* input section */}
<div className="mt-44 space-y-16 text-center">
<div className="space-y-4">
<Tagline>Because watching legally is boring</Tagline>
<Title>What movie do you want to watch?</Title>
</div>
<SearchBarInput
onChange={setSearch}
value={search}
placeholder="What movie do you want to watch?"
/>
</div>
{/* results view */}
{loading ? (
<SearchLoading />
) : searching ? (
<SearchResultsView
searchQuery={debouncedSearch}
clear={() => setSearch({ searchQuery: "" })}
/>
) : (
<ExtraItems />
)}
</ThinContainer>
</>
);
}
function ExtraItems() {
const { getFilteredBookmarks } = useBookmarkContext();
const { getFilteredWatched } = useWatchedContext();
@@ -207,3 +160,53 @@ function ExtraItems() {
</div>
);
}
export function SearchView() {
const [searching, setSearching] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [search, setSearch] = useSearchQuery();
const debouncedSearch = useDebounce<MWQuery>(search, 2000);
useEffect(() => {
setSearching(search.searchQuery !== "");
setLoading(search.searchQuery !== "");
}, [search]);
useEffect(() => {
setLoading(false);
}, [debouncedSearch]);
const resultView = useMemo(() => {
if (loading) return <SearchLoading />;
if (searching)
return (
<SearchResultsView
searchQuery={debouncedSearch}
clear={() => setSearch({ searchQuery: "" })}
/>
);
return <ExtraItems />;
}, [loading, searching, debouncedSearch, setSearch]);
return (
<>
<Navigation />
<ThinContainer>
{/* input section */}
<div className="mt-44 space-y-16 text-center">
<div className="space-y-4">
<Tagline>Because watching legally is boring</Tagline>
<Title>What movie do you want to watch?</Title>
</div>
<SearchBarInput
onChange={setSearch}
value={search}
placeholder="What movie do you want to watch?"
/>
</div>
{/* results view */}
{resultView}
</ThinContainer>
</>
);
}