chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
356 lines (349 loc) • 14 kB
JavaScript
"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