@sumaris-net/ngx-components 0.24.3 → 0.25.0

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