diff --git a/src/components/overlays/OverlayAnchor.tsx b/src/components/overlays/OverlayAnchor.tsx index 8bd36794..4939ab1d 100644 --- a/src/components/overlays/OverlayAnchor.tsx +++ b/src/components/overlays/OverlayAnchor.tsx @@ -1,10 +1,20 @@ +import classNames from "classnames"; import { ReactNode } from "react"; interface Props { id: string; children?: ReactNode; + className?: string; } export function OverlayAnchor(props: Props) { - return
{props.children}
; + return ( +
+
+ {props.children} +
+ ); } diff --git a/src/components/overlays/positions/OverlayAnchorPosition.tsx b/src/components/overlays/positions/OverlayAnchorPosition.tsx index 58fcaf27..028f721d 100644 --- a/src/components/overlays/positions/OverlayAnchorPosition.tsx +++ b/src/components/overlays/positions/OverlayAnchorPosition.tsx @@ -1,16 +1,67 @@ import classNames from "classnames"; -import { ReactNode } from "react"; +import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; + +import { useOverlayStore } from "@/stores/overlay/store"; interface AnchorPositionProps { children?: ReactNode; className?: string; } +function useCalculatePositions() { + const anchorPoint = useOverlayStore((s) => s.anchorPoint); + const ref = useRef(null); + const [left, setLeft] = useState(0); + const [top, setTop] = useState(0); + const [cardRect, setCardRect] = useState(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) { + const [ref, left, top] = useCalculatePositions(); + return (
s.display); const { isPaused } = usePlayerStore((s) => s.mediaPlaying); @@ -13,6 +13,7 @@ export function Pause() { return ( diff --git a/src/components/player/atoms/Settings.tsx b/src/components/player/atoms/Settings.tsx new file mode 100644 index 00000000..7f372767 --- /dev/null +++ b/src/components/player/atoms/Settings.tsx @@ -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 ( + + + +

This is settings menu, welcome!

+
+
+
+ ); +} + +export function Settings() { + const router = useOverlayRouter("settings"); + const setHasOpenOverlay = usePlayerStore((s) => s.setHasOpenOverlay); + + useEffect(() => { + setHasOpenOverlay(router.isRouterActive); + }, [setHasOpenOverlay, router.isRouterActive]); + + return ( + + router.open()} icon={Icons.GEAR} /> + + + ); +} diff --git a/src/components/player/atoms/Skips.tsx b/src/components/player/atoms/Skips.tsx index 82261a5b..455ee2fc 100644 --- a/src/components/player/atoms/Skips.tsx +++ b/src/components/player/atoms/Skips.tsx @@ -4,7 +4,7 @@ import { Icons } from "@/components/Icon"; import { VideoPlayerButton } from "@/components/player/internals/Button"; import { usePlayerStore } from "@/stores/player/store"; -export function SkipForward() { +export function SkipForward(props: { iconSizeClass?: string }) { const display = usePlayerStore((s) => s.display); const time = usePlayerStore((s) => s.progress.time); @@ -12,10 +12,16 @@ export function SkipForward() { display?.setTime(time + 10); }, [display, time]); - return ; + return ( + + ); } -export function SkipBackward() { +export function SkipBackward(props: { iconSizeClass?: string }) { const display = usePlayerStore((s) => s.display); const time = usePlayerStore((s) => s.progress.time); @@ -23,5 +29,11 @@ export function SkipBackward() { display?.setTime(time - 10); }, [display, time]); - return ; + return ( + + ); } diff --git a/src/components/player/atoms/Title.tsx b/src/components/player/atoms/Title.tsx index 1fcf79b0..54453c18 100644 --- a/src/components/player/atoms/Title.tsx +++ b/src/components/player/atoms/Title.tsx @@ -2,5 +2,5 @@ import { usePlayerStore } from "@/stores/player/store"; export function Title() { const title = usePlayerStore((s) => s.meta?.title); - return

{title || "Beep beep, Richie!"}

; + return

{title}

; } diff --git a/src/components/player/atoms/Volume.tsx b/src/components/player/atoms/Volume.tsx index 6e5ddc1a..7a303099 100644 --- a/src/components/player/atoms/Volume.tsx +++ b/src/components/player/atoms/Volume.tsx @@ -24,7 +24,6 @@ export function Volume(props: Props) { const commitVolume = useCallback( (percentage) => { - console.log("setting", percentage); setVolume(percentage); }, [setVolume] diff --git a/src/components/player/atoms/index.ts b/src/components/player/atoms/index.ts index 3487d9de..e54adc08 100644 --- a/src/components/player/atoms/index.ts +++ b/src/components/player/atoms/index.ts @@ -8,3 +8,4 @@ export * from "./AutoPlayStart"; export * from "./Volume"; export * from "./Title"; export * from "./EpisodeTitle"; +export * from "./Settings"; diff --git a/src/components/player/base/CenterMobileControls.tsx b/src/components/player/base/CenterMobileControls.tsx new file mode 100644 index 00000000..3c4d4bbc --- /dev/null +++ b/src/components/player/base/CenterMobileControls.tsx @@ -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 ( + +
*]:pointer-events-auto", + props.className, + ])} + > + {props.children} +
+
+ ); +} diff --git a/src/components/player/base/LeftSideControls.tsx b/src/components/player/base/LeftSideControls.tsx index 38401f8e..acbe4754 100644 --- a/src/components/player/base/LeftSideControls.tsx +++ b/src/components/player/base/LeftSideControls.tsx @@ -1,8 +1,12 @@ +import classNames from "classnames"; import { useCallback, useEffect } from "react"; 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( (s) => s.setHoveringLeftControls ); @@ -18,7 +22,10 @@ export function LeftSideControls(props: { children: React.ReactNode }) { }, [setHoveringLeftControls]); return ( -
+
{props.children}
); diff --git a/src/components/player/hooks/useShouldShowControls.tsx b/src/components/player/hooks/useShouldShowControls.tsx index c0f3af9f..ed74b742 100644 --- a/src/components/player/hooks/useShouldShowControls.tsx +++ b/src/components/player/hooks/useShouldShowControls.tsx @@ -1,9 +1,20 @@ import { PlayerHoverState } from "@/stores/player/slices/interface"; import { usePlayerStore } from "@/stores/player/store"; -export function useShouldShowControls() { - const { hovering } = usePlayerStore((s) => s.interface); - const { isPaused } = usePlayerStore((s) => s.mediaPlaying); +export function useShouldShowControls(opts?: { touchOnly: boolean }) { + const hovering = usePlayerStore((s) => s.interface.hovering); + 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; } diff --git a/src/components/player/hooks/useVolume.ts b/src/components/player/hooks/useVolume.ts index 35242a43..06a94515 100644 --- a/src/components/player/hooks/useVolume.ts +++ b/src/components/player/hooks/useVolume.ts @@ -1,25 +1,26 @@ -import { - getStoredVolume, - setStoredVolume, -} from "@/_oldvideo/components/hooks/volumeStore"; +import { setStoredVolume } from "@/_oldvideo/components/hooks/volumeStore"; import { usePlayerStore } from "@/stores/player/store"; // TODO use new stored volume export function useVolume() { 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 toggleVolume = (_isKeyboardEvent = false) => { // TODO use keyboard event + let newVolume = 0; + if (volume > 0) { - setStoredVolume(volume); - display?.setVolume(0); - } else { - const storedVolume = getStoredVolume(); - if (storedVolume > 0) display?.setVolume(storedVolume); - else display?.setVolume(1); - } + newVolume = 0; + setLastVolume(volume); + } else if (lastVolume > 0) newVolume = lastVolume; + else newVolume = 1; + + display?.setVolume(newVolume); + setStoredVolume(newVolume); }; return { @@ -28,6 +29,7 @@ export function useVolume() { }, setVolume(vol: number) { setStoredVolume(vol); + setLastVolume(vol); display?.setVolume(vol); }, }; diff --git a/src/hooks/useOverlayRouter.ts b/src/hooks/useOverlayRouter.ts index fbfc3135..046156de 100644 --- a/src/hooks/useOverlayRouter.ts +++ b/src/hooks/useOverlayRouter.ts @@ -94,6 +94,7 @@ export function useOverlayRouter(id: string) { const router = useInternalOverlayRouter(id); return { id, + isRouterActive: router.isOverlayActive(), open: router.open, close: router.close, navigate: router.navigate, diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx index 688458f8..eb7bdaa6 100644 --- a/src/pages/PlayerView.tsx +++ b/src/pages/PlayerView.tsx @@ -7,15 +7,21 @@ import { AutoPlayStart } from "@/components/player/atoms"; import { usePlayer } from "@/components/player/hooks/usePlayer"; import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls"; import { ScrapingPart } from "@/pages/parts/player/ScrapingPart"; +import { PlayerHoverState } from "@/stores/player/slices/interface"; import { PlayerMeta, metaToScrapeMedia, playerStatus, } from "@/stores/player/slices/source"; +import { usePlayerStore } from "@/stores/player/store"; export function PlayerView() { const { status, setScrapeStatus, playMedia, setMeta } = usePlayer(); + const { lastHoveringState } = usePlayerStore((s) => s.interface); + const desktopControlsVisible = useShouldShowControls(); + const touchControlsVisible = useShouldShowControls({ touchOnly: true }); + const meta = useMemo( () => ({ type: "show", @@ -71,6 +77,15 @@ export function PlayerView() { + + + + + +
@@ -91,14 +106,19 @@ export function PlayerView() {
- + -
+ + {/* Do mobile controls here :) */} + + +
+
diff --git a/src/pages/developer/TestView.tsx b/src/pages/developer/TestView.tsx index 81d0c007..6c175eb2 100644 --- a/src/pages/developer/TestView.tsx +++ b/src/pages/developer/TestView.tsx @@ -1,5 +1,3 @@ -import { useEffect, useRef } from "react"; - import { OverlayAnchor } from "@/components/overlays/OverlayAnchor"; import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay"; import { OverlayPage } from "@/components/overlays/OverlayPage"; @@ -21,9 +19,10 @@ export default function TestView() { > Open - -
- + diff --git a/src/stores/player/slices/interface.ts b/src/stores/player/slices/interface.ts index 5435fb63..a08b2313 100644 --- a/src/stores/player/slices/interface.ts +++ b/src/stores/player/slices/interface.ts @@ -15,7 +15,10 @@ export interface InterfaceSlice { interface: { isFullscreen: boolean; isSeeking: boolean; + lastVolume: number; + hasOpenOverlay: boolean; hovering: PlayerHoverState; + lastHoveringState: PlayerHoverState; 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" @@ -27,19 +30,34 @@ export interface InterfaceSlice { setSeeking(seeking: boolean): void; setTimeFormat(format: VideoPlayerTimeFormat): void; setHoveringLeftControls(state: boolean): void; + setHasOpenOverlay(state: boolean): void; + setLastVolume(state: number): void; } export const createInterfaceSlice: MakeSlice = (set, get) => ({ interface: { + hasOpenOverlay: false, isFullscreen: false, isSeeking: false, + lastVolume: 0, leftControlHovering: false, hovering: PlayerHoverState.NOT_HOVERING, + lastHoveringState: PlayerHoverState.NOT_HOVERING, volumeChangedWithKeybind: false, volumeChangedWithKeybindDebounce: null, timeFormat: VideoPlayerTimeFormat.REGULAR, }, + setLastVolume(state) { + set((s) => { + s.interface.lastVolume = state; + }); + }, + setHasOpenOverlay(state) { + set((s) => { + s.interface.hasOpenOverlay = state; + }); + }, setTimeFormat(format) { set((s) => { s.interface.timeFormat = format; @@ -47,6 +65,8 @@ export const createInterfaceSlice: MakeSlice = (set, get) => ({ }, updateInterfaceHovering(newState: PlayerHoverState) { set((s) => { + if (newState !== PlayerHoverState.NOT_HOVERING) + s.interface.lastHoveringState = newState; s.interface.hovering = newState; }); },