mirror of
https://github.com/movie-web/movie-web.git
synced 2025-01-15 03:09:10 +01:00
Add cool new popout stuff
Co-authored-by: mrjvs <mistrjvs@gmail.com>
This commit is contained in:
parent
89f77debca
commit
c0867182d7
@ -116,7 +116,12 @@ export function FloatingCard(props: RootFloatingCardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PopoutFloatingCard(props: FloatingCardProps) {
|
export function PopoutFloatingCard(props: FloatingCardProps) {
|
||||||
return <FloatingCard className="overflow-hidden rounded-md" {...props} />;
|
return (
|
||||||
|
<FloatingCard
|
||||||
|
className="overflow-hidden rounded-md bg-ash-300"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FloatingCardView = {
|
export const FloatingCardView = {
|
||||||
@ -149,7 +154,7 @@ export const FloatingCardView = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-[-1px] flex flex-col bg-[#1C161B] bg-opacity-80 backdrop-blur-xl">
|
<div className="flex flex-col bg-[#1C161B]">
|
||||||
<FloatingDragHandle />
|
<FloatingDragHandle />
|
||||||
<PopoutSection>
|
<PopoutSection>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@ -165,12 +170,20 @@ export const FloatingCardView = {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Content(props: { children: React.ReactNode }) {
|
Content(props: { children: React.ReactNode; noSection?: boolean }) {
|
||||||
return (
|
return (
|
||||||
<PopoutSection className="bg-ash-300">
|
<div className="grid h-full grid-rows-[auto,minmax(0,1fr)]">
|
||||||
|
{props.noSection ? (
|
||||||
|
<div className="relative h-full overflow-y-auto bg-ash-300">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<PopoutSection className="relative h-full overflow-y-auto bg-ash-300">
|
||||||
{props.children}
|
{props.children}
|
||||||
<MobilePopoutSpacer />
|
|
||||||
</PopoutSection>
|
</PopoutSection>
|
||||||
|
)}
|
||||||
|
<MobilePopoutSpacer />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ export function FloatingDragHandle() {
|
|||||||
if (!isMobile) return null;
|
if (!isMobile) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto my-3 -mb-3 h-1 w-12 rounded-full bg-ash-500 bg-opacity-30" />
|
<div className="relative z-50 mx-auto my-3 -mb-3 h-1 w-12 rounded-full bg-ash-500 bg-opacity-30" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,10 @@ export function FloatingView(props: Props) {
|
|||||||
show={props.show}
|
show={props.show}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={[props.className ?? ""].join(" ")}
|
className={[
|
||||||
|
props.className ?? "",
|
||||||
|
"grid grid-rows-[auto,minmax(0,1fr)]",
|
||||||
|
].join(" ")}
|
||||||
data-floating-page={props.show ? "true" : undefined}
|
data-floating-page={props.show ? "true" : undefined}
|
||||||
style={{
|
style={{
|
||||||
height: props.height ? `${props.height}px` : undefined,
|
height: props.height ? `${props.height}px` : undefined,
|
||||||
|
@ -10,10 +10,7 @@ import { MobileCenterAction } from "@/video/components/actions/MobileCenterActio
|
|||||||
import { PageTitleAction } from "@/video/components/actions/PageTitleAction";
|
import { PageTitleAction } from "@/video/components/actions/PageTitleAction";
|
||||||
import { PauseAction } from "@/video/components/actions/PauseAction";
|
import { PauseAction } from "@/video/components/actions/PauseAction";
|
||||||
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
||||||
import { QualityDisplayAction } from "@/video/components/actions/QualityDisplayAction";
|
|
||||||
import { SeriesSelectionAction } from "@/video/components/actions/SeriesSelectionAction";
|
import { SeriesSelectionAction } from "@/video/components/actions/SeriesSelectionAction";
|
||||||
import { SourceSelectionAction } from "@/video/components/actions/SourceSelectionAction";
|
|
||||||
import { CaptionsSelectionAction } from "@/video/components/actions/CaptionsSelectionAction";
|
|
||||||
import { ShowTitleAction } from "@/video/components/actions/ShowTitleAction";
|
import { ShowTitleAction } from "@/video/components/actions/ShowTitleAction";
|
||||||
import { KeyboardShortcutsAction } from "@/video/components/actions/KeyboardShortcutsAction";
|
import { KeyboardShortcutsAction } from "@/video/components/actions/KeyboardShortcutsAction";
|
||||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PopoutListAction } from "../popouts/PopoutUtils";
|
import { PopoutListAction } from "../../popouts/PopoutUtils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClick: () => any;
|
onClick: () => any;
|
@ -1,6 +1,6 @@
|
|||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PopoutListAction } from "../popouts/PopoutUtils";
|
import { PopoutListAction } from "../../popouts/PopoutUtils";
|
||||||
import { QualityDisplayAction } from "./QualityDisplayAction";
|
import { QualityDisplayAction } from "./QualityDisplayAction";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
@ -1,6 +1,9 @@
|
|||||||
import { getCaptionUrl } from "@/backend/helpers/captions";
|
import { getCaptionUrl } from "@/backend/helpers/captions";
|
||||||
import { MWCaption } from "@/backend/helpers/streams";
|
import { MWCaption } from "@/backend/helpers/streams";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import { FloatingCardView } from "@/components/popout/FloatingCard";
|
||||||
|
import { FloatingView } from "@/components/popout/FloatingView";
|
||||||
|
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
import { useControls } from "@/video/state/logic/controls";
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
@ -14,7 +17,10 @@ function makeCaptionId(caption: MWCaption, isLinked: boolean): string {
|
|||||||
return isLinked ? `linked-${caption.langIso}` : `external-${caption.langIso}`;
|
return isLinked ? `linked-${caption.langIso}` : `external-${caption.langIso}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CaptionSelectionPopout() {
|
export function CaptionSelectionPopout(props: {
|
||||||
|
router: ReturnType<typeof useFloatingRouter>;
|
||||||
|
prefix: string;
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const descriptor = useVideoPlayerDescriptor();
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
@ -39,11 +45,17 @@ export function CaptionSelectionPopout() {
|
|||||||
const currentCaption = source.source?.caption?.id;
|
const currentCaption = source.source?.caption?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<FloatingView
|
||||||
<PopoutSection className="bg-ash-100 font-bold text-white">
|
{...props.router.pageProps(props.prefix)}
|
||||||
<div>{t("videoPlayer.popouts.captions")}</div>
|
width={320}
|
||||||
</PopoutSection>
|
height={500}
|
||||||
<div className="relative overflow-y-auto">
|
>
|
||||||
|
<FloatingCardView.Header
|
||||||
|
title={t("videoPlayer.popouts.sources")}
|
||||||
|
description="What provider do you want to use?"
|
||||||
|
goBack={() => props.router.navigate("/")}
|
||||||
|
/>
|
||||||
|
<FloatingCardView.Content noSection>
|
||||||
<PopoutSection>
|
<PopoutSection>
|
||||||
<PopoutListEntry
|
<PopoutListEntry
|
||||||
active={!currentCaption}
|
active={!currentCaption}
|
||||||
@ -56,7 +68,7 @@ export function CaptionSelectionPopout() {
|
|||||||
</PopoutListEntry>
|
</PopoutListEntry>
|
||||||
</PopoutSection>
|
</PopoutSection>
|
||||||
|
|
||||||
<p className="sticky top-0 z-10 flex items-center space-x-1 bg-ash-200 px-5 py-3 text-sm font-bold uppercase">
|
<p className="sticky top-0 z-10 flex items-center space-x-1 bg-ash-300 px-5 py-3 text-xs font-bold uppercase">
|
||||||
<Icon className="text-base" icon={Icons.LINK} />
|
<Icon className="text-base" icon={Icons.LINK} />
|
||||||
<span>{t("videoPlayer.popouts.linkedCaptions")}</span>
|
<span>{t("videoPlayer.popouts.linkedCaptions")}</span>
|
||||||
</p>
|
</p>
|
||||||
@ -79,7 +91,7 @@ export function CaptionSelectionPopout() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</PopoutSection>
|
</PopoutSection>
|
||||||
</div>
|
</FloatingCardView.Content>
|
||||||
</>
|
</FloatingView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,21 @@ import { useControls } from "@/video/state/logic/controls";
|
|||||||
import { useWatchedContext } from "@/state/watched";
|
import { useWatchedContext } from "@/state/watched";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FloatingView } from "@/components/popout/FloatingView";
|
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() {
|
export function EpisodeSelectionPopout() {
|
||||||
const params = useParams<{
|
const params = useParams<{
|
||||||
media: string;
|
media: string;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { pageProps, navigate } = useFloatingRouter("/season/episodes");
|
||||||
|
|
||||||
const descriptor = useVideoPlayerDescriptor();
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
const meta = useMeta(descriptor);
|
const meta = useMeta(descriptor);
|
||||||
const controls = useControls(descriptor);
|
const controls = useControls(descriptor);
|
||||||
|
|
||||||
const [isPickingSeason, setIsPickingSeason] = useState<boolean>(false);
|
|
||||||
const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{
|
const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{
|
||||||
seasonId: string;
|
seasonId: string;
|
||||||
season?: MWSeasonWithEpisodeMeta;
|
season?: MWSeasonWithEpisodeMeta;
|
||||||
@ -41,7 +43,6 @@ export function EpisodeSelectionPopout() {
|
|||||||
seasonId: sId,
|
seasonId: sId,
|
||||||
season: undefined,
|
season: undefined,
|
||||||
});
|
});
|
||||||
setIsPickingSeason(false);
|
|
||||||
reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => {
|
reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => {
|
||||||
if (v?.meta.type !== MWMediaType.SERIES) return;
|
if (v?.meta.type !== MWMediaType.SERIES) return;
|
||||||
setCurrentVisibleSeason({
|
setCurrentVisibleSeason({
|
||||||
@ -80,81 +81,59 @@ export function EpisodeSelectionPopout() {
|
|||||||
)?.episodes;
|
)?.episodes;
|
||||||
}, [meta, currentSeasonId, currentVisibleSeason]);
|
}, [meta, currentSeasonId, currentVisibleSeason]);
|
||||||
|
|
||||||
const toggleIsPickingSeason = () => {
|
|
||||||
setIsPickingSeason(!isPickingSeason);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSeason = (id: string) => {
|
const setSeason = (id: string) => {
|
||||||
requestSeason(id);
|
requestSeason(id);
|
||||||
setCurrentVisibleSeason({ seasonId: id });
|
setCurrentVisibleSeason({ seasonId: id });
|
||||||
|
navigate("/season");
|
||||||
};
|
};
|
||||||
|
|
||||||
const { watched } = useWatchedContext();
|
const { watched } = useWatchedContext();
|
||||||
|
|
||||||
const titlePositionClass = useMemo(() => {
|
const closePopout = () => {
|
||||||
const offset = isPickingSeason ? "left-0" : "left-10";
|
controls.closePopout();
|
||||||
return [
|
};
|
||||||
"absolute w-full transition-[left,opacity] duration-200",
|
|
||||||
offset,
|
|
||||||
].join(" ");
|
|
||||||
}, [isPickingSeason]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FloatingView show height={500} width={320}>
|
<>
|
||||||
<div className="grid h-full grid-rows-[auto,minmax(0,1fr)]">
|
<FloatingView {...pageProps("episodes")} height={600} width={375}>
|
||||||
<PopoutSection className="bg-ash-100 font-bold text-white">
|
<FloatingCardView.Header
|
||||||
<div className="relative flex items-center">
|
title="Seasons"
|
||||||
<button
|
description="Choose which season you want to watch"
|
||||||
className={[
|
goBack={() => navigate("/season")}
|
||||||
"-m-1.5 rounded-lg p-1.5 transition-opacity duration-100 hover:bg-ash-200",
|
backText={`To ${currentSeasonInfo?.title.toLowerCase()}`}
|
||||||
isPickingSeason ? "pointer-events-none opacity-0" : "opacity-1",
|
/>
|
||||||
].join(" ")}
|
<FloatingCardView.Content>
|
||||||
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
|
{currentSeasonInfo
|
||||||
? meta?.seasons?.map?.((season) => (
|
? meta?.seasons?.map?.((season) => (
|
||||||
<PopoutListEntry
|
<PopoutListEntry
|
||||||
key={season.id}
|
key={season.id}
|
||||||
active={meta?.episode?.seasonId === season.id}
|
active={meta?.episode?.seasonId === season.id}
|
||||||
onClick={() => setSeason(season.id)}
|
onClick={() => setSeason(season.id)}
|
||||||
isOnDarkBackground
|
|
||||||
>
|
>
|
||||||
{season.title}
|
{season.title}
|
||||||
</PopoutListEntry>
|
</PopoutListEntry>
|
||||||
))
|
))
|
||||||
: "No season"}
|
: "No season"}
|
||||||
</PopoutSection>
|
</FloatingCardView.Content>
|
||||||
<PopoutSection className="relative h-full overflow-y-auto">
|
</FloatingView>
|
||||||
|
<FloatingView {...pageProps("season")} height={600} width={375}>
|
||||||
|
<FloatingCardView.Header
|
||||||
|
title={currentSeasonInfo?.title ?? "Unknown season"}
|
||||||
|
description="Pick an episode"
|
||||||
|
goBack={closePopout}
|
||||||
|
close
|
||||||
|
action={
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/season/episodes")}
|
||||||
|
className="flex cursor-pointer items-center space-x-2 transition-colors duration-200 hover:text-white"
|
||||||
|
>
|
||||||
|
<span>Other seasons</span>
|
||||||
|
<Icon icon={Icons.CHEVRON_RIGHT} />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<FloatingCardView.Content>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Loading />
|
<Loading />
|
||||||
@ -203,9 +182,115 @@ export function EpisodeSelectionPopout() {
|
|||||||
: "No episodes"}
|
: "No episodes"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</PopoutSection>
|
</FloatingCardView.Content>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FloatingView>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { useSyncPopouts } from "@/video/components/hooks/useSyncPopouts";
|
import { useSyncPopouts } from "@/video/components/hooks/useSyncPopouts";
|
||||||
import { EpisodeSelectionPopout } from "@/video/components/popouts/EpisodeSelectionPopout";
|
import { EpisodeSelectionPopout } from "@/video/components/popouts/EpisodeSelectionPopout";
|
||||||
import { SourceSelectionPopout } from "@/video/components/popouts/SourceSelectionPopout";
|
|
||||||
import { CaptionSelectionPopout } from "@/video/components/popouts/CaptionSelectionPopout";
|
|
||||||
import { SettingsPopout } from "@/video/components/popouts/SettingsPopout";
|
import { SettingsPopout } from "@/video/components/popouts/SettingsPopout";
|
||||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
import { useControls } from "@/video/state/logic/controls";
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
@ -14,8 +12,6 @@ import "./Popouts.css";
|
|||||||
|
|
||||||
function ShowPopout(props: { popoutId: string | null; onClose: () => void }) {
|
function ShowPopout(props: { popoutId: string | null; onClose: () => void }) {
|
||||||
const popoutMap = {
|
const popoutMap = {
|
||||||
source: <SourceSelectionPopout />,
|
|
||||||
captions: <CaptionSelectionPopout />,
|
|
||||||
settings: <SettingsPopout />,
|
settings: <SettingsPopout />,
|
||||||
episodes: <EpisodeSelectionPopout />,
|
episodes: <EpisodeSelectionPopout />,
|
||||||
};
|
};
|
||||||
|
@ -1,49 +1,29 @@
|
|||||||
|
import { FloatingCardView } from "@/components/popout/FloatingCard";
|
||||||
|
import { FloatingDragHandle } from "@/components/popout/FloatingDragHandle";
|
||||||
import { FloatingView } from "@/components/popout/FloatingView";
|
import { FloatingView } from "@/components/popout/FloatingView";
|
||||||
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
||||||
import { DownloadAction } from "@/video/components/actions/list-entries/DownloadAction";
|
import { DownloadAction } from "@/video/components/actions/list-entries/DownloadAction";
|
||||||
import { CaptionsSelectionAction } from "../actions/CaptionsSelectionAction";
|
import { CaptionsSelectionAction } from "@/video/components/actions/list-entries/CaptionsSelectionAction";
|
||||||
import { SourceSelectionAction } from "../actions/SourceSelectionAction";
|
import { SourceSelectionAction } from "@/video/components/actions/list-entries/SourceSelectionAction";
|
||||||
import { CaptionSelectionPopout } from "./CaptionSelectionPopout";
|
import { CaptionSelectionPopout } from "./CaptionSelectionPopout";
|
||||||
import { PopoutSection } from "./PopoutUtils";
|
|
||||||
import { SourceSelectionPopout } from "./SourceSelectionPopout";
|
import { SourceSelectionPopout } from "./SourceSelectionPopout";
|
||||||
|
|
||||||
function TestPopout(props: { router: ReturnType<typeof useFloatingRouter> }) {
|
|
||||||
const isCollapsed = props.router.isLoaded("embed");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p onClick={() => props.router.navigate("/")}>go back</p>
|
|
||||||
<p>{isCollapsed ? "opened" : "closed"}</p>
|
|
||||||
<p onClick={() => props.router.navigate("/source/embed")}>Open</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsPopout() {
|
export function SettingsPopout() {
|
||||||
const floatingRouter = useFloatingRouter();
|
const floatingRouter = useFloatingRouter();
|
||||||
const { pageProps, navigate, isLoaded, isActive } = floatingRouter;
|
const { pageProps, navigate } = floatingRouter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FloatingView {...pageProps("/")} width={320}>
|
<FloatingView {...pageProps("/")} width={320}>
|
||||||
<PopoutSection>
|
<FloatingDragHandle />
|
||||||
|
<FloatingCardView.Content>
|
||||||
<DownloadAction />
|
<DownloadAction />
|
||||||
<SourceSelectionAction onClick={() => navigate("/source")} />
|
<SourceSelectionAction onClick={() => navigate("/source")} />
|
||||||
<CaptionsSelectionAction onClick={() => navigate("/captions")} />
|
<CaptionsSelectionAction onClick={() => navigate("/captions")} />
|
||||||
</PopoutSection>
|
</FloatingCardView.Content>
|
||||||
</FloatingView>
|
|
||||||
<FloatingView
|
|
||||||
active={isActive("source")}
|
|
||||||
show={isLoaded("source")}
|
|
||||||
height={500}
|
|
||||||
width={320}
|
|
||||||
>
|
|
||||||
{/* <TestPopout router={floatingRouter} /> */}
|
|
||||||
<SourceSelectionPopout />
|
|
||||||
</FloatingView>
|
|
||||||
<FloatingView {...pageProps("captions")} height={500} width={320}>
|
|
||||||
<CaptionSelectionPopout />
|
|
||||||
</FloatingView>
|
</FloatingView>
|
||||||
|
<SourceSelectionPopout router={floatingRouter} prefix="source" />
|
||||||
|
<CaptionSelectionPopout router={floatingRouter} prefix="captions" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { Loading } from "@/components/layout/Loading";
|
import { Loading } from "@/components/layout/Loading";
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||||
@ -15,7 +15,10 @@ import { runEmbedScraper, runProvider } from "@/backend/helpers/run";
|
|||||||
import { MWProviderScrapeResult } from "@/backend/helpers/provider";
|
import { MWProviderScrapeResult } from "@/backend/helpers/provider";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MWEmbed, MWEmbedType } from "@/backend/helpers/embed";
|
import { MWEmbed, MWEmbedType } from "@/backend/helpers/embed";
|
||||||
import { PopoutListEntry, PopoutSection } from "./PopoutUtils";
|
import { FloatingCardView } from "@/components/popout/FloatingCard";
|
||||||
|
import { FloatingView } from "@/components/popout/FloatingView";
|
||||||
|
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
||||||
|
import { PopoutListEntry } from "./PopoutUtils";
|
||||||
|
|
||||||
interface EmbedEntryProps {
|
interface EmbedEntryProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -49,7 +52,10 @@ export function EmbedEntry(props: EmbedEntryProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SourceSelectionPopout() {
|
export function SourceSelectionPopout(props: {
|
||||||
|
router: ReturnType<typeof useFloatingRouter>;
|
||||||
|
prefix: string;
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const descriptor = useVideoPlayerDescriptor();
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
@ -66,7 +72,6 @@ export function SourceSelectionPopout() {
|
|||||||
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
||||||
const [scrapeResult, setScrapeResult] =
|
const [scrapeResult, setScrapeResult] =
|
||||||
useState<MWProviderScrapeResult | null>(null);
|
useState<MWProviderScrapeResult | null>(null);
|
||||||
const showingProvider = !!selectedProvider;
|
|
||||||
const selectedProviderPopulated = useMemo(
|
const selectedProviderPopulated = useMemo(
|
||||||
() => providers.find((v) => v.id === selectedProvider) ?? null,
|
() => providers.find((v) => v.id === selectedProvider) ?? null,
|
||||||
[providers, selectedProvider]
|
[providers, selectedProvider]
|
||||||
@ -106,6 +111,7 @@ export function SourceSelectionPopout() {
|
|||||||
if (!providerId) {
|
if (!providerId) {
|
||||||
providerRef.current = null;
|
providerRef.current = null;
|
||||||
setSelectedProvider(null);
|
setSelectedProvider(null);
|
||||||
|
props.router.navigate(`/${props.prefix}/source`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,16 +141,9 @@ export function SourceSelectionPopout() {
|
|||||||
});
|
});
|
||||||
providerRef.current = providerId;
|
providerRef.current = providerId;
|
||||||
setSelectedProvider(providerId);
|
setSelectedProvider(providerId);
|
||||||
|
props.router.navigate(`/${props.prefix}/source/embeds`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const titlePositionClass = useMemo(() => {
|
|
||||||
const offset = !showingProvider ? "left-0" : "left-10";
|
|
||||||
return [
|
|
||||||
"absolute w-full transition-[left,opacity] duration-200",
|
|
||||||
offset,
|
|
||||||
].join(" ");
|
|
||||||
}, [showingProvider]);
|
|
||||||
|
|
||||||
const visibleEmbeds = useMemo(() => {
|
const visibleEmbeds = useMemo(() => {
|
||||||
const embeds = scrapeResult?.embeds || [];
|
const embeds = scrapeResult?.embeds || [];
|
||||||
|
|
||||||
@ -174,45 +173,43 @@ export function SourceSelectionPopout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PopoutSection className="bg-ash-100 font-bold text-white">
|
{/* List providers */}
|
||||||
<div className="relative flex items-center">
|
<FloatingView
|
||||||
<button
|
{...props.router.pageProps(props.prefix)}
|
||||||
className={[
|
width={320}
|
||||||
"-m-1.5 rounded-lg p-1.5 transition-opacity duration-100 hover:bg-ash-200",
|
height={500}
|
||||||
!showingProvider ? "pointer-events-none opacity-0" : "opacity-1",
|
|
||||||
].join(" ")}
|
|
||||||
onClick={() => selectProvider()}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<Icon icon={Icons.CHEVRON_LEFT} />
|
<FloatingCardView.Header
|
||||||
</button>
|
title={t("videoPlayer.popouts.sources")}
|
||||||
<span
|
description="What provider do you want to use?"
|
||||||
className={[
|
goBack={() => props.router.navigate("/")}
|
||||||
titlePositionClass,
|
/>
|
||||||
showingProvider ? "opacity-1" : "opacity-0",
|
<FloatingCardView.Content>
|
||||||
].join(" ")}
|
{providers.map((v) => (
|
||||||
|
<PopoutListEntry
|
||||||
|
key={v.id}
|
||||||
|
onClick={() => {
|
||||||
|
selectProvider(v.id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{selectedProviderPopulated?.displayName ?? ""}
|
{v.displayName}
|
||||||
</span>
|
</PopoutListEntry>
|
||||||
<span
|
))}
|
||||||
className={[
|
</FloatingCardView.Content>
|
||||||
titlePositionClass,
|
</FloatingView>
|
||||||
!showingProvider ? "opacity-1" : "opacity-0",
|
|
||||||
].join(" ")}
|
{/* List embeds */}
|
||||||
>
|
<FloatingView
|
||||||
{t("videoPlayer.popouts.sources")}
|
{...props.router.pageProps(`embeds`)}
|
||||||
</span>
|
width={320}
|
||||||
</div>
|
height={500}
|
||||||
</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",
|
|
||||||
showingProvider
|
|
||||||
? "max-h-full border-t"
|
|
||||||
: "max-h-0 overflow-hidden py-0",
|
|
||||||
].join(" ")}
|
|
||||||
>
|
>
|
||||||
|
<FloatingCardView.Header
|
||||||
|
title={selectedProviderPopulated?.displayName ?? ""}
|
||||||
|
description="Choose which video to view"
|
||||||
|
goBack={() => props.router.navigate(`/${props.prefix}`)}
|
||||||
|
/>
|
||||||
|
<FloatingCardView.Content>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Loading />
|
<Loading />
|
||||||
@ -268,22 +265,8 @@ export function SourceSelectionPopout() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</PopoutSection>
|
</FloatingCardView.Content>
|
||||||
<PopoutSection className="relative h-full overflow-y-auto">
|
</FloatingView>
|
||||||
<div>
|
|
||||||
{providers.map((v) => (
|
|
||||||
<PopoutListEntry
|
|
||||||
key={v.id}
|
|
||||||
onClick={() => {
|
|
||||||
selectProvider(v.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{v.displayName}
|
|
||||||
</PopoutListEntry>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</PopoutSection>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user