@vgip/meta-ui 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.eslintrc.json +57 -0
  2. package/karma.conf.js +35 -0
  3. package/ng-package.json +10 -0
  4. package/package.json +3 -15
  5. package/src/lib/common/fieldNormalizer/boolean.ts +11 -0
  6. package/src/lib/common/fieldNormalizer/datetime.ts +8 -0
  7. package/src/lib/common/fieldNormalizer/index.ts +171 -0
  8. package/src/lib/common/fieldNormalizer/number.ts +13 -0
  9. package/src/lib/common/fieldNormalizer/options.ts +48 -0
  10. package/src/lib/common/fieldNormalizer/radio.ts +29 -0
  11. package/src/lib/common/fieldNormalizer/reference.ts +32 -0
  12. package/src/lib/common/fieldNormalizer/richtext.ts +15 -0
  13. package/src/lib/common/fieldNormalizer/string.ts +23 -0
  14. package/src/lib/common/fieldNormalizer/text.ts +17 -0
  15. package/src/lib/common/fieldNormalizer/uniqueNameFilter.ts +21 -0
  16. package/src/lib/common/metaAutofocus.directive.ts +31 -0
  17. package/src/lib/common/metaContext.resolver.ts +25 -0
  18. package/src/lib/common/metaIcons.pipe.spec.ts +15 -0
  19. package/src/lib/common/metaIcons.pipe.ts +29 -0
  20. package/src/lib/common/metaModel.pipe.ts +19 -0
  21. package/src/lib/common/metaNormalizer.ts +366 -0
  22. package/src/lib/common/metaStripHtml.pipe.ts +18 -0
  23. package/src/lib/common/utils/colorThemes.ts +86 -0
  24. package/src/lib/common/utils/indexedDbStore/index.ts +244 -0
  25. package/src/lib/common/utils/indexedDbStore/indexedDbStore.spec.ts +149 -0
  26. package/src/lib/common/utils/relativeTimeBuilder.ts +49 -0
  27. package/src/lib/common/utils/resourceCardLabel.ts +25 -0
  28. package/src/lib/common/utils/smartProp.spec.ts +24 -0
  29. package/src/lib/common/utils/smartProp.ts +28 -0
  30. package/src/lib/common/utils/templateBuilder.ts +99 -0
  31. package/src/lib/field.scss +207 -0
  32. package/src/lib/fieldAbstract.ts +327 -0
  33. package/src/lib/fieldBoolean/index.ts +55 -0
  34. package/src/lib/fieldBoolean/style.scss +22 -0
  35. package/src/lib/fieldBoolean/test.spec.ts +43 -0
  36. package/src/lib/fieldBoolean/view.html +30 -0
  37. package/src/lib/fieldComposite/index.ts +86 -0
  38. package/src/lib/fieldComposite/style.scss +6 -0
  39. package/src/lib/fieldComposite/test.spec.ts +43 -0
  40. package/src/lib/fieldComposite/view.html +9 -0
  41. package/src/lib/fieldDatetime/index.ts +359 -0
  42. package/src/lib/fieldDatetime/style.scss +81 -0
  43. package/src/lib/fieldDatetime/test.spec.ts +43 -0
  44. package/src/lib/fieldDatetime/view.html +26 -0
  45. package/src/lib/fieldHidden/index.ts +15 -0
  46. package/src/lib/fieldHidden/view.html +0 -0
  47. package/src/lib/fieldInput/index.ts +477 -0
  48. package/src/lib/fieldInput/style.scss +128 -0
  49. package/src/lib/fieldInput/test.spec.ts +43 -0
  50. package/src/lib/fieldInput/view.html +81 -0
  51. package/src/lib/fieldList/index.ts +73 -0
  52. package/src/lib/fieldList/style.scss +26 -0
  53. package/src/lib/fieldList/test.spec.ts +43 -0
  54. package/src/lib/fieldList/view.html +25 -0
  55. package/src/lib/fieldRadio/index.ts +93 -0
  56. package/src/lib/fieldRadio/style.scss +32 -0
  57. package/src/lib/fieldRadio/test.spec.ts +43 -0
  58. package/src/lib/fieldRadio/view.html +24 -0
  59. package/src/lib/fieldReference/index.ts +871 -0
  60. package/src/lib/fieldReference/style.scss +273 -0
  61. package/src/lib/fieldReference/test.spec.ts +44 -0
  62. package/src/lib/fieldReference/view.html +163 -0
  63. package/src/lib/fieldRichtext/index.ts +98 -0
  64. package/src/lib/fieldRichtext/quill.scss +6 -0
  65. package/src/lib/fieldRichtext/style.scss +87 -0
  66. package/src/lib/fieldRichtext/test.spec.ts +43 -0
  67. package/src/lib/fieldRichtext/view.html +17 -0
  68. package/src/lib/fieldSelect/index.ts +597 -0
  69. package/src/lib/fieldSelect/style.scss +165 -0
  70. package/src/lib/fieldSelect/test.spec.ts +44 -0
  71. package/src/lib/fieldSelect/view.html +128 -0
  72. package/src/lib/fieldText/index.ts +86 -0
  73. package/src/lib/fieldText/style.scss +24 -0
  74. package/src/lib/fieldText/test.spec.ts +43 -0
  75. package/src/lib/fieldText/view.html +23 -0
  76. package/src/lib/fieldUnknown/index.ts +15 -0
  77. package/src/lib/fieldUnknown/test.spec.ts +34 -0
  78. package/src/lib/fieldUnknown/view.html +9 -0
  79. package/src/lib/index.ts +127 -0
  80. package/src/lib/layout/index.ts +255 -0
  81. package/src/lib/layout/style.scss +67 -0
  82. package/src/lib/layout/view.html +45 -0
  83. package/src/lib/metaField/index.ts +133 -0
  84. package/src/lib/metaField/test.spec.ts +32 -0
  85. package/src/lib/refDialog/index.ts +157 -0
  86. package/src/lib/refDialog/style.scss +154 -0
  87. package/src/lib/refDialog/view.html +24 -0
  88. package/src/lib/resource/index.ts +559 -0
  89. package/src/lib/resource/style.scss +132 -0
  90. package/src/lib/resource/view.html +70 -0
  91. package/src/lib/resourceCard/index.ts +44 -0
  92. package/src/lib/resourceCard/style.scss +7 -0
  93. package/src/lib/resourceCard/view.html +14 -0
  94. package/src/lib/services/metaContext/index.ts +61 -0
  95. package/src/lib/services/metaMsg/index.ts +84 -0
  96. package/src/lib/services/metaReference/index.ts +98 -0
  97. package/src/lib/services/metaResource/index.ts +163 -0
  98. package/src/lib/services/metaResource/metaHttpClient.ts +76 -0
  99. package/src/lib/services/metaResource/metaResource.spec.ts +24 -0
  100. package/src/lib/services/metaTracker/index.ts +38 -0
  101. package/src/lib/services/resourceDrafts/index.ts +81 -0
  102. package/src/lib/services/resourceDrafts/resourceDrafts.spec.ts +24 -0
  103. package/src/lib/styles.scss +13 -0
  104. package/src/public-api.ts +5 -0
  105. package/src/test.ts +17 -0
  106. package/tsconfig.lib.json +25 -0
  107. package/tsconfig.lib.prod.json +9 -0
  108. package/tsconfig.spec.json +17 -0
  109. package/fesm2022/vgip-meta-ui.mjs +0 -6079
  110. package/fesm2022/vgip-meta-ui.mjs.map +0 -1
  111. package/index.d.ts +0 -709
@@ -0,0 +1,559 @@
1
+ import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core';
2
+ import { NgForm } from '@angular/forms';
3
+ import { MetaResourceService } from '../services/metaResource';
4
+ import { metaDark, metaLight } from '../common/utils/colorThemes';
5
+ import { MetaContextService } from '../services/metaContext';
6
+ import { MetaMsgService } from '../services/metaMsg';
7
+ import { metaNormalizer } from '../common/metaNormalizer';
8
+ import { templateBuilder } from '../common/utils/templateBuilder';
9
+ import { Subject } from 'rxjs';
10
+ import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
11
+
12
+ @Component({
13
+ selector: 'vgip-meta-resource',
14
+ templateUrl: './view.html',
15
+ styleUrls: ['./style.scss'],
16
+ standalone: false
17
+ })
18
+ export class MetaResource implements OnInit, OnChanges {
19
+ @Input() resource: any;
20
+ @Input() model: any;
21
+ @Input() metaResourceServiceDelegated: MetaResourceService; // cary it from the parent to keep the interceptors
22
+ @Input() theme: string;
23
+ @Input() editMode = true;
24
+ @Output() public done = new EventEmitter();
25
+ @Output() public resourceChange = new EventEmitter();
26
+
27
+ @ViewChild('resourceForm') public resourceForm: NgForm;
28
+
29
+ integrationCode: string;
30
+ resourceType: string;
31
+ meta: any;
32
+ service: MetaResourceService;
33
+ currentLayoutId: any;
34
+ formErrors: any;
35
+ fieldsMap: any;
36
+ edit = true;
37
+ busy: boolean;
38
+ metaLoading: boolean;
39
+ generalError: string;
40
+ resourceMenuVisible: boolean;
41
+ contactsSuggestionsDone = false;
42
+ isEditable: boolean;
43
+ isPersistent: boolean;
44
+ lockedLayoutId: string;
45
+ resourceChanged: Subject<string> = new Subject<string>();
46
+ resourceVrn: string;
47
+ skipLayoutMatchCheck: boolean;
48
+
49
+ constructor(
50
+ private ref: ChangeDetectorRef,
51
+ private el: ElementRef,
52
+ private metaResourceService: MetaResourceService,
53
+ private metaContext: MetaContextService,
54
+ private metaMsgService: MetaMsgService
55
+ ) {
56
+
57
+ }
58
+
59
+ get niceFields() {
60
+ if (
61
+ !this.resource.niceFields
62
+ && this.resource.meta
63
+ && this.resource.meta.layout
64
+ && this.resource.meta.layout.sections
65
+ ) {
66
+ const extractSectionFields = (s) => {
67
+ for (const f of s.fields) {
68
+ if (f.name) {
69
+ this.resource.niceFields[f.name] = f;
70
+ }
71
+ if (f.fields && f.fields.length) {
72
+ extractSectionFields(f);
73
+ }
74
+ }
75
+ };
76
+ this.resource.niceFields = {};
77
+ for (const s of this.resource.meta.layout.sections) {
78
+ extractSectionFields(s);
79
+ }
80
+ }
81
+ return this.resource.niceFields;
82
+ }
83
+
84
+ get fieldNames() {
85
+ const names = Object.keys(this.niceFields);
86
+ if (this.resource.meta.layout.children) {
87
+ for (const child of this.resource.meta.layout.children) {
88
+ names.push(child.name);
89
+ }
90
+ }
91
+ return names;
92
+ }
93
+
94
+ ngOnInit() {
95
+ this.integrationCode = (this.resource.integrationCode || '').toUpperCase();
96
+ this.resourceType = this.resource.resourceType;
97
+ this.service = (this.metaResourceServiceDelegated || this.metaResourceService).new(this.resource);
98
+ if (!this.model) {
99
+ this.model = {};
100
+ }
101
+ this.isPersistent = this.resource?.externalId;
102
+ if (this.isPersistent) {
103
+ this.resourceVrn = `vrn:vgis:${this.integrationCode.toLowerCase()}:${this.resource.resourceType}:${this.resource.externalId}`;
104
+ }
105
+ if (this.resource.activity && this.resource.ref.editable === false && !this.resource.externalId) {
106
+ this.resource.externalId = 'MISSING_EXTERNAL_ID';
107
+ }
108
+ if (!this.resource.meta) {
109
+ this.loadMetadata(this.resource.layoutId);
110
+ } else {
111
+ // if (this.isPersistent && (!this.isEditable || this.resources[ref.name].meta.layout.children)) {
112
+ // this.edit = false;
113
+ // }
114
+ this.meta = this.resource.meta;
115
+ this.isEditable = this.meta.layout && this.meta.layout.editable !== false;
116
+ if (this.isPersistent && (!this.isEditable || this.editMode === false || this.meta.layout.children)) {
117
+ this.edit = false;
118
+ }
119
+ this.currentLayoutId = this.meta.layout.id;
120
+ this.getDetails();
121
+ this.getFieldsMap();
122
+ }
123
+ if (this.theme !== 'inherit') {
124
+ this.applyTheme();
125
+ }
126
+ this.resourceChanged.pipe(debounceTime(400), distinctUntilChanged()).subscribe((value: string) => {
127
+ this.resourceChange.emit(value);
128
+ });
129
+ }
130
+
131
+ ngOnChanges(changes: any) {
132
+ if (changes.theme) {
133
+ this.theme = changes.theme.currentValue;
134
+ if (this.theme !== 'inherit') {
135
+ this.applyTheme();
136
+ }
137
+ }
138
+ }
139
+
140
+ setEditMode() {
141
+ this.edit = true;
142
+ }
143
+
144
+ loadMetadata(layoutId?, refresh = false, ev?) {
145
+ const layoutStoreKey = `vgis.layout_${this.metaContext.vgipUserId}_${this.integrationCode}_${this.resource.resourceType}`;
146
+ if (!layoutId) {
147
+ layoutId = localStorage.getItem(layoutStoreKey);
148
+ }
149
+ this.busy = true;
150
+ setTimeout(() => {
151
+ this.metaLoading = true;
152
+ }, 0);
153
+ delete this.resource.niceFields;
154
+ this.service.getMetadata(layoutId, refresh).subscribe({
155
+ next: (meta: any) => {
156
+ this.currentLayoutId = meta.layout.id || '';
157
+ if (this.currentLayoutId.indexOf(this.lockedLayoutId) === -1) {
158
+ delete this.lockedLayoutId;
159
+ }
160
+ if (layoutId !== meta.layout.id && !this.isPersistent) {
161
+ localStorage.removeItem(layoutStoreKey);
162
+ }
163
+ this.meta = metaNormalizer(meta, this.integrationCode, this.resource.resourceType);
164
+ this.isEditable = this.meta.layout && this.meta.layout.editable !== false;
165
+ this.resource.meta = this.meta;
166
+ if (!refresh && this.isPersistent && (!this.isEditable || this.editMode === false || this.meta.layout.children)) {
167
+ this.edit = false;
168
+ }
169
+ delete this.busy;
170
+ this.getDetails();
171
+ this.getFieldsMap();
172
+ },
173
+ error: (metaError: any) => {
174
+ delete this.busy;
175
+ this.generalError = `Error loading resource metadata (${metaError.statusText})`;
176
+ localStorage.removeItem(layoutStoreKey);
177
+ },
178
+ complete: () => {
179
+ delete this.metaLoading;
180
+ this.forceUiUpdate();
181
+ }
182
+ });
183
+ if (ev) {
184
+ ev.preventDefault();
185
+ ev.stopPropagation();
186
+ return false;
187
+ }
188
+ }
189
+
190
+ changeLayout() {
191
+ localStorage.setItem(
192
+ `vgis.layout_${this.metaContext.vgipUserId}_${this.integrationCode}_${this.resource.resourceType}`,
193
+ this.currentLayoutId
194
+ );
195
+ this.loadMetadata(this.currentLayoutId);
196
+ }
197
+
198
+ getDetails() {
199
+ delete this.lockedLayoutId;
200
+ if (this.resource && (this.resource.externalId || this.resource.id)) {
201
+ if (this.resource.parent && this.resource.activity // skip fetch
202
+ && this.meta.layout.editable === false && !this.meta.layout.children
203
+ ) {
204
+ for (const p in this.resource.activity) {
205
+ if (this.resource.activity[p] && this.fieldNames.indexOf(p) !== -1) {
206
+ this.model[p] = this.resource.activity[p];
207
+ }
208
+ }
209
+ } else {
210
+ this.busy = true;
211
+ this.service.getDetails(
212
+ (this.resource.externalId || this.resource.id), {
213
+ eventId: this.resource.eventId !== 'NO_EVENT' ? this.resource.eventId : null, layoutId: this.resource.layoutId
214
+ }
215
+ ).subscribe({
216
+ next: (resource: any) => {
217
+ /* eslint-disable no-underscore-dangle */
218
+ if (resource._vgis) {
219
+ if (resource._vgis.eventId) {
220
+ this.metaContext.vgipEventId = resource._vgis.eventId;
221
+ }
222
+ this.lockedLayoutId = resource._vgis.layoutId || resource._vgis.recordTypeId
223
+ // sample orig layoutId: 012000000000000AAA, custom layoutId: 66ccbb3d1810985aa3a16202:012000000000000AAA
224
+ // so if the current layout is not inherited from the original one, reload the orig meta
225
+ // CCOMNI-155 - allow form layout change (*unless we are certain that the current and locked match)
226
+ if (!this.skipLayoutMatchCheck && this.lockedLayoutId && (this.currentLayoutId || '').indexOf(this.lockedLayoutId) === -1) {
227
+ this.skipLayoutMatchCheck = true; // ensure prevent HPBR-8782 loop condition
228
+ if ((this.meta.availableLayouts || []).find((l: any) => (l.id || '').indexOf(this.lockedLayoutId) !== -1 )) { // if it's not available, don't even try
229
+ this.loadMetadata(this.lockedLayoutId);
230
+ } else {
231
+ console.warn('Unable to match layout Ids', this.lockedLayoutId, this.currentLayoutId)
232
+ }
233
+ }
234
+ }
235
+ /* eslint-enable no-underscore-dangle */
236
+ for (const p in resource) {
237
+ if ((typeof (resource[p]) !== 'undefined') && (this.fieldNames.indexOf(p) !== -1 || p === '_vgis')) {
238
+ this.model[p] = resource[p];
239
+ }
240
+ }
241
+ delete this.busy;
242
+ },
243
+ error: (resourceError) => {
244
+ this.generalError = resourceError.error ? resourceError.error.message || resourceError.message : resourceError.message;
245
+ delete this.busy;
246
+ },
247
+ complete: () => {
248
+ this.forceUiUpdate();
249
+ }
250
+ });
251
+ }
252
+ } else {
253
+ if (this.resource.ref && this.resource.ref.activity) { // reuse failed autoactivities data
254
+ for (const p in this.resource.ref.activity) {
255
+ if (this.resource.ref.activity && this.fieldNames.indexOf(p) !== -1) {
256
+ this.model[p] = this.resource.ref.activity[p]; // VGIS-7215 (copy only valid fields)
257
+ }
258
+ }
259
+ }
260
+ this.performAutoFill();
261
+ }
262
+ }
263
+
264
+ performAutoFill() {
265
+ this.metaMsgService.sendMessage(
266
+ this.integrationCode,
267
+ {
268
+ action: 'autoFill',
269
+ integration: this.integrationCode,
270
+ resourceType: this.resourceType,
271
+ resource: JSON.parse(JSON.stringify(this.model)),
272
+ fields: this.niceFields,
273
+ event: this.metaContext.context,
274
+ profile: this.metaContext.profiles[this.integrationCode]
275
+ },
276
+ (response) => {
277
+ if (!response.error) {
278
+ for (const afp in response.autoFill) {
279
+ if (!this.model[afp]) {
280
+ this.model[afp] = response.autoFill[afp];
281
+ }
282
+ }
283
+ }
284
+ }
285
+ );
286
+ }
287
+
288
+ getFieldsMap() {
289
+ if (this.meta.fieldsMap) {
290
+ this.fieldsMap = this.meta.fieldsMap;
291
+ this.markContactables();
292
+ this.buildContactsSuggestions();
293
+ if (!this.resource.externalId) {
294
+ this.buildSubjectTemplate();
295
+ this.execTransformer();
296
+ }
297
+ } else {
298
+ this.metaMsgService.sendMessage(
299
+ this.integrationCode,
300
+ { action: 'fieldsMap', integration: this.integrationCode, resourceType: this.resourceType },
301
+ (response) => {
302
+ if (!response.error) {
303
+ this.fieldsMap = response.fieldsMap;
304
+ this.markContactables();
305
+ this.buildContactsSuggestions();
306
+ if (!this.resource.externalId) {
307
+ this.buildSubjectTemplate();
308
+ this.execTransformer();
309
+ }
310
+ } else {
311
+ console.log('No fields map', this.integrationCode, this.resourceType, response.error);
312
+ }
313
+ }
314
+ );
315
+ }
316
+ }
317
+
318
+ openResourceMenu() {
319
+ this.resourceMenuVisible = true;
320
+ setTimeout(() => {
321
+ document.addEventListener('click', this.onMenuClickout);
322
+ }, 10);
323
+ }
324
+
325
+ close(result?) {
326
+ this.done.emit(result);
327
+ }
328
+
329
+ showParsedError(resourceForm, parsedError) {
330
+ this.formErrors = parsedError.message || 'There is a problem with one or more fields';
331
+ if (parsedError.errors && parsedError.errors.length) {
332
+ this.formErrors += ` (${parsedError.errors.map(e => e.message).join(', ')})`;
333
+ for (const pe of parsedError.errors) {
334
+ if (resourceForm.controls[pe.field]) {
335
+ resourceForm.controls[pe.field].setErrors({ invalid: true, custom: pe.message });
336
+ }
337
+ }
338
+ setTimeout(() => {
339
+ const firstInvalid: HTMLInputElement = this.el.nativeElement.querySelector('.Vlt-form__element--error .main:not(.standalone)');
340
+ if (firstInvalid) {
341
+ firstInvalid.focus();
342
+ }
343
+ });
344
+ }
345
+ }
346
+
347
+ submit(resourceForm) {
348
+ delete this.formErrors;
349
+ resourceForm.form.submitted = true;
350
+ if (resourceForm.valid) {
351
+ this.busy = true;
352
+ /* eslint-disable no-underscore-dangle */
353
+ if (!this.model._vgis) {
354
+ this.model._vgis = {
355
+ connector: this.integrationCode,
356
+ resourceType: this.resourceType
357
+ };
358
+ }
359
+ this.model._vgis.layoutId = this.currentLayoutId;
360
+ if (this.metaContext.vgipEventId) {
361
+ this.model._vgis.eventId = this.metaContext.vgipEventId;
362
+ }
363
+ /* eslint-enable no-underscore-dangle */
364
+ this.service.save(this.model).subscribe((result: any) => {
365
+ this.model = result;
366
+ this.resource.externalId = result._vgis.externalId; // eslint-disable-line no-underscore-dangle
367
+ this.resourceVrn = `vrn:vgis:${this.integrationCode.toLowerCase()}:${this.resource.resourceType}:${this.resource.externalId}`;
368
+ this.isPersistent = true;
369
+ this.close(result);
370
+ this.edit = false;
371
+ delete this.busy;
372
+ }, (errorResult) => {
373
+ if ((errorResult.status === 422 && errorResult.error && !errorResult.error.errors && errorResult.error.integrationError)
374
+ || (errorResult.error && errorResult.error.errors && errorResult.error.errors.length && !errorResult.error.errors[0].field)
375
+ // backend implemented error parsing but broken VGIS-7366
376
+ ) {
377
+ this.formErrors = 'There is a problem with one or more fields';
378
+ try {
379
+ this.metaMsgService.sendMessage(
380
+ this.integrationCode,
381
+ {
382
+ action: 'parseErrorMessage',
383
+ integrationCode: this.integrationCode,
384
+ resourceType: errorResult.error.resourceType,
385
+ rawError: errorResult.error.integrationError,
386
+ profile: this.metaContext.profiles[this.integrationCode.toUpperCase()]
387
+ },
388
+ (response) => {
389
+ if (response.parsedError) {
390
+ this.showParsedError(resourceForm, response.parsedError);
391
+ } else {
392
+ // eslint-disable-next-line no-underscore-dangle
393
+ console.error('No error parser for', this.integrationCode, errorResult.error.integrationError);
394
+ this.formErrors = errorResult.error;
395
+ }
396
+ }
397
+ );
398
+ } catch (err) {
399
+ console.log('....', err);
400
+ }
401
+ } else if (errorResult.status === 422) {
402
+ this.showParsedError(resourceForm, errorResult.error);
403
+ } else {
404
+ this.formErrors = errorResult.error;
405
+ }
406
+ delete this.busy;
407
+ });
408
+ } else {
409
+ this.formErrors = Object.keys(resourceForm.controls)
410
+ .filter((c) => resourceForm.controls[c].invalid)
411
+ .map((f) => (this.niceFields[f] ? (this.niceFields[f].label || f) : f)).join(', ');
412
+ setTimeout(() => {
413
+ const selector = `form[name="${this.resourceType}"] .Vlt-form__element--error .main:not(.standalone)`;
414
+ const firstInvalid: HTMLInputElement = this.el.nativeElement.querySelector(selector);
415
+ if (firstInvalid) {
416
+ firstInvalid.focus();
417
+ }
418
+ });
419
+ }
420
+ }
421
+
422
+ clearFormErrors() {
423
+ delete this.formErrors;
424
+ }
425
+
426
+ onFormChange() {
427
+ this.resourceChanged.next(JSON.stringify(this.model));
428
+ }
429
+
430
+ private onMenuClickout = () => {
431
+ delete this.resourceMenuVisible;
432
+ document.removeEventListener('click', this.onMenuClickout);
433
+ };
434
+
435
+ private suggestFieldProperty(key, value) {
436
+ if (key && value) {
437
+ const fieldNames = this.fieldNames;
438
+ if (this.model) {
439
+ const nestMatch = key.match(/(\S+)\[\d\]\.(\S+)/);
440
+ if (nestMatch && fieldNames.indexOf(nestMatch[1]) !== -1) {
441
+ const nestKeys: any = {};
442
+ nestKeys[nestMatch[2]] = value;
443
+ this.model[nestMatch[1]] = [nestKeys];
444
+ } else if (fieldNames.indexOf(key) !== -1) {
445
+ this.model[key] = value;
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ private execTransformer() {
452
+ if (this.metaContext && this.fieldsMap) {
453
+ setTimeout(() => {
454
+ this.suggestFieldProperty(this.fieldsMap.phoneNumber, this.metaContext.phoneNumber);
455
+ if (this.fieldsMap.firstName || this.fieldsMap.lastName) {
456
+ const names = (this.metaContext.displayName || '').split(' ');
457
+ this.suggestFieldProperty(this.fieldsMap.firstName, names[0]);
458
+ this.suggestFieldProperty(this.fieldsMap.lastName, names[1]);
459
+ } else if (this.fieldsMap.displayName) {
460
+ this.suggestFieldProperty(this.fieldsMap.displayName, this.metaContext.displayName);
461
+ }
462
+ }, 100);
463
+ }
464
+ }
465
+
466
+ private markContactables() {
467
+ if (this.fieldsMap.contactables && this.fieldsMap.contactables.length) {
468
+ for (const c of this.fieldsMap.contactables) {
469
+ const field = this.niceFields[c];
470
+ if (field) {
471
+ Object.defineProperty(field, '$isContactable', {
472
+ value: true,
473
+ configurable: true,
474
+ enumerable: false
475
+ });
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ private buildSubjectTemplate() {
482
+ if (!this.model[this.fieldsMap.subject] && this.fieldsMap.subject && this.niceFields[this.fieldsMap.subject]) {
483
+ let subjectTemplate: string;
484
+ if (this.niceFields[this.fieldsMap.subject].default) {
485
+ subjectTemplate = this.niceFields[this.fieldsMap.subject].default.id || this.niceFields[this.fieldsMap.subject].default;
486
+ } else if (this.metaContext.profiles[this.integrationCode]) {
487
+ const profile = this.metaContext.profiles[this.integrationCode] || {};
488
+ subjectTemplate = profile.defaultSubject ?
489
+ (profile.defaultSubject[(this.metaContext.context?.type || 'CALL').toUpperCase()] || profile.defaultSubject.CALL) :
490
+ profile.activityDefaultSubjectTemplate;
491
+ }
492
+ if (subjectTemplate) {
493
+ this.model[this.fieldsMap.subject] = templateBuilder(this.metaContext, subjectTemplate);
494
+ }
495
+ }
496
+ }
497
+
498
+ private buildContactsSuggestions() {
499
+ // let hasDefaultContact = false;
500
+ if (this.metaContext.contactables[this.integrationCode] && this.meta.layout && this.meta.layout.sections) {
501
+ for (const s of this.meta.layout.sections) {
502
+ for (const f of (s.fields || [])) {
503
+ if (f.type === 'reference') {
504
+ if (!f.suggestions) {
505
+ f.suggestions = [];
506
+ } else {
507
+ f.suggestions = f.suggestions.filter(sg => !sg.auto); // keep meta suggestions if any
508
+ }
509
+ const refTypes = (f.reference.length ? f.reference : [f.reference]).map(r => r.name);
510
+ for (const c of this.metaContext.contactables[this.integrationCode]) {
511
+ if (refTypes.indexOf(c.resourceType || c.type) !== -1) {
512
+ let tLabel = c.displayName || c.name;
513
+ if (c.phoneNumbers) {
514
+ tLabel += (' ✆' + c.phoneNumbers.map(p => p.phoneNumber).join(','));
515
+ }
516
+ f.suggestions.push({
517
+ id: c.externalId || c.identifier || c.id,
518
+ label: tLabel,
519
+ type: c.resourceType || c.type,
520
+ auto: true
521
+ });
522
+ }
523
+ }
524
+ if ((this.fieldsMap.contactables || []).indexOf(f.name) !== -1) { // evaluate usability?
525
+ f.alwaysVisible = true;
526
+ }
527
+ // if (
528
+ // !hasDefaultContact && this.metaContext.defaultContactables[this.integrationCode]
529
+ // && refTypes.indexOf(this.metaContext.defaultContactables[this.integrationCode].type) !== -1
530
+ // ) {
531
+ // this.resource[f.name] = this.metaContext.defaultContactables[this.integrationCode];
532
+ // hasDefaultContact = true;
533
+ // } else if (!hasDefaultContact && this.fieldsMap.contactables && f.suggestions && f.suggestions.length === 1) {
534
+ // if (this.fieldsMap.contactables.indexOf(f.name) !== -1 && !f.default) {
535
+ // f.default = f.suggestions[0];
536
+ // hasDefaultContact = true;
537
+ // }
538
+ // }
539
+ }
540
+ }
541
+ }
542
+ this.contactsSuggestionsDone = true;
543
+ }
544
+ }
545
+
546
+ private applyTheme() {
547
+ const metaTheme = this.theme === 'dark' ? metaDark : metaLight;
548
+ for (const key of Object.keys(metaTheme.properties)) {
549
+ this.el.nativeElement.style.setProperty(key, metaTheme.properties[key]);
550
+ }
551
+ }
552
+
553
+ private forceUiUpdate() { // iOS webview does not update the UI after asyc changes. Temp fix :(
554
+ // this.ref.detectChanges();
555
+ const e = document.createEvent('Events');
556
+ e.initEvent('click', true, false);
557
+ this.el.nativeElement.dispatchEvent(e);
558
+ }
559
+ }
@@ -0,0 +1,132 @@
1
+ :host {
2
+ // height: 100%;
3
+ flex: 1;
4
+ display: flex;
5
+ flex-direction: column;
6
+ min-height: 0;
7
+ color: var(--vgip-meta-resource-color);
8
+ }
9
+ .Vlt-callout--banner {
10
+ -webkit-box-pack: initial;
11
+ justify-content: initial;
12
+ text-align: initial;
13
+ overflow-y: auto;
14
+ &.Vlt-callout--dismissed {
15
+ padding: 0;
16
+ }
17
+ &:not(.Vlt-callout--dismissed) {
18
+ min-height: 78px;
19
+ }
20
+ }
21
+ .Vlt-spinner:before, .Vlt-spinner:after {
22
+ border: 6px solid #616266;
23
+ border-color: #616266 transparent transparent;
24
+ }
25
+
26
+ .Vlt-card__content {
27
+ padding: 8px 8px 8px 16px;
28
+ display: flex;
29
+ flex-direction: column;
30
+ min-height: 0;
31
+ flex: 1;
32
+ overflow-y: scroll;
33
+ overflow-x: hidden;
34
+ background-color: var(--vgip-meta-resource-bg-color);
35
+ @media (hover:none) { // fix zooming on iOS
36
+ padding-right: 16px;
37
+ }
38
+ &::-webkit-scrollbar {
39
+ width: 8px;
40
+ }
41
+ &::-webkit-scrollbar-thumb {
42
+ background-color: var(--vgip-meta-scrollbar-color);
43
+ border: 2px solid transparent;
44
+ border-radius: 6px;
45
+ background-clip: content-box;
46
+ }
47
+ }
48
+
49
+ .Vlt-card__footer {
50
+ background: var(--vgip-meta-resource-bar-color);
51
+ border-top: 1px solid var(--vgip-meta-separator-color);
52
+ display: flex;
53
+ flex-direction: row;
54
+ margin: 16px -24px -24px -24px;
55
+ padding: 8px 8px 8px 16px;
56
+ box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
57
+ z-index: 1;
58
+ .Vlt-select select {
59
+ color: var(--vgip-meta-input-color);
60
+ &:disabled {
61
+ background: initial;
62
+ cursor: not-allowed;
63
+ }
64
+ }
65
+ .Vlt-select:after {
66
+ background-image: var(--vgip-meta-dropdown-icon);
67
+ }
68
+ .resource-menu .Vlt-icon:not(:hover) {
69
+ fill: var(--vgip-meta-input-label-color);
70
+ }
71
+ button.Vlt-btn--secondary {
72
+ border: none;
73
+ &:not([type='submit']) {
74
+ color: var(--vgip-meta-input-color);
75
+ }
76
+ box-shadow: inset 0 0 0 1px var(--vgip-meta-button-border-color);
77
+ &:hover {
78
+ &[type='submit'] {
79
+ background-color: var(--vgip-meta-submit-hover-bg-color);
80
+ }
81
+ box-shadow: inset 0 0 0 1px var(--vgip-meta-input-accent-color);
82
+ }
83
+ }
84
+ }
85
+
86
+ .Vlt-progress {
87
+ margin: 0;
88
+ position: sticky;
89
+ .Vlt-progress__bar {
90
+ width: 0;
91
+ transition: width 5s;
92
+ &.loading {
93
+ width: 100%;
94
+ }
95
+ }
96
+ }
97
+
98
+ form {
99
+ border-radius: 6px;
100
+ padding: 24px;
101
+ display: flex;
102
+ flex-direction: column;
103
+ min-height: 0;
104
+ flex: 1;
105
+ .form-content {
106
+ margin: -24px -24px -16px -24px;
107
+ padding-bottom: 0;
108
+ flex: 1;
109
+ display: flex;
110
+ flex-direction: column;
111
+ // min-height: 0;
112
+ }
113
+ }
114
+
115
+ .busy-mask {
116
+ position: absolute;
117
+ top: 0;
118
+ right: 0;
119
+ bottom: 0;
120
+ left: 0;
121
+ background: rgba(0,0,0,.32);
122
+ opacity: 1;
123
+ z-index: 898;
124
+ display: none;
125
+ border-bottom-left-radius: 6px;
126
+ border-bottom-right-radius: 6px;
127
+ margin: 1px;
128
+ cursor: progress;
129
+ &.active {
130
+ display: block;
131
+ }
132
+ }