"use client"; import { AsyncUnzipInflate, Unzip } from "fflate"; import { useEffect, useRef, useState } from "react"; import { z } from "zod"; import { Schema_File } from "~/schema"; import { cn } from "~/utils"; import Icon from "~/components/Icon"; import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert"; import { Button } from "~/components/ui/button"; import { Carousel, CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from "~/components/ui/carousel"; import { Progress } from "~/components/ui/progress"; import useMediaQuery from "~/hooks/useMediaQuery"; import config from "~/config/gIndex.config"; import { CreateDownloadToken } from "./actions"; type Props = { file: z.infer; }; export default function PreviewManga({ file }: Props) { const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [images, setImages] = useState<{ name: string; blob: string }[]>([]); const [currentImage, setCurrentImage] = useState(1); const [viewSize, setViewSize] = useState<"fit" | "full">("fit"); const [api, setApi] = useState(); const [loadedPercent, setLoadedPercent] = useState(0); const [loadFirstX, setLoadFirstX] = useState(5); // 5MB const abortController = useRef(new AbortController()); const isDesktop = useMediaQuery("(min-width: 768px)"); useEffect(() => { (async () => { try { if (!abortController.current) { abortController.current = new AbortController(); } if (!file.encryptedWebContentLink) { setError("No video to preview"); return; } const token = await CreateDownloadToken(); const streamURL = new URL( `/api/stream/${file.encryptedId}`, config.basePath, ); streamURL.searchParams.set("token", token); const bufferStream = await fetch(streamURL, { signal: abortController.current.signal, headers: { Range: `bytes=0-${ Math.min(Number(file.size || 1), loadFirstX * 1024 * 1024) - 1 }`, }, }); const contentLength = bufferStream.headers.get("Content-Length"); const totalBytes = parseInt(contentLength || "0", 10); const reader = bufferStream.body?.getReader(); if (reader) { const chunks: Uint8Array[] = []; let receivedLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; if (value) { chunks.push(value); receivedLength += value.length; const percent = (receivedLength / totalBytes) * 100; setLoadedPercent((prev) => { if (percent > prev) return percent; return prev; }); } } const buffer = new Uint8Array(receivedLength); let position = 0; for (const chunk of chunks) { buffer.set(chunk, position); position += chunk.length; } const unzip = new Unzip((file) => { file.ondata = (err, data, final) => { if (err) throw err; // Check if the file is an image if ( !file.name .toLowerCase() .includes(".jpg" || ".jpeg" || ".png" || ".gif" || ".webp") ) return; if (!final) return; // Skip if not fully loaded const blob = new Blob([data]); const reader = new FileReader(); reader.onload = () => { setImages((prev) => { const exist = prev.find((p) => p.name === file.name); if (exist) return prev; return [ ...prev, { name: file.name, blob: reader.result as string }, ]; }); }; reader.readAsDataURL(blob); }; file.start(); }); unzip.register(AsyncUnzipInflate); unzip.push(buffer); } } catch (error) { const e = error as Error; console.error(e); setError(e.message); } finally { setLoading(false); } })(); // return () => { // abortController.current.abort("Cancelled by user"); // }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!api) return; api.on("select", (embla, e) => { const index = embla.selectedScrollSnap(); setCurrentImage(index + 1); }); }, [api]); return (
{loading ? (

Please wait while we downloading the content for preview

{Math.round(loadedPercent)}% loaded
) : error ? (
{error}
) : (
{images .sort((a, b) => a.name.localeCompare(b.name)) .map((image, index) => ( {`${image.name} {image.name} ))} {isDesktop ? ( <> ) : null}
{currentImage}/{images.length} {/*
setViewSize(viewSize === "fit" ? "full" : "fit")} >
*/}
Preview Only We only load the first {loadFirstX}MB of the file for preview. Please download the file for full content.
)}
); }