Browse Source

Patterns state.

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
0b5307f31e
  1. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  2. 12
      src/Squidex/app/features/settings/pages/patterns/pattern.component.html
  3. 83
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  4. 17
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html
  5. 63
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
  6. 1
      src/Squidex/app/shared/internal.ts
  7. 10
      src/Squidex/app/shared/module.ts
  8. 49
      src/Squidex/app/shared/services/app-patterns.service.spec.ts
  9. 28
      src/Squidex/app/shared/services/app-patterns.service.ts
  10. 99
      src/Squidex/app/shared/state/patterns.state.spec.ts
  11. 131
      src/Squidex/app/shared/state/patterns.state.ts

2
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -26,7 +26,7 @@ export class ClientsPageComponent implements OnInit {
constructor(
public readonly appsState: AppsState,
private readonly clientsState: ClientsState,
public readonly clientsState: ClientsState,
private readonly formBuilder: FormBuilder
) {
}

12
src/Squidex/app/features/settings/pages/patterns/pattern.component.html

@ -1,31 +1,31 @@
<div class="table-items-row" [class.table-items-footer]="!pattern">
<form [formGroup]="editForm" (ngSubmit)="save()" class="row no-gutters">
<form [formGroup]="editForm.form" (ngSubmit)="save()" class="row no-gutters">
<div class="col col-name">
<sqx-control-errors for="name" [submitted]="editFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="editForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="pattern-name" maxlength="100" formControlName="name" placeholder="Name" />
</div>
<div class="col pl-2 pr-2">
<sqx-control-errors for="pattern" [submitted]="editFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="pattern" [submitted]="editForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="pattern-pattern" maxlength="1000" formControlName="pattern" placeholder="Pattern" />
</div>
<div class="col col-message">
<sqx-control-errors for="message" [submitted]="editFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="message" [submitted]="editForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="pattern-message" maxlength="1000" formControlName="message" placeholder="Message" />
</div>
<div class="col col-auto pl-2 col-options" *ngIf="pattern">
<button type="submit" class="btn btn-primary" [class.disabled]="!editForm.dirty">
<button type="submit" class="btn btn-primary" [class.disabled]="!editForm.form.dirty">
<i class="icon-checkmark"></i>
</button>
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="removing.emit(pattern)"
(sqxConfirmClick)="delete()"
confirmTitle="Remove pattern"
confirmText="Do you really want to remove this pattern?">
<i class="icon-bin2"></i>

83
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -5,13 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
AppPatternDto,
UpdatePatternDto,
ValidatorsEx
PatternsState,
EditPatternForm
} from '@app/shared';
@Component({
@ -20,72 +20,47 @@ import {
templateUrl: './pattern.component.html'
})
export class PatternComponent implements OnInit {
@Input()
public isNew = false;
@Input()
public pattern: AppPatternDto;
@Output()
public removing = new EventEmitter<any>();
@Output()
public updating = new EventEmitter<UpdatePatternDto>();
public editFormSubmitted = false;
public editForm =
this.formBuilder.group({
name: ['',
[
Validators.required,
Validators.maxLength(100),
ValidatorsEx.pattern('[A-z0-9]+[A-z0-9\- ]*[A-z0-9]', 'Name can only contain letters, numbers, dashes and spaces.')
]
],
pattern: ['',
[
Validators.required
]
],
message: [
'',
[
Validators.maxLength(1000)
]
]
});
public editForm = new EditPatternForm(this.formBuilder);
constructor(
private readonly patternsState: PatternsState,
private readonly formBuilder: FormBuilder
) {
}
public ngOnInit() {
const pattern = this.pattern;
if (pattern) {
this.editForm.setValue({ name: pattern.name, pattern: pattern.pattern, message: pattern.message || '' });
}
this.editForm.load(this.pattern);
}
public cancel() {
this.editFormSubmitted = false;
this.editForm.reset();
this.editForm.submitCompleted(this.pattern);
}
public save() {
this.editFormSubmitted = true;
if (this.editForm.valid) {
const requestDto = new UpdatePatternDto(
this.editForm.controls['name'].value,
this.editForm.controls['pattern'].value,
this.editForm.controls['message'].value);
this.updating.emit(requestDto);
public delete() {
this.patternsState.delete(this.pattern).onErrorResumeNext().subscribe();
}
if (!this.pattern) {
this.cancel();
public save() {
const value = this.editForm.submit();
if (value) {
if (this.pattern) {
this.patternsState.update(this.pattern, value)
.subscribe(() => {
this.editForm.submitCompleted();
}, error => {
this.editForm.submitFailed();
});
} else {
this.patternsState.create(value)
.subscribe(() => {
this.editForm.submitCompleted({});
}, error => {
this.editForm.submitFailed();
});
}
}
}

17
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html

@ -2,20 +2,17 @@
<sqx-panel desiredWidth="60rem">
<ng-container title>
Contributors
Patterns
</ng-container>
<ng-container content>
<ng-container *ngIf="appPatterns">
<div *ngFor="let pattern of appPatterns.patterns">
<sqx-pattern [pattern]="pattern"
(removing)="removePattern(pattern)"
(updating)="updatePattern(pattern, $event)">
</sqx-pattern>
<ng-container *ngIf="patternsState.patterns | async; let patterns">
<div class="table-items-row table-items-row-empty" *ngIf="patterns.length === 0">
No pattern created yet.
</div>
<sqx-pattern isNew="true"
(updating)="addPattern($event)">
</sqx-pattern>
<sqx-pattern *ngFor="let pattern of patterns" [pattern]="pattern"></sqx-pattern>
<sqx-pattern></sqx-pattern>
</ng-container>
</ng-container>
</sqx-panel>

63
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts

@ -7,14 +7,7 @@
import { Component, OnInit } from '@angular/core';
import {
AppPatternDto,
AppPatternsDto,
AppPatternsService,
AppsState,
DialogService,
UpdatePatternDto
} from '@app/shared';
import { AppsState, PatternsState } from '@app/shared';
@Component({
selector: 'sqx-patterns-page',
@ -22,63 +15,13 @@ import {
templateUrl: './patterns-page.component.html'
})
export class PatternsPageComponent implements OnInit {
public appPatterns: AppPatternsDto;
constructor(
public readonly appsState: AppsState,
private readonly dialogs: DialogService,
private readonly appPatternsService: AppPatternsService
public readonly patternsState: PatternsState
) {
}
public ngOnInit() {
this.load();
}
public load() {
this.appPatternsService.getPatterns(this.appsState.appName)
.subscribe(dtos => {
this.updatePatterns(dtos);
}, error => {
this.dialogs.notifyError(error);
});
}
public addPattern(pattern: AppPatternDto) {
const requestDto = new UpdatePatternDto(pattern.name, pattern.pattern, pattern.message);
this.appPatternsService.postPattern(this.appsState.appName, requestDto, this.appPatterns.version)
.subscribe(dto => {
this.updatePatterns(this.appPatterns.addPattern(dto.payload, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
}
public updatePattern(pattern: AppPatternDto, update: UpdatePatternDto) {
this.appPatternsService.putPattern(this.appsState.appName, pattern.patternId, update, this.appPatterns.version)
.subscribe(dto => {
this.updatePatterns(this.appPatterns.updatePattern(pattern.update(update), dto.version));
}, error => {
this.dialogs.notifyError(error);
});
}
public removePattern(pattern: AppPatternDto) {
this.appPatternsService.deletePattern(this.appsState.appName, pattern.patternId, this.appPatterns.version)
.subscribe(dto => {
this.updatePatterns(this.appPatterns.deletePattern(pattern, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
}
private updatePatterns(patterns: AppPatternsDto) {
this.appPatterns =
new AppPatternsDto(
patterns.patterns.sort((a, b) => {
return a.name.localeCompare(b.name);
}),
patterns.version);
this.patternsState.load().onErrorResumeNext().subscribe();
}
}

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

@ -41,6 +41,7 @@ export * from './services/users.service';
export * from './state/apps.state';
export * from './state/assets.state';
export * from './state/clients.state';
export * from './state/patterns.state';
export * from './state/schemas.state';
export * from './utils/messages';

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

@ -47,13 +47,16 @@ import {
MarkdownEditorComponent,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PatternsState,
PlansService,
ResolveAppLanguagesGuard,
ResolveContentGuard,
RichEditorComponent,
RulesService,
SchemaMustExistGuard,
SchemaMustExistPublishedGuard,
SchemasService,
RulesService,
SchemasState,
UIService,
UnsetAppGuard,
UsagesService,
@ -64,9 +67,7 @@ import {
UserPicturePipe,
UserPictureRefPipe,
UsersProviderService,
UsersService,
RichEditorComponent,
SchemasState
UsersService
} from './declarations';
@NgModule({
@ -147,6 +148,7 @@ export class SqxSharedModule {
LoadAppsGuard,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PatternsState,
PlansService,
ResolveAppLanguagesGuard,
ResolveContentGuard,

49
src/Squidex/app/shared/services/app-patterns.service.spec.ts

@ -13,53 +13,10 @@ import {
AppPatternDto,
AppPatternsDto,
AppPatternsService,
UpdatePatternDto,
EditAppPatternDto,
Version
} from './../';
describe('ApppatternsDto', () => {
const pattern1 = new AppPatternDto('1', 'Any', '.*', 'Message1');
const pattern2 = new AppPatternDto('2', 'Number', '[0-9]', 'Message2');
const pattern2_new = new AppPatternDto('2', 'Numbers', '[0-9]*', 'Message2_1');
const version = new Version('1');
const newVersion = new Version('2');
it('should update patterns when adding pattern', () => {
const patterns_1 = new AppPatternsDto([pattern1], version);
const patterns_2 = patterns_1.addPattern(pattern2, newVersion);
expect(patterns_2.patterns).toEqual([pattern1, pattern2]);
expect(patterns_2.version).toEqual(newVersion);
});
it('should update patterns when removing pattern', () => {
const patterns_1 = new AppPatternsDto([pattern1, pattern2], version);
const patterns_2 = patterns_1.deletePattern(pattern1, newVersion);
expect(patterns_2.patterns).toEqual([pattern2]);
expect(patterns_2.version).toEqual(newVersion);
});
it('should update patterns when updating pattern', () => {
const patterns_1 = new AppPatternsDto([pattern1, pattern2], version);
const patterns_2 = patterns_1.updatePattern(pattern2_new, newVersion);
expect(patterns_2.patterns).toEqual([pattern1, pattern2_new]);
expect(patterns_2.version).toEqual(newVersion);
});
});
describe('AppPatternDto', () => {
it('should update properties when updating', () => {
const pattern_1 = new AppPatternDto('1', 'Number', '[0-9]', 'Message1');
const pattern_2 = pattern_1.update(new UpdatePatternDto('Numbers', '[0-9]*', 'Message2'));
expect(pattern_2.name).toBe('Numbers');
expect(pattern_2.pattern).toBe('[0-9]*');
expect(pattern_2.message).toBe('Message2');
});
});
describe('AppPatternsService', () => {
const version = new Version('1');
@ -121,7 +78,7 @@ describe('AppPatternsService', () => {
it('should make post request to add pattern',
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => {
const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1');
const dto = new EditAppPatternDto('Number', '[0-9]', 'Message1');
let pattern: AppPatternDto | null = null;
@ -147,7 +104,7 @@ describe('AppPatternsService', () => {
it('should make put request to update pattern',
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => {
const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1');
const dto = new EditAppPatternDto('Number', '[0-9]', 'Message1');
patternService.putPattern('my-app', '1', dto, version).subscribe();

28
src/Squidex/app/shared/services/app-patterns.service.ts

@ -24,39 +24,19 @@ export class AppPatternsDto {
public readonly version: Version
) {
}
public addPattern(pattern: AppPatternDto, version: Version) {
return new AppPatternsDto([...this.patterns, pattern], version);
}
public updatePattern(pattern: AppPatternDto, version: Version) {
return new AppPatternsDto(this.patterns.map(p => p.patternId === pattern.patternId ? pattern : p), version);
}
public deletePattern(pattern: AppPatternDto, version: Version) {
return new AppPatternsDto(this.patterns.filter(c => c.patternId !== pattern.patternId), version);
}
}
export class AppPatternDto {
constructor(
public readonly patternId: string,
public readonly id: string,
public readonly name: string,
public readonly pattern: string,
public readonly message: string
) {
}
public update(update: UpdatePatternDto) {
return new AppPatternDto(
this.patternId,
update.name,
update.pattern,
update.message);
}
}
export class UpdatePatternDto {
export class EditAppPatternDto {
constructor(
public readonly name: string,
public readonly pattern: string,
@ -96,7 +76,7 @@ export class AppPatternsService {
.pretifyError('Failed to add pattern. Please reload.');
}
public postPattern(appName: string, dto: UpdatePatternDto, version: Version): Observable<Versioned<AppPatternDto>> {
public postPattern(appName: string, dto: EditAppPatternDto, version: Version): Observable<Versioned<AppPatternDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.postVersioned<any>(this.http, url, dto, version)
@ -114,7 +94,7 @@ export class AppPatternsService {
.pretifyError('Failed to add pattern. Please reload.');
}
public putPattern(appName: string, id: string, dto: UpdatePatternDto, version: Version): Observable<Versioned<any>> {
public putPattern(appName: string, id: string, dto: EditAppPatternDto, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`);
return HTTP.putVersioned(this.http, url, dto, version)

99
src/Squidex/app/shared/state/patterns.state.spec.ts

@ -0,0 +1,99 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Observable } from 'rxjs';
import { IMock, Mock } from 'typemoq';
import {
AppsState,
AppPatternDto,
AppPatternsDto,
AppPatternsService,
PatternsState,
DialogService,
EditAppPatternDto,
Version,
Versioned
} from '@app/shared';
describe('PatternsState', () => {
const app = 'my-app';
const version = new Version('1');
const newVersion = new Version('2');
const oldPatterns = [
new AppPatternDto('id1', 'name1', 'pattern1', ''),
new AppPatternDto('id2', 'name2', 'pattern2', '')
];
let dialogs: IMock<DialogService>;
let appsState: IMock<AppsState>;
let patternsService: IMock<AppPatternsService>;
let patternsState: PatternsState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
appsState = Mock.ofType<AppsState>();
appsState.setup(x => x.appName)
.returns(() => app);
patternsService = Mock.ofType<AppPatternsService>();
patternsService.setup(x => x.getPatterns(app))
.returns(() => Observable.of(new AppPatternsDto(oldPatterns, version)));
patternsState = new PatternsState(patternsService.object, appsState.object, dialogs.object);
patternsState.load().subscribe();
});
it('should load patterns', () => {
expect(patternsState.snapshot.patterns.values).toEqual(oldPatterns);
expect(patternsState.snapshot.version).toEqual(version);
});
it('should add pattern to snapshot', () => {
const newPattern = new AppPatternDto('id3', 'name3', 'pattern3', '');
const request = new EditAppPatternDto('name3', 'pattern3', '');
patternsService.setup(x => x.postPattern(app, request, version))
.returns(() => Observable.of(new Versioned<AppPatternDto>(newVersion, newPattern)));
patternsState.create(request).subscribe();
expect(patternsState.snapshot.patterns.values).toEqual([...oldPatterns, newPattern]);
expect(patternsState.snapshot.version).toEqual(newVersion);
});
it('should update pattern in snapshot', () => {
const request = new EditAppPatternDto('a_name2', 'a_pattern2', 'a_message2');
patternsService.setup(x => x.putPattern(app, oldPatterns[1].id, request, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {})));
patternsState.update(oldPatterns[1], request).subscribe();
const pattern_1 = patternsState.snapshot.patterns.at(0);
expect(pattern_1.name).toBe('a_name2');
expect(pattern_1.pattern).toBe('a_pattern2');
expect(pattern_1.message).toBe('a_message2');
expect(patternsState.snapshot.version).toEqual(newVersion);
});
it('should remove pattern from snapshot', () => {
patternsService.setup(x => x.deletePattern(app, oldPatterns[0].id, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {})));
patternsState.delete(oldPatterns[0]).subscribe();
expect(patternsState.snapshot.patterns.values).toEqual([oldPatterns[1]]);
expect(patternsState.snapshot.version).toEqual(newVersion);
});
});

131
src/Squidex/app/shared/state/patterns.state.ts

@ -0,0 +1,131 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
import {
DialogService,
ImmutableArray,
Form,
State,
ValidatorsEx,
Version
} from '@app/framework';
import { AppsState } from './apps.state';
import {
AppPatternDto,
AppPatternsService,
EditAppPatternDto
} from './../services/app-patterns.service';
export class EditPatternForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
name: ['',
[
Validators.required,
Validators.maxLength(100),
ValidatorsEx.pattern('[A-z0-9]+[A-z0-9\- ]*[A-z0-9]', 'Name can only contain letters, numbers, dashes and spaces.')
]
],
pattern: ['',
[
Validators.required
]
],
message: ['',
[
Validators.maxLength(1000)
]
]
}));
}
}
interface Snapshot {
patterns: ImmutableArray<AppPatternDto>;
isLoaded: boolean;
version: Version;
}
@Injectable()
export class PatternsState extends State<Snapshot> {
public patterns =
this.changes.map(x => x.patterns);
public isLoaded =
this.changes.map(x => x.isLoaded);
constructor(
private readonly appPatternsService: AppPatternsService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ patterns: ImmutableArray.empty(), version: new Version(''), isLoaded: false });
}
public load(): Observable<any> {
return this.appPatternsService.getPatterns(this.appName)
.do(dtos => {
this.next(s => {
return { patterns: ImmutableArray.of(dtos.patterns), isLoaded: true, version: dtos.version };
});
})
.notify(this.dialogs);
}
public create(request: EditAppPatternDto): Observable<any> {
return this.appPatternsService.postPattern(this.appName, request, this.snapshot.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.push(dto.payload).sortByStringAsc(x => x.name);
return { ...s, patterns, version: dto.version };
});
})
.notify(this.dialogs);
}
public update(pattern: AppPatternDto, request: EditAppPatternDto): Observable<any> {
return this.appPatternsService.putPattern(this.appName, pattern.id, request, this.snapshot.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.replaceBy('id', update(pattern, request)).sortByStringAsc(x => x.name);
return { ...s, patterns, version: dto.version };
});
})
.notify(this.dialogs);
}
public delete(pattern: AppPatternDto): Observable<any> {
return this.appPatternsService.deletePattern(this.appName, pattern.id, this.snapshot.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.filter(c => c.id !== pattern.id);
return { ...s, patterns, version: dto.version };
});
})
.notify(this.dialogs);
}
private get appName() {
return this.appsState.appName;
}
}
const update = (pattern: AppPatternDto, request: EditAppPatternDto) =>
new AppPatternDto(pattern.id, request.name, request.pattern, request.message);
Loading…
Cancel
Save