import React, { Component, ReactNode, MouseEventHandler, MouseEvent, CSSProperties, SVGProps, FC } from 'react'; import cls from 'classnames'; import PropTypes from 'prop-types'; import { cssClasses, strings } from '@douyinfe/semi-foundation/upload/constants'; import FileCardFoundation, { FileCardAdapter } from '@douyinfe/semi-foundation/upload/fileCardFoundation'; import { getFileSize } from '@douyinfe/semi-foundation/upload/utils'; import { IconAlertCircle, IconClose, IconClear, IconFile, IconRefresh, IconEyeOpened } from '@douyinfe/semi-icons'; import LocaleConsumer from '../locale/localeConsumer'; import { Locale } from '../locale/interface'; import BaseComponent from '../_base/baseComponent'; import Button from '../button/index'; import Progress from '../progress/index'; import Tooltip from '../tooltip/index'; import Spin from '../spin/index'; import { isElement } from '../_base/reactUtils'; import { RenderFileItemProps } from './interface'; const prefixCls = cssClasses.PREFIX; const ErrorSvg: FC> = (props = {}) => ( ); const ReplaceSvg: FC> = (props = {}) => ( ); const DirectorySvg: FC> = (props = {}) => ( ); export interface FileCardProps extends RenderFileItemProps { className?: string; style?: CSSProperties; picWidth?: string | number; picHeight?: string | number } export interface FileCardState { fallbackPreview?: boolean } class FileCard extends BaseComponent { static propTypes = { className: PropTypes.string, disabled: PropTypes.bool, listType: PropTypes.string, name: PropTypes.string, onPreviewClick: PropTypes.func, onRemove: PropTypes.func, onReplace: PropTypes.func, onRetry: PropTypes.func, percent: PropTypes.number, preview: PropTypes.bool, previewFile: PropTypes.func, picWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), picHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), showReplace: PropTypes.bool, showRetry: PropTypes.bool, size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), status: PropTypes.string, style: PropTypes.object, url: PropTypes.string, validateMessage: PropTypes.node, index: PropTypes.number }; static defaultProps = { listType: strings.FILE_LIST_DEFAULT, name: '', onRemove: (): void => undefined, onRetry: (): void => undefined, preview: false, size: '', }; constructor(props: FileCardProps) { super(props); this.state = { fallbackPreview: false, }; this.foundation = new FileCardFoundation(this.adapter); } get adapter(): FileCardAdapter { return { ...super.adapter, updateFallbackPreview: (fallbackPreview: boolean): void => this.setState({ fallbackPreview }), }; } transSize(size: string | number): string { if (typeof size === 'number') { return getFileSize(size); } return size; } renderValidateMessage(): ReactNode { const { status, validateMessage } = this.props; let content = null; switch (true) { case typeof validateMessage === 'string' && status === strings.FILE_STATUS_VALIDATING: content = (<>{validateMessage}); break; case typeof validateMessage === 'string': content = (<>{validateMessage}); break; case isElement(validateMessage): content = validateMessage; break; default: break; } return content; } renderPicValidateMsg(): ReactNode { const { status, validateMessage } = this.props; let icon = null; switch (true) { case validateMessage && status === strings.FILE_STATUS_VALIDATING: icon = (); break; case validateMessage && (status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL): icon = (
); break; default: break; } return icon ? {icon} : null; } renderPic(locale: Locale['Upload']): ReactNode { const { fallbackPreview } = this.state; const { url, percent, status, disabled, style, onPreviewClick, showPicInfo, renderPicInfo, renderPicClose, renderPicPreviewIcon, renderThumbnail, name, index, picHeight, picWidth } = this.props; const showProgress = status === strings.FILE_STATUS_UPLOADING && percent !== 100; const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && this.props.showRetry; const showReplace = status === strings.FILE_STATUS_SUCCESS && this.props.showReplace; const showPreview = status === strings.FILE_STATUS_SUCCESS && !this.props.showReplace; const customThumbnail = typeof renderThumbnail === 'function'; const filePicCardCls = cls({ [`${prefixCls}-picture-file-card`]: true, [`${prefixCls}-picture-file-card-preview-fallback`]: fallbackPreview, [`${prefixCls}-picture-file-card-disabled`]: disabled, [`${prefixCls}-picture-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined', [`${prefixCls}-picture-file-card-error`]: status === strings.FILE_STATUS_UPLOAD_FAIL, [`${prefixCls}-picture-file-card-uploading`]: showProgress, [`${prefixCls}-picture-file-card-custom-thumbnail`]: customThumbnail && picHeight && picWidth }); const retry = (
this.onRetry(e)}>
); const replace = (
this.onReplace(e)}>
); const preview = (
{typeof renderPicPreviewIcon === 'function' ? renderPicPreviewIcon(this.props) : null}
); const close = ( <> {typeof renderPicClose === 'function' ? <>{renderPicClose({ className: `${prefixCls}-picture-file-card-close`, remove: e => this.onRemove(e) })} :
this.onRemove(e)}>
} ); const picInfo = typeof renderPicInfo === 'function' ? renderPicInfo(this.props) : (
{index + 1}
); let imgStyle: { height?: number | string; width?: number | string } = {}; let itemStyle = style ? { ...style } : {}; if (picHeight) { itemStyle.height = picHeight; imgStyle.height = picHeight; } if (picWidth) { itemStyle.width = picWidth; imgStyle.width = picWidth; } const defaultThumbTail = !fallbackPreview ? {name} this.foundation.handleImageError(error)} style={imgStyle} /> : ; const thumbnail = customThumbnail ? renderThumbnail(this.props) : defaultThumbTail; return (
{thumbnail} {showProgress ? : null} {showRetry ? retry : null} {showReplace && replace} {showPreview && preview} {showPicInfo && picInfo} {!disabled && close} {this.renderPicValidateMsg()}
); } renderFile(locale: Locale["Upload"]) { const { name, size, percent, url, showRetry: propsShowRetry, showReplace: propsShowReplace, preview, previewFile, status, style, onPreviewClick, renderFileOperation } = this.props; const { fallbackPreview } = this.state; const fileCardCls = cls({ [`${prefixCls}-file-card`]: true, [`${prefixCls}-file-card-fail`]: status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL, [`${prefixCls}-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined', }); const previewCls = cls({ [`${prefixCls}-file-card-preview`]: true, [`${prefixCls}-file-card-preview-placeholder`]: !preview || previewFile || fallbackPreview }); const infoCls = `${prefixCls}-file-card-info`; const closeCls = `${prefixCls}-file-card-close`; const replaceCls = `${prefixCls}-file-card-replace`; const showProgress = !(percent === 100 || typeof percent === 'undefined') && status === strings.FILE_STATUS_UPLOADING; // only show retry when upload fail & showRetry is true, no need to show during validate fail const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && propsShowRetry; const showReplace = status === strings.FILE_STATUS_SUCCESS && propsShowReplace; const fileSize = this.transSize(size); let previewContent: ReactNode = (preview && !fallbackPreview) ? ({name} this.foundation.handleImageError(error)} />) : (); if (previewFile) { previewContent = previewFile(this.props); } const operation = typeof renderFileOperation === 'function' ? renderFileOperation(this.props) :