UNPKG

@seafile/react-image-lightbox

Version:
1,495 lines (1,424 loc) 79.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); var PropTypes = _interopDefault(require('prop-types')); var Modal = _interopDefault(require('react-modal')); function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } /** * Placeholder for future translate functionality */ function translate(str) { var replaceStrings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (!str) { return ''; } var translated = str; if (replaceStrings) { Object.keys(replaceStrings).forEach(function (placeholder) { translated = translated.replace(placeholder, replaceStrings[placeholder]); }); } return translated; } function getWindowWidth() { return typeof global.window !== 'undefined' ? global.window.innerWidth : 0; } function getWindowHeight() { return typeof global.window !== 'undefined' ? global.window.innerHeight : 0; } var isMobile = typeof window !== 'undefined' && (window.innerWidth < 768 || navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null); // Get the highest window context that isn't cross-origin // (When in an iframe) function getHighestSafeWindowContext() { var self = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : global.window.self; var referrer = self.document.referrer; // If we reached the top level, return self if (self === global.window.top || !referrer) { return self; } var getOrigin = function getOrigin(href) { return href.match(/(.*\/\/.*?)(\/|$)/)[1]; }; // If parent is the same origin, we can move up one context // Reference: https://stackoverflow.com/a/21965342/1601953 if (getOrigin(self.location.href) === getOrigin(referrer)) { return getHighestSafeWindowContext(self.parent); } // If a different origin, we consider the current level // as the top reachable one return self; } // Min image zoom level var MIN_ZOOM_LEVEL = 0; // Max image zoom level var MAX_ZOOM_LEVEL = 5; // Size ratio between previous and next zoom levels var ZOOM_RATIO = 1.2; // How much to increase/decrease the zoom level when the zoom buttons are clicked var ZOOM_BUTTON_INCREMENT_SIZE = 1; // Used to judge the amount of vertical scroll needed to initiate a zoom action var WHEEL_MOVE_Y_THRESHOLD = 1; var KEYS = { ESC: 27, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40 }; // Actions var ACTION_NONE = 0; var ACTION_MOVE = 1; var ACTION_SWIPE = 2; var ACTION_PINCH = 3; // Events source var SOURCE_ANY = 0; var SOURCE_MOUSE = 1; var SOURCE_TOUCH = 2; var SOURCE_POINTER = 3; // Minimal swipe distance var MIN_SWIPE_DISTANCE = 200; var ReactImageLightbox = /*#__PURE__*/function (_Component) { _inherits(ReactImageLightbox, _Component); var _super = _createSuper(ReactImageLightbox); function ReactImageLightbox(props) { var _this; _classCallCheck(this, ReactImageLightbox); _this = _super.call(this, props); _defineProperty(_assertThisInitialized(_this), "handleWheel", function (event) { event.preventDefault(); }); _this.state = { //----------------------------- // Animation //----------------------------- // Lightbox is closing // When Lightbox is mounted, if animation is enabled it will open with the reverse of the closing animation isClosing: !props.animationDisabled, // Component parts should animate (e.g., when images are moving, or image is being zoomed) shouldAnimate: false, //----------------------------- // Zoom settings //----------------------------- // Zoom level of image zoomLevel: MIN_ZOOM_LEVEL, //----------------------------- // Image position settings //----------------------------- // Horizontal offset from center offsetX: 0, // Vertical offset from center offsetY: 0, // image load error for srcType loadErrorStatus: {}, // rotate image degree rotateDeg: 0 }; // Refs _this.outerEl = /*#__PURE__*/React__default.createRef(); _this.zoomInBtn = /*#__PURE__*/React__default.createRef(); _this.zoomOutBtn = /*#__PURE__*/React__default.createRef(); _this.caption = /*#__PURE__*/React__default.createRef(); _this.closeIfClickInner = _this.closeIfClickInner.bind(_assertThisInitialized(_this)); _this.handleImageDoubleClick = _this.handleImageDoubleClick.bind(_assertThisInitialized(_this)); _this.handleImageClick = _this.handleImageClick.bind(_assertThisInitialized(_this)); _this.handleImageMouseWheel = _this.handleImageMouseWheel.bind(_assertThisInitialized(_this)); _this.handleKeyInput = _this.handleKeyInput.bind(_assertThisInitialized(_this)); _this.handleMouseUp = _this.handleMouseUp.bind(_assertThisInitialized(_this)); _this.handleMouseDown = _this.handleMouseDown.bind(_assertThisInitialized(_this)); _this.handleMouseMove = _this.handleMouseMove.bind(_assertThisInitialized(_this)); _this.handleOuterMousewheel = _this.handleOuterMousewheel.bind(_assertThisInitialized(_this)); _this.handleTouchStart = _this.handleTouchStart.bind(_assertThisInitialized(_this)); _this.handleTouchMove = _this.handleTouchMove.bind(_assertThisInitialized(_this)); _this.handleTouchEnd = _this.handleTouchEnd.bind(_assertThisInitialized(_this)); _this.handlePointerEvent = _this.handlePointerEvent.bind(_assertThisInitialized(_this)); _this.handleCaptionMousewheel = _this.handleCaptionMousewheel.bind(_assertThisInitialized(_this)); _this.handleWindowResize = _this.handleWindowResize.bind(_assertThisInitialized(_this)); _this.handleZoomInButtonClick = _this.handleZoomInButtonClick.bind(_assertThisInitialized(_this)); _this.handleZoomOutButtonClick = _this.handleZoomOutButtonClick.bind(_assertThisInitialized(_this)); _this.requestClose = _this.requestClose.bind(_assertThisInitialized(_this)); _this.requestMoveNext = _this.requestMoveNext.bind(_assertThisInitialized(_this)); _this.requestMovePrev = _this.requestMovePrev.bind(_assertThisInitialized(_this)); _this.requestMoveUp = _this.requestMoveUp.bind(_assertThisInitialized(_this)); _this.requestMoveDown = _this.requestMoveDown.bind(_assertThisInitialized(_this)); _this.rotateImage = _this.rotateImage.bind(_assertThisInitialized(_this)); _this.isMobile = isMobile; return _this; } // eslint-disable-next-line camelcase _createClass(ReactImageLightbox, [{ key: "UNSAFE_componentWillMount", value: function UNSAFE_componentWillMount() { // Timeouts - always clear it before umount this.timeouts = []; // Current action this.currentAction = ACTION_NONE; // Events source this.eventsSource = SOURCE_ANY; // Empty pointers list this.pointerList = []; // Prevent inner close this.preventInnerClose = false; this.preventInnerCloseTimeout = null; // Used to disable animation when changing props.mainSrc|nextSrc|prevSrc this.keyPressed = false; // Used to store load state / dimensions of images this.imageCache = {}; // Time the last keydown event was called (used in keyboard action rate limiting) this.lastKeyDownTime = 0; // Used for debouncing window resize event this.resizeTimeout = null; // Used to determine when actions are triggered by the scroll wheel this.wheelActionTimeout = null; this.resetScrollTimeout = null; this.scrollX = 0; this.scrollY = 0; // Used in panning zoomed images this.moveStartX = 0; this.moveStartY = 0; this.moveStartOffsetX = 0; this.moveStartOffsetY = 0; // Used to swipe this.swipeStartX = 0; this.swipeStartY = 0; this.swipeEndX = 0; this.swipeEndY = 0; // Used to pinch this.pinchTouchList = null; this.pinchDistance = 0; // Used to differentiate between images with identical src this.keyCounter = 0; // Used to detect a move when all src's remain unchanged (four or more of the same image in a row) this.moveRequested = false; if (!this.props.animationDisabled) { // Make opening animation play this.setState({ isClosing: false }); } } }, { key: "componentDidMount", value: function componentDidMount() { var _this2 = this; // Prevents cross-origin errors when using a cross-origin iframe this.windowContext = getHighestSafeWindowContext(); this.listeners = { resize: this.handleWindowResize, mouseup: this.handleMouseUp, touchend: this.handleTouchEnd, touchcancel: this.handleTouchEnd, pointerdown: this.handlePointerEvent, pointermove: this.handlePointerEvent, pointerup: this.handlePointerEvent, pointercancel: this.handlePointerEvent }; Object.keys(this.listeners).forEach(function (type) { _this2.windowContext.addEventListener(type, _this2.listeners[type]); }); document.addEventListener('wheel', this.handleWheel, { passive: false }); this.loadAllImages(); } // eslint-disable-next-line camelcase }, { key: "UNSAFE_componentWillReceiveProps", value: function UNSAFE_componentWillReceiveProps(nextProps) { var _this3 = this; // Iterate through the source types for prevProps and nextProps to // determine if any of the sources changed var sourcesChanged = false; var prevSrcDict = {}; var nextSrcDict = {}; this.getSrcTypes().forEach(function (srcType) { if (_this3.props[srcType.name] !== nextProps[srcType.name]) { sourcesChanged = true; prevSrcDict[_this3.props[srcType.name]] = true; nextSrcDict[nextProps[srcType.name]] = true; } }); if (sourcesChanged || this.moveRequested) { // Reset the loaded state for images not rendered next Object.keys(prevSrcDict).forEach(function (prevSrc) { if (!(prevSrc in nextSrcDict) && prevSrc in _this3.imageCache) { _this3.imageCache[prevSrc].loaded = false; } }); this.moveRequested = false; // Load any new images this.loadAllImages(nextProps); } } }, { key: "shouldComponentUpdate", value: function shouldComponentUpdate() { // Wait for move... return !this.moveRequested; } }, { key: "componentWillUnmount", value: function componentWillUnmount() { var _this4 = this; this.didUnmount = true; Object.keys(this.listeners).forEach(function (type) { _this4.windowContext.removeEventListener(type, _this4.listeners[type]); }); document.removeEventListener('wheel', this.handleWheel, { passive: false }); this.timeouts.forEach(function (tid) { return clearTimeout(tid); }); } }, { key: "setTimeout", value: function (_setTimeout) { function setTimeout(_x, _x2) { return _setTimeout.apply(this, arguments); } setTimeout.toString = function () { return _setTimeout.toString(); }; return setTimeout; }(function (func, time) { var _this5 = this; var id = setTimeout(function () { _this5.timeouts = _this5.timeouts.filter(function (tid) { return tid !== id; }); func(); }, time); this.timeouts.push(id); return id; }) }, { key: "setPreventInnerClose", value: function setPreventInnerClose() { var _this6 = this; if (this.preventInnerCloseTimeout) { this.clearTimeout(this.preventInnerCloseTimeout); } this.preventInnerClose = true; this.preventInnerCloseTimeout = this.setTimeout(function () { _this6.preventInnerClose = false; _this6.preventInnerCloseTimeout = null; }, 100); } // Get info for the best suited image to display with the given srcType }, { key: "getBestImageForType", value: function getBestImageForType(srcType) { var imageSrc = this.props[srcType]; var fitSizes = {}; if (this.isImageLoaded(imageSrc)) { // Use full-size image if available fitSizes = this.getFitSizes(this.imageCache[imageSrc].width, this.imageCache[imageSrc].height); } else if (this.isImageLoaded(this.props["".concat(srcType, "Thumbnail")])) { // Fall back to using thumbnail if the image has not been loaded imageSrc = this.props["".concat(srcType, "Thumbnail")]; fitSizes = this.getFitSizes(this.imageCache[imageSrc].width, this.imageCache[imageSrc].height, true); } else { return null; } return { src: imageSrc, height: this.imageCache[imageSrc].height, width: this.imageCache[imageSrc].width, targetHeight: fitSizes.height, targetWidth: fitSizes.width }; } // Get sizing for when an image is larger than the window }, { key: "getFitSizes", value: function getFitSizes(width, height, stretch) { var boxSize = this.getLightboxRect(); // Padding (px) between the edge of the window and the lightbox var imagePadding = this.isMobile ? 0 : 70; var maxHeight = boxSize.height - imagePadding * 2; var maxWidth = boxSize.width - imagePadding * 2; if (!stretch) { maxHeight = Math.min(maxHeight, height); maxWidth = Math.min(maxWidth, width); } var maxRatio = maxWidth / maxHeight; var srcRatio = width / height; if (maxRatio > srcRatio) { // height is the constraining dimension of the photo return { width: width * maxHeight / height, height: maxHeight }; } return { width: maxWidth, height: height * maxWidth / width }; } }, { key: "getMaxOffsets", value: function getMaxOffsets() { var zoomLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.zoomLevel; var currentImageInfo = this.getBestImageForType('mainSrc'); if (currentImageInfo === null) { return { maxX: 0, minX: 0, maxY: 0, minY: 0 }; } var boxSize = this.getLightboxRect(); var zoomMultiplier = this.getZoomMultiplier(zoomLevel); var maxX = 0; if (zoomMultiplier * currentImageInfo.width - boxSize.width < 0) { // if there is still blank space in the X dimension, don't limit except to the opposite edge maxX = (boxSize.width - zoomMultiplier * currentImageInfo.width) / 2; } else { maxX = (zoomMultiplier * currentImageInfo.width - boxSize.width) / 2; } var maxY = 0; if (zoomMultiplier * currentImageInfo.height - boxSize.height < 0) { // if there is still blank space in the Y dimension, don't limit except to the opposite edge maxY = (boxSize.height - zoomMultiplier * currentImageInfo.height) / 2; } else { maxY = (zoomMultiplier * currentImageInfo.height - boxSize.height) / 2; } return { maxX: maxX, maxY: maxY, minX: -1 * maxX, minY: -1 * maxY }; } // Get image src types }, { key: "getSrcTypes", value: function getSrcTypes() { return [{ name: 'mainSrc', keyEnding: "i".concat(this.keyCounter) }, { name: 'mainSrcThumbnail', keyEnding: "t".concat(this.keyCounter) }, { name: 'nextSrc', keyEnding: "i".concat(this.keyCounter + 1) }, { name: 'nextSrcThumbnail', keyEnding: "t".concat(this.keyCounter + 1) }, { name: 'prevSrc', keyEnding: "i".concat(this.keyCounter - 1) }, { name: 'prevSrcThumbnail', keyEnding: "t".concat(this.keyCounter - 1) }]; } /** * Get sizing when the image is scaled */ }, { key: "getZoomMultiplier", value: function getZoomMultiplier() { var zoomLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.zoomLevel; return Math.pow(ZOOM_RATIO, zoomLevel); } /** * Get the size of the lightbox in pixels */ }, { key: "getLightboxRect", value: function getLightboxRect() { if (this.outerEl.current) { return this.outerEl.current.getBoundingClientRect(); } return { width: getWindowWidth(), height: getWindowHeight(), top: 0, right: 0, bottom: 0, left: 0 }; } }, { key: "clearTimeout", value: function (_clearTimeout) { function clearTimeout(_x3) { return _clearTimeout.apply(this, arguments); } clearTimeout.toString = function () { return _clearTimeout.toString(); }; return clearTimeout; }(function (id) { this.timeouts = this.timeouts.filter(function (tid) { return tid !== id; }); clearTimeout(id); } // Change zoom level ) }, { key: "changeZoom", value: function changeZoom(zoomLevel, clientX, clientY) { // Ignore if zoom disabled if (!this.props.enableZoom) { return; } // Constrain zoom level to the set bounds var nextZoomLevel = Math.max(MIN_ZOOM_LEVEL, Math.min(MAX_ZOOM_LEVEL, zoomLevel)); // Ignore requests that don't change the zoom level if (nextZoomLevel === this.state.zoomLevel) { return; } if (nextZoomLevel === MIN_ZOOM_LEVEL) { // Snap back to center if zoomed all the way out this.setState({ zoomLevel: nextZoomLevel, offsetX: 0, offsetY: 0 }); return; } var imageBaseSize = this.getBestImageForType('mainSrc'); if (imageBaseSize === null) { return; } var currentZoomMultiplier = this.getZoomMultiplier(); var nextZoomMultiplier = this.getZoomMultiplier(nextZoomLevel); // Default to the center of the image to zoom when no mouse position specified var boxRect = this.getLightboxRect(); var pointerX = typeof clientX !== 'undefined' ? clientX - boxRect.left : boxRect.width / 2; var pointerY = typeof clientY !== 'undefined' ? clientY - boxRect.top : boxRect.height / 2; var currentImageOffsetX = (boxRect.width - imageBaseSize.width * currentZoomMultiplier) / 2; var currentImageOffsetY = (boxRect.height - imageBaseSize.height * currentZoomMultiplier) / 2; var currentImageRealOffsetX = currentImageOffsetX - this.state.offsetX; var currentImageRealOffsetY = currentImageOffsetY - this.state.offsetY; var currentPointerXRelativeToImage = (pointerX - currentImageRealOffsetX) / currentZoomMultiplier; var currentPointerYRelativeToImage = (pointerY - currentImageRealOffsetY) / currentZoomMultiplier; var nextImageRealOffsetX = pointerX - currentPointerXRelativeToImage * nextZoomMultiplier; var nextImageRealOffsetY = pointerY - currentPointerYRelativeToImage * nextZoomMultiplier; var nextImageOffsetX = (boxRect.width - imageBaseSize.width * nextZoomMultiplier) / 2; var nextImageOffsetY = (boxRect.height - imageBaseSize.height * nextZoomMultiplier) / 2; var nextOffsetX = nextImageOffsetX - nextImageRealOffsetX; var nextOffsetY = nextImageOffsetY - nextImageRealOffsetY; // When zooming out, limit the offset so things don't get left askew if (this.currentAction !== ACTION_PINCH) { var maxOffsets = this.getMaxOffsets(); if (this.state.zoomLevel > nextZoomLevel) { nextOffsetX = Math.max(maxOffsets.minX, Math.min(maxOffsets.maxX, nextOffsetX)); nextOffsetY = Math.max(maxOffsets.minY, Math.min(maxOffsets.maxY, nextOffsetY)); } } this.setState({ zoomLevel: nextZoomLevel, offsetX: nextOffsetX, offsetY: nextOffsetY }); } }, { key: "closeIfClickInner", value: function closeIfClickInner(event) { if (!this.preventInnerClose && event.target.className.search(/\bril-inner\b/) > -1) { this.requestClose(event); } } /** * Handle user keyboard actions */ }, { key: "handleKeyInput", value: function handleKeyInput(event) { event.stopPropagation(); // Ignore key input during animations if (this.isAnimating()) { return; } // Allow slightly faster navigation through the images when user presses keys repeatedly if (event.type === 'keyup') { this.lastKeyDownTime -= this.props.keyRepeatKeyupBonus; return; } var keyCode = event.which || event.keyCode; // Ignore key presses that happen too close to each other (when rapid fire key pressing or holding down the key) // But allow it if it's a lightbox closing action var currentTime = new Date(); if (currentTime.getTime() - this.lastKeyDownTime < this.props.keyRepeatLimit && keyCode !== KEYS.ESC) { return; } this.lastKeyDownTime = currentTime.getTime(); switch (keyCode) { // ESC key closes the lightbox case KEYS.ESC: event.preventDefault(); this.requestClose(event); break; // Left arrow key moves to previous image case KEYS.LEFT_ARROW: if (!this.props.prevSrc) { return; } event.preventDefault(); this.keyPressed = true; this.requestMovePrev(event); break; // Right arrow key moves to next image case KEYS.RIGHT_ARROW: if (!this.props.nextSrc) { return; } event.preventDefault(); this.keyPressed = true; this.requestMoveNext(event); break; // Up arrow key moves to upper row case KEYS.UP_ARROW: { if (this.props.onClickMoveUp) { event.preventDefault(); this.keyPressed = true; this.requestMoveUp(event); } break; } // Down arrow key moves to down row case KEYS.DOWN_ARROW: { if (this.props.onClickMoveUp) { event.preventDefault(); this.keyPressed = true; this.requestMoveDown(event); } break; } } } /** * Handle a mouse wheel event over the lightbox container */ }, { key: "handleOuterMousewheel", value: function handleOuterMousewheel(event) { var _this7 = this; // Prevent scrolling of the background event.stopPropagation(); this.clearTimeout(this.resetScrollTimeout); this.resetScrollTimeout = this.setTimeout(function () { _this7.scrollX = 0; _this7.scrollY = 0; }, 300); } }, { key: "handleImageMouseWheel", value: function handleImageMouseWheel(event) { // when gesture move up/down/left/right, event.deltaY is integer, move image if (parseInt(event.deltaY) === parseFloat(event.deltaY)) { if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) { var newOffsetY = this.state.offsetY + event.deltaY; newOffsetY = newOffsetY < 0 ? 0 : newOffsetY; this.setState({ offsetY: newOffsetY }); } else { var newOffsetX = this.state.offsetX + event.deltaX; newOffsetX = newOffsetX < 0 ? 0 : newOffsetX; this.setState({ offsetX: newOffsetX }); } return; } // when gesture zoom in or zoom out, event.deltaY is decimal, zoom image var yThreshold = WHEEL_MOVE_Y_THRESHOLD; if (Math.abs(event.deltaY) >= Math.abs(event.deltaX)) { event.stopPropagation(); // If the vertical scroll amount was large enough, perform a zoom if (Math.abs(event.deltaY) < yThreshold) { return; } this.scrollX = 0; this.scrollY += event.deltaY; this.changeZoom(this.state.zoomLevel - event.deltaY, event.clientX, event.clientY); } } /** * Handle a double click on the current image */ }, { key: "handleImageDoubleClick", value: function handleImageDoubleClick(event) { if (this.state.zoomLevel > MIN_ZOOM_LEVEL) { // A double click when zoomed in zooms all the way out this.changeZoom(MIN_ZOOM_LEVEL, event.clientX, event.clientY); } else { // A double click when zoomed all the way out zooms in this.changeZoom(this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE, event.clientX, event.clientY); } } }, { key: "handleImageClick", value: function handleImageClick(event) { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); } }, { key: "shouldHandleEvent", value: function shouldHandleEvent(source) { if (this.eventsSource === source) { return true; } if (this.eventsSource === SOURCE_ANY) { this.eventsSource = source; return true; } switch (source) { case SOURCE_MOUSE: return false; case SOURCE_TOUCH: this.eventsSource = SOURCE_TOUCH; this.filterPointersBySource(); return true; case SOURCE_POINTER: if (this.eventsSource === SOURCE_MOUSE) { this.eventsSource = SOURCE_POINTER; this.filterPointersBySource(); return true; } return false; default: return false; } } }, { key: "addPointer", value: function addPointer(pointer) { this.pointerList.push(pointer); } }, { key: "removePointer", value: function removePointer(pointer) { this.pointerList = this.pointerList.filter(function (_ref) { var id = _ref.id; return id !== pointer.id; }); } }, { key: "filterPointersBySource", value: function filterPointersBySource() { var _this8 = this; this.pointerList = this.pointerList.filter(function (_ref2) { var source = _ref2.source; return source === _this8.eventsSource; }); } }, { key: "handleMouseDown", value: function handleMouseDown(event) { if (this.shouldHandleEvent(SOURCE_MOUSE) && ReactImageLightbox.isTargetMatchImage(event.target)) { this.addPointer(ReactImageLightbox.parseMouseEvent(event)); this.multiPointerStart(event); } } }, { key: "handleMouseMove", value: function handleMouseMove(event) { if (this.shouldHandleEvent(SOURCE_MOUSE)) { this.multiPointerMove(event, [ReactImageLightbox.parseMouseEvent(event)]); } } }, { key: "handleMouseUp", value: function handleMouseUp(event) { if (this.shouldHandleEvent(SOURCE_MOUSE)) { this.removePointer(ReactImageLightbox.parseMouseEvent(event)); this.multiPointerEnd(event); } } }, { key: "handlePointerEvent", value: function handlePointerEvent(event) { if (this.shouldHandleEvent(SOURCE_POINTER)) { switch (event.type) { case 'pointerdown': if (ReactImageLightbox.isTargetMatchImage(event.target)) { this.addPointer(ReactImageLightbox.parsePointerEvent(event)); this.multiPointerStart(event); } break; case 'pointermove': this.multiPointerMove(event, [ReactImageLightbox.parsePointerEvent(event)]); break; case 'pointerup': case 'pointercancel': this.removePointer(ReactImageLightbox.parsePointerEvent(event)); this.multiPointerEnd(event); break; } } } }, { key: "handleTouchStart", value: function handleTouchStart(event) { var _this9 = this; if (this.shouldHandleEvent(SOURCE_TOUCH) && ReactImageLightbox.isTargetMatchImage(event.target)) { [].forEach.call(event.changedTouches, function (eventTouch) { return _this9.addPointer(ReactImageLightbox.parseTouchPointer(eventTouch)); }); this.multiPointerStart(event); } } }, { key: "handleTouchMove", value: function handleTouchMove(event) { if (this.shouldHandleEvent(SOURCE_TOUCH)) { this.multiPointerMove(event, [].map.call(event.changedTouches, function (eventTouch) { return ReactImageLightbox.parseTouchPointer(eventTouch); })); } } }, { key: "handleTouchEnd", value: function handleTouchEnd(event) { var _this10 = this; if (this.shouldHandleEvent(SOURCE_TOUCH)) { [].map.call(event.changedTouches, function (touch) { return _this10.removePointer(ReactImageLightbox.parseTouchPointer(touch)); }); this.multiPointerEnd(event); } } }, { key: "decideMoveOrSwipe", value: function decideMoveOrSwipe(pointer) { if (this.state.zoomLevel <= MIN_ZOOM_LEVEL) { this.handleSwipeStart(pointer); } else { this.handleMoveStart(pointer); } } }, { key: "multiPointerStart", value: function multiPointerStart(event) { this.handleEnd(null); switch (this.pointerList.length) { case 1: { event.preventDefault(); this.decideMoveOrSwipe(this.pointerList[0]); break; } case 2: { event.preventDefault(); this.handlePinchStart(this.pointerList); break; } } } }, { key: "multiPointerMove", value: function multiPointerMove(event, pointerList) { switch (this.currentAction) { case ACTION_MOVE: { event.preventDefault(); this.handleMove(pointerList[0]); break; } case ACTION_SWIPE: { event.preventDefault(); this.handleSwipe(pointerList[0]); break; } case ACTION_PINCH: { event.preventDefault(); this.handlePinch(pointerList); break; } } } }, { key: "multiPointerEnd", value: function multiPointerEnd(event) { if (this.currentAction !== ACTION_NONE) { this.setPreventInnerClose(); this.handleEnd(event); } switch (this.pointerList.length) { case 0: { this.eventsSource = SOURCE_ANY; break; } case 1: { event.preventDefault(); this.decideMoveOrSwipe(this.pointerList[0]); break; } case 2: { event.preventDefault(); this.handlePinchStart(this.pointerList); break; } } } }, { key: "handleEnd", value: function handleEnd(event) { switch (this.currentAction) { case ACTION_MOVE: this.handleMoveEnd(event); break; case ACTION_SWIPE: this.handleSwipeEnd(event); break; case ACTION_PINCH: this.handlePinchEnd(event); break; } } // Handle move start over the lightbox container // This happens: // - On a mouseDown event // - On a touchstart event }, { key: "handleMoveStart", value: function handleMoveStart(_ref3) { var clientX = _ref3.x, clientY = _ref3.y; if (!this.props.enableZoom) { return; } this.currentAction = ACTION_MOVE; this.moveStartX = clientX; this.moveStartY = clientY; this.moveStartOffsetX = this.state.offsetX; this.moveStartOffsetY = this.state.offsetY; } // Handle dragging over the lightbox container // This happens: // - After a mouseDown and before a mouseUp event // - After a touchstart and before a touchend event }, { key: "handleMove", value: function handleMove(_ref4) { var clientX = _ref4.x, clientY = _ref4.y; var newOffsetX = this.moveStartX - clientX + this.moveStartOffsetX; var newOffsetY = this.moveStartY - clientY + this.moveStartOffsetY; if (this.state.offsetX !== newOffsetX || this.state.offsetY !== newOffsetY) { this.setState({ offsetX: newOffsetX, offsetY: newOffsetY }); } } }, { key: "handleMoveEnd", value: function handleMoveEnd() { var _this11 = this; this.currentAction = ACTION_NONE; this.moveStartX = 0; this.moveStartY = 0; this.moveStartOffsetX = 0; this.moveStartOffsetY = 0; // Snap image back into frame if outside max offset range var maxOffsets = this.getMaxOffsets(); var nextOffsetX = Math.max(maxOffsets.minX, Math.min(maxOffsets.maxX, this.state.offsetX)); var nextOffsetY = Math.max(maxOffsets.minY, Math.min(maxOffsets.maxY, this.state.offsetY)); if (nextOffsetX !== this.state.offsetX || nextOffsetY !== this.state.offsetY) { this.setState({ offsetX: nextOffsetX, offsetY: nextOffsetY, shouldAnimate: true }); this.setTimeout(function () { _this11.setState({ shouldAnimate: false }); }, this.props.animationDuration); } } }, { key: "handleSwipeStart", value: function handleSwipeStart(_ref5) { var clientX = _ref5.x, clientY = _ref5.y; this.currentAction = ACTION_SWIPE; this.swipeStartX = clientX; this.swipeStartY = clientY; this.swipeEndX = clientX; this.swipeEndY = clientY; } }, { key: "handleSwipe", value: function handleSwipe(_ref6) { var clientX = _ref6.x, clientY = _ref6.y; this.swipeEndX = clientX; this.swipeEndY = clientY; } }, { key: "handleSwipeEnd", value: function handleSwipeEnd(event) { var xDiff = this.swipeEndX - this.swipeStartX; var xDiffAbs = Math.abs(xDiff); var yDiffAbs = Math.abs(this.swipeEndY - this.swipeStartY); this.currentAction = ACTION_NONE; this.swipeStartX = 0; this.swipeStartY = 0; this.swipeEndX = 0; this.swipeEndY = 0; if (!event || this.isAnimating() || xDiffAbs < yDiffAbs * 1.5) { return; } if (xDiffAbs < MIN_SWIPE_DISTANCE) { var boxRect = this.getLightboxRect(); if (xDiffAbs < boxRect.width / 4) { return; } } if (xDiff > 0 && this.props.prevSrc) { event.preventDefault(); this.requestMovePrev(); } else if (xDiff < 0 && this.props.nextSrc) { event.preventDefault(); this.requestMoveNext(); } } }, { key: "calculatePinchDistance", value: function calculatePinchDistance() { var _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pinchTouchList, _ref8 = _slicedToArray(_ref7, 2), a = _ref8[0], b = _ref8[1]; return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)); } }, { key: "calculatePinchCenter", value: function calculatePinchCenter() { var _ref9 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pinchTouchList, _ref10 = _slicedToArray(_ref9, 2), a = _ref10[0], b = _ref10[1]; return { x: a.x - (a.x - b.x) / 2, y: a.y - (a.y - b.y) / 2 }; } }, { key: "handlePinchStart", value: function handlePinchStart(pointerList) { if (!this.props.enableZoom) { return; } this.currentAction = ACTION_PINCH; this.pinchTouchList = pointerList.map(function (_ref11) { var id = _ref11.id, x = _ref11.x, y = _ref11.y; return { id: id, x: x, y: y }; }); this.pinchDistance = this.calculatePinchDistance(); } }, { key: "handlePinch", value: function handlePinch(pointerList) { this.pinchTouchList = this.pinchTouchList.map(function (oldPointer) { for (var i = 0; i < pointerList.length; i += 1) { if (pointerList[i].id === oldPointer.id) { return pointerList[i]; } } return oldPointer; }); var newDistance = this.calculatePinchDistance(); var zoomLevel = this.state.zoomLevel + newDistance - this.pinchDistance; this.pinchDistance = newDistance; var _this$calculatePinchC = this.calculatePinchCenter(this.pinchTouchList), clientX = _this$calculatePinchC.x, clientY = _this$calculatePinchC.y; this.changeZoom(zoomLevel, clientX, clientY); } }, { key: "handlePinchEnd", value: function handlePinchEnd() { this.currentAction = ACTION_NONE; this.pinchTouchList = null; this.pinchDistance = 0; } // Handle the window resize event }, { key: "handleWindowResize", value: function handleWindowResize() { this.clearTimeout(this.resizeTimeout); this.resizeTimeout = this.setTimeout(this.forceUpdate.bind(this), 100); } }, { key: "handleZoomInButtonClick", value: function handleZoomInButtonClick() { var nextZoomLevel = this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE; this.changeZoom(nextZoomLevel); if (nextZoomLevel === MAX_ZOOM_LEVEL) { this.zoomOutBtn.current.focus(); } } }, { key: "handleZoomOutButtonClick", value: function handleZoomOutButtonClick() { var nextZoomLevel = this.state.zoomLevel - ZOOM_BUTTON_INCREMENT_SIZE; this.changeZoom(nextZoomLevel); if (nextZoomLevel === MIN_ZOOM_LEVEL) { this.zoomInBtn.current.focus(); } } }, { key: "handleCaptionMousewheel", value: function handleCaptionMousewheel(event) { event.stopPropagation(); if (!this.caption.current) { return; } var _this$caption$current = this.caption.current.getBoundingClientRect(), height = _this$caption$current.height; var _this$caption$current2 = this.caption.current, scrollHeight = _this$caption$current2.scrollHeight, scrollTop = _this$caption$current2.scrollTop; if (event.deltaY > 0 && height + scrollTop >= scrollHeight || event.deltaY < 0 && scrollTop <= 0) { event.preventDefault(); } } // Detach key and mouse input events }, { key: "isAnimating", value: function isAnimating() { return this.state.shouldAnimate || this.state.isClosing; } // Check if image is loaded }, { key: "isImageLoaded", value: function isImageLoaded(imageSrc) { return imageSrc && imageSrc in this.imageCache && this.imageCache[imageSrc].loaded; } // Load image from src and call callback with image width and height on load }, { key: "loadImage", value: function loadImage(srcType, imageSrc, done) { var _this12 = this; // Return the image info if it is already cached if (this.isImageLoaded(imageSrc)) { this.setTimeout(function () { done(); }, 1); return; } var inMemoryImage = new global.Image(); if (this.props.imageCrossOrigin) { inMemoryImage.crossOrigin = this.props.imageCrossOrigin; } inMemoryImage.onerror = function (errorEvent) { _this12.props.onImageLoadError(imageSrc, srcType, errorEvent); // failed to load so set the state loadErrorStatus _this12.setState(function (prevState) { return { loadErrorStatus: _objectSpread2(_objectSpread2({}, prevState.loadErrorStatus), {}, _defineProperty({}, srcType, true)) }; }); done(errorEvent); }; inMemoryImage.onload = function () { _this12.props.onImageLoad(imageSrc, srcType, inMemoryImage); _this12.imageCache[imageSrc] = { loaded: true, width: inMemoryImage.width, height: inMemoryImage.height }; done(); }; inMemoryImage.src = imageSrc; } // Load all images and their thumbnails }, { key: "loadAllImages", value: function loadAllImages() { var _this13 = this; var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props; var generateLoadDoneCallback = function generateLoadDoneCallback(srcType, imageSrc) { return function (err) { // Give up showing image on error if (err) { return; } // Don't rerender if the src is not the same as when the load started // or if the component has unmounted if (_this13.props[srcType] !== imageSrc || _this13.didUnmount) { return; } // Force rerender with the new image _this13.forceUpdate(); }; }; // Load the images this.getSrcTypes().forEach(function (srcType) { var type = srcType.name; // there is no error when we try to load it initially if (props[type] && _this13.state.loadErrorStatus[type]) { _this13.setState(function (prevState) { return { loadErrorStatus: _objectSpread2(_objectSpread2({}, prevState.loadErrorStatus), {}, _defineProperty({}, type, false)) }; }); } // Load unloaded images if (props[type] && !_this13.isImageLoaded(props[type])) { _this13.loadImage(type, props[type], generateLoadDoneCallback(type, props[type])); } }); } // Request that the lightbox be closed }, { key: "requestClose", value: function requestClose(event) { var _this14 = this; // Call the parent close request var closeLightbox = function closeLightbox() { _this14.saveRotateImage(); _this14.props.onCloseRequest(event); }; if (this.props.animationDisabled || event.type === 'keydown' && !this.props.animationOnKeyInput) { // No animation closeLightbox(); return; } // With animation // Start closing animation this.setState({ isClosing: true }); // Perform the actual closing at the