UNPKG

ng-devui

Version:

DevUI components based on Angular

955 lines (946 loc) 129 kB
import * as i0 from '@angular/core'; import { EventEmitter, Directive, Input, Output, HostListener, Component, ViewChild, Injectable, Inject, forwardRef, HostBinding, NgModule } from '@angular/core'; import * as i1 from 'ng-devui/i18n'; import { from, of, merge, Observable } from 'rxjs'; import { catchError, toArray, mergeMap, concatMap, last, map, debounceTime } from 'rxjs/operators'; import * as i6 from '@angular/common'; import { DOCUMENT, CommonModule } from '@angular/common'; import { __decorate, __metadata } from 'tslib'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i5 from 'ng-devui/toast'; import { ToastModule } from 'ng-devui/toast'; import * as i4 from 'ng-devui/utils'; import { WithConfig } from 'ng-devui/utils'; import * as i7 from 'ng-devui/button'; import { ButtonModule } from 'ng-devui/button'; import * as i8 from 'ng-devui/progress'; import { ProgressModule } from 'ng-devui/progress'; class FileDropDirective { constructor(element) { this.enableDrop = false; this.isSingle = false; this.fileOver = new EventEmitter(); this.fileDrop = new EventEmitter(); this.element = element; } onDrop(event) { if (!this.enableDrop) { return; } const transfer = this._getTransfer(event); if (!transfer) { return; } this._preventAndStop(event); if (this.isSingle) { this.fileDrop.emit([transfer.files[0]]); } else { this.fileDrop.emit(transfer.files); } } onDragOver(event) { if (!this.enableDrop) { return; } const transfer = this._getTransfer(event); if (!this._haveFiles(transfer.types)) { return; } this._preventAndStop(event); this.fileOver.emit(true); } onDragLeave(event) { if (!this.enableDrop) { return; } if (this.element) { if (event.currentTarget === this.element[0]) { return; } } this._preventAndStop(event); this.fileOver.emit(false); } _getTransfer(event) { return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; } _preventAndStop(event) { event.preventDefault(); event.stopPropagation(); } _haveFiles(types) { if (!types) { return false; } if (types.indexOf) { return types.indexOf('Files') !== -1; } else if (types.contains) { return types.contains('Files'); } else { return false; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FileDropDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FileDropDirective, selector: "[d-file-drop]", inputs: { enableDrop: "enableDrop", isSingle: "isSingle" }, outputs: { fileOver: "fileOver", fileDrop: "fileDrop" }, host: { listeners: { "drop": "onDrop($event)", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FileDropDirective, decorators: [{ type: Directive, args: [{ /* eslint-disable-next-line @angular-eslint/directive-selector*/ selector: '[d-file-drop]' }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { enableDrop: [{ type: Input }], isSingle: [{ type: Input }], fileOver: [{ type: Output }], fileDrop: [{ type: Output }], onDrop: [{ type: HostListener, args: ['drop', ['$event']] }], onDragOver: [{ type: HostListener, args: ['dragover', ['$event']] }], onDragLeave: [{ type: HostListener, args: ['dragleave', ['$event']] }] } }); class IUploadOptions { } class IFileOptions { } var UploadStatus; (function (UploadStatus) { UploadStatus[UploadStatus["preLoad"] = 0] = "preLoad"; UploadStatus[UploadStatus["uploading"] = 1] = "uploading"; UploadStatus[UploadStatus["uploaded"] = 2] = "uploaded"; UploadStatus[UploadStatus["failed"] = 3] = "failed"; })(UploadStatus || (UploadStatus = {})); class FileUploader { constructor(file, uploadOptions) { this.file = file; this.uploadOptions = uploadOptions; this.percentage = 0; this.file = file; this.uploadOptions = uploadOptions; this.status = UploadStatus.preLoad; } sendCommonHandle(uploadFiles) { const { uri, method, headers, authToken, authTokenHeader, additionalParameter, fileFieldName, withCredentials, responseType } = this.uploadOptions; const authTokenHeader_ = authTokenHeader || 'Authorization'; const fileFieldName_ = fileFieldName || 'file'; this.xhr = new XMLHttpRequest(); this.xhr.open(method || 'POST', uri); if (withCredentials) { this.xhr.withCredentials = withCredentials; } if (responseType) { this.xhr.responseType = responseType; } if (authToken) { this.xhr.setRequestHeader(authTokenHeader_, authToken); } if (headers) { Object.keys(headers).forEach((key) => { this.xhr.setRequestHeader(key, headers[key]); }); } this.xhr.upload.onprogress = (e) => { this.percentage = Math.round((e.loaded * 100) / e.total); }; const formData = uploadFiles && uploadFiles.length ? this.oneTimeUploadFiles(fileFieldName_, additionalParameter, uploadFiles) : this.parallelUploadFiles(fileFieldName_, additionalParameter); this.xhr.send(formData); this.status = UploadStatus.uploading; this.xhr.onabort = () => { this.status = UploadStatus.preLoad; this.xhr = null; }; } sendErrorAndLoadHandle(resolve, reject, isMultiple = false) { this.xhr.onerror = () => { this.response = this.xhr.response; this.status = UploadStatus.failed; if (isMultiple) { reject({ file: this.file, response: this.xhr.response, status: UploadStatus.failed }); } else { reject({ file: this.file, response: this.xhr.response }); } }; this.xhr.onload = () => { if (this.xhr.readyState === 4 && this.xhr.status >= 200 && this.xhr.status < 300) { this.response = this.xhr.response; this.status = UploadStatus.uploaded; if (isMultiple) { resolve({ file: this.file, response: this.xhr.response, status: UploadStatus.uploaded }); } else { resolve({ file: this.file, response: this.xhr.response }); } } else { this.response = this.xhr.response; this.status = UploadStatus.failed; if (isMultiple) { reject({ file: this.file, response: this.xhr.response, status: UploadStatus.failed }); } else { reject({ file: this.file, response: this.xhr.response }); } } }; } send(uploadFiles) { return new Promise((resolve, reject) => { this.sendCommonHandle(uploadFiles); this.sendErrorAndLoadHandle(resolve, reject); }); } sendMultiple(uploadFiles) { return new Promise((resolve, reject) => { this.sendCommonHandle(uploadFiles); this.sendErrorAndLoadHandle(resolve, reject, true); }); } parallelUploadFiles(fileFieldName_, additionalParameter) { const formData = new FormData(); formData.append(fileFieldName_, this.file, this.file.name); if (additionalParameter) { Object.keys(additionalParameter).forEach((key) => { formData.append(key, additionalParameter[key]); }); } return formData; } oneTimeUploadFiles(fileFieldName_, additionalParameter, uploadFiles) { const formData = new FormData(); uploadFiles.forEach((element) => { formData.append(fileFieldName_, element.file, element.file.name); if (additionalParameter) { Object.keys(additionalParameter).forEach((key) => { formData.append(key, additionalParameter[key]); }); } }); return formData; } cancel() { if (this.xhr) { this.xhr.abort(); } } } class UploadComponent { constructor() { this.fileUploaders = []; this.filesWithSameName = []; } addFile(file, options) { if (options && options.checkSameName) { if (this.checkFileSame(file.name)) { this.fileUploaders.push(new FileUploader(file, options)); } } else { this.fileUploaders.push(new FileUploader(file, options)); } } checkFileSame(fileName) { let checkRel = true; for (let i = 0; i < this.fileUploaders.length; i++) { if (fileName === this.fileUploaders[i].file.name) { checkRel = false; if (this.filesWithSameName.indexOf(fileName) === -1) { this.filesWithSameName.push(fileName); } break; } } return checkRel; } getFiles() { return this.fileUploaders.map((fileUploader) => { return fileUploader.file; }); } getFullFiles() { return this.fileUploaders.map((fileUploader) => { return fileUploader; }); } upload(oneFile, isMultipleUpload = false) { let uploads = []; if (oneFile) { oneFile.percentage = 0; uploads.push(from(oneFile.send())); } else { const preFiles = this.fileUploaders.filter((fileUploader) => fileUploader.status === UploadStatus.preLoad); const failedFiles = this.fileUploaders.filter((fileUploader) => fileUploader.status === UploadStatus.failed); const uploadFiles = preFiles.length > 0 ? preFiles : failedFiles; uploads = uploadFiles.map((fileUploader) => { fileUploader.percentage = 0; if (isMultipleUpload) { return from(fileUploader.sendMultiple()).pipe(catchError((error) => of(error))); } else { return from(fileUploader.send()); } }); } if (uploads.length > 0) { return merge(...uploads).pipe(toArray()); } return from(Promise.reject('no files')); } oneTimeUpload() { const uploads = this.fileUploaders.filter((fileUploader) => fileUploader.status !== UploadStatus.uploaded); return from(this.dealOneTimeUploadFiles(uploads)); } async dealOneTimeUploadFiles(uploads) { if (!uploads || !uploads.length) { return Promise.reject('no files'); } // 触发文件上传 let finalUploads = []; await uploads[0].send(uploads).finally(() => { finalUploads = uploads.map((file) => { file.status = uploads[0].status; file.percentage = uploads[0].percentage; return { file: file.file, response: uploads[0].response }; }); }); return finalUploads; } deleteFile(file) { this.fileUploaders = this.fileUploaders.filter((fileUploader) => { return file !== fileUploader.file; }); } removeFiles() { this.fileUploaders = []; this.filesWithSameName = []; } getSameNameFiles() { return this.filesWithSameName.join(); } resetSameNameFiles() { this.filesWithSameName = []; } } class UploadedFilesComponent { constructor(i18n) { this.i18n = i18n; this.uploadedFiles = []; this.deleteUploadedFileEvent = new EventEmitter(); // 解决templateContext 传递method.bind(this)引发模板中内嵌组件initialize问题 this.deleteFileProxy = filePath => { this.deleteFile(filePath); }; } ngOnInit() { this.i18nText = this.i18n.getI18nText().upload; this.i18nSubscription = this.i18n.langChange().subscribe((data) => { this.i18nText = data.upload; }); } cleanUploadedFiles() { this.uploadedFiles = []; } addAndOverwriteFile(file) { this.cleanUploadedFiles(); this.uploadedFiles.push(file); } addFile(file) { this.uploadedFiles.push(file); } deleteFile(filePath) { this.uploadedFiles = this.uploadedFiles.filter((file) => { return filePath !== file[this.filePath]; }); this.deleteUploadedFileEvent.emit(filePath); } ngOnDestroy() { if (this.i18nSubscription) { this.i18nSubscription.unsubscribe(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: UploadedFilesComponent, deps: [{ token: i1.I18nService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: UploadedFilesComponent, selector: "d-uploaded-files", inputs: { uploadedFiles: "uploadedFiles", uploadedFilesRef: "uploadedFilesRef", filePath: "filePath" }, outputs: { deleteUploadedFileEvent: "deleteUploadedFileEvent" }, exportAs: ["dUploadFiles"], ngImport: i0, template: "<ng-template\n [ngTemplateOutlet]=\"uploadedFilesRef\"\n [ngTemplateOutletContext]=\"{ $implicit: this, uploadedFiles: uploadedFiles, filePath: filePath, deleteFile: deleteFileProxy }\"\n>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: UploadedFilesComponent, decorators: [{ type: Component, args: [{ selector: 'd-uploaded-files', exportAs: 'dUploadFiles', preserveWhitespaces: false, template: "<ng-template\n [ngTemplateOutlet]=\"uploadedFilesRef\"\n [ngTemplateOutletContext]=\"{ $implicit: this, uploadedFiles: uploadedFiles, filePath: filePath, deleteFile: deleteFileProxy }\"\n>\n</ng-template>\n" }] }], ctorParameters: () => [{ type: i1.I18nService }], propDecorators: { uploadedFiles: [{ type: Input }], uploadedFilesRef: [{ type: Input }], filePath: [{ type: Input }], deleteUploadedFileEvent: [{ type: Output }] } }); class MultipleUploadViewComponent extends UploadComponent { constructor(i18n) { super(); this.i18n = i18n; this.uploadedFiles = []; this.deleteUploadedFileEvent = new EventEmitter(); this.UploadStatus = UploadStatus; this.fileUploaders = []; // 解决templateContext 传递method.bind(this)引发模板中内嵌组件initialize问题 this.deleteFileProxy = file => { this.deleteFile(file); }; } ngOnInit() { this.i18nText = this.i18n.getI18nText().upload; this.i18nSubscription = this.i18n.langChange().subscribe((data) => { this.i18nText = data.upload; }); } addFile(file) { let uploadOptions = this.uploadOptions; if (this.setCustomUploadOptions) { uploadOptions = this.setCustomUploadOptions(file, this.uploadOptions); } super.addFile(file, uploadOptions); } deleteFile(file) { super.deleteFile(file); this.deleteUploadedFileEvent.emit(file); } deletePreUploadFile(file) { super.deleteFile(file); } removeFiles() { super.removeFiles(); } _onDeleteUploadedFile(filePath) { this.deleteUploadedFileEvent.emit(filePath); } ngOnDestroy() { if (this.i18nSubscription) { this.i18nSubscription.unsubscribe(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MultipleUploadViewComponent, deps: [{ token: i1.I18nService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: MultipleUploadViewComponent, selector: "d-multiple-upload-view", inputs: { uploadOptions: "uploadOptions", preloadFilesRef: "preloadFilesRef", uploadedFiles: "uploadedFiles", uploadedFilesRef: "uploadedFilesRef", filePath: "filePath", setCustomUploadOptions: "setCustomUploadOptions" }, outputs: { deleteUploadedFileEvent: "deleteUploadedFileEvent" }, viewQueries: [{ propertyName: "uploadedFilesComponent", first: true, predicate: ["dUploadedFiles"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<d-uploaded-files\n #dUploadedFiles\n [uploadedFiles]=\"uploadedFiles\"\n [filePath]=\"filePath\"\n [uploadedFilesRef]=\"uploadedFilesRef\"\n (deleteUploadedFileEvent)=\"_onDeleteUploadedFile($event)\"\n>\n</d-uploaded-files>\n<ng-template\n [ngTemplateOutlet]=\"preloadFilesRef\"\n [ngTemplateOutletContext]=\"{ $implicit: this, fileUploaders: fileUploaders, UploadStatus: UploadStatus, deleteFile: deleteFileProxy }\"\n>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UploadedFilesComponent, selector: "d-uploaded-files", inputs: ["uploadedFiles", "uploadedFilesRef", "filePath"], outputs: ["deleteUploadedFileEvent"], exportAs: ["dUploadFiles"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MultipleUploadViewComponent, decorators: [{ type: Component, args: [{ selector: 'd-multiple-upload-view', preserveWhitespaces: false, template: "<d-uploaded-files\n #dUploadedFiles\n [uploadedFiles]=\"uploadedFiles\"\n [filePath]=\"filePath\"\n [uploadedFilesRef]=\"uploadedFilesRef\"\n (deleteUploadedFileEvent)=\"_onDeleteUploadedFile($event)\"\n>\n</d-uploaded-files>\n<ng-template\n [ngTemplateOutlet]=\"preloadFilesRef\"\n [ngTemplateOutletContext]=\"{ $implicit: this, fileUploaders: fileUploaders, UploadStatus: UploadStatus, deleteFile: deleteFileProxy }\"\n>\n</ng-template>\n" }] }], ctorParameters: () => [{ type: i1.I18nService }], propDecorators: { uploadedFilesComponent: [{ type: ViewChild, args: ['dUploadedFiles', { static: true }] }], uploadOptions: [{ type: Input }], preloadFilesRef: [{ type: Input }], uploadedFiles: [{ type: Input }], uploadedFilesRef: [{ type: Input }], filePath: [{ type: Input }], deleteUploadedFileEvent: [{ type: Output }], setCustomUploadOptions: [{ type: Input }] } }); class SelectFiles { constructor(i18n, doc) { this.i18n = i18n; this.doc = doc; this.selectFiles = ({ multiple, accept, webkitdirectory }) => { return new Promise((resolve) => { const tempNode = this.document.getElementById('d-upload-temp'); if (tempNode) { this.document.body.removeChild(tempNode); } const input = this.document.createElement('input'); input.style.position = 'fixed'; input.style.left = '-2000px'; input.style.top = '-2000px'; input.setAttribute('id', 'd-upload-temp'); input.setAttribute('type', 'file'); if (multiple) { input.setAttribute('multiple', ''); } if (accept) { input.setAttribute('accept', accept); } if (webkitdirectory) { input.setAttribute('webkitdirectory', ''); } input.addEventListener('change', event => { resolve(Array.prototype.slice.call(event.target.files)); }); this.document.body.appendChild(input); // Fix compatibility issue with Internet Explorer 11 this.simulateClickEvent(input); }); }; this.isAllowedFileType = (accept, file) => { if (accept) { const acceptArr = accept.split(','); const baseMimeType = file.type.replace(/\/.*$/, ''); return acceptArr.some((type) => { const validType = type.trim(); // suffix name (e.g. '.png,.xlsx') if (validType.startsWith('.')) { return (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.toLowerCase().length - validType.toLowerCase().length) > -1); // mime type like 'image/*' } else if (/\/\*$/.test(validType)) { return baseMimeType === validType.replace(/\/.*$/, ''); } // mime type like 'text/plain,application/json' return file.type === validType; }); } return true; }; this.beyondMaximalSize = (fileSize, maximumSize) => { if (maximumSize) { return fileSize > 1024 * 1024 * maximumSize; } return false; }; this.beyondAllFilesMaximalSize = (fileSize, maximumSize) => { if (maximumSize) { return fileSize > 1024 * 1024 * maximumSize; } return false; }; this.beyondMaximumFileCount = (files, maximumCount) => { if (maximumCount) { return files > maximumCount; } return false; }; this.triggerSelectFiles = (fileOptions, uploadOptions) => { const { multiple, accept, webkitdirectory } = fileOptions; return from(this.selectFiles({ multiple, accept, webkitdirectory })).pipe(mergeMap(file => file)); }; this.triggerDropFiles = (fileOptions, uploadOptions, files) => { return new Observable(observer => observer.next(files)).pipe(mergeMap(file => file)); }; this.document = this.doc; this.i18nText = this.i18n.getI18nText().upload; this.i18nSubscription = this.i18n.langChange().subscribe((data) => { this.i18nText = data.upload; }); } checkAllFilesSize(fileSize, maximumSize) { if (this.beyondMaximalSize(fileSize, maximumSize)) { this.BEYOND_MAXIMAL_FILE_SIZE_MSG = this.i18nText.getAllFilesBeyondMaximalFileSizeMsg(maximumSize); return { checkError: true, errorMsg: this.BEYOND_MAXIMAL_FILE_SIZE_MSG }; } } _validateFiles(filesLen, currentFile, accept, uploadOptions) { if (!this.isAllowedFileType(accept, currentFile)) { this.NOT_ALLOWED_FILE_TYPE_MSG = this.i18nText.getNotAllowedFileTypeMsg(currentFile.name, accept); return { checkError: true, errorMsg: this.NOT_ALLOWED_FILE_TYPE_MSG }; } if (this.beyondMaximalSize(currentFile.size, uploadOptions.maximumSize)) { this.BEYOND_MAXIMAL_FILE_SIZE_MSG = this.i18nText.getBeyondMaximalFileSizeMsg(currentFile.name, uploadOptions.maximumSize); return { checkError: true, errorMsg: this.BEYOND_MAXIMAL_FILE_SIZE_MSG }; } if (this.beyondMaximumFileCount(filesLen, uploadOptions.maximumCount)) { this.BEYOND_MAXIMAL_FILE_COUNT_MSG = this.i18nText.getBeyondMaximumFileCountMsg(uploadOptions.maximumCount); return { checkError: true, errorMsg: this.BEYOND_MAXIMAL_FILE_COUNT_MSG }; } return { checkError: false, errorMsg: undefined }; } simulateClickEvent(input) { const evt = new MouseEvent('click'); evt.stopPropagation(); input.dispatchEvent(evt); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SelectFiles, deps: [{ token: i1.I18nService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SelectFiles }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SelectFiles, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.I18nService }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] }] }); class SliceUploadService { constructor() { this.defaultChunkSize = 1024 * 1024 * 20; // 单位是byte 默认分片大小 20兆。 } isNeedChunk(fileUploaders, uploadOptions) { return (fileUploaders || []).some((item) => item?.file.size > (uploadOptions?.chunkSize || this.defaultChunkSize)); } async sliceUpload(instance, viewInstance) { const fileUploaders = viewInstance.fileUploaders; const customizeFunc = instance.dynamicUploadOptionsFn || instance.setCustomUploadOptions; for (let i = 0; i < fileUploaders.length; i++) { // 判断不需要切片的文件不调用自定义参数方法 const file = fileUploaders[i].file; const isNeedChunk = file.size > (instance.uploadOptions?.chunkSize || this.defaultChunkSize); const fileChunkList = this.createFileChunk(file, fileUploaders[i].uploadOptions || instance.uploadOptions, isNeedChunk ? customizeFunc : undefined); const currentFile = fileUploaders[i]; const uploadObservable = this.uploadChunkList(fileChunkList, currentFile, instance, viewInstance); (await uploadObservable).subscribe((results) => { currentFile.percentage = 100; currentFile.status = UploadStatus.uploaded; const successRes = [ { file: currentFile.file, response: results[0].response, chunkList: results, }, ]; instance.successEvent.emit(successRes); results.forEach((result) => viewInstance.uploadedFilesComponent.addAndOverwriteFile(result.file)); }, (error) => { error.file = currentFile.file; this.chunkRequestError(error, currentFile, instance, viewInstance, false); }); } } async uploadChunkList(fileChunkList, currentFile, instance, viewInstance) { let uploads = []; const chunkPercentage = (1 / fileChunkList.length) * 100; currentFile.status = UploadStatus.uploading; currentFile.percentage = 0; if (instance.uploadOptions.chunkInSequence) { return of(...fileChunkList).pipe(concatMap((uploader) => { let result; if (currentFile.status === UploadStatus.failed) { result = from(Promise.reject(new Error('upload canceled'))); if (uploads.length === 0) { uploads.push(result); } } else { result = from(uploader.send()); result.subscribe(() => { currentFile.percentage = currentFile.percentage + chunkPercentage; }, (error) => this.chunkRequestError(error, currentFile, instance, viewInstance)); uploads.push(result); } return result; }), // last的过滤函数直接返回false,从而使用默认值uploads返回 last(() => false, uploads)); } else { uploads = fileChunkList.map((fileUploader) => { fileUploader.percentage = 0; return from(fileUploader.send()); }); if (uploads.length > 0) { const uploadObservable = merge(...uploads); (await uploadObservable).subscribe((results) => { currentFile.percentage = currentFile.percentage + chunkPercentage; }, (error) => this.chunkRequestError(error, currentFile, instance, viewInstance)); return merge(...uploads).pipe(toArray()); } } return from(Promise.reject(new Error('no files'))); } // 生成分片上传的数组 createFileChunk(file, uploadOptions, uploadOptionsFunc) { const chunkSize = uploadOptions.chunkSize || this.defaultChunkSize; const { name, type, lastModified, size } = file; const fileId = new Date().getTime(); const fileChunkList = []; let fileSliceStart = 0; let chunkedFileIndex = 0; const chunks = Math.ceil(size / chunkSize); while (fileSliceStart < file.size) { chunkedFileIndex = chunkedFileIndex + 1; const currentUploadOptions = uploadOptionsFunc?.(file, uploadOptions, chunkedFileIndex) || uploadOptions; const slicedFile = file.slice(fileSliceStart, fileSliceStart + chunkSize); const newChunkFile = new File([slicedFile], `${fileId}-${chunkedFileIndex}-${chunks}-${size}-${lastModified}-${name}`, { type }); fileChunkList.push(new FileUploader(newChunkFile, currentUploadOptions)); fileSliceStart += chunkSize; } return fileChunkList; } chunkRequestError(error, currentFile, instance, viewInstance, isChunk = true) { currentFile.status = UploadStatus.failed; viewInstance.uploadedFilesComponent.cleanUploadedFiles(); if (isChunk) { instance.errorChunkEvent.emit(error); } else { instance.errorEvent.emit(error); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SliceUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SliceUploadService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SliceUploadService, decorators: [{ type: Injectable }] }); class MultipleUploadComponent { get hasGlowStyle() { return this.showGlowStyle; } constructor(selectFiles, i18n, doc, sliceUploadService, devConfigService, toastService) { this.selectFiles = selectFiles; this.i18n = i18n; this.doc = doc; this.sliceUploadService = sliceUploadService; this.devConfigService = devConfigService; this.toastService = toastService; this.autoUpload = false; this.withoutBtn = false; this.showTip = false; this.uploadedFiles = []; this.oneTimeUpload = false; this.disabled = false; this.enableDrop = false; this.showGlowStyle = true; this.successEvent = new EventEmitter(); this.errorEvent = new EventEmitter(); this.errorChunkEvent = new EventEmitter(); this.deleteUploadedFileEvent = new EventEmitter(); this.fileDrop = new EventEmitter(); this.fileOver = new EventEmitter(); this.fileSelect = new EventEmitter(); this.isDropOVer = false; this.errorMsg = []; this.UploadStatus = UploadStatus; this.onChange = (_) => null; this.onTouched = () => null; this.document = this.doc; } ngOnInit() { this.i18nText = this.i18n.getI18nText().upload; this.i18nCommonText = this.i18n.getI18nText().common; this.i18nSubscription = this.i18n.langChange().subscribe((data) => { this.i18nText = data.upload; this.i18nCommonText = data.common; }); } writeValue(files) { if (files) { const simulateFiles = from(this.simulateSelectFiles(files)).pipe(mergeMap((file) => file)); this._dealFiles(simulateFiles); } } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } simulateSelectFiles(files) { return new Promise((resolve) => { resolve(Array.prototype.slice.call(files)); }); } _dealFiles(observale) { this.multipleUploadViewComponent.resetSameNameFiles(); observale .pipe(map((file) => this.multipleUploadViewComponent.addFile(file)), debounceTime(100)) .subscribe(() => { this.checkValid(); const sameNameFiles = this.multipleUploadViewComponent.getSameNameFiles(); if (this.uploadOptions.checkSameName && sameNameFiles.length) { this.alertMsg(this.i18nText.getExistSameNameFilesMsg(sameNameFiles)); } this.onChange(this.multipleUploadViewComponent.fileUploaders.map((fileUploader) => fileUploader.file)); const selectedFiles = this.multipleUploadViewComponent.fileUploaders .filter((fileUploader) => fileUploader.status === UploadStatus.preLoad) .map((fileUploader) => fileUploader.file); this.onFileSelect(selectedFiles); if (this.autoUpload) { this.upload(); } }, (error) => { this.alertMsg(error.message); }); } checkValid() { let totalFileSize = 0; this.multipleUploadViewComponent.fileUploaders.forEach((fileUploader) => { totalFileSize += fileUploader.file.size; const checkResult = this.selectFiles._validateFiles(this.multipleUploadViewComponent.fileUploaders.length, fileUploader.file, this.fileOptions.accept, fileUploader.uploadOptions); if (checkResult && checkResult.checkError) { this.multipleUploadViewComponent.deletePreUploadFile(fileUploader.file); this.alertMsg(checkResult.errorMsg); return; } }); if (this.oneTimeUpload) { const checkResult = this.selectFiles.checkAllFilesSize(totalFileSize, this.uploadOptions.maximumSize); if (checkResult && checkResult.checkError) { this.multipleUploadViewComponent.removeFiles(); this.alertMsg(checkResult.errorMsg); } } } onClick(event) { if (this.disabled) { return; } this._dealFiles(this.selectFiles.triggerSelectFiles(this.fileOptions, this.uploadOptions)); } onFileDrop(files) { this.isDropOVer = false; this._dealFiles(this.selectFiles.triggerDropFiles(this.fileOptions, this.uploadOptions, files)); this.fileDrop.emit(files); } onFileOver(event) { this.isDropOVer = event; this.fileOver.emit(event); } onFileSelect(files) { this.fileSelect.emit(files); } handleOneTimeUpload(uploadObservable) { uploadObservable.pipe(last()).subscribe((results) => { this.successEvent.emit(results); results.forEach((result) => { this.multipleUploadViewComponent.uploadedFilesComponent.addFile(result.file); }); }, (error) => { this.errorEvent.emit(error); }); } handleUpload(uploadObservable) { uploadObservable.pipe(last()).subscribe((results) => { const successResult = results .filter((item) => item.status === UploadStatus.uploaded) .map((item) => { return { file: item.file, response: item.response }; }); const failResult = results .filter((item) => item.status === UploadStatus.failed) .map((item) => { return { file: item.file, response: item.response }; }); if (failResult.length) { this.errorEvent.emit(failResult); } if (successResult.length) { this.successEvent.emit(successResult); successResult.forEach((result) => { this.multipleUploadViewComponent.uploadedFilesComponent.addFile(result.file); }); } }); } upload(event, fileUploader) { if (event) { event.stopPropagation(); } this.canUpload().then((canUpload) => { if (!canUpload) { this.multipleUploadViewComponent.removeFiles(); return; } const tempNode = this.document.getElementById('d-upload-temp'); if (tempNode) { this.document.body.removeChild(tempNode); } if (this.uploadOptions.isChunked && this.sliceUploadService.isNeedChunk(this.multipleUploadViewComponent.fileUploaders, this.uploadOptions)) { this.sliceUploadService.sliceUpload(this, this.multipleUploadViewComponent); } else if (this.oneTimeUpload) { const oneTimeUploadObservable = this.multipleUploadViewComponent.oneTimeUpload(); this.handleOneTimeUpload(oneTimeUploadObservable); } else { const uploadObservable = this.multipleUploadViewComponent.upload(fileUploader, true); this.handleUpload(uploadObservable); } }); } canUpload() { let uploadResult = Promise.resolve(true); if (this.beforeUpload) { const result = this.beforeUpload(this.multipleUploadViewComponent.getFullFiles()); if (typeof result !== 'undefined') { if (result.then) { uploadResult = result; } else if (result.subscribe) { uploadResult = result.toPromise(); } else { uploadResult = Promise.resolve(result); } } } return uploadResult; } _onDeleteUploadedFile(filePath) { this.deleteUploadedFileEvent.emit(filePath); this.onChange(this.multipleUploadViewComponent.fileUploaders.map((fileUploader) => fileUploader.file)); } deleteFile($event, file) { $event.stopPropagation(); this.multipleUploadViewComponent.deleteFile(file); } alertMsg(errorMsg) { this.toastService.open({ value: [{ severity: 'warn', content: errorMsg }], }); } getStatus() { let uploadingCount = 0; let uploadedCount = 0; let failedCount = 0; const filesCount = this.multipleUploadViewComponent.fileUploaders.length; this.multipleUploadViewComponent.fileUploaders.forEach((fileUploader) => { if (fileUploader.status === UploadStatus.uploading) { uploadingCount++; } else if (fileUploader.status === UploadStatus.uploaded) { uploadedCount++; } else if (fileUploader.status === UploadStatus.failed) { failedCount++; } }); if (failedCount > 0) { this.uploadTips = this.i18nText.getFailedFilesCount(failedCount); return 'failed'; } if (uploadingCount > 0) { this.uploadTips = this.i18nText.getUploadingFilesCount(uploadingCount, filesCount); return 'uploading'; } if (uploadedCount === filesCount && uploadedCount !== 0) { return 'uploaded'; } if (filesCount !== 0) { this.uploadTips = this.i18nText.getSelectedFilesCount(filesCount); return 'selected'; } } cancelUpload() { this.multipleUploadViewComponent.fileUploaders .filter((fileUploader) => fileUploader.status === UploadStatus.uploading) .forEach((fileUploader) => { fileUploader.status = UploadStatus.failed; }); } ngOnDestroy() { if (this.i18nSubscription) { this.i18nSubscription.unsubscribe(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MultipleUploadComponent, deps: [{ token: SelectFiles }, { token: i1.I18nService }, { token: DOCUMENT }, { token: SliceUploadService }, { token: i4.DevConfigService }, { token: i5.ToastService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: MultipleUploadComponent, selector: "d-multiple-upload", inputs: { uploadOptions: "uploadOptions", fileOptions: "fileOptions", autoUpload: "autoUpload", withoutBtn: "withoutBtn", showTip: "showTip", uploadedFiles: "uploadedFiles", uploadedFilesRef: "uploadedFilesRef", preloadFilesRef: "preloadFilesRef", filePath: "filePath", placeholderText: "placeholderText", uploadText: "uploadText", confirmText: "confirmText", oneTimeUpload: "oneTimeUpload", disabled: "disabled", beforeUpload: "beforeUpload", setCustomUploadOptions: "setCustomUploadOptions", enableDrop: "enableDrop", showGlowStyle: "showGlowStyle" }, outputs: { successEvent: "successEvent", errorEvent: "errorEvent", errorChunkEvent: "errorChunkEvent", deleteUploadedFileEvent: "deleteUploadedFileEvent", fileDrop: "fileDrop", fileOver: "fileOver", fileSelect: "fileSelect" }, host: { properties: { "class.devui-glow-style": "this.hasGlowStyle" } }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultipleUploadComponent), multi: true, }, ], viewQueries: [{ propertyName: "multipleUploadViewComponent", first: true, predicate: ["dMultipleUploadView"], descendants: true, static: true }], exportAs: ["dMultipleUpload"], ngImport: i0, template: "<div\n d-file-drop\n [enableDrop]=\"enableDrop\"\n (fileDrop)=\"onFileDrop($event)\"\n (fileOver)=\"onFileOver($event)\"\n (fileSelect)=\"onFileSelect($event)\"\n [ngStyle]=\"{ border: isDropOVer ? '1px solid #15bf15' : '0' }\"\n style=\"display: flex\"\n>\n <div class=\"devui-input-group\" [class.disabled]=\"disabled\" [class.with-button]=\"!withoutBtn\" (click)=\"onClick($event)\">\n <div *ngIf=\"dMultipleUploadView.fileUploaders.length === 0\" class=\"devui-form-control devui-upload-placeholder\">\n {{ placeholderText ? placeholderText : i18nText?.chooseFile }}\n </div>\n <ul *ngIf=\"dMultipleUploadView.fileUploaders.length > 0\" class=\"devui-form-control devui-files-list\">\n <li\n *ngFor=\"let fileUploader of dMultipleUploadView.fileUploaders; let index = index\"\n class=\"devui-file-item devui-file-tag\"\n style=\"display: inline-block; margin: 0 2px 2px 0\"\n title=\"{{ fileUploader.file.name }}\"\n >\n <span class=\"devui-filename {{ fileUploader.status === UploadStatus.failed ? 'devui-failed-color' : '' }}\">\n {{ fileUploader.file.name }}\n </span>\n <span\n class=\"icon devui-upload-remove {{ fileUploader.status === UploadStatus.failed ? 'devui-upload-delete-file-button' : '' }}\n {{\n fileUploader.status === UploadStatus.uploading || fileUploader.status === UploadStatus.uploaded ? 'devui-uploading-delete' : ''\n }}\"\n (click)=\"deleteFile($event, fileUploader.file)\"\n >\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <g transform=\"translate(-270.000000, -2376.000000)\">\n <g transform=\"translate(198.000000, 1991.000000)\">\n <g transform=\"translate(0.000000, 383.000000)\">\n <g transform=\"translate(72.000000, 2.000000)\">\n <path\n d=\"M4.570404,4.06442993 L4.6207661,4.10650663 L8,7.48581818 L11.3792339,4.10650663 C11.5212427,3.96449779 11.7514845,3.96449779 11.8934934,4.10650663 C12.0197234,4.2327367 12.033749,4.42868172 11.9355701,4.570404 L11.8934934,4.6207661 L8.51418182,8 L11.8934934,11.3792339 C12.0355022,11.5212427 12.0355022,11.7514845 11.8934934,11.8934934 C11.7672633,12.0197234 11.5713183,12.033749 11.429596,11.9355701 L11.3792339,11.8934934 L8,8.51418182 L4.6207661,11.8934934 C4.47875727,12.0355022 4.24851546,12.0355022 4.10650663,11.8934934 C3.98027655,11.7672633 3.96625099,11.5713183 4.06442993,11.429596 L4.10650663,11.3792339 L7.48581818,8 L4.10650663,4.6207661 C3.96449779,4.47875727 3.96449779,4.24851546 4.10650663,4.10650663 C4.2327367,3.98027655 4.42868172,3.96625099 4.570404,4.06442993 Z\"\n ></path>\n </g>\n </g>\n </g>\n </g>\n </g>\n </svg>\n </span>\n <div *ngIf=\"fileUploader.status === UploadStatus.uploading\" class=\"icon devui-upload-progress\">\n <d-progress\n [type]=\"'circle'\"\n [percentage]=\"fileUploader.percentage\"\n [strokeColor]=\"'#029931'\"\n [strokeWidth]=\"8\"\n [showContent]=\"false\"\n >\n </d-progress>\n </div>\n <span *ngIf=\"fileUploader.status === UploadStatus.failed\" class=\"icon icon-running\" (click)=\"upload($event, fileUploader)\"> </span>\n <span *ngIf=\"fileUploader.status === UploadStatus.uploaded\" class=\"icon icon-right\"></span>\n </li>\n </ul>\n <span class=\"devui-input-group-addon\">\n <svg class=\"svg-icon-dot\" height=\"1em\" width=\"1em\" viewBox=\"0 0 1024 1024\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"m400.31892 176.970574c0-61.574381 50.113351-111.680569 111.679545-111.680569 61.576427 0 111.680569 50.106188 111.680569 111.680569 0 61.568241-50.104141 111.679545-111.680569 111.679545-61.566194 0-111.679545-50.111304-111.679545-111.679545zm0 335.028403c0-61.568241 50.113351-111.678522 111.679545-111.678522 61.576427 0 111.680569 50.110281 111.680569 111.678522 0 61.574381-50.105165 111.682615-111.680569 111.682615-61.566194 0-111.679545-50.108235-111.679545-111.682615zm0 335.037612c0-61.572334 50.113351-111.679545 111.679545-111.679545 61.575404 0 111.680569 50.107211 111.680569 111.679545 0 61.567217-50.105165 111.672382-111.680569 111.672382-61.566194 0-111.679545-50.105164-111.679545-111.672382zm0 0\"\n />\n </svg>\n </span>\n </div>\n <d-button\n *ngIf=\"!autoUpload && !withoutBtn\"\n [disabled]=\"disabled\"\n (btnClick)=\"upload($event)\"\n [style.margin-left.px]=\"8\"\n bsStyle=\"common\"\n >\n {{ uploadText || i18nText?.upload }}\n </d-button>\n</div>\n<ng-container *ngIf=\"showTip\">\n <div class=\"devui-upload-tip\">\n <span *ngIf=\"getStatus() === 'selected'\" class=\"devui-loading\">{{ uploadTips }}</span>\n <span *ngIf=\"getStatus() === 'uploading'\" class=\"devui-loading\">\n <span style=\"margin-right: 8px\">{{ uploadTips }}</span>\n <a (click)=\"cancelUpload()\">{{ i18nText?.cancelUpload }}</a>\n </span>\n <div *ngIf=\"getStatus() === 'uploaded'\" class=\"devui-loaded\">\n <i class=\"icon icon-right-o\"></i>\n <span style=\"vertical-align: middle\">{{ i18nText?.uploadSuccess }}</span>\n </div>\n <div *ngIf=\"getStatus() === 'failed'\" class=\"devui-upload-failed\">\n <i class=\"icon icon-info-o\"></i>\n <span style=\"vertical-align: middle\">\n <span style=\"margin-right: 8px\">{{ uploadTips }}</span>\n <a (click)=\"upload($event)\">{{ i18nText?.reUpload }}</a>\n </span>\n </div>\n </div>\n</ng-container>\n<d-multiple-upload-view\n #dMultipleUploadView\n [uploadedFiles]=\"uploadedFiles\"\n [uploadedFilesRef]=\"uploadedFilesRef\"\n [preloadFilesRef]=\"preloadFilesRef\"\n [uploadOptions]=\"uploadOptions\"\n [filePath]=\"filePath\"\n [setCustomUploadOptions]=\"setCustomUploadOptions\"\n (deleteUploadedFileEvent)=\"_onDeleteUploadedFile($event)\"\n>\n</d-multiple-upload-view>\n", styles: [".devui-font-size-base{font-size:var(--devui-font-size, 12px)}.devui-font-base{font-size:var(--devui-font-size, 12px);font-weight:var(--devui-font-content-weight, normal);line-height:var(--devui-line-height-base, 1.5)}.devui-font-size-modal-title{font-size:var(--devui-font-size-modal-title, 18px)}.devui-font-modal-title{font-size:var(--devui-font-size-modal-title, 18px);font-weight:var(--devui-font-title-weight, bold);line-height:var(--devui-line-height-base, 1.5)}.dev