@sumaris-net/ngx-components 0.24.3 → 0.25.3

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 (40) hide show
  1. package/bundles/sumaris-net.ngx-components.umd.js +1100 -906
  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 +26 -74
  9. package/esm2015/src/app/core/install/install-upgrade-card.component.js +134 -46
  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/model/peer.model.js +15 -2
  14. package/esm2015/src/app/core/services/network.service.js +134 -169
  15. package/esm2015/src/app/core/services/platform.service.js +46 -17
  16. package/esm2015/src/app/core/services/storage/entities-storage.service.js +59 -63
  17. package/esm2015/src/app/shared/audio/audio.js +10 -39
  18. package/esm2015/src/app/shared/material/autocomplete/testing/autocomplete.test.js +2 -2
  19. package/esm2015/src/app/shared/services/startable-service.class.js +77 -0
  20. package/esm2015/src/environments/environment.class.js +1 -1
  21. package/esm2015/src/environments/environment.js +5 -1
  22. package/fesm2015/sumaris-net.ngx-components.js +724 -649
  23. package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
  24. package/package.json +1 -1
  25. package/public_api.d.ts +1 -0
  26. package/src/app/admin/users/list/users.d.ts +1 -0
  27. package/src/app/core/graphql/graphql.service.d.ts +5 -13
  28. package/src/app/core/install/install-upgrade-card.component.d.ts +14 -7
  29. package/src/app/core/services/account.service.d.ts +11 -10
  30. package/src/app/core/services/config.service.d.ts +6 -5
  31. package/src/app/core/services/local-settings.service.d.ts +4 -9
  32. package/src/app/core/services/model/peer.model.d.ts +1 -0
  33. package/src/app/core/services/network.service.d.ts +27 -38
  34. package/src/app/core/services/platform.service.d.ts +3 -1
  35. package/src/app/core/services/storage/entities-storage.service.d.ts +13 -13
  36. package/src/app/shared/audio/audio.d.ts +4 -8
  37. package/src/app/shared/services/startable-service.class.d.ts +26 -0
  38. package/src/assets/i18n/fr.json +3 -0
  39. package/src/environments/environment.class.d.ts +1 -0
  40. 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,97 @@ AppFormField.propDecorators = {
6888
6888
  matInput: [{ type: ViewChild, args: ['matInput',] }]
6889
6889
  };
6890
6890
 
6891
+ class StartableService {
6892
+ constructor(platform) {
6893
+ this.onStart = new Subject();
6894
+ this._debug = false;
6895
+ this._data = null;
6896
+ this._started = false;
6897
+ this._startPrerequisite = platform
6898
+ ? () => platform.ready()
6899
+ : () => Promise.resolve();
6900
+ }
6901
+ start() {
6902
+ if (this._startPromise)
6903
+ return this._startPromise;
6904
+ if (this._started)
6905
+ return Promise.resolve(this._data);
6906
+ this._startPromise = this._startPrerequisite()
6907
+ .then(() => this.ngOnStart())
6908
+ .then(data => {
6909
+ this._data = data;
6910
+ this._started = true;
6911
+ this._startPromise = undefined;
6912
+ this.onStart.next(this._data);
6913
+ return this._data;
6914
+ })
6915
+ .catch(err => {
6916
+ console.error('Failed to start a service: ' + (err && err.message || err), err);
6917
+ this._started = false;
6918
+ this._startPromise = null;
6919
+ return null;
6920
+ });
6921
+ return this._startPromise;
6922
+ }
6923
+ stop() {
6924
+ return __awaiter(this, void 0, void 0, function* () {
6925
+ try {
6926
+ yield this.ngOnStop();
6927
+ this._started = false;
6928
+ this._startPromise = undefined;
6929
+ }
6930
+ catch (err) {
6931
+ console.error('Failed to stop a service: ' + (err && err.message || err), err);
6932
+ }
6933
+ });
6934
+ }
6935
+ restart() {
6936
+ return __awaiter(this, void 0, void 0, function* () {
6937
+ if (this._startPromise)
6938
+ yield this._startPromise; // Wait end of previous loading
6939
+ if (this._started)
6940
+ yield this.stop(); // Then stop if started
6941
+ return this.start(); // Then start again
6942
+ });
6943
+ }
6944
+ get started() {
6945
+ return this._started;
6946
+ }
6947
+ get starting() {
6948
+ return !!this._startPromise;
6949
+ }
6950
+ ready() {
6951
+ if (this._started)
6952
+ return Promise.resolve(this._data);
6953
+ return this.start();
6954
+ }
6955
+ ngOnStop() {
6956
+ return __awaiter(this, void 0, void 0, function* () {
6957
+ // Can be override by subclasses
6958
+ });
6959
+ }
6960
+ }
6961
+ StartableService.ctorParameters = () => [
6962
+ { type: undefined, decorators: [{ type: Optional }] }
6963
+ ];
6964
+
6891
6965
  const SYSTEM_SOUNDS = [
6892
6966
  { id: 'beep-confirm', assetPath: 'assets/audio/beep-confirm.mp3', vibration: 250 },
6893
6967
  { id: 'beep-error', assetPath: 'assets/audio/beep-error.mp3', vibration: 1000 },
6894
6968
  { id: 'startup', assetPath: 'assets/audio/unfa-ping.mp3', vibration: [1, 500, 250, 750] },
6895
6969
  ];
6896
- class AudioProvider {
6970
+ class AudioProvider extends StartableService {
6897
6971
  constructor(platform, nativeAudio, vibration, audioManagement) {
6972
+ super(platform);
6898
6973
  this.platform = platform;
6899
6974
  this.nativeAudio = nativeAudio;
6900
6975
  this.vibration = vibration;
6901
6976
  this.audioManagement = audioManagement;
6902
- this._started = false;
6903
6977
  this._audioMode = AudioManagement.AudioMode.NORMAL;
6904
6978
  this._preloadedSounds = {};
6905
6979
  this._htmlAudioCache = {};
6906
- this.onStart = new Subject();
6907
6980
  this.start();
6908
6981
  }
6909
- ready() {
6910
- return __awaiter(this, void 0, void 0, function* () {
6911
- if (this._started)
6912
- return Promise.resolve();
6913
- yield this.start();
6914
- });
6915
- }
6916
6982
  playBeepConfirm() {
6917
6983
  return this.play('beep-confirm', {
6918
6984
  // Vibrate only if in vibration mode
@@ -6954,7 +7020,7 @@ class AudioProvider {
6954
7020
  play(id, opts) {
6955
7021
  return __awaiter(this, void 0, void 0, function* () {
6956
7022
  // Make sure provider is ready
6957
- if (!this._started)
7023
+ if (!this.started)
6958
7024
  yield this.ready();
6959
7025
  const sound = this._preloadedSounds[id];
6960
7026
  if (!sound) {
@@ -7015,7 +7081,7 @@ class AudioProvider {
7015
7081
  }
7016
7082
  vibrate(timeInMs) {
7017
7083
  return __awaiter(this, void 0, void 0, function* () {
7018
- if (!this._started)
7084
+ if (!this.started)
7019
7085
  yield this.ready();
7020
7086
  if (!this.vibration)
7021
7087
  return; // Skip if vibrate plugin
@@ -7023,45 +7089,24 @@ class AudioProvider {
7023
7089
  });
7024
7090
  }
7025
7091
  /* -- protected methods -- */
7026
- start() {
7027
- if (this._startPromise)
7028
- return this._startPromise;
7029
- if (this._started)
7030
- return Promise.resolve();
7031
- let cordova;
7032
- this._startPromise = this.platform.ready()
7033
- .then(() => __awaiter(this, void 0, void 0, function* () {
7034
- cordova = this.platform.is('cordova');
7092
+ ngOnStart() {
7093
+ return __awaiter(this, void 0, void 0, function* () {
7094
+ let cordova = this.platform.is('cordova');
7035
7095
  this._audioType = cordova && this.nativeAudio ? 'native' : 'html5';
7036
7096
  console.info(`[audio] Starting audio provider {${this._audioType}}...`);
7037
7097
  // Listen audio mode changed
7038
7098
  if (cordova && this.audioManagement) {
7039
7099
  yield this.readAudioMode();
7040
7100
  }
7041
- }))
7042
7101
  // Pre-loading system sounds
7043
- .then(() => {
7044
7102
  console.debug('[audio] Preloading audio sounds...');
7045
- return Promise.all(SYSTEM_SOUNDS.map(s => {
7103
+ yield Promise.all(SYSTEM_SOUNDS.map(s => {
7046
7104
  // Disable vibration is cordova not enabled
7047
7105
  if (!cordova)
7048
7106
  s.vibration = undefined;
7049
7107
  return this.preload(s);
7050
7108
  }));
7051
- })
7052
- .then(() => {
7053
- this._started = true;
7054
- this._startPromise = null;
7055
- console.info('[audio] Audio provider started');
7056
- // Emit event
7057
- this.onStart.next();
7058
- })
7059
- .catch(err => {
7060
- console.error('[audio] Unable to start audio provider: ' + (err && err.message || err), err);
7061
- this._started = false;
7062
- this._startPromise = null;
7063
7109
  });
7064
- return this._startPromise;
7065
7110
  }
7066
7111
  readAudioMode() {
7067
7112
  return __awaiter(this, void 0, void 0, function* () {
@@ -7980,6 +8025,10 @@ const environment = Object.freeze({
7980
8025
  {
7981
8026
  host: 'sih.sfa.sc',
7982
8027
  port: 80
8028
+ },
8029
+ {
8030
+ host: 'test.sumaris.net',
8031
+ port: 443
7983
8032
  }
7984
8033
  ],
7985
8034
  defaultAppName: 'SUMARiS',
@@ -8119,6 +8168,19 @@ let Peer = Peer_1 = class Peer extends Entity {
8119
8168
  path: noTrailingSlash(url.pathname)
8120
8169
  });
8121
8170
  }
8171
+ static path(peer, ...paths) {
8172
+ if (!peer)
8173
+ throw new Error('Missing required argument \'peer\'!');
8174
+ // Remove starting slashes
8175
+ paths = (paths || []).map(path => {
8176
+ if (path.startsWith('./'))
8177
+ return path.substring(2);
8178
+ if (path.startsWith('/'))
8179
+ return path.substring(1);
8180
+ return path;
8181
+ }).filter(isNotNilOrBlank);
8182
+ return [noTrailingSlash(Peer_1.fromObject(peer).url)].concat(...paths).join('/');
8183
+ }
8122
8184
  asObject(options) {
8123
8185
  return super.asObject(options);
8124
8186
  }
@@ -8508,14 +8570,14 @@ const DEFAULT_SETTINGS = {
8508
8570
  };
8509
8571
  const APP_LOCAL_SETTINGS = new InjectionToken('DefaultLocalSettings');
8510
8572
  const APP_LOCAL_SETTINGS_OPTIONS = new InjectionToken('LocalSettingsOptions');
8511
- class LocalSettingsService {
8573
+ class LocalSettingsService extends StartableService {
8512
8574
  constructor(translate, platform, storage, environment, defaultSettings, defaultOptionsMap) {
8575
+ super(platform);
8513
8576
  this.translate = translate;
8514
8577
  this.platform = platform;
8515
8578
  this.storage = storage;
8516
8579
  this.environment = environment;
8517
8580
  this.defaultSettings = defaultSettings;
8518
- this._started = false;
8519
8581
  this.onChange = new Subject();
8520
8582
  this.defaultSettings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), this.defaultSettings);
8521
8583
  this._optionDefs = Object.values(defaultOptionsMap);
@@ -8525,60 +8587,40 @@ class LocalSettingsService {
8525
8587
  console.debug('[settings] Creating service');
8526
8588
  }
8527
8589
  get settings() {
8528
- return this.data || this.defaultSettings;
8590
+ return this._data || this.defaultSettings;
8529
8591
  }
8530
8592
  get locale() {
8531
- return this.data && this.data.locale || this.translate.currentLang || this.translate.defaultLang;
8593
+ return this._data && this._data.locale || this.translate.currentLang || this.translate.defaultLang;
8532
8594
  }
8533
8595
  get latLongFormat() {
8534
- return this.data && this.data.latLongFormat || 'DDMM';
8596
+ return this._data && this._data.latLongFormat || 'DDMM';
8535
8597
  }
8536
8598
  get usageMode() {
8537
- return (this.data && this.data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8599
+ return (this._data && this._data.usageMode || (this.mobile ? 'FIELD' : 'DESK'));
8538
8600
  }
8539
8601
  get mobile() {
8540
- return this.data && toBoolean(this.data.mobile, this.platform.is('mobile'));
8602
+ return this._data && toBoolean(this._data.mobile, this.platform.is('mobile'));
8541
8603
  }
8542
8604
  set mobile(value) {
8543
- this.data.mobile = value;
8605
+ this._data.mobile = value;
8544
8606
  }
8545
8607
  get touchUi() {
8546
- return this.data.touchUi;
8608
+ return this._data.touchUi;
8547
8609
  }
8548
8610
  set touchUi(value) {
8549
- this.data.touchUi = value;
8611
+ this._data.touchUi = value;
8550
8612
  }
8551
8613
  get pageHistory() {
8552
- return (this.data && this.data.pageHistory || []);
8614
+ return (this._data && this._data.pageHistory || []);
8553
8615
  }
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...');
8616
+ ngOnStart() {
8617
+ console.info('[settings] Starting service...');
8560
8618
  // 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();
8619
+ this._data.mobile = isNotNil(this._data.mobile) ? this._data.mobile : this.platform.is('mobile');
8620
+ this._data.touchUi = this._data.mobile || this.platform.is('phablet') || this.platform.is('tablet');
8621
+ this._data.usageMode = this.platform.is('android') ? 'FIELD' : 'DESK'; // FIELD by default if Android
8622
+ // Restoring local settings
8623
+ return this.restoreLocally();
8582
8624
  }
8583
8625
  isUsageMode(mode) {
8584
8626
  return this.usageMode === mode;
@@ -8588,6 +8630,7 @@ class LocalSettingsService {
8588
8630
  }
8589
8631
  restoreLocally() {
8590
8632
  return __awaiter(this, void 0, void 0, function* () {
8633
+ let data = this._data || {};
8591
8634
  // Restore from storage
8592
8635
  const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
8593
8636
  // Restore local settings (or keep old settings)
@@ -8598,28 +8641,29 @@ class LocalSettingsService {
8598
8641
  SETTINGS_TRANSIENT_PROPERTIES.forEach(transientKey => {
8599
8642
  delete restoredData[transientKey];
8600
8643
  });
8601
- this.data = Object.assign(this.data, restoredData);
8644
+ // Merge into existing data
8645
+ data = Object.assign(data, restoredData);
8602
8646
  }
8603
8647
  // Emit event
8604
- this.onChange.next(this.data);
8605
- return this.data;
8648
+ this.onChange.next(data);
8649
+ return data;
8606
8650
  });
8607
8651
  }
8608
8652
  setProperty(keyOrDef, value) {
8609
- if (!this.data)
8653
+ if (!this._data)
8610
8654
  return;
8611
8655
  if (typeof keyOrDef === 'object') {
8612
8656
  this.setProperty(keyOrDef.key, value);
8613
8657
  return;
8614
8658
  }
8615
- this.data.properties = this.data.properties || {};
8616
- this.data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8659
+ this._data.properties = this._data.properties || {};
8660
+ this._data.properties[keyOrDef] = isNil(value) ? undefined : value.toString();
8617
8661
  }
8618
8662
  getProperty(keyOrDef, defaultValue) {
8619
8663
  if (typeof keyOrDef === 'object') {
8620
8664
  return this.getProperty(keyOrDef.key, isNil(defaultValue) ? keyOrDef.defaultValue : defaultValue);
8621
8665
  }
8622
- const value = this.data && this.data.properties && this.data.properties[keyOrDef];
8666
+ const value = this._data && this._data.properties && this._data.properties[keyOrDef];
8623
8667
  return isNotNil(value) ? value : defaultValue;
8624
8668
  }
8625
8669
  getPropertyAsBoolean(definition, defaultValue) {
@@ -8642,7 +8686,9 @@ class LocalSettingsService {
8642
8686
  }
8643
8687
  apply(settings, opts) {
8644
8688
  return __awaiter(this, void 0, void 0, function* () {
8645
- this.data = Object.assign(Object.assign({}, this.data), settings);
8689
+ if (!this.started)
8690
+ yield this.ready();
8691
+ this._data = Object.assign(Object.assign({}, this._data), settings);
8646
8692
  // Save locally
8647
8693
  if (opts && opts.persistImmediate) {
8648
8694
  yield this.persistLocally(true);
@@ -8652,7 +8698,7 @@ class LocalSettingsService {
8652
8698
  }
8653
8699
  // Emit event
8654
8700
  if (!opts || opts.emitEvent !== false) {
8655
- this.onChange.next(this.data);
8701
+ this.onChange.next(this._data);
8656
8702
  }
8657
8703
  });
8658
8704
  }
@@ -8664,38 +8710,38 @@ class LocalSettingsService {
8664
8710
  });
8665
8711
  }
8666
8712
  getPageSettings(pageId, propertyName) {
8667
- if (!this.data || !this.data.pages)
8713
+ if (!this._data || !this._data.pages)
8668
8714
  return undefined;
8669
8715
  const key = pageId.replace(/[/]/g, '__');
8670
8716
  if (isNotNilOrBlank(propertyName)) {
8671
- return getPropertyByPath(this.data.pages, key + '.' + propertyName);
8717
+ return getPropertyByPath(this._data.pages, key + '.' + propertyName);
8672
8718
  }
8673
- return this.data.pages[key];
8719
+ return this._data.pages[key];
8674
8720
  }
8675
8721
  savePageSetting(pageId, value, propertyName) {
8676
8722
  return __awaiter(this, void 0, void 0, function* () {
8677
- this.data = this.data || this.defaultSettings;
8678
- this.data.pages = this.data.pages || {};
8723
+ this._data = this._data || this.defaultSettings;
8724
+ this._data.pages = this._data.pages || {};
8679
8725
  const key = pageId.replace(/[/]/g, '__');
8680
8726
  if (propertyName) {
8681
- this.data.pages[key] = this.data.pages[key] || {};
8682
- this.data.pages[key][propertyName] = value;
8727
+ this._data.pages[key] = this._data.pages[key] || {};
8728
+ this._data.pages[key][propertyName] = value;
8683
8729
  }
8684
8730
  else {
8685
- this.data.pages[key] = value;
8731
+ this._data.pages[key] = value;
8686
8732
  }
8687
8733
  // Update local settings
8688
8734
  this.persistLocally();
8689
8735
  });
8690
8736
  }
8691
8737
  getOfflineFeature(featureName) {
8692
- if (!this.data || !this.data.offlineFeatures || isEmptyArray(this.data.offlineFeatures))
8738
+ if (!this._data || !this._data.offlineFeatures || isEmptyArray(this._data.offlineFeatures))
8693
8739
  return undefined;
8694
8740
  if (!featureName)
8695
8741
  throw Error('Missing \'featureName\' argument');
8696
8742
  featureName = featureName.toLowerCase();
8697
8743
  const featurePrefix = featureName + '#';
8698
- const feature = this.data.offlineFeatures.find(f => {
8744
+ const feature = this._data.offlineFeatures.find(f => {
8699
8745
  if (typeof f === 'string')
8700
8746
  return f.toLowerCase().startsWith(featurePrefix);
8701
8747
  if (typeof f === 'object' && f.name)
@@ -8718,24 +8764,24 @@ class LocalSettingsService {
8718
8764
  hasOfflineFeature(featureName) {
8719
8765
  if (featureName)
8720
8766
  return isNotNil(this.getOfflineFeature(featureName));
8721
- return this.data && isNotEmptyArray(this.data.offlineFeatures);
8767
+ return this._data && isNotEmptyArray(this._data.offlineFeatures);
8722
8768
  }
8723
8769
  saveOfflineFeature(feature) {
8724
- this.data = this.data || this.defaultSettings;
8725
- this.data.offlineFeatures = this.data.offlineFeatures || [];
8770
+ this._data = this._data || this.defaultSettings;
8771
+ this._data.offlineFeatures = this._data.offlineFeatures || [];
8726
8772
  feature.name = feature.name.toLowerCase();
8727
8773
  const featurePrefix = feature.name + '#';
8728
- const existingIndex = this.data.offlineFeatures.findIndex(f => {
8774
+ const existingIndex = this._data.offlineFeatures.findIndex(f => {
8729
8775
  if (typeof f === 'string')
8730
8776
  return f.toLowerCase().startsWith(featurePrefix);
8731
8777
  if (typeof f === 'object' && f.name)
8732
8778
  return f.name === feature.name;
8733
8779
  });
8734
8780
  if (existingIndex !== -1) {
8735
- this.data.offlineFeatures[existingIndex] = feature;
8781
+ this._data.offlineFeatures[existingIndex] = feature;
8736
8782
  }
8737
8783
  else {
8738
- this.data.offlineFeatures.push(feature);
8784
+ this._data.offlineFeatures.push(feature);
8739
8785
  }
8740
8786
  // Update local settings
8741
8787
  this.persistLocally();
@@ -8754,14 +8800,14 @@ class LocalSettingsService {
8754
8800
  this.saveOfflineFeature(feature);
8755
8801
  }
8756
8802
  removeOfflineFeatures() {
8757
- if (this.data && this.data.offlineFeatures) {
8758
- this.data.offlineFeatures = [];
8803
+ if (this._data && this._data.offlineFeatures) {
8804
+ this._data.offlineFeatures = [];
8759
8805
  // Update local settings
8760
8806
  this.persistLocally();
8761
8807
  }
8762
8808
  }
8763
8809
  getFieldDisplayAttributes(fieldName, defaultAttributes) {
8764
- const value = this.data && this.data.properties && this.data.properties[`sumaris.field.${fieldName}.attributes`];
8810
+ const value = this._data && this._data.properties && this._data.properties[`sumaris.field.${fieldName}.attributes`];
8765
8811
  // Nothing found in settings: return defaults
8766
8812
  if (!value)
8767
8813
  return defaultAttributes || ['label', 'name'];
@@ -8790,7 +8836,7 @@ class LocalSettingsService {
8790
8836
  // If not inside recursive call: fill page history defaults
8791
8837
  if (!pageHistory)
8792
8838
  this.fillPageHistoryDefaults(page, opts);
8793
- pageHistory = pageHistory || this.data.pageHistory;
8839
+ pageHistory = pageHistory || this._data.pageHistory;
8794
8840
  const index = pageHistory.findIndex(p => (
8795
8841
  // same path
8796
8842
  p.path === page.path
@@ -8826,10 +8872,10 @@ class LocalSettingsService {
8826
8872
  }
8827
8873
  }
8828
8874
  // Save locally (only if not a recursive execution)
8829
- if (pageHistory === this.data.pageHistory) {
8875
+ if (pageHistory === this._data.pageHistory) {
8830
8876
  // 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);
8877
+ if (this._data.pageHistory.length > this._data.pageHistoryMaxSize) {
8878
+ const removedPages = pageHistory.splice(this._data.pageHistoryMaxSize, pageHistory.length - this._data.pageHistoryMaxSize);
8833
8879
  console.debug('[settings] Pages removed from history: ', removedPages);
8834
8880
  }
8835
8881
  // Apply new value
@@ -8840,7 +8886,7 @@ class LocalSettingsService {
8840
8886
  removePageHistory(path, opts, pageHistory // used for recursive call to children)
8841
8887
  ) {
8842
8888
  return __awaiter(this, void 0, void 0, function* () {
8843
- pageHistory = pageHistory || this.data.pageHistory;
8889
+ pageHistory = pageHistory || this._data.pageHistory;
8844
8890
  const index = pageHistory.findIndex(p => p.path === path);
8845
8891
  let found = index !== -1;
8846
8892
  if (found) {
@@ -8856,9 +8902,9 @@ class LocalSettingsService {
8856
8902
  .findIndex(children => this.removePageHistory(path, opts, children)) !== -1;
8857
8903
  }
8858
8904
  // Save locally (only if not a recursive execution)
8859
- if (found && pageHistory === this.data.pageHistory) {
8905
+ if (found && pageHistory === this._data.pageHistory) {
8860
8906
  // Apply changes
8861
- yield this.applyProperty('pageHistory', this.data.pageHistory);
8907
+ yield this.applyProperty('pageHistory', this._data.pageHistory);
8862
8908
  }
8863
8909
  return found;
8864
8910
  });
@@ -8871,26 +8917,26 @@ class LocalSettingsService {
8871
8917
  }
8872
8918
  /* -- Protected methods -- */
8873
8919
  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 = [];
8920
+ this._data = Object.assign(Object.assign({}, this._data), this.defaultSettings);
8921
+ this._data.locale = this.translate.currentLang || this.translate.defaultLang;
8922
+ this._data.mobile = undefined;
8923
+ this._data.usageMode = undefined;
8924
+ this._data.pageHistory = [];
8879
8925
  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);
8926
+ this._data.peerUrl = defaultPeer && defaultPeer.url || undefined;
8927
+ if (this.started)
8928
+ this.onChange.next(this._data);
8883
8929
  }
8884
8930
  persistLocally(immediate) {
8885
8931
  // Execute immediate
8886
8932
  if (immediate) {
8887
- if (!this.data) {
8933
+ if (!this._data) {
8888
8934
  console.debug('[settings] Removing local settings from storage');
8889
8935
  return this.storage.remove(SETTINGS_STORAGE_KEY);
8890
8936
  }
8891
8937
  else {
8892
- console.debug('[settings] Store local settings', this.data);
8893
- return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this.data));
8938
+ console.debug('[settings] Store local settings', this._data);
8939
+ return this.storage.set(SETTINGS_STORAGE_KEY, JSON.stringify(this._data));
8894
8940
  }
8895
8941
  }
8896
8942
  // Execute with delay
@@ -8900,7 +8946,7 @@ class LocalSettingsService {
8900
8946
  this._$persist = new EventEmitter(true);
8901
8947
  this._$persist
8902
8948
  .pipe(debounceTime(2000), // add a delay of 2s
8903
- filter(() => this._started))
8949
+ filter(() => this.started))
8904
8950
  .subscribe(() => this.persistLocally(true));
8905
8951
  }
8906
8952
  this._$persist.emit();
@@ -9086,8 +9132,9 @@ const NetworkRefreshTimerPeriod = {
9086
9132
  MOBILE: 1000,
9087
9133
  DESKTOP: 1000
9088
9134
  }*/
9089
- class NetworkService {
9135
+ class NetworkService extends StartableService {
9090
9136
  constructor(_document, platform, modalCtrl, cryptoService, storage, settings, cache, http, environment, network, splashScreen, translate, toastController) {
9137
+ super(platform);
9091
9138
  this._document = _document;
9092
9139
  this.platform = platform;
9093
9140
  this.modalCtrl = modalCtrl;
@@ -9101,13 +9148,11 @@ class NetworkService {
9101
9148
  this.splashScreen = splashScreen;
9102
9149
  this.translate = translate;
9103
9150
  this.toastController = toastController;
9104
- this._started = false;
9105
- this._subscription = new Subscription();
9106
- this._listeners = {};
9107
- this.onStart = new Subject();
9108
9151
  this.onPeerChanges = this.onStart.pipe(map(peer => peer && peer.url), filter(isNotNilOrBlank), distinctUntilChanged());
9109
9152
  this.onNetworkStatusChanges = new BehaviorSubject(null);
9110
9153
  this.onResetNetworkCache = new EventEmitter(true);
9154
+ this._subscription = new Subscription();
9155
+ this._listeners = {};
9111
9156
  this._mobile = this.platform.is('mobile');
9112
9157
  if (this._mobile) {
9113
9158
  this._timerRefreshPeriod = NetworkRefreshTimerPeriod.MOBILE;
@@ -9118,6 +9163,7 @@ class NetworkService {
9118
9163
  this._timerRefreshCondition = () => true; // Always check
9119
9164
  }
9120
9165
  this.resetData();
9166
+ this.onStart.subscribe(() => this.ngOnAfterStart());
9121
9167
  // For DEV only
9122
9168
  this._debug = !environment.production;
9123
9169
  }
@@ -9131,16 +9177,14 @@ class NetworkService {
9131
9177
  // If force offline: return 'none'
9132
9178
  return this._forceOffline && 'none'
9133
9179
  // Else, return device connection type (or unknown)
9134
- || (this._started && this._deviceConnectionType || 'unknown');
9180
+ || (this.started && this._deviceConnectionType || 'unknown');
9135
9181
  }
9136
9182
  get peer() {
9137
- return this._peer && this._peer.clone();
9183
+ return this._data && this._data.clone();
9138
9184
  }
9139
9185
  set peer(peer) {
9140
- this.restart(peer);
9141
- }
9142
- get started() {
9143
- return this._started;
9186
+ this._startingPeer = peer;
9187
+ this.restart();
9144
9188
  }
9145
9189
  /**
9146
9190
  * Register to network event
@@ -9162,78 +9206,6 @@ class NetworkService {
9162
9206
  return this.addListener(eventType, callback);
9163
9207
  }
9164
9208
  }
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
9209
  tryOnline(opts) {
9238
9210
  return __awaiter(this, void 0, void 0, function* () {
9239
9211
  // If offline mode not forced, and device says there is no connection: skip
@@ -9260,7 +9232,8 @@ class NetworkService {
9260
9232
  // Disable the offline mode
9261
9233
  this.setForceOffline(false);
9262
9234
  // Restart
9263
- yield this.restart(peer);
9235
+ this._startingPeer = peer;
9236
+ yield this.restart();
9264
9237
  // Wait a promise, before recheck
9265
9238
  yield this.emit('beforeTryOnlineFinish', this.online);
9266
9239
  }
@@ -9288,7 +9261,7 @@ class NetworkService {
9288
9261
  // Display toast (without await, because not need to wait toast close event)
9289
9262
  return this.showOfflineToast({ showRetryButton: false });
9290
9263
  }
9291
- return this._started && online;
9264
+ return this.started && online;
9292
9265
  });
9293
9266
  }
9294
9267
  showOfflineToast(opts) {
@@ -9336,86 +9309,6 @@ class NetworkService {
9336
9309
  return false;
9337
9310
  });
9338
9311
  }
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
9312
  /**
9420
9313
  * Check if the peer is alive
9421
9314
  *
@@ -9531,6 +9424,123 @@ class NetworkService {
9531
9424
  });
9532
9425
  });
9533
9426
  }
9427
+ /* -- protected functions -- */
9428
+ ngOnStart() {
9429
+ return __awaiter(this, void 0, void 0, function* () {
9430
+ console.info('[network] Starting network...', this._startingPeer);
9431
+ // Restoring local settings
9432
+ let peer = this._startingPeer || (yield this.restoreLocally());
9433
+ // Make sure to hide the splashscreen, before open the modal
9434
+ if (!peer && this.splashScreen)
9435
+ this.splashScreen.hide();
9436
+ // No peer in settings: ask user to choose
9437
+ while (!peer) {
9438
+ console.debug('[network] No peer defined. Asking user to choose a peer.');
9439
+ peer = yield this.showSelectPeerModal({ allowSelectDownPeer: false });
9440
+ }
9441
+ console.info(`[network] Starting service [OK] {peer: '${peer.url}', online: ${this.online}}`);
9442
+ return peer;
9443
+ });
9444
+ }
9445
+ ngOnAfterStart() {
9446
+ var _a;
9447
+ return __awaiter(this, void 0, void 0, function* () {
9448
+ // Wait settings starts, then save peer in settings
9449
+ yield this.settings.apply({ peerUrl: this._data.url });
9450
+ this.onDeviceConnectionChanged(((_a = this.network) === null || _a === void 0 ? void 0 : _a.type) || 'unknown');
9451
+ // Start the refresh timer
9452
+ this.startRefreshTimer();
9453
+ // Listen for device network changes
9454
+ if (this.network) {
9455
+ this._subscription.add(this.network.onDisconnect().subscribe(() => this.onDeviceConnectionChanged('none')));
9456
+ this._subscription.add(this.network.onConnect().subscribe(() => this.onDeviceConnectionChanged(this.network.type)));
9457
+ }
9458
+ });
9459
+ }
9460
+ ngOnStop() {
9461
+ return __awaiter(this, void 0, void 0, function* () {
9462
+ this.resetData();
9463
+ // Stop timer if cannot refresh anymore
9464
+ if (this._timerRefreshCondition() === false) {
9465
+ this.stopRefreshTimer();
9466
+ }
9467
+ this._subscription.unsubscribe();
9468
+ this._subscription = new Subscription();
9469
+ });
9470
+ }
9471
+ /**
9472
+ * Try to restore peer from the local storage
9473
+ */
9474
+ restoreLocally() {
9475
+ return __awaiter(this, void 0, void 0, function* () {
9476
+ // Restore from storage
9477
+ const settingsStr = yield this.storage.get(SETTINGS_STORAGE_KEY);
9478
+ const settings = settingsStr && JSON.parse(settingsStr) || undefined;
9479
+ if (settings && settings.peerUrl) {
9480
+ console.debug(`[network] Use peer {${settings.peerUrl}} (found in the local storage)`);
9481
+ return Peer.parseUrl(settings.peerUrl);
9482
+ }
9483
+ // Else, use default peer in env, if exists
9484
+ if (this.environment.defaultPeer) {
9485
+ return Peer.fromObject(this.environment.defaultPeer);
9486
+ }
9487
+ // Else, if App is hosted, try the web site as a peer
9488
+ const location = this._document && this._document.location;
9489
+ if (location && location.protocol && location.protocol.startsWith('http')) {
9490
+ const hostname = this._document.location.host;
9491
+ const detectedPeer = Peer.parseUrl(`${this._document.location.protocol}${hostname}${this.environment.baseUrl}`);
9492
+ if (yield this.checkPeerAlive(detectedPeer)) {
9493
+ return detectedPeer;
9494
+ }
9495
+ }
9496
+ return undefined;
9497
+ });
9498
+ }
9499
+ /**
9500
+ * Stop to network state
9501
+ *
9502
+ * @protected
9503
+ */
9504
+ stopRefreshTimer() {
9505
+ if (this._timerSubscription) {
9506
+ this._timerSubscription.unsubscribe();
9507
+ this._timerSubscription = undefined;
9508
+ }
9509
+ }
9510
+ /**
9511
+ * Refresh the network state
9512
+ *
9513
+ * @protected
9514
+ */
9515
+ startRefreshTimer() {
9516
+ if (this._timerSubscription)
9517
+ return; // Already running: skip
9518
+ console.info(`[network] Starting refresh timer, every ${this._timerRefreshPeriod}ms...`);
9519
+ let lastInfo;
9520
+ this._timerSubscription = timer(this._timerRefreshPeriod, this._timerRefreshPeriod)
9521
+ .pipe(
9522
+ // Skip some timer event (see constructor)
9523
+ filter(this._timerRefreshCondition),
9524
+ // Checkin if peer alive
9525
+ tap(() => console.debug('[network] Checking connection to pod...')), mergeMap(() => this.checkPeerAlive(this.peer)),
9526
+ // Filter to keep only changes
9527
+ filter(info => !!info !== !!lastInfo), tap(info => lastInfo = info),
9528
+ // Check compatibility
9529
+ mergeMap((info) => this.checkPeerCompatible(info, { showToast: true })))
9530
+ .subscribe(alive => {
9531
+ if (alive && this.offline) {
9532
+ this.setForceOffline(false);
9533
+ // Restart the service (to force re auth)
9534
+ this.restart();
9535
+ }
9536
+ else if (!alive && this.online) {
9537
+ this.setForceOffline(true);
9538
+ // Stop the service
9539
+ this.stop();
9540
+ }
9541
+ });
9542
+ this._timerSubscription.add(() => console.debug('[network] Refresh timer stopped'));
9543
+ }
9534
9544
  get(path, opts) {
9535
9545
  return __awaiter(this, void 0, void 0, function* () {
9536
9546
  let uri = path;
@@ -9582,7 +9592,7 @@ class NetworkService {
9582
9592
  }
9583
9593
  }
9584
9594
  resetData() {
9585
- this._peer = null;
9595
+ this._data = null;
9586
9596
  }
9587
9597
  /**
9588
9598
  * Get default peers, from environment
@@ -9599,7 +9609,7 @@ class NetworkService {
9599
9609
  addListener(name, callback) {
9600
9610
  this._listeners[name] = this._listeners[name] || [];
9601
9611
  this._listeners[name].push(callback);
9602
- // When unsubcribe, remove from the listener
9612
+ // When unsubscribe, remove from the listener
9603
9613
  return new Subscription(() => {
9604
9614
  const index = this._listeners[name].indexOf(callback);
9605
9615
  if (index !== -1) {
@@ -10142,31 +10152,32 @@ class EntityStore {
10142
10152
  }
10143
10153
 
10144
10154
  const APP_LOCAL_STORAGE_TYPE_POLICIES = new InjectionToken('localStorageTypePolicies');
10145
- class EntitiesStorage {
10155
+ ;
10156
+ class EntitiesStorage extends StartableService {
10146
10157
  constructor(platform, progressBarService, storage, environment, typePolicies) {
10158
+ super(platform);
10147
10159
  this.platform = platform;
10148
10160
  this.progressBarService = progressBarService;
10149
10161
  this.storage = storage;
10150
10162
  this.environment = environment;
10151
- this._started = false;
10152
10163
  this._subscription = new Subscription();
10153
- this._stores = {};
10154
10164
  this._$save = new EventEmitter(true);
10155
10165
  this._dirty = false;
10156
10166
  this._saving = false;
10157
- this.onStart = new Subject();
10158
10167
  this._typePolicies = typePolicies || {};
10168
+ this._saveTimerPeriod = environment.storageSavePeriodMs || 10000 /* = 10s */;
10169
+ this._data = {};
10159
10170
  // For DEV only
10160
10171
  this._debug = !environment.production;
10161
10172
  if (this._debug)
10162
10173
  console.debug('[entities-storage] Creating service');
10163
10174
  }
10164
10175
  get dirty() {
10165
- return this._dirty || Object.entries(this._stores).find(([_, store]) => store.dirty) !== undefined;
10176
+ return this._dirty || Object.values(this._data).some(store => store.dirty);
10166
10177
  }
10167
10178
  watchAll(entityName, variables, opts) {
10168
10179
  // Make sure store is ready
10169
- if (!this._started) {
10180
+ if (!this.started) {
10170
10181
  return defer(() => this.ready())
10171
10182
  .pipe(switchMap(() => this.watchAll(entityName, variables, opts))); // Loop
10172
10183
  }
@@ -10180,7 +10191,7 @@ class EntitiesStorage {
10180
10191
  loadAll(entityName, variables, opts) {
10181
10192
  return __awaiter(this, void 0, void 0, function* () {
10182
10193
  // Make sure store is ready
10183
- if (!this._started)
10194
+ if (!this.started)
10184
10195
  yield this.ready();
10185
10196
  try {
10186
10197
  this.progressBarService.increase();
@@ -10243,7 +10254,8 @@ class EntitiesStorage {
10243
10254
  return __awaiter(this, void 0, void 0, function* () {
10244
10255
  if (!entity)
10245
10256
  return; // skip
10246
- yield this.ready();
10257
+ if (!this.started)
10258
+ yield this.ready();
10247
10259
  try {
10248
10260
  this.progressBarService.increase();
10249
10261
  this._dirty = true;
@@ -10263,7 +10275,8 @@ class EntitiesStorage {
10263
10275
  return __awaiter(this, void 0, void 0, function* () {
10264
10276
  if (isEmptyArray(entities) && (!opts || opts.reset !== true))
10265
10277
  return entities; // Skip (nothing to save)
10266
- yield this.ready();
10278
+ if (!this.started)
10279
+ yield this.ready();
10267
10280
  try {
10268
10281
  this.progressBarService.increase();
10269
10282
  this._dirty = true;
@@ -10279,13 +10292,15 @@ class EntitiesStorage {
10279
10292
  return __awaiter(this, void 0, void 0, function* () {
10280
10293
  if (!entity)
10281
10294
  return undefined; // skip
10282
- yield this.ready();
10295
+ if (!this.started)
10296
+ yield this.ready();
10283
10297
  return this.deleteById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10284
10298
  });
10285
10299
  }
10286
10300
  deleteById(id, opts) {
10287
10301
  return __awaiter(this, void 0, void 0, function* () {
10288
- yield this.ready();
10302
+ if (!this.started)
10303
+ yield this.ready();
10289
10304
  if (!opts || isNilOrBlank(opts.entityName))
10290
10305
  throw new Error('Missing argument \'opts\' or \'entityName\'');
10291
10306
  //if (id >= 0) throw new Error('Invalid id a local entity (not a negative number): ' + id);
@@ -10311,7 +10326,8 @@ class EntitiesStorage {
10311
10326
  }
10312
10327
  deleteMany(ids, opts) {
10313
10328
  return __awaiter(this, void 0, void 0, function* () {
10314
- yield this.ready();
10329
+ if (!this.started)
10330
+ yield this.ready();
10315
10331
  if (!opts || isNilOrBlank(opts.entityName))
10316
10332
  throw new Error('Missing argument \'opts\' or \'opts.entityName\'');
10317
10333
  try {
@@ -10339,7 +10355,8 @@ class EntitiesStorage {
10339
10355
  return __awaiter(this, void 0, void 0, function* () {
10340
10356
  if (!entity)
10341
10357
  return undefined; // skip
10342
- yield this.ready();
10358
+ if (!this.started)
10359
+ yield this.ready();
10343
10360
  return this.deleteFromTrashById(entity.id, Object.assign(Object.assign({}, opts), { entityName: opts && opts.entityName || this.detectEntityName(entity) }));
10344
10361
  });
10345
10362
  }
@@ -10355,7 +10372,8 @@ class EntitiesStorage {
10355
10372
  return __awaiter(this, void 0, void 0, function* () {
10356
10373
  if (!entity)
10357
10374
  return undefined; // skip
10358
- yield this.ready();
10375
+ if (!this.started)
10376
+ yield this.ready();
10359
10377
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10360
10378
  // Delete entity by id, if exists
10361
10379
  const entityStore = this.getEntityStore(entityName);
@@ -10372,7 +10390,8 @@ class EntitiesStorage {
10372
10390
  }
10373
10391
  moveManyToTrash(ids, opts) {
10374
10392
  return __awaiter(this, void 0, void 0, function* () {
10375
- yield this.ready();
10393
+ if (!this.started)
10394
+ yield this.ready();
10376
10395
  if (!opts || isNilOrBlank(opts.entityName))
10377
10396
  throw new Error('Missing argument \'opts.entityName\'');
10378
10397
  const entityStore = this.getEntityStore(opts.entityName, { create: false });
@@ -10399,7 +10418,8 @@ class EntitiesStorage {
10399
10418
  return __awaiter(this, void 0, void 0, function* () {
10400
10419
  if (!entity)
10401
10420
  return undefined; // skip
10402
- yield this.ready();
10421
+ if (!this.started)
10422
+ yield this.ready();
10403
10423
  const entityName = opts && opts.entityName || this.detectEntityName(entity);
10404
10424
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10405
10425
  this.getEntityStore(trashName).save(entity, opts);
@@ -10409,7 +10429,8 @@ class EntitiesStorage {
10409
10429
  }
10410
10430
  clearTrash(entityName) {
10411
10431
  return __awaiter(this, void 0, void 0, function* () {
10412
- yield this.ready();
10432
+ if (!this.started)
10433
+ yield this.ready();
10413
10434
  const trashName = EntitiesStorage.TRASH_PREFIX + entityName;
10414
10435
  const entityStore = this.getEntityStore(trashName, { create: false });
10415
10436
  if (!entityStore)
@@ -10420,41 +10441,29 @@ class EntitiesStorage {
10420
10441
  });
10421
10442
  }
10422
10443
  persist() {
10423
- if (this._dirty) {
10424
- return this.storeLocally();
10425
- }
10426
- return Promise.resolve();
10427
- }
10428
- ready() {
10429
- if (this._started)
10430
- return Promise.resolve();
10431
- return this.start();
10444
+ if (this._dirty) {
10445
+ return this.storeLocally();
10446
+ }
10447
+ return Promise.resolve();
10432
10448
  }
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(() => {
10449
+ ngOnStart() {
10450
+ return __awaiter(this, void 0, void 0, function* () {
10451
+ const now = Date.now();
10452
+ console.info(`[entities-storage] Starting entity storage...`);
10453
+ // Restore sequences
10454
+ yield this.restoreLocally();
10443
10455
  // Start a save timer
10444
- this._subscription.add(merge(this._$save, timer(2000, 10000))
10445
- .pipe(throttleTime(10000))
10456
+ this._subscription.add(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
10457
+ .pipe(
10458
+ // Avoid to many call (e.g. when $save AND timer are triggered
10459
+ throttleTime(this._saveTimerPeriod))
10446
10460
  .subscribe(() => this.storeLocally()));
10447
- this._started = true;
10448
- this._startPromise = undefined;
10449
10461
  console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
10450
- // Emit event
10451
- this.onStart.next();
10462
+ return this._data;
10452
10463
  });
10453
- return this._startPromise;
10454
10464
  }
10455
- stop() {
10465
+ ngOnStop() {
10456
10466
  return __awaiter(this, void 0, void 0, function* () {
10457
- this._started = false;
10458
10467
  this._subscription.unsubscribe();
10459
10468
  this._subscription = new Subscription();
10460
10469
  if (this.dirty) {
@@ -10462,22 +10471,15 @@ class EntitiesStorage {
10462
10471
  }
10463
10472
  });
10464
10473
  }
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
10474
  /* -- protected methods -- */
10473
10475
  getEntityStore(name, opts) {
10474
- let store = this._stores[name];
10476
+ let store = this._data[name];
10475
10477
  if (!store && (!opts || opts.create !== false)) {
10476
10478
  if (this._debug)
10477
10479
  console.debug(`[entities-storage] Creating store ${name}`);
10478
10480
  const typePolicy = this._typePolicies[name];
10479
10481
  store = new EntityStore(name, this.storage, typePolicy);
10480
- this._stores[name] = store;
10482
+ this._data[name] = store;
10481
10483
  }
10482
10484
  return store;
10483
10485
  }
@@ -10520,12 +10522,12 @@ class EntitiesStorage {
10520
10522
  this._saving = true;
10521
10523
  this._dirty = false;
10522
10524
  this.progressBarService.increase();
10523
- const entityNames = this._stores && Object.keys(this._stores) || [];
10525
+ const entityNames = this._data && Object.keys(this._data) || [];
10524
10526
  const now = Date.now();
10525
10527
  if (this._debug)
10526
10528
  console.debug('[entities-storage] Persisting...');
10527
10529
  let currentEntityName;
10528
- return concat(...entityNames.map(entityName => defer(() => {
10530
+ return chainPromises(entityNames.map(entityName => () => {
10529
10531
  currentEntityName = entityName;
10530
10532
  const entityStore = this.getEntityStore(entityName, { create: false });
10531
10533
  if (!entityStore) {
@@ -10539,18 +10541,20 @@ class EntitiesStorage {
10539
10541
  entityNames.splice(entityNames.findIndex(e => e === entityName), 1);
10540
10542
  }
10541
10543
  });
10542
- })), defer(() => {
10544
+ }))
10545
+ .then(() => {
10543
10546
  currentEntityName = undefined;
10544
10547
  return isEmptyArray(entityNames) ?
10545
10548
  this.storage.remove(ENTITIES_STORAGE_KEY_PREFIX) :
10546
10549
  this.storage.set(ENTITIES_STORAGE_KEY_PREFIX, entityNames);
10547
- }), defer(() => {
10550
+ })
10551
+ .then(() => {
10548
10552
  if (this._debug)
10549
10553
  console.debug(`[entities-storage] Persisting [OK] ${entityNames.length} stores saved in ${Date.now() - now}ms...`);
10550
10554
  this._saving = false;
10551
10555
  this.progressBarService.decrease();
10552
- }))
10553
- .pipe(catchError(err => {
10556
+ })
10557
+ .catch(err => {
10554
10558
  this._saving = false;
10555
10559
  this.progressBarService.decrease();
10556
10560
  if (currentEntityName) {
@@ -10559,8 +10563,8 @@ class EntitiesStorage {
10559
10563
  else {
10560
10564
  console.error(`[entities-storage] Error while persisting: ${err && err.message || err}`, err);
10561
10565
  }
10562
- return err;
10563
- })).toPromise();
10566
+ throw err;
10567
+ });
10564
10568
  });
10565
10569
  }
10566
10570
  }
@@ -10574,7 +10578,7 @@ EntitiesStorage.ctorParameters = () => [
10574
10578
  { type: Platform },
10575
10579
  { type: ProgressBarService },
10576
10580
  { type: Storage },
10577
- { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10581
+ { type: Environment, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] },
10578
10582
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APP_LOCAL_STORAGE_TYPE_POLICIES,] }] }
10579
10583
  ];
10580
10584
 
@@ -11045,8 +11049,9 @@ function restoreTrackedQueries(opts) {
11045
11049
  }
11046
11050
 
11047
11051
  const APP_GRAPHQL_TYPE_POLICIES = new InjectionToken('graphqlTypePolicies');
11048
- class GraphqlService {
11052
+ class GraphqlService extends StartableService {
11049
11053
  constructor(platform, apollo, httpLink, network, storage, cryptoService, environment, typePolicies) {
11054
+ super(platform); // Wait network
11050
11055
  this.platform = platform;
11051
11056
  this.apollo = apollo;
11052
11057
  this.httpLink = httpLink;
@@ -11055,21 +11060,12 @@ class GraphqlService {
11055
11060
  this.cryptoService = cryptoService;
11056
11061
  this.environment = environment;
11057
11062
  this.typePolicies = typePolicies;
11058
- this._started = false;
11059
11063
  this._subscription = new Subscription();
11060
11064
  this.connectionParams = {};
11061
11065
  this.onNetworkError = new Subject();
11062
11066
  this.customErrors = {};
11063
- this.onStart = new Subject();
11064
11067
  this._debug = !environment.production;
11065
11068
  this._defaultFetchPolicy = environment.apolloFetchPolicy;
11066
- // Restart if network restart
11067
- this.network.on('start', () => this.restart());
11068
- // Clear cache
11069
- this.network.on('resetCache', () => __awaiter(this, void 0, void 0, function* () {
11070
- yield this.ready();
11071
- yield this.clearCache();
11072
- }));
11073
11069
  // Listen network status
11074
11070
  this._networkStatusChanged$ = network.onNetworkStatusChanges
11075
11071
  .pipe(filter(isNotNil), distinctUntilChanged());
@@ -11078,11 +11074,8 @@ class GraphqlService {
11078
11074
  .pipe(throttleTime(300), filter(() => this.network.online), mergeMap(() => this.network.checkPeerAlive()), filter(alive => !alive))
11079
11075
  .subscribe(() => this.network.setForceOffline(true, { showToast: true }));
11080
11076
  }
11081
- get started() {
11082
- return this._started;
11083
- }
11084
11077
  get client() {
11085
- return this.apollo.client;
11078
+ return this._data;
11086
11079
  }
11087
11080
  get cache() {
11088
11081
  return this.apollo.client.cache;
@@ -11090,33 +11083,6 @@ class GraphqlService {
11090
11083
  get defaultFetchPolicy() {
11091
11084
  return this._defaultFetchPolicy;
11092
11085
  }
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
11086
  setAuthToken(token) {
11121
11087
  if (token) {
11122
11088
  console.debug('[graphql] Apply token authentication to headers');
@@ -11149,22 +11115,22 @@ class GraphqlService {
11149
11115
  */
11150
11116
  addResolver(resolvers) {
11151
11117
  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);
11118
+ if (!this.started)
11119
+ yield this.ready();
11120
+ this.apollo.client.addResolvers(resolvers);
11157
11121
  });
11158
11122
  }
11159
11123
  query(opts) {
11160
11124
  return __awaiter(this, void 0, void 0, function* () {
11125
+ if (!this.started)
11126
+ yield this.ready();
11161
11127
  let res;
11162
11128
  try {
11163
- res = yield (yield this.getApollo()).query({
11129
+ res = yield this.client.query({
11164
11130
  query: opts.query,
11165
11131
  variables: opts.variables,
11166
11132
  fetchPolicy: opts.fetchPolicy || this._defaultFetchPolicy || undefined
11167
- }).toPromise();
11133
+ });
11168
11134
  }
11169
11135
  catch (err) {
11170
11136
  res = this.toApolloError(err, opts.error);
@@ -11504,10 +11470,11 @@ class GraphqlService {
11504
11470
  }
11505
11471
  clearCache(client) {
11506
11472
  return __awaiter(this, void 0, void 0, function* () {
11507
- client = (client || this.apollo.client);
11473
+ client = (client || this.client);
11508
11474
  if (client) {
11509
- const now = this._debug && Date.now();
11510
11475
  console.info('[graphql] Clearing Apollo client\'s cache... ');
11476
+ const now = this._debug && Date.now();
11477
+ // Clearing the cache
11511
11478
  yield client.cache.reset();
11512
11479
  if (this._debug)
11513
11480
  console.debug(`[graphql] Apollo client's cache cleared, in ${Date.now() - now}ms`);
@@ -11518,8 +11485,10 @@ class GraphqlService {
11518
11485
  this.customErrors = Object.assign(Object.assign({}, this.customErrors), error);
11519
11486
  }
11520
11487
  /* -- protected methods -- */
11521
- initApollo() {
11488
+ ngOnStart() {
11522
11489
  return __awaiter(this, void 0, void 0, function* () {
11490
+ yield this.network.ready();
11491
+ console.info('[graphql] Starting graphql...');
11523
11492
  const mobile = this.platform.is('mobile') || this.platform.is('mobileweb');
11524
11493
  const enableTrackMutationQueries = !mobile;
11525
11494
  const peer = this.network.peer;
@@ -11594,9 +11563,8 @@ class GraphqlService {
11594
11563
  const queueLink = new QueueLink();
11595
11564
  this._subscription.add(this._networkStatusChanged$
11596
11565
  .subscribe(type => {
11597
- const offline = type === 'none';
11598
11566
  // Network is offline: start buffering into queue
11599
- if (offline) {
11567
+ if (type === 'none') {
11600
11568
  console.info('[graphql] offline mode: enable mutations buffer');
11601
11569
  queueLink.close();
11602
11570
  }
@@ -11655,24 +11623,20 @@ class GraphqlService {
11655
11623
  console.error('[graphql] Failed to restore tracked queries from storage: ' + (err && err.message || err), err);
11656
11624
  }
11657
11625
  }
11626
+ // Listen for network restart
11627
+ this._subscription.add(this.network.on('start', () => this.restart()));
11628
+ // Listen for clear cache request, from network
11629
+ this._subscription.add(this.network.on('resetCache', () => this.clearCache()));
11630
+ console.info('[graphql] Starting graphql [OK]');
11631
+ return client;
11658
11632
  });
11659
11633
  }
11660
- stop() {
11634
+ ngOnStop() {
11661
11635
  return __awaiter(this, void 0, void 0, function* () {
11662
11636
  console.info('[graphql] Stopping graphql service...');
11663
11637
  this._subscription.unsubscribe();
11664
11638
  this._subscription = new Subscription();
11665
11639
  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
11640
  });
11677
11641
  }
11678
11642
  resetClient(client) {
@@ -11768,15 +11732,6 @@ class GraphqlService {
11768
11732
  }
11769
11733
  return undefined;
11770
11734
  }
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
11735
  }
11781
11736
  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
11737
  GraphqlService.decorators = [
@@ -12120,7 +12075,12 @@ class AccountService extends BaseGraphqlService {
12120
12075
  this.storage = storage;
12121
12076
  this.file = file;
12122
12077
  this.environment = environment;
12123
- this.data = {
12078
+ this.onLogin = new Subject();
12079
+ this.onLogout = new Subject();
12080
+ this.onChange = new Subject();
12081
+ this.onAuthTokenChange = new Subject();
12082
+ this.onAuthBasicChange = new Subject();
12083
+ this._data = {
12124
12084
  loaded: false,
12125
12085
  keypair: null,
12126
12086
  authToken: null,
@@ -12134,11 +12094,6 @@ class AccountService extends BaseGraphqlService {
12134
12094
  this._started = false;
12135
12095
  this._$additionalFields = new BehaviorSubject([]);
12136
12096
  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
12097
  this._debug = !environment.production;
12143
12098
  if (this._debug)
12144
12099
  console.debug('[account-service] Creating service');
@@ -12146,7 +12101,7 @@ class AccountService extends BaseGraphqlService {
12146
12101
  // Send auth token to the graphql layer, when changed
12147
12102
  this.onAuthTokenChange.subscribe((token) => this.graphql.setAuthToken(token));
12148
12103
  this.onAuthBasicChange.subscribe((basic) => this.graphql.setAuthBasic(basic));
12149
- // Listen network restart
12104
+ // Listen graphql start (or restart)
12150
12105
  this.graphql.onStart.subscribe(() => __awaiter(this, void 0, void 0, function* () {
12151
12106
  if (!this._started) {
12152
12107
  this.ready();
@@ -12168,19 +12123,19 @@ class AccountService extends BaseGraphqlService {
12168
12123
  }));
12169
12124
  }
12170
12125
  get account() {
12171
- return this.data.loaded ? this.data.account : undefined;
12126
+ return this._data.loaded ? this._data.account : undefined;
12172
12127
  }
12173
12128
  get person() {
12174
- if (this.data.loaded && !this.data.person) {
12175
- this.data.person = this.data.loaded ? this.data.account.asPerson() : undefined;
12129
+ if (this._data.loaded && !this._data.person) {
12130
+ this._data.person = this._data.loaded ? this._data.account.asPerson() : undefined;
12176
12131
  }
12177
- return this.data.person;
12132
+ return this._data.person;
12178
12133
  }
12179
12134
  get department() {
12180
- if (this.data.loaded && !this.data.department) {
12181
- this.data.department = this.data.loaded ? this.data.account.asPerson().department : undefined;
12135
+ if (this._data.loaded && !this._data.department) {
12136
+ this._data.department = this._data.loaded ? this._data.account.asPerson().department : undefined;
12182
12137
  }
12183
- return this.data.department;
12138
+ return this._data.department;
12184
12139
  }
12185
12140
  get tokenType() {
12186
12141
  return this._tokenType$.value;
@@ -12190,28 +12145,17 @@ class AccountService extends BaseGraphqlService {
12190
12145
  console.info('[account] Using authentication token type: ' + value);
12191
12146
  this._tokenType$.next(value);
12192
12147
  // Reset values
12193
- this.data.authToken = undefined;
12148
+ this._data.authToken = undefined;
12194
12149
  this.onAuthTokenChange.next(undefined);
12195
- this.data.authBasic = undefined;
12150
+ this._data.authBasic = undefined;
12196
12151
  this.onAuthBasicChange.next(undefined);
12197
12152
  }
12198
12153
  }
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
12154
  start() {
12211
12155
  if (this._startPromise)
12212
12156
  return this._startPromise;
12213
12157
  if (this._started)
12214
- return Promise.resolve();
12158
+ return Promise.resolve(this.account);
12215
12159
  // Restoring local settings
12216
12160
  this._startPromise = Promise.all([
12217
12161
  this.settings.ready(),
@@ -12222,6 +12166,7 @@ class AccountService extends BaseGraphqlService {
12222
12166
  .then(() => {
12223
12167
  this._started = true;
12224
12168
  this._startPromise = undefined;
12169
+ return this.account;
12225
12170
  });
12226
12171
  return this._startPromise;
12227
12172
  }
@@ -12233,8 +12178,8 @@ class AccountService extends BaseGraphqlService {
12233
12178
  if (this._started || this._startPromise) {
12234
12179
  this._started = false;
12235
12180
  this._startPromise = undefined;
12236
- const hadAuthToken = this.data.authToken && true;
12237
- const hadAuthBasic = this.data.authBasic && true;
12181
+ const hadAuthToken = this._data.authToken && true;
12182
+ const hadAuthBasic = this._data.authBasic && true;
12238
12183
  const hadAuth = hadAuthToken || hadAuthBasic;
12239
12184
  this.resetData();
12240
12185
  if (hadAuth) {
@@ -12256,35 +12201,35 @@ class AccountService extends BaseGraphqlService {
12256
12201
  }
12257
12202
  ready() {
12258
12203
  if (this._started)
12259
- return Promise.resolve();
12204
+ return Promise.resolve(this.account);
12260
12205
  return this.start();
12261
12206
  }
12262
12207
  isLogin() {
12263
- return !!(this.data.pubkey && this.data.loaded);
12208
+ return !!(this._data.pubkey && this._data.loaded);
12264
12209
  }
12265
12210
  isAuth() {
12266
- return !!(this.data.pubkey && this.data.keypair && this.data.keypair.secretKey);
12211
+ return !!(this._data.pubkey && this._data.keypair && this._data.keypair.secretKey);
12267
12212
  }
12268
12213
  hasMinProfile(userProfile) {
12269
12214
  // 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)) {
12215
+ if (!this._data.account || !this._data.account.pubkey ||
12216
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY)) {
12272
12217
  return false;
12273
12218
  }
12274
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12219
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12275
12220
  }
12276
12221
  hasExactProfile(label) {
12277
12222
  // 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))
12223
+ if (!this._data.account || !this._data.account.pubkey ||
12224
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12280
12225
  return false;
12281
- return this.data.account.profiles.some(profile => profile === label);
12226
+ return this._data.account.profiles.some(profile => profile === label);
12282
12227
  }
12283
12228
  hasProfileAndIsEnable(userProfile) {
12284
12229
  // should be login, and status ENABLE
12285
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12230
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12286
12231
  return false;
12287
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, userProfile);
12232
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, userProfile);
12288
12233
  }
12289
12234
  isAdmin() {
12290
12235
  return this.hasProfileAndIsEnable('ADMIN');
@@ -12304,11 +12249,11 @@ class AccountService extends BaseGraphqlService {
12304
12249
  }
12305
12250
  isOnlyGuest() {
12306
12251
  // 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))
12252
+ if (!this._data.account || !this._data.account.pubkey ||
12253
+ (this._data.account.statusId !== StatusIds.ENABLE && this._data.account.statusId !== StatusIds.TEMPORARY))
12309
12254
  return false;
12310
12255
  // Profile less then user
12311
- return !PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'USER');
12256
+ return !PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'USER');
12312
12257
  }
12313
12258
  canUserWriteDataForDepartment(recorderDepartment) {
12314
12259
  if (ReferentialUtils.isEmpty(recorderDepartment)) {
@@ -12317,17 +12262,17 @@ class AccountService extends BaseGraphqlService {
12317
12262
  return this.isAdmin();
12318
12263
  }
12319
12264
  // Should be login, and status ENABLE
12320
- if (!this.data.account || !this.data.account.pubkey || this.data.account.statusId !== StatusIds.ENABLE)
12265
+ if (!this._data.account || !this._data.account.pubkey || this._data.account.statusId !== StatusIds.ENABLE)
12321
12266
  return false;
12322
- if (!this.data.account.department || !this.data.account.department.id) {
12267
+ if (!this._data.account.department || !this._data.account.department.id) {
12323
12268
  console.warn('User account has no department ! Unable to check write right against recorderDepartment');
12324
12269
  return false;
12325
12270
  }
12326
12271
  // Same recorder department: OK, user can write
12327
- if (this.data.account.department.id === recorderDepartment.id)
12272
+ if (this._data.account.department.id === recorderDepartment.id)
12328
12273
  return true;
12329
12274
  // Else, check if supervisor (or more)
12330
- return PersonUtils.hasUpperOrEqualsProfile(this.data.account.profiles, 'SUPERVISOR');
12275
+ return PersonUtils.hasUpperOrEqualsProfile(this._data.account.profiles, 'SUPERVISOR');
12331
12276
  }
12332
12277
  register(data) {
12333
12278
  return __awaiter(this, void 0, void 0, function* () {
@@ -12338,7 +12283,7 @@ class AccountService extends BaseGraphqlService {
12338
12283
  throw new Error('Missing required username or password');
12339
12284
  if (this._debug)
12340
12285
  console.debug('[account] Register new user account...', data.account);
12341
- this.data.loaded = false;
12286
+ this._data.loaded = false;
12342
12287
  const now = Date.now();
12343
12288
  try {
12344
12289
  const keypair = yield this.cryptoService.scryptKeypair(data.username, data.password);
@@ -12348,22 +12293,22 @@ class AccountService extends BaseGraphqlService {
12348
12293
  data.account.settings.locale = this.settings.locale;
12349
12294
  data.account.settings.latLongFormat = this.settings.latLongFormat;
12350
12295
  data.account.department.id = data.account.department.id || this.environment.defaultDepartmentId;
12351
- this.data.keypair = keypair;
12296
+ this._data.keypair = keypair;
12352
12297
  const account = yield this.saveRemotely(data.account, keypair);
12353
12298
  // Default values
12354
12299
  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;
12300
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12301
+ this._data.account = account;
12302
+ this._data.pubkey = account.pubkey;
12358
12303
  // Try to auth on pod
12359
12304
  yield this.authenticate(data);
12360
- this.data.loaded = true;
12305
+ this._data.loaded = true;
12361
12306
  yield this.saveLocally();
12362
12307
  console.debug(`[account] Account successfully registered in ${Date.now() - now}ms`);
12363
12308
  // Emit events
12364
- this.onLogin.next(this.data.account);
12365
- this.onChange.next(this.data.account);
12366
- return this.data.account;
12309
+ this.onLogin.next(this._data.account);
12310
+ this.onChange.next(this._data.account);
12311
+ return this._data.account;
12367
12312
  }
12368
12313
  catch (error) {
12369
12314
  console.error(error && error.message || error);
@@ -12379,20 +12324,20 @@ class AccountService extends BaseGraphqlService {
12379
12324
  // Basic auth
12380
12325
  if (tokenType === 'basic' || tokenType === 'basic-and-token') {
12381
12326
  // Generate the authBasic, if used
12382
- if (!this.data.authBasic) {
12327
+ if (!this._data.authBasic) {
12383
12328
  // Skip if token already provided
12384
- if (!(this.data.authToken && tokenType === 'basic-and-token')) {
12329
+ if (!(this._data.authToken && tokenType === 'basic-and-token')) {
12385
12330
  if (!data || !data.username || !data.password)
12386
12331
  throw new Error('Missing username and password');
12387
- this.data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12332
+ this._data.authBasic = this.cryptoService.encodeBase64(`${data.username}:${data.password}`);
12388
12333
  }
12389
12334
  }
12390
- this.onAuthBasicChange.next(this.data.authBasic);
12335
+ this.onAuthBasicChange.next(this._data.authBasic);
12391
12336
  }
12392
12337
  // Generate the authToken, if used
12393
12338
  if (tokenType === 'token' || tokenType === 'basic-and-token') {
12394
12339
  try {
12395
- this.data.authToken = yield this.authenticateAndGetToken(this.data.authToken);
12340
+ this._data.authToken = yield this.authenticateAndGetToken(this._data.authToken);
12396
12341
  }
12397
12342
  catch (error) {
12398
12343
  // Never authenticate, or not ready for offline mode => exit
@@ -12403,7 +12348,7 @@ class AccountService extends BaseGraphqlService {
12403
12348
  }
12404
12349
  // Forget authBasic, to switch to authToken
12405
12350
  if (tokenType === 'basic-and-token') {
12406
- this.data.authBasic = undefined;
12351
+ this._data.authBasic = undefined;
12407
12352
  this.onAuthBasicChange.next(undefined);
12408
12353
  }
12409
12354
  });
@@ -12423,18 +12368,18 @@ class AccountService extends BaseGraphqlService {
12423
12368
  throw { code: ErrorCodes$2.UNKNOWN_ERROR, message: 'ERROR.SCRYPT_ERROR' };
12424
12369
  }
12425
12370
  // Store pubkey+keypair
12426
- this.data.pubkey = Base58.encode(keypair.publicKey);
12427
- this.data.keypair = keypair;
12371
+ this._data.pubkey = Base58.encode(keypair.publicKey);
12372
+ this._data.keypair = keypair;
12428
12373
  // Try to load previous token
12429
12374
  let previousToken = yield this.storage.get(TOKEN_STORAGE_KEY);
12430
- previousToken = previousToken && previousToken.startsWith(this.data.pubkey) && previousToken || null;
12375
+ previousToken = previousToken && previousToken.startsWith(this._data.pubkey) && previousToken || null;
12431
12376
  // Offline mode
12432
12377
  const offline = this.settings.hasOfflineFeature() && (this.network.offline || data.offline === true);
12433
12378
  if (offline) {
12434
- this.data.authToken = previousToken;
12379
+ this._data.authToken = previousToken;
12435
12380
  // Make sure network if set as offline
12436
12381
  this.network.setForceOffline(true, { showToast: false });
12437
- console.info(`[account] Login [OK] {pubkey: ${this.data.pubkey.substr(0, 8)}}, {offline: true}`);
12382
+ console.info(`[account] Login [OK] {pubkey: ${this._data.pubkey.substr(0, 8)}}, {offline: true}`);
12438
12383
  }
12439
12384
  // Online mode: try to auth on pod
12440
12385
  else {
@@ -12484,14 +12429,14 @@ class AccountService extends BaseGraphqlService {
12484
12429
  throw error;
12485
12430
  }
12486
12431
  // Emit event to observers
12487
- this.onLogin.next(this.data.account);
12488
- this.onChange.next(this.data.account);
12489
- return this.data.account;
12432
+ this.onLogin.next(this._data.account);
12433
+ this.onChange.next(this._data.account);
12434
+ return this._data.account;
12490
12435
  });
12491
12436
  }
12492
12437
  refresh() {
12493
12438
  return __awaiter(this, void 0, void 0, function* () {
12494
- if (!this.data.pubkey)
12439
+ if (!this._data.pubkey)
12495
12440
  throw new Error('User not logged');
12496
12441
  if (this.network.offline)
12497
12442
  throw new Error('Cannot check account in offline mode');
@@ -12499,9 +12444,9 @@ class AccountService extends BaseGraphqlService {
12499
12444
  yield this.saveLocally();
12500
12445
  console.debug('[account] Successfully reload account');
12501
12446
  // Emit login event to subscribers
12502
- this.onLogin.next(this.data.account);
12503
- this.onChange.next(this.data.account);
12504
- return this.data.account;
12447
+ this.onLogin.next(this._data.account);
12448
+ this.onChange.next(this._data.account);
12449
+ return this._data.account;
12505
12450
  });
12506
12451
  }
12507
12452
  /**
@@ -12511,29 +12456,29 @@ class AccountService extends BaseGraphqlService {
12511
12456
  */
12512
12457
  save(account) {
12513
12458
  return __awaiter(this, void 0, void 0, function* () {
12514
- if (!this.data.pubkey)
12459
+ if (!this._data.pubkey)
12515
12460
  return Promise.reject('User not logged');
12516
- if (this.data.pubkey !== account.pubkey)
12461
+ if (this._data.pubkey !== account.pubkey)
12517
12462
  return Promise.reject('Not user account');
12518
- account = yield this.saveRemotely(account, this.data.keypair);
12463
+ account = yield this.saveRemotely(account, this._data.keypair);
12519
12464
  // Set defaults
12520
12465
  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;
12466
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12467
+ this._data.account = account;
12468
+ this._data.loaded = true;
12524
12469
  // Save locally (in storage)
12525
12470
  yield this.saveLocally();
12526
12471
  // Send event
12527
- this.onLogin.next(this.data.account);
12528
- this.onChange.next(this.data.account);
12529
- return this.data.account;
12472
+ this.onLogin.next(this._data.account);
12473
+ this.onChange.next(this._data.account);
12474
+ return this._data.account;
12530
12475
  });
12531
12476
  }
12532
12477
  logout() {
12533
12478
  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;
12479
+ const hadAuthToken = this._data.authToken && true;
12480
+ const hadAuthBasic = this._data.authBasic && true;
12481
+ const pubkey = this._data && this._data.pubkey;
12537
12482
  this.resetData();
12538
12483
  if (!this.settings.hasOfflineFeature()) {
12539
12484
  // Remove all data from the local storage
@@ -12586,9 +12531,9 @@ class AccountService extends BaseGraphqlService {
12586
12531
  if (offline) {
12587
12532
  json = yield this.storage.get(ACCOUNT_STORAGE_KEY);
12588
12533
  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);
12534
+ json = json && this._data.pubkey && (json.pubkey === this._data.pubkey) && json || null;
12535
+ if (!json && this._data.pubkey) {
12536
+ json = yield this.storage.get(ACCOUNT_STORAGE_KEY + '#' + this._data.pubkey);
12592
12537
  json = json && (typeof json === 'string') && JSON.parse(json) || json;
12593
12538
  }
12594
12539
  }
@@ -12683,7 +12628,7 @@ class AccountService extends BaseGraphqlService {
12683
12628
  });
12684
12629
  }
12685
12630
  listenChanges() {
12686
- if (!this.data.pubkey)
12631
+ if (!this._data.pubkey)
12687
12632
  return Subscription.EMPTY;
12688
12633
  const self = this;
12689
12634
  console.debug('[account] [WS] Listening account changes');
@@ -12697,34 +12642,30 @@ class AccountService extends BaseGraphqlService {
12697
12642
  message: 'ERROR.ACCOUNT.SUBSCRIBE_ACCOUNT_ERROR'
12698
12643
  }
12699
12644
  }).subscribe({
12700
- next({ data }) {
12645
+ next: ({ data }) => __awaiter(this, void 0, void 0, function* () {
12701
12646
  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() {
12647
+ if (!data)
12648
+ return;
12649
+ const existingUpdateDate = toDateISOString((_a = self._data.account) === null || _a === void 0 ? void 0 : _a.updateDate);
12650
+ if (existingUpdateDate !== data.updateDate) {
12651
+ console.debug(`[account] [WS] Detected update on {${data.updateDate}}`);
12652
+ yield self.refresh();
12653
+ }
12654
+ }),
12655
+ error: (err) => __awaiter(this, void 0, void 0, function* () {
12656
+ if (err && +err.code === ServerErrorCodes.NOT_FOUND) {
12657
+ console.info('[account] Account not exists anymore: force user to logout...', err);
12658
+ yield self.logout();
12659
+ }
12660
+ else if (err && +err.code === ServerErrorCodes.UNAUTHORIZED) {
12661
+ console.info('[account] Account not authorized: force user to logout...', err);
12662
+ yield self.logout();
12663
+ }
12664
+ else {
12665
+ console.warn('[account] [WS] Received error:', err);
12666
+ }
12667
+ }),
12668
+ complete: () => {
12728
12669
  console.debug('[account] [WS] Completed');
12729
12670
  }
12730
12671
  });
@@ -12765,29 +12706,40 @@ class AccountService extends BaseGraphqlService {
12765
12706
  this._$additionalFields.next(values.concat(field));
12766
12707
  }
12767
12708
  /* -- protected method -- */
12709
+ resetData() {
12710
+ this._data.loaded = false;
12711
+ this._data.keypair = null;
12712
+ this._data.authToken = null;
12713
+ this._data.authBasic = null;
12714
+ this._data.pubkey = null;
12715
+ this._data.mainProfile = null;
12716
+ this._data.account = new Account();
12717
+ this._data.person = null;
12718
+ this._data.department = null;
12719
+ }
12768
12720
  loadData(opts) {
12769
12721
  return __awaiter(this, void 0, void 0, function* () {
12770
- if (!this.data.pubkey)
12722
+ if (!this._data.pubkey)
12771
12723
  throw new Error('User not logged');
12772
- this.data.loaded = false;
12724
+ this._data.loaded = false;
12773
12725
  try {
12774
- let account = (yield this.load(opts)) || new Account();
12726
+ const account = (yield this.load(opts)) || new Account();
12775
12727
  // Set defaults
12776
12728
  account.avatar = account.avatar || (this.environment.baseUrl + DEFAULT_AVATAR_IMAGE);
12777
12729
  account.settings = account.settings || new UserSettings();
12778
12730
  account.settings.locale = account.settings.locale || this.settings.locale;
12779
12731
  account.settings.latLongFormat = account.settings.latLongFormat || this.settings.latLongFormat || 'DDMM';
12780
12732
  // Read main profile
12781
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12733
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12782
12734
  // Update, instead of replace it
12783
- if (this.data.account) {
12784
- this.data.account.fromObject(account);
12735
+ if (this._data.account) {
12736
+ this._data.account.fromObject(account);
12785
12737
  }
12786
12738
  else {
12787
- this.data.account = account;
12739
+ this._data.account = account;
12788
12740
  }
12789
- this.data.loaded = true;
12790
- return this.data.account;
12741
+ this._data.loaded = true;
12742
+ return this._data.account;
12791
12743
  }
12792
12744
  catch (error) {
12793
12745
  this.resetData();
@@ -12821,9 +12773,9 @@ class AccountService extends BaseGraphqlService {
12821
12773
  return;
12822
12774
  if (this._debug)
12823
12775
  console.debug(`[account] Account restoration...`);
12824
- this.data.authToken = token;
12825
- this.data.pubkey = pubkey;
12826
- this.data.keypair = seckey && {
12776
+ this._data.authToken = token;
12777
+ this._data.pubkey = pubkey;
12778
+ this._data.keypair = seckey && {
12827
12779
  publicKey: Base58.decode(pubkey),
12828
12780
  secretKey: Base58.decode(seckey)
12829
12781
  } || null;
@@ -12831,7 +12783,7 @@ class AccountService extends BaseGraphqlService {
12831
12783
  if (this.network.online) {
12832
12784
  try {
12833
12785
  yield this.authenticate();
12834
- if (!this.data.authToken && !this.data.authBasic)
12786
+ if (!this._data.authToken && !this._data.authBasic)
12835
12787
  throw new Error('Authentication failed');
12836
12788
  }
12837
12789
  catch (error) {
@@ -12862,14 +12814,14 @@ class AccountService extends BaseGraphqlService {
12862
12814
  // Transform to entity
12863
12815
  const account = Account.fromObject(jsonAccount);
12864
12816
  // Update data
12865
- this.data.account = account;
12866
- this.data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12867
- this.data.loaded = true;
12817
+ this._data.account = account;
12818
+ this._data.mainProfile = PersonUtils.getMainProfile(account.profiles);
12819
+ this._data.loaded = true;
12868
12820
  // Emit event
12869
- this.onLogin.next(this.data.account);
12870
- this.onChange.next(this.data.account);
12821
+ this.onLogin.next(this._data.account);
12822
+ this.onChange.next(this._data.account);
12871
12823
  if (this._debug)
12872
- console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this.data.mainProfile}}`);
12824
+ console.debug(`[account] Account restoration [OK] {pubkey: ${pubkey.substr(0, 8)}}, {profile: ${this._data.mainProfile}}`);
12873
12825
  return account;
12874
12826
  });
12875
12827
  }
@@ -12878,13 +12830,13 @@ class AccountService extends BaseGraphqlService {
12878
12830
  */
12879
12831
  saveLocally() {
12880
12832
  return __awaiter(this, void 0, void 0, function* () {
12881
- if (!this.data.pubkey)
12833
+ if (!this._data.pubkey)
12882
12834
  throw new Error('User not logged');
12883
12835
  if (this._debug)
12884
- console.debug(`[account] Saving account {${this.data.pubkey.substring(0, 6)}} in local storage...`);
12836
+ console.debug(`[account] Saving account {${this._data.pubkey.substring(0, 6)}} in local storage...`);
12885
12837
  // 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;
12838
+ const json = this._data.account.asObject({ keepTypename: true });
12839
+ const seckey = this._data.keypair && this._data.keypair.secretKey && Base58.encode(this._data.keypair.secretKey) || null;
12888
12840
  // Convert avatar URL to dataUrl (e.g. 'data:image/png:<base64 content>')
12889
12841
  const hasAvatarUrl = json.avatar && !json.avatar.endsWith(DEFAULT_AVATAR_IMAGE) &&
12890
12842
  (json.avatar.startsWith('http://') || (json.avatar.startsWith('https://')));
@@ -12905,9 +12857,9 @@ class AccountService extends BaseGraphqlService {
12905
12857
  }
12906
12858
  try {
12907
12859
  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),
12860
+ this.storage.set(PUBKEY_STORAGE_KEY, this._data.pubkey),
12861
+ this.storage.set(TOKEN_STORAGE_KEY, this._data.authToken),
12862
+ this.storage.set(`${ACCOUNT_STORAGE_KEY}#${this._data.pubkey}`, json),
12911
12863
  // Secret key (optional)
12912
12864
  seckey && this.storage.set(SECKEY_STORAGE_KEY, seckey) || this.storage.remove(SECKEY_STORAGE_KEY),
12913
12865
  // Remove old storage key
@@ -12965,7 +12917,7 @@ class AccountService extends BaseGraphqlService {
12965
12917
  }
12966
12918
  authenticateAndGetToken(token, counter) {
12967
12919
  return __awaiter(this, void 0, void 0, function* () {
12968
- if (!this.data.pubkey)
12920
+ if (!this._data.pubkey)
12969
12921
  throw new Error('User not logged');
12970
12922
  if (!counter)
12971
12923
  console.info('[account] Authentication on pod...');
@@ -12991,7 +12943,7 @@ class AccountService extends BaseGraphqlService {
12991
12943
  if (data && data.authenticate) {
12992
12944
  // Store the token
12993
12945
  this.onAuthTokenChange.next(token);
12994
- console.info(`[account] Authentication on pod [OK] {pubkey: '${this.data.pubkey.substr(0, 8)}'}`);
12946
+ console.info(`[account] Authentication on pod [OK] {pubkey: '${this._data.pubkey.substr(0, 8)}'}`);
12995
12947
  return token; // return the token
12996
12948
  }
12997
12949
  // Continue (will retry with another challenge)
@@ -13017,8 +12969,8 @@ class AccountService extends BaseGraphqlService {
13017
12969
  }
13018
12970
  // TODO: check server pubkey as a valid certificate
13019
12971
  // 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}`;
12972
+ const signature = yield this.cryptoService.sign(data.authChallenge.challenge, this._data.keypair);
12973
+ const newToken = `${this._data.pubkey}:${data.authChallenge.challenge}|${signature}`;
13022
12974
  // iterate with the new token
13023
12975
  return yield this.authenticateAndGetToken(newToken, (counter || 1) + 1 /* increment */);
13024
12976
  });
@@ -13377,9 +13329,8 @@ class ConfigService extends BaseGraphqlService {
13377
13329
  }
13378
13330
  get config() {
13379
13331
  // If first call: start loading
13380
- if (!this._started) {
13332
+ if (!this._started)
13381
13333
  this.start();
13382
- }
13383
13334
  return this.$data.pipe(filter(isNotNil));
13384
13335
  }
13385
13336
  start() {
@@ -13390,30 +13341,38 @@ class ConfigService extends BaseGraphqlService {
13390
13341
  console.info('[config] Starting configuration...');
13391
13342
  this._startPromise = this.graphql.ready()
13392
13343
  .then(() => this.loadOrRestoreLocally())
13393
- .then(() => {
13344
+ .then((data) => {
13394
13345
  this._started = true;
13395
13346
  this._startPromise = undefined;
13347
+ this.$data.next(data);
13348
+ return data;
13396
13349
  })
13397
13350
  .catch((err) => {
13398
13351
  console.error(err && err.message || err, err);
13352
+ this._started = false;
13399
13353
  this._startPromise = undefined;
13354
+ return null;
13400
13355
  });
13401
13356
  return this._startPromise;
13402
13357
  }
13403
13358
  stop() {
13404
- this._subscription.unsubscribe();
13405
- this._subscription = new Subscription();
13406
- this._started = false;
13407
- this._startPromise = undefined;
13359
+ return __awaiter(this, void 0, void 0, function* () {
13360
+ this._subscription.unsubscribe();
13361
+ this._subscription = new Subscription();
13362
+ this._started = false;
13363
+ this._startPromise = undefined;
13364
+ });
13408
13365
  }
13409
13366
  restart() {
13410
- if (this.started)
13411
- this.stop();
13412
- return this.start();
13367
+ return __awaiter(this, void 0, void 0, function* () {
13368
+ if (this.started)
13369
+ yield this.stop();
13370
+ return this.start();
13371
+ });
13413
13372
  }
13414
13373
  ready() {
13415
13374
  if (this._started)
13416
- return Promise.resolve();
13375
+ return Promise.resolve(this.$data.value);
13417
13376
  if (this._startPromise)
13418
13377
  return this._startPromise;
13419
13378
  return this.start();
@@ -13468,7 +13427,7 @@ class ConfigService extends BaseGraphqlService {
13468
13427
  console.debug('[config] Pod configuration saved!');
13469
13428
  const reloadedConfig = yield this.loadDefault({ fetchPolicy: 'network-only' });
13470
13429
  // If this is the default config
13471
- const defaultConfig = this.$data.getValue();
13430
+ const defaultConfig = this.$data.value;
13472
13431
  if (isNotNil(defaultConfig) && reloadedConfig.label === defaultConfig.label) {
13473
13432
  // Emit update event when is default config
13474
13433
  this.$data.next(reloadedConfig);
@@ -13545,7 +13504,7 @@ class ConfigService extends BaseGraphqlService {
13545
13504
  if (wasJustLoaded) {
13546
13505
  // TODO
13547
13506
  }
13548
- this.$data.next(data);
13507
+ return data;
13549
13508
  });
13550
13509
  }
13551
13510
  restoreLocally() {
@@ -13700,6 +13659,7 @@ class PlatformService {
13700
13659
  this.browser = browser;
13701
13660
  this.downloader = downloader;
13702
13661
  this._started = false;
13662
+ this._downloadingPromise = new Map();
13703
13663
  this._debug = !environment.production;
13704
13664
  if (this._debug)
13705
13665
  console.debug('[platform] Creating service');
@@ -13724,6 +13684,9 @@ class PlatformService {
13724
13684
  isAndroidCordova() {
13725
13685
  return this._android && this._cordova;
13726
13686
  }
13687
+ get canDownload() {
13688
+ return this._android && !!this.downloader;
13689
+ }
13727
13690
  width() {
13728
13691
  return this.platform.width();
13729
13692
  }
@@ -13765,7 +13728,7 @@ class PlatformService {
13765
13728
  .then(() => {
13766
13729
  this._started = true;
13767
13730
  this._startPromise = undefined;
13768
- console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}} in ${Date.now() - now}ms`);
13731
+ console.info(`[platform] Starting platform [OK] {mobile: ${this._mobile}, touchUi: ${this.touchUi}, canDownload: ${this.canDownload}} in ${Date.now() - now}ms`);
13769
13732
  // Update cache configuration when network changed
13770
13733
  this.networkService.onNetworkStatusChanges.subscribe((type) => this.configureCache(type !== 'none'));
13771
13734
  // Update authentication type
@@ -13813,18 +13776,42 @@ class PlatformService {
13813
13776
  }
13814
13777
  }
13815
13778
  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
- }
13779
+ return __awaiter(this, void 0, void 0, function* () {
13780
+ if (!request || !request.uri)
13781
+ throw new Error('Missing argument \'request\' or \'request.uri\'');
13782
+ if (this._android && this.downloader) {
13783
+ // Check if not already downloading file
13784
+ let promise = this._downloadingPromise.get(request.uri);
13785
+ if (promise)
13786
+ return promise;
13787
+ request = Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted, title: request.title || request.filename }, request);
13788
+ try {
13789
+ console.debug('[platform] Downloading, using request: ' + JSON.stringify(request));
13790
+ // Start download
13791
+ promise = this.downloader.download(request);
13792
+ // Remember this request uri
13793
+ this._downloadingPromise.set(request.uri, promise);
13794
+ const location = yield promise;
13795
+ console.info('[platform] File successfully downloaded at:' + location);
13796
+ return location;
13797
+ }
13798
+ catch (err) {
13799
+ console.error('[platform] Unable to download: ' + (err && err.message || err));
13800
+ throw err;
13801
+ }
13802
+ finally {
13803
+ // Forget the request
13804
+ this._downloadingPromise.delete(request.uri);
13805
+ }
13806
+ }
13807
+ // Fallback (if not Android and Cordova): opening the URI using the browser
13808
+ // TODO: find a way to download under iOS (see https://www.c-sharpcorner.com/article/how-to-download-a-file-using-file-transfer-plugin-in-ionic-3/)
13809
+ else {
13810
+ console.warn('[platform] Cannot use Android downloader: using browser open()');
13811
+ this.open(request.uri, '_system', 'location=no', true);
13812
+ return undefined;
13813
+ }
13814
+ });
13828
13815
  }
13829
13816
  /* -- protected methods -- */
13830
13817
  configureCordovaPlugins(mobile) {
@@ -13921,13 +13908,14 @@ class PlatformService {
13921
13908
  storeName: forage.config().storeName,
13922
13909
  driver: [forage.INDEXEDDB, forage.WEBSQL]
13923
13910
  });
13924
- // IF data stored in the OLD storage: start migration
13925
13911
  const keys = yield oldForage.keys();
13912
+ // No data in the odl storage: skip migration
13926
13913
  if (isEmptyArray(keys)) {
13927
- // Drop the old instance
13928
- console.info(`[platform] Drop old storage {name: '${forage.config().name}', driver: '${oldForage.driver()}'}`);
13914
+ // Drop the old instance (not need anymore)
13915
+ console.debug(`[platform] Old storage is empty: dropping unused instance {name: '${forage.config().name}', driver: '${oldForage.driver()}'}`);
13929
13916
  yield oldForage.dropInstance();
13930
13917
  }
13918
+ // IF some data stored in the OLD storage: start migration
13931
13919
  else {
13932
13920
  const now = Date.now();
13933
13921
  console.info(`[platform] Starting storage migration...`);
@@ -15337,7 +15325,7 @@ class AutocompleteTestPage {
15337
15325
  AutocompleteTestPage.decorators = [
15338
15326
  { type: Component, args: [{
15339
15327
  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"
15328
+ 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
15329
  },] }
15342
15330
  ];
15343
15331
  AutocompleteTestPage.ctorParameters = () => [
@@ -18487,22 +18475,25 @@ const MimeTypes = Object.freeze({
18487
18475
  ANDROID_APK: 'application/vnd.android.package-archive'
18488
18476
  });
18489
18477
  class AppInstallUpgradeCard {
18490
- constructor(modalCtrl, configService, platform, cd, network, environment) {
18478
+ constructor(modalCtrl, configService, toastController, alertController, translate, cd, platform, network, environment) {
18491
18479
  this.modalCtrl = modalCtrl;
18492
18480
  this.configService = configService;
18493
- this.platform = platform;
18481
+ this.toastController = toastController;
18482
+ this.alertController = alertController;
18483
+ this.translate = translate;
18494
18484
  this.cd = cd;
18485
+ this.platform = platform;
18495
18486
  this.network = network;
18496
18487
  this.environment = environment;
18497
18488
  this._subscription = new Subscription();
18498
18489
  this._showUpdateOfflineFeature = false;
18499
18490
  this.loading = true;
18491
+ this.downloading = false;
18500
18492
  this.waitingNetwork = false;
18501
18493
  this.showUpgradeWarning = true;
18502
18494
  this.showOfflineWarning = true;
18503
18495
  this.showInstallButton = false;
18504
18496
  this.onUpdateOfflineModeClick = new EventEmitter();
18505
- this.isAndroidCordova = platform.isAndroidCordova();
18506
18497
  }
18507
18498
  set showUpdateOfflineFeature(value) {
18508
18499
  if (value === this._showUpdateOfflineFeature)
@@ -18519,18 +18510,8 @@ class AppInstallUpgradeCard {
18519
18510
  this.offline = this.network.offline;
18520
18511
  // Listen pod config
18521
18512
  this._subscription.add(this.configService.config
18522
- .subscribe(config => {
18523
- console.info('[install] Checking if upgrade or install is need...');
18524
- const installLinks = this.getAllInstallLinks(config);
18525
- // Check for upgrade
18526
- this.updateLinks = this.getCompatibleUpgradeLinks(installLinks, config);
18527
- // Check for install links (if no upgrade need)
18528
- this.installLinks = !this.updateLinks && this.getCompatibleInstallLinks(installLinks);
18529
- setTimeout(() => {
18530
- this.loading = false;
18531
- this.markForCheck();
18532
- }, 2000); // Add a delay, for animation
18533
- }));
18513
+ .pipe(debounceTime(1000))
18514
+ .subscribe(config => this.checkNeedInstallOrUpdate(config)));
18534
18515
  // Listen network changes
18535
18516
  this._subscription.add(this.network.onNetworkStatusChanges
18536
18517
  .pipe(
@@ -18538,34 +18519,69 @@ class AppInstallUpgradeCard {
18538
18519
  //tap(() => this.waitingNetwork = false),
18539
18520
  map(connectionType => connectionType === 'none'), distinctUntilChanged())
18540
18521
  .subscribe(offline => {
18541
- this.offline = offline;
18542
- this.markForCheck();
18522
+ if (this.offline !== offline) {
18523
+ this.offline = offline;
18524
+ this.markForCheck();
18525
+ }
18543
18526
  }));
18544
18527
  });
18545
18528
  }
18546
18529
  ngOnDestroy() {
18547
18530
  this._subscription.unsubscribe();
18531
+ this._subscription = null;
18548
18532
  }
18549
- downloadLink(event, link) {
18550
- if (!link || !link.url)
18551
- return; // Skip
18552
- this.platform.download({
18553
- uri: link.url,
18554
- filename: link.downloadFilename,
18555
- mimeType: link.mimeType
18533
+ download(event, link) {
18534
+ return __awaiter(this, void 0, void 0, function* () {
18535
+ if (!link || !link.url) {
18536
+ console.error('[install-upgrade-card] Missing required argument \'link.url\'');
18537
+ return; // Skip
18538
+ }
18539
+ this.downloading = true;
18540
+ try {
18541
+ console.info('[install-upgrade-card] User click to download file: ' + link.url);
18542
+ const location = yield this.platform.download({
18543
+ uri: link.url,
18544
+ title: link.name,
18545
+ filename: link.downloadFilename,
18546
+ mimeType: link.mimeType
18547
+ });
18548
+ console.info('[install-upgrade-card] File downloaded at: ' + location);
18549
+ }
18550
+ // Failed: display a toast
18551
+ catch (err) {
18552
+ console.error('[install-upgrade] Download failed: ' + (err && err.message || err));
18553
+ this.showToast({
18554
+ message: 'ERROR.DOWNLOAD_FAILED',
18555
+ messageParams: { error: err },
18556
+ type: 'error',
18557
+ showCloseButton: true
18558
+ });
18559
+ return; // Stop here
18560
+ }
18561
+ finally {
18562
+ this.downloading = false;
18563
+ this.markForCheck();
18564
+ }
18565
+ if (!this._subscription)
18566
+ return; // Skip if component destroyed
18567
+ yield this.showDownloadCompleteDialog();
18556
18568
  });
18557
18569
  }
18558
18570
  tryOnline() {
18559
- this.waitingNetwork = true;
18560
- this.markForCheck();
18561
- this.network.tryOnline({
18562
- showLoadingToast: false,
18563
- showOnlineToast: true,
18564
- showOfflineToast: false
18565
- })
18566
- .then(() => {
18567
- this.waitingNetwork = false;
18571
+ return __awaiter(this, void 0, void 0, function* () {
18572
+ this.waitingNetwork = true;
18568
18573
  this.markForCheck();
18574
+ try {
18575
+ yield this.network.tryOnline({
18576
+ showLoadingToast: false,
18577
+ showOnlineToast: true,
18578
+ showOfflineToast: false
18579
+ });
18580
+ }
18581
+ finally {
18582
+ this.waitingNetwork = false;
18583
+ this.markForCheck();
18584
+ }
18569
18585
  });
18570
18586
  }
18571
18587
  getPlatformName(platform) {
@@ -18582,6 +18598,23 @@ class AppInstallUpgradeCard {
18582
18598
  return value;
18583
18599
  }
18584
18600
  /* -- private method -- */
18601
+ checkNeedInstallOrUpdate(config) {
18602
+ return __awaiter(this, void 0, void 0, function* () {
18603
+ console.info('[install] Check if need to install or upgrade...');
18604
+ try {
18605
+ const installLinks = this.getAllInstallLinks(config);
18606
+ // Check for upgrade
18607
+ this.updateLinks = this.getCompatibleUpgradeLinks(installLinks, config);
18608
+ // Check for install links (if no upgrade need)
18609
+ this.installLinks = !this.updateLinks && this.getCompatibleInstallLinks(installLinks);
18610
+ yield sleep(500); // Add a delay, for animation
18611
+ }
18612
+ finally {
18613
+ this.loading = false;
18614
+ this.markForCheck();
18615
+ }
18616
+ });
18617
+ }
18585
18618
  getCompatibleInstallLinks(installLinks) {
18586
18619
  // Cordova already running: not need to install
18587
18620
  if (this.platform.is('cordova'))
@@ -18593,15 +18626,20 @@ class AppInstallUpgradeCard {
18593
18626
  return undefined;
18594
18627
  }
18595
18628
  getCompatibleUpgradeLinks(installLinks, config) {
18596
- const appMinVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18597
- const needUpgrade = appMinVersion && !VersionUtils.isCompatible(appMinVersion, this.environment.version);
18629
+ const appVersion = this.environment.version;
18630
+ const appMinVersionFromPod = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18631
+ if (!appVersion) {
18632
+ console.error('Missing value for \'environment.version\': cannot check app compatibility!');
18633
+ return undefined;
18634
+ }
18635
+ const needUpgrade = appMinVersionFromPod && !VersionUtils.isCompatible(appMinVersionFromPod, appVersion);
18598
18636
  if (!needUpgrade)
18599
18637
  return undefined;
18600
18638
  const upgradeLinks = installLinks
18601
18639
  .filter(link => this.platform.is('mobileweb') || (link.platform && this.platform.is(link.platform)));
18602
18640
  // Use min version as default version
18603
18641
  upgradeLinks.forEach(link => {
18604
- link.version = link.version || appMinVersion;
18642
+ link.version = link.version || appMinVersionFromPod;
18605
18643
  });
18606
18644
  return isNotEmptyArray(upgradeLinks) ? upgradeLinks : undefined;
18607
18645
  }
@@ -18613,6 +18651,12 @@ class AppInstallUpgradeCard {
18613
18651
  let url = config.getProperty(CORE_CONFIG_OPTIONS.ANDROID_INSTALL_URL);
18614
18652
  if (isNilOrBlank(url))
18615
18653
  url = this.environment.defaultAndroidInstallUrl || null;
18654
+ // Resolve relative URL
18655
+ const peer = this.network.peer;
18656
+ if (peer && (url.startsWith('./') || url.startsWith('/'))) {
18657
+ url = Peer.path(peer, url);
18658
+ }
18659
+ const minVersion = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18616
18660
  // Compute App name
18617
18661
  const name = isNotNilOrBlank(url) && config.label || this.environment.defaultAppName || 'SUMARiS';
18618
18662
  if (url) {
@@ -18621,7 +18665,7 @@ class AppInstallUpgradeCard {
18621
18665
  let mimeType;
18622
18666
  // Get file name
18623
18667
  const filename = this.getFilename(url);
18624
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18668
+ version = minVersion || 'latest';
18625
18669
  // OK, this is a downloadable APK file (e.g. NOT a link to a playstore)
18626
18670
  if (filename === null || filename === void 0 ? void 0 : filename.endsWith('.apk')) {
18627
18671
  // Define mime type
@@ -18631,13 +18675,12 @@ class AppInstallUpgradeCard {
18631
18675
  version = versionMatches && versionMatches[1] || version;
18632
18676
  // Compute a new file name, with the version
18633
18677
  if (isNotNilOrBlank(name)) {
18634
- downloadFilename = `${name}-${version}.apk`;
18678
+ downloadFilename = `${name}-v${version}.apk`.toLowerCase();
18635
18679
  }
18636
18680
  else {
18637
18681
  downloadFilename = filename;
18638
- // Replace 'latest' with the app min version
18682
+ // Replace 'latest' with the app version, if present
18639
18683
  if (downloadFilename.indexOf('latest')) {
18640
- version = config.getProperty(CORE_CONFIG_OPTIONS.APP_MIN_VERSION);
18641
18684
  downloadFilename = downloadFilename.replace('latest', version);
18642
18685
  }
18643
18686
  }
@@ -18666,11 +18709,38 @@ class AppInstallUpgradeCard {
18666
18709
  markForCheck() {
18667
18710
  this.cd.markForCheck();
18668
18711
  }
18712
+ showToast(opts) {
18713
+ return __awaiter(this, void 0, void 0, function* () {
18714
+ yield Toasts.show(this.toastController, this.translate, opts);
18715
+ });
18716
+ }
18717
+ showDownloadCompleteDialog() {
18718
+ return __awaiter(this, void 0, void 0, function* () {
18719
+ const translations = yield this.translate.get([
18720
+ 'INFO.ALERT_HEADER',
18721
+ 'INFO.DOWNLOAD_UPDATE_SUCCEED',
18722
+ 'COMMON.BTN_CLOSE'
18723
+ ]).toPromise();
18724
+ const alert = yield this.alertController.create({
18725
+ header: translations['INFO.ALERT_HEADER'],
18726
+ message: translations['INFO.DOWNLOAD_UPDATE_SUCCEED'],
18727
+ buttons: [
18728
+ {
18729
+ text: translations['COMMON.BTN_CLOSE'],
18730
+ role: 'cancel',
18731
+ cssClass: 'secondary'
18732
+ }
18733
+ ]
18734
+ });
18735
+ yield alert.present();
18736
+ yield alert.onDidDismiss();
18737
+ });
18738
+ }
18669
18739
  }
18670
18740
  AppInstallUpgradeCard.decorators = [
18671
18741
  { type: Component, args: [{
18672
18742
  selector: 'app-install-upgrade-card',
18673
- template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n <!-- Waiting spinner -->\n <ng-template #waitingSpinner>\n <ion-spinner *ngIf=\"waitingNetwork\" color=\"light\"></ion-spinner>\n </ng-template>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && updateLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of updateLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n\n <!-- Download button -->\n <ion-button *ngIf=\"link.downloadFilename; else redirectButton\"\n [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.downloadFilename; else redirectButton\">\n\n <!-- Cordova download button -->\n <ion-button *ngIf=\"isAndroidCordova; else webDownloadButton\"\n (click)=\"downloadLink($event, asLink(link))\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n",
18743
+ template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && updateLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of updateLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.downloadFilename; else redirectButton\">\n\n <ng-container *ngIf=\"platform.canDownload; else webDownloadButton\">\n <!-- Download using platform -->\n <ion-button *ngIf=\"!downloading; else waitingSpinner\"\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"downloading\"\n color=\"tertiary\" class=\"ion-float-end\">\n <ion-icon slot=\"start\" *ngIf=\"!downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-container>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n",
18674
18744
  changeDetection: ChangeDetectionStrategy.OnPush,
18675
18745
  animations: [slideUpDownAnimation],
18676
18746
  styles: ["ion-text small{font-size:85%}"]
@@ -18679,8 +18749,11 @@ AppInstallUpgradeCard.decorators = [
18679
18749
  AppInstallUpgradeCard.ctorParameters = () => [
18680
18750
  { type: ModalController },
18681
18751
  { type: ConfigService },
18682
- { type: PlatformService },
18752
+ { type: ToastController },
18753
+ { type: AlertController },
18754
+ { type: TranslateService },
18683
18755
  { type: ChangeDetectorRef },
18756
+ { type: PlatformService },
18684
18757
  { type: NetworkService },
18685
18758
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
18686
18759
  ];
@@ -23532,6 +23605,7 @@ class UsersPage extends AppTable {
23532
23605
  this.filterCriteriaCount = 0;
23533
23606
  this.statusList = StatusList;
23534
23607
  this.statusById = StatusById;
23608
+ this.useSticky = false;
23535
23609
  this.referentialToString = referentialToString;
23536
23610
  this.inlineEdition = accountService.isAdmin(); // Allow inline edition only if admin
23537
23611
  this.canEdit = accountService.isAdmin();
@@ -23635,13 +23709,13 @@ class UsersPage extends AppTable {
23635
23709
  UsersPage.decorators = [
23636
23710
  { type: Component, args: [{
23637
23711
  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",
23712
+ 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
23713
  providers: [
23640
23714
  { provide: ValidatorService, useExisting: PersonValidatorService }
23641
23715
  ],
23642
23716
  animations: [slideUpDownAnimation],
23643
23717
  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}"]
23718
+ 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
23719
  },] }
23646
23720
  ];
23647
23721
  UsersPage.ctorParameters = () => [
@@ -23661,6 +23735,7 @@ UsersPage.ctorParameters = () => [
23661
23735
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
23662
23736
  ];
23663
23737
  UsersPage.propDecorators = {
23738
+ useSticky: [{ type: Input }],
23664
23739
  filterExpansionPanel: [{ type: ViewChild, args: [MatExpansionPanel, { static: true },] }]
23665
23740
  };
23666
23741
 
@@ -23726,5 +23801,5 @@ const ErrorCodes = {
23726
23801
  * Generated bundle index. Do not edit.
23727
23802
  */
23728
23803
 
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 };
23804
+ 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
23805
  //# sourceMappingURL=sumaris-net.ngx-components.js.map