|
|
|
@ -13,19 +13,21 @@ import { useControls } from "@/video/state/logic/controls";
@@ -13,19 +13,21 @@ import { useControls } from "@/video/state/logic/controls";
|
|
|
|
|
import { useWatchedContext } from "@/state/watched"; |
|
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
import { FloatingView } from "@/components/popout/FloatingView"; |
|
|
|
|
import { PopoutListEntry, PopoutSection } from "./PopoutUtils"; |
|
|
|
|
import { useFloatingRouter } from "@/hooks/useFloatingRouter"; |
|
|
|
|
import { FloatingCardView } from "@/components/popout/FloatingCard"; |
|
|
|
|
import { PopoutListEntry } from "./PopoutUtils"; |
|
|
|
|
|
|
|
|
|
export function EpisodeSelectionPopout() { |
|
|
|
|
const params = useParams<{ |
|
|
|
|
media: string; |
|
|
|
|
}>(); |
|
|
|
|
const { t } = useTranslation(); |
|
|
|
|
const { pageProps, navigate } = useFloatingRouter("/season/episodes"); |
|
|
|
|
|
|
|
|
|
const descriptor = useVideoPlayerDescriptor(); |
|
|
|
|
const meta = useMeta(descriptor); |
|
|
|
|
const controls = useControls(descriptor); |
|
|
|
|
|
|
|
|
|
const [isPickingSeason, setIsPickingSeason] = useState<boolean>(false); |
|
|
|
|
const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{ |
|
|
|
|
seasonId: string; |
|
|
|
|
season?: MWSeasonWithEpisodeMeta; |
|
|
|
@ -41,7 +43,6 @@ export function EpisodeSelectionPopout() {
@@ -41,7 +43,6 @@ export function EpisodeSelectionPopout() {
|
|
|
|
|
seasonId: sId, |
|
|
|
|
season: undefined, |
|
|
|
|
}); |
|
|
|
|
setIsPickingSeason(false); |
|
|
|
|
reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => { |
|
|
|
|
if (v?.meta.type !== MWMediaType.SERIES) return; |
|
|
|
|
setCurrentVisibleSeason({ |
|
|
|
@ -80,132 +81,216 @@ export function EpisodeSelectionPopout() {
@@ -80,132 +81,216 @@ export function EpisodeSelectionPopout() {
|
|
|
|
|
)?.episodes; |
|
|
|
|
}, [meta, currentSeasonId, currentVisibleSeason]); |
|
|
|
|
|
|
|
|
|
const toggleIsPickingSeason = () => { |
|
|
|
|
setIsPickingSeason(!isPickingSeason); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const setSeason = (id: string) => { |
|
|
|
|
requestSeason(id); |
|
|
|
|
setCurrentVisibleSeason({ seasonId: id }); |
|
|
|
|
navigate("/season"); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const { watched } = useWatchedContext(); |
|
|
|
|
|
|
|
|
|
const titlePositionClass = useMemo(() => { |
|
|
|
|
const offset = isPickingSeason ? "left-0" : "left-10"; |
|
|
|
|
return [ |
|
|
|
|
"absolute w-full transition-[left,opacity] duration-200", |
|
|
|
|
offset, |
|
|
|
|
].join(" "); |
|
|
|
|
}, [isPickingSeason]); |
|
|
|
|
const closePopout = () => { |
|
|
|
|
controls.closePopout(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<FloatingView show height={500} width={320}> |
|
|
|
|
<div className="grid h-full grid-rows-[auto,minmax(0,1fr)]"> |
|
|
|
|
<PopoutSection className="bg-ash-100 font-bold text-white"> |
|
|
|
|
<div className="relative flex items-center"> |
|
|
|
|
<> |
|
|
|
|
<FloatingView {...pageProps("episodes")} height={600} width={375}> |
|
|
|
|
<FloatingCardView.Header |
|
|
|
|
title="Seasons" |
|
|
|
|
description="Choose which season you want to watch" |
|
|
|
|
goBack={() => navigate("/season")} |
|
|
|
|
backText={`To ${currentSeasonInfo?.title.toLowerCase()}`} |
|
|
|
|
/> |
|
|
|
|
<FloatingCardView.Content> |
|
|
|
|
{currentSeasonInfo |
|
|
|
|
? meta?.seasons?.map?.((season) => ( |
|
|
|
|
<PopoutListEntry |
|
|
|
|
key={season.id} |
|
|
|
|
active={meta?.episode?.seasonId === season.id} |
|
|
|
|
onClick={() => setSeason(season.id)} |
|
|
|
|
> |
|
|
|
|
{season.title} |
|
|
|
|
</PopoutListEntry> |
|
|
|
|
)) |
|
|
|
|
: "No season"} |
|
|
|
|
</FloatingCardView.Content> |
|
|
|
|
</FloatingView> |
|
|
|
|
<FloatingView {...pageProps("season")} height={600} width={375}> |
|
|
|
|
<FloatingCardView.Header |
|
|
|
|
title={currentSeasonInfo?.title ?? "Unknown season"} |
|
|
|
|
description="Pick an episode" |
|
|
|
|
goBack={closePopout} |
|
|
|
|
close |
|
|
|
|
action={ |
|
|
|
|
<button |
|
|
|
|
className={[ |
|
|
|
|
"-m-1.5 rounded-lg p-1.5 transition-opacity duration-100 hover:bg-ash-200", |
|
|
|
|
isPickingSeason ? "pointer-events-none opacity-0" : "opacity-1", |
|
|
|
|
].join(" ")} |
|
|
|
|
onClick={toggleIsPickingSeason} |
|
|
|
|
type="button" |
|
|
|
|
onClick={() => navigate("/season/episodes")} |
|
|
|
|
className="flex cursor-pointer items-center space-x-2 transition-colors duration-200 hover:text-white" |
|
|
|
|
> |
|
|
|
|
<Icon icon={Icons.CHEVRON_LEFT} /> |
|
|
|
|
<span>Other seasons</span> |
|
|
|
|
<Icon icon={Icons.CHEVRON_RIGHT} /> |
|
|
|
|
</button> |
|
|
|
|
<span |
|
|
|
|
className={[ |
|
|
|
|
titlePositionClass, |
|
|
|
|
!isPickingSeason ? "opacity-1" : "opacity-0", |
|
|
|
|
].join(" ")} |
|
|
|
|
> |
|
|
|
|
{currentSeasonInfo?.title || ""} |
|
|
|
|
</span> |
|
|
|
|
<span |
|
|
|
|
className={[ |
|
|
|
|
titlePositionClass, |
|
|
|
|
isPickingSeason ? "opacity-1" : "opacity-0", |
|
|
|
|
].join(" ")} |
|
|
|
|
> |
|
|
|
|
{t("videoPlayer.popouts.seasons")} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
</PopoutSection> |
|
|
|
|
<div className="relative grid h-full grid-rows-[minmax(1px,1fr)]"> |
|
|
|
|
<PopoutSection |
|
|
|
|
className={[ |
|
|
|
|
"absolute inset-0 z-30 overflow-y-auto border-ash-400 bg-ash-100 transition-[max-height,padding] duration-200", |
|
|
|
|
isPickingSeason |
|
|
|
|
? "max-h-full border-t" |
|
|
|
|
: "max-h-0 overflow-hidden py-0", |
|
|
|
|
].join(" ")} |
|
|
|
|
> |
|
|
|
|
{currentSeasonInfo |
|
|
|
|
? meta?.seasons?.map?.((season) => ( |
|
|
|
|
<PopoutListEntry |
|
|
|
|
key={season.id} |
|
|
|
|
active={meta?.episode?.seasonId === season.id} |
|
|
|
|
onClick={() => setSeason(season.id)} |
|
|
|
|
isOnDarkBackground |
|
|
|
|
> |
|
|
|
|
{season.title} |
|
|
|
|
</PopoutListEntry> |
|
|
|
|
)) |
|
|
|
|
: "No season"} |
|
|
|
|
</PopoutSection> |
|
|
|
|
<PopoutSection className="relative h-full 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"> |
|
|
|
|
{t("videoPLayer.popouts.errors.loadingWentWrong", { |
|
|
|
|
seasonTitle: currentSeasonInfo?.title?.toLowerCase(), |
|
|
|
|
})} |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
) : ( |
|
|
|
|
<div> |
|
|
|
|
{currentSeasonEpisodes && currentSeasonInfo |
|
|
|
|
? currentSeasonEpisodes.map((e) => ( |
|
|
|
|
<PopoutListEntry |
|
|
|
|
key={e.id} |
|
|
|
|
active={e.id === meta?.episode?.episodeId} |
|
|
|
|
onClick={() => { |
|
|
|
|
if (e.id === meta?.episode?.episodeId) |
|
|
|
|
controls.closePopout(); |
|
|
|
|
else setCurrent(currentSeasonInfo.id, e.id); |
|
|
|
|
}} |
|
|
|
|
percentageCompleted={ |
|
|
|
|
watched.items.find( |
|
|
|
|
(item) => |
|
|
|
|
item.item?.series?.seasonId === |
|
|
|
|
currentSeasonInfo.id && |
|
|
|
|
item.item?.series?.episodeId === e.id |
|
|
|
|
)?.percentage |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
{t("videoPlayer.popouts.episode", { |
|
|
|
|
index: e.number, |
|
|
|
|
title: e.title, |
|
|
|
|
})} |
|
|
|
|
</PopoutListEntry> |
|
|
|
|
)) |
|
|
|
|
: "No episodes"} |
|
|
|
|
} |
|
|
|
|
/> |
|
|
|
|
<FloatingCardView.Content> |
|
|
|
|
{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"> |
|
|
|
|
{t("videoPLayer.popouts.errors.loadingWentWrong", { |
|
|
|
|
seasonTitle: currentSeasonInfo?.title?.toLowerCase(), |
|
|
|
|
})} |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</PopoutSection> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</FloatingView> |
|
|
|
|
</div> |
|
|
|
|
) : ( |
|
|
|
|
<div> |
|
|
|
|
{currentSeasonEpisodes && currentSeasonInfo |
|
|
|
|
? currentSeasonEpisodes.map((e) => ( |
|
|
|
|
<PopoutListEntry |
|
|
|
|
key={e.id} |
|
|
|
|
active={e.id === meta?.episode?.episodeId} |
|
|
|
|
onClick={() => { |
|
|
|
|
if (e.id === meta?.episode?.episodeId) |
|
|
|
|
controls.closePopout(); |
|
|
|
|
else setCurrent(currentSeasonInfo.id, e.id); |
|
|
|
|
}} |
|
|
|
|
percentageCompleted={ |
|
|
|
|
watched.items.find( |
|
|
|
|
(item) => |
|
|
|
|
item.item?.series?.seasonId === |
|
|
|
|
currentSeasonInfo.id && |
|
|
|
|
item.item?.series?.episodeId === e.id |
|
|
|
|
)?.percentage |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
{t("videoPlayer.popouts.episode", { |
|
|
|
|
index: e.number, |
|
|
|
|
title: e.title, |
|
|
|
|
})} |
|
|
|
|
</PopoutListEntry> |
|
|
|
|
)) |
|
|
|
|
: "No episodes"} |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</FloatingCardView.Content> |
|
|
|
|
</FloatingView> |
|
|
|
|
</> |
|
|
|
|
// <FloatingView show height={500} width={320}>
|
|
|
|
|
// <div className="grid h-full grid-rows-[auto,minmax(0,1fr)]">
|
|
|
|
|
// <PopoutSection className="bg-ash-100 font-bold text-white">
|
|
|
|
|
// <div className="relative flex items-center">
|
|
|
|
|
// <button
|
|
|
|
|
// className={[
|
|
|
|
|
// "-m-1.5 rounded-lg p-1.5 transition-opacity duration-100 hover:bg-ash-200",
|
|
|
|
|
// isPickingSeason ? "pointer-events-none opacity-0" : "opacity-1",
|
|
|
|
|
// ].join(" ")}
|
|
|
|
|
// onClick={toggleIsPickingSeason}
|
|
|
|
|
// type="button"
|
|
|
|
|
// >
|
|
|
|
|
// <Icon icon={Icons.CHEVRON_LEFT} />
|
|
|
|
|
// </button>
|
|
|
|
|
// <span
|
|
|
|
|
// className={[
|
|
|
|
|
// titlePositionClass,
|
|
|
|
|
// !isPickingSeason ? "opacity-1" : "opacity-0",
|
|
|
|
|
// ].join(" ")}
|
|
|
|
|
// >
|
|
|
|
|
// {currentSeasonInfo?.title || ""}
|
|
|
|
|
// </span>
|
|
|
|
|
// <span
|
|
|
|
|
// className={[
|
|
|
|
|
// titlePositionClass,
|
|
|
|
|
// isPickingSeason ? "opacity-1" : "opacity-0",
|
|
|
|
|
// ].join(" ")}
|
|
|
|
|
// >
|
|
|
|
|
// {t("videoPlayer.popouts.seasons")}
|
|
|
|
|
// </span>
|
|
|
|
|
// </div>
|
|
|
|
|
// </PopoutSection>
|
|
|
|
|
// <div className="relative grid h-full grid-rows-[minmax(1px,1fr)]">
|
|
|
|
|
// <PopoutSection
|
|
|
|
|
// className={[
|
|
|
|
|
// "absolute inset-0 z-30 overflow-y-auto border-ash-400 bg-ash-100 transition-[max-height,padding] duration-200",
|
|
|
|
|
// isPickingSeason
|
|
|
|
|
// ? "max-h-full border-t"
|
|
|
|
|
// : "max-h-0 overflow-hidden py-0",
|
|
|
|
|
// ].join(" ")}
|
|
|
|
|
// >
|
|
|
|
|
// {currentSeasonInfo
|
|
|
|
|
// ? meta?.seasons?.map?.((season) => (
|
|
|
|
|
// <PopoutListEntry
|
|
|
|
|
// key={season.id}
|
|
|
|
|
// active={meta?.episode?.seasonId === season.id}
|
|
|
|
|
// onClick={() => setSeason(season.id)}
|
|
|
|
|
// isOnDarkBackground
|
|
|
|
|
// >
|
|
|
|
|
// {season.title}
|
|
|
|
|
// </PopoutListEntry>
|
|
|
|
|
// ))
|
|
|
|
|
// : "No season"}
|
|
|
|
|
// </PopoutSection>
|
|
|
|
|
// <PopoutSection className="relative h-full 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">
|
|
|
|
|
// {t("videoPLayer.popouts.errors.loadingWentWrong", {
|
|
|
|
|
// seasonTitle: currentSeasonInfo?.title?.toLowerCase(),
|
|
|
|
|
// })}
|
|
|
|
|
// </p>
|
|
|
|
|
// </div>
|
|
|
|
|
// </div>
|
|
|
|
|
// ) : (
|
|
|
|
|
// <div>
|
|
|
|
|
// {currentSeasonEpisodes && currentSeasonInfo
|
|
|
|
|
// ? currentSeasonEpisodes.map((e) => (
|
|
|
|
|
// <PopoutListEntry
|
|
|
|
|
// key={e.id}
|
|
|
|
|
// active={e.id === meta?.episode?.episodeId}
|
|
|
|
|
// onClick={() => {
|
|
|
|
|
// if (e.id === meta?.episode?.episodeId)
|
|
|
|
|
// controls.closePopout();
|
|
|
|
|
// else setCurrent(currentSeasonInfo.id, e.id);
|
|
|
|
|
// }}
|
|
|
|
|
// percentageCompleted={
|
|
|
|
|
// watched.items.find(
|
|
|
|
|
// (item) =>
|
|
|
|
|
// item.item?.series?.seasonId ===
|
|
|
|
|
// currentSeasonInfo.id &&
|
|
|
|
|
// item.item?.series?.episodeId === e.id
|
|
|
|
|
// )?.percentage
|
|
|
|
|
// }
|
|
|
|
|
// >
|
|
|
|
|
// {t("videoPlayer.popouts.episode", {
|
|
|
|
|
// index: e.number,
|
|
|
|
|
// title: e.title,
|
|
|
|
|
// })}
|
|
|
|
|
// </PopoutListEntry>
|
|
|
|
|
// ))
|
|
|
|
|
// : "No episodes"}
|
|
|
|
|
// </div>
|
|
|
|
|
// )}
|
|
|
|
|
// </PopoutSection>
|
|
|
|
|
// </div>
|
|
|
|
|
// </div>
|
|
|
|
|
// </FloatingView>
|
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|