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