feat: Upload support customize previewFile (#15984)

* support preview file

* use promise

* dealy load

* use canvas of render

* use domHook of test

* update demo

* add snapshot

* update types

* update testcase
This commit is contained in:
zombieJ 2019-04-11 13:40:28 +08:00 committed by GitHub
parent 574313d2d8
commit c4283ebe72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 372 additions and 117 deletions

View File

@ -0,0 +1,54 @@
export function spyElementPrototypes(Element, properties) {
const propNames = Object.keys(properties);
const originDescriptors = {};
propNames.forEach(propName => {
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
originDescriptors[propName] = originDescriptor;
const spyProp = properties[propName];
if (typeof spyProp === 'function') {
// If is a function
Element.prototype[propName] = function spyFunc(...args) {
return spyProp.call(this, originDescriptor, ...args);
};
} else {
// Otherwise tread as a property
Object.defineProperty(Element.prototype, propName, {
...spyProp,
set(value) {
if (spyProp.set) {
return spyProp.set.call(this, originDescriptor, value);
}
return originDescriptor.set(value);
},
get() {
if (spyProp.get) {
return spyProp.get.call(this, originDescriptor);
}
return originDescriptor.get();
},
});
}
});
return {
mockRestore() {
propNames.forEach(propName => {
const originDescriptor = originDescriptors[propName];
if (typeof originDescriptor === 'function') {
Element.prototype[propName] = originDescriptor;
} else {
Object.defineProperty(Element.prototype, propName, originDescriptor);
}
});
},
};
}
export function spyElementPrototype(Element, propName, property) {
return spyElementPrototypes(Element, {
[propName]: property,
});
}

View File

@ -238,12 +238,13 @@ class Upload extends React.Component<UploadProps, UploadState> {
};
renderUploadList = (locale: UploadLocale) => {
const { showUploadList, listType, onPreview } = this.props;
const { showUploadList, listType, onPreview, previewFile } = this.props;
const { showRemoveIcon, showPreviewIcon } = showUploadList as any;
return (
<UploadList
listType={listType}
items={this.state.fileList}
previewFile={previewFile}
onPreview={onPreview}
onRemove={this.handleManualRemove}
showRemoveIcon={showRemoveIcon}

View File

@ -2,39 +2,12 @@ import * as React from 'react';
import Animate from 'rc-animate';
import classNames from 'classnames';
import { UploadListProps, UploadFile, UploadListType } from './interface';
import { previewImage, isImageUrl } from './utils';
import Icon from '../icon';
import Tooltip from '../tooltip';
import Progress from '../progress';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
const extname = (url: string) => {
if (!url) {
return '';
}
const temp = url.split('/');
const filename = temp[temp.length - 1];
const filenameWithoutSuffix = filename.split(/#|\?/)[0];
return (/\.[^./\\]*$/.exec(filenameWithoutSuffix) || [''])[0];
};
const isImageFileType = (type: string): boolean => !!type && type.indexOf('image/') === 0;
const isImageUrl = (file: UploadFile): boolean => {
if (isImageFileType(file.type)) {
return true;
}
const url: string = (file.thumbUrl || file.url) as string;
const extension = extname(url);
if (/^data:image\//.test(url) || /(webp|svg|png|gif|jpg|jpeg|bmp|dpg)$/i.test(extension)) {
return true;
} else if (/^data:/.test(url)) {
// other file types of base64
return false;
} else if (extension) {
// other file types which have extension
return false;
}
return true;
};
export default class UploadList extends React.Component<UploadListProps, any> {
static defaultProps = {
listType: 'text' as UploadListType, // or picture
@ -44,6 +17,7 @@ export default class UploadList extends React.Component<UploadListProps, any> {
},
showRemoveIcon: true,
showPreviewIcon: true,
previewFile: previewImage,
};
handleClose = (file: UploadFile) => {
@ -62,21 +36,12 @@ export default class UploadList extends React.Component<UploadListProps, any> {
return onPreview(file);
};
// https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
previewFile = (file: File | Blob, callback: Function) => {
if (!isImageFileType(file.type)) {
return callback('');
}
const reader = new FileReader();
reader.onloadend = () => callback(reader.result);
reader.readAsDataURL(file);
};
componentDidUpdate() {
if (this.props.listType !== 'picture' && this.props.listType !== 'picture-card') {
const { listType, items, previewFile } = this.props;
if (listType !== 'picture' && listType !== 'picture-card') {
return;
}
(this.props.items || []).forEach(file => {
(items || []).forEach(file => {
if (
typeof document === 'undefined' ||
typeof window === 'undefined' ||
@ -88,10 +53,13 @@ export default class UploadList extends React.Component<UploadListProps, any> {
return;
}
file.thumbUrl = '';
this.previewFile(file.originFileObj, (previewDataUrl: string) => {
file.thumbUrl = previewDataUrl;
this.forceUpdate();
});
if (previewFile) {
previewFile(file.originFileObj).then((previewDataUrl: string) => {
// Need append '' to avoid dead loop
file.thumbUrl = previewDataUrl || '';
this.forceUpdate();
});
}
});
}

View File

@ -631,6 +631,21 @@ exports[`renders ./components/upload/demo/picture-style.md correctly 1`] = `
</div>
`;
exports[`renders ./components/upload/demo/preview-file.md correctly 1`] = `
<div>
<span
class=""
>
<div
class="ant-upload ant-upload-select ant-upload-select-picture"
/>
<div
class="ant-upload-list ant-upload-list-picture"
/>
</span>
</div>
`;
exports[`renders ./components/upload/demo/upload-manually.md correctly 1`] = `
<div>
<span

View File

@ -3,6 +3,7 @@ import { mount } from 'enzyme';
import Upload from '..';
import UploadList from '../UploadList';
import Form from '../../form';
import { spyElementPrototypes } from '../../__tests__/util/domHook';
import { errorRequest, successRequest } from './requests';
import { setup, teardown } from './mock';
@ -26,8 +27,58 @@ const fileList = [
];
describe('Upload List', () => {
// jsdom not support `createObjectURL` yet. Let's handle this.
const originCreateObjectURL = window.URL.createObjectURL;
window.URL.createObjectURL = jest.fn(() => '');
// Mock dom
let size = { width: 0, height: 0 };
function setSize(width, height) {
size = { width, height };
}
const imageSpy = spyElementPrototypes(Image, {
src: {
set() {
if (this.onload) {
this.onload();
}
},
},
width: {
get: () => size.width,
},
height: {
get: () => size.height,
},
});
let drawImageCallback = null;
function hookDrawImageCall(callback) {
drawImageCallback = callback;
}
const canvasSpy = spyElementPrototypes(HTMLCanvasElement, {
getContext: () => ({
drawImage: (...args) => {
if (drawImageCallback) drawImageCallback(...args);
},
}),
toDataURL: () => 'data:image/png;base64,',
});
// HTMLCanvasElement.prototype
beforeEach(() => setup());
afterEach(() => teardown());
afterEach(() => {
teardown();
drawImageCallback = null;
});
afterAll(() => {
window.URL.createObjectURL = originCreateObjectURL;
imageSpy.mockRestore();
canvasSpy.mockRestore();
});
// https://github.com/ant-design/ant-design/issues/4653
it('should use file.thumbUrl for <img /> in priority', () => {
@ -198,20 +249,45 @@ describe('Upload List', () => {
expect(handleChange.mock.calls.length).toBe(2);
});
it('should generate thumbUrl from file', async () => {
const handlePreview = jest.fn();
const newFileList = [...fileList];
const newFile = { ...fileList[0], uid: '-3', originFileObj: new File([], 'xxx.png') };
delete newFile.thumbUrl;
newFileList.push(newFile);
const wrapper = mount(
<Upload listType="picture-card" defaultFileList={newFileList} onPreview={handlePreview}>
<button type="button">upload</button>
</Upload>,
);
wrapper.setState({});
await delay(0);
expect(wrapper.state().fileList[2].thumbUrl).not.toBe(undefined);
describe('should generate thumbUrl from file', () => {
[
{ width: 100, height: 200, name: 'height large than width' },
{ width: 200, height: 100, name: 'width large than height' },
].forEach(({ width, height, name }) => {
it(name, async () => {
setSize(width, height);
const onDrawImage = jest.fn();
hookDrawImageCall(onDrawImage);
const handlePreview = jest.fn();
const newFileList = [...fileList];
const newFile = {
...fileList[0],
uid: '-3',
originFileObj: new File([], 'xxx.png', { type: 'image/png' }),
};
delete newFile.thumbUrl;
newFileList.push(newFile);
const wrapper = mount(
<Upload listType="picture-card" defaultFileList={newFileList} onPreview={handlePreview}>
<button type="button">upload</button>
</Upload>,
);
wrapper.setState({});
await delay(0);
expect(wrapper.state().fileList[2].thumbUrl).not.toBe(undefined);
expect(onDrawImage).toHaveBeenCalled();
// Offset check
const [, offsetX, offsetY] = onDrawImage.mock.calls[0];
if (width > height) {
expect(offsetX < 0).toBeTruthy();
} else {
expect(offsetY < 0).toBeTruthy();
}
});
});
});
it('should non-image format file preview', () => {
@ -356,15 +432,13 @@ describe('Upload List', () => {
expect(wrapper.handlePreview()).toBe(undefined);
});
it('previewFile should work correctly', () => {
const callback = jest.fn();
it('previewFile should work correctly', async () => {
const file = new File([''], 'test.txt', { type: 'text/plain' });
const items = [{ uid: 'upload-list-item', url: '' }];
const wrapper = mount(
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
).instance();
wrapper.previewFile(file, callback);
expect(callback).toHaveBeenCalled();
return wrapper.props.previewFile(file);
});
it('extname should work correctly when url not exists', () => {
@ -404,7 +478,7 @@ describe('Upload List', () => {
expect(onPreview).toHaveBeenCalled();
});
it('upload image file should be converted to the base64', done => {
it('upload image file should be converted to the base64', async () => {
const mockFile = new File([''], 'foo.png', {
type: 'image/png',
});
@ -413,14 +487,12 @@ describe('Upload List', () => {
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
);
const instance = wrapper.instance();
const callback = dataUrl => {
return instance.props.previewFile(mockFile).then(dataUrl => {
expect(dataUrl).toEqual('data:image/png;base64,');
done();
};
instance.previewFile(mockFile, callback);
});
});
it("upload non image file shouldn't be converted to the base64", () => {
it("upload non image file shouldn't be converted to the base64", async () => {
const mockFile = new File([''], 'foo.7z', {
type: 'application/x-7z-compressed',
});
@ -429,8 +501,33 @@ describe('Upload List', () => {
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
);
const instance = wrapper.instance();
const callback = jest.fn();
instance.previewFile(mockFile, callback);
expect(callback).toHaveBeenCalledWith('');
return instance.props.previewFile(mockFile).then(dataUrl => {
expect(dataUrl).toBe('');
});
});
it('customize previewFile support', async () => {
const mockThumbnail = 'mock-image';
const previewFile = jest.fn(() => {
return Promise.resolve(mockThumbnail);
});
const file = {
...fileList[0],
originFileObj: new File([], 'xxx.png'),
};
delete file.thumbUrl;
const wrapper = mount(
<Upload listType="picture" defaultFileList={[file]} previewFile={previewFile}>
<button type="button" />
</Upload>,
);
wrapper.setState({});
await delay(0);
expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
wrapper.update();
expect(wrapper.find('.ant-upload-list-item-thumbnail img').prop('src')).toBe(mockThumbnail);
});
});

View File

@ -0,0 +1,44 @@
---
order: 9
title:
zh-CN: 自定义预览
en-US: Customize preview file
---
## zh-CN
自定义本地预览,用于处理非图片格式文件(例如视频文件)。
## en-US
Customize local preview. Can handle with non-image format files such as video.
````jsx
import { Upload, Button, Icon } from 'antd';
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
listType: 'picture',
previewFile(file) {
console.log('Your upload file:', file);
// Your process logic. Here we just mock to the same file
return fetch('https://next.json-generator.com/api/json/get/4ytyBoLK8', {
method: 'POST',
body: file,
})
.then(res => res.json())
.then(({ thumbnail }) => thumbnail);
},
};
ReactDOM.render(
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Upload
</Button>
</Upload>
</div>,
mountNode
);
````

View File

@ -16,28 +16,29 @@ Uploading is the process of publishing information (web pages, text, pictures, v
## API
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| accept | File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - |
| action | Uploading URL | string\|(file) => `Promise` | - |
| directory | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false |
| beforeUpload | Hook function which will be executed before uploading. Uploading will be stopped with `false` or a rejected Promise returned. **Warningthis function is not supported in IE9**。 | (file, fileList) => `boolean | Promise` | - |
| customRequest | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest | Function | - |
| data | Uploading params or function which can return uploading params. | object\|function(file) | - |
| defaultFileList | Default list of files that have been uploaded. | object\[] | - |
| disabled | disable upload button | boolean | false |
| fileList | List of files that have been uploaded (controlled). Here is a common issue [#2423](https://github.com/ant-design/ant-design/issues/2423) when using it | object\[] | - |
| headers | Set request headers, valid above IE10. | object | - |
| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | 'text' |
| multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false |
| name | The name of uploading file | string | 'file' |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true |
| supportServerRender | Need to be turned on while the server side is rendering | boolean | false |
| withCredentials | ajax upload with cookie sent | boolean | false |
| openFileDialogOnClick | click open file dialog | boolean | true |
| onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onChange) | Function | - |
| onPreview | A callback function, will be executed when file link or preview icon is clicked | Function(file) | - |
| onRemove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is `false` or a Promise which resolve(false) or reject | Function(file): `boolean | Promise` | - |
| Property | Description | Type | Default | Version |
| -------- | ----------- | ---- | ------- | ------- |
| accept | File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - | |
| action | Uploading URL | string\|(file) => `Promise` | - | |
| directory | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false | |
| beforeUpload | Hook function which will be executed before uploading. Uploading will be stopped with `false` or a rejected Promise returned. **Warningthis function is not supported in IE9**。 | (file, fileList) => `boolean | Promise` | - | |
| customRequest | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest | Function | - | |
| data | Uploading params or function which can return uploading params. | object\|function(file) | - | |
| defaultFileList | Default list of files that have been uploaded. | object\[] | - | |
| disabled | disable upload button | boolean | false | |
| fileList | List of files that have been uploaded (controlled). Here is a common issue [#2423](https://github.com/ant-design/ant-design/issues/2423) when using it | object\[] | - | |
| headers | Set request headers, valid above IE10. | object | - | |
| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | 'text' | |
| multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | |
| name | The name of uploading file | string | 'file' | |
| previewFile | Customize preview file logic | (file: File \| Blob) => Promise<dataURL: string> | - | 3.17.0 |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| supportServerRender | Need to be turned on while the server side is rendering | boolean | false | |
| withCredentials | ajax upload with cookie sent | boolean | false | |
| openFileDialogOnClick | click open file dialog | boolean | true | |
| onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onChange) | Function | - | |
| onPreview | A callback function, will be executed when file link or preview icon is clicked | Function(file) | - | |
| onRemove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is `false` or a Promise which resolve(false) or reject | Function(file): `boolean | Promise` | - | |
### onChange

View File

@ -17,28 +17,29 @@ title: Upload
## API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | 无 |
| action | 上传的地址 | string\|(file) => `Promise` | 无 |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory)| boolean | false |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。**注意IE9 不支持该方法**。 | (file, fileList) => `boolean | Promise` | 无 |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | Function | 无 |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | 无 |
| defaultFileList | 默认已经上传的文件列表 | object\[] | 无 |
| disabled | 是否禁用 | boolean | false |
| fileList | 已经上传的文件列表(受控),使用此参数时,如果遇到 `onChange` 只调用一次的问题,请参考 [#2423](https://github.com/ant-design/ant-design/issues/2423) | object\[] | 无 |
| headers | 设置上传的请求头部IE10 以上有效 | object | 无 |
| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | 'text' |
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件 | boolean | false |
| name | 发到后台的文件参数名 | string | 'file' |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon``showRemoveIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true |
| supportServerRender | 服务端渲染时需要打开这个 | boolean | false |
| withCredentials | 上传请求时是否携带 cookie | boolean | false |
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true |
| onChange | 上传文件改变时的状态,详见 [onChange](#onChange) | Function | 无 |
| onPreview | 点击文件链接或预览图标时的回调 | Function(file) | 无 |
| onRemove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除。               | Function(file): `boolean | Promise` | 无   |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | 无 | |
| action | 上传的地址 | string\|(file) => `Promise` | 无 | |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory)| boolean | false | |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。**注意IE9 不支持该方法**。 | (file, fileList) => `boolean | Promise` | 无 | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | Function | 无 | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | 无 | |
| defaultFileList | 默认已经上传的文件列表 | object\[] | 无 | |
| disabled | 是否禁用 | boolean | false | |
| fileList | 已经上传的文件列表(受控),使用此参数时,如果遇到 `onChange` 只调用一次的问题,请参考 [#2423](https://github.com/ant-design/ant-design/issues/2423) | object\[] | 无 | |
| headers | 设置上传的请求头部IE10 以上有效 | object | 无 | |
| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | 'text' | |
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件 | boolean | false | |
| name | 发到后台的文件参数名 | string | 'file' | |
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise<dataURL: string> | 无 | 3.17.0 |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon``showRemoveIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| supportServerRender | 服务端渲染时需要打开这个 | boolean | false | |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | |
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | |
| onChange | 上传文件改变时的状态,详见 [onChange](#onChange) | Function | 无 | |
| onPreview | 点击文件链接或预览图标时的回调 | Function(file) | 无 | |
| onRemove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除。               | Function(file): `boolean | Promise` | 无   | |
### onChange

View File

@ -50,6 +50,8 @@ export interface UploadLocale {
export type UploadType = 'drag' | 'select';
export type UploadListType = 'text' | 'picture' | 'picture-card';
type PreviewFileHandler = (file: File | Blob) => PromiseLike<string>;
export interface UploadProps {
type?: UploadType;
name?: string;
@ -77,6 +79,7 @@ export interface UploadProps {
openFileDialogOnClick?: boolean;
locale?: UploadLocale;
id?: string;
previewFile?: PreviewFileHandler;
}
export interface UploadState {
@ -94,4 +97,5 @@ export interface UploadListProps {
showRemoveIcon?: boolean;
showPreviewIcon?: boolean;
locale: UploadLocale;
previewFile?: PreviewFileHandler;
}

View File

@ -56,3 +56,73 @@ export function removeFileItem(file: UploadFile, fileList: UploadFile[]) {
}
return removed;
}
// ==================== Default Image Preview ====================
const extname = (url: string) => {
if (!url) {
return '';
}
const temp = url.split('/');
const filename = temp[temp.length - 1];
const filenameWithoutSuffix = filename.split(/#|\?/)[0];
return (/\.[^./\\]*$/.exec(filenameWithoutSuffix) || [''])[0];
};
const isImageFileType = (type: string): boolean => !!type && type.indexOf('image/') === 0;
export const isImageUrl = (file: UploadFile): boolean => {
if (isImageFileType(file.type)) {
return true;
}
const url: string = (file.thumbUrl || file.url) as string;
const extension = extname(url);
if (/^data:image\//.test(url) || /(webp|svg|png|gif|jpg|jpeg|bmp|dpg)$/i.test(extension)) {
return true;
} else if (/^data:/.test(url)) {
// other file types of base64
return false;
} else if (extension) {
// other file types which have extension
return false;
}
return true;
};
const MEASURE_SIZE = 200;
export function previewImage(file: File | Blob): Promise<string> {
return new Promise(resolve => {
if (!isImageFileType(file.type)) {
resolve('');
return;
}
const canvas = document.createElement('canvas');
canvas.width = MEASURE_SIZE;
canvas.height = MEASURE_SIZE;
canvas.style.cssText = `position: fixed; left: 0; top: 0; width: ${MEASURE_SIZE}px; height: ${MEASURE_SIZE}px; z-index: 9999; display: none;`;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function() {
const { width, height } = img;
let drawWidth = MEASURE_SIZE;
let drawHeight = MEASURE_SIZE;
let offsetX = 0;
let offsetY = 0;
if (width < height) {
drawHeight = height * (MEASURE_SIZE / width);
offsetY = -(drawHeight - drawWidth) / 2;
} else {
drawWidth = width * (MEASURE_SIZE / height);
offsetX = -(drawWidth - drawHeight) / 2;
}
ctx!.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
const dataURL = canvas.toDataURL();
document.body.removeChild(canvas);
resolve(dataURL);
};
img.src = window.URL.createObjectURL(file);
});
}