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