From af97cb38310b181e790dafeb7ff728d7f8b2e04a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 31 Mar 2018 00:03:52 +0200 Subject: [PATCH] State updated --- .../pages/users/user-page.component.ts | 13 +- .../administration/state/users.state.ts | 70 ++-- .../pages/schema/schema-page.component.ts | 2 +- .../pages/schemas/schemas-page.component.ts | 8 +- .../features/schemas/state/schemas.state.ts | 344 +++++++++++++----- 5 files changed, 302 insertions(+), 135 deletions(-) diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 7a3b4a4b1..c8f0b3548 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -11,7 +11,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { UserDto } from './../../services/users.service'; -import { UserForm, UsersState, getSelectedUser } from './../../state/users.state'; +import { UserForm, UsersState } from './../../state/users.state'; @Component({ selector: 'sqx-user-page', @@ -25,7 +25,7 @@ export class UserPageComponent implements OnDestroy, OnInit { public userForm: UserForm; public selectedUser = - this.usersState.changes.map(getSelectedUser) + this.usersState.changes.map(x => x.selectedUser) .distinctUntilChanged(); public isCurrentUser = @@ -46,10 +46,11 @@ export class UserPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.selectedUserSubscription = - this.usersState.changes.map(getSelectedUser).subscribe(user => { - this.user = user; - this.userForm.load(user); - }); + this.usersState.changes.map(x => x.selectedUser) + .subscribe(user => { + this.user = user; + this.userForm.load(user); + }); } public save() { diff --git a/src/Squidex/app/features/administration/state/users.state.ts b/src/Squidex/app/features/administration/state/users.state.ts index f63c5e9c4..f852dea75 100644 --- a/src/Squidex/app/features/administration/state/users.state.ts +++ b/src/Squidex/app/features/administration/state/users.state.ts @@ -14,6 +14,7 @@ import '@app/framework/utils/rxjs-extensions'; import { AuthService, DialogService, + ImmutableArray, Pager, Form, State, @@ -23,7 +24,8 @@ import { import { CreateUserDto, UserDto, - UsersService + UsersService, + UpdateUserDto } from './../services/users.service'; export class UserForm extends Form { @@ -69,10 +71,10 @@ export class UserForm extends Form { } interface Snapshot { - users: UserDto[]; + users: ImmutableArray; usersPager: Pager; usersQuery?: string; - selectedUserId?: string | null; + selectedUser?: UserDto; isCurrentUser?: boolean; } @@ -83,15 +85,15 @@ export class UsersState extends State { private readonly dialogs: DialogService, private readonly usersService: UsersService ) { - super({ users: [], usersPager: new Pager(10) }); + super({ users: ImmutableArray.empty(), usersPager: new Pager(10) }); } - public selectUser(selectedUserId: string | null): Observable { - return this.loadUser(selectedUserId) - .do(user => { - const isCurrentUser = selectedUserId === this.authState.user!.id; + public selectUser(id: string | null): Observable { + return this.loadUser(id) + .do(selectedUser => { + const isCurrentUser = id === this.authState.user!.id; - this.next(s => ({...s, selectedUserId, isCurrentUser })); + this.next(s => ({...s, selectedUser, isCurrentUser })); }); } @@ -111,26 +113,26 @@ export class UsersState extends State { public loadUsers(notify = false): Observable { return this.usersService.getUsers(this.snapshot.usersPager.pageSize, this.snapshot.usersPager.skip, this.snapshot.usersQuery) - .catch(error => this.dialogs.notifyError(error)) .do(dtos => { if (notify) { this.dialogs.notifyInfo('Users reloaded.'); } this.next(s => { - const users = dtos.items; + const users = ImmutableArray.of(dtos.items); const usersPager = s.usersPager.setCount(dtos.total); return { ...s, users, usersPager, usersLoading: false }; }); - }); + }) + .catch(error => this.dialogs.notifyError(error)); } public createUser(request: CreateUserDto): Observable { return this.usersService.postUser(request) .do(dto => { this.next(s => { - const users = [dto, ...s.users]; + const users = s.users.pushFront(dto); const usersPager = s.usersPager.incrementCount(); return { ...s, users, usersPager }; @@ -143,36 +145,24 @@ export class UsersState extends State { .do(() => { this.dialogs.notifyInfo('User saved successsfull'); - this.next(s => { - const users = s.users.map(u => u.id === user.id ? u.update(request.email, request.displayName) : u); - - return { ...s, users }; - }); + this.replaceUser(update(user, request)); }); } public lockUser(user: UserDto): Observable { return this.usersService.lockUser(user.id) - .catch(error => this.dialogs.notifyError(error)) .do(() => { - this.next(s => { - const users = s.users.map(u => u.id === user.id ? u.lock() : u); - - return { ...s, users }; - }); - }); + this.replaceUser(setLocked(user, true)); + }) + .catch(error => this.dialogs.notifyError(error)); } public unlockUser(user: UserDto): Observable { return this.usersService.unlockUser(user.id) - .catch(error => this.dialogs.notifyError(error)) .do(() => { - this.next(s => { - const users = s.users.map(u => u.id === user.id ? u.unlock() : u); - - return { ...s, users }; - }); - }); + this.replaceUser(setLocked(user, false)); + }) + .catch(error => this.dialogs.notifyError(error)); } public search(query: string): Observable { @@ -192,6 +182,20 @@ export class UsersState extends State { return this.loadUsers(); } + + private replaceUser(user: UserDto) { + return this.next(s => { + const users = s.users.replaceBy('id', user); + const selectedUser = s.selectedUser && s.selectedUser.id === user.id ? user : s.selectedUser; + + return { ...s, users, selectedUser }; + }); + } } -export const getSelectedUser = (c: Snapshot) => c.users.find(x => x.id === c.selectedUserId); \ No newline at end of file + +const update = (user: UserDto, request: UpdateUserDto) => + new UserDto(user.id, request.email, request.displayName, user.pictureUrl, user.isLocked); + +const setLocked = (user: UserDto, locked: boolean) => + new UserDto(user.id, user.email, user.displayName, user.pictureUrl, locked); \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index df2737b39..2043e724a 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -65,7 +65,7 @@ export class SchemaPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.selectedSchemaSubscription = - this.schemasState.selectedSchema + this.schemasState.changes.map(x => x.selectedSchema) .subscribe(schema => { this.schema = schema!; diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts index 4cdad2be5..b52b363ab 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts @@ -32,14 +32,14 @@ export class SchemasPageComponent implements OnDestroy, OnInit { public schemasFilter = new FormControl(); public schemasFiltered = - this.schemasState.schemasItems + this.schemasState.changes.map(x => x.schemas) .combineLatest(this.schemasFilter.valueChanges.startWith(''), (schemas, query) => { if (query && query.length > 0) { - schemas = schemas.filter(t => t.name.indexOf(query) >= 0); + return schemas.filter(t => t.name.indexOf(query) >= 0); + } else { + return schemas; } - - return schemas.sortByStringAsc(x => x.name); }); public import: any; diff --git a/src/Squidex/app/features/schemas/state/schemas.state.ts b/src/Squidex/app/features/schemas/state/schemas.state.ts index 1f222dade..d1564fb98 100644 --- a/src/Squidex/app/features/schemas/state/schemas.state.ts +++ b/src/Squidex/app/features/schemas/state/schemas.state.ts @@ -7,7 +7,7 @@ import { Injectable } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import '@app/framework/utils/rxjs-extensions'; @@ -25,10 +25,14 @@ import { SchemaDto, SchemaDetailsDto, SchemasService, + State, UpdateFieldDto, UpdateSchemaScriptsDto, UpdateSchemaDto, - ValidatorsEx + ValidatorsEx, + Version, + SchemaPropertiesDto, + FieldPropertiesDto } from '@app/shared'; const FALLBACK_NAME = 'my-schema'; @@ -133,21 +137,13 @@ export class AddFieldForm extends Form { } } -@Injectable() -export class SchemasState { - public schemasItems = new BehaviorSubject>(ImmutableArray.empty()); - - public selectedSchema = new BehaviorSubject(null); - public selectedReferencedSchema = new BehaviorSubject(null); - - public get schemaName() { - return this.selectedSchema.value!.name; - } - - public get referencedSchemaName() { - return this.selectedReferencedSchema.value!.name; - } +interface Snapshot { + schemas: ImmutableArray; + selectedSchema?: SchemaDetailsDto | null; +} +@Injectable() +export class SchemasState extends State { private get appName() { return this.appsState.appName; } @@ -157,31 +153,29 @@ export class SchemasState { } constructor( - private readonly schemasService: SchemasService, - private readonly dialogs: DialogService, + private readonly appsState: AppsState, private readonly authState: AuthService, - private readonly appsState: AppsState + private readonly dialogs: DialogService, + private readonly schemasService: SchemasService ) { + super({ schemas: ImmutableArray.of() }); } - public selectSchema(id: string | null): Observable { - return this.loadSchema(id) + public selectSchema(selectedSchemaId: string | null): Observable { + return this.loadSchema(selectedSchemaId) .do(schema => { - this.selectedSchema.next(schema); - }); - } + this.next(s => { + const schemas = schema ? s.schemas.replaceBy('id', schema) : s.schemas; - public selectReferencedSchema(id: string | null): Observable { - return this.loadSchema(id) - .do(schema => { - this.selectedReferencedSchema.next(schema); + return { ...s, selectedSchemaId, schemas }; + }); }); } private loadSchema(id: string | null) { return !id ? Observable.of(null) : - Observable.of(this.schemasItems.value.find(x => x.id === id && x instanceof SchemaDetailsDto)) + Observable.of(this.snapshot.schemas.find(x => x.id === id && x instanceof SchemaDetailsDto)) .switchMap(schema => { if (!schema) { return this.schemasService.getSchema(this.appName, id).catch(() => Observable.of(null)); @@ -193,144 +187,312 @@ export class SchemasState { public load(): Observable { return this.schemasService.getSchemas(this.appName) - .catch(error => this.dialogs.notifyError(error)) .do(dtos => { - this.schemasItems.nextBy(v => ImmutableArray.of(dtos)); - }); + return this.next(s => { + const schemas = ImmutableArray.of(dtos).sortByStringAsc(x => x.displayName); + + return { ...s, schemas }; + }); + }) + .catch(error => this.dialogs.notifyError(error)); + } + + public delete(schema: SchemaDto): Observable { + return this.schemasService.deleteSchema(this.appName, schema.name, schema.version) + .do(dto => { + return this.next(s => { + const schemas = s.schemas.filter(x => x.id !== schema.id); + + return { ...s, schemas }; + }); + }) + .catch(error => this.dialogs.notifyError(error)); } public create(request: CreateSchemaDto) { return this.schemasService.postSchema(this.appName, request, this.user, DateTime.now()) .do(dto => { - this.schemasItems.nextBy(v => v.push(dto)); + return this.next(s => { + const schemas = s.schemas.push(dto).sortByStringAsc(x => x.displayName); + + return { ...s, schemas }; + }); }); } public addField(schema: SchemaDetailsDto, request: AddFieldDto): Observable { return this.schemasService.postField(this.appName, schema.name, request, schema.version) .do(dto => { - this.replaceSchema(schema.addField(dto.payload, this.user, dto.version)); + this.replaceSchema(addField(schema, dto.payload, this.user, dto.version)); }).map(d => d.payload); } public publish(schema: SchemaDto): Observable { return this.schemasService.publishSchema(this.appName, schema.name, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.publish(this.user, dto.version)); - }); + this.replaceSchema(setPublished(schema, true, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public unpublish(schema: SchemaDto): Observable { return this.schemasService.unpublishSchema(this.appName, schema.name, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.unpublish(this.user, dto.version)); - }); + this.replaceSchema(setPublished(schema, true, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public enableField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.enableField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.enable(), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, setDisabled(field, false), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public disableField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.disableField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.disable(), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, setDisabled(field, true), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public lockField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.lockField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.lock(), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, setLocked(field, true), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public showField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.showField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.show(), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, setHidden(field, false), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public hideField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.hideField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.hide(), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, setHidden(field, true), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public deleteField(schema: SchemaDetailsDto, field: FieldDto): Observable { return this.schemasService.deleteField(this.appName, schema.name, field.fieldId, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.removeField(field, this.user, dto.version)); - }); + this.replaceSchema(removeField(schema, field, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public sortFields(schema: SchemaDetailsDto, fields: FieldDto[]): Observable { return this.schemasService.putFieldOrdering(this.appName, schema.name, fields.map(t => t.fieldId), schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.replaceFields(fields, this.user, dto.version)); - }); + this.replaceSchema(replaceFields(schema, fields, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public updateField(schema: SchemaDetailsDto, field: FieldDto, request: UpdateFieldDto): Observable { return this.schemasService.putField(this.appName, schema.name, field.fieldId, request, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.updateField(field.update(request.properties), this.user, dto.version)); - }); + this.replaceSchema(updateField(schema, update(field, request.properties), this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto): Observable { return this.schemasService.putSchemaScripts(this.appName, schema.name, request, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.configureScripts(request, this.user, dto.version)); - }); + this.replaceSchema(configureScripts(schema, request, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } public update(schema: SchemaDetailsDto, request: UpdateSchemaDto): Observable { return this.schemasService.putSchema(this.appName, schema.name, request, schema.version) - .catch(error => this.dialogs.notifyError(error)) .do(dto => { - this.replaceSchema(schema.update(request, this.user, dto.version)); - }); - } - - public delete(schema: SchemaDto): Observable { - return this.schemasService.deleteSchema(this.appName, schema.name, schema.version) - .catch(error => this.dialogs.notifyError(error)) - .do(dto => { - this.schemasItems.nextBy(v => v.filter(s => s.id !== schema.id)); - }); + this.replaceSchema(updateProperties(schema, request, this.user, dto.version)); + }) + .catch(error => this.dialogs.notifyError(error)); } private replaceSchema(schema: SchemaDto) { - this.schemasItems.nextBy(v => v.replaceBy('id', schema)); - - if (schema instanceof SchemaDetailsDto) { - this.selectedSchema.nextBy(v => v !== null && v.id === schema.id ? schema : v); - this.selectedReferencedSchema.nextBy(v => v !== null && v.id === schema.id ? schema : v); - } - } + return this.next(s => { + const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName); + const selectedSchema = s.selectedSchema && s.selectedSchema.id === schema.id ? schema : s.selectedSchema; - public trackBySchema(index: number, schema: SchemaDto): any { - return schema.id; + return { ...s, schemas, selectedSchema }; + }); } +} - public trackByField(index: number, field: FieldDto): any { - return field.fieldId; - } -} \ No newline at end of file +const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, user: string, version: Version, now?: DateTime) => { + if (schema instanceof SchemaDetailsDto) { + return new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + true, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields, + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + } else { + return new SchemaDto( + schema.id, + schema.name, + schema.properties, + true, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version); + } +}; + +const configureScripts = (schema: SchemaDetailsDto, scripts: UpdateSchemaScriptsDto, user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields, + scripts.scriptQuery, + scripts.scriptCreate, + scripts.scriptUpdate, + scripts.scriptDelete, + scripts.scriptChange); + +const updateProperties = (schema: SchemaDetailsDto, properties: SchemaPropertiesDto, user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields, + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + +const addField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + [...schema.fields, field], + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + +const updateField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields.map(f => f.fieldId === field.fieldId ? field : f), + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + +const replaceFields = (schema: SchemaDetailsDto, fields: FieldDto[], user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + fields, + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + +const removeField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) => + new SchemaDetailsDto( + schema.id, + schema.name, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields.filter(f => f.fieldId !== field.fieldId), + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + +const setLocked = (field: FieldDto, isLocked: boolean) => + new FieldDto( + field.fieldId, + field.name, + isLocked, + field.isHidden, + field.isDisabled, + field.partitioning, + field.properties); + +const setHidden = (field: FieldDto, isHidden: boolean) => + new FieldDto( + field.fieldId, + field.name, + field.isLocked, + isHidden, + field.isDisabled, + field.partitioning, + field.properties); + +const setDisabled = (field: FieldDto, isDisabled: boolean) => + new FieldDto( + field.fieldId, + field.name, + field.isLocked, + field.isDisabled, + isDisabled, + field.partitioning, + field.properties); + +const update = (field: FieldDto, properties: FieldPropertiesDto) => + new FieldDto( + field.fieldId, + field.name, + field.isLocked, + field.isHidden, + field.isDisabled, + field.partitioning, + properties); \ No newline at end of file