responsiveness and loading states

This commit is contained in:
mrjvs 2022-03-13 17:26:46 +01:00
parent 570ca14905
commit 7709ffd90f
6 changed files with 48 additions and 52 deletions

View File

@ -16,7 +16,7 @@ interface DropdownProps {
export const Dropdown = React.forwardRef<HTMLDivElement, DropdownProps>( export const Dropdown = React.forwardRef<HTMLDivElement, DropdownProps>(
(props: DropdownProps) => ( (props: DropdownProps) => (
<div className="relative my-4 w-72 "> <div className="relative my-4 max-w-[18rem]">
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}> <Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
{({ open }) => ( {({ open }) => (
<> <>
@ -37,7 +37,7 @@ export const Dropdown = React.forwardRef<HTMLDivElement, DropdownProps>(
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="bg-denim-500 scrollbar-thin scrollbar-track-denim-400 scrollbar-thumb-denim-200 absolute bottom-11 z-10 mt-1 max-h-60 w-72 overflow-auto rounded-md py-1 text-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:bottom-10 sm:text-sm"> <Listbox.Options className="bg-denim-500 scrollbar-thin scrollbar-track-denim-400 scrollbar-thumb-denim-200 absolute bottom-11 left-0 right-0 z-10 mt-1 max-h-60 overflow-auto rounded-md py-1 text-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:bottom-10 sm:text-sm">
{props.options.map((opt) => ( {props.options.map((opt) => (
<Listbox.Option <Listbox.Option
className={({ active }) => className={({ active }) =>

View File

@ -1,4 +1,6 @@
import { IconPatch } from "components/buttons/IconPatch";
import { Dropdown, OptionItem } from "components/Dropdown"; import { Dropdown, OptionItem } from "components/Dropdown";
import { Icons } from "components/Icon";
import { WatchedEpisode } from "components/media/WatchedEpisodeButton"; import { WatchedEpisode } from "components/media/WatchedEpisodeButton";
import { useLoading } from "hooks/useLoading"; import { useLoading } from "hooks/useLoading";
import { serializePortableMedia } from "hooks/usePortableMedia"; import { serializePortableMedia } from "hooks/usePortableMedia";
@ -17,6 +19,28 @@ export interface SeasonsProps {
media: MWMedia; media: MWMedia;
} }
export function LoadingSeasons(props: { error?: boolean }) {
return (
<div>
<div>
<div className="bg-denim-400 mb-3 mt-5 h-10 w-56 rounded opacity-50" />
</div>
{!props.error ? (
<>
<div className="bg-denim-400 mr-3 mb-3 inline-block h-10 w-10 rounded opacity-50" />
<div className="bg-denim-400 mr-3 mb-3 inline-block h-10 w-10 rounded opacity-50" />
<div className="bg-denim-400 mr-3 mb-3 inline-block h-10 w-10 rounded opacity-50" />
</>
) : (
<div className="flex items-center space-x-3">
<IconPatch icon={Icons.WARNING} className="text-red-400" />
<p>Failed to load seasons and episodes</p>
</div>
)}
</div>
);
}
export function Seasons(props: SeasonsProps) { export function Seasons(props: SeasonsProps) {
const [searchSeasons, loading, error, success] = useLoading( const [searchSeasons, loading, error, success] = useLoading(
(portableMedia: MWPortableMedia) => getSeasonDataFromMedia(portableMedia) (portableMedia: MWPortableMedia) => getSeasonDataFromMedia(portableMedia)
@ -58,8 +82,8 @@ export function Seasons(props: SeasonsProps) {
return ( return (
<> <>
{loading ? <p>Loading...</p> : null} {loading ? <LoadingSeasons /> : null}
{error ? <p>error!</p> : null} {error ? <LoadingSeasons error /> : null}
{success && seasons.seasons.length ? ( {success && seasons.seasons.length ? (
<> <>
<Dropdown <Dropdown

View File

@ -1,6 +1,5 @@
import { import {
convertMediaToPortable, convertMediaToPortable,
getEpisodeFromMedia,
getProviderFromId, getProviderFromId,
MWMediaMeta, MWMediaMeta,
MWMediaType, MWMediaType,

View File

@ -1,43 +0,0 @@
import {
MWMediaProvider,
MWMediaSeasons,
MWMediaType,
MWPortableMedia,
MWQuery,
} from "providers/types";
import { MWMediaStream, MWProviderMediaResult } from "providers";
export const tempScraper: MWMediaProvider = {
id: "temp",
enabled: true,
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
displayName: "temp",
async getMediaFromPortable(
media: MWPortableMedia
): Promise<MWProviderMediaResult> {
return {
...media,
year: "1234",
title: "temp",
};
},
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
return [];
},
async getStream(media: MWPortableMedia): Promise<MWMediaStream> {
return {
url: "hi",
type: "mp4",
};
},
async getSeasonDataFromMedia(media): Promise<MWMediaSeasons> {
return {
seasons: [],
};
},
};

View File

@ -84,13 +84,22 @@ export const theFlixScraper: MWMediaProvider = {
.querySelectorAll(`script[id="__NEXT_DATA__"]`) .querySelectorAll(`script[id="__NEXT_DATA__"]`)
)[0]; )[0];
const data = JSON.parse(node.innerHTML).props.pageProps.selectedTv.seasons; let data = JSON.parse(node.innerHTML).props.pageProps.selectedTv.seasons;
data = data.filter((season: any) => season.releaseDate != null);
data = data.map((season: any) => {
const episodes = season.episodes.filter(
(episode: any) => episode.releaseDate != null
);
return { ...season, episodes };
});
return { return {
seasons: data.map((d: any) => ({ seasons: data.map((d: any) => ({
sort: d.seasonNumber === 0 ? 999 : d.seasonNumber, sort: d.seasonNumber === 0 ? 999 : d.seasonNumber,
id: d.seasonNumber.toString(), id: d.seasonNumber.toString(),
type: d.seasonNumber === 0 ? "special" : "season", type: d.seasonNumber === 0 ? "special" : "season",
title: d.seasonNumber === 0 ? "Specials" : undefined, title: d.name,
episodes: d.episodes.map((e: any) => ({ episodes: d.episodes.map((e: any) => ({
title: e.name, title: e.name,
sort: e.episodeNumber, sort: e.episodeNumber,

View File

@ -2,7 +2,7 @@ import { IconPatch } from "components/buttons/IconPatch";
import { Icons } from "components/Icon"; import { Icons } from "components/Icon";
import { Navigation } from "components/layout/Navigation"; import { Navigation } from "components/layout/Navigation";
import { Paper } from "components/layout/Paper"; import { Paper } from "components/layout/Paper";
import { Seasons } from "components/layout/Seasons"; import { LoadingSeasons, Seasons } from "components/layout/Seasons";
import { SkeletonVideoPlayer, VideoPlayer } from "components/media/VideoPlayer"; import { SkeletonVideoPlayer, VideoPlayer } from "components/media/VideoPlayer";
import { ArrowLink } from "components/text/ArrowLink"; import { ArrowLink } from "components/text/ArrowLink";
import { DotList } from "components/text/DotList"; import { DotList } from "components/text/DotList";
@ -110,7 +110,14 @@ function LoadingMediaFooter(props: { error?: boolean }) {
<span className="bg-denim-400 mr-4 inline-block h-2 w-12 rounded-full" /> <span className="bg-denim-400 mr-4 inline-block h-2 w-12 rounded-full" />
<span className="bg-denim-400 mr-4 inline-block h-2 w-12 rounded-full" /> <span className="bg-denim-400 mr-4 inline-block h-2 w-12 rounded-full" />
</div> </div>
{props.error ? "error!" : null} {props.error ? (
<div className="flex items-center space-x-3">
<IconPatch icon={Icons.WARNING} className="text-red-400" />
<p>Your url may be invalid</p>
</div>
) : (
<LoadingSeasons />
)}
</div> </div>
</div> </div>
</Paper> </Paper>