am-editor-001/docs/config/upload.md

12 KiB

toc
menu

Upload configuration

The editor implements upload logic by default

We can access it in request.upload in the engine instance

request.upload internally uses XMLHttpRequest to upload files, the advantage is that you can get the upload progress

engine.request.upload(options: UploaderOptions, files: Array<File>, name?: string)
// Upload optional type
export type UploaderOptions = {
    // Upload address
    url: string;
    // Request type, default json
    type?: string;
    // content type
    contentType?: string;
    // additional data
    data?: {};
    // cross domain
    crossOrigin?: boolean;
    // request header
    headers?: {[key: string]: string };
    // Before uploading, you can judge the file size limit
    onBefore?: (file: File) => Promise<boolean | void>;
    // Start upload
    onReady?: (fileInfo: FileInfo, file: File) => Promise<void>;
    // uploading
    onUploading?: (file: File, progress: {percent: number }) => void;
    // upload error
    onError?: (error: Error, file: File) => void;
    // Upload successfully
    onSuccess?: (response: any, file: File) => void;
};
// FileInfo type
export type FileInfo = {
    uid: string;
    src: string | ArrayBuffer | null;
    name: string;
    size: number;
    type: string;
    ext: string;
};

In addition to upload, there is a utility method called getFiles(options?: OpenDialogOptions) that can pop up a local file selector

export type OpenDialogOptions = {
	event?: MouseEvent;
	accept?: string;
	multiple?: boolean | number;
};

The following plugins all rely on engine.request.upload to achieve upload

We only need to follow the instructions of the corresponding plug-in and simply configure it to upload.

  • ImageUploader
  • FileUploader
  • VideoUploader

Custom upload

Single plugin upload

Take ImageUploader as an example

import {
	getExtensionName,
	FileInfo,
	File,
	isAndroid,
	isEngine,
} from '@aomao/engine';
import { ImageComponent, ImageUploader } from '@aomao/plugin-image';
import { ImageValue } from 'plugins/image/dist/component';
// Inherit the original ImageUploader class and override the execute method
class CustomizeImageUploader extends ImageUploader {
	// The card instance currently being uploaded
	private imageComponents: Record<string, ImageComponent> = {};
	// Process the picture before uploading, and the base64 of the obtained picture will be displayed in the editor while the upload is waiting
	handleBefore(uid: string, file: File) {
		const { type, name, size } = file;
		// Get the file extension
		const ext = getExtensionName(file);
		// read files asynchronously
		return new Promise<false | { file: File; info: FileInfo }>(
			(resolve, reject) => {
				const fileReader = new FileReader();
				fileReader.addEventListener(
					'load',
					() => {
						resolve({
							file,
							info: {
								// unique number
								uid,
								// Blob
								src: fileReader.result,
								// file name
								name,
								// File size
								size,
								// file type
								type,
								// File suffix
								ext,
							},
						});
					},
					false,
				);
				fileReader.addEventListener('error', () => {
					reject(false);
				});
				fileReader.readAsDataURL(file);
			},
		);
	}
	// Insert the editor before uploading
	onReady(fileInfo: FileInfo) {
		// If the ImageComponent instance of the current picture exists, it will not be processed
		if (!isEngine(this.editor) || !!this.imageComponents[fileInfo.uid])
			return;
		// Insert ImageComponent card
		const component = this.editor.card.insert(ImageComponent.cardName, {
			// Set the status to uploading
			status: 'uploading',
			// Display the base64 image obtained in handleBefore, so as not to cause the editor area to be blank
			src: fileInfo.src,
		}) as ImageComponent;
		// Record the card instance of the currently uploaded file
		this.imageComponents[fileInfo.uid] = component;
	}
	// uploading
	onUploading(uid: string, { percent }: { percent: number }) {
		// Get the ImageComponent instance corresponding to file
		const component = this.imageComponents[uid];
		if (!component) return;
		// Set the current upload progress percentage
		component.setProgressPercent(percent);
	}
	// Upload successfully
	onSuccess(response: any, uid: string) {
		// Get the ImageComponent instance corresponding to file
		const component = this.imageComponents[uid];
		if (!component) return;
		// Get the image address after the upload is successful
		let src = '';
		// Process the response returned by the server, and update the status value of the ImageComponent instance corresponding to the file if there is an error in the upload
		if (!response.result) {
			// Update the value of the card
			this.editor.card.update(component.id, {
				status: 'error',
				message:
					response.message ||
					this.editor.language.get('image', 'uploadError'),
			});
		} else {
			// Upload successfully
			src = response.data;
		}
		// Set the status value of the ImageComponent instance corresponding to file to done
		const value: ImageValue = {
			status: 'done',
			src,
		};
		// There is a url after the uploaded image is obtained
		if (src) {
			// Call the method of the current instance of ImageUploader to load the url image. If the loading fails, set the status to error and display that it cannot be loaded, otherwise the image will be loaded normally
			this.loadImage(component.id, value);
		}
		// Delete the current temporary record
		delete this.imageComponents[uid];
	}

	// upload error
	onError(error: Error, uid: string) {
		const component = this.imageComponents[uid];
		if (!component) return;
		// Update the card status to error and display the error message
		this.editor.card.update(component.id, {
			status: 'error',
			message:
				error.message ||
				this.editor.language.get('image', 'uploadError'),
		});
		// Delete the current temporary record
		delete this.imageComponents[uid];
	}
	async execute(files?: Array<File> | string | MouseEvent) {
		// It is the reader View that will not handle it
		if (!isEngine(this.editor)) return;
		// Get the currently passed in optional value
		const { request, language } = this.editor;
		const { multiple } = this.options.file;
		// Upload size limit
		const limitSize = this.options.file.limitSize || 5 * 1024 * 1024;
		// The incoming files is not an array to get a picture address, that is, MouseEvent pops up the file selector
		if (!Array.isArray(files) && typeof files !== 'string') {
			// A file selector pops up, allowing the user to select a file
			files = await request.getFiles({
				// Click event of user target
				event: files,
				// Selectable file suffix name. this.extensionNames is the combined value of the suffixes supported by default in the ImageUploader plugin and the suffixes passed in by the options
				accept: isAndroid
					? 'image/*'
					: this.extensionNames.length > 0
					? '.' + this.extensionNames.join(',.')
					: '',
				// The maximum number can be selected
				multiple,
			});
		}
		// If the file address is passed in, then upload the image address. If insertRemote judges that it is a third-party website image address, it will request the api to download from the server, and then the server will store it before returning the new image address.
		// Because the pictures of third-party websites that are not on this site may be cross-domain or inaccessible, it is recommended to perform back-end download processing
		else if (typeof files === 'string') {
			this.insertRemote(files);
			return;
		}
		// don't process if there is no file
		if (files.length === 0) return;
		const promiseList = [];
		for (let f = 0; f < files.length; f++) {
			const file = files[f];
			// The unique identifier of the currently uploaded file
			const uid = Date.now() + '-' + f;
			// Determine the file size
			if (file.size > limitSize) {
				// Display error
				this.editor.messageError(
					language
						.get<string>('image', 'uploadLimitError')
						.replace(
							'$size',
							(limitSize / 1024 / 1024).toFixed(0) + 'M',
						),
				);
				return;
			}
			promiseList.push(this.handleBefore(uid, file));
		}
		//After all the pictures are read, insert the editor
		Promise.all(promiseList).then((values) => {
			if (values.some((value) => value === false)) {
				this.editor.messageError('read image failed');
				return;
			}
			const files = values as { file: File; info: FileInfo }[];
			files.forEach((v) => {
				// insert editor
				this.onReady(v.info);
			});
			// Process upload
			this.handleUpload(files);
		});
	}

	/**
	 * Process file upload
	 * @param values
	 */
	handleUpload(values: { file: File; info: FileInfo }[]) {
		const files = values.map((v) => {
			v.file.uid = v.info.uid;
			return v.file;
		});
		// Custom upload method
		this.editor.request.upload(
			{
				url: this.options.file.action,
				onUploading: (file, percent) => {
					this.onUploading(file.uid || '', percent);
				},
				onSuccess: (response, file) => {
					this.onSuccess(response, file.uid || '');
				},
				onError: (error, file) => {
					this.onError(error, file.uid || '');
				},
			},
			files,
		);
	}
}

export default CustomizeImageUploader;

Global upload

Override the editor engine.request.upload method

import Engine, {
	EngineInterface,
	FileInfo,
	File,
	getExtensionName,
	UploaderOptions,
} from '@aomao/engine';

export default class {
	// Process the picture before uploading, and the blob of the file will be displayed in the editor while waiting for upload
	handleBefore(uid: string, file: File) {
		const { type, name, size } = file;
		// Get the file extension
		const ext = getExtensionName(file);
		// read files asynchronously
		return new Promise<false | { file: File; info: FileInfo }>(
			(resolve, reject) => {
				const fileReader = new FileReader();
				fileReader.addEventListener(
					'load',
					() => {
						resolve({
							file,
							info: {
								// unique number
								uid,
								// Blob format
								src: fileReader.result,
								// file name
								name,
								// File size
								size,
								// file type
								type,
								// File suffix
								ext,
							},
						});
					},
					false,
				);
				fileReader.addEventListener('error', () => {
					reject(false);
				});
				fileReader.readAsDataURL(file);
			},
		);
	}

	setGlobalUpload(engine: EngineInterface = new Engine('.container')) {
		// Override the upload method in the editor
		engine.request.upload = async (options, files, name) => {
			const { onBefore, onReady } = options;
			// do not process if there is no file
			if (files.length === 0) return;
			const promiseList = [];
			for (let f = 0; f < files.length; f++) {
				const file = files[f];
				// The unique identifier of the currently uploaded file
				const uid = Date.now() + '-' + f;
				file.uid = uid;
				if (onBefore && (await onBefore(file)) === false) return;
				promiseList.push(this.handleBefore(uid, file));
			}
			//Insert the editor after reading all files
			Promise.all(promiseList).then(async (values) => {
				if (values.some((value) => value === false)) {
					engine.messageError('read image failed');
					return;
				}
				const files = values as { file: File; info: FileInfo }[];
				Promise.all([
					...files.map(async (v) => {
						return new Promise(async (resolve) => {
							if (onReady) {
								await onReady(v.info, v.file);
							}
							resolve(true);
						});
					}),
				]).then(() => {
					files.forEach(async (file) => {
						// Process upload
						this.handleUpload(file.file, options, name);
					});
				});
			});
		};
	}
	/**
	 * Process upload
	 * @param url upload address
	 * @param name formData parameter name
	 * @param file file
	 */
	handleUpload(file: File, options: UploaderOptions, name: string = 'file') {
		// form data
		const formData = new FormData();
		formData.append(name, file, file.name);
		if (file.data) {
			Object.keys(file.data).forEach((key) => {
				formData.append(key, file.data![key]);
			});
		}
		const {
			// Upload address
			url,
			// additional data
			data,
			// Progress callback during upload
			onUploading,
			// Upload successful callback
			onSuccess,
			// Upload error callback
			onError,
		} = options;
		if (data) {
			Object.keys(data).forEach((key) => {
				formData.append(key, data![key]);
			});
		}

		// Custom upload and call onUploading onSuccess onError callback method
	}
}