forked from EVOgeek/Vmeda.Online
653 lines
24 KiB
TypeScript
653 lines
24 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { Injectable } from '@angular/core';
|
|
import { ModalController } from 'ionic-angular';
|
|
import { Camera, CameraOptions } from '@ionic-native/camera';
|
|
import { FileEntry } from '@ionic-native/file';
|
|
import { MediaCapture, MediaFile, CaptureError, CaptureAudioOptions, CaptureVideoOptions } from '@ionic-native/media-capture';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { CoreFileProvider } from '@providers/file';
|
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
|
import { CoreLoggerProvider } from '@providers/logger';
|
|
import { CoreSitesProvider } from '@providers/sites';
|
|
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
|
import { CoreWSFileUploadOptions, CoreWSExternalFile } from '@providers/ws';
|
|
import { Subject } from 'rxjs';
|
|
import { CoreApp } from '@providers/app';
|
|
import { makeSingleton } from '@singletons/core.singletons';
|
|
|
|
/**
|
|
* File upload options.
|
|
*/
|
|
export interface CoreFileUploaderOptions extends CoreWSFileUploadOptions {
|
|
/**
|
|
* Whether the file should be deleted after the upload (if success).
|
|
*/
|
|
deleteAfterUpload?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Service to upload files.
|
|
*/
|
|
@Injectable()
|
|
export class CoreFileUploaderProvider {
|
|
static LIMITED_SIZE_WARNING = 1048576; // 1 MB.
|
|
static WIFI_SIZE_WARNING = 10485760; // 10 MB.
|
|
|
|
protected logger;
|
|
|
|
// Observers to notify when a media file starts/stops being recorded/selected.
|
|
onGetPicture: Subject<boolean> = new Subject<boolean>();
|
|
onAudioCapture: Subject<boolean> = new Subject<boolean>();
|
|
onVideoCapture: Subject<boolean> = new Subject<boolean>();
|
|
|
|
constructor(logger: CoreLoggerProvider,
|
|
protected fileProvider: CoreFileProvider,
|
|
protected textUtils: CoreTextUtilsProvider,
|
|
protected utils: CoreUtilsProvider,
|
|
protected sitesProvider: CoreSitesProvider,
|
|
protected timeUtils: CoreTimeUtilsProvider,
|
|
protected mimeUtils: CoreMimetypeUtilsProvider,
|
|
protected filepoolProvider: CoreFilepoolProvider,
|
|
protected translate: TranslateService,
|
|
protected mediaCapture: MediaCapture,
|
|
protected camera: Camera,
|
|
protected modalCtrl: ModalController) {
|
|
this.logger = logger.getInstance('CoreFileUploaderProvider');
|
|
}
|
|
|
|
/**
|
|
* Add a dot to the beginning of an extension.
|
|
*
|
|
* @param extension Extension.
|
|
* @return Treated extension.
|
|
*/
|
|
protected addDot(extension: string): string {
|
|
return '.' + extension;
|
|
}
|
|
|
|
/**
|
|
* Compares two file lists and returns if they are different.
|
|
*
|
|
* @param a First file list.
|
|
* @param b Second file list.
|
|
* @return Whether both lists are different.
|
|
*/
|
|
areFileListDifferent(a: any[], b: any[]): boolean {
|
|
a = a || [];
|
|
b = b || [];
|
|
if (a.length != b.length) {
|
|
return true;
|
|
}
|
|
|
|
// Currently we are going to compare the order of the files as well.
|
|
// This function can be improved comparing more fields or not comparing the order.
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (a[i].name != b[i].name || a[i].filename != b[i].filename) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Start the audio recorder application and return information about captured audio clip files.
|
|
*
|
|
* @param options Options.
|
|
* @return Promise resolved with the result.
|
|
*/
|
|
captureAudio(options: CaptureAudioOptions): Promise<MediaFile[] | CaptureError> {
|
|
this.onAudioCapture.next(true);
|
|
|
|
return this.mediaCapture.captureAudio(options).finally(() => {
|
|
this.onAudioCapture.next(false);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Record an audio file without using an external app.
|
|
*
|
|
* @return Promise resolved with the file.
|
|
*/
|
|
captureAudioInApp(): Promise<MediaFile> {
|
|
return new Promise((resolve, reject): any => {
|
|
const params = {
|
|
type: 'audio',
|
|
};
|
|
|
|
const modal = this.modalCtrl.create('CoreEmulatorCaptureMediaPage', params, { enableBackdropDismiss: false });
|
|
modal.present();
|
|
modal.onDidDismiss((data: any, role: string) => {
|
|
if (role == 'success') {
|
|
resolve(data[0]);
|
|
} else {
|
|
reject(data);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start the video recorder application and return information about captured video clip files.
|
|
*
|
|
* @param options Options.
|
|
* @return Promise resolved with the result.
|
|
*/
|
|
captureVideo(options: CaptureVideoOptions): Promise<MediaFile[] | CaptureError> {
|
|
this.onVideoCapture.next(true);
|
|
|
|
return this.mediaCapture.captureVideo(options).finally(() => {
|
|
this.onVideoCapture.next(false);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clear temporary attachments to be uploaded.
|
|
* Attachments already saved in an offline store will NOT be deleted.
|
|
*
|
|
* @param files List of files.
|
|
*/
|
|
clearTmpFiles(files: any[]): void {
|
|
// Delete the local files.
|
|
files.forEach((file) => {
|
|
if (!file.offline && file.remove) {
|
|
// Pass an empty function to prevent missing parameter error.
|
|
file.remove(() => {
|
|
// Nothing to do.
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the upload options for a file taken with the Camera Cordova plugin.
|
|
*
|
|
* @param uri File URI.
|
|
* @param isFromAlbum True if the image was taken from album, false if it's a new image taken with camera.
|
|
* @return Options.
|
|
*/
|
|
getCameraUploadOptions(uri: string, isFromAlbum?: boolean): CoreFileUploaderOptions {
|
|
const extension = this.mimeUtils.guessExtensionFromUrl(uri);
|
|
const mimetype = this.mimeUtils.getMimeType(extension);
|
|
const isIOS = CoreApp.instance.isIOS();
|
|
const options: CoreFileUploaderOptions = {
|
|
deleteAfterUpload: !isFromAlbum,
|
|
mimeType: mimetype
|
|
};
|
|
const fileName = this.fileProvider.getFileAndDirectoryFromPath(uri).name;
|
|
|
|
if (isIOS && (mimetype == 'image/jpeg' || mimetype == 'image/png')) {
|
|
// In iOS, the pictures can have repeated names, even if they come from the album.
|
|
// Add a timestamp to the filename to make it unique.
|
|
const split = fileName.split('.');
|
|
split[0] += '_' + this.timeUtils.readableTimestamp();
|
|
|
|
options.fileName = split.join('.');
|
|
} else {
|
|
// Use the same name that the file already has.
|
|
options.fileName = fileName;
|
|
}
|
|
|
|
if (isFromAlbum) {
|
|
// If the file was picked from the album, delete it only if it was copied to the app's folder.
|
|
options.deleteAfterUpload = this.fileProvider.isFileInAppFolder(uri);
|
|
|
|
if (CoreApp.instance.isAndroid()) {
|
|
// Picking an image from album in Android adds a timestamp at the end of the file. Delete it.
|
|
options.fileName = options.fileName.replace(/(\.[^\.]*)\?[^\.]*$/, '$1');
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Get the upload options for a file of any type.
|
|
*
|
|
* @param uri File URI.
|
|
* @param name File name.
|
|
* @param type File type.
|
|
* @param deleteAfterUpload Whether the file should be deleted after upload.
|
|
* @param fileArea File area to upload the file to. It defaults to 'draft'.
|
|
* @param itemId Draft ID to upload the file to, 0 to create new.
|
|
* @return Options.
|
|
*/
|
|
getFileUploadOptions(uri: string, name: string, type: string, deleteAfterUpload?: boolean, fileArea?: string, itemId?: number)
|
|
: CoreFileUploaderOptions {
|
|
const options: CoreFileUploaderOptions = {};
|
|
options.fileName = name;
|
|
options.mimeType = type || this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(options.fileName));
|
|
options.deleteAfterUpload = !!deleteAfterUpload;
|
|
options.itemId = itemId || 0;
|
|
options.fileArea = fileArea;
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Get the upload options for a file taken with the media capture Cordova plugin.
|
|
*
|
|
* @param mediaFile File object to upload.
|
|
* @return Options.
|
|
*/
|
|
getMediaUploadOptions(mediaFile: MediaFile): CoreFileUploaderOptions {
|
|
const options: CoreFileUploaderOptions = {};
|
|
let filename = mediaFile.name;
|
|
|
|
if (!filename.match(/_\d{14}(\..*)?$/)) {
|
|
// Add a timestamp to the filename to make it unique.
|
|
const split = filename.split('.');
|
|
split[0] += '_' + this.timeUtils.readableTimestamp();
|
|
filename = split.join('.');
|
|
}
|
|
|
|
options.fileName = filename;
|
|
options.deleteAfterUpload = true;
|
|
if (mediaFile.type) {
|
|
options.mimeType = mediaFile.type;
|
|
} else {
|
|
options.mimeType = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(options.fileName));
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Take a picture or video, or load one from the library.
|
|
*
|
|
* @param options Options.
|
|
* @return Promise resolved with the result.
|
|
*/
|
|
getPicture(options: CameraOptions): Promise<any> {
|
|
this.onGetPicture.next(true);
|
|
|
|
return this.camera.getPicture(options).finally(() => {
|
|
this.onGetPicture.next(false);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the files stored in a folder, marking them as offline.
|
|
*
|
|
* @param folderPath Folder where to get the files.
|
|
* @return Promise resolved with the list of files.
|
|
*/
|
|
getStoredFiles(folderPath: string): Promise<any[]> {
|
|
return this.fileProvider.getDirectoryContents(folderPath).then((files) => {
|
|
return this.markOfflineFiles(files);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get stored files from combined online and offline file object.
|
|
*
|
|
* @param filesObject The combined offline and online files object.
|
|
* @param folderPath Folder path to get files from.
|
|
* @return Promise resolved with files.
|
|
*/
|
|
getStoredFilesFromOfflineFilesObject(filesObject: { online: any[], offline: number }, folderPath: string): Promise<any[]> {
|
|
let files = [];
|
|
|
|
if (filesObject) {
|
|
if (filesObject.online && filesObject.online.length > 0) {
|
|
files = this.utils.clone(filesObject.online);
|
|
}
|
|
|
|
if (filesObject.offline > 0) {
|
|
return this.getStoredFiles(folderPath).then((offlineFiles) => {
|
|
return files.concat(offlineFiles);
|
|
}).catch(() => {
|
|
// Ignore not found files.
|
|
return files;
|
|
});
|
|
}
|
|
}
|
|
|
|
return Promise.resolve(files);
|
|
}
|
|
|
|
/**
|
|
* Check if a file's mimetype is invalid based on the list of accepted mimetypes. This function needs either the file's
|
|
* mimetype or the file's path/name.
|
|
*
|
|
* @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported.
|
|
* @param path File's path or name.
|
|
* @param mimetype File's mimetype.
|
|
* @return Undefined if file is valid, error message if file is invalid.
|
|
*/
|
|
isInvalidMimetype(mimetypes?: string[], path?: string, mimetype?: string): string {
|
|
let extension: string;
|
|
|
|
if (mimetypes) {
|
|
// Verify that the mimetype of the file is supported.
|
|
if (mimetype) {
|
|
extension = this.mimeUtils.getExtension(mimetype);
|
|
|
|
if (mimetypes.indexOf(mimetype) == -1) {
|
|
// Get the "main" mimetype of the extension.
|
|
// It's possible that the list of accepted mimetypes only includes the "main" mimetypes.
|
|
mimetype = this.mimeUtils.getMimeType(extension);
|
|
}
|
|
} else {
|
|
extension = this.mimeUtils.getFileExtension(path);
|
|
mimetype = this.mimeUtils.getMimeType(extension);
|
|
}
|
|
|
|
if (mimetype && mimetypes.indexOf(mimetype) == -1) {
|
|
extension = extension || this.translate.instant('core.unknown');
|
|
|
|
return this.translate.instant('core.fileuploader.invalidfiletype', { $a: extension });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark files as offline.
|
|
*
|
|
* @param files Files to mark as offline.
|
|
* @return Files marked as offline.
|
|
*/
|
|
markOfflineFiles(files: any[]): any[] {
|
|
// Mark the files as pending offline.
|
|
files.forEach((file) => {
|
|
file.offline = true;
|
|
file.filename = file.name;
|
|
});
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Parse filetypeList to get the list of allowed mimetypes and the data to render information.
|
|
*
|
|
* @param filetypeList Formatted string list where the mimetypes can be checked.
|
|
* @return Mimetypes and the filetypes informations. Undefined if all types supported.
|
|
*/
|
|
prepareFiletypeList(filetypeList: string): { info: any[], mimetypes: string[] } {
|
|
filetypeList = filetypeList && filetypeList.trim();
|
|
|
|
if (!filetypeList || filetypeList == '*') {
|
|
// All types supported, return undefined.
|
|
return undefined;
|
|
}
|
|
|
|
const filetypes = filetypeList.split(/[;, ]+/g),
|
|
mimetypes = {}, // Use an object to prevent duplicates.
|
|
typesInfo = [];
|
|
|
|
filetypes.forEach((filetype) => {
|
|
filetype = filetype.trim();
|
|
|
|
if (filetype) {
|
|
if (filetype.indexOf('/') != -1) {
|
|
// It's a mimetype.
|
|
typesInfo.push({
|
|
name: this.mimeUtils.getMimetypeDescription(filetype),
|
|
extlist: this.mimeUtils.getExtensions(filetype).map(this.addDot).join(' ')
|
|
});
|
|
|
|
mimetypes[filetype] = true;
|
|
} else if (filetype.indexOf('.') === 0) {
|
|
// It's an extension.
|
|
const mimetype = this.mimeUtils.getMimeType(filetype);
|
|
typesInfo.push({
|
|
name: mimetype ? this.mimeUtils.getMimetypeDescription(mimetype) : false,
|
|
extlist: filetype
|
|
});
|
|
|
|
if (mimetype) {
|
|
mimetypes[mimetype] = true;
|
|
}
|
|
} else {
|
|
// It's a group.
|
|
const groupExtensions = this.mimeUtils.getGroupMimeInfo(filetype, 'extensions'),
|
|
groupMimetypes = this.mimeUtils.getGroupMimeInfo(filetype, 'mimetypes');
|
|
|
|
if (groupExtensions.length > 0) {
|
|
typesInfo.push({
|
|
name: this.mimeUtils.getTranslatedGroupName(filetype),
|
|
extlist: groupExtensions ? groupExtensions.map(this.addDot).join(' ') : ''
|
|
});
|
|
|
|
groupMimetypes.forEach((mimetype) => {
|
|
if (mimetype) {
|
|
mimetypes[mimetype] = true;
|
|
}
|
|
});
|
|
} else {
|
|
// Treat them as extensions.
|
|
filetype = this.addDot(filetype);
|
|
|
|
const mimetype = this.mimeUtils.getMimeType(filetype);
|
|
typesInfo.push({
|
|
name: mimetype ? this.mimeUtils.getMimetypeDescription(mimetype) : false,
|
|
extlist: filetype
|
|
});
|
|
|
|
if (mimetype) {
|
|
mimetypes[mimetype] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
info: typesInfo,
|
|
mimetypes: Object.keys(mimetypes)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Given a list of files (either online files or local files), store the local files in a local folder
|
|
* to be uploaded later.
|
|
*
|
|
* @param folderPath Path of the folder where to store the files.
|
|
* @param files List of files.
|
|
* @return Promise resolved if success.
|
|
*/
|
|
storeFilesToUpload(folderPath: string, files: any[]): Promise<{ online: any[], offline: number }> {
|
|
const result = {
|
|
online: [],
|
|
offline: 0
|
|
};
|
|
|
|
if (!files || !files.length) {
|
|
return Promise.resolve(result);
|
|
}
|
|
|
|
// Remove unused files from previous saves.
|
|
return this.fileProvider.removeUnusedFiles(folderPath, files).then(() => {
|
|
const promises = [];
|
|
|
|
files.forEach((file) => {
|
|
if (file.filename && !file.name) {
|
|
// It's an online file, add it to the result and ignore it.
|
|
result.online.push({
|
|
filename: file.filename,
|
|
fileurl: file.fileurl
|
|
});
|
|
} else if (!file.name) {
|
|
// Error.
|
|
promises.push(Promise.reject(null));
|
|
} else if (file.fullPath && file.fullPath.indexOf(folderPath) != -1) {
|
|
// File already in the submission folder.
|
|
result.offline++;
|
|
} else {
|
|
// Local file, copy it.
|
|
// Use copy instead of move to prevent having a unstable state if some copies succeed and others don't.
|
|
const destFile = this.textUtils.concatenatePaths(folderPath, file.name);
|
|
promises.push(this.fileProvider.copyFile(file.toURL(), destFile));
|
|
result.offline++;
|
|
}
|
|
});
|
|
|
|
return Promise.all(promises).then(() => {
|
|
return result;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Upload a file.
|
|
*
|
|
* @param uri File URI.
|
|
* @param options Options for the upload.
|
|
* @param onProgress Function to call on progress.
|
|
* @param siteId Id of the site to upload the file to. If not defined, use current site.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
uploadFile(uri: string, options?: CoreFileUploaderOptions, onProgress?: (event: ProgressEvent) => any,
|
|
siteId?: string): Promise<any> {
|
|
options = options || {};
|
|
|
|
const deleteAfterUpload = options.deleteAfterUpload,
|
|
ftOptions = this.utils.clone(options);
|
|
|
|
delete ftOptions.deleteAfterUpload;
|
|
|
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
|
return site.uploadFile(uri, ftOptions, onProgress);
|
|
}).then((result) => {
|
|
if (deleteAfterUpload) {
|
|
setTimeout(() => {
|
|
// Use set timeout, otherwise in Electron the upload threw an error sometimes.
|
|
this.fileProvider.removeExternalFile(uri);
|
|
}, 500);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Given a list of files (either online files or local files), upload the local files to the draft area.
|
|
* Local files are not deleted from the device after upload.
|
|
*
|
|
* @param itemId Draft ID.
|
|
* @param files List of files.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the itemId.
|
|
*/
|
|
async uploadFiles(itemId: number, files: (CoreWSExternalFile | FileEntry)[], siteId?: string): Promise<void> {
|
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
|
|
|
if (!files || !files.length) {
|
|
return;
|
|
}
|
|
|
|
await Promise.all(files.map(async (file) => {
|
|
if ((<CoreWSExternalFile> file).filename && !(<FileEntry> file).name) {
|
|
// File already uploaded, ignore it.
|
|
return;
|
|
}
|
|
|
|
file = <FileEntry> file;
|
|
|
|
// Now upload the file.
|
|
const options = this.getFileUploadOptions(file.toURL(), file.name, undefined, false, 'draft', itemId);
|
|
|
|
await this.uploadFile(file.toURL(), options, undefined, siteId);
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Upload a file to a draft area and return the draft ID.
|
|
*
|
|
* If the file is an online file it will be downloaded and then re-uploaded.
|
|
* If the file is a local file it will not be deleted from the device after upload.
|
|
*
|
|
* @param file Online file or local FileEntry.
|
|
* @param itemId Draft ID to use. Undefined or 0 to create a new draft ID.
|
|
* @param component The component to set to the downloaded files.
|
|
* @param componentId An ID to use in conjunction with the component.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the itemId.
|
|
*/
|
|
uploadOrReuploadFile(file: any, itemId?: number, component?: string, componentId?: string | number,
|
|
siteId?: string): Promise<number> {
|
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
|
|
|
let promise,
|
|
fileName;
|
|
|
|
const isOnline = file.filename && !file.name;
|
|
|
|
if (isOnline) {
|
|
// It's an online file. We need to download it and re-upload it.
|
|
fileName = file.filename;
|
|
promise = this.filepoolProvider.downloadUrl(siteId, file.url || file.fileurl, false, component, componentId,
|
|
file.timemodified, undefined, undefined, file).then((path) => {
|
|
return this.fileProvider.getExternalFile(path);
|
|
});
|
|
} else {
|
|
// Local file, we already have the file entry.
|
|
fileName = file.name;
|
|
promise = Promise.resolve(file);
|
|
}
|
|
|
|
return promise.then((fileEntry) => {
|
|
// Now upload the file.
|
|
const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, fileEntry.type, isOnline, 'draft', itemId);
|
|
|
|
return this.uploadFile(fileEntry.toURL(), options, undefined, siteId).then((result) => {
|
|
return result.itemid;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Given a list of files (either online files or local files), upload them to a draft area and return the draft ID.
|
|
*
|
|
* Online files will be downloaded and then re-uploaded.
|
|
* Local files are not deleted from the device after upload.
|
|
* If there are no files to upload it will return a fake draft ID (1).
|
|
*
|
|
* @param files List of files.
|
|
* @param component The component to set to the downloaded files.
|
|
* @param componentId An ID to use in conjunction with the component.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the itemId.
|
|
*/
|
|
uploadOrReuploadFiles(files: any[], component?: string, componentId?: string | number, siteId?: string): Promise<number> {
|
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
|
|
|
if (!files || !files.length) {
|
|
// Return fake draft ID.
|
|
return Promise.resolve(1);
|
|
}
|
|
|
|
// Upload only the first file first to get a draft id.
|
|
return this.uploadOrReuploadFile(files[0], 0, component, componentId, siteId).then((itemId) => {
|
|
const promises = [];
|
|
|
|
for (let i = 1; i < files.length; i++) {
|
|
const file = files[i];
|
|
promises.push(this.uploadOrReuploadFile(file, itemId, component, componentId, siteId));
|
|
}
|
|
|
|
return Promise.all(promises).then(() => {
|
|
return itemId;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export class CoreFileUploader extends makeSingleton(CoreFileUploaderProvider) {}
|