chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
349 lines (342 loc) • 12.2 kB
JavaScript
/**
* @component
*/
import classNames from 'clsx';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { isString } from '../../utils/is';
import { isServer } from '../../utils/isServer';
import { getDataUrlFromFile } from '../utils/getDataUrl';
import "./Gallery.css";
import Image from './Image';
import ImageContainer from './ImageContainer';
/**
* 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`.
*/
export default class Gallery extends Component {
static getBigImageUrls(images) {
return images.map(image => {
const img = image.url || image.file || image;
return isString(img) ? img : 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.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.createElement("div", {
key: key,
id: key,
className: 'cc__gallery__image cc__gallery__image--dropzone' + (show ? " cc__gallery__image--show_dropzone" : "")
}, /*#__PURE__*/React.createElement(ImageContainer, null, /*#__PURE__*/React.createElement("div", {
className: 'cc__gallery__image__dropzone chayns__background-color--101 chayns__border-color--300'
})));
return /*#__PURE__*/React.createElement("div", {
className: classNames('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.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.createElement(ImageContainer, {
tools: tools
}, /*#__PURE__*/React.createElement(Image
// 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;
}));
}
}
Gallery.propTypes = {
/**
* An array of strings or File objects that will be the image sources.
*/
images: isServer() // eslint-disable-line react/require-default-props
? PropTypes.array.isRequired : PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({
url: PropTypes.string.isRequired
}), PropTypes.shape({
file: PropTypes.instanceOf(File).isRequired
}), PropTypes.string, PropTypes.instanceOf(File)]).isRequired).isRequired,
/**
* A function that is called when an Image is clicked.
*/
onClick: PropTypes.func,
/**
* A function that is called when an image is deleted in deletion mode.
*/
onDelete: PropTypes.func,
/**
* Wether the deletion mode is active.
*/
deleteMode: PropTypes.bool,
/**
* The height of the gallery as a number of pixels or CSS string.
*/
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* The width of the gallery as a number of pixels or CSS string.
*/
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* A classname string that will be applied to the root container.
*/
className: PropTypes.string,
/**
* A React style object that is applied to the root container.
*/
style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
/**
* Wether to stop propagation of click events to parent elements.
*/
stopPropagation: PropTypes.bool,
/**
* Wether drag mode is active.
*/
dragMode: PropTypes.bool,
/**
* Called after the user finished reordering the array. Receives the new
* array as its first parameter.
*/
onDragEnd: PropTypes.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.bool,
/**
* This option changes the layout to the layout known from delete- and
* drag-mode without activating this modes.
*/
smallTiles: PropTypes.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