diff --git a/src/components/popout/FloatingCard.tsx b/src/components/popout/FloatingCard.tsx index 253d5016..6cd0d363 100644 --- a/src/components/popout/FloatingCard.tsx +++ b/src/components/popout/FloatingCard.tsx @@ -116,7 +116,12 @@ export function FloatingCard(props: RootFloatingCardProps) { } export function PopoutFloatingCard(props: FloatingCardProps) { - return ; + return ( + + ); } export const FloatingCardView = { @@ -149,7 +154,7 @@ export const FloatingCardView = { ); return ( -
+
@@ -165,12 +170,20 @@ export const FloatingCardView = {
); }, - Content(props: { children: React.ReactNode }) { + Content(props: { children: React.ReactNode; noSection?: boolean }) { return ( - - {props.children} +
+ {props.noSection ? ( +
+ {props.children} +
+ ) : ( + + {props.children} + + )} - +
); }, }; diff --git a/src/components/popout/FloatingDragHandle.tsx b/src/components/popout/FloatingDragHandle.tsx index 711da021..81927557 100644 --- a/src/components/popout/FloatingDragHandle.tsx +++ b/src/components/popout/FloatingDragHandle.tsx @@ -6,7 +6,7 @@ export function FloatingDragHandle() { if (!isMobile) return null; return ( -
+
); } diff --git a/src/components/popout/FloatingView.tsx b/src/components/popout/FloatingView.tsx index d75f859f..9ae797ee 100644 --- a/src/components/popout/FloatingView.tsx +++ b/src/components/popout/FloatingView.tsx @@ -22,7 +22,10 @@ export function FloatingView(props: Props) { show={props.show} >
any; diff --git a/src/video/components/actions/QualityDisplayAction.tsx b/src/video/components/actions/list-entries/QualityDisplayAction.tsx similarity index 100% rename from src/video/components/actions/QualityDisplayAction.tsx rename to src/video/components/actions/list-entries/QualityDisplayAction.tsx diff --git a/src/video/components/actions/SourceSelectionAction.tsx b/src/video/components/actions/list-entries/SourceSelectionAction.tsx similarity index 89% rename from src/video/components/actions/SourceSelectionAction.tsx rename to src/video/components/actions/list-entries/SourceSelectionAction.tsx index 8ce938fc..f230646f 100644 --- a/src/video/components/actions/SourceSelectionAction.tsx +++ b/src/video/components/actions/list-entries/SourceSelectionAction.tsx @@ -1,6 +1,6 @@ import { Icon, Icons } from "@/components/Icon"; import { useTranslation } from "react-i18next"; -import { PopoutListAction } from "../popouts/PopoutUtils"; +import { PopoutListAction } from "../../popouts/PopoutUtils"; import { QualityDisplayAction } from "./QualityDisplayAction"; interface Props { diff --git a/src/video/components/popouts/CaptionSelectionPopout.tsx b/src/video/components/popouts/CaptionSelectionPopout.tsx index e5ecdaeb..43ed729f 100644 --- a/src/video/components/popouts/CaptionSelectionPopout.tsx +++ b/src/video/components/popouts/CaptionSelectionPopout.tsx @@ -1,6 +1,9 @@ import { getCaptionUrl } from "@/backend/helpers/captions"; import { MWCaption } from "@/backend/helpers/streams"; 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 { useVideoPlayerDescriptor } from "@/video/state/hooks"; 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}`; } -export function CaptionSelectionPopout() { +export function CaptionSelectionPopout(props: { + router: ReturnType; + prefix: string; +}) { const { t } = useTranslation(); const descriptor = useVideoPlayerDescriptor(); @@ -39,11 +45,17 @@ export function CaptionSelectionPopout() { const currentCaption = source.source?.caption?.id; return ( - <> - -
{t("videoPlayer.popouts.captions")}
-
-
+ + props.router.navigate("/")} + /> + -

+

{t("videoPlayer.popouts.linkedCaptions")}

@@ -79,7 +91,7 @@ export function CaptionSelectionPopout() { ))}
-
- + + ); } diff --git a/src/video/components/popouts/EpisodeSelectionPopout.tsx b/src/video/components/popouts/EpisodeSelectionPopout.tsx index b5dbd4f0..6d5bed39 100644 --- a/src/video/components/popouts/EpisodeSelectionPopout.tsx +++ b/src/video/components/popouts/EpisodeSelectionPopout.tsx @@ -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(false); const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{ seasonId: string; season?: MWSeasonWithEpisodeMeta; @@ -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() { )?.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 ( - -
- -
+ <> + + navigate("/season")} + backText={`To ${currentSeasonInfo?.title.toLowerCase()}`} + /> + + {currentSeasonInfo + ? meta?.seasons?.map?.((season) => ( + setSeason(season.id)} + > + {season.title} + + )) + : "No season"} + + + + navigate("/season/episodes")} + className="flex cursor-pointer items-center space-x-2 transition-colors duration-200 hover:text-white" > - + Other seasons + - - {currentSeasonInfo?.title || ""} - - - {t("videoPlayer.popouts.seasons")} - -
-
-
- - {currentSeasonInfo - ? meta?.seasons?.map?.((season) => ( - setSeason(season.id)} - isOnDarkBackground - > - {season.title} - - )) - : "No season"} - - - {loading ? ( -
- + } + /> + + {loading ? ( +
+ +
+ ) : error ? ( +
+
+ +

+ {t("videoPLayer.popouts.errors.loadingWentWrong", { + seasonTitle: currentSeasonInfo?.title?.toLowerCase(), + })} +

- ) : error ? ( -
-
- -

- {t("videoPLayer.popouts.errors.loadingWentWrong", { - seasonTitle: currentSeasonInfo?.title?.toLowerCase(), - })} -

-
-
- ) : ( -
- {currentSeasonEpisodes && currentSeasonInfo - ? currentSeasonEpisodes.map((e) => ( - { - 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, - })} - - )) - : "No episodes"} -
- )} - -
-
- +
+ ) : ( +
+ {currentSeasonEpisodes && currentSeasonInfo + ? currentSeasonEpisodes.map((e) => ( + { + 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, + })} + + )) + : "No episodes"} +
+ )} + + + + // + //
+ // + //
+ // + // + // {currentSeasonInfo?.title || ""} + // + // + // {t("videoPlayer.popouts.seasons")} + // + //
+ //
+ //
+ // + // {currentSeasonInfo + // ? meta?.seasons?.map?.((season) => ( + // setSeason(season.id)} + // isOnDarkBackground + // > + // {season.title} + // + // )) + // : "No season"} + // + // + // {loading ? ( + //
+ // + //
+ // ) : error ? ( + //
+ //
+ // + //

+ // {t("videoPLayer.popouts.errors.loadingWentWrong", { + // seasonTitle: currentSeasonInfo?.title?.toLowerCase(), + // })} + //

+ //
+ //
+ // ) : ( + //
+ // {currentSeasonEpisodes && currentSeasonInfo + // ? currentSeasonEpisodes.map((e) => ( + // { + // 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, + // })} + // + // )) + // : "No episodes"} + //
+ // )} + //
+ //
+ //
+ //
); } diff --git a/src/video/components/popouts/PopoutProviderAction.tsx b/src/video/components/popouts/PopoutProviderAction.tsx index 104b0703..3c90d46d 100644 --- a/src/video/components/popouts/PopoutProviderAction.tsx +++ b/src/video/components/popouts/PopoutProviderAction.tsx @@ -1,7 +1,5 @@ import { useSyncPopouts } from "@/video/components/hooks/useSyncPopouts"; 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 { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useControls } from "@/video/state/logic/controls"; @@ -14,8 +12,6 @@ import "./Popouts.css"; function ShowPopout(props: { popoutId: string | null; onClose: () => void }) { const popoutMap = { - source: , - captions: , settings: , episodes: , }; diff --git a/src/video/components/popouts/SettingsPopout.tsx b/src/video/components/popouts/SettingsPopout.tsx index 1777b1e3..9640397d 100644 --- a/src/video/components/popouts/SettingsPopout.tsx +++ b/src/video/components/popouts/SettingsPopout.tsx @@ -1,49 +1,29 @@ +import { FloatingCardView } from "@/components/popout/FloatingCard"; +import { FloatingDragHandle } from "@/components/popout/FloatingDragHandle"; import { FloatingView } from "@/components/popout/FloatingView"; import { useFloatingRouter } from "@/hooks/useFloatingRouter"; import { DownloadAction } from "@/video/components/actions/list-entries/DownloadAction"; -import { CaptionsSelectionAction } from "../actions/CaptionsSelectionAction"; -import { SourceSelectionAction } from "../actions/SourceSelectionAction"; +import { CaptionsSelectionAction } from "@/video/components/actions/list-entries/CaptionsSelectionAction"; +import { SourceSelectionAction } from "@/video/components/actions/list-entries/SourceSelectionAction"; import { CaptionSelectionPopout } from "./CaptionSelectionPopout"; -import { PopoutSection } from "./PopoutUtils"; import { SourceSelectionPopout } from "./SourceSelectionPopout"; -function TestPopout(props: { router: ReturnType }) { - const isCollapsed = props.router.isLoaded("embed"); - - return ( -
-

props.router.navigate("/")}>go back

-

{isCollapsed ? "opened" : "closed"}

-

props.router.navigate("/source/embed")}>Open

-
- ); -} - export function SettingsPopout() { const floatingRouter = useFloatingRouter(); - const { pageProps, navigate, isLoaded, isActive } = floatingRouter; + const { pageProps, navigate } = floatingRouter; return ( <> - + + navigate("/source")} /> navigate("/captions")} /> - - - - {/* */} - - - - + + + ); } diff --git a/src/video/components/popouts/SourceSelectionPopout.tsx b/src/video/components/popouts/SourceSelectionPopout.tsx index 2eee6339..fe2d5ca2 100644 --- a/src/video/components/popouts/SourceSelectionPopout.tsx +++ b/src/video/components/popouts/SourceSelectionPopout.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef, useState } from "react"; -import { Icon, Icons } from "@/components/Icon"; +import { Icons } from "@/components/Icon"; import { useLoading } from "@/hooks/useLoading"; import { Loading } from "@/components/layout/Loading"; import { IconPatch } from "@/components/buttons/IconPatch"; @@ -15,7 +15,10 @@ import { runEmbedScraper, runProvider } from "@/backend/helpers/run"; import { MWProviderScrapeResult } from "@/backend/helpers/provider"; import { useTranslation } from "react-i18next"; 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 { name: string; @@ -49,7 +52,10 @@ export function EmbedEntry(props: EmbedEntryProps) { ); } -export function SourceSelectionPopout() { +export function SourceSelectionPopout(props: { + router: ReturnType; + prefix: string; +}) { const { t } = useTranslation(); const descriptor = useVideoPlayerDescriptor(); @@ -66,7 +72,6 @@ export function SourceSelectionPopout() { const [selectedProvider, setSelectedProvider] = useState(null); const [scrapeResult, setScrapeResult] = useState(null); - const showingProvider = !!selectedProvider; const selectedProviderPopulated = useMemo( () => providers.find((v) => v.id === selectedProvider) ?? null, [providers, selectedProvider] @@ -106,6 +111,7 @@ export function SourceSelectionPopout() { if (!providerId) { providerRef.current = null; setSelectedProvider(null); + props.router.navigate(`/${props.prefix}/source`); return; } @@ -135,16 +141,9 @@ export function SourceSelectionPopout() { }); providerRef.current = 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 embeds = scrapeResult?.embeds || []; @@ -174,45 +173,43 @@ export function SourceSelectionPopout() { return ( <> - -
- - - {selectedProviderPopulated?.displayName ?? ""} - - - {t("videoPlayer.popouts.sources")} - -
-
-
- + {/* List providers */} + + props.router.navigate("/")} + /> + + {providers.map((v) => ( + { + selectProvider(v.id); + }} + > + {v.displayName} + + ))} + + + + {/* List embeds */} + + props.router.navigate(`/${props.prefix}`)} + /> + {loading ? (
@@ -268,22 +265,8 @@ export function SourceSelectionPopout() { )} )} - - -
- {providers.map((v) => ( - { - selectProvider(v.id); - }} - > - {v.displayName} - - ))} -
-
-
+
+
); }