From 517f8d0254ceb80a3625611d8197d3ef5771d753 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 6 Oct 2023 00:20:19 +0200 Subject: [PATCH] scraping animation and some cleanup --- src/hooks/useProviderScrape.tsx | 169 ++++++++++++++++++++++++ src/pages/PlayerView.tsx | 11 +- src/pages/parts/player/ScrapingPart.tsx | 156 +++------------------- 3 files changed, 186 insertions(+), 150 deletions(-) create mode 100644 src/hooks/useProviderScrape.tsx diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx new file mode 100644 index 00000000..165b18b0 --- /dev/null +++ b/src/hooks/useProviderScrape.tsx @@ -0,0 +1,169 @@ +import { ScrapeMedia } from "@movie-web/providers"; +import { RefObject, useCallback, useEffect, useRef, useState } from "react"; + +import { providers } from "@/utils/providers"; + +export interface ScrapingItems { + id: string; + children: string[]; +} + +export interface ScrapingSegment { + name: string; + id: string; + status: "failure" | "pending" | "notfound" | "success" | "waiting"; + reason?: string; + percentage: number; +} + +export function useScrape() { + const [sources, setSources] = useState>({}); + const [sourceOrder, setSourceOrder] = useState([]); + const [currentSource, setCurrentSource] = useState(); + + const startScraping = useCallback( + async (media: ScrapeMedia) => { + if (!providers) return null; + const output = await providers.runAll({ + media, + events: { + init(evt) { + setSources( + evt.sourceIds + .map((v) => { + const source = providers.getMetadata(v); + if (!source) throw new Error("invalid source id"); + const out: ScrapingSegment = { + name: source.name, + id: source.id, + status: "waiting", + percentage: 0, + }; + return out; + }) + .reduce>((a, v) => { + a[v.id] = v; + return a; + }, {}) + ); + setSourceOrder(evt.sourceIds.map((v) => ({ id: v, children: [] }))); + }, + start(id) { + setSources((s) => { + if (s[id]) s[id].status = "pending"; + return { ...s }; + }); + setCurrentSource(id); + }, + update(evt) { + setSources((s) => { + if (s[evt.id]) { + s[evt.id].status = evt.status; + s[evt.id].reason = evt.reason; + s[evt.id].percentage = evt.percentage; + } + return { ...s }; + }); + }, + discoverEmbeds(evt) { + setSources((s) => { + evt.embeds.forEach((v) => { + const source = providers.getMetadata(v.embedScraperId); + if (!source) throw new Error("invalid source id"); + const out: ScrapingSegment = { + name: source.name, + id: v.id, + status: "waiting", + percentage: 0, + }; + s[v.id] = out; + }); + return { ...s }; + }); + setSourceOrder((s) => { + const source = s.find((v) => v.id === evt.sourceId); + if (!source) throw new Error("invalid source id"); + source.children = evt.embeds.map((v) => v.id); + return [...s]; + }); + }, + }, + }); + + return output; + }, + [setSourceOrder, setSources] + ); + + return { + startScraping, + sourceOrder, + sources, + currentSource, + }; +} + +export function useListCenter( + containerRef: RefObject, + listRef: RefObject, + sourceOrder: ScrapingItems[], + currentSource: string | undefined +) { + const [renderedOnce, setRenderedOnce] = useState(false); + + const updatePosition = useCallback(() => { + 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 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.transform = `translateY(${listNewTop}px) translateX(${listNewLeft}px)`; + setTimeout(() => { + setRenderedOnce(true); + }, 150); + }, [currentSource, containerRef, listRef, setRenderedOnce]); + + const updatePositionRef = useRef(updatePosition); + + useEffect(() => { + updatePosition(); + updatePositionRef.current = updatePosition; + }, [updatePosition, sourceOrder]); + + useEffect(() => { + function resize() { + updatePositionRef.current(); + } + window.addEventListener("resize", resize); + return () => { + window.removeEventListener("resize", resize); + }; + }, []); + + return renderedOnce; +} diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx index ac2ff2b2..825bd8bd 100644 --- a/src/pages/PlayerView.tsx +++ b/src/pages/PlayerView.tsx @@ -1,12 +1,9 @@ -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"; @@ -14,19 +11,13 @@ 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 ? ( ) => void; } -export interface ScrapingSegment { - name: string; - id: string; - status: "failure" | "pending" | "notfound" | "success" | "waiting"; - reason?: string; - percentage: number; -} - -export interface ScrapingItems { - id: string; - children: string[]; -} - -function useScrape() { - const [sources, setSources] = useState>({}); - const [sourceOrder, setSourceOrder] = useState([]); - const [currentSource, setCurrentSource] = useState(); - - const startScraping = useCallback( - async (media: ScrapeMedia) => { - if (!providers) return null; - const output = await providers.runAll({ - media, - events: { - init(evt) { - setSources( - evt.sourceIds - .map((v) => { - const source = providers.getMetadata(v); - if (!source) throw new Error("invalid source id"); - const out: ScrapingSegment = { - name: source.name, - id: source.id, - status: "waiting", - percentage: 0, - }; - return out; - }) - .reduce>((a, v) => { - a[v.id] = v; - return a; - }, {}) - ); - setSourceOrder(evt.sourceIds.map((v) => ({ id: v, children: [] }))); - }, - start(id) { - setSources((s) => { - if (s[id]) s[id].status = "pending"; - return { ...s }; - }); - setCurrentSource(id); - }, - update(evt) { - setSources((s) => { - if (s[evt.id]) { - s[evt.id].status = evt.status; - s[evt.id].reason = evt.reason; - s[evt.id].percentage = evt.percentage; - } - return { ...s }; - }); - }, - discoverEmbeds(evt) { - setSources((s) => { - evt.embeds.forEach((v) => { - const source = providers.getMetadata(v.embedScraperId); - if (!source) throw new Error("invalid source id"); - const out: ScrapingSegment = { - name: source.name, - id: v.id, - status: "waiting", - percentage: 0, - }; - s[v.id] = out; - }); - return { ...s }; - }); - setSourceOrder((s) => { - const source = s.find((v) => v.id === evt.sourceId); - if (!source) throw new Error("invalid source id"); - source.children = evt.embeds.map((v) => v.id); - return [...s]; - }); - }, - }, - }); - - return output; - }, - [setSourceOrder, setSources] - ); - - return { - startScraping, - sourceOrder, - sources, - currentSource, - }; -} - export function ScrapingPart(props: ScrapingProps) { const { playMedia } = usePlayer(); 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 renderedOnce = useListCenter( + containerRef, + listRef, + sourceOrder, + currentSource + ); const started = useRef(false); useEffect(() => { @@ -170,7 +40,13 @@ export function ScrapingPart(props: ScrapingProps) { return (
-
+
{sourceOrder.map((order) => { const source = sources[order.id]; return (