3 changed files with 186 additions and 150 deletions
@ -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<Record<string, ScrapingSegment>>({}); |
||||||
|
const [sourceOrder, setSourceOrder] = useState<ScrapingItems[]>([]); |
||||||
|
const [currentSource, setCurrentSource] = useState<string>(); |
||||||
|
|
||||||
|
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<Record<string, ScrapingSegment>>((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<HTMLDivElement | null>, |
||||||
|
listRef: RefObject<HTMLDivElement | null>, |
||||||
|
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; |
||||||
|
} |
Loading…
Reference in new issue