Browse Source

Using uploader

pull/356/head
Sebastian Stehle 7 years ago
parent
commit
4b99c96fa7
  1. 14
      src/Squidex/app/framework/utils/rxjs-extensions.ts
  2. 2
      src/Squidex/app/shared/components/asset-uploader.component.html
  3. 17
      src/Squidex/app/shared/components/asset-uploader.component.ts
  4. 14
      src/Squidex/app/shared/components/asset.component.ts
  5. 11
      src/Squidex/app/shared/components/markdown-editor.component.ts
  6. 23
      src/Squidex/app/shared/components/rich-editor.component.ts
  7. 1
      src/Squidex/app/shared/internal.ts
  8. 6
      src/Squidex/app/shared/module.ts
  9. 181
      src/Squidex/app/shared/state/asset-uploader.state.ts
  10. 180
      src/Squidex/app/shared/state/assets.state.spec.ts
  11. 21
      src/Squidex/app/shared/state/clients.state.spec.ts
  12. 3
      src/Squidex/app/shared/state/contents.state.ts
  13. 12
      src/Squidex/app/shared/state/contributors.state.ts

14
src/Squidex/app/framework/utils/rxjs-extensions.ts

@ -7,8 +7,8 @@
// tslint:disable: only-arrow-functions
import { Observable, throwError } from 'rxjs';
import { catchError, map, onErrorResumeNext, shareReplay, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { map, onErrorResumeNext, shareReplay, switchMap } from 'rxjs/operators';
import { DialogService } from './../services/dialog.service';
@ -26,16 +26,6 @@ export function mapVersioned<T = any, R = any>(project: (value: T, version: Vers
};
}
export function notify<T>(dialogs: DialogService) {
return function mapOperation(source: Observable<T>) {
return source.pipe(catchError(error => {
dialogs.notifyError(error);
return throwError(error);
}));
};
}
type Options<T, R = T> = { silent?: boolean, project?: ((value: T) => R) };
export function shareSubscribed<T, R = T>(dialogs: DialogService, options?: Options<T, R>) {

2
src/Squidex/app/shared/components/asset-uploader.component.html

@ -1,4 +1,4 @@
<ul class="nav navbar-nav" *ngIf="assets.uploadsDelayed | async; let uploads" (sqxFileDrop)="addFiles($event)">
<ul class="nav navbar-nav" *ngIf="assetUploader.uploads | async; let uploads" (sqxFileDrop)="addFiles($event)">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" (click)="modalMenu.toggle()">
<i class="icon-upload-3"></i>

17
src/Squidex/app/shared/components/asset-uploader.component.ts

@ -9,8 +9,10 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
AssetsState,
AssetUploaderState,
DialogModel,
fadeAnimation
fadeAnimation,
Upload
} from '@app/shared/internal';
@Component({
@ -26,11 +28,20 @@ export class AssetUploaderComponent {
public modalMenu = new DialogModel(true);
constructor(
public readonly assets: AssetsState
public readonly assetUploader: AssetUploaderState,
public readonly assetsState: AssetsState
) {
}
public addFiles() {
public addFiles(files: File[]) {
for (let file of files) {
this.assetUploader.uploadFile(file, this.assetsState);
}
this.modalMenu.show();
}
public stopUpload(upload: Upload) {
this.assetUploader.stopUpload(upload);
}
}

14
src/Squidex/app/shared/components/asset.component.ts

@ -8,17 +8,15 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@angular/core';
import {
AppsState,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogModel,
DialogService,
fadeAnimation,
StatefulComponent,
Types
} from '@app/shared/internal';
import { AssetUploaderState } from '../state/asset-uploader.state';
interface State {
progress: number;
@ -82,9 +80,7 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
public editDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly assetUploader: AssetUploaderState,
private readonly dialogs: DialogService
) {
super(changeDetector, {
@ -98,7 +94,7 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
if (initFile) {
this.setProgress(1);
this.assetsService.uploadFile(this.appsState.appName, initFile, this.authState.user!.token, DateTime.now())
this.assetUploader.uploadFile(initFile)
.subscribe(dto => {
if (Types.isNumber(dto)) {
this.setProgress(dto);
@ -117,12 +113,12 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
if (files.length === 1) {
this.setProgress(1);
this.assetsService.replaceFile(this.appsState.appName, this.asset.id, files[0], this.asset.version)
this.assetUploader.uploadUpdate(this.asset, files[0])
.subscribe(dto => {
if (Types.isNumber(dto)) {
this.setProgress(dto);
} else {
this.updateAsset(this.asset.update(dto.payload, this.authState.user!.token, dto.version), true);
this.updateAsset(dto, true);
}
}, error => {
this.dialogs.notifyError(error);

11
src/Squidex/app/shared/components/markdown-editor.component.ts

@ -9,16 +9,13 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, E
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AppsState,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogModel,
ResourceLoaderService,
StatefulControlComponent,
Types
} from '@app/shared/internal';
import { AssetUploaderState } from '../state/asset-uploader.state';
declare var SimpleMDE: any;
@ -54,9 +51,7 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
public assetsDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly assetUploader: AssetUploaderState,
private readonly renderer: Renderer2,
private readonly resourceLoader: ResourceLoaderService
) {
@ -245,7 +240,7 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
}
};
this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now())
this.assetUploader.uploadFile(file)
.subscribe(asset => {
if (Types.is(asset, AssetDto)) {
replaceText(`![${asset.fileName}](${asset.url} '${asset.fileName}')`);

23
src/Squidex/app/shared/components/rich-editor.component.ts

@ -11,16 +11,13 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, E
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AppsState,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogModel,
ResourceLoaderService,
StatefulControlComponent,
Types
} from '@app/shared/internal';
import { AssetUploaderState } from '../state/asset-uploader.state';
declare var tinymce: any;
@ -57,9 +54,7 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
public assetsDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly assetUploader: AssetUploaderState,
private readonly resourceLoader: ResourceLoaderService
) {
super(changeDetector, {});
@ -99,7 +94,7 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
images_upload_handler: (blob: any, success: (url: string) => void, failed: () => void) => {
const file = new File([blob.blob()], blob.filename(), { lastModified: new Date().getTime() });
this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now())
this.assetUploader.uploadFile(file)
.subscribe(asset => {
if (Types.is(asset, AssetDto)) {
success(asset.url);
@ -131,11 +126,13 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
});
self.tinyEditor.on('paste', (event: ClipboardEvent) => {
for (let i = 0; i < event.clipboardData.items.length; i++) {
const file = event.clipboardData.items[i].getAsFile();
if (event.clipboardData) {
for (let i = 0; i < event.clipboardData.items.length; i++) {
const file = event.clipboardData.items[i].getAsFile();
if (file && ImageTypes.indexOf(file.type) >= 0) {
self.uploadFile(file);
if (file && ImageTypes.indexOf(file.type) >= 0) {
self.uploadFile(file);
}
}
}
});
@ -213,7 +210,7 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
this.tinyEditor.setContent(content);
};
this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now())
this.assetUploader.uploadFile(file)
.subscribe(asset => {
if (Types.is(asset, AssetDto)) {
replaceText(`<img src="${asset.url}" alt="${asset.fileName}" />`);

1
src/Squidex/app/shared/internal.ts

@ -47,6 +47,7 @@ export * from './services/users.service';
export * from './state/apps.forms';
export * from './state/apps.state';
export * from './state/asset-uploader.state';
export * from './state/assets.forms';
export * from './state/assets.state';
export * from './state/backups.forms';

6
src/Squidex/app/shared/module.ts

@ -21,11 +21,13 @@ import {
AssetComponent,
AssetDialogComponent,
AssetPreviewUrlPipe,
AssetsDialogState,
AssetsListComponent,
AssetsSelectorComponent,
AssetsService,
AssetsState,
AssetUploaderComponent,
AssetUploaderState,
AssetUrlPipe,
AuthInterceptor,
AuthService,
@ -165,6 +167,9 @@ import {
UserPicturePipe,
UserPictureRefPipe,
TableHeaderComponent
],
providers: [
AssetsDialogState
]
})
export class SqxSharedModule {
@ -182,6 +187,7 @@ export class SqxSharedModule {
AppsState,
AssetsState,
AssetsService,
AssetUploaderState,
AuthService,
BackupsService,
BackupsState,

181
src/Squidex/app/shared/state/asset-uploader.state.ts

@ -0,0 +1,181 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { Observable, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import {
DateTime,
DialogService,
ImmutableArray,
MathHelper,
State,
Types
} from '@app/framework';
import { AuthService } from '../services/auth.service';
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.
subscription: Subscription;
// The progress.
progress: number;
// The status.
status: string;
}
interface Snapshot {
// The uploads.
uploads: UploadList;
}
type UploadList = ImmutableArray<Upload>;
type UploadResult = AssetDto | number;
@Injectable()
export class AssetUploaderState extends State<Snapshot> {
public uploads =
this.changes.pipe(map(x => x.uploads),
distinctUntilChanged());
constructor(
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authService: AuthService,
private readonly dialogs: DialogService
) {
super({ uploads: ImmutableArray.empty() });
}
public stopUpload(upload: Upload) {
upload.subscription.unsubscribe();
this.next(s => {
const uploads = s.uploads.removeBy('id', upload);
return { ...s, uploads };
});
}
public uploadFile(file: File, target?: AssetsState, now?: DateTime): Observable<UploadResult> {
const observable = this.assetsService.uploadFile(this.appName, file, this.user, now || DateTime.now());
let upload: Upload;
const subject = new Subject<UploadResult>();
const subscription = observable.subscribe(event => {
if (Types.isNumber(event)) {
this.update(upload, { progress: event });
} else {
if (event.isDuplicate) {
this.dialogs.notifyError('Asset has already been uploaded.');
}
if (target) {
target.add(event);
}
}
subject.next(event);
}, error => {
subject.error(error);
this.remove(upload, 'failed');
}, () => {
subject.complete();
this.remove(upload, 'completed');
});
upload = { id: MathHelper.guid(), name: file.name, progress: 1, subscription, status: 'running' };
this.addUpload(upload);
return subject;
}
public uploadUpdate(asset: AssetDto, file: File, now?: DateTime): Observable<UploadResult> {
const observable = this.assetsService.replaceFile(this.appName, asset.id, file, asset.version);
let upload: Upload;
const subject = new Subject<UploadResult>();
const subscription = observable.subscribe(event => {
if (Types.isNumber(event)) {
this.update(upload, { progress: event });
subject.next(event);
} else {
subject.next(asset.update(event.payload, this.user, event.version, now));
}
}, error => {
subject.error(error);
this.remove(upload, 'failed');
}, () => {
subject.complete();
this.remove(upload, 'completed');
});
upload = { id: asset.id, name: file.name, progress: 1, subscription, status: 'running' };
this.addUpload(upload);
return subject;
}
private remove(upload: Upload, status: string) {
this.update(upload, { status });
setTimeout(() => {
this.next(s => {
const uploads = s.uploads.removeBy('id', upload);
return { ...s, uploads };
});
}, 10000);
}
private update(upload: Upload, update: Partial<Upload>) {
this.next(s => {
const uploads = s.uploads.replaceBy('id', { ...upload, ...update });
return { ...s, uploads };
});
}
private addUpload(upload: Upload) {
this.next(s => {
const uploads = s.uploads.push(upload);
return { ...s, uploads };
});
}
private get appName() {
return this.appsState.appName;
}
private get user() {
return this.authService.user!.token;
}
}

180
src/Squidex/app/shared/state/assets.state.spec.ts

@ -36,6 +36,8 @@ describe('AssetsState', () => {
new AssetDto('id2', creator, creator, creation, creation, 'name2', 'hash2', 'type2', 2, 2, 'mime2', false, false, null, null, 'slug2', ['tag2', 'shared'], 'url2', version)
];
const newAsset = new AssetDto('id1', modifier, modifier, modified, modified, 'name3', 'hash3', 'type3', 3, 3, 'mime3', false, true, 0, 0, 'slug3', ['new'], 'url3', version);
let dialogs: IMock<DialogService>;
let assetsService: IMock<AssetsService>;
let assetsState: AssetsState;
@ -44,123 +46,147 @@ describe('AssetsState', () => {
dialogs = Mock.ofType<DialogService>();
assetsService = Mock.ofType<AssetsService>();
assetsState = new AssetsState(appsState.object, assetsService.object, dialogs.object);
});
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(200, oldAssets)));
afterEach(() => {
assetsService.verifyAll();
});
assetsService.setup(x => x.getTags(app))
.returns(() => of({ tag1: 1, shared: 2, tag2: 1 }));
describe('Loading', () => {
it('should load assets', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(200, oldAssets))).verifiable();
assetsState = new AssetsState(appsState.object, assetsService.object, dialogs.object);
assetsState.load().subscribe();
});
assetsService.setup(x => x.getTags(app))
.returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable();
it('should load assets', () => {
assetsState.load().subscribe();
assetsState.load().subscribe();
expect(assetsState.snapshot.assets.values).toEqual(oldAssets);
expect(assetsState.snapshot.assetsPager.numberOfItems).toEqual(200);
expect(assetsState.snapshot.isLoaded).toBeTruthy();
expect(assetsState.snapshot.assets.values).toEqual(oldAssets);
expect(assetsState.snapshot.assetsPager.numberOfItems).toEqual(200);
expect(assetsState.snapshot.isLoaded).toBeTruthy();
assetsService.verify(x => x.getAssets(app, 30, 0, undefined, []), Times.exactly(2));
assetsService.verify(x => x.getTags(app), Times.exactly(2));
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when reload is true', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(200, oldAssets)));
it('should show notification on load when reload is true', () => {
assetsState.load(true).subscribe();
assetsService.setup(x => x.getTags(app))
.returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable();
expect().nothing();
assetsState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
expect().nothing();
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);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
assetsState.add(newAsset);
it('should load with tags when tag toggled', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1']))
.returns(() => of(new AssetsDto(0, [])));
expect(assetsState.snapshot.assets.values).toEqual([newAsset, ...oldAssets]);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(201);
});
assetsState.toggleTag('tag1').subscribe();
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);
expect(assetsState.isTagSelected('tag1')).toBeTruthy();
});
assetsState.update(newAsset);
it('should load without tags when tag toggled', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1']))
.returns(() => of(new AssetsDto(0, [])));
const asset_1 = assetsState.snapshot.assets.at(0);
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(0, [])));
expect(asset_1).toBe(newAsset);
expect(assetsState.snapshot.tags).toEqual({ tag2: 1, shared: 1, new: 1 });
});
assetsState.toggleTag('tag1').subscribe();
assetsState.toggleTag('tag1').subscribe();
it('should remove asset from snapshot when deleted', () => {
assetsService.setup(x => x.deleteAsset(app, oldAssets[0].id, version))
.returns(() => of(versioned(newVersion)));
expect(assetsState.isTagSelected('tag1')).toBeFalsy();
});
assetsState.delete(oldAssets[0]).subscribe();
it('should load with tags when tags selected', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1', 'tag2']))
.returns(() => of(new AssetsDto(0, [])));
expect(assetsState.snapshot.assets.values.length).toBe(1);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(199);
expect(assetsState.snapshot.tags).toEqual({ shared: 1, tag2: 1 });
});
assetsState.selectTags(['tag1', 'tag2']).subscribe();
it('should load next page and prev page when paging', () => {
assetsService.setup(x => x.getAssets(app, 30, 30, undefined, []))
.returns(() => of(new AssetsDto(200, [])));
expect(assetsState.isTagSelected('tag1')).toBeTruthy();
});
assetsState.goNext().subscribe();
assetsState.goPrev().subscribe();
it('should load without tags when tags reset', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(0, [])));
expect().nothing();
assetsState.resetTags().subscribe();
assetsService.verify(x => x.getAssets(app, 30, 30, undefined, []), Times.once());
assetsService.verify(x => x.getAssets(app, 30, 0, undefined, []), Times.exactly(2));
});
expect(assetsState.isTagSelectionEmpty()).toBeTruthy();
});
it('should load next page and prev page when paging', () => {
assetsService.setup(x => x.getAssets(app, 30, 30, undefined, []))
.returns(() => of(new AssetsDto(200, [])));
it('should load with query when searching', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, 'my-query', []))
.returns(() => of(new AssetsDto(0, [])));
assetsState.goNext().subscribe();
assetsState.goPrev().subscribe();
assetsState.search('my-query').subscribe();
expect().nothing();
});
expect(assetsState.snapshot.assetsQuery).toEqual('my-query');
it('should load with query when searching', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, 'my-query', []))
.returns(() => of(new AssetsDto(0, [])));
assetsService.verify(x => x.getAssets(app, 30, 0, 'my-query', []), Times.once());
assetsState.search('my-query').subscribe();
expect(assetsState.snapshot.assetsQuery).toEqual('my-query');
});
});
it('should load with tags when tag toggled', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1']))
.returns(() => of(new AssetsDto(0, [])));
describe('Updates', () => {
beforeEach(() => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(200, oldAssets))).verifiable();
assetsState.toggleTag('tag1').subscribe();
assetsService.setup(x => x.getTags(app))
.returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable();
expect(assetsState.isTagSelected('tag1')).toBeTruthy();
assetsState.load(true).subscribe();
});
assetsService.verify(x => x.getAssets(app, 30, 0, undefined, ['tag1']), Times.once());
});
it('should add asset to snapshot when created', () => {
assetsState.add(newAsset);
it('should load with tags when tags selected', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1', 'tag2']))
.returns(() => of(new AssetsDto(0, [])));
expect(assetsState.snapshot.assets.values).toEqual([newAsset, ...oldAssets]);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(201);
});
assetsState.selectTags(['tag1', 'tag2']).subscribe();
it('should increment tags when asset added', () => {
assetsState.add(newAsset);
assetsState.add(newAsset);
expect(assetsState.isTagSelected('tag1')).toBeTruthy();
expect(assetsState.snapshot.tags).toEqual({ tag1: 1, tag2: 1, shared: 2, new: 2 });
});
assetsService.verify(x => x.getAssets(app, 30, 0, undefined, ['tag1', 'tag2']), Times.once());
});
it('should update properties when updated', () => {
assetsState.update(newAsset);
const asset_1 = assetsState.snapshot.assets.at(0);
it('should load without tags when tags reset', () => {
assetsService.setup(x => x.getAssets(app, 30, 0, undefined, []))
.returns(() => of(new AssetsDto(0, [])));
expect(asset_1).toBe(newAsset);
expect(assetsState.snapshot.tags).toEqual({ tag2: 1, shared: 1, new: 1 });
});
assetsState.resetTags().subscribe();
it('should remove asset from snapshot when deleted', () => {
assetsService.setup(x => x.deleteAsset(app, oldAssets[0].id, version))
.returns(() => of(versioned(newVersion)));
expect(assetsState.isTagSelectionEmpty()).toBeTruthy();
assetsState.delete(oldAssets[0]).subscribe();
assetsService.verify(x => x.getAssets(app, 30, 0, undefined, []), Times.exactly(2));
expect(assetsState.snapshot.assets.values.length).toBe(1);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(199);
expect(assetsState.snapshot.tags).toEqual({ shared: 1, tag2: 1 });
});
});
});

21
src/Squidex/app/shared/state/clients.state.spec.ts

@ -94,8 +94,23 @@ describe('ClientsState', () => {
expect(clientsState.snapshot.version).toEqual(newVersion);
});
it('should update properties when updated', () => {
const request = { name: 'NewName', role: 'NewRole' };
it('should update properties when role updated', () => {
const request = { role: 'Owner' };
clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version))
.returns(() => of(versioned(newVersion))).verifiable();
clientsState.update(oldClients[0], request).subscribe();
const client_1 = clientsState.snapshot.clients.at(0);
expect(client_1.name).toBe('name1');
expect(client_1.role).toBe('Owner');
expect(clientsState.snapshot.version).toEqual(newVersion);
});
it('should update properties when name updated', () => {
const request = { name: 'NewName' };
clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version))
.returns(() => of(versioned(newVersion))).verifiable();
@ -105,7 +120,7 @@ describe('ClientsState', () => {
const client_1 = clientsState.snapshot.clients.at(0);
expect(client_1.name).toBe('NewName');
expect(client_1.role).toBe('NewRole');
expect(client_1.role).toBe('Developer');
expect(clientsState.snapshot.version).toEqual(newVersion);
});

3
src/Squidex/app/shared/state/contents.state.ts

@ -14,7 +14,6 @@ import {
DialogService,
ErrorDto,
ImmutableArray,
notify,
Pager,
shareSubscribed,
State,
@ -312,7 +311,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
public loadVersion(content: ContentDto, version: Version): Observable<Versioned<any>> {
return this.contentsService.getVersionData(this.appName, this.schemaName, content.id, version).pipe(
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
private get appName() {

12
src/Squidex/app/shared/state/contributors.state.ts

@ -132,7 +132,7 @@ export class ContributorsState extends State<Snapshot> {
const contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === id)) {
return contributors.map(x => x.contributor.contributorId === id ? this.createContributor(contributor, x) : x);
return contributors.map(x => x.contributor.contributorId === id ? this.createContributor(contributor) : x);
} else {
return contributors.push(this.createContributor(contributor));
}
@ -161,13 +161,7 @@ export class ContributorsState extends State<Snapshot> {
return this.snapshot.version;
}
private createContributor(contributor: ContributorDto, current?: SnapshotContributor): SnapshotContributor {
if (!contributor) {
return null!;
} else if (current && current.contributor === contributor) {
return current;
} else {
return { contributor, isCurrentUser: contributor.contributorId === this.userId };
}
private createContributor(contributor: ContributorDto): SnapshotContributor {
return { contributor, isCurrentUser: contributor.contributorId === this.userId };
}
}
Loading…
Cancel
Save