@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.
- package/.eslintrc.json +57 -0
- package/karma.conf.js +35 -0
- package/ng-package.json +10 -0
- package/package.json +3 -15
- package/src/lib/common/fieldNormalizer/boolean.ts +11 -0
- package/src/lib/common/fieldNormalizer/datetime.ts +8 -0
- package/src/lib/common/fieldNormalizer/index.ts +171 -0
- package/src/lib/common/fieldNormalizer/number.ts +13 -0
- package/src/lib/common/fieldNormalizer/options.ts +48 -0
- package/src/lib/common/fieldNormalizer/radio.ts +29 -0
- package/src/lib/common/fieldNormalizer/reference.ts +32 -0
- package/src/lib/common/fieldNormalizer/richtext.ts +15 -0
- package/src/lib/common/fieldNormalizer/string.ts +23 -0
- package/src/lib/common/fieldNormalizer/text.ts +17 -0
- package/src/lib/common/fieldNormalizer/uniqueNameFilter.ts +21 -0
- package/src/lib/common/metaAutofocus.directive.ts +31 -0
- package/src/lib/common/metaContext.resolver.ts +25 -0
- package/src/lib/common/metaIcons.pipe.spec.ts +15 -0
- package/src/lib/common/metaIcons.pipe.ts +29 -0
- package/src/lib/common/metaModel.pipe.ts +19 -0
- package/src/lib/common/metaNormalizer.ts +366 -0
- package/src/lib/common/metaStripHtml.pipe.ts +18 -0
- package/src/lib/common/utils/colorThemes.ts +86 -0
- package/src/lib/common/utils/indexedDbStore/index.ts +244 -0
- package/src/lib/common/utils/indexedDbStore/indexedDbStore.spec.ts +149 -0
- package/src/lib/common/utils/relativeTimeBuilder.ts +49 -0
- package/src/lib/common/utils/resourceCardLabel.ts +25 -0
- package/src/lib/common/utils/smartProp.spec.ts +24 -0
- package/src/lib/common/utils/smartProp.ts +28 -0
- package/src/lib/common/utils/templateBuilder.ts +99 -0
- package/src/lib/field.scss +207 -0
- package/src/lib/fieldAbstract.ts +327 -0
- package/src/lib/fieldBoolean/index.ts +55 -0
- package/src/lib/fieldBoolean/style.scss +22 -0
- package/src/lib/fieldBoolean/test.spec.ts +43 -0
- package/src/lib/fieldBoolean/view.html +30 -0
- package/src/lib/fieldComposite/index.ts +86 -0
- package/src/lib/fieldComposite/style.scss +6 -0
- package/src/lib/fieldComposite/test.spec.ts +43 -0
- package/src/lib/fieldComposite/view.html +9 -0
- package/src/lib/fieldDatetime/index.ts +359 -0
- package/src/lib/fieldDatetime/style.scss +81 -0
- package/src/lib/fieldDatetime/test.spec.ts +43 -0
- package/src/lib/fieldDatetime/view.html +26 -0
- package/src/lib/fieldHidden/index.ts +15 -0
- package/src/lib/fieldHidden/view.html +0 -0
- package/src/lib/fieldInput/index.ts +477 -0
- package/src/lib/fieldInput/style.scss +128 -0
- package/src/lib/fieldInput/test.spec.ts +43 -0
- package/src/lib/fieldInput/view.html +81 -0
- package/src/lib/fieldList/index.ts +73 -0
- package/src/lib/fieldList/style.scss +26 -0
- package/src/lib/fieldList/test.spec.ts +43 -0
- package/src/lib/fieldList/view.html +25 -0
- package/src/lib/fieldRadio/index.ts +93 -0
- package/src/lib/fieldRadio/style.scss +32 -0
- package/src/lib/fieldRadio/test.spec.ts +43 -0
- package/src/lib/fieldRadio/view.html +24 -0
- package/src/lib/fieldReference/index.ts +871 -0
- package/src/lib/fieldReference/style.scss +273 -0
- package/src/lib/fieldReference/test.spec.ts +44 -0
- package/src/lib/fieldReference/view.html +163 -0
- package/src/lib/fieldRichtext/index.ts +98 -0
- package/src/lib/fieldRichtext/quill.scss +6 -0
- package/src/lib/fieldRichtext/style.scss +87 -0
- package/src/lib/fieldRichtext/test.spec.ts +43 -0
- package/src/lib/fieldRichtext/view.html +17 -0
- package/src/lib/fieldSelect/index.ts +597 -0
- package/src/lib/fieldSelect/style.scss +165 -0
- package/src/lib/fieldSelect/test.spec.ts +44 -0
- package/src/lib/fieldSelect/view.html +128 -0
- package/src/lib/fieldText/index.ts +86 -0
- package/src/lib/fieldText/style.scss +24 -0
- package/src/lib/fieldText/test.spec.ts +43 -0
- package/src/lib/fieldText/view.html +23 -0
- package/src/lib/fieldUnknown/index.ts +15 -0
- package/src/lib/fieldUnknown/test.spec.ts +34 -0
- package/src/lib/fieldUnknown/view.html +9 -0
- package/src/lib/index.ts +127 -0
- package/src/lib/layout/index.ts +255 -0
- package/src/lib/layout/style.scss +67 -0
- package/src/lib/layout/view.html +45 -0
- package/src/lib/metaField/index.ts +133 -0
- package/src/lib/metaField/test.spec.ts +32 -0
- package/src/lib/refDialog/index.ts +157 -0
- package/src/lib/refDialog/style.scss +154 -0
- package/src/lib/refDialog/view.html +24 -0
- package/src/lib/resource/index.ts +559 -0
- package/src/lib/resource/style.scss +132 -0
- package/src/lib/resource/view.html +70 -0
- package/src/lib/resourceCard/index.ts +44 -0
- package/src/lib/resourceCard/style.scss +7 -0
- package/src/lib/resourceCard/view.html +14 -0
- package/src/lib/services/metaContext/index.ts +61 -0
- package/src/lib/services/metaMsg/index.ts +84 -0
- package/src/lib/services/metaReference/index.ts +98 -0
- package/src/lib/services/metaResource/index.ts +163 -0
- package/src/lib/services/metaResource/metaHttpClient.ts +76 -0
- package/src/lib/services/metaResource/metaResource.spec.ts +24 -0
- package/src/lib/services/metaTracker/index.ts +38 -0
- package/src/lib/services/resourceDrafts/index.ts +81 -0
- package/src/lib/services/resourceDrafts/resourceDrafts.spec.ts +24 -0
- package/src/lib/styles.scss +13 -0
- package/src/public-api.ts +5 -0
- package/src/test.ts +17 -0
- package/tsconfig.lib.json +25 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +17 -0
- package/fesm2022/vgip-meta-ui.mjs +0 -6079
- package/fesm2022/vgip-meta-ui.mjs.map +0 -1
- package/index.d.ts +0 -709
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div *ngIf='generalError' class="Vlt-callout Vlt-callout--critical" style='overflow: auto; margin: 20px;'>
|
|
2
|
+
<i></i>
|
|
3
|
+
<div class="Vlt-callout__content">
|
|
4
|
+
<p>{{generalError}}</p>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
<div *ngIf='!meta' class="Vlt-progress">
|
|
8
|
+
<div class="Vlt-progress__bar" [ngClass]="{ loading: metaLoading }" role="progressbar" aria-valuemin="0" aria-valuetext="Loading Metadata" aria-valuemax="100"></div>
|
|
9
|
+
</div>
|
|
10
|
+
<form [vgipMetaFormAutofocus]='!resource || !resource.externalId' *ngIf='meta && !generalError' #resourceForm="ngForm" [name]='resource.resourceType' (ngSubmit)='submit(resourceForm)' (change)='onFormChange()' novalidate>
|
|
11
|
+
<div class='form-content' style='overflow: hidden;'>
|
|
12
|
+
<div [ngClass]="{ 'Vlt-callout--dismissed': !formErrors }" class="Vlt-callout Vlt-callout--banner Vlt-callout--critical">
|
|
13
|
+
<i></i>
|
|
14
|
+
<div class="Vlt-callout__content" style="margin-top: auto;">
|
|
15
|
+
<p>There is a problem with one or more fields</p>
|
|
16
|
+
<small style='font-weight: 600; font-size: 1.0em;' *ngIf='formErrors'>{{ formErrors.message || formErrors }}</small>
|
|
17
|
+
</div>
|
|
18
|
+
<button type='button' class="Vlt-callout__dismiss" (click)='clearFormErrors()' aria-label="Dismiss"></button>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="Vlt-card__content" style='min-height: 40px;'>
|
|
21
|
+
<vgip-meta-layout *ngIf='edit' [meta]='meta.layout' [resource]='model' [preview]='false' [integration]='meta.integration' [type]='resource.resourceType' theme='inherit'></vgip-meta-layout>
|
|
22
|
+
<vgip-meta-layout *ngIf='!edit' [meta]='meta.layout' [resource]='model' [preview]='true' [integration]='meta.integration' [type]='resource.resourceType' theme='inherit' [attr.data-vrn]='resourceVrn'></vgip-meta-layout>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="Vlt-card__footer">
|
|
26
|
+
<button class="Vlt-btn Vlt-btn--secondary Vlt-btn--app Vlt-btn--outline" type='button' aria-label='Cancel' (click)='close()'>
|
|
27
|
+
{{ edit ? 'Cancel' : 'Close' }}
|
|
28
|
+
</button>
|
|
29
|
+
<button *ngIf='edit' class="Vlt-btn Vlt-btn--app" [ngClass]="{ 'Vlt-btn--primary': isPersistent, 'Vlt-btn--secondary': !isPersistent }" type='submit'>
|
|
30
|
+
{{ (resource && resource.externalId) ? 'Update' : 'Create' }}
|
|
31
|
+
</button>
|
|
32
|
+
<button (click)='setEditMode()' *ngIf='!edit && isEditable' [disabled]='!isEditable' class="Vlt-btn Vlt-btn--app Vlt-btn--primary" type='button' aria-label='Edit'>
|
|
33
|
+
Edit
|
|
34
|
+
</button>
|
|
35
|
+
<div style='margin-right: 8px; flex: 1;'>
|
|
36
|
+
<div *ngIf='meta.availableLayouts && meta.availableLayouts.length' class="Vlt-form__element Vlt-form__element--big" style='padding: 0; margin: -8px -18px -8px 0;'>
|
|
37
|
+
<div class="Vlt-select">
|
|
38
|
+
<label style='all: inherit;'>
|
|
39
|
+
<select style='border: 0; margin-top: 3px; background: transparent;' (change)='changeLayout()' [(ngModel)]='currentLayoutId' [ngModelOptions]='{ standalone: true }' [disabled]='lockedLayoutId === currentLayoutId'>
|
|
40
|
+
<option selected disabled>Select layout</option>
|
|
41
|
+
<option *ngFor='let layout of meta.availableLayouts' [ngValue]='layout.id'>{{layout.name}}</option>
|
|
42
|
+
</select>
|
|
43
|
+
<label>Layout</label> <!-- eslint-disable-line @angular-eslint/template/label-has-associated-control -->
|
|
44
|
+
</label>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="Vlt-dropdown resource-menu" [ngClass]="{ 'Vlt-dropdown--expanded': resourceMenuVisible }" style='margin-left: 2px;'>
|
|
49
|
+
<div class="Vlt-dropdown__panel" style='bottom: 40px; right: -4px; top: initial;'>
|
|
50
|
+
<div class="Vlt-dropdown__panel__content">
|
|
51
|
+
<a href='#' class="Vlt-dropdown__link" (click)='loadMetadata(currentLayoutId, true, $event)'>
|
|
52
|
+
<div>
|
|
53
|
+
<svg class='Vlt-icon Vlt-icon--smaller'><use xlink:href="volta/volta-icons.svg#Vlt-icon-refresh"/></svg> Refresh metadata
|
|
54
|
+
</div>
|
|
55
|
+
</a>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="Vlt-dropdown__trigger">
|
|
59
|
+
<button type='button' (click)='openResourceMenu()' class="Vlt-btn Vlt-btn--link" aria-label='Resource menu'>
|
|
60
|
+
<svg class='Vlt-icon Vlt-icon--small' style='margin: 0;'><use xlink:href="volta/volta-icons.svg#Vlt-icon-more-v-negative"/></svg>
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</form>
|
|
66
|
+
<div class='busy-mask' [ngClass]="{ active: busy }">
|
|
67
|
+
<div style='height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;'>
|
|
68
|
+
<div class="Vlt-spinner"></div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Component, Input, OnInit, ViewContainerRef } from '@angular/core';
|
|
2
|
+
import { MetaResourceService } from '../services/metaResource';
|
|
3
|
+
import { MetaReferenceService } from '../services/metaReference';
|
|
4
|
+
import { MetaRefDialog } from '../refDialog';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'vgip-meta-resource-card',
|
|
8
|
+
templateUrl: './view.html',
|
|
9
|
+
styleUrls: ['./style.scss'],
|
|
10
|
+
standalone: false
|
|
11
|
+
})
|
|
12
|
+
export class MetaResourceCard implements OnInit {
|
|
13
|
+
@Input() resource: any;
|
|
14
|
+
@Input() model: any;
|
|
15
|
+
|
|
16
|
+
integrationCode: string;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private metaResource: MetaResourceService,
|
|
20
|
+
private referenceService: MetaReferenceService,
|
|
21
|
+
private viewContainerRef: ViewContainerRef
|
|
22
|
+
) { }
|
|
23
|
+
|
|
24
|
+
ngOnInit() {
|
|
25
|
+
this.integrationCode = (this.resource.integration || this.resource.integrationCode || '').toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
open(reference, model?) {
|
|
29
|
+
const refDialogComponent = this.viewContainerRef.createComponent(MetaRefDialog);
|
|
30
|
+
this.referenceService.openDialog(
|
|
31
|
+
refDialogComponent, this.metaResource, this.integrationCode, reference, model,
|
|
32
|
+
model && (reference.editable === false) // is preview mode
|
|
33
|
+
).subscribe((result: any) => {
|
|
34
|
+
console.log('dialog result', result);
|
|
35
|
+
// if (result && result.raw) {
|
|
36
|
+
// if (!this.parent[reference.type]) {
|
|
37
|
+
// this.parent[reference.type] = [];
|
|
38
|
+
// }
|
|
39
|
+
// this.parent[reference.type].push(result.raw);
|
|
40
|
+
// }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="Vlt-card Vlt-bg-white">
|
|
2
|
+
<div class="Vlt-card__content Vlt-btn-on-hover">
|
|
3
|
+
<h5>{{model.Subject || model.subject || model.summary || model.title || model.CaseNumber || model.comment || model.body || model.name || model.topic || '...' }}</h5>
|
|
4
|
+
<div style='font-size: 12px; line-height: 16px;'>
|
|
5
|
+
Created: <span class='Vlt-black'>{{model._vgis.createdDate | date:'mediumDate'}}</span>
|
|
6
|
+
Edited: <span class='Vlt-black'>{{model._vgis.modifiedDate | date:'medium'}}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="Vlt-badge Vlt-badge--app Vlt-badge--purple Vlt-badge--small">{{model._vgis.resourceType}}</div>
|
|
9
|
+
<div class="Vlt-btn-group Vlt-btn-group--hover">
|
|
10
|
+
<a *ngIf='model._vgis.externalLink' attr.href='{{model._vgis.externalLink}}' target='_blank' rel='noopener' class="Vlt-btn Vlt-btn--tertiary Vlt-btn--icon" aria-label='Open external'><svg style='margin-left: 0; margin-right: 0;'><use xlink:href="volta/volta-icons.svg#Vlt-icon-open"/></svg></a>
|
|
11
|
+
<button type='button' (click)='open(resource, model)' class="Vlt-btn Vlt-btn--tertiary Vlt-btn--icon" aria-label='Edit'><svg style='margin-left: 0; margin-right: 0;'><use xlink:href="volta/volta-icons.svg#Vlt-icon-edit"/></svg></button>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2019-09-19 17:35:19
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2020-03-06 10:10:09
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Injectable } from '@angular/core';
|
|
9
|
+
import { BehaviorSubject } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
@Injectable({
|
|
12
|
+
providedIn: 'root'
|
|
13
|
+
})
|
|
14
|
+
export class MetaContextService {
|
|
15
|
+
phoneNumber: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
defaultContactables: {[integrationCode: string]: any} = {};
|
|
18
|
+
workers: {[integrationCode: string]: any} = {};
|
|
19
|
+
contactables: {[integrationCode: string]: any} = {};
|
|
20
|
+
profiles: {[integrationCode: string]: any} = {};
|
|
21
|
+
vgipEventId: string;
|
|
22
|
+
vgipUserId: string;
|
|
23
|
+
_context: any;
|
|
24
|
+
uid: string;
|
|
25
|
+
subject: string;
|
|
26
|
+
onClick2Dial: (phoneNumber, vrn?: string) => void;
|
|
27
|
+
onContextChange: BehaviorSubject<any>;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.onContextChange = new BehaviorSubject({});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get context() {
|
|
34
|
+
return this._context;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set context(value) {
|
|
38
|
+
this._context = value;
|
|
39
|
+
this.onContextChange.next(this._context);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set(context?) {
|
|
43
|
+
if (context) {
|
|
44
|
+
if (context.phoneNumber) {
|
|
45
|
+
this.phoneNumber = context.phoneNumber;
|
|
46
|
+
}
|
|
47
|
+
if (context.displayName) {
|
|
48
|
+
this.displayName = context.displayName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setOnClick2Dial(callback: (phoneNumber, vrn?: string) => void) {
|
|
54
|
+
if (typeof(callback) === 'function') {
|
|
55
|
+
this.onClick2Dial = callback;
|
|
56
|
+
} else {
|
|
57
|
+
delete this.onClick2Dial;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2019-09-19 17:35:19
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2020-05-03 21:31:16
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Worker messages systems. Every integration may extend the base functionality by registering an integration worker.
|
|
10
|
+
* This service provides single response for the worker message requests as a callback.
|
|
11
|
+
*/
|
|
12
|
+
import { Injectable } from '@angular/core';
|
|
13
|
+
|
|
14
|
+
import { MetaContextService } from '../metaContext';
|
|
15
|
+
|
|
16
|
+
@Injectable({
|
|
17
|
+
providedIn: 'root'
|
|
18
|
+
})
|
|
19
|
+
export class MetaMsgService {
|
|
20
|
+
private msgCallbacks: {[state: number]: any} = {}; // message subscribers holder
|
|
21
|
+
private msgListeneres: Array<string> = []; // ensure listener is registerd only once
|
|
22
|
+
constructor(private metaContext: MetaContextService) {
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
sendMessage(provider: string, msg: any, callback: (response) => void, isLong = false) {
|
|
26
|
+
provider = (provider || '').toUpperCase();
|
|
27
|
+
const providerWorker = this.metaContext.workers[provider];
|
|
28
|
+
if (providerWorker) {
|
|
29
|
+
if (this.msgListeneres.indexOf(provider)) {
|
|
30
|
+
this.msgListeneres.push(provider);
|
|
31
|
+
providerWorker.addEventListener('message', this.workerMsgListener, false); // register a short time listener
|
|
32
|
+
}
|
|
33
|
+
let state; // message unique identifier
|
|
34
|
+
do {
|
|
35
|
+
state = new Date().getTime();
|
|
36
|
+
} while (this.msgCallbacks[state]);
|
|
37
|
+
|
|
38
|
+
this.msgCallbacks[state] = callback; // register message callback
|
|
39
|
+
msg.$state = state;
|
|
40
|
+
providerWorker.postMessage(msg);
|
|
41
|
+
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
this.cancelMsgListener(msg.action, state); // give up, worker does not replied on time
|
|
44
|
+
const listenerIndex = this.msgListeneres.indexOf(provider);
|
|
45
|
+
if (listenerIndex !== -1) {
|
|
46
|
+
this.msgListeneres.splice(listenerIndex, 1);
|
|
47
|
+
}
|
|
48
|
+
providerWorker.removeEventListener('message', this.workerMsgListener); // remove listener when done
|
|
49
|
+
}, isLong ? 60000 : 2000);
|
|
50
|
+
} else {
|
|
51
|
+
callback({ action: msg.action, error: `Worker ${provider} is not registered` });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private cancelMsgListener(action, storedState) {
|
|
56
|
+
const storedCallback = this.msgCallbacks[storedState];
|
|
57
|
+
if (storedCallback) {
|
|
58
|
+
storedCallback({ action, error: 'Timeout', state: storedState }); // always reply with something
|
|
59
|
+
}
|
|
60
|
+
delete this.msgCallbacks[storedState];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private workerMsgListener = (msg) => {
|
|
64
|
+
if (msg && msg.data && msg.data.action && msg.data.$state) {
|
|
65
|
+
const state = msg.data.$state;
|
|
66
|
+
const callback = this.msgCallbacks[state]; // original requester callback
|
|
67
|
+
if (callback) {
|
|
68
|
+
delete msg.data.$state;
|
|
69
|
+
callback(msg.data);
|
|
70
|
+
}
|
|
71
|
+
delete this.msgCallbacks[state]; // get rid of the listener callback when done
|
|
72
|
+
}
|
|
73
|
+
this.removeExpiredListeners(); // wipe out the expired listeners if any
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private removeExpiredListeners() {
|
|
77
|
+
const now = new Date();
|
|
78
|
+
for (const state in this.msgCallbacks) { // cleanup expired listeners (< 1 min ago);
|
|
79
|
+
if (parseInt(state, 10) < now.setMinutes(now.getMinutes() - 1)) {
|
|
80
|
+
delete this.msgCallbacks[state];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2019-09-19 17:35:19
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2020-05-03 21:31:44
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Injectable } from '@angular/core';
|
|
9
|
+
import { Router } from '@angular/router';
|
|
10
|
+
import { Observable } from 'rxjs';
|
|
11
|
+
|
|
12
|
+
@Injectable({
|
|
13
|
+
providedIn: 'root'
|
|
14
|
+
})
|
|
15
|
+
export class MetaReferenceService {
|
|
16
|
+
dialogs: Array<any>;
|
|
17
|
+
constructor(
|
|
18
|
+
private router: Router) {
|
|
19
|
+
this.dialogs = [];
|
|
20
|
+
this.router.events.subscribe(() => {
|
|
21
|
+
if (this.dialogs.length) {
|
|
22
|
+
for (let d = (this.dialogs.length - 1); d >= 0; d--) {
|
|
23
|
+
this.dialogs[d].onComplete();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
openDialog(
|
|
30
|
+
componentRef, metaResource, integrationCode, reference, resource,
|
|
31
|
+
preview?: boolean, preferResourceType?, theme?: string, overlayContainer?: HTMLElement
|
|
32
|
+
) {
|
|
33
|
+
const references = !(reference instanceof Array) ? [reference] : reference.filter((r) => r.creatable !== false);
|
|
34
|
+
if (resource && resource.type) {
|
|
35
|
+
if (!references.find((f) => (f.name || f.resourceType) === (resource.resourceType || resource.type))) {
|
|
36
|
+
references.push({
|
|
37
|
+
name: resource.resourceType || resource.type,
|
|
38
|
+
resourceType: resource.resourceType || resource.type,
|
|
39
|
+
label: resource.label || resource.resourceType || resource.type
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const modalObservable = new Observable(observer => {
|
|
45
|
+
const refDialog = componentRef.instance;
|
|
46
|
+
refDialog.integrationCode = integrationCode;
|
|
47
|
+
refDialog.reference = references;
|
|
48
|
+
refDialog.resource = resource;
|
|
49
|
+
refDialog.metaResource = metaResource;
|
|
50
|
+
refDialog.edit = !preview;
|
|
51
|
+
refDialog.preferResourceType = preferResourceType;
|
|
52
|
+
refDialog.theme = theme;
|
|
53
|
+
|
|
54
|
+
const domElem = componentRef.hostView.rootNodes[0] as HTMLElement;
|
|
55
|
+
|
|
56
|
+
const dialogIndex = this.dialogs.push(refDialog);
|
|
57
|
+
|
|
58
|
+
if (dialogIndex > 1) {
|
|
59
|
+
const modalPanel: HTMLElement = domElem.querySelector('.Vlt-modal__panel');
|
|
60
|
+
if (modalPanel) {
|
|
61
|
+
modalPanel.style.marginLeft = `${(dialogIndex * 5)}px`;
|
|
62
|
+
modalPanel.style.marginTop = `${(dialogIndex * 10)}px`;
|
|
63
|
+
modalPanel.style.marginBottom = `${((dialogIndex - 1) * 8)}px`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (overlayContainer && overlayContainer.appendChild) {
|
|
68
|
+
overlayContainer.appendChild(domElem);
|
|
69
|
+
} else {
|
|
70
|
+
document.body.appendChild(domElem);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
refDialog.onComplete = (result) => {
|
|
74
|
+
if (result) {
|
|
75
|
+
/* eslint-disable no-underscore-dangle */
|
|
76
|
+
if (result._vgis) {
|
|
77
|
+
observer.next({
|
|
78
|
+
id: result._vgis.externalId || result._vgis.id,
|
|
79
|
+
label: result._vgis.label,
|
|
80
|
+
type: result._vgis.resourceType,
|
|
81
|
+
raw: result
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
console.log('onComplete non VGIS result', result);
|
|
85
|
+
}
|
|
86
|
+
/* eslint-enable no-underscore-dangle */
|
|
87
|
+
} else {
|
|
88
|
+
observer.next(undefined);
|
|
89
|
+
}
|
|
90
|
+
observer.complete();
|
|
91
|
+
componentRef.destroy();
|
|
92
|
+
this.dialogs.splice(dialogIndex - 1, 1);
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return modalObservable;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2019-09-19 17:35:19
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2020-08-10 22:35:31
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Injectable } from '@angular/core';
|
|
9
|
+
|
|
10
|
+
import { MetaHttpClient } from './metaHttpClient';
|
|
11
|
+
|
|
12
|
+
const STORAGE_PARAMS_REGEXES = {
|
|
13
|
+
localStorage: /^localStorage\.(.*)/,
|
|
14
|
+
sessionStorage: /^sessionStorage\.(.*)/,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const INTEGRATION_URL_TEMPLATE =
|
|
18
|
+
'/extensions/v2/accounts/${localStorage.vgis.accountId}/users/${localStorage.vgis.userId}/connectors/${integrationCode}';
|
|
19
|
+
const DELEGATE_INTEGRATION_URL_TEMPLATE =
|
|
20
|
+
'/portal/v1/accounts/${sessionStorage.vgis.accountId}/users/${sessionStorage.vgis.userId}/integrations/${integrationCode}';
|
|
21
|
+
const RESOURCE_URL_TEMPLATE = INTEGRATION_URL_TEMPLATE + '${parentsPath}/${resourceType}';
|
|
22
|
+
const NEW_RESOURCE_URL_TEMPLATE = INTEGRATION_URL_TEMPLATE + '${parentsPath}/resources/${resourceType}';
|
|
23
|
+
|
|
24
|
+
export class IMetaParentConfig {
|
|
25
|
+
resourceType: string;
|
|
26
|
+
externalId: string;
|
|
27
|
+
parent?: IMetaParentConfig;
|
|
28
|
+
}
|
|
29
|
+
export class IMetaResourceConfig {
|
|
30
|
+
integrationCode: string;
|
|
31
|
+
resourceType: string;
|
|
32
|
+
externalId?: string;
|
|
33
|
+
parent?: IMetaParentConfig;
|
|
34
|
+
delegate?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Injectable({
|
|
38
|
+
providedIn: 'root'
|
|
39
|
+
})
|
|
40
|
+
export class MetaResourceService {
|
|
41
|
+
baseIntegrationUrl: string;
|
|
42
|
+
baseResourceUrl: string;
|
|
43
|
+
newResourceUrl: string;
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
constructor(private httpClient: MetaHttpClient) { }
|
|
47
|
+
|
|
48
|
+
setConfig(config: IMetaResourceConfig) {
|
|
49
|
+
const baseUrlTemplate = config.delegate ? DELEGATE_INTEGRATION_URL_TEMPLATE : INTEGRATION_URL_TEMPLATE;
|
|
50
|
+
this.baseIntegrationUrl = baseUrlTemplate.replace(/\${\s*([\w.]+)\s*}/g, (m, value) => {
|
|
51
|
+
for (const storage of Object.keys(STORAGE_PARAMS_REGEXES)) {
|
|
52
|
+
const match = value.match(STORAGE_PARAMS_REGEXES[storage]);
|
|
53
|
+
if (match) {
|
|
54
|
+
return window[storage].getItem(match[1]) || window[storage].getItem(`cti.${match[1]}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return config[value];
|
|
58
|
+
});
|
|
59
|
+
this.baseResourceUrl = RESOURCE_URL_TEMPLATE.replace(/\${\s*([\w.]+)\s*}/g, (m, value) => {
|
|
60
|
+
for (const storage of Object.keys(STORAGE_PARAMS_REGEXES)) {
|
|
61
|
+
const match = value.match(STORAGE_PARAMS_REGEXES[storage]);
|
|
62
|
+
if (match) {
|
|
63
|
+
return window[storage].getItem(match[1]) || window[storage].getItem(`cti.${match[1]}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (value === 'parentsPath') {
|
|
67
|
+
let parentsPath = '';
|
|
68
|
+
if (config.parent) { // config.parent: { resourceType: '...', id: '...', parent: {...} }
|
|
69
|
+
let rootParent: any = config.parent;
|
|
70
|
+
while (rootParent) {
|
|
71
|
+
parentsPath += `/${rootParent.resourceType || rootParent.type}/${rootParent.externalId || rootParent.id}`;
|
|
72
|
+
rootParent = rootParent.parent;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return parentsPath;
|
|
76
|
+
} else {
|
|
77
|
+
return config[value];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
this.newResourceUrl = NEW_RESOURCE_URL_TEMPLATE.replace(/\${\s*([\w.]+)\s*}/g, (m, value) => {
|
|
81
|
+
for (const storage of Object.keys(STORAGE_PARAMS_REGEXES)) {
|
|
82
|
+
const match = value.match(STORAGE_PARAMS_REGEXES[storage]);
|
|
83
|
+
if (match) {
|
|
84
|
+
return window[storage].getItem(match[1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (value === 'parentsPath') {
|
|
88
|
+
let parentsPath = '';
|
|
89
|
+
if (config.parent) { // config.parent: { resourceType: '...', id: '...', parent: {...} }
|
|
90
|
+
let rootParent: any = config.parent;
|
|
91
|
+
while (rootParent) {
|
|
92
|
+
parentsPath += `/${rootParent.resourceType || rootParent.type}/${rootParent.externalId || rootParent.id}`;
|
|
93
|
+
rootParent = rootParent.parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return parentsPath;
|
|
97
|
+
} else {
|
|
98
|
+
return config[value];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this.id = config.externalId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
new(config: IMetaResourceConfig) {
|
|
105
|
+
const s = new MetaResourceService(this.httpClient);
|
|
106
|
+
s.setConfig(config);
|
|
107
|
+
return s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
searchIntegration(query, scope?: string) {
|
|
111
|
+
let searchUrl = `${this.baseIntegrationUrl}/search?q=${encodeURIComponent(query)}`;
|
|
112
|
+
if (scope) {
|
|
113
|
+
searchUrl += `&scope=${encodeURIComponent(scope)}`;
|
|
114
|
+
}
|
|
115
|
+
return this.httpClient.get(searchUrl);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getMetadata(layoutId?, refresh?) {
|
|
119
|
+
let url = `${this.baseResourceUrl.replace('/v2/', '/v3/')}/metadata`;
|
|
120
|
+
// url += `?t=${new Date().getTime()}`;
|
|
121
|
+
if (refresh) {
|
|
122
|
+
url += `${ url.indexOf('?') === -1 ? '?' : '&' }refresh=true`;
|
|
123
|
+
}
|
|
124
|
+
if (layoutId) {
|
|
125
|
+
url += `${ url.indexOf('?') === -1 ? '?' : '&' }layoutId=${layoutId}`;
|
|
126
|
+
}
|
|
127
|
+
return this.httpClient.get(url);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getDetails(externalId, extraParams?) {
|
|
131
|
+
let params = 'referenceDetails=true';
|
|
132
|
+
if (extraParams) {
|
|
133
|
+
for (const p in extraParams) {
|
|
134
|
+
if (extraParams[p]) {
|
|
135
|
+
params += `&${p}=${extraParams[p]}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return this.httpClient.get(`${this.baseResourceUrl}/${externalId}?${params}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getByPath(path) { // TODO not universal!
|
|
143
|
+
let fullPath = `${this.newResourceUrl}${path}`;
|
|
144
|
+
if (/^..\//.test(path)) {
|
|
145
|
+
fullPath = `${this.baseIntegrationUrl}${path.substring(2)}`;
|
|
146
|
+
}
|
|
147
|
+
return this.httpClient.get(fullPath);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
save(payload) {
|
|
151
|
+
if (this.id) {
|
|
152
|
+
return this.httpClient.put(
|
|
153
|
+
`${this.baseResourceUrl}/${this.id}`,
|
|
154
|
+
payload
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
return this.httpClient.post(
|
|
158
|
+
`${this.baseResourceUrl}`,
|
|
159
|
+
payload
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2019-09-19 17:35:19
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2019-11-22 14:57:07
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Injectable } from '@angular/core';
|
|
9
|
+
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
10
|
+
|
|
11
|
+
interface HttpClientOptions {
|
|
12
|
+
headers?: HttpHeaders | { [header: string]: string | string[] };
|
|
13
|
+
params?: HttpParams;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Injectable({
|
|
17
|
+
providedIn: 'root'
|
|
18
|
+
})
|
|
19
|
+
export class MetaHttpClient {
|
|
20
|
+
|
|
21
|
+
constructor(private httpClient: HttpClient) {}
|
|
22
|
+
|
|
23
|
+
delete(url: string, queryParams = {}) {
|
|
24
|
+
const options = this.prepareOptions(queryParams);
|
|
25
|
+
|
|
26
|
+
return this.httpClient.delete(`${this.constructUrl(url)}`, options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get<T>(url: string, queryParams = {}) {
|
|
30
|
+
const options = this.prepareOptions(queryParams);
|
|
31
|
+
return this.httpClient.get<T>(`${this.constructUrl(url)}`, options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
post<T>(url: string, body: any, queryParams = {}) {
|
|
35
|
+
const options = this.prepareOptions(queryParams);
|
|
36
|
+
|
|
37
|
+
return this.httpClient.post<T>(`${this.constructUrl(url)}`, body, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
put<T>(url: string, body: any, queryParams = {}) {
|
|
41
|
+
const options = this.prepareOptions(queryParams);
|
|
42
|
+
|
|
43
|
+
return this.httpClient.put<T>(`${this.constructUrl(url)}`, body, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
patch<T>(url: string, body: any, queryParams = {}) {
|
|
47
|
+
const options = this.prepareOptions(queryParams);
|
|
48
|
+
|
|
49
|
+
return this.httpClient.patch<T>(`${this.constructUrl(url)}`, body, options);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private constructUrl(path: string): string {
|
|
53
|
+
return /^http/.test(path) ? path : `${path}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private prepareOptions(queryParams: any): HttpClientOptions {
|
|
57
|
+
Object.keys(queryParams).forEach(key => {
|
|
58
|
+
if (queryParams[key] == null || queryParams[key] === '') {
|
|
59
|
+
delete queryParams[key];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const params = new HttpParams({
|
|
64
|
+
fromObject: queryParams
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const options: HttpClientOptions = {
|
|
68
|
+
headers: new HttpHeaders({
|
|
69
|
+
Accept: 'application/json'
|
|
70
|
+
}),
|
|
71
|
+
params
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return options;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: Alexander.Vangelov@vonage.com
|
|
3
|
+
* @Date: 2020-02-10 18:47:45
|
|
4
|
+
* @Last Modified by: Alexander.Vangelov@vonage.com
|
|
5
|
+
* @Last Modified time: 2020-02-10 18:48:29
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { TestBed } from '@angular/core/testing';
|
|
9
|
+
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
|
10
|
+
|
|
11
|
+
import { MetaResourceService } from './';
|
|
12
|
+
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
|
13
|
+
|
|
14
|
+
describe('MetaResourceService', () => {
|
|
15
|
+
beforeEach(() => TestBed.configureTestingModule({
|
|
16
|
+
imports: [],
|
|
17
|
+
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()]
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
it('should be created', () => {
|
|
21
|
+
const service: MetaResourceService = ((TestBed as any).inject || (TestBed as any).get)(MetaResourceService);
|
|
22
|
+
expect(service).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
});
|