UNPKG

react-ipdf-viewer-lite

Version:

A lightweight, dependency-free media viewer for PDFs and other media types with advanced controls

301 lines (300 loc) 17.4 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import React, { useState, useEffect, useRef } from "react"; import * as pdfjsLib from "pdfjs-dist"; import { Button, Divider, Input, Spin, Tooltip } from "antd"; import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined, MoonOutlined, SunOutlined, ZoomInOutlined, ZoomOutOutlined, } from "@ant-design/icons"; pdfjsLib.GlobalWorkerOptions.workerSrc = "//cdnjs.cloudflare.com/ajax/libs/pdf.js/".concat(pdfjsLib.version, "/pdf.worker.min.js"); var PDFRenderer = function (_a) { var fileUrl = _a.fileUrl, themes = _a.themes; var _b = useState(null), pdf = _b[0], setPdf = _b[1]; var _c = useState(1), zoomLevel = _c[0], setZoomLevel = _c[1]; var _d = useState(false), isFullScreen = _d[0], SetIsFullScreen = _d[1]; var _e = useState(true), theme = _e[0], SetTheme = _e[1]; var _f = useState(1), currentPage = _f[0], SetCurPage = _f[1]; var _g = useState(0), totalPage = _g[0], SetTotalPage = _g[1]; var _h = useState(false), showSpinner = _h[0], setShowSpinner = _h[1]; var _j = useState(false), showError = _j[0], setShowError = _j[1]; var canvasRefs = useRef([]); var containerRef = useRef(null); useEffect(function () { SetTheme(themes); }, [themes, currentPage]); useEffect(function () { if (!pdf || !containerRef.current) return; var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { var index = canvasRefs.current.findIndex(function (ref) { return ref.current === entry.target; }); if (index !== -1) { SetCurPage(index + 1); console.log("Page at top: ".concat(index + 1)); } } }); }, { root: containerRef.current, threshold: 0.1, rootMargin: "0px 0px 0px 0px", }); canvasRefs.current.forEach(function (ref) { if (ref.current) observer.observe(ref.current); }); return function () { canvasRefs.current.forEach(function (ref) { if (ref.current) observer.unobserve(ref.current); }); }; }, [pdf]); useEffect(function () { if (!pdf) { var spinnerTimeout_1 = setTimeout(function () { setShowSpinner(true); }, 100); var errorTimeout_1 = setTimeout(function () { setShowError(true); setShowSpinner(false); }, 10000); return function () { clearTimeout(spinnerTimeout_1); clearTimeout(errorTimeout_1); }; } }, [pdf]); useEffect(function () { pdfjsLib .getDocument(fileUrl) .promise.then(function (loadedPdf) { setPdf(loadedPdf); renderAllPages(loadedPdf); }) .catch(function (error) { console.error("Error loading PDF:", error); }); }, [fileUrl]); var renderAllPages = function (loadedPdf) { return __awaiter(void 0, void 0, void 0, function () { var numPages, pageNumber, page, canvas, context, viewport, renderContext; return __generator(this, function (_a) { switch (_a.label) { case 0: numPages = loadedPdf.numPages; SetTotalPage(numPages); canvasRefs.current = Array(numPages) .fill(null) .map(function () { return React.createRef(); }); pageNumber = 1; _a.label = 1; case 1: if (!(pageNumber <= numPages)) return [3 /*break*/, 5]; return [4 /*yield*/, loadedPdf.getPage(pageNumber)]; case 2: page = _a.sent(); canvas = canvasRefs.current[pageNumber - 1].current; if (!canvas) return [3 /*break*/, 4]; context = canvas.getContext("2d"); if (!context) return [3 /*break*/, 4]; viewport = page.getViewport({ scale: 1.5 }); canvas.height = viewport.height; canvas.width = viewport.width; renderContext = { canvasContext: context, viewport: viewport, }; return [4 /*yield*/, page.render(renderContext).promise]; case 3: _a.sent(); _a.label = 4; case 4: pageNumber++; return [3 /*break*/, 1]; case 5: return [2 /*return*/]; } }); }); }; var handleZoomIn = function () { var newZoom = zoomLevel * 1.1; var _zoomLevel = newZoom > 5 ? 5 : newZoom; setZoomLevel(_zoomLevel); }; var handleZoomOut = function () { var newZoom = zoomLevel / 1.1; var _zoomLevel = newZoom < 0.4 ? 0.4 : newZoom; setZoomLevel(_zoomLevel); }; var handleDownload = function () { return __awaiter(void 0, void 0, void 0, function () { var response, blob, url, link, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 4]); return [4 /*yield*/, fetch(fileUrl)]; case 1: response = _a.sent(); if (!response.ok) throw new Error("Failed to fetch PDF"); return [4 /*yield*/, response.blob()]; case 2: blob = _a.sent(); url = window.URL.createObjectURL(blob); link = document.createElement("a"); link.href = url; link.download = fileUrl.split("/").pop() || "document.pdf"; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); return [3 /*break*/, 4]; case 3: error_1 = _a.sent(); console.error("Download failed:", error_1); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }; var toggleFullScreen = function () { if (typeof window !== undefined) { SetIsFullScreen(!isFullScreen); if (!document.fullscreenElement) { document.documentElement.requestFullscreen().catch(function (err) { console.error("Error attempting fullscreen: ".concat(err.message)); }); } else { document.exitFullscreen(); } } }; return (_jsx(_Fragment, { children: _jsxs("div", __assign({ style: { border: theme ? "2px solid black" : "2px solid #ECECEC", margin: "4px", marginBottom: "50px", height: "95vh", display: "flex", flexDirection: "column", overflow: "hidden", } }, { children: [_jsxs("div", __assign({ style: { width: "100%", display: "flex", alignItems: "center", gap: 10, justifyContent: "space-between", background: theme ? "#3C3C3C" : "#ECECEC", height: "45px", paddingRight: "10px", position: "sticky", top: 0, zIndex: 10, padding: "0 5px", } }, { children: [_jsxs("div", __assign({ style: { margin: "0px 10px" } }, { children: [_jsx(Tooltip, __assign({ title: theme ? "Light mode" : "Dark mode" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 5px", }, onClick: function () { SetTheme(!theme); } }, { children: theme ? _jsx(SunOutlined, {}) : _jsx(MoonOutlined, {}) })) })), _jsx(Divider, { style: { borderColor: theme ? "#fff" : "black", margin: "0px 5px", }, type: "vertical" }), _jsx(Tooltip, __assign({ title: !isFullScreen ? "Enter Full Screen" : "Exit Full Screen" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 10px", }, onClick: toggleFullScreen }, { children: !isFullScreen ? (_jsx(FullscreenOutlined, {})) : (_jsx(FullscreenExitOutlined, {})) })) }))] })), _jsxs("div", __assign({ style: { margin: "0px 10px" } }, { children: [_jsx(Tooltip, __assign({ title: "Current page" }, { children: _jsx(Input, { style: { width: "50px", marginRight: "8px", background: theme ? "#282828" : "#ffffff", color: theme ? "#fff" : "black", borderColor: theme ? "#3C3C3C" : "#ECECEC", }, value: currentPage, readOnly: true }) })), _jsx("span", __assign({ style: { color: theme ? "#fff" : "black" } }, { children: "of" })), _jsx(Tooltip, __assign({ title: "Total Pages" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 2px", } }, { children: totalPage })) }))] })), _jsxs("div", __assign({ style: { margin: "0px 10px" } }, { children: [_jsx(Tooltip, __assign({ title: "Zoom Out" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 5px" }, onClick: handleZoomOut }, { children: _jsx(ZoomOutOutlined, {}) })) })), _jsx(Tooltip, __assign({ title: "Zoom In" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 5px" }, onClick: handleZoomIn }, { children: _jsx(ZoomInOutlined, {}) })) })), _jsx(Divider, { style: { borderColor: theme ? "#fff" : "black", margin: "0px 5px", }, type: "vertical" }), _jsx(Tooltip, __assign({ title: "Download" }, { children: _jsx(Button, __assign({ color: "default", variant: "filled", style: { color: theme ? "#fff" : "black", margin: "0px 5px" }, onClick: handleDownload }, { children: _jsx(DownloadOutlined, {}) })) }))] }))] })), _jsxs("div", __assign({ ref: containerRef, style: { flex: 1, overflow: "auto", background: theme ? "#282828" : "#ffffff", height: "100vh", display: "flex", alignItems: zoomLevel > 1.9 ? "" : "center", justifyContent: zoomLevel > 1.9 ? "" : "center", flexDirection: "column", gap: 3, } }, { children: [_jsx("div", __assign({ style: { transform: "scale(".concat(zoomLevel, ")"), // transformOrigin: "left top", transformOrigin: zoomLevel > 1.9 ? "left top" : "center top", width: "60%", padding: "5px", minHeight: "3px", } }, { children: pdf && Array.from({ length: pdf.numPages }, function (_, index) { return (_jsx("canvas", { ref: canvasRefs.current[index], style: { width: "100%", marginBottom: "3px", border: "1px solid #ccc", } }, index)); }) })), !pdf && (_jsxs("div", __assign({ style: { display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 10, width: "300px", padding: "20px", } }, { children: [showSpinner && (_jsx("div", __assign({ style: { textAlign: "center", marginTop: "50px" } }, { children: _jsx(Spin, { size: "large", tip: "Loading PDF..." }) }))), showError && (_jsx("div", __assign({ style: { background: "red", padding: "1rem", color: "#fff", textAlign: "center", } }, { children: "The uploaded document is either corrupted or not a valid PDF. Ensure the document is a valid PDF and try again." })))] })))] }))] })) })); }; export default PDFRenderer;