react-file-previewer
Advanced tools
Comparing version 0.2.3 to 0.3.1
@@ -23,2 +23,54 @@ 'use strict'; | ||
function _typeof(obj) { | ||
"@babel/helpers - typeof"; | ||
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { | ||
_typeof = function (obj) { | ||
return typeof obj; | ||
}; | ||
} else { | ||
_typeof = function (obj) { | ||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
}; | ||
} | ||
return _typeof(obj); | ||
} | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { | ||
try { | ||
var info = gen[key](arg); | ||
var value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
Promise.resolve(value).then(_next, _throw); | ||
} | ||
} | ||
function _asyncToGenerator(fn) { | ||
return function () { | ||
var self = this, | ||
args = arguments; | ||
return new Promise(function (resolve, reject) { | ||
var gen = fn.apply(self, args); | ||
function _next(value) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); | ||
} | ||
function _throw(err) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); | ||
} | ||
_next(undefined); | ||
}); | ||
}; | ||
} | ||
function _slicedToArray(arr, i) { | ||
@@ -75,3 +127,3 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
var setZoomIn = R.converge(R.assoc('scale'), [R.compose(R.min(SCALE_ABSOLUTE_MAX), R.add(SCALE_FACTOR), R.propOr(1, 'scale')), R.identity]); | ||
var setZoomIn = R.converge(R.assoc('scale'), [R.compose(R.map(R.min(SCALE_ABSOLUTE_MAX)), R.map(R.add(SCALE_FACTOR)), R.when(R.is(Number), R.of), R.propOr([1], 'scale')), R.identity]); | ||
@@ -87,3 +139,3 @@ var SCALE_FACTOR$1 = 0.25; | ||
var setZoomOut = R.converge(R.assoc('scale'), [R.compose(R.max(SCALE_ABSOLUTE_MIN), R.subtract(R.__, SCALE_FACTOR$1), R.propOr(1, 'scale')), R.identity]); | ||
var setZoomOut = R.converge(R.assoc('scale'), [R.compose(R.map(R.max(SCALE_ABSOLUTE_MIN)), R.map(R.subtract(R.__, SCALE_FACTOR$1)), R.when(R.is(Number), R.of), R.propOr([1], 'scale')), R.identity]); | ||
@@ -99,19 +151,67 @@ /** | ||
var useViewportSize = function useViewportSize(viewportElem) { | ||
var isClient = (typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object'; | ||
var _useState = React.useState({ | ||
width: R.pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: R.pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}), | ||
_useState2 = _slicedToArray(_useState, 2), | ||
size = _useState2[0], | ||
setSize = _useState2[1]; | ||
React.useEffect(function () { | ||
setSize({ | ||
width: R.pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: R.pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}); | ||
}, viewportElem); | ||
React.useEffect(function () { | ||
if (!isClient) { | ||
return false; | ||
} | ||
function handleResize() { | ||
setSize({ | ||
width: R.pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: R.pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}); | ||
} | ||
window.addEventListener('resize', handleResize); | ||
return function () { | ||
return window.removeEventListener('resize', handleResize); | ||
}; | ||
}, []); | ||
return size; | ||
}; | ||
/** | ||
* Get the `scale` attribute for the "fit to screen" option. | ||
* | ||
* @param {Object} viewportElement | ||
* @param {Object} contentElement | ||
* @param {Object} viewportElem | ||
* @param {Object} contentElem | ||
* @return {Number} | ||
*/ | ||
var getFitToScreenScale = function getFitToScreenScale(viewportElement, contentElement) { | ||
// Get the viewport ratio. | ||
var viewportWidth = viewportElement.clientWidth; | ||
var viewportHeight = viewportElement.clientHeight; | ||
var getFitToScreenScale = function getFitToScreenScale(viewportElem, contentElem) { | ||
// Get the preview bar height. | ||
var previewBarHeight = 52; // Get the viewport ratio. | ||
var viewportWidth = viewportElem.width || viewportElem.clientWidth; | ||
var viewportHeight = (viewportElem.height || viewportElem.clientHeight) - previewBarHeight - 35; | ||
var viewportRatio = viewportWidth / viewportHeight; // Get the content ratio. | ||
var contentWidth = contentElement.offsetWidth; | ||
var contentHeight = contentElement.offsetHeight; | ||
var contentRatio = contentWidth / contentHeight; // Get the scaling ratio in `0.25` steps. | ||
var contentWidth = contentElem.width || contentElem.offsetWidth; | ||
var contentHeight = contentElem.height || contentElem.offsetHeight; | ||
var contentRatio = contentWidth / contentHeight; | ||
if (contentHeight > viewportHeight) { | ||
return viewportHeight / contentHeight; | ||
} | ||
if (contentWidth > viewportWidth) { | ||
return viewportWidth / contentHeight; | ||
} // Get the scaling ratio in `0.25` steps. | ||
return Math.round((viewportRatio > contentRatio ? viewportHeight / contentHeight : viewportWidth / contentWidth) * 4) / 4; | ||
@@ -266,6 +366,5 @@ }; | ||
var handleLoadSucess = React.useCallback(function (_ref3) { | ||
var numPages = _ref3.numPages; | ||
onLoadSuccess(numPages); | ||
setTotalPages(numPages); | ||
var handleLoadSucess = React.useCallback(function (pdf) { | ||
onLoadSuccess(pdf); | ||
setTotalPages(pdf.numPages); | ||
}, [onLoadSuccess]); | ||
@@ -280,4 +379,4 @@ return React__default.createElement(reactPdf.Document, { | ||
index: index, | ||
scale: file.scale, | ||
onPageChange: onPageChange | ||
onPageChange: onPageChange, | ||
scale: Array.isArray(file.scale) ? file.scale[index] : file.scale | ||
}); | ||
@@ -298,12 +397,63 @@ }, totalPages)); | ||
/** | ||
* Get the scale for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
var getScale = R.pathOr(1, ['scale', 0]); | ||
/** | ||
* Get the rotation for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
var getRotation = R.propOr(0, 'rotate'); | ||
var getResponseLikePDF = function getResponseLikePDF(imgRef) { | ||
var _imgRef$current = imgRef.current, | ||
clientWidth = _imgRef$current.clientWidth, | ||
clientHeight = _imgRef$current.clientHeight; | ||
return { | ||
numPages: 1, | ||
getPage: function getPage() { | ||
return { | ||
getViewport: function getViewport() { | ||
return { | ||
width: clientWidth, | ||
height: clientHeight | ||
}; | ||
} | ||
}; | ||
} | ||
}; | ||
}; | ||
/** | ||
* `ImageViewer` react component. | ||
* | ||
* @param {Object} params | ||
* @param {Object} params.file | ||
* @param {String} params.file.url | ||
* @param {String} params.file.data | ||
* @param {Array} params.file.scale | ||
* @param {Number} params.file.rotate | ||
* @param {String} params.file.mimeType | ||
* @param {Function} params.onLoadSuccess | ||
*/ | ||
var ImageViewer = function ImageViewer(_ref) { | ||
var file = _ref.file, | ||
onLoadSuccess = _ref.onLoadSuccess; | ||
var imgRef = React.useRef(null); | ||
React.useEffect(function () { | ||
onLoadSuccess(1); | ||
onLoadSuccess(getResponseLikePDF(imgRef)); | ||
}, []); | ||
return React__default.createElement("img", { | ||
ref: imgRef, | ||
src: file.url || "data:".concat(file.mimeType, ";base64,").concat(file.data), | ||
style: { | ||
transform: "rotate(".concat(file.rotate || 0, "deg) scale(").concat(file.scale || 1, ")") | ||
transform: "rotate(".concat(getRotation(file), "deg) scale(").concat(getScale(file), ")") | ||
} | ||
@@ -316,5 +466,6 @@ }); | ||
url: PropTypes.string, | ||
mimeType: PropTypes.string, | ||
data: PropTypes.string, | ||
name: PropTypes.string | ||
scale: PropTypes.array, | ||
rotate: PropTypes.number, | ||
mimeType: PropTypes.string | ||
}), | ||
@@ -336,12 +487,10 @@ onLoadSuccess: PropTypes.func.isRequired | ||
thumbnail = _ref.thumbnail, | ||
contentRef = _ref.contentRef, | ||
viewportRef = _ref.viewportRef, | ||
onLoadSuccess = _ref.onLoadSuccess, | ||
onPageChange = _ref.onPageChange; | ||
containerRef = _ref.containerRef, | ||
onPageChange = _ref.onPageChange, | ||
onLoadSuccess = _ref.onLoadSuccess; | ||
return React__default.createElement("div", { | ||
ref: viewportRef, | ||
ref: containerRef, | ||
className: classnames(thumbnail ? 'media-thumbnail' : 'preview-content') | ||
}, React__default.createElement("div", { | ||
className: "preview-file", | ||
ref: contentRef | ||
className: "preview-file" | ||
}, isPDF(file) ? React__default.createElement(PDFViewer, { | ||
@@ -365,4 +514,2 @@ file: file, | ||
thumbnail: PropTypes.bool, | ||
contentRef: PropTypes.any, | ||
viewportRef: PropTypes.any, | ||
onPageChange: PropTypes.func.isRequired, | ||
@@ -372,6 +519,17 @@ onLoadSuccess: PropTypes.func.isRequired | ||
/** | ||
* `FilePreviewer` react component. | ||
* | ||
* @param {Object} props | ||
* @param {Function} props.onClick | ||
* @param {Boolean} props.thumbnail | ||
* @param {Object} props.file | ||
* @param {String} props.file.url | ||
* @param {String} props.file.data | ||
* @param {String} props.file.name | ||
* @param {String} props.file.mimeType | ||
* @return {Object} | ||
*/ | ||
var FilePreviewer = function FilePreviewer(props) { | ||
var contentRef = React.useRef(null); | ||
var viewportRef = React.useRef(null); | ||
var _useState = React.useState(props.file), | ||
@@ -392,15 +550,22 @@ _useState2 = _slicedToArray(_useState, 2), | ||
React.useEffect(function () { | ||
var f = props.file; // if file passed is uploaded file, handle it correctly | ||
var _useState7 = React.useState([]), | ||
_useState8 = _slicedToArray(_useState7, 2), | ||
originalSizes = _useState8[0], | ||
setOriginalSizes = _useState8[1]; | ||
if (props.file && props.file instanceof File) { | ||
f.url = URL.createObjectURL(props.file); | ||
f.mimeType = props.file.type; | ||
} | ||
var _useState9 = React.useState(false), | ||
_useState10 = _slicedToArray(_useState9, 2), | ||
usingFitToScreen = _useState10[0], | ||
setUsingFitToScreen = _useState10[1]; | ||
setFile(f); | ||
setCurrentPage(0); | ||
}, [props.file]); // Scroll to the pervious page and update the index. | ||
var viewportRef = React.useRef(null); | ||
var containerRef = React.useRef(null); | ||
var viewportSize = useViewportSize(viewportRef); | ||
/** | ||
* Handler. Scroll to the pervious page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handlePageUp = function handlePageUp() { | ||
var onPageUp = function onPageUp() { | ||
var previousIndex = R.clamp(0, totalPages, currentPage - 1); | ||
@@ -410,7 +575,12 @@ setCurrentPage(previousIndex); | ||
viewportRef.current.scrollTop = previousPage.offsetTop - 10; | ||
}; // Scroll to the next page and update the index. | ||
containerRef.current.scrollTop = previousPage.offsetTop - 10; | ||
}; | ||
/** | ||
* Handler. Scroll to the next page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handlePageDown = function handlePageDown() { | ||
var onPageDown = function onPageDown() { | ||
var nextIndex = R.clamp(0, totalPages, currentPage + 1); | ||
@@ -420,61 +590,166 @@ setCurrentPage(nextIndex); | ||
viewportRef.current.scrollTop = nextPage.offsetTop - 10; | ||
}; // Handlers for rotate and zooming. | ||
containerRef.current.scrollTop = nextPage.offsetTop - 10; | ||
}; | ||
/** | ||
* Handler. Scale all the pages or image | ||
* up by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleZoomIn = function handleZoomIn() { | ||
return setFile(setZoomIn(file)); | ||
var onZoomIn = function onZoomIn() { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomIn(file)); | ||
}; | ||
/** | ||
* Handler. Scale all the pages or image | ||
* down by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleZoomOut = function handleZoomOut() { | ||
return setFile(setZoomOut(file)); | ||
var onZoomOut = function onZoomOut() { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomOut(file)); | ||
}; | ||
/** | ||
* Handler. Rotate the PDF (all pages) or Image. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleRotate = function handleRotate() { | ||
return setFile(setNewRotation(file)); | ||
var onRotate = function onRotate() { | ||
setFile(setNewRotation(file)); | ||
}; | ||
/** | ||
* Handler. Fit to screen. | ||
* | ||
* (This activates the "using fit to screen" mode, which | ||
* makes the document or image resize based on the viewport size.) | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleDownload = function handleDownload() { | ||
var onFitToScreen = function onFitToScreen() { | ||
setUsingFitToScreen(true); // Get the "fit to screen" scale. | ||
var newScale = originalSizes.map(function (originalSize) { | ||
return getFitToScreenScale(viewportSize, originalSize); | ||
}); | ||
setFile(function (prevValue) { | ||
return R.assoc('scale', newScale, prevValue); | ||
}); | ||
}; | ||
/** | ||
* Handler. Download the current file, it can be a | ||
* PDF or Image, as `url` or `base64` content. | ||
* | ||
* @return {Void} | ||
*/ | ||
var onDownload = function onDownload() { | ||
var url = file.url || "data:".concat(file.mimeType, ";base64,").concat(file.data); | ||
return saveFile(url, file.name || 'download.pdf'); | ||
}; | ||
/** | ||
* Handler. Callback after the PDF gets | ||
* processed successfully by `react-pdf`. | ||
* | ||
* @param {Object} pdf | ||
* @return {Void} | ||
*/ | ||
var handleFitToScreen = function handleFitToScreen() { | ||
// Get the "fit to screen" scale. | ||
var newScale = getFitToScreenScale(viewportRef.current, contentRef.current); | ||
setFile(R.assoc('scale', newScale, file)); | ||
}; | ||
if (!file) { | ||
return null; | ||
} | ||
var onLoadSuccess = function onLoadSuccess(pdf) { | ||
// Wait until the original sizes gets calculated. | ||
var promises = R.times( /*#__PURE__*/function () { | ||
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(i) { | ||
var page; | ||
return regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.next = 2; | ||
return pdf.getPage(i + 1); | ||
case 2: | ||
page = _context.sent; | ||
return _context.abrupt("return", R.pick(['width', 'height'], page.getViewport({ | ||
scale: 1 | ||
}))); | ||
case 4: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee); | ||
})); | ||
return function (_x) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(), pdf.numPages); // Set the original sizes for this file. | ||
Promise.all(promises).then(setOriginalSizes); | ||
setTotalPages(pdf.numPages); | ||
setUsingFitToScreen(true); | ||
}; // Set an effect to process the file resizing when the | ||
// viewport sizes changes and it's using "fit to screen". | ||
React.useEffect(function () { | ||
// Check if it's using "fit to screen". | ||
if (usingFitToScreen) { | ||
// Get the "fit to screen" scale. | ||
var newScale = originalSizes.map(function (originalSize) { | ||
return getFitToScreenScale(viewportSize, originalSize); | ||
}); // Update the scale. | ||
setFile(function (prevValue) { | ||
return R.assoc('scale', newScale, prevValue); | ||
}); | ||
} | ||
}, [usingFitToScreen, originalSizes, viewportSize]); // Reset all the attributes when the file prop changes. | ||
React.useEffect(function () { | ||
var file = props.file; // if file passed is uploaded file, handle it correctly | ||
if (props.file && props.file instanceof File) { | ||
file.url = URL.createObjectURL(props.file); | ||
file.mimeType = props.file.type; | ||
} | ||
setFile(file); | ||
setTotalPages(1); | ||
setCurrentPage(0); | ||
}, [props.file]); | ||
return React__default.createElement("div", { | ||
id: props.id, | ||
ref: viewportRef, | ||
onClick: props.onClick, | ||
className: "preview-wrapper", | ||
style: { | ||
height: props.height, | ||
width: props.width | ||
} | ||
className: "preview-wrapper" | ||
}, React__default.createElement(PreviewBar, { | ||
onPageUp: handlePageUp, | ||
onPageUp: onPageUp, | ||
onRotate: onRotate, | ||
totalPages: totalPages, | ||
onRotate: handleRotate, | ||
onDownload: onDownload, | ||
onPageDown: onPageDown, | ||
hidden: props.thumbnail, | ||
currentPage: currentPage, | ||
onDownload: handleDownload, | ||
onPageDown: handlePageDown | ||
currentPage: currentPage | ||
}), React__default.createElement(ViewportContent, { | ||
file: file, | ||
contentRef: contentRef, | ||
viewportRef: viewportRef, | ||
containerRef: containerRef, | ||
thumbnail: props.thumbnail, | ||
onLoadSuccess: setTotalPages, | ||
onPageChange: setCurrentPage | ||
onPageChange: setCurrentPage, | ||
onLoadSuccess: onLoadSuccess | ||
}), React__default.createElement(ViewportControl, { | ||
onZoomIn: handleZoomIn, | ||
onZoomIn: onZoomIn, | ||
onZoomOut: onZoomOut, | ||
hidden: props.thumbnail, | ||
onZoomOut: handleZoomOut, | ||
onFitToScreen: handleFitToScreen | ||
onFitToScreen: onFitToScreen | ||
})); | ||
@@ -484,3 +759,2 @@ }; | ||
FilePreviewer.propTypes = { | ||
id: PropTypes.any, | ||
file: PropTypes.shape({ | ||
@@ -493,9 +767,6 @@ url: PropTypes.string, | ||
onClick: PropTypes.func, | ||
thumbnail: PropTypes.bool, | ||
height: PropTypes.string, | ||
width: PropTypes.string | ||
thumbnail: PropTypes.bool | ||
}; | ||
FilePreviewer.defaultProps = { | ||
height: '100%', | ||
width: '100%' | ||
onClick: function onClick() {} | ||
}; | ||
@@ -502,0 +773,0 @@ |
@@ -1,5 +0,5 @@ | ||
import { converge, assoc, compose, min, add, propOr, identity, max, subtract, __, modulo, times, either, o, endsWith, propEq, clamp } from 'ramda'; | ||
import { converge, assoc, compose, map, min, add, when, is, of, propOr, identity, max, subtract, __, modulo, pathOr, times, either, o, endsWith, propEq, clamp, pick } from 'ramda'; | ||
import saveFile from 'file-saver'; | ||
import PropTypes from 'prop-types'; | ||
import React, { useState, useCallback, useEffect, useRef } from 'react'; | ||
import React, { useState, useEffect, useCallback, useRef } from 'react'; | ||
import ChevronUp from 'mdi-material-ui/ChevronUp'; | ||
@@ -16,2 +16,54 @@ import ChevronDown from 'mdi-material-ui/ChevronDown'; | ||
function _typeof(obj) { | ||
"@babel/helpers - typeof"; | ||
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { | ||
_typeof = function (obj) { | ||
return typeof obj; | ||
}; | ||
} else { | ||
_typeof = function (obj) { | ||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
}; | ||
} | ||
return _typeof(obj); | ||
} | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { | ||
try { | ||
var info = gen[key](arg); | ||
var value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
Promise.resolve(value).then(_next, _throw); | ||
} | ||
} | ||
function _asyncToGenerator(fn) { | ||
return function () { | ||
var self = this, | ||
args = arguments; | ||
return new Promise(function (resolve, reject) { | ||
var gen = fn.apply(self, args); | ||
function _next(value) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); | ||
} | ||
function _throw(err) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); | ||
} | ||
_next(undefined); | ||
}); | ||
}; | ||
} | ||
function _slicedToArray(arr, i) { | ||
@@ -68,3 +120,3 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
var setZoomIn = converge(assoc('scale'), [compose(min(SCALE_ABSOLUTE_MAX), add(SCALE_FACTOR), propOr(1, 'scale')), identity]); | ||
var setZoomIn = converge(assoc('scale'), [compose(map(min(SCALE_ABSOLUTE_MAX)), map(add(SCALE_FACTOR)), when(is(Number), of), propOr([1], 'scale')), identity]); | ||
@@ -80,3 +132,3 @@ var SCALE_FACTOR$1 = 0.25; | ||
var setZoomOut = converge(assoc('scale'), [compose(max(SCALE_ABSOLUTE_MIN), subtract(__, SCALE_FACTOR$1), propOr(1, 'scale')), identity]); | ||
var setZoomOut = converge(assoc('scale'), [compose(map(max(SCALE_ABSOLUTE_MIN)), map(subtract(__, SCALE_FACTOR$1)), when(is(Number), of), propOr([1], 'scale')), identity]); | ||
@@ -92,19 +144,67 @@ /** | ||
var useViewportSize = function useViewportSize(viewportElem) { | ||
var isClient = (typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object'; | ||
var _useState = useState({ | ||
width: pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}), | ||
_useState2 = _slicedToArray(_useState, 2), | ||
size = _useState2[0], | ||
setSize = _useState2[1]; | ||
useEffect(function () { | ||
setSize({ | ||
width: pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}); | ||
}, viewportElem); | ||
useEffect(function () { | ||
if (!isClient) { | ||
return false; | ||
} | ||
function handleResize() { | ||
setSize({ | ||
width: pathOr(0, ['current', 'clientWidth'], viewportElem), | ||
height: pathOr(0, ['current', 'clientHeight'], viewportElem) | ||
}); | ||
} | ||
window.addEventListener('resize', handleResize); | ||
return function () { | ||
return window.removeEventListener('resize', handleResize); | ||
}; | ||
}, []); | ||
return size; | ||
}; | ||
/** | ||
* Get the `scale` attribute for the "fit to screen" option. | ||
* | ||
* @param {Object} viewportElement | ||
* @param {Object} contentElement | ||
* @param {Object} viewportElem | ||
* @param {Object} contentElem | ||
* @return {Number} | ||
*/ | ||
var getFitToScreenScale = function getFitToScreenScale(viewportElement, contentElement) { | ||
// Get the viewport ratio. | ||
var viewportWidth = viewportElement.clientWidth; | ||
var viewportHeight = viewportElement.clientHeight; | ||
var getFitToScreenScale = function getFitToScreenScale(viewportElem, contentElem) { | ||
// Get the preview bar height. | ||
var previewBarHeight = 52; // Get the viewport ratio. | ||
var viewportWidth = viewportElem.width || viewportElem.clientWidth; | ||
var viewportHeight = (viewportElem.height || viewportElem.clientHeight) - previewBarHeight - 35; | ||
var viewportRatio = viewportWidth / viewportHeight; // Get the content ratio. | ||
var contentWidth = contentElement.offsetWidth; | ||
var contentHeight = contentElement.offsetHeight; | ||
var contentRatio = contentWidth / contentHeight; // Get the scaling ratio in `0.25` steps. | ||
var contentWidth = contentElem.width || contentElem.offsetWidth; | ||
var contentHeight = contentElem.height || contentElem.offsetHeight; | ||
var contentRatio = contentWidth / contentHeight; | ||
if (contentHeight > viewportHeight) { | ||
return viewportHeight / contentHeight; | ||
} | ||
if (contentWidth > viewportWidth) { | ||
return viewportWidth / contentHeight; | ||
} // Get the scaling ratio in `0.25` steps. | ||
return Math.round((viewportRatio > contentRatio ? viewportHeight / contentHeight : viewportWidth / contentWidth) * 4) / 4; | ||
@@ -259,6 +359,5 @@ }; | ||
var handleLoadSucess = useCallback(function (_ref3) { | ||
var numPages = _ref3.numPages; | ||
onLoadSuccess(numPages); | ||
setTotalPages(numPages); | ||
var handleLoadSucess = useCallback(function (pdf) { | ||
onLoadSuccess(pdf); | ||
setTotalPages(pdf.numPages); | ||
}, [onLoadSuccess]); | ||
@@ -273,4 +372,4 @@ return React.createElement(Document, { | ||
index: index, | ||
scale: file.scale, | ||
onPageChange: onPageChange | ||
onPageChange: onPageChange, | ||
scale: Array.isArray(file.scale) ? file.scale[index] : file.scale | ||
}); | ||
@@ -291,12 +390,63 @@ }, totalPages)); | ||
/** | ||
* Get the scale for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
var getScale = pathOr(1, ['scale', 0]); | ||
/** | ||
* Get the rotation for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
var getRotation = propOr(0, 'rotate'); | ||
var getResponseLikePDF = function getResponseLikePDF(imgRef) { | ||
var _imgRef$current = imgRef.current, | ||
clientWidth = _imgRef$current.clientWidth, | ||
clientHeight = _imgRef$current.clientHeight; | ||
return { | ||
numPages: 1, | ||
getPage: function getPage() { | ||
return { | ||
getViewport: function getViewport() { | ||
return { | ||
width: clientWidth, | ||
height: clientHeight | ||
}; | ||
} | ||
}; | ||
} | ||
}; | ||
}; | ||
/** | ||
* `ImageViewer` react component. | ||
* | ||
* @param {Object} params | ||
* @param {Object} params.file | ||
* @param {String} params.file.url | ||
* @param {String} params.file.data | ||
* @param {Array} params.file.scale | ||
* @param {Number} params.file.rotate | ||
* @param {String} params.file.mimeType | ||
* @param {Function} params.onLoadSuccess | ||
*/ | ||
var ImageViewer = function ImageViewer(_ref) { | ||
var file = _ref.file, | ||
onLoadSuccess = _ref.onLoadSuccess; | ||
var imgRef = useRef(null); | ||
useEffect(function () { | ||
onLoadSuccess(1); | ||
onLoadSuccess(getResponseLikePDF(imgRef)); | ||
}, []); | ||
return React.createElement("img", { | ||
ref: imgRef, | ||
src: file.url || "data:".concat(file.mimeType, ";base64,").concat(file.data), | ||
style: { | ||
transform: "rotate(".concat(file.rotate || 0, "deg) scale(").concat(file.scale || 1, ")") | ||
transform: "rotate(".concat(getRotation(file), "deg) scale(").concat(getScale(file), ")") | ||
} | ||
@@ -309,5 +459,6 @@ }); | ||
url: PropTypes.string, | ||
mimeType: PropTypes.string, | ||
data: PropTypes.string, | ||
name: PropTypes.string | ||
scale: PropTypes.array, | ||
rotate: PropTypes.number, | ||
mimeType: PropTypes.string | ||
}), | ||
@@ -329,12 +480,10 @@ onLoadSuccess: PropTypes.func.isRequired | ||
thumbnail = _ref.thumbnail, | ||
contentRef = _ref.contentRef, | ||
viewportRef = _ref.viewportRef, | ||
onLoadSuccess = _ref.onLoadSuccess, | ||
onPageChange = _ref.onPageChange; | ||
containerRef = _ref.containerRef, | ||
onPageChange = _ref.onPageChange, | ||
onLoadSuccess = _ref.onLoadSuccess; | ||
return React.createElement("div", { | ||
ref: viewportRef, | ||
ref: containerRef, | ||
className: classnames(thumbnail ? 'media-thumbnail' : 'preview-content') | ||
}, React.createElement("div", { | ||
className: "preview-file", | ||
ref: contentRef | ||
className: "preview-file" | ||
}, isPDF(file) ? React.createElement(PDFViewer, { | ||
@@ -358,4 +507,2 @@ file: file, | ||
thumbnail: PropTypes.bool, | ||
contentRef: PropTypes.any, | ||
viewportRef: PropTypes.any, | ||
onPageChange: PropTypes.func.isRequired, | ||
@@ -365,6 +512,17 @@ onLoadSuccess: PropTypes.func.isRequired | ||
/** | ||
* `FilePreviewer` react component. | ||
* | ||
* @param {Object} props | ||
* @param {Function} props.onClick | ||
* @param {Boolean} props.thumbnail | ||
* @param {Object} props.file | ||
* @param {String} props.file.url | ||
* @param {String} props.file.data | ||
* @param {String} props.file.name | ||
* @param {String} props.file.mimeType | ||
* @return {Object} | ||
*/ | ||
var FilePreviewer = function FilePreviewer(props) { | ||
var contentRef = useRef(null); | ||
var viewportRef = useRef(null); | ||
var _useState = useState(props.file), | ||
@@ -385,15 +543,22 @@ _useState2 = _slicedToArray(_useState, 2), | ||
useEffect(function () { | ||
var f = props.file; // if file passed is uploaded file, handle it correctly | ||
var _useState7 = useState([]), | ||
_useState8 = _slicedToArray(_useState7, 2), | ||
originalSizes = _useState8[0], | ||
setOriginalSizes = _useState8[1]; | ||
if (props.file && props.file instanceof File) { | ||
f.url = URL.createObjectURL(props.file); | ||
f.mimeType = props.file.type; | ||
} | ||
var _useState9 = useState(false), | ||
_useState10 = _slicedToArray(_useState9, 2), | ||
usingFitToScreen = _useState10[0], | ||
setUsingFitToScreen = _useState10[1]; | ||
setFile(f); | ||
setCurrentPage(0); | ||
}, [props.file]); // Scroll to the pervious page and update the index. | ||
var viewportRef = useRef(null); | ||
var containerRef = useRef(null); | ||
var viewportSize = useViewportSize(viewportRef); | ||
/** | ||
* Handler. Scroll to the pervious page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handlePageUp = function handlePageUp() { | ||
var onPageUp = function onPageUp() { | ||
var previousIndex = clamp(0, totalPages, currentPage - 1); | ||
@@ -403,7 +568,12 @@ setCurrentPage(previousIndex); | ||
viewportRef.current.scrollTop = previousPage.offsetTop - 10; | ||
}; // Scroll to the next page and update the index. | ||
containerRef.current.scrollTop = previousPage.offsetTop - 10; | ||
}; | ||
/** | ||
* Handler. Scroll to the next page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handlePageDown = function handlePageDown() { | ||
var onPageDown = function onPageDown() { | ||
var nextIndex = clamp(0, totalPages, currentPage + 1); | ||
@@ -413,61 +583,166 @@ setCurrentPage(nextIndex); | ||
viewportRef.current.scrollTop = nextPage.offsetTop - 10; | ||
}; // Handlers for rotate and zooming. | ||
containerRef.current.scrollTop = nextPage.offsetTop - 10; | ||
}; | ||
/** | ||
* Handler. Scale all the pages or image | ||
* up by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleZoomIn = function handleZoomIn() { | ||
return setFile(setZoomIn(file)); | ||
var onZoomIn = function onZoomIn() { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomIn(file)); | ||
}; | ||
/** | ||
* Handler. Scale all the pages or image | ||
* down by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleZoomOut = function handleZoomOut() { | ||
return setFile(setZoomOut(file)); | ||
var onZoomOut = function onZoomOut() { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomOut(file)); | ||
}; | ||
/** | ||
* Handler. Rotate the PDF (all pages) or Image. | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleRotate = function handleRotate() { | ||
return setFile(setNewRotation(file)); | ||
var onRotate = function onRotate() { | ||
setFile(setNewRotation(file)); | ||
}; | ||
/** | ||
* Handler. Fit to screen. | ||
* | ||
* (This activates the "using fit to screen" mode, which | ||
* makes the document or image resize based on the viewport size.) | ||
* | ||
* @return {Void} | ||
*/ | ||
var handleDownload = function handleDownload() { | ||
var onFitToScreen = function onFitToScreen() { | ||
setUsingFitToScreen(true); // Get the "fit to screen" scale. | ||
var newScale = originalSizes.map(function (originalSize) { | ||
return getFitToScreenScale(viewportSize, originalSize); | ||
}); | ||
setFile(function (prevValue) { | ||
return assoc('scale', newScale, prevValue); | ||
}); | ||
}; | ||
/** | ||
* Handler. Download the current file, it can be a | ||
* PDF or Image, as `url` or `base64` content. | ||
* | ||
* @return {Void} | ||
*/ | ||
var onDownload = function onDownload() { | ||
var url = file.url || "data:".concat(file.mimeType, ";base64,").concat(file.data); | ||
return saveFile(url, file.name || 'download.pdf'); | ||
}; | ||
/** | ||
* Handler. Callback after the PDF gets | ||
* processed successfully by `react-pdf`. | ||
* | ||
* @param {Object} pdf | ||
* @return {Void} | ||
*/ | ||
var handleFitToScreen = function handleFitToScreen() { | ||
// Get the "fit to screen" scale. | ||
var newScale = getFitToScreenScale(viewportRef.current, contentRef.current); | ||
setFile(assoc('scale', newScale, file)); | ||
}; | ||
if (!file) { | ||
return null; | ||
} | ||
var onLoadSuccess = function onLoadSuccess(pdf) { | ||
// Wait until the original sizes gets calculated. | ||
var promises = times( /*#__PURE__*/function () { | ||
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(i) { | ||
var page; | ||
return regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.next = 2; | ||
return pdf.getPage(i + 1); | ||
case 2: | ||
page = _context.sent; | ||
return _context.abrupt("return", pick(['width', 'height'], page.getViewport({ | ||
scale: 1 | ||
}))); | ||
case 4: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee); | ||
})); | ||
return function (_x) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(), pdf.numPages); // Set the original sizes for this file. | ||
Promise.all(promises).then(setOriginalSizes); | ||
setTotalPages(pdf.numPages); | ||
setUsingFitToScreen(true); | ||
}; // Set an effect to process the file resizing when the | ||
// viewport sizes changes and it's using "fit to screen". | ||
useEffect(function () { | ||
// Check if it's using "fit to screen". | ||
if (usingFitToScreen) { | ||
// Get the "fit to screen" scale. | ||
var newScale = originalSizes.map(function (originalSize) { | ||
return getFitToScreenScale(viewportSize, originalSize); | ||
}); // Update the scale. | ||
setFile(function (prevValue) { | ||
return assoc('scale', newScale, prevValue); | ||
}); | ||
} | ||
}, [usingFitToScreen, originalSizes, viewportSize]); // Reset all the attributes when the file prop changes. | ||
useEffect(function () { | ||
var file = props.file; // if file passed is uploaded file, handle it correctly | ||
if (props.file && props.file instanceof File) { | ||
file.url = URL.createObjectURL(props.file); | ||
file.mimeType = props.file.type; | ||
} | ||
setFile(file); | ||
setTotalPages(1); | ||
setCurrentPage(0); | ||
}, [props.file]); | ||
return React.createElement("div", { | ||
id: props.id, | ||
ref: viewportRef, | ||
onClick: props.onClick, | ||
className: "preview-wrapper", | ||
style: { | ||
height: props.height, | ||
width: props.width | ||
} | ||
className: "preview-wrapper" | ||
}, React.createElement(PreviewBar, { | ||
onPageUp: handlePageUp, | ||
onPageUp: onPageUp, | ||
onRotate: onRotate, | ||
totalPages: totalPages, | ||
onRotate: handleRotate, | ||
onDownload: onDownload, | ||
onPageDown: onPageDown, | ||
hidden: props.thumbnail, | ||
currentPage: currentPage, | ||
onDownload: handleDownload, | ||
onPageDown: handlePageDown | ||
currentPage: currentPage | ||
}), React.createElement(ViewportContent, { | ||
file: file, | ||
contentRef: contentRef, | ||
viewportRef: viewportRef, | ||
containerRef: containerRef, | ||
thumbnail: props.thumbnail, | ||
onLoadSuccess: setTotalPages, | ||
onPageChange: setCurrentPage | ||
onPageChange: setCurrentPage, | ||
onLoadSuccess: onLoadSuccess | ||
}), React.createElement(ViewportControl, { | ||
onZoomIn: handleZoomIn, | ||
onZoomIn: onZoomIn, | ||
onZoomOut: onZoomOut, | ||
hidden: props.thumbnail, | ||
onZoomOut: handleZoomOut, | ||
onFitToScreen: handleFitToScreen | ||
onFitToScreen: onFitToScreen | ||
})); | ||
@@ -477,3 +752,2 @@ }; | ||
FilePreviewer.propTypes = { | ||
id: PropTypes.any, | ||
file: PropTypes.shape({ | ||
@@ -486,9 +760,6 @@ url: PropTypes.string, | ||
onClick: PropTypes.func, | ||
thumbnail: PropTypes.bool, | ||
height: PropTypes.string, | ||
width: PropTypes.string | ||
thumbnail: PropTypes.bool | ||
}; | ||
FilePreviewer.defaultProps = { | ||
height: '100%', | ||
width: '100%' | ||
onClick: function onClick() {} | ||
}; | ||
@@ -495,0 +766,0 @@ |
{ | ||
"name": "react-file-previewer", | ||
"version": "0.2.3", | ||
"version": "0.3.1", | ||
"main": "dist/index.cjs.js", | ||
@@ -5,0 +5,0 @@ "module": "dist/index.es.js", |
import * as R from 'ramda'; | ||
import saveFile from 'file-saver'; | ||
import PropTypes from 'prop-types'; | ||
import React, { useEffect, useRef, useState } from 'react'; | ||
import React, { useState, useEffect, useRef } from 'react'; | ||
@@ -9,2 +9,3 @@ import setZoomIn from './utils/setZoomIn'; | ||
import setNewRotation from './utils/setNewRotation'; | ||
import useViewportSize from './utils/useViewportSize'; | ||
import getFitToScreenScale from './utils/getFitToScreenScale'; | ||
@@ -16,28 +17,32 @@ | ||
/** | ||
* `FilePreviewer` react component. | ||
* | ||
* @param {Object} props | ||
* @param {Function} props.onClick | ||
* @param {Boolean} props.thumbnail | ||
* @param {Object} props.file | ||
* @param {String} props.file.url | ||
* @param {String} props.file.data | ||
* @param {String} props.file.name | ||
* @param {String} props.file.mimeType | ||
* @return {Object} | ||
*/ | ||
const FilePreviewer = props => { | ||
const contentRef = useRef(null); | ||
const viewportRef = useRef(null); | ||
const [file, setFile] = useState(props.file); | ||
const [totalPages, setTotalPages] = useState(1); | ||
const [currentPage, setCurrentPage] = useState(0); | ||
const [originalSizes, setOriginalSizes] = useState([]); | ||
const [usingFitToScreen, setUsingFitToScreen] = useState(false); | ||
useEffect(() => { | ||
let f = props.file; | ||
const viewportRef = useRef(null); | ||
const containerRef = useRef(null); | ||
const viewportSize = useViewportSize(viewportRef); | ||
// if file passed is uploaded file, handle it correctly | ||
if (props.file && props.file instanceof File) { | ||
f.url = URL.createObjectURL(props.file); | ||
f.mimeType = props.file.type; | ||
} | ||
setFile(f); | ||
setCurrentPage(0); | ||
}, [props.file]); | ||
// Scroll to the pervious page and update the index. | ||
const handlePageUp = () => { | ||
/** | ||
* Handler. Scroll to the pervious page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onPageUp = () => { | ||
const previousIndex = R.clamp(0, totalPages, currentPage - 1); | ||
@@ -52,7 +57,11 @@ | ||
// Scroll the viewport to the page position. | ||
viewportRef.current.scrollTop = previousPage.offsetTop - 10; | ||
containerRef.current.scrollTop = previousPage.offsetTop - 10; | ||
}; | ||
// Scroll to the next page and update the index. | ||
const handlePageDown = () => { | ||
/** | ||
* Handler. Scroll to the next page and update the index. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onPageDown = () => { | ||
const nextIndex = R.clamp(0, totalPages, currentPage + 1); | ||
@@ -65,47 +74,128 @@ | ||
// Scroll the viewport to the page position. | ||
viewportRef.current.scrollTop = nextPage.offsetTop - 10; | ||
containerRef.current.scrollTop = nextPage.offsetTop - 10; | ||
}; | ||
// Handlers for rotate and zooming. | ||
const handleZoomIn = () => setFile(setZoomIn(file)); | ||
const handleZoomOut = () => setFile(setZoomOut(file)); | ||
const handleRotate = () => setFile(setNewRotation(file)); | ||
/** | ||
* Handler. Scale all the pages or image | ||
* up by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onZoomIn = () => { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomIn(file)); | ||
}; | ||
const handleDownload = () => { | ||
const url = file.url || `data:${file.mimeType};base64,${file.data}`; | ||
return saveFile(url, file.name || 'download.pdf'); | ||
/** | ||
* Handler. Scale all the pages or image | ||
* down by a factor of 0.25. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onZoomOut = () => { | ||
setUsingFitToScreen(false); | ||
setFile(setZoomOut(file)); | ||
}; | ||
const handleFitToScreen = () => { | ||
/** | ||
* Handler. Rotate the PDF (all pages) or Image. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onRotate = () => { | ||
setFile(setNewRotation(file)); | ||
}; | ||
/** | ||
* Handler. Fit to screen. | ||
* | ||
* (This activates the "using fit to screen" mode, which | ||
* makes the document or image resize based on the viewport size.) | ||
* | ||
* @return {Void} | ||
*/ | ||
const onFitToScreen = () => { | ||
setUsingFitToScreen(true); | ||
// Get the "fit to screen" scale. | ||
const newScale = getFitToScreenScale( | ||
viewportRef.current, | ||
contentRef.current, | ||
const newScale = originalSizes.map(originalSize => | ||
getFitToScreenScale(viewportSize, originalSize), | ||
); | ||
setFile(R.assoc('scale', newScale, file)); | ||
setFile(prevValue => R.assoc('scale', newScale, prevValue)); | ||
}; | ||
if (!file) { | ||
return null; | ||
} | ||
/** | ||
* Handler. Download the current file, it can be a | ||
* PDF or Image, as `url` or `base64` content. | ||
* | ||
* @return {Void} | ||
*/ | ||
const onDownload = () => { | ||
const url = file.url || `data:${file.mimeType};base64,${file.data}`; | ||
return saveFile(url, file.name || 'download.pdf'); | ||
}; | ||
/** | ||
* Handler. Callback after the PDF gets | ||
* processed successfully by `react-pdf`. | ||
* | ||
* @param {Object} pdf | ||
* @return {Void} | ||
*/ | ||
const onLoadSuccess = pdf => { | ||
// Wait until the original sizes gets calculated. | ||
const promises = R.times(async i => { | ||
// Wait until the original sizes gets calculated. | ||
const page = await pdf.getPage(i + 1); | ||
return R.pick(['width', 'height'], page.getViewport({ scale: 1 })); | ||
}, pdf.numPages); | ||
// Set the original sizes for this file. | ||
Promise.all(promises).then(setOriginalSizes); | ||
setTotalPages(pdf.numPages); | ||
setUsingFitToScreen(true); | ||
}; | ||
// Set an effect to process the file resizing when the | ||
// viewport sizes changes and it's using "fit to screen". | ||
useEffect(() => { | ||
// Check if it's using "fit to screen". | ||
if (usingFitToScreen) { | ||
// Get the "fit to screen" scale. | ||
const newScale = originalSizes.map(originalSize => | ||
getFitToScreenScale(viewportSize, originalSize), | ||
); | ||
// Update the scale. | ||
setFile(prevValue => R.assoc('scale', newScale, prevValue)); | ||
} | ||
}, [usingFitToScreen, originalSizes, viewportSize]); | ||
// Reset all the attributes when the file prop changes. | ||
useEffect(() => { | ||
const file = props.file; | ||
// if file passed is uploaded file, handle it correctly | ||
if (props.file && props.file instanceof File) { | ||
file.url = URL.createObjectURL(props.file); | ||
file.mimeType = props.file.type; | ||
} | ||
setFile(file); | ||
setTotalPages(1); | ||
setCurrentPage(0); | ||
}, [props.file]); | ||
return ( | ||
<div | ||
id={props.id} | ||
onClick={props.onClick} | ||
className="preview-wrapper" | ||
style={{ | ||
height: props.height, | ||
width: props.width, | ||
}} | ||
> | ||
<div ref={viewportRef} onClick={props.onClick} className="preview-wrapper"> | ||
<PreviewBar | ||
onPageUp={handlePageUp} | ||
onPageUp={onPageUp} | ||
onRotate={onRotate} | ||
totalPages={totalPages} | ||
onRotate={handleRotate} | ||
onDownload={onDownload} | ||
onPageDown={onPageDown} | ||
hidden={props.thumbnail} | ||
currentPage={currentPage} | ||
onDownload={handleDownload} | ||
onPageDown={handlePageDown} | ||
/> | ||
@@ -115,14 +205,13 @@ | ||
file={file} | ||
contentRef={contentRef} | ||
viewportRef={viewportRef} | ||
containerRef={containerRef} | ||
thumbnail={props.thumbnail} | ||
onLoadSuccess={setTotalPages} | ||
onPageChange={setCurrentPage} | ||
onLoadSuccess={onLoadSuccess} | ||
/> | ||
<ViewportControl | ||
onZoomIn={handleZoomIn} | ||
onZoomIn={onZoomIn} | ||
onZoomOut={onZoomOut} | ||
hidden={props.thumbnail} | ||
onZoomOut={handleZoomOut} | ||
onFitToScreen={handleFitToScreen} | ||
onFitToScreen={onFitToScreen} | ||
/> | ||
@@ -134,3 +223,2 @@ </div> | ||
FilePreviewer.propTypes = { | ||
id: PropTypes.any, | ||
file: PropTypes.shape({ | ||
@@ -144,11 +232,8 @@ url: PropTypes.string, | ||
thumbnail: PropTypes.bool, | ||
height: PropTypes.string, | ||
width: PropTypes.string, | ||
}; | ||
FilePreviewer.defaultProps = { | ||
height: '100%', | ||
width: '100%', | ||
onClick: () => {}, | ||
}; | ||
export default FilePreviewer; |
@@ -1,7 +0,52 @@ | ||
import React, { useEffect } from 'react'; | ||
import { propOr, pathOr } from 'ramda'; | ||
import PropTypes from 'prop-types'; | ||
import React, { useRef, useEffect } from 'react'; | ||
/** | ||
* Get the scale for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
const getScale = pathOr(1, ['scale', 0]); | ||
/** | ||
* Get the rotation for this image. | ||
* | ||
* @param {Object} file | ||
* @return {Number} | ||
*/ | ||
const getRotation = propOr(0, 'rotate'); | ||
const getResponseLikePDF = imgRef => { | ||
const { clientWidth, clientHeight } = imgRef.current; | ||
return { | ||
numPages: 1, | ||
getPage: () => ({ | ||
getViewport: () => ({ | ||
width: clientWidth, | ||
height: clientHeight, | ||
}), | ||
}), | ||
}; | ||
}; | ||
/** | ||
* `ImageViewer` react component. | ||
* | ||
* @param {Object} params | ||
* @param {Object} params.file | ||
* @param {String} params.file.url | ||
* @param {String} params.file.data | ||
* @param {Array} params.file.scale | ||
* @param {Number} params.file.rotate | ||
* @param {String} params.file.mimeType | ||
* @param {Function} params.onLoadSuccess | ||
*/ | ||
const ImageViewer = ({ file, onLoadSuccess }) => { | ||
const imgRef = useRef(null); | ||
useEffect(() => { | ||
onLoadSuccess(1); | ||
onLoadSuccess(getResponseLikePDF(imgRef)); | ||
}, []); | ||
@@ -11,5 +56,6 @@ | ||
<img | ||
ref={imgRef} | ||
src={file.url || `data:${file.mimeType};base64,${file.data}`} | ||
style={{ | ||
transform: `rotate(${file.rotate || 0}deg) scale(${file.scale || 1})`, | ||
transform: `rotate(${getRotation(file)}deg) scale(${getScale(file)})`, | ||
}} | ||
@@ -23,5 +69,6 @@ /> | ||
url: PropTypes.string, | ||
data: PropTypes.string, | ||
scale: PropTypes.array, | ||
rotate: PropTypes.number, | ||
mimeType: PropTypes.string, | ||
data: PropTypes.string, | ||
name: PropTypes.string, | ||
}), | ||
@@ -28,0 +75,0 @@ onLoadSuccess: PropTypes.func.isRequired, |
@@ -1,2 +0,1 @@ | ||
import React, { useEffect, useCallback, useState } from 'react'; | ||
import * as R from 'ramda'; | ||
@@ -6,2 +5,3 @@ import PropTypes from 'prop-types'; | ||
import { useInView } from 'react-intersection-observer'; | ||
import React, { useEffect, useCallback, useState } from 'react'; | ||
@@ -26,5 +26,5 @@ const PDFPage = ({ index, onPageChange, scale = 1 }) => { | ||
const handleLoadSucess = useCallback( | ||
({ numPages }) => { | ||
onLoadSuccess(numPages); | ||
setTotalPages(numPages); | ||
pdf => { | ||
onLoadSuccess(pdf); | ||
setTotalPages(pdf.numPages); | ||
}, | ||
@@ -45,4 +45,4 @@ [onLoadSuccess], | ||
index={index} | ||
scale={file.scale} | ||
onPageChange={onPageChange} | ||
scale={Array.isArray(file.scale) ? file.scale[index] : file.scale} | ||
/> | ||
@@ -49,0 +49,0 @@ ), |
/** | ||
* Get the `scale` attribute for the "fit to screen" option. | ||
* | ||
* @param {Object} viewportElement | ||
* @param {Object} contentElement | ||
* @param {Object} viewportElem | ||
* @param {Object} contentElem | ||
* @return {Number} | ||
*/ | ||
const getFitToScreenScale = (viewportElement, contentElement) => { | ||
const getFitToScreenScale = (viewportElem, contentElem) => { | ||
// Get the preview bar height. | ||
const previewBarHeight = 52; | ||
// Get the viewport ratio. | ||
const viewportWidth = viewportElement.clientWidth; | ||
const viewportHeight = viewportElement.clientHeight; | ||
const viewportWidth = viewportElem.width || viewportElem.clientWidth; | ||
const viewportHeight = | ||
(viewportElem.height || viewportElem.clientHeight) - previewBarHeight - 35; | ||
const viewportRatio = viewportWidth / viewportHeight; | ||
// Get the content ratio. | ||
const contentWidth = contentElement.offsetWidth; | ||
const contentHeight = contentElement.offsetHeight; | ||
const contentWidth = contentElem.width || contentElem.offsetWidth; | ||
const contentHeight = contentElem.height || contentElem.offsetHeight; | ||
const contentRatio = contentWidth / contentHeight; | ||
if (contentHeight > viewportHeight) { | ||
return viewportHeight / contentHeight; | ||
} | ||
if (contentWidth > viewportWidth) { | ||
return viewportWidth / contentHeight; | ||
} | ||
// Get the scaling ratio in `0.25` steps. | ||
@@ -20,0 +34,0 @@ return ( |
@@ -14,5 +14,6 @@ import * as R from 'ramda'; | ||
R.compose( | ||
R.min(SCALE_ABSOLUTE_MAX), | ||
R.add(SCALE_FACTOR), | ||
R.propOr(1, 'scale'), | ||
R.map(R.min(SCALE_ABSOLUTE_MAX)), | ||
R.map(R.add(SCALE_FACTOR)), | ||
R.when(R.is(Number), R.of), | ||
R.propOr([1], 'scale'), | ||
), | ||
@@ -19,0 +20,0 @@ R.identity, |
@@ -14,5 +14,6 @@ import * as R from 'ramda'; | ||
R.compose( | ||
R.max(SCALE_ABSOLUTE_MIN), | ||
R.subtract(R.__, SCALE_FACTOR), | ||
R.propOr(1, 'scale'), | ||
R.map(R.max(SCALE_ABSOLUTE_MIN)), | ||
R.map(R.subtract(R.__, SCALE_FACTOR)), | ||
R.when(R.is(Number), R.of), | ||
R.propOr([1], 'scale'), | ||
), | ||
@@ -19,0 +20,0 @@ R.identity, |
@@ -23,12 +23,11 @@ import React from 'react'; | ||
thumbnail, | ||
contentRef, | ||
viewportRef, | ||
containerRef, | ||
onPageChange, | ||
onLoadSuccess, | ||
onPageChange, | ||
}) => ( | ||
<div | ||
ref={viewportRef} | ||
ref={containerRef} | ||
className={classnames(thumbnail ? 'media-thumbnail' : 'preview-content')} | ||
> | ||
<div className="preview-file" ref={contentRef}> | ||
<div className="preview-file"> | ||
{isPDF(file) ? ( | ||
@@ -55,4 +54,2 @@ <PDFViewer | ||
thumbnail: PropTypes.bool, | ||
contentRef: PropTypes.any, | ||
viewportRef: PropTypes.any, | ||
onPageChange: PropTypes.func.isRequired, | ||
@@ -59,0 +56,0 @@ onLoadSuccess: PropTypes.func.isRequired, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
142034
29
2201