UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

668 lines (667 loc) 23.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _pick2 = _interopRequireDefault(require("lodash/pick")); var _noop2 = _interopRequireDefault(require("lodash/noop")); var _react = _interopRequireDefault(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _propTypes = _interopRequireDefault(require("prop-types")); var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/upload/foundation")); var _constants = require("@douyinfe/semi-foundation/lib/cjs/upload/constants"); var _fileCard = _interopRequireDefault(require("./fileCard")); var _baseComponent = _interopRequireDefault(require("../_base/baseComponent")); var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer")); var _semiIcons = require("@douyinfe/semi-icons"); require("@douyinfe/semi-foundation/lib/cjs/upload/upload.css"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var __rest = void 0 && (void 0).__rest || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; const prefixCls = _constants.cssClasses.PREFIX; class Upload extends _baseComponent.default { constructor(props) { super(props); this.inputRef = null; this.replaceInputRef = null; this.onClick = () => { const { inputRef, props } = this; const { onOpenFileDialog } = props; const isDisabled = Boolean(this.props.disabled); if (isDisabled || !inputRef || !inputRef.current) { return; } inputRef.current.click(); if (onOpenFileDialog && typeof onOpenFileDialog) { onOpenFileDialog(); } }; this.onChange = e => { const { files } = e.target; this.foundation.handleChange(files); }; this.replace = index => { this.setState({ replaceIdx: index }, () => { this.replaceInputRef.current.click(); }); }; this.onReplaceChange = e => { const { files } = e.target; this.foundation.handleReplaceChange(files); }; this.clear = () => { this.foundation.handleClear(); }; this.remove = fileItem => { this.foundation.handleRemove(fileItem); }; /** * ref method * insert files at index * @param files Array<CustomFile> * @param index number * @returns */ this.insert = (files, index) => { return this.foundation.insertFileToList(files, index); }; /** * ref method * manual upload by user */ this.upload = () => { this.foundation.manualUpload(); }; /** * ref method * manual open file select dialog */ this.openFileDialog = () => { this.onClick(); }; this.renderFile = (file, index, locale) => { const { name, status, validateMessage, _sizeInvalid, uid } = file; const { previewFile, listType, itemStyle, showPicInfo, renderPicInfo, renderPicClose, renderPicPreviewIcon, renderFileOperation, renderFileItem, renderThumbnail, disabled, onPreviewClick, picWidth, picHeight, showTooltip } = this.props; const onRemove = () => this.remove(file); const onRetry = () => { this.foundation.retry(file); }; const onReplace = () => { this.replace(index); }; const fileCardProps = Object.assign(Object.assign(Object.assign({}, (0, _pick2.default)(this.props, ['showRetry', 'showReplace', ''])), file), { previewFile, listType, onRemove, onRetry, index, key: uid || `${name}${index}`, style: itemStyle, disabled, showPicInfo, renderPicInfo, renderPicPreviewIcon, renderPicClose, renderFileOperation, renderThumbnail, onReplace, onPreviewClick: typeof onPreviewClick !== 'undefined' ? () => this.foundation.handlePreviewClick(file) : undefined, picWidth, picHeight, showTooltip }); if (status === _constants.strings.FILE_STATUS_UPLOAD_FAIL && !validateMessage) { fileCardProps.validateMessage = locale.fail; } if (_sizeInvalid && !validateMessage) { fileCardProps.validateMessage = locale.illegalSize; } if (typeof renderFileItem === 'undefined') { return /*#__PURE__*/_react.default.createElement(_fileCard.default, Object.assign({}, fileCardProps)); } else { return renderFileItem(fileCardProps); } }; this.renderFileList = () => { const { listType } = this.props; if (listType === _constants.strings.FILE_LIST_PIC) { return this.renderFileListPic(); } if (listType === _constants.strings.FILE_LIST_DEFAULT) { return this.renderFileListDefault(); } return null; }; this.renderFileListPic = () => { const { showUploadList, limit, disabled, children, draggable, hotSpotLocation, picHeight, picWidth } = this.props; const { fileList: stateFileList, dragAreaStatus } = this.state; const fileList = this.props.fileList || stateFileList; const showAddTriggerInList = limit ? limit > fileList.length : true; const dragAreaBaseCls = `${prefixCls}-drag-area`; const uploadAddCls = (0, _classnames.default)(`${prefixCls}-add`, { [`${prefixCls}-picture-add`]: true, [`${prefixCls}-picture-add-disabled`]: disabled }); const fileListCls = (0, _classnames.default)(`${prefixCls}-file-list`, { [`${prefixCls}-picture-file-list`]: true }); const dragAreaCls = (0, _classnames.default)({ [`${dragAreaBaseCls}-legal`]: dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL, [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL }); const mainCls = `${prefixCls}-file-list-main`; const addContentProps = { role: 'button', className: uploadAddCls, onClick: this.onClick, style: { height: picHeight, width: picWidth } }; const containerProps = { className: fileListCls }; const draggableProps = { onDrop: this.onDrop, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDragEnter: this.onDragEnter }; if (draggable) { Object.assign(addContentProps, draggableProps, { className: (0, _classnames.default)(uploadAddCls, dragAreaCls) }); } const addContent = /*#__PURE__*/_react.default.createElement("div", Object.assign({}, addContentProps, { "x-semi-prop": "children" }), children); if (!showUploadList || !fileList.length) { if (showAddTriggerInList) { return addContent; } return null; } return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "Upload" }, locale => (/*#__PURE__*/_react.default.createElement("div", Object.assign({}, containerProps), /*#__PURE__*/_react.default.createElement("div", { className: mainCls, role: "list", "aria-label": "picture list" }, showAddTriggerInList && hotSpotLocation === 'start' ? addContent : null, fileList.map((file, index) => this.renderFile(file, index, locale)), showAddTriggerInList && hotSpotLocation === 'end' ? addContent : null)))); }; this.renderFileListDefault = () => { const { showUploadList, limit, disabled } = this.props; const { fileList: stateFileList } = this.state; const fileList = this.props.fileList || stateFileList; const fileListCls = (0, _classnames.default)(`${prefixCls}-file-list`); const titleCls = `${prefixCls}-file-list-title`; const mainCls = `${prefixCls}-file-list-main`; const showTitle = limit !== 1 && fileList.length; const showClear = this.props.showClear && !disabled; const containerProps = { className: fileListCls }; if (!showUploadList || !fileList.length) { return null; } return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "Upload" }, locale => (/*#__PURE__*/_react.default.createElement("div", Object.assign({}, containerProps), showTitle ? (/*#__PURE__*/_react.default.createElement("div", { className: titleCls }, /*#__PURE__*/_react.default.createElement("span", { className: `${titleCls}-choosen` }, locale.selectedFiles), showClear ? (/*#__PURE__*/_react.default.createElement("span", { role: "button", tabIndex: 0, onClick: this.clear, className: `${titleCls}-clear` }, locale.clear)) : null)) : null, /*#__PURE__*/_react.default.createElement("div", { className: mainCls, role: "list", "aria-label": "file list" }, fileList.map((file, index) => this.renderFile(file, index, locale)))))); }; this.onDrop = e => { this.foundation.handleDrop(e); }; this.onDragOver = e => { // When a drag element moves within the target element this.foundation.handleDragOver(e); }; this.onDragLeave = e => { this.foundation.handleDragLeave(e); }; this.onDragEnter = e => { this.foundation.handleDragEnter(e); }; this.renderAddContent = () => { const { draggable, children, listType, disabled } = this.props; const uploadAddCls = (0, _classnames.default)(`${prefixCls}-add`); if (listType === _constants.strings.FILE_LIST_PIC) { return null; } if (draggable) { return this.renderDragArea(); } return /*#__PURE__*/_react.default.createElement("div", { role: "button", tabIndex: 0, "aria-disabled": disabled, className: uploadAddCls, onClick: this.onClick }, children); }; this.renderDragArea = () => { const { dragAreaStatus } = this.state; const { children, dragIcon, dragMainText, dragSubText, disabled } = this.props; const dragAreaBaseCls = `${prefixCls}-drag-area`; const dragAreaCls = (0, _classnames.default)(dragAreaBaseCls, { [`${dragAreaBaseCls}-legal`]: dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL, [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL, [`${dragAreaBaseCls}-custom`]: children }); return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "Upload" }, locale => (/*#__PURE__*/_react.default.createElement("div", { role: "button", tabIndex: 0, "aria-disabled": disabled, className: dragAreaCls, onDrop: this.onDrop, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDragEnter: this.onDragEnter, onClick: this.onClick }, children ? children : (/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", { className: `${dragAreaBaseCls}-icon`, "x-semi-prop": "dragIcon" }, dragIcon || /*#__PURE__*/_react.default.createElement(_semiIcons.IconUpload, { size: "extra-large" })), /*#__PURE__*/_react.default.createElement("div", { className: `${dragAreaBaseCls}-text` }, /*#__PURE__*/_react.default.createElement("div", { className: `${dragAreaBaseCls}-main-text`, "x-semi-prop": "dragMainText" }, dragMainText || locale.mainText), /*#__PURE__*/_react.default.createElement("div", { className: `${dragAreaBaseCls}-sub-text`, "x-semi-prop": "dragSubText" }, dragSubText), /*#__PURE__*/_react.default.createElement("div", { className: `${dragAreaBaseCls}-tips` }, dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL && (/*#__PURE__*/_react.default.createElement("span", { className: `${dragAreaBaseCls}-tips-legal` }, locale.legalTips)), dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL && (/*#__PURE__*/_react.default.createElement("span", { className: `${dragAreaBaseCls}-tips-illegal` }, locale.illegalTips))))))))); }; this.state = { fileList: props.defaultFileList || [], replaceIdx: -1, inputKey: Math.random(), replaceInputKey: Math.random(), // Status of the drag zone dragAreaStatus: 'default', localUrls: [] }; this.foundation = new _foundation.default(this.adapter); this.inputRef = /*#__PURE__*/_react.default.createRef(); this.replaceInputRef = /*#__PURE__*/_react.default.createRef(); } /** * Notes: * The input parameter and return value here do not declare the type, otherwise tsc may report an error in form/fields.tsx when wrap after withField * `The types of the parameters "props" and "nextProps" are incompatible. The attribute "action" is missing in the type "Readonly<any>", but it is required in the type "UploadProps".` * which seems to be a bug, remove props type declare here */ static getDerivedStateFromProps(props) { const { fileList } = props; if ('fileList' in props) { return { fileList: fileList || [] }; } return null; } get adapter() { return Object.assign(Object.assign({}, super.adapter), { notifyFileSelect: files => this.props.onFileChange(files), notifyError: (error, fileInstance, fileList, xhr) => this.props.onError(error, fileInstance, fileList, xhr), notifySuccess: (responseBody, file, fileList) => this.props.onSuccess(responseBody, file, fileList), notifyProgress: (percent, file, fileList) => this.props.onProgress(percent, file, fileList), notifyRemove: (file, fileList, fileItem) => this.props.onRemove(file, fileList, fileItem), notifySizeError: (file, fileList) => this.props.onSizeError(file, fileList), notifyExceed: fileList => this.props.onExceed(fileList), updateFileList: (fileList, cb) => { if (typeof cb === 'function') { this.setState({ fileList }, cb); } else { this.setState({ fileList }); } }, notifyBeforeUpload: _ref => { let { file, fileList } = _ref; return this.props.beforeUpload({ file, fileList }); }, notifyAfterUpload: _ref2 => { let { response, file, fileList } = _ref2; return this.props.afterUpload({ response, file, fileList }); }, resetInput: () => { this.setState(prevState => ({ inputKey: Math.random() })); }, resetReplaceInput: () => { this.setState(prevState => ({ replaceInputKey: Math.random() })); }, isMac: () => { return navigator.platform.toUpperCase().indexOf('MAC') >= 0; }, registerPastingHandler: cb => { document.body.addEventListener('keydown', cb); this.pastingCb = cb; }, unRegisterPastingHandler: () => { if (this.pastingCb) { document.body.removeEventListener('keydown', this.pastingCb); } }, notifyPastingError: error => this.props.onPastingError(error), updateDragAreaStatus: dragAreaStatus => this.setState({ dragAreaStatus }), notifyChange: _ref3 => { let { currentFile, fileList } = _ref3; return this.props.onChange({ currentFile, fileList }); }, updateLocalUrls: urls => this.setState({ localUrls: urls }), notifyClear: () => this.props.onClear(), notifyPreviewClick: file => this.props.onPreviewClick(file), notifyDrop: (e, files, fileList) => this.props.onDrop(e, files, fileList), notifyAcceptInvalid: invalidFiles => this.props.onAcceptInvalid(invalidFiles), notifyBeforeRemove: (file, fileList) => this.props.beforeRemove(file, fileList), notifyBeforeClear: fileList => this.props.beforeClear(fileList) }); } componentDidMount() { this.foundation.init(); } componentWillUnmount() { this.foundation.destroy(); } render() { const _a = this.props, { style, className, multiple, accept, disabled, children, capture, listType, prompt, promptPosition, draggable, validateMessage, validateStatus, directory } = _a, rest = __rest(_a, ["style", "className", "multiple", "accept", "disabled", "children", "capture", "listType", "prompt", "promptPosition", "draggable", "validateMessage", "validateStatus", "directory"]); const uploadCls = (0, _classnames.default)(prefixCls, { [`${prefixCls}-picture`]: listType === _constants.strings.FILE_LIST_PIC, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-default`]: validateStatus === 'default', [`${prefixCls}-error`]: validateStatus === 'error', [`${prefixCls}-warning`]: validateStatus === 'warning', [`${prefixCls}-success`]: validateStatus === 'success' }, className); const inputCls = (0, _classnames.default)(`${prefixCls}-hidden-input`); const inputReplaceCls = (0, _classnames.default)(`${prefixCls}-hidden-input-replace`); const promptCls = (0, _classnames.default)(`${prefixCls}-prompt`); const validateMsgCls = (0, _classnames.default)(`${prefixCls}-validate-message`); const dirProps = directory ? { directory: 'directory', webkitdirectory: 'webkitdirectory' } : {}; return /*#__PURE__*/_react.default.createElement("div", Object.assign({ className: uploadCls, style: style, "x-prompt-pos": promptPosition }, this.getDataAttr(rest)), /*#__PURE__*/_react.default.createElement("input", Object.assign({ key: this.state.inputKey, capture: capture, multiple: multiple, accept: accept, onChange: this.onChange, type: "file", autoComplete: "off", tabIndex: -1, className: inputCls, ref: this.inputRef }, dirProps)), /*#__PURE__*/_react.default.createElement("input", { key: this.state.replaceInputKey, multiple: false, accept: accept, onChange: this.onReplaceChange, type: "file", autoComplete: "off", tabIndex: -1, className: inputReplaceCls, ref: this.replaceInputRef }), this.renderAddContent(), prompt ? (/*#__PURE__*/_react.default.createElement("div", { className: promptCls, "x-semi-prop": "prompt" }, prompt)) : null, validateMessage ? (/*#__PURE__*/_react.default.createElement("div", { className: validateMsgCls, "x-semi-prop": "validateMessage" }, validateMessage)) : null, this.renderFileList()); } } Upload.propTypes = { accept: _propTypes.default.string, action: _propTypes.default.string.isRequired, addOnPasting: _propTypes.default.bool, afterUpload: _propTypes.default.func, beforeClear: _propTypes.default.func, beforeRemove: _propTypes.default.func, beforeUpload: _propTypes.default.func, children: _propTypes.default.node, className: _propTypes.default.string, customRequest: _propTypes.default.func, data: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]), defaultFileList: _propTypes.default.array, directory: _propTypes.default.bool, disabled: _propTypes.default.bool, dragIcon: _propTypes.default.node, dragMainText: _propTypes.default.node, dragSubText: _propTypes.default.node, draggable: _propTypes.default.bool, fileList: _propTypes.default.array, fileName: _propTypes.default.string, headers: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]), hotSpotLocation: _propTypes.default.oneOf(['start', 'end']), itemStyle: _propTypes.default.object, limit: _propTypes.default.number, listType: _propTypes.default.oneOf(_constants.strings.LIST_TYPE), maxSize: _propTypes.default.number, minSize: _propTypes.default.number, multiple: _propTypes.default.bool, name: _propTypes.default.string, onAcceptInvalid: _propTypes.default.func, onChange: _propTypes.default.func, onClear: _propTypes.default.func, onDrop: _propTypes.default.func, onError: _propTypes.default.func, onExceed: _propTypes.default.func, onFileChange: _propTypes.default.func, onOpenFileDialog: _propTypes.default.func, onPreviewClick: _propTypes.default.func, onProgress: _propTypes.default.func, onRemove: _propTypes.default.func, onRetry: _propTypes.default.func, onSizeError: _propTypes.default.func, onSuccess: _propTypes.default.func, onPastingError: _propTypes.default.func, previewFile: _propTypes.default.func, prompt: _propTypes.default.node, promptPosition: _propTypes.default.oneOf(_constants.strings.PROMPT_POSITION), picWidth: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), picHeight: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), renderFileItem: _propTypes.default.func, renderPicPreviewIcon: _propTypes.default.func, renderFileOperation: _propTypes.default.func, renderPicClose: _propTypes.default.func, renderPicInfo: _propTypes.default.func, renderThumbnail: _propTypes.default.func, showClear: _propTypes.default.bool, showPicInfo: _propTypes.default.bool, showReplace: _propTypes.default.bool, showRetry: _propTypes.default.bool, showUploadList: _propTypes.default.bool, style: _propTypes.default.object, timeout: _propTypes.default.number, transformFile: _propTypes.default.func, uploadTrigger: _propTypes.default.oneOf(_constants.strings.UPLOAD_TRIGGER), validateMessage: _propTypes.default.node, validateStatus: _propTypes.default.oneOf(_constants.strings.VALIDATE_STATUS), withCredentials: _propTypes.default.bool, showTooltip: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.object]) }; Upload.defaultProps = { defaultFileList: [], disabled: false, listType: 'list', hotSpotLocation: 'end', multiple: false, onAcceptInvalid: _noop2.default, onChange: _noop2.default, beforeRemove: () => true, beforeClear: () => true, onClear: _noop2.default, onDrop: _noop2.default, onError: _noop2.default, onExceed: _noop2.default, onFileChange: _noop2.default, onOpenFileDialog: _noop2.default, onProgress: _noop2.default, onRemove: _noop2.default, onRetry: _noop2.default, onSizeError: _noop2.default, onSuccess: _noop2.default, onPastingError: _noop2.default, promptPosition: 'right', showClear: true, showPicInfo: false, showReplace: false, showRetry: true, showUploadList: true, uploadTrigger: 'auto', withCredentials: false, showTooltip: true }; Upload.FileCard = _fileCard.default; var _default = exports.default = Upload;