@nyaruka/temba-components 0.111.7 → 0.112.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.
- package/CHANGELOG.md +14 -9
- package/dist/temba-components.js +383 -266
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/button/Button.js +4 -0
- package/out-tsc/src/button/Button.js.map +1 -1
- package/out-tsc/src/chat/Chat.js +24 -20
- package/out-tsc/src/chat/Chat.js.map +1 -1
- package/out-tsc/src/compose/Compose.js +9 -7
- package/out-tsc/src/compose/Compose.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +172 -21
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/options/Options.js +1 -0
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/select/Select.js +30 -3
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/store/EndpointMonitorElement.js +4 -4
- package/out-tsc/src/store/EndpointMonitorElement.js.map +1 -1
- package/out-tsc/src/store/Store.js +8 -7
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/store/StoreMonitorElement.js +1 -6
- package/out-tsc/src/store/StoreMonitorElement.js.map +1 -1
- package/out-tsc/src/tabpane/TabPane.js +6 -4
- package/out-tsc/src/tabpane/TabPane.js.map +1 -1
- package/out-tsc/src/user/TembaUser.js +43 -23
- package/out-tsc/src/user/TembaUser.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +2 -1
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-contact-tickets.test.js +2 -2
- package/out-tsc/test/temba-contact-tickets.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +25 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/src/button/Button.ts +4 -0
- package/src/chat/Chat.ts +28 -28
- package/src/compose/Compose.ts +9 -7
- package/src/contacts/ContactChat.ts +176 -23
- package/src/interfaces.ts +2 -1
- package/src/options/Options.ts +1 -0
- package/src/select/Select.ts +34 -8
- package/src/store/EndpointMonitorElement.ts +5 -5
- package/src/store/Store.ts +8 -9
- package/src/store/StoreMonitorElement.ts +2 -10
- package/src/tabpane/TabPane.ts +6 -4
- package/src/user/TembaUser.ts +48 -26
- package/test/temba-contact-chat.test.ts +3 -0
- package/test/temba-contact-tickets.test.ts +2 -5
- package/test/utils.test.ts +27 -0
- package/test-assets/api/users/admin1.json +13 -0
- package/test-assets/api/users/agent1.json +13 -0
- package/test-assets/api/users/editor1.json +13 -0
- package/test-assets/api/users/viewer1.json +13 -0
package/src/select/Select.ts
CHANGED
|
@@ -81,8 +81,11 @@ export class Select extends FormElement {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
.wrapper-bg {
|
|
84
|
-
background: #fff;
|
|
85
|
-
box-shadow:
|
|
84
|
+
background: var(--select-wrapper-bg, #fff);
|
|
85
|
+
box-shadow: var(
|
|
86
|
+
--select-wrapper-shadow,
|
|
87
|
+
inset 0px 0px 4px rgb(0 0 0 / 10%)
|
|
88
|
+
);
|
|
86
89
|
border-radius: var(--curvature-widget);
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -170,6 +173,10 @@ export class Select extends FormElement {
|
|
|
170
173
|
margin: 2px 2px;
|
|
171
174
|
}
|
|
172
175
|
|
|
176
|
+
.option-name > span {
|
|
177
|
+
text-align: left;
|
|
178
|
+
}
|
|
179
|
+
|
|
173
180
|
.selected-item .option-name {
|
|
174
181
|
padding: 0px;
|
|
175
182
|
font-size: var(--temba-select-selected-font-size);
|
|
@@ -411,6 +418,9 @@ export class Select extends FormElement {
|
|
|
411
418
|
@property({ type: Boolean })
|
|
412
419
|
clearable: boolean;
|
|
413
420
|
|
|
421
|
+
@property({ type: Boolean })
|
|
422
|
+
sorted: boolean;
|
|
423
|
+
|
|
414
424
|
@property({ type: String })
|
|
415
425
|
flavor = 'default';
|
|
416
426
|
|
|
@@ -447,7 +457,7 @@ export class Select extends FormElement {
|
|
|
447
457
|
shouldExclude: (option: any) => boolean;
|
|
448
458
|
|
|
449
459
|
@property({ attribute: false })
|
|
450
|
-
sortFunction: (a: any, b: any) => number;
|
|
460
|
+
sortFunction: (a: any, b: any) => number = null;
|
|
451
461
|
|
|
452
462
|
@property({ attribute: false })
|
|
453
463
|
renderOption: (option: any, selected: boolean) => TemplateResult;
|
|
@@ -470,6 +480,9 @@ export class Select extends FormElement {
|
|
|
470
480
|
@property({ attribute: false })
|
|
471
481
|
getOptions: (response: WebResponse) => any[] = this.getOptionsDefault;
|
|
472
482
|
|
|
483
|
+
@property({ attribute: false })
|
|
484
|
+
prepareOptions: (options: any[]) => any[] = (options: any[]) => options;
|
|
485
|
+
|
|
473
486
|
@property({ attribute: false })
|
|
474
487
|
isComplete: (newestOptions: any[], response: WebResponse) => boolean =
|
|
475
488
|
this.isCompleteDefault;
|
|
@@ -477,6 +490,14 @@ export class Select extends FormElement {
|
|
|
477
490
|
@property({ type: Array, attribute: 'options' })
|
|
478
491
|
private staticOptions: any[] = [];
|
|
479
492
|
|
|
493
|
+
private alphaSort = (a: any, b: any) => {
|
|
494
|
+
// by default, all endpoint values are sorted by name
|
|
495
|
+
if (this.endpoint) {
|
|
496
|
+
return this.getName(a).localeCompare(this.getName(b));
|
|
497
|
+
}
|
|
498
|
+
return 0;
|
|
499
|
+
};
|
|
500
|
+
|
|
480
501
|
private lastQuery: number;
|
|
481
502
|
|
|
482
503
|
// private cancelToken: CancelTokenSource;
|
|
@@ -572,6 +593,10 @@ export class Select extends FormElement {
|
|
|
572
593
|
public updated(changedProperties: Map<string, any>) {
|
|
573
594
|
super.updated(changedProperties);
|
|
574
595
|
|
|
596
|
+
if (changedProperties.has('sorted')) {
|
|
597
|
+
this.sortFunction = this.sorted ? this.alphaSort : null;
|
|
598
|
+
}
|
|
599
|
+
|
|
575
600
|
if (changedProperties.has('values')) {
|
|
576
601
|
this.updateInputs();
|
|
577
602
|
if (
|
|
@@ -962,6 +987,8 @@ export class Select extends FormElement {
|
|
|
962
987
|
// if we are searchable, but doing it locally, fetch all the options
|
|
963
988
|
if (this.searchable && !this.queryParam) {
|
|
964
989
|
fetchResults(url).then((results: any) => {
|
|
990
|
+
results = this.prepareOptions(results);
|
|
991
|
+
|
|
965
992
|
if (this.cache && !this.tags) {
|
|
966
993
|
this.lruCache.set(url, {
|
|
967
994
|
options: results,
|
|
@@ -978,11 +1005,10 @@ export class Select extends FormElement {
|
|
|
978
1005
|
} else {
|
|
979
1006
|
getUrl(url)
|
|
980
1007
|
.then((response: WebResponse) => {
|
|
981
|
-
|
|
982
|
-
(option
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
);
|
|
1008
|
+
let results = this.getOptions(response).filter((option: any) => {
|
|
1009
|
+
return this.isMatch(option, q);
|
|
1010
|
+
});
|
|
1011
|
+
results = this.prepareOptions(results);
|
|
986
1012
|
|
|
987
1013
|
this.next = null;
|
|
988
1014
|
const json = response.json;
|
|
@@ -18,6 +18,11 @@ export class EndpointMonitorElement extends StoreMonitorElement {
|
|
|
18
18
|
@property({ type: Object, attribute: false })
|
|
19
19
|
data: any;
|
|
20
20
|
|
|
21
|
+
connectedCallback(): void {
|
|
22
|
+
super.connectedCallback();
|
|
23
|
+
this.prepareData = this.prepareData.bind(this);
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
prepareData(data: any): any {
|
|
22
27
|
return data;
|
|
23
28
|
}
|
|
@@ -52,9 +57,4 @@ export class EndpointMonitorElement extends StoreMonitorElement {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
connectedCallback(): void {
|
|
57
|
-
super.connectedCallback();
|
|
58
|
-
this.prepareData = this.prepareData.bind(this);
|
|
59
|
-
}
|
|
60
60
|
}
|
package/src/store/Store.ts
CHANGED
|
@@ -104,7 +104,7 @@ export class Store extends RapidElement {
|
|
|
104
104
|
private groups: { [uuid: string]: ContactGroup } = {};
|
|
105
105
|
private shortcuts: Shortcut[] = [];
|
|
106
106
|
private languages: any = {};
|
|
107
|
-
private users: User[];
|
|
107
|
+
private users: User[] = [];
|
|
108
108
|
private workspace: Workspace;
|
|
109
109
|
private featuredFields: ContactField[] = [];
|
|
110
110
|
|
|
@@ -255,10 +255,6 @@ export class Store extends RapidElement {
|
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
public getUser(email: string) {
|
|
259
|
-
return this.users.find((user: User) => user.email === email);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
258
|
public firstUpdated() {
|
|
263
259
|
this.reset();
|
|
264
260
|
}
|
|
@@ -521,16 +517,19 @@ export class Store extends RapidElement {
|
|
|
521
517
|
const previousRequest = this.fetching[url];
|
|
522
518
|
const now = new Date().getTime();
|
|
523
519
|
// if the request was recently made, don't do anything
|
|
524
|
-
if (previousRequest
|
|
520
|
+
if (previousRequest) {
|
|
521
|
+
setTimeout(() => {
|
|
522
|
+
this.makeRequest(url, options);
|
|
523
|
+
}, 500);
|
|
525
524
|
return;
|
|
526
525
|
}
|
|
527
526
|
|
|
528
|
-
this.fetching[url] = now;
|
|
529
|
-
options = options || {};
|
|
530
527
|
const cached = this.cache.get(url);
|
|
531
528
|
if (cached && !options.force) {
|
|
532
529
|
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data: cached });
|
|
533
530
|
} else {
|
|
531
|
+
options = options || {};
|
|
532
|
+
this.fetching[url] = now;
|
|
534
533
|
fetchResults(url).then((data) => {
|
|
535
534
|
if (!data) {
|
|
536
535
|
delete this.fetching[url];
|
|
@@ -539,8 +538,8 @@ export class Store extends RapidElement {
|
|
|
539
538
|
|
|
540
539
|
data = options.prepareData ? options.prepareData(data) : data;
|
|
541
540
|
this.cache.set(url, data);
|
|
542
|
-
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data });
|
|
543
541
|
delete this.fetching[url];
|
|
542
|
+
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data });
|
|
544
543
|
});
|
|
545
544
|
}
|
|
546
545
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html,
|
|
1
|
+
import { html, TemplateResult } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { CustomEventType } from '../interfaces';
|
|
4
4
|
import { RapidElement } from '../RapidElement';
|
|
@@ -39,20 +39,12 @@ export class StoreMonitorElement extends RapidElement {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
private handleStoreUpdated(event: CustomEvent) {
|
|
42
|
-
this.
|
|
43
|
-
this.storeUpdated(event);
|
|
44
|
-
});
|
|
42
|
+
this.storeUpdated(event);
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
48
46
|
protected storeUpdated(event: CustomEvent) {}
|
|
49
47
|
|
|
50
|
-
protected updated(
|
|
51
|
-
properties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
52
|
-
): void {
|
|
53
|
-
super.updated(properties);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
48
|
connectedCallback(): void {
|
|
57
49
|
super.connectedCallback();
|
|
58
50
|
this.store = document.querySelector('temba-store') as Store;
|
package/src/tabpane/TabPane.ts
CHANGED
|
@@ -223,17 +223,19 @@ export class TabPane extends RapidElement {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
.embedded.tabs {
|
|
226
|
-
|
|
226
|
+
padding-top: 0em;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
.embedded .tab {
|
|
230
|
-
border-top: none !important;
|
|
231
230
|
border-bottom: none !important;
|
|
232
|
-
border-radius:
|
|
231
|
+
border-radius: 0em;
|
|
232
|
+
border-top: none !important;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
.embedded .tab.first {
|
|
236
|
-
|
|
236
|
+
margin-left: 0em;
|
|
237
|
+
border-top: none !important;
|
|
238
|
+
border-left: none;
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
.embedded.tabs .tab.selected {
|
package/src/user/TembaUser.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { PropertyValueMap, TemplateResult, css, html } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { User } from '../interfaces';
|
|
4
|
-
import { StoreMonitorElement } from '../store/StoreMonitorElement';
|
|
5
4
|
import { colorHash, extractInitials } from '../utils';
|
|
5
|
+
import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
|
|
6
|
+
import { DEFAULT_AVATAR } from '../webchat/assets';
|
|
6
7
|
|
|
7
|
-
export class TembaUser extends
|
|
8
|
+
export class TembaUser extends EndpointMonitorElement {
|
|
8
9
|
public static styles = css`
|
|
9
10
|
:host {
|
|
10
11
|
display: flex;
|
|
@@ -33,45 +34,66 @@ export class TembaUser extends StoreMonitorElement {
|
|
|
33
34
|
scale: number;
|
|
34
35
|
|
|
35
36
|
@property({ type: Boolean })
|
|
36
|
-
name:
|
|
37
|
+
name: boolean;
|
|
37
38
|
|
|
38
|
-
@property({ type:
|
|
39
|
-
|
|
39
|
+
@property({ type: Boolean })
|
|
40
|
+
system: boolean;
|
|
40
41
|
|
|
41
42
|
@property({ type: String, attribute: false })
|
|
42
|
-
background: string;
|
|
43
|
+
background: string = '#e6e6e6';
|
|
43
44
|
|
|
44
45
|
@property({ type: String, attribute: false })
|
|
45
|
-
initials: string;
|
|
46
|
+
initials: string = '';
|
|
46
47
|
|
|
47
|
-
@property({ type: String
|
|
48
|
-
|
|
48
|
+
@property({ type: String })
|
|
49
|
+
fullname: string;
|
|
50
|
+
|
|
51
|
+
@property({ type: Object, attribute: false })
|
|
52
|
+
data: User;
|
|
53
|
+
|
|
54
|
+
prepareData(data: any) {
|
|
55
|
+
if (data.length > 0) {
|
|
56
|
+
return data[0];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.fullname = this.email;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
49
62
|
|
|
50
63
|
public updated(
|
|
51
64
|
changed: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
52
65
|
): void {
|
|
53
66
|
super.updated(changed);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
|
|
68
|
+
if (changed.has('email') && this.email) {
|
|
69
|
+
this.url = `/api/v2/users.json?email=${this.email}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (changed.has('system') && this.system) {
|
|
73
|
+
this.background = `url('${DEFAULT_AVATAR}') center / contain no-repeat`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (changed.has('data') && this.data) {
|
|
77
|
+
if (this.data.first_name && this.data.last_name) {
|
|
78
|
+
this.fullname = [this.data.first_name, this.data.last_name].join(' ');
|
|
79
|
+
this.background = colorHash.hex(this.fullname);
|
|
80
|
+
this.initials = extractInitials(this.fullname);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.data.avatar) {
|
|
84
|
+
this.background = `url('${this.data.avatar}') center / contain no-repeat`;
|
|
85
|
+
this.initials = '';
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
|
-
}
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
if (changed.has('fullname') && this.fullname && !this.data) {
|
|
90
|
+
this.background = colorHash.hex(this.fullname);
|
|
91
|
+
this.initials = extractInitials(this.fullname);
|
|
72
92
|
}
|
|
93
|
+
}
|
|
73
94
|
|
|
74
|
-
|
|
95
|
+
public render(): TemplateResult {
|
|
96
|
+
return html`<div class="wrapper">
|
|
75
97
|
<div
|
|
76
98
|
class="avatar-circle"
|
|
77
99
|
style="
|
|
@@ -103,7 +125,7 @@ export class TembaUser extends StoreMonitorElement {
|
|
|
103
125
|
style="margin: 0px ${this.scale - 0.5}em;font-size:${this.scale +
|
|
104
126
|
0.2}em"
|
|
105
127
|
>
|
|
106
|
-
${this.
|
|
128
|
+
${this.fullname}
|
|
107
129
|
</div>`
|
|
108
130
|
: null}
|
|
109
131
|
</div>`;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getClip,
|
|
9
9
|
getComponent,
|
|
10
10
|
loadStore,
|
|
11
|
+
mockAPI,
|
|
11
12
|
mockGET,
|
|
12
13
|
mockNow,
|
|
13
14
|
mockPOST
|
|
@@ -58,6 +59,8 @@ describe('temba-contact-chat', () => {
|
|
|
58
59
|
/\/contact\/history\/contact-.*/,
|
|
59
60
|
'/test-assets/contacts/history.json'
|
|
60
61
|
);
|
|
62
|
+
|
|
63
|
+
mockAPI();
|
|
61
64
|
clock = useFakeTimers();
|
|
62
65
|
});
|
|
63
66
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getClip,
|
|
6
6
|
getComponent,
|
|
7
7
|
loadStore,
|
|
8
|
-
|
|
8
|
+
mockAPI,
|
|
9
9
|
mockNow
|
|
10
10
|
} from './utils.test';
|
|
11
11
|
|
|
@@ -25,10 +25,7 @@ const getContactTickets = async (attrs: any = {}) => {
|
|
|
25
25
|
mockNow('2023-04-07T00:00:00.000-00:00');
|
|
26
26
|
describe('temba-contact-tickets', () => {
|
|
27
27
|
beforeEach(() => {
|
|
28
|
-
|
|
29
|
-
/\/api\/v2\/tickets.json\?contact=24d64810-3315-4ff5-be85-48e3fe055bf9/,
|
|
30
|
-
'/test-assets/contacts/contact-tickets.json'
|
|
31
|
-
);
|
|
28
|
+
mockAPI();
|
|
32
29
|
loadStore();
|
|
33
30
|
});
|
|
34
31
|
|
package/test/utils.test.ts
CHANGED
|
@@ -114,6 +114,33 @@ after(() => {
|
|
|
114
114
|
(window.fetch as any).restore();
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
const mockMapping = {
|
|
118
|
+
'/test-assets/api/users/admin1.json': [
|
|
119
|
+
/\/api\/v2\/users.json\?email=admin1@nyaruka.com/
|
|
120
|
+
],
|
|
121
|
+
'/test-assets/api/users/editor1.json': [
|
|
122
|
+
/\/api\/v2\/users.json\?email=editor1@nyaruka.com/
|
|
123
|
+
],
|
|
124
|
+
'/test-assets/api/users/agent1.json': [
|
|
125
|
+
/\/api\/v2\/users.json\?email=agent1@nyaruka.com/
|
|
126
|
+
],
|
|
127
|
+
'/test-assets/api/users/viewer1.json': [
|
|
128
|
+
/\/api\/v2\/users.json\?email=viewer1@nyaruka.com/
|
|
129
|
+
],
|
|
130
|
+
'/test-assets/contacts/contact-tickets.json': [
|
|
131
|
+
/\/api\/v2\/tickets.json\?contact=24d64810-3315-4ff5-be85-48e3fe055bf9/
|
|
132
|
+
]
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const mockAPI = () => {
|
|
136
|
+
for (const key in mockMapping) {
|
|
137
|
+
const urls = mockMapping[key];
|
|
138
|
+
for (const url of urls) {
|
|
139
|
+
mockGET(url, key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
117
144
|
export const mockGET = (
|
|
118
145
|
endpoint: RegExp,
|
|
119
146
|
body: any,
|