volume storage fixed, title cleanup, settings cog start, touch controls start

Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
mrjvs 2023-10-11 23:04:41 +02:00
parent 7b3452c535
commit f3084d37a8
17 changed files with 233 additions and 34 deletions

View File

@ -1,10 +1,20 @@
import classNames from "classnames";
import { ReactNode } from "react"; import { ReactNode } from "react";
interface Props { interface Props {
id: string; id: string;
children?: ReactNode; children?: ReactNode;
className?: string;
} }
export function OverlayAnchor(props: Props) { export function OverlayAnchor(props: Props) {
return <div id={`__overlayRouter::${props.id}`}>{props.children}</div>; return (
<div className={classNames("relative", props.className)}>
<div
id={`__overlayRouter::${props.id}`}
className="absolute inset-0 -z-10"
/>
{props.children}
</div>
);
} }

View File

@ -1,16 +1,67 @@
import classNames from "classnames"; import classNames from "classnames";
import { ReactNode } from "react"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { useOverlayStore } from "@/stores/overlay/store";
interface AnchorPositionProps { interface AnchorPositionProps {
children?: ReactNode; children?: ReactNode;
className?: string; className?: string;
} }
function useCalculatePositions() {
const anchorPoint = useOverlayStore((s) => s.anchorPoint);
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 calculateAndSetCoords = useCallback(
(anchor: typeof anchorPoint, card: DOMRect) => {
if (!anchor) return;
const buttonCenter = anchor.x + anchor.w / 2;
const bottomReal = window.innerHeight - (anchor.y + anchor.h);
setTop(window.innerHeight - bottomReal - anchor.h - card.height - 30);
setLeft(
Math.min(
buttonCenter - card.width / 2,
window.innerWidth - card.width - 30
)
);
},
[]
);
useEffect(() => {
if (!anchorPoint || !cardRect) return;
calculateAndSetCoords(anchorPoint, cardRect);
}, [anchorPoint, 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();
};
}, []);
return [ref, left, top] as const;
}
export function OverlayAnchorPosition(props: AnchorPositionProps) { export function OverlayAnchorPosition(props: AnchorPositionProps) {
const [ref, left, top] = useCalculatePositions();
return ( return (
<div <div
ref={ref}
style={{ style={{
transform: `translateX(0px) translateY(0px)`, transform: `translateX(${left}px) translateY(${top}px)`,
}} }}
className={classNames([ className={classNames([
"pointer-events-auto z-10 inline-block origin-top-left touch-none", "pointer-events-auto z-10 inline-block origin-top-left touch-none",

View File

@ -6,4 +6,5 @@ export * from "./base/BottomControls";
export * from "./base/BlackOverlay"; export * from "./base/BlackOverlay";
export * from "./base/BackLink"; export * from "./base/BackLink";
export * from "./base/LeftSideControls"; export * from "./base/LeftSideControls";
export * from "./base/CenterMobileControls";
export * from "./internals/BookmarkButton"; export * from "./internals/BookmarkButton";

View File

@ -2,7 +2,7 @@ import { Icons } from "@/components/Icon";
import { VideoPlayerButton } from "@/components/player/internals/Button"; import { VideoPlayerButton } from "@/components/player/internals/Button";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
export function Pause() { export function Pause(props: { iconSizeClass?: string }) {
const display = usePlayerStore((s) => s.display); const display = usePlayerStore((s) => s.display);
const { isPaused } = usePlayerStore((s) => s.mediaPlaying); const { isPaused } = usePlayerStore((s) => s.mediaPlaying);
@ -13,6 +13,7 @@ export function Pause() {
return ( return (
<VideoPlayerButton <VideoPlayerButton
iconSizeClass={props.iconSizeClass}
onClick={toggle} onClick={toggle}
icon={isPaused ? Icons.PLAY : Icons.PAUSE} icon={isPaused ? Icons.PLAY : Icons.PAUSE}
/> />

View File

@ -0,0 +1,38 @@
import { useEffect } from "react";
import { Icons } from "@/components/Icon";
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
import { Overlay } from "@/components/overlays/OverlayDisplay";
import { OverlayPage } from "@/components/overlays/OverlayPage";
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
import { VideoPlayerButton } from "@/components/player/internals/Button";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
function SettingsOverlay({ id }: { id: string }) {
return (
<Overlay id={id}>
<OverlayRouter id={id}>
<OverlayPage id={id} path="/" width={400} height={400}>
<p>This is settings menu, welcome!</p>
</OverlayPage>
</OverlayRouter>
</Overlay>
);
}
export function Settings() {
const router = useOverlayRouter("settings");
const setHasOpenOverlay = usePlayerStore((s) => s.setHasOpenOverlay);
useEffect(() => {
setHasOpenOverlay(router.isRouterActive);
}, [setHasOpenOverlay, router.isRouterActive]);
return (
<OverlayAnchor id={router.id}>
<VideoPlayerButton onClick={() => router.open()} icon={Icons.GEAR} />
<SettingsOverlay id={router.id} />
</OverlayAnchor>
);
}

View File

@ -4,7 +4,7 @@ import { Icons } from "@/components/Icon";
import { VideoPlayerButton } from "@/components/player/internals/Button"; import { VideoPlayerButton } from "@/components/player/internals/Button";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
export function SkipForward() { export function SkipForward(props: { iconSizeClass?: string }) {
const display = usePlayerStore((s) => s.display); const display = usePlayerStore((s) => s.display);
const time = usePlayerStore((s) => s.progress.time); const time = usePlayerStore((s) => s.progress.time);
@ -12,10 +12,16 @@ export function SkipForward() {
display?.setTime(time + 10); display?.setTime(time + 10);
}, [display, time]); }, [display, time]);
return <VideoPlayerButton onClick={commit} icon={Icons.SKIP_FORWARD} />; return (
<VideoPlayerButton
iconSizeClass={props.iconSizeClass || ""}
onClick={commit}
icon={Icons.SKIP_FORWARD}
/>
);
} }
export function SkipBackward() { export function SkipBackward(props: { iconSizeClass?: string }) {
const display = usePlayerStore((s) => s.display); const display = usePlayerStore((s) => s.display);
const time = usePlayerStore((s) => s.progress.time); const time = usePlayerStore((s) => s.progress.time);
@ -23,5 +29,11 @@ export function SkipBackward() {
display?.setTime(time - 10); display?.setTime(time - 10);
}, [display, time]); }, [display, time]);
return <VideoPlayerButton onClick={commit} icon={Icons.SKIP_BACKWARD} />; return (
<VideoPlayerButton
iconSizeClass={props.iconSizeClass || ""}
onClick={commit}
icon={Icons.SKIP_BACKWARD}
/>
);
} }

View File

@ -2,5 +2,5 @@ import { usePlayerStore } from "@/stores/player/store";
export function Title() { export function Title() {
const title = usePlayerStore((s) => s.meta?.title); const title = usePlayerStore((s) => s.meta?.title);
return <p>{title || "Beep beep, Richie!"}</p>; return <p>{title}</p>;
} }

View File

@ -24,7 +24,6 @@ export function Volume(props: Props) {
const commitVolume = useCallback( const commitVolume = useCallback(
(percentage) => { (percentage) => {
console.log("setting", percentage);
setVolume(percentage); setVolume(percentage);
}, },
[setVolume] [setVolume]

View File

@ -8,3 +8,4 @@ export * from "./AutoPlayStart";
export * from "./Volume"; export * from "./Volume";
export * from "./Title"; export * from "./Title";
export * from "./EpisodeTitle"; export * from "./EpisodeTitle";
export * from "./Settings";

View File

@ -0,0 +1,26 @@
import classNames from "classnames";
import { Transition } from "@/components/Transition";
export function CenterMobileControls(props: {
children: React.ReactNode;
show: boolean;
className?: string;
}) {
return (
<Transition
animation="fade"
show={props.show}
className="pointer-events-none"
>
<div
className={classNames([
"absolute inset-0 flex space-x-6 items-center justify-center pointer-events-none [&>*]:pointer-events-auto",
props.className,
])}
>
{props.children}
</div>
</Transition>
);
}

View File

@ -1,8 +1,12 @@
import classNames from "classnames";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
export function LeftSideControls(props: { children: React.ReactNode }) { export function LeftSideControls(props: {
children: React.ReactNode;
className?: string;
}) {
const setHoveringLeftControls = usePlayerStore( const setHoveringLeftControls = usePlayerStore(
(s) => s.setHoveringLeftControls (s) => s.setHoveringLeftControls
); );
@ -18,7 +22,10 @@ export function LeftSideControls(props: { children: React.ReactNode }) {
}, [setHoveringLeftControls]); }, [setHoveringLeftControls]);
return ( return (
<div className="flex space-x-3 items-center" onMouseLeave={mouseLeave}> <div
className={classNames(["flex space-x-3 items-center", props.className])}
onMouseLeave={mouseLeave}
>
{props.children} {props.children}
</div> </div>
); );

View File

@ -1,9 +1,20 @@
import { PlayerHoverState } from "@/stores/player/slices/interface"; import { PlayerHoverState } from "@/stores/player/slices/interface";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
export function useShouldShowControls() { export function useShouldShowControls(opts?: { touchOnly: boolean }) {
const { hovering } = usePlayerStore((s) => s.interface); const hovering = usePlayerStore((s) => s.interface.hovering);
const { isPaused } = usePlayerStore((s) => s.mediaPlaying); const lastHoveringState = usePlayerStore(
(s) => s.interface.lastHoveringState
);
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
const hasOpenOverlay = usePlayerStore((s) => s.interface.hasOpenOverlay);
return hovering !== PlayerHoverState.NOT_HOVERING || isPaused; const showTouchControls =
lastHoveringState === PlayerHoverState.MOBILE_TAPPED;
const notNotHovering = hovering !== PlayerHoverState.NOT_HOVERING;
if (opts?.touchOnly)
return (showTouchControls && notNotHovering) || isPaused || hasOpenOverlay;
return notNotHovering || isPaused || hasOpenOverlay;
} }

View File

@ -1,25 +1,26 @@
import { import { setStoredVolume } from "@/_oldvideo/components/hooks/volumeStore";
getStoredVolume,
setStoredVolume,
} from "@/_oldvideo/components/hooks/volumeStore";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
// TODO use new stored volume // TODO use new stored volume
export function useVolume() { export function useVolume() {
const volume = usePlayerStore((s) => s.mediaPlaying.volume); const volume = usePlayerStore((s) => s.mediaPlaying.volume);
const lastVolume = usePlayerStore((s) => s.interface.lastVolume);
const setLastVolume = usePlayerStore((s) => s.setLastVolume);
const display = usePlayerStore((s) => s.display); const display = usePlayerStore((s) => s.display);
const toggleVolume = (_isKeyboardEvent = false) => { const toggleVolume = (_isKeyboardEvent = false) => {
// TODO use keyboard event // TODO use keyboard event
let newVolume = 0;
if (volume > 0) { if (volume > 0) {
setStoredVolume(volume); newVolume = 0;
display?.setVolume(0); setLastVolume(volume);
} else { } else if (lastVolume > 0) newVolume = lastVolume;
const storedVolume = getStoredVolume(); else newVolume = 1;
if (storedVolume > 0) display?.setVolume(storedVolume);
else display?.setVolume(1); display?.setVolume(newVolume);
} setStoredVolume(newVolume);
}; };
return { return {
@ -28,6 +29,7 @@ export function useVolume() {
}, },
setVolume(vol: number) { setVolume(vol: number) {
setStoredVolume(vol); setStoredVolume(vol);
setLastVolume(vol);
display?.setVolume(vol); display?.setVolume(vol);
}, },
}; };

View File

@ -94,6 +94,7 @@ export function useOverlayRouter(id: string) {
const router = useInternalOverlayRouter(id); const router = useInternalOverlayRouter(id);
return { return {
id, id,
isRouterActive: router.isOverlayActive(),
open: router.open, open: router.open,
close: router.close, close: router.close,
navigate: router.navigate, navigate: router.navigate,

View File

@ -7,15 +7,21 @@ import { AutoPlayStart } from "@/components/player/atoms";
import { usePlayer } from "@/components/player/hooks/usePlayer"; import { usePlayer } from "@/components/player/hooks/usePlayer";
import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls"; import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls";
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart"; import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
import { PlayerHoverState } from "@/stores/player/slices/interface";
import { import {
PlayerMeta, PlayerMeta,
metaToScrapeMedia, metaToScrapeMedia,
playerStatus, playerStatus,
} from "@/stores/player/slices/source"; } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store";
export function PlayerView() { export function PlayerView() {
const { status, setScrapeStatus, playMedia, setMeta } = usePlayer(); const { status, setScrapeStatus, playMedia, setMeta } = usePlayer();
const { lastHoveringState } = usePlayerStore((s) => s.interface);
const desktopControlsVisible = useShouldShowControls(); const desktopControlsVisible = useShouldShowControls();
const touchControlsVisible = useShouldShowControls({ touchOnly: true });
const meta = useMemo<PlayerMeta>( const meta = useMemo<PlayerMeta>(
() => ({ () => ({
type: "show", type: "show",
@ -71,6 +77,15 @@ export function PlayerView() {
<AutoPlayStart /> <AutoPlayStart />
</Player.CenterControls> </Player.CenterControls>
<Player.CenterMobileControls
className="text-white"
show={touchControlsVisible}
>
<Player.SkipBackward iconSizeClass="text-3xl" />
<Player.Pause iconSizeClass="text-5xl" />
<Player.SkipForward iconSizeClass="text-3xl" />
</Player.CenterMobileControls>
<Player.TopControls show={desktopControlsVisible}> <Player.TopControls show={desktopControlsVisible}>
<div className="grid grid-cols-[1fr,auto] xl:grid-cols-3 items-center"> <div className="grid grid-cols-[1fr,auto] xl:grid-cols-3 items-center">
<div className="flex space-x-3 items-center"> <div className="flex space-x-3 items-center">
@ -91,14 +106,19 @@ export function PlayerView() {
<Player.BottomControls show={desktopControlsVisible}> <Player.BottomControls show={desktopControlsVisible}>
<Player.ProgressBar /> <Player.ProgressBar />
<div className="flex justify-between"> <div className="flex justify-between">
<Player.LeftSideControls> <Player.LeftSideControls className="hidden lg:flex">
<Player.Pause /> <Player.Pause />
<Player.SkipBackward /> <Player.SkipBackward />
<Player.SkipForward /> <Player.SkipForward />
<Player.Volume /> <Player.Volume />
<Player.Time /> <Player.Time />
</Player.LeftSideControls> </Player.LeftSideControls>
<div> <Player.LeftSideControls className="flex lg:hidden">
{/* Do mobile controls here :) */}
<Player.Time />
</Player.LeftSideControls>
<div className="flex items-center">
<Player.Settings />
<Player.Fullscreen /> <Player.Fullscreen />
</div> </div>
</div> </div>

View File

@ -1,5 +1,3 @@
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";
@ -21,9 +19,10 @@ export default function TestView() {
> >
Open Open
</button> </button>
<OverlayAnchor id={router.id}> <OverlayAnchor
<div className="h-20 w-20 hover:w-24 mt-[50rem] bg-white" /> id={router.id}
</OverlayAnchor> className="h-20 w-20 hover:w-24 mt-[50rem] bg-white"
/>
<Overlay id={router.id}> <Overlay id={router.id}>
<OverlayRouter id={router.id}> <OverlayRouter id={router.id}>
<OverlayPage id={router.id} path="/" width={400} height={400}> <OverlayPage id={router.id} path="/" width={400} height={400}>

View File

@ -15,7 +15,10 @@ export interface InterfaceSlice {
interface: { interface: {
isFullscreen: boolean; isFullscreen: boolean;
isSeeking: boolean; isSeeking: boolean;
lastVolume: number;
hasOpenOverlay: boolean;
hovering: PlayerHoverState; hovering: PlayerHoverState;
lastHoveringState: PlayerHoverState;
volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently? volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently?
volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig" volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig"
@ -27,19 +30,34 @@ export interface InterfaceSlice {
setSeeking(seeking: boolean): void; setSeeking(seeking: boolean): void;
setTimeFormat(format: VideoPlayerTimeFormat): void; setTimeFormat(format: VideoPlayerTimeFormat): void;
setHoveringLeftControls(state: boolean): void; setHoveringLeftControls(state: boolean): void;
setHasOpenOverlay(state: boolean): void;
setLastVolume(state: number): void;
} }
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({ export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
interface: { interface: {
hasOpenOverlay: false,
isFullscreen: false, isFullscreen: false,
isSeeking: false, isSeeking: false,
lastVolume: 0,
leftControlHovering: false, leftControlHovering: false,
hovering: PlayerHoverState.NOT_HOVERING, hovering: PlayerHoverState.NOT_HOVERING,
lastHoveringState: PlayerHoverState.NOT_HOVERING,
volumeChangedWithKeybind: false, volumeChangedWithKeybind: false,
volumeChangedWithKeybindDebounce: null, volumeChangedWithKeybindDebounce: null,
timeFormat: VideoPlayerTimeFormat.REGULAR, timeFormat: VideoPlayerTimeFormat.REGULAR,
}, },
setLastVolume(state) {
set((s) => {
s.interface.lastVolume = state;
});
},
setHasOpenOverlay(state) {
set((s) => {
s.interface.hasOpenOverlay = state;
});
},
setTimeFormat(format) { setTimeFormat(format) {
set((s) => { set((s) => {
s.interface.timeFormat = format; s.interface.timeFormat = format;
@ -47,6 +65,8 @@ export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
}, },
updateInterfaceHovering(newState: PlayerHoverState) { updateInterfaceHovering(newState: PlayerHoverState) {
set((s) => { set((s) => {
if (newState !== PlayerHoverState.NOT_HOVERING)
s.interface.lastHoveringState = newState;
s.interface.hovering = newState; s.interface.hovering = newState;
}); });
}, },