@sumaris-net/ngx-components 0.24.0 → 0.25.0

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 (50) hide show
  1. package/bundles/{ngx-sumaris-components.umd.js → sumaris-net.ngx-components.umd.js} +898 -872
  2. package/bundles/sumaris-net.ngx-components.umd.js.map +1 -0
  3. package/bundles/sumaris-net.ngx-components.umd.min.js +25 -0
  4. package/bundles/sumaris-net.ngx-components.umd.min.js.map +1 -0
  5. package/esm2015/public_api.js +2 -1
  6. package/esm2015/src/app/admin/services/filter/person.filter.js +4 -2
  7. package/esm2015/src/app/admin/services/person.service.js +3 -2
  8. package/esm2015/src/app/admin/users/list/users.js +6 -4
  9. package/esm2015/src/app/core/graphql/graphql.service.js +17 -63
  10. package/esm2015/src/app/core/graphql/graphql.utils.js +1 -1
  11. package/esm2015/src/app/core/install/install-upgrade-card.component.js +14 -8
  12. package/esm2015/src/app/core/services/account.service.js +146 -149
  13. package/esm2015/src/app/core/services/config.service.js +21 -14
  14. package/esm2015/src/app/core/services/local-settings.service.js +72 -87
  15. package/esm2015/src/app/core/services/network.service.js +135 -170
  16. package/esm2015/src/app/core/services/platform.service.js +26 -14
  17. package/esm2015/src/app/core/services/storage/entities-storage.service.js +59 -63
  18. package/esm2015/src/app/shared/audio/audio.js +10 -39
  19. package/esm2015/src/app/shared/functions.js +6 -7
  20. package/esm2015/src/app/shared/material/boolean/material.boolean.js +1 -1
  21. package/esm2015/src/app/shared/services/startable-service.class.js +74 -0
  22. package/esm2015/src/environments/environment.class.js +1 -1
  23. package/esm2015/src/environments/environment.js +5 -1
  24. package/esm2015/sumaris-net.ngx-components.js +13 -0
  25. package/fesm2015/{ngx-sumaris-components.js → sumaris-net.ngx-components.js} +585 -618
  26. package/fesm2015/sumaris-net.ngx-components.js.map +1 -0
  27. package/package.json +13 -9
  28. package/public_api.d.ts +1 -0
  29. package/src/app/admin/services/filter/person.filter.d.ts +1 -0
  30. package/src/app/admin/users/list/users.d.ts +1 -0
  31. package/src/app/core/graphql/graphql.service.d.ts +5 -13
  32. package/src/app/core/graphql/graphql.utils.d.ts +2 -1
  33. package/src/app/core/services/account.service.d.ts +11 -10
  34. package/src/app/core/services/config.service.d.ts +6 -5
  35. package/src/app/core/services/local-settings.service.d.ts +4 -9
  36. package/src/app/core/services/network.service.d.ts +27 -38
  37. package/src/app/core/services/platform.service.d.ts +1 -1
  38. package/src/app/core/services/storage/entities-storage.service.d.ts +13 -13
  39. package/src/app/shared/audio/audio.d.ts +4 -8
  40. package/src/app/shared/functions.d.ts +1 -1
  41. package/src/app/shared/services/startable-service.class.d.ts +25 -0
  42. package/src/environments/environment.class.d.ts +1 -0
  43. package/{ngx-sumaris-components.d.ts → sumaris-net.ngx-components.d.ts} +0 -0
  44. package/sumaris-net.ngx-components.metadata.json +1 -0
  45. package/bundles/ngx-sumaris-components.umd.js.map +0 -1
  46. package/bundles/ngx-sumaris-components.umd.min.js +0 -25
  47. package/bundles/ngx-sumaris-components.umd.min.js.map +0 -1
  48. package/esm2015/ngx-sumaris-components.js +0 -13
  49. package/fesm2015/ngx-sumaris-components.js.map +0 -1
  50. package/ngx-sumaris-components.metadata.json +0 -1
@@ -36,7 +36,7 @@ import { MatAutocomplete, MatAutocompleteTrigger, MatAutocompleteModule, MAT_AUT
36
36
  import { __awaiter, __decorate } from 'tslib';
37
37
  import * as i1$6 from '@angular/forms';
38
38
  import { NG_VALUE_ACCESSOR, FormGroupDirective, ReactiveFormsModule, AbstractControl, Validators, FormBuilder, FormControl, FormGroup, FormArray } from '@angular/forms';
39
- import { timer, merge, fromEvent, Subscription, BehaviorSubject, Subject, isObservable, of, noop as noop$8, Observable, defer, concat, combineLatest, EMPTY } from 'rxjs';
39
+ import { timer, merge, fromEvent, Subscription, BehaviorSubject, Subject, isObservable, of, noop as noop$8, Observable, defer, combineLatest, EMPTY } from 'rxjs';
40
40
  import { filter, first, map, takeUntil, startWith, debounceTime, takeWhile, switchMap, tap, distinctUntilChanged, mergeMap, catchError, throttleTime } from 'rxjs/operators';
41
41
  import * as i1$1 from '@ngx-translate/core';
42
42
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
@@ -337,16 +337,15 @@ function propertiesPathComparator(keys, defaultValues) {
337
337
  // Stop if not equals, otherwise continue with the next key
338
338
  .find(res => res !== 0) || 0;
339
339
  }
340
- function sort(array, attribute) {
340
+ function sort(array, attribute, opts) {
341
341
  if (isEmptyArray(array))
342
342
  return array;
343
+ const compareFn = opts
344
+ ? new Intl.Collator(undefined, opts).compare
345
+ : (v1, v2) => v1 === v2 ? 0 : v1 > v2 ? 1 : -1;
343
346
  return array
344
347
  .slice() // copy
345
- .sort((a, b) => {
346
- const valueA = a[attribute];
347
- const valueB = b[attribute];
348
- return valueA === valueB ? 0 : (valueA > valueB ? 1 : -1);
349
- });
348
+ .sort((a, b) => compareFn(a[attribute], b[attribute]));
350
349
  }
351
350
  const INTEGER_REGEXP = /^[-]?\d+$/;
352
351
  function isInt(value) {
@@ -4967,7 +4966,7 @@ MatBooleanField.decorators = [
4967
4966
  DEFAULT_VALUE_ACCESSOR$1
4968
4967
  ],
4969
4968
  changeDetection: ChangeDetectionStrategy.OnPush,
4970
- styles: [":host{width:100%}.mat-form-field.mat-boolean-field-radio{margin-top:-2px}.mat-form-field.mat-boolean-field-button{margin-top:-4px}.mat-form-field .mat-form-field-buttons{width:100%;display:flex;flex-flow:row wrap;justify-content:flex-start;align-items:center}.mat-form-field .mat-form-field-buttons .mat-form-field-button{--flex-basis:calc(100% / var(--buttons-col-count, 2));flex:0 1 var(--flex-basis);max-width:calc(min(var(--flex-basis), 150px));white-space:normal;padding:0 2px;height:40px}"]
4969
+ styles: [":host{width:100%}.mat-form-field.mat-boolean-field-radio{margin-top:-2px}.mat-form-field.mat-boolean-field-button{margin-top:-4px}.mat-form-field .mat-form-field-buttons{width:100%;display:flex;flex-flow:row wrap;justify-content:flex-start;align-items:center}.mat-form-field .mat-form-field-buttons .mat-form-field-button{--flex-basis:calc(100% / var(--buttons-col-count, 2));flex:0 1 var(--flex-basis);max-width:min(var(--flex-basis),150px);white-space:normal;padding:0 2px;height:40px}"]
4971
4970
  },] }
4972
4971
  ];
4973
4972
  MatBooleanField.ctorParameters = () => [
@@ -6889,31 +6888,94 @@ AppFormField.propDecorators = {
6889
6888
  matInput: [{ type: ViewChild, args: ['matInput',] }]
6890
6889
  };
6891
6890
 
6891
+ class StartableService {
6892
+ constructor(platform) {
6893
+ this.onStart = new Subject();
6894
+ this._debug = false;
6895
+ this._data = null;
6896
+ this._started = false;
6897
+ this._startPrerequisite = platform
6898
+ ? () => platform.ready()
6899
+ : () => Promise.resolve();
6900
+ }
6901
+ start() {
6902
+ if (this._startPromise)
6903
+ return this._startPromise;
6904
+ if (this._started)
6905
+ return Promise.resolve(this._data);
6906
+ this._startPromise = this._startPrerequisite()
6907
+ .then(() => this.ngOnStart())
6908
+ .then(data => {
6909
+ this._data = data;
6910
+ this._started = true;
6911
+ this._startPromise = undefined;
6912
+ this.onStart.next(this._data);
6913
+ return this._data;
6914
+ })
6915
+ .catch(err => {
6916
+ console.error('Failed to start a service: ' + (err && err.message || err), err);
6917
+ this._started = false;
6918
+ this._startPromise = null;
6919
+ return null;
6920
+ });
6921
+ return this._startPromise;
6922
+ }
6923
+ stop() {
6924
+ return __awaiter(this, void 0, void 0, function* () {
6925
+ try {
6926
+ yield this.ngOnStop();
6927
+ this._started = false;
6928
+ this._startPromise = undefined;
6929
+ }
6930
+ catch (err) {
6931
+ console.error('Failed to stop a service: ' + (err && err.message || err), err);
6932
+ }
6933
+ });
6934
+ }
6935
+ restart() {
6936
+ return __awaiter(this, void 0, void 0, function* () {
6937
+ if (this._startPromise)
6938
+ yield this._startPromise; // Wait end of previous loading
6939
+ if (this._started)
6940
+ yield this.stop(); // Then stop if started
6941
+ return this.start(); // Then start again
6942
+ });
6943
+ }
6944
+ get started() {
6945
+ return this._started;
6946
+ }
6947
+ ready() {
6948
+ if (this._started)
6949
+ return Promise.resolve(this._data);
6950
+ return this.start();
6951
+ }
6952
+ ngOnStop() {
6953
+ return __awaiter(this, void 0, void 0, function* () {
6954
+ // Can be override by subclasses
6955
+ });
6956
+ }
6957
+ }
6958
+ StartableService.ctorParameters = () => [
6959
+ { type: undefined, decorators: [{ type: Optional }] }
6960
+ ];
6961
+
6892
6962
  const SYSTEM_SOUNDS = [
6893
6963
  { id: 'beep-confirm', assetPath: 'assets/audio/beep-confirm.mp3', vibration: 250 },
6894
6964
  { id: 'beep-error', assetPath: 'assets/audio/beep-error.mp3', vibration: 1000 },
6895
6965
  { id: 'startup', assetPath: 'assets/audio/unfa-ping.mp3', vibration: [1, 500, 250, 750] },
6896
6966
  ];
6897
- class AudioProvider {
6967
+ class AudioProvider extends StartableService {
6898
6968
  constructor(platform, nativeAudio, vibration, audioManagement) {
6969
+ super(platform);
6899
6970
  this.platform = platform;
6900
6971
  this.nativeAudio = nativeAudio;
6901
6972
  this.vibration = vibration;
6902
6973
  this.audioManagement = audioManagement;
6903
- this._started = false;
6904
6974
  this._audioMode = AudioManagement.AudioMode.NORMAL;
6905
6975
  this._preloadedSounds = {};
6906
6976
  this._htmlAudioCache = {};
6907
- this.onStart = new Subject();
6908
6977
  this.start();
6909
6978
  }
6910
- ready() {
6911
- return __awaiter(this, void 0, void 0, function* () {
6912
- if (this._started)
6913
- return Promise.resolve();
6914
- yield this.start();
6915
- });
6916
- }
6917
6979
  playBeepConfirm() {
6918
6980
  return this.play('beep-confirm', {
6919
6981
  // Vibrate only if in vibration mode
@@ -6955,7 +7017,7 @@ class AudioProvider {
6955
7017
  play(id, opts) {
6956
7018
  return __awaiter(this, void 0, void 0, function* () {
6957
7019
  // Make sure provider is ready
6958
- if (!this._started)
7020
+ if (!this.started)
6959
7021
  yield this.ready();
6960
7022
  const sound = this._preloadedSounds[id];
6961
7023
  if (!sound) {
@@ -7016,7 +7078,7 @@ class AudioProvider {
7016
7078
  }
7017
7079
  vibrate(timeInMs) {
7018
7080
  return __awaiter(this, void 0, void 0, function* () {
7019
- if (!this._started)
7081
+ if (!this.started)
7020
7082
  yield this.ready();
7021
7083
  if (!this.vibration)
7022
7084
  return; // Skip if vibrate plugin
@@ -7024,45 +7086,24 @@ class AudioProvider {
7024
7086
  });
7025
7087
  }
7026
7088
  /* -- protected methods -- */
7027
- start() {
7028
- if (this._startPromise)
7029
- return this._startPromise;
7030
- if (this._started)
7031
- return Promise.resolve();
7032
- let cordova;
7033
- this._startPromise = this.platform.ready()
7034
- .then(() => __awaiter(this, void 0, void 0, function* () {
7035
- cordova = this.platform.is('cordova');
7089
+ ngOnStart() {
7090
+ return __awaiter(this, void 0, void 0, function* () {
7091
+ let cordova = this.platform.is('cordova');
7036
7092
  this._audioType = cordova && this.nativeAudio ? 'native' : 'html5';
7037
7093
  console.info(`[audio] Starting audio provider {${this._audioType}}...`);
7038
7094
  // Listen audio mode changed
7039
7095
  if (cordova && this.audioManagement) {
7040
7096
  yield this.readAudioMode();
7041
7097
  }
7042
- }))
7043
7098
  // Pre-loading system sounds
7044
- .then(() => {
7045
7099
  console.debug('[audio] Preloading audio sounds...');
7046
- return Promise.all(SYSTEM_SOUNDS.map(s => {
7100
+ yield Promise.all(SYSTEM_SOUNDS.map(s => {
7047
7101
  // Disable vibration is cordova not enabled
7048
7102
  if (!cordova)
7049
7103
  s.vibration = undefined;
7050
7104
  return this.preload(s);
7051
7105
  }));
7052
- })
7053
- .then(() => {
7054
- this._started = true;
7055
- this._startPromise = null;
7056
- console.info('[audio] Audio provider started');
7057
- // Emit event
7058
- this.onStart.next();
7059
- })
7060
- .catch(err => {
7061
- console.error('[audio] Unable to start audio provider: ' + (err && err.message || err), err);
7062
- this._started = false;
7063
- this._startPromise = null;
7064
7106
  });
7065
- return this._startPromise;
7066
7107
  }
7067
7108
  readAudioMode() {
7068
7109
  return __awaiter(this, void 0, void 0, function* () {
@@ -7981,6 +8022,10 @@ const environment = Object.freeze({
7981
8022
  {
7982
8023
  host: 'sih.sfa.sc',
7983
8024
  port: 80
8025
+ },
8026
+ {
8027
+ host: 'test.sumaris.net',
8028
+ port: 443
7984
8029
  }
7985
8030
  ],
7986
8031
  defaultAppName: 'SUMARiS',
@@ -8509,14 +8554,14 @@ const DEFAULT_SETTINGS = {
8509
8554
  };
8510
8555
  const APP_LOCAL_SETTINGS = new InjectionToken('DefaultLocalSettings');
8511
8556
  const APP_LOCAL_SETTINGS_OPTIONS = new InjectionToken('LocalSettingsOptions');
8512
- class LocalSettingsService {
8557
+ class LocalSettingsService extends StartableService {
8513
8558
  constructor(translate, platform, storage, environment, defaultSettings, defaultOptionsMap) {
8559
+ super(platform);
8514
8560
  this.translate = translate;
8515
8561
  this.platform = platform;
8516
8562
  this.storage = storage;
8517
8563
  this.environment = environment;
8518
8564
  this.defaultSettings = defaultSettings;
8519
- this._started = false;
8520
8565
  this.onChange = new Subject();
8521
8566
  this.defaultSettings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), this.defaultSettings);
8522
8567
  this._optionDefs = Object.values(defaultOptionsMap);
@@ -8526,60 +8571,40 @@ class LocalSettingsService {
8526
8571
  console.debug('[settings] Creating service');
8527
8572
  }
8528
8573
  get settings() {
8529
- return this.data || this.defaultSettings;
8574
+ return this._data || this.defaultSettings;
8530
8575
  }
8531
8576
  get locale() {
8532
- return this.data && this.data.locale || this.translate.currentLang || this.translate.defaultLang;
8577
+ return this._data && this._data.locale || this.translate.currentLang || this.translate.defaultLang;
8533
8578
  }
8534
8579
  get latLongFormat() {
8535
- return this.data && this.data.latLongFormat || 'DDMM';
8580
+ return this._data && this._data.latLongFormat || 'DDMM';
8536
8581
  }
8537
8582
  get usageMode() {
8538
- return (this.data && this.data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8583
+ return (this._data && this._data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8539
8584
  }
8540
8585
  get mobile() {
8541
- return this.data && toBoolean(this.data.mobile, this.platform.is('mobile'));
8586
+ return this._data && toBoolean(this._data.mobile, this.platform.is('mobile'));
8542
8587
  }
8543
8588
  set mobile(value) {
8544
- this.data.mobile = value;
8589
+ this._data.mobile = value;
8545
8590
  }
8546
8591
  get touchUi() {
8547
- return this.data.touchUi;
8592
+ return this._data.touchUi;
8548
8593
  }
8549
8594
  set touchUi(value) {
8550
- this.data.touchUi = value;
8595
+ this._data.touchUi = value;
8551
8596
  }
8552
8597
  get pageHistory() {
8553
- return (this.data && this.data.pageHistory || []);
8598
+ return (this._data && this._data.pageHistory || []);
8554
8599
  }
8555
- start() {
8556
- if (this._startPromise)
8557
- return this._startPromise;
8558
- if (this._started)
8559
- return Promise.resolve(this.data);
8560
- console.info('[settings] Starting settings...');
8600
+ ngOnStart() {
8601
+ console.info('[settings] Starting service...');
8561
8602
  // Restoring local settings
8562
- this._startPromise = this.platform.ready()
8563
- .then(() => {
8564
- this.data.mobile = isNotNil(this.data.mobile) ? this.data.mobile : this.platform.is('mobile');
8565
- this.data.touchUi = this.data.mobile || this.platform.is('phablet') || this.platform.is('tablet');
8566
- this.data.usageMode = this.platform.is('android') ? 'FIELD' : 'DESK'; // FIELD by default if Android
8567
- })
8568
- .then(() => this.restoreLocally())
8569
- .then(data => {
8570
- this._started = true;
8571
- this._startPromise = undefined;
8572
- return data;
8573
- });
8574
- return this._startPromise;
8575
- }
8576
- get started() {
8577
- return this._started;
8578
- }
8579
- ready() {
8580
- if (this._started)
8581
- return Promise.resolve(this.data);
8582
- return this.start();
8603
+ this._data.mobile = isNotNil(this._data.mobile) ? this._data.mobile : this.platform.is('mobile');
8604
+ this._data.touchUi = this._data.mobile || this.platform.is('phablet') || this.platform.is('tablet');
8605
+ this._data.usageMode = this.platform.is('android') ? 'FIELD' : 'DESK'; // FIELD by default if Android
8606
+ // Restoring local settings
8607
+ return this.restoreLocally();
8583
8608
  }
8584
8609
  isUsageMode(mode) {
8585
8610
  return this.usageMode === mode;
@@ -8589,6 +8614,7 @@ class LocalSettingsService {
8589
8614
  }
8590
8615
  restoreLocally() {
8591
8616
  return __awaiter(this, void 0, void 0, function* () {
8617
+ let data = this._data || {};
8592
8618
  // Restore from storage
8593
8619
  const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
8594
8620
  // Restore local settings (or keep old settings)
@@ -8599,28 +8625,29 @@ class LocalSettingsService {
8599
8625
  SETTINGS_TRANSIENT_PROPERTIES.forEach(transientKey => {
8600
8626
  delete restoredData[transientKey];
8601
8627
  });
8602
- this.data = Object.assign(this.data, restoredData);
8628
+ // Merge into existing data
8629
+ data = Object.assign(data, restoredData);
8603
8630
  }
8604
8631
  // Emit event
8605
- this.onChange.next(this.data);
8606
- return this.data;
8632
+ this.onChange.next(data);
8633
+ return data;
8607
8634
  });
8608
8635
  }
8609
8636
  setProperty(keyOrDef, value) {
8610
- if (!this.data)
8637
+ if (!this._data)
8611
8638
  return;
8612
8639
  if (typeof keyOrDef === 'object') {
8613
8640
  this.setProperty(keyOrDef.key, value);
8614
8641
  return;
8615
8642
  }
8616
- this.data.properties = this.data.properties || {};
8617
- this.data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8643
+ this._data.properties = this._data.properties || {};
8644
+ this._data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8618
8645
  }
8619
8646
  getProperty(keyOrDef, defaultValue) {
8620
8647
  if (typeof keyOrDef === 'object') {
8621
8648
  return this.getProperty(keyOrDef.key, isNil(defaultValue) ? keyOrDef.defaultValue : defaultValue);
8622
8649
  }
8623
- const value = this.data && this.data.properties && this.data.properties[keyOrDef];
8650
+ const value = this._data && this._data.properties && this._data.properties[keyOrDef];
8624
8651
  return isNotNil(value) ? value : defaultValue;
8625
8652
  }
8626
8653
  getPropertyAsBoolean(definition, defaultValue) {
@@ -8643,7 +8670,9 @@ class LocalSettingsService {
8643
8670
  }
8644
8671
  apply(settings, opts) {
8645
8672
  return __awaiter(this, void 0, void 0, function* () {
8646
- this.data = Object.assign(Object.assign({}, this.data), settings);
8673
+ if (!this.started)
8674
+ yield this.ready();
8675
+ this._data = Object.assign(Object.assign({}, this._data), settings);
8647
8676
  // Save locally
8648
8677
  if (opts && opts.persistImmediate) {
8649
8678
  yield this.persistLocally(true);
@@ -8653,7 +8682,7 @@ class LocalSettingsService {
8653
8682
  }
8654
8683
  // Emit event
8655
8684
  if (!opts || opts.emitEvent !== false) {
8656
- this.onChange.next(this.data);
8685
+ this.onChange.next(this._data);
8657
8686
  }
8658
8687
  });
8659
8688
  }
@@ -8665,38 +8694,38 @@ class LocalSettingsService {
8665
8694
  });
8666
8695
  }
8667
8696
  getPageSettings(pageId, propertyName) {
8668
- if (!this.data || !this.data.pages)
8697
+ if (!this._data || !this._data.pages)
8669
8698
  return undefined;
8670
8699
  const key = pageId.replace(/[/]/g, '__');
8671
8700
  if (isNotNilOrBlank(propertyName)) {
8672
- return getPropertyByPath(this.data.pages, key + '.' + propertyName);
8701
+ return getPropertyByPath(this._data.pages, key + '.' + propertyName);
8673
8702
  }
8674
- return this.data.pages[key];
8703
+ return this._data.pages[key];
8675
8704
  }
8676
8705
  savePageSetting(pageId, value, propertyName) {
8677
8706
  return __awaiter(this, void 0, void 0, function* () {
8678
- this.data = this.data || this.defaultSettings;
8679
- this.data.pages = this.data.pages || {};
8707
+ this._data = this._data || this.defaultSettings;
8708
+ this._data.pages = this._data.pages || {};
8680
8709
  const key = pageId.replace(/[/]/g, '__');
8681
8710
  if (propertyName) {
8682
- this.data.pages[key] = this.data.pages[key] || {};
8683
- this.data.pages[key][propertyName] = value;
8711
+ this._data.pages[key] = this._data.pages[key] || {};
8712
+ this._data.pages[key][propertyName] = value;
8684
8713
  }
8685
8714
  else {
8686
- this.data.pages[key] = value;
8715
+ this._data.pages[key] = value;
8687
8716
  }
8688
8717
  // Update local settings
8689
8718
  this.persistLocally();
8690
8719
  });
8691
8720
  }
8692
8721
  getOfflineFeature(featureName) {
8693
- if (!this.data || !this.data.offlineFeatures || isEmptyArray(this.data.offlineFeatures))
8722
+ if (!this._data || !this._data.offlineFeatures || isEmptyArray(this._data.offlineFeatures))
8694
8723
  return undefined;
8695
8724
  if (!featureName)
8696
8725
  throw Error('Missing \'featureName\' argument');
8697
8726
  featureName = featureName.toLowerCase();
8698
8727
  const featurePrefix = featureName + '#';
8699
- const feature = this.data.offlineFeatures.find(f => {
8728
+ const feature = this._data.offlineFeatures.find(f => {
8700
8729
  if (typeof f === 'string')
8701
8730
  return f.toLowerCase().startsWith(featurePrefix);
8702
8731
  if (typeof f === 'object' && f.name)
@@ -8719,24 +8748,24 @@ class LocalSettingsService {
8719
8748
  hasOfflineFeature(featureName) {
8720
8749
  if (featureName)
8721
8750
  return isNotNil(this.getOfflineFeature(featureName));
8722
- return this.data && isNotEmptyArray(this.data.offlineFeatures);
8751
+ return this._data && isNotEmptyArray(this._data.offlineFeatures);
8723
8752
  }
8724
8753
  saveOfflineFeature(feature) {
8725
- this.data = this.data || this.defaultSettings;
8726
- this.data.offlineFeatures = this.data.offlineFeatures || [];
8754
+ this._data = this._data || this.defaultSettings;
8755
+ this._data.offlineFeatures = this._data.offlineFeatures || [];
8727
8756
  feature.name = feature.name.toLowerCase();
8728
8757
  const featurePrefix = feature.name + '#';
8729
- const existingIndex = this.data.offlineFeatures.findIndex(f => {
8758
+ const existingIndex = this._data.offlineFeatures.findIndex(f => {
8730
8759
  if (typeof f === 'string')
8731
8760
  return f.toLowerCase().startsWith(featurePrefix);
8732
8761
  if (typeof f === 'object' && f.name)
8733
8762
  return f.name === feature.name;
8734
8763
  });
8735
8764
  if (existingIndex !== -1) {
8736
- this.data.offlineFeatures[existingIndex] = feature;
8765
+ this._data.offlineFeatures[existingIndex] = feature;
8737
8766
  }
8738
8767
  else {
8739
- this.data.offlineFeatures.push(feature);
8768
+ this._data.offlineFeatures.push(feature);
8740
8769
  }
8741
8770
  // Update local settings
8742
8771
  this.persistLocally();
@@ -8755,14 +8784,14 @@ class LocalSettingsService {
8755
8784
  this.saveOfflineFeature(feature);
8756
8785
  }
8757
8786
  removeOfflineFeatures() {
8758
- if (this.data && this.data.offlineFeatures) {
8759
- this.data.offlineFeatures = [];
8787
+ if (this._data && this._data.offlineFeatures) {
8788
+ this._data.offlineFeatures = [];
8760
8789
  // Update local settings
8761
8790
  this.persistLocally();
8762
8791
  }
8763
8792
  }
8764
8793
  getFieldDisplayAttributes(fieldName, defaultAttributes) {
8765
- const value = this.data && this.data.properties && this.data.properties[`sumaris.field.${fieldName}.attributes`];
8794
+ const value = this._data && this._data.properties && this._data.properties[`sumaris.field.${fieldName}.attributes`];
8766
8795
  // Nothing found in settings: return defaults
8767
8796
  if (!value)
8768
8797
  return defaultAttributes || ['label', 'name'];
@@ -8791,7 +8820,7 @@ class LocalSettingsService {
8791
8820
  // If not inside recursive call: fill page history defaults
8792
8821
  if (!pageHistory)
8793
8822
  this.fillPageHistoryDefaults(page, opts);
8794
- pageHistory = pageHistory || this.data.pageHistory;
8823
+ pageHistory = pageHistory || this._data.pageHistory;
8795
8824
  const index = pageHistory.findIndex(p => (
8796
8825
  // same path
8797
8826
  p.path === page.path
@@ -8827,10 +8856,10 @@ class LocalSettingsService {
8827
8856
  }
8828
8857
  }
8829
8858
  // Save locally (only if not a recursive execution)
8830
- if (pageHistory === this.data.pageHistory) {
8859
+ if (pageHistory === this._data.pageHistory) {
8831
8860
  // If max has been reached, remove old pages
8832
- if (this.data.pageHistory.length > this.data.pageHistoryMaxSize) {
8833
- const removedPages = pageHistory.splice(this.data.pageHistoryMaxSize, pageHistory.length - this.data.pageHistoryMaxSize);
8861
+ if (this._data.pageHistory.length > this._data.pageHistoryMaxSize) {
8862
+ const removedPages = pageHistory.splice(this._data.pageHistoryMaxSize, pageHistory.length - this._data.pageHistoryMaxSize);
8834
8863
  console.debug('[settings] Pages removed from history: ', removedPages);
8835
8864
  }
8836
8865
  // Apply new value
@@ -8841,7 +8870,7 @@ class LocalSettingsService {
8841
8870
  removePageHistory(path, opts, pageHistory // used for recursive call to children)
8842
8871
  ) {
8843
8872
  return __awaiter(this, void 0, void 0, function* () {
8844
- pageHistory = pageHistory || this.data.pageHistory;
8873
+ pageHistory = pageHistory || this._data.pageHistory;
8845
8874
  const index = pageHistory.findIndex(p => p.path === path);
8846
8875
  let found = index !== -1;
8847
8876
  if (found) {
@@ -8857,9 +8886,9 @@ class LocalSettingsService {
8857
8886
  .findIndex(children => this.removePageHistory(path, opts, children)) !== -1;
8858
8887
  }
8859
8888
  // Save locally (only if not a recursive execution)
8860
- if (found && pageHistory === this.data.pageHistory) {
8889
+ if (found && pageHistory === this._data.pageHistory) {
8861
8890
  // Apply changes
8862
- yield this.applyProperty('pageHistory', this.data.pageHistory);
8891
+ yield this.applyProperty('pageHistory', this._data.pageHistory);
8863
8892
  }
8864
8893
  return found;
8865
8894
  });
@@ -8872,26 +8901,26 @@ class LocalSettingsService {
8872
8901
  }
8873
8902
  /* -- Protected methods -- */
8874
8903
  resetData() {
8875
- this.data = Object.assign(Object.assign({}, this.data), this.defaultSettings);
8876
- this.data.locale = this.translate.currentLang || this.translate.defaultLang;
8877
- this.data.mobile = undefined;
8878
- this.data.usageMode = undefined;
8879
- this.data.pageHistory = [];
8904
+ this._data = Object.assign(Object.assign({}, this._data), this.defaultSettings);
8905
+ this._data.locale = this.translate.currentLang || this.translate.defaultLang;
8906
+ this._data.mobile = undefined;
8907
+ this._data.usageMode = undefined;
8908
+ this._data.pageHistory = [];
8880
8909
  const defaultPeer = this.environment.defaultPeer && Peer.fromObject(this.environment.defaultPeer);
8881
- this.data.peerUrl = defaultPeer && defaultPeer.url || undefined;
8882
- if (this._started)
8883
- this.onChange.next(this.data);
8910
+ this._data.peerUrl = defaultPeer && defaultPeer.url || undefined;
8911
+ if (this.started)
8912
+ this.onChange.next(this._data);
8884
8913
  }
8885
8914
  persistLocally(immediate) {
8886
8915
  // Execute immediate
8887
8916
  if (immediate) {
8888
- if (!this.data) {
8917
+ if (!this._data) {
8889
8918
  console.debug('[settings] Removing local settings from storage');
8890
8919
  return this.storage.remove(SETTINGS_STORAGE_KEY);
8891
8920
  }
8892
8921
  else {
8893
- console.debug('[settings] Store local settings', this.data);
8894
- return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this.data));
8922
+ console.debug('[settings] Store local settings', this._data);
8923
+ return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this._data));
8895
8924
  }
8896
8925
  }
8897
8926
  // Execute with delay
@@ -8901,7 +8930,7 @@ class LocalSettingsService {
8901
8930
  this._$persist = new EventEmitter(true);
8902
8931
  this._$persist
8903
8932
  .pipe(debounceTime(2000), // add a delay of 2s
8904
- filter(() => this._started))
8933
+ filter(() => this.started))
8905
8934
  .subscribe(() => this.persistLocally(true));
8906
8935
  }
8907
8936
  this._$persist.emit();
@@ -9063,6 +9092,8 @@ function getConnectionType(type) {
9063
9092
  switch (type) {
9064
9093
  case Connection.NONE:
9065
9094
  return 'none';
9095
+ case Connection.ETHERNET:
9096
+ return 'ethernet';
9066
9097
  case Connection.WIFI:
9067
9098
  return 'wifi';
9068
9099
  case Connection.CELL:
@@ -9070,8 +9101,6 @@ function getConnectionType(type) {
9070
9101
  case Connection.CELL_3G:
9071
9102
  case Connection.CELL_4G:
9072
9103
  return 'cell';
9073
- case Connection.ETHERNET:
9074
- return 'ethernet';
9075
9104
  case Connection.UNKNOWN:
9076
9105
  return 'unknown';
9077
9106
  default:
@@ -9087,8 +9116,9 @@ const NetworkRefreshTimerPeriod = {
9087
9116
  MOBILE: 1000,
9088
9117
  DESKTOP: 1000
9089
9118
  }*/
9090
- class NetworkService {
9119
+ class NetworkService extends StartableService {
9091
9120
  constructor(_document, platform, modalCtrl, cryptoService, storage, settings, cache, http, environment, network, splashScreen, translate, toastController) {
9121
+ super(platform);
9092
9122
  this._document = _document;
9093
9123
  this.platform = platform;
9094
9124
  this.modalCtrl = modalCtrl;
@@ -9102,13 +9132,11 @@ class NetworkService {
9102
9132
  this.splashScreen = splashScreen;
9103
9133
  this.translate = translate;
9104
9134
  this.toastController = toastController;
9105
- this._started = false;
9106
- this._subscription = new Subscription();
9107
- this._listeners = {};
9108
- this.onStart = new Subject();
9109
9135
  this.onPeerChanges = this.onStart.pipe(map(peer => peer && peer.url), filter(isNotNilOrBlank), distinctUntilChanged());
9110
9136
  this.onNetworkStatusChanges = new BehaviorSubject(null);
9111
9137
  this.onResetNetworkCache = new EventEmitter(true);
9138
+ this._subscription = new Subscription();
9139
+ this._listeners = {};
9112
9140
  this._mobile = this.platform.is('mobile');
9113
9141
  if (this._mobile) {
9114
9142
  this._timerRefreshPeriod = NetworkRefreshTimerPeriod.MOBILE;
@@ -9119,6 +9147,7 @@ class NetworkService {
9119
9147
  this._timerRefreshCondition = () => true; // Always check
9120
9148
  }
9121
9149
  this.resetData();
9150
+ this.onStart.subscribe(() => this.ngOnAfterStart());
9122
9151
  // For DEV only
9123
9152
  this._debug = !environment.production;
9124
9153
  }
@@ -9132,16 +9161,14 @@ class NetworkService {
9132
9161
  // If force offline: return 'none'
9133
9162
  return this._forceOffline && 'none'
9134
9163
  // Else, return device connection type (or unknown)
9135
- || (this._started && this._deviceConnectionType || 'unknown');
9164
+ || (this.started && this._deviceConnectionType || 'unknown');
9136
9165
  }
9137
9166
  get peer() {
9138
- return this._peer && this._peer.clone();
9167
+ return this._data && this._data.clone();
9139
9168
  }
9140
9169
  set peer(peer) {
9141
- this.restart(peer);
9142
- }
9143
- get started() {
9144
- return this._started;
9170
+ this._startingPeer = peer;
9171
+ this.restart();
9145
9172
  }
9146
9173
  /**
9147
9174
  * Register to network event
@@ -9163,78 +9190,6 @@ class NetworkService {
9163
9190
  return this.addListener(eventType, callback);
9164
9191
  }
9165
9192
  }
9166
- start(peer) {
9167
- return __awaiter(this, void 0, void 0, function* () {
9168
- if (this._startPromise)
9169
- return this._startPromise;
9170
- if (this._started)
9171
- return;
9172
- console.info('[network] Starting network...');
9173
- // Restoring local settings
9174
- this._startPromise = (!peer && this.restoreLocally() || Promise.resolve(peer))
9175
- .then((peer) => __awaiter(this, void 0, void 0, function* () {
9176
- // Make sure to hide the splashscreen, before open the modal
9177
- if (!peer && this.splashScreen)
9178
- this.splashScreen.hide();
9179
- // No peer in settings: ask user to choose
9180
- while (!peer) {
9181
- console.debug('[network] No peer defined. Asking user to choose a peer.');
9182
- peer = yield this.showSelectPeerModal({ allowSelectDownPeer: false });
9183
- }
9184
- this._peer = peer;
9185
- this._started = true;
9186
- this._startPromise = undefined;
9187
- this.onStart.next(peer);
9188
- console.info(`[platform] Starting network [OK] {online: ${this.online}}`);
9189
- }))
9190
- .catch((err) => {
9191
- console.error(err && err.message || err, err);
9192
- this._started = false;
9193
- this._startPromise = undefined;
9194
- })
9195
- // Wait settings starts, then save peer in settings
9196
- .then(() => this.settings.ready())
9197
- .then(() => this.settings.apply({ peerUrl: this._peer.url }))
9198
- .then(() => this.onDeviceConnectionChanged(this.network && this.network.type || 'unknown'))
9199
- // Start the refresh timer
9200
- .then(() => this.startRefreshTimer());
9201
- // Listen for device network changes
9202
- if (this.network) {
9203
- this._subscription.add(this.network.onDisconnect().subscribe(() => this.onDeviceConnectionChanged('none')));
9204
- this._subscription.add(this.network.onConnect().subscribe(() => this.onDeviceConnectionChanged(this.network.type)));
9205
- }
9206
- return this._startPromise;
9207
- });
9208
- }
9209
- ready() {
9210
- if (this._started)
9211
- return Promise.resolve();
9212
- return this.start();
9213
- }
9214
- stop() {
9215
- return __awaiter(this, void 0, void 0, function* () {
9216
- this.resetData();
9217
- this._started = false;
9218
- this._startPromise = undefined;
9219
- // Stop timer if cannot refresh anymore
9220
- if (this._timerRefreshCondition() === false) {
9221
- this.stopRefreshTimer();
9222
- }
9223
- this._subscription.unsubscribe();
9224
- this._subscription = new Subscription();
9225
- });
9226
- }
9227
- restart(peer) {
9228
- return __awaiter(this, void 0, void 0, function* () {
9229
- if (this._started) {
9230
- yield this.stop()
9231
- .then(() => this.start(peer));
9232
- }
9233
- else {
9234
- yield this.start(peer);
9235
- }
9236
- });
9237
- }
9238
9193
  tryOnline(opts) {
9239
9194
  return __awaiter(this, void 0, void 0, function* () {
9240
9195
  // If offline mode not forced, and device says there is no connection: skip
@@ -9261,7 +9216,8 @@ class NetworkService {
9261
9216
  // Disable the offline mode
9262
9217
  this.setForceOffline(false);
9263
9218
  // Restart
9264
- yield this.restart(peer);
9219
+ this._startingPeer = peer;
9220
+ yield this.restart();
9265
9221
  // Wait a promise, before recheck
9266
9222
  yield this.emit('beforeTryOnlineFinish', this.online);
9267
9223
  }
@@ -9289,7 +9245,7 @@ class NetworkService {
9289
9245
  // Display toast (without await, because not need to wait toast close event)
9290
9246
  return this.showOfflineToast({ showRetryButton: false });
9291
9247
  }
9292
- return this._started && online;
9248
+ return this.started && online;
9293
9249
  });
9294
9250
  }
9295
9251
  showOfflineToast(opts) {
@@ -9337,86 +9293,6 @@ class NetworkService {
9337
9293
  return false;
9338
9294
  });
9339
9295
  }
9340
- /**
9341
- * Try to restore peer from the local storage
9342
- */
9343
- restoreLocally() {
9344
- return __awaiter(this, void 0, void 0, function* () {
9345
- // Restore from storage
9346
- const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
9347
- const settings = settingsStr && JSON.parse(settingsStr) || undefined;
9348
- if (settings && settings.peerUrl) {
9349
- console.debug(`[network] Use peer {${settings.peerUrl}} (found in the local storage)`);
9350
- return Peer.parseUrl(settings.peerUrl);
9351
- }
9352
- // Else, use default peer in env, if exists
9353
- if (this.environment.defaultPeer) {
9354
- return Peer.fromObject(this.environment.defaultPeer);
9355
- }
9356
- // Else, if App is hosted, try the web site as a peer
9357
- const location = this._document && this._document.location;
9358
- if (location && location.protocol && location.protocol.startsWith('http')) {
9359
- const hostname = this._document.location.host;
9360
- const detectedPeer = Peer.parseUrl(`${this._document.location.protocol}${hostname}${this.environment.baseUrl}`);
9361
- if (yield this.checkPeerAlive(detectedPeer)) {
9362
- return detectedPeer;
9363
- }
9364
- }
9365
- return undefined;
9366
- });
9367
- }
9368
- /**
9369
- * Refresh network state, using a ping to pod
9370
- */
9371
- refreshPeerState(opts) {
9372
- return __awaiter(this, void 0, void 0, function* () {
9373
- });
9374
- }
9375
- /**
9376
- * Stop to network state
9377
- *
9378
- * @protected
9379
- */
9380
- stopRefreshTimer() {
9381
- if (this._timerSubscription) {
9382
- this._timerSubscription.unsubscribe();
9383
- this._timerSubscription = undefined;
9384
- }
9385
- }
9386
- /**
9387
- * Refresh the network state
9388
- *
9389
- * @protected
9390
- */
9391
- startRefreshTimer() {
9392
- if (this._timerSubscription)
9393
- return; // Already running: skip
9394
- console.info(`[network] Starting refresh timer, every ${this._timerRefreshPeriod}ms...`);
9395
- let lastInfo;
9396
- this._timerSubscription = timer(this._timerRefreshPeriod, this._timerRefreshPeriod)
9397
- .pipe(
9398
- // Skip some timer event (see constructor)
9399
- filter(this._timerRefreshCondition),
9400
- // Checkin if peer alive
9401
- tap(() => console.debug('[network] Checking connection to pod...')), mergeMap(() => this.checkPeerAlive(this.peer)),
9402
- // Filter to keep only changes
9403
- filter(info => !!info !== !!lastInfo), tap(info => lastInfo = info),
9404
- // Check compatibility
9405
- mergeMap((info) => this.checkPeerCompatible(info, { showToast: true })))
9406
- .subscribe(alive => {
9407
- if (alive && this.offline) {
9408
- this.setForceOffline(false);
9409
- // Restart the service (to force re auth)
9410
- this.restart();
9411
- }
9412
- else if (!alive && this.online) {
9413
- this.setForceOffline(true);
9414
- // Stop the service
9415
- this.stop();
9416
- }
9417
- });
9418
- this._timerSubscription.add(() => console.debug('[network] Refresh timer stopped'));
9419
- }
9420
9296
  /**
9421
9297
  * Check if the peer is alive
9422
9298
  *
@@ -9532,19 +9408,136 @@ class NetworkService {
9532
9408
  });
9533
9409
  });
9534
9410
  }
9535
- get(path, opts) {
9411
+ /* -- protected functions -- */
9412
+ ngOnStart() {
9536
9413
  return __awaiter(this, void 0, void 0, function* () {
9537
- let uri = path;
9538
- // If path is not an URI: prepend with peer URL
9539
- if (!uri.startsWith('http://') && !uri.startsWith('https://')) {
9540
- let peerUrl = (this.peer && this.peer.url);
9541
- // Remove trailing slash
9542
- if (peerUrl.endsWith('/')) {
9543
- peerUrl = peerUrl.substr(0, peerUrl.length - 1);
9544
- }
9545
- // Add first path
9546
- if (!path.startsWith('/')) {
9547
- path = '/' + path;
9414
+ console.info('[network] Starting network...', this._startingPeer);
9415
+ // Restoring local settings
9416
+ let peer = this._startingPeer || (yield this.restoreLocally());
9417
+ // Make sure to hide the splashscreen, before open the modal
9418
+ if (!peer && this.splashScreen)
9419
+ this.splashScreen.hide();
9420
+ // No peer in settings: ask user to choose
9421
+ while (!peer) {
9422
+ console.debug('[network] No peer defined. Asking user to choose a peer.');
9423
+ peer = yield this.showSelectPeerModal({ allowSelectDownPeer: false });
9424
+ }
9425
+ console.info(`[network] Starting service [OK] {peer: '${peer.url}', online: ${this.online}}`);
9426
+ return peer;
9427
+ });
9428
+ }
9429
+ ngOnAfterStart() {
9430
+ var _a;
9431
+ return __awaiter(this, void 0, void 0, function* () {
9432
+ // Wait settings starts, then save peer in settings
9433
+ yield this.settings.apply({ peerUrl: this._data.url });
9434
+ this.onDeviceConnectionChanged(((_a = this.network) === null || _a === void 0 ? void 0 : _a.type) || 'unknown');
9435
+ // Start the refresh timer
9436
+ this.startRefreshTimer();
9437
+ // Listen for device network changes
9438
+ if (this.network) {
9439
+ this._subscription.add(this.network.onDisconnect().subscribe(() => this.onDeviceConnectionChanged('none')));
9440
+ this._subscription.add(this.network.onConnect().subscribe(() => this.onDeviceConnectionChanged(this.network.type)));
9441
+ }
9442
+ });
9443
+ }
9444
+ ngOnStop() {
9445
+ return __awaiter(this, void 0, void 0, function* () {
9446
+ this.resetData();
9447
+ // Stop timer if cannot refresh anymore
9448
+ if (this._timerRefreshCondition() === false) {
9449
+ this.stopRefreshTimer();
9450
+ }
9451
+ this._subscription.unsubscribe();
9452
+ this._subscription = new Subscription();
9453
+ });
9454
+ }
9455
+ /**
9456
+ * Try to restore peer from the local storage
9457
+ */
9458
+ restoreLocally() {
9459
+ return __awaiter(this, void 0, void 0, function* () {
9460
+ // Restore from storage
9461
+ const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
9462
+ const settings = settingsStr && JSON.parse(settingsStr) || undefined;
9463
+ if (settings && settings.peerUrl) {
9464
+ console.debug(`[network] Use peer {${settings.peerUrl}} (found in the local storage)`);
9465
+ return Peer.parseUrl(settings.peerUrl);
9466
+ }
9467
+ // Else, use default peer in env, if exists
9468
+ if (this.environment.defaultPeer) {
9469
+ return Peer.fromObject(this.environment.defaultPeer);
9470
+ }
9471
+ // Else, if App is hosted, try the web site as a peer
9472
+ const location = this._document && this._document.location;
9473
+ if (location && location.protocol && location.protocol.startsWith('http')) {
9474
+ const hostname = this._document.location.host;
9475
+ const detectedPeer = Peer.parseUrl(`${this._document.location.protocol}${hostname}${this.environment.baseUrl}`);
9476
+ if (yield this.checkPeerAlive(detectedPeer)) {
9477
+ return detectedPeer;
9478
+ }
9479
+ }
9480
+ return undefined;
9481
+ });
9482
+ }
9483
+ /**
9484
+ * Stop to network state
9485
+ *
9486
+ * @protected
9487
+ */
9488
+ stopRefreshTimer() {
9489
+ if (this._timerSubscription) {
9490
+ this._timerSubscription.unsubscribe();
9491
+ this._timerSubscription = undefined;
9492
+ }
9493
+ }
9494
+ /**
9495
+ * Refresh the network state
9496
+ *
9497
+ * @protected
9498
+ */
9499
+ startRefreshTimer() {
9500
+ if (this._timerSubscription)
9501
+ return; // Already running: skip
9502
+ console.info(`[network] Starting refresh timer, every ${this._timerRefreshPeriod}ms...`);
9503
+ let lastInfo;
9504
+ this._timerSubscription = timer(this._timerRefreshPeriod, this._timerRefreshPeriod)
9505
+ .pipe(
9506
+ // Skip some timer event (see constructor)
9507
+ filter(this._timerRefreshCondition),
9508
+ // Checkin if peer alive
9509
+ tap(() => console.debug('[network] Checking connection to pod...')), mergeMap(() => this.checkPeerAlive(this.peer)),
9510
+ // Filter to keep only changes
9511
+ filter(info => !!info !== !!lastInfo), tap(info => lastInfo = info),
9512
+ // Check compatibility
9513
+ mergeMap((info) => this.checkPeerCompatible(info, { showToast: true })))
9514
+ .subscribe(alive => {
9515
+ if (alive && this.offline) {
9516
+ this.setForceOffline(false);
9517
+ // Restart the service (to force re auth)
9518
+ this.restart();
9519
+ }
9520
+ else if (!alive && this.online) {
9521
+ this.setForceOffline(true);
9522
+ // Stop the service
9523
+ this.stop();
9524
+ }
9525
+ });
9526
+ this._timerSubscription.add(() => console.debug('[network] Refresh timer stopped'));
9527
+ }
9528
+ get(path, opts) {
9529
+ return __awaiter(this, void 0, void 0, function* () {
9530
+ let uri = path;
9531
+ // If path is not an URI: prepend with peer URL
9532
+ if (!uri.startsWith('http://') && !uri.startsWith('https://')) {
9533
+ let peerUrl = (this.peer && this.peer.url);
9534
+ // Remove trailing slash
9535
+ if (peerUrl.endsWith('/')) {
9536
+ peerUrl = peerUrl.substr(0, peerUrl.length - 1);
9537
+ }
9538
+ // Add first path
9539
+ if (!path.startsWith('/')) {
9540
+ path = '/' + path;
9548
9541
  }
9549
9542
  // Create the URI: concat peer URL and path
9550
9543
  uri = peerUrl + path;
@@ -9583,7 +9576,7 @@ class NetworkService {
9583
9576
  }
9584
9577
  }
9585
9578
  resetData() {
9586
- this._peer = null;
9579
+ this._data = null;
9587
9580
  }
9588
9581
  /**
9589
9582
  * Get default peers, from environment
@@ -10143,31 +10136,32 @@ class EntityStore {
10143
10136
  }
10144
10137
 
10145
10138
  const APP_LOCAL_STORAGE_TYPE_POLICIES = new InjectionToken('localStorageTypePolicies');
10146
- class EntitiesStorage {
10139
+ ;
10140
+ class EntitiesStorage extends StartableService {
10147
10141
  constructor(platform, progressBarService, storage, environment, typePolicies) {
10142
+ super(platform);
10148
10143
  this.platform = platform;
10149
10144
  this.progressBarService = progressBarService;
10150
10145
  this.storage = storage;
10151
10146
  this.environment = environment;
10152
- this._started = false;
10153
10147
  this._subscription = new Subscription();
10154
- this._stores = {};
10155
10148
  this._$save = new EventEmitter(true);
10156
10149
  this._dirty = false;
10157
10150
  this._saving = false;
10158
- this.onStart = new Subject();
10159
10151
  this._typePolicies = typePolicies || {};
10152
+ this._saveTimerPeriod = environment.storageSavePeriodMs || 10000 /* = 10s */;
10153
+ this._data = {};
10160
10154
  // For DEV only
10161
10155
  this._debug = !environment.production;
10162
10156
  if (this._debug)
10163
10157
  console.debug('[entities-storage] Creating service');
10164
10158
  }
10165
10159
  get dirty() {
10166
- return this._dirty || Object.entries(this._stores).find(([_, store]) => store.dirty) !== undefined;
10160
+ return this._dirty || Object.values(this._data).some(store => store.dirty);
10167
10161
  }
10168
10162
  watchAll(entityName, variables, opts) {
10169
10163
  // Make sure store is ready
10170
- if (!this._started) {
10164
+ if (!this.started) {
10171
10165
  return defer(() => this.ready())
10172
10166
  .pipe(switchMap(() => this.watchAll(entityName, variables, opts))); // Loop
10173
10167
  }
@@ -10181,7 +10175,7 @@ class EntitiesStorage {
10181
10175
  loadAll(entityName, variables, opts) {
10182
10176
  return __awaiter(this, void 0, void 0, function* () {
10183
10177
  // Make sure store is ready
10184
- if (!this._started)
10178
+ if (!this.started)
10185
10179
  yield this.ready();
10186
10180
  try {
10187
10181
  this.progressBarService.increase();
@@ -10244,7 +10238,8 @@ class EntitiesStorage {
10244
10238
  return __awaiter(this, void 0, void 0, function* () {
10245
10239
  if (!entity)
10246
10240
  return; // skip
10247
- yield this.ready();
10241
+ if (!this.started)
10242
+ yield this.ready();
10248
10243
  try {
10249
10244
  this.progressBarService.increase();
10250
10245
  this._dirty = true;
@@ -10264,7 +10259,8 @@ class EntitiesStorage {
10264
10259
  return __awaiter(this, void 0, void 0, function* () {
10265
10260
  if (isEmptyArray(entities) && (!opts || opts.reset !== true))
10266
10261
  return entities; // Skip (nothing to save)
10267
- yield this.ready();
10262
+ if (!this.started)
10263
+ yield this.ready();
10268
10264
  try {
10269
10265
  this.progressBarService.increase();
10270
10266
  this._dirty = true;
@@ -10280,13 +10276,15 @@ class EntitiesStorage {
10280
10276
  return __awaiter(this, void 0, void 0, function* () {
10281
10277
  if (!entity)
10282
10278
  return undefined; // skip
10283
- yield this.ready();
10279
+ if (!this.started)
10280
+ yield this.ready();
10284
10281
  return this.deleteById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10285
10282
  });
10286
10283
  }
10287
10284
  deleteById(id, opts) {
10288
10285
  return __awaiter(this, void 0, void 0, function* () {
10289
- yield this.ready();
10286
+ if (!this.started)
10287
+ yield this.ready();
10290
10288
  if (!opts || isNilOrBlank(opts.entityName))
10291
10289
  throw new Error('Missing argument \'opts\' or \'entityName\'');
10292
10290
  //if (id >= 0) throw new Error('Invalid id a local entity (not a negative number): ' + id);
@@ -10312,7 +10310,8 @@ class EntitiesStorage {
10312
10310
  }
10313
10311
  deleteMany(ids, opts) {
10314
10312
  return __awaiter(this, void 0, void 0, function* () {
10315
- yield this.ready();
10313
+ if (!this.started)
10314
+ yield this.ready();
10316
10315
  if (!opts || isNilOrBlank(opts.entityName))
10317
10316
  throw new Error('Missing argument \'opts\' or \'opts.entityName\'');
10318
10317
  try {
@@ -10340,7 +10339,8 @@ class EntitiesStorage {
10340
10339
  return __awaiter(this, void 0, void 0, function* () {
10341
10340
  if (!entity)
10342
10341
  return undefined; // skip
10343
- yield this.ready();
10342
+ if (!this.started)
10343
+ yield this.ready();
10344
10344
  return this.deleteFromTrashById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10345
10345
  });
10346
10346
  }
@@ -10356,7 +10356,8 @@ class EntitiesStorage {
10356
10356
  return __awaiter(this, void 0, void 0, function* () {
10357
10357
  if (!entity)
10358
10358
  return undefined; // skip
10359
- yield this.ready();
10359
+ if (!this.started)
10360
+ yield this.ready();
10360
10361
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10361
10362
  // Delete entity by id, if exists
10362
10363
  const entityStore = this.getEntityStore(entityName);
@@ -10373,7 +10374,8 @@ class EntitiesStorage {
10373
10374
  }
10374
10375
  moveManyToTrash(ids, opts) {
10375
10376
  return __awaiter(this, void 0, void 0, function* () {
10376
- yield this.ready();
10377
+ if (!this.started)
10378
+ yield this.ready();
10377
10379
  if (!opts || isNilOrBlank(opts.entityName))
10378
10380
  throw new Error('Missing argument \'opts.entityName\'');
10379
10381
  const entityStore = this.getEntityStore(opts.entityName, { create: false });
@@ -10400,7 +10402,8 @@ class EntitiesStorage {
10400
10402
  return __awaiter(this, void 0, void 0, function* () {
10401
10403
  if (!entity)
10402
10404
  return undefined; // skip
10403
- yield this.ready();
10405
+ if (!this.started)
10406
+ yield this.ready();
10404
10407
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10405
10408
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10406
10409
  this.getEntityStore(trashName).save(entity, opts);
@@ -10410,7 +10413,8 @@ class EntitiesStorage {
10410
10413
  }
10411
10414
  clearTrash(entityName) {
10412
10415
  return __awaiter(this, void 0, void 0, function* () {
10413
- yield this.ready();
10416
+ if (!this.started)
10417
+ yield this.ready();
10414
10418
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10415
10419
  const entityStore = this.getEntityStore(trashName, { create: false });
10416
10420
  if (!entityStore)
@@ -10426,36 +10430,24 @@ class EntitiesStorage {
10426
10430
  }
10427
10431
  return Promise.resolve();
10428
10432
  }
10429
- ready() {
10430
- if (this._started)
10431
- return Promise.resolve();
10432
- return this.start();
10433
- }
10434
- start() {
10435
- if (this._startPromise)
10436
- return this._startPromise;
10437
- if (this._started)
10438
- return Promise.resolve();
10439
- const now = Date.now();
10440
- console.info(`[entities-storage] Starting entity storage...`);
10441
- // Restore sequences
10442
- this._startPromise = this.restoreLocally()
10443
- .then(() => {
10433
+ ngOnStart() {
10434
+ return __awaiter(this, void 0, void 0, function* () {
10435
+ const now = Date.now();
10436
+ console.info(`[entities-storage] Starting entity storage...`);
10437
+ // Restore sequences
10438
+ yield this.restoreLocally();
10444
10439
  // Start a save timer
10445
- this._subscription.add(merge(this._$save, timer(2000, 10000))
10446
- .pipe(throttleTime(10000))
10440
+ this._subscription.add(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
10441
+ .pipe(
10442
+ // Avoid to many call (e.g. when $save AND timer are triggered
10443
+ throttleTime(this._saveTimerPeriod))
10447
10444
  .subscribe(() => this.storeLocally()));
10448
- this._started = true;
10449
- this._startPromise = undefined;
10450
10445
  console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
10451
- // Emit event
10452
- this.onStart.next();
10446
+ return this._data;
10453
10447
  });
10454
- return this._startPromise;
10455
10448
  }
10456
- stop() {
10449
+ ngOnStop() {
10457
10450
  return __awaiter(this, void 0, void 0, function* () {
10458
- this._started = false;
10459
10451
  this._subscription.unsubscribe();
10460
10452
  this._subscription = new Subscription();
10461
10453
  if (this.dirty) {
@@ -10463,22 +10455,15 @@ class EntitiesStorage {
10463
10455
  }
10464
10456
  });
10465
10457
  }
10466
- restart() {
10467
- return __awaiter(this, void 0, void 0, function* () {
10468
- if (this._started)
10469
- yield this.stop();
10470
- yield this.start();
10471
- });
10472
- }
10473
10458
  /* -- protected methods -- */
10474
10459
  getEntityStore(name, opts) {
10475
- let store = this._stores[name];
10460
+ let store = this._data[name];
10476
10461
  if (!store && (!opts || opts.create !== false)) {
10477
10462
  if (this._debug)
10478
10463
  console.debug(`[entities-storage] Creating store ${name}`);
10479
10464
  const typePolicy = this._typePolicies[name];
10480
10465
  store = new EntityStore(name, this.storage, typePolicy);
10481
- this._stores[name] = store;
10466
+ this._data[name] = store;
10482
10467
  }
10483
10468
  return store;
10484
10469
  }
@@ -10521,12 +10506,12 @@ class EntitiesStorage {
10521
10506
  this._saving = true;
10522
10507
  this._dirty = false;
10523
10508
  this.progressBarService.increase();
10524
- const entityNames = this._stores && Object.keys(this._stores) || [];
10509
+ const entityNames = this._data && Object.keys(this._data) || [];
10525
10510
  const now = Date.now();
10526
10511
  if (this._debug)
10527
10512
  console.debug('[entities-storage] Persisting...');
10528
10513
  let currentEntityName;
10529
- return concat(...entityNames.map(entityName => defer(() => {
10514
+ return chainPromises(entityNames.map(entityName => () => {
10530
10515
  currentEntityName = entityName;
10531
10516
  const entityStore = this.getEntityStore(entityName, { create: false });
10532
10517
  if (!entityStore) {
@@ -10540,18 +10525,20 @@ class EntitiesStorage {
10540
10525
  entityNames.splice(entityNames.findIndex(e => e === entityName), 1);
10541
10526
  }
10542
10527
  });
10543
- })), defer(() => {
10528
+ }))
10529
+ .then(() => {
10544
10530
  currentEntityName = undefined;
10545
10531
  return isEmptyArray(entityNames) ?
10546
10532
  this.storage.remove(ENTITIES_STORAGE_KEY_PREFIX) :
10547
10533
  this.storage.set(ENTITIES_STORAGE_KEY_PREFIX, entityNames);
10548
- }), defer(() => {
10534
+ })
10535
+ .then(() => {
10549
10536
  if (this._debug)
10550
10537
  console.debug(`[entities-storage] Persisting [OK] ${entityNames.length} stores saved in ${Date.now() - now}ms...`);
10551
10538
  this._saving = false;
10552
10539
  this.progressBarService.decrease();
10553
- }))
10554
- .pipe(catchError(err => {
10540
+ })
10541
+ .catch(err => {
10555
10542
  this._saving = false;
10556
10543
  this.progressBarService.decrease();
10557
10544
  if (currentEntityName) {
@@ -10560,8 +10547,8 @@ class EntitiesStorage {
10560
10547
  else {
10561
10548
  console.error(`[entities-storage] Error while persisting: ${err && err.message || err}`, err);
10562
10549
  }
10563
- return err;
10564
- })).toPromise();
10550
+ throw err;
10551
+ });
10565
10552
  });
10566
10553
  }
10567
10554
  }
@@ -10575,7 +10562,7 @@ EntitiesStorage.ctorParameters = () => [
10575
10562
  { type: Platform },
10576
10563
  { type: ProgressBarService },
10577
10564
  { type: Storage },
10578
- { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10565
+ { type: Environment, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10579
10566
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APP_LOCAL_STORAGE_TYPE_POLICIES,] }] }
10580
10567
  ];
10581
10568
 
@@ -11046,8 +11033,9 @@ function restoreTrackedQueries(opts) {
11046
11033
  }
11047
11034
 
11048
11035
  const APP_GRAPHQL_TYPE_POLICIES = new InjectionToken('graphqlTypePolicies');
11049
- class GraphqlService {
11036
+ class GraphqlService extends StartableService {
11050
11037
  constructor(platform, apollo, httpLink, network, storage, cryptoService, environment, typePolicies) {
11038
+ super(network); // Wait network
11051
11039
  this.platform = platform;
11052
11040
  this.apollo = apollo;
11053
11041
  this.httpLink = httpLink;
@@ -11056,12 +11044,10 @@ class GraphqlService {
11056
11044
  this.cryptoService = cryptoService;
11057
11045
  this.environment = environment;
11058
11046
  this.typePolicies = typePolicies;
11059
- this._started = false;
11060
11047
  this._subscription = new Subscription();
11061
11048
  this.connectionParams = {};
11062
11049
  this.onNetworkError = new Subject();
11063
11050
  this.customErrors = {};
11064
- this.onStart = new Subject();
11065
11051
  this._debug = !environment.production;
11066
11052
  this._defaultFetchPolicy = environment.apolloFetchPolicy;
11067
11053
  // Restart if network restart
@@ -11079,11 +11065,8 @@ class GraphqlService {
11079
11065
  .pipe(throttleTime(300), filter(() => this.network.online), mergeMap(() => this.network.checkPeerAlive()), filter(alive => !alive))
11080
11066
  .subscribe(() => this.network.setForceOffline(true, { showToast: true }));
11081
11067
  }
11082
- get started() {
11083
- return this._started;
11084
- }
11085
11068
  get client() {
11086
- return this.apollo.client;
11069
+ return this._data;
11087
11070
  }
11088
11071
  get cache() {
11089
11072
  return this.apollo.client.cache;
@@ -11091,33 +11074,6 @@ class GraphqlService {
11091
11074
  get defaultFetchPolicy() {
11092
11075
  return this._defaultFetchPolicy;
11093
11076
  }
11094
- ready() {
11095
- if (this._started)
11096
- return Promise.resolve();
11097
- return this.start();
11098
- }
11099
- start() {
11100
- if (this._startPromise)
11101
- return this._startPromise;
11102
- if (this._started)
11103
- return Promise.resolve();
11104
- console.info('[graphql] Starting graphql...');
11105
- // Waiting for network service
11106
- this._startPromise = this.network.ready()
11107
- .then(() => this.initApollo())
11108
- .then(() => {
11109
- this._started = true;
11110
- this._startPromise = undefined;
11111
- // Emit event
11112
- this.onStart.next();
11113
- console.info('[graphql] Starting graphql [OK]');
11114
- })
11115
- .catch((err) => {
11116
- console.error(err && err.message || err, err);
11117
- this._startPromise = undefined;
11118
- });
11119
- return this._startPromise;
11120
- }
11121
11077
  setAuthToken(token) {
11122
11078
  if (token) {
11123
11079
  console.debug('[graphql] Apply token authentication to headers');
@@ -11150,22 +11106,22 @@ class GraphqlService {
11150
11106
  */
11151
11107
  addResolver(resolvers) {
11152
11108
  return __awaiter(this, void 0, void 0, function* () {
11153
- if (!this._started) {
11154
- this.onStart.toPromise().then(() => this.addResolver(resolvers)); // Loop
11155
- }
11156
- const client = this.apollo.getClient();
11157
- client.addResolvers(resolvers);
11109
+ if (!this.started)
11110
+ yield this.ready();
11111
+ this.apollo.client.addResolvers(resolvers);
11158
11112
  });
11159
11113
  }
11160
11114
  query(opts) {
11161
11115
  return __awaiter(this, void 0, void 0, function* () {
11116
+ if (!this.started)
11117
+ yield this.ready();
11162
11118
  let res;
11163
11119
  try {
11164
- res = yield (yield this.getApollo()).query({
11120
+ res = yield this.client.query({
11165
11121
  query: opts.query,
11166
11122
  variables: opts.variables,
11167
11123
  fetchPolicy: opts.fetchPolicy || this._defaultFetchPolicy || undefined
11168
- }).toPromise();
11124
+ });
11169
11125
  }
11170
11126
  catch (err) {
11171
11127
  res = this.toApolloError(err, opts.error);
@@ -11519,8 +11475,9 @@ class GraphqlService {
11519
11475
  this.customErrors = Object.assign(Object.assign({}, this.customErrors), error);
11520
11476
  }
11521
11477
  /* -- protected methods -- */
11522
- initApollo() {
11478
+ ngOnStart() {
11523
11479
  return __awaiter(this, void 0, void 0, function* () {
11480
+ console.info('[graphql] Starting graphql...');
11524
11481
  const mobile = this.platform.is('mobile') || this.platform.is('mobileweb');
11525
11482
  const enableTrackMutationQueries = !mobile;
11526
11483
  const peer = this.network.peer;
@@ -11655,24 +11612,16 @@ class GraphqlService {
11655
11612
  console.error('[graphql] Failed to restore tracked queries from storage: ' + (err && err.message || err), err);
11656
11613
  }
11657
11614
  }
11615
+ console.info('[graphql] Starting graphql [OK]');
11616
+ return client;
11658
11617
  });
11659
11618
  }
11660
- stop() {
11619
+ ngOnStop() {
11661
11620
  return __awaiter(this, void 0, void 0, function* () {
11662
11621
  console.info('[graphql] Stopping graphql service...');
11663
11622
  this._subscription.unsubscribe();
11664
11623
  this._subscription = new Subscription();
11665
11624
  yield this.resetClient();
11666
- this._started = false;
11667
- this._startPromise = undefined;
11668
- });
11669
- }
11670
- restart() {
11671
- return __awaiter(this, void 0, void 0, function* () {
11672
- if (this.started) {
11673
- return this.stop().then(() => this.start());
11674
- }
11675
- return this.start();
11676
11625
  });
11677
11626
  }
11678
11627
  resetClient(client) {
@@ -11768,15 +11717,6 @@ class GraphqlService {
11768
11717
  }
11769
11718
  return undefined;
11770
11719
  }
11771
- getApollo() {
11772
- return __awaiter(this, void 0, void 0, function* () {
11773
- if (!this._started) {
11774
- console.debug('[graphql] Waiting apollo client... ');
11775
- yield this.onStart.toPromise();
11776
- }
11777
- return this.apollo;
11778
- });
11779
- }
11780
11720
  }
11781
11721
  GraphqlService.ɵprov = i0.ɵɵdefineInjectable({ factory: function GraphqlService_Factory() { return new GraphqlService(i0.ɵɵinject(i1$4.Platform), i0.ɵɵinject(i2$3.Apollo), i0.ɵɵinject(i3$2.HttpLink), i0.ɵɵinject(NetworkService), i0.ɵɵinject(i3$1.Storage), i0.ɵɵinject(CryptoService), i0.ɵɵinject(ENVIRONMENT), i0.ɵɵinject(APP_GRAPHQL_TYPE_POLICIES, 8)); }, token: GraphqlService, providedIn: "root" });
11782
11722
  GraphqlService.decorators = [
@@ -12120,7 +12060,12 @@ class AccountService extends BaseGraphqlService {
12120
12060
  this.storage = storage;
12121
12061
  this.file = file;
12122
12062
  this.environment = environment;
12123
- this.data = {
12063
+ this.onLogin = new Subject();
12064
+ this.onLogout = new Subject();
12065
+ this.onChange = new Subject();
12066
+ this.onAuthTokenChange = new Subject();
12067
+ this.onAuthBasicChange = new Subject();
12068
+ this._data = {
12124
12069
  loaded: false,
12125
12070
  keypair: null,
12126
12071
  authToken: null,
@@ -12134,11 +12079,6 @@ class AccountService extends BaseGraphqlService {
12134
12079
  this._started = false;
12135
12080
  this._$additionalFields = new BehaviorSubject([]);
12136
12081
  this._tokenType$ = new BehaviorSubject(undefined);
12137
- this.onLogin = new Subject();
12138
- this.onLogout = new Subject();
12139
- this.onChange = new Subject();
12140
- this.onAuthTokenChange = new Subject();
12141
- this.onAuthBasicChange = new Subject();
12142
12082
  this._debug = !environment.production;
12143
12083
  if (this._debug)
12144
12084
  console.debug('[account-service] Creating service');
@@ -12146,7 +12086,7 @@ class AccountService extends BaseGraphqlService {
12146
12086
  // Send auth token to the graphql layer, when changed
12147
12087
  this.onAuthTokenChange.subscribe((token) => this.graphql.setAuthToken(token));
12148
12088
  this.onAuthBasicChange.subscribe((basic) => this.graphql.setAuthBasic(basic));
12149
- // Listen network restart
12089
+ // Listen graphql start (or restart)
12150
12090
  this.graphql.onStart.subscribe(() => __awaiter(this, void 0, void 0, function* () {
12151
12091
  if (!this._started) {
12152
12092
  this.ready();
@@ -12168,19 +12108,19 @@ class AccountService extends BaseGraphqlService {
12168
12108
  }));
12169
12109
  }
12170
12110
  get account() {
12171
- return this.data.loaded ? this.data.account : undefined;
12111
+ return this._data.loaded ? this._data.account : undefined;
12172
12112
  }
12173
12113
  get person() {
12174
- if (this.data.loaded && !this.data.person) {
12175
- this.data.person = this.data.loaded ? this.data.account.asPerson() : undefined;
12114
+ if (this._data.loaded && !this._data.person) {
12115
+ this._data.person = this._data.loaded ? this._data.account.asPerson() : undefined;
12176
12116
  }
12177
- return this.data.person;
12117
+ return this._data.person;
12178
12118
  }
12179
12119
  get department() {
12180
- if (this.data.loaded && !this.data.department) {
12181
- this.data.department = this.data.loaded ? this.data.account.asPerson().department : undefined;
12120
+ if (this._data.loaded && !this._data.department) {
12121
+ this._data.department = this._data.loaded ? this._data.account.asPerson().department : undefined;
12182
12122
  }
12183
- return this.data.department;
12123
+ return this._data.department;
12184
12124
  }
12185
12125
  get tokenType() {
12186
12126
  return this._tokenType$.value;
@@ -12190,28 +12130,17 @@ class AccountService extends BaseGraphqlService {
12190
12130
  console.info('[account] Using authentication token type: ' + value);
12191
12131
  this._tokenType$.next(value);
12192
12132
  // Reset values
12193
- this.data.authToken = undefined;
12133
+ this._data.authToken = undefined;
12194
12134
  this.onAuthTokenChange.next(undefined);
12195
- this.data.authBasic = undefined;
12135
+ this._data.authBasic = undefined;
12196
12136
  this.onAuthBasicChange.next(undefined);
12197
12137
  }
12198
12138
  }
12199
- resetData() {
12200
- this.data.loaded = false;
12201
- this.data.keypair = null;
12202
- this.data.authToken = null;
12203
- this.data.authBasic = null;
12204
- this.data.pubkey = null;
12205
- this.data.mainProfile = null;
12206
- this.data.account = new Account();
12207
- this.data.person = null;
12208
- this.data.department = null;
12209
- }
12210
12139
  start() {
12211
12140
  if (this._startPromise)
12212
12141
  return this._startPromise;
12213
12142
  if (this._started)
12214
- return Promise.resolve();
12143
+ return Promise.resolve(this.account);
12215
12144
  // Restoring local settings
12216
12145
  this._startPromise = Promise.all([
12217
12146
  this.settings.ready(),
@@ -12222,6 +12151,7 @@ class AccountService extends BaseGraphqlService {
12222
12151
  .then(() => {
12223
12152
  this._started = true;
12224
12153
  this._startPromise = undefined;
12154
+ return this.account;
12225
12155
  });
12226
12156
  return this._startPromise;
12227
12157
  }
@@ -12233,8 +12163,8 @@ class AccountService extends BaseGraphqlService {
12233
12163
  if (this._started || this._startPromise) {
12234
12164
  this._started = false;
12235
12165
  this._startPromise = undefined;
12236
- const hadAuthToken = this.data.authToken && true;
12237
- const hadAuthBasic = this.data.authBasic && true;
12166
+ const hadAuthToken = this._data.authToken && true;
12167
+ const hadAuthBasic = this._data.authBasic && true;
12238
12168
  const hadAuth = hadAuthToken || hadAuthBasic;
12239
12169
  this.resetData();
12240
12170
  if (hadAuth) {
@@ -12256,35 +12186,35 @@ class AccountService extends BaseGraphqlService {
12256
12186
  }
12257
12187
  ready() {
12258
12188
  if (this._started)
12259
- return Promise.resolve();
12189
+ return Promise.resolve(this.account);
12260
12190
  return this.start();
12261
12191
  }
12262
12192
  isLogin() {
12263
- return !!(this.data.pubkey && this.data.loaded);
12193
+ return !!(this._data.pubkey && this._data.loaded);
12264
12194
  }
12265
12195
  isAuth() {
12266
- return !!(this.data.pubkey && this.data.keypair && this.data.keypair.secretKey);
12196
+ return !!(this._data.pubkey && this._data.keypair && this._data.keypair.secretKey);
12267
12197
  }
12268
12198
  hasMinProfile(userProfile) {
12269
12199
  // should be login, and status ENABLE or TEMPORARY
12270
- if (!this.data.account || !this.data.account.pubkey ||
12271
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY)) {
12200
+ if (!this._data.account || !this._data.account.pubkey ||
12201
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY)) {
12272
12202
  return false;
12273
12203
  }
12274
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12204
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12275
12205
  }
12276
12206
  hasExactProfile(label) {
12277
12207
  // should be login, and status ENABLE or TEMPORARY
12278
- if (!this.data.account || !this.data.account.pubkey ||
12279
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY))
12208
+ if (!this._data.account || !this._data.account.pubkey ||
12209
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12280
12210
  return false;
12281
- return this.data.account.profiles.some(profile => profile === label);
12211
+ return this._data.account.profiles.some(profile => profile === label);
12282
12212
  }
12283
12213
  hasProfileAndIsEnable(userProfile) {
12284
12214
  // should be login, and status ENABLE
12285
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12215
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12286
12216
  return false;
12287
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12217
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12288
12218
  }
12289
12219
  isAdmin() {
12290
12220
  return this.hasProfileAndIsEnable('ADMIN');
@@ -12304,11 +12234,11 @@ class AccountService extends BaseGraphqlService {
12304
12234
  }
12305
12235
  isOnlyGuest() {
12306
12236
  // Should be login, and status ENABLE or TEMPORARY
12307
- if (!this.data.account || !this.data.account.pubkey ||
12308
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY))
12237
+ if (!this._data.account || !this._data.account.pubkey ||
12238
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12309
12239
  return false;
12310
12240
  // Profile less then user
12311
- return !PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'USER');
12241
+ return !PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'USER');
12312
12242
  }
12313
12243
  canUserWriteDataForDepartment(recorderDepartment) {
12314
12244
  if (ReferentialUtils.isEmpty(recorderDepartment)) {
@@ -12317,17 +12247,17 @@ class AccountService extends BaseGraphqlService {
12317
12247
  return this.isAdmin();
12318
12248
  }
12319
12249
  // Should be login, and status ENABLE
12320
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12250
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12321
12251
  return false;
12322
- if (!this.data.account.department || !this.data.account.department.id) {
12252
+ if (!this._data.account.department || !this._data.account.department.id) {
12323
12253
  console.warn('User account has no department ! Unable to check write right against recorderDepartment');
12324
12254
  return false;
12325
12255
  }
12326
12256
  // Same recorder department: OK, user can write
12327
- if (this.data.account.department.id === recorderDepartment.id)
12257
+ if (this._data.account.department.id === recorderDepartment.id)
12328
12258
  return true;
12329
12259
  // Else, check if supervisor (or more)
12330
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'SUPERVISOR');
12260
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'SUPERVISOR');
12331
12261
  }
12332
12262
  register(data) {
12333
12263
  return __awaiter(this, void 0, void 0, function* () {
@@ -12338,7 +12268,7 @@ class AccountService extends BaseGraphqlService {
12338
12268
  throw new Error('Missing required username or password');
12339
12269
  if (this._debug)
12340
12270
  console.debug('[account] Register new user account...', data.account);
12341
- this.data.loaded = false;
12271
+ this._data.loaded = false;
12342
12272
  const now = Date.now();
12343
12273
  try {
12344
12274
  const keypair = yield this.cryptoService.scryptKeypair(data.username, data.password);
@@ -12348,22 +12278,22 @@ class AccountService extends BaseGraphqlService {
12348
12278
  data.account.settings.locale = this.settings.locale;
12349
12279
  data.account.settings.latLongFormat = this.settings.latLongFormat;
12350
12280
  data.account.department.id = data.account.department.id || this.environment.defaultDepartmentId;
12351
- this.data.keypair = keypair;
12281
+ this._data.keypair = keypair;
12352
12282
  const account = yield this.saveRemotely(data.account, keypair);
12353
12283
  // Default values
12354
12284
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12355
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12356
- this.data.account = account;
12357
- this.data.pubkey = account.pubkey;
12285
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12286
+ this._data.account = account;
12287
+ this._data.pubkey = account.pubkey;
12358
12288
  // Try to auth on pod
12359
12289
  yield this.authenticate(data);
12360
- this.data.loaded = true;
12290
+ this._data.loaded = true;
12361
12291
  yield this.saveLocally();
12362
12292
  console.debug(`[account] Account successfully registered in ${Date.now() - now}ms`);
12363
12293
  // Emit events
12364
- this.onLogin.next(this.data.account);
12365
- this.onChange.next(this.data.account);
12366
- return this.data.account;
12294
+ this.onLogin.next(this._data.account);
12295
+ this.onChange.next(this._data.account);
12296
+ return this._data.account;
12367
12297
  }
12368
12298
  catch (error) {
12369
12299
  console.error(error && error.message || error);
@@ -12379,20 +12309,20 @@ class AccountService extends BaseGraphqlService {
12379
12309
  // Basic auth
12380
12310
  if (tokenType === 'basic' || tokenType === 'basic-and-token') {
12381
12311
  // Generate the authBasic, if used
12382
- if (!this.data.authBasic) {
12312
+ if (!this._data.authBasic) {
12383
12313
  // Skip if token already provided
12384
- if (!(this.data.authToken && tokenType === 'basic-and-token')) {
12314
+ if (!(this._data.authToken && tokenType === 'basic-and-token')) {
12385
12315
  if (!data || !data.username || !data.password)
12386
12316
  throw new Error('Missing username and password');
12387
- this.data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12317
+ this._data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12388
12318
  }
12389
12319
  }
12390
- this.onAuthBasicChange.next(this.data.authBasic);
12320
+ this.onAuthBasicChange.next(this._data.authBasic);
12391
12321
  }
12392
12322
  // Generate the authToken, if used
12393
12323
  if (tokenType === 'token' || tokenType === 'basic-and-token') {
12394
12324
  try {
12395
- this.data.authToken = yield this.authenticateAndGetToken(this.data.authToken);
12325
+ this._data.authToken = yield this.authenticateAndGetToken(this._data.authToken);
12396
12326
  }
12397
12327
  catch (error) {
12398
12328
  // Never authenticate, or not ready for offline mode => exit
@@ -12403,7 +12333,7 @@ class AccountService extends BaseGraphqlService {
12403
12333
  }
12404
12334
  // Forget authBasic, to switch to authToken
12405
12335
  if (tokenType === 'basic-and-token') {
12406
- this.data.authBasic = undefined;
12336
+ this._data.authBasic = undefined;
12407
12337
  this.onAuthBasicChange.next(undefined);
12408
12338
  }
12409
12339
  });
@@ -12423,18 +12353,18 @@ class AccountService extends BaseGraphqlService {
12423
12353
  throw { code: ErrorCodes$2.UNKNOWN_ERROR, message: 'ERROR.SCRYPT_ERROR' };
12424
12354
  }
12425
12355
  // Store pubkey+keypair
12426
- this.data.pubkey = Base58.encode(keypair.publicKey);
12427
- this.data.keypair = keypair;
12356
+ this._data.pubkey = Base58.encode(keypair.publicKey);
12357
+ this._data.keypair = keypair;
12428
12358
  // Try to load previous token
12429
12359
  let previousToken = yield this.storage.get(TOKEN_STORAGE_KEY);
12430
- previousToken = previousToken && previousToken.startsWith(this.data.pubkey) && previousToken || null;
12360
+ previousToken = previousToken && previousToken.startsWith(this._data.pubkey) && previousToken || null;
12431
12361
  // Offline mode
12432
12362
  const offline = this.settings.hasOfflineFeature() && (this.network.offline || data.offline === true);
12433
12363
  if (offline) {
12434
- this.data.authToken = previousToken;
12364
+ this._data.authToken = previousToken;
12435
12365
  // Make sure network if set as offline
12436
12366
  this.network.setForceOffline(true, { showToast: false });
12437
- console.info(`[account] Login [OK] {pubkey: ${this.data.pubkey.substr(0, 8)}}, {offline: true}`);
12367
+ console.info(`[account] Login [OK] {pubkey: ${this._data.pubkey.substr(0, 8)}}, {offline: true}`);
12438
12368
  }
12439
12369
  // Online mode: try to auth on pod
12440
12370
  else {
@@ -12484,14 +12414,14 @@ class AccountService extends BaseGraphqlService {
12484
12414
  throw error;
12485
12415
  }
12486
12416
  // Emit event to observers
12487
- this.onLogin.next(this.data.account);
12488
- this.onChange.next(this.data.account);
12489
- return this.data.account;
12417
+ this.onLogin.next(this._data.account);
12418
+ this.onChange.next(this._data.account);
12419
+ return this._data.account;
12490
12420
  });
12491
12421
  }
12492
12422
  refresh() {
12493
12423
  return __awaiter(this, void 0, void 0, function* () {
12494
- if (!this.data.pubkey)
12424
+ if (!this._data.pubkey)
12495
12425
  throw new Error('User not logged');
12496
12426
  if (this.network.offline)
12497
12427
  throw new Error('Cannot check account in offline mode');
@@ -12499,9 +12429,9 @@ class AccountService extends BaseGraphqlService {
12499
12429
  yield this.saveLocally();
12500
12430
  console.debug('[account] Successfully reload account');
12501
12431
  // Emit login event to subscribers
12502
- this.onLogin.next(this.data.account);
12503
- this.onChange.next(this.data.account);
12504
- return this.data.account;
12432
+ this.onLogin.next(this._data.account);
12433
+ this.onChange.next(this._data.account);
12434
+ return this._data.account;
12505
12435
  });
12506
12436
  }
12507
12437
  /**
@@ -12511,29 +12441,29 @@ class AccountService extends BaseGraphqlService {
12511
12441
  */
12512
12442
  save(account) {
12513
12443
  return __awaiter(this, void 0, void 0, function* () {
12514
- if (!this.data.pubkey)
12444
+ if (!this._data.pubkey)
12515
12445
  return Promise.reject('User not logged');
12516
- if (this.data.pubkey !== account.pubkey)
12446
+ if (this._data.pubkey !== account.pubkey)
12517
12447
  return Promise.reject('Not user account');
12518
- account = yield this.saveRemotely(account, this.data.keypair);
12448
+ account = yield this.saveRemotely(account, this._data.keypair);
12519
12449
  // Set defaults
12520
12450
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12521
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12522
- this.data.account = account;
12523
- this.data.loaded = true;
12451
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12452
+ this._data.account = account;
12453
+ this._data.loaded = true;
12524
12454
  // Save locally (in storage)
12525
12455
  yield this.saveLocally();
12526
12456
  // Send event
12527
- this.onLogin.next(this.data.account);
12528
- this.onChange.next(this.data.account);
12529
- return this.data.account;
12457
+ this.onLogin.next(this._data.account);
12458
+ this.onChange.next(this._data.account);
12459
+ return this._data.account;
12530
12460
  });
12531
12461
  }
12532
12462
  logout() {
12533
12463
  return __awaiter(this, void 0, void 0, function* () {
12534
- const hadAuthToken = this.data.authToken && true;
12535
- const hadAuthBasic = this.data.authBasic && true;
12536
- const pubkey = this.data && this.data.pubkey;
12464
+ const hadAuthToken = this._data.authToken && true;
12465
+ const hadAuthBasic = this._data.authBasic && true;
12466
+ const pubkey = this._data && this._data.pubkey;
12537
12467
  this.resetData();
12538
12468
  if (!this.settings.hasOfflineFeature()) {
12539
12469
  // Remove all data from the local storage
@@ -12586,9 +12516,9 @@ class AccountService extends BaseGraphqlService {
12586
12516
  if (offline) {
12587
12517
  json = yield this.storage.get(ACCOUNT_STORAGE_KEY);
12588
12518
  json = json && (typeof json === 'string') && JSON.parse(json) || json;
12589
- json = json && this.data.pubkey && (json.pubkey === this.data.pubkey) && json || null;
12590
- if (!json && this.data.pubkey) {
12591
- json = yield this.storage.get(ACCOUNT_STORAGE_KEY + '#' + this.data.pubkey);
12519
+ json = json && this._data.pubkey && (json.pubkey === this._data.pubkey) && json || null;
12520
+ if (!json && this._data.pubkey) {
12521
+ json = yield this.storage.get(ACCOUNT_STORAGE_KEY + '#' + this._data.pubkey);
12592
12522
  json = json && (typeof json === 'string') && JSON.parse(json) || json;
12593
12523
  }
12594
12524
  }
@@ -12683,7 +12613,7 @@ class AccountService extends BaseGraphqlService {
12683
12613
  });
12684
12614
  }
12685
12615
  listenChanges() {
12686
- if (!this.data.pubkey)
12616
+ if (!this._data.pubkey)
12687
12617
  return Subscription.EMPTY;
12688
12618
  const self = this;
12689
12619
  console.debug('[account] [WS] Listening account changes');
@@ -12697,34 +12627,30 @@ class AccountService extends BaseGraphqlService {
12697
12627
  message: 'ERROR.ACCOUNT.SUBSCRIBE_ACCOUNT_ERROR'
12698
12628
  }
12699
12629
  }).subscribe({
12700
- next({ data }) {
12630
+ next: ({ data }) => __awaiter(this, void 0, void 0, function* () {
12701
12631
  var _a;
12702
- return __awaiter(this, void 0, void 0, function* () {
12703
- if (!data)
12704
- return;
12705
- const existingUpdateDate = toDateISOString((_a = self.data.account) === null || _a === void 0 ? void 0 : _a.updateDate);
12706
- if (existingUpdateDate !== data.updateDate) {
12707
- console.debug(`[account] [WS] Detected update on {${data.updateDate}}`);
12708
- yield self.refresh();
12709
- }
12710
- });
12711
- },
12712
- error(err) {
12713
- return __awaiter(this, void 0, void 0, function* () {
12714
- if (err && +err.code === ServerErrorCodes.NOT_FOUND) {
12715
- console.info('[account] Account not exists anymore: force user to logout...', err);
12716
- yield self.logout();
12717
- }
12718
- else if (err && +err.code === ServerErrorCodes.UNAUTHORIZED) {
12719
- console.info('[account] Account not authorized: force user to logout...', err);
12720
- yield self.logout();
12721
- }
12722
- else {
12723
- console.warn('[account] [WS] Received error:', err);
12724
- }
12725
- });
12726
- },
12727
- complete() {
12632
+ if (!data)
12633
+ return;
12634
+ const existingUpdateDate = toDateISOString((_a = self._data.account) === null || _a === void 0 ? void 0 : _a.updateDate);
12635
+ if (existingUpdateDate !== data.updateDate) {
12636
+ console.debug(`[account] [WS] Detected update on {${data.updateDate}}`);
12637
+ yield self.refresh();
12638
+ }
12639
+ }),
12640
+ error: (err) => __awaiter(this, void 0, void 0, function* () {
12641
+ if (err && +err.code === ServerErrorCodes.NOT_FOUND) {
12642
+ console.info('[account] Account not exists anymore: force user to logout...', err);
12643
+ yield self.logout();
12644
+ }
12645
+ else if (err && +err.code === ServerErrorCodes.UNAUTHORIZED) {
12646
+ console.info('[account] Account not authorized: force user to logout...', err);
12647
+ yield self.logout();
12648
+ }
12649
+ else {
12650
+ console.warn('[account] [WS] Received error:', err);
12651
+ }
12652
+ }),
12653
+ complete: () => {
12728
12654
  console.debug('[account] [WS] Completed');
12729
12655
  }
12730
12656
  });
@@ -12765,29 +12691,40 @@ class AccountService extends BaseGraphqlService {
12765
12691
  this._$additionalFields.next(values.concat(field));
12766
12692
  }
12767
12693
  /* -- protected method -- */
12694
+ resetData() {
12695
+ this._data.loaded = false;
12696
+ this._data.keypair = null;
12697
+ this._data.authToken = null;
12698
+ this._data.authBasic = null;
12699
+ this._data.pubkey = null;
12700
+ this._data.mainProfile = null;
12701
+ this._data.account = new Account();
12702
+ this._data.person = null;
12703
+ this._data.department = null;
12704
+ }
12768
12705
  loadData(opts) {
12769
12706
  return __awaiter(this, void 0, void 0, function* () {
12770
- if (!this.data.pubkey)
12707
+ if (!this._data.pubkey)
12771
12708
  throw new Error('User not logged');
12772
- this.data.loaded = false;
12709
+ this._data.loaded = false;
12773
12710
  try {
12774
- let account = (yield this.load(opts)) || new Account();
12711
+ const account = (yield this.load(opts)) || new Account();
12775
12712
  // Set defaults
12776
12713
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12777
12714
  account.settings = account.settings || new UserSettings();
12778
12715
  account.settings.locale = account.settings.locale || this.settings.locale;
12779
12716
  account.settings.latLongFormat = account.settings.latLongFormat || this.settings.latLongFormat || 'DDMM';
12780
12717
  // Read main profile
12781
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12718
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12782
12719
  // Update, instead of replace it
12783
- if (this.data.account) {
12784
- this.data.account.fromObject(account);
12720
+ if (this._data.account) {
12721
+ this._data.account.fromObject(account);
12785
12722
  }
12786
12723
  else {
12787
- this.data.account = account;
12724
+ this._data.account = account;
12788
12725
  }
12789
- this.data.loaded = true;
12790
- return this.data.account;
12726
+ this._data.loaded = true;
12727
+ return this._data.account;
12791
12728
  }
12792
12729
  catch (error) {
12793
12730
  this.resetData();
@@ -12821,9 +12758,9 @@ class AccountService extends BaseGraphqlService {
12821
12758
  return;
12822
12759
  if (this._debug)
12823
12760
  console.debug(`[account] Account restoration...`);
12824
- this.data.authToken = token;
12825
- this.data.pubkey = pubkey;
12826
- this.data.keypair = seckey && {
12761
+ this._data.authToken = token;
12762
+ this._data.pubkey = pubkey;
12763
+ this._data.keypair = seckey && {
12827
12764
  publicKey: Base58.decode(pubkey),
12828
12765
  secretKey: Base58.decode(seckey)
12829
12766
  } || null;
@@ -12831,7 +12768,7 @@ class AccountService extends BaseGraphqlService {
12831
12768
  if (this.network.online) {
12832
12769
  try {
12833
12770
  yield this.authenticate();
12834
- if (!this.data.authToken && !this.data.authBasic)
12771
+ if (!this._data.authToken && !this._data.authBasic)
12835
12772
  throw new Error('Authentication failed');
12836
12773
  }
12837
12774
  catch (error) {
@@ -12862,14 +12799,14 @@ class AccountService extends BaseGraphqlService {
12862
12799
  // Transform to entity
12863
12800
  const account = Account.fromObject(jsonAccount);
12864
12801
  // Update data
12865
- this.data.account = account;
12866
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12867
- this.data.loaded = true;
12802
+ this._data.account = account;
12803
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12804
+ this._data.loaded = true;
12868
12805
  // Emit event
12869
- this.onLogin.next(this.data.account);
12870
- this.onChange.next(this.data.account);
12806
+ this.onLogin.next(this._data.account);
12807
+ this.onChange.next(this._data.account);
12871
12808
  if (this._debug)
12872
- console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this.data.mainProfile}}`);
12809
+ console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this._data.mainProfile}}`);
12873
12810
  return account;
12874
12811
  });
12875
12812
  }
@@ -12878,13 +12815,13 @@ class AccountService extends BaseGraphqlService {
12878
12815
  */
12879
12816
  saveLocally() {
12880
12817
  return __awaiter(this, void 0, void 0, function* () {
12881
- if (!this.data.pubkey)
12818
+ if (!this._data.pubkey)
12882
12819
  throw new Error('User not logged');
12883
12820
  if (this._debug)
12884
- console.debug(`[account] Saving account {${this.data.pubkey.substring(0, 6)}} in local storage...`);
12821
+ console.debug(`[account] Saving account {${this._data.pubkey.substring(0, 6)}} in local storage...`);
12885
12822
  // Convert account to json
12886
- const json = this.data.account.asObject({ keepTypename: true });
12887
- const seckey = this.data.keypair && this.data.keypair.secretKey && Base58.encode(this.data.keypair.secretKey) || null;
12823
+ const json = this._data.account.asObject({ keepTypename: true });
12824
+ const seckey = this._data.keypair && this._data.keypair.secretKey && Base58.encode(this._data.keypair.secretKey) || null;
12888
12825
  // Convert avatar URL to dataUrl (e.g. 'data:image/png:<base64 content>')
12889
12826
  const hasAvatarUrl = json.avatar && !json.avatar.endsWith(DEFAULT_AVATAR_IMAGE) &&
12890
12827
  (json.avatar.startsWith('http://') || (json.avatar.startsWith('https://')));
@@ -12905,9 +12842,9 @@ class AccountService extends BaseGraphqlService {
12905
12842
  }
12906
12843
  try {
12907
12844
  yield Promise.all([
12908
- this.storage.set(PUBKEY_STORAGE_KEY, this.data.pubkey),
12909
- this.storage.set(TOKEN_STORAGE_KEY, this.data.authToken),
12910
- this.storage.set(`${ACCOUNT_STORAGE_KEY}#${this.data.pubkey}`, json),
12845
+ this.storage.set(PUBKEY_STORAGE_KEY, this._data.pubkey),
12846
+ this.storage.set(TOKEN_STORAGE_KEY, this._data.authToken),
12847
+ this.storage.set(`${ACCOUNT_STORAGE_KEY}#${this._data.pubkey}`, json),
12911
12848
  // Secret key (optional)
12912
12849
  seckey && this.storage.set(SECKEY_STORAGE_KEY, seckey) || this.storage.remove(SECKEY_STORAGE_KEY),
12913
12850
  // Remove old storage key
@@ -12965,7 +12902,7 @@ class AccountService extends BaseGraphqlService {
12965
12902
  }
12966
12903
  authenticateAndGetToken(token, counter) {
12967
12904
  return __awaiter(this, void 0, void 0, function* () {
12968
- if (!this.data.pubkey)
12905
+ if (!this._data.pubkey)
12969
12906
  throw new Error('User not logged');
12970
12907
  if (!counter)
12971
12908
  console.info('[account] Authentication on pod...');
@@ -12991,7 +12928,7 @@ class AccountService extends BaseGraphqlService {
12991
12928
  if (data && data.authenticate) {
12992
12929
  // Store the token
12993
12930
  this.onAuthTokenChange.next(token);
12994
- console.info(`[account] Authentication on pod [OK] {pubkey: '${this.data.pubkey.substr(0, 8)}'}`);
12931
+ console.info(`[account] Authentication on pod [OK] {pubkey: '${this._data.pubkey.substr(0, 8)}'}`);
12995
12932
  return token; // return the token
12996
12933
  }
12997
12934
  // Continue (will retry with another challenge)
@@ -13017,8 +12954,8 @@ class AccountService extends BaseGraphqlService {
13017
12954
  }
13018
12955
  // TODO: check server pubkey as a valid certificate
13019
12956
  // Do the challenge
13020
- const signature = yield this.cryptoService.sign(data.authChallenge.challenge, this.data.keypair);
13021
- const newToken = `${this.data.pubkey}:${data.authChallenge.challenge}|${signature}`;
12957
+ const signature = yield this.cryptoService.sign(data.authChallenge.challenge, this._data.keypair);
12958
+ const newToken = `${this._data.pubkey}:${data.authChallenge.challenge}|${signature}`;
13022
12959
  // iterate with the new token
13023
12960
  return yield this.authenticateAndGetToken(newToken, (counter || 1) + 1 /* increment */);
13024
12961
  });
@@ -13377,9 +13314,8 @@ class ConfigService extends BaseGraphqlService {
13377
13314
  }
13378
13315
  get config() {
13379
13316
  // If first call: start loading
13380
- if (!this._started) {
13317
+ if (!this._started)
13381
13318
  this.start();
13382
- }
13383
13319
  return this.$data.pipe(filter(isNotNil));
13384
13320
  }
13385
13321
  start() {
@@ -13390,30 +13326,38 @@ class ConfigService extends BaseGraphqlService {
13390
13326
  console.info('[config] Starting configuration...');
13391
13327
  this._startPromise = this.graphql.ready()
13392
13328
  .then(() => this.loadOrRestoreLocally())
13393
- .then(() => {
13329
+ .then((data) => {
13394
13330
  this._started = true;
13395
13331
  this._startPromise = undefined;
13332
+ this.$data.next(data);
13333
+ return data;
13396
13334
  })
13397
13335
  .catch((err) => {
13398
13336
  console.error(err && err.message || err, err);
13337
+ this._started = false;
13399
13338
  this._startPromise = undefined;
13339
+ return null;
13400
13340
  });
13401
13341
  return this._startPromise;
13402
13342
  }
13403
13343
  stop() {
13404
- this._subscription.unsubscribe();
13405
- this._subscription = new Subscription();
13406
- this._started = false;
13407
- this._startPromise = undefined;
13344
+ return __awaiter(this, void 0, void 0, function* () {
13345
+ this._subscription.unsubscribe();
13346
+ this._subscription = new Subscription();
13347
+ this._started = false;
13348
+ this._startPromise = undefined;
13349
+ });
13408
13350
  }
13409
13351
  restart() {
13410
- if (this.started)
13411
- this.stop();
13412
- return this.start();
13352
+ return __awaiter(this, void 0, void 0, function* () {
13353
+ if (this.started)
13354
+ yield this.stop();
13355
+ return this.start();
13356
+ });
13413
13357
  }
13414
13358
  ready() {
13415
13359
  if (this._started)
13416
- return Promise.resolve();
13360
+ return Promise.resolve(this.$data.value);
13417
13361
  if (this._startPromise)
13418
13362
  return this._startPromise;
13419
13363
  return this.start();
@@ -13468,7 +13412,7 @@ class ConfigService extends BaseGraphqlService {
13468
13412
  console.debug('[config] Pod configuration saved!');
13469
13413
  const reloadedConfig = yield this.loadDefault({ fetchPolicy: 'network-only' });
13470
13414
  // If this is the default config
13471
- const defaultConfig = this.$data.getValue();
13415
+ const defaultConfig = this.$data.value;
13472
13416
  if (isNotNil(defaultConfig) && reloadedConfig.label === defaultConfig.label) {
13473
13417
  // Emit update event when is default config
13474
13418
  this.$data.next(reloadedConfig);
@@ -13545,7 +13489,7 @@ class ConfigService extends BaseGraphqlService {
13545
13489
  if (wasJustLoaded) {
13546
13490
  // TODO
13547
13491
  }
13548
- this.$data.next(data);
13492
+ return data;
13549
13493
  });
13550
13494
  }
13551
13495
  restoreLocally() {
@@ -13765,7 +13709,7 @@ class PlatformService {
13765
13709
  .then(() => {
13766
13710
  this._started = true;
13767
13711
  this._startPromise = undefined;
13768
- console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}} in ${Date.now() - now}ms`);
13712
+ console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}, downloader: ${!!this.downloader} in ${Date.now() - now}ms`);
13769
13713
  // Update cache configuration when network changed
13770
13714
  this.networkService.onNetworkStatusChanges.subscribe((type) => this.configureCache(type !== 'none'));
13771
13715
  // Update authentication type
@@ -13813,18 +13757,30 @@ class PlatformService {
13813
13757
  }
13814
13758
  }
13815
13759
  download(request) {
13816
- if (!request || !request.uri)
13817
- throw new Error('Missing argument \'request\' or \'request.uri\'');
13818
- if (this._android && this._cordova) {
13819
- request = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted, title: request.filename || '' }, request), { destinationInExternalFilesDir: Object.assign({ dirType: 'Downloads', subPath: request.filename || request.title || '' }, request.destinationInExternalFilesDir) });
13820
- this.downloader.download(request)
13821
- .then((location) => console.info('[platform] File successfully downloaded at:' + location))
13822
- .catch((error) => console.error(error));
13823
- }
13824
- // Web mode: open URI using the browser
13825
- else {
13826
- this.open(request.uri, '_system', 'location=yes');
13827
- }
13760
+ return __awaiter(this, void 0, void 0, function* () {
13761
+ if (!request || !request.uri)
13762
+ throw new Error('Missing argument \'request\' or \'request.uri\'');
13763
+ const downloader = this.downloader || window.plugins.Downloader;
13764
+ if (downloader && this._android && this._cordova) {
13765
+ request = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted, title: request.filename || '' }, request), { destinationInExternalFilesDir: Object.assign({ dirType: 'Downloads', subPath: request.filename || request.title || '' }, request.destinationInExternalFilesDir) });
13766
+ try {
13767
+ const location = yield downloader.download(request);
13768
+ console.info('[platform] File successfully downloaded at:' + location);
13769
+ return location;
13770
+ }
13771
+ catch (err) {
13772
+ console.error('[platform] Error while downloading: ' + (err && err.message || err), err);
13773
+ throw err;
13774
+ }
13775
+ }
13776
+ // Fallback (no Cordova)
13777
+ // Web mode: open URI using the browser
13778
+ else {
13779
+ console.warn('[platform] Cannot use Cordova downloader: using browser open()');
13780
+ this.open(request.uri, '_system', 'location=no', true);
13781
+ return undefined;
13782
+ }
13783
+ });
13828
13784
  }
13829
13785
  /* -- protected methods -- */
13830
13786
  configureCordovaPlugins(mobile) {
@@ -18549,6 +18505,7 @@ class AppInstallUpgradeCard {
18549
18505
  downloadLink(event, link) {
18550
18506
  if (!link || !link.url)
18551
18507
  return; // Skip
18508
+ console.info(`[install-upgrade-card] Downloading '${link.url}' ...`);
18552
18509
  this.platform.download({
18553
18510
  uri: link.url,
18554
18511
  filename: link.downloadFilename,
@@ -18593,15 +18550,20 @@ class AppInstallUpgradeCard {
18593
18550
  return undefined;
18594
18551
  }
18595
18552
  getCompatibleUpgradeLinks(installLinks, config) {
18596
- const appMinVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18597
- const needUpgrade = appMinVersion && !VersionUtils.isCompatible(appMinVersion, this.environment.version);
18553
+ const appVersion = this.environment.version;
18554
+ const appMinVersionFromPod = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18555
+ if (!appVersion) {
18556
+ console.error('Missing value for \'environment.version\': cannot check app compatibility!');
18557
+ return undefined;
18558
+ }
18559
+ const needUpgrade = appMinVersionFromPod && !VersionUtils.isCompatible(appMinVersionFromPod, appVersion);
18598
18560
  if (!needUpgrade)
18599
18561
  return undefined;
18600
18562
  const upgradeLinks = installLinks
18601
18563
  .filter(link => this.platform.is('mobileweb') || (link.platform && this.platform.is(link.platform)));
18602
18564
  // Use min version as default version
18603
18565
  upgradeLinks.forEach(link => {
18604
- link.version = link.version || appMinVersion;
18566
+ link.version = link.version || appMinVersionFromPod;
18605
18567
  });
18606
18568
  return isNotEmptyArray(upgradeLinks) ? upgradeLinks : undefined;
18607
18569
  }
@@ -18613,6 +18575,7 @@ class AppInstallUpgradeCard {
18613
18575
  let url = config.getProperty(CORE_CONFIG_OPTIONS.ANDROID_INSTALL_URL);
18614
18576
  if (isNilOrBlank(url))
18615
18577
  url = this.environment.defaultAndroidInstallUrl || null;
18578
+ const minVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18616
18579
  // Compute App name
18617
18580
  const name = isNotNilOrBlank(url) && config.label || this.environment.defaultAppName || 'SUMARiS';
18618
18581
  if (url) {
@@ -18621,7 +18584,7 @@ class AppInstallUpgradeCard {
18621
18584
  let mimeType;
18622
18585
  // Get file name
18623
18586
  const filename = this.getFilename(url);
18624
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18587
+ version = minVersion || 'latest';
18625
18588
  // OK, this is a downloadable APK file (e.g. NOT a link to a playstore)
18626
18589
  if (filename === null || filename === void 0 ? void 0 : filename.endsWith('.apk')) {
18627
18590
  // Define mime type
@@ -18631,13 +18594,12 @@ class AppInstallUpgradeCard {
18631
18594
  version = versionMatches && versionMatches[1] || version;
18632
18595
  // Compute a new file name, with the version
18633
18596
  if (isNotNilOrBlank(name)) {
18634
- downloadFilename = `${name}-${version}.apk`;
18597
+ downloadFilename = `${name}-v${version}.apk`.toLowerCase();
18635
18598
  }
18636
18599
  else {
18637
18600
  downloadFilename = filename;
18638
- // Replace 'latest' with the app min version
18601
+ // Replace 'latest' with the app version, if present
18639
18602
  if (downloadFilename.indexOf('latest')) {
18640
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18641
18603
  downloadFilename = downloadFilename.replace('latest', version);
18642
18604
  }
18643
18605
  }
@@ -23271,6 +23233,7 @@ let PersonFilter = PersonFilter_1 = class PersonFilter extends EntityFilter {
23271
23233
  this.userProfiles = source.userProfiles;
23272
23234
  this.excludedIds = source.excludedIds;
23273
23235
  this.searchAttribute = source.searchAttribute;
23236
+ this.searchAttributes = source.searchAttributes;
23274
23237
  }
23275
23238
  asObject(opts) {
23276
23239
  const target = super.asObject(opts);
@@ -23281,6 +23244,7 @@ let PersonFilter = PersonFilter_1 = class PersonFilter extends EntityFilter {
23281
23244
  target.userProfiles = this.userProfiles;
23282
23245
  target.excludedIds = this.excludedIds;
23283
23246
  target.searchAttribute = this.searchAttribute;
23247
+ target.searchAttributes = this.searchAttributes;
23284
23248
  return target;
23285
23249
  }
23286
23250
  buildFilter() {
@@ -23294,7 +23258,7 @@ let PersonFilter = PersonFilter_1 = class PersonFilter extends EntityFilter {
23294
23258
  filterFns.push(e => isNil(e.id) || !this.excludedIds.includes(e.id));
23295
23259
  }
23296
23260
  // Search text
23297
- const searchTextFilter = EntityUtils.searchTextFilter(this.searchAttribute || ['lastName', 'firstName', 'department.name'], this.searchText);
23261
+ const searchTextFilter = EntityUtils.searchTextFilter(this.searchAttribute || this.searchAttributes || ['lastName', 'firstName', 'department.name'], this.searchText);
23298
23262
  if (searchTextFilter)
23299
23263
  filterFns.push(searchTextFilter);
23300
23264
  return filterFns;
@@ -23411,7 +23375,8 @@ class PersonService extends BaseEntityService {
23411
23375
  if (ReferentialUtils.isNotEmpty(value))
23412
23376
  return { data: [value] };
23413
23377
  value = (typeof value === 'string' && value !== '*') && value || undefined;
23414
- return this.loadAll(0, !value ? 30 : 10, sortBy || (filter === null || filter === void 0 ? void 0 : filter.searchAttribute), sortDirection, Object.assign(Object.assign({}, filter), { searchText: value, statusIds: filter && filter.statusIds || [StatusIds.ENABLE], userProfiles: filter && filter.userProfiles }), { withTotal: true /* need by autocomplete */ });
23378
+ sortBy = sortBy || filter.searchAttribute || (filter.searchAttributes && filter.searchAttributes[0]);
23379
+ return this.loadAll(0, !value ? 30 : 10, sortBy, sortDirection, Object.assign(Object.assign({}, filter), { searchText: value, statusIds: filter && filter.statusIds || [StatusIds.ENABLE], userProfiles: filter && filter.userProfiles }), { withTotal: true /* need by autocomplete */ });
23415
23380
  });
23416
23381
  }
23417
23382
  executeImport(progression, opts) {
@@ -23529,6 +23494,7 @@ class UsersPage extends AppTable {
23529
23494
  this.filterCriteriaCount = 0;
23530
23495
  this.statusList = StatusList;
23531
23496
  this.statusById = StatusById;
23497
+ this.useSticky = false;
23532
23498
  this.referentialToString = referentialToString;
23533
23499
  this.inlineEdition = accountService.isAdmin(); // Allow inline edition only if admin
23534
23500
  this.canEdit = accountService.isAdmin();
@@ -23632,13 +23598,13 @@ class UsersPage extends AppTable {
23632
23598
  UsersPage.decorators = [
23633
23599
  { type: Component, args: [{
23634
23600
  selector: 'app-users-table',
23635
- template: "<app-toolbar [title]=\"'USER.LIST.TITLE'|translate\"\n color=\"primary\"\n [canGoBack]=\"false\"\n [hasValidate]=\"!(loadingSubject|async) && dirty\"\n (onValidate)=\"save()\">\n <ion-buttons slot=\"end\">\n <ng-container *ngIf=\"!selection.hasValue(); else hasSelection\">\n <!-- Add -->\n <button mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"'COMMON.BTN_ADD'|translate\"\n (click)=\"addRow()\">\n <mat-icon>add</mat-icon>\n </button>\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <!-- reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\"\n *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold;\">close</mat-icon>\n </button>\n\n <!-- show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon *ngIf=\"filterCriteriaCount; else emptyFilter\"\n [matBadge]=\"filterCriteriaCount\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\">filter_list_alt</mat-icon>\n <ng-template #emptyFilter>\n <mat-icon>filter_list_alt</mat-icon>\n </ng-template>\n </button>\n </ng-container>\n\n <ng-template #hasSelection>\n <!-- delete -->\n <button mat-icon-button\n class=\"hidden-xs hidden-sm\"\n [title]=\"'COMMON.BTN_DELETE'|translate\"\n (click)=\"deleteSelection($event)\">\n <mat-icon>delete</mat-icon>\n </button>\n </ng-template>\n </ion-buttons>\n</app-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <!-- error -->\n <ion-item *ngIf=\"errorSubject|async ; let error\" lines=\"none\" @slideUpDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel class=\"ion-no-padding filter-panel filter-panel-floating\">\n <form class=\"form-container ion-padding\" [formGroup]=\"filterForm\" (ngSubmit)=\"onRefresh.emit()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <input matInput [placeholder]=\"'USER.LIST.FILTER.SEARCH'|translate\" formControlName=\"searchText\">\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- status -->\n <mat-form-field>\n <mat-select formControlName=\"statusId\" [placeholder]=\"'USER.STATUS'|translate\" >\n <mat-option [value]=\"null\"><i><span translate>COMMON.EMPTY_OPTION</span></i></mat-option>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label |translate }}\n </mat-option>\n </mat-select>\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.statusId)\"\n [hidden]=\"filterForm.controls.statusId.disabled || !filterForm.controls.statusId.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label [hidden]=\"(loadingSubject|async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\">\n {{ (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: {count: (totalRowCount |\n numberFormat)} }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\"\n (click)=\"filterExpansionPanel.close()\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n\n </mat-action-row>\n </mat-expansion-panel>\n\n <!-- error -->\n <ion-item *ngIf=\"error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"table-container\">\n <table #table mat-table matSort\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\" [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">{{ row.currentData.id }}</td>\n </ng-container>\n\n <!-- avatar Column -->\n <ng-container matColumnDef=\"avatar\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let row\">\n <div class=\"avatar\" *ngIf=\"row.currentData.avatar; else generateIcon\"\n [ngStyle]=\"{'background-image':'url('+row.currentData.avatar+')'}\"></div>\n <ng-template #generateIcon>\n <div class=\"avatar\">\n <svg width=\"38\" width=\"38\" [data-jdenticon-value]=\"row.currentData.id\"></svg>\n </div>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- lastName -->\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.LAST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['lastName']\" [placeholder]=\"'USER.LAST_NAME'|translate\"\n [readonly]=\"!row.editing\" [appAutofocus]=\"row == -1 && row.editing\">\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('minlength')\">\n <span>{{'ERROR.FIELD_MIN_LENGTH' | translate: {minLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n </td>\n\n </ng-container>\n\n <!-- firstname -->\n <ng-container matColumnDef=\"firstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.FIRST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['firstName']\" [placeholder]=\"'USER.FIRST_NAME'|translate\"\n [readonly]=\"!row.editing\">\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('minlength')\">\n <span>{{'ERROR.FIELD_MIN_LENGTH' | translate: {minLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- email -->\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.EMAIL</span>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['email']\" [placeholder]=\"'USER.EMAIL'|translate\"\n [readonly]=\"!row.editing\">\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('email')\">\n <span translate>ERROR.FIELD_NOT_VALID_EMAIL</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- additional fields -->\n <ng-container *ngFor=\"let definition of additionalFields\" [matColumnDef]=\"definition.key\">\n <th mat-header-cell *matHeaderCellDef>\n <span>{{definition.label|translate}}</span>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <app-form-field floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"row.validator.controls[definition.key]\"\n [required]=\"definition.extra?.account?.required\">\n </app-form-field>\n </td>\n </ng-container>\n\n <!-- profile column -->\n <ng-container matColumnDef=\"profile\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.PROFILE</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\">\n <mat-form-field floatLabel=\"never\">\n <mat-select [formControl]=\"row.validator.controls['mainProfile']\" [placeholder]=\"'USER.PROFILE'|translate\">\n <mat-option *ngFor=\"let item of profiles\" [value]=\"item\">\n {{ ('USER.PROFILE_ENUM.' + item) | uppercase |translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['mainProfile'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- Status column -->\n <ng-container matColumnDef=\"status\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.STATUS</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\">\n <mat-form-field floatLabel=\"never\">\n <ion-icon matPrefix *ngIf=\"row.validator.controls['statusId'].value &gt;=0\" [name]=\"statusById[row.validator.controls['statusId'].value]?.icon\"></ion-icon>\n\n <mat-select [formControl]=\"row.validator.controls['statusId']\" [placeholder]=\"'REFERENTIAL.STATUS'|translate\">\n <mat-select-trigger>\n <span *ngIf=\"row.validator.controls['statusId'].value &gt;=0\">\n {{ statusById[row.validator.controls['statusId'].value]?.label | translate}}</span>\n </mat-select-trigger>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label |translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['statusId'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- username -->\n <ng-container matColumnDef=\"username\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls.username\" [placeholder]=\"'USER.USERNAME'|translate\"\n [readonly]=\"!row.editing\">\n <mat-error *ngIf=\"row.validator.controls.username.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- username extranet -->\n <ng-container matColumnDef=\"usernameExtranet\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME_EXTRANET</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls.usernameExtranet\" [placeholder]=\"'USER.USERNAME_EXTRANET'|translate\"\n [readonly]=\"!row.editing\">\n <mat-error *ngIf=\"row.validator.controls.usernameExtranet.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- pubkey -->\n <ng-container matColumnDef=\"pubkey\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.PUBKEY</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.mat-form-field-disabled]=\"!row.editing\" [title]=\"row.validator.controls['pubkey'].value\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['pubkey']\" [placeholder]=\"'USER.PUBKEY'|translate\"\n [readonly]=\"!row.editing\" autocomplete=\"off\">\n <mat-error *ngIf=\"row.validator.controls['pubkey'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['pubkey'].hasError('pubkey')\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column [stickyEnd]=\"true\"\n (optionsClick)=\"openSelectColumnsModal($event)\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\">\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-error]=\"row.validator.invalid\"\n [class.mat-row-dirty]=\"row.currentData.dirty\"\n [class.mat-row-disabled]=\"!row.editing\"\n (click)=\"clickRow($event, row)\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n\n<ion-footer>\n <mat-paginator class=\"mat-paginator-footer\"\n [length]=\"totalRowCount\" [pageSize]=\"defaultPageSize\"\n [pageSizeOptions]=\"defaultPageSizeOptions\" showFirstLastButtons>\n </mat-paginator>\n\n <app-form-buttons-bar *ngIf=\"!mobile && inlineEdition\"\n (onCancel)=\"onRefresh.emit()\" (onSave)=\"save()\" [disabled]=\"(loadingSubject|async) || !dirty\"></app-form-buttons-bar>\n</ion-footer>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addRow()\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n",
23601
+ template: "<app-toolbar [title]=\"'USER.LIST.TITLE'|translate\"\n color=\"primary\"\n [canGoBack]=\"false\"\n [hasValidate]=\"!(loadingSubject|async) && dirty\"\n (onValidate)=\"save()\">\n <ion-buttons slot=\"end\">\n <ng-container *ngIf=\"!selection.hasValue(); else hasSelection\">\n <!-- Add -->\n <button mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"'COMMON.BTN_ADD'|translate\"\n (click)=\"addRow()\">\n <mat-icon>add</mat-icon>\n </button>\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <!-- reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\"\n *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold;\">close</mat-icon>\n </button>\n\n <!-- show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon *ngIf=\"filterCriteriaCount; else emptyFilter\"\n [matBadge]=\"filterCriteriaCount\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\">filter_list_alt\n </mat-icon>\n <ng-template #emptyFilter>\n <mat-icon>filter_list_alt</mat-icon>\n </ng-template>\n </button>\n </ng-container>\n\n <ng-template #hasSelection>\n <!-- delete -->\n <button mat-icon-button\n class=\"hidden-xs hidden-sm\"\n [title]=\"'COMMON.BTN_DELETE'|translate\"\n (click)=\"deleteSelection($event)\">\n <mat-icon>delete</mat-icon>\n </button>\n </ng-template>\n </ion-buttons>\n</app-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <!-- error -->\n <ion-item *ngIf=\"errorSubject|async ; let error\" lines=\"none\" @slideUpDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel class=\"ion-no-padding filter-panel filter-panel-floating\">\n <form class=\"form-container ion-padding\" [formGroup]=\"filterForm\" (ngSubmit)=\"onRefresh.emit()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <input matInput [placeholder]=\"'USER.LIST.FILTER.SEARCH'|translate\" formControlName=\"searchText\">\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- status -->\n <mat-form-field>\n <mat-select formControlName=\"statusId\" [placeholder]=\"'USER.STATUS'|translate\">\n <mat-option [value]=\"null\"><i><span translate>COMMON.EMPTY_OPTION</span></i></mat-option>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label |translate }}\n </mat-option>\n </mat-select>\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.statusId)\"\n [hidden]=\"filterForm.controls.statusId.disabled || !filterForm.controls.statusId.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label [hidden]=\"(loadingSubject|async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\">\n {{ (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: {\n count: (totalRowCount |\n numberFormat)\n } }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\"\n (click)=\"filterExpansionPanel.close()\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n\n </mat-action-row>\n </mat-expansion-panel>\n\n <!-- error -->\n <ion-item *ngIf=\"error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"table-container\">\n <table #table mat-table matSort\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\" [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">{{ row.currentData.id }}</td>\n </ng-container>\n\n <!-- avatar Column -->\n <ng-container matColumnDef=\"avatar\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let row\">\n <div class=\"avatar\" *ngIf=\"row.currentData.avatar; else generateIcon\"\n [ngStyle]=\"{'background-image':'url('+row.currentData.avatar+')'}\"></div>\n <ng-template #generateIcon>\n <div class=\"avatar\">\n <svg width=\"38\" width=\"38\" [data-jdenticon-value]=\"row.currentData.id\"></svg>\n </div>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- lastName -->\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.LAST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn='lastName'\">\n <mat-form-field floatLabel=\"never\">\n <input matInput\n [formControl]=\"row.validator.controls['lastName']\"\n [placeholder]=\"'USER.LAST_NAME'|translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn==='lastName'\">\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('minlength')\">\n <span>{{'ERROR.FIELD_MIN_LENGTH' | translate: {minLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n </td>\n\n </ng-container>\n\n <!-- firstname -->\n <ng-container matColumnDef=\"firstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.FIRST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n\n (click)=\"focusColumn='firstName'\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['firstName']\"\n [placeholder]=\"'USER.FIRST_NAME'|translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn==='firstName'\">\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('minlength')\">\n <span>{{'ERROR.FIELD_MIN_LENGTH' | translate: {minLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- email -->\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.EMAIL</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn='email'\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls['email']\"\n [placeholder]=\"'USER.EMAIL'|translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn==='email'\">\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('email')\">\n <span translate>ERROR.FIELD_NOT_VALID_EMAIL</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- additional fields -->\n <ng-container *ngFor=\"let definition of additionalFields\" [matColumnDef]=\"definition.key\">\n <th mat-header-cell *matHeaderCellDef>\n <span>{{definition.label|translate}}</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n (click)=\"focusColumn=definition.key\">\n <app-form-field floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"row.validator.controls[definition.key]\"\n [required]=\"definition.extra?.account?.required\"\n [autofocus]=\"row.editing && focusColumn===definition.ke\">\n </app-form-field>\n </td>\n </ng-container>\n\n <!-- profile column -->\n <ng-container matColumnDef=\"profile\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.PROFILE</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n (click)=\"focusColumn='profile'\">\n <mat-form-field floatLabel=\"never\">\n <mat-select [formControl]=\"row.validator.controls['mainProfile']\"\n [placeholder]=\"'USER.PROFILE'|translate\">\n <mat-option *ngFor=\"let item of profiles\" [value]=\"item\">\n {{ ('USER.PROFILE_ENUM.' + item) | uppercase |translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['mainProfile'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- Status column -->\n <ng-container matColumnDef=\"status\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.STATUS</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n (click)=\"focusColumn='status'\">\n <mat-form-field floatLabel=\"never\">\n <ion-icon matPrefix *ngIf=\"row.validator.controls['statusId'].value &gt;=0\" [name]=\"statusById[row.validator.controls['statusId'].value]?.icon\"></ion-icon>\n\n <mat-select [formControl]=\"row.validator.controls['statusId']\"\n [placeholder]=\"'REFERENTIAL.STATUS'|translate\">\n <mat-select-trigger>\n <span *ngIf=\"row.validator.controls['statusId'].value &gt;=0\">\n {{ statusById[row.validator.controls['statusId'].value]?.label | translate}}</span>\n </mat-select-trigger>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label |translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['statusId'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- username -->\n <ng-container matColumnDef=\"username\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n (click)=\"focusColumn='username'\">\n <mat-form-field floatLabel=\"never\" >\n <input matInput [formControl]=\"row.validator.controls.username\"\n [placeholder]=\"'USER.USERNAME'|translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn==='username'\">\n <mat-error *ngIf=\"row.validator.controls.username.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- username extranet -->\n <ng-container matColumnDef=\"usernameExtranet\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME_EXTRANET</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n (click)=\"focusColumn='usernameExtranet'\">\n <mat-form-field floatLabel=\"never\">\n <input matInput [formControl]=\"row.validator.controls.usernameExtranet\"\n [placeholder]=\"'USER.USERNAME_EXTRANET'|translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn==='usernameExtranet'\">\n <mat-error *ngIf=\"row.validator.controls.usernameExtranet.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- pubkey -->\n <ng-container matColumnDef=\"pubkey\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.PUBKEY</span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [title]=\"row.validator.controls['pubkey'].value\"\n (click)=\"focusColumn='pubkey'\">\n <mat-form-field floatLabel=\"never\" >\n <input matInput [formControl]=\"row.validator.controls['pubkey']\" [placeholder]=\"'USER.PUBKEY'|translate\"\n [readonly]=\"!row.editing\" autocomplete=\"off\">\n <mat-error *ngIf=\"row.validator.controls['pubkey'].hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"row.validator.controls['pubkey'].hasError('pubkey')\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column [stickyEnd]=\"useSticky\"\n (optionsClick)=\"openSelectColumnsModal($event)\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\"\n [canCancel]=\"false\">\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-error]=\"row.validator.invalid\"\n [class.mat-row-dirty]=\"row.currentData.dirty\"\n [class.mat-row-disabled]=\"!row.editing\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator.invalid\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n\n<ion-footer>\n <mat-paginator class=\"mat-paginator-footer\"\n [length]=\"totalRowCount\" [pageSize]=\"defaultPageSize\"\n [pageSizeOptions]=\"defaultPageSizeOptions\" showFirstLastButtons>\n </mat-paginator>\n\n <app-form-buttons-bar *ngIf=\"!mobile && inlineEdition\"\n (onCancel)=\"onRefresh.emit()\" (onSave)=\"save()\" [disabled]=\"(loadingSubject|async) || !dirty\"></app-form-buttons-bar>\n</ion-footer>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addRow()\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n",
23636
23602
  providers: [
23637
23603
  { provide: ValidatorService, useExisting: PersonValidatorService }
23638
23604
  ],
23639
23605
  animations: [slideUpDownAnimation],
23640
23606
  changeDetection: ChangeDetectionStrategy.OnPush,
23641
- styles: [".mat-expansion-panel{margin-bottom:5px}.mat-expansion-panel .form-container mat-form-field{width:100%}.mat-expansion-panel mat-action-row ion-label{line-height:36px}.table-container{height:calc(100%)}.mat-table .mat-cell .avatar{height:40px;width:40px;margin:2px 0 0;background-size:cover;background-repeat:no-repeat;background-position:50%}.mat-table .mat-column-avatar{min-width:50px}.mat-table .mat-column-profile{min-width:110px}.mat-table .mat-column-status{min-width:130px}.mat-table .mat-column-department{min-width:150px}"]
23607
+ styles: [".mat-expansion-panel{margin-bottom:5px}.mat-expansion-panel .form-container mat-form-field{width:100%}.mat-expansion-panel mat-action-row ion-label{line-height:36px}.table-container{height:100%}.mat-table .mat-cell .avatar{height:40px;width:40px;margin:2px 0 0;background-size:cover;background-repeat:no-repeat;background-position:50%}.mat-table .mat-column-id{width:30px}.mat-table .mat-column-avatar{width:50px}.mat-table .mat-column-avatar .avatar{border-radius:5px;border:1px solid rgba(var(--ion-color-secondary-rgb),.5)}.mat-table .mat-column-firstName,.mat-table .mat-column-lastName{min-width:80px}.mat-table .mat-column-email{min-width:120px}.mat-table .mat-column-profile{min-width:110px}.mat-table .mat-column-status{min-width:130px}.mat-table .mat-column-department{min-width:150px}"]
23642
23608
  },] }
23643
23609
  ];
23644
23610
  UsersPage.ctorParameters = () => [
@@ -23658,6 +23624,7 @@ UsersPage.ctorParameters = () => [
23658
23624
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
23659
23625
  ];
23660
23626
  UsersPage.propDecorators = {
23627
+ useSticky: [{ type: Input }],
23661
23628
  filterExpansionPanel: [{ type: ViewChild, args: [MatExpansionPanel, { static: true },] }]
23662
23629
  };
23663
23630
 
@@ -23723,5 +23690,5 @@ const ErrorCodes = {
23723
23690
  * Generated bundle index. Do not edit.
23724
23691
  */
23725
23692
 
23726
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AnimationState, AppEditorOptions, AppEntityEditor, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableDataSourceOptions, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CryptoService, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileService, FileSizePipe, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormFieldValuesHolder, Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialChipsModule, MaterialTestingModule, MaterialTestingPage, MathAbsPipe, MenuComponent, MenuItems, MenuService, ModalToolbarComponent, NetworkService, NgInitDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialRef, ReferentialUtils, RegisterConfirmPage, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, SettingsPage, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedValidators, SocialModule, Software, StatusById, StatusIds, StatusList, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UserEvent, UserEventFilter, UserEventService, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equalsOrNil, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isControlHasInput, isEmptyArray, isInputElement, isInstanceOf, isInt, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitIdle, waitWhilePending, ɵ0$5 as ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh };
23727
- //# sourceMappingURL=ngx-sumaris-components.js.map
23693
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AnimationState, AppEditorOptions, AppEntityEditor, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableDataSourceOptions, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CryptoService, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileService, FileSizePipe, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormFieldValuesHolder, Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialChipsModule, MaterialTestingModule, MaterialTestingPage, MathAbsPipe, MenuComponent, MenuItems, MenuService, ModalToolbarComponent, NetworkService, NgInitDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialRef, ReferentialUtils, RegisterConfirmPage, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, SettingsPage, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedValidators, SocialModule, Software, StartableService, StatusById, StatusIds, StatusList, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UserEvent, UserEventFilter, UserEventService, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equalsOrNil, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isControlHasInput, isEmptyArray, isInputElement, isInstanceOf, isInt, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitIdle, waitWhilePending, ɵ0$5 as ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh };
23694
+ //# sourceMappingURL=sumaris-net.ngx-components.js.map