mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-14 18:55:10 +01:00
195 lines
6.2 KiB
TypeScript
195 lines
6.2 KiB
TypeScript
import React, { useCallback, useMemo, useState } from "react";
|
|
import { useParams } from "react-router-dom";
|
|
import { Icon, Icons } from "@/components/Icon";
|
|
import { useLoading } from "@/hooks/useLoading";
|
|
import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
|
|
import { getMetaFromId } from "@/backend/metadata/getmeta";
|
|
import { decodeJWId } from "@/backend/metadata/justwatch";
|
|
import { Loading } from "@/components/layout/Loading";
|
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
|
import { useVideoPlayerState } from "../VideoContext";
|
|
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
|
|
import { VideoPopout } from "../parts/VideoPopout";
|
|
|
|
interface Props {
|
|
className?: string;
|
|
}
|
|
|
|
function PopupSection(props: {
|
|
children?: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div className={["p-4", props.className || ""].join(" ")}>
|
|
{props.children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PopupEpisodeSelect() {
|
|
const params = useParams<{
|
|
media: string;
|
|
}>();
|
|
const { videoState } = useVideoPlayerState();
|
|
const [isPickingSeason, setIsPickingSeason] = useState<boolean>(false);
|
|
const { current, seasons } = videoState.seasonData;
|
|
const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{
|
|
seasonId: string;
|
|
season?: MWSeasonWithEpisodeMeta;
|
|
} | null>(null);
|
|
const [reqSeasonMeta, loading, error] = useLoading(
|
|
(id: string, seasonId: string) => {
|
|
return getMetaFromId(MWMediaType.SERIES, id, seasonId);
|
|
}
|
|
);
|
|
const requestSeason = useCallback(
|
|
(sId: string) => {
|
|
setCurrentVisibleSeason({
|
|
seasonId: sId,
|
|
season: undefined,
|
|
});
|
|
setIsPickingSeason(false);
|
|
reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => {
|
|
if (v?.meta.type !== MWMediaType.SERIES) return;
|
|
setCurrentVisibleSeason({
|
|
seasonId: sId,
|
|
season: v?.meta.seasonData,
|
|
});
|
|
});
|
|
},
|
|
[reqSeasonMeta, params.media]
|
|
);
|
|
|
|
const currentSeasonId = currentVisibleSeason?.seasonId ?? current?.seasonId;
|
|
|
|
const setCurrent = useCallback(
|
|
(seasonId: string, episodeId: string) => {
|
|
videoState.setCurrentEpisode(seasonId, episodeId);
|
|
},
|
|
[videoState]
|
|
);
|
|
|
|
const currentSeasonInfo = useMemo(() => {
|
|
return seasons?.find((season) => season.id === currentSeasonId);
|
|
}, [seasons, currentSeasonId]);
|
|
|
|
const currentSeasonEpisodes = useMemo(() => {
|
|
if (currentVisibleSeason?.season) {
|
|
return currentVisibleSeason?.season?.episodes;
|
|
}
|
|
return videoState?.seasonData.seasons?.find?.(
|
|
(season) => season && season.id === currentSeasonId
|
|
)?.episodes;
|
|
}, [videoState, currentSeasonId, currentVisibleSeason]);
|
|
|
|
const toggleIsPickingSeason = () => {
|
|
setIsPickingSeason(!isPickingSeason);
|
|
};
|
|
|
|
const setSeason = (id: string) => {
|
|
requestSeason(id);
|
|
setCurrentVisibleSeason({ seasonId: id });
|
|
};
|
|
|
|
if (isPickingSeason)
|
|
return (
|
|
<>
|
|
<PopupSection className="flex items-center space-x-3 border-b border-denim-500 font-bold text-white">
|
|
Pick a season
|
|
</PopupSection>
|
|
<PopupSection className="overflow-y-auto">
|
|
<div className="space-y-1">
|
|
{currentSeasonInfo
|
|
? videoState?.seasonData?.seasons?.map?.((season) => (
|
|
<div
|
|
className="text-denim-800 -mx-2 flex items-center space-x-1 rounded p-2 text-white hover:bg-denim-600"
|
|
key={season.id}
|
|
onClick={() => setSeason(season.id)}
|
|
>
|
|
{season.title}
|
|
</div>
|
|
))
|
|
: "No season"}
|
|
</div>
|
|
</PopupSection>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<PopupSection className="flex items-center space-x-3 border-b border-denim-500 font-bold text-white">
|
|
<button
|
|
className="-m-1.5 rounded p-1.5 hover:bg-denim-600"
|
|
onClick={toggleIsPickingSeason}
|
|
type="button"
|
|
>
|
|
<Icon icon={Icons.CHEVRON_LEFT} />
|
|
</button>
|
|
<span>{currentSeasonInfo?.title || ""}</span>
|
|
</PopupSection>
|
|
<PopupSection className="overflow-y-auto">
|
|
{loading ? (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<Loading />
|
|
</div>
|
|
) : error ? (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<div className="flex flex-col flex-wrap items-center text-slate-400">
|
|
<IconPatch
|
|
icon={Icons.EYE_SLASH}
|
|
className="text-xl text-bink-600"
|
|
/>
|
|
<p className="mt-6 w-full text-center">
|
|
Something went wrong loading the episodes for{" "}
|
|
{currentSeasonInfo?.title?.toLowerCase()}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-1">
|
|
{currentSeasonEpisodes && currentSeasonInfo
|
|
? currentSeasonEpisodes.map((e) => (
|
|
<div
|
|
className={[
|
|
"text-denim-800 -mx-2 flex items-center space-x-1 rounded p-2 text-white hover:bg-denim-600",
|
|
current?.episodeId === e.id &&
|
|
"outline outline-2 outline-denim-700",
|
|
].join(" ")}
|
|
onClick={() => setCurrent(currentSeasonInfo.id, e.id)}
|
|
key={e.id}
|
|
>
|
|
{e.number}. {e.title}
|
|
</div>
|
|
))
|
|
: "No episodes"}
|
|
</div>
|
|
)}
|
|
</PopupSection>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function SeriesSelectionControl(props: Props) {
|
|
const { videoState } = useVideoPlayerState();
|
|
|
|
if (!videoState.seasonData.isSeries) return null;
|
|
|
|
return (
|
|
<div className={props.className}>
|
|
<div className="relative">
|
|
<VideoPopout
|
|
id="episodes"
|
|
className="grid grid-rows-[auto,minmax(0,1fr)]"
|
|
>
|
|
<PopupEpisodeSelect />
|
|
</VideoPopout>
|
|
<VideoPlayerIconButton
|
|
icon={Icons.EPISODES}
|
|
text="Episodes"
|
|
onClick={() => videoState.openPopout("episodes")}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|