@ecodev/natural 62.1.2 → 62.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/fesm2022/ecodev-natural-vanilla.mjs +1199 -0
  2. package/fesm2022/ecodev-natural-vanilla.mjs.map +1 -0
  3. package/fesm2022/ecodev-natural.mjs +625 -644
  4. package/fesm2022/ecodev-natural.mjs.map +1 -1
  5. package/lib/classes/abstract-editable-list.d.ts +3 -2
  6. package/lib/classes/network-activity.service.d.ts +54 -0
  7. package/lib/classes/validators.d.ts +1 -1
  8. package/lib/modules/avatar/component/avatar.component.d.ts +21 -36
  9. package/lib/modules/avatar/service/avatar.service.d.ts +3 -2
  10. package/lib/modules/columns-picker/columns-picker.component.d.ts +2 -2
  11. package/lib/modules/common/services/seo.provider.d.ts +2 -2
  12. package/lib/modules/dropdown-components/type-natural-select/type-natural-select.component.d.ts +1 -0
  13. package/lib/modules/dropdown-components/type-select/type-select.component.d.ts +1 -1
  14. package/lib/modules/file/abstract-file.d.ts +8 -5
  15. package/lib/modules/file/component/file.component.d.ts +9 -4
  16. package/lib/modules/file/file-drop.directive.d.ts +2 -3
  17. package/lib/modules/fixed-button-detail/fixed-button-detail.component.d.ts +2 -3
  18. package/lib/modules/hierarchic-selector/classes/model-node.d.ts +1 -1
  19. package/lib/modules/hierarchic-selector/hierarchic-selector/hierarchic-selector.component.d.ts +3 -3
  20. package/lib/modules/icon/icon.module.d.ts +2 -2
  21. package/lib/modules/panels/panels.service.d.ts +17 -2
  22. package/lib/modules/relations/relations.component.d.ts +4 -4
  23. package/lib/modules/search/dropdown-container/dropdown-container.component.d.ts +2 -3
  24. package/lib/modules/search/group/group.component.d.ts +2 -3
  25. package/lib/modules/search/input/input.component.d.ts +5 -5
  26. package/lib/modules/search/search/search.component.d.ts +5 -4
  27. package/lib/modules/select/abstract-select.component.d.ts +3 -3
  28. package/lib/modules/select/select/select.component.d.ts +4 -4
  29. package/lib/modules/sidenav/sidenav-container/sidenav-container.component.d.ts +1 -1
  30. package/lib/modules/table-button/table-button.component.d.ts +18 -18
  31. package/package.json +16 -14
  32. package/public-api.d.ts +1 -0
  33. package/src/lib/_natural.theme.scss +1 -2
  34. package/vanilla/index.d.ts +5 -0
  35. package/vanilla/package.json +3 -0
  36. package/vanilla/public-api.d.ts +11 -0
  37. package/vanilla/src/lib/classes/crypto.d.ts +8 -0
  38. package/vanilla/src/lib/classes/data-source.d.ts +32 -0
  39. package/vanilla/src/lib/classes/query-variable-manager-utils.d.ts +2 -0
  40. package/vanilla/src/lib/classes/query-variable-manager.d.ts +91 -0
  41. package/vanilla/src/lib/classes/signing.d.ts +7 -0
  42. package/vanilla/src/lib/classes/utility.d.ts +77 -0
  43. package/vanilla/src/lib/modules/search/classes/graphql-doctrine.types.d.ts +83 -0
  44. package/vanilla/src/lib/modules/search/classes/utils.d.ts +17 -0
  45. package/vanilla/src/lib/modules/search/types/dropdown-component.d.ts +20 -0
  46. package/vanilla/src/lib/modules/search/types/facet.d.ts +75 -0
  47. package/vanilla/src/lib/modules/search/types/values.d.ts +32 -0
  48. package/vanilla/src/lib/services/abstract-model.service.d.ts +244 -0
  49. package/vanilla/src/lib/services/debounce.service.d.ts +52 -0
  50. package/vanilla/src/lib/types/types.d.ts +100 -0
  51. package/esm2022/ecodev-natural.mjs +0 -5
  52. package/esm2022/lib/classes/abstract-detail.mjs +0 -229
  53. package/esm2022/lib/classes/abstract-editable-list.mjs +0 -99
  54. package/esm2022/lib/classes/abstract-list.mjs +0 -461
  55. package/esm2022/lib/classes/abstract-navigable-list.mjs +0 -133
  56. package/esm2022/lib/classes/apollo-utils.mjs +0 -59
  57. package/esm2022/lib/classes/crypto.mjs +0 -23
  58. package/esm2022/lib/classes/cumulative-changes.mjs +0 -50
  59. package/esm2022/lib/classes/data-source.mjs +0 -71
  60. package/esm2022/lib/classes/providers.mjs +0 -13
  61. package/esm2022/lib/classes/query-variable-manager-utils.mjs +0 -14
  62. package/esm2022/lib/classes/query-variable-manager.mjs +0 -172
  63. package/esm2022/lib/classes/rxjs.mjs +0 -54
  64. package/esm2022/lib/classes/signing.mjs +0 -38
  65. package/esm2022/lib/classes/tld.mjs +0 -1476
  66. package/esm2022/lib/classes/utility.mjs +0 -234
  67. package/esm2022/lib/classes/validators.mjs +0 -179
  68. package/esm2022/lib/directives/http-prefix.directive.mjs +0 -47
  69. package/esm2022/lib/modules/alert/alert.service.mjs +0 -53
  70. package/esm2022/lib/modules/alert/confirm.component.mjs +0 -16
  71. package/esm2022/lib/modules/alert/public-api.mjs +0 -6
  72. package/esm2022/lib/modules/avatar/component/avatar.component.mjs +0 -203
  73. package/esm2022/lib/modules/avatar/public-api.mjs +0 -6
  74. package/esm2022/lib/modules/avatar/service/avatar.service.mjs +0 -63
  75. package/esm2022/lib/modules/avatar/sources/gravatar.mjs +0 -29
  76. package/esm2022/lib/modules/avatar/sources/image.mjs +0 -13
  77. package/esm2022/lib/modules/avatar/sources/initials.mjs +0 -39
  78. package/esm2022/lib/modules/avatar/sources/source.mjs +0 -16
  79. package/esm2022/lib/modules/columns-picker/columns-picker.component.mjs +0 -145
  80. package/esm2022/lib/modules/columns-picker/public-api.mjs +0 -5
  81. package/esm2022/lib/modules/columns-picker/types.mjs +0 -2
  82. package/esm2022/lib/modules/common/directives/background-density.directive.mjs +0 -63
  83. package/esm2022/lib/modules/common/directives/linkable-tab.directive.mjs +0 -93
  84. package/esm2022/lib/modules/common/directives/src-density.directive.mjs +0 -72
  85. package/esm2022/lib/modules/common/pipes/capitalize.pipe.mjs +0 -24
  86. package/esm2022/lib/modules/common/pipes/ellipsis.pipe.mjs +0 -17
  87. package/esm2022/lib/modules/common/pipes/enum.pipe.mjs +0 -24
  88. package/esm2022/lib/modules/common/pipes/time-ago.pipe.mjs +0 -140
  89. package/esm2022/lib/modules/common/public-api.mjs +0 -14
  90. package/esm2022/lib/modules/common/services/memory-storage.mjs +0 -110
  91. package/esm2022/lib/modules/common/services/seo.provider.mjs +0 -23
  92. package/esm2022/lib/modules/common/services/seo.service.mjs +0 -235
  93. package/esm2022/lib/modules/detail-header/detail-header.component.mjs +0 -84
  94. package/esm2022/lib/modules/detail-header/public-api.mjs +0 -5
  95. package/esm2022/lib/modules/dialog-trigger/dialog-trigger.component.mjs +0 -72
  96. package/esm2022/lib/modules/dialog-trigger/public-api.mjs +0 -5
  97. package/esm2022/lib/modules/dropdown-components/abstract-association-select-component.directive.mjs +0 -100
  98. package/esm2022/lib/modules/dropdown-components/public-api.mjs +0 -14
  99. package/esm2022/lib/modules/dropdown-components/type-boolean/type-boolean.component.mjs +0 -39
  100. package/esm2022/lib/modules/dropdown-components/type-date/type-date.component.mjs +0 -173
  101. package/esm2022/lib/modules/dropdown-components/type-date-range/type-date-range.component.mjs +0 -134
  102. package/esm2022/lib/modules/dropdown-components/type-hierarchic-selector/type-hierarchic-selector.component.mjs +0 -80
  103. package/esm2022/lib/modules/dropdown-components/type-natural-select/type-natural-select.component.mjs +0 -48
  104. package/esm2022/lib/modules/dropdown-components/type-number/type-number.component.mjs +0 -110
  105. package/esm2022/lib/modules/dropdown-components/type-options/type-options.component.mjs +0 -64
  106. package/esm2022/lib/modules/dropdown-components/type-select/type-select.component.mjs +0 -175
  107. package/esm2022/lib/modules/dropdown-components/type-text/type-text.component.mjs +0 -62
  108. package/esm2022/lib/modules/dropdown-components/types.mjs +0 -41
  109. package/esm2022/lib/modules/dropdown-components/utils.mjs +0 -35
  110. package/esm2022/lib/modules/file/abstract-file.mjs +0 -230
  111. package/esm2022/lib/modules/file/component/file.component.mjs +0 -172
  112. package/esm2022/lib/modules/file/file-drop.directive.mjs +0 -111
  113. package/esm2022/lib/modules/file/file-select.directive.mjs +0 -26
  114. package/esm2022/lib/modules/file/file.service.mjs +0 -43
  115. package/esm2022/lib/modules/file/public-api.mjs +0 -9
  116. package/esm2022/lib/modules/file/types.mjs +0 -2
  117. package/esm2022/lib/modules/file/utils.mjs +0 -129
  118. package/esm2022/lib/modules/fixed-button/fixed-button.component.mjs +0 -30
  119. package/esm2022/lib/modules/fixed-button/public-api.mjs +0 -5
  120. package/esm2022/lib/modules/fixed-button-detail/fixed-button-detail.component.mjs +0 -56
  121. package/esm2022/lib/modules/fixed-button-detail/public-api.mjs +0 -5
  122. package/esm2022/lib/modules/hierarchic-selector/classes/flat-node.mjs +0 -18
  123. package/esm2022/lib/modules/hierarchic-selector/classes/hierarchic-configuration.mjs +0 -2
  124. package/esm2022/lib/modules/hierarchic-selector/classes/hierarchic-filters-configuration.mjs +0 -2
  125. package/esm2022/lib/modules/hierarchic-selector/classes/model-node.mjs +0 -14
  126. package/esm2022/lib/modules/hierarchic-selector/hierarchic-selector/hierarchic-selector.component.mjs +0 -398
  127. package/esm2022/lib/modules/hierarchic-selector/hierarchic-selector/hierarchic-selector.service.mjs +0 -243
  128. package/esm2022/lib/modules/hierarchic-selector/hierarchic-selector-dialog/hierarchic-selector-dialog.component.mjs +0 -38
  129. package/esm2022/lib/modules/hierarchic-selector/hierarchic-selector-dialog/hierarchic-selector-dialog.service.mjs +0 -22
  130. package/esm2022/lib/modules/hierarchic-selector/public-api.mjs +0 -10
  131. package/esm2022/lib/modules/icon/icon.directive.mjs +0 -96
  132. package/esm2022/lib/modules/icon/icon.module.mjs +0 -33
  133. package/esm2022/lib/modules/icon/public-api.mjs +0 -6
  134. package/esm2022/lib/modules/logger/error-handler.mjs +0 -87
  135. package/esm2022/lib/modules/logger/error.module.mjs +0 -22
  136. package/esm2022/lib/modules/logger/public-api.mjs +0 -6
  137. package/esm2022/lib/modules/matomo/matomo.service.mjs +0 -96
  138. package/esm2022/lib/modules/matomo/public-api.mjs +0 -5
  139. package/esm2022/lib/modules/panels/abstract-panel.mjs +0 -76
  140. package/esm2022/lib/modules/panels/fallback-if-no-opened-panels.urlmatcher.mjs +0 -12
  141. package/esm2022/lib/modules/panels/panels.component.mjs +0 -27
  142. package/esm2022/lib/modules/panels/panels.module.mjs +0 -10
  143. package/esm2022/lib/modules/panels/panels.service.mjs +0 -329
  144. package/esm2022/lib/modules/panels/panels.urlmatcher.mjs +0 -75
  145. package/esm2022/lib/modules/panels/public-api.mjs +0 -11
  146. package/esm2022/lib/modules/panels/types.mjs +0 -3
  147. package/esm2022/lib/modules/relations/public-api.mjs +0 -5
  148. package/esm2022/lib/modules/relations/relations.component.mjs +0 -254
  149. package/esm2022/lib/modules/search/classes/graphql-doctrine.mjs +0 -111
  150. package/esm2022/lib/modules/search/classes/graphql-doctrine.types.mjs +0 -14
  151. package/esm2022/lib/modules/search/classes/transformers.mjs +0 -142
  152. package/esm2022/lib/modules/search/classes/url.mjs +0 -53
  153. package/esm2022/lib/modules/search/classes/utils.mjs +0 -25
  154. package/esm2022/lib/modules/search/dropdown-container/dropdown-container-animations.mjs +0 -44
  155. package/esm2022/lib/modules/search/dropdown-container/dropdown-container.component.mjs +0 -87
  156. package/esm2022/lib/modules/search/dropdown-container/dropdown-ref.mjs +0 -24
  157. package/esm2022/lib/modules/search/dropdown-container/dropdown.service.mjs +0 -90
  158. package/esm2022/lib/modules/search/facet-selector/facet-selector.component.mjs +0 -45
  159. package/esm2022/lib/modules/search/group/group.component.mjs +0 -53
  160. package/esm2022/lib/modules/search/input/input.component.mjs +0 -365
  161. package/esm2022/lib/modules/search/public-api.mjs +0 -7
  162. package/esm2022/lib/modules/search/search/search.component.mjs +0 -102
  163. package/esm2022/lib/modules/search/types/dropdown-component.mjs +0 -2
  164. package/esm2022/lib/modules/search/types/facet.mjs +0 -2
  165. package/esm2022/lib/modules/search/types/values.mjs +0 -2
  166. package/esm2022/lib/modules/select/abstract-select.component.mjs +0 -232
  167. package/esm2022/lib/modules/select/public-api.mjs +0 -7
  168. package/esm2022/lib/modules/select/select/select.component.mjs +0 -310
  169. package/esm2022/lib/modules/select/select-enum/select-enum.component.mjs +0 -57
  170. package/esm2022/lib/modules/select/select-hierarchic/select-hierarchic.component.mjs +0 -155
  171. package/esm2022/lib/modules/sidenav/public-api.mjs +0 -9
  172. package/esm2022/lib/modules/sidenav/sidenav/sidenav.component.mjs +0 -15
  173. package/esm2022/lib/modules/sidenav/sidenav-container/sidenav-container.component.mjs +0 -90
  174. package/esm2022/lib/modules/sidenav/sidenav-content/sidenav-content.component.mjs +0 -11
  175. package/esm2022/lib/modules/sidenav/sidenav-stack.service.mjs +0 -50
  176. package/esm2022/lib/modules/sidenav/sidenav.service.mjs +0 -196
  177. package/esm2022/lib/modules/stamp/public-api.mjs +0 -5
  178. package/esm2022/lib/modules/stamp/stamp.component.mjs +0 -23
  179. package/esm2022/lib/modules/table-button/public-api.mjs +0 -5
  180. package/esm2022/lib/modules/table-button/table-button.component.mjs +0 -78
  181. package/esm2022/lib/services/abstract-model.service.mjs +0 -526
  182. package/esm2022/lib/services/debounce.service.mjs +0 -149
  183. package/esm2022/lib/services/enum.service.mjs +0 -64
  184. package/esm2022/lib/services/link-mutation.service.mjs +0 -154
  185. package/esm2022/lib/services/persistence.service.mjs +0 -115
  186. package/esm2022/lib/services/swiss-parsing-date-adapter.service.mjs +0 -63
  187. package/esm2022/lib/types/types.mjs +0 -2
  188. package/esm2022/public-api.mjs +0 -46
@@ -0,0 +1,1199 @@
1
+ import { pickBy, cloneDeep, uniq, groupBy, mergeWith, defaultsDeep, omit, merge, pick, defaults } from 'lodash-es';
2
+ import { BehaviorSubject, throwError, from, switchMap, ReplaySubject, Subject, debounceTime, raceWith, take, mergeMap, EMPTY, shareReplay, catchError, of, Observable, forkJoin, map, first, combineLatest } from 'rxjs';
3
+ import { Apollo, gql } from 'apollo-angular';
4
+ import { NetworkStatus } from '@apollo/client/core';
5
+ import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
6
+ import { takeWhile, map as map$1, switchMap as switchMap$1, filter, debounceTime as debounceTime$1, tap, shareReplay as shareReplay$1, startWith } from 'rxjs/operators';
7
+ import * as i0 from '@angular/core';
8
+ import { Injectable, inject } from '@angular/core';
9
+
10
+ function formatIsoDate(date) {
11
+ if (!date) {
12
+ return null;
13
+ }
14
+ const y = date.getFullYear();
15
+ const m = date.getMonth() + 1;
16
+ const d = date.getDate();
17
+ return y + '-' + (m < 10 ? '0' : '') + m + '-' + (d < 10 ? '0' : '') + d;
18
+ }
19
+ /**
20
+ * Format a date and time in a way that will preserve the local time zone.
21
+ * This allows the server side to know the day (without time) that was selected on client side.
22
+ *
23
+ * So something like: "2021-09-23T17:57:16+09:00"
24
+ */
25
+ function formatIsoDateTime(date) {
26
+ const timezoneOffsetInMinutes = date.getTimezoneOffset();
27
+ const timezoneOffsetInHours = -Math.trunc(timezoneOffsetInMinutes / 60); // UTC minus local time
28
+ const sign = timezoneOffsetInHours >= 0 ? '+' : '-';
29
+ const hoursLeadingZero = Math.abs(timezoneOffsetInHours) < 10 ? '0' : '';
30
+ const remainderMinutes = -(timezoneOffsetInMinutes % 60);
31
+ const minutesLeadingZero = Math.abs(remainderMinutes) < 10 ? '0' : '';
32
+ // It's a bit unfortunate that we need to construct a new Date instance,
33
+ // but we don't want the original Date instance to be modified
34
+ const correctedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
35
+ correctedDate.setHours(date.getHours() + timezoneOffsetInHours);
36
+ const iso = correctedDate
37
+ .toISOString()
38
+ .replace(/\.\d{3}Z/, '')
39
+ .replace('Z', '');
40
+ return (iso +
41
+ sign +
42
+ hoursLeadingZero +
43
+ Math.abs(timezoneOffsetInHours).toString() +
44
+ ':' +
45
+ minutesLeadingZero +
46
+ remainderMinutes);
47
+ }
48
+ /**
49
+ * Relations to full objects are converted to their IDs only.
50
+ *
51
+ * So {user: {id: 123}} becomes {user: 123}
52
+ */
53
+ function relationsToIds(object) {
54
+ const newObj = {};
55
+ Object.keys(object).forEach(key => {
56
+ let value = object[key];
57
+ if (value === null || value === undefined) {
58
+ // noop
59
+ }
60
+ else if (hasId(value)) {
61
+ value = value.id;
62
+ }
63
+ else if (Array.isArray(value)) {
64
+ value = value.map((i) => (hasId(i) ? i.id : i));
65
+ }
66
+ else if (typeof value === 'object' && !(value instanceof File) && !(value instanceof Date)) {
67
+ value = pickBy(value, (v, k) => k !== '__typename'); // omit(value, ['__typename']) ?
68
+ }
69
+ newObj[key] = value;
70
+ });
71
+ return newObj;
72
+ }
73
+ function hasId(value) {
74
+ return !!value && typeof value === 'object' && 'id' in value && !!value.id;
75
+ }
76
+ /**
77
+ * Returns the plural form of the given name
78
+ *
79
+ * This is **not** necessarily valid english grammar. Its only purpose is for internal usage, not for humans.
80
+ *
81
+ * This **MUST** be kept in sync with `\Ecodev\Felix\Api\Plural:make()`.
82
+ *
83
+ * This is a bit performance-sensitive, so we should keep it fast and only cover cases that we actually need.
84
+ */
85
+ function makePlural(name) {
86
+ // Words ending in a y preceded by a vowel form their plurals by adding -s:
87
+ if (/[aeiou]y$/.exec(name)) {
88
+ return name + 's';
89
+ }
90
+ const plural = name + 's';
91
+ return plural.replace(/ys$/, 'ies').replace(/ss$/, 'ses').replace(/xs$/, 'xes');
92
+ }
93
+ /**
94
+ * Returns the string with the first letter as capital
95
+ */
96
+ function upperCaseFirstLetter(term) {
97
+ return term.charAt(0).toUpperCase() + term.slice(1);
98
+ }
99
+ /**
100
+ * Replace all attributes of first object with the ones provided by the second, but keeps the reference
101
+ */
102
+ function replaceObjectKeepingReference(obj, newObj) {
103
+ if (!obj || !newObj) {
104
+ return;
105
+ }
106
+ Object.keys(obj).forEach(key => {
107
+ delete obj[key];
108
+ });
109
+ Object.keys(newObj).forEach(key => {
110
+ obj[key] = newObj[key];
111
+ });
112
+ }
113
+ /**
114
+ * Get contrasted color for text in the slider thumb
115
+ * @param hexBgColor string in hexadecimals representing the background color
116
+ */
117
+ function getForegroundColor(hexBgColor) {
118
+ const rgb = hexToRgb(hexBgColor.slice(0, 7)); // splice remove alpha and consider only "visible" color at 100% alpha
119
+ const o = Math.round((rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000);
120
+ return o > 125 ? 'black' : 'white';
121
+ }
122
+ function hexToRgb(hex) {
123
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
124
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
125
+ hex = hex.replace(shorthandRegex, (m, r, g, b) => {
126
+ return r + r + g + g + b + b;
127
+ });
128
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
129
+ return result
130
+ ? {
131
+ r: parseInt(result[1], 16),
132
+ g: parseInt(result[2], 16),
133
+ b: parseInt(result[3], 16),
134
+ }
135
+ : {
136
+ r: 0,
137
+ g: 0,
138
+ b: 0,
139
+ };
140
+ }
141
+ /**
142
+ * Convert RGB color to hexadecimal color
143
+ *
144
+ * ```ts
145
+ * rgbToHex('rgb(255, 00, 255)'); // '#FF00FF'
146
+ * ```
147
+ */
148
+ function rgbToHex(rgb) {
149
+ const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(rgb);
150
+ if (!m) {
151
+ return rgb;
152
+ }
153
+ return '#' + [m[1], m[2], m[3]].map(x => parseInt(x).toString(16).toUpperCase().padStart(2, '0')).join('');
154
+ }
155
+ /**
156
+ * During lodash.mergeWith, overrides arrays
157
+ */
158
+ function mergeOverrideArray(destValue, source) {
159
+ if (Array.isArray(source)) {
160
+ return source;
161
+ }
162
+ }
163
+ /**
164
+ * Copy text to clipboard.
165
+ * Accepts line breaks `\n` as textarea do.
166
+ */
167
+ function copyToClipboard(document, text) {
168
+ const input = document.createElement('textarea');
169
+ document.body.append(input);
170
+ input.value = text;
171
+ input.select();
172
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
173
+ document.execCommand('copy');
174
+ document.body.removeChild(input);
175
+ }
176
+ function deepFreeze(o) {
177
+ Object.values(o).forEach(v => Object.isFrozen(v) || deepFreeze(v));
178
+ return Object.freeze(o);
179
+ }
180
+ /**
181
+ * Return a valid PaginationInput from whatever is available from data. Invalid properties/types will be dropped.
182
+ */
183
+ function validatePagination(data) {
184
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
185
+ return null;
186
+ }
187
+ const pagination = {};
188
+ if ('offset' in data && (data.offset === null || typeof data.offset === 'number')) {
189
+ pagination.offset = data.offset;
190
+ }
191
+ if ('pageIndex' in data && (data.pageIndex === null || typeof data.pageIndex === 'number')) {
192
+ pagination.pageIndex = data.pageIndex;
193
+ }
194
+ if ('pageSize' in data && (data.pageSize === null || typeof data.pageSize === 'number')) {
195
+ pagination.pageSize = data.pageSize;
196
+ }
197
+ return pagination;
198
+ }
199
+ /**
200
+ * Return a valid Sortings from whatever is available from data. Invalid properties/types will be dropped.
201
+ */
202
+ function validateSorting(data) {
203
+ if (!Array.isArray(data)) {
204
+ return null;
205
+ }
206
+ const result = [];
207
+ data.forEach(s => {
208
+ const r = validateOneSorting(s);
209
+ if (r) {
210
+ result.push(r);
211
+ }
212
+ });
213
+ return result;
214
+ }
215
+ function validateOneSorting(data) {
216
+ if (!data || typeof data !== 'object' || !('field' in data)) {
217
+ return null;
218
+ }
219
+ const sorting = { field: data.field };
220
+ if ('order' in data &&
221
+ (data.order === SortingOrder.ASC || data.order === SortingOrder.DESC || data.order === null)) {
222
+ sorting.order = data.order;
223
+ }
224
+ if ('nullAsHighest' in data && (data.nullAsHighest === null || typeof data.nullAsHighest === 'boolean')) {
225
+ sorting.nullAsHighest = data.nullAsHighest;
226
+ }
227
+ if ('emptyStringAsHighest' in data &&
228
+ (data.emptyStringAsHighest === null || typeof data.emptyStringAsHighest === 'boolean')) {
229
+ sorting.emptyStringAsHighest = data.emptyStringAsHighest;
230
+ }
231
+ return sorting;
232
+ }
233
+ /**
234
+ * Return valid columns from whatever is available from data. Invalid properties/types will be dropped.
235
+ */
236
+ function validateColumns(data) {
237
+ if (typeof data !== 'string') {
238
+ return null;
239
+ }
240
+ return data.split(',').filter(string => string);
241
+ }
242
+
243
+ // Basic; loosely typed structure for graphql-doctrine filters
244
+ // Logical operator to be used in conditions
245
+ var LogicalOperator;
246
+ (function (LogicalOperator) {
247
+ LogicalOperator["AND"] = "AND";
248
+ LogicalOperator["OR"] = "OR";
249
+ })(LogicalOperator || (LogicalOperator = {}));
250
+ // Join types to be used in DQL
251
+ var JoinType;
252
+ (function (JoinType) {
253
+ JoinType["innerJoin"] = "innerJoin";
254
+ JoinType["leftJoin"] = "leftJoin";
255
+ })(JoinType || (JoinType = {}));
256
+
257
+ function hasMixedGroupLogic(groups) {
258
+ // Complete lack of definition by fallback on AND operator
259
+ const completedGroups = cloneDeep(groups).map(group => {
260
+ if (!group.groupLogic) {
261
+ group.groupLogic = LogicalOperator.AND;
262
+ }
263
+ return group;
264
+ });
265
+ const groupLogics = uniq(Object.keys(groupBy(completedGroups.slice(1), 'groupLogic')));
266
+ return groupLogics.length > 1;
267
+ }
268
+
269
+ var SortingOrder;
270
+ (function (SortingOrder) {
271
+ SortingOrder["ASC"] = "ASC";
272
+ SortingOrder["DESC"] = "DESC";
273
+ })(SortingOrder || (SortingOrder = {}));
274
+ /**
275
+ * During lodash merge, concat arrays
276
+ */
277
+ function mergeConcatArray(destValue, source) {
278
+ if (Array.isArray(source)) {
279
+ if (destValue) {
280
+ return destValue.concat(source);
281
+ }
282
+ else {
283
+ return source;
284
+ }
285
+ }
286
+ }
287
+ /**
288
+ * Filter manager stores a set of channels that contain a variable object and exposes an observable "variables" that updates with the result
289
+ * of all channels merged together.
290
+ *
291
+ * A channel is supposed to be used by a given aspect of the GUI (pagination, sorting, search, others ?).
292
+ *
293
+ * ```ts
294
+ * const fm = new QueryVariablesManager();
295
+ * fm.merge('componentA-variables', {a : [1, 2, 3]});
296
+ * ```
297
+ *
298
+ * Variables attributes is a BehaviorSubject. That mean it's not mandatory to subscribe, we can just call getValue or value attributes on
299
+ * it :
300
+ *
301
+ * ```ts
302
+ * console.log(fm.variables.value); // {a : [1, 2, 3]}
303
+ * ```
304
+ *
305
+ * Set new variables for 'componentA-variables':
306
+ *
307
+ * ```ts
308
+ * fm.merge('componentA-variables', {a : [1, 2]});
309
+ * console.log(fm.variables.value); // {a : [1, 2, 3]}
310
+ * ```
311
+ *
312
+ * Set new variables for new channel:
313
+ *
314
+ * ```ts
315
+ * fm.merge('componentB-variables', {a : [3, 4]});
316
+ * console.log(fm.variables.value); // {a : [1, 2, 3, 4]}
317
+ * ```
318
+ */
319
+ class NaturalQueryVariablesManager {
320
+ variables = new BehaviorSubject(undefined);
321
+ channels = new Map();
322
+ constructor(queryVariablesManager) {
323
+ if (queryVariablesManager) {
324
+ this.channels = queryVariablesManager.getChannelsCopy();
325
+ this.updateVariables();
326
+ }
327
+ }
328
+ /**
329
+ * Set or override all the variables that may exist in the given channel
330
+ */
331
+ set(channelName, variables) {
332
+ // cloneDeep to change reference and prevent some interactions when merge
333
+ if (variables) {
334
+ this.channels.set(channelName, cloneDeep(variables));
335
+ }
336
+ else {
337
+ this.channels.delete(channelName);
338
+ }
339
+ this.updateVariables();
340
+ }
341
+ /**
342
+ * Return a deep clone of the variables for the given channel name.
343
+ *
344
+ * Avoid returning the same reference to prevent an attribute change, then another channel update that would
345
+ * used this changed attribute without having explicitly asked QueryVariablesManager to update it.
346
+ */
347
+ get(channelName) {
348
+ return cloneDeep(this.channels.get(channelName));
349
+ }
350
+ /**
351
+ * Merge variable into a channel, overriding arrays in same channel / key
352
+ */
353
+ merge(channelName, newVariables) {
354
+ const variables = this.channels.get(channelName);
355
+ if (variables) {
356
+ mergeWith(variables, cloneDeep(newVariables), mergeOverrideArray); // merge preserves references, cloneDeep prevent that
357
+ this.updateVariables();
358
+ }
359
+ else {
360
+ this.set(channelName, newVariables);
361
+ }
362
+ }
363
+ /**
364
+ * Apply default values to a channel
365
+ * Note : lodash defaults only defines values on destinations keys that are undefined
366
+ */
367
+ defaults(channelName, newVariables) {
368
+ const variables = this.channels.get(channelName);
369
+ if (variables) {
370
+ defaultsDeep(variables, newVariables);
371
+ this.updateVariables();
372
+ }
373
+ else {
374
+ this.set(channelName, newVariables);
375
+ }
376
+ }
377
+ getChannelsCopy() {
378
+ return new Map(this.channels);
379
+ }
380
+ /**
381
+ * Merge channels in a single object
382
+ * Arrays are concatenated
383
+ * Filter groups are combined smartly (see mergeGroupList)
384
+ */
385
+ updateVariables() {
386
+ const merged = {};
387
+ this.channels.forEach(channelVariables => {
388
+ if (channelVariables.filter) {
389
+ // Merge filter's groups first
390
+ const groups = this.mergeGroupList(merged.filter?.groups ? merged.filter.groups : [], channelVariables.filter.groups || []);
391
+ // Merge filter key (that contain groups)
392
+ if (groups?.length) {
393
+ if (merged.filter) {
394
+ merged.filter.groups = groups;
395
+ }
396
+ else {
397
+ merged.filter = { groups: groups };
398
+ }
399
+ }
400
+ else {
401
+ mergeWith(merged, { filter: channelVariables.filter }, mergeConcatArray);
402
+ }
403
+ }
404
+ // Merge other attributes than filter
405
+ mergeWith(merged, omit(channelVariables, 'filter'), mergeConcatArray);
406
+ });
407
+ this.variables.next(merged);
408
+ }
409
+ /**
410
+ * Cross merge two filters
411
+ * Only accepts groups with same groupLogic (ignores the first one, because there is no groupLogic in this one)
412
+ * @throws In case two non-empty lists of groups are given and at one of them mix groupLogic value, throws an error
413
+ */
414
+ mergeGroupList(groupsA, groupsB) {
415
+ if (groupsA.length === 0 && groupsB.length === 0) {
416
+ return []; // empty listings, return empty lists
417
+ }
418
+ if (groupsA.length === 0 && groupsB.length > 0) {
419
+ return groupsB; // One list is empty, return the one that is not
420
+ }
421
+ if (groupsB.length === 0 && groupsA.length > 0) {
422
+ return groupsA; // One list is empty, return the one that is not
423
+ }
424
+ const groups = [];
425
+ if (hasMixedGroupLogic(groupsA) || hasMixedGroupLogic(groupsB)) {
426
+ throw Error('QueryVariables groups contain mixed group logics');
427
+ }
428
+ groupsA.forEach(groupA => {
429
+ groupsB.forEach(groupB => {
430
+ groups.push(mergeWith(cloneDeep(groupA), groupB, mergeConcatArray));
431
+ });
432
+ });
433
+ return groups;
434
+ }
435
+ }
436
+
437
+ function bufferToHexa(hashBuffer) {
438
+ const hashArray = new Uint8Array(hashBuffer); // convert buffer to byte array
439
+ return hashArray.reduce((result, byte) => result + byte.toString(16).padStart(2, '0'), ''); // convert bytes to hex string
440
+ }
441
+ /**
442
+ * Thin wrapper around browsers' native SubtleCrypto for convenience of use
443
+ */
444
+ async function sha256(message) {
445
+ const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
446
+ const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message
447
+ return bufferToHexa(hashBuffer);
448
+ }
449
+ /**
450
+ * Thin wrapper around browsers' native SubtleCrypto for convenience of use
451
+ */
452
+ async function hmacSha256(secret, payload) {
453
+ const encoder = new TextEncoder();
454
+ const algorithm = { name: 'HMAC', hash: 'SHA-256' };
455
+ const key = await crypto.subtle.importKey('raw', encoder.encode(secret), algorithm, false, ['sign']);
456
+ const signature = await crypto.subtle.sign(algorithm.name, key, encoder.encode(payload));
457
+ return bufferToHexa(signature);
458
+ }
459
+
460
+ function getOperations(req) {
461
+ if (req.body instanceof FormData) {
462
+ const operations = req.body.get('operations');
463
+ if (typeof operations !== 'string') {
464
+ throw new Error('Cannot sign a GraphQL query that is using FormData but that is missing the key `operations`');
465
+ }
466
+ return operations;
467
+ }
468
+ else {
469
+ return JSON.stringify(req.body);
470
+ }
471
+ }
472
+ /**
473
+ * Sign all HTTP POST requests that are GraphQL queries against `/graphql` endpoint with a custom signature.
474
+ *
475
+ * The server will validate the signature before executing the GraphQL query.
476
+ */
477
+ function graphqlQuerySigner(key) {
478
+ // Validates the configuration exactly 1 time (not for
479
+ // every query), and if not reject **all** HTTP requests
480
+ if (!key) {
481
+ return () => throwError(() => new Error('graphqlQuerySigner requires a non-empty key. Configure it in local.php under signedQueries.'));
482
+ }
483
+ return (req, next) => {
484
+ const mustSign = req.method === 'POST' && /\/graphql(\?|$)/.exec(req.url);
485
+ if (!mustSign) {
486
+ return next(req);
487
+ }
488
+ const operations = getOperations(req);
489
+ const timestamp = Math.round(Date.now() / 1000);
490
+ const payload = timestamp + operations;
491
+ return from(hmacSha256(key, payload)).pipe(switchMap(hash => {
492
+ const header = `v1.${timestamp}.${hash}`;
493
+ const signedRequest = req.clone({
494
+ headers: req.headers.set('X-Signature', header),
495
+ });
496
+ return next(signedRequest);
497
+ }));
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Debounce subscriptions to update mutations, with the possibility to cancel one, flush one, or flush all of them.
503
+ *
504
+ * `modelService` is also used to separate objects by their types. So User with ID 1 is not confused with Product with ID 1.
505
+ *
506
+ * `id` must be the ID of the object that will be updated.
507
+ */
508
+ class NaturalDebounceService {
509
+ /**
510
+ * Stores the debounced update function
511
+ */
512
+ allDebouncedUpdateCache = new Map();
513
+ /**
514
+ * Debounce the `modelService.updateNow()` mutation for a short time. If called multiple times with the same
515
+ * modelService and id, it will postpone the subscription to the mutation.
516
+ *
517
+ * All input variables for the same object (same service and ID) will be cumulated over time. So it is possible
518
+ * to update `field1`, then `field2`, and they will be batched into a single XHR including `field1` and `field2`.
519
+ *
520
+ * But it will always keep the same debouncing timeline.
521
+ */
522
+ debounce(modelService, id, object) {
523
+ const debouncedUpdateCache = this.getMap(modelService);
524
+ let debounced = debouncedUpdateCache.get(id);
525
+ if (debounced) {
526
+ debounced.object = {
527
+ ...debounced.object,
528
+ ...object,
529
+ };
530
+ }
531
+ else {
532
+ const debouncer = new ReplaySubject(1);
533
+ let wasCancelled = false;
534
+ const canceller = new Subject();
535
+ canceller.subscribe(() => {
536
+ wasCancelled = true;
537
+ debouncer.complete();
538
+ canceller.complete();
539
+ this.delete(modelService, id);
540
+ });
541
+ const flusher = new Subject();
542
+ debounced = {
543
+ object,
544
+ debouncer,
545
+ canceller,
546
+ flusher,
547
+ modelService: modelService,
548
+ result: debouncer.pipe(debounceTime(2000), // Wait 2 seconds...
549
+ raceWith(flusher), // ...unless flusher is triggered
550
+ take(1), mergeMap(() => {
551
+ this.delete(modelService, id);
552
+ if (wasCancelled || !debounced) {
553
+ return EMPTY;
554
+ }
555
+ return modelService.updateNow(debounced.object);
556
+ }), shareReplay()),
557
+ };
558
+ debouncedUpdateCache.set(id, debounced);
559
+ }
560
+ // Notify our debounced update each time we ask to update
561
+ debounced.debouncer.next();
562
+ // Return and observable that is updated when mutation is done
563
+ return debounced.result;
564
+ }
565
+ cancelOne(modelService, id) {
566
+ const debounced = this.allDebouncedUpdateCache.get(modelService)?.get(id);
567
+ debounced?.canceller.next();
568
+ }
569
+ /**
570
+ * Immediately execute the pending update, if any.
571
+ *
572
+ * It should typically be called before resolving the object, to mutate it before re-fetching it from server.
573
+ *
574
+ * The returned observable will complete when the update completes, even if it errors.
575
+ */
576
+ flushOne(modelService, id) {
577
+ const debounced = this.allDebouncedUpdateCache.get(modelService)?.get(id);
578
+ return this.internalFlush(debounced ? [debounced] : []);
579
+ }
580
+ /**
581
+ * Immediately execute all pending updates.
582
+ *
583
+ * It should typically be called before login out.
584
+ *
585
+ * The returned observable will complete when all updates complete, even if some of them error.
586
+ */
587
+ flush() {
588
+ const all = [];
589
+ this.allDebouncedUpdateCache.forEach(map => map.forEach(debounced => all.push(debounced)));
590
+ return this.internalFlush(all);
591
+ }
592
+ internalFlush(debounceds) {
593
+ const all = [];
594
+ const allFlusher = [];
595
+ debounceds.forEach(debounced => {
596
+ all.push(debounced.result.pipe(catchError(() => of(undefined))));
597
+ allFlusher.push(debounced.flusher);
598
+ });
599
+ if (!all.length) {
600
+ all.push(of(undefined));
601
+ }
602
+ return new Observable(subscriber => {
603
+ const subscription = forkJoin(all)
604
+ .pipe(map(() => undefined))
605
+ .subscribe(subscriber);
606
+ // Flush only after subscription process is finished
607
+ allFlusher.forEach(flusher => flusher.next());
608
+ return subscription;
609
+ });
610
+ }
611
+ /**
612
+ * Count of pending updates
613
+ */
614
+ get count() {
615
+ let count = 0;
616
+ this.allDebouncedUpdateCache.forEach(map => (count += map.size));
617
+ return count;
618
+ }
619
+ getMap(modelService) {
620
+ let debouncedUpdateCache = this.allDebouncedUpdateCache.get(modelService);
621
+ if (!debouncedUpdateCache) {
622
+ debouncedUpdateCache = new Map();
623
+ this.allDebouncedUpdateCache.set(modelService, debouncedUpdateCache);
624
+ }
625
+ return debouncedUpdateCache;
626
+ }
627
+ delete(modelService, id) {
628
+ const map = this.allDebouncedUpdateCache.get(modelService);
629
+ if (!map) {
630
+ return;
631
+ }
632
+ map.delete(id);
633
+ if (!map.size) {
634
+ this.allDebouncedUpdateCache.delete(modelService);
635
+ }
636
+ }
637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: NaturalDebounceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
638
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: NaturalDebounceService, providedIn: 'root' });
639
+ }
640
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: NaturalDebounceService, decorators: [{
641
+ type: Injectable,
642
+ args: [{
643
+ providedIn: 'root',
644
+ }]
645
+ }] });
646
+
647
+ /**
648
+ * Lookup a facet by its `name` and then by its `field`, or return null if not found
649
+ */
650
+ function getFacetFromSelection(facets, selection) {
651
+ if (!facets) {
652
+ return null;
653
+ }
654
+ return (facets.find(facet => facet.name != null && facet.name === selection.name) ||
655
+ facets.find(facet => facet.field === selection.field) ||
656
+ null);
657
+ }
658
+ /**
659
+ * Deep clone a literal via JSON serializing/unserializing
660
+ *
661
+ * It will **not** work with:
662
+ *
663
+ * - functions (will be removed)
664
+ * - `undefined` (will be removed)
665
+ * - cyclic references (will crash)
666
+ * - objects (will be converted to `{}`)
667
+ */
668
+ function deepClone(obj) {
669
+ return JSON.parse(JSON.stringify(obj));
670
+ }
671
+
672
+ class NaturalAbstractModelService {
673
+ name;
674
+ oneQuery;
675
+ allQuery;
676
+ createMutation;
677
+ updateMutation;
678
+ deleteMutation;
679
+ createName;
680
+ updateName;
681
+ deleteName;
682
+ /**
683
+ * Store the creation mutations that are pending
684
+ */
685
+ creatingCache = new Map();
686
+ apollo = inject(Apollo);
687
+ naturalDebounceService = inject(NaturalDebounceService);
688
+ plural;
689
+ /**
690
+ *
691
+ * @param name service and single object query name (eg. userForFront or user).
692
+ * @param oneQuery GraphQL query to fetch a single object from ID (eg. userForCrudQuery).
693
+ * @param allQuery GraphQL query to fetch a filtered list of objects (eg. usersForCrudQuery).
694
+ * @param createMutation GraphQL mutation to create an object.
695
+ * @param updateMutation GraphQL mutation to update an object.
696
+ * @param deleteMutation GraphQL mutation to delete a list of objects.
697
+ * @param plural list query name (eg. usersForFront or users).
698
+ * @param createName create object mutation name (eg. createUser).
699
+ * @param updateName update object mutation name (eg. updateUser).
700
+ * @param deleteName delete object mutation name (eg. deleteUsers).
701
+ */
702
+ constructor(name, oneQuery, allQuery, createMutation, updateMutation, deleteMutation, plural = null, createName = null, updateName = null, deleteName = null) {
703
+ this.name = name;
704
+ this.oneQuery = oneQuery;
705
+ this.allQuery = allQuery;
706
+ this.createMutation = createMutation;
707
+ this.updateMutation = updateMutation;
708
+ this.deleteMutation = deleteMutation;
709
+ this.createName = createName;
710
+ this.updateName = updateName;
711
+ this.deleteName = deleteName;
712
+ this.plural = plural ?? makePlural(this.name);
713
+ }
714
+ /**
715
+ * List of individual fields validators
716
+ */
717
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
718
+ getFormValidators(model) {
719
+ return {};
720
+ }
721
+ /**
722
+ * List of individual async fields validators
723
+ */
724
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
725
+ getFormAsyncValidators(model) {
726
+ return {};
727
+ }
728
+ /**
729
+ * List of grouped fields validators (like password + confirm password)
730
+ */
731
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
732
+ getFormGroupValidators(model) {
733
+ return [];
734
+ }
735
+ /**
736
+ * List of async group fields validators (like unique constraint on multiple columns)
737
+ */
738
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
739
+ getFormGroupAsyncValidators(model) {
740
+ return [];
741
+ }
742
+ getFormConfig(model) {
743
+ const values = { ...this.getDefaultForServer(), ...this.getFormExtraFieldDefaultValues() };
744
+ const validators = this.getFormValidators(model);
745
+ const asyncValidators = this.getFormAsyncValidators(model);
746
+ const controls = {};
747
+ const disabled = model.permissions ? !model.permissions.update : false;
748
+ if (model.id) {
749
+ controls.id = new UntypedFormControl({ value: model.id, disabled: true });
750
+ }
751
+ // Configure form for each field of model
752
+ for (const key of Object.keys(values)) {
753
+ const value = model[key] !== undefined ? model[key] : values[key];
754
+ const formState = {
755
+ value: value,
756
+ disabled: disabled,
757
+ };
758
+ const validator = typeof validators[key] !== 'undefined' ? validators[key] : null;
759
+ const asyncValidator = typeof asyncValidators[key] !== 'undefined' ? asyncValidators[key] : null;
760
+ controls[key] = new UntypedFormControl(formState, validator, asyncValidator);
761
+ }
762
+ // Configure form for extra validators that are not on a specific field
763
+ for (const key of Object.keys(validators)) {
764
+ if (!controls[key]) {
765
+ const formState = {
766
+ value: model[key] ? model[key] : null,
767
+ disabled: disabled,
768
+ };
769
+ controls[key] = new UntypedFormControl(formState, validators[key]);
770
+ }
771
+ }
772
+ for (const key of Object.keys(asyncValidators)) {
773
+ if (controls[key] && asyncValidators[key]) {
774
+ controls[key].setAsyncValidators(asyncValidators[key]);
775
+ }
776
+ else {
777
+ const formState = {
778
+ value: model[key] ? model[key] : null,
779
+ disabled: disabled,
780
+ };
781
+ controls[key] = new UntypedFormControl(formState, null, asyncValidators[key]);
782
+ }
783
+ }
784
+ return controls;
785
+ }
786
+ /**
787
+ * Create the final FormGroup for the object, including all validators
788
+ *
789
+ * This method should **not** be overridden, but instead `getFormConfig`,
790
+ * `getFormGroupValidators`, `getFormGroupAsyncValidators` might be.
791
+ */
792
+ getFormGroup(model) {
793
+ const formConfig = this.getFormConfig(deepClone(model));
794
+ return new UntypedFormGroup(formConfig, {
795
+ validators: this.getFormGroupValidators(model),
796
+ asyncValidators: this.getFormGroupAsyncValidators(model),
797
+ });
798
+ }
799
+ /**
800
+ * Get a single object
801
+ *
802
+ * If available it will emit object from cache immediately, then it
803
+ * will **always** fetch from network and then the observable will be completed.
804
+ *
805
+ * You must subscribe to start getting results (and fetch from network).
806
+ */
807
+ getOne(id) {
808
+ return this.prepareOneQuery(id, 'cache-and-network').pipe(takeWhile(result => result.networkStatus !== NetworkStatus.ready, true), map$1(result => result.data[this.name]));
809
+ }
810
+ /**
811
+ * Watch a single object
812
+ *
813
+ * If available it will emit object from cache immediately, then it
814
+ * will **always** fetch from network, and then keep watching the cache forever.
815
+ *
816
+ * You must subscribe to start getting results (and fetch from network).
817
+ *
818
+ * You **MUST** unsubscribe.
819
+ */
820
+ watchOne(id, fetchPolicy = 'cache-and-network') {
821
+ return this.prepareOneQuery(id, fetchPolicy).pipe(map$1(result => result.data[this.name]));
822
+ }
823
+ prepareOneQuery(id, fetchPolicy) {
824
+ this.throwIfObservable(id);
825
+ this.throwIfNotQuery(this.oneQuery);
826
+ return this.getVariablesForOne(id).pipe(switchMap$1(variables => {
827
+ this.throwIfNotQuery(this.oneQuery);
828
+ return this.apollo.watchQuery({
829
+ query: this.oneQuery,
830
+ variables: variables,
831
+ fetchPolicy: fetchPolicy,
832
+ nextFetchPolicy: 'cache-only',
833
+ }).valueChanges;
834
+ }), filter(result => !!result.data));
835
+ }
836
+ /**
837
+ * Get a collection of objects
838
+ *
839
+ * It will **always** fetch from network and then the observable will be completed.
840
+ * No cache is ever used, so it's slow but correct.
841
+ */
842
+ getAll(queryVariablesManager) {
843
+ this.throwIfNotQuery(this.allQuery);
844
+ return this.getPartialVariablesForAll().pipe(first(), switchMap$1(partialVariables => {
845
+ this.throwIfNotQuery(this.allQuery);
846
+ // Copy manager to prevent to apply internal variables to external QueryVariablesManager
847
+ const manager = new NaturalQueryVariablesManager(queryVariablesManager);
848
+ manager.merge('partial-variables', partialVariables);
849
+ return this.apollo.query({
850
+ query: this.allQuery,
851
+ variables: manager.variables.value,
852
+ fetchPolicy: 'network-only',
853
+ });
854
+ }), this.mapAll());
855
+ }
856
+ /**
857
+ * Get a collection of objects
858
+ *
859
+ * Every time the observable variables change, and they are not undefined,
860
+ * it will return result from cache, then it will **always** fetch from network,
861
+ * and then keep watching the cache forever.
862
+ *
863
+ * You must subscribe to start getting results (and fetch from network).
864
+ *
865
+ * You **MUST** unsubscribe.
866
+ */
867
+ watchAll(queryVariablesManager, fetchPolicy = 'cache-and-network') {
868
+ this.throwIfNotQuery(this.allQuery);
869
+ return combineLatest({
870
+ variables: queryVariablesManager.variables.pipe(
871
+ // Ignore very fast variable changes
872
+ debounceTime$1(20),
873
+ // Wait for variables to be defined to prevent duplicate query: with and without variables
874
+ // Null is accepted value for "no variables"
875
+ filter(variables => typeof variables !== 'undefined')),
876
+ partialVariables: this.getPartialVariablesForAll(),
877
+ }).pipe(switchMap$1(result => {
878
+ // Apply partial variables from service
879
+ // Copy manager to prevent to apply internal variables to external QueryVariablesManager
880
+ const manager = new NaturalQueryVariablesManager(queryVariablesManager);
881
+ manager.merge('partial-variables', result.partialVariables);
882
+ this.throwIfNotQuery(this.allQuery);
883
+ return this.apollo
884
+ .watchQuery({
885
+ query: this.allQuery,
886
+ variables: manager.variables.value,
887
+ fetchPolicy: fetchPolicy,
888
+ })
889
+ .valueChanges.pipe(catchError(() => EMPTY), filter(r => !!r.data), this.mapAll());
890
+ }));
891
+ }
892
+ /**
893
+ * This functions allow to quickly create or update objects.
894
+ *
895
+ * Manages a "creation is pending" status, and update when creation is ready.
896
+ * Uses regular update/updateNow and create methods.
897
+ * Used mainly when editing multiple objects in same controller (like in editable arrays)
898
+ */
899
+ createOrUpdate(object, now = false) {
900
+ this.throwIfObservable(object);
901
+ this.throwIfNotQuery(this.createMutation);
902
+ this.throwIfNotQuery(this.updateMutation);
903
+ // If creation is pending, listen to creation observable and when ready, fire update
904
+ const pendingCreation = this.creatingCache.get(object);
905
+ if (pendingCreation) {
906
+ return pendingCreation.pipe(switchMap$1(created => {
907
+ return this.update({
908
+ id: created.id,
909
+ ...object,
910
+ });
911
+ }));
912
+ }
913
+ // If object has Id, just save it
914
+ if ('id' in object && object.id) {
915
+ if (now) {
916
+ // used mainly for tests, because lodash debounced used in update() does not work fine with fakeAsync and tick()
917
+ return this.updateNow(object);
918
+ }
919
+ else {
920
+ return this.update(object);
921
+ }
922
+ }
923
+ // If object was not saving, and has no ID, create it
924
+ const creation = this.create(object).pipe(tap(() => {
925
+ this.creatingCache.delete(object); // remove from cache
926
+ }));
927
+ // stores creating observable in a cache replayable version of the observable,
928
+ // so several update() can subscribe to the same creation
929
+ this.creatingCache.set(object, creation.pipe(shareReplay$1()));
930
+ return creation;
931
+ }
932
+ /**
933
+ * Create an object in DB and then refetch the list of objects
934
+ */
935
+ create(object) {
936
+ this.throwIfObservable(object);
937
+ this.throwIfNotQuery(this.createMutation);
938
+ const variables = merge({}, { input: this.getInput(object, true) }, this.getPartialVariablesForCreation(object));
939
+ return this.apollo
940
+ .mutate({
941
+ mutation: this.createMutation,
942
+ variables: variables,
943
+ })
944
+ .pipe(map$1(result => {
945
+ this.apollo.client.reFetchObservableQueries();
946
+ return this.mapCreation(result);
947
+ }));
948
+ }
949
+ /**
950
+ * Update an object, after a short debounce
951
+ */
952
+ update(object) {
953
+ this.throwIfObservable(object);
954
+ this.throwIfNotQuery(this.updateMutation);
955
+ // Keep a single instance of the debounced update function
956
+ const id = object.id;
957
+ return this.naturalDebounceService.debounce(this, id, object);
958
+ }
959
+ /**
960
+ * Update an object immediately when subscribing
961
+ */
962
+ updateNow(object) {
963
+ this.throwIfObservable(object);
964
+ this.throwIfNotQuery(this.updateMutation);
965
+ const variables = merge({
966
+ id: object.id,
967
+ input: this.getInput(object, false),
968
+ }, this.getPartialVariablesForUpdate(object));
969
+ return this.apollo
970
+ .mutate({
971
+ mutation: this.updateMutation,
972
+ variables: variables,
973
+ })
974
+ .pipe(map$1(result => {
975
+ this.apollo.client.reFetchObservableQueries();
976
+ return this.mapUpdate(result);
977
+ }));
978
+ }
979
+ /**
980
+ * Delete objects and then refetch the list of objects
981
+ */
982
+ delete(objects) {
983
+ this.throwIfObservable(objects);
984
+ this.throwIfNotQuery(this.deleteMutation);
985
+ const ids = objects.map(o => {
986
+ // Cancel pending update
987
+ this.naturalDebounceService.cancelOne(this, o.id);
988
+ return o.id;
989
+ });
990
+ const variables = merge({
991
+ ids: ids,
992
+ }, this.getPartialVariablesForDelete(objects));
993
+ return this.apollo
994
+ .mutate({
995
+ mutation: this.deleteMutation,
996
+ variables: variables,
997
+ })
998
+ .pipe(
999
+ // Delay the observable until Apollo refetch is completed
1000
+ switchMap$1(result => {
1001
+ const mappedResult = this.mapDelete(result);
1002
+ return from(this.apollo.client.reFetchObservableQueries()).pipe(map$1(() => mappedResult));
1003
+ }));
1004
+ }
1005
+ /**
1006
+ * If the id is provided, resolves an observable model. The observable model will only be emitted after we are sure
1007
+ * that Apollo cache is fresh and warm. Then the component can subscribe to the observable model to get the model
1008
+ * immediately from Apollo cache and any subsequents future mutations that may happen to Apollo cache.
1009
+ *
1010
+ * Without id, returns default values, in order to show a creation form.
1011
+ */
1012
+ resolve(id) {
1013
+ if (id) {
1014
+ const onlyNetwork = this.watchOne(id, 'network-only').pipe(first());
1015
+ const onlyCache = this.watchOne(id, 'cache-first');
1016
+ // In theory, we can rely on Apollo Cache to return a result instantly. It is very fast indeed,
1017
+ // but it is still asynchronous, so there may be a very short time when we don't have the model
1018
+ // available. To fix that, we can rely on RxJS, which is able to emit synchronously the value we just
1019
+ // got from server. Once Apollo Client moves to RxJS (https://github.com/apollographql/apollo-feature-requests/issues/375),
1020
+ // we could try to remove `startWith()`.
1021
+ return onlyNetwork.pipe(map$1(firstValue => onlyCache.pipe(startWith(firstValue))));
1022
+ }
1023
+ else {
1024
+ return of(of(this.getDefaultForServer()));
1025
+ }
1026
+ }
1027
+ /**
1028
+ * Return an object that match the GraphQL input type.
1029
+ * It creates an object with manually filled data and add uncompleted data (like required attributes that can be empty strings)
1030
+ */
1031
+ getInput(object, forCreation) {
1032
+ // Convert relations to their IDs for mutation
1033
+ object = relationsToIds(object);
1034
+ // Pick only attributes that we can find in the empty object
1035
+ // In other words, prevent to select data that has unwanted attributes
1036
+ const emptyObject = this.getDefaultForServer();
1037
+ let input = pick(object, Object.keys(emptyObject));
1038
+ // Complete a potentially uncompleted object with default values
1039
+ if (forCreation) {
1040
+ input = defaults(input, emptyObject);
1041
+ }
1042
+ return input;
1043
+ }
1044
+ /**
1045
+ * Return the number of objects matching the query. It may never complete.
1046
+ *
1047
+ * This is used for the unique validator
1048
+ */
1049
+ count(queryVariablesManager) {
1050
+ const queryName = 'Count' + upperCaseFirstLetter(this.plural);
1051
+ const filterType = upperCaseFirstLetter(this.name) + 'Filter';
1052
+ const query = gql `
1053
+ query ${queryName} ($filter: ${filterType}) {
1054
+ count: ${this.plural} (filter: $filter, pagination: {pageSize: 0, pageIndex: 0}) {
1055
+ length
1056
+ }
1057
+ }`;
1058
+ return this.getPartialVariablesForAll().pipe(switchMap$1(partialVariables => {
1059
+ // Copy manager to prevent to apply internal variables to external QueryVariablesManager
1060
+ const manager = new NaturalQueryVariablesManager(queryVariablesManager);
1061
+ manager.merge('partial-variables', partialVariables);
1062
+ return this.apollo.query({
1063
+ query: query,
1064
+ variables: manager.variables.value,
1065
+ fetchPolicy: 'network-only',
1066
+ });
1067
+ }), map$1(result => result.data.count.length));
1068
+ }
1069
+ /**
1070
+ * Return empty object with some default values from server perspective
1071
+ *
1072
+ * This is typically useful when showing a form for creation
1073
+ */
1074
+ getDefaultForServer() {
1075
+ return {};
1076
+ }
1077
+ /**
1078
+ * You probably **should not** use this.
1079
+ *
1080
+ * If you are trying to *call* this method, instead you probably want to call `getDefaultForServer()` to get default
1081
+ * values for a model, or `getFormConfig()` to get a configured form that includes extra form fields.
1082
+ *
1083
+ * If you are trying to *override* this method, instead you probably want to override `getDefaultForServer()`.
1084
+ *
1085
+ * The only and **very rare** reason to override this method is if the client needs extra form fields that cannot be
1086
+ * accepted by the server (not part of `XXXInput` type) and that are strictly for the client form needs. In that case,
1087
+ * then you can return default values for those extra form fields, and the form returned by `getFormConfig()` will
1088
+ * include those extra fields.
1089
+ */
1090
+ getFormExtraFieldDefaultValues() {
1091
+ return {};
1092
+ }
1093
+ /**
1094
+ * This is used to extract only the array of fetched objects out of the entire fetched data
1095
+ */
1096
+ mapAll() {
1097
+ return map$1(result => result.data[this.plural]); // See https://github.com/apollographql/apollo-client/issues/5662
1098
+ }
1099
+ /**
1100
+ * This is used to extract only the created object out of the entire fetched data
1101
+ */
1102
+ mapCreation(result) {
1103
+ const name = this.createName ?? 'create' + upperCaseFirstLetter(this.name);
1104
+ return result.data[name]; // See https://github.com/apollographql/apollo-client/issues/5662
1105
+ }
1106
+ /**
1107
+ * This is used to extract only the updated object out of the entire fetched data
1108
+ */
1109
+ mapUpdate(result) {
1110
+ const name = this.updateName ?? 'update' + upperCaseFirstLetter(this.name);
1111
+ return result.data[name]; // See https://github.com/apollographql/apollo-client/issues/5662
1112
+ }
1113
+ /**
1114
+ * This is used to extract only flag when deleting an object
1115
+ */
1116
+ mapDelete(result) {
1117
+ const name = this.deleteName ?? 'delete' + upperCaseFirstLetter(this.plural);
1118
+ return result.data[name]; // See https://github.com/apollographql/apollo-client/issues/5662
1119
+ }
1120
+ /**
1121
+ * Returns additional variables to be used when getting a single object
1122
+ *
1123
+ * This is typically a site or state ID, and is needed to get appropriate access rights
1124
+ */
1125
+ getPartialVariablesForOne() {
1126
+ return of({});
1127
+ }
1128
+ /**
1129
+ * Returns additional variables to be used when getting multiple objects
1130
+ *
1131
+ * This is typically a site or state ID, but it could be something else to further filter the query
1132
+ */
1133
+ getPartialVariablesForAll() {
1134
+ return of({});
1135
+ }
1136
+ /**
1137
+ * Returns additional variables to be used when creating an object
1138
+ *
1139
+ * This is typically a site or state ID
1140
+ */
1141
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1142
+ getPartialVariablesForCreation(object) {
1143
+ return {};
1144
+ }
1145
+ /**
1146
+ * Returns additional variables to be used when updating an object
1147
+ *
1148
+ * This is typically a site or state ID
1149
+ */
1150
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1151
+ getPartialVariablesForUpdate(object) {
1152
+ return {};
1153
+ }
1154
+ /**
1155
+ * Return additional variables to be used when deleting an object
1156
+ *
1157
+ * This is typically a site or state ID
1158
+ */
1159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1160
+ getPartialVariablesForDelete(objects) {
1161
+ return {};
1162
+ }
1163
+ /**
1164
+ * Throw exception to prevent executing queries with invalid variables
1165
+ */
1166
+ throwIfObservable(value) {
1167
+ if (value instanceof Observable) {
1168
+ throw new Error('Cannot use Observable as variables. Instead you should use .subscribe() to call the method with a real value');
1169
+ }
1170
+ }
1171
+ /**
1172
+ * Merge given ID with additional partial variables if there is any
1173
+ */
1174
+ getVariablesForOne(id) {
1175
+ return this.getPartialVariablesForOne().pipe(map$1(partialVariables => merge({}, { id: id }, partialVariables)));
1176
+ }
1177
+ /**
1178
+ * Throw exception to prevent executing null queries
1179
+ */
1180
+ throwIfNotQuery(query) {
1181
+ if (!query) {
1182
+ throw new Error('GraphQL query for this method was not configured in this service constructor');
1183
+ }
1184
+ }
1185
+ }
1186
+
1187
+ /**
1188
+ * **DO NOT MODIFY UNLESS STRICTLY REQUIRED FOR VANILLA**
1189
+ *
1190
+ * This is a minimal service specialized for Vanilla and any modification,
1191
+ * including adding `import` in this file, might break https://navigations.ichtus.club.
1192
+ */
1193
+
1194
+ /**
1195
+ * Generated bundle index. Do not edit.
1196
+ */
1197
+
1198
+ export { NaturalAbstractModelService, NaturalQueryVariablesManager, formatIsoDateTime, graphqlQuerySigner };
1199
+ //# sourceMappingURL=ecodev-natural-vanilla.mjs.map