diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 9045a0e5d..bb466fb5c 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -1,7 +1,7 @@
-
+
{{asset.fileType}} @@ -46,11 +46,11 @@
-
- +
+
-
+
Drop to update
@@ -77,14 +77,14 @@
-
+
-
+
- +
@@ -113,11 +113,11 @@
-
- +
+
-
+
Drop to update
diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index 30fdd6eae..6550d3bda 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -5,15 +5,26 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@angular/core'; import { + AppsState, AssetDto, + AssetsService, + AuthService, + DateTime, DialogModel, + DialogService, fadeAnimation, - UploadingAsset + StatefulComponent, + Types, + Versioned } from '@app/shared/internal'; +interface State { + progress: number; +} + @Component({ selector: 'sqx-asset', styleUrls: ['./asset.component.scss'], @@ -23,9 +34,9 @@ import { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class AssetComponent { +export class AssetComponent extends StatefulComponent implements OnInit { @Input() - public upload: UploadingAsset; + public initFile: File; @Input() public asset: AssetDto; @@ -52,13 +63,16 @@ export class AssetComponent { public allTags: string[]; @Output() - public remove = new EventEmitter(); + public load = new EventEmitter(); @Output() - public update = new EventEmitter(); + public loadError = new EventEmitter(); @Output() - public uploadFile = new EventEmitter(); + public remove = new EventEmitter(); + + @Output() + public update = new EventEmitter(); @Output() public delete = new EventEmitter(); @@ -68,9 +82,54 @@ export class AssetComponent { public editDialog = new DialogModel(); - public updateFile(files: File[]) { + constructor(changeDetector: ChangeDetectorRef, + private readonly appsState: AppsState, + private readonly assetsService: AssetsService, + private readonly authState: AuthService, + private readonly dialogs: DialogService + ) { + super(changeDetector, { + progress: 0 + }); + } + + public ngOnInit() { + const initFile = this.initFile; + + if (initFile) { + this.setProgress(1); + + this.assetsService.uploadFile(this.appsState.appName, initFile, this.authState.user!.token, DateTime.now()) + .subscribe(dto => { + if (Types.is(dto, AssetDto)) { + this.emitLoad(dto); + } else { + this.setProgress(dto); + } + }, error => { + this.dialogs.notifyError(error); + + this.emitLoadError(error); + }); + } + } + + public updateFile(files: FileList) { if (files.length === 1) { - this.uploadFile.emit(files[0]); + this.setProgress(1); + + this.assetsService.replaceFile(this.appsState.appName, this.asset.id, files[0], this.asset.version) + .subscribe(dto => { + if (Types.is(dto, Versioned)) { + this.updateAsset(this.asset.update(dto.payload, this.authState.user!.token, dto.version), true); + } else { + this.setProgress(dto); + } + }, error => { + this.dialogs.notifyError(error); + + this.setProgress(0); + }); } } @@ -92,6 +151,14 @@ export class AssetComponent { this.delete.emit(this.asset); } + public emitLoad(asset: AssetDto) { + this.load.emit(asset); + } + + public emitLoadError(error: any) { + this.loadError.emit(error); + } + public emitUpdate() { this.update.emit(this.asset); } @@ -100,6 +167,10 @@ export class AssetComponent { this.remove.emit(this.asset); } + private setProgress(progress: number) { + this.next(s => ({ ...s, progress })); + } + public updateAsset(asset: AssetDto, emitEvent: boolean) { this.asset = asset; @@ -107,6 +178,8 @@ export class AssetComponent { this.emitUpdate(); } + this.next(s => ({ ...s, progress: 0 })); + this.cancelEdit(); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/assets.state.spec.ts b/src/Squidex/app/shared/state/assets.state.spec.ts index b69927318..702954a2f 100644 --- a/src/Squidex/app/shared/state/assets.state.spec.ts +++ b/src/Squidex/app/shared/state/assets.state.spec.ts @@ -23,7 +23,6 @@ describe('AssetsState', () => { const { app, appsState, - authService, creation, creator, modified, @@ -52,7 +51,7 @@ describe('AssetsState', () => { assetsService.setup(x => x.getTags(app)) .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })); - assetsState = new AssetsState(appsState.object, assetsService.object, authService.object, dialogs.object); + assetsState = new AssetsState(appsState.object, assetsService.object, dialogs.object); assetsState.load().subscribe(); }); @@ -77,6 +76,15 @@ describe('AssetsState', () => { dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once()); }); + it('should add asset to snapshot when created', () => { + const newAsset = new AssetDto('id3', creator, creator, creation, creation, 'name3', 'hash3', 'type3', 3, 3, 'mime3', false, true, 0, 0, 'slug2', [], 'url3', version); + + assetsState.add(newAsset); + + expect(assetsState.snapshot.assets.values).toEqual([newAsset, ...oldAssets]); + expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(201); + }); + it('should update properties when updated', () => { const newAsset = new AssetDto('id1', modifier, modifier, modified, modified, 'name3', 'hash3', 'type3', 3, 3, 'mime3', false, true, 0, 0, 'slug3', ['new'], 'url3', version); @@ -90,7 +98,7 @@ describe('AssetsState', () => { it('should remove asset from snapshot when deleted', () => { assetsService.setup(x => x.deleteAsset(app, oldAssets[0].id, version)) - .returns(() => of(new Versioned(newVersion, {}))); + .returns(() => of(new Versioned(newVersion, {}))); assetsState.delete(oldAssets[0]).subscribe(); diff --git a/src/Squidex/app/shared/state/assets.state.ts b/src/Squidex/app/shared/state/assets.state.ts index 5186a5e6d..5c6fb58cd 100644 --- a/src/Squidex/app/shared/state/assets.state.ts +++ b/src/Squidex/app/shared/state/assets.state.ts @@ -6,49 +6,20 @@ */ import { Injectable } from '@angular/core'; -import { combineLatest, Observable, Subject, Subscription } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { - DateTime, DialogService, ImmutableArray, - MathHelper, notify, Pager, - State, - Types + State } from '@app/framework'; -import { AuthService } from '../services/auth.service'; -import { AssetDto, AssetsService } from './../services/assets.service'; +import { AssetDto, AssetsService} from './../services/assets.service'; import { AppsState } from './apps.state'; -export interface UploadingAsset { - // Unique id. - id: string; - - // The name of the asset. - name: string; - - // The upload subscription. - subscription: Subscription; - - // The progress. - progress: number; - - // Indicates if the upload has been completed. - isCompleted?: boolean; -} - -export interface AssetWithUpload { - // The asset. - asset: AssetDto; - - // The corresponding upload. - upload?: UploadingAsset; -} - interface Snapshot { // All assets tags. tags: { [name: string]: number }; @@ -56,12 +27,6 @@ interface Snapshot { // The selected asset tags. tagsSelected: { [name: string]: boolean }; - // The uploads. - uploadsDirect: ImmutableArray; - - // The uploads removed with a delay. - uploadsDelayed: ImmutableArray; - // The current assets. assets: ImmutableArray; @@ -93,10 +58,6 @@ export class AssetsState extends State { this.changes.pipe(map(x => x.assets), distinctUntilChanged()); - public assetsWithUploads = - this.changes.pipe(map(x => getAssetsWithUploads(x)), - distinctUntilChanged()); - public assetsQuery = this.changes.pipe(map(x => x.assetsQuery), distinctUntilChanged()); @@ -105,14 +66,6 @@ export class AssetsState extends State { this.changes.pipe(map(x => x.assetsPager), distinctUntilChanged()); - public uploads = - this.changes.pipe(map(x => x.uploadsDirect), - distinctUntilChanged()); - - public uploadsDelayed = - this.changes.pipe(map(x => x.uploadsDelayed), - distinctUntilChanged()); - public isLoaded = this.changes.pipe(map(x => !!x.isLoaded), distinctUntilChanged()); @@ -120,17 +73,9 @@ export class AssetsState extends State { constructor( private readonly appsState: AppsState, private readonly assetsService: AssetsService, - private readonly authService: AuthService, private readonly dialogs: DialogService ) { - super({ - assets: ImmutableArray.empty(), - assetsPager: new Pager(0, 0, 30), - uploadsDirect: ImmutableArray.empty(), - uploadsDelayed: ImmutableArray.empty(), - tags: {}, - tagsSelected: {} - }); + super({ assets: ImmutableArray.empty(), assetsPager: new Pager(0, 0, 30), tags: {}, tagsSelected: {} }); } public load(isReload = false): Observable { @@ -165,100 +110,6 @@ export class AssetsState extends State { notify(this.dialogs)); } - public remove(upload: UploadingAsset, delayed = false) { - upload.subscription.unsubscribe(); - - this.next(s => { - let uploadsDirect = s.uploadsDirect.removeBy('id', upload); - let uploadsDelayed = s.uploadsDelayed; - - if (!delayed) { - uploadsDelayed = s.uploadsDelayed.removeBy('id', upload); - } - - return { ...s, uploadsDirect, uploadsDelayed }; - }); - - if (!delayed) { - setTimeout(() => { - this.remove(upload, true); - }, 10000); - } - } - - public upload(file: File, now?: DateTime): Observable { - const observable = this.assetsService.uploadFile(this.appName, file, this.user, now || DateTime.now()); - - let upload: UploadingAsset; - - const subject = new Subject(); - const subscription = observable.subscribe(event => { - if (Types.isNumber(event)) { - this.setProgress(upload, event); - - subject.next(event); - } else { - if (event.isDuplicate) { - this.dialogs.notifyError('The same asset has already been uploaded.'); - } else { - this.add(event); - } - - subject.next(event); - } - - }, error => { - subject.error(error); - - this.remove(upload, true); - }, () => { - subject.complete(); - - this.remove(upload, true); - }); - - upload = { id: MathHelper.guid(), name: file.name, progress: 0, subscription }; - - this.addUpload(upload); - - return subject; - } - - public replaceFile(asset: AssetDto, file: File, now?: DateTime): Observable { - const observable = this.assetsService.replaceFile(this.appName, asset.id, file, asset.version); - - let upload: UploadingAsset; - - const subject = new Subject(); - const subscription = observable.subscribe(event => { - if (Types.isNumber(event)) { - this.setProgress(upload, event); - - subject.next(event); - } else { - const newAsset = asset.update(event.payload, this.user, event.version, now || DateTime.now()); - - this.update(newAsset); - - subject.next(newAsset); - } - }, error => { - subject.error(error); - - this.remove(upload, true); - }, () => { - subject.complete(); - - this.remove(upload, true); - }); - - upload = { id: asset.id, name: file.name, progress: 0, subscription }; - - this.addUpload(upload); - - return subject; - } - public add(asset: AssetDto) { this.next(s => { const assets = s.assets.pushFront(asset); @@ -373,41 +224,9 @@ export class AssetsState extends State { return Object.keys(this.snapshot.tagsSelected).length === 0; } - private setProgress(upload: UploadingAsset, progress: number) { - this.next(s => { - const newUpload = { ...upload, progress }; - - const uploadsDirect = s.uploadsDirect.replaceBy('id', newUpload); - const uploadsDelayed = s.uploadsDelayed.replaceBy('id', newUpload); - - return { ...s, uploadsDirect, uploadsDelayed }; - }); - } - - private addUpload(upload: UploadingAsset) { - this.next(s => { - const uploadsDirect = s.uploadsDirect.pushFront(upload); - const uploadsDelayed = s.uploadsDelayed.pushFront(upload); - - return { ...s, uploadsDirect, uploadsDelayed }; - }); - } - private get appName() { return this.appsState.appName; } - - private get user() { - return this.authService.user!.token; - } -} - -function getAssetsWithUploads(state: Snapshot) { - return state.assets.map(asset => { - const upload = state.uploadsDirect.find(x => x.id === x.id); - - return { upload, asset }; - }); } function addTags(asset: AssetDto, tags: { [x: string]: number; }) { @@ -443,4 +262,7 @@ function sort(tags: { [name: string]: number }) { }).map(key => { return { name: key, count: tags[key] }; }); -} \ No newline at end of file +} + +@Injectable() +export class AssetsDialogState extends AssetsState { } \ No newline at end of file