mirror of
https://github.com/movie-web/movie-web.git
synced 2025-01-28 05:25:31 +01:00
buffer more visible, fix volume mute, rewrote entire overlay router system
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
4a2a8e89cc
commit
7b3452c535
@ -4,7 +4,7 @@ import {
|
|||||||
} from "@headlessui/react";
|
} from "@headlessui/react";
|
||||||
import { Fragment, ReactNode } from "react";
|
import { Fragment, ReactNode } from "react";
|
||||||
|
|
||||||
type TransitionAnimations =
|
export type TransitionAnimations =
|
||||||
| "slide-down"
|
| "slide-down"
|
||||||
| "slide-full-left"
|
| "slide-full-left"
|
||||||
| "slide-full-right"
|
| "slide-full-right"
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import { ReactNode, useEffect, useRef } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
export function createOverlayAnchorEvent(id: string): string {
|
|
||||||
return `__overlay::anchor::${id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@ -10,38 +6,5 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function OverlayAnchor(props: Props) {
|
export function OverlayAnchor(props: Props) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
return <div id={`__overlayRouter::${props.id}`}>{props.children}</div>;
|
||||||
const old = useRef<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
|
|
||||||
let cancelled = false;
|
|
||||||
function render() {
|
|
||||||
if (cancelled) return;
|
|
||||||
|
|
||||||
if (ref.current) {
|
|
||||||
const current = old.current;
|
|
||||||
const newer = ref.current.getBoundingClientRect();
|
|
||||||
const newerStr = JSON.stringify(newer);
|
|
||||||
if (current !== newerStr) {
|
|
||||||
old.current = newerStr;
|
|
||||||
const evtStr = createOverlayAnchorEvent(props.id);
|
|
||||||
(window as any)[evtStr] = newer;
|
|
||||||
const evObj = new CustomEvent(createOverlayAnchorEvent(props.id), {
|
|
||||||
detail: newer,
|
|
||||||
});
|
|
||||||
document.dispatchEvent(evObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.requestAnimationFrame(render);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(render);
|
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, [props]);
|
|
||||||
|
|
||||||
return <div ref={ref}>{props.children}</div>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,44 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import { Transition } from "@/components/Transition";
|
import { Transition, TransitionAnimations } from "@/components/Transition";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter";
|
import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||||
|
import { useOverlayStore } from "@/stores/overlay/store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
path: string;
|
path: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
height?: number;
|
height: number;
|
||||||
width?: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OverlayPage(props: Props) {
|
export function OverlayPage(props: Props) {
|
||||||
const router = useInternalOverlayRouter(props.id);
|
const router = useInternalOverlayRouter(props.id);
|
||||||
const backwards = router.showBackwardsTransition(props.path);
|
const backwards = router.showBackwardsTransition(props.path);
|
||||||
const show = router.isCurrentPage(props.path);
|
const show = router.isCurrentPage(props.path);
|
||||||
|
const registerRoute = useOverlayStore((s) => s.registerRoute);
|
||||||
|
const path = useMemo(() => router.makePath(props.path), [props.path, router]);
|
||||||
const { isMobile } = useIsMobile();
|
const { isMobile } = useIsMobile();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerRoute({
|
||||||
|
id: path,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
});
|
||||||
|
}, [props.height, props.width, path, registerRoute]);
|
||||||
|
|
||||||
const width = !isMobile ? `${props.width}px` : "100%";
|
const width = !isMobile ? `${props.width}px` : "100%";
|
||||||
|
let animation: TransitionAnimations = "none";
|
||||||
|
if (backwards === "yes" || backwards === "no")
|
||||||
|
animation = backwards === "yes" ? "slide-full-left" : "slide-full-right";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
animation={backwards ? "slide-full-left" : "slide-full-right"}
|
animation={animation}
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
durationClass="duration-[400ms]"
|
durationClass="duration-[400ms]"
|
||||||
show={show}
|
show={show}
|
||||||
@ -33,7 +48,6 @@ export function OverlayPage(props: Props) {
|
|||||||
props.className,
|
props.className,
|
||||||
"grid grid-rows-[auto,minmax(0,1fr)]",
|
"grid grid-rows-[auto,minmax(0,1fr)]",
|
||||||
])}
|
])}
|
||||||
data-floating-page={show ? "true" : undefined}
|
|
||||||
style={{
|
style={{
|
||||||
height: props.height ? `${props.height}px` : undefined,
|
height: props.height ? `${props.height}px` : undefined,
|
||||||
maxHeight: "70vh",
|
maxHeight: "70vh",
|
||||||
|
@ -1,18 +1,90 @@
|
|||||||
import { ReactNode } from "react";
|
import { a, easings, useSpring } from "@react-spring/web";
|
||||||
|
import { ReactNode, useEffect, useMemo, useRef } from "react";
|
||||||
|
|
||||||
import { OverlayAnchorPosition } from "@/components/overlays/positions/OverlayAnchorPosition";
|
import { OverlayAnchorPosition } from "@/components/overlays/positions/OverlayAnchorPosition";
|
||||||
import { OverlayMobilePosition } from "@/components/overlays/positions/OverlayMobilePosition";
|
import { OverlayMobilePosition } from "@/components/overlays/positions/OverlayMobilePosition";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
|
import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||||
|
import { useOverlayStore } from "@/stores/overlay/store";
|
||||||
|
|
||||||
interface OverlayRouterProps {
|
interface OverlayRouterProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RouterBase(props: { id: string; children: ReactNode }) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const { isMobile } = useIsMobile();
|
||||||
|
|
||||||
|
const routes = useOverlayStore((s) => s.routes);
|
||||||
|
const router = useInternalOverlayRouter(props.id);
|
||||||
|
const routeMeta = useMemo(
|
||||||
|
() => routes[router.currentRoute ?? ""],
|
||||||
|
[routes, router]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [dimensions, api] = useSpring(
|
||||||
|
() => ({
|
||||||
|
from: {
|
||||||
|
height: `${routeMeta?.height ?? 0}px`,
|
||||||
|
width: isMobile ? "100%" : `${routeMeta?.width ?? 0}px`,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
easing: easings.linear,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentState = useRef<null | string>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const data = {
|
||||||
|
height: routeMeta?.height,
|
||||||
|
width: routeMeta?.width,
|
||||||
|
isMobile,
|
||||||
|
};
|
||||||
|
const dataStr = JSON.stringify(data);
|
||||||
|
if (dataStr !== currentState.current) {
|
||||||
|
const oldData = currentState.current
|
||||||
|
? JSON.parse(currentState.current)
|
||||||
|
: null;
|
||||||
|
currentState.current = dataStr;
|
||||||
|
if (data.isMobile) {
|
||||||
|
api.set({
|
||||||
|
width: "100%",
|
||||||
|
});
|
||||||
|
api({
|
||||||
|
height: `${routeMeta?.height ?? 0}px`,
|
||||||
|
});
|
||||||
|
} else if (oldData?.height === undefined && data.height !== undefined) {
|
||||||
|
api.set({
|
||||||
|
height: `${routeMeta?.height ?? 0}px`,
|
||||||
|
width: `${routeMeta?.width ?? 0}px`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api({
|
||||||
|
height: `${routeMeta?.height ?? 0}px`,
|
||||||
|
width: `${routeMeta?.width ?? 0}px`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [routeMeta?.height, routeMeta?.width, isMobile, api]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a.div
|
||||||
|
ref={ref}
|
||||||
|
style={dimensions}
|
||||||
|
className="relative flex items-center justify-center overflow-hidden bg-red-500"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</a.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function OverlayRouter(props: OverlayRouterProps) {
|
export function OverlayRouter(props: OverlayRouterProps) {
|
||||||
const { isMobile } = useIsMobile();
|
const { isMobile } = useIsMobile();
|
||||||
const content = props.children;
|
const content = <RouterBase id={props.id}>{props.children}</RouterBase>;
|
||||||
|
|
||||||
if (isMobile) return <OverlayMobilePosition>{content}</OverlayMobilePosition>;
|
if (isMobile) return <OverlayMobilePosition>{content}</OverlayMobilePosition>;
|
||||||
return <OverlayAnchorPosition id={props.id}>{content}</OverlayAnchorPosition>;
|
return <OverlayAnchorPosition>{content}</OverlayAnchorPosition>;
|
||||||
}
|
}
|
||||||
|
@ -1,78 +1,19 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import { createOverlayAnchorEvent } from "@/components/overlays/OverlayAnchor";
|
|
||||||
|
|
||||||
interface AnchorPositionProps {
|
interface AnchorPositionProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
id: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OverlayAnchorPosition(props: AnchorPositionProps) {
|
export function OverlayAnchorPosition(props: AnchorPositionProps) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
const [left, setLeft] = useState<number>(0);
|
|
||||||
const [top, setTop] = useState<number>(0);
|
|
||||||
const [cardRect, setCardRect] = useState<DOMRect | null>(null);
|
|
||||||
const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null);
|
|
||||||
|
|
||||||
const calculateAndSetCoords = useCallback(
|
|
||||||
(anchor: DOMRect, card: DOMRect) => {
|
|
||||||
const buttonCenter = anchor.left + anchor.width / 2;
|
|
||||||
const bottomReal = window.innerHeight - anchor.bottom;
|
|
||||||
|
|
||||||
setTop(
|
|
||||||
window.innerHeight - bottomReal - anchor.height - card.height - 30
|
|
||||||
);
|
|
||||||
setLeft(
|
|
||||||
Math.min(
|
|
||||||
buttonCenter - card.width / 2,
|
|
||||||
window.innerWidth - card.width - 30
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!anchorRect || !cardRect) return;
|
|
||||||
calculateAndSetCoords(anchorRect, cardRect);
|
|
||||||
}, [anchorRect, calculateAndSetCoords, cardRect]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
function checkBox() {
|
|
||||||
const divRect = ref.current?.getBoundingClientRect();
|
|
||||||
setCardRect(divRect ?? null);
|
|
||||||
}
|
|
||||||
checkBox();
|
|
||||||
const observer = new ResizeObserver(checkBox);
|
|
||||||
observer.observe(ref.current);
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const evtStr = createOverlayAnchorEvent(props.id);
|
|
||||||
if ((window as any)[evtStr]) setAnchorRect((window as any)[evtStr]);
|
|
||||||
function listen(ev: CustomEvent<DOMRect>) {
|
|
||||||
setAnchorRect(ev.detail);
|
|
||||||
}
|
|
||||||
document.addEventListener(evtStr, listen as any);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener(evtStr, listen as any);
|
|
||||||
};
|
|
||||||
}, [props.id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
|
||||||
style={{
|
style={{
|
||||||
transform: `translateX(${left}px) translateY(${top}px)`,
|
transform: `translateX(0px) translateY(0px)`,
|
||||||
}}
|
}}
|
||||||
className={classNames([
|
className={classNames([
|
||||||
"pointer-events-auto z-10 inline-block origin-top-left touch-none overflow-hidden",
|
"pointer-events-auto z-10 inline-block origin-top-left touch-none",
|
||||||
props.className,
|
props.className,
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
|
@ -10,7 +10,7 @@ export function OverlayMobilePosition(props: MobilePositionProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames([
|
className={classNames([
|
||||||
"pointer-events-auto z-10 inline-block origin-top-left touch-none overflow-hidden",
|
"pointer-events-auto z-10 block origin-top-left touch-none overflow-hidden",
|
||||||
props.className,
|
props.className,
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
|
@ -46,7 +46,7 @@ export function ProgressBar() {
|
|||||||
>
|
>
|
||||||
{/* Pre-loaded content bar */}
|
{/* Pre-loaded content bar */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 h-full rounded-full bg-video-progress-preloaded bg-opacity-25 flex justify-end items-center"
|
className="absolute top-0 left-0 h-full rounded-full bg-video-progress-preloaded bg-opacity-50 flex justify-end items-center"
|
||||||
style={{
|
style={{
|
||||||
width: `${(buffered / duration) * 100}%`,
|
width: `${(buffered / duration) * 100}%`,
|
||||||
}}
|
}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
|
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import {
|
import {
|
||||||
@ -24,6 +24,7 @@ export function Volume(props: Props) {
|
|||||||
|
|
||||||
const commitVolume = useCallback(
|
const commitVolume = useCallback(
|
||||||
(percentage) => {
|
(percentage) => {
|
||||||
|
console.log("setting", percentage);
|
||||||
setVolume(percentage);
|
setVolume(percentage);
|
||||||
},
|
},
|
||||||
[setVolume]
|
[setVolume]
|
||||||
|
@ -9,12 +9,12 @@ export function BottomControls(props: {
|
|||||||
<Transition
|
<Transition
|
||||||
animation="fade"
|
animation="fade"
|
||||||
show={props.show}
|
show={props.show}
|
||||||
className="pointer-events-none flex justify-end pt-32 bg-gradient-to-t from-black to-transparent [margin-bottom:env(safe-area-inset-bottom)] transition-opacity duration-200 absolute bottom-0 w-full"
|
className="pointer-events-none flex justify-end pt-32 bg-gradient-to-t from-black to-transparent transition-opacity duration-200 absolute bottom-0 w-full"
|
||||||
/>
|
/>
|
||||||
<Transition
|
<Transition
|
||||||
animation="slide-up"
|
animation="slide-up"
|
||||||
show={props.show}
|
show={props.show}
|
||||||
className="pointer-events-auto px-4 pb-3 absolute bottom-0 w-full"
|
className="pointer-events-auto pl-[calc(2rem+env(safe-area-inset-left))] pr-[calc(2rem+env(safe-area-inset-right))] pb-3 mb-[env(safe-area-inset-bottom)] absolute bottom-0 w-full"
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -79,11 +79,13 @@ export function Container(props: PlayerProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseContainer>
|
<div className="relative">
|
||||||
<VideoContainer />
|
<VideoContainer />
|
||||||
<VideoClickTarget />
|
<BaseContainer>
|
||||||
<HeadUpdater />
|
<VideoClickTarget />
|
||||||
{props.children}
|
<HeadUpdater />
|
||||||
</BaseContainer>
|
{props.children}
|
||||||
|
</BaseContainer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ export function TopControls(props: {
|
|||||||
<Transition
|
<Transition
|
||||||
animation="slide-down"
|
animation="slide-down"
|
||||||
show={props.show}
|
show={props.show}
|
||||||
className="pointer-events-auto px-4 pt-6 absolute top-0 w-full text-white"
|
className="pointer-events-auto pl-[calc(2rem+env(safe-area-inset-left))] pr-[calc(2rem+env(safe-area-inset-right))] pt-6 absolute top-0 w-full text-white"
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -26,6 +26,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
|||||||
function setSource() {
|
function setSource() {
|
||||||
if (!videoElement || !source) return;
|
if (!videoElement || !source) return;
|
||||||
videoElement.src = source.url;
|
videoElement.src = source.url;
|
||||||
|
|
||||||
videoElement.addEventListener("play", () => {
|
videoElement.addEventListener("play", () => {
|
||||||
emit("play", undefined);
|
emit("play", undefined);
|
||||||
emit("loading", false);
|
emit("loading", false);
|
||||||
@ -35,7 +36,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
|||||||
videoElement.addEventListener("canplay", () => emit("loading", false));
|
videoElement.addEventListener("canplay", () => emit("loading", false));
|
||||||
videoElement.addEventListener("waiting", () => emit("loading", true));
|
videoElement.addEventListener("waiting", () => emit("loading", true));
|
||||||
videoElement.addEventListener("volumechange", () =>
|
videoElement.addEventListener("volumechange", () =>
|
||||||
emit("volumechange", videoElement?.volume ?? 0)
|
emit("volumechange", videoElement?.muted ? 0 : videoElement?.volume ?? 0)
|
||||||
);
|
);
|
||||||
videoElement.addEventListener("timeupdate", () =>
|
videoElement.addEventListener("timeupdate", () =>
|
||||||
emit("time", videoElement?.currentTime ?? 0)
|
emit("time", videoElement?.currentTime ?? 0)
|
||||||
@ -118,9 +119,16 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
|||||||
// clamp time between 0 and 1
|
// clamp time between 0 and 1
|
||||||
let volume = Math.min(v, 1);
|
let volume = Math.min(v, 1);
|
||||||
volume = Math.max(0, volume);
|
volume = Math.max(0, volume);
|
||||||
|
videoElement.muted = volume === 0; // Muted attribute is always supported
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
if (await canChangeVolume()) videoElement.volume = volume;
|
const isChangeable = await canChangeVolume();
|
||||||
|
if (isChangeable) {
|
||||||
|
videoElement.volume = volume;
|
||||||
|
} else {
|
||||||
|
// For browsers where it can't be changed
|
||||||
|
emit("volumechange", volume === 0 ? 0 : 1);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggleFullscreen() {
|
toggleFullscreen() {
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
@ -37,7 +37,14 @@ function VideoElement() {
|
|||||||
}
|
}
|
||||||
}, [display, videoEl]);
|
}, [display, videoEl]);
|
||||||
|
|
||||||
return <video className="w-full h-screen bg-black" autoPlay ref={videoEl} />;
|
return (
|
||||||
|
<video
|
||||||
|
className="absolute inset-0 w-full h-screen bg-black"
|
||||||
|
autoPlay
|
||||||
|
playsInline
|
||||||
|
ref={videoEl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoContainer() {
|
export function VideoContainer() {
|
||||||
|
@ -16,8 +16,13 @@ export function useInternalOverlayRouter(id: string) {
|
|||||||
const [route, setRoute] = useQueryParam("r");
|
const [route, setRoute] = useQueryParam("r");
|
||||||
const transition = useOverlayStore((s) => s.transition);
|
const transition = useOverlayStore((s) => s.transition);
|
||||||
const setTransition = useOverlayStore((s) => s.setTransition);
|
const setTransition = useOverlayStore((s) => s.setTransition);
|
||||||
|
const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
|
||||||
const routerActive = !!route && route.startsWith(`/${id}`);
|
const routerActive = !!route && route.startsWith(`/${id}`);
|
||||||
|
|
||||||
|
function makePath(path: string) {
|
||||||
|
return joinPath(splitPath(path, id));
|
||||||
|
}
|
||||||
|
|
||||||
function navigate(path: string) {
|
function navigate(path: string) {
|
||||||
const oldRoute = route;
|
const oldRoute = route;
|
||||||
const newRoute = joinPath(splitPath(path, id));
|
const newRoute = joinPath(splitPath(path, id));
|
||||||
@ -29,17 +34,17 @@ export function useInternalOverlayRouter(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showBackwardsTransition(path: string) {
|
function showBackwardsTransition(path: string) {
|
||||||
if (!transition) return false;
|
if (!transition) return "none";
|
||||||
const current = joinPath(splitPath(path, id));
|
const current = joinPath(splitPath(path, id));
|
||||||
|
|
||||||
if (current === transition.to && transition.from.startsWith(transition.to))
|
if (current === transition.to && transition.from.startsWith(transition.to))
|
||||||
return true;
|
return "yes";
|
||||||
if (
|
if (
|
||||||
current === transition.from &&
|
current === transition.from &&
|
||||||
transition.to.startsWith(transition.from)
|
transition.to.startsWith(transition.from)
|
||||||
)
|
)
|
||||||
return true;
|
return "yes";
|
||||||
return false;
|
return "no";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCurrentPage(path: string) {
|
function isCurrentPage(path: string) {
|
||||||
@ -56,9 +61,22 @@ export function useInternalOverlayRouter(id: string) {
|
|||||||
}, [setRoute, setTransition]);
|
}, [setRoute, setTransition]);
|
||||||
|
|
||||||
const open = useCallback(() => {
|
const open = useCallback(() => {
|
||||||
|
const anchor = document.getElementById(`__overlayRouter::${id}`);
|
||||||
|
if (anchor) {
|
||||||
|
const rect = anchor.getBoundingClientRect();
|
||||||
|
setAnchorPoint({
|
||||||
|
h: rect.height,
|
||||||
|
w: rect.width,
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAnchorPoint(null);
|
||||||
|
}
|
||||||
|
|
||||||
setTransition(null);
|
setTransition(null);
|
||||||
setRoute(`/${id}`);
|
setRoute(`/${id}`);
|
||||||
}, [id, setRoute, setTransition]);
|
}, [id, setRoute, setTransition, setAnchorPoint]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showBackwardsTransition,
|
showBackwardsTransition,
|
||||||
@ -67,6 +85,8 @@ export function useInternalOverlayRouter(id: string) {
|
|||||||
navigate,
|
navigate,
|
||||||
close,
|
close,
|
||||||
open,
|
open,
|
||||||
|
makePath,
|
||||||
|
currentRoute: route,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,19 +19,11 @@ export function PlayerView() {
|
|||||||
const meta = useMemo<PlayerMeta>(
|
const meta = useMemo<PlayerMeta>(
|
||||||
() => ({
|
() => ({
|
||||||
type: "show",
|
type: "show",
|
||||||
title: "House",
|
title: "Normal People",
|
||||||
tmdbId: "1408",
|
releaseYear: 2020,
|
||||||
releaseYear: 2004,
|
tmdbId: "89905",
|
||||||
episode: {
|
episode: { number: 12, tmdbId: "2207576", title: "Episode 12" },
|
||||||
number: 1,
|
season: { number: 1, tmdbId: "125160", title: "Season 1" },
|
||||||
title: "Pilot",
|
|
||||||
tmdbId: "63738",
|
|
||||||
},
|
|
||||||
season: {
|
|
||||||
number: 1,
|
|
||||||
tmdbId: "3674",
|
|
||||||
title: "Season 1",
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -48,10 +40,20 @@ export function PlayerView() {
|
|||||||
media={scrapeMedia}
|
media={scrapeMedia}
|
||||||
onGetStream={(out) => {
|
onGetStream={(out) => {
|
||||||
if (out?.stream.type !== "file") return;
|
if (out?.stream.type !== "file") return;
|
||||||
const qualities = Object.keys(
|
console.log(out.stream.qualities);
|
||||||
out.stream.qualities
|
const qualities = Object.keys(out.stream.qualities).sort(
|
||||||
|
(a, b) => Number(b) - Number(a)
|
||||||
) as (keyof typeof out.stream.qualities)[];
|
) as (keyof typeof out.stream.qualities)[];
|
||||||
const file = out.stream.qualities[qualities[0]];
|
|
||||||
|
let file;
|
||||||
|
for (const quality of qualities) {
|
||||||
|
if (out.stream.qualities[quality]?.url) {
|
||||||
|
console.log(quality);
|
||||||
|
file = out.stream.qualities[quality];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
playMedia({
|
playMedia({
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
|
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
|
||||||
import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay";
|
import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay";
|
||||||
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
||||||
@ -20,7 +22,7 @@ export default function TestView() {
|
|||||||
Open
|
Open
|
||||||
</button>
|
</button>
|
||||||
<OverlayAnchor id={router.id}>
|
<OverlayAnchor id={router.id}>
|
||||||
<div className="h-20 w-20 mt-64 bg-white" />
|
<div className="h-20 w-20 hover:w-24 mt-[50rem] bg-white" />
|
||||||
</OverlayAnchor>
|
</OverlayAnchor>
|
||||||
<Overlay id={router.id}>
|
<Overlay id={router.id}>
|
||||||
<OverlayRouter id={router.id}>
|
<OverlayRouter id={router.id}>
|
||||||
@ -45,7 +47,7 @@ export default function TestView() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</OverlayPage>
|
</OverlayPage>
|
||||||
<OverlayPage id={router.id} path="/one">
|
<OverlayPage id={router.id} path="/one" width={300} height={300}>
|
||||||
<div className="bg-blue-900 p-4">
|
<div className="bg-blue-900 p-4">
|
||||||
<p>ONE</p>
|
<p>ONE</p>
|
||||||
<button
|
<button
|
||||||
@ -58,7 +60,7 @@ export default function TestView() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</OverlayPage>
|
</OverlayPage>
|
||||||
<OverlayPage id={router.id} path="/two">
|
<OverlayPage id={router.id} path="/two" width={200} height={200}>
|
||||||
<div className="bg-blue-900 p-4">
|
<div className="bg-blue-900 p-4">
|
||||||
<p>TWO</p>
|
<p>TWO</p>
|
||||||
<button
|
<button
|
||||||
|
@ -65,6 +65,16 @@ body[data-no-select] {
|
|||||||
height: 60vh;
|
height: 60vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-screen {
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-h-screen {
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
/*generated with Input range slider CSS style generator (version 20211225)
|
/*generated with Input range slider CSS style generator (version 20211225)
|
||||||
https://toughengineer.github.io/demo/slider-styler*/
|
https://toughengineer.github.io/demo/slider-styler*/
|
||||||
:root {
|
:root {
|
||||||
|
@ -6,18 +6,47 @@ export interface OverlayTransition {
|
|||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OverlayRoute {
|
||||||
|
id: string;
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActiveAnchorPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface OverlayStore {
|
interface OverlayStore {
|
||||||
transition: null | OverlayTransition;
|
transition: null | OverlayTransition;
|
||||||
|
routes: Record<string, OverlayRoute>;
|
||||||
|
anchorPoint: ActiveAnchorPoint | null;
|
||||||
setTransition(newTrans: OverlayTransition | null): void;
|
setTransition(newTrans: OverlayTransition | null): void;
|
||||||
|
registerRoute(route: OverlayRoute): void;
|
||||||
|
setAnchorPoint(point: ActiveAnchorPoint | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useOverlayStore = create(
|
export const useOverlayStore = create(
|
||||||
immer<OverlayStore>((set) => ({
|
immer<OverlayStore>((set) => ({
|
||||||
transition: null,
|
transition: null,
|
||||||
|
routes: {},
|
||||||
|
anchorPoint: null,
|
||||||
setTransition(newTrans) {
|
setTransition(newTrans) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.transition = newTrans;
|
s.transition = newTrans;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
registerRoute(route) {
|
||||||
|
set((s) => {
|
||||||
|
s.routes[route.id] = route;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setAnchorPoint(point) {
|
||||||
|
set((s) => {
|
||||||
|
s.anchorPoint = point;
|
||||||
|
});
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ export const createPlayingSlice: MakeSlice<PlayingSlice> = (set) => ({
|
|||||||
isDragSeeking: false,
|
isDragSeeking: false,
|
||||||
isFirstLoading: true,
|
isFirstLoading: true,
|
||||||
hasPlayedOnce: false,
|
hasPlayedOnce: false,
|
||||||
volume: 0,
|
volume: 1,
|
||||||
playbackSpeed: 1,
|
playbackSpeed: 1,
|
||||||
},
|
},
|
||||||
play() {
|
play() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user