@sumaris-net/ngx-components 1.20.36 → 1.21.0-beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/bundles/sumaris-net.ngx-components.umd.js +1099 -326
  2. package/bundles/sumaris-net.ngx-components.umd.js.map +1 -1
  3. package/bundles/sumaris-net.ngx-components.umd.min.js +2 -2
  4. package/bundles/sumaris-net.ngx-components.umd.min.js.map +1 -1
  5. package/doc/changelog.md +0 -9
  6. package/esm2015/public_api.js +8 -3
  7. package/esm2015/src/app/core/form/editor.class.js +1 -3
  8. package/esm2015/src/app/core/icon/icon.component.js +5 -2
  9. package/esm2015/src/app/core/menu/menu.component.js +2 -2
  10. package/esm2015/src/app/core/services/local-settings.service.js +2 -2
  11. package/esm2015/src/app/core/table/table.class.js +11 -11
  12. package/esm2015/src/app/shared/functions.js +60 -1
  13. package/esm2015/src/app/shared/material/autocomplete/material.autocomplete.js +4 -5
  14. package/esm2015/src/app/shared/material/autocomplete/testing/autocomplete.test.js +7 -10
  15. package/esm2015/src/app/shared/material/chips/material.chips.js +2 -3
  16. package/esm2015/src/app/shared/material/chips/testing/chips.test.js +2 -3
  17. package/esm2015/src/app/shared/material/datetime/material.date.js +2 -3
  18. package/esm2015/src/app/shared/material/datetime/material.datetime.js +2 -3
  19. package/esm2015/src/app/shared/observables.js +5 -4
  20. package/esm2015/src/app/shared/pipes/arrays.pipe.js +1 -1
  21. package/esm2015/src/app/shared/pipes/form.pipes.js +4 -2
  22. package/esm2015/src/app/shared/pipes/highlight.pipe.js +4 -4
  23. package/esm2015/src/app/shared/pipes/string.pipes.js +18 -7
  24. package/esm2015/src/app/shared/services/memory-entity-service.class.js +1 -13
  25. package/esm2015/src/app/shared/types.js +1 -1
  26. package/esm2015/src/app/social/job/job.model.js +24 -0
  27. package/esm2015/src/app/social/job/job.module.js +25 -0
  28. package/esm2015/src/app/social/job/job.service.js +54 -0
  29. package/esm2015/src/app/social/job/progression/job-progression.component.js +192 -0
  30. package/esm2015/src/app/social/job/progression/job-progression.list.js +20 -0
  31. package/esm2015/src/app/social/message/message.module.js +25 -0
  32. package/esm2015/src/app/social/social.errors.js +5 -2
  33. package/esm2015/src/app/social/social.module.js +10 -15
  34. package/esm2015/src/app/social/user-event/notification/user-event-notification.component.js +95 -0
  35. package/esm2015/src/app/social/user-event/notification/user-event-notification.list.js +39 -0
  36. package/esm2015/src/app/social/user-event/user-event.model.js +58 -2
  37. package/esm2015/src/app/social/user-event/user-event.module.js +28 -0
  38. package/esm2015/src/app/social/user-event/user-event.service.js +294 -166
  39. package/esm2015/src/app/social/user-event/user-events.table.js +17 -12
  40. package/esm2015/sumaris-net.ngx-components.js +8 -6
  41. package/fesm2015/sumaris-net.ngx-components.js +927 -288
  42. package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
  43. package/package.json +2 -3
  44. package/public_api.d.ts +7 -2
  45. package/src/app/core/icon/icon.component.d.ts +4 -4
  46. package/src/app/core/table/table.class.d.ts +6 -8
  47. package/src/app/shared/functions.d.ts +9 -0
  48. package/src/app/shared/material/autocomplete/testing/autocomplete.test.d.ts +2 -1
  49. package/src/app/shared/material/badge/badge-icon.test.d.ts +1 -1
  50. package/src/app/shared/pipes/form.pipes.d.ts +2 -1
  51. package/src/app/shared/services/memory-entity-service.class.d.ts +0 -6
  52. package/src/app/shared/types.d.ts +4 -0
  53. package/src/app/social/job/job.model.d.ts +32 -0
  54. package/src/app/social/job/job.module.d.ts +2 -0
  55. package/src/app/social/job/job.service.d.ts +23 -0
  56. package/src/app/social/job/progression/job-progression.component.d.ts +43 -0
  57. package/src/app/social/job/progression/job-progression.list.d.ts +12 -0
  58. package/src/app/social/message/message.module.d.ts +2 -0
  59. package/src/app/social/social.errors.d.ts +3 -0
  60. package/src/app/social/social.module.d.ts +8 -0
  61. package/src/app/social/user-event/notification/user-event-notification.component.d.ts +25 -0
  62. package/src/app/social/user-event/notification/user-event-notification.list.d.ts +20 -0
  63. package/src/app/social/user-event/user-event.model.d.ts +51 -6
  64. package/src/app/social/user-event/user-event.module.d.ts +2 -0
  65. package/src/app/social/user-event/user-event.service.d.ts +80 -51
  66. package/src/app/social/user-event/user-events.table.d.ts +6 -6
  67. package/src/assets/i18n/en-US.json +15 -2
  68. package/src/assets/i18n/en.json +15 -2
  69. package/src/assets/i18n/fr.json +14 -1
  70. package/src/theme/_ngx-components.scss +7 -0
  71. package/sumaris-net.ngx-components.d.ts +7 -5
  72. package/sumaris-net.ngx-components.metadata.json +1 -1
  73. package/esm2015/src/app/shared/services.js +0 -61
  74. package/src/app/shared/services.d.ts +0 -9
@@ -241,6 +241,64 @@ function changeCaseToUnderscore(value) {
241
241
  function removeDiacritics(text) {
242
242
  return text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
243
243
  }
244
+ function suggestFromArray(items, value, opts) {
245
+ if (isNotNil(value) && typeof value === 'object')
246
+ return { data: [value] };
247
+ value = (typeof value === 'string' && value !== '*') && value.toUpperCase() || undefined;
248
+ const keys = opts && (opts.searchAttribute && [opts.searchAttribute] || opts.searchAttributes) || ['label'];
249
+ if (!items) {
250
+ return { data: [], total: 0 };
251
+ }
252
+ // Filter items
253
+ let filteredItems = items;
254
+ if (isNotNilOrBlank(value)) {
255
+ // If wildcard, search using regexp
256
+ if (value.indexOf('*') !== -1) {
257
+ value = value.replace(/[*]+/g, '.*');
258
+ filteredItems = filteredItems.filter(v => keys.findIndex(key => matchUpperCase(getPropertyByPathAsString(v, key), value)) !== -1);
259
+ }
260
+ // Else (no wildcard), search using startsWith
261
+ else {
262
+ filteredItems = filteredItems.filter(v => keys.findIndex(key => startsWithUpperCase(getPropertyByPathAsString(v, key), value)) !== -1);
263
+ }
264
+ }
265
+ // Exclude ids
266
+ if (isNotEmptyArray(opts === null || opts === void 0 ? void 0 : opts.excludedIds)) {
267
+ filteredItems = filteredItems.filter(v => !opts.excludedIds.includes(+v['id']));
268
+ }
269
+ // Sort
270
+ if (!opts || opts.skipSort !== true) {
271
+ // Make to copy input items, before sorting (if not yet already done)
272
+ if (filteredItems === items)
273
+ filteredItems = filteredItems.slice();
274
+ // Sort by keys
275
+ filteredItems.sort(propertiesPathComparator(keys));
276
+ }
277
+ const total = filteredItems.length || 0;
278
+ // Pagination (truncate if need)
279
+ let pageData = filteredItems;
280
+ if (opts) {
281
+ if ((opts.offset || 0) > 0) {
282
+ pageData = pageData.slice(opts.offset);
283
+ }
284
+ if (isNotNil(opts.size) && pageData.length > opts.size) {
285
+ pageData = pageData.slice(0, opts.size);
286
+ }
287
+ }
288
+ const result = {
289
+ data: pageData,
290
+ total
291
+ };
292
+ // Add fetch more capability, if total was fetched
293
+ const nextOffset = (opts && opts.offset || 0) + pageData.length;
294
+ if (nextOffset < total) {
295
+ result.fetchMore = (_) => __awaiter(this, void 0, void 0, function* () {
296
+ return suggestFromArray(filteredItems, undefined /*OPTIMISATION: already applied*/, Object.assign(Object.assign({}, opts), { offset: nextOffset, skipSort: true, excludedIds: undefined // OPTIMISATION: already filtered
297
+ }));
298
+ });
299
+ }
300
+ return result;
301
+ }
244
302
  function suggestFromStringArray(values, value, options) {
245
303
  value = (typeof value === 'string' && value !== '*') && value.toUpperCase() || undefined;
246
304
  if (isNilOrBlank(value))
@@ -821,11 +879,12 @@ function waitFor(predicate, opts) {
821
879
  }
822
880
  function waitForTrue(observable, opts) {
823
881
  return __awaiter(this, void 0, void 0, function* () {
882
+ const firstTrueObservable = firstTrue(observable);
824
883
  // Timeout (+ dueTime)
825
884
  if (opts && opts.timeout > 0) {
826
885
  const $timeout = timer(opts.timeout);
827
886
  return merge(timer(opts.dueTime || 0)
828
- .pipe(switchMap(() => firstTrue(observable)), takeUntil($timeout)), $timeout
887
+ .pipe(switchMap(() => firstTrueObservable), takeUntil($timeout)), $timeout
829
888
  .pipe(map(() => {
830
889
  throw new Error(`Timeout ready() - after ${opts.timeout}ms`);
831
890
  })))
@@ -834,10 +893,10 @@ function waitForTrue(observable, opts) {
834
893
  // dueTime (without timeout)
835
894
  if (opts && opts.dueTime > 0) {
836
895
  return timer(opts.dueTime)
837
- .pipe(switchMap(() => firstTrue(observable)))
896
+ .pipe(switchMap(() => firstTrueObservable))
838
897
  .toPromise();
839
898
  }
840
- return firstTrue(observable).toPromise();
899
+ return firstTrueObservable.toPromise();
841
900
  });
842
901
  }
843
902
 
@@ -1041,65 +1100,6 @@ const isMobile = (win) => {
1041
1100
  return _cache.mobile;
1042
1101
  };
1043
1102
 
1044
- function suggestFromArray(items, value, opts) {
1045
- if (isNotNil(value) && typeof value === 'object')
1046
- return { data: [value] };
1047
- value = (typeof value === 'string' && value !== '*') && value.toUpperCase() || undefined;
1048
- const keys = opts && (opts.searchAttribute && [opts.searchAttribute] || opts.searchAttributes) || ['label'];
1049
- if (!items) {
1050
- return { data: [], total: 0 };
1051
- }
1052
- // Filter items
1053
- let filteredItems = items;
1054
- if (isNotNilOrBlank(value)) {
1055
- // If wildcard, search using regexp
1056
- if (value.indexOf('*') !== -1) {
1057
- value = value.replace(/[*]+/g, '.*');
1058
- filteredItems = filteredItems.filter(v => keys.findIndex(key => matchUpperCase(getPropertyByPathAsString(v, key), value)) !== -1);
1059
- }
1060
- // Else (no wildcard), search using startsWith
1061
- else {
1062
- filteredItems = filteredItems.filter(v => keys.findIndex(key => startsWithUpperCase(getPropertyByPathAsString(v, key), value)) !== -1);
1063
- }
1064
- }
1065
- // Exclude ids
1066
- if (isNotEmptyArray(opts === null || opts === void 0 ? void 0 : opts.excludedIds)) {
1067
- filteredItems = filteredItems.filter(v => !opts.excludedIds.includes(+v['id']));
1068
- }
1069
- // Sort
1070
- if (!opts || opts.skipSort !== true) {
1071
- // Make to copy input items, before sorting (if not yet already done)
1072
- if (filteredItems === items)
1073
- filteredItems = filteredItems.slice();
1074
- // Sort by keys
1075
- filteredItems.sort(propertiesPathComparator(keys));
1076
- }
1077
- const total = filteredItems.length || 0;
1078
- // Pagination (truncate if need)
1079
- let pageData = filteredItems;
1080
- if (opts) {
1081
- if ((opts.offset || 0) > 0) {
1082
- pageData = pageData.slice(opts.offset);
1083
- }
1084
- if (isNotNil(opts.size) && pageData.length > opts.size) {
1085
- pageData = pageData.slice(0, opts.size);
1086
- }
1087
- }
1088
- const result = {
1089
- data: pageData,
1090
- total
1091
- };
1092
- // Add fetch more capability, if total was fetched
1093
- const nextOffset = (opts && opts.offset || 0) + pageData.length;
1094
- if (nextOffset < total) {
1095
- result.fetchMore = (_) => __awaiter(this, void 0, void 0, function* () {
1096
- return suggestFromArray(filteredItems, undefined /*OPTIMISATION: already applied*/, Object.assign(Object.assign({}, opts), { offset: nextOffset, skipSort: true, excludedIds: undefined // OPTIMISATION: already filtered
1097
- }));
1098
- });
1099
- }
1100
- return result;
1101
- }
1102
-
1103
1103
  class MatAutocompleteConfigHolder {
1104
1104
  constructor(options) {
1105
1105
  this.options = options;
@@ -1360,11 +1360,11 @@ class MatAutocompleteField {
1360
1360
  // Search text (no distinctUntilChanged() because of the debounceTime() pipe)
1361
1361
  this.formControl.valueChanges
1362
1362
  .pipe(filter(value => this.enabled && isNotNilString(value)),
1363
- // Mark as loading (to show loading spinner) - as soon as possible, and before debounceTime
1363
+ // Mark as loading (to show loading spinner) - earlier as possible, so before the debounce time
1364
1364
  tap(_ => this.showLoadingSpinner && this.markAsLoading()),
1365
1365
  // Filter if not enough character
1366
1366
  filter(value => {
1367
- if (((value === null || value === void 0 ? void 0 : value.length) || 0) < this.suggestLengthThreshold) {
1367
+ if (value.length <= this.suggestLengthThreshold) {
1368
1368
  // Update display value, to enable the message "not enough characters"
1369
1369
  this.displayValue = value;
1370
1370
  return false;
@@ -2328,9 +2328,9 @@ class HighlightPipe {
2328
2328
  const cleanedSearchText = removeDiacritics(searchRegexp).toUpperCase();
2329
2329
  const index = removeDiacritics(value.toUpperCase()).indexOf(cleanedSearchText);
2330
2330
  if (index !== -1) {
2331
- return value.substring(0, index - 1) +
2332
- '<b>' + value.substring(index, cleanedSearchText.length) + '</b>' +
2333
- value.substring(index + cleanedSearchText.length);
2331
+ return value.substr(0, index - 1) +
2332
+ '<b>' + value.substr(index, cleanedSearchText.length) + '</b>' +
2333
+ value.substr(index + cleanedSearchText.length);
2334
2334
  }
2335
2335
  return value;
2336
2336
  }
@@ -2728,50 +2728,60 @@ class IsNotNilOrBlankPipe {
2728
2728
  return isNotNilOrBlank(value);
2729
2729
  }
2730
2730
  }
2731
+ IsNotNilOrBlankPipe.ɵprov = ɵɵdefineInjectable({ factory: function IsNotNilOrBlankPipe_Factory() { return new IsNotNilOrBlankPipe(); }, token: IsNotNilOrBlankPipe, providedIn: "root" });
2731
2732
  IsNotNilOrBlankPipe.decorators = [
2732
2733
  { type: Pipe, args: [{
2733
2734
  name: 'isNotNilOrBlank'
2734
- },] }
2735
+ },] },
2736
+ { type: Injectable, args: [{ providedIn: 'root' },] }
2735
2737
  ];
2736
2738
  class IsNilOrBlankPipe {
2737
2739
  transform(value) {
2738
2740
  return isNilOrBlank(value);
2739
2741
  }
2740
2742
  }
2743
+ IsNilOrBlankPipe.ɵprov = ɵɵdefineInjectable({ factory: function IsNilOrBlankPipe_Factory() { return new IsNilOrBlankPipe(); }, token: IsNilOrBlankPipe, providedIn: "root" });
2741
2744
  IsNilOrBlankPipe.decorators = [
2742
2745
  { type: Pipe, args: [{
2743
2746
  name: 'isNilOrBlank'
2744
- },] }
2747
+ },] },
2748
+ { type: Injectable, args: [{ providedIn: 'root' },] }
2745
2749
  ];
2746
2750
  class ToStringPipe {
2747
2751
  transform(value) {
2748
2752
  return (value !== null && value !== undefined) ? value.toString() : '';
2749
2753
  }
2750
2754
  }
2755
+ ToStringPipe.ɵprov = ɵɵdefineInjectable({ factory: function ToStringPipe_Factory() { return new ToStringPipe(); }, token: ToStringPipe, providedIn: "root" });
2751
2756
  ToStringPipe.decorators = [
2752
2757
  { type: Pipe, args: [{
2753
2758
  name: 'toString'
2754
- },] }
2759
+ },] },
2760
+ { type: Injectable, args: [{ providedIn: 'root' },] }
2755
2761
  ];
2756
2762
  class StrLengthPipe {
2757
2763
  transform(value) {
2758
2764
  return value && value.length || 0;
2759
2765
  }
2760
2766
  }
2767
+ StrLengthPipe.ɵprov = ɵɵdefineInjectable({ factory: function StrLengthPipe_Factory() { return new StrLengthPipe(); }, token: StrLengthPipe, providedIn: "root" });
2761
2768
  StrLengthPipe.decorators = [
2762
2769
  { type: Pipe, args: [{
2763
2770
  name: 'strLength'
2764
- },] }
2771
+ },] },
2772
+ { type: Injectable, args: [{ providedIn: 'root' },] }
2765
2773
  ];
2766
2774
  class StrIncludesPipe {
2767
2775
  transform(value, searchString, position) {
2768
2776
  return (value === null || value === void 0 ? void 0 : value.includes(searchString, position)) || false;
2769
2777
  }
2770
2778
  }
2779
+ StrIncludesPipe.ɵprov = ɵɵdefineInjectable({ factory: function StrIncludesPipe_Factory() { return new StrIncludesPipe(); }, token: StrIncludesPipe, providedIn: "root" });
2771
2780
  StrIncludesPipe.decorators = [
2772
2781
  { type: Pipe, args: [{
2773
2782
  name: 'strIncludes'
2774
- },] }
2783
+ },] },
2784
+ { type: Injectable, args: [{ providedIn: 'root' },] }
2775
2785
  ];
2776
2786
 
2777
2787
  class TranslateContextService {
@@ -4222,7 +4232,8 @@ FormGetGroupPipe.decorators = [
4222
4232
  },] }
4223
4233
  ];
4224
4234
  class FormGetValuePipe {
4225
- constructor(_ref) {
4235
+ constructor(adapter, _ref) {
4236
+ this.adapter = adapter;
4226
4237
  this._ref = _ref;
4227
4238
  this.value = undefined;
4228
4239
  this._lastControl = null;
@@ -4287,6 +4298,7 @@ FormGetValuePipe.decorators = [
4287
4298
  },] }
4288
4299
  ];
4289
4300
  FormGetValuePipe.ctorParameters = () => [
4301
+ { type: FormErrorTranslator },
4290
4302
  { type: ChangeDetectorRef }
4291
4303
  ];
4292
4304
 
@@ -5157,8 +5169,7 @@ class MatDate {
5157
5169
  set tabindex(value) {
5158
5170
  if (this._tabindex !== value) {
5159
5171
  this._tabindex = value;
5160
- if (!this._writing)
5161
- this.updateTabIndex();
5172
+ setTimeout(() => this.updateTabIndex());
5162
5173
  }
5163
5174
  }
5164
5175
  get tabindex() {
@@ -5517,8 +5528,7 @@ class MatDateTime {
5517
5528
  set tabindex(value) {
5518
5529
  if (this._tabindex !== value) {
5519
5530
  this._tabindex = value;
5520
- if (!this._writing)
5521
- this.updateTabIndex();
5531
+ setTimeout(() => this.updateTabIndex());
5522
5532
  }
5523
5533
  }
5524
5534
  get tabindex() {
@@ -10697,7 +10707,7 @@ class LocalSettingsService extends StartableService {
10697
10707
  }
10698
10708
  get usageMode() {
10699
10709
  var _a;
10700
- if (isNil((_a = this._data) === null || _a === void 0 ? void 0 : _a.usageMode)) {
10710
+ if (isNil((_a = this._data) === null || _a === void 0 ? void 0 : _a.mobile)) {
10701
10711
  console.warn("[settings] Accessing to property 'usageMode' BEFORE service started! Please use ready()");
10702
10712
  return isCordova(window) ? 'FIELD' : 'DESK';
10703
10713
  }
@@ -18203,18 +18213,6 @@ class InMemoryEntitiesService extends EntitiesService {
18203
18213
  return this._equalsFn(d1, d2);
18204
18214
  return d1 && d1.equals ? d1.equals(d2) : EntityUtils.equals(d1, d2, 'id');
18205
18215
  }
18206
- /**
18207
- * Count data stored by the service (without hidden data).
18208
- * @param opts
18209
- */
18210
- get count() {
18211
- var _a;
18212
- return (((_a = this.data) === null || _a === void 0 ? void 0 : _a.length) || 0);
18213
- }
18214
- get hiddenCount() {
18215
- var _a;
18216
- return ((_a = this._hiddenData) === null || _a === void 0 ? void 0 : _a.length) || 0;
18217
- }
18218
18216
  /* -- protected methods -- */
18219
18217
  filter(data, _filter, hiddenData) {
18220
18218
  // if filter is DataFilter instance, use its test function
@@ -21433,7 +21431,7 @@ class MenuComponent {
21433
21431
  MenuComponent.decorators = [
21434
21432
  { type: Component, args: [{
21435
21433
  selector: 'app-menu',
21436
- template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
21434
+ template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n<!-- todo ajouter ici les 2 composants du module social -->\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
21437
21435
  animations: [fadeInAnimation],
21438
21436
  changeDetection: ChangeDetectionStrategy.OnPush,
21439
21437
  styles: ["ion-menu{--ion-item-background:transparent;--ion-item-divider-background:transparent;--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint);--ion-item-background-selected:var(--ion-color-secondary100);--ion-item-text-color-selected:var(--ion-color-primary);--ion-item-icon-color-selected:var(--ion-color-primary);--ion-item-text-color-disable:var(--ion-color-medium);--ion-item-icon-color-disable:var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:50%;background-color:var(--ion-color-secondary);border:1px solid var(--ion-color-primary);overflow:hidden!important;font-size:var(--avatar-size,60px)!important;line-height:var(--avatar-size,60px);height:var(--avatar-size,60px)!important;width:var(--avatar-size,60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size,60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top:188px}ion-menu ion-content.no-profile-header{--offset-top:79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color:var(--ion-color-secondary-tint);--ion-item-text-color:var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color:var(--ion-color-tertiary-tint);--ion-item-text-color:var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color:var(--ion-color-danger-tint);--ion-item-text-color:var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color:var(--ion-color-medium-shade);--ion-item-text-color:var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color:var(--ion-color-dark-tint);--ion-item-text-color:var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item ion-icon,ion-menu ion-content ion-list ion-item mat-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-label,ion-menu ion-content ion-list ion-item ion-text{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover:var(--ion-item-background-selected)!important}ion-menu ion-content ion-list ion-item.selected ion-icon,ion-menu ion-content ion-list ion-item.selected mat-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-label,ion-menu ion-content ion-list ion-item.selected ion-text{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover:var(--ion-item-background-selected)!important;--ion-item-icon-color-selected:var(--ion-item-icon-color)!important;--ion-item-text-color-selected:var(--ion-item-text-color)!important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width:40px}@media screen and (max-width:767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width:768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size,80px)!important;line-height:var(--avatar-size,80px);height:var(--avatar-size,80px)!important;width:var(--avatar-size,80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size,80px)}}"]
@@ -21497,13 +21495,14 @@ class AppIconComponent {
21497
21495
  this.icon = value.icon;
21498
21496
  this.matIcon = value.matIcon;
21499
21497
  this.matSvgIcon = value.matSvgIcon;
21498
+ this.color = value.color;
21500
21499
  }
21501
21500
  ;
21502
21501
  }
21503
21502
  AppIconComponent.decorators = [
21504
21503
  { type: Component, args: [{
21505
21504
  selector: 'app-icon',
21506
- template: "<ion-icon *ngIf=\"icon; else matIconTemplate\" slot=\"icon-only\"\n [color]=\"color\"\n [name]=\"icon\"\n></ion-icon>\n<ng-template #matIconTemplate>\n <mat-icon [svgIcon]=\"matSvgIcon\"\n [style.color]=\"'var(--ion-color-'+color+')'\">{{matIcon}}</mat-icon>\n</ng-template>\n\n"
21505
+ template: "<ion-icon *ngIf=\"icon; else matIconTemplate\" slot=\"icon-only\"\n [color]=\"color\"\n [name]=\"icon\"\n [style.height.px]=\"height\"\n [style.width.px]=\"width\"\n></ion-icon>\n<ng-template #matIconTemplate>\n <mat-icon [svgIcon]=\"matSvgIcon\"\n [style.color]=\"'var(--ion-color-'+color+')'\"\n [style.height.px]=\"height\"\n [style.width.px]=\"width\"\n [style.font-size.px]=\"height || width\"\n >{{matIcon}}</mat-icon>\n</ng-template>\n\n"
21507
21506
  },] }
21508
21507
  ];
21509
21508
  AppIconComponent.propDecorators = {
@@ -21511,6 +21510,8 @@ AppIconComponent.propDecorators = {
21511
21510
  matIcon: [{ type: Input }],
21512
21511
  matSvgIcon: [{ type: Input }],
21513
21512
  color: [{ type: Input }],
21513
+ height: [{ type: Input }],
21514
+ width: [{ type: Input }],
21514
21515
  ref: [{ type: Input }]
21515
21516
  };
21516
21517
 
@@ -23012,7 +23013,7 @@ class AppTable {
23012
23013
  .then(() => this.editRowById(event, row.id, { focusColumn: this.lastUserColumn }));
23013
23014
  return true;
23014
23015
  }
23015
- // Edit previous row
23016
+ // Edit next row
23016
23017
  this.editRow(event, row, { focusColumn: this.lastUserColumn });
23017
23018
  return true;
23018
23019
  }
@@ -23035,7 +23036,8 @@ class AppTable {
23035
23036
  if (id < 0)
23036
23037
  return;
23037
23038
  if (id >= this.visibleRowCount) {
23038
- this.addRow(event, undefined, opts);
23039
+ this.focusColumn = opts && opts.focusColumn || this.firstUserColumn;
23040
+ this.addRow(event);
23039
23041
  }
23040
23042
  else {
23041
23043
  const row = yield this.dataSource.getRow(id);
@@ -23108,7 +23110,7 @@ class AppTable {
23108
23110
  this.cancelExistingRow(event, row, opts);
23109
23111
  }
23110
23112
  }
23111
- addRow(event, insertAt, opts) {
23113
+ addRow(event, insertAt) {
23112
23114
  if (this.debug)
23113
23115
  console.debug('[table] Asking for new row...');
23114
23116
  if (!this._enabled)
@@ -23123,7 +23125,7 @@ class AppTable {
23123
23125
  return false;
23124
23126
  }
23125
23127
  // Add new row
23126
- this.addRowToTable(insertAt, opts);
23128
+ this.addRowToTable(insertAt);
23127
23129
  return true;
23128
23130
  }
23129
23131
  save(opts) {
@@ -23238,7 +23240,7 @@ class AppTable {
23238
23240
  * @param event
23239
23241
  * @param row
23240
23242
  * @param opts Use interactive=false to avoid user interaction (e.g. user confirmation)
23241
- * And to force deletion even if table is busy
23243
+ * Use skipIfLoading=false to force deletion even if table is busy
23242
23244
  */
23243
23245
  deleteRow(event, row, opts) {
23244
23246
  return __awaiter(this, void 0, void 0, function* () {
@@ -23251,7 +23253,7 @@ class AppTable {
23251
23253
  * @param event
23252
23254
  * @param rows
23253
23255
  * @param opts Use interactive=false to avoid user interaction (e.g. user confirmation)
23254
- * And to force deletion even if table is busy
23256
+ * Use skipIfLoading=false to force deletion even if table is busy
23255
23257
  */
23256
23258
  deleteRows(event, rows, opts) {
23257
23259
  var _a;
@@ -23264,8 +23266,8 @@ class AppTable {
23264
23266
  event === null || event === void 0 ? void 0 : event.preventDefault();
23265
23267
  if (!this._enabled || isEmptyArray(rows))
23266
23268
  return 0; // Skip is disabled, or no rows to delete
23267
- if (this.loading && (!opts || opts.interactive !== false)) {
23268
- console.warn('[app-table] Skip deleteRows() because table is busy (loading). Use opts.interactive = false to force deletion');
23269
+ if (this.loading && (!opts || opts.skipIfLoading !== false)) {
23270
+ console.warn('[app-table] Skip deleteRows() because table is busy (loading). Use opts.skipLoading = false to force deletion');
23269
23271
  return 0; // Skip if loading
23270
23272
  }
23271
23273
  // Make sure to keep newly created row
@@ -23788,10 +23790,9 @@ class AppTable {
23788
23790
  //if (this.debug) console.debug("[table] id = " + id);
23789
23791
  return id;
23790
23792
  }
23791
- addRowToTable(insertAt, opts) {
23793
+ addRowToTable(insertAt) {
23792
23794
  return __awaiter(this, void 0, void 0, function* () {
23793
23795
  this.focusFirstColumn = true;
23794
- this.focusColumn = (opts === null || opts === void 0 ? void 0 : opts.focusColumn) || this.firstUserColumn;
23795
23796
  yield this._dataSource.asyncCreateNew(insertAt);
23796
23797
  this.editedRow = this._dataSource.getRow(-1);
23797
23798
  // Emit start editing event
@@ -24307,8 +24308,6 @@ class AppEditor {
24307
24308
  }
24308
24309
  ready(opts) {
24309
24310
  return __awaiter(this, void 0, void 0, function* () {
24310
- if (this._$ready.value)
24311
- return;
24312
24311
  return waitForTrue(this._$ready, opts);
24313
24312
  });
24314
24313
  }
@@ -25824,7 +25823,10 @@ AppInMemoryTable.propDecorators = {
25824
25823
  const SocialErrorCodes = {
25825
25824
  LOAD_USER_EVENTS_ERROR: 50000,
25826
25825
  SAVE_USER_EVENT_ERROR: 50001,
25827
- SEND_MESSAGE_ERROR: 50003
25826
+ COUNT_USER_EVENT_ERROR: 50002,
25827
+ SEND_MESSAGE_ERROR: 50003,
25828
+ SUBSCRIBE_USER_EVENTS_ERROR: 50005,
25829
+ SUBSCRIBE_JOB_PROGRESSION_ERROR: 50010
25828
25830
  };
25829
25831
 
25830
25832
  var UserEvent_1;
@@ -25839,16 +25841,23 @@ let UserEvent = UserEvent_1 = class UserEvent extends Entity {
25839
25841
  }
25840
25842
  asObject(opts) {
25841
25843
  const target = super.asObject(opts);
25844
+ target.creationDate = toDateISOString(this.creationDate);
25845
+ target.readDate = toDateISOString(this.readDate);
25842
25846
  // Serialize content
25843
25847
  if (typeof target.content === 'object') {
25844
25848
  target.content = JSON.stringify(target.content);
25845
25849
  }
25850
+ delete target.avatar;
25851
+ delete target.avatarIcon;
25852
+ delete target.icon;
25853
+ delete target.actions;
25846
25854
  return target;
25847
25855
  }
25848
25856
  fromObject(source) {
25849
25857
  Object.assign(this, source); // Copy all properties
25850
25858
  super.fromObject(source);
25851
25859
  this.creationDate = fromDateISOString(source.creationDate);
25860
+ this.readDate = fromDateISOString(source.readDate);
25852
25861
  try {
25853
25862
  // Deserialize content
25854
25863
  if (typeof source.content === 'string' && source.content.startsWith('{')) {
@@ -25867,6 +25876,52 @@ let UserEvent = UserEvent_1 = class UserEvent extends Entity {
25867
25876
  UserEvent = UserEvent_1 = __decorate([
25868
25877
  EntityClass({ typename: 'UserEventVO' })
25869
25878
  ], UserEvent);
25879
+ // @dynamic
25880
+ let UserEventFilter = class UserEventFilter extends EntityFilter {
25881
+ constructor() {
25882
+ super(...arguments);
25883
+ this.types = [];
25884
+ this.levels = [];
25885
+ this.issuers = [];
25886
+ this.recipients = [];
25887
+ this.startDate = null;
25888
+ this.excludeRead = false;
25889
+ }
25890
+ fromObject(source, opts) {
25891
+ super.fromObject(source, opts);
25892
+ this.types = source.types || [];
25893
+ this.levels = source.levels || [];
25894
+ this.issuers = source.issuers || [];
25895
+ this.recipients = source.recipients || [];
25896
+ this.startDate = fromDateISOString(source.startDate);
25897
+ this.excludeRead = source.excludeRead || false;
25898
+ }
25899
+ buildFilter() {
25900
+ const filterFns = super.buildFilter();
25901
+ if (isNotEmptyArray(this.types)) {
25902
+ filterFns.push(t => this.types.includes(t.type));
25903
+ }
25904
+ if (isNotEmptyArray(this.levels)) {
25905
+ filterFns.push(t => this.levels.includes(t.level));
25906
+ }
25907
+ if (isNotEmptyArray(this.issuers)) {
25908
+ filterFns.push(t => this.issuers.includes(t.issuer));
25909
+ }
25910
+ if (isNotEmptyArray(this.recipients)) {
25911
+ filterFns.push(t => this.recipients.includes(t.recipient));
25912
+ }
25913
+ if (isNotNil(this.startDate)) {
25914
+ filterFns.push(t => this.startDate.isSameOrBefore(t.creationDate));
25915
+ }
25916
+ if (this.excludeRead === true) {
25917
+ filterFns.push(t => isNil(t.readSignature)); // todo or t.signature ?
25918
+ }
25919
+ return filterFns;
25920
+ }
25921
+ };
25922
+ UserEventFilter = __decorate([
25923
+ EntityClass({ typename: 'UserEventFilterVO' })
25924
+ ], UserEventFilter);
25870
25925
 
25871
25926
  const UserEventFragments = {
25872
25927
  userEvent: gql `fragment UserEventFragment on UserEventVO {
@@ -25894,179 +25949,159 @@ const UserEventFragments = {
25894
25949
  }`
25895
25950
  };
25896
25951
 
25897
- // @dynamic
25898
- let UserEventFilter = class UserEventFilter extends EntityFilter {
25899
- constructor() {
25900
- super(...arguments);
25901
- this.issuer = null;
25902
- this.recipient = null;
25903
- }
25904
- fromObject(source, opts) {
25905
- super.fromObject(source, opts);
25906
- this.issuer = source.issuer;
25907
- this.recipient = source.recipient;
25908
- }
25909
- buildFilter() {
25910
- const filterFns = super.buildFilter();
25911
- // issuer
25912
- if (this.issuer) {
25913
- filterFns.push(t => (t.issuer === this.issuer));
25914
- }
25915
- if (this.recipient) {
25916
- filterFns.push(t => (t.recipient === this.recipient));
25917
- }
25918
- return filterFns;
25919
- }
25920
- };
25921
- UserEventFilter = __decorate([
25922
- EntityClass({ typename: 'UserEventFilterVO' })
25923
- ], UserEventFilter);
25924
- const SaveQuery = gql `
25925
- mutation SaveUserEvent($data: UserEventVOInput){
25926
- data: saveUserEvent(userEvent: $data){
25927
- ...UserEventFragment
25928
- }
25929
- }
25930
- ${UserEventFragments.userEvent}
25931
- `;
25932
- const LoadAllQuery = gql `
25933
- query UserEvents($filter: UserEventFilterVOInput, $page: PageInput){
25934
- data: userEvents(filter: $filter, page: $page){
25935
- ...LightUserEventFragment
25936
- }
25937
- }
25938
- ${UserEventFragments.lightUserEvent}
25939
- `;
25940
- const DeleteByIdsMutation = gql `
25941
- mutation DeleteUserEvents($ids:[Int]){
25942
- deleteUserEvents(ids: $ids)
25943
- }
25944
- `;
25945
- const LoadAllWithContentQuery = gql `
25946
- query UserEventsWithContent($filter: UserEventFilterVOInput, $page: PageInput){
25947
- data: userEvents(filter: $filter, page: $page){
25948
- ...UserEventFragment
25949
- }
25950
- }
25951
- ${UserEventFragments.userEvent}
25952
- `;
25953
- class UserEventService extends BaseGraphqlService {
25954
- constructor(graphql, accountService, network, translate, toastController, environment) {
25952
+ const moment$6 = momentImported;
25953
+ const UserEventServiceToken = new InjectionToken('UserEventService');
25954
+ class AbstractUserEventService extends BaseGraphqlService {
25955
+ constructor(graphql, accountService, network, translate, toastController, queries, mutations, subscriptions, environment) {
25955
25956
  super(graphql, environment);
25956
25957
  this.graphql = graphql;
25957
25958
  this.accountService = accountService;
25958
25959
  this.network = network;
25959
25960
  this.translate = translate;
25960
25961
  this.toastController = toastController;
25962
+ this.queries = queries;
25963
+ this.mutations = mutations;
25964
+ this.subscriptions = subscriptions;
25961
25965
  this.environment = environment;
25962
- this._userEventActions = [];
25966
+ this._count$ = new BehaviorSubject(0);
25967
+ this._subscriptions = new Subscription();
25968
+ this.listeners = [];
25969
+ this._logPrefix = '[user-event-service] ';
25963
25970
  // For DEV only
25964
- this._debug = environment && !environment.production;
25971
+ this._debug = !(environment === null || environment === void 0 ? void 0 : environment.production);
25972
+ // listen login/logout
25973
+ this._subscriptions.add(accountService.onLogin.subscribe(() => __awaiter(this, void 0, void 0, function* () {
25974
+ const count = yield this.count(this.defaultFilter());
25975
+ if (count && this._debug) {
25976
+ console.debug(`${this._logPrefix}Received ${count} unread notifications`);
25977
+ }
25978
+ this._count$.next(count);
25979
+ })));
25980
+ }
25981
+ get userEventCount() {
25982
+ return this._count$.asObservable();
25983
+ }
25984
+ ngOnDestroy() {
25985
+ this._subscriptions.unsubscribe();
25965
25986
  }
25966
- /**
25967
- *
25968
- * @param offset
25969
- * @param size
25970
- * @param sortBy
25971
- * @param sortDirection
25972
- * @param filter
25973
- * @param options
25974
- * @deprecated use watchPage() instead
25975
- */
25976
25987
  watchAll(offset, size, sortBy, sortDirection, filter, options) {
25977
25988
  return this.watchPage({ offset, size, sortBy, sortDirection }, filter, options);
25978
25989
  }
25979
25990
  watchPage(page, filter, options) {
25991
+ var _a;
25980
25992
  let now = this._debug && Date.now();
25981
25993
  //if (this._debug)
25982
- console.debug('[user-event-service] Loading user events...', filter);
25994
+ console.debug(`${this._logPrefix}Loading user events...`, filter);
25983
25995
  filter = this.asFilter(filter);
25984
25996
  // Force recipient to current issuer, if not admin and not specified
25985
- if (isNilOrBlank(filter.recipient) || !this.accountService.isAdmin()) {
25986
- const recipient = this.accountService.account.pubkey;
25987
- if (recipient !== filter.recipient) {
25997
+ if (isEmptyArray(filter.recipients) || !this.accountService.isAdmin()) {
25998
+ const recipient = this.defaultRecipient();
25999
+ if (!((_a = filter.recipients) === null || _a === void 0 ? void 0 : _a.includes(recipient))) {
25988
26000
  console.warn('[user-events-service] Force user event filter.recipient=' + recipient);
25989
- filter.recipient = recipient;
26001
+ filter.recipients = [recipient];
25990
26002
  }
25991
26003
  }
25992
- const withContent = options && options.withContent === true;
26004
+ const withContent = (options === null || options === void 0 ? void 0 : options.withContent) === true;
25993
26005
  return this.mutableWatchQuery({
25994
26006
  queryName: withContent ? 'LoadAllWithContent' : 'LoadAll',
25995
- query: withContent ? LoadAllWithContentQuery : LoadAllQuery,
26007
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
25996
26008
  variables: {
25997
- page: Object.assign(Object.assign({ sortBy: 'updateDate' }, page), { sortDirection: (page.sortDirection || 'DESC').toUpperCase() }),
26009
+ page: Object.assign({ sortBy: 'updateDate', sortDirection: 'desc' }, page),
25998
26010
  filter: filter && filter.asPodObject()
25999
26011
  },
26000
26012
  arrayFieldName: 'data',
26001
26013
  error: { code: SocialErrorCodes.LOAD_USER_EVENTS_ERROR, message: 'SOCIAL.ERROR.LOAD_USER_EVENTS_ERROR' },
26002
- fetchPolicy: options && options.fetchPolicy || undefined
26014
+ fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || "cache-first"
26003
26015
  })
26004
26016
  .pipe(map(res => {
26005
- const data = res && (res.data || []).map(UserEvent.fromObject);
26017
+ const data = ((res === null || res === void 0 ? void 0 : res.data) || []).map(this.processUserEvent);
26006
26018
  if (now) {
26007
- console.debug(`[user-event-service] ${data.length} user events loaded in ${Date.now() - now}ms`);
26019
+ console.debug(`${this._logPrefix}${data.length} user events loaded in ${Date.now() - now}ms`);
26008
26020
  now = null;
26009
26021
  }
26022
+ const unreadCount = this.unreadEvents(data);
26010
26023
  return {
26011
26024
  data,
26012
- total: res && toNumber(res.total, data.length)
26025
+ total: toNumber(res === null || res === void 0 ? void 0 : res.total, unreadCount)
26013
26026
  };
26014
26027
  }));
26015
26028
  }
26016
- saveAll(data, options) {
26017
- return Promise.all(data
26018
- .map(entity => this.save(entity, options)));
26029
+ add(entity) {
26030
+ // TODO set an id !!!!!!!!!!!!!!!!!!!!!!!!!
26031
+ entity = this.processUserEvent(entity);
26032
+ const withContent = !!entity.content;
26033
+ // Add user event locally
26034
+ this.insertIntoMutableCachedQueries(this.graphql.cache, {
26035
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26036
+ data: entity
26037
+ });
26038
+ // Update count
26039
+ this._count$.next(this._count$.value + 1);
26019
26040
  }
26020
- /**
26021
- * Save a userEvent entity
26022
- *
26023
- * @param entity
26024
- */
26025
- save(entity, options) {
26041
+ count(filter, options) {
26042
+ var _a;
26026
26043
  return __awaiter(this, void 0, void 0, function* () {
26027
- this.fillDefaultProperties(entity);
26028
- // Transform into json
26029
- const isNew = isNil(entity.id);
26030
- const json = entity.asObject();
26031
- const now = Date.now();
26044
+ if (!((_a = this.queries) === null || _a === void 0 ? void 0 : _a.count)) {
26045
+ console.warn(`${this._logPrefix}Query 'count' not provided, skip.`);
26046
+ return undefined;
26047
+ }
26048
+ if (!filter)
26049
+ throw new Error(`Missing argument 'filter'`);
26050
+ filter = this.asFilter(filter);
26032
26051
  if (this._debug)
26033
- console.debug(`[user-event-service] Saving user event...`, json);
26034
- yield this.graphql.mutate({
26035
- mutation: SaveQuery,
26052
+ console.debug(`${this._logPrefix}Counting user events with filter:`, filter);
26053
+ const res = yield this.graphql.query({
26054
+ query: this.queries.count,
26036
26055
  variables: {
26037
- data: json
26056
+ filter: filter.asPodObject()
26038
26057
  },
26039
- error: { code: SocialErrorCodes.SAVE_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.SAVE_USER_EVENT_ERROR' },
26040
- update: (proxy, { data }) => {
26041
- // Update entity
26042
- const savedEntity = data && data.data;
26043
- if (savedEntity) {
26044
- if (this._debug)
26045
- console.debug(`[user-event-service] User event saved in ${Date.now() - now}ms`, entity);
26046
- this.copyIdAndUpdateDate(savedEntity, entity);
26047
- // Add to cache
26048
- if (isNew) {
26049
- this.insertIntoMutableCachedQueries(proxy, {
26050
- query: LoadAllQuery,
26051
- data: Object.assign(Object.assign({}, savedEntity), { content: null })
26052
- });
26053
- this.insertIntoMutableCachedQueries(proxy, {
26054
- query: LoadAllWithContentQuery,
26055
- data: savedEntity
26056
- });
26057
- }
26058
- }
26059
- }
26058
+ error: { code: SocialErrorCodes.COUNT_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.COUNT_USER_EVENT_ERROR' },
26059
+ fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || 'network-only'
26060
26060
  });
26061
- return entity;
26061
+ return res.total;
26062
+ });
26063
+ }
26064
+ listenChanges(filter, options) {
26065
+ var _a;
26066
+ if (!((_a = this.subscriptions) === null || _a === void 0 ? void 0 : _a.listenChanges)) {
26067
+ console.warn(`${this._logPrefix}Subscription query 'listenChanges' not provided, skip.`);
26068
+ return of();
26069
+ }
26070
+ if (!filter)
26071
+ throw new Error(`Missing argument 'filter'`);
26072
+ filter = this.asFilter(filter);
26073
+ const withContent = (options === null || options === void 0 ? void 0 : options.withContent) === true;
26074
+ if (this._debug)
26075
+ console.debug(`${this._logPrefix}[WS] Listening changes for user events with filter:`, filter);
26076
+ return this.graphql.subscribe({
26077
+ query: this.subscriptions.listenChanges,
26078
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
26079
+ variables: { filter: filter.asPodObject(), interval: toNumber(options === null || options === void 0 ? void 0 : options.interval, 10) },
26080
+ error: {
26081
+ code: SocialErrorCodes.SUBSCRIBE_USER_EVENTS_ERROR,
26082
+ message: 'SOCIAL.ERROR.SUBSCRIBE_USER_EVENTS_ERROR'
26083
+ }
26084
+ }).pipe(map(({ data }) => {
26085
+ if (data && this._debug)
26086
+ console.debug(`${this._logPrefix}Received new user events:`, data);
26087
+ const updatedData = data === null || data === void 0 ? void 0 : data.map(this.processUserEvent);
26088
+ // Update cache
26089
+ this.insertIntoMutableCachedQueries(this.graphql.cache, {
26090
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26091
+ data: updatedData
26092
+ });
26093
+ // update count
26094
+ this._count$.next(this.unreadEvents(updatedData));
26095
+ return updatedData;
26096
+ }));
26097
+ }
26098
+ delete(entity) {
26099
+ return __awaiter(this, void 0, void 0, function* () {
26100
+ if (!entity)
26101
+ return; // skip
26102
+ yield this.deleteAll([entity]);
26062
26103
  });
26063
26104
  }
26064
- /**
26065
- * Save many trips
26066
- *
26067
- * @param entities
26068
- * @param opts
26069
- */
26070
26105
  deleteAll(entities, opts) {
26071
26106
  return __awaiter(this, void 0, void 0, function* () {
26072
26107
  const ids = entities && entities
@@ -26075,49 +26110,206 @@ class UserEventService extends BaseGraphqlService {
26075
26110
  return; // stop, if nothing else to do
26076
26111
  const now = Date.now();
26077
26112
  if (this._debug)
26078
- console.debug('[user-event-service] Deleting events... ids:', ids);
26113
+ console.debug(`${this._logPrefix}Deleting events... ids:`, ids);
26079
26114
  yield this.graphql.mutate({
26080
- mutation: DeleteByIdsMutation,
26115
+ mutation: this.mutations.deleteByIds,
26081
26116
  variables: {
26082
26117
  ids
26083
26118
  },
26084
26119
  update: (proxy) => {
26085
26120
  // Remove from caches
26086
26121
  this.removeFromMutableCachedQueriesByIds(proxy, {
26087
- query: LoadAllQuery,
26122
+ query: this.queries.loadAll,
26088
26123
  ids
26089
26124
  });
26090
26125
  this.removeFromMutableCachedQueriesByIds(proxy, {
26091
- query: LoadAllWithContentQuery,
26126
+ query: this.queries.loadAllWithContent,
26092
26127
  ids
26093
26128
  });
26094
26129
  if (this._debug)
26095
- console.debug(`[user-event-service] Events deleted in ${Date.now() - now}ms`);
26130
+ console.debug(`${this._logPrefix}Events deleted in ${Date.now() - now}ms`);
26096
26131
  }
26097
26132
  });
26098
26133
  });
26099
26134
  }
26100
- /**
26101
- * Delete userEvent entities
26102
- */
26103
- delete(data) {
26135
+ save(entity, options) {
26104
26136
  return __awaiter(this, void 0, void 0, function* () {
26105
- if (!data)
26106
- return; // skip
26107
- yield this.deleteAll([data]);
26137
+ this.fillDefaultProperties(entity);
26138
+ // Transform into json
26139
+ const isNew = isNil(entity.id);
26140
+ const json = entity.asObject();
26141
+ const now = Date.now();
26142
+ if (this._debug)
26143
+ console.debug(`${this._logPrefix}Saving user event...`, json);
26144
+ yield this.graphql.mutate({
26145
+ mutation: this.mutations.save,
26146
+ variables: {
26147
+ data: json
26148
+ },
26149
+ error: { code: SocialErrorCodes.SAVE_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.SAVE_USER_EVENT_ERROR' },
26150
+ update: (proxy, { data }) => {
26151
+ // Update entity
26152
+ const savedEntity = data && data.data;
26153
+ if (savedEntity) {
26154
+ if (this._debug)
26155
+ console.debug(`${this._logPrefix}User event saved in ${Date.now() - now}ms`, entity);
26156
+ this.copyIdAndUpdateDate(savedEntity, entity);
26157
+ // Add to cache
26158
+ if (isNew) {
26159
+ this.insertIntoMutableCachedQueries(proxy, {
26160
+ query: this.queries.loadAll,
26161
+ data: Object.assign(Object.assign({}, savedEntity), { content: null })
26162
+ });
26163
+ this.insertIntoMutableCachedQueries(proxy, {
26164
+ query: this.queries.loadAllWithContent,
26165
+ data: savedEntity
26166
+ });
26167
+ }
26168
+ }
26169
+ }
26170
+ });
26171
+ return entity;
26108
26172
  });
26109
26173
  }
26110
- listenChanges(id, options) {
26111
- // TODO
26112
- console.warn('TODO: implement listen changes on user events');
26113
- return of();
26174
+ saveAll(data, opts) {
26175
+ return Promise.all(data
26176
+ .map(entity => this.save(entity, opts)));
26177
+ }
26178
+ markAsRead(entity) {
26179
+ return __awaiter(this, void 0, void 0, function* () {
26180
+ // Get onRead listeners
26181
+ const onReadListeners = (this.listeners || []).filter(listener => !!listener.onRead).filter(listener => listener.accept(entity));
26182
+ if (onReadListeners.length) {
26183
+ for (const listener of onReadListeners) {
26184
+ yield listener.onRead(entity);
26185
+ }
26186
+ }
26187
+ else {
26188
+ // Use default
26189
+ yield this.defaultMarkAsRead(entity);
26190
+ }
26191
+ });
26192
+ }
26193
+ registerListener(listener) {
26194
+ if (!this.listeners)
26195
+ this.listeners = [];
26196
+ this.listeners.push(listener);
26197
+ }
26198
+ /* -- protected methods -- */
26199
+ processUserEvent(source) {
26200
+ // Convert json source to IUserEvent
26201
+ let target = this.fromObject(source);
26202
+ // Get listeners
26203
+ const listeners = (this.listeners || []).filter(listener => listener.onReceived).filter(listener => listener.accept(target));
26204
+ for (const listener of listeners) {
26205
+ if (target) {
26206
+ target = listener.onReceived(target);
26207
+ }
26208
+ else {
26209
+ if (this._debug)
26210
+ console.debug(`${this._logPrefix}Event has been rejected by a listener`, source);
26211
+ break;
26212
+ }
26213
+ }
26214
+ return target;
26215
+ }
26216
+ defaultMarkAsRead(userEvent) {
26217
+ return __awaiter(this, void 0, void 0, function* () {
26218
+ if (!userEvent)
26219
+ throw new Error(`${this._logPrefix}Invalid event to save`);
26220
+ // set read date
26221
+ userEvent.readDate = moment$6();
26222
+ if (!userEvent.id || userEvent.id < 0) {
26223
+ if (this._debug)
26224
+ console.debug(`${this._logPrefix}Save local user event`);
26225
+ // TODO !!!!!!!!!!!!!
26226
+ }
26227
+ else {
26228
+ yield this.save(userEvent);
26229
+ }
26230
+ });
26231
+ }
26232
+ defaultFilter() {
26233
+ // By default
26234
+ return {
26235
+ recipients: [this.defaultRecipient()]
26236
+ };
26237
+ }
26238
+ defaultRecipient() {
26239
+ return this.accountService.person.pubkey;
26240
+ }
26241
+ fillDefaultProperties(entity) {
26242
+ }
26243
+ unreadEvents(events) {
26244
+ // default calculation on unread events from a list
26245
+ if (isEmptyArray(events))
26246
+ return undefined;
26247
+ const lastReadDate = events
26248
+ .filter(event => isNotNil(event.readDate))
26249
+ .reduce((lastDate, event) => DateUtils.max(lastDate, event.readDate), undefined);
26250
+ return events
26251
+ .filter(event => event.creationDate.isSameOrAfter(lastReadDate)).length;
26252
+ }
26253
+ copyIdAndUpdateDate(source, target) {
26254
+ EntityUtils.copyIdAndUpdateDate(source, target);
26255
+ }
26256
+ }
26257
+ AbstractUserEventService.decorators = [
26258
+ { type: Directive }
26259
+ ];
26260
+ AbstractUserEventService.ctorParameters = () => [
26261
+ { type: GraphqlService },
26262
+ { type: AccountService },
26263
+ { type: NetworkService },
26264
+ { type: TranslateService },
26265
+ { type: ToastController },
26266
+ { type: undefined },
26267
+ { type: undefined },
26268
+ { type: undefined },
26269
+ { type: Environment, decorators: [{ type: Optional }, { type: Inject, args: [ENVIRONMENT,] }] }
26270
+ ];
26271
+ const userEventQueries = {
26272
+ loadAll: gql `
26273
+ query UserEvents($filter: UserEventFilterVOInput, $page: PageInput){
26274
+ data: userEvents(filter: $filter, page: $page){
26275
+ ...LightUserEventFragment
26276
+ }
26277
+ }
26278
+ ${UserEventFragments.lightUserEvent}
26279
+ `,
26280
+ loadAllWithContent: gql `
26281
+ query UserEventsWithContent($filter: UserEventFilterVOInput, $page: PageInput){
26282
+ data: userEvents(filter: $filter, page: $page){
26283
+ ...UserEventFragment
26284
+ }
26114
26285
  }
26115
- registerAction(definition) {
26116
- console.info(`[user-event-service] Registering action ${definition.name} for ${definition.__typename}`);
26117
- this._userEventActions.push(definition);
26286
+ ${UserEventFragments.userEvent}
26287
+ `
26288
+ };
26289
+ const userEventMutations = {
26290
+ deleteByIds: gql `
26291
+ mutation DeleteUserEvents($ids:[Int]){
26292
+ deleteUserEvents(ids: $ids)
26293
+ }
26294
+ `,
26295
+ save: gql `
26296
+ mutation SaveUserEvent($data: UserEventVOInput){
26297
+ data: saveUserEvent(userEvent: $data){
26298
+ ...UserEventFragment
26299
+ }
26118
26300
  }
26119
- getActionsByTypename(typename) {
26120
- return this._userEventActions.filter(def => def.__typename === typename);
26301
+ ${UserEventFragments.userEvent}
26302
+ `
26303
+ };
26304
+ class UserEventService extends AbstractUserEventService {
26305
+ constructor(graphql, accountService, network, translate, toastController, environment) {
26306
+ super(graphql, accountService, network, translate, toastController, userEventQueries, userEventMutations, undefined, environment);
26307
+ this.graphql = graphql;
26308
+ this.accountService = accountService;
26309
+ this.network = network;
26310
+ this.translate = translate;
26311
+ this.toastController = toastController;
26312
+ this.environment = environment;
26121
26313
  }
26122
26314
  showToastErrorWithContext(opts) {
26123
26315
  return __awaiter(this, void 0, void 0, function* () {
@@ -26181,13 +26373,16 @@ class UserEventService extends BaseGraphqlService {
26181
26373
  }
26182
26374
  sendDataForDebug(data) {
26183
26375
  const userEvent = new UserEvent();
26184
- userEvent.eventType = UserEventTypes.DEBUG_DATA;
26376
+ userEvent.type = UserEventTypes.DEBUG_DATA;
26185
26377
  userEvent.content = this.convertObjectToString(data);
26186
26378
  return this.save(userEvent);
26187
26379
  }
26188
26380
  asFilter(filter) {
26189
26381
  return UserEventFilter.fromObject(filter);
26190
26382
  }
26383
+ fromObject(source) {
26384
+ return UserEvent.fromObject(source);
26385
+ }
26191
26386
  /* -- protected methods -- */
26192
26387
  convertObjectToString(data) {
26193
26388
  if (typeof data === 'string') {
@@ -26216,13 +26411,9 @@ class UserEventService extends BaseGraphqlService {
26216
26411
  // TODO: compute sign
26217
26412
  console.warn('TODO: sign user event before sending');
26218
26413
  }
26219
- copyIdAndUpdateDate(source, target) {
26220
- EntityUtils.copyIdAndUpdateDate(source, target);
26221
- }
26222
26414
  }
26223
- UserEventService.ɵprov = ɵɵdefineInjectable({ factory: function UserEventService_Factory() { return new UserEventService(ɵɵinject(GraphqlService), ɵɵinject(AccountService), ɵɵinject(NetworkService), ɵɵinject(TranslateService), ɵɵinject(ToastController), ɵɵinject(ENVIRONMENT, 8)); }, token: UserEventService, providedIn: "root" });
26224
26415
  UserEventService.decorators = [
26225
- { type: Injectable, args: [{ providedIn: 'root' },] }
26416
+ { type: Injectable }
26226
26417
  ];
26227
26418
  UserEventService.ctorParameters = () => [
26228
26419
  { type: GraphqlService },
@@ -26235,7 +26426,7 @@ UserEventService.ctorParameters = () => [
26235
26426
 
26236
26427
  const ICONS_MAP = {
26237
26428
  DEBUG_DATA: { matIcon: 'bug_report' },
26238
- INBOX_MESSAGE: { matIcon: 'mail' },
26429
+ INBOX_MESSAGE: { matIcon: 'mail' }
26239
26430
  };
26240
26431
  // TODO: refactor with a registration done by data service:
26241
26432
  // - userEventService.registerEventAction({__typename: 'TripVO', ...})
@@ -26301,7 +26492,7 @@ class UserEventsTable extends AppTable {
26301
26492
  }
26302
26493
  }));
26303
26494
  const filter = this.filter || new UserEventFilter();
26304
- filter.recipient = this.recipient;
26495
+ filter.recipients = [this.recipient];
26305
26496
  this.setFilter(filter, { emitEvent: true });
26306
26497
  }
26307
26498
  start() {
@@ -26314,17 +26505,17 @@ class UserEventsTable extends AppTable {
26314
26505
  });
26315
26506
  }
26316
26507
  getIcon(source) {
26317
- return ICONS_MAP[source.eventType];
26508
+ return ICONS_MAP[source.type];
26318
26509
  }
26319
26510
  getDetail(source) {
26320
26511
  if (!source)
26321
26512
  return undefined;
26322
- if (source.content && source.eventType === UserEventTypes.DEBUG_DATA) {
26513
+ if (source.content && source.type === UserEventTypes.DEBUG_DATA) {
26323
26514
  const context = source.content.context;
26324
26515
  if (context && context.__typename) {
26325
- const actions = this.service.getActionsByTypename(context.__typename);
26516
+ // const actions = this.service.getActionsByTypename(context.__typename);
26326
26517
  return {
26327
- actions,
26518
+ // actions,
26328
26519
  title: source.content.error && source.content.error.message || source.content.message,
26329
26520
  description: source.content.error && source.content.error.details || undefined
26330
26521
  };
@@ -26336,11 +26527,11 @@ class UserEventsTable extends AppTable {
26336
26527
  doAction(action, row) {
26337
26528
  return __awaiter(this, void 0, void 0, function* () {
26338
26529
  const event = row.currentData;
26339
- const context = event.content && event.content.context;
26530
+ // const context = event.content && event.content.context;
26340
26531
  this.markAsLoading();
26341
26532
  if (action && typeof action.executeAction === 'function') {
26342
26533
  try {
26343
- let res = action.executeAction(event, context);
26534
+ let res = action.executeAction(event /*, context*/);
26344
26535
  res = (res instanceof Promise) ? yield res : res;
26345
26536
  }
26346
26537
  catch (err) {
@@ -26359,13 +26550,18 @@ UserEventsTable.decorators = [
26359
26550
  selector: 'app-user-events-table',
26360
26551
  template: "\n<!-- Type = options menu -->\n<mat-menu #optionsMenu=\"matMenu\" xPosition=\"after\">\n\n <!-- display columns -->\n <button mat-menu-item\n (click)=\"openSelectColumnsModal($event)\">\n <mat-icon>view_column</mat-icon>\n <ion-label translate>COMMON.DISPLAYED_COLUMNS_DOTS</ion-label>\n </button>\n\n</mat-menu>\n\n<!-- top header -->\n<mat-toolbar>\n\n <ng-container *ngIf=\"!selection.hasValue(); else hasSelection\">\n\n <button mat-icon-button [title]=\"'COMMON.BTN_REFRESH'|translate\" (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n </ng-container>\n\n <!-- if row selection -->\n <ng-template #hasSelection>\n\n <!-- delete -->\n <button mat-icon-button class=\"hidden-xs hidden-sm\" *ngIf=\"canDelete\"\n [title]=\"'COMMON.BTN_DELETE'|translate\" (click)=\"deleteSelection($event)\">\n <mat-icon>delete</mat-icon>\n </button>\n </ng-template>\n\n <!-- error -->\n <ion-item *ngIf=\"error\" hidden-xs hidden-sm hidden-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=\"toolbar-spacer\"></div>\n\n <button mat-icon-button\n [title]=\"'COMMON.BTN_OPTIONS'|translate\"\n [matMenuTriggerFor]=\"optionsMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n</mat-toolbar>\n\n\n<mat-table [dataSource]=\"dataSource\" matSort\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear [trackBy]=\"trackByFn\">\n\n <!-- select -->\n <ng-container matColumnDef=\"select\">\n <mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </mat-cell>\n </ng-container>\n\n <!-- id -->\n <ng-container matColumnDef=\"id\">\n <mat-header-cell *matHeaderCellDef mat-sort-header [class.cdk-visually-hidden]=\"!isAdmin\">\n <app-loading-spinner [loading]=\"loadingSubject|async\"><ion-label>#</ion-label></app-loading-spinner>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!isAdmin\">{{ row.currentData.id | mathAbs }}</mat-cell>\n </ng-container>\n\n <!-- creationDate -->\n <ng-container matColumnDef=\"creationDate\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.CREATION_DATE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ion-text>\n {{row.currentData.creationDate | dateFromNow }}<br/>\n <ion-text color=\"medium\"><small>{{row.currentData.creationDate | dateFormat: {time: true} }}</small></ion-text>\n </ion-text>\n </mat-cell>\n </ng-container>\n\n <!-- icon -->\n <ng-container matColumnDef=\"icon\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getIcon(row.currentData); let details\">\n <ion-icon slot=\"start\" *ngIf=\"details.icon\" [name]=\"details.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"details.matIcon\">{{details.matIcon}}</mat-icon>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- event type -->\n <ng-container matColumnDef=\"eventType\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.EVENT_TYPE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <span>{{'SOCIAL.USER_EVENT.TYPE_ENUM.' + row.currentData.eventType | translate}}</span>\n </mat-cell>\n </ng-container>\n\n <!-- message -->\n <ng-container matColumnDef=\"message\">\n <mat-header-cell *matHeaderCellDef>\n <ion-label translate>SOCIAL.USER_EVENT.MESSAGE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <ion-text [innerHTML]=\"details.title|translate\">\n </ion-text>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- Actions buttons column -->\n <ng-container matColumnDef=\"actions\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <button mat-icon-button color=\"light\"\n *ngFor=\"let action of details.actions\"\n [title]=\"action.title |translate\"\n [color]=\"action.color\"\n (click)=\"doAction(action, row)\">\n <ion-icon slot=\"start\" *ngIf=\"action.icon\" [name]=\"action.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"action.matIcon\">{{action.matIcon}}</mat-icon>\n </button>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <mat-header-row *matHeaderRowDef=\"displayedColumns\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n class=\"mat-row-disabled\"\n (click)=\"clickRow($event, row)\"></mat-row>\n\n</mat-table>\n\n<ion-row class=\"ion-no-padding\">\n <ion-col></ion-col>\n <ion-col class=\"ion-no-padding\" size=\"auto\">\n <mat-paginator [length]=\"totalRowCount\" [pageSize]=\"defaultPageSize\" [pageSizeOptions]=\"defaultPageSizeOptions\" showFirstLastButtons>\n </mat-paginator>\n </ion-col>\n</ion-row>\n",
26361
26552
  changeDetection: ChangeDetectionStrategy.OnPush,
26553
+ providers: [
26554
+ {
26555
+ provide: UserEventServiceToken, useClass: UserEventService
26556
+ }
26557
+ ],
26362
26558
  styles: [".mat-header-row .mat-column-icon,.mat-row .mat-column-icon{max-width:40px;padding-left:0}.mat-header-row .mat-column-icon ion-icon,.mat-header-row .mat-column-icon mat-icon,.mat-row .mat-column-icon ion-icon,.mat-row .mat-column-icon mat-icon{font-size:1.2em;height:1.2em;width:1.2em}.mat-header-row .mat-column-creationDate,.mat-row .mat-column-creationDate{min-width:100px;max-width:120px}"]
26363
26559
  },] }
26364
26560
  ];
26365
26561
  UserEventsTable.ctorParameters = () => [
26366
26562
  { type: Injector },
26367
26563
  { type: AccountService },
26368
- { type: UserEventService },
26564
+ { type: undefined, decorators: [{ type: Inject, args: [UserEventServiceToken,] }] },
26369
26565
  { type: EntitiesStorage },
26370
26566
  { type: ChangeDetectorRef },
26371
26567
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
@@ -26377,6 +26573,150 @@ UserEventsTable.propDecorators = {
26377
26573
  defaultSortDirection: [{ type: Input }]
26378
26574
  };
26379
26575
 
26576
+ class UserEventNotificationList {
26577
+ constructor(popoverController) {
26578
+ this.popoverController = popoverController;
26579
+ }
26580
+ ngOnInit() {
26581
+ }
26582
+ read($event, userEvent) {
26583
+ var _a;
26584
+ if (userEvent === null || userEvent === void 0 ? void 0 : userEvent.readDate)
26585
+ return;
26586
+ (_a = this.readEvent) === null || _a === void 0 ? void 0 : _a.emit(userEvent);
26587
+ }
26588
+ executeAction(action, userEvent) {
26589
+ // Execute then close popover
26590
+ action.executeAction(userEvent);
26591
+ this.dismiss();
26592
+ }
26593
+ dismiss() {
26594
+ this.popoverController.dismiss();
26595
+ }
26596
+ }
26597
+ UserEventNotificationList.decorators = [
26598
+ { type: Component, args: [{
26599
+ selector: 'app-user-event-notification-list',
26600
+ template: "\n<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!(userEvents | async)?.length\">\n {{'SOCIAL.USER_EVENT.NOTIFICATION.EMPTY' | translate}}\n </ion-item>\n <ion-item\n *ngFor=\"let userEvent of userEvents | async\"\n (click)=\"read($event, userEvent)\"\n >\n\n <ion-avatar slot=\"start\">\n\n <img *ngIf=\"userEvent.avatar; else avatarIcon\" src=\"{{ userEvent.avatar }}\">\n\n <ng-template #avatarIcon>\n <app-icon\n *ngIf=\"userEvent.avatarIcon\"\n [ref]=\"userEvent.avatarIcon\"\n height=\"40\"\n width=\"40\"\n ></app-icon>\n </ng-template>\n\n </ion-avatar>\n\n <ion-grid class=\"ion-no-margin ion-no-padding\">\n <ion-row>\n <ion-col>\n <p [class.unread]=\"!userEvent.readDate\">\n {{userEvent.message}}\n </p>\n </ion-col>\n </ion-row>\n <ion-row *ngIf=\"userEvent.actions?.length\">\n <ion-col>\n <p>\n <span *ngFor=\"let action of userEvent.actions\">\n <a\n [style.margin-right.px]=\"8\"\n [style.cursor]=\"'pointer'\"\n (click)=\"executeAction(action, userEvent)\"\n >\n {{ '&#10142;&nbsp;' + action.name}}\n </a>\n </span>\n </p>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <app-icon\n *ngIf=\"userEvent.icon\"\n [ref]=\"userEvent.icon\"\n height=\"16\"\n width=\"16\"\n [style.vertical-align]=\"'sub'\"\n ></app-icon>\n <small>\n <span>{{userEvent.creationDate|dateFromNow}}</span>\n <span [style.color]=\"'gray'\">{{ ' | ' + (userEvent.creationDate|dateFormat:{time:true}) }}</span>\n </small>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n</ion-list>\n",
26601
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}.unread{font-weight:700}"]
26602
+ },] }
26603
+ ];
26604
+ UserEventNotificationList.ctorParameters = () => [
26605
+ { type: PopoverController }
26606
+ ];
26607
+ UserEventNotificationList.propDecorators = {
26608
+ titleI18n: [{ type: Input }],
26609
+ userEvents: [{ type: Input }],
26610
+ readEvent: [{ type: Output }]
26611
+ };
26612
+
26613
+ class UserEventNotificationComponent {
26614
+ constructor(userEventService, accountService, popoverController, cd) {
26615
+ this.userEventService = userEventService;
26616
+ this.accountService = accountService;
26617
+ this.popoverController = popoverController;
26618
+ this.cd = cd;
26619
+ this.debug = false;
26620
+ this.titleI18n = 'SOCIAL.USER_EVENT.NOTIFICATION.TITLE';
26621
+ this.disabled = false;
26622
+ this.userEventsSubject = new BehaviorSubject(undefined);
26623
+ this._subscriptions = new Subscription();
26624
+ this._logPrefix = '[user-event-notification] ';
26625
+ this._readEvent = new EventEmitter();
26626
+ }
26627
+ get count() {
26628
+ var _a;
26629
+ return (_a = this.userEventService) === null || _a === void 0 ? void 0 : _a.userEventCount.pipe(map(value => value === 0 ? undefined : value));
26630
+ }
26631
+ ngOnInit() {
26632
+ var _a;
26633
+ if (isNil(this.userEventService)) {
26634
+ console.warn(`${this._logPrefix}No service injected`);
26635
+ this.disabled = true;
26636
+ return;
26637
+ }
26638
+ this._subscriptions.add(this.userEventService.watchAll(0, 10, 'creationDate', "desc", {
26639
+ recipients: [(_a = this.accountService.person) === null || _a === void 0 ? void 0 : _a.pubkey]
26640
+ }).subscribe(value => {
26641
+ const data = value.data;
26642
+ if (this.debug) {
26643
+ console.debug(`${this._logPrefix}Receiving user events`, data);
26644
+ }
26645
+ this.userEventsSubject.next(data);
26646
+ }));
26647
+ this._readEvent.subscribe(value => {
26648
+ this.userEventService.markAsRead(value);
26649
+ });
26650
+ }
26651
+ showList(event) {
26652
+ return __awaiter(this, void 0, void 0, function* () {
26653
+ const popover = yield this.popoverController.create({
26654
+ component: UserEventNotificationList,
26655
+ componentProps: {
26656
+ titleI18n: this.titleI18n,
26657
+ userEvents: this.userEventsSubject,
26658
+ readEvent: this._readEvent
26659
+ },
26660
+ backdropDismiss: true,
26661
+ keyboardClose: true,
26662
+ event,
26663
+ translucent: true,
26664
+ cssClass: 'popover-large'
26665
+ });
26666
+ yield popover.present();
26667
+ });
26668
+ }
26669
+ markForCheck() {
26670
+ this.cd.markForCheck();
26671
+ }
26672
+ ngOnDestroy() {
26673
+ if (this.debug) {
26674
+ console.debug(`${this._logPrefix}Destroying`);
26675
+ }
26676
+ this._subscriptions.unsubscribe();
26677
+ }
26678
+ }
26679
+ UserEventNotificationComponent.decorators = [
26680
+ { type: Component, args: [{
26681
+ selector: 'app-user-event-notification-component',
26682
+ template: "<button\n #button\n mat-icon-button\n [title]=\"titleI18n | translate\"\n [disabled]=\"disabled\"\n (click)=\"showList($event)\"\n>\n <mat-icon\n [matBadge]=\"count | async\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n >notifications\n </mat-icon>\n</button>\n\n",
26683
+ changeDetection: ChangeDetectionStrategy.OnPush,
26684
+ styles: [""]
26685
+ },] }
26686
+ ];
26687
+ UserEventNotificationComponent.ctorParameters = () => [
26688
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [UserEventServiceToken,] }] },
26689
+ { type: AccountService },
26690
+ { type: PopoverController },
26691
+ { type: ChangeDetectorRef }
26692
+ ];
26693
+ UserEventNotificationComponent.propDecorators = {
26694
+ debug: [{ type: Input }],
26695
+ titleI18n: [{ type: Input }],
26696
+ disabled: [{ type: Input }]
26697
+ };
26698
+
26699
+ class UserEventModule {
26700
+ }
26701
+ UserEventModule.decorators = [
26702
+ { type: NgModule, args: [{
26703
+ imports: [
26704
+ CommonModule,
26705
+ CoreModule,
26706
+ SharedModule
26707
+ ],
26708
+ declarations: [
26709
+ UserEventsTable,
26710
+ UserEventNotificationComponent,
26711
+ UserEventNotificationList,
26712
+ ],
26713
+ exports: [
26714
+ UserEventsTable,
26715
+ UserEventNotificationComponent
26716
+ ]
26717
+ },] }
26718
+ ];
26719
+
26380
26720
  const MessageTypes = {
26381
26721
  INBOX_MESSAGE: 'INBOX_MESSAGE',
26382
26722
  EMAIL: 'EMAIL',
@@ -26588,9 +26928,9 @@ MessageForm.propDecorators = {
26588
26928
  canSelectType: [{ type: Input }]
26589
26929
  };
26590
26930
 
26591
- class SocialModule {
26931
+ class MessageModule {
26592
26932
  }
26593
- SocialModule.decorators = [
26933
+ MessageModule.decorators = [
26594
26934
  { type: NgModule, args: [{
26595
26935
  imports: [
26596
26936
  CommonModule,
@@ -26598,17 +26938,318 @@ SocialModule.decorators = [
26598
26938
  SharedModule
26599
26939
  ],
26600
26940
  declarations: [
26601
- UserEventsTable,
26602
26941
  MessageModal,
26603
26942
  MessageForm
26604
26943
  ],
26605
26944
  exports: [
26606
- UserEventsTable,
26607
26945
  MessageModal
26608
26946
  ]
26609
26947
  },] }
26610
26948
  ];
26611
26949
 
26950
+ var JobProgression_1;
26951
+ // todo use entity
26952
+ class Job {
26953
+ }
26954
+ // @dynamic
26955
+ let JobProgression = JobProgression_1 = class JobProgression extends Entity {
26956
+ constructor() {
26957
+ super(JobProgression_1.TYPENAME);
26958
+ }
26959
+ fromObject(source) {
26960
+ super.fromObject(source);
26961
+ this.message = source.message;
26962
+ this.current = source.current;
26963
+ this.total = source.total;
26964
+ }
26965
+ };
26966
+ JobProgression = JobProgression_1 = __decorate([
26967
+ EntityClass({ typename: 'JobProgressionVO' })
26968
+ ], JobProgression);
26969
+
26970
+ const JobProgressionServiceToken = new InjectionToken('JobProgressionService');
26971
+ const jobProgressionSubscription = gql `subscription UpdateJobProgression($id: Int!, $interval: Int){
26972
+ data: updateJobProgression(id: $id, interval: $interval) {
26973
+ id
26974
+ message
26975
+ current
26976
+ total
26977
+ updateDate
26978
+ }
26979
+ }`;
26980
+ class JobProgressionService extends BaseGraphqlService {
26981
+ constructor(graphql, environment) {
26982
+ super(graphql, environment);
26983
+ this.graphql = graphql;
26984
+ this.environment = environment;
26985
+ this._logPrefix = '[job-progression-service] ';
26986
+ // For DEV only
26987
+ this._debug = !(environment === null || environment === void 0 ? void 0 : environment.production);
26988
+ }
26989
+ listenChanges(id, options) {
26990
+ if (isNil(id))
26991
+ throw new Error(`${this._logPrefix}Missing argument 'id'`);
26992
+ if (this._debug)
26993
+ console.debug(`${this._logPrefix}[WS] Listening changes for job progression {${id}}...`);
26994
+ return this.graphql.subscribe({
26995
+ query: jobProgressionSubscription,
26996
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
26997
+ variables: { id, interval: toNumber(options === null || options === void 0 ? void 0 : options.interval, 10) },
26998
+ error: { code: SocialErrorCodes.SUBSCRIBE_JOB_PROGRESSION_ERROR, message: 'SOCIAL.ERROR.SUBSCRIBE_JOB_PROGRESSION_ERROR' }
26999
+ }).pipe(map(({ data }) => {
27000
+ const progression = data && JobProgression.fromObject(data);
27001
+ if (progression && this._debug)
27002
+ console.debug(`${this._logPrefix}Job progression ${id} updated on server`, progression);
27003
+ return progression;
27004
+ }));
27005
+ }
27006
+ }
27007
+ JobProgressionService.decorators = [
27008
+ { type: Injectable }
27009
+ ];
27010
+ JobProgressionService.ctorParameters = () => [
27011
+ { type: GraphqlService },
27012
+ { type: Environment, decorators: [{ type: Optional }, { type: Inject, args: [ENVIRONMENT,] }] }
27013
+ ];
27014
+
27015
+ class JobProgressionList {
27016
+ constructor() {
27017
+ }
27018
+ ngOnInit() {
27019
+ }
27020
+ }
27021
+ JobProgressionList.decorators = [
27022
+ { type: Component, args: [{
27023
+ selector: 'app-job-progression-list',
27024
+ template: "<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!jobProgressions?.length\">\n {{'SOCIAL.JOB.PROGRESSION.EMPTY' | translate}}\n </ion-item>\n <ion-item *ngFor=\"let jobProgression of jobProgressions\">\n <ion-grid class=\"ion-no-margin\">\n <ion-row>\n <ion-col>\n {{jobProgression.message}}\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <ion-progress-bar\n [value]=\"jobProgression.total > 0 ? jobProgression.current / jobProgression.total : undefined\"\n [type]=\"jobProgression.total > 0 ? 'determinate' : 'indeterminate'\">\n </ion-progress-bar>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n</ion-list>\n",
27025
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}"]
27026
+ },] }
27027
+ ];
27028
+ JobProgressionList.ctorParameters = () => [];
27029
+ JobProgressionList.propDecorators = {
27030
+ titleI18n: [{ type: Input }],
27031
+ jobProgressions: [{ type: Input }]
27032
+ };
27033
+
27034
+ class JobProgressionComponent {
27035
+ constructor(jobProgressionService, popoverController, cd) {
27036
+ this.jobProgressionService = jobProgressionService;
27037
+ this.popoverController = popoverController;
27038
+ this.cd = cd;
27039
+ this.debug = false;
27040
+ this.titleI18n = 'SOCIAL.JOB.PROGRESSION.TITLE';
27041
+ this.jobFinished = new EventEmitter();
27042
+ this.disabled = true;
27043
+ this.color = 'accent';
27044
+ this.mode = 'determinate';
27045
+ this.value = 0;
27046
+ this.jobProgressions = [];
27047
+ this._subscriptions = new Subscription();
27048
+ this._subscriptionsById = {};
27049
+ this._logPrefix = '[job-progression] ';
27050
+ }
27051
+ ngOnInit() {
27052
+ var _a, _b, _c, _d;
27053
+ if (isNil(this.jobProgressionService)) {
27054
+ console.warn(`${this._logPrefix}No service injected`);
27055
+ }
27056
+ // parse options
27057
+ this.autoHide = toBoolean((_a = this.options) === null || _a === void 0 ? void 0 : _a.autoHide, false);
27058
+ this.visible = !this.autoHide;
27059
+ this.autoHideDelay = toNumber((_b = this.options) === null || _b === void 0 ? void 0 : _b.autoHideDelay, 1000);
27060
+ this.autoRemove = toBoolean((_c = this.options) === null || _c === void 0 ? void 0 : _c.autoRemove, true);
27061
+ this.autoRemoveDelay = toNumber((_d = this.options) === null || _d === void 0 ? void 0 : _d.autoRemoveDelay, 1000);
27062
+ }
27063
+ addJob(id) {
27064
+ if (isNil(this.jobProgressionService)) {
27065
+ console.warn(`${this._logPrefix}No service injected. Can't add a job`);
27066
+ return;
27067
+ }
27068
+ if (this.debug) {
27069
+ console.debug(`${this._logPrefix}Add job id=${id}`);
27070
+ }
27071
+ if (!!this.getProgression(id)) {
27072
+ console.warn(`${this._logPrefix}Job (id=${id}) already present`);
27073
+ return;
27074
+ }
27075
+ this.disabled = false;
27076
+ const sub = this.jobProgressionService.listenChanges(id)
27077
+ .pipe(filter(progression => {
27078
+ if (id !== progression.id) {
27079
+ console.error(`${this._logPrefix}Job progression (id=${progression.id}) doesn't match expected job (id=${id})`);
27080
+ return false;
27081
+ }
27082
+ return true;
27083
+ }), map(progression => {
27084
+ // Ensure visible
27085
+ this.visible = true;
27086
+ // Get current progression object
27087
+ const currentProgression = this.getProgression(id);
27088
+ if (!currentProgression) {
27089
+ // add it
27090
+ this.jobProgressions.push(progression);
27091
+ if (this.debug) {
27092
+ console.debug(`${this._logPrefix}Progression added id=${id}`, progression);
27093
+ }
27094
+ }
27095
+ else {
27096
+ // update current
27097
+ Object.assign(currentProgression, progression);
27098
+ if (this.debug) {
27099
+ console.debug(`${this._logPrefix}Progression updated id=${id}`, progression);
27100
+ }
27101
+ }
27102
+ return progression;
27103
+ }))
27104
+ .subscribe(progression => {
27105
+ // update value
27106
+ this.updateValue();
27107
+ // job finished
27108
+ if (progression.total > 0 && progression.current == progression.total) {
27109
+ if (this.debug) {
27110
+ console.debug(`${this._logPrefix}Finished job id=${progression.id}`);
27111
+ }
27112
+ // emit event
27113
+ this.jobFinished.emit(progression.id);
27114
+ if (this.autoRemove) {
27115
+ setTimeout(() => {
27116
+ var _a;
27117
+ this.removeJob(progression.id);
27118
+ // If last job
27119
+ if (this.autoHide && !((_a = this.jobProgressions) === null || _a === void 0 ? void 0 : _a.length)) {
27120
+ setTimeout(() => {
27121
+ this.visible = false;
27122
+ this.markForCheck();
27123
+ }, this.autoHideDelay);
27124
+ }
27125
+ }, this.autoRemoveDelay);
27126
+ }
27127
+ }
27128
+ });
27129
+ this._subscriptionsById[id] = sub;
27130
+ this._subscriptions.add(sub);
27131
+ }
27132
+ removeJob(id) {
27133
+ if (this.debug) {
27134
+ console.debug(`${this._logPrefix}Remove job id=${id}`);
27135
+ }
27136
+ const sub = this._subscriptionsById[id];
27137
+ if (sub) {
27138
+ this._subscriptions.remove(sub);
27139
+ sub.unsubscribe();
27140
+ }
27141
+ const progression = this.getProgression(id);
27142
+ if (progression) {
27143
+ this.jobProgressions.splice(this.jobProgressions.indexOf(progression), 1);
27144
+ }
27145
+ this.updateValue();
27146
+ }
27147
+ getProgression(id) {
27148
+ return this.jobProgressions.find(progression => progression.id === id);
27149
+ }
27150
+ updateValue() {
27151
+ if (this.debug) {
27152
+ console.debug(`${this._logPrefix}Updating value`);
27153
+ }
27154
+ let value = 0;
27155
+ const nbProgression = arraySize(this.jobProgressions);
27156
+ let allIndeterminate = nbProgression > 0;
27157
+ this.jobProgressions.forEach(progression => {
27158
+ const indeterminate = progression.total === 0;
27159
+ allIndeterminate = allIndeterminate && indeterminate;
27160
+ if (!indeterminate) {
27161
+ value = value + progression.current * 100 / progression.total;
27162
+ }
27163
+ });
27164
+ this.value = nbProgression > 0 ? value / nbProgression : 0;
27165
+ this.mode = allIndeterminate ? 'indeterminate' : 'determinate';
27166
+ if (this.debug) {
27167
+ console.debug(`${this._logPrefix}Setting value=${this.value}, mode=${this.mode}`);
27168
+ }
27169
+ this.markForCheck();
27170
+ }
27171
+ showList(event) {
27172
+ return __awaiter(this, void 0, void 0, function* () {
27173
+ const popover = yield this.popoverController.create({
27174
+ component: JobProgressionList,
27175
+ componentProps: {
27176
+ titleI18n: this.titleI18n,
27177
+ jobProgressions: this.jobProgressions
27178
+ },
27179
+ backdropDismiss: true,
27180
+ keyboardClose: true,
27181
+ event,
27182
+ translucent: true,
27183
+ cssClass: 'popover-large'
27184
+ });
27185
+ yield popover.present();
27186
+ });
27187
+ }
27188
+ markForCheck() {
27189
+ this.cd.markForCheck();
27190
+ }
27191
+ ngOnDestroy() {
27192
+ if (this.debug) {
27193
+ console.debug(`${this._logPrefix}Destroying`);
27194
+ }
27195
+ this._subscriptions.unsubscribe();
27196
+ }
27197
+ }
27198
+ JobProgressionComponent.decorators = [
27199
+ { type: Component, args: [{
27200
+ selector: 'app-job-progression',
27201
+ template: "<button\n #button\n mat-icon-button\n [title]=\"titleI18n | translate\"\n [disabled]=\"disabled\"\n *ngIf=\"visible\"\n (click)=\"showList($event)\"\n>\n <mat-icon\n [matBadge]=\"jobProgressions?.length\"\n [matBadgeHidden]=\"!jobProgressions?.length\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n >{{ disabled || jobProgressions?.length ? 'schedule' : 'task_alt' }}\n </mat-icon>\n <mat-spinner\n #spinner\n *ngIf=\"jobProgressions?.length\"\n class=\"floating-spinner\"\n [color]=\"color\"\n [mode]=\"mode\"\n [value]=\"value\"\n diameter=\"30\"\n strokeWidth=\"3\"\n ></mat-spinner>\n</button>\n\n",
27202
+ changeDetection: ChangeDetectionStrategy.OnPush,
27203
+ styles: [".floating-spinner{position:absolute;top:6px;left:5px}"]
27204
+ },] }
27205
+ ];
27206
+ JobProgressionComponent.ctorParameters = () => [
27207
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [JobProgressionServiceToken,] }] },
27208
+ { type: PopoverController },
27209
+ { type: ChangeDetectorRef }
27210
+ ];
27211
+ JobProgressionComponent.propDecorators = {
27212
+ debug: [{ type: Input }],
27213
+ titleI18n: [{ type: Input }],
27214
+ options: [{ type: Input }],
27215
+ jobFinished: [{ type: Output }]
27216
+ };
27217
+
27218
+ class JobModule {
27219
+ }
27220
+ JobModule.decorators = [
27221
+ { type: NgModule, args: [{
27222
+ imports: [
27223
+ CommonModule,
27224
+ SharedModule,
27225
+ TranslateModule.forChild(),
27226
+ ],
27227
+ declarations: [
27228
+ JobProgressionComponent,
27229
+ JobProgressionList
27230
+ ],
27231
+ exports: [
27232
+ JobProgressionComponent
27233
+ ]
27234
+ },] }
27235
+ ];
27236
+
27237
+ const SocialModuleOptionsToken = new InjectionToken("SocialModuleOptions");
27238
+ class SocialModule {
27239
+ }
27240
+ SocialModule.decorators = [
27241
+ { type: NgModule, args: [{
27242
+ imports: [
27243
+ CommonModule,
27244
+ CoreModule,
27245
+ SharedModule,
27246
+ UserEventModule,
27247
+ JobModule,
27248
+ MessageModule,
27249
+ ],
27250
+ },] }
27251
+ ];
27252
+
26612
27253
  const Mutations = {
26613
27254
  send: gql `mutation SendMessage($data: MessageVOInput){
26614
27255
  done: sendMessage(message: $data)
@@ -27254,12 +27895,10 @@ class AutocompleteTestPage {
27254
27895
  });
27255
27896
  }
27256
27897
  ngAfterViewInit() {
27257
- if (this.farEntityField) {
27258
- setTimeout(() => {
27259
- this.farEntityField.suggestLengthThreshold = 2;
27260
- this.farEntityField.reloadItems();
27261
- }, 2000);
27262
- }
27898
+ setTimeout(() => {
27899
+ this.farEntityField.suggestLengthThreshold = 2;
27900
+ this.farEntityField.reloadItems();
27901
+ }, 2000);
27263
27902
  setTimeout(() => this.loadData(), 1000);
27264
27903
  }
27265
27904
  // Load the form with data
@@ -27347,7 +27986,7 @@ class AutocompleteTestPage {
27347
27986
  AutocompleteTestPage.decorators = [
27348
27987
  { type: Component, args: [{
27349
27988
  selector: 'app-autocomplete-test',
27350
- template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Entity far in the list (not in first page)\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre style=\"white-space: normal !important;\">value: {{stringify(form.controls.farEntity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"farEntity\" #farEntityField\n [config]=\"autocompleteFields.get('entity-far')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-far]'\"\n [debug]=\"true\"\n [required]=\"true\"\n [clearable]=\"true\"\n placeholder=\"Far entity (in the list)\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-desktop-suggest]'\"\n [debug]=\"true\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
27989
+ template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Entity far in the list (not in first page)\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre style=\"white-space: normal !important;\">value: {{stringify(form.controls.farEntity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"farEntity\" #farEntityField\n [config]=\"autocompleteFields.get('entity-far')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-far]'\"\n [debug]=\"true\"\n [required]=\"true\"\n [clearable]=\"true\"\n placeholder=\"Far entity (in the list)\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-desktop-suggest]'\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
27351
27990
  },] }
27352
27991
  ];
27353
27992
  AutocompleteTestPage.ctorParameters = () => [
@@ -27423,13 +28062,13 @@ LatLongTestPage.ctorParameters = () => [
27423
28062
  { type: FormBuilder }
27424
28063
  ];
27425
28064
 
27426
- const moment$6 = momentImported;
28065
+ const moment$7 = momentImported;
27427
28066
  class SwipeTestPage {
27428
28067
  constructor(formBuilder, dateFormatPipe) {
27429
28068
  this.formBuilder = formBuilder;
27430
28069
  this.dateFormatPipe = dateFormatPipe;
27431
28070
  this.$dates = new BehaviorSubject(undefined);
27432
- this._today = moment$6().startOf('day');
28071
+ this._today = moment$7().startOf('day');
27433
28072
  this.form = formBuilder.group({
27434
28073
  empty: [null, Validators.required],
27435
28074
  date: [null, Validators.compose([Validators.required, SharedValidators.validDate])],
@@ -27442,7 +28081,7 @@ class SwipeTestPage {
27442
28081
  ngOnInit() {
27443
28082
  const dates = [];
27444
28083
  for (let d = 0; d < 7; d++) {
27445
- dates[d] = moment$6(this._today).add(d - 3, 'day');
28084
+ dates[d] = moment$7(this._today).add(d - 3, 'day');
27446
28085
  }
27447
28086
  this.$dates.next(dates);
27448
28087
  this.loadData();
@@ -27483,7 +28122,7 @@ SwipeTestPage.ctorParameters = () => [
27483
28122
  { type: DateFormatPipe }
27484
28123
  ];
27485
28124
 
27486
- const moment$7 = momentImported;
28125
+ const moment$8 = momentImported;
27487
28126
  class DateTimeTestPage {
27488
28127
  constructor(platform, formBuilder, cd) {
27489
28128
  this.platform = platform;
@@ -27515,7 +28154,7 @@ class DateTimeTestPage {
27515
28154
  // Load the form with data
27516
28155
  loadData() {
27517
28156
  return __awaiter(this, void 0, void 0, function* () {
27518
- const now = moment$7();
28157
+ const now = moment$8();
27519
28158
  const data = {
27520
28159
  empty: null,
27521
28160
  emptyRequired: null,
@@ -27761,7 +28400,7 @@ NumpadTestPage.ctorParameters = () => [
27761
28400
  { type: FormBuilder }
27762
28401
  ];
27763
28402
 
27764
- const moment$8 = momentImported;
28403
+ const moment$9 = momentImported;
27765
28404
  class DateTestPage {
27766
28405
  constructor(formBuilder, cd) {
27767
28406
  this.formBuilder = formBuilder;
@@ -27794,7 +28433,7 @@ class DateTestPage {
27794
28433
  // Load the form with data
27795
28434
  loadData() {
27796
28435
  return __awaiter(this, void 0, void 0, function* () {
27797
- const now = moment$8();
28436
+ const now = moment$9();
27798
28437
  const nowAtMahe = now.clone().tz(this.timezone).startOf('day');
27799
28438
  const data = {
27800
28439
  empty: null,
@@ -28540,5 +29179,5 @@ CoreTestingModule.decorators = [
28540
29179
  * Generated bundle index. Do not edit.
28541
29180
  */
28542
29181
 
28543
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, 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, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, 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, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments$1 as 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, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, ResizableComponent, ResizableDirective, ResizableModule, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEvent, UserEventFilter, UserEventFragments, 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, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, 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, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moment$5 as moment, 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, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, tz, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ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, AppUpdateOfflineModeCard as ɵi, AppIconComponent as ɵj, DateTestPage as ɵk, NumpadTestPage as ɵl, MatBadgeIconTestPage as ɵm, ToastTestingModule as ɵn, ToastTestingPage as ɵo };
29182
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, 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, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, 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, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments$1 as Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, Job, JobModule, JobProgression, JobProgressionComponent, JobProgressionService, JobProgressionServiceToken, 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, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, ResizableComponent, ResizableDirective, ResizableModule, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEvent, UserEventFilter, UserEventFragments, UserEventModule, UserEventNotificationComponent, UserEventService, UserEventServiceToken, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, 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, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moment$5 as moment, 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, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, tz, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ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, AppUpdateOfflineModeCard as ɵi, AppIconComponent as ɵj, UserEventNotificationList as ɵk, JobProgressionList as ɵl, DateTestPage as ɵm, NumpadTestPage as ɵn, MatBadgeIconTestPage as ɵo, ToastTestingModule as ɵp, ToastTestingPage as ɵq };
28544
29183
  //# sourceMappingURL=sumaris-net.ngx-components.js.map