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
JavaScript
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;