mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
Add cool new popout stuff
Co-authored-by: mrjvs <mistrjvs@gmail.com>
This commit is contained in:
parent
89f77debca
commit
c0867182d7
12 changed files with 308 additions and 239 deletions
|
@ -116,7 +116,12 @@ export function FloatingCard(props: RootFloatingCardProps) {
|
|||
}
|
||||
|
||||
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 = {
|
||||
|
@ -149,7 +154,7 @@ export const FloatingCardView = {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="mb-[-1px] flex flex-col bg-[#1C161B] bg-opacity-80 backdrop-blur-xl">
|
||||
<div className="flex flex-col bg-[#1C161B]">
|
||||
<FloatingDragHandle />
|
||||
<PopoutSection>
|
||||
<div className="flex justify-between">
|
||||
|
@ -165,12 +170,20 @@ export const FloatingCardView = {
|
|||
</div>
|
||||
);
|
||||
},
|
||||
Content(props: { children: React.ReactNode }) {
|
||||
Content(props: { children: React.ReactNode; noSection?: boolean }) {
|
||||
return (
|
||||
<PopoutSection className="bg-ash-300">
|
||||
{props.children}
|
||||
<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}
|
||||
</PopoutSection>
|
||||
)}
|
||||
<MobilePopoutSpacer />
|
||||
</PopoutSection>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ export function FloatingDragHandle() {
|
|||
if (!isMobile) return null;
|
||||
|
||||
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}
|
||||
>
|
||||
<div
|
||||
className={[props.className ?? ""].join(" ")}
|
||||
className={[
|
||||
props.className ?? "",
|
||||
"grid grid-rows-[auto,minmax(0,1fr)]",
|
||||
].join(" ")}
|
||||
data-floating-page={props.show ? "true" : undefined}
|
||||
style={{
|
||||
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 { PauseAction } from "@/video/components/actions/PauseAction";
|
||||
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
||||
import { QualityDisplayAction } from "@/video/components/actions/QualityDisplayAction";
|
||||
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 { KeyboardShortcutsAction } from "@/video/components/actions/KeyboardShortcutsAction";
|
||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Icons } from "@/components/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PopoutListAction } from "../popouts/PopoutUtils";
|
||||
import { PopoutListAction } from "../../popouts/PopoutUtils";
|
||||
|
||||
interface Props {
|
||||
onClick: () => any;
|
|
@ -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 {
|
|
@ -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<typeof useFloatingRouter>;
|
||||
prefix: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
|
@ -39,11 +45,17 @@ export function CaptionSelectionPopout() {
|
|||
const currentCaption = source.source?.caption?.id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PopoutSection className="bg-ash-100 font-bold text-white">
|
||||
<div>{t("videoPlayer.popouts.captions")}</div>
|
||||
</PopoutSection>
|
||||
<div className="relative overflow-y-auto">
|
||||
<FloatingView
|
||||
{...props.router.pageProps(props.prefix)}
|
||||
width={320}
|
||||
height={500}
|
||||
>
|
||||
<FloatingCardView.Header
|
||||
title={t("videoPlayer.popouts.sources")}
|
||||
description="What provider do you want to use?"
|
||||
goBack={() => props.router.navigate("/")}
|
||||
/>
|
||||
<FloatingCardView.Content noSection>
|
||||
<PopoutSection>
|
||||
<PopoutListEntry
|
||||
active={!currentCaption}
|
||||
|
@ -56,7 +68,7 @@ export function CaptionSelectionPopout() {
|
|||
</PopoutListEntry>
|
||||
</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} />
|
||||
<span>{t("videoPlayer.popouts.linkedCaptions")}</span>
|
||||
</p>
|
||||
|
@ -79,7 +91,7 @@ export function CaptionSelectionPopout() {
|
|||
))}
|
||||
</div>
|
||||
</PopoutSection>
|
||||
</div>
|
||||
</>
|
||||
</FloatingCardView.Content>
|
||||
</FloatingView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
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 (
|
||||
<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 />
|
||||
}
|
||||
/>
|
||||
<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>
|
||||
) : 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>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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: <SourceSelectionPopout />,
|
||||
captions: <CaptionSelectionPopout />,
|
||||
settings: <SettingsPopout />,
|
||||
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 { 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<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() {
|
||||
const floatingRouter = useFloatingRouter();
|
||||
const { pageProps, navigate, isLoaded, isActive } = floatingRouter;
|
||||
const { pageProps, navigate } = floatingRouter;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FloatingView {...pageProps("/")} width={320}>
|
||||
<PopoutSection>
|
||||
<FloatingDragHandle />
|
||||
<FloatingCardView.Content>
|
||||
<DownloadAction />
|
||||
<SourceSelectionAction onClick={() => navigate("/source")} />
|
||||
<CaptionsSelectionAction onClick={() => navigate("/captions")} />
|
||||
</PopoutSection>
|
||||
</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 />
|
||||
</FloatingCardView.Content>
|
||||
</FloatingView>
|
||||
<SourceSelectionPopout router={floatingRouter} prefix="source" />
|
||||
<CaptionSelectionPopout router={floatingRouter} prefix="captions" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<typeof useFloatingRouter>;
|
||||
prefix: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
|
@ -66,7 +72,6 @@ export function SourceSelectionPopout() {
|
|||
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
||||
const [scrapeResult, setScrapeResult] =
|
||||
useState<MWProviderScrapeResult | null>(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 (
|
||||
<>
|
||||
<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",
|
||||
!showingProvider ? "pointer-events-none opacity-0" : "opacity-1",
|
||||
].join(" ")}
|
||||
onClick={() => selectProvider()}
|
||||
type="button"
|
||||
>
|
||||
<Icon icon={Icons.CHEVRON_LEFT} />
|
||||
</button>
|
||||
<span
|
||||
className={[
|
||||
titlePositionClass,
|
||||
showingProvider ? "opacity-1" : "opacity-0",
|
||||
].join(" ")}
|
||||
>
|
||||
{selectedProviderPopulated?.displayName ?? ""}
|
||||
</span>
|
||||
<span
|
||||
className={[
|
||||
titlePositionClass,
|
||||
!showingProvider ? "opacity-1" : "opacity-0",
|
||||
].join(" ")}
|
||||
>
|
||||
{t("videoPlayer.popouts.sources")}
|
||||
</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",
|
||||
showingProvider
|
||||
? "max-h-full border-t"
|
||||
: "max-h-0 overflow-hidden py-0",
|
||||
].join(" ")}
|
||||
>
|
||||
{/* List providers */}
|
||||
<FloatingView
|
||||
{...props.router.pageProps(props.prefix)}
|
||||
width={320}
|
||||
height={500}
|
||||
>
|
||||
<FloatingCardView.Header
|
||||
title={t("videoPlayer.popouts.sources")}
|
||||
description="What provider do you want to use?"
|
||||
goBack={() => props.router.navigate("/")}
|
||||
/>
|
||||
<FloatingCardView.Content>
|
||||
{providers.map((v) => (
|
||||
<PopoutListEntry
|
||||
key={v.id}
|
||||
onClick={() => {
|
||||
selectProvider(v.id);
|
||||
}}
|
||||
>
|
||||
{v.displayName}
|
||||
</PopoutListEntry>
|
||||
))}
|
||||
</FloatingCardView.Content>
|
||||
</FloatingView>
|
||||
|
||||
{/* List embeds */}
|
||||
<FloatingView
|
||||
{...props.router.pageProps(`embeds`)}
|
||||
width={320}
|
||||
height={500}
|
||||
>
|
||||
<FloatingCardView.Header
|
||||
title={selectedProviderPopulated?.displayName ?? ""}
|
||||
description="Choose which video to view"
|
||||
goBack={() => props.router.navigate(`/${props.prefix}`)}
|
||||
/>
|
||||
<FloatingCardView.Content>
|
||||
{loading ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Loading />
|
||||
|
@ -268,22 +265,8 @@ export function SourceSelectionPopout() {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
</PopoutSection>
|
||||
<PopoutSection className="relative h-full overflow-y-auto">
|
||||
<div>
|
||||
{providers.map((v) => (
|
||||
<PopoutListEntry
|
||||
key={v.id}
|
||||
onClick={() => {
|
||||
selectProvider(v.id);
|
||||
}}
|
||||
>
|
||||
{v.displayName}
|
||||
</PopoutListEntry>
|
||||
))}
|
||||
</div>
|
||||
</PopoutSection>
|
||||
</div>
|
||||
</FloatingCardView.Content>
|
||||
</FloatingView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue