@sumaris-net/ngx-components 0.24.2 → 0.25.2

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 (48) hide show
  1. package/bundles/{ngx-sumaris-components.umd.js → sumaris-net.ngx-components.umd.js} +1038 -926
  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/doc/changelog.md +4 -0
  6. package/esm2015/public_api.js +2 -1
  7. package/esm2015/src/app/admin/users/list/users.js +6 -4
  8. package/esm2015/src/app/core/graphql/graphql.service.js +25 -72
  9. package/esm2015/src/app/core/graphql/graphql.utils.js +1 -1
  10. package/esm2015/src/app/core/install/install-upgrade-card.component.js +105 -46
  11. package/esm2015/src/app/core/services/account.service.js +146 -149
  12. package/esm2015/src/app/core/services/config.service.js +21 -14
  13. package/esm2015/src/app/core/services/local-settings.service.js +72 -87
  14. package/esm2015/src/app/core/services/network.service.js +136 -171
  15. package/esm2015/src/app/core/services/platform.service.js +34 -17
  16. package/esm2015/src/app/core/services/storage/entities-storage.service.js +59 -63
  17. package/esm2015/src/app/shared/audio/audio.js +10 -39
  18. package/esm2015/src/app/shared/material/boolean/material.boolean.js +1 -1
  19. package/esm2015/src/app/shared/services/startable-service.class.js +77 -0
  20. package/esm2015/src/environments/environment.class.js +1 -1
  21. package/esm2015/src/environments/environment.js +5 -1
  22. package/esm2015/sumaris-net.ngx-components.js +13 -0
  23. package/fesm2015/{ngx-sumaris-components.js → sumaris-net.ngx-components.js} +669 -646
  24. package/fesm2015/sumaris-net.ngx-components.js.map +1 -0
  25. package/package.json +13 -9
  26. package/public_api.d.ts +1 -0
  27. package/src/app/admin/users/list/users.d.ts +1 -0
  28. package/src/app/core/graphql/graphql.service.d.ts +5 -13
  29. package/src/app/core/graphql/graphql.utils.d.ts +2 -1
  30. package/src/app/core/install/install-upgrade-card.component.d.ts +12 -7
  31. package/src/app/core/services/account.service.d.ts +11 -10
  32. package/src/app/core/services/config.service.d.ts +6 -5
  33. package/src/app/core/services/local-settings.service.d.ts +4 -9
  34. package/src/app/core/services/network.service.d.ts +27 -38
  35. package/src/app/core/services/platform.service.d.ts +2 -1
  36. package/src/app/core/services/storage/entities-storage.service.d.ts +13 -13
  37. package/src/app/shared/audio/audio.d.ts +4 -8
  38. package/src/app/shared/services/startable-service.class.d.ts +26 -0
  39. package/src/assets/i18n/fr.json +2 -0
  40. package/src/environments/environment.class.d.ts +1 -0
  41. package/{ngx-sumaris-components.d.ts → sumaris-net.ngx-components.d.ts} +0 -0
  42. package/sumaris-net.ngx-components.metadata.json +1 -0
  43. package/bundles/ngx-sumaris-components.umd.js.map +0 -1
  44. package/bundles/ngx-sumaris-components.umd.min.js +0 -25
  45. package/bundles/ngx-sumaris-components.umd.min.js.map +0 -1
  46. package/esm2015/ngx-sumaris-components.js +0 -13
  47. package/fesm2015/ngx-sumaris-components.js.map +0 -1
  48. 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';
@@ -4966,7 +4966,7 @@ MatBooleanField.decorators = [
4966
4966
  DEFAULT_VALUE_ACCESSOR$1
4967
4967
  ],
4968
4968
  changeDetection: ChangeDetectionStrategy.OnPush,
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: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}"]
4970
4970
  },] }
4971
4971
  ];
4972
4972
  MatBooleanField.ctorParameters = () => [
@@ -6888,31 +6888,97 @@ AppFormField.propDecorators = {
6888
6888
  matInput: [{ type: ViewChild, args: ['matInput',] }]
6889
6889
  };
6890
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
+ get starting() {
6948
+ return !!this._startPromise;
6949
+ }
6950
+ ready() {
6951
+ if (this._started)
6952
+ return Promise.resolve(this._data);
6953
+ return this.start();
6954
+ }
6955
+ ngOnStop() {
6956
+ return __awaiter(this, void 0, void 0, function* () {
6957
+ // Can be override by subclasses
6958
+ });
6959
+ }
6960
+ }
6961
+ StartableService.ctorParameters = () => [
6962
+ { type: undefined, decorators: [{ type: Optional }] }
6963
+ ];
6964
+
6891
6965
  const SYSTEM_SOUNDS = [
6892
6966
  { id: 'beep-confirm', assetPath: 'assets/audio/beep-confirm.mp3', vibration: 250 },
6893
6967
  { id: 'beep-error', assetPath: 'assets/audio/beep-error.mp3', vibration: 1000 },
6894
6968
  { id: 'startup', assetPath: 'assets/audio/unfa-ping.mp3', vibration: [1, 500, 250, 750] },
6895
6969
  ];
6896
- class AudioProvider {
6970
+ class AudioProvider extends StartableService {
6897
6971
  constructor(platform, nativeAudio, vibration, audioManagement) {
6972
+ super(platform);
6898
6973
  this.platform = platform;
6899
6974
  this.nativeAudio = nativeAudio;
6900
6975
  this.vibration = vibration;
6901
6976
  this.audioManagement = audioManagement;
6902
- this._started = false;
6903
6977
  this._audioMode = AudioManagement.AudioMode.NORMAL;
6904
6978
  this._preloadedSounds = {};
6905
6979
  this._htmlAudioCache = {};
6906
- this.onStart = new Subject();
6907
6980
  this.start();
6908
6981
  }
6909
- ready() {
6910
- return __awaiter(this, void 0, void 0, function* () {
6911
- if (this._started)
6912
- return Promise.resolve();
6913
- yield this.start();
6914
- });
6915
- }
6916
6982
  playBeepConfirm() {
6917
6983
  return this.play('beep-confirm', {
6918
6984
  // Vibrate only if in vibration mode
@@ -6954,7 +7020,7 @@ class AudioProvider {
6954
7020
  play(id, opts) {
6955
7021
  return __awaiter(this, void 0, void 0, function* () {
6956
7022
  // Make sure provider is ready
6957
- if (!this._started)
7023
+ if (!this.started)
6958
7024
  yield this.ready();
6959
7025
  const sound = this._preloadedSounds[id];
6960
7026
  if (!sound) {
@@ -7015,7 +7081,7 @@ class AudioProvider {
7015
7081
  }
7016
7082
  vibrate(timeInMs) {
7017
7083
  return __awaiter(this, void 0, void 0, function* () {
7018
- if (!this._started)
7084
+ if (!this.started)
7019
7085
  yield this.ready();
7020
7086
  if (!this.vibration)
7021
7087
  return; // Skip if vibrate plugin
@@ -7023,45 +7089,24 @@ class AudioProvider {
7023
7089
  });
7024
7090
  }
7025
7091
  /* -- protected methods -- */
7026
- start() {
7027
- if (this._startPromise)
7028
- return this._startPromise;
7029
- if (this._started)
7030
- return Promise.resolve();
7031
- let cordova;
7032
- this._startPromise = this.platform.ready()
7033
- .then(() => __awaiter(this, void 0, void 0, function* () {
7034
- cordova = this.platform.is('cordova');
7092
+ ngOnStart() {
7093
+ return __awaiter(this, void 0, void 0, function* () {
7094
+ let cordova = this.platform.is('cordova');
7035
7095
  this._audioType = cordova && this.nativeAudio ? 'native' : 'html5';
7036
7096
  console.info(`[audio] Starting audio provider {${this._audioType}}...`);
7037
7097
  // Listen audio mode changed
7038
7098
  if (cordova && this.audioManagement) {
7039
7099
  yield this.readAudioMode();
7040
7100
  }
7041
- }))
7042
7101
  // Pre-loading system sounds
7043
- .then(() => {
7044
7102
  console.debug('[audio] Preloading audio sounds...');
7045
- return Promise.all(SYSTEM_SOUNDS.map(s => {
7103
+ yield Promise.all(SYSTEM_SOUNDS.map(s => {
7046
7104
  // Disable vibration is cordova not enabled
7047
7105
  if (!cordova)
7048
7106
  s.vibration = undefined;
7049
7107
  return this.preload(s);
7050
7108
  }));
7051
- })
7052
- .then(() => {
7053
- this._started = true;
7054
- this._startPromise = null;
7055
- console.info('[audio] Audio provider started');
7056
- // Emit event
7057
- this.onStart.next();
7058
- })
7059
- .catch(err => {
7060
- console.error('[audio] Unable to start audio provider: ' + (err && err.message || err), err);
7061
- this._started = false;
7062
- this._startPromise = null;
7063
7109
  });
7064
- return this._startPromise;
7065
7110
  }
7066
7111
  readAudioMode() {
7067
7112
  return __awaiter(this, void 0, void 0, function* () {
@@ -7980,6 +8025,10 @@ const environment = Object.freeze({
7980
8025
  {
7981
8026
  host: 'sih.sfa.sc',
7982
8027
  port: 80
8028
+ },
8029
+ {
8030
+ host: 'test.sumaris.net',
8031
+ port: 443
7983
8032
  }
7984
8033
  ],
7985
8034
  defaultAppName: 'SUMARiS',
@@ -8508,14 +8557,14 @@ const DEFAULT_SETTINGS = {
8508
8557
  };
8509
8558
  const APP_LOCAL_SETTINGS = new InjectionToken('DefaultLocalSettings');
8510
8559
  const APP_LOCAL_SETTINGS_OPTIONS = new InjectionToken('LocalSettingsOptions');
8511
- class LocalSettingsService {
8560
+ class LocalSettingsService extends StartableService {
8512
8561
  constructor(translate, platform, storage, environment, defaultSettings, defaultOptionsMap) {
8562
+ super(platform);
8513
8563
  this.translate = translate;
8514
8564
  this.platform = platform;
8515
8565
  this.storage = storage;
8516
8566
  this.environment = environment;
8517
8567
  this.defaultSettings = defaultSettings;
8518
- this._started = false;
8519
8568
  this.onChange = new Subject();
8520
8569
  this.defaultSettings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), this.defaultSettings);
8521
8570
  this._optionDefs = Object.values(defaultOptionsMap);
@@ -8525,60 +8574,40 @@ class LocalSettingsService {
8525
8574
  console.debug('[settings] Creating service');
8526
8575
  }
8527
8576
  get settings() {
8528
- return this.data || this.defaultSettings;
8577
+ return this._data || this.defaultSettings;
8529
8578
  }
8530
8579
  get locale() {
8531
- return this.data && this.data.locale || this.translate.currentLang || this.translate.defaultLang;
8580
+ return this._data && this._data.locale || this.translate.currentLang || this.translate.defaultLang;
8532
8581
  }
8533
8582
  get latLongFormat() {
8534
- return this.data && this.data.latLongFormat || 'DDMM';
8583
+ return this._data && this._data.latLongFormat || 'DDMM';
8535
8584
  }
8536
8585
  get usageMode() {
8537
- return (this.data && this.data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8586
+ return (this._data && this._data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8538
8587
  }
8539
8588
  get mobile() {
8540
- return this.data && toBoolean(this.data.mobile, this.platform.is('mobile'));
8589
+ return this._data && toBoolean(this._data.mobile, this.platform.is('mobile'));
8541
8590
  }
8542
8591
  set mobile(value) {
8543
- this.data.mobile = value;
8592
+ this._data.mobile = value;
8544
8593
  }
8545
8594
  get touchUi() {
8546
- return this.data.touchUi;
8595
+ return this._data.touchUi;
8547
8596
  }
8548
8597
  set touchUi(value) {
8549
- this.data.touchUi = value;
8598
+ this._data.touchUi = value;
8550
8599
  }
8551
8600
  get pageHistory() {
8552
- return (this.data && this.data.pageHistory || []);
8601
+ return (this._data && this._data.pageHistory || []);
8553
8602
  }
8554
- start() {
8555
- if (this._startPromise)
8556
- return this._startPromise;
8557
- if (this._started)
8558
- return Promise.resolve(this.data);
8559
- console.info('[settings] Starting settings...');
8603
+ ngOnStart() {
8604
+ console.info('[settings] Starting service...');
8560
8605
  // Restoring local settings
8561
- this._startPromise = this.platform.ready()
8562
- .then(() => {
8563
- this.data.mobile = isNotNil(this.data.mobile) ? this.data.mobile : this.platform.is('mobile');
8564
- this.data.touchUi = this.data.mobile || this.platform.is('phablet') || this.platform.is('tablet');
8565
- this.data.usageMode = this.platform.is('android') ? 'FIELD' : 'DESK'; // FIELD by default if Android
8566
- })
8567
- .then(() => this.restoreLocally())
8568
- .then(data => {
8569
- this._started = true;
8570
- this._startPromise = undefined;
8571
- return data;
8572
- });
8573
- return this._startPromise;
8574
- }
8575
- get started() {
8576
- return this._started;
8577
- }
8578
- ready() {
8579
- if (this._started)
8580
- return Promise.resolve(this.data);
8581
- return this.start();
8606
+ this._data.mobile = isNotNil(this._data.mobile) ? this._data.mobile : this.platform.is('mobile');
8607
+ this._data.touchUi = this._data.mobile || this.platform.is('phablet') || this.platform.is('tablet');
8608
+ this._data.usageMode = this.platform.is('android') ? 'FIELD' : 'DESK'; // FIELD by default if Android
8609
+ // Restoring local settings
8610
+ return this.restoreLocally();
8582
8611
  }
8583
8612
  isUsageMode(mode) {
8584
8613
  return this.usageMode === mode;
@@ -8588,6 +8617,7 @@ class LocalSettingsService {
8588
8617
  }
8589
8618
  restoreLocally() {
8590
8619
  return __awaiter(this, void 0, void 0, function* () {
8620
+ let data = this._data || {};
8591
8621
  // Restore from storage
8592
8622
  const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
8593
8623
  // Restore local settings (or keep old settings)
@@ -8598,28 +8628,29 @@ class LocalSettingsService {
8598
8628
  SETTINGS_TRANSIENT_PROPERTIES.forEach(transientKey => {
8599
8629
  delete restoredData[transientKey];
8600
8630
  });
8601
- this.data = Object.assign(this.data, restoredData);
8631
+ // Merge into existing data
8632
+ data = Object.assign(data, restoredData);
8602
8633
  }
8603
8634
  // Emit event
8604
- this.onChange.next(this.data);
8605
- return this.data;
8635
+ this.onChange.next(data);
8636
+ return data;
8606
8637
  });
8607
8638
  }
8608
8639
  setProperty(keyOrDef, value) {
8609
- if (!this.data)
8640
+ if (!this._data)
8610
8641
  return;
8611
8642
  if (typeof keyOrDef === 'object') {
8612
8643
  this.setProperty(keyOrDef.key, value);
8613
8644
  return;
8614
8645
  }
8615
- this.data.properties = this.data.properties || {};
8616
- this.data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8646
+ this._data.properties = this._data.properties || {};
8647
+ this._data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8617
8648
  }
8618
8649
  getProperty(keyOrDef, defaultValue) {
8619
8650
  if (typeof keyOrDef === 'object') {
8620
8651
  return this.getProperty(keyOrDef.key, isNil(defaultValue) ? keyOrDef.defaultValue : defaultValue);
8621
8652
  }
8622
- const value = this.data && this.data.properties && this.data.properties[keyOrDef];
8653
+ const value = this._data && this._data.properties && this._data.properties[keyOrDef];
8623
8654
  return isNotNil(value) ? value : defaultValue;
8624
8655
  }
8625
8656
  getPropertyAsBoolean(definition, defaultValue) {
@@ -8642,7 +8673,9 @@ class LocalSettingsService {
8642
8673
  }
8643
8674
  apply(settings, opts) {
8644
8675
  return __awaiter(this, void 0, void 0, function* () {
8645
- this.data = Object.assign(Object.assign({}, this.data), settings);
8676
+ if (!this.started)
8677
+ yield this.ready();
8678
+ this._data = Object.assign(Object.assign({}, this._data), settings);
8646
8679
  // Save locally
8647
8680
  if (opts && opts.persistImmediate) {
8648
8681
  yield this.persistLocally(true);
@@ -8652,7 +8685,7 @@ class LocalSettingsService {
8652
8685
  }
8653
8686
  // Emit event
8654
8687
  if (!opts || opts.emitEvent !== false) {
8655
- this.onChange.next(this.data);
8688
+ this.onChange.next(this._data);
8656
8689
  }
8657
8690
  });
8658
8691
  }
@@ -8664,38 +8697,38 @@ class LocalSettingsService {
8664
8697
  });
8665
8698
  }
8666
8699
  getPageSettings(pageId, propertyName) {
8667
- if (!this.data || !this.data.pages)
8700
+ if (!this._data || !this._data.pages)
8668
8701
  return undefined;
8669
8702
  const key = pageId.replace(/[/]/g, '__');
8670
8703
  if (isNotNilOrBlank(propertyName)) {
8671
- return getPropertyByPath(this.data.pages, key + '.' + propertyName);
8704
+ return getPropertyByPath(this._data.pages, key + '.' + propertyName);
8672
8705
  }
8673
- return this.data.pages[key];
8706
+ return this._data.pages[key];
8674
8707
  }
8675
8708
  savePageSetting(pageId, value, propertyName) {
8676
8709
  return __awaiter(this, void 0, void 0, function* () {
8677
- this.data = this.data || this.defaultSettings;
8678
- this.data.pages = this.data.pages || {};
8710
+ this._data = this._data || this.defaultSettings;
8711
+ this._data.pages = this._data.pages || {};
8679
8712
  const key = pageId.replace(/[/]/g, '__');
8680
8713
  if (propertyName) {
8681
- this.data.pages[key] = this.data.pages[key] || {};
8682
- this.data.pages[key][propertyName] = value;
8714
+ this._data.pages[key] = this._data.pages[key] || {};
8715
+ this._data.pages[key][propertyName] = value;
8683
8716
  }
8684
8717
  else {
8685
- this.data.pages[key] = value;
8718
+ this._data.pages[key] = value;
8686
8719
  }
8687
8720
  // Update local settings
8688
8721
  this.persistLocally();
8689
8722
  });
8690
8723
  }
8691
8724
  getOfflineFeature(featureName) {
8692
- if (!this.data || !this.data.offlineFeatures || isEmptyArray(this.data.offlineFeatures))
8725
+ if (!this._data || !this._data.offlineFeatures || isEmptyArray(this._data.offlineFeatures))
8693
8726
  return undefined;
8694
8727
  if (!featureName)
8695
8728
  throw Error('Missing \'featureName\' argument');
8696
8729
  featureName = featureName.toLowerCase();
8697
8730
  const featurePrefix = featureName + '#';
8698
- const feature = this.data.offlineFeatures.find(f => {
8731
+ const feature = this._data.offlineFeatures.find(f => {
8699
8732
  if (typeof f === 'string')
8700
8733
  return f.toLowerCase().startsWith(featurePrefix);
8701
8734
  if (typeof f === 'object' && f.name)
@@ -8718,24 +8751,24 @@ class LocalSettingsService {
8718
8751
  hasOfflineFeature(featureName) {
8719
8752
  if (featureName)
8720
8753
  return isNotNil(this.getOfflineFeature(featureName));
8721
- return this.data && isNotEmptyArray(this.data.offlineFeatures);
8754
+ return this._data && isNotEmptyArray(this._data.offlineFeatures);
8722
8755
  }
8723
8756
  saveOfflineFeature(feature) {
8724
- this.data = this.data || this.defaultSettings;
8725
- this.data.offlineFeatures = this.data.offlineFeatures || [];
8757
+ this._data = this._data || this.defaultSettings;
8758
+ this._data.offlineFeatures = this._data.offlineFeatures || [];
8726
8759
  feature.name = feature.name.toLowerCase();
8727
8760
  const featurePrefix = feature.name + '#';
8728
- const existingIndex = this.data.offlineFeatures.findIndex(f => {
8761
+ const existingIndex = this._data.offlineFeatures.findIndex(f => {
8729
8762
  if (typeof f === 'string')
8730
8763
  return f.toLowerCase().startsWith(featurePrefix);
8731
8764
  if (typeof f === 'object' && f.name)
8732
8765
  return f.name === feature.name;
8733
8766
  });
8734
8767
  if (existingIndex !== -1) {
8735
- this.data.offlineFeatures[existingIndex] = feature;
8768
+ this._data.offlineFeatures[existingIndex] = feature;
8736
8769
  }
8737
8770
  else {
8738
- this.data.offlineFeatures.push(feature);
8771
+ this._data.offlineFeatures.push(feature);
8739
8772
  }
8740
8773
  // Update local settings
8741
8774
  this.persistLocally();
@@ -8754,14 +8787,14 @@ class LocalSettingsService {
8754
8787
  this.saveOfflineFeature(feature);
8755
8788
  }
8756
8789
  removeOfflineFeatures() {
8757
- if (this.data && this.data.offlineFeatures) {
8758
- this.data.offlineFeatures = [];
8790
+ if (this._data && this._data.offlineFeatures) {
8791
+ this._data.offlineFeatures = [];
8759
8792
  // Update local settings
8760
8793
  this.persistLocally();
8761
8794
  }
8762
8795
  }
8763
8796
  getFieldDisplayAttributes(fieldName, defaultAttributes) {
8764
- const value = this.data && this.data.properties && this.data.properties[`sumaris.field.${fieldName}.attributes`];
8797
+ const value = this._data && this._data.properties && this._data.properties[`sumaris.field.${fieldName}.attributes`];
8765
8798
  // Nothing found in settings: return defaults
8766
8799
  if (!value)
8767
8800
  return defaultAttributes || ['label', 'name'];
@@ -8790,7 +8823,7 @@ class LocalSettingsService {
8790
8823
  // If not inside recursive call: fill page history defaults
8791
8824
  if (!pageHistory)
8792
8825
  this.fillPageHistoryDefaults(page, opts);
8793
- pageHistory = pageHistory || this.data.pageHistory;
8826
+ pageHistory = pageHistory || this._data.pageHistory;
8794
8827
  const index = pageHistory.findIndex(p => (
8795
8828
  // same path
8796
8829
  p.path === page.path
@@ -8826,10 +8859,10 @@ class LocalSettingsService {
8826
8859
  }
8827
8860
  }
8828
8861
  // Save locally (only if not a recursive execution)
8829
- if (pageHistory === this.data.pageHistory) {
8862
+ if (pageHistory === this._data.pageHistory) {
8830
8863
  // If max has been reached, remove old pages
8831
- if (this.data.pageHistory.length > this.data.pageHistoryMaxSize) {
8832
- const removedPages = pageHistory.splice(this.data.pageHistoryMaxSize, pageHistory.length - this.data.pageHistoryMaxSize);
8864
+ if (this._data.pageHistory.length > this._data.pageHistoryMaxSize) {
8865
+ const removedPages = pageHistory.splice(this._data.pageHistoryMaxSize, pageHistory.length - this._data.pageHistoryMaxSize);
8833
8866
  console.debug('[settings] Pages removed from history: ', removedPages);
8834
8867
  }
8835
8868
  // Apply new value
@@ -8840,7 +8873,7 @@ class LocalSettingsService {
8840
8873
  removePageHistory(path, opts, pageHistory // used for recursive call to children)
8841
8874
  ) {
8842
8875
  return __awaiter(this, void 0, void 0, function* () {
8843
- pageHistory = pageHistory || this.data.pageHistory;
8876
+ pageHistory = pageHistory || this._data.pageHistory;
8844
8877
  const index = pageHistory.findIndex(p => p.path === path);
8845
8878
  let found = index !== -1;
8846
8879
  if (found) {
@@ -8856,9 +8889,9 @@ class LocalSettingsService {
8856
8889
  .findIndex(children => this.removePageHistory(path, opts, children)) !== -1;
8857
8890
  }
8858
8891
  // Save locally (only if not a recursive execution)
8859
- if (found && pageHistory === this.data.pageHistory) {
8892
+ if (found && pageHistory === this._data.pageHistory) {
8860
8893
  // Apply changes
8861
- yield this.applyProperty('pageHistory', this.data.pageHistory);
8894
+ yield this.applyProperty('pageHistory', this._data.pageHistory);
8862
8895
  }
8863
8896
  return found;
8864
8897
  });
@@ -8871,26 +8904,26 @@ class LocalSettingsService {
8871
8904
  }
8872
8905
  /* -- Protected methods -- */
8873
8906
  resetData() {
8874
- this.data = Object.assign(Object.assign({}, this.data), this.defaultSettings);
8875
- this.data.locale = this.translate.currentLang || this.translate.defaultLang;
8876
- this.data.mobile = undefined;
8877
- this.data.usageMode = undefined;
8878
- this.data.pageHistory = [];
8907
+ this._data = Object.assign(Object.assign({}, this._data), this.defaultSettings);
8908
+ this._data.locale = this.translate.currentLang || this.translate.defaultLang;
8909
+ this._data.mobile = undefined;
8910
+ this._data.usageMode = undefined;
8911
+ this._data.pageHistory = [];
8879
8912
  const defaultPeer = this.environment.defaultPeer && Peer.fromObject(this.environment.defaultPeer);
8880
- this.data.peerUrl = defaultPeer && defaultPeer.url || undefined;
8881
- if (this._started)
8882
- this.onChange.next(this.data);
8913
+ this._data.peerUrl = defaultPeer && defaultPeer.url || undefined;
8914
+ if (this.started)
8915
+ this.onChange.next(this._data);
8883
8916
  }
8884
8917
  persistLocally(immediate) {
8885
8918
  // Execute immediate
8886
8919
  if (immediate) {
8887
- if (!this.data) {
8920
+ if (!this._data) {
8888
8921
  console.debug('[settings] Removing local settings from storage');
8889
8922
  return this.storage.remove(SETTINGS_STORAGE_KEY);
8890
8923
  }
8891
8924
  else {
8892
- console.debug('[settings] Store local settings', this.data);
8893
- return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this.data));
8925
+ console.debug('[settings] Store local settings', this._data);
8926
+ return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this._data));
8894
8927
  }
8895
8928
  }
8896
8929
  // Execute with delay
@@ -8900,7 +8933,7 @@ class LocalSettingsService {
8900
8933
  this._$persist = new EventEmitter(true);
8901
8934
  this._$persist
8902
8935
  .pipe(debounceTime(2000), // add a delay of 2s
8903
- filter(() => this._started))
8936
+ filter(() => this.started))
8904
8937
  .subscribe(() => this.persistLocally(true));
8905
8938
  }
8906
8939
  this._$persist.emit();
@@ -9062,6 +9095,8 @@ function getConnectionType(type) {
9062
9095
  switch (type) {
9063
9096
  case Connection.NONE:
9064
9097
  return 'none';
9098
+ case Connection.ETHERNET:
9099
+ return 'ethernet';
9065
9100
  case Connection.WIFI:
9066
9101
  return 'wifi';
9067
9102
  case Connection.CELL:
@@ -9069,8 +9104,6 @@ function getConnectionType(type) {
9069
9104
  case Connection.CELL_3G:
9070
9105
  case Connection.CELL_4G:
9071
9106
  return 'cell';
9072
- case Connection.ETHERNET:
9073
- return 'ethernet';
9074
9107
  case Connection.UNKNOWN:
9075
9108
  return 'unknown';
9076
9109
  default:
@@ -9086,8 +9119,9 @@ const NetworkRefreshTimerPeriod = {
9086
9119
  MOBILE: 1000,
9087
9120
  DESKTOP: 1000
9088
9121
  }*/
9089
- class NetworkService {
9122
+ class NetworkService extends StartableService {
9090
9123
  constructor(_document, platform, modalCtrl, cryptoService, storage, settings, cache, http, environment, network, splashScreen, translate, toastController) {
9124
+ super(platform);
9091
9125
  this._document = _document;
9092
9126
  this.platform = platform;
9093
9127
  this.modalCtrl = modalCtrl;
@@ -9101,13 +9135,11 @@ class NetworkService {
9101
9135
  this.splashScreen = splashScreen;
9102
9136
  this.translate = translate;
9103
9137
  this.toastController = toastController;
9104
- this._started = false;
9105
- this._subscription = new Subscription();
9106
- this._listeners = {};
9107
- this.onStart = new Subject();
9108
9138
  this.onPeerChanges = this.onStart.pipe(map(peer => peer && peer.url), filter(isNotNilOrBlank), distinctUntilChanged());
9109
9139
  this.onNetworkStatusChanges = new BehaviorSubject(null);
9110
9140
  this.onResetNetworkCache = new EventEmitter(true);
9141
+ this._subscription = new Subscription();
9142
+ this._listeners = {};
9111
9143
  this._mobile = this.platform.is('mobile');
9112
9144
  if (this._mobile) {
9113
9145
  this._timerRefreshPeriod = NetworkRefreshTimerPeriod.MOBILE;
@@ -9118,6 +9150,7 @@ class NetworkService {
9118
9150
  this._timerRefreshCondition = () => true; // Always check
9119
9151
  }
9120
9152
  this.resetData();
9153
+ this.onStart.subscribe(() => this.ngOnAfterStart());
9121
9154
  // For DEV only
9122
9155
  this._debug = !environment.production;
9123
9156
  }
@@ -9131,16 +9164,14 @@ class NetworkService {
9131
9164
  // If force offline: return 'none'
9132
9165
  return this._forceOffline && 'none'
9133
9166
  // Else, return device connection type (or unknown)
9134
- || (this._started && this._deviceConnectionType || 'unknown');
9167
+ || (this.started && this._deviceConnectionType || 'unknown');
9135
9168
  }
9136
9169
  get peer() {
9137
- return this._peer && this._peer.clone();
9170
+ return this._data && this._data.clone();
9138
9171
  }
9139
9172
  set peer(peer) {
9140
- this.restart(peer);
9141
- }
9142
- get started() {
9143
- return this._started;
9173
+ this._startingPeer = peer;
9174
+ this.restart();
9144
9175
  }
9145
9176
  /**
9146
9177
  * Register to network event
@@ -9162,78 +9193,6 @@ class NetworkService {
9162
9193
  return this.addListener(eventType, callback);
9163
9194
  }
9164
9195
  }
9165
- start(peer) {
9166
- return __awaiter(this, void 0, void 0, function* () {
9167
- if (this._startPromise)
9168
- return this._startPromise;
9169
- if (this._started)
9170
- return;
9171
- console.info('[network] Starting network...');
9172
- // Restoring local settings
9173
- this._startPromise = (!peer && this.restoreLocally() || Promise.resolve(peer))
9174
- .then((peer) => __awaiter(this, void 0, void 0, function* () {
9175
- // Make sure to hide the splashscreen, before open the modal
9176
- if (!peer && this.splashScreen)
9177
- this.splashScreen.hide();
9178
- // No peer in settings: ask user to choose
9179
- while (!peer) {
9180
- console.debug('[network] No peer defined. Asking user to choose a peer.');
9181
- peer = yield this.showSelectPeerModal({ allowSelectDownPeer: false });
9182
- }
9183
- this._peer = peer;
9184
- this._started = true;
9185
- this._startPromise = undefined;
9186
- this.onStart.next(peer);
9187
- console.info(`[platform] Starting network [OK] {online: ${this.online}}`);
9188
- }))
9189
- .catch((err) => {
9190
- console.error(err && err.message || err, err);
9191
- this._started = false;
9192
- this._startPromise = undefined;
9193
- })
9194
- // Wait settings starts, then save peer in settings
9195
- .then(() => this.settings.ready())
9196
- .then(() => this.settings.apply({ peerUrl: this._peer.url }))
9197
- .then(() => this.onDeviceConnectionChanged(this.network && this.network.type || 'unknown'))
9198
- // Start the refresh timer
9199
- .then(() => this.startRefreshTimer());
9200
- // Listen for device network changes
9201
- if (this.network) {
9202
- this._subscription.add(this.network.onDisconnect().subscribe(() => this.onDeviceConnectionChanged('none')));
9203
- this._subscription.add(this.network.onConnect().subscribe(() => this.onDeviceConnectionChanged(this.network.type)));
9204
- }
9205
- return this._startPromise;
9206
- });
9207
- }
9208
- ready() {
9209
- if (this._started)
9210
- return Promise.resolve();
9211
- return this.start();
9212
- }
9213
- stop() {
9214
- return __awaiter(this, void 0, void 0, function* () {
9215
- this.resetData();
9216
- this._started = false;
9217
- this._startPromise = undefined;
9218
- // Stop timer if cannot refresh anymore
9219
- if (this._timerRefreshCondition() === false) {
9220
- this.stopRefreshTimer();
9221
- }
9222
- this._subscription.unsubscribe();
9223
- this._subscription = new Subscription();
9224
- });
9225
- }
9226
- restart(peer) {
9227
- return __awaiter(this, void 0, void 0, function* () {
9228
- if (this._started) {
9229
- yield this.stop()
9230
- .then(() => this.start(peer));
9231
- }
9232
- else {
9233
- yield this.start(peer);
9234
- }
9235
- });
9236
- }
9237
9196
  tryOnline(opts) {
9238
9197
  return __awaiter(this, void 0, void 0, function* () {
9239
9198
  // If offline mode not forced, and device says there is no connection: skip
@@ -9260,7 +9219,8 @@ class NetworkService {
9260
9219
  // Disable the offline mode
9261
9220
  this.setForceOffline(false);
9262
9221
  // Restart
9263
- yield this.restart(peer);
9222
+ this._startingPeer = peer;
9223
+ yield this.restart();
9264
9224
  // Wait a promise, before recheck
9265
9225
  yield this.emit('beforeTryOnlineFinish', this.online);
9266
9226
  }
@@ -9288,7 +9248,7 @@ class NetworkService {
9288
9248
  // Display toast (without await, because not need to wait toast close event)
9289
9249
  return this.showOfflineToast({ showRetryButton: false });
9290
9250
  }
9291
- return this._started && online;
9251
+ return this.started && online;
9292
9252
  });
9293
9253
  }
9294
9254
  showOfflineToast(opts) {
@@ -9336,86 +9296,6 @@ class NetworkService {
9336
9296
  return false;
9337
9297
  });
9338
9298
  }
9339
- /**
9340
- * Try to restore peer from the local storage
9341
- */
9342
- restoreLocally() {
9343
- return __awaiter(this, void 0, void 0, function* () {
9344
- // Restore from storage
9345
- const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
9346
- const settings = settingsStr && JSON.parse(settingsStr) || undefined;
9347
- if (settings && settings.peerUrl) {
9348
- console.debug(`[network] Use peer {${settings.peerUrl}} (found in the local storage)`);
9349
- return Peer.parseUrl(settings.peerUrl);
9350
- }
9351
- // Else, use default peer in env, if exists
9352
- if (this.environment.defaultPeer) {
9353
- return Peer.fromObject(this.environment.defaultPeer);
9354
- }
9355
- // Else, if App is hosted, try the web site as a peer
9356
- const location = this._document && this._document.location;
9357
- if (location && location.protocol && location.protocol.startsWith('http')) {
9358
- const hostname = this._document.location.host;
9359
- const detectedPeer = Peer.parseUrl(`${this._document.location.protocol}${hostname}${this.environment.baseUrl}`);
9360
- if (yield this.checkPeerAlive(detectedPeer)) {
9361
- return detectedPeer;
9362
- }
9363
- }
9364
- return undefined;
9365
- });
9366
- }
9367
- /**
9368
- * Refresh network state, using a ping to pod
9369
- */
9370
- refreshPeerState(opts) {
9371
- return __awaiter(this, void 0, void 0, function* () {
9372
- });
9373
- }
9374
- /**
9375
- * Stop to network state
9376
- *
9377
- * @protected
9378
- */
9379
- stopRefreshTimer() {
9380
- if (this._timerSubscription) {
9381
- this._timerSubscription.unsubscribe();
9382
- this._timerSubscription = undefined;
9383
- }
9384
- }
9385
- /**
9386
- * Refresh the network state
9387
- *
9388
- * @protected
9389
- */
9390
- startRefreshTimer() {
9391
- if (this._timerSubscription)
9392
- return; // Already running: skip
9393
- console.info(`[network] Starting refresh timer, every ${this._timerRefreshPeriod}ms...`);
9394
- let lastInfo;
9395
- this._timerSubscription = timer(this._timerRefreshPeriod, this._timerRefreshPeriod)
9396
- .pipe(
9397
- // Skip some timer event (see constructor)
9398
- filter(this._timerRefreshCondition),
9399
- // Checkin if peer alive
9400
- tap(() => console.debug('[network] Checking connection to pod...')), mergeMap(() => this.checkPeerAlive(this.peer)),
9401
- // Filter to keep only changes
9402
- filter(info => !!info !== !!lastInfo), tap(info => lastInfo = info),
9403
- // Check compatibility
9404
- mergeMap((info) => this.checkPeerCompatible(info, { showToast: true })))
9405
- .subscribe(alive => {
9406
- if (alive && this.offline) {
9407
- this.setForceOffline(false);
9408
- // Restart the service (to force re auth)
9409
- this.restart();
9410
- }
9411
- else if (!alive && this.online) {
9412
- this.setForceOffline(true);
9413
- // Stop the service
9414
- this.stop();
9415
- }
9416
- });
9417
- this._timerSubscription.add(() => console.debug('[network] Refresh timer stopped'));
9418
- }
9419
9299
  /**
9420
9300
  * Check if the peer is alive
9421
9301
  *
@@ -9531,6 +9411,123 @@ class NetworkService {
9531
9411
  });
9532
9412
  });
9533
9413
  }
9414
+ /* -- protected functions -- */
9415
+ ngOnStart() {
9416
+ return __awaiter(this, void 0, void 0, function* () {
9417
+ console.info('[network] Starting network...', this._startingPeer);
9418
+ // Restoring local settings
9419
+ let peer = this._startingPeer || (yield this.restoreLocally());
9420
+ // Make sure to hide the splashscreen, before open the modal
9421
+ if (!peer && this.splashScreen)
9422
+ this.splashScreen.hide();
9423
+ // No peer in settings: ask user to choose
9424
+ while (!peer) {
9425
+ console.debug('[network] No peer defined. Asking user to choose a peer.');
9426
+ peer = yield this.showSelectPeerModal({ allowSelectDownPeer: false });
9427
+ }
9428
+ console.info(`[network] Starting service [OK] {peer: '${peer.url}', online: ${this.online}}`);
9429
+ return peer;
9430
+ });
9431
+ }
9432
+ ngOnAfterStart() {
9433
+ var _a;
9434
+ return __awaiter(this, void 0, void 0, function* () {
9435
+ // Wait settings starts, then save peer in settings
9436
+ yield this.settings.apply({ peerUrl: this._data.url });
9437
+ this.onDeviceConnectionChanged(((_a = this.network) === null || _a === void 0 ? void 0 : _a.type) || 'unknown');
9438
+ // Start the refresh timer
9439
+ this.startRefreshTimer();
9440
+ // Listen for device network changes
9441
+ if (this.network) {
9442
+ this._subscription.add(this.network.onDisconnect().subscribe(() => this.onDeviceConnectionChanged('none')));
9443
+ this._subscription.add(this.network.onConnect().subscribe(() => this.onDeviceConnectionChanged(this.network.type)));
9444
+ }
9445
+ });
9446
+ }
9447
+ ngOnStop() {
9448
+ return __awaiter(this, void 0, void 0, function* () {
9449
+ this.resetData();
9450
+ // Stop timer if cannot refresh anymore
9451
+ if (this._timerRefreshCondition() === false) {
9452
+ this.stopRefreshTimer();
9453
+ }
9454
+ this._subscription.unsubscribe();
9455
+ this._subscription = new Subscription();
9456
+ });
9457
+ }
9458
+ /**
9459
+ * Try to restore peer from the local storage
9460
+ */
9461
+ restoreLocally() {
9462
+ return __awaiter(this, void 0, void 0, function* () {
9463
+ // Restore from storage
9464
+ const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
9465
+ const settings = settingsStr && JSON.parse(settingsStr) || undefined;
9466
+ if (settings && settings.peerUrl) {
9467
+ console.debug(`[network] Use peer {${settings.peerUrl}} (found in the local storage)`);
9468
+ return Peer.parseUrl(settings.peerUrl);
9469
+ }
9470
+ // Else, use default peer in env, if exists
9471
+ if (this.environment.defaultPeer) {
9472
+ return Peer.fromObject(this.environment.defaultPeer);
9473
+ }
9474
+ // Else, if App is hosted, try the web site as a peer
9475
+ const location = this._document && this._document.location;
9476
+ if (location && location.protocol && location.protocol.startsWith('http')) {
9477
+ const hostname = this._document.location.host;
9478
+ const detectedPeer = Peer.parseUrl(`${this._document.location.protocol}${hostname}${this.environment.baseUrl}`);
9479
+ if (yield this.checkPeerAlive(detectedPeer)) {
9480
+ return detectedPeer;
9481
+ }
9482
+ }
9483
+ return undefined;
9484
+ });
9485
+ }
9486
+ /**
9487
+ * Stop to network state
9488
+ *
9489
+ * @protected
9490
+ */
9491
+ stopRefreshTimer() {
9492
+ if (this._timerSubscription) {
9493
+ this._timerSubscription.unsubscribe();
9494
+ this._timerSubscription = undefined;
9495
+ }
9496
+ }
9497
+ /**
9498
+ * Refresh the network state
9499
+ *
9500
+ * @protected
9501
+ */
9502
+ startRefreshTimer() {
9503
+ if (this._timerSubscription)
9504
+ return; // Already running: skip
9505
+ console.info(`[network] Starting refresh timer, every ${this._timerRefreshPeriod}ms...`);
9506
+ let lastInfo;
9507
+ this._timerSubscription = timer(this._timerRefreshPeriod, this._timerRefreshPeriod)
9508
+ .pipe(
9509
+ // Skip some timer event (see constructor)
9510
+ filter(this._timerRefreshCondition),
9511
+ // Checkin if peer alive
9512
+ tap(() => console.debug('[network] Checking connection to pod...')), mergeMap(() => this.checkPeerAlive(this.peer)),
9513
+ // Filter to keep only changes
9514
+ filter(info => !!info !== !!lastInfo), tap(info => lastInfo = info),
9515
+ // Check compatibility
9516
+ mergeMap((info) => this.checkPeerCompatible(info, { showToast: true })))
9517
+ .subscribe(alive => {
9518
+ if (alive && this.offline) {
9519
+ this.setForceOffline(false);
9520
+ // Restart the service (to force re auth)
9521
+ this.restart();
9522
+ }
9523
+ else if (!alive && this.online) {
9524
+ this.setForceOffline(true);
9525
+ // Stop the service
9526
+ this.stop();
9527
+ }
9528
+ });
9529
+ this._timerSubscription.add(() => console.debug('[network] Refresh timer stopped'));
9530
+ }
9534
9531
  get(path, opts) {
9535
9532
  return __awaiter(this, void 0, void 0, function* () {
9536
9533
  let uri = path;
@@ -9582,7 +9579,7 @@ class NetworkService {
9582
9579
  }
9583
9580
  }
9584
9581
  resetData() {
9585
- this._peer = null;
9582
+ this._data = null;
9586
9583
  }
9587
9584
  /**
9588
9585
  * Get default peers, from environment
@@ -9599,7 +9596,7 @@ class NetworkService {
9599
9596
  addListener(name, callback) {
9600
9597
  this._listeners[name] = this._listeners[name] || [];
9601
9598
  this._listeners[name].push(callback);
9602
- // When unsubcribe, remove from the listener
9599
+ // When unsubscribe, remove from the listener
9603
9600
  return new Subscription(() => {
9604
9601
  const index = this._listeners[name].indexOf(callback);
9605
9602
  if (index !== -1) {
@@ -10142,31 +10139,32 @@ class EntityStore {
10142
10139
  }
10143
10140
 
10144
10141
  const APP_LOCAL_STORAGE_TYPE_POLICIES = new InjectionToken('localStorageTypePolicies');
10145
- class EntitiesStorage {
10142
+ ;
10143
+ class EntitiesStorage extends StartableService {
10146
10144
  constructor(platform, progressBarService, storage, environment, typePolicies) {
10145
+ super(platform);
10147
10146
  this.platform = platform;
10148
10147
  this.progressBarService = progressBarService;
10149
10148
  this.storage = storage;
10150
10149
  this.environment = environment;
10151
- this._started = false;
10152
10150
  this._subscription = new Subscription();
10153
- this._stores = {};
10154
10151
  this._$save = new EventEmitter(true);
10155
10152
  this._dirty = false;
10156
10153
  this._saving = false;
10157
- this.onStart = new Subject();
10158
10154
  this._typePolicies = typePolicies || {};
10155
+ this._saveTimerPeriod = environment.storageSavePeriodMs || 10000 /* = 10s */;
10156
+ this._data = {};
10159
10157
  // For DEV only
10160
10158
  this._debug = !environment.production;
10161
10159
  if (this._debug)
10162
10160
  console.debug('[entities-storage] Creating service');
10163
10161
  }
10164
10162
  get dirty() {
10165
- return this._dirty || Object.entries(this._stores).find(([_, store]) => store.dirty) !== undefined;
10163
+ return this._dirty || Object.values(this._data).some(store => store.dirty);
10166
10164
  }
10167
10165
  watchAll(entityName, variables, opts) {
10168
10166
  // Make sure store is ready
10169
- if (!this._started) {
10167
+ if (!this.started) {
10170
10168
  return defer(() => this.ready())
10171
10169
  .pipe(switchMap(() => this.watchAll(entityName, variables, opts))); // Loop
10172
10170
  }
@@ -10180,7 +10178,7 @@ class EntitiesStorage {
10180
10178
  loadAll(entityName, variables, opts) {
10181
10179
  return __awaiter(this, void 0, void 0, function* () {
10182
10180
  // Make sure store is ready
10183
- if (!this._started)
10181
+ if (!this.started)
10184
10182
  yield this.ready();
10185
10183
  try {
10186
10184
  this.progressBarService.increase();
@@ -10243,7 +10241,8 @@ class EntitiesStorage {
10243
10241
  return __awaiter(this, void 0, void 0, function* () {
10244
10242
  if (!entity)
10245
10243
  return; // skip
10246
- yield this.ready();
10244
+ if (!this.started)
10245
+ yield this.ready();
10247
10246
  try {
10248
10247
  this.progressBarService.increase();
10249
10248
  this._dirty = true;
@@ -10263,7 +10262,8 @@ class EntitiesStorage {
10263
10262
  return __awaiter(this, void 0, void 0, function* () {
10264
10263
  if (isEmptyArray(entities) && (!opts || opts.reset !== true))
10265
10264
  return entities; // Skip (nothing to save)
10266
- yield this.ready();
10265
+ if (!this.started)
10266
+ yield this.ready();
10267
10267
  try {
10268
10268
  this.progressBarService.increase();
10269
10269
  this._dirty = true;
@@ -10279,13 +10279,15 @@ class EntitiesStorage {
10279
10279
  return __awaiter(this, void 0, void 0, function* () {
10280
10280
  if (!entity)
10281
10281
  return undefined; // skip
10282
- yield this.ready();
10282
+ if (!this.started)
10283
+ yield this.ready();
10283
10284
  return this.deleteById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10284
10285
  });
10285
10286
  }
10286
10287
  deleteById(id, opts) {
10287
10288
  return __awaiter(this, void 0, void 0, function* () {
10288
- yield this.ready();
10289
+ if (!this.started)
10290
+ yield this.ready();
10289
10291
  if (!opts || isNilOrBlank(opts.entityName))
10290
10292
  throw new Error('Missing argument \'opts\' or \'entityName\'');
10291
10293
  //if (id >= 0) throw new Error('Invalid id a local entity (not a negative number): ' + id);
@@ -10311,7 +10313,8 @@ class EntitiesStorage {
10311
10313
  }
10312
10314
  deleteMany(ids, opts) {
10313
10315
  return __awaiter(this, void 0, void 0, function* () {
10314
- yield this.ready();
10316
+ if (!this.started)
10317
+ yield this.ready();
10315
10318
  if (!opts || isNilOrBlank(opts.entityName))
10316
10319
  throw new Error('Missing argument \'opts\' or \'opts.entityName\'');
10317
10320
  try {
@@ -10339,7 +10342,8 @@ class EntitiesStorage {
10339
10342
  return __awaiter(this, void 0, void 0, function* () {
10340
10343
  if (!entity)
10341
10344
  return undefined; // skip
10342
- yield this.ready();
10345
+ if (!this.started)
10346
+ yield this.ready();
10343
10347
  return this.deleteFromTrashById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10344
10348
  });
10345
10349
  }
@@ -10355,7 +10359,8 @@ class EntitiesStorage {
10355
10359
  return __awaiter(this, void 0, void 0, function* () {
10356
10360
  if (!entity)
10357
10361
  return undefined; // skip
10358
- yield this.ready();
10362
+ if (!this.started)
10363
+ yield this.ready();
10359
10364
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10360
10365
  // Delete entity by id, if exists
10361
10366
  const entityStore = this.getEntityStore(entityName);
@@ -10372,7 +10377,8 @@ class EntitiesStorage {
10372
10377
  }
10373
10378
  moveManyToTrash(ids, opts) {
10374
10379
  return __awaiter(this, void 0, void 0, function* () {
10375
- yield this.ready();
10380
+ if (!this.started)
10381
+ yield this.ready();
10376
10382
  if (!opts || isNilOrBlank(opts.entityName))
10377
10383
  throw new Error('Missing argument \'opts.entityName\'');
10378
10384
  const entityStore = this.getEntityStore(opts.entityName, { create: false });
@@ -10399,7 +10405,8 @@ class EntitiesStorage {
10399
10405
  return __awaiter(this, void 0, void 0, function* () {
10400
10406
  if (!entity)
10401
10407
  return undefined; // skip
10402
- yield this.ready();
10408
+ if (!this.started)
10409
+ yield this.ready();
10403
10410
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10404
10411
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10405
10412
  this.getEntityStore(trashName).save(entity, opts);
@@ -10409,7 +10416,8 @@ class EntitiesStorage {
10409
10416
  }
10410
10417
  clearTrash(entityName) {
10411
10418
  return __awaiter(this, void 0, void 0, function* () {
10412
- yield this.ready();
10419
+ if (!this.started)
10420
+ yield this.ready();
10413
10421
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10414
10422
  const entityStore = this.getEntityStore(trashName, { create: false });
10415
10423
  if (!entityStore)
@@ -10425,36 +10433,24 @@ class EntitiesStorage {
10425
10433
  }
10426
10434
  return Promise.resolve();
10427
10435
  }
10428
- ready() {
10429
- if (this._started)
10430
- return Promise.resolve();
10431
- return this.start();
10432
- }
10433
- start() {
10434
- if (this._startPromise)
10435
- return this._startPromise;
10436
- if (this._started)
10437
- return Promise.resolve();
10438
- const now = Date.now();
10439
- console.info(`[entities-storage] Starting entity storage...`);
10440
- // Restore sequences
10441
- this._startPromise = this.restoreLocally()
10442
- .then(() => {
10436
+ ngOnStart() {
10437
+ return __awaiter(this, void 0, void 0, function* () {
10438
+ const now = Date.now();
10439
+ console.info(`[entities-storage] Starting entity storage...`);
10440
+ // Restore sequences
10441
+ yield this.restoreLocally();
10443
10442
  // Start a save timer
10444
- this._subscription.add(merge(this._$save, timer(2000, 10000))
10445
- .pipe(throttleTime(10000))
10443
+ this._subscription.add(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
10444
+ .pipe(
10445
+ // Avoid to many call (e.g. when $save AND timer are triggered
10446
+ throttleTime(this._saveTimerPeriod))
10446
10447
  .subscribe(() => this.storeLocally()));
10447
- this._started = true;
10448
- this._startPromise = undefined;
10449
10448
  console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
10450
- // Emit event
10451
- this.onStart.next();
10449
+ return this._data;
10452
10450
  });
10453
- return this._startPromise;
10454
10451
  }
10455
- stop() {
10452
+ ngOnStop() {
10456
10453
  return __awaiter(this, void 0, void 0, function* () {
10457
- this._started = false;
10458
10454
  this._subscription.unsubscribe();
10459
10455
  this._subscription = new Subscription();
10460
10456
  if (this.dirty) {
@@ -10462,22 +10458,15 @@ class EntitiesStorage {
10462
10458
  }
10463
10459
  });
10464
10460
  }
10465
- restart() {
10466
- return __awaiter(this, void 0, void 0, function* () {
10467
- if (this._started)
10468
- yield this.stop();
10469
- yield this.start();
10470
- });
10471
- }
10472
10461
  /* -- protected methods -- */
10473
10462
  getEntityStore(name, opts) {
10474
- let store = this._stores[name];
10463
+ let store = this._data[name];
10475
10464
  if (!store && (!opts || opts.create !== false)) {
10476
10465
  if (this._debug)
10477
10466
  console.debug(`[entities-storage] Creating store ${name}`);
10478
10467
  const typePolicy = this._typePolicies[name];
10479
10468
  store = new EntityStore(name, this.storage, typePolicy);
10480
- this._stores[name] = store;
10469
+ this._data[name] = store;
10481
10470
  }
10482
10471
  return store;
10483
10472
  }
@@ -10520,12 +10509,12 @@ class EntitiesStorage {
10520
10509
  this._saving = true;
10521
10510
  this._dirty = false;
10522
10511
  this.progressBarService.increase();
10523
- const entityNames = this._stores && Object.keys(this._stores) || [];
10512
+ const entityNames = this._data && Object.keys(this._data) || [];
10524
10513
  const now = Date.now();
10525
10514
  if (this._debug)
10526
10515
  console.debug('[entities-storage] Persisting...');
10527
10516
  let currentEntityName;
10528
- return concat(...entityNames.map(entityName => defer(() => {
10517
+ return chainPromises(entityNames.map(entityName => () => {
10529
10518
  currentEntityName = entityName;
10530
10519
  const entityStore = this.getEntityStore(entityName, { create: false });
10531
10520
  if (!entityStore) {
@@ -10539,18 +10528,20 @@ class EntitiesStorage {
10539
10528
  entityNames.splice(entityNames.findIndex(e => e === entityName), 1);
10540
10529
  }
10541
10530
  });
10542
- })), defer(() => {
10531
+ }))
10532
+ .then(() => {
10543
10533
  currentEntityName = undefined;
10544
10534
  return isEmptyArray(entityNames) ?
10545
10535
  this.storage.remove(ENTITIES_STORAGE_KEY_PREFIX) :
10546
10536
  this.storage.set(ENTITIES_STORAGE_KEY_PREFIX, entityNames);
10547
- }), defer(() => {
10537
+ })
10538
+ .then(() => {
10548
10539
  if (this._debug)
10549
10540
  console.debug(`[entities-storage] Persisting [OK] ${entityNames.length} stores saved in ${Date.now() - now}ms...`);
10550
10541
  this._saving = false;
10551
10542
  this.progressBarService.decrease();
10552
- }))
10553
- .pipe(catchError(err => {
10543
+ })
10544
+ .catch(err => {
10554
10545
  this._saving = false;
10555
10546
  this.progressBarService.decrease();
10556
10547
  if (currentEntityName) {
@@ -10559,8 +10550,8 @@ class EntitiesStorage {
10559
10550
  else {
10560
10551
  console.error(`[entities-storage] Error while persisting: ${err && err.message || err}`, err);
10561
10552
  }
10562
- return err;
10563
- })).toPromise();
10553
+ throw err;
10554
+ });
10564
10555
  });
10565
10556
  }
10566
10557
  }
@@ -10574,7 +10565,7 @@ EntitiesStorage.ctorParameters = () => [
10574
10565
  { type: Platform },
10575
10566
  { type: ProgressBarService },
10576
10567
  { type: Storage },
10577
- { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10568
+ { type: Environment, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10578
10569
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APP_LOCAL_STORAGE_TYPE_POLICIES,] }] }
10579
10570
  ];
10580
10571
 
@@ -11045,8 +11036,9 @@ function restoreTrackedQueries(opts) {
11045
11036
  }
11046
11037
 
11047
11038
  const APP_GRAPHQL_TYPE_POLICIES = new InjectionToken('graphqlTypePolicies');
11048
- class GraphqlService {
11039
+ class GraphqlService extends StartableService {
11049
11040
  constructor(platform, apollo, httpLink, network, storage, cryptoService, environment, typePolicies) {
11041
+ super(platform); // Wait network
11050
11042
  this.platform = platform;
11051
11043
  this.apollo = apollo;
11052
11044
  this.httpLink = httpLink;
@@ -11055,21 +11047,12 @@ class GraphqlService {
11055
11047
  this.cryptoService = cryptoService;
11056
11048
  this.environment = environment;
11057
11049
  this.typePolicies = typePolicies;
11058
- this._started = false;
11059
11050
  this._subscription = new Subscription();
11060
11051
  this.connectionParams = {};
11061
11052
  this.onNetworkError = new Subject();
11062
11053
  this.customErrors = {};
11063
- this.onStart = new Subject();
11064
11054
  this._debug = !environment.production;
11065
11055
  this._defaultFetchPolicy = environment.apolloFetchPolicy;
11066
- // Restart if network restart
11067
- this.network.on('start', () => this.restart());
11068
- // Clear cache
11069
- this.network.on('resetCache', () => __awaiter(this, void 0, void 0, function* () {
11070
- yield this.ready();
11071
- yield this.clearCache();
11072
- }));
11073
11056
  // Listen network status
11074
11057
  this._networkStatusChanged$ = network.onNetworkStatusChanges
11075
11058
  .pipe(filter(isNotNil), distinctUntilChanged());
@@ -11078,11 +11061,8 @@ class GraphqlService {
11078
11061
  .pipe(throttleTime(300), filter(() => this.network.online), mergeMap(() => this.network.checkPeerAlive()), filter(alive => !alive))
11079
11062
  .subscribe(() => this.network.setForceOffline(true, { showToast: true }));
11080
11063
  }
11081
- get started() {
11082
- return this._started;
11083
- }
11084
11064
  get client() {
11085
- return this.apollo.client;
11065
+ return this._data;
11086
11066
  }
11087
11067
  get cache() {
11088
11068
  return this.apollo.client.cache;
@@ -11090,33 +11070,6 @@ class GraphqlService {
11090
11070
  get defaultFetchPolicy() {
11091
11071
  return this._defaultFetchPolicy;
11092
11072
  }
11093
- ready() {
11094
- if (this._started)
11095
- return Promise.resolve();
11096
- return this.start();
11097
- }
11098
- start() {
11099
- if (this._startPromise)
11100
- return this._startPromise;
11101
- if (this._started)
11102
- return Promise.resolve();
11103
- console.info('[graphql] Starting graphql...');
11104
- // Waiting for network service
11105
- this._startPromise = this.network.ready()
11106
- .then(() => this.initApollo())
11107
- .then(() => {
11108
- this._started = true;
11109
- this._startPromise = undefined;
11110
- // Emit event
11111
- this.onStart.next();
11112
- console.info('[graphql] Starting graphql [OK]');
11113
- })
11114
- .catch((err) => {
11115
- console.error(err && err.message || err, err);
11116
- this._startPromise = undefined;
11117
- });
11118
- return this._startPromise;
11119
- }
11120
11073
  setAuthToken(token) {
11121
11074
  if (token) {
11122
11075
  console.debug('[graphql] Apply token authentication to headers');
@@ -11149,22 +11102,22 @@ class GraphqlService {
11149
11102
  */
11150
11103
  addResolver(resolvers) {
11151
11104
  return __awaiter(this, void 0, void 0, function* () {
11152
- if (!this._started) {
11153
- this.onStart.toPromise().then(() => this.addResolver(resolvers)); // Loop
11154
- }
11155
- const client = this.apollo.getClient();
11156
- client.addResolvers(resolvers);
11105
+ if (!this.started)
11106
+ yield this.ready();
11107
+ this.apollo.client.addResolvers(resolvers);
11157
11108
  });
11158
11109
  }
11159
11110
  query(opts) {
11160
11111
  return __awaiter(this, void 0, void 0, function* () {
11112
+ if (!this.started)
11113
+ yield this.ready();
11161
11114
  let res;
11162
11115
  try {
11163
- res = yield (yield this.getApollo()).query({
11116
+ res = yield this.client.query({
11164
11117
  query: opts.query,
11165
11118
  variables: opts.variables,
11166
11119
  fetchPolicy: opts.fetchPolicy || this._defaultFetchPolicy || undefined
11167
- }).toPromise();
11120
+ });
11168
11121
  }
11169
11122
  catch (err) {
11170
11123
  res = this.toApolloError(err, opts.error);
@@ -11504,10 +11457,11 @@ class GraphqlService {
11504
11457
  }
11505
11458
  clearCache(client) {
11506
11459
  return __awaiter(this, void 0, void 0, function* () {
11507
- client = (client || this.apollo.client);
11460
+ client = (client || this.client);
11508
11461
  if (client) {
11509
- const now = this._debug && Date.now();
11510
11462
  console.info('[graphql] Clearing Apollo client\'s cache... ');
11463
+ const now = this._debug && Date.now();
11464
+ // Clearing the cache
11511
11465
  yield client.cache.reset();
11512
11466
  if (this._debug)
11513
11467
  console.debug(`[graphql] Apollo client's cache cleared, in ${Date.now() - now}ms`);
@@ -11518,8 +11472,10 @@ class GraphqlService {
11518
11472
  this.customErrors = Object.assign(Object.assign({}, this.customErrors), error);
11519
11473
  }
11520
11474
  /* -- protected methods -- */
11521
- initApollo() {
11475
+ ngOnStart() {
11522
11476
  return __awaiter(this, void 0, void 0, function* () {
11477
+ yield this.network.ready();
11478
+ console.info('[graphql] Starting graphql...');
11523
11479
  const mobile = this.platform.is('mobile') || this.platform.is('mobileweb');
11524
11480
  const enableTrackMutationQueries = !mobile;
11525
11481
  const peer = this.network.peer;
@@ -11654,24 +11610,20 @@ class GraphqlService {
11654
11610
  console.error('[graphql] Failed to restore tracked queries from storage: ' + (err && err.message || err), err);
11655
11611
  }
11656
11612
  }
11613
+ // Listen for network restart
11614
+ this._subscription.add(this.network.on('start', () => this.restart()));
11615
+ // Listen for clear cache request, from network
11616
+ this._subscription.add(this.network.on('resetCache', () => this.clearCache()));
11617
+ console.info('[graphql] Starting graphql [OK]');
11618
+ return client;
11657
11619
  });
11658
11620
  }
11659
- stop() {
11621
+ ngOnStop() {
11660
11622
  return __awaiter(this, void 0, void 0, function* () {
11661
11623
  console.info('[graphql] Stopping graphql service...');
11662
11624
  this._subscription.unsubscribe();
11663
11625
  this._subscription = new Subscription();
11664
11626
  yield this.resetClient();
11665
- this._started = false;
11666
- this._startPromise = undefined;
11667
- });
11668
- }
11669
- restart() {
11670
- return __awaiter(this, void 0, void 0, function* () {
11671
- if (this.started) {
11672
- return this.stop().then(() => this.start());
11673
- }
11674
- return this.start();
11675
11627
  });
11676
11628
  }
11677
11629
  resetClient(client) {
@@ -11767,15 +11719,6 @@ class GraphqlService {
11767
11719
  }
11768
11720
  return undefined;
11769
11721
  }
11770
- getApollo() {
11771
- return __awaiter(this, void 0, void 0, function* () {
11772
- if (!this._started) {
11773
- console.debug('[graphql] Waiting apollo client... ');
11774
- yield this.onStart.toPromise();
11775
- }
11776
- return this.apollo;
11777
- });
11778
- }
11779
11722
  }
11780
11723
  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" });
11781
11724
  GraphqlService.decorators = [
@@ -12119,7 +12062,12 @@ class AccountService extends BaseGraphqlService {
12119
12062
  this.storage = storage;
12120
12063
  this.file = file;
12121
12064
  this.environment = environment;
12122
- this.data = {
12065
+ this.onLogin = new Subject();
12066
+ this.onLogout = new Subject();
12067
+ this.onChange = new Subject();
12068
+ this.onAuthTokenChange = new Subject();
12069
+ this.onAuthBasicChange = new Subject();
12070
+ this._data = {
12123
12071
  loaded: false,
12124
12072
  keypair: null,
12125
12073
  authToken: null,
@@ -12133,11 +12081,6 @@ class AccountService extends BaseGraphqlService {
12133
12081
  this._started = false;
12134
12082
  this._$additionalFields = new BehaviorSubject([]);
12135
12083
  this._tokenType$ = new BehaviorSubject(undefined);
12136
- this.onLogin = new Subject();
12137
- this.onLogout = new Subject();
12138
- this.onChange = new Subject();
12139
- this.onAuthTokenChange = new Subject();
12140
- this.onAuthBasicChange = new Subject();
12141
12084
  this._debug = !environment.production;
12142
12085
  if (this._debug)
12143
12086
  console.debug('[account-service] Creating service');
@@ -12145,7 +12088,7 @@ class AccountService extends BaseGraphqlService {
12145
12088
  // Send auth token to the graphql layer, when changed
12146
12089
  this.onAuthTokenChange.subscribe((token) => this.graphql.setAuthToken(token));
12147
12090
  this.onAuthBasicChange.subscribe((basic) => this.graphql.setAuthBasic(basic));
12148
- // Listen network restart
12091
+ // Listen graphql start (or restart)
12149
12092
  this.graphql.onStart.subscribe(() => __awaiter(this, void 0, void 0, function* () {
12150
12093
  if (!this._started) {
12151
12094
  this.ready();
@@ -12167,19 +12110,19 @@ class AccountService extends BaseGraphqlService {
12167
12110
  }));
12168
12111
  }
12169
12112
  get account() {
12170
- return this.data.loaded ? this.data.account : undefined;
12113
+ return this._data.loaded ? this._data.account : undefined;
12171
12114
  }
12172
12115
  get person() {
12173
- if (this.data.loaded && !this.data.person) {
12174
- this.data.person = this.data.loaded ? this.data.account.asPerson() : undefined;
12116
+ if (this._data.loaded && !this._data.person) {
12117
+ this._data.person = this._data.loaded ? this._data.account.asPerson() : undefined;
12175
12118
  }
12176
- return this.data.person;
12119
+ return this._data.person;
12177
12120
  }
12178
12121
  get department() {
12179
- if (this.data.loaded && !this.data.department) {
12180
- this.data.department = this.data.loaded ? this.data.account.asPerson().department : undefined;
12122
+ if (this._data.loaded && !this._data.department) {
12123
+ this._data.department = this._data.loaded ? this._data.account.asPerson().department : undefined;
12181
12124
  }
12182
- return this.data.department;
12125
+ return this._data.department;
12183
12126
  }
12184
12127
  get tokenType() {
12185
12128
  return this._tokenType$.value;
@@ -12189,28 +12132,17 @@ class AccountService extends BaseGraphqlService {
12189
12132
  console.info('[account] Using authentication token type: ' + value);
12190
12133
  this._tokenType$.next(value);
12191
12134
  // Reset values
12192
- this.data.authToken = undefined;
12135
+ this._data.authToken = undefined;
12193
12136
  this.onAuthTokenChange.next(undefined);
12194
- this.data.authBasic = undefined;
12137
+ this._data.authBasic = undefined;
12195
12138
  this.onAuthBasicChange.next(undefined);
12196
12139
  }
12197
12140
  }
12198
- resetData() {
12199
- this.data.loaded = false;
12200
- this.data.keypair = null;
12201
- this.data.authToken = null;
12202
- this.data.authBasic = null;
12203
- this.data.pubkey = null;
12204
- this.data.mainProfile = null;
12205
- this.data.account = new Account();
12206
- this.data.person = null;
12207
- this.data.department = null;
12208
- }
12209
12141
  start() {
12210
12142
  if (this._startPromise)
12211
12143
  return this._startPromise;
12212
12144
  if (this._started)
12213
- return Promise.resolve();
12145
+ return Promise.resolve(this.account);
12214
12146
  // Restoring local settings
12215
12147
  this._startPromise = Promise.all([
12216
12148
  this.settings.ready(),
@@ -12221,6 +12153,7 @@ class AccountService extends BaseGraphqlService {
12221
12153
  .then(() => {
12222
12154
  this._started = true;
12223
12155
  this._startPromise = undefined;
12156
+ return this.account;
12224
12157
  });
12225
12158
  return this._startPromise;
12226
12159
  }
@@ -12232,8 +12165,8 @@ class AccountService extends BaseGraphqlService {
12232
12165
  if (this._started || this._startPromise) {
12233
12166
  this._started = false;
12234
12167
  this._startPromise = undefined;
12235
- const hadAuthToken = this.data.authToken && true;
12236
- const hadAuthBasic = this.data.authBasic && true;
12168
+ const hadAuthToken = this._data.authToken && true;
12169
+ const hadAuthBasic = this._data.authBasic && true;
12237
12170
  const hadAuth = hadAuthToken || hadAuthBasic;
12238
12171
  this.resetData();
12239
12172
  if (hadAuth) {
@@ -12255,35 +12188,35 @@ class AccountService extends BaseGraphqlService {
12255
12188
  }
12256
12189
  ready() {
12257
12190
  if (this._started)
12258
- return Promise.resolve();
12191
+ return Promise.resolve(this.account);
12259
12192
  return this.start();
12260
12193
  }
12261
12194
  isLogin() {
12262
- return !!(this.data.pubkey && this.data.loaded);
12195
+ return !!(this._data.pubkey && this._data.loaded);
12263
12196
  }
12264
12197
  isAuth() {
12265
- return !!(this.data.pubkey && this.data.keypair && this.data.keypair.secretKey);
12198
+ return !!(this._data.pubkey && this._data.keypair && this._data.keypair.secretKey);
12266
12199
  }
12267
12200
  hasMinProfile(userProfile) {
12268
12201
  // should be login, and status ENABLE or TEMPORARY
12269
- if (!this.data.account || !this.data.account.pubkey ||
12270
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY)) {
12202
+ if (!this._data.account || !this._data.account.pubkey ||
12203
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY)) {
12271
12204
  return false;
12272
12205
  }
12273
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12206
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12274
12207
  }
12275
12208
  hasExactProfile(label) {
12276
12209
  // should be login, and status ENABLE or TEMPORARY
12277
- if (!this.data.account || !this.data.account.pubkey ||
12278
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY))
12210
+ if (!this._data.account || !this._data.account.pubkey ||
12211
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12279
12212
  return false;
12280
- return this.data.account.profiles.some(profile => profile === label);
12213
+ return this._data.account.profiles.some(profile => profile === label);
12281
12214
  }
12282
12215
  hasProfileAndIsEnable(userProfile) {
12283
12216
  // should be login, and status ENABLE
12284
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12217
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12285
12218
  return false;
12286
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12219
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12287
12220
  }
12288
12221
  isAdmin() {
12289
12222
  return this.hasProfileAndIsEnable('ADMIN');
@@ -12303,11 +12236,11 @@ class AccountService extends BaseGraphqlService {
12303
12236
  }
12304
12237
  isOnlyGuest() {
12305
12238
  // Should be login, and status ENABLE or TEMPORARY
12306
- if (!this.data.account || !this.data.account.pubkey ||
12307
- (this.data.account.statusId !== StatusIds.ENABLE && this.data.account.statusId !== StatusIds.TEMPORARY))
12239
+ if (!this._data.account || !this._data.account.pubkey ||
12240
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12308
12241
  return false;
12309
12242
  // Profile less then user
12310
- return !PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'USER');
12243
+ return !PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'USER');
12311
12244
  }
12312
12245
  canUserWriteDataForDepartment(recorderDepartment) {
12313
12246
  if (ReferentialUtils.isEmpty(recorderDepartment)) {
@@ -12316,17 +12249,17 @@ class AccountService extends BaseGraphqlService {
12316
12249
  return this.isAdmin();
12317
12250
  }
12318
12251
  // Should be login, and status ENABLE
12319
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12252
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12320
12253
  return false;
12321
- if (!this.data.account.department || !this.data.account.department.id) {
12254
+ if (!this._data.account.department || !this._data.account.department.id) {
12322
12255
  console.warn('User account has no department ! Unable to check write right against recorderDepartment');
12323
12256
  return false;
12324
12257
  }
12325
12258
  // Same recorder department: OK, user can write
12326
- if (this.data.account.department.id === recorderDepartment.id)
12259
+ if (this._data.account.department.id === recorderDepartment.id)
12327
12260
  return true;
12328
12261
  // Else, check if supervisor (or more)
12329
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'SUPERVISOR');
12262
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'SUPERVISOR');
12330
12263
  }
12331
12264
  register(data) {
12332
12265
  return __awaiter(this, void 0, void 0, function* () {
@@ -12337,7 +12270,7 @@ class AccountService extends BaseGraphqlService {
12337
12270
  throw new Error('Missing required username or password');
12338
12271
  if (this._debug)
12339
12272
  console.debug('[account] Register new user account...', data.account);
12340
- this.data.loaded = false;
12273
+ this._data.loaded = false;
12341
12274
  const now = Date.now();
12342
12275
  try {
12343
12276
  const keypair = yield this.cryptoService.scryptKeypair(data.username, data.password);
@@ -12347,22 +12280,22 @@ class AccountService extends BaseGraphqlService {
12347
12280
  data.account.settings.locale = this.settings.locale;
12348
12281
  data.account.settings.latLongFormat = this.settings.latLongFormat;
12349
12282
  data.account.department.id = data.account.department.id || this.environment.defaultDepartmentId;
12350
- this.data.keypair = keypair;
12283
+ this._data.keypair = keypair;
12351
12284
  const account = yield this.saveRemotely(data.account, keypair);
12352
12285
  // Default values
12353
12286
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12354
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12355
- this.data.account = account;
12356
- this.data.pubkey = account.pubkey;
12287
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12288
+ this._data.account = account;
12289
+ this._data.pubkey = account.pubkey;
12357
12290
  // Try to auth on pod
12358
12291
  yield this.authenticate(data);
12359
- this.data.loaded = true;
12292
+ this._data.loaded = true;
12360
12293
  yield this.saveLocally();
12361
12294
  console.debug(`[account] Account successfully registered in ${Date.now() - now}ms`);
12362
12295
  // Emit events
12363
- this.onLogin.next(this.data.account);
12364
- this.onChange.next(this.data.account);
12365
- return this.data.account;
12296
+ this.onLogin.next(this._data.account);
12297
+ this.onChange.next(this._data.account);
12298
+ return this._data.account;
12366
12299
  }
12367
12300
  catch (error) {
12368
12301
  console.error(error && error.message || error);
@@ -12378,20 +12311,20 @@ class AccountService extends BaseGraphqlService {
12378
12311
  // Basic auth
12379
12312
  if (tokenType === 'basic' || tokenType === 'basic-and-token') {
12380
12313
  // Generate the authBasic, if used
12381
- if (!this.data.authBasic) {
12314
+ if (!this._data.authBasic) {
12382
12315
  // Skip if token already provided
12383
- if (!(this.data.authToken && tokenType === 'basic-and-token')) {
12316
+ if (!(this._data.authToken && tokenType === 'basic-and-token')) {
12384
12317
  if (!data || !data.username || !data.password)
12385
12318
  throw new Error('Missing username and password');
12386
- this.data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12319
+ this._data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12387
12320
  }
12388
12321
  }
12389
- this.onAuthBasicChange.next(this.data.authBasic);
12322
+ this.onAuthBasicChange.next(this._data.authBasic);
12390
12323
  }
12391
12324
  // Generate the authToken, if used
12392
12325
  if (tokenType === 'token' || tokenType === 'basic-and-token') {
12393
12326
  try {
12394
- this.data.authToken = yield this.authenticateAndGetToken(this.data.authToken);
12327
+ this._data.authToken = yield this.authenticateAndGetToken(this._data.authToken);
12395
12328
  }
12396
12329
  catch (error) {
12397
12330
  // Never authenticate, or not ready for offline mode => exit
@@ -12402,7 +12335,7 @@ class AccountService extends BaseGraphqlService {
12402
12335
  }
12403
12336
  // Forget authBasic, to switch to authToken
12404
12337
  if (tokenType === 'basic-and-token') {
12405
- this.data.authBasic = undefined;
12338
+ this._data.authBasic = undefined;
12406
12339
  this.onAuthBasicChange.next(undefined);
12407
12340
  }
12408
12341
  });
@@ -12422,18 +12355,18 @@ class AccountService extends BaseGraphqlService {
12422
12355
  throw { code: ErrorCodes$2.UNKNOWN_ERROR, message: 'ERROR.SCRYPT_ERROR' };
12423
12356
  }
12424
12357
  // Store pubkey+keypair
12425
- this.data.pubkey = Base58.encode(keypair.publicKey);
12426
- this.data.keypair = keypair;
12358
+ this._data.pubkey = Base58.encode(keypair.publicKey);
12359
+ this._data.keypair = keypair;
12427
12360
  // Try to load previous token
12428
12361
  let previousToken = yield this.storage.get(TOKEN_STORAGE_KEY);
12429
- previousToken = previousToken && previousToken.startsWith(this.data.pubkey) && previousToken || null;
12362
+ previousToken = previousToken && previousToken.startsWith(this._data.pubkey) && previousToken || null;
12430
12363
  // Offline mode
12431
12364
  const offline = this.settings.hasOfflineFeature() && (this.network.offline || data.offline === true);
12432
12365
  if (offline) {
12433
- this.data.authToken = previousToken;
12366
+ this._data.authToken = previousToken;
12434
12367
  // Make sure network if set as offline
12435
12368
  this.network.setForceOffline(true, { showToast: false });
12436
- console.info(`[account] Login [OK] {pubkey: ${this.data.pubkey.substr(0, 8)}}, {offline: true}`);
12369
+ console.info(`[account] Login [OK] {pubkey: ${this._data.pubkey.substr(0, 8)}}, {offline: true}`);
12437
12370
  }
12438
12371
  // Online mode: try to auth on pod
12439
12372
  else {
@@ -12483,14 +12416,14 @@ class AccountService extends BaseGraphqlService {
12483
12416
  throw error;
12484
12417
  }
12485
12418
  // Emit event to observers
12486
- this.onLogin.next(this.data.account);
12487
- this.onChange.next(this.data.account);
12488
- return this.data.account;
12419
+ this.onLogin.next(this._data.account);
12420
+ this.onChange.next(this._data.account);
12421
+ return this._data.account;
12489
12422
  });
12490
12423
  }
12491
12424
  refresh() {
12492
12425
  return __awaiter(this, void 0, void 0, function* () {
12493
- if (!this.data.pubkey)
12426
+ if (!this._data.pubkey)
12494
12427
  throw new Error('User not logged');
12495
12428
  if (this.network.offline)
12496
12429
  throw new Error('Cannot check account in offline mode');
@@ -12498,9 +12431,9 @@ class AccountService extends BaseGraphqlService {
12498
12431
  yield this.saveLocally();
12499
12432
  console.debug('[account] Successfully reload account');
12500
12433
  // Emit login event to subscribers
12501
- this.onLogin.next(this.data.account);
12502
- this.onChange.next(this.data.account);
12503
- return this.data.account;
12434
+ this.onLogin.next(this._data.account);
12435
+ this.onChange.next(this._data.account);
12436
+ return this._data.account;
12504
12437
  });
12505
12438
  }
12506
12439
  /**
@@ -12510,29 +12443,29 @@ class AccountService extends BaseGraphqlService {
12510
12443
  */
12511
12444
  save(account) {
12512
12445
  return __awaiter(this, void 0, void 0, function* () {
12513
- if (!this.data.pubkey)
12446
+ if (!this._data.pubkey)
12514
12447
  return Promise.reject('User not logged');
12515
- if (this.data.pubkey !== account.pubkey)
12448
+ if (this._data.pubkey !== account.pubkey)
12516
12449
  return Promise.reject('Not user account');
12517
- account = yield this.saveRemotely(account, this.data.keypair);
12450
+ account = yield this.saveRemotely(account, this._data.keypair);
12518
12451
  // Set defaults
12519
12452
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12520
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12521
- this.data.account = account;
12522
- this.data.loaded = true;
12453
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12454
+ this._data.account = account;
12455
+ this._data.loaded = true;
12523
12456
  // Save locally (in storage)
12524
12457
  yield this.saveLocally();
12525
12458
  // Send event
12526
- this.onLogin.next(this.data.account);
12527
- this.onChange.next(this.data.account);
12528
- return this.data.account;
12459
+ this.onLogin.next(this._data.account);
12460
+ this.onChange.next(this._data.account);
12461
+ return this._data.account;
12529
12462
  });
12530
12463
  }
12531
12464
  logout() {
12532
12465
  return __awaiter(this, void 0, void 0, function* () {
12533
- const hadAuthToken = this.data.authToken && true;
12534
- const hadAuthBasic = this.data.authBasic && true;
12535
- const pubkey = this.data && this.data.pubkey;
12466
+ const hadAuthToken = this._data.authToken && true;
12467
+ const hadAuthBasic = this._data.authBasic && true;
12468
+ const pubkey = this._data && this._data.pubkey;
12536
12469
  this.resetData();
12537
12470
  if (!this.settings.hasOfflineFeature()) {
12538
12471
  // Remove all data from the local storage
@@ -12585,9 +12518,9 @@ class AccountService extends BaseGraphqlService {
12585
12518
  if (offline) {
12586
12519
  json = yield this.storage.get(ACCOUNT_STORAGE_KEY);
12587
12520
  json = json && (typeof json === 'string') && JSON.parse(json) || json;
12588
- json = json && this.data.pubkey && (json.pubkey === this.data.pubkey) && json || null;
12589
- if (!json && this.data.pubkey) {
12590
- json = yield this.storage.get(ACCOUNT_STORAGE_KEY + '#' + this.data.pubkey);
12521
+ json = json && this._data.pubkey && (json.pubkey === this._data.pubkey) && json || null;
12522
+ if (!json && this._data.pubkey) {
12523
+ json = yield this.storage.get(ACCOUNT_STORAGE_KEY + '#' + this._data.pubkey);
12591
12524
  json = json && (typeof json === 'string') && JSON.parse(json) || json;
12592
12525
  }
12593
12526
  }
@@ -12682,7 +12615,7 @@ class AccountService extends BaseGraphqlService {
12682
12615
  });
12683
12616
  }
12684
12617
  listenChanges() {
12685
- if (!this.data.pubkey)
12618
+ if (!this._data.pubkey)
12686
12619
  return Subscription.EMPTY;
12687
12620
  const self = this;
12688
12621
  console.debug('[account] [WS] Listening account changes');
@@ -12696,34 +12629,30 @@ class AccountService extends BaseGraphqlService {
12696
12629
  message: 'ERROR.ACCOUNT.SUBSCRIBE_ACCOUNT_ERROR'
12697
12630
  }
12698
12631
  }).subscribe({
12699
- next({ data }) {
12632
+ next: ({ data }) => __awaiter(this, void 0, void 0, function* () {
12700
12633
  var _a;
12701
- return __awaiter(this, void 0, void 0, function* () {
12702
- if (!data)
12703
- return;
12704
- const existingUpdateDate = toDateISOString((_a = self.data.account) === null || _a === void 0 ? void 0 : _a.updateDate);
12705
- if (existingUpdateDate !== data.updateDate) {
12706
- console.debug(`[account] [WS] Detected update on {${data.updateDate}}`);
12707
- yield self.refresh();
12708
- }
12709
- });
12710
- },
12711
- error(err) {
12712
- return __awaiter(this, void 0, void 0, function* () {
12713
- if (err && +err.code === ServerErrorCodes.NOT_FOUND) {
12714
- console.info('[account] Account not exists anymore: force user to logout...', err);
12715
- yield self.logout();
12716
- }
12717
- else if (err && +err.code === ServerErrorCodes.UNAUTHORIZED) {
12718
- console.info('[account] Account not authorized: force user to logout...', err);
12719
- yield self.logout();
12720
- }
12721
- else {
12722
- console.warn('[account] [WS] Received error:', err);
12723
- }
12724
- });
12725
- },
12726
- complete() {
12634
+ if (!data)
12635
+ return;
12636
+ const existingUpdateDate = toDateISOString((_a = self._data.account) === null || _a === void 0 ? void 0 : _a.updateDate);
12637
+ if (existingUpdateDate !== data.updateDate) {
12638
+ console.debug(`[account] [WS] Detected update on {${data.updateDate}}`);
12639
+ yield self.refresh();
12640
+ }
12641
+ }),
12642
+ error: (err) => __awaiter(this, void 0, void 0, function* () {
12643
+ if (err && +err.code === ServerErrorCodes.NOT_FOUND) {
12644
+ console.info('[account] Account not exists anymore: force user to logout...', err);
12645
+ yield self.logout();
12646
+ }
12647
+ else if (err && +err.code === ServerErrorCodes.UNAUTHORIZED) {
12648
+ console.info('[account] Account not authorized: force user to logout...', err);
12649
+ yield self.logout();
12650
+ }
12651
+ else {
12652
+ console.warn('[account] [WS] Received error:', err);
12653
+ }
12654
+ }),
12655
+ complete: () => {
12727
12656
  console.debug('[account] [WS] Completed');
12728
12657
  }
12729
12658
  });
@@ -12764,29 +12693,40 @@ class AccountService extends BaseGraphqlService {
12764
12693
  this._$additionalFields.next(values.concat(field));
12765
12694
  }
12766
12695
  /* -- protected method -- */
12696
+ resetData() {
12697
+ this._data.loaded = false;
12698
+ this._data.keypair = null;
12699
+ this._data.authToken = null;
12700
+ this._data.authBasic = null;
12701
+ this._data.pubkey = null;
12702
+ this._data.mainProfile = null;
12703
+ this._data.account = new Account();
12704
+ this._data.person = null;
12705
+ this._data.department = null;
12706
+ }
12767
12707
  loadData(opts) {
12768
12708
  return __awaiter(this, void 0, void 0, function* () {
12769
- if (!this.data.pubkey)
12709
+ if (!this._data.pubkey)
12770
12710
  throw new Error('User not logged');
12771
- this.data.loaded = false;
12711
+ this._data.loaded = false;
12772
12712
  try {
12773
- let account = (yield this.load(opts)) || new Account();
12713
+ const account = (yield this.load(opts)) || new Account();
12774
12714
  // Set defaults
12775
12715
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12776
12716
  account.settings = account.settings || new UserSettings();
12777
12717
  account.settings.locale = account.settings.locale || this.settings.locale;
12778
12718
  account.settings.latLongFormat = account.settings.latLongFormat || this.settings.latLongFormat || 'DDMM';
12779
12719
  // Read main profile
12780
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12720
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12781
12721
  // Update, instead of replace it
12782
- if (this.data.account) {
12783
- this.data.account.fromObject(account);
12722
+ if (this._data.account) {
12723
+ this._data.account.fromObject(account);
12784
12724
  }
12785
12725
  else {
12786
- this.data.account = account;
12726
+ this._data.account = account;
12787
12727
  }
12788
- this.data.loaded = true;
12789
- return this.data.account;
12728
+ this._data.loaded = true;
12729
+ return this._data.account;
12790
12730
  }
12791
12731
  catch (error) {
12792
12732
  this.resetData();
@@ -12820,9 +12760,9 @@ class AccountService extends BaseGraphqlService {
12820
12760
  return;
12821
12761
  if (this._debug)
12822
12762
  console.debug(`[account] Account restoration...`);
12823
- this.data.authToken = token;
12824
- this.data.pubkey = pubkey;
12825
- this.data.keypair = seckey && {
12763
+ this._data.authToken = token;
12764
+ this._data.pubkey = pubkey;
12765
+ this._data.keypair = seckey && {
12826
12766
  publicKey: Base58.decode(pubkey),
12827
12767
  secretKey: Base58.decode(seckey)
12828
12768
  } || null;
@@ -12830,7 +12770,7 @@ class AccountService extends BaseGraphqlService {
12830
12770
  if (this.network.online) {
12831
12771
  try {
12832
12772
  yield this.authenticate();
12833
- if (!this.data.authToken && !this.data.authBasic)
12773
+ if (!this._data.authToken && !this._data.authBasic)
12834
12774
  throw new Error('Authentication failed');
12835
12775
  }
12836
12776
  catch (error) {
@@ -12861,14 +12801,14 @@ class AccountService extends BaseGraphqlService {
12861
12801
  // Transform to entity
12862
12802
  const account = Account.fromObject(jsonAccount);
12863
12803
  // Update data
12864
- this.data.account = account;
12865
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12866
- this.data.loaded = true;
12804
+ this._data.account = account;
12805
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12806
+ this._data.loaded = true;
12867
12807
  // Emit event
12868
- this.onLogin.next(this.data.account);
12869
- this.onChange.next(this.data.account);
12808
+ this.onLogin.next(this._data.account);
12809
+ this.onChange.next(this._data.account);
12870
12810
  if (this._debug)
12871
- console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this.data.mainProfile}}`);
12811
+ console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this._data.mainProfile}}`);
12872
12812
  return account;
12873
12813
  });
12874
12814
  }
@@ -12877,13 +12817,13 @@ class AccountService extends BaseGraphqlService {
12877
12817
  */
12878
12818
  saveLocally() {
12879
12819
  return __awaiter(this, void 0, void 0, function* () {
12880
- if (!this.data.pubkey)
12820
+ if (!this._data.pubkey)
12881
12821
  throw new Error('User not logged');
12882
12822
  if (this._debug)
12883
- console.debug(`[account] Saving account {${this.data.pubkey.substring(0, 6)}} in local storage...`);
12823
+ console.debug(`[account] Saving account {${this._data.pubkey.substring(0, 6)}} in local storage...`);
12884
12824
  // Convert account to json
12885
- const json = this.data.account.asObject({ keepTypename: true });
12886
- const seckey = this.data.keypair && this.data.keypair.secretKey && Base58.encode(this.data.keypair.secretKey) || null;
12825
+ const json = this._data.account.asObject({ keepTypename: true });
12826
+ const seckey = this._data.keypair && this._data.keypair.secretKey && Base58.encode(this._data.keypair.secretKey) || null;
12887
12827
  // Convert avatar URL to dataUrl (e.g. 'data:image/png:<base64 content>')
12888
12828
  const hasAvatarUrl = json.avatar && !json.avatar.endsWith(DEFAULT_AVATAR_IMAGE) &&
12889
12829
  (json.avatar.startsWith('http://') || (json.avatar.startsWith('https://')));
@@ -12904,9 +12844,9 @@ class AccountService extends BaseGraphqlService {
12904
12844
  }
12905
12845
  try {
12906
12846
  yield Promise.all([
12907
- this.storage.set(PUBKEY_STORAGE_KEY, this.data.pubkey),
12908
- this.storage.set(TOKEN_STORAGE_KEY, this.data.authToken),
12909
- this.storage.set(`${ACCOUNT_STORAGE_KEY}#${this.data.pubkey}`, json),
12847
+ this.storage.set(PUBKEY_STORAGE_KEY, this._data.pubkey),
12848
+ this.storage.set(TOKEN_STORAGE_KEY, this._data.authToken),
12849
+ this.storage.set(`${ACCOUNT_STORAGE_KEY}#${this._data.pubkey}`, json),
12910
12850
  // Secret key (optional)
12911
12851
  seckey && this.storage.set(SECKEY_STORAGE_KEY, seckey) || this.storage.remove(SECKEY_STORAGE_KEY),
12912
12852
  // Remove old storage key
@@ -12964,7 +12904,7 @@ class AccountService extends BaseGraphqlService {
12964
12904
  }
12965
12905
  authenticateAndGetToken(token, counter) {
12966
12906
  return __awaiter(this, void 0, void 0, function* () {
12967
- if (!this.data.pubkey)
12907
+ if (!this._data.pubkey)
12968
12908
  throw new Error('User not logged');
12969
12909
  if (!counter)
12970
12910
  console.info('[account] Authentication on pod...');
@@ -12990,7 +12930,7 @@ class AccountService extends BaseGraphqlService {
12990
12930
  if (data && data.authenticate) {
12991
12931
  // Store the token
12992
12932
  this.onAuthTokenChange.next(token);
12993
- console.info(`[account] Authentication on pod [OK] {pubkey: '${this.data.pubkey.substr(0, 8)}'}`);
12933
+ console.info(`[account] Authentication on pod [OK] {pubkey: '${this._data.pubkey.substr(0, 8)}'}`);
12994
12934
  return token; // return the token
12995
12935
  }
12996
12936
  // Continue (will retry with another challenge)
@@ -13016,8 +12956,8 @@ class AccountService extends BaseGraphqlService {
13016
12956
  }
13017
12957
  // TODO: check server pubkey as a valid certificate
13018
12958
  // Do the challenge
13019
- const signature = yield this.cryptoService.sign(data.authChallenge.challenge, this.data.keypair);
13020
- const newToken = `${this.data.pubkey}:${data.authChallenge.challenge}|${signature}`;
12959
+ const signature = yield this.cryptoService.sign(data.authChallenge.challenge, this._data.keypair);
12960
+ const newToken = `${this._data.pubkey}:${data.authChallenge.challenge}|${signature}`;
13021
12961
  // iterate with the new token
13022
12962
  return yield this.authenticateAndGetToken(newToken, (counter || 1) + 1 /* increment */);
13023
12963
  });
@@ -13376,9 +13316,8 @@ class ConfigService extends BaseGraphqlService {
13376
13316
  }
13377
13317
  get config() {
13378
13318
  // If first call: start loading
13379
- if (!this._started) {
13319
+ if (!this._started)
13380
13320
  this.start();
13381
- }
13382
13321
  return this.$data.pipe(filter(isNotNil));
13383
13322
  }
13384
13323
  start() {
@@ -13389,30 +13328,38 @@ class ConfigService extends BaseGraphqlService {
13389
13328
  console.info('[config] Starting configuration...');
13390
13329
  this._startPromise = this.graphql.ready()
13391
13330
  .then(() => this.loadOrRestoreLocally())
13392
- .then(() => {
13331
+ .then((data) => {
13393
13332
  this._started = true;
13394
13333
  this._startPromise = undefined;
13334
+ this.$data.next(data);
13335
+ return data;
13395
13336
  })
13396
13337
  .catch((err) => {
13397
13338
  console.error(err && err.message || err, err);
13339
+ this._started = false;
13398
13340
  this._startPromise = undefined;
13341
+ return null;
13399
13342
  });
13400
13343
  return this._startPromise;
13401
13344
  }
13402
13345
  stop() {
13403
- this._subscription.unsubscribe();
13404
- this._subscription = new Subscription();
13405
- this._started = false;
13406
- this._startPromise = undefined;
13346
+ return __awaiter(this, void 0, void 0, function* () {
13347
+ this._subscription.unsubscribe();
13348
+ this._subscription = new Subscription();
13349
+ this._started = false;
13350
+ this._startPromise = undefined;
13351
+ });
13407
13352
  }
13408
13353
  restart() {
13409
- if (this.started)
13410
- this.stop();
13411
- return this.start();
13354
+ return __awaiter(this, void 0, void 0, function* () {
13355
+ if (this.started)
13356
+ yield this.stop();
13357
+ return this.start();
13358
+ });
13412
13359
  }
13413
13360
  ready() {
13414
13361
  if (this._started)
13415
- return Promise.resolve();
13362
+ return Promise.resolve(this.$data.value);
13416
13363
  if (this._startPromise)
13417
13364
  return this._startPromise;
13418
13365
  return this.start();
@@ -13467,7 +13414,7 @@ class ConfigService extends BaseGraphqlService {
13467
13414
  console.debug('[config] Pod configuration saved!');
13468
13415
  const reloadedConfig = yield this.loadDefault({ fetchPolicy: 'network-only' });
13469
13416
  // If this is the default config
13470
- const defaultConfig = this.$data.getValue();
13417
+ const defaultConfig = this.$data.value;
13471
13418
  if (isNotNil(defaultConfig) && reloadedConfig.label === defaultConfig.label) {
13472
13419
  // Emit update event when is default config
13473
13420
  this.$data.next(reloadedConfig);
@@ -13544,7 +13491,7 @@ class ConfigService extends BaseGraphqlService {
13544
13491
  if (wasJustLoaded) {
13545
13492
  // TODO
13546
13493
  }
13547
- this.$data.next(data);
13494
+ return data;
13548
13495
  });
13549
13496
  }
13550
13497
  restoreLocally() {
@@ -13723,6 +13670,9 @@ class PlatformService {
13723
13670
  isAndroidCordova() {
13724
13671
  return this._android && this._cordova;
13725
13672
  }
13673
+ get canDownload() {
13674
+ return this._android && !!this.downloader;
13675
+ }
13726
13676
  width() {
13727
13677
  return this.platform.width();
13728
13678
  }
@@ -13764,7 +13714,7 @@ class PlatformService {
13764
13714
  .then(() => {
13765
13715
  this._started = true;
13766
13716
  this._startPromise = undefined;
13767
- console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}} in ${Date.now() - now}ms`);
13717
+ console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}, canDownload: ${this.canDownload}} in ${Date.now() - now}ms`);
13768
13718
  // Update cache configuration when network changed
13769
13719
  this.networkService.onNetworkStatusChanges.subscribe((type) => this.configureCache(type !== 'none'));
13770
13720
  // Update authentication type
@@ -13812,18 +13762,31 @@ class PlatformService {
13812
13762
  }
13813
13763
  }
13814
13764
  download(request) {
13815
- if (!request || !request.uri)
13816
- throw new Error('Missing argument \'request\' or \'request.uri\'');
13817
- if (this._android && this._cordova) {
13818
- 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) });
13819
- this.downloader.download(request)
13820
- .then((location) => console.info('[platform] File successfully downloaded at:' + location))
13821
- .catch((error) => console.error(error));
13822
- }
13823
- // Web mode: open URI using the browser
13824
- else {
13825
- this.open(request.uri, '_system', 'location=yes');
13826
- }
13765
+ return __awaiter(this, void 0, void 0, function* () {
13766
+ if (!request || !request.uri)
13767
+ throw new Error('Missing argument \'request\' or \'request.uri\'');
13768
+ if (this._android && this.downloader) {
13769
+ request = Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted }, request);
13770
+ try {
13771
+ console.debug('[platform] Downloading, using request: ' + JSON.stringify(request));
13772
+ const location = yield this.downloader.download(request);
13773
+ console.info('[platform] File successfully downloaded at:' + location);
13774
+ return location;
13775
+ }
13776
+ catch (err) {
13777
+ console.error('[platform] Unable to download: ' + (err && err.message || err));
13778
+ throw err;
13779
+ }
13780
+ }
13781
+ // Fallback (no Cordova)
13782
+ // Web mode: open URI using the browser
13783
+ // TODO: find a way to download under iOS (see https://www.c-sharpcorner.com/article/how-to-download-a-file-using-file-transfer-plugin-in-ionic-3/)
13784
+ else {
13785
+ console.warn('[platform] Cannot use Cordova downloader: using browser open()');
13786
+ this.open(request.uri, '_system', 'location=no', true);
13787
+ return undefined;
13788
+ }
13789
+ });
13827
13790
  }
13828
13791
  /* -- protected methods -- */
13829
13792
  configureCordovaPlugins(mobile) {
@@ -13920,13 +13883,14 @@ class PlatformService {
13920
13883
  storeName: forage.config().storeName,
13921
13884
  driver: [forage.INDEXEDDB, forage.WEBSQL]
13922
13885
  });
13923
- // IF data stored in the OLD storage: start migration
13924
13886
  const keys = yield oldForage.keys();
13887
+ // No data in the odl storage: skip migration
13925
13888
  if (isEmptyArray(keys)) {
13926
- // Drop the old instance
13927
- console.info(`[platform] Drop old storage {name: '${forage.config().name}', driver: '${oldForage.driver()}'}`);
13889
+ // Drop the old instance (not need anymore)
13890
+ console.debug(`[platform] Old storage is empty: dropping unused instance {name: '${forage.config().name}', driver: '${oldForage.driver()}'}`);
13928
13891
  yield oldForage.dropInstance();
13929
13892
  }
13893
+ // IF some data stored in the OLD storage: start migration
13930
13894
  else {
13931
13895
  const now = Date.now();
13932
13896
  console.info(`[platform] Starting storage migration...`);
@@ -18486,22 +18450,24 @@ const MimeTypes = Object.freeze({
18486
18450
  ANDROID_APK: 'application/vnd.android.package-archive'
18487
18451
  });
18488
18452
  class AppInstallUpgradeCard {
18489
- constructor(modalCtrl, configService, platform, cd, network, environment) {
18453
+ constructor(modalCtrl, configService, toastController, translate, cd, platform, network, environment) {
18490
18454
  this.modalCtrl = modalCtrl;
18491
18455
  this.configService = configService;
18492
- this.platform = platform;
18456
+ this.toastController = toastController;
18457
+ this.translate = translate;
18493
18458
  this.cd = cd;
18459
+ this.platform = platform;
18494
18460
  this.network = network;
18495
18461
  this.environment = environment;
18496
18462
  this._subscription = new Subscription();
18497
18463
  this._showUpdateOfflineFeature = false;
18498
18464
  this.loading = true;
18465
+ this.downloading = false;
18499
18466
  this.waitingNetwork = false;
18500
18467
  this.showUpgradeWarning = true;
18501
18468
  this.showOfflineWarning = true;
18502
18469
  this.showInstallButton = false;
18503
18470
  this.onUpdateOfflineModeClick = new EventEmitter();
18504
- this.isAndroidCordova = platform.isAndroidCordova();
18505
18471
  }
18506
18472
  set showUpdateOfflineFeature(value) {
18507
18473
  if (value === this._showUpdateOfflineFeature)
@@ -18518,18 +18484,8 @@ class AppInstallUpgradeCard {
18518
18484
  this.offline = this.network.offline;
18519
18485
  // Listen pod config
18520
18486
  this._subscription.add(this.configService.config
18521
- .subscribe(config => {
18522
- console.info('[install] Checking if upgrade or install is need...');
18523
- const installLinks = this.getAllInstallLinks(config);
18524
- // Check for upgrade
18525
- this.updateLinks = this.getCompatibleUpgradeLinks(installLinks, config);
18526
- // Check for install links (if no upgrade need)
18527
- this.installLinks = !this.updateLinks && this.getCompatibleInstallLinks(installLinks);
18528
- setTimeout(() => {
18529
- this.loading = false;
18530
- this.markForCheck();
18531
- }, 2000); // Add a delay, for animation
18532
- }));
18487
+ .pipe(debounceTime(1000))
18488
+ .subscribe(config => this.checkNeedInstallOrUpdate(config)));
18533
18489
  // Listen network changes
18534
18490
  this._subscription.add(this.network.onNetworkStatusChanges
18535
18491
  .pipe(
@@ -18537,34 +18493,70 @@ class AppInstallUpgradeCard {
18537
18493
  //tap(() => this.waitingNetwork = false),
18538
18494
  map(connectionType => connectionType === 'none'), distinctUntilChanged())
18539
18495
  .subscribe(offline => {
18540
- this.offline = offline;
18541
- this.markForCheck();
18496
+ if (this.offline !== offline) {
18497
+ this.offline = offline;
18498
+ this.markForCheck();
18499
+ }
18542
18500
  }));
18543
18501
  });
18544
18502
  }
18545
18503
  ngOnDestroy() {
18546
18504
  this._subscription.unsubscribe();
18547
18505
  }
18548
- downloadLink(event, link) {
18549
- if (!link || !link.url)
18550
- return; // Skip
18551
- this.platform.download({
18552
- uri: link.url,
18553
- filename: link.downloadFilename,
18554
- mimeType: link.mimeType
18506
+ download(event, link) {
18507
+ return __awaiter(this, void 0, void 0, function* () {
18508
+ if (!link || !link.url) {
18509
+ console.error('[install-upgrade-card] Missing required argument \'link.url\'');
18510
+ return; // Skip
18511
+ }
18512
+ this.downloading = true;
18513
+ try {
18514
+ console.info('[install-upgrade-card] User ask download of: ' + link.url);
18515
+ const location = this.platform.download({
18516
+ uri: link.url,
18517
+ title: link.name,
18518
+ filename: link.downloadFilename,
18519
+ mimeType: link.mimeType
18520
+ });
18521
+ console.info('[install-upgrade-card] File downloaded at: ' + location);
18522
+ }
18523
+ // Failed: display a toast
18524
+ catch (err) {
18525
+ console.error('[install-upgrade] Download failed: ' + (err && err.message || err));
18526
+ this.showToast({
18527
+ message: 'ERROR.DOWNLOAD_FAILED',
18528
+ messageParams: { error: err },
18529
+ type: 'error',
18530
+ showCloseButton: true
18531
+ });
18532
+ return; // Stop here
18533
+ }
18534
+ finally {
18535
+ this.downloading = false;
18536
+ this.markForCheck();
18537
+ }
18538
+ this.showToast({
18539
+ message: 'INFO.DOWNLOAD_UPDATE_SUCCEED',
18540
+ type: 'info',
18541
+ showCloseButton: true
18542
+ });
18555
18543
  });
18556
18544
  }
18557
18545
  tryOnline() {
18558
- this.waitingNetwork = true;
18559
- this.markForCheck();
18560
- this.network.tryOnline({
18561
- showLoadingToast: false,
18562
- showOnlineToast: true,
18563
- showOfflineToast: false
18564
- })
18565
- .then(() => {
18566
- this.waitingNetwork = false;
18546
+ return __awaiter(this, void 0, void 0, function* () {
18547
+ this.waitingNetwork = true;
18567
18548
  this.markForCheck();
18549
+ try {
18550
+ yield this.network.tryOnline({
18551
+ showLoadingToast: false,
18552
+ showOnlineToast: true,
18553
+ showOfflineToast: false
18554
+ });
18555
+ }
18556
+ finally {
18557
+ this.waitingNetwork = false;
18558
+ this.markForCheck();
18559
+ }
18568
18560
  });
18569
18561
  }
18570
18562
  getPlatformName(platform) {
@@ -18581,6 +18573,23 @@ class AppInstallUpgradeCard {
18581
18573
  return value;
18582
18574
  }
18583
18575
  /* -- private method -- */
18576
+ checkNeedInstallOrUpdate(config) {
18577
+ return __awaiter(this, void 0, void 0, function* () {
18578
+ console.info('[install] Check if need to install or upgrade...');
18579
+ try {
18580
+ const installLinks = this.getAllInstallLinks(config);
18581
+ // Check for upgrade
18582
+ this.updateLinks = this.getCompatibleUpgradeLinks(installLinks, config);
18583
+ // Check for install links (if no upgrade need)
18584
+ this.installLinks = !this.updateLinks && this.getCompatibleInstallLinks(installLinks);
18585
+ yield sleep(500); // Add a delay, for animation
18586
+ }
18587
+ finally {
18588
+ this.loading = false;
18589
+ this.markForCheck();
18590
+ }
18591
+ });
18592
+ }
18584
18593
  getCompatibleInstallLinks(installLinks) {
18585
18594
  // Cordova already running: not need to install
18586
18595
  if (this.platform.is('cordova'))
@@ -18592,15 +18601,20 @@ class AppInstallUpgradeCard {
18592
18601
  return undefined;
18593
18602
  }
18594
18603
  getCompatibleUpgradeLinks(installLinks, config) {
18595
- const appMinVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18596
- const needUpgrade = appMinVersion && !VersionUtils.isCompatible(appMinVersion, this.environment.version);
18604
+ const appVersion = this.environment.version;
18605
+ const appMinVersionFromPod = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18606
+ if (!appVersion) {
18607
+ console.error('Missing value for \'environment.version\': cannot check app compatibility!');
18608
+ return undefined;
18609
+ }
18610
+ const needUpgrade = appMinVersionFromPod && !VersionUtils.isCompatible(appMinVersionFromPod, appVersion);
18597
18611
  if (!needUpgrade)
18598
18612
  return undefined;
18599
18613
  const upgradeLinks = installLinks
18600
18614
  .filter(link => this.platform.is('mobileweb') || (link.platform && this.platform.is(link.platform)));
18601
18615
  // Use min version as default version
18602
18616
  upgradeLinks.forEach(link => {
18603
- link.version = link.version || appMinVersion;
18617
+ link.version = link.version || appMinVersionFromPod;
18604
18618
  });
18605
18619
  return isNotEmptyArray(upgradeLinks) ? upgradeLinks : undefined;
18606
18620
  }
@@ -18612,6 +18626,7 @@ class AppInstallUpgradeCard {
18612
18626
  let url = config.getProperty(CORE_CONFIG_OPTIONS.ANDROID_INSTALL_URL);
18613
18627
  if (isNilOrBlank(url))
18614
18628
  url = this.environment.defaultAndroidInstallUrl || null;
18629
+ const minVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18615
18630
  // Compute App name
18616
18631
  const name = isNotNilOrBlank(url) && config.label || this.environment.defaultAppName || 'SUMARiS';
18617
18632
  if (url) {
@@ -18620,7 +18635,7 @@ class AppInstallUpgradeCard {
18620
18635
  let mimeType;
18621
18636
  // Get file name
18622
18637
  const filename = this.getFilename(url);
18623
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18638
+ version = minVersion || 'latest';
18624
18639
  // OK, this is a downloadable APK file (e.g. NOT a link to a playstore)
18625
18640
  if (filename === null || filename === void 0 ? void 0 : filename.endsWith('.apk')) {
18626
18641
  // Define mime type
@@ -18630,13 +18645,12 @@ class AppInstallUpgradeCard {
18630
18645
  version = versionMatches && versionMatches[1] || version;
18631
18646
  // Compute a new file name, with the version
18632
18647
  if (isNotNilOrBlank(name)) {
18633
- downloadFilename = `${name}-${version}.apk`;
18648
+ downloadFilename = `${name}-v${version}.apk`.toLowerCase();
18634
18649
  }
18635
18650
  else {
18636
18651
  downloadFilename = filename;
18637
- // Replace 'latest' with the app min version
18652
+ // Replace 'latest' with the app version, if present
18638
18653
  if (downloadFilename.indexOf('latest')) {
18639
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18640
18654
  downloadFilename = downloadFilename.replace('latest', version);
18641
18655
  }
18642
18656
  }
@@ -18665,11 +18679,16 @@ class AppInstallUpgradeCard {
18665
18679
  markForCheck() {
18666
18680
  this.cd.markForCheck();
18667
18681
  }
18682
+ showToast(opts) {
18683
+ return __awaiter(this, void 0, void 0, function* () {
18684
+ yield Toasts.show(this.toastController, this.translate, opts);
18685
+ });
18686
+ }
18668
18687
  }
18669
18688
  AppInstallUpgradeCard.decorators = [
18670
18689
  { type: Component, args: [{
18671
18690
  selector: 'app-install-upgrade-card',
18672
- template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n <!-- Waiting spinner -->\n <ng-template #waitingSpinner>\n <ion-spinner *ngIf=\"waitingNetwork\" color=\"light\"></ion-spinner>\n </ng-template>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && updateLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of updateLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n\n <!-- Download button -->\n <ion-button *ngIf=\"link.downloadFilename; else redirectButton\"\n [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.downloadFilename; else redirectButton\">\n\n <!-- Cordova download button -->\n <ion-button *ngIf=\"isAndroidCordova; else webDownloadButton\"\n (click)=\"downloadLink($event, asLink(link))\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n",
18691
+ template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n <!-- Waiting spinner -->\n <ng-template #waitingSpinner>\n <ion-spinner *ngIf=\"waitingNetwork\" color=\"light\"></ion-spinner>\n </ng-template>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && updateLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of updateLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.downloadFilename; else redirectButton\">\n\n <!-- Download using platform -->\n <ion-button *ngIf=\"platform.canDownload; else webDownloadButton\"\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"downloading\"\n color=\"tertiary\" class=\"ion-float-end\">\n <ion-icon slot=\"start\" *ngIf=\"!downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n",
18673
18692
  changeDetection: ChangeDetectionStrategy.OnPush,
18674
18693
  animations: [slideUpDownAnimation],
18675
18694
  styles: ["ion-text small{font-size:85%}"]
@@ -18678,8 +18697,10 @@ AppInstallUpgradeCard.decorators = [
18678
18697
  AppInstallUpgradeCard.ctorParameters = () => [
18679
18698
  { type: ModalController },
18680
18699
  { type: ConfigService },
18681
- { type: PlatformService },
18700
+ { type: ToastController },
18701
+ { type: TranslateService },
18682
18702
  { type: ChangeDetectorRef },
18703
+ { type: PlatformService },
18683
18704
  { type: NetworkService },
18684
18705
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
18685
18706
  ];
@@ -23531,6 +23552,7 @@ class UsersPage extends AppTable {
23531
23552
  this.filterCriteriaCount = 0;
23532
23553
  this.statusList = StatusList;
23533
23554
  this.statusById = StatusById;
23555
+ this.useSticky = false;
23534
23556
  this.referentialToString = referentialToString;
23535
23557
  this.inlineEdition = accountService.isAdmin(); // Allow inline edition only if admin
23536
23558
  this.canEdit = accountService.isAdmin();
@@ -23634,13 +23656,13 @@ class UsersPage extends AppTable {
23634
23656
  UsersPage.decorators = [
23635
23657
  { type: Component, args: [{
23636
23658
  selector: 'app-users-table',
23637
- 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",
23659
+ 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",
23638
23660
  providers: [
23639
23661
  { provide: ValidatorService, useExisting: PersonValidatorService }
23640
23662
  ],
23641
23663
  animations: [slideUpDownAnimation],
23642
23664
  changeDetection: ChangeDetectionStrategy.OnPush,
23643
- 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}"]
23665
+ 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}"]
23644
23666
  },] }
23645
23667
  ];
23646
23668
  UsersPage.ctorParameters = () => [
@@ -23660,6 +23682,7 @@ UsersPage.ctorParameters = () => [
23660
23682
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
23661
23683
  ];
23662
23684
  UsersPage.propDecorators = {
23685
+ useSticky: [{ type: Input }],
23663
23686
  filterExpansionPanel: [{ type: ViewChild, args: [MatExpansionPanel, { static: true },] }]
23664
23687
  };
23665
23688
 
@@ -23725,5 +23748,5 @@ const ErrorCodes = {
23725
23748
  * Generated bundle index. Do not edit.
23726
23749
  */
23727
23750
 
23728
- 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 };
23729
- //# sourceMappingURL=ngx-sumaris-components.js.map
23751
+ 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 };
23752
+ //# sourceMappingURL=sumaris-net.ngx-components.js.map