@llamaindex/ui
Version: 
A comprehensive UI component library built with React, TypeScript, and Tailwind CSS for LlamaIndex applications
319 lines (316 loc) • 11.2 kB
JavaScript
import { PdfNavigator, BoundingBoxOverlay } from './chunk-XIANQWOZ.mjs';
import './chunk-A734CEDD.mjs';
import './chunk-KPGB4IWL.mjs';
import './chunk-MG2ARK3A.mjs';
import { __spreadProps, __spreadValues } from './chunk-JD6AELXS.mjs';
import { useState, useRef, useEffect } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
if (typeof window !== "undefined") {
  pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
}
import('react-pdf/dist/Page/AnnotationLayer.css');
import('react-pdf/dist/Page/TextLayer.css');
var pdfOptions = {
  cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`,
  wasmUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/wasm/`
};
var FILE_SIZE_THRESHOLD = 10 * 1024 * 1024;
var DEFAULT_FILE_NAME = "document.pdf";
var PdfPreviewImpl = ({
  fileName,
  url,
  onDownload,
  onRemove,
  highlight,
  toolbarClassName
}) => {
  var _a;
  const [numPages, setNumPages] = useState();
  const [currentPage, setCurrentPage] = useState(1);
  const [scale, setScale] = useState(1);
  const [file, setFile] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [renderedPages, setRenderedPages] = useState(0);
  const [isRendering, setIsRendering] = useState(true);
  const containerRef = useRef(null);
  const pageRefs = useRef({});
  const isInitialScaleSet = useRef(false);
  const [pageBaseDims, setPageBaseDims] = useState({});
  const [showHighlight, setShowHighlight] = useState(false);
  const boundingBoxes = [
    {
      id: "highlight",
      x: (highlight == null ? void 0 : highlight.x) || 0,
      y: (highlight == null ? void 0 : highlight.y) || 0,
      width: (highlight == null ? void 0 : highlight.width) || 0,
      height: (highlight == null ? void 0 : highlight.height) || 0,
      color: "rgba(255, 215, 0, 0.25)"
    }
  ];
  function onDocumentLoadSuccess({ numPages: numPages2 }) {
    setNumPages(numPages2);
    setRenderedPages(0);
    setIsRendering(true);
  }
  function handlePageRenderSuccess() {
    setRenderedPages((prev) => {
      const next = prev + 1;
      if (next === numPages) {
        setIsRendering(false);
      }
      return next;
    });
  }
  useEffect(() => {
    if (!highlight) return;
    if (!numPages) return;
    const pageEl = pageRefs.current[highlight.page];
    if (pageEl) {
      goToPage(highlight.page);
      setShowHighlight(true);
    }
  }, [highlight, numPages]);
  const handleLoadPage = (page) => {
    const viewport = page.getViewport({ scale: 1 });
    setPageBaseDims((prev) => __spreadProps(__spreadValues({}, prev), {
      [page.pageNumber]: {
        width: viewport.width,
        height: viewport.height
      }
    }));
    if (!isInitialScaleSet.current && page.pageNumber === 1 && containerRef.current) {
      const containerWidth = containerRef.current.clientWidth;
      const padding = 16;
      const availableWidth = containerWidth - padding;
      const newScale = availableWidth / viewport.width;
      setScale(newScale);
      isInitialScaleSet.current = true;
    }
  };
  const handleClickOnPage = () => {
    if (showHighlight) {
      setShowHighlight(false);
    }
  };
  useEffect(() => {
    const handleScroll = () => {
      if (!containerRef.current) return;
      const containerRect = containerRef.current.getBoundingClientRect();
      const containerCenter = containerRect.top + containerRect.height / 2;
      let closestPage = 1;
      let closestDistance = Infinity;
      Object.entries(pageRefs.current).forEach(([pageNumber, element]) => {
        if (element) {
          const rect = element.getBoundingClientRect();
          const pageCenter = rect.top + rect.height / 2;
          const distance = Math.abs(pageCenter - containerCenter);
          if (distance < closestDistance) {
            closestDistance = distance;
            closestPage = parseInt(pageNumber);
          }
        }
      });
      setCurrentPage(closestPage);
    };
    const container = containerRef.current;
    if (container) {
      container.addEventListener("scroll", handleScroll);
      return () => container.removeEventListener("scroll", handleScroll);
    }
  }, [numPages]);
  const lastLoadedUrl = useRef(null);
  useEffect(() => {
    if (lastLoadedUrl.current === url) {
      return;
    }
    lastLoadedUrl.current = url;
    const fetchFile = async () => {
      setIsLoading(true);
      const response = await fetch(url);
      const blob = await response.blob();
      setFile(new File([blob], DEFAULT_FILE_NAME, { type: "application/pdf" }));
      setIsLoading(false);
    };
    fetchFile();
    return () => {
      setFile(null);
    };
  }, [url]);
  const goToPage = (pageNumber) => {
    const pageElement = pageRefs.current[pageNumber];
    if (pageElement && containerRef.current) {
      pageElement.scrollIntoView({
        behavior: "instant",
        block: "center"
      });
    }
  };
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    const handleKeyDown = (event) => {
      if (event.key === "ArrowLeft") {
        event.preventDefault();
        if (currentPage > 1) {
          goToPage(currentPage - 1);
        }
      } else if (event.key === "ArrowRight") {
        event.preventDefault();
        if (currentPage < (numPages || 1)) {
          goToPage(currentPage + 1);
        }
      } else if (event.key === "=" || event.key === "+") {
        event.preventDefault();
        setScale((prev) => Math.min(prev + 0.25, 3));
      } else if (event.key === "-") {
        event.preventDefault();
        setScale((prev) => Math.max(prev - 0.25, 0.5));
      }
    };
    container.addEventListener("keydown", handleKeyDown);
    container.tabIndex = 0;
    return () => {
      container.removeEventListener("keydown", handleKeyDown);
    };
  }, [currentPage, numPages]);
  const handleDownload = () => {
    if (onDownload) {
      onDownload();
    } else {
      if (file) {
        const blobUrl = URL.createObjectURL(file);
        const link = document.createElement("a");
        link.href = blobUrl;
        link.download = file.name;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(blobUrl);
      } else {
        window.open(url, "_blank");
      }
    }
  };
  const handleReset = () => {
    setCurrentPage(1);
    goToPage(1);
  };
  const toggleFullscreen = () => {
    var _a2;
    if (!document.fullscreenElement) {
      (_a2 = containerRef.current) == null ? void 0 : _a2.requestFullscreen();
    } else {
      document.exitFullscreen();
    }
  };
  if (isLoading) {
    return /* @__PURE__ */ jsx("div", { className: "relative h-full flex items-center justify-center bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
      /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-2" }),
      /* @__PURE__ */ jsx("p", { className: "text-gray-600", children: "Loading PDF..." })
    ] }) });
  }
  return /* @__PURE__ */ jsxs("div", { className: "relative h-full flex flex-col", children: [
    isRendering && file && file.size > FILE_SIZE_THRESHOLD && /* @__PURE__ */ jsx(
      PdfRenderingProgress,
      {
        renderedPages,
        numPages
      }
    ),
    numPages && /* @__PURE__ */ jsxs(Fragment, { children: [
      /* @__PURE__ */ jsx(
        PdfNavigator,
        {
          fileName: (_a = fileName != null ? fileName : file == null ? void 0 : file.name) != null ? _a : DEFAULT_FILE_NAME,
          currentPage,
          totalPages: numPages,
          scale,
          onPageChange: goToPage,
          onScaleChange: setScale,
          onDownload: handleDownload,
          onRemove,
          onReset: handleReset,
          onFullscreen: toggleFullscreen,
          className: toolbarClassName
        }
      ),
      /* @__PURE__ */ jsx("div", { className: "h-3 bg-[#F3F3F3]" })
    ] }),
    /* @__PURE__ */ jsx(
      "div",
      {
        ref: containerRef,
        className: "overflow-auto h-full bg-[#F3F3F3] rounded-lg flex-1 min-h-0",
        children: /* @__PURE__ */ jsx(
          Document,
          {
            file,
            onLoadSuccess: onDocumentLoadSuccess,
            loading: isLoading,
            options: pdfOptions,
            children: Array.from(new Array(numPages), (_, index) => /* @__PURE__ */ jsx(
              "div",
              {
                ref: (el) => {
                  pageRefs.current[index + 1] = el;
                },
                className: "mb-4 flex justify-center",
                children: /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [
                  /* @__PURE__ */ jsx(
                    Page,
                    {
                      pageNumber: index + 1,
                      scale,
                      renderTextLayer: true,
                      renderAnnotationLayer: true,
                      onLoadSuccess: handleLoadPage,
                      onClick: handleClickOnPage,
                      onRenderSuccess: handlePageRenderSuccess
                    }
                  ),
                  highlight && showHighlight && highlight.page === index + 1 && pageBaseDims[index + 1] && /* @__PURE__ */ jsx(
                    BoundingBoxOverlay,
                    {
                      boundingBoxes,
                      zoom: scale,
                      containerWidth: pageBaseDims[index + 1].width,
                      containerHeight: pageBaseDims[index + 1].height
                    }
                  )
                ] })
              },
              `page_${index + 1}`
            ))
          }
        )
      }
    )
  ] });
};
function PdfRenderingProgress({
  renderedPages,
  numPages
}) {
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-50 bg-opacity-90 z-50", children: /* @__PURE__ */ jsxs("div", { className: "bg-white shadow-lg rounded-2xl p-6 w-80 text-center", children: [
    /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-10 w-10 border-4 border-primary border-t-transparent" }) }),
    /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-800 mb-2", children: "Rendering PDF\u2026" }),
    /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-600 mb-3", children: [
      "Page ",
      renderedPages,
      " of ",
      numPages
    ] }),
    /* @__PURE__ */ jsx("div", { className: "w-full bg-gray-200 rounded-full h-2.5 mb-4", children: /* @__PURE__ */ jsx(
      "div",
      {
        className: "bg-primary h-2.5 rounded-full transition-all duration-300",
        style: {
          width: numPages && numPages > 0 ? `${Math.round(renderedPages / numPages * 100)}%` : "0%"
        }
      }
    ) }),
    /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: "Large PDFs or those with heavy images may take longer to render." })
  ] }) });
}
export { PdfPreviewImpl };