Browse Source

Introduce isLoaded

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
c05a1e4dfd
  1. 1
      src/Squidex/app/features/administration/state/event-consumers.state.spec.ts
  2. 11
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  3. 1
      src/Squidex/app/features/administration/state/users.state.spec.ts
  4. 8
      src/Squidex/app/features/administration/state/users.state.ts
  5. 2
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  6. 11
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  7. 5
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  8. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  9. 11
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  10. 3
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  11. 128
      src/Squidex/app/features/settings/pages/backups/backups-page.component.html
  12. 8
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  13. 4
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  14. 110
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  15. 4
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  16. 4
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  17. 8
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html
  18. 4
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
  19. 1
      src/Squidex/app/shared/state/assets.state.spec.ts
  20. 28
      src/Squidex/app/shared/state/assets.state.ts
  21. 3
      src/Squidex/app/shared/state/backups.state.spec.ts
  22. 14
      src/Squidex/app/shared/state/backups.state.ts
  23. 19
      src/Squidex/app/shared/state/clients.state.spec.ts
  24. 29
      src/Squidex/app/shared/state/clients.state.ts
  25. 19
      src/Squidex/app/shared/state/contributors.state.spec.ts
  26. 32
      src/Squidex/app/shared/state/contributors.state.ts
  27. 10
      src/Squidex/app/shared/state/patterns.state.spec.ts
  28. 16
      src/Squidex/app/shared/state/patterns.state.ts
  29. 30
      src/Squidex/app/shared/state/plans.state.ts
  30. 1
      src/Squidex/app/shared/state/rules.state.spec.ts
  31. 35
      src/Squidex/app/shared/state/rules.state.ts
  32. 1
      src/Squidex/app/shared/state/schemas.state.spec.ts
  33. 15
      src/Squidex/app/shared/state/schemas.state.ts

1
src/Squidex/app/features/administration/state/event-consumers.state.spec.ts

@ -37,6 +37,7 @@ describe('EventConsumersState', () => {
it('should load event consumers', () => {
expect(eventConsumersState.snapshot.eventConsumers.values).toEqual(oldConsumers);
expect(eventConsumersState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});

11
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -20,12 +20,19 @@ import { EventConsumerDto, EventConsumersService } from './../services/event-con
interface Snapshot {
eventConsumers: ImmutableArray<EventConsumerDto>;
isLoaded?: false;
}
@Injectable()
export class EventConsumersState extends State<Snapshot> {
public eventConsumers =
this.changes.map(x => x.eventConsumers);
this.changes.map(x => x.eventConsumers)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly dialogs: DialogService,
@ -44,7 +51,7 @@ export class EventConsumersState extends State<Snapshot> {
this.next(s => {
const eventConsumers = ImmutableArray.of(dtos);
return { ...s, eventConsumers };
return { ...s, eventConsumers, isLoaded: true };
});
})
.catch(error => {

1
src/Squidex/app/features/administration/state/users.state.spec.ts

@ -53,6 +53,7 @@ describe('UsersState', () => {
it('should load users', () => {
expect(usersState.snapshot.users.values).toEqual(oldUsers.map(x => u(x)));
expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200);
expect(usersState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});

8
src/Squidex/app/features/administration/state/users.state.ts

@ -81,6 +81,8 @@ interface Snapshot {
usersPager: Pager;
usersQuery?: string;
isLoaded?: boolean;
selectedUser?: SnapshotUser;
}
@ -98,6 +100,10 @@ export class UsersState extends State<Snapshot> {
this.changes.map(x => x.selectedUser)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly authState: AuthService,
private readonly dialogs: DialogService,
@ -144,7 +150,7 @@ export class UsersState extends State<Snapshot> {
selectedUser = users.find(x => x.user.id === selectedUser!.user.id) || selectedUser;
}
return { ...s, users, usersPager, selectedUser };
return { ...s, users, usersPager, selectedUser, isLoaded: true };
});
})
.notify(this.dialogs);

2
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -44,7 +44,7 @@
</ng-container>
<ng-container *ngSwitchCase="'ContentChanged'">
<sqx-content-changed-trigger
[schemas]="schemas.values"
[schemas]="schemas"
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">

11
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts

@ -30,7 +30,7 @@ export interface TriggerSchemaForm {
})
export class ContentChangedTriggerComponent implements OnInit {
@Input()
public schemas: SchemaDto[];
public schemas: ImmutableArray<SchemaDto>;
@Input()
public trigger: any;
@ -79,12 +79,11 @@ export class ContentChangedTriggerComponent implements OnInit {
}).filter(s => s !== null).map(s => s!)).sortByStringAsc(s => s.schema.name);
this.schemasToAdd =
ImmutableArray.of(
this.schemas.filter(schema =>
!triggerSchemas.find(s => s.schemaId === schema.id)))
!triggerSchemas.find(s => s.schemaId === schema.id))
.sortByStringAsc(x => x.name);
this.schemaToAdd = this.schemasToAdd.values[0];
this.schemaToAdd = this.schemasToAdd.at(0);
}
public removeSchema(schemaForm: TriggerSchemaForm) {
@ -93,7 +92,7 @@ export class ContentChangedTriggerComponent implements OnInit {
this.updateValue();
this.schemasToAdd = this.schemasToAdd.push(schemaForm.schema).sortByStringAsc(x => x.name);
this.schemaToAdd = this.schemasToAdd.values[0];
this.schemaToAdd = this.schemasToAdd.at(0);
}
public addSchema() {
@ -111,7 +110,7 @@ export class ContentChangedTriggerComponent implements OnInit {
this.updateValue();
this.schemasToAdd = this.schemasToAdd.remove(this.schemaToAdd).sortByStringAsc(x => x.name);
this.schemaToAdd = this.schemasToAdd.values[0];
this.schemaToAdd = this.schemasToAdd.at(0);
}
public toggle(schemaForm: TriggerSchemaForm, property: string) {

5
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -14,6 +14,7 @@ import {
EditFieldForm,
fadeAnimation,
FieldDto,
ImmutableArray,
ModalView,
SchemaDetailsDto,
SchemasState,
@ -33,10 +34,10 @@ export class FieldComponent implements OnInit {
public field: FieldDto;
@Input()
public patterns: AppPatternDto;
public schema: SchemaDetailsDto;
@Input()
public schema: SchemaDetailsDto;
public patterns: ImmutableArray<AppPatternDto>;
public dropdown = new ModalView(false, true);

2
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -61,7 +61,7 @@
<div dnd-sortable-container [sortableData]="schema.fields">
<div *ngFor="let field of schema.fields; let i = index; trackBy: trackByField" dnd-sortable [sortableIndex]="i" (sqxSorted)="sortFields($event)">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns"></sqx-field>
<sqx-field [field]="field" [schema]="schema" [patterns]="patternsState.patterns | async"></sqx-field>
</div>
<button class="btn btn-success field-button" (click)="addFieldDialog.show()">

11
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -11,13 +11,13 @@ import { Subscription } from 'rxjs';
import {
AppPatternDto,
AppPatternsService,
AppsState,
fadeAnimation,
FieldDto,
fieldTypes,
MessageBus,
ModalView,
PatternsState,
SchemaDetailsDto,
SchemasState
} from '@app/shared';
@ -39,8 +39,6 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
public fieldTypes = fieldTypes;
public patterns: AppPatternDto[] = [];
public schemaExport: any;
public schema: SchemaDetailsDto;
@ -56,7 +54,7 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
constructor(
public readonly appsState: AppsState,
public readonly schemasState: SchemasState,
private readonly patternsService: AppPatternsService,
public readonly patternsState: PatternsState,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly messageBus: MessageBus
@ -68,10 +66,7 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
this.patternsService.getPatterns(this.appsState.appName)
.subscribe(dtos => {
this.patterns = dtos.patterns;
});
this.patternsState.load().onErrorResumeNext().subscribe();
this.selectedSchemaSubscription =
this.schemasState.selectedSchema

3
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -11,6 +11,7 @@ import { Observable, Subscription } from 'rxjs';
import {
AppPatternDto,
ImmutableArray,
ModalView,
StringFieldPropertiesDto
} from '@app/shared';
@ -30,7 +31,7 @@ export class StringValidationComponent implements OnDestroy, OnInit {
public properties: StringFieldPropertiesDto;
@Input()
public patterns: AppPatternDto[] = [];
public patterns: ImmutableArray<AppPatternDto>;
public showDefaultValue: Observable<boolean>;
public showPatternMessage: boolean;

128
src/Squidex/app/features/settings/pages/backups/backups-page.component.html

@ -6,81 +6,89 @@
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh backups (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<button class="btn btn-success" [disabled]="backupsState.maxBackupsReached | async" (click)="start()">
Start Backup
</button>
</ng-container>
<ng-container content>
<div class="panel-alert panel-alert-danger" *ngIf="backupsState.maxBackupsReached | async">
Your have reached the maximum number of backups: 10.
</div>
<ng-container *ngIf="backupsState.isLoaded | async">
<div class="panel-alert panel-alert-danger" *ngIf="backupsState.maxBackupsReached | async">
Your have reached the maximum number of backups: 10.
</div>
<ng-container *ngIf="backupsState.backups | async; let backups">
<div class="table-items-row table-items-row-empty" *ngIf="backups.length === 0">
No backups created yet.
<ng-container *ngIf="backupsState.backups | async; let backups">
<div class="table-items-row table-items-row-empty" *ngIf="backups.length === 0">
No backups created yet.
<button class="btn btn-success btn-sm ml-2" (click)="start()">
Start Backup
</button>
</div>
<div class="table-items-row" *ngFor="let backup of backups; trackBy: trackByBackup">
<div class="row no-gutter">
<div class="col col-auto">
<div *ngIf="!backup.stopped" class="backup-status backup-status-pending spin">
<i class="icon-hour-glass"></i>
</div>
<div *ngIf="backup.stopped && backup.isFailed" class="backup-status backup-status-failed">
<i class="icon-exclamation"></i>
</div>
<div *ngIf="backup.stopped && !backup.isFailed" class="backup-status backup-status-success">
<i class="icon-checkmark"></i>
</div>
</div>
<div class="col col-auto">
<div>
Started:
</div>
<div>
Duration:
</div>
</div>
<div class="col col-auto">
<div>
{{backup.started | sqxISODate}}
<button class="btn btn-success btn-sm ml-2" (click)="start()">
Start Backup
</button>
</div>
<div class="table-items-row" *ngFor="let backup of backups; trackBy: trackByBackup">
<div class="row no-gutter">
<div class="col col-auto">
<div *ngIf="!backup.stopped" class="backup-status backup-status-pending spin">
<i class="icon-hour-glass"></i>
</div>
<div *ngIf="backup.stopped && backup.isFailed" class="backup-status backup-status-failed">
<i class="icon-exclamation"></i>
</div>
<div *ngIf="backup.stopped && !backup.isFailed" class="backup-status backup-status-success">
<i class="icon-checkmark"></i>
</div>
</div>
<div *ngIf="backup.stopped">
{{backup | sqxBackupDuration}}
<div class="col col-auto">
<div>
Started:
</div>
<div>
Duration:
</div>
</div>
</div>
<div class="col">
<div>
<span title="Archived events">
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong>
</span>,
<span title="Archived assets">
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong>
</span>
<div class="col col-auto">
<div>
{{backup.started | sqxISODate}}
</div>
<div *ngIf="backup.stopped">
{{backup | sqxBackupDuration}}
</div>
</div>
<div *ngIf="backup.stopped && !backup.isFailed">
Download:
<div class="col">
<div>
<span title="Archived events">
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong>
</span>,
<span title="Archived assets">
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong>
</span>
</div>
<div *ngIf="backup.stopped && !backup.isFailed">
Download:
<a href="{{backup | sqxBackupDownloadUrl}}" target="_blank">
Ready
</a>
<a href="{{backup | sqxBackupDownloadUrl}}" target="_blank">
Ready
</a>
</div>
</div>
<div class="col col-auto">
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="delete(backup)"
confirmTitle="Delete backup"
confirmText="Do you really want to delete the backup?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<div class="col col-auto">
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="delete(backup)"
confirmTitle="Delete backup"
confirmText="Do you really want to delete the backup?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
</div>
</ng-container>
</ng-container>
</ng-container>
</sqx-panel>

8
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -4,6 +4,14 @@
<ng-container title>
Clients
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh clients (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
</ng-container>
<ng-container content>
<ng-container *ngIf="clientsState.clients | async; let clients">

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

@ -35,6 +35,10 @@ export class ClientsPageComponent implements OnInit {
this.clientsState.load().onErrorResumeNext().subscribe();
}
public reload() {
this.clientsState.load(true).onErrorResumeNext().subscribe();
}
public attachClient() {
const value = this.addClientForm.submit();

110
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -5,60 +5,70 @@
Contributors
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh contributors (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
</ng-container>
<ng-container content>
<ng-container *ngIf="contributorsState.maxContributors | async; let maxContributors">
<div class="panel-alert panel-alert-success" *ngIf="maxContributors > 0">
Your plan allows up to {{maxContributors}} contributors.
</div>
</ng-container>
<ng-container *ngIf="contributorsState.contributors | async; let contributors">
<table class="table table-items table-fixed">
<tbody>
<ng-template ngFor let-contributorInfo [ngForOf]="contributors" [ngForTrackBy]="trackByContributor">
<tr>
<td class="cell-user">
<img class="user-picture" [attr.title]="contributorInfo.contributor.contributorId | sqxUserName" [attr.src]="contributorInfo.contributor.contributorId | sqxUserPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{contributorInfo.contributor.contributorId | sqxUserName}}</span>
</td>
<td class="cell-time">
<select class="form-control" [ngModel]="contributorInfo.contributor.permission" (ngModelChange)="changePermission(contributorInfo.contributor, $event)" [disabled]="contributorInfo.isCurrentUser">
<option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
<td class="cell-actions">
<button *ngIf="!contributorInfo.isCurrentUser" type="button" class="btn btn-link btn-danger" (click)="remove(contributorInfo.contributor)">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
<ng-container *ngIf="contributorsState.isLoaded | async">
<ng-container *ngIf="contributorsState.maxContributors | async; let maxContributors">
<div class="panel-alert panel-alert-success" *ngIf="maxContributors > 0">
Your plan allows up to {{maxContributors}} contributors.
</div>
</ng-container>
<div class="table-items-footer" *ngIf="(contributorsState.isMaxReached | async) === false">
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters">
<div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName">
<ng-template let-user="$implicit">
<span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" />
<ng-container *ngIf="contributorsState.contributors | async; let contributors">
<table class="table table-items table-fixed">
<tbody>
<ng-template ngFor let-contributorInfo [ngForOf]="contributors" [ngForTrackBy]="trackByContributor">
<tr>
<td class="cell-user">
<img class="user-picture" [attr.title]="contributorInfo.contributor.contributorId | sqxUserName" [attr.src]="contributorInfo.contributor.contributorId | sqxUserPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{contributorInfo.contributor.contributorId | sqxUserName}}</span>
</td>
<td class="cell-time">
<select class="form-control" [ngModel]="contributorInfo.contributor.permission" (ngModelChange)="changePermission(contributorInfo.contributor, $event)" [disabled]="contributorInfo.isCurrentUser">
<option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
<td class="cell-actions">
<button *ngIf="!contributorInfo.isCurrentUser" type="button" class="btn btn-link btn-danger" (click)="remove(contributorInfo.contributor)">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
<div class="table-items-footer" *ngIf="(contributorsState.isMaxReached | async) === false">
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters">
<div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName">
<ng-template let-user="$implicit">
<span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" />
<span class="user-name autocomplete-user-name">{{user.displayName}}</span>
</span>
</ng-template>
</sqx-autocomplete>
</div>
<div class="col col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.hasNoUser | async">Add Contributor</button>
<span class="user-name autocomplete-user-name">{{user.displayName}}</span>
</span>
</ng-template>
</sqx-autocomplete>
</div>
<div class="col col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.hasNoUser | async">Add Contributor</button>
</div>
</div>
</div>
</form>
</div>
</form>
</div>
</ng-container>
</ng-container>
</ng-container>
</sqx-panel>

4
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -67,6 +67,10 @@ export class ContributorsPageComponent implements OnInit {
this.contributorsState.load().onErrorResumeNext().subscribe();
}
public reload() {
this.contributorsState.load(true).onErrorResumeNext().subscribe();
}
public remove(contributor: AppContributorDto) {
this.contributorsState.revoke(contributor).onErrorResumeNext().subscribe();
}

4
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -89,14 +89,14 @@ export class LanguageComponent implements OnInit, OnChanges, OnDestroy {
this.fallbackLanguages.splice(this.fallbackLanguages.indexOf(language), 1);
this.otherLanguages = [...this.otherLanguages, language];
this.otherLanguage = this.otherLanguages.values[0];
this.otherLanguage = this.otherLanguages[0];
}
public addFallbackLanguage(language: AppLanguageDto) {
this.fallbackLanguages.push(language);
this.otherLanguages = this.otherLanguages.filter(l => l.iso2Code !== language.iso2Code);
this.otherLanguage = this.otherLanguages.values[0];
this.otherLanguage = this.otherLanguages[0];
}
public save() {

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

@ -5,6 +5,14 @@
Patterns
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh patterns (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
</ng-container>
<ng-container content>
<ng-container *ngIf="patternsState.patterns | async; let patterns">
<div class="table-items-row table-items-row-empty" *ngIf="patterns.length === 0">

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

@ -29,6 +29,10 @@ export class PatternsPageComponent implements OnInit {
this.patternsState.load().onErrorResumeNext().subscribe();
}
public reload() {
this.patternsState.load(true).onErrorResumeNext().subscribe();
}
public trackByPattern(index: number, pattern: AppPatternDto) {
return pattern.id;
}

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

@ -61,6 +61,7 @@ describe('AssetsState', () => {
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));

28
src/Squidex/app/shared/state/assets.state.ts

@ -39,7 +39,7 @@ interface Snapshot {
assetsPager: Pager;
assetsQuery?: string;
loaded: false;
isLoaded?: false;
}
@Injectable()
@ -52,16 +52,20 @@ export class AssetsState extends State<Snapshot> {
this.changes.map(x => x.assetsPager)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly dialogs: DialogService
) {
super({ assets: ImmutableArray.empty(), assetsPager: new Pager(0, 0, 30), loaded: false });
super({ assets: ImmutableArray.empty(), assetsPager: new Pager(0, 0, 30) });
}
public load(notifyLoad = false, noReload = false): Observable<any> {
if (this.snapshot.loaded && noReload) {
if (this.snapshot.isLoaded && noReload) {
return Observable.of({});
}
@ -75,7 +79,7 @@ export class AssetsState extends State<Snapshot> {
const assets = ImmutableArray.of(dtos.items);
const assetsPager = s.assetsPager.setCount(dtos.total);
return { ...s, assets, assetsPager, loaded: true };
return { ...s, assets, assetsPager, isLoaded: true };
});
})
.notify(this.dialogs);
@ -90,14 +94,6 @@ export class AssetsState extends State<Snapshot> {
});
}
public update(asset: AssetDto) {
this.next(s => {
const assets = s.assets.replaceBy('id', asset);
return { ...s, assets };
});
}
public delete(asset: AssetDto): Observable<any> {
return this.assetsService.deleteAsset(this.appName, asset.id, asset.version)
.do(dto => {
@ -111,6 +107,14 @@ export class AssetsState extends State<Snapshot> {
.notify(this.dialogs);
}
public update(asset: AssetDto) {
this.next(s => {
const assets = s.assets.replaceBy('id', asset);
return { ...s, assets };
});
}
public search(query: string): Observable<any> {
this.next(s => ({ ...s, assetsPager: new Pager(0, 0, 30), assetsQuery: query }));

3
src/Squidex/app/shared/state/backups.state.spec.ts

@ -49,6 +49,9 @@ describe('BackupsState', () => {
it('should load backups', () => {
expect(backupsState.snapshot.backups.values).toEqual(oldBackups);
expect(backupsState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {

14
src/Squidex/app/shared/state/backups.state.ts

@ -22,15 +22,23 @@ import { BackupDto, BackupsService } from './../services/backups.service';
interface Snapshot {
backups: ImmutableArray<BackupDto>;
isLoaded?: boolean;
}
@Injectable()
export class BackupsState extends State<Snapshot> {
public backups =
this.changes.map(x => x.backups);
this.changes.map(x => x.backups)
.distinctUntilChanged();
public maxBackupsReached =
this.changes.map(x => x.backups.length >= 10);
this.changes.map(x => x.backups.length >= 10)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appsState: AppsState,
@ -50,7 +58,7 @@ export class BackupsState extends State<Snapshot> {
this.next(s => {
const backups = ImmutableArray.of(dtos);
return { ...s, backups };
return { ...s, backups, isLoaded: true };
});
})
.catch(error => {

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

@ -6,7 +6,7 @@
*/
import { Observable } from 'rxjs';
import { IMock, Mock } from 'typemoq';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppsState,
@ -54,8 +54,17 @@ describe('ClientsState', () => {
});
it('should load clients', () => {
expect(clientsState.snapshot.clients!.values).toEqual(oldClients);
expect(clientsState.snapshot.clients.values).toEqual(oldClients);
expect(clientsState.snapshot.version).toEqual(version);
expect(clientsState.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {
clientsState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should add client to snapshot when created', () => {
@ -68,7 +77,7 @@ describe('ClientsState', () => {
clientsState.attach(request).subscribe();
expect(clientsState.snapshot.clients!.values).toEqual([...oldClients, newClient]);
expect(clientsState.snapshot.clients.values).toEqual([...oldClients, newClient]);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
@ -80,7 +89,7 @@ describe('ClientsState', () => {
clientsState.update(oldClients[0], request).subscribe();
const client_1 = clientsState.snapshot.clients!.at(0);
const client_1 = clientsState.snapshot.clients.at(0);
expect(client_1.name).toBe('NewName');
expect(client_1.permission).toBe('NewPermission');
@ -93,7 +102,7 @@ describe('ClientsState', () => {
clientsState.revoke(oldClients[0]).subscribe();
expect(clientsState.snapshot.clients!.values).toEqual([oldClients[1]]);
expect(clientsState.snapshot.clients.values).toEqual([oldClients[1]]);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
});

29
src/Squidex/app/shared/state/clients.state.ts

@ -58,27 +58,38 @@ export class AttachClientForm extends Form<FormGroup> {
}
interface Snapshot {
clients?: ImmutableArray<AppClientDto>;
clients: ImmutableArray<AppClientDto>;
version?: Version;
version: Version;
isLoaded?: boolean;
}
@Injectable()
export class ClientsState extends State<Snapshot> {
public clients =
this.changes.map(x => x.clients);
this.changes.map(x => x.clients)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appClientsService: AppClientsService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({});
super({ clients: ImmutableArray.empty(), version: new Version('') });
}
public load(): Observable<any> {
public load(notifyLoad = false): Observable<any> {
return this.appClientsService.getClients(this.appName)
.do(dtos => {
if (notifyLoad) {
this.dialogs.notifyInfo('Clients reloaded.');
}
this.next(s => {
const clients = ImmutableArray.of(dtos.clients);
@ -92,7 +103,7 @@ export class ClientsState extends State<Snapshot> {
return this.appClientsService.postClient(this.appName, request, this.version)
.do(dto => {
this.next(s => {
const clients = s.clients!.push(dto.payload);
const clients = s.clients.push(dto.payload);
return { ...s, clients, version: dto.version };
});
@ -104,7 +115,7 @@ export class ClientsState extends State<Snapshot> {
return this.appClientsService.deleteClient(this.appName, client.id, this.version)
.do(dto => {
this.next(s => {
const clients = s.clients!.filter(c => c.id !== client.id);
const clients = s.clients.filter(c => c.id !== client.id);
return { ...s, clients, version: dto.version };
});
@ -116,7 +127,7 @@ export class ClientsState extends State<Snapshot> {
return this.appClientsService.putClient(this.appName, client.id, request, this.version)
.do(dto => {
this.next(s => {
const clients = s.clients!.replaceBy('id', update(client, request));
const clients = s.clients.replaceBy('id', update(client, request));
return { ...s, clients, version: dto.version };
});
@ -129,7 +140,7 @@ export class ClientsState extends State<Snapshot> {
}
private get version() {
return this.snapshot.version!;
return this.snapshot.version;
}
}

19
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -6,7 +6,7 @@
*/
import { Observable } from 'rxjs';
import { IMock, Mock } from 'typemoq';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppsState,
@ -59,10 +59,19 @@ describe('ContributorsState', () => {
});
it('should load contributors', () => {
expect(contributorsState.snapshot.contributors!.values).toEqual(oldContributors.map(x => c(x)));
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.map(x => c(x)));
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(version);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {
contributorsState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should add contributor to snapshot when assigned', () => {
@ -75,7 +84,7 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors!.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]);
expect(contributorsState.snapshot.contributors.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]);
expect(contributorsState.snapshot.isMaxReached).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
@ -91,7 +100,7 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors!.values).toEqual([c(oldContributors[0]), c(newContributor)]);
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[0]), c(newContributor)]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
@ -103,7 +112,7 @@ describe('ContributorsState', () => {
contributorsState.revoke(oldContributors[0]).subscribe();
expect(contributorsState.snapshot.contributors!.values).toEqual([c(oldContributors[1])]);
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[1])]);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});

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

@ -45,25 +45,33 @@ interface SnapshotContributor {
}
interface Snapshot {
contributors?: ImmutableArray<SnapshotContributor>;
contributors: ImmutableArray<SnapshotContributor>;
isMaxReached?: boolean;
isLoaded?: boolean;
maxContributors: number;
version?: Version;
version: Version;
}
@Injectable()
export class ContributorsState extends State<Snapshot> {
public contributors =
this.changes.map(x => x.contributors);
this.changes.map(x => x.contributors)
.distinctUntilChanged();
public isMaxReached =
this.changes.map(x => x.isMaxReached);
this.changes.map(x => x.isMaxReached)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
public maxContributors =
this.changes.map(x => x.maxContributors);
this.changes.map(x => x.maxContributors)
.distinctUntilChanged();
constructor(
private readonly appContributorsService: AppContributorsService,
@ -71,12 +79,16 @@ export class ContributorsState extends State<Snapshot> {
private readonly authState: AuthService,
private readonly dialogs: DialogService
) {
super({ maxContributors: -1 });
super({ contributors: ImmutableArray.empty(), version: new Version(''), maxContributors: -1 });
}
public load(): Observable<any> {
public load(notifyLoad = false): Observable<any> {
return this.appContributorsService.getContributors(this.appName)
.do(dtos => {
if (notifyLoad) {
this.dialogs.notifyInfo('Contributors reloaded.');
}
const contributors = ImmutableArray.of(dtos.contributors.map(x => this.createContributor(x)));
this.replaceContributors(contributors, dtos.version, dtos.maxContributors);
@ -87,7 +99,7 @@ export class ContributorsState extends State<Snapshot> {
public revoke(contributor: AppContributorDto): Observable<any> {
return this.appContributorsService.deleteContributor(this.appName, contributor.contributorId, this.version)
.do(dto => {
const contributors = this.snapshot.contributors!.filter(x => x.contributor.contributorId !== contributor.contributorId);
const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId);
this.replaceContributors(contributors, dto.version);
})
@ -99,7 +111,7 @@ export class ContributorsState extends State<Snapshot> {
.do(dto => {
const contributor = this.createContributor(new AppContributorDto(dto.payload.contributorId, request.permission));
let contributors = this.snapshot.contributors!;
let contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === dto.payload.contributorId)) {
contributors = contributors.map(c => c.contributor.contributorId === dto.payload.contributorId ? contributor : c);
@ -128,7 +140,7 @@ export class ContributorsState extends State<Snapshot> {
}
private get version() {
return this.snapshot.version!;
return this.snapshot.version;
}
private createContributor(contributor: AppContributorDto): SnapshotContributor {

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

@ -6,7 +6,7 @@
*/
import { Observable } from 'rxjs';
import { IMock, Mock } from 'typemoq';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppsState,
@ -55,6 +55,14 @@ describe('PatternsState', () => {
it('should load patterns', () => {
expect(patternsState.snapshot.patterns.values).toEqual(oldPatterns);
expect(patternsState.snapshot.version).toEqual(version);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {
patternsState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should add pattern to snapshot when created', () => {

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

@ -55,7 +55,7 @@ export class EditPatternForm extends Form<FormGroup> {
interface Snapshot {
patterns: ImmutableArray<AppPatternDto>;
isLoaded: boolean;
isLoaded?: boolean;
version: Version;
}
@ -63,22 +63,28 @@ interface Snapshot {
@Injectable()
export class PatternsState extends State<Snapshot> {
public patterns =
this.changes.map(x => x.patterns);
this.changes.map(x => x.patterns)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => x.isLoaded);
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appPatternsService: AppPatternsService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ patterns: ImmutableArray.empty(), version: new Version(''), isLoaded: false });
super({ patterns: ImmutableArray.empty(), version: new Version('') });
}
public load(): Observable<any> {
public load(notifyLoad = false): Observable<any> {
return this.appPatternsService.getPatterns(this.appName)
.do(dtos => {
if (notifyLoad) {
this.dialogs.notifyInfo('Patterns reloaded.');
}
this.next(s => {
const patterns = ImmutableArray.of(dtos.patterns).sortByStringAsc(x => x.name);

30
src/Squidex/app/shared/state/plans.state.ts

@ -34,29 +34,38 @@ interface PlanInfo {
}
interface Snapshot {
plans?: ImmutableArray<PlanInfo>;
version?: Version;
plans: ImmutableArray<PlanInfo>;
isOwner?: boolean;
isLoaded?: boolean;
isDisabled?: boolean;
hasPortal?: boolean;
version: Version;
}
@Injectable()
export class PlansState extends State<Snapshot> {
public plans =
this.changes.map(x => x.plans);
this.changes.map(x => x.plans)
.distinctUntilChanged();
public isOwner =
this.changes.map(x => x.isOwner);
this.changes.map(x => !!x.isOwner)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
public isDisabled =
this.changes.map(x => x.isDisabled || !x.isOwner);
this.changes.map(x => !!x.isDisabled || !x.isOwner)
.distinctUntilChanged();
public hasPortal =
this.changes.map(x => x.hasPortal);
this.changes.map(x => x.hasPortal)
.distinctUntilChanged();
public window = window;
@ -66,7 +75,7 @@ export class PlansState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly plansService: PlansService
) {
super({});
super({ plans: ImmutableArray.empty(), version: new Version('') });
}
public load(notifyLoad = false, overridePlanId?: string): Observable<any> {
@ -83,6 +92,7 @@ export class PlansState extends State<Snapshot> {
...s,
plans: ImmutableArray.of(dto.plans.map(x => this.createPlan(x, planId))),
isOwner: !dto.planOwner || dto.planOwner === this.userId,
isLoaded: true,
isDisabled: false,
hasPortal: dto.hasPortal
};
@ -102,7 +112,7 @@ export class PlansState extends State<Snapshot> {
this.next(s => {
return {
...s,
plans: s.plans!.map(x => this.createPlan(x.plan, planId)),
plans: s.plans.map(x => this.createPlan(x.plan, planId)),
isOwner: true,
isDisabled: false
};
@ -125,7 +135,7 @@ export class PlansState extends State<Snapshot> {
}
private get version() {
return this.snapshot.version!;
return this.snapshot.version;
}
}

1
src/Squidex/app/shared/state/rules.state.spec.ts

@ -67,6 +67,7 @@ describe('RulesState', () => {
it('should load rules', () => {
expect(rulesState.snapshot.rules.values).toEqual(oldRules);
expect(rulesState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});

35
src/Squidex/app/shared/state/rules.state.ts

@ -11,11 +11,11 @@ import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
import {
DateTime,
DialogService,
ImmutableArray,
State,
Version,
DateTime
Version
} from '@app/framework';
import { AppsState } from './apps.state';
@ -30,12 +30,19 @@ import {
interface Snapshot {
rules: ImmutableArray<RuleDto>;
isLoaded?: boolean;
}
@Injectable()
export class RulesState extends State<Snapshot> {
public rules =
this.changes.map(x => x.rules);
this.changes.map(x => x.rules)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appsState: AppsState,
@ -56,7 +63,7 @@ export class RulesState extends State<Snapshot> {
this.next(s => {
const rules = ImmutableArray.of(dtos);
return { ...s, rules };
return { ...s, rules, isLoaded: true };
});
})
.notify(this.dialogs);
@ -105,7 +112,7 @@ export class RulesState extends State<Snapshot> {
public enable(rule: RuleDto, now?: DateTime): Observable<any> {
return this.rulesService.enableRule(this.appName, rule.id, rule.version)
.do(dto => {
this.replaceRule(enable(rule, this.user, dto.version, now));
this.replaceRule(setEnabled(rule, true, this.user, dto.version, now));
})
.notify(this.dialogs);
}
@ -113,7 +120,7 @@ export class RulesState extends State<Snapshot> {
public disable(rule: RuleDto, now?: DateTime): Observable<any> {
return this.rulesService.disableRule(this.appName, rule.id, rule.version)
.do(dto => {
this.replaceRule(disable(rule, this.user, dto.version, now));
this.replaceRule(setEnabled(rule, false, this.user, dto.version, now));
})
.notify(this.dialogs);
}
@ -159,25 +166,13 @@ const updateAction = (rule: RuleDto, action: any, user: string, version: Version
action,
action.actionType);
const enable = (rule: RuleDto, user: string, version: Version, now?: DateTime) =>
new RuleDto(
rule.id,
rule.createdBy, user,
rule.created, now || DateTime.now(),
version,
true,
rule.trigger,
rule.triggerType,
rule.action,
rule.actionType);
const disable = (rule: RuleDto, user: string, version: Version, now?: DateTime) =>
const setEnabled = (rule: RuleDto, isEnabled: boolean, user: string, version: Version, now?: DateTime) =>
new RuleDto(
rule.id,
rule.createdBy, user,
rule.created, now || DateTime.now(),
version,
false,
isEnabled,
rule.trigger,
rule.triggerType,
rule.action,

1
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -89,6 +89,7 @@ describe('SchemasState', () => {
it('should load schemas', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
schemasService.verifyAll();
});

15
src/Squidex/app/shared/state/schemas.state.ts

@ -143,21 +143,28 @@ export class AddFieldForm extends Form<FormGroup> {
interface Snapshot {
schemas: ImmutableArray<SchemaDto>;
isLoaded?: boolean;
selectedSchema?: SchemaDetailsDto | null;
}
@Injectable()
export class SchemasState extends State<Snapshot> {
public selectedSchema =
this.changes.map(s => s.selectedSchema)
this.changes.map(x => x.selectedSchema)
.distinctUntilChanged();
public schemas =
this.changes.map(s => s.schemas)
this.changes.map(x => x.schemas)
.distinctUntilChanged();
public publishedSchemas =
this.changes.map(s => s.schemas.filter(x => x.isPublished))
this.changes.map(x => x.schemas.filter(s => s.isPublished))
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
public get schemaName() {
@ -196,7 +203,7 @@ export class SchemasState extends State<Snapshot> {
return this.next(s => {
const schemas = ImmutableArray.of(dtos).sortByStringAsc(x => x.displayName);
return { ...s, schemas };
return { ...s, schemas, isLoaded: true };
});
})
.notify(this.dialogs);

Loading…
Cancel
Save