UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

356 lines (349 loc) 14 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _is = require("../../utils/is"); var _isServer = require("../../utils/isServer"); var _getDataUrl = require("../utils/getDataUrl"); require("./Gallery.css"); var _Image = _interopRequireDefault(require("./Image")); var _ImageContainer = _interopRequireDefault(require("./ImageContainer")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * @component */ /** * An image gallery that displays up to four images by default. Also supports * reordering and deletion of images and blurred image previews for images * loaded from `tsimg.cloud`. */ class Gallery extends _react.Component { static getBigImageUrls(images) { return images.map(image => { const img = image.url || image.file || image; return (0, _is.isString)(img) ? img : (0, _getDataUrl.getDataUrlFromFile)(img); }); } constructor(props) { super(props); this.onDown = (event, index, image) => { // deactivate refresh scroll in apps if (chayns.env.isApp || chayns.env.isMyChaynsApp) chayns.disallowRefreshScroll(); this.index = index; this.image = image; this.pageXStart = event.changedTouches ? event.changedTouches[0].pageX : event.pageX; this.pageYStart = event.changedTouches ? event.changedTouches[0].pageY : event.pageY; this.selectedElement = event.target.parentElement.parentElement.parentElement; this.selectedElementStartPosition = this.selectedElement.getBoundingClientRect(); this.galleryStartPosition = this.galleryRef.current.getBoundingClientRect(); this.galleryOffsetX = this.galleryStartPosition.left; this.galleryOffsetY = this.galleryStartPosition.top; this.offsetX = this.pageXStart - this.selectedElementStartPosition.left; this.offsetY = this.pageYStart - this.selectedElementStartPosition.top; document.addEventListener('mousemove', this.onMove); document.addEventListener('touchmove', this.onMove, { passive: false }); document.addEventListener('mouseup', this.onUp); document.addEventListener('touchend', this.onUp); document.addEventListener('touchcancel', this.onUp); }; this.onMove = event => { event.preventDefault(); const { pageX, pageY } = event.changedTouches ? event.changedTouches[0] : event; const { clientWidth: galleryWidth } = this.galleryRef.current; const { clientHeight: itemHeight, clientWidth: itemWidth } = event.target.parentElement.parentElement.parentElement; // move item this.selectedElement.style.left = `${pageX - this.galleryOffsetX - this.offsetX}px`; this.selectedElement.style.top = `${pageY - this.galleryOffsetY - this.offsetY}px`; // determine new position const itemsPerRow = Math.round(galleryWidth / itemWidth); const middleX = pageX - this.galleryOffsetX - this.offsetX + itemWidth / 2; const middleY = pageY - this.galleryOffsetY - this.offsetY + itemHeight / 2; const row = Math.floor(middleY / itemHeight); const column = Math.floor(middleX / itemWidth); this.newPosition = row * itemsPerRow + column; const { dropzone: oldDropzone } = this.state; const newDropzone = this.newPosition + (this.newPosition > this.index ? 1 : 0); if (oldDropzone !== newDropzone) { this.setState({ dropzone: newDropzone, active: this.index }); } // show corresponding dropzone let insertPosition = this.newPosition * 2; // dropzones and images are alternating if (this.newPosition > this.index) { insertPosition += 2; } const dropzone = this.galleryRef.current.children[insertPosition]; this.lastDropzone = dropzone; }; this.onUp = () => { if (chayns.env.isApp || chayns.env.isMyChaynsApp) chayns.allowRefreshScroll(); document.removeEventListener('mousemove', this.onMove); document.removeEventListener('touchmove', this.onMove); document.removeEventListener('mouseup', this.onUp); document.removeEventListener('touchend', this.onUp); document.removeEventListener('touchcancel', this.onUp); if (this.lastDropzone) { // there's no lastDropzone if user hasn't moved const { onDragEnd, images } = this.props; const rect = this.lastDropzone.getBoundingClientRect(); this.selectedElement.classList.add('cc__gallery__image--transition'); this.selectedElement.style.left = `${rect.left - this.galleryOffsetX}px`; this.selectedElement.style.top = `${rect.top - this.galleryOffsetY}px`; const onTransitionEnd = () => { if (this.selectedElement && !this.transitionEnded) { this.transitionEnded = true; this.selectedElement.removeEventListener('transitionend', onTransitionEnd); this.selectedElement.classList.remove('cc__gallery__image--transition'); const image = images[this.index]; const newArray = images.slice(); newArray.splice(this.index, 1); newArray.splice(this.newPosition, 0, image); if (onDragEnd) { onDragEnd(newArray); } this.setState({ dropzone: null, active: null, images: newArray }); } }; this.transitionEnded = false; this.selectedElement.addEventListener('transitionend', onTransitionEnd); } else { this.selectedElement.classList.remove('cc__gallery__image--active'); } // Enable scrolling. document.ontouchmove = () => true; }; this.galleryRef = /*#__PURE__*/_react.default.createRef(); this.state = { active: null, images: props.images, dropzone: null, galleryWidth: null }; } componentDidMount() { this.setState({ galleryWidth: this.galleryRef.current.offsetWidth }); } componentDidUpdate(prevProps) { const { images } = this.props; if (prevProps.images !== images) { // eslint-disable-next-line react/no-did-update-set-state this.setState({ images }); } } render() { const { height, width, onDelete, deleteMode, dragMode, className, preventParams, stopPropagation, onClick, smallTiles } = this.props; const { style: propStyle } = this.props; const style = { ...propStyle }; const defaultMode = !dragMode && !deleteMode && !smallTiles; const { active, dropzone: dropzoneId, images, galleryWidth } = this.state; let styleHeight; if (defaultMode) { if (height) { styleHeight = height; } else if (galleryWidth < 420) { styleHeight = galleryWidth; } else { styleHeight = 420; } style.height = `${styleHeight}px`; } if (width) { style.width = width; } const numberOfImages = images.length; const dropzone = (key, show) => /*#__PURE__*/_react.default.createElement("div", { key: key, id: key, className: 'cc__gallery__image cc__gallery__image--dropzone' + (show ? " cc__gallery__image--show_dropzone" : "") }, /*#__PURE__*/_react.default.createElement(_ImageContainer.default, null, /*#__PURE__*/_react.default.createElement("div", { className: 'cc__gallery__image__dropzone chayns__background-color--101 chayns__border-color--300' }))); return /*#__PURE__*/_react.default.createElement("div", { className: (0, _clsx.default)('cc__gallery', className, defaultMode && 'cc__gallery--default-mode', deleteMode && 'cc__gallery--delete-mode', dragMode && 'cc__gallery--drag-mode'), style: style, ref: this.galleryRef, key: "gallery" }, dragMode ? dropzone('dropzone', dropzoneId === 0) : null, images.map((image, index) => { if (index < 4 || deleteMode || dragMode) { const tools = []; if (dragMode && images.length > 1) { // Show drag icon only if a reorder is possible tools.push({ icon: 'ts-bars', className: 'cc__gallery__image__tool--drag', onDown: event => { event.preventDefault(); this.onDown(event, index, image); }, noScroll: true }); } if (deleteMode) { tools.push({ icon: 'ts-wrong', onClick: () => { onDelete(image, index); } }); } return [/*#__PURE__*/_react.default.createElement("div", { className: 'cc__gallery__image' + (index === active ? " cc__gallery__image--active" : ""), id: `image${index}` // eslint-disable-next-line react/no-array-index-key , key: `imageDiv${index}` }, /*#__PURE__*/_react.default.createElement(_ImageContainer.default, { tools: tools }, /*#__PURE__*/_react.default.createElement(_Image.default // eslint-disable-next-line react/no-array-index-key , { key: `image${index}`, preventParams: preventParams, image: image.url || image.file || image, moreImages: index === 3 && defaultMode ? numberOfImages - 1 - index : 0, onClick: onClick || defaultMode ? event => { if (stopPropagation) event.stopPropagation(); if (onClick) { onClick(Gallery.getBigImageUrls(images), index); } else if (defaultMode) { chayns.openImage(Gallery.getBigImageUrls(images), index); } } : null, className: "cc__gallery__image--cover" }))), dragMode ? dropzone(`dropzone${index}`, dropzoneId === index + 1) : null]; } return null; })); } } exports.default = Gallery; Gallery.propTypes = { /** * An array of strings or File objects that will be the image sources. */ images: (0, _isServer.isServer)() // eslint-disable-line react/require-default-props ? _propTypes.default.array.isRequired : _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.shape({ url: _propTypes.default.string.isRequired }), _propTypes.default.shape({ file: _propTypes.default.instanceOf(File).isRequired }), _propTypes.default.string, _propTypes.default.instanceOf(File)]).isRequired).isRequired, /** * A function that is called when an Image is clicked. */ onClick: _propTypes.default.func, /** * A function that is called when an image is deleted in deletion mode. */ onDelete: _propTypes.default.func, /** * Wether the deletion mode is active. */ deleteMode: _propTypes.default.bool, /** * The height of the gallery as a number of pixels or CSS string. */ height: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), /** * The width of the gallery as a number of pixels or CSS string. */ width: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), /** * A classname string that will be applied to the root container. */ className: _propTypes.default.string, /** * A React style object that is applied to the root container. */ style: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])), /** * Wether to stop propagation of click events to parent elements. */ stopPropagation: _propTypes.default.bool, /** * Wether drag mode is active. */ dragMode: _propTypes.default.bool, /** * Called after the user finished reordering the array. Receives the new * array as its first parameter. */ onDragEnd: _propTypes.default.func, /** * This will be forwarded to the `Image`-component. It prevents parameters * of the loaded image. E.g. supply `{ width: true }` to prevent the * `width`-parameter on the loaded image. */ preventParams: _propTypes.default.bool, /** * This option changes the layout to the layout known from delete- and * drag-mode without activating this modes. */ smallTiles: _propTypes.default.bool }; Gallery.defaultProps = { onClick: null, onDelete: null, deleteMode: false, height: null, width: null, className: null, style: {}, stopPropagation: false, dragMode: false, onDragEnd: null, preventParams: false, smallTiles: false }; Gallery.displayName = 'Gallery'; //# sourceMappingURL=Gallery.js.map