-
-
+
+
+ No pattern created yet.
-
-
+
+
+
diff --git a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
index 022e3a52f..37a335605 100644
--- a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
+++ b/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();
}
}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/internal.ts b/src/Squidex/app/shared/internal.ts
index f33f3ebbc..075fc4ff7 100644
--- a/src/Squidex/app/shared/internal.ts
+++ b/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';
diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts
index 0e6906307..402acb6d4 100644
--- a/src/Squidex/app/shared/module.ts
+++ b/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,
diff --git a/src/Squidex/app/shared/services/app-patterns.service.spec.ts b/src/Squidex/app/shared/services/app-patterns.service.spec.ts
index cc685eac5..8a376fe17 100644
--- a/src/Squidex/app/shared/services/app-patterns.service.spec.ts
+++ b/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();
diff --git a/src/Squidex/app/shared/services/app-patterns.service.ts b/src/Squidex/app/shared/services/app-patterns.service.ts
index a26f2730f..4eb5918c2 100644
--- a/src/Squidex/app/shared/services/app-patterns.service.ts
+++ b/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
> {
+ public postPattern(appName: string, dto: EditAppPatternDto, version: Version): Observable> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.postVersioned(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> {
+ public putPattern(appName: string, id: string, dto: EditAppPatternDto, version: Version): Observable> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`);
return HTTP.putVersioned(this.http, url, dto, version)
diff --git a/src/Squidex/app/shared/state/patterns.state.spec.ts b/src/Squidex/app/shared/state/patterns.state.spec.ts
new file mode 100644
index 000000000..ee34fc337
--- /dev/null
+++ b/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;
+ let appsState: IMock;
+ let patternsService: IMock;
+ let patternsState: PatternsState;
+
+ beforeEach(() => {
+ dialogs = Mock.ofType();
+
+ appsState = Mock.ofType();
+
+ appsState.setup(x => x.appName)
+ .returns(() => app);
+
+ patternsService = Mock.ofType();
+
+ 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(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(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(newVersion, {})));
+
+ patternsState.delete(oldPatterns[0]).subscribe();
+
+ expect(patternsState.snapshot.patterns.values).toEqual([oldPatterns[1]]);
+ expect(patternsState.snapshot.version).toEqual(newVersion);
+ });
+});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/state/patterns.state.ts b/src/Squidex/app/shared/state/patterns.state.ts
new file mode 100644
index 000000000..e1c3a3adf
--- /dev/null
+++ b/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 {
+ 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;
+
+ isLoaded: boolean;
+
+ version: Version;
+}
+
+@Injectable()
+export class PatternsState extends State {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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);
\ No newline at end of file