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

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 +1110 -328
  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 +53 -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 +100 -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 +299 -168
  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 +936 -290
  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 +79 -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,162 @@ 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, 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
- this.toastController = toastController;
25961
+ this.queries = queries;
25962
+ this.mutations = mutations;
25963
+ this.subscriptions = subscriptions;
25961
25964
  this.environment = environment;
25962
- this._userEventActions = [];
25965
+ this._count$ = new BehaviorSubject(0);
25966
+ this._subscriptions = new Subscription();
25967
+ this.listeners = [];
25968
+ this._logPrefix = '[user-event-service] ';
25963
25969
  // For DEV only
25964
- this._debug = environment && !environment.production;
25970
+ this._debug = !(environment === null || environment === void 0 ? void 0 : environment.production);
25971
+ // listen login/logout
25972
+ this._subscriptions.add(accountService.onLogin.subscribe(() => __awaiter(this, void 0, void 0, function* () {
25973
+ const count = yield this.count(undefined);
25974
+ if (count && this._debug) {
25975
+ console.debug(`${this._logPrefix}Received ${count} unread notifications`);
25976
+ }
25977
+ this._count$.next(count);
25978
+ })));
25979
+ }
25980
+ get userEventCount() {
25981
+ return this._count$.asObservable();
25982
+ }
25983
+ ngOnDestroy() {
25984
+ this._subscriptions.unsubscribe();
25965
25985
  }
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
25986
  watchAll(offset, size, sortBy, sortDirection, filter, options) {
25977
25987
  return this.watchPage({ offset, size, sortBy, sortDirection }, filter, options);
25978
25988
  }
25979
25989
  watchPage(page, filter, options) {
25990
+ var _a;
25980
25991
  let now = this._debug && Date.now();
25981
- //if (this._debug)
25982
- console.debug('[user-event-service] Loading user events...', filter);
25992
+ if (!filter) {
25993
+ filter = this.defaultFilter();
25994
+ }
25995
+ console.debug(`${this._logPrefix}Loading user events...`, filter);
25983
25996
  filter = this.asFilter(filter);
25984
25997
  // 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) {
25998
+ if (isEmptyArray(filter === null || filter === void 0 ? void 0 : filter.recipients) || !this.accountService.isAdmin()) {
25999
+ const recipient = this.defaultRecipient();
26000
+ if (!((_a = filter === null || filter === void 0 ? void 0 : filter.recipients) === null || _a === void 0 ? void 0 : _a.includes(recipient))) {
25988
26001
  console.warn('[user-events-service] Force user event filter.recipient=' + recipient);
25989
- filter.recipient = recipient;
26002
+ filter.recipients = [recipient];
25990
26003
  }
25991
26004
  }
25992
- const withContent = options && options.withContent === true;
26005
+ const withContent = (options === null || options === void 0 ? void 0 : options.withContent) === true;
25993
26006
  return this.mutableWatchQuery({
25994
26007
  queryName: withContent ? 'LoadAllWithContent' : 'LoadAll',
25995
- query: withContent ? LoadAllWithContentQuery : LoadAllQuery,
26008
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
25996
26009
  variables: {
25997
- page: Object.assign(Object.assign({ sortBy: 'updateDate' }, page), { sortDirection: (page.sortDirection || 'DESC').toUpperCase() }),
26010
+ page: Object.assign(Object.assign({ sortBy: 'creationDate' }, page), {
26011
+ // @ts-ignore
26012
+ sortDirection: (page.sortDirection || 'desc').toUpperCase() }),
25998
26013
  filter: filter && filter.asPodObject()
25999
26014
  },
26000
26015
  arrayFieldName: 'data',
26001
26016
  error: { code: SocialErrorCodes.LOAD_USER_EVENTS_ERROR, message: 'SOCIAL.ERROR.LOAD_USER_EVENTS_ERROR' },
26002
- fetchPolicy: options && options.fetchPolicy || undefined
26017
+ fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || "cache-first"
26003
26018
  })
26004
26019
  .pipe(map(res => {
26005
- const data = res && (res.data || []).map(UserEvent.fromObject);
26020
+ const data = ((res === null || res === void 0 ? void 0 : res.data) || []).map(value => this.processUserEvent(value, this));
26006
26021
  if (now) {
26007
- console.debug(`[user-event-service] ${data.length} user events loaded in ${Date.now() - now}ms`);
26022
+ console.debug(`${this._logPrefix}${data.length} user events loaded in ${Date.now() - now}ms`);
26008
26023
  now = null;
26009
26024
  }
26025
+ const unreadCount = this.unreadEvents(data);
26010
26026
  return {
26011
26027
  data,
26012
- total: res && toNumber(res.total, data.length)
26028
+ total: toNumber(res === null || res === void 0 ? void 0 : res.total, unreadCount)
26013
26029
  };
26014
26030
  }));
26015
26031
  }
26016
- saveAll(data, options) {
26017
- return Promise.all(data
26018
- .map(entity => this.save(entity, options)));
26032
+ add(entity) {
26033
+ // TODO set an id !!!!!!!!!!!!!!!!!!!!!!!!!
26034
+ entity = this.processUserEvent(entity);
26035
+ const withContent = !!entity.content;
26036
+ // Add user event locally
26037
+ this.insertIntoMutableCachedQueries(this.graphql.cache, {
26038
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26039
+ data: entity
26040
+ });
26041
+ // Update count
26042
+ this._count$.next(this._count$.value + 1);
26019
26043
  }
26020
- /**
26021
- * Save a userEvent entity
26022
- *
26023
- * @param entity
26024
- */
26025
- save(entity, options) {
26044
+ count(filter, options) {
26045
+ var _a;
26026
26046
  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();
26047
+ if (!((_a = this.queries) === null || _a === void 0 ? void 0 : _a.count)) {
26048
+ console.warn(`${this._logPrefix}Query 'count' not provided, skip.`);
26049
+ return undefined;
26050
+ }
26051
+ if (!filter)
26052
+ filter = this.defaultFilter();
26053
+ filter = this.asFilter(filter);
26032
26054
  if (this._debug)
26033
- console.debug(`[user-event-service] Saving user event...`, json);
26034
- yield this.graphql.mutate({
26035
- mutation: SaveQuery,
26055
+ console.debug(`${this._logPrefix}Counting user events with filter:`, filter);
26056
+ const res = yield this.graphql.query({
26057
+ query: this.queries.count,
26036
26058
  variables: {
26037
- data: json
26059
+ filter: filter.asPodObject()
26038
26060
  },
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
- }
26061
+ error: { code: SocialErrorCodes.COUNT_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.COUNT_USER_EVENT_ERROR' },
26062
+ fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || 'network-only'
26060
26063
  });
26061
- return entity;
26064
+ return res.total;
26065
+ });
26066
+ }
26067
+ listenChanges(filter, options) {
26068
+ var _a;
26069
+ if (!((_a = this.subscriptions) === null || _a === void 0 ? void 0 : _a.listenChanges)) {
26070
+ console.warn(`${this._logPrefix}Subscription query 'listenChanges' not provided, skip.`);
26071
+ return of();
26072
+ }
26073
+ if (!filter)
26074
+ filter = this.defaultFilter();
26075
+ filter = this.asFilter(filter);
26076
+ const withContent = (options === null || options === void 0 ? void 0 : options.withContent) === true;
26077
+ if (this._debug)
26078
+ console.debug(`${this._logPrefix}[WS] Listening changes for user events with filter:`, filter);
26079
+ return this.graphql.subscribe({
26080
+ query: this.subscriptions.listenChanges,
26081
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
26082
+ variables: { filter: filter.asPodObject(), interval: toNumber(options === null || options === void 0 ? void 0 : options.interval, 10) },
26083
+ error: {
26084
+ code: SocialErrorCodes.SUBSCRIBE_USER_EVENTS_ERROR,
26085
+ message: 'SOCIAL.ERROR.SUBSCRIBE_USER_EVENTS_ERROR'
26086
+ }
26087
+ }).pipe(map(({ data }) => {
26088
+ if (data && this._debug)
26089
+ console.debug(`${this._logPrefix}Received new user events:`, data);
26090
+ const updatedData = data === null || data === void 0 ? void 0 : data.map(value => this.processUserEvent(value, this));
26091
+ // Update cache
26092
+ this.insertIntoMutableCachedQueries(this.graphql.cache, {
26093
+ query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26094
+ data: updatedData
26095
+ });
26096
+ // update count
26097
+ this._count$.next(this.unreadEvents(updatedData));
26098
+ return updatedData;
26099
+ }));
26100
+ }
26101
+ delete(entity) {
26102
+ return __awaiter(this, void 0, void 0, function* () {
26103
+ if (!entity)
26104
+ return; // skip
26105
+ yield this.deleteAll([entity]);
26062
26106
  });
26063
26107
  }
26064
- /**
26065
- * Save many trips
26066
- *
26067
- * @param entities
26068
- * @param opts
26069
- */
26070
26108
  deleteAll(entities, opts) {
26071
26109
  return __awaiter(this, void 0, void 0, function* () {
26072
26110
  const ids = entities && entities
@@ -26075,49 +26113,206 @@ class UserEventService extends BaseGraphqlService {
26075
26113
  return; // stop, if nothing else to do
26076
26114
  const now = Date.now();
26077
26115
  if (this._debug)
26078
- console.debug('[user-event-service] Deleting events... ids:', ids);
26116
+ console.debug(`${this._logPrefix}Deleting events... ids:`, ids);
26079
26117
  yield this.graphql.mutate({
26080
- mutation: DeleteByIdsMutation,
26118
+ mutation: this.mutations.deleteByIds,
26081
26119
  variables: {
26082
26120
  ids
26083
26121
  },
26084
26122
  update: (proxy) => {
26085
26123
  // Remove from caches
26086
26124
  this.removeFromMutableCachedQueriesByIds(proxy, {
26087
- query: LoadAllQuery,
26125
+ query: this.queries.loadAll,
26088
26126
  ids
26089
26127
  });
26090
26128
  this.removeFromMutableCachedQueriesByIds(proxy, {
26091
- query: LoadAllWithContentQuery,
26129
+ query: this.queries.loadAllWithContent,
26092
26130
  ids
26093
26131
  });
26094
26132
  if (this._debug)
26095
- console.debug(`[user-event-service] Events deleted in ${Date.now() - now}ms`);
26133
+ console.debug(`${this._logPrefix}Events deleted in ${Date.now() - now}ms`);
26096
26134
  }
26097
26135
  });
26098
26136
  });
26099
26137
  }
26100
- /**
26101
- * Delete userEvent entities
26102
- */
26103
- delete(data) {
26138
+ save(entity, options) {
26104
26139
  return __awaiter(this, void 0, void 0, function* () {
26105
- if (!data)
26106
- return; // skip
26107
- yield this.deleteAll([data]);
26140
+ this.fillDefaultProperties(entity);
26141
+ // Transform into json
26142
+ const isNew = isNil(entity.id);
26143
+ const json = entity.asObject();
26144
+ const now = Date.now();
26145
+ if (this._debug)
26146
+ console.debug(`${this._logPrefix}Saving user event...`, json);
26147
+ yield this.graphql.mutate({
26148
+ mutation: this.mutations.save,
26149
+ variables: {
26150
+ data: json
26151
+ },
26152
+ error: { code: SocialErrorCodes.SAVE_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.SAVE_USER_EVENT_ERROR' },
26153
+ update: (proxy, { data }) => {
26154
+ // Update entity
26155
+ const savedEntity = data && data.data;
26156
+ if (savedEntity) {
26157
+ if (this._debug)
26158
+ console.debug(`${this._logPrefix}User event saved in ${Date.now() - now}ms`, entity);
26159
+ this.copyIdAndUpdateDate(savedEntity, entity);
26160
+ // Add to cache
26161
+ if (isNew) {
26162
+ this.insertIntoMutableCachedQueries(proxy, {
26163
+ query: this.queries.loadAll,
26164
+ data: Object.assign(Object.assign({}, savedEntity), { content: null })
26165
+ });
26166
+ this.insertIntoMutableCachedQueries(proxy, {
26167
+ query: this.queries.loadAllWithContent,
26168
+ data: savedEntity
26169
+ });
26170
+ }
26171
+ }
26172
+ }
26173
+ });
26174
+ return entity;
26108
26175
  });
26109
26176
  }
26110
- listenChanges(id, options) {
26111
- // TODO
26112
- console.warn('TODO: implement listen changes on user events');
26113
- return of();
26177
+ saveAll(data, opts) {
26178
+ return Promise.all(data
26179
+ .map(entity => this.save(entity, opts)));
26180
+ }
26181
+ markAsRead(entity) {
26182
+ return __awaiter(this, void 0, void 0, function* () {
26183
+ // Get onRead listeners
26184
+ const onReadListeners = (this.listeners || []).filter(listener => !!listener.onRead).filter(listener => listener.accept(entity));
26185
+ if (onReadListeners.length) {
26186
+ for (const listener of onReadListeners) {
26187
+ yield listener.onRead(entity);
26188
+ }
26189
+ }
26190
+ else {
26191
+ // Use default
26192
+ yield this.defaultMarkAsRead(entity);
26193
+ }
26194
+ });
26195
+ }
26196
+ registerListener(listener) {
26197
+ if (!this.listeners)
26198
+ this.listeners = [];
26199
+ this.listeners.push(listener);
26200
+ }
26201
+ /* -- protected methods -- */
26202
+ processUserEvent(source, that) {
26203
+ that = that || this;
26204
+ // Convert json source to IUserEvent
26205
+ let target = that.fromObject(source);
26206
+ // Get listeners
26207
+ const listeners = (that.listeners || []).filter(listener => listener.onReceived).filter(listener => listener.accept(target));
26208
+ for (const listener of listeners) {
26209
+ if (target) {
26210
+ target = listener.onReceived(target);
26211
+ }
26212
+ else {
26213
+ if (that._debug)
26214
+ console.debug(`${that._logPrefix}Event has been rejected by a listener`, source);
26215
+ break;
26216
+ }
26217
+ }
26218
+ return target;
26219
+ }
26220
+ defaultMarkAsRead(userEvent) {
26221
+ return __awaiter(this, void 0, void 0, function* () {
26222
+ if (!userEvent)
26223
+ throw new Error(`${this._logPrefix}Invalid event to save`);
26224
+ // set read date
26225
+ userEvent.readDate = moment$6();
26226
+ if (!userEvent.id || userEvent.id < 0) {
26227
+ if (this._debug)
26228
+ console.debug(`${this._logPrefix}Save local user event`);
26229
+ // TODO !!!!!!!!!!!!!
26230
+ }
26231
+ else {
26232
+ yield this.save(userEvent);
26233
+ }
26234
+ });
26235
+ }
26236
+ defaultFilter() {
26237
+ // By default
26238
+ return {
26239
+ recipients: [this.defaultRecipient()]
26240
+ };
26241
+ }
26242
+ defaultRecipient() {
26243
+ return this.accountService.person.pubkey;
26244
+ }
26245
+ fillDefaultProperties(entity) {
26246
+ }
26247
+ unreadEvents(events) {
26248
+ // default calculation on unread events from a list
26249
+ if (isEmptyArray(events))
26250
+ return undefined;
26251
+ const lastReadDate = events
26252
+ .filter(event => isNotNil(event.readDate))
26253
+ .reduce((lastDate, event) => DateUtils.max(lastDate, event.readDate), undefined);
26254
+ return events
26255
+ .filter(event => event.creationDate.isSameOrAfter(lastReadDate)).length;
26256
+ }
26257
+ copyIdAndUpdateDate(source, target) {
26258
+ EntityUtils.copyIdAndUpdateDate(source, target);
26259
+ }
26260
+ }
26261
+ AbstractUserEventService.decorators = [
26262
+ { type: Directive }
26263
+ ];
26264
+ AbstractUserEventService.ctorParameters = () => [
26265
+ { type: GraphqlService },
26266
+ { type: AccountService },
26267
+ { type: NetworkService },
26268
+ { type: TranslateService },
26269
+ { type: undefined },
26270
+ { type: undefined },
26271
+ { type: undefined },
26272
+ { type: Environment, decorators: [{ type: Optional }, { type: Inject, args: [ENVIRONMENT,] }] }
26273
+ ];
26274
+ const userEventQueries = {
26275
+ loadAll: gql `
26276
+ query UserEvents($filter: UserEventFilterVOInput, $page: PageInput){
26277
+ data: userEvents(filter: $filter, page: $page){
26278
+ ...LightUserEventFragment
26279
+ }
26280
+ }
26281
+ ${UserEventFragments.lightUserEvent}
26282
+ `,
26283
+ loadAllWithContent: gql `
26284
+ query UserEventsWithContent($filter: UserEventFilterVOInput, $page: PageInput){
26285
+ data: userEvents(filter: $filter, page: $page){
26286
+ ...UserEventFragment
26287
+ }
26114
26288
  }
26115
- registerAction(definition) {
26116
- console.info(`[user-event-service] Registering action ${definition.name} for ${definition.__typename}`);
26117
- this._userEventActions.push(definition);
26289
+ ${UserEventFragments.userEvent}
26290
+ `
26291
+ };
26292
+ const userEventMutations = {
26293
+ deleteByIds: gql `
26294
+ mutation DeleteUserEvents($ids:[Int]){
26295
+ deleteUserEvents(ids: $ids)
26296
+ }
26297
+ `,
26298
+ save: gql `
26299
+ mutation SaveUserEvent($data: UserEventVOInput){
26300
+ data: saveUserEvent(userEvent: $data){
26301
+ ...UserEventFragment
26302
+ }
26118
26303
  }
26119
- getActionsByTypename(typename) {
26120
- return this._userEventActions.filter(def => def.__typename === typename);
26304
+ ${UserEventFragments.userEvent}
26305
+ `
26306
+ };
26307
+ class UserEventService extends AbstractUserEventService {
26308
+ constructor(graphql, accountService, network, translate, toastController, environment) {
26309
+ super(graphql, accountService, network, translate, userEventQueries, userEventMutations, undefined, environment);
26310
+ this.graphql = graphql;
26311
+ this.accountService = accountService;
26312
+ this.network = network;
26313
+ this.translate = translate;
26314
+ this.toastController = toastController;
26315
+ this.environment = environment;
26121
26316
  }
26122
26317
  showToastErrorWithContext(opts) {
26123
26318
  return __awaiter(this, void 0, void 0, function* () {
@@ -26181,13 +26376,16 @@ class UserEventService extends BaseGraphqlService {
26181
26376
  }
26182
26377
  sendDataForDebug(data) {
26183
26378
  const userEvent = new UserEvent();
26184
- userEvent.eventType = UserEventTypes.DEBUG_DATA;
26379
+ userEvent.type = UserEventTypes.DEBUG_DATA;
26185
26380
  userEvent.content = this.convertObjectToString(data);
26186
26381
  return this.save(userEvent);
26187
26382
  }
26188
26383
  asFilter(filter) {
26189
26384
  return UserEventFilter.fromObject(filter);
26190
26385
  }
26386
+ fromObject(source) {
26387
+ return UserEvent.fromObject(source);
26388
+ }
26191
26389
  /* -- protected methods -- */
26192
26390
  convertObjectToString(data) {
26193
26391
  if (typeof data === 'string') {
@@ -26216,13 +26414,9 @@ class UserEventService extends BaseGraphqlService {
26216
26414
  // TODO: compute sign
26217
26415
  console.warn('TODO: sign user event before sending');
26218
26416
  }
26219
- copyIdAndUpdateDate(source, target) {
26220
- EntityUtils.copyIdAndUpdateDate(source, target);
26221
- }
26222
26417
  }
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
26418
  UserEventService.decorators = [
26225
- { type: Injectable, args: [{ providedIn: 'root' },] }
26419
+ { type: Injectable }
26226
26420
  ];
26227
26421
  UserEventService.ctorParameters = () => [
26228
26422
  { type: GraphqlService },
@@ -26235,7 +26429,7 @@ UserEventService.ctorParameters = () => [
26235
26429
 
26236
26430
  const ICONS_MAP = {
26237
26431
  DEBUG_DATA: { matIcon: 'bug_report' },
26238
- INBOX_MESSAGE: { matIcon: 'mail' },
26432
+ INBOX_MESSAGE: { matIcon: 'mail' }
26239
26433
  };
26240
26434
  // TODO: refactor with a registration done by data service:
26241
26435
  // - userEventService.registerEventAction({__typename: 'TripVO', ...})
@@ -26301,7 +26495,7 @@ class UserEventsTable extends AppTable {
26301
26495
  }
26302
26496
  }));
26303
26497
  const filter = this.filter || new UserEventFilter();
26304
- filter.recipient = this.recipient;
26498
+ filter.recipients = [this.recipient];
26305
26499
  this.setFilter(filter, { emitEvent: true });
26306
26500
  }
26307
26501
  start() {
@@ -26314,17 +26508,17 @@ class UserEventsTable extends AppTable {
26314
26508
  });
26315
26509
  }
26316
26510
  getIcon(source) {
26317
- return ICONS_MAP[source.eventType];
26511
+ return ICONS_MAP[source.type];
26318
26512
  }
26319
26513
  getDetail(source) {
26320
26514
  if (!source)
26321
26515
  return undefined;
26322
- if (source.content && source.eventType === UserEventTypes.DEBUG_DATA) {
26516
+ if (source.content && source.type === UserEventTypes.DEBUG_DATA) {
26323
26517
  const context = source.content.context;
26324
26518
  if (context && context.__typename) {
26325
- const actions = this.service.getActionsByTypename(context.__typename);
26519
+ // const actions = this.service.getActionsByTypename(context.__typename);
26326
26520
  return {
26327
- actions,
26521
+ // actions,
26328
26522
  title: source.content.error && source.content.error.message || source.content.message,
26329
26523
  description: source.content.error && source.content.error.details || undefined
26330
26524
  };
@@ -26336,11 +26530,11 @@ class UserEventsTable extends AppTable {
26336
26530
  doAction(action, row) {
26337
26531
  return __awaiter(this, void 0, void 0, function* () {
26338
26532
  const event = row.currentData;
26339
- const context = event.content && event.content.context;
26533
+ // const context = event.content && event.content.context;
26340
26534
  this.markAsLoading();
26341
26535
  if (action && typeof action.executeAction === 'function') {
26342
26536
  try {
26343
- let res = action.executeAction(event, context);
26537
+ let res = action.executeAction(event /*, context*/);
26344
26538
  res = (res instanceof Promise) ? yield res : res;
26345
26539
  }
26346
26540
  catch (err) {
@@ -26359,13 +26553,18 @@ UserEventsTable.decorators = [
26359
26553
  selector: 'app-user-events-table',
26360
26554
  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
26555
  changeDetection: ChangeDetectionStrategy.OnPush,
26556
+ providers: [
26557
+ {
26558
+ provide: UserEventServiceToken, useClass: UserEventService
26559
+ }
26560
+ ],
26362
26561
  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
26562
  },] }
26364
26563
  ];
26365
26564
  UserEventsTable.ctorParameters = () => [
26366
26565
  { type: Injector },
26367
26566
  { type: AccountService },
26368
- { type: UserEventService },
26567
+ { type: undefined, decorators: [{ type: Inject, args: [UserEventServiceToken,] }] },
26369
26568
  { type: EntitiesStorage },
26370
26569
  { type: ChangeDetectorRef },
26371
26570
  { type: undefined, decorators: [{ type: Inject, args: [ENVIRONMENT,] }] }
@@ -26377,6 +26576,155 @@ UserEventsTable.propDecorators = {
26377
26576
  defaultSortDirection: [{ type: Input }]
26378
26577
  };
26379
26578
 
26579
+ class UserEventNotificationList {
26580
+ constructor(popoverController) {
26581
+ this.popoverController = popoverController;
26582
+ }
26583
+ ngOnInit() {
26584
+ }
26585
+ read($event, userEvent) {
26586
+ var _a;
26587
+ if (userEvent === null || userEvent === void 0 ? void 0 : userEvent.readDate)
26588
+ return;
26589
+ (_a = this.readEvent) === null || _a === void 0 ? void 0 : _a.emit(userEvent);
26590
+ }
26591
+ executeAction(action, userEvent) {
26592
+ // Execute then close popover
26593
+ action.executeAction(userEvent);
26594
+ this.dismiss();
26595
+ }
26596
+ dismiss() {
26597
+ this.popoverController.dismiss();
26598
+ }
26599
+ }
26600
+ UserEventNotificationList.decorators = [
26601
+ { type: Component, args: [{
26602
+ selector: 'app-user-event-notification-list',
26603
+ 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",
26604
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}.unread{font-weight:700}"]
26605
+ },] }
26606
+ ];
26607
+ UserEventNotificationList.ctorParameters = () => [
26608
+ { type: PopoverController }
26609
+ ];
26610
+ UserEventNotificationList.propDecorators = {
26611
+ titleI18n: [{ type: Input }],
26612
+ userEvents: [{ type: Input }],
26613
+ readEvent: [{ type: Output }]
26614
+ };
26615
+
26616
+ class UserEventNotificationComponent {
26617
+ constructor(userEventService, accountService, popoverController, cd) {
26618
+ this.userEventService = userEventService;
26619
+ this.accountService = accountService;
26620
+ this.popoverController = popoverController;
26621
+ this.cd = cd;
26622
+ this.debug = false;
26623
+ this.titleI18n = 'SOCIAL.USER_EVENT.NOTIFICATION.TITLE';
26624
+ this.disabled = false;
26625
+ this.userEventsSubject = new BehaviorSubject(undefined);
26626
+ this._subscriptions = new Subscription();
26627
+ this._logPrefix = '[user-event-notification] ';
26628
+ this._readEvent = new EventEmitter();
26629
+ }
26630
+ get count() {
26631
+ var _a;
26632
+ return (_a = this.userEventService) === null || _a === void 0 ? void 0 : _a.userEventCount.pipe(map(value => value === 0 ? undefined : value));
26633
+ }
26634
+ ngOnInit() {
26635
+ if (isNil(this.userEventService)) {
26636
+ console.warn(`${this._logPrefix}No service injected`);
26637
+ this.disabled = true;
26638
+ return;
26639
+ }
26640
+ // Watch first
26641
+ this._subscriptions.add(this.userEventService.watchAll(0, 10, 'creationDate', 'desc').subscribe(value => {
26642
+ const data = value.data;
26643
+ if (this.debug) {
26644
+ console.debug(`${this._logPrefix}Receiving user events`, data);
26645
+ }
26646
+ this.userEventsSubject.next(data);
26647
+ }));
26648
+ // Then listen changes
26649
+ this._subscriptions.add(this.userEventService.listenChanges(undefined).subscribe(data => {
26650
+ if (this.debug) {
26651
+ console.debug(`${this._logPrefix}Receiving new user events`, data);
26652
+ }
26653
+ this.userEventsSubject.next(data);
26654
+ }));
26655
+ this._readEvent.subscribe(value => {
26656
+ this.userEventService.markAsRead(value);
26657
+ });
26658
+ }
26659
+ showList(event) {
26660
+ return __awaiter(this, void 0, void 0, function* () {
26661
+ const popover = yield this.popoverController.create({
26662
+ component: UserEventNotificationList,
26663
+ componentProps: {
26664
+ titleI18n: this.titleI18n,
26665
+ userEvents: this.userEventsSubject,
26666
+ readEvent: this._readEvent
26667
+ },
26668
+ backdropDismiss: true,
26669
+ keyboardClose: true,
26670
+ event,
26671
+ translucent: true,
26672
+ cssClass: 'popover-large'
26673
+ });
26674
+ yield popover.present();
26675
+ });
26676
+ }
26677
+ markForCheck() {
26678
+ this.cd.markForCheck();
26679
+ }
26680
+ ngOnDestroy() {
26681
+ if (this.debug) {
26682
+ console.debug(`${this._logPrefix}Destroying`);
26683
+ }
26684
+ this._subscriptions.unsubscribe();
26685
+ }
26686
+ }
26687
+ UserEventNotificationComponent.decorators = [
26688
+ { type: Component, args: [{
26689
+ selector: 'app-user-event-notification-component',
26690
+ 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",
26691
+ changeDetection: ChangeDetectionStrategy.OnPush,
26692
+ styles: [""]
26693
+ },] }
26694
+ ];
26695
+ UserEventNotificationComponent.ctorParameters = () => [
26696
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [UserEventServiceToken,] }] },
26697
+ { type: AccountService },
26698
+ { type: PopoverController },
26699
+ { type: ChangeDetectorRef }
26700
+ ];
26701
+ UserEventNotificationComponent.propDecorators = {
26702
+ debug: [{ type: Input }],
26703
+ titleI18n: [{ type: Input }],
26704
+ disabled: [{ type: Input }]
26705
+ };
26706
+
26707
+ class UserEventModule {
26708
+ }
26709
+ UserEventModule.decorators = [
26710
+ { type: NgModule, args: [{
26711
+ imports: [
26712
+ CommonModule,
26713
+ CoreModule,
26714
+ SharedModule
26715
+ ],
26716
+ declarations: [
26717
+ UserEventsTable,
26718
+ UserEventNotificationComponent,
26719
+ UserEventNotificationList,
26720
+ ],
26721
+ exports: [
26722
+ UserEventsTable,
26723
+ UserEventNotificationComponent
26724
+ ]
26725
+ },] }
26726
+ ];
26727
+
26380
26728
  const MessageTypes = {
26381
26729
  INBOX_MESSAGE: 'INBOX_MESSAGE',
26382
26730
  EMAIL: 'EMAIL',
@@ -26588,9 +26936,9 @@ MessageForm.propDecorators = {
26588
26936
  canSelectType: [{ type: Input }]
26589
26937
  };
26590
26938
 
26591
- class SocialModule {
26939
+ class MessageModule {
26592
26940
  }
26593
- SocialModule.decorators = [
26941
+ MessageModule.decorators = [
26594
26942
  { type: NgModule, args: [{
26595
26943
  imports: [
26596
26944
  CommonModule,
@@ -26598,17 +26946,317 @@ SocialModule.decorators = [
26598
26946
  SharedModule
26599
26947
  ],
26600
26948
  declarations: [
26601
- UserEventsTable,
26602
26949
  MessageModal,
26603
26950
  MessageForm
26604
26951
  ],
26605
26952
  exports: [
26606
- UserEventsTable,
26607
26953
  MessageModal
26608
26954
  ]
26609
26955
  },] }
26610
26956
  ];
26611
26957
 
26958
+ var JobProgression_1;
26959
+ // todo use entity
26960
+ class Job {
26961
+ }
26962
+ // @dynamic
26963
+ let JobProgression = JobProgression_1 = class JobProgression extends Entity {
26964
+ constructor() {
26965
+ super(JobProgression_1.TYPENAME);
26966
+ }
26967
+ fromObject(source) {
26968
+ super.fromObject(source);
26969
+ this.message = source.message;
26970
+ this.current = source.current;
26971
+ this.total = source.total;
26972
+ }
26973
+ };
26974
+ JobProgression = JobProgression_1 = __decorate([
26975
+ EntityClass({ typename: 'JobProgressionVO' })
26976
+ ], JobProgression);
26977
+
26978
+ const JobProgressionServiceToken = new InjectionToken('JobProgressionService');
26979
+ const jobProgressionSubscription = gql `subscription UpdateJobProgression($id: Int!, $interval: Int){
26980
+ data: updateJobProgression(id: $id, interval: $interval) {
26981
+ id
26982
+ message
26983
+ current
26984
+ total
26985
+ }
26986
+ }`;
26987
+ class JobProgressionService extends BaseGraphqlService {
26988
+ constructor(graphql, environment) {
26989
+ super(graphql, environment);
26990
+ this.graphql = graphql;
26991
+ this.environment = environment;
26992
+ this._logPrefix = '[job-progression-service] ';
26993
+ // For DEV only
26994
+ this._debug = !(environment === null || environment === void 0 ? void 0 : environment.production);
26995
+ }
26996
+ listenChanges(id, options) {
26997
+ if (isNil(id))
26998
+ throw new Error(`${this._logPrefix}Missing argument 'id'`);
26999
+ if (this._debug)
27000
+ console.debug(`${this._logPrefix}[WS] Listening changes for job progression {${id}}...`);
27001
+ return this.graphql.subscribe({
27002
+ query: jobProgressionSubscription,
27003
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
27004
+ variables: { id, interval: toNumber(options === null || options === void 0 ? void 0 : options.interval, 10) },
27005
+ error: { code: SocialErrorCodes.SUBSCRIBE_JOB_PROGRESSION_ERROR, message: 'SOCIAL.ERROR.SUBSCRIBE_JOB_PROGRESSION_ERROR' }
27006
+ }).pipe(map(({ data }) => {
27007
+ const progression = data && JobProgression.fromObject(data);
27008
+ if (progression && this._debug)
27009
+ console.debug(`${this._logPrefix}Job progression ${id} updated on server`, progression);
27010
+ return progression;
27011
+ }));
27012
+ }
27013
+ }
27014
+ JobProgressionService.decorators = [
27015
+ { type: Injectable }
27016
+ ];
27017
+ JobProgressionService.ctorParameters = () => [
27018
+ { type: GraphqlService },
27019
+ { type: Environment, decorators: [{ type: Optional }, { type: Inject, args: [ENVIRONMENT,] }] }
27020
+ ];
27021
+
27022
+ class JobProgressionList {
27023
+ constructor() {
27024
+ }
27025
+ ngOnInit() {
27026
+ }
27027
+ }
27028
+ JobProgressionList.decorators = [
27029
+ { type: Component, args: [{
27030
+ selector: 'app-job-progression-list',
27031
+ 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",
27032
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}"]
27033
+ },] }
27034
+ ];
27035
+ JobProgressionList.ctorParameters = () => [];
27036
+ JobProgressionList.propDecorators = {
27037
+ titleI18n: [{ type: Input }],
27038
+ jobProgressions: [{ type: Input }]
27039
+ };
27040
+
27041
+ class JobProgressionComponent {
27042
+ constructor(jobProgressionService, popoverController, cd) {
27043
+ this.jobProgressionService = jobProgressionService;
27044
+ this.popoverController = popoverController;
27045
+ this.cd = cd;
27046
+ this.debug = false;
27047
+ this.titleI18n = 'SOCIAL.JOB.PROGRESSION.TITLE';
27048
+ this.jobFinished = new EventEmitter();
27049
+ this.disabled = true;
27050
+ this.color = 'accent';
27051
+ this.mode = 'determinate';
27052
+ this.value = 0;
27053
+ this.jobProgressions = [];
27054
+ this._subscriptions = new Subscription();
27055
+ this._subscriptionsById = {};
27056
+ this._logPrefix = '[job-progression] ';
27057
+ }
27058
+ ngOnInit() {
27059
+ var _a, _b, _c, _d;
27060
+ if (isNil(this.jobProgressionService)) {
27061
+ console.warn(`${this._logPrefix}No service injected`);
27062
+ }
27063
+ // parse options
27064
+ this.autoHide = toBoolean((_a = this.options) === null || _a === void 0 ? void 0 : _a.autoHide, false);
27065
+ this.visible = !this.autoHide;
27066
+ this.autoHideDelay = toNumber((_b = this.options) === null || _b === void 0 ? void 0 : _b.autoHideDelay, 1000);
27067
+ this.autoRemove = toBoolean((_c = this.options) === null || _c === void 0 ? void 0 : _c.autoRemove, true);
27068
+ this.autoRemoveDelay = toNumber((_d = this.options) === null || _d === void 0 ? void 0 : _d.autoRemoveDelay, 1000);
27069
+ }
27070
+ addJob(id) {
27071
+ if (isNil(this.jobProgressionService)) {
27072
+ console.warn(`${this._logPrefix}No service injected. Can't add a job`);
27073
+ return;
27074
+ }
27075
+ if (this.debug) {
27076
+ console.debug(`${this._logPrefix}Add job id=${id}`);
27077
+ }
27078
+ if (!!this.getProgression(id)) {
27079
+ console.warn(`${this._logPrefix}Job (id=${id}) already present`);
27080
+ return;
27081
+ }
27082
+ this.disabled = false;
27083
+ const sub = this.jobProgressionService.listenChanges(id)
27084
+ .pipe(filter(progression => {
27085
+ if (id !== progression.id) {
27086
+ console.error(`${this._logPrefix}Job progression (id=${progression.id}) doesn't match expected job (id=${id})`);
27087
+ return false;
27088
+ }
27089
+ return true;
27090
+ }), map(progression => {
27091
+ // Ensure visible
27092
+ this.visible = true;
27093
+ // Get current progression object
27094
+ const currentProgression = this.getProgression(id);
27095
+ if (!currentProgression) {
27096
+ // add it
27097
+ this.jobProgressions.push(progression);
27098
+ if (this.debug) {
27099
+ console.debug(`${this._logPrefix}Progression added id=${id}`, progression);
27100
+ }
27101
+ }
27102
+ else {
27103
+ // update current
27104
+ Object.assign(currentProgression, progression);
27105
+ if (this.debug) {
27106
+ console.debug(`${this._logPrefix}Progression updated id=${id}`, progression);
27107
+ }
27108
+ }
27109
+ return progression;
27110
+ }))
27111
+ .subscribe(progression => {
27112
+ // update value
27113
+ this.updateValue();
27114
+ // job finished
27115
+ if (progression.total > 0 && progression.current == progression.total) {
27116
+ if (this.debug) {
27117
+ console.debug(`${this._logPrefix}Finished job id=${progression.id}`);
27118
+ }
27119
+ // emit event
27120
+ this.jobFinished.emit(progression.id);
27121
+ if (this.autoRemove) {
27122
+ setTimeout(() => {
27123
+ var _a;
27124
+ this.removeJob(progression.id);
27125
+ // If last job
27126
+ if (this.autoHide && !((_a = this.jobProgressions) === null || _a === void 0 ? void 0 : _a.length)) {
27127
+ setTimeout(() => {
27128
+ this.visible = false;
27129
+ this.markForCheck();
27130
+ }, this.autoHideDelay);
27131
+ }
27132
+ }, this.autoRemoveDelay);
27133
+ }
27134
+ }
27135
+ });
27136
+ this._subscriptionsById[id] = sub;
27137
+ this._subscriptions.add(sub);
27138
+ }
27139
+ removeJob(id) {
27140
+ if (this.debug) {
27141
+ console.debug(`${this._logPrefix}Remove job id=${id}`);
27142
+ }
27143
+ const sub = this._subscriptionsById[id];
27144
+ if (sub) {
27145
+ this._subscriptions.remove(sub);
27146
+ sub.unsubscribe();
27147
+ }
27148
+ const progression = this.getProgression(id);
27149
+ if (progression) {
27150
+ this.jobProgressions.splice(this.jobProgressions.indexOf(progression), 1);
27151
+ }
27152
+ this.updateValue();
27153
+ }
27154
+ getProgression(id) {
27155
+ return this.jobProgressions.find(progression => progression.id === id);
27156
+ }
27157
+ updateValue() {
27158
+ if (this.debug) {
27159
+ console.debug(`${this._logPrefix}Updating value`);
27160
+ }
27161
+ let value = 0;
27162
+ const nbProgression = arraySize(this.jobProgressions);
27163
+ let allIndeterminate = nbProgression > 0;
27164
+ this.jobProgressions.forEach(progression => {
27165
+ const indeterminate = progression.total === 0;
27166
+ allIndeterminate = allIndeterminate && indeterminate;
27167
+ if (!indeterminate) {
27168
+ value = value + progression.current * 100 / progression.total;
27169
+ }
27170
+ });
27171
+ this.value = nbProgression > 0 ? value / nbProgression : 0;
27172
+ this.mode = allIndeterminate ? 'indeterminate' : 'determinate';
27173
+ if (this.debug) {
27174
+ console.debug(`${this._logPrefix}Setting value=${this.value}, mode=${this.mode}`);
27175
+ }
27176
+ this.markForCheck();
27177
+ }
27178
+ showList(event) {
27179
+ return __awaiter(this, void 0, void 0, function* () {
27180
+ const popover = yield this.popoverController.create({
27181
+ component: JobProgressionList,
27182
+ componentProps: {
27183
+ titleI18n: this.titleI18n,
27184
+ jobProgressions: this.jobProgressions
27185
+ },
27186
+ backdropDismiss: true,
27187
+ keyboardClose: true,
27188
+ event,
27189
+ translucent: true,
27190
+ cssClass: 'popover-large'
27191
+ });
27192
+ yield popover.present();
27193
+ });
27194
+ }
27195
+ markForCheck() {
27196
+ this.cd.markForCheck();
27197
+ }
27198
+ ngOnDestroy() {
27199
+ if (this.debug) {
27200
+ console.debug(`${this._logPrefix}Destroying`);
27201
+ }
27202
+ this._subscriptions.unsubscribe();
27203
+ }
27204
+ }
27205
+ JobProgressionComponent.decorators = [
27206
+ { type: Component, args: [{
27207
+ selector: 'app-job-progression',
27208
+ 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",
27209
+ changeDetection: ChangeDetectionStrategy.OnPush,
27210
+ styles: [".floating-spinner{position:absolute;top:6px;left:5px}"]
27211
+ },] }
27212
+ ];
27213
+ JobProgressionComponent.ctorParameters = () => [
27214
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [JobProgressionServiceToken,] }] },
27215
+ { type: PopoverController },
27216
+ { type: ChangeDetectorRef }
27217
+ ];
27218
+ JobProgressionComponent.propDecorators = {
27219
+ debug: [{ type: Input }],
27220
+ titleI18n: [{ type: Input }],
27221
+ options: [{ type: Input }],
27222
+ jobFinished: [{ type: Output }]
27223
+ };
27224
+
27225
+ class JobModule {
27226
+ }
27227
+ JobModule.decorators = [
27228
+ { type: NgModule, args: [{
27229
+ imports: [
27230
+ CommonModule,
27231
+ SharedModule,
27232
+ TranslateModule.forChild(),
27233
+ ],
27234
+ declarations: [
27235
+ JobProgressionComponent,
27236
+ JobProgressionList
27237
+ ],
27238
+ exports: [
27239
+ JobProgressionComponent
27240
+ ]
27241
+ },] }
27242
+ ];
27243
+
27244
+ const SocialModuleOptionsToken = new InjectionToken("SocialModuleOptions");
27245
+ class SocialModule {
27246
+ }
27247
+ SocialModule.decorators = [
27248
+ { type: NgModule, args: [{
27249
+ imports: [
27250
+ CommonModule,
27251
+ CoreModule,
27252
+ SharedModule,
27253
+ UserEventModule,
27254
+ JobModule,
27255
+ MessageModule,
27256
+ ],
27257
+ },] }
27258
+ ];
27259
+
26612
27260
  const Mutations = {
26613
27261
  send: gql `mutation SendMessage($data: MessageVOInput){
26614
27262
  done: sendMessage(message: $data)
@@ -27254,12 +27902,10 @@ class AutocompleteTestPage {
27254
27902
  });
27255
27903
  }
27256
27904
  ngAfterViewInit() {
27257
- if (this.farEntityField) {
27258
- setTimeout(() => {
27259
- this.farEntityField.suggestLengthThreshold = 2;
27260
- this.farEntityField.reloadItems();
27261
- }, 2000);
27262
- }
27905
+ setTimeout(() => {
27906
+ this.farEntityField.suggestLengthThreshold = 2;
27907
+ this.farEntityField.reloadItems();
27908
+ }, 2000);
27263
27909
  setTimeout(() => this.loadData(), 1000);
27264
27910
  }
27265
27911
  // Load the form with data
@@ -27347,7 +27993,7 @@ class AutocompleteTestPage {
27347
27993
  AutocompleteTestPage.decorators = [
27348
27994
  { type: Component, args: [{
27349
27995
  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"
27996
+ 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
27997
  },] }
27352
27998
  ];
27353
27999
  AutocompleteTestPage.ctorParameters = () => [
@@ -27423,13 +28069,13 @@ LatLongTestPage.ctorParameters = () => [
27423
28069
  { type: FormBuilder }
27424
28070
  ];
27425
28071
 
27426
- const moment$6 = momentImported;
28072
+ const moment$7 = momentImported;
27427
28073
  class SwipeTestPage {
27428
28074
  constructor(formBuilder, dateFormatPipe) {
27429
28075
  this.formBuilder = formBuilder;
27430
28076
  this.dateFormatPipe = dateFormatPipe;
27431
28077
  this.$dates = new BehaviorSubject(undefined);
27432
- this._today = moment$6().startOf('day');
28078
+ this._today = moment$7().startOf('day');
27433
28079
  this.form = formBuilder.group({
27434
28080
  empty: [null, Validators.required],
27435
28081
  date: [null, Validators.compose([Validators.required, SharedValidators.validDate])],
@@ -27442,7 +28088,7 @@ class SwipeTestPage {
27442
28088
  ngOnInit() {
27443
28089
  const dates = [];
27444
28090
  for (let d = 0; d < 7; d++) {
27445
- dates[d] = moment$6(this._today).add(d - 3, 'day');
28091
+ dates[d] = moment$7(this._today).add(d - 3, 'day');
27446
28092
  }
27447
28093
  this.$dates.next(dates);
27448
28094
  this.loadData();
@@ -27483,7 +28129,7 @@ SwipeTestPage.ctorParameters = () => [
27483
28129
  { type: DateFormatPipe }
27484
28130
  ];
27485
28131
 
27486
- const moment$7 = momentImported;
28132
+ const moment$8 = momentImported;
27487
28133
  class DateTimeTestPage {
27488
28134
  constructor(platform, formBuilder, cd) {
27489
28135
  this.platform = platform;
@@ -27515,7 +28161,7 @@ class DateTimeTestPage {
27515
28161
  // Load the form with data
27516
28162
  loadData() {
27517
28163
  return __awaiter(this, void 0, void 0, function* () {
27518
- const now = moment$7();
28164
+ const now = moment$8();
27519
28165
  const data = {
27520
28166
  empty: null,
27521
28167
  emptyRequired: null,
@@ -27761,7 +28407,7 @@ NumpadTestPage.ctorParameters = () => [
27761
28407
  { type: FormBuilder }
27762
28408
  ];
27763
28409
 
27764
- const moment$8 = momentImported;
28410
+ const moment$9 = momentImported;
27765
28411
  class DateTestPage {
27766
28412
  constructor(formBuilder, cd) {
27767
28413
  this.formBuilder = formBuilder;
@@ -27794,7 +28440,7 @@ class DateTestPage {
27794
28440
  // Load the form with data
27795
28441
  loadData() {
27796
28442
  return __awaiter(this, void 0, void 0, function* () {
27797
- const now = moment$8();
28443
+ const now = moment$9();
27798
28444
  const nowAtMahe = now.clone().tz(this.timezone).startOf('day');
27799
28445
  const data = {
27800
28446
  empty: null,
@@ -28540,5 +29186,5 @@ CoreTestingModule.decorators = [
28540
29186
  * Generated bundle index. Do not edit.
28541
29187
  */
28542
29188
 
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 };
29189
+ 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
29190
  //# sourceMappingURL=sumaris-net.ngx-components.js.map