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 typeexport type UploaderOptions = {// Upload addressurl: string;// Request type, default jsontype?: string;// content typecontentType?: string;// additional datadata?: Record<string, RequestDataValue> | FormData | (() => Promise<Record<string, RequestDataValue> | FormData>)// cross domaincrossOrigin?: boolean;// request headerheaders?: {[key: string]: string };// Before uploading, you can judge the file size limitonBefore?: (file: File) => Promise<boolean | void>;// Start uploadonReady?: (fileInfo: FileInfo, file: File) => Promise<void>;// uploadingonUploading?: (file: File, progress: {percent: number }) => void;// upload erroronError?: (error: Error, file: File) => void;// Upload successfullyonSuccess?: (response: any, file: File) => void;};// FileInfo typeexport 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 plugin and simply configure it to 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 methodclass CustomizeImageUploader extends ImageUploader {// The card instance currently being uploadedprivate 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 waitinghandleBefore(uid: string, file: File) {const { type, name, size } = file;// Get the file extensionconst ext = getExtensionName(file);// read files asynchronouslyreturn new Promise<| false| {file: File;info: FileInfo;base64: string;size: Record<string, number>;}>((resolve, reject) => {const fileReader = new FileReader();fileReader.addEventListener('load',() => {const values = {file,info: {// unique numberuid,// Blobsrc: fileReader.result,// file namename,// File sizesize,// file typetype,// File suffixext,},};// If it is a picture, get the width and height of the pictureconst base64 =typeof values.info.src !== 'string'? window.btoa(String.fromCharCode(...new Uint8Array(values.info.src),),): values.info.src;const image = new Image();image.src = values.info.src;const imagePlugin =this.editor.plugin.findPlugin<ImageOptions>('image');image.onload = () => {const { naturalWidth, naturalHeight, height, width } =image;let imageWidth: number = width;let imageHeight: number = height;const maxHeight: number | undefined =imagePlugin?.options?.maxHeight;if (maxHeight &&naturalHeight > naturalWidth &&height > maxHeight) {imageHeight = maxHeight;imageWidth =naturalWidth * (maxHeight / naturalHeight);}values.base64 = base64;(values.size = {width: imageWidth,height: imageHeight,naturalHeight: image.naturalHeight,naturalWidth: image.naturalWidth,}),resolve(values);};image.onerror = () => {reject(false);};},false,);fileReader.addEventListener('error', () => {reject(false);});fileReader.readAsDataURL(file);});}// Insert the editor before uploadingonReady(fileInfo: FileInfo, base64: string, size: Record<string, number>) {// If the ImageComponent instance of the current picture exists, it will not be processedif (!isEngine(this.editor) || !!this.imageComponents[fileInfo.uid])return;// Insert ImageComponent cardconst component = this.editor.card.insert(ImageComponent.cardName,{// Set the status to uploadingstatus: 'uploading',size,},// Display the base64 image obtained in handleBefore, so as not to cause the editor area to be blankbase64,) as ImageComponent;// Record the card instance of the currently uploaded filethis.imageComponents[fileInfo.uid] = component;}// uploadingonUploading(uid: string, { percent }: { percent: number }) {// Get the ImageComponent instance corresponding to fileconst component = this.imageComponents[uid];if (!component) return;// Set the current upload progress percentagecomponent.setProgressPercent(percent);}// Upload successfullyonSuccess(response: any, uid: string) {// Get the ImageComponent instance corresponding to fileconst component = this.imageComponents[uid];if (!component) return;// Get the image address after the upload is successfullet 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 uploadif (!response.result) {// Update the value of the cardthis.editor.card.update(component.id, {status: 'error',message:response.message ||this.editor.language.get('image', 'uploadError'),});} else {// Upload successfullysrc = response.data;}// Set the status value of the ImageComponent instance corresponding to file to doneconst value: ImageValue = {status: 'done',src,};// There is a url after the uploaded image is obtainedif (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 normallythis.loadImage(component.id, value);}// Delete the current temporary recorddelete this.imageComponents[uid];}// upload erroronError(error: Error, uid: string) {const component = this.imageComponents[uid];if (!component) return;// Update the card status to error and display the error messagethis.editor.card.update(component.id, {status: 'error',message:error.message ||this.editor.language.get('image', 'uploadError'),});// Delete the current temporary recorddelete this.imageComponents[uid];}async execute(files?: Array<File> | string | MouseEvent) {// It is the reader View that will not handle itif (!isEngine(this.editor)) return;// Get the currently passed in optional valueconst { request, language } = this.editor;const { multiple } = this.options.file;// Upload size limitconst 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 selectorif (!Array.isArray(files) && typeof files !== 'string') {// A file selector pops up, allowing the user to select a filefiles = await request.getFiles({// Click event of user targetevent: 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 optionsaccept: isAndroid? 'image/*': this.extensionNames.length > 0? '.' + this.extensionNames.join(',.'): '',// The maximum number can be selectedmultiple,});}// 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 processingelse if (typeof files === 'string') {this.insertRemote(files);return;}// don't process if there is no fileif (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 fileconst uid = Date.now() + '-' + f;// Determine the file sizeif (file.size > limitSize) {// Display errorthis.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 editorPromise.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;base64: string;size: Record<string, number>;}[];files.forEach((v) => {// insert editorthis.onReady(v.info, v.base64, v.size);});// Process uploadthis.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 methodthis.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;
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 uploadhandleBefore(uid: string, file: File) {const { type, name, size } = file;// Get the file extensionconst ext = getExtensionName(file);// read files asynchronouslyreturn new Promise<false | { file: File; info: FileInfo }>((resolve, reject) => {const fileReader = new FileReader();fileReader.addEventListener('load',() => {const values = {file,info: {// unique numberuid,// Blob formatsrc: fileReader.result,// file namename,// File sizesize,// file typetype,// File suffixext,},};resolve(values);},false,);fileReader.addEventListener('error', () => {reject(false);});fileReader.readAsDataURL(file);},);}setGlobalUpload(engine: EngineInterface = new Engine('.container')) {// Override the upload method in the editorengine.request.upload = async (options, files, name) => {const { onBefore, onReady } = options;// do not process if there is no fileif (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 fileconst 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 filesPromise.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 uploadthis.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 dataconst 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 addressurl,// additional datadata,// Progress callback during uploadonUploading,// Upload successful callbackonSuccess,// Upload error callbackonError,} = options;if (data) {Object.keys(data).forEach((key) => {formData.append(key, data![key]);});}// Custom upload and call onUploading onSuccess onError callback method}}