/* * Squidex Headless CMS * * @license * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ import { Injectable } from '@angular/core'; import { DialogService, MathHelper, State, Types } from '@app/framework'; import { Observable, Subject } from 'rxjs'; import { map, publishReplay, refCount, takeUntil } from 'rxjs/operators'; import { AssetDto, AssetsService } from './../services/assets.service'; import { AppsState } from './apps.state'; import { AssetsState } from './assets.state'; export interface Upload { // Unique id. id: string; // The name of the asset. name: string; // The upload subscription. cancel: Subject; // The progress. progress: number; // The status. status: string; } interface Snapshot { // The uploads. uploads: UploadList; } export class UploadCanceled {} type UploadList = ReadonlyArray; type UploadResult = AssetDto | number; @Injectable() export class AssetUploaderState extends State { public uploads = this.project(x => x.uploads); constructor( private readonly appsState: AppsState, private readonly assetsService: AssetsService, private readonly dialogs: DialogService ) { super({ uploads: [] }, 'AssetUploader'); } public stopUpload(upload: Upload) { upload.cancel.error(new UploadCanceled()); this.next(s => { const uploads = s.uploads.removeBy('id', upload); return { ...s, uploads }; }, 'Stopped'); } public uploadFile(file: File, target?: AssetsState): Observable { const parentId = target?.parentId; const stream = this.assetsService.postAssetFile(this.appName, file, parentId); return this.upload(stream, MathHelper.guid(), file.name, asset => { if (asset.isDuplicate) { this.dialogs.notifyError('i18n:assets.duplicateFile'); } else if (target) { target.addAsset(asset); } return asset; }); } public uploadAsset(asset: AssetDto, file: Blob): Observable { const stream = this.assetsService.putAssetFile(this.appName, asset, file, asset.version); return this.upload(stream, asset.id, file['name'] || asset.fileName); } private upload(source: Observable, id: string, name: string, complete?: ((completion: AssetDto) => AssetDto)) { let upload = { id, name, progress: 1, status: 'Running', cancel: new Subject() }; this.addUpload(upload); const stream = source.pipe(takeUntil(upload.cancel), map(event => { if (Types.isNumber(event)) { return event; } else { if (complete) { return complete(event); } else { return event; } } }), publishReplay(), refCount()); stream.subscribe(event => { if (Types.isNumber(event)) { upload = this.update(upload, { progress: event }); } }, () => { upload = this.remove(upload, { status: 'Failed' }); }, () => { upload = this.remove(upload, { status: 'Completed', progress: 100 }); }); return stream; } private remove(upload: Upload, update: Partial) { upload = this.update(upload, update); setTimeout(() => { this.next(s => { const uploads = s.uploads.removeBy('id', upload); return { ...s, uploads }; }, 'Upload Done'); }, 10000); return upload; } private update(upload: Upload, update: Partial) { upload = { ...upload, ...update }; this.next(s => { const uploads = s.uploads.replaceBy('id', upload); return { ...s, uploads }; }, 'Updated'); return upload; } private addUpload(upload: Upload) { this.next(s => { const uploads = [upload, ...s.uploads]; return { ...s, uploads }; }, 'Upload Started'); } private get appName() { return this.appsState.appName; } }