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