feat: input-image功能优化 (#6645)

This commit is contained in:
sqzhou 2023-04-25 16:38:42 +08:00 committed by GitHub
parent a624a8d98a
commit d2c52b0101
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 303 additions and 171 deletions

View File

@ -449,6 +449,7 @@ app.listen(8080, function () {});
| fixedSize | `boolean` | | 是否开启固定尺寸,若开启,需同时设置 fixedSizeClassName |
| fixedSizeClassName | `string` | | 开启固定尺寸时,根据此值控制展示尺寸。例如`h-30`,即图片框高为 h-30,AMIS 将自动缩放比率设置默认图所占位置的宽度,最终上传图片根据此尺寸对应缩放。 |
| initAutoFill | `boolean` | `false` | 表单反显时是否执行 autoFill |
| uploadBtnText | `string` \| [SchemaNode](../../docs/types/schemanode) | | 上传按钮文案。支持tpl、schema形式配置。 |
| dropCrop | `boolean` | `true` | 图片上传后是否进入裁剪模式 |
| initCrop | `boolean` | `false` | 图片选择器初始化后是否立即进入裁剪模式 |

View File

@ -10,6 +10,7 @@
}
&-addBtn {
position: relative;
margin: 0;
width: px2rem(120px);
height: px2rem(120px);
@ -23,6 +24,20 @@
var(--inputImage-base-default-bottom-left-border-radius);
cursor: pointer;
margin-right: var(--gap-base);
&-bg {
position: absolute;
z-index: 0;
overflow: hidden;
left: 0;
right: 0;
top: 0;
bottom: 0;
.#{$ns}Image {
border: 0;
}
}
@include button-variant(
var(--ImageControl-addBtn-bg),
@ -88,6 +103,7 @@
);
svg {
z-index: 1;
top: 0;
margin-bottom: var(--inputImage-base-default-icon-margin);
font-size: var(--inputImage-base-default-icon-size);
@ -97,11 +113,17 @@
}
&-text {
z-index: 1;
font-size: var(--inputImage-base-default-fontSize);
font-weight: var(--inputImage-base-default-fontWeight);
}
&:not(:disabled):not(.is-disabled) {
&.is-invalid:hover {
border-color: var(--FileControl-danger-color);
}
&:hover {
svg {
color: var(--inputImage-base-hover-icon-color);
@ -115,6 +137,10 @@
}
}
&.is-invalid {
border-color: var(--FileControl-danger-color);
}
&.is-disabled {
pointer-events: none;
@ -145,7 +171,7 @@
margin: 30px 0 0 var(--Tooltip--attr-gap);
}
&-dropzone:focus &-addBtn {
&-dropzone:focus &-addBtn:not(.is-invalid) {
border-color: var(--inputImage-base-hover-top-border-color)
var(--inputImage-base-hover-right-border-color)
var(--inputImage-base-hover-bottom-border-color)
@ -159,7 +185,7 @@
}
&-dropzone:hover {
&-addBtn:not(.is-disabled) {
&-addBtn:not(.is-disabled):not(.is-invalid) {
border-color: var(--inputImage-base-hover-top-border-color)
var(--inputImage-base-hover-right-border-color)
var(--inputImage-base-hover-bottom-border-color)
@ -173,7 +199,7 @@
}
&-item {
// border: var(--borderWidth) solid var(--borderColor);
border: var(--borderWidth) solid var(--borderColor);
border-radius: var(--ImageControl-addBtn-borderRadius);
vertical-align: top;
padding: var(--gap-xs);
@ -188,6 +214,10 @@
border-color: var(--FileControl-danger-color);
}
&-errorTip {
color: var(--FileControl-danger-color);
}
svg.icon-refresh {
transform: rotate(180deg);
}

View File

@ -5,7 +5,6 @@
*/
import React from 'react';
import cx from 'classnames';
import {ClassNamesFn, themeable} from 'amis-core';
interface TooltipProps extends React.HTMLProps<HTMLDivElement> {
@ -19,6 +18,7 @@ interface TooltipProps extends React.HTMLProps<HTMLDivElement> {
placement?: string;
showArrow?: boolean;
tooltipTheme?: string;
bodyClassName?: string;
[propName: string]: any;
}
@ -48,6 +48,7 @@ export class Tooltip extends React.Component<TooltipProps> {
showArrow,
onMouseEnter,
onMouseLeave,
bodyClassName,
...rest
} = this.props;
@ -69,7 +70,7 @@ export class Tooltip extends React.Component<TooltipProps> {
<div className={cx(`Tooltip-arrow`)} {...arrowProps} />
) : null}
{title ? <div className={cx('Tooltip-title')}>{title}</div> : null}
<div className={cx('Tooltip-body')}>{children}</div>
<div className={cx(bodyClassName, 'Tooltip-body')}>{children}</div>
</div>
);
}

View File

@ -80,6 +80,11 @@ export interface TooltipObject {
* CSS类名
*/
tooltipClassName?: string;
/**
* Body的CSS类名
*/
tooltipBodyClassName?: string;
/**
* html xss filter
*/
@ -299,6 +304,7 @@ export class TooltipWrapper extends React.Component<
trigger,
rootClose,
tooltipClassName,
tooltipBodyClassName,
style,
disabled = false,
offset,
@ -347,6 +353,7 @@ export class TooltipWrapper extends React.Component<
className={tooltipClassName}
tooltipTheme={tooltipTheme}
showArrow={showArrow}
bodyClassName={tooltipBodyClassName}
onMouseEnter={
~triggers.indexOf('hover') ? this.tooltipMouseEnter : () => {}
}

View File

@ -141,7 +141,6 @@ register('de-DE', {
'File.sizeLimit': 'Die maximale Dateigröße ist {{maxSize}}',
'File.start': 'Hochladen beginnen',
'File.upload': 'Hochladen',
'Image.upload': 'Hochladen',
'File.uploadFailed': 'Zurückgegebene Daten der Upload-API sind leer',
'File.uploading': 'Wird hochgeladen...',
'FormItem.autoFillLoadFailed':
@ -157,6 +156,7 @@ register('de-DE', {
'Iframe.invalid': 'Ungültige Iframe-URL',
'Iframe.invalidProtocol':
'HTTP-URL-Iframe kann nicht in https verwendet werden',
'Image.upload': 'Bild hochladen',
'Image.configError':
'Es können nur eine Beschneidung oder mehrere festgelegt werden',
'Image.crop': 'Bild beschneiden',
@ -241,7 +241,8 @@ register('de-DE', {
'Table.valueField': 'valueField muss vorhanden sein',
'Table.index': 'Index',
'Table.add': 'Neu',
'Table.addButtonDisabledTip': 'Reichen Sie bei der Inhaltsbearbeitung zuerst ein und erstellen Sie dann eine neue Option',
'Table.addButtonDisabledTip':
'Reichen Sie bei der Inhaltsbearbeitung zuerst ein und erstellen Sie dann eine neue Option',
'Table.toggleColumn': 'Spalten anzeigen',
'Table.searchFields': 'Abfragefelder setzen',
'Tag.placeholder': 'Noch kein Tag',

View File

@ -139,7 +139,6 @@ register('en-US', {
'File.sizeLimit': 'The maximum file size is {{maxSize}}',
'File.start': 'Start upload',
'File.upload': 'Upload',
'Image.upload': 'Upload image',
'File.uploadFailed': 'return data of udpload api is empty',
'File.uploading': 'Uploading',
'FormItem.autoFillLoadFailed': 'return data of autoUpdate api is error',
@ -152,6 +151,8 @@ register('en-US', {
'Form.nestedError': 'Form cannot appear as a descendant of form',
'Iframe.invalid': 'Invalid iframe url',
'Iframe.invalidProtocol': 'Can not use http url iframe in https',
'Image.upload': 'Upload image',
'Image.errorRetry': 'upload failed, please try again',
'Image.configError': 'Can only set one of crop or multiple',
'Image.crop': 'Crop image',
'Image.dragDrop': `Drag 'n' drop some photos here`,
@ -233,7 +234,8 @@ register('en-US', {
'Table.valueField': 'Must have valueField',
'Table.index': 'Index',
'Table.add': 'Add',
'Table.addButtonDisabledTip': 'In content editing, please submit first and then create a new option',
'Table.addButtonDisabledTip':
'In content editing, please submit first and then create a new option',
'Table.toggleColumn': 'Display columns',
'Table.searchFields': 'Set query fields',
'Tag.placeholder': 'No tag yet',

View File

@ -130,7 +130,7 @@ register('zh-CN', {
'File.dragDrop': '将文件拖到此处,或',
'File.clickUpload': '点击上传',
'File.helpText': '帮助文档',
'File.errorRetry': '文件上传失败请重试',
'File.errorRetry': '文件上传失败请重试',
'File.failed': '失败文件',
'File.invalidType': '{{files}} 不符合类型的 {{accept}} 的设定,请仔细检查',
'File.maxLength': '最多上传 {{maxLength}} 个文件',
@ -143,7 +143,6 @@ register('zh-CN', {
'File.sizeLimit': '文件大小不超过 {{maxSize}}',
'File.start': '开始上传',
'File.upload': '文件上传',
'Image.upload': '图片上传',
'File.uploadFailed': '接口返回错误,请仔细检查',
'File.uploading': '上传中...',
'FormItem.autoFillLoadFailed': '接口返回错误,请仔细检查',
@ -156,6 +155,8 @@ register('zh-CN', {
'Form.nestedError': '表单不要直接嵌套在表单下面',
'Iframe.invalid': 'iframe 地址不合法',
'Iframe.invalidProtocol': '无法加载 http 协议的 iframe',
'Image.upload': '图片上传',
'Image.errorRetry': '上传失败,请重试',
'Image.configError': '图片多选配置和裁剪配置只能设置一个',
'Image.crop': '裁剪图片',
'Image.dragDrop': '将图片拖拽到此处',

View File

@ -10,14 +10,13 @@ import {
// import 'cropperjs/dist/cropper.css';
const Cropper = React.lazy(() => import('react-cropper'));
import DropZone from 'react-dropzone';
import {FileRejection, DropEvent} from 'react-dropzone';
import {FileRejection, ErrorCode, DropEvent} from 'react-dropzone';
import 'blueimp-canvastoblob';
import find from 'lodash/find';
import {Payload, ActionObject} from 'amis-core';
import {buildApi} from 'amis-core';
import {createObject, qsstringify, guid, isEmpty, qsparse} from 'amis-core';
import {Icon} from 'amis-ui';
import {Button} from 'amis-ui';
import {Icon, TooltipWrapper, Button} from 'amis-ui';
import accepts from 'attr-accept';
import {getNameFromUrl} from './InputFile';
import ImageComponent, {ImageThumbProps} from '../Image';
@ -34,6 +33,7 @@ import {filter} from 'amis-core';
import isPlainObject from 'lodash/isPlainObject';
import merge from 'lodash/merge';
import omit from 'lodash/omit';
import {TplSchema} from '../Tpl';
/**
* Image
@ -75,6 +75,11 @@ export interface ImageControlSchema extends FormBaseControlSchema {
*/
autoUpload?: boolean;
/**
*
*/
uploadBtnText?: string | TplSchema;
/**
* CSS
*/
@ -304,6 +309,8 @@ export interface ImageState {
cropFileName?: string; // 主要是用于后续上传的时候获得用户名
submitOnChange?: boolean;
frameImageWidth?: number;
// drop-zone 组件是否能多选的限制,主要是为了限制重选情况下只能单选,其他情况和 props 的 multiple 一致
dropMultiple?: boolean;
}
export interface FileValue {
@ -324,6 +331,7 @@ export interface FileX extends File {
preview?: string;
state?: 'init' | 'error' | 'pending' | 'uploading' | 'uploaded' | 'invalid';
progress?: number;
error?: string;
[propName: string]: any;
}
@ -384,7 +392,8 @@ export default class ImageControl extends React.Component<
state: ImageState = {
uploading: false,
locked: false,
files: []
files: [],
dropMultiple: false
};
files: Array<FileValue | FileX> = [];
@ -429,6 +438,7 @@ export default class ImageControl extends React.Component<
...this.state,
files: (this.files = files),
crop: this.buildCrop(props),
dropMultiple: props.multiple,
frameImageWidth: 0
};
@ -453,6 +463,7 @@ export default class ImageControl extends React.Component<
this.handlePaste = this.handlePaste.bind(this);
this.syncAutoFill = this.syncAutoFill.bind(this);
this.handleReSelect = this.handleReSelect.bind(this);
this.handleFileCancel = this.handleFileCancel.bind(this);
}
componentDidMount() {
@ -518,6 +529,12 @@ export default class ImageControl extends React.Component<
);
}
if (prevProps.multiple !== props.multiple) {
this.setState({
dropMultiple: props.multiple
});
}
if (prevProps.crop !== props.crop) {
this.setState({
crop: this.buildCrop(props)
@ -564,31 +581,91 @@ export default class ImageControl extends React.Component<
if (evt.type !== 'change' && evt.type !== 'drop') {
return;
}
const {multiple, env, accept, translate: __} = this.props;
const files = rejectedFiles.map(fileRejection => ({
...fileRejection.file,
state: 'invalid',
id: guid(),
name: fileRejection.file.name
}));
const {
accept,
multiple,
formItem,
maxLength,
maxSize,
translate: __
} = this.props;
// this.setState({
// files: this.files = multiple
// ? this.files.concat(files)
// : this.files.length
// ? this.files
// : files.slice(0, 1)
// });
let reFiles = rejectedFiles.map(item => item.file);
let currentFiles = this.files;
env.alert(
__('File.invalidType', {
files: files.map((file: any) => `${file.name}`).join(' '),
accept
})
if (!multiple && currentFiles.length) {
currentFiles = [];
}
const allowed =
(multiple
? maxLength
? maxLength
: reFiles.length + currentFiles.length
: 1) - currentFiles.length;
// 限制过多的错误文件
if (allowed <= 0) {
return;
}
const errorFiles = [].slice.call(reFiles, 0, allowed);
const formatFile = (file: FileX): FileX => {
file.id = guid();
const errors = rejectedFiles.find(i => i.file === file)?.errors;
if (errors) {
file.error = errors
.map(err => {
// 类型错误
if (err.code === ErrorCode.FileInvalidType) {
return __('File.invalidType', {
files: file.name,
accept
});
}
// 文件太大
else if (err.code === ErrorCode.FileTooLarge) {
return __('File.sizeLimit', {maxSize});
}
})
.join('; ');
}
file.state = 'invalid';
return file;
};
if (multiple) {
if (this.reuploadIndex !== undefined) {
currentFiles.splice(this.reuploadIndex, 1, formatFile(errorFiles[0]));
this.reuploadIndex = undefined;
} else {
errorFiles.forEach((item: any) => {
currentFiles.push(formatFile(item));
});
}
} else {
const file = formatFile(errorFiles[0]);
currentFiles.splice(0, 1, file);
formItem?.setError(file?.error ?? '');
}
return this.setState(
{
files: (this.files = currentFiles),
dropMultiple: multiple
},
this.tick
);
}
handleFileCancel() {
this.setState({
dropMultiple: this.props.multiple
});
}
startUpload(retry: boolean = false) {
if (this.state.uploading) {
return;
@ -630,8 +707,7 @@ export default class ImageControl extends React.Component<
return;
}
const env = this.props.env;
const __ = this.props.translate;
const {translate: __, formItem, multiple} = this.props;
const file = find(this.files, item => item.state === 'pending') as FileX;
if (file) {
this.current = file;
@ -658,21 +734,12 @@ export default class ImageControl extends React.Component<
newFile.state =
file.state !== 'uploading' ? file.state : 'error';
newFile.error = error;
if (!this.props.multiple && newFile.state === 'invalid') {
files.splice(idx, 1);
this.current = null;
return this.setState(
{
files: (this.files = files),
error: error
},
this.tick
);
if (!multiple) {
formItem?.setError(error);
if (files.length === 1) {
files.splice(0, 1);
}
}
env.notify('error', error || __('File.errorRetry'));
} else {
newFile = {
name: file.name || this.state.cropFileName,
@ -682,7 +749,7 @@ export default class ImageControl extends React.Component<
}
files.splice(idx, 1, newFile);
this.current = null;
this.setState(
return this.setState(
{
files: (this.files = files)
},
@ -713,11 +780,10 @@ export default class ImageControl extends React.Component<
},
async () => {
await this.onChange(!!this.resolve, false);
if (this.resolve) {
this.resolve(
this.files.some(file => file.state === 'error')
? __('File.errorRetry')
? __('Image.errorRetry')
: null
);
this.resolve = undefined;
@ -831,7 +897,7 @@ export default class ImageControl extends React.Component<
}
}
onChange((this.emitValue = newValue || ''), undefined, changeImmediately);
onChange(this.emitValue, undefined, changeImmediately);
curInitAutoFill && this.syncAutoFill();
}
@ -910,7 +976,12 @@ export default class ImageControl extends React.Component<
if (event && event.type === 'drop' && this.reuploadIndex !== undefined) {
this.reuploadIndex = undefined;
}
this.addFiles(files);
this.setState(
{
dropMultiple: multiple
},
() => this.addFiles(files)
);
}
handlePaste(e: React.ClipboardEvent<any>) {
@ -976,7 +1047,7 @@ export default class ImageControl extends React.Component<
return;
}
const {multiple, maxLength, maxSize, accept, translate: __} = this.props;
const {multiple, maxLength, maxSize, translate: __} = this.props;
let currentFiles = this.files;
if (!multiple && currentFiles.length) {
@ -1115,7 +1186,7 @@ export default class ImageControl extends React.Component<
this._send(file, this.props.receiver as string, {}, onProgress)
.then(async (ret: Payload) => {
if (ret.status && (ret as any).status !== '0') {
throw new Error(ret.msg || __('File.errorRetry'));
throw new Error(ret.msg || __('Image.errorRetry'));
}
const obj: FileValue = {
@ -1143,7 +1214,7 @@ export default class ImageControl extends React.Component<
if (dispatcher?.prevented) {
return;
}
cb(error.message || __('File.errorRetry'), file);
cb(error.message || __('Image.errorRetry'), file);
});
}
@ -1280,7 +1351,7 @@ export default class ImageControl extends React.Component<
}
validate(): any {
const __ = this.props.translate;
const {translate: __, multiple} = this.props;
if (this.state.locked && this.state.lockedReason) {
return this.state.lockedReason;
@ -1297,8 +1368,10 @@ export default class ImageControl extends React.Component<
this.resolve = resolve;
this.startUpload();
});
} else if (this.files.some(item => item.state === 'error')) {
return __('File.errorRetry');
} else if (
this.files.some(i => i.state && ['error', 'invalid'].includes(i.state))
) {
return multiple ? ' ' : this.files[0].error;
}
}
@ -1335,16 +1408,20 @@ export default class ImageControl extends React.Component<
// 重新上传
handleReSelect(index: number) {
this.reuploadIndex = index;
this.dropzone.current && this.dropzone.current.open();
this.setState(
{
dropMultiple: false
},
() => {
this.dropzone.current && this.dropzone.current.open();
}
);
}
render() {
const {
className,
style,
classnames: cx,
placeholder,
placeholderPlacement,
disabled,
multiple,
accept,
@ -1357,6 +1434,9 @@ export default class ImageControl extends React.Component<
frameImage,
fixedSize,
fixedSizeClassName,
uploadBtnText,
maxSize,
render,
themeCss,
inputImageControlClassName,
addBtnControlClassName,
@ -1414,8 +1494,15 @@ export default class ImageControl extends React.Component<
null
);
const {files, error, crop, uploading, cropFile, frameImageWidth} =
this.state;
const {
files,
error,
crop,
uploading,
cropFile,
frameImageWidth,
dropMultiple
} = this.state;
let frameImageStyle: any = {};
if (fixedSizeClassName && frameImageWidth && fixedSize) {
frameImageStyle.width = frameImageWidth;
@ -1472,17 +1559,18 @@ export default class ImageControl extends React.Component<
ref={this.dropzone}
onDrop={this.handleDrop}
onDropRejected={this.handleDropRejected}
onFileDialogCancel={this.handleFileCancel}
accept={accept}
multiple={multiple}
multiple={dropMultiple}
disabled={disabled}
maxSize={maxSize}
>
{({
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
isFocused
isDragReject
}) => (
<div
{...getRootProps({
@ -1527,51 +1615,71 @@ export default class ImageControl extends React.Component<
>
{file.state === 'invalid' ||
file.state === 'error' ? (
<div className={cx('Image--thumb')}>
<div className={cx('Image-thumbWrap')}>
<div
className={cx(
'Image-thumb',
'ImageControl-filename'
)}
>
<Icon icon="image" className="icon" />
<span
title={
file.name ||
getNameFromUrl(file.value || file.url)
}
<TooltipWrapper
placement="top"
tooltip={{
content: file.error,
disabled: !multiple && files.length === 1,
tooltipBodyClassName: cx(
'ImageControl-item-errorTip'
)
}}
trigger="hover"
>
<div className={cx('Image--thumb')}>
<div className={cx('Image-thumbWrap')}>
<div
className={cx(
'Image-thumb',
'ImageControl-filename'
)}
>
{file.name ||
getNameFromUrl(file.value || file.url)}
</span>
</div>
<Icon icon="image" className="icon" />
<span
title={
file.name ||
getNameFromUrl(file.value || file.url)
}
>
{file.name ||
getNameFromUrl(
file.value || file.url
)}
</span>
</div>
<div className={cx('Image-overlay')}>
<a
data-tooltip={__('File.repick')}
data-position="bottom"
onClick={this.handleRetry.bind(this, key)}
>
<Icon icon="upload" className="icon" />
</a>
{!disabled ? (
<div className={cx('Image-overlay')}>
<a
data-tooltip={__('Select.clear')}
data-tooltip={__('File.repick')}
data-position="bottom"
onClick={this.removeFile.bind(
onClick={this.handleReSelect.bind(
this,
file,
key
)}
>
<Icon icon="remove" className="icon" />
<Icon icon="upload" className="icon" />
</a>
) : null}
{!disabled ? (
<a
data-tooltip={__('Select.clear')}
data-position="bottom"
onClick={this.removeFile.bind(
this,
file,
key
)}
>
<Icon
icon="remove"
className="icon"
/>
</a>
) : null}
</div>
</div>
</div>
</div>
</TooltipWrapper>
) : file.state === 'uploading' ? (
<>
<a
@ -1627,24 +1735,6 @@ export default class ImageControl extends React.Component<
thumbRatio={thumbRatio}
overlays={
<>
{/* {file.info ? (
[
<div key="info">
{file.info.width} x{' '}
{file.info.height}
</div>,
file.info.len ? (
<div key="size">
{prettyBytes(
file.info.len
, 1024)}
</div>
) : null
]
) : (
<div>...</div>
)} */}
<a
data-tooltip={__('Image.zoomIn')}
data-position="bottom"
@ -1730,55 +1820,58 @@ export default class ImageControl extends React.Component<
{(multiple && (!maxLength || files.length < maxLength)) ||
(!multiple && !files.length) ? (
<label
className={cx(
'ImageControl-addBtn',
{
'is-disabled': disabled
},
fixedSize ? 'ImageControl-fixed-size' : '',
fixedSize ? fixedSizeClassName : '',
addBtnControlClassName
)}
style={frameImageStyle}
onClick={this.handleSelect}
data-tooltip={__(placeholder)}
data-position={placeholderPlacement}
ref={this.frameImageRef}
<TooltipWrapper
placement="top"
trigger="hover"
tooltip={{
content: error,
disabled: !multiple || !error
}}
>
{filterFrameImage ? (
<ImageComponent
key="upload-default-image"
src={filterFrameImage}
className={cx(
fixedSize ? 'Image-thumb--fixed-size' : ''
<label
className={cx(
'ImageControl-addBtn',
{
'is-disabled': disabled
},
fixedSize ? 'ImageControl-fixed-size' : '',
fixedSize ? fixedSizeClassName : '',
addBtnControlClassName,
error ? 'is-invalid' : ''
)}
style={frameImageStyle}
onClick={this.handleSelect}
ref={this.frameImageRef}
>
<Icon
icon="plus-fine"
className="icon"
iconContent={cx(
':ImageControl-addBtn-icon',
iconControlClassName
)}
onLoad={this.handleFrameImageLoaded.bind(this)}
thumbMode={thumbMode}
thumbRatio={thumbRatio}
/>
) : (
<>
<Icon
icon="plus-fine"
className="icon"
iconContent={cx(
':ImageControl-addBtn-icon',
iconControlClassName
)}
/>
<span className={cx('ImageControl-addBtn-text')}>
{__('Image.upload')}
</span>
</>
)}
{isFocused ? (
<span className={cx('ImageControl-pasteTip')}>
{__('Image.pasteTip')}
<span className={cx('ImageControl-addBtn-text')}>
{!uploadBtnText
? __('Image.upload')
: render(`btn-upload-text`, uploadBtnText, {})}
</span>
) : null}
</label>
{filterFrameImage ? (
<div className={cx('ImageControl-addBtn-bg')}>
<ImageComponent
key="upload-default-image"
src={filterFrameImage}
className={cx(
fixedSize ? 'Image-thumb--fixed-size' : ''
)}
onLoad={this.handleFrameImageLoaded.bind(this)}
thumbMode={thumbMode}
thumbRatio={thumbRatio}
/>
</div>
) : null}
</label>
</TooltipWrapper>
) : null}
{!autoUpload && !hideUploadButton && files.length ? (
@ -1791,10 +1884,6 @@ export default class ImageControl extends React.Component<
{__(uploading ? 'File.pause' : 'File.start')}
</Button>
) : null}
{error ? (
<div className={cx('ImageControl-errorMsg')}>{error}</div>
) : null}
</>
)}
</div>