From faff7ee7e0d6282509dbb0243fb3cf70e7ddd03a Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 6 Oct 2023 00:01:35 +0200 Subject: [PATCH] scrape styling and attempt at centering Co-authored-by: William Oldham --- src/components/Icon.tsx | 3 +- .../player/internals/ScrapeCard.tsx | 65 +++++++++ .../player/internals/StatusCircle.tsx | 70 +++++++--- src/pages/PlayerView.tsx | 11 +- src/pages/parts/player/ScrapingPart.tsx | 123 +++++++++++------- 5 files changed, 202 insertions(+), 70 deletions(-) create mode 100644 src/components/player/internals/ScrapeCard.tsx diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index 21f6ac24..f43f14cb 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import { memo, useEffect, useRef } from "react"; export enum Icons { @@ -113,7 +114,7 @@ export const Icon = memo((props: IconProps) => { return ( ); }); diff --git a/src/components/player/internals/ScrapeCard.tsx b/src/components/player/internals/ScrapeCard.tsx new file mode 100644 index 00000000..91d2d997 --- /dev/null +++ b/src/components/player/internals/ScrapeCard.tsx @@ -0,0 +1,65 @@ +import classNames from "classnames"; +import { ReactNode } from "react"; + +import { StatusCircle } from "@/components/player/internals/StatusCircle"; +import { Transition } from "@/components/Transition"; + +export interface ScrapeItemProps { + status: "failure" | "pending" | "notfound" | "success" | "waiting"; + name: string; + id?: string; + percentage?: number; + children?: ReactNode; +} + +export interface ScrapeCardProps extends ScrapeItemProps { + hasChildren?: boolean; +} + +const statusTextMap: Partial> = { + notfound: "Doesn't have the video", + failure: "Error occured", + pending: "Checking for videos...", +}; + +const statusMap: Record = { + failure: "error", + notfound: "noresult", + pending: "loading", + success: "success", + waiting: "waiting", +}; + +export function ScrapeItem(props: ScrapeItemProps) { + const text = statusTextMap[props.status]; + const status = statusMap[props.status]; + + return ( +
+ +
+

{props.name}

+
+ +

{text}

+
+
+ {props.children} +
+
+ ); +} + +export function ScrapeCard(props: ScrapeCardProps) { + return ( +
+ +
+ ); +} diff --git a/src/components/player/internals/StatusCircle.tsx b/src/components/player/internals/StatusCircle.tsx index bd1f59b8..961347f3 100644 --- a/src/components/player/internals/StatusCircle.tsx +++ b/src/components/player/internals/StatusCircle.tsx @@ -1,10 +1,15 @@ -import { Icon, Icons } from "@/components/Icon"; +import { a, to, useSpring } from "@react-spring/web"; +import classNames from "classnames"; -interface StatusCircle { - type: "loading" | "done" | "error" | "pending" | "noresult"; +import { Icon, Icons } from "@/components/Icon"; +import { Transition } from "@/components/Transition"; + +export interface StatusCircle { + type: "loading" | "success" | "error" | "noresult" | "waiting"; + percentage?: number; } -interface StatusCircleLoading extends StatusCircle { +export interface StatusCircleLoading extends StatusCircle { type: "loading"; percentage: number; } @@ -16,19 +21,27 @@ function statusIsLoading( } export function StatusCircle(props: StatusCircle | StatusCircleLoading) { - let classes = ""; - if (props.type === "loading") classes = "text-video-scraping-loading"; - if (props.type === "noresult") - classes = "text-video-scraping-noresult text-opacity-50"; - if (props.type === "error") - classes = "text-video-scraping-error bg-video-scraping-error"; + const [spring] = useSpring( + () => ({ + percentage: statusIsLoading(props) ? props.percentage : 0, + }), + [props] + ); return (
- {statusIsLoading(props) ? ( - + `${val} 100`)} r="25%" cx="50%" cy="50%" fill="transparent" stroke="currentColor" + className="transition-[strokeDasharray]" /> - ) : null} + - {props.type === "error" ? ( - - ) : null} + + + + + + + +
+
+
+
); } diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx index 597f9d97..ac2ff2b2 100644 --- a/src/pages/PlayerView.tsx +++ b/src/pages/PlayerView.tsx @@ -1,9 +1,12 @@ +import { useEffect, useState } from "react"; + import { MWStreamType } from "@/backend/helpers/streams"; import { BrandPill } from "@/components/layout/BrandPill"; import { Player } from "@/components/player"; import { AutoPlayStart } from "@/components/player/atoms"; import { usePlayer } from "@/components/player/hooks/usePlayer"; import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls"; +import { StatusCircle } from "@/components/player/internals/StatusCircle"; import { ScrapingPart } from "@/pages/parts/player/ScrapingPart"; import { playerStatus } from "@/stores/player/slices/source"; @@ -11,13 +14,19 @@ export function PlayerView() { const { status, setScrapeStatus, playMedia } = usePlayer(); const desktopControlsVisible = useShouldShowControls(); + const [a, setA] = useState(0); + useEffect(() => { + const dsf = setInterval(() => setA(Math.floor(Math.random() * 100)), 1000); + return () => clearInterval(dsf); + }, [setA]); + return ( {status === playerStatus.SCRAPING ? ( >({}); const [sourceOrder, setSourceOrder] = useState([]); + const [currentSource, setCurrentSource] = useState(); const startScraping = useCallback( async (media: ScrapeMedia) => { @@ -60,6 +65,7 @@ function useScrape() { if (s[id]) s[id].status = "pending"; return { ...s }; }); + setCurrentSource(id); }, update(evt) { setSources((s) => { @@ -105,12 +111,52 @@ function useScrape() { startScraping, sourceOrder, sources, + currentSource, }; } export function ScrapingPart(props: ScrapingProps) { const { playMedia } = usePlayer(); - const { startScraping, sourceOrder, sources } = useScrape(); + const { startScraping, sourceOrder, sources, currentSource } = useScrape(); + + const containerRef = useRef(null); + const listRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + if (!listRef.current) return; + + const elements = [ + ...listRef.current.querySelectorAll("div[data-source-id]"), + ] as HTMLDivElement[]; + + const currentIndex = elements.findIndex( + (e) => e.getAttribute("data-source-id") === currentSource + ); + + const currentElement = elements[currentIndex]; + + if (!currentElement) return; + + const containerWidth = containerRef.current.getBoundingClientRect().width; + const listWidth = listRef.current.getBoundingClientRect().width; + + const containerHeight = containerRef.current.getBoundingClientRect().height; + const listHeight = listRef.current.getBoundingClientRect().height; + + const listTop = listRef.current.getBoundingClientRect().top; + + const currentTop = currentElement.getBoundingClientRect().top; + const currentHeight = currentElement.getBoundingClientRect().height; + + const topDifference = currentTop - listTop; + + const listNewLeft = containerWidth / 2 - listWidth / 2; + const listNewTop = containerHeight / 2 - topDifference + currentHeight / 2; + + listRef.current.style.left = `${listNewLeft}px`; + listRef.current.style.top = `${listNewTop}px`; + }, [sourceOrder, currentSource]); const started = useRef(false); useEffect(() => { @@ -123,52 +169,35 @@ export function ScrapingPart(props: ScrapingProps) { }, [startScraping, props, playMedia]); return ( -
- {sourceOrder.map((order) => { - const source = sources[order.id]; - if (!source) return null; - - // Progress circle - let Circle = ; - if (source.status === "pending") - Circle = ( - +
+
+ {sourceOrder.map((order) => { + const source = sources[order.id]; + return ( + 0} + percentage={source.percentage} + key={order.id} + > + {order.children.map((embedId) => { + const embed = sources[embedId]; + return ( + + ); + })} + ); - if (source.status === "notfound") - Circle = ; - - // Main thing - return ( -
-
- {Circle} -
-

{source.name}

-

- status: {source.status} ({source.percentage}%) -

-

reason: {source.reason}

-
-
- {order.children.map((embedId) => { - const embed = sources[embedId]; - if (!embed) return null; - return ( -
-

{embed.name}

-

- status: {embed.status} ({embed.percentage}%) -

-

reason: {embed.reason}

-
- ); - })} -
- ); - })} + })} +
); }