You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.8 KiB
164 lines
4.8 KiB
import { ProviderControls, ScrapeMedia } from "@movie-web/providers"; |
|
import classNames from "classnames"; |
|
import { useEffect, useRef, useState } from "react"; |
|
import { useTranslation } from "react-i18next"; |
|
import { useMountedState } from "react-use"; |
|
import type { AsyncReturnType } from "type-fest"; |
|
|
|
import { |
|
scrapePartsToProviderMetric, |
|
useReportProviders, |
|
} from "@/backend/helpers/report"; |
|
import { Icon, Icons } from "@/components/Icon"; |
|
import { Loading } from "@/components/layout/Loading"; |
|
import { |
|
ScrapeCard, |
|
ScrapeItem, |
|
} from "@/components/player/internals/ScrapeCard"; |
|
import { |
|
ScrapingItems, |
|
ScrapingSegment, |
|
useListCenter, |
|
useScrape, |
|
} from "@/hooks/useProviderScrape"; |
|
import { LargeTextPart } from "@/pages/parts/util/LargeTextPart"; |
|
|
|
export interface ScrapingProps { |
|
media: ScrapeMedia; |
|
onGetStream?: (stream: AsyncReturnType<ProviderControls["runAll"]>) => void; |
|
onResult?: ( |
|
sources: Record<string, ScrapingSegment>, |
|
sourceOrder: ScrapingItems[], |
|
) => void; |
|
} |
|
|
|
export function ScrapingPart(props: ScrapingProps) { |
|
const { report } = useReportProviders(); |
|
const { startScraping, sourceOrder, sources, currentSource } = useScrape(); |
|
const isMounted = useMountedState(); |
|
const { t } = useTranslation(); |
|
|
|
const containerRef = useRef<HTMLDivElement | null>(null); |
|
const listRef = useRef<HTMLDivElement | null>(null); |
|
const [failedStartScrape, setFailedStartScrape] = useState<boolean>(false); |
|
const renderedOnce = useListCenter( |
|
containerRef, |
|
listRef, |
|
sourceOrder, |
|
currentSource, |
|
); |
|
|
|
const resultRef = useRef({ |
|
sourceOrder, |
|
sources, |
|
}); |
|
useEffect(() => { |
|
resultRef.current = { |
|
sourceOrder, |
|
sources, |
|
}; |
|
}, [sourceOrder, sources]); |
|
|
|
const started = useRef(false); |
|
useEffect(() => { |
|
if (started.current) return; |
|
started.current = true; |
|
(async () => { |
|
const output = await startScraping(props.media); |
|
if (!isMounted()) return; |
|
props.onResult?.( |
|
resultRef.current.sources, |
|
resultRef.current.sourceOrder, |
|
); |
|
report( |
|
scrapePartsToProviderMetric( |
|
props.media, |
|
resultRef.current.sourceOrder, |
|
resultRef.current.sources, |
|
), |
|
); |
|
props.onGetStream?.(output); |
|
})().catch(() => setFailedStartScrape(true)); |
|
}, [startScraping, props, report, isMounted]); |
|
|
|
let currentProviderIndex = sourceOrder.findIndex( |
|
(s) => s.id === currentSource || s.children.includes(currentSource ?? ""), |
|
); |
|
if (currentProviderIndex === -1) |
|
currentProviderIndex = sourceOrder.length - 1; |
|
|
|
if (failedStartScrape) |
|
return ( |
|
<LargeTextPart |
|
iconSlot={ |
|
<Icon className="text-type-danger text-2xl" icon={Icons.WARNING} /> |
|
} |
|
> |
|
{t("player.turnstile.error")} |
|
</LargeTextPart> |
|
); |
|
|
|
return ( |
|
<div |
|
className="h-full w-full relative dir-neutral:origin-top-left flex" |
|
ref={containerRef} |
|
> |
|
{!sourceOrder || sourceOrder.length === 0 ? ( |
|
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center flex flex-col justify-center z-0"> |
|
<Loading className="mb-8" /> |
|
<p>{t("player.turnstile.verifyingHumanity")}</p> |
|
</div> |
|
) : null} |
|
<div |
|
className={classNames({ |
|
"absolute transition-[transform,opacity] opacity-0 dir-neutral:left-0": |
|
true, |
|
"!opacity-100": renderedOnce, |
|
})} |
|
ref={listRef} |
|
> |
|
{sourceOrder.map((order) => { |
|
const source = sources[order.id]; |
|
const distance = Math.abs( |
|
sourceOrder.findIndex((o) => o.id === order.id) - |
|
currentProviderIndex, |
|
); |
|
return ( |
|
<div |
|
className="transition-opacity duration-100" |
|
style={{ opacity: Math.max(0, 1 - distance * 0.3) }} |
|
key={order.id} |
|
> |
|
<ScrapeCard |
|
id={order.id} |
|
name={source.name} |
|
status={source.status} |
|
hasChildren={order.children.length > 0} |
|
percentage={source.percentage} |
|
> |
|
<div |
|
className={classNames({ |
|
"space-y-6 mt-8": order.children.length > 0, |
|
})} |
|
> |
|
{order.children.map((embedId) => { |
|
const embed = sources[embedId]; |
|
return ( |
|
<ScrapeItem |
|
id={embedId} |
|
name={embed.name} |
|
status={embed.status} |
|
percentage={embed.percentage} |
|
key={embedId} |
|
/> |
|
); |
|
})} |
|
</div> |
|
</ScrapeCard> |
|
</div> |
|
); |
|
})} |
|
</div> |
|
</div> |
|
); |
|
}
|
|
|