UNPKG

chayns-components

Version:

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

287 lines (284 loc) 10.1 kB
/** * @component */ import classNames from 'clsx'; import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; import Icon from '../../react-chayns-icon/component/Icon'; import SmallWaitCursor from '../../react-chayns-smallwaitcursor/component/SmallWaitCursor'; import { isFunction } from '../../utils/is'; import fileInputCall from '../utils/fileInputCall'; import supportsFileInput from '../utils/supportsFileInput'; /** * Accepts specified file types via dialog or drag and drop. */ export default class FileInput extends PureComponent { constructor(props) { super(props); this.onDragEnter = (event, item, index) => { if (this.checkFileType(event.dataTransfer.items[0].type, item.types)) { this.itemRefs[index].classList.add('cc__file-input--hover'); } }; this.onDragLeave = index => { this.itemRefs[index].classList.remove('cc__file-input--hover'); }; this.onError = (item, errorMessage) => { if (isFunction(item.onError)) { return item.onError(errorMessage); } return chayns.dialog.alert('', errorMessage); }; this.onChange = async (event, item, index) => { const { errorMessages } = this.props; const { files } = event.target; this.onDragLeave(index); if (files && files.length > 0) { this.setState({ isLoading: true }); const invalidFiles = []; const validFiles = []; for (let i = 0, l = files.length; i < l; i++) { const file = files[i]; if (!this.checkFileType(file.type, item.types)) { invalidFiles.push(file); this.onError(item, errorMessages.wrongFileType); } else if (item.maxNumberOfFiles > 0 && validFiles.length >= item.maxNumberOfFiles) { invalidFiles.push(file); this.onError(item, errorMessages.tooMuchFiles.replace('##NUMBER##', item.maxNumberOfFiles)); } else if (item.maxFileSize > 0 && file.size > item.maxFileSize) { this.onError(item, errorMessages.fileTooBig.replace('##SIZE##', `${Math.ceil(item.maxFileSize / (1024 * 1024))} MB`)); invalidFiles.push(file); } else { validFiles.push(file); } } item.onChange(validFiles, invalidFiles); this.setState({ isLoading: false }); } if (this.fileInputRefs[index]) { this.fileInputRefs[index].value = null; } }; this.onClick = async (event, item, index) => { const { hasMemoryAccess } = this.state; const { stopPropagation, errorMessages } = this.props; if (stopPropagation) event.stopPropagation(); if (isFunction(item.onClick)) item.onClick(event); if (item.onChange) { if (this.needAppCall) { const compatibilityEvent = await fileInputCall(); // TODO remove in future version this.onChange(compatibilityEvent, item, index); } else if (!hasMemoryAccess) { const result = await chayns.invokeCall({ action: 239 }, true); if (result.status === 1) { this.setState({ hasMemoryAccess: true }); if (event.isTrusted) { // trigger another click on file input after permission is granted, // but only when event was generated by user action to prevent multiple file select dialogs this.fileInputRefs[index].click(); } } else if (result.status === 2 && errorMessages.temporaryNoPermission) { this.onError(item, errorMessages.temporaryNoPermission); } else if (result.status === 3 && errorMessages.permanentNoPermission) { await this.onError(item, errorMessages.permanentNoPermission); chayns.invokeCall({ action: 239, value: { showAppInfo: true } }); } } } }; this.checkFileType = (fileType, supportedTypes) => { if (fileType === '') { return true; } for (let i = 0; i < supportedTypes.length; i += 1) { const type = supportedTypes[i]; if (type === FileInput.types.ALL) { return true; } if (type === fileType) { return true; } const fileTypeMatch = fileType.match(/(.)+\//g); const typeMatch = type.match(/(.)+\//g); if (type.match(/\/\*/g) && fileTypeMatch && typeMatch && fileTypeMatch[0] === typeMatch[0]) { return true; } } return false; }; this.itemRefs = []; this.fileInputRefs = []; this.needAppCall = !supportsFileInput(); this.state = { hasMemoryAccess: !(chayns.env.isAndroid && (chayns.env.isApp || chayns.env.isMyChaynsApp) && (chayns.env.myChaynsAppVersion || chayns.env.appVersion) >= 6244), isLoading: false }; } render() { const { items, className, style, disabled } = this.props; const { hasMemoryAccess, isLoading } = this.state; return /*#__PURE__*/React.createElement("div", { className: classNames("cc__file-input cc__file-input--custom", className, disabled && 'cc__file-input--disabled'), style: style }, items.map((item, index) => /*#__PURE__*/React.createElement("div", { className: classNames('cc__file-input__split', item.className, (item.disabled || isLoading) && 'cc__file-input__split--disabled'), style: item.style // eslint-disable-next-line react/no-array-index-key , key: `item${index}` }, item.content && item.content.children ? item.content.children : /*#__PURE__*/React.createElement("div", { className: "cc__file-input--placeholder", ref: ref => { this.itemRefs[index] = ref; }, onClick: event => this.onClick(event, item, index) }, item.onChange && !this.needAppCall ? /*#__PURE__*/React.createElement("input", { style: !hasMemoryAccess ? { display: 'none' } : null, title: "", multiple: item.maxNumberOfFiles !== 1, directory: item.directory ? '' : null, webkitdirectory: item.directory ? '' : null, className: "cc__file-input__input", type: "file", onChange: event => this.onChange(event, item, index), accept: item.types, onDragEnter: event => this.onDragEnter(event, item, index), onDragLeave: () => this.onDragLeave(index), ref: ref => { this.fileInputRefs[index] = ref; } }) : null, !isLoading ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { className: "cc__file-input__icon" }, /*#__PURE__*/React.createElement(Icon, { icon: item.content && item.content.icon ? item.content.icon : 'fa fa-upload' })), /*#__PURE__*/React.createElement("div", { className: "cc__file-input__message" }, item.content && item.content.text ? item.content.text : 'Datei hochladen')) : /*#__PURE__*/React.createElement(SmallWaitCursor, { show: true, showBackground: false, className: "cc__file-input__wait-cursor" }))))); } } FileInput.types = { IMAGE: 'image/*', VIDEO: 'video/*', AUDIO: 'audio/*', ALL: '*' }; FileInput.typePresets = { TSIMG_CLOUD: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml', 'image/avif'], STREAMINGSERVICE: ['video/mp4', 'video/webm', 'video/avi', 'video/flv', 'video/wmv', 'video/mpg', 'video/quicktime'] }; FileInput.propTypes = { /** * A classname string that is applied to the root element. */ className: PropTypes.string, /** * A React style object that is applied to the root element. */ style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), /** * Wether to stop propagation of click events to parent elements. */ stopPropagation: PropTypes.bool, /** * Disables any interaction with the component and renders it in a disabled * style. */ disabled: PropTypes.bool, /** * Custom error messages for the component. */ errorMessages: PropTypes.shape({ tooMuchFiles: PropTypes.string, fileTooBig: PropTypes.string, wrongFileType: PropTypes.string, permanentNoPermission: PropTypes.string, temporaryNoPermission: PropTypes.string }), /** * The different fields that will be shown in the file input. */ items: PropTypes.arrayOf(PropTypes.shape({ types: PropTypes.arrayOf(PropTypes.string), maxFileSize: PropTypes.number, maxNumberOfFiles: PropTypes.number, directory: PropTypes.bool, onClick: PropTypes.func, onChange: PropTypes.func, onError: PropTypes.func, className: PropTypes.string, style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), disabled: PropTypes.bool, content: PropTypes.oneOfType([PropTypes.shape({ text: PropTypes.string, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) }), PropTypes.shape({ children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]) })]) })) }; FileInput.defaultProps = { className: null, style: null, stopPropagation: false, disabled: false, errorMessages: { tooMuchFiles: 'Du kannst nur ##NUMBER## Dateien hochladen.', fileTooBig: 'Es sind nur Dateien bis ##SIZE## erlaubt.', wrongFileType: 'Mindestens eine Datei hat das falsche Dateiformat.', permanentNoPermission: 'Bitte überprüfe die Einstellungen Deiner App und erlaube den Dateizugriff auf Deinem Gerät.', temporaryNoPermission: null }, items: [{ types: [FileInput.types.ALL], maxFileSize: 4 * 1024 * 1024, // 4 MB maxNumberOfFiles: 0, // 0=infinity directory: false, onClick: null, onChange: null, onError: null, className: null, style: null, disabled: false, content: null }] }; FileInput.displayName = 'FileInput'; //# sourceMappingURL=FileInput.js.map