@myrmidon/auth-jwt-admin 0.1.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/esm2020/lib/auth-jwt-admin.module.mjs +11 -11
  2. package/esm2020/lib/components/auth-jwt-registration/auth-jwt-registration.component.mjs +6 -6
  3. package/esm2020/lib/components/confirm-dialog/confirm-dialog.component.mjs +3 -3
  4. package/esm2020/lib/components/password-strength-bar/password-strength-bar.component.mjs +3 -3
  5. package/esm2020/lib/components/state/user-list.repository.mjs +144 -0
  6. package/esm2020/lib/components/user-editor/user-editor.component.mjs +7 -7
  7. package/esm2020/lib/components/user-filter/user-filter.component.mjs +16 -19
  8. package/esm2020/lib/components/user-list/user-list.component.mjs +37 -103
  9. package/esm2020/lib/services/auth-jwt-account.service.mjs +7 -7
  10. package/esm2020/lib/services/dialog.service.mjs +3 -3
  11. package/fesm2015/myrmidon-auth-jwt-admin.mjs +190 -224
  12. package/fesm2015/myrmidon-auth-jwt-admin.mjs.map +1 -1
  13. package/fesm2020/myrmidon-auth-jwt-admin.mjs +189 -222
  14. package/fesm2020/myrmidon-auth-jwt-admin.mjs.map +1 -1
  15. package/lib/components/auth-jwt-registration/auth-jwt-registration.component.d.ts +1 -1
  16. package/lib/components/confirm-dialog/confirm-dialog.component.d.ts +1 -1
  17. package/lib/components/password-strength-bar/password-strength-bar.component.d.ts +1 -1
  18. package/lib/components/state/user-list.repository.d.ts +36 -0
  19. package/lib/components/user-editor/user-editor.component.d.ts +1 -1
  20. package/lib/components/user-filter/user-filter.component.d.ts +7 -8
  21. package/lib/components/user-list/user-list.component.d.ts +11 -20
  22. package/lib/services/auth-jwt-account.service.d.ts +1 -3
  23. package/package.json +6 -6
  24. package/esm2020/lib/components/state/users.paginator.mjs +0 -12
  25. package/esm2020/lib/components/state/users.query.mjs +0 -20
  26. package/esm2020/lib/components/state/users.service.mjs +0 -65
  27. package/esm2020/lib/components/state/users.store.mjs +0 -27
  28. package/lib/components/state/users.paginator.d.ts +0 -3
  29. package/lib/components/state/users.query.d.ts +0 -12
  30. package/lib/components/state/users.service.d.ts +0 -19
  31. package/lib/components/state/users.store.d.ts +0 -13
@@ -1,43 +1,45 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Component, Input, Output, InjectionToken, inject, Optional, Inject, NgModule } from '@angular/core';
2
+ import { Injectable, EventEmitter, Component, Input, Output, Optional, Inject, NgModule } from '@angular/core';
3
3
  import * as i1$1 from '@angular/forms';
4
4
  import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
5
- import { retry, catchError, take, startWith, tap, switchMap, map } from 'rxjs/operators';
5
+ import { retry, catchError, take } from 'rxjs/operators';
6
6
  import * as i2$1 from '@angular/material/snack-bar';
7
7
  import { MatSnackBarModule } from '@angular/material/snack-bar';
8
8
  import * as i1 from '@angular/common/http';
9
9
  import { HttpParams, HttpClientModule } from '@angular/common/http';
10
10
  import * as i2 from '@myrmidon/ng-tools';
11
11
  import { NgToolsModule } from '@myrmidon/ng-tools';
12
- import * as i7 from '@angular/common';
12
+ import * as i4 from '@angular/common';
13
13
  import { CommonModule } from '@angular/common';
14
- import * as i4 from '@angular/material/button';
14
+ import * as i5 from '@angular/material/button';
15
15
  import { MatButtonModule } from '@angular/material/button';
16
16
  import * as i6 from '@angular/material/icon';
17
17
  import { MatIconModule } from '@angular/material/icon';
18
- import * as i7$1 from '@angular/material/form-field';
19
- import * as i8 from '@angular/material/input';
18
+ import * as i7 from '@angular/material/input';
20
19
  import { MatInputModule } from '@angular/material/input';
20
+ import * as i8 from '@angular/material/form-field';
21
21
  import * as i9 from '@angular/material/progress-spinner';
22
22
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
23
- import * as i15 from '@datorama/akita';
24
- import { EntityStore, StoreConfig, QueryEntity, PaginatorPlugin } from '@datorama/akita';
25
- import { __decorate } from 'tslib';
26
- import * as i8$1 from '@angular/material/tooltip';
23
+ import { __rest } from 'tslib';
24
+ import { BehaviorSubject, combineLatest, map, debounceTime, take as take$1 } from 'rxjs';
25
+ import { select, createStore, withProps } from '@ngneat/elf';
26
+ import { selectActiveEntity, withEntities, withActiveId, upsertEntities, deleteAllEntities, setActiveId } from '@ngneat/elf-entities';
27
+ import { selectPaginationData, selectCurrentPageEntities, deleteAllPages, withPagination, updatePaginationData, setPage, hasPage, setCurrentPage } from '@ngneat/elf-pagination';
28
+ import { selectRequestStatus, withRequestsCache, withRequestsStatus, updateRequestStatus } from '@ngneat/elf-requests';
29
+ import * as i9$1 from '@angular/material/tooltip';
27
30
  import { MatTooltipModule } from '@angular/material/tooltip';
28
- import { BehaviorSubject, combineLatest } from 'rxjs';
29
31
  import * as i1$2 from '@angular/material/dialog';
30
32
  import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
31
33
  import * as i2$2 from '@myrmidon/auth-jwt-login';
32
34
  import { AuthJwtLoginModule } from '@myrmidon/auth-jwt-login';
33
- import * as i10 from '@angular/material/paginator';
35
+ import * as i7$1 from '@angular/material/paginator';
34
36
  import { MatPaginatorModule } from '@angular/material/paginator';
35
- import * as i11 from '@angular/material/progress-bar';
37
+ import * as i8$1 from '@angular/material/progress-bar';
36
38
  import { MatProgressBarModule } from '@angular/material/progress-bar';
37
- import * as i5 from '@angular/material/checkbox';
39
+ import * as i5$1 from '@angular/material/checkbox';
38
40
  import { MatCheckboxModule } from '@angular/material/checkbox';
39
- import { MatCardModule } from '@angular/material/card';
40
41
  import { RouterModule } from '@angular/router';
42
+ import { MatCardModule } from '@angular/material/card';
41
43
 
42
44
  class PasswordValidators {
43
45
  /** "Standard" password validator for my API services. */
@@ -148,10 +150,10 @@ class AuthJwtAccountService {
148
150
  })
149
151
  .pipe(retry(3), catchError(this._error.handleError));
150
152
  }
151
- getUsers(filter) {
153
+ getUsers(filter, pageNumber = 1, pageSize = 20) {
152
154
  let httpParams = new HttpParams();
153
- httpParams = httpParams.set('pageNumber', filter.pageNumber.toString());
154
- httpParams = httpParams.set('pageSize', filter.pageSize.toString());
155
+ httpParams = httpParams.set('pageNumber', pageNumber.toString());
156
+ httpParams = httpParams.set('pageSize', pageSize.toString());
155
157
  if (filter.name) {
156
158
  httpParams = httpParams.set('name', filter.name);
157
159
  }
@@ -248,9 +250,9 @@ class AuthJwtAccountService {
248
250
  .pipe(catchError(this._error.handleError));
249
251
  }
250
252
  }
251
- AuthJwtAccountService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAccountService, deps: [{ token: i1.HttpClient }, { token: i2.ErrorService }, { token: i2.EnvService }], target: i0.ɵɵFactoryTarget.Injectable });
252
- AuthJwtAccountService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAccountService, providedIn: 'root' });
253
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAccountService, decorators: [{
253
+ AuthJwtAccountService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAccountService, deps: [{ token: i1.HttpClient }, { token: i2.ErrorService }, { token: i2.EnvService }], target: i0.ɵɵFactoryTarget.Injectable });
254
+ AuthJwtAccountService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAccountService, providedIn: 'root' });
255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAccountService, decorators: [{
254
256
  type: Injectable,
255
257
  args: [{
256
258
  providedIn: 'root',
@@ -324,9 +326,9 @@ class PasswordStrengthBarComponent {
324
326
  }
325
327
  }
326
328
  }
327
- PasswordStrengthBarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: PasswordStrengthBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
328
- PasswordStrengthBarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: PasswordStrengthBarComponent, selector: "auth-jwt-password-strength-bar", inputs: { passwordToCheck: "passwordToCheck" }, outputs: { strengthChange: "strengthChange" }, usesOnChanges: true, ngImport: i0, template: "<div id=\"strength\">\n <small>strength:</small>\n <ul id=\"strengthBar\">\n <li class=\"point\" [style.background-color]=\"bars[0]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[1]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[2]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[3]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[4]\"></li>\n </ul>\n</div>\n", styles: ["ul#strengthBar{display:inline;list-style:none;margin:0 0 0 15px;padding:0;vertical-align:2px}li.point:last{margin:0!important}li.point{background:#ddd;border-radius:2px;display:inline-block;height:5px;margin-right:1px;width:20px}\n"] });
329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: PasswordStrengthBarComponent, decorators: [{
329
+ PasswordStrengthBarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: PasswordStrengthBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
330
+ PasswordStrengthBarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: PasswordStrengthBarComponent, selector: "auth-jwt-password-strength-bar", inputs: { passwordToCheck: "passwordToCheck" }, outputs: { strengthChange: "strengthChange" }, usesOnChanges: true, ngImport: i0, template: "<div id=\"strength\">\n <small>strength:</small>\n <ul id=\"strengthBar\">\n <li class=\"point\" [style.background-color]=\"bars[0]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[1]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[2]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[3]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[4]\"></li>\n </ul>\n</div>\n", styles: ["ul#strengthBar{display:inline;list-style:none;margin:0 0 0 15px;padding:0;vertical-align:2px}li.point:last{margin:0!important}li.point{background:#ddd;border-radius:2px;display:inline-block;height:5px;margin-right:1px;width:20px}\n"] });
331
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: PasswordStrengthBarComponent, decorators: [{
330
332
  type: Component,
331
333
  args: [{ selector: 'auth-jwt-password-strength-bar', template: "<div id=\"strength\">\n <small>strength:</small>\n <ul id=\"strengthBar\">\n <li class=\"point\" [style.background-color]=\"bars[0]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[1]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[2]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[3]\"></li>\n <li class=\"point\" [style.background-color]=\"bars[4]\"></li>\n </ul>\n</div>\n", styles: ["ul#strengthBar{display:inline;list-style:none;margin:0 0 0 15px;padding:0;vertical-align:2px}li.point:last{margin:0!important}li.point{background:#ddd;border-radius:2px;display:inline-block;height:5px;margin-right:1px;width:20px}\n"] }]
332
334
  }], ctorParameters: function () { return []; }, propDecorators: { passwordToCheck: [{
@@ -514,118 +516,155 @@ class AuthJwtRegistrationComponent {
514
516
  });
515
517
  }
516
518
  }
517
- AuthJwtRegistrationComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtRegistrationComponent, deps: [{ token: i1$1.UntypedFormBuilder }, { token: i2$1.MatSnackBar }, { token: AuthJwtAccountService }], target: i0.ɵɵFactoryTarget.Component });
518
- AuthJwtRegistrationComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: AuthJwtRegistrationComponent, selector: "auth-jwt-registration", outputs: { registered: "registered" }, ngImport: i0, template: "<div>\n <div>\n <p>\n To register a new user, you must provide his email address, choose a\n password, and choose a username, which must be unique (you will be\n notified if another user has already taken that name). The username must\n include only letters/digits, start with a letter, and be no shorter than 3\n characters, nor longer than 50.\n </p>\n <p>\n To promote a decent security level, the password must include at least 8\n characters, uppercase and lowercase letters, digits, and punctuation (like\n dashes, stops, parentheses, etc.).\n </p>\n <p>\n Once registered, the user will receive an email message to the email\n address you specified; he will have to click on the provided link to\n complete the registration process.\n </p>\n </div>\n\n <form role=\"form\" [formGroup]=\"registration\" (submit)=\"onSubmit()\">\n <fieldset>\n <label>register user</label>\n <!-- email -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"email\"\n id=\"email\"\n maxlength=\"256\"\n required\n autofocus\n spellcheck=\"false\"\n placeholder=\"email\"\n formControlName=\"email\"\n />\n <mat-error>{{ getEmailErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"email.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"name\"\n maxlength=\"50\"\n required\n pattern=\"^[a-zA-Z][a-zA-Z0-9]{2,49}$\"\n spellcheck=\"false\"\n placeholder=\"username\"\n formControlName=\"name\"\n />\n <mat-error>{{ getNameErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"name.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- first name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"firstName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"first name\"\n formControlName=\"firstName\"\n />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n class=\"text-danger small\"\n >\n first name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- last name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"lastName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"last name\"\n formControlName=\"lastName\"\n />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') &&\n (lastName.dirty || lastName.touched)\n \"\n class=\"text-danger small\"\n >\n last name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <div [formGroup]=\"passwords\">\n <!-- password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"password\"\n autocomplete=\"new-password\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"password\"\n formControlName=\"password\"\n />\n <auth-jwt-password-strength-bar [passwordToCheck]=\"password.value\">\n </auth-jwt-password-strength-bar>\n <mat-error>{{ getPasswordErrorLabel() }}</mat-error>\n </mat-form-field>\n </div>\n\n <!-- confirm password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"confirmPassword\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"confirm password\"\n formControlName=\"confirmPassword\"\n />\n <mat-error\n *ngIf=\"\n passwords.hasError('areEqual') &&\n (confirmPassword.dirty || confirmPassword.touched)\n \"\n >\n password differs from confirmation password\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n\n <button\n mat-raised-button\n type=\"submit\"\n color=\"primary\"\n [disabled]=\"\n !registration.valid || busy || name.pending || email.pending\n \"\n >\n register\n </button>\n <mat-progress-spinner\n diameter=\"20\"\n *ngIf=\"busy\"\n aria-label=\"Busy\"\n ></mat-progress-spinner>\n </fieldset>\n </form>\n</div>\n", styles: ["mat-form-field{width:400px}fieldset{border:1px solid silver;border-radius:8px;padding:16px}\n"], dependencies: [{ kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i4.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i7$1.MatError, selector: "mat-error", inputs: ["id"] }, { kind: "component", type: i7$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: PasswordStrengthBarComponent, selector: "auth-jwt-password-strength-bar", inputs: ["passwordToCheck"], outputs: ["strengthChange"] }] });
519
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtRegistrationComponent, decorators: [{
519
+ AuthJwtRegistrationComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtRegistrationComponent, deps: [{ token: i1$1.UntypedFormBuilder }, { token: i2$1.MatSnackBar }, { token: AuthJwtAccountService }], target: i0.ɵɵFactoryTarget.Component });
520
+ AuthJwtRegistrationComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: AuthJwtRegistrationComponent, selector: "auth-jwt-registration", outputs: { registered: "registered" }, ngImport: i0, template: "<div>\n <div>\n <p>\n To register a new user, you must provide his email address, choose a\n password, and choose a username, which must be unique (you will be\n notified if another user has already taken that name). The username must\n include only letters/digits, start with a letter, and be no shorter than 3\n characters, nor longer than 50.\n </p>\n <p>\n To promote a decent security level, the password must include at least 8\n characters, uppercase and lowercase letters, digits, and punctuation (like\n dashes, stops, parentheses, etc.).\n </p>\n <p>\n Once registered, the user will receive an email message to the email\n address you specified; he will have to click on the provided link to\n complete the registration process.\n </p>\n </div>\n\n <form role=\"form\" [formGroup]=\"registration\" (submit)=\"onSubmit()\">\n <fieldset>\n <label>register user</label>\n <!-- email -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"email\"\n id=\"email\"\n maxlength=\"256\"\n required\n autofocus\n spellcheck=\"false\"\n placeholder=\"email\"\n formControlName=\"email\"\n />\n <mat-error>{{ getEmailErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"email.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"name\"\n maxlength=\"50\"\n required\n pattern=\"^[a-zA-Z][a-zA-Z0-9]{2,49}$\"\n spellcheck=\"false\"\n placeholder=\"username\"\n formControlName=\"name\"\n />\n <mat-error>{{ getNameErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"name.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- first name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"firstName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"first name\"\n formControlName=\"firstName\"\n />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n class=\"text-danger small\"\n >\n first name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- last name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"lastName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"last name\"\n formControlName=\"lastName\"\n />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') &&\n (lastName.dirty || lastName.touched)\n \"\n class=\"text-danger small\"\n >\n last name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <div [formGroup]=\"passwords\">\n <!-- password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"password\"\n autocomplete=\"new-password\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"password\"\n formControlName=\"password\"\n />\n <auth-jwt-password-strength-bar [passwordToCheck]=\"password.value\">\n </auth-jwt-password-strength-bar>\n <mat-error>{{ getPasswordErrorLabel() }}</mat-error>\n </mat-form-field>\n </div>\n\n <!-- confirm password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"confirmPassword\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"confirm password\"\n formControlName=\"confirmPassword\"\n />\n <mat-error\n *ngIf=\"\n passwords.hasError('areEqual') &&\n (confirmPassword.dirty || confirmPassword.touched)\n \"\n >\n password differs from confirmation password\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n\n <button\n mat-raised-button\n type=\"submit\"\n color=\"primary\"\n [disabled]=\"\n !registration.valid || busy || name.pending || email.pending\n \"\n >\n register\n </button>\n <mat-progress-spinner\n diameter=\"20\"\n *ngIf=\"busy\"\n aria-label=\"Busy\"\n ></mat-progress-spinner>\n </fieldset>\n </form>\n</div>\n", styles: ["mat-form-field{width:400px}fieldset{border:1px solid silver;border-radius:8px;padding:16px}\n"], dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i9.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: PasswordStrengthBarComponent, selector: "auth-jwt-password-strength-bar", inputs: ["passwordToCheck"], outputs: ["strengthChange"] }] });
521
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtRegistrationComponent, decorators: [{
520
522
  type: Component,
521
523
  args: [{ selector: 'auth-jwt-registration', template: "<div>\n <div>\n <p>\n To register a new user, you must provide his email address, choose a\n password, and choose a username, which must be unique (you will be\n notified if another user has already taken that name). The username must\n include only letters/digits, start with a letter, and be no shorter than 3\n characters, nor longer than 50.\n </p>\n <p>\n To promote a decent security level, the password must include at least 8\n characters, uppercase and lowercase letters, digits, and punctuation (like\n dashes, stops, parentheses, etc.).\n </p>\n <p>\n Once registered, the user will receive an email message to the email\n address you specified; he will have to click on the provided link to\n complete the registration process.\n </p>\n </div>\n\n <form role=\"form\" [formGroup]=\"registration\" (submit)=\"onSubmit()\">\n <fieldset>\n <label>register user</label>\n <!-- email -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"email\"\n id=\"email\"\n maxlength=\"256\"\n required\n autofocus\n spellcheck=\"false\"\n placeholder=\"email\"\n formControlName=\"email\"\n />\n <mat-error>{{ getEmailErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"email.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"name\"\n maxlength=\"50\"\n required\n pattern=\"^[a-zA-Z][a-zA-Z0-9]{2,49}$\"\n spellcheck=\"false\"\n placeholder=\"username\"\n formControlName=\"name\"\n />\n <mat-error>{{ getNameErrorLabel() }}</mat-error>\n <mat-icon *ngIf=\"name.pending\">hourglass</mat-icon>\n </mat-form-field>\n </div>\n\n <!-- first name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"firstName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"first name\"\n formControlName=\"firstName\"\n />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n class=\"text-danger small\"\n >\n first name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- last name -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"text\"\n id=\"lastName\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"last name\"\n formControlName=\"lastName\"\n />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') &&\n (lastName.dirty || lastName.touched)\n \"\n class=\"text-danger small\"\n >\n last name required\n </mat-error>\n </mat-form-field>\n </div>\n\n <div [formGroup]=\"passwords\">\n <!-- password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"password\"\n autocomplete=\"new-password\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"password\"\n formControlName=\"password\"\n />\n <auth-jwt-password-strength-bar [passwordToCheck]=\"password.value\">\n </auth-jwt-password-strength-bar>\n <mat-error>{{ getPasswordErrorLabel() }}</mat-error>\n </mat-form-field>\n </div>\n\n <!-- confirm password -->\n <div>\n <mat-form-field>\n <input\n matInput\n type=\"password\"\n name=\"confirmPassword\"\n maxlength=\"50\"\n required\n spellcheck=\"false\"\n placeholder=\"confirm password\"\n formControlName=\"confirmPassword\"\n />\n <mat-error\n *ngIf=\"\n passwords.hasError('areEqual') &&\n (confirmPassword.dirty || confirmPassword.touched)\n \"\n >\n password differs from confirmation password\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n\n <button\n mat-raised-button\n type=\"submit\"\n color=\"primary\"\n [disabled]=\"\n !registration.valid || busy || name.pending || email.pending\n \"\n >\n register\n </button>\n <mat-progress-spinner\n diameter=\"20\"\n *ngIf=\"busy\"\n aria-label=\"Busy\"\n ></mat-progress-spinner>\n </fieldset>\n </form>\n</div>\n", styles: ["mat-form-field{width:400px}fieldset{border:1px solid silver;border-radius:8px;padding:16px}\n"] }]
522
524
  }], ctorParameters: function () { return [{ type: i1$1.UntypedFormBuilder }, { type: i2$1.MatSnackBar }, { type: AuthJwtAccountService }]; }, propDecorators: { registered: [{
523
525
  type: Output
524
526
  }] } });
525
527
 
526
- const initialState = {
527
- filter: {
528
- pageNumber: 1,
529
- pageSize: 20,
530
- },
531
- active: null
532
- };
533
- let UsersStore = class UsersStore extends EntityStore {
534
- constructor() {
535
- super(initialState);
536
- }
537
- };
538
- UsersStore.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
539
- UsersStore.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersStore, providedIn: 'root' });
540
- UsersStore = __decorate([
541
- StoreConfig({ name: 'Users', idKey: 'userName', resettable: true })
542
- ], UsersStore);
543
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersStore, decorators: [{
544
- type: Injectable,
545
- args: [{ providedIn: 'root' }]
546
- }], ctorParameters: function () { return []; } });
547
-
548
- class UsersQuery extends QueryEntity {
549
- constructor(_store) {
550
- super(_store);
551
- this._store = _store;
552
- }
553
- selectFilter() {
554
- return this.select((state) => state.filter);
528
+ const PAGE_SIZE = 20;
529
+ class UserListRepository {
530
+ constructor(_accService) {
531
+ this._accService = _accService;
532
+ // create store
533
+ this._store = this.createStore();
534
+ this._lastPageSize = PAGE_SIZE;
535
+ this._loading$ = new BehaviorSubject(false);
536
+ this._saving$ = new BehaviorSubject(false);
537
+ this.loading$ = this._loading$.asObservable();
538
+ this.saving$ = this._saving$.asObservable();
539
+ // combine pagination parameters with page data for our consumers
540
+ this.pagination$ = combineLatest([
541
+ this._store.pipe(selectPaginationData()),
542
+ this._store.pipe(selectCurrentPageEntities()),
543
+ ]).pipe(map(([pagination, data]) => (Object.assign(Object.assign({}, pagination), { data }))), debounceTime(0));
544
+ // the active user, if required
545
+ this.activeUser$ = this._store.pipe(selectActiveEntity());
546
+ // the filter, if required
547
+ this.filter$ = this._store.pipe(select((state) => state.filter));
548
+ this.filter$.subscribe((filter) => {
549
+ // when filter changed, reset any existing page and move to page 1
550
+ const paginationData = this._store.getValue().pagination;
551
+ this._store.update(deleteAllPages());
552
+ // load page 1
553
+ this.loadPage(1, paginationData.perPage);
554
+ });
555
+ // the request status
556
+ this.status$ = this._store.pipe(selectRequestStatus('user-list'));
557
+ // load page 1 and subscribe to pagination
558
+ this.loadPage(1, PAGE_SIZE);
559
+ this.pagination$.subscribe(console.log);
560
+ }
561
+ createStore() {
562
+ const store = createStore({ name: 'user-list' }, withProps({
563
+ filter: {},
564
+ }), withEntities({ idKey: 'userName' }), withActiveId(), withRequestsCache(), withRequestsStatus(), withPagination());
565
+ return store;
566
+ }
567
+ adaptPage(page) {
568
+ // adapt the server page DataPage<T> to Elf pagination
569
+ return {
570
+ currentPage: page.pageNumber,
571
+ perPage: page.pageSize,
572
+ lastPage: page.pageCount,
573
+ total: page.total,
574
+ data: page.items,
575
+ };
555
576
  }
556
- }
557
- UsersQuery.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersQuery, deps: [{ token: UsersStore }], target: i0.ɵɵFactoryTarget.Injectable });
558
- UsersQuery.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersQuery, providedIn: 'root' });
559
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersQuery, decorators: [{
560
- type: Injectable,
561
- args: [{ providedIn: 'root' }]
562
- }], ctorParameters: function () { return [{ type: UsersStore }]; } });
563
-
564
- class UsersService {
565
- constructor(_store, _accountService) {
566
- this._store = _store;
567
- this._accountService = _accountService;
577
+ addPage(response) {
578
+ const { data } = response, paginationData = __rest(response, ["data"]);
579
+ this._store.update(upsertEntities(data), updatePaginationData(paginationData), setPage(paginationData.currentPage, data.map((c) => c.userName)));
568
580
  }
569
- /**
570
- * Update the filter in the store.
571
- * @param filter The filter.
572
- */
573
- updateFilter(filter) {
574
- this._store.update({
575
- filter: filter,
581
+ loadPage(pageNumber, pageSize) {
582
+ if (!pageSize) {
583
+ pageSize = PAGE_SIZE;
584
+ }
585
+ // if the page exists and page size is the same, just move to it
586
+ if (this._store.query(hasPage(pageNumber)) &&
587
+ pageSize === this._lastPageSize) {
588
+ console.log('Page exists: ' + pageNumber);
589
+ this._store.update(setCurrentPage(pageNumber));
590
+ return;
591
+ }
592
+ // reset cached pages if page size changed
593
+ if (this._lastPageSize !== pageSize) {
594
+ this._store.update(deleteAllPages());
595
+ this._lastPageSize = pageSize;
596
+ }
597
+ // load page from server
598
+ this._store.update(updateRequestStatus('user-list', 'pending'));
599
+ this._loading$.next(true);
600
+ this._accService
601
+ .getUsers(this._store.getValue().filter, pageNumber, pageSize)
602
+ .pipe(take$1(1))
603
+ .subscribe((page) => {
604
+ this._loading$.next(false);
605
+ this.addPage(Object.assign(Object.assign({}, this.adaptPage(page)), { data: page.items }));
606
+ this._store.update(updateRequestStatus('user-list', 'success'));
576
607
  });
577
608
  }
609
+ setFilter(filter) {
610
+ this._store.update((state) => (Object.assign(Object.assign({}, state), { filter: filter })));
611
+ }
612
+ clearCache() {
613
+ this._store.update(deleteAllEntities(), deleteAllPages());
614
+ }
578
615
  setActive(name) {
579
- this._store.setActive(name);
616
+ this._store.update(setActiveId(name));
580
617
  }
581
618
  updateActive(user) {
582
619
  const promise = new Promise((resolve, reject) => {
583
- this._store.setLoading(true);
584
- this._accountService.updateUser(user).subscribe((_) => {
585
- this._store.setLoading(false);
586
- this._store.setError(null);
587
- this._store.updateActive((u) => {
588
- return Object.assign({}, user);
589
- });
590
- resolve(true);
591
- }, (error) => {
592
- console.error(error);
593
- this._store.setLoading(false);
594
- this._store.setError('Error updating user');
595
- resolve(false);
620
+ this._saving$.next(true);
621
+ this._accService.updateUser(user).subscribe({
622
+ next: (_) => {
623
+ this._saving$.next(false);
624
+ this._store.update(upsertEntities(user));
625
+ resolve(true);
626
+ },
627
+ error: (error) => {
628
+ this._saving$.next(false);
629
+ console.error(`Error updating user ${user.userName}: ` +
630
+ JSON.stringify(error || {}));
631
+ resolve(false);
632
+ },
596
633
  });
597
634
  });
598
635
  return promise;
599
636
  }
600
637
  deleteUser(name) {
601
638
  const promise = new Promise((resolve, reject) => {
602
- this._store.setLoading(true);
603
- this._accountService.deleteUser(name).subscribe((_) => {
604
- this._store.setLoading(false);
605
- this._store.setError(null);
606
- this._store.remove(name);
607
- resolve(true);
608
- }, (error) => {
609
- console.error(error);
610
- this._store.setLoading(false);
611
- this._store.setError('Error deleting user');
612
- reject(error);
639
+ this._saving$.next(true);
640
+ this._accService.deleteUser(name).subscribe({
641
+ next: (_) => {
642
+ this._saving$.next(false);
643
+ this.clearCache();
644
+ this.loadPage(1);
645
+ resolve(true);
646
+ },
647
+ error: (error) => {
648
+ this._saving$.next(false);
649
+ console.error(`Error deleting user ${name}: ` + JSON.stringify(error || {}));
650
+ reject(error);
651
+ },
613
652
  });
614
653
  });
615
654
  return promise;
616
655
  }
617
656
  }
618
- UsersService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersService, deps: [{ token: UsersStore }, { token: AuthJwtAccountService }], target: i0.ɵɵFactoryTarget.Injectable });
619
- UsersService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersService, providedIn: 'root' });
620
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UsersService, decorators: [{
657
+ UserListRepository.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserListRepository, deps: [{ token: AuthJwtAccountService }], target: i0.ɵɵFactoryTarget.Injectable });
658
+ UserListRepository.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserListRepository, providedIn: 'root' });
659
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserListRepository, decorators: [{
621
660
  type: Injectable,
622
661
  args: [{ providedIn: 'root' }]
623
- }], ctorParameters: function () { return [{ type: UsersStore }, { type: AuthJwtAccountService }]; } });
662
+ }], ctorParameters: function () { return [{ type: AuthJwtAccountService }]; } });
624
663
 
625
664
  class UserFilterComponent {
626
- constructor(formBuilder, query, _corporaService) {
627
- this._corporaService = _corporaService;
628
- this.filter$ = query.selectFilter();
665
+ constructor(formBuilder, _repository) {
666
+ this._repository = _repository;
667
+ this.filter$ = _repository.filter$;
629
668
  // form
630
669
  this.name = formBuilder.control(null);
631
670
  this.form = formBuilder.group({
@@ -638,7 +677,7 @@ class UserFilterComponent {
638
677
  });
639
678
  }
640
679
  updateForm(filter) {
641
- this.name.setValue(filter.name);
680
+ this.name.setValue(filter.name || null);
642
681
  this.form.markAsPristine();
643
682
  }
644
683
  reset() {
@@ -648,8 +687,6 @@ class UserFilterComponent {
648
687
  getFilter() {
649
688
  var _a;
650
689
  return {
651
- pageNumber: 1,
652
- pageSize: 20,
653
690
  name: (_a = this.name.value) === null || _a === void 0 ? void 0 : _a.trim(),
654
691
  };
655
692
  }
@@ -659,27 +696,18 @@ class UserFilterComponent {
659
696
  }
660
697
  const filter = this.getFilter();
661
698
  // update filter in state
662
- this._corporaService.updateFilter(filter);
699
+ this._repository.setFilter(filter);
663
700
  }
664
701
  }
665
- UserFilterComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserFilterComponent, deps: [{ token: i1$1.UntypedFormBuilder }, { token: UsersQuery }, { token: UsersService }], target: i0.ɵɵFactoryTarget.Component });
666
- UserFilterComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: UserFilterComponent, selector: "auth-jwt-user-filter", inputs: { disabled: "disabled" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" ng-disabled=\"disabled\">\n <mat-form-field>\n <input matInput [formControl]=\"name\" placeholder=\"name or ID\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"reset()\"\n color=\"warn\"\n matTooltip=\"Reset filters\"\n [disabled]=\"disabled\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </mat-form-field>\n\n <button\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"disabled\"\n matTooltip=\"Apply filters\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n</form>\n", styles: [""], dependencies: [{ kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i7$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7$1.MatSuffix, selector: "[matSuffix]" }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i8$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
667
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserFilterComponent, decorators: [{
702
+ UserFilterComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserFilterComponent, deps: [{ token: i1$1.FormBuilder }, { token: UserListRepository }], target: i0.ɵɵFactoryTarget.Component });
703
+ UserFilterComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: UserFilterComponent, selector: "auth-jwt-user-filter", inputs: { disabled: "disabled" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" ng-disabled=\"disabled\">\n <div class=\"form-row\">\n <mat-form-field>\n <input matInput [formControl]=\"name\" placeholder=\"name or ID\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"reset()\"\n color=\"warn\"\n matTooltip=\"Reset filters\"\n [disabled]=\"disabled\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </mat-form-field>\n\n <button\n style=\"margin-top: -20px\"\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"disabled\"\n matTooltip=\"Apply filters\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n </div>\n</form>\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]" }, { kind: "directive", type: i9$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
704
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserFilterComponent, decorators: [{
668
705
  type: Component,
669
- args: [{ selector: 'auth-jwt-user-filter', template: "<form [formGroup]=\"form\" (submit)=\"apply()\" ng-disabled=\"disabled\">\n <mat-form-field>\n <input matInput [formControl]=\"name\" placeholder=\"name or ID\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"reset()\"\n color=\"warn\"\n matTooltip=\"Reset filters\"\n [disabled]=\"disabled\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </mat-form-field>\n\n <button\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"disabled\"\n matTooltip=\"Apply filters\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n</form>\n" }]
670
- }], ctorParameters: function () { return [{ type: i1$1.UntypedFormBuilder }, { type: UsersQuery }, { type: UsersService }]; }, propDecorators: { disabled: [{
706
+ args: [{ selector: 'auth-jwt-user-filter', template: "<form [formGroup]=\"form\" (submit)=\"apply()\" ng-disabled=\"disabled\">\n <div class=\"form-row\">\n <mat-form-field>\n <input matInput [formControl]=\"name\" placeholder=\"name or ID\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"reset()\"\n color=\"warn\"\n matTooltip=\"Reset filters\"\n [disabled]=\"disabled\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </mat-form-field>\n\n <button\n style=\"margin-top: -20px\"\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"disabled\"\n matTooltip=\"Apply filters\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n </div>\n</form>\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
707
+ }], ctorParameters: function () { return [{ type: i1$1.FormBuilder }, { type: UserListRepository }]; }, propDecorators: { disabled: [{
671
708
  type: Input
672
709
  }] } });
673
710
 
674
- // create a factory provider for the paginator
675
- const USERS_PAGINATOR = new InjectionToken('USERS_PAGINATOR', {
676
- providedIn: 'root',
677
- factory: () => {
678
- const query = inject(UsersQuery);
679
- return new PaginatorPlugin(query).withControls().withRange();
680
- },
681
- });
682
-
683
711
  // https://medium.com/@tarik.nzl/making-use-of-dialogs-in-material-2-mddialog-7533d27df41
684
712
  class ConfirmDialogComponent {
685
713
  constructor(dialogRef, data) {
@@ -692,9 +720,9 @@ class ConfirmDialogComponent {
692
720
  }
693
721
  ngOnInit() { }
694
722
  }
695
- ConfirmDialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: ConfirmDialogComponent, deps: [{ token: i1$2.MatDialogRef }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
696
- ConfirmDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: ConfirmDialogComponent, selector: "ng-component", ngImport: i0, template: "<h1 mat-dialog-title>{{ title }}</h1>\r\n<mat-dialog-content>\r\n {{ prompt }}\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button\r\n type=\"button\"\r\n mat-raised-button\r\n color=\"warn\"\r\n (click)=\"dialogRef.close(true)\"\r\n >\r\n {{ ok }}\r\n </button>\r\n <button type=\"button\" mat-button (click)=\"dialogRef.close()\">\r\n {{ cancel }}\r\n </button>\r\n</mat-dialog-actions>\r\n", styles: [""], dependencies: [{ kind: "component", type: i4.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i1$2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }] });
697
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
723
+ ConfirmDialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: ConfirmDialogComponent, deps: [{ token: i1$2.MatDialogRef }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
724
+ ConfirmDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: ConfirmDialogComponent, selector: "ng-component", ngImport: i0, template: "<h1 mat-dialog-title>{{ title }}</h1>\r\n<mat-dialog-content>\r\n {{ prompt }}\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button\r\n type=\"button\"\r\n mat-raised-button\r\n color=\"warn\"\r\n (click)=\"dialogRef.close(true)\"\r\n >\r\n {{ ok }}\r\n </button>\r\n <button type=\"button\" mat-button (click)=\"dialogRef.close()\">\r\n {{ cancel }}\r\n </button>\r\n</mat-dialog-actions>\r\n", styles: [""], dependencies: [{ kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i1$2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }] });
725
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
698
726
  type: Component,
699
727
  args: [{ template: "<h1 mat-dialog-title>{{ title }}</h1>\r\n<mat-dialog-content>\r\n {{ prompt }}\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button\r\n type=\"button\"\r\n mat-raised-button\r\n color=\"warn\"\r\n (click)=\"dialogRef.close(true)\"\r\n >\r\n {{ ok }}\r\n </button>\r\n <button type=\"button\" mat-button (click)=\"dialogRef.close()\">\r\n {{ cancel }}\r\n </button>\r\n</mat-dialog-actions>\r\n" }]
700
728
  }], ctorParameters: function () {
@@ -730,9 +758,9 @@ class DialogService {
730
758
  return dialogRef.afterClosed();
731
759
  }
732
760
  }
733
- DialogService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: DialogService, deps: [{ token: i1$2.MatDialog }], target: i0.ɵɵFactoryTarget.Injectable });
734
- DialogService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: DialogService, providedIn: 'root' });
735
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: DialogService, decorators: [{
761
+ DialogService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: DialogService, deps: [{ token: i1$2.MatDialog }], target: i0.ɵɵFactoryTarget.Injectable });
762
+ DialogService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: DialogService, providedIn: 'root' });
763
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: DialogService, decorators: [{
736
764
  type: Injectable,
737
765
  args: [{
738
766
  providedIn: 'root'
@@ -828,11 +856,11 @@ class UserEditorComponent {
828
856
  this.userChange.emit(this.getUserFromForm());
829
857
  }
830
858
  }
831
- UserEditorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserEditorComponent, deps: [{ token: i1$1.UntypedFormBuilder }, { token: i2$2.AuthJwtService }], target: i0.ɵɵFactoryTarget.Component });
832
- UserEditorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: UserEditorComponent, selector: "auth-jwt-user-editor", inputs: { user: "user" }, outputs: { userChange: "userChange", editorClose: "editorClose" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\n <!-- email -->\n <div>\n <mat-form-field>\n <input type=\"text\" matInput [formControl]=\"email\" placeholder=\"email\" />\n <mat-error\n *ngIf=\"email.hasError('required') && (email.dirty || email.touched)\"\n >\n email address required\n </mat-error>\n <mat-error\n *ngIf=\"email.hasError('pattern') && (email.dirty || email.touched)\"\n >\n invalid email address\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- emailConfirmed -->\n <div>\n <mat-checkbox [formControl]=\"emailConfirmed\"\n >email address confirmed</mat-checkbox\n >\n </div>\n\n <!-- lockoutEnabled -->\n <div>\n <mat-checkbox [formControl]=\"lockoutEnabled\"\n >lockout enabled</mat-checkbox\n >\n &nbsp;\n <button\n mat-stroked-button\n (click)=\"endLockout()\"\n [disabled]=\"unlocked\"\n matTooltip=\"Unlock this user if locked\"\n >\n unlock\n </button>\n </div>\n\n <!-- firstName -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"firstName\"\n placeholder=\"first name\"\n />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name required\n </mat-error>\n <mat-error\n *ngIf=\"\n firstName.hasError('maxlength') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- lastName -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"lastName\"\n placeholder=\"last name\"\n />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') && (lastName.dirty || lastName.touched)\n \"\n >\n last name required\n </mat-error>\n <mat-error\n *ngIf=\"\n lastName.hasError('maxlength') && (lastName.dirty || lastName.touched)\n \"\n >\n last name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- roles -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"roles\"\n placeholder=\"roles (separated by space)\"\n />\n <mat-error\n *ngIf=\"roles.hasError('maxlength') && (roles.dirty || roles.touched)\"\n >\n too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- buttons -->\n <br />\n <div>\n <button mat-icon-button color=\"warn\" matTooltip=\"Close\" (click)=\"close()\">\n <mat-icon>cancel</mat-icon>\n </button>\n &nbsp;\n <button\n type=\"submit\"\n mat-button\n color=\"primary\"\n [disabled]=\"form.invalid\"\n matTooltip=\"Save user data\"\n >\n <mat-icon>check_circle</mat-icon> save\n </button>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i7$1.MatError, selector: "mat-error", inputs: ["id"] }, { kind: "component", type: i7$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i8$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
833
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserEditorComponent, decorators: [{
859
+ UserEditorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserEditorComponent, deps: [{ token: i1$1.UntypedFormBuilder }, { token: i2$2.AuthJwtService }], target: i0.ɵɵFactoryTarget.Component });
860
+ UserEditorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: UserEditorComponent, selector: "auth-jwt-user-editor", inputs: { user: "user" }, outputs: { userChange: "userChange", editorClose: "editorClose" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\n <div class=\"form-row\">\n <!-- email -->\n <mat-form-field>\n <mat-label>email</mat-label>\n <input type=\"text\" matInput [formControl]=\"email\" />\n <mat-error\n *ngIf=\"email.hasError('required') && (email.dirty || email.touched)\"\n >\n email address required\n </mat-error>\n <mat-error\n *ngIf=\"email.hasError('pattern') && (email.dirty || email.touched)\"\n >\n invalid email address\n </mat-error>\n </mat-form-field>\n\n <!-- emailConfirmed -->\n <mat-checkbox [formControl]=\"emailConfirmed\"\n >email address confirmed</mat-checkbox\n >\n </div>\n\n <!-- lockoutEnabled -->\n <div class=\"form-row\">\n <mat-checkbox [formControl]=\"lockoutEnabled\">lockout enabled</mat-checkbox>\n <button\n mat-icon-button\n color=\"primary\"\n (click)=\"endLockout()\"\n [disabled]=\"unlocked\"\n matTooltip=\"Unlock this user if locked\"\n >\n <mat-icon>lock_open</mat-icon>\n </button>\n </div>\n\n <div class=\"form-row\">\n <!-- firstName -->\n <mat-form-field>\n <mat-label>first name</mat-label>\n <input type=\"text\" matInput [formControl]=\"firstName\" />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name required\n </mat-error>\n <mat-error\n *ngIf=\"\n firstName.hasError('maxlength') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name too long\n </mat-error>\n </mat-form-field>\n\n <!-- lastName -->\n <mat-form-field>\n <mat-label>last name</mat-label>\n <input type=\"text\" matInput [formControl]=\"lastName\" />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') && (lastName.dirty || lastName.touched)\n \"\n >\n last name required\n </mat-error>\n <mat-error\n *ngIf=\"\n lastName.hasError('maxlength') && (lastName.dirty || lastName.touched)\n \"\n >\n last name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- roles -->\n <div>\n <mat-form-field style=\"width: 16em\">\n <mat-label>roles</mat-label>\n <input type=\"text\" matInput [formControl]=\"roles\" />\n <mat-error\n *ngIf=\"roles.hasError('maxlength') && (roles.dirty || roles.touched)\"\n >\n too long\n </mat-error>\n <mat-hint>roles (separated by space)</mat-hint>\n </mat-form-field>\n </div>\n\n <!-- buttons -->\n <br />\n <div>\n <button mat-icon-button color=\"warn\" matTooltip=\"Close\" (click)=\"close()\">\n <mat-icon>cancel</mat-icon>\n </button>\n <button\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"form.invalid\"\n matTooltip=\"Save user\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n </div>\n</form>\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i5$1.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatLabel, selector: "mat-label" }, { kind: "directive", type: i8.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i8.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i9$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
861
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserEditorComponent, decorators: [{
834
862
  type: Component,
835
- args: [{ selector: 'auth-jwt-user-editor', template: "<form [formGroup]=\"form\" (submit)=\"save()\">\n <!-- email -->\n <div>\n <mat-form-field>\n <input type=\"text\" matInput [formControl]=\"email\" placeholder=\"email\" />\n <mat-error\n *ngIf=\"email.hasError('required') && (email.dirty || email.touched)\"\n >\n email address required\n </mat-error>\n <mat-error\n *ngIf=\"email.hasError('pattern') && (email.dirty || email.touched)\"\n >\n invalid email address\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- emailConfirmed -->\n <div>\n <mat-checkbox [formControl]=\"emailConfirmed\"\n >email address confirmed</mat-checkbox\n >\n </div>\n\n <!-- lockoutEnabled -->\n <div>\n <mat-checkbox [formControl]=\"lockoutEnabled\"\n >lockout enabled</mat-checkbox\n >\n &nbsp;\n <button\n mat-stroked-button\n (click)=\"endLockout()\"\n [disabled]=\"unlocked\"\n matTooltip=\"Unlock this user if locked\"\n >\n unlock\n </button>\n </div>\n\n <!-- firstName -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"firstName\"\n placeholder=\"first name\"\n />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name required\n </mat-error>\n <mat-error\n *ngIf=\"\n firstName.hasError('maxlength') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- lastName -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"lastName\"\n placeholder=\"last name\"\n />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') && (lastName.dirty || lastName.touched)\n \"\n >\n last name required\n </mat-error>\n <mat-error\n *ngIf=\"\n lastName.hasError('maxlength') && (lastName.dirty || lastName.touched)\n \"\n >\n last name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- roles -->\n <div>\n <mat-form-field>\n <input\n type=\"text\"\n matInput\n [formControl]=\"roles\"\n placeholder=\"roles (separated by space)\"\n />\n <mat-error\n *ngIf=\"roles.hasError('maxlength') && (roles.dirty || roles.touched)\"\n >\n too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- buttons -->\n <br />\n <div>\n <button mat-icon-button color=\"warn\" matTooltip=\"Close\" (click)=\"close()\">\n <mat-icon>cancel</mat-icon>\n </button>\n &nbsp;\n <button\n type=\"submit\"\n mat-button\n color=\"primary\"\n [disabled]=\"form.invalid\"\n matTooltip=\"Save user data\"\n >\n <mat-icon>check_circle</mat-icon> save\n </button>\n </div>\n</form>\n" }]
863
+ args: [{ selector: 'auth-jwt-user-editor', template: "<form [formGroup]=\"form\" (submit)=\"save()\">\n <div class=\"form-row\">\n <!-- email -->\n <mat-form-field>\n <mat-label>email</mat-label>\n <input type=\"text\" matInput [formControl]=\"email\" />\n <mat-error\n *ngIf=\"email.hasError('required') && (email.dirty || email.touched)\"\n >\n email address required\n </mat-error>\n <mat-error\n *ngIf=\"email.hasError('pattern') && (email.dirty || email.touched)\"\n >\n invalid email address\n </mat-error>\n </mat-form-field>\n\n <!-- emailConfirmed -->\n <mat-checkbox [formControl]=\"emailConfirmed\"\n >email address confirmed</mat-checkbox\n >\n </div>\n\n <!-- lockoutEnabled -->\n <div class=\"form-row\">\n <mat-checkbox [formControl]=\"lockoutEnabled\">lockout enabled</mat-checkbox>\n <button\n mat-icon-button\n color=\"primary\"\n (click)=\"endLockout()\"\n [disabled]=\"unlocked\"\n matTooltip=\"Unlock this user if locked\"\n >\n <mat-icon>lock_open</mat-icon>\n </button>\n </div>\n\n <div class=\"form-row\">\n <!-- firstName -->\n <mat-form-field>\n <mat-label>first name</mat-label>\n <input type=\"text\" matInput [formControl]=\"firstName\" />\n <mat-error\n *ngIf=\"\n firstName.hasError('required') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name required\n </mat-error>\n <mat-error\n *ngIf=\"\n firstName.hasError('maxlength') &&\n (firstName.dirty || firstName.touched)\n \"\n >\n first name too long\n </mat-error>\n </mat-form-field>\n\n <!-- lastName -->\n <mat-form-field>\n <mat-label>last name</mat-label>\n <input type=\"text\" matInput [formControl]=\"lastName\" />\n <mat-error\n *ngIf=\"\n lastName.hasError('required') && (lastName.dirty || lastName.touched)\n \"\n >\n last name required\n </mat-error>\n <mat-error\n *ngIf=\"\n lastName.hasError('maxlength') && (lastName.dirty || lastName.touched)\n \"\n >\n last name too long\n </mat-error>\n </mat-form-field>\n </div>\n\n <!-- roles -->\n <div>\n <mat-form-field style=\"width: 16em\">\n <mat-label>roles</mat-label>\n <input type=\"text\" matInput [formControl]=\"roles\" />\n <mat-error\n *ngIf=\"roles.hasError('maxlength') && (roles.dirty || roles.touched)\"\n >\n too long\n </mat-error>\n <mat-hint>roles (separated by space)</mat-hint>\n </mat-form-field>\n </div>\n\n <!-- buttons -->\n <br />\n <div>\n <button mat-icon-button color=\"warn\" matTooltip=\"Close\" (click)=\"close()\">\n <mat-icon>cancel</mat-icon>\n </button>\n <button\n type=\"submit\"\n mat-icon-button\n color=\"primary\"\n [disabled]=\"form.invalid\"\n matTooltip=\"Save user\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n </div>\n</form>\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
836
864
  }], ctorParameters: function () { return [{ type: i1$1.UntypedFormBuilder }, { type: i2$2.AuthJwtService }]; }, propDecorators: { user: [{
837
865
  type: Input
838
866
  }], userChange: [{
@@ -842,77 +870,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
842
870
  }] } });
843
871
 
844
872
  class UserListComponent {
845
- constructor(paginator, userQuery, _accountService, _usersService, _dialogService, _gravatarService, formBuilder) {
846
- this.paginator = paginator;
847
- this._accountService = _accountService;
848
- this._usersService = _usersService;
873
+ constructor(_repository, _dialogService, _gravatarService) {
874
+ this._repository = _repository;
849
875
  this._dialogService = _dialogService;
850
876
  this._gravatarService = _gravatarService;
851
- this.pageSize = formBuilder.control(20);
852
- this._refresh$ = new BehaviorSubject(0);
853
- this._filter$ = userQuery.selectFilter();
854
- // https://netbasal.com/manage-your-entities-with-akita-like-a-boss-768732f8d4d1
855
- this.active$ = userQuery.selectActive();
856
- this.pagination$ = combineLatest([
857
- this.paginator.pageChanges,
858
- this.pageSize.valueChanges.pipe(
859
- // we are required to emit at least the initial value
860
- // as combineLatest emits only if ALL observables have emitted
861
- startWith(20),
862
- // clear the cache when page size changes
863
- tap((_) => {
864
- this.paginator.clearCache();
865
- })),
866
- this._filter$.pipe(
867
- // clear the cache when filters changed
868
- tap((_) => {
869
- this.paginator.clearCache();
870
- })),
871
- this._refresh$.pipe(
872
- // clear the cache when forcing refresh
873
- tap((_) => {
874
- this.paginator.clearCache();
875
- })),
876
- ]).pipe(
877
- // for each emitted value, combine into a filter and use it
878
- // to request the page from server
879
- switchMap(([pageNumber, pageSize, filter, refresh]) => {
880
- // const filter = { ...this._docsQuery.getValue().filter };
881
- const f = Object.assign({}, filter);
882
- f.pageNumber = pageNumber;
883
- f.pageSize = pageSize;
884
- const request = this.getRequest(f);
885
- // update saved filters
886
- this.paginator.metadata.set('filter', f);
887
- return this.paginator.getPage(request);
888
- }));
889
- }
890
- ngOnDestroy() {
891
- this.paginator.destroy();
892
- }
893
- getRequest(filter) {
894
- return () => this._accountService.getUsers(filter).pipe(
895
- // adapt server results to the paginator plugin
896
- map((p) => {
897
- return {
898
- currentPage: p.pageNumber,
899
- perPage: p.pageSize,
900
- lastPage: p.pageCount,
901
- data: p.items,
902
- total: p.total,
903
- };
904
- }));
877
+ this.pagination$ = _repository.pagination$;
878
+ this.active$ = _repository.activeUser$;
879
+ this.loading$ = _repository.loading$;
905
880
  }
906
881
  pageChange(event) {
907
- // https://material.angular.io/components/paginator/api
908
- this.paginator.setPage(event.pageIndex + 1);
909
- if (event.pageSize !== this.pageSize.value) {
910
- this.pageSize.setValue(event.pageSize);
911
- }
912
- }
913
- refresh() {
914
- this.paginator.clearCache();
915
- this._refresh$.next(this._refresh$.value + 1);
882
+ this._repository.loadPage(event.pageIndex + 1, event.pageSize);
916
883
  }
917
884
  deleteUser(user) {
918
885
  this._dialogService
@@ -922,45 +889,44 @@ class UserListComponent {
922
889
  if (!yes) {
923
890
  return;
924
891
  }
925
- this._usersService.deleteUser(user.userName).finally(() => {
926
- this.refresh();
892
+ this._repository.deleteUser(user.userName).finally(() => {
893
+ this._repository.clearCache();
894
+ this._repository.loadPage(1);
927
895
  });
928
896
  });
929
897
  }
930
898
  setActiveUser(user) {
931
- this._usersService.setActive((user === null || user === void 0 ? void 0 : user.userName) || null);
899
+ this._repository.setActive((user === null || user === void 0 ? void 0 : user.userName) || null);
932
900
  }
933
901
  resetActiveUser() {
934
- this._usersService.setActive(null);
902
+ this._repository.setActive(null);
935
903
  }
936
904
  saveActiveUser(user) {
937
- this._usersService.updateActive(user).finally(() => {
938
- this.refresh();
939
- });
905
+ this._repository.updateActive(user);
906
+ this._repository.setActive(null);
940
907
  }
941
908
  onUserEditorClose() {
942
- this.setActiveUser(null);
909
+ this._repository.setActive(null);
943
910
  }
944
911
  getGravatarUrl(email, size = 80) {
945
912
  return this._gravatarService.buildGravatarUrl(email, size);
946
913
  }
914
+ clearCache() {
915
+ this._repository.clearCache();
916
+ this._repository.loadPage(1);
917
+ }
947
918
  }
948
- UserListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserListComponent, deps: [{ token: USERS_PAGINATOR }, { token: UsersQuery }, { token: AuthJwtAccountService }, { token: UsersService }, { token: DialogService }, { token: i2$2.GravatarService }, { token: i1$1.UntypedFormBuilder }], target: i0.ɵɵFactoryTarget.Component });
949
- UserListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: UserListComponent, selector: "auth-jwt-user-list", ngImport: i0, template: "<div\r\n gdAreas=\"filters | progress | list | pager | editor\"\r\n gdRows=\"80px 64px 2fr 40px 1fr\"\r\n gdColumns=\"1fr\"\r\n gdGap=\"8px\"\r\n>\r\n <div>\r\n <!-- filters -->\r\n <div gdArea=\"filters\">\r\n <auth-jwt-user-filter></auth-jwt-user-filter>\r\n </div>\r\n\r\n <!-- progress -->\r\n <div *ngIf=\"paginator.isLoading$ | async\" gdArea=\"progress\">\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n </div>\r\n\r\n <!-- list -->\r\n <div gdArea=\"list\" *ngIf=\"pagination$ | async as pagination\">\r\n <table>\r\n <thead>\r\n <td></td>\r\n <td></td>\r\n <td></td>\r\n <th>name</th>\r\n <th>first</th>\r\n <th>last</th>\r\n <th>email</th>\r\n <th>roles</th>\r\n <th>lock end</th>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let user of pagination.data\">\r\n <td class=\"command\">\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n matTooltip=\"Edit {{ user.userName }}\"\r\n color=\"primary\"\r\n (click)=\"setActiveUser(user)\"\r\n >\r\n <mat-icon>mode_edit</mat-icon>\r\n </button>\r\n </td>\r\n <td class=\"command\">\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n matTooltip=\"Delete {{ user.userName }}\"\r\n color=\"warn\"\r\n (click)=\"deleteUser(user)\"\r\n >\r\n <mat-icon>remove_circle</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n <img\r\n [src]=\"getGravatarUrl(user.email, 32)\"\r\n [alt]=\"user.userName\"\r\n />\r\n </td>\r\n <td>{{ user.userName }}</td>\r\n <td>{{ user.firstName }}</td>\r\n <td>{{ user.lastName }}</td>\r\n <td>\r\n <a [href]=\"'mailto:' + user.email\">{{ user.email }}</a>\r\n </td>\r\n <td>{{ user.roles?.join(\" \") }}</td>\r\n <td>{{ user.lockoutEnd }}</td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n <!-- pagination -->\r\n <mat-paginator\r\n gdArea=\"pager\"\r\n gdAlignColumns=\"center\"\r\n gdAlignRows=\"start\"\r\n [length]=\"pagination.total\"\r\n [pageSize]=\"pageSize.value\"\r\n [pageSizeOptions]=\"[20, 50, 75, 100]\"\r\n [pageIndex]=\"pagination.currentPage - 1\"\r\n [showFirstLastButtons]=\"true\"\r\n (page)=\"pageChange($event)\"\r\n ></mat-paginator>\r\n </div>\r\n </div>\r\n\r\n <!-- editor -->\r\n <div gdArea=\"editor\" *ngIf=\"active$ | async as active\">\r\n <fieldset>\r\n <legend>{{ active.userName }}</legend>\r\n <auth-jwt-user-editor\r\n [user]=\"active\"\r\n (userChange)=\"saveActiveUser($event)\"\r\n (editorClose)=\"resetActiveUser()\"\r\n ></auth-jwt-user-editor>\r\n </fieldset>\r\n </div>\r\n</div>\r\n", styles: ["tr:nth-child(odd){background-color:#f8f8f8}th{padding:0 8px;text-align:left;color:silver;font-weight:400}td{padding:0 8px}td.command{width:24px}table{width:100%;border-collapse:collapse}fieldset{border:1px solid silver;border-radius:8px;padding:16px}\n"], dependencies: [{ kind: "directive", type: i7.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i10.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "component", type: i11.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "directive", type: i8$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }, { kind: "component", type: UserFilterComponent, selector: "auth-jwt-user-filter", inputs: ["disabled"] }, { kind: "component", type: UserEditorComponent, selector: "auth-jwt-user-editor", inputs: ["user"], outputs: ["userChange", "editorClose"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }] });
950
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: UserListComponent, decorators: [{
919
+ UserListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserListComponent, deps: [{ token: UserListRepository }, { token: DialogService }, { token: i2$2.GravatarService }], target: i0.ɵɵFactoryTarget.Component });
920
+ UserListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.0", type: UserListComponent, selector: "auth-jwt-user-list", ngImport: i0, template: "<div id=\"container\">\n <div>\n <!-- filters -->\n <div style=\"grid-area: filters\">\n <auth-jwt-user-filter></auth-jwt-user-filter>\n </div>\n\n <!-- list -->\n <div style=\"grid-area: list\" *ngIf=\"pagination$ | async as pagination\">\n <div *ngIf=\"loading$ | async\" gdArea=\"progress\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n <table>\n <thead>\n <td></td>\n <td></td>\n <th>name</th>\n <th class=\"noif-lt-md\">first</th>\n <th class=\"noif-lt-md\">last</th>\n <th class=\"noif-lt-md\">email</th>\n <th>roles</th>\n <th class=\"noif-lt-md\">lock end</th>\n </thead>\n <tbody>\n <tr *ngFor=\"let user of pagination.data\">\n <td>\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Edit {{ user.userName }}\"\n color=\"primary\"\n (click)=\"setActiveUser(user)\"\n >\n <mat-icon>mode_edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Delete {{ user.userName }}\"\n color=\"warn\"\n (click)=\"deleteUser(user)\"\n >\n <mat-icon>remove_circle</mat-icon>\n </button>\n </td>\n <td>\n <img\n [src]=\"getGravatarUrl(user.email, 32)\"\n [alt]=\"user.userName\"\n />\n </td>\n <td>{{ user.userName }}</td>\n <td class=\"noif-lt-md\">{{ user.firstName }}</td>\n <td class=\"noif-lt-md\">{{ user.lastName }}</td>\n <td class=\"noif-lt-md\">\n <a [href]=\"'mailto:' + user.email\">{{ user.email }}</a>\n </td>\n <td>{{ user.roles.join(\" \") }}</td>\n <td class=\"noif-lt-md\">{{ user.lockoutEnd }}</td>\n </tr>\n </tbody>\n </table>\n\n <!-- paginator -->\n <div style=\"grid-area: paginator; justify-content: end\" class=\"form-row\">\n <button\n type=\"button\"\n mat-icon-button\n color=\"warn\"\n matTooltip=\"Clear items cache\"\n (click)=\"clearCache()\"\n >\n <mat-icon>autorenew</mat-icon>\n </button>\n\n <mat-paginator\n gdArea=\"pager\"\n gdAlignColumns=\"center\"\n gdAlignRows=\"start\"\n [length]=\"pagination.total\"\n [pageSize]=\"pagination.perPage\"\n [pageSizeOptions]=\"[20, 50, 75, 100]\"\n [pageIndex]=\"pagination.currentPage - 1\"\n [showFirstLastButtons]=\"true\"\n (page)=\"pageChange($event)\"\n ></mat-paginator>\n </div>\n </div>\n </div>\n\n <!-- editor -->\n <div style=\"grid-area: editor\" *ngIf=\"active$ | async as active\">\n <fieldset>\n <legend>{{ active.userName }}</legend>\n <auth-jwt-user-editor\n [user]=\"active\"\n (userChange)=\"saveActiveUser($event)\"\n (editorClose)=\"resetActiveUser()\"\n ></auth-jwt-user-editor>\n </fieldset>\n </div>\n</div>\n", styles: ["tr:nth-child(odd){background-color:#f8f8f8}th{padding:0 8px;text-align:left;color:silver;font-weight:400}td{padding:0 8px}td.command{width:24px}table{width:100%;border-collapse:collapse}fieldset{border:1px solid silver;border-radius:8px;padding:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:auto 1fr auto auto;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));grid-template-areas:\"filters\" \"list\" \"paginator\" \"editor\";gap:8px}@media only screen and (max-width: 959px){.noif-lt-md{display:none}}\n"], dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i7$1.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "directive", type: i9$1.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }, { kind: "component", type: UserFilterComponent, selector: "auth-jwt-user-filter", inputs: ["disabled"] }, { kind: "component", type: UserEditorComponent, selector: "auth-jwt-user-editor", inputs: ["user"], outputs: ["userChange", "editorClose"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
921
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: UserListComponent, decorators: [{
951
922
  type: Component,
952
- args: [{ selector: 'auth-jwt-user-list', template: "<div\r\n gdAreas=\"filters | progress | list | pager | editor\"\r\n gdRows=\"80px 64px 2fr 40px 1fr\"\r\n gdColumns=\"1fr\"\r\n gdGap=\"8px\"\r\n>\r\n <div>\r\n <!-- filters -->\r\n <div gdArea=\"filters\">\r\n <auth-jwt-user-filter></auth-jwt-user-filter>\r\n </div>\r\n\r\n <!-- progress -->\r\n <div *ngIf=\"paginator.isLoading$ | async\" gdArea=\"progress\">\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n </div>\r\n\r\n <!-- list -->\r\n <div gdArea=\"list\" *ngIf=\"pagination$ | async as pagination\">\r\n <table>\r\n <thead>\r\n <td></td>\r\n <td></td>\r\n <td></td>\r\n <th>name</th>\r\n <th>first</th>\r\n <th>last</th>\r\n <th>email</th>\r\n <th>roles</th>\r\n <th>lock end</th>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let user of pagination.data\">\r\n <td class=\"command\">\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n matTooltip=\"Edit {{ user.userName }}\"\r\n color=\"primary\"\r\n (click)=\"setActiveUser(user)\"\r\n >\r\n <mat-icon>mode_edit</mat-icon>\r\n </button>\r\n </td>\r\n <td class=\"command\">\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n matTooltip=\"Delete {{ user.userName }}\"\r\n color=\"warn\"\r\n (click)=\"deleteUser(user)\"\r\n >\r\n <mat-icon>remove_circle</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n <img\r\n [src]=\"getGravatarUrl(user.email, 32)\"\r\n [alt]=\"user.userName\"\r\n />\r\n </td>\r\n <td>{{ user.userName }}</td>\r\n <td>{{ user.firstName }}</td>\r\n <td>{{ user.lastName }}</td>\r\n <td>\r\n <a [href]=\"'mailto:' + user.email\">{{ user.email }}</a>\r\n </td>\r\n <td>{{ user.roles?.join(\" \") }}</td>\r\n <td>{{ user.lockoutEnd }}</td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n <!-- pagination -->\r\n <mat-paginator\r\n gdArea=\"pager\"\r\n gdAlignColumns=\"center\"\r\n gdAlignRows=\"start\"\r\n [length]=\"pagination.total\"\r\n [pageSize]=\"pageSize.value\"\r\n [pageSizeOptions]=\"[20, 50, 75, 100]\"\r\n [pageIndex]=\"pagination.currentPage - 1\"\r\n [showFirstLastButtons]=\"true\"\r\n (page)=\"pageChange($event)\"\r\n ></mat-paginator>\r\n </div>\r\n </div>\r\n\r\n <!-- editor -->\r\n <div gdArea=\"editor\" *ngIf=\"active$ | async as active\">\r\n <fieldset>\r\n <legend>{{ active.userName }}</legend>\r\n <auth-jwt-user-editor\r\n [user]=\"active\"\r\n (userChange)=\"saveActiveUser($event)\"\r\n (editorClose)=\"resetActiveUser()\"\r\n ></auth-jwt-user-editor>\r\n </fieldset>\r\n </div>\r\n</div>\r\n", styles: ["tr:nth-child(odd){background-color:#f8f8f8}th{padding:0 8px;text-align:left;color:silver;font-weight:400}td{padding:0 8px}td.command{width:24px}table{width:100%;border-collapse:collapse}fieldset{border:1px solid silver;border-radius:8px;padding:16px}\n"] }]
953
- }], ctorParameters: function () {
954
- return [{ type: i15.PaginatorPlugin, decorators: [{
955
- type: Inject,
956
- args: [USERS_PAGINATOR]
957
- }] }, { type: UsersQuery }, { type: AuthJwtAccountService }, { type: UsersService }, { type: DialogService }, { type: i2$2.GravatarService }, { type: i1$1.UntypedFormBuilder }];
958
- } });
923
+ args: [{ selector: 'auth-jwt-user-list', template: "<div id=\"container\">\n <div>\n <!-- filters -->\n <div style=\"grid-area: filters\">\n <auth-jwt-user-filter></auth-jwt-user-filter>\n </div>\n\n <!-- list -->\n <div style=\"grid-area: list\" *ngIf=\"pagination$ | async as pagination\">\n <div *ngIf=\"loading$ | async\" gdArea=\"progress\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n <table>\n <thead>\n <td></td>\n <td></td>\n <th>name</th>\n <th class=\"noif-lt-md\">first</th>\n <th class=\"noif-lt-md\">last</th>\n <th class=\"noif-lt-md\">email</th>\n <th>roles</th>\n <th class=\"noif-lt-md\">lock end</th>\n </thead>\n <tbody>\n <tr *ngFor=\"let user of pagination.data\">\n <td>\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Edit {{ user.userName }}\"\n color=\"primary\"\n (click)=\"setActiveUser(user)\"\n >\n <mat-icon>mode_edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Delete {{ user.userName }}\"\n color=\"warn\"\n (click)=\"deleteUser(user)\"\n >\n <mat-icon>remove_circle</mat-icon>\n </button>\n </td>\n <td>\n <img\n [src]=\"getGravatarUrl(user.email, 32)\"\n [alt]=\"user.userName\"\n />\n </td>\n <td>{{ user.userName }}</td>\n <td class=\"noif-lt-md\">{{ user.firstName }}</td>\n <td class=\"noif-lt-md\">{{ user.lastName }}</td>\n <td class=\"noif-lt-md\">\n <a [href]=\"'mailto:' + user.email\">{{ user.email }}</a>\n </td>\n <td>{{ user.roles.join(\" \") }}</td>\n <td class=\"noif-lt-md\">{{ user.lockoutEnd }}</td>\n </tr>\n </tbody>\n </table>\n\n <!-- paginator -->\n <div style=\"grid-area: paginator; justify-content: end\" class=\"form-row\">\n <button\n type=\"button\"\n mat-icon-button\n color=\"warn\"\n matTooltip=\"Clear items cache\"\n (click)=\"clearCache()\"\n >\n <mat-icon>autorenew</mat-icon>\n </button>\n\n <mat-paginator\n gdArea=\"pager\"\n gdAlignColumns=\"center\"\n gdAlignRows=\"start\"\n [length]=\"pagination.total\"\n [pageSize]=\"pagination.perPage\"\n [pageSizeOptions]=\"[20, 50, 75, 100]\"\n [pageIndex]=\"pagination.currentPage - 1\"\n [showFirstLastButtons]=\"true\"\n (page)=\"pageChange($event)\"\n ></mat-paginator>\n </div>\n </div>\n </div>\n\n <!-- editor -->\n <div style=\"grid-area: editor\" *ngIf=\"active$ | async as active\">\n <fieldset>\n <legend>{{ active.userName }}</legend>\n <auth-jwt-user-editor\n [user]=\"active\"\n (userChange)=\"saveActiveUser($event)\"\n (editorClose)=\"resetActiveUser()\"\n ></auth-jwt-user-editor>\n </fieldset>\n </div>\n</div>\n", styles: ["tr:nth-child(odd){background-color:#f8f8f8}th{padding:0 8px;text-align:left;color:silver;font-weight:400}td{padding:0 8px}td.command{width:24px}table{width:100%;border-collapse:collapse}fieldset{border:1px solid silver;border-radius:8px;padding:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:auto 1fr auto auto;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));grid-template-areas:\"filters\" \"list\" \"paginator\" \"editor\";gap:8px}@media only screen and (max-width: 959px){.noif-lt-md{display:none}}\n"] }]
924
+ }], ctorParameters: function () { return [{ type: UserListRepository }, { type: DialogService }, { type: i2$2.GravatarService }]; } });
959
925
 
960
926
  class AuthJwtAdminModule {
961
927
  }
962
- AuthJwtAdminModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAdminModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
963
- AuthJwtAdminModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAdminModule, declarations: [AuthJwtRegistrationComponent,
928
+ AuthJwtAdminModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAdminModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
929
+ AuthJwtAdminModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAdminModule, declarations: [AuthJwtRegistrationComponent,
964
930
  PasswordStrengthBarComponent,
965
931
  UserFilterComponent,
966
932
  UserListComponent,
@@ -987,7 +953,7 @@ AuthJwtAdminModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", vers
987
953
  PasswordStrengthBarComponent,
988
954
  UserFilterComponent,
989
955
  UserListComponent] });
990
- AuthJwtAdminModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAdminModule, imports: [CommonModule,
956
+ AuthJwtAdminModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAdminModule, imports: [CommonModule,
991
957
  HttpClientModule,
992
958
  FormsModule,
993
959
  RouterModule,
@@ -1006,7 +972,7 @@ AuthJwtAdminModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", vers
1006
972
  MatTooltipModule,
1007
973
  AuthJwtLoginModule,
1008
974
  NgToolsModule] });
1009
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AuthJwtAdminModule, decorators: [{
975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: AuthJwtAdminModule, decorators: [{
1010
976
  type: NgModule,
1011
977
  args: [{
1012
978
  declarations: [