@nyaruka/temba-components 0.91.6 → 0.92.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 +9 -0
- package/demo/index.html +1 -1
- package/dist/temba-components.js +760 -1189
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/chat/Chat.js +714 -0
- package/out-tsc/src/chat/Chat.js.map +1 -0
- package/out-tsc/src/completion/helpers.js +1 -29
- package/out-tsc/src/completion/helpers.js.map +1 -1
- package/out-tsc/src/compose/Compose.js +6 -2
- package/out-tsc/src/compose/Compose.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +518 -54
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/contacts/events.js +1 -998
- package/out-tsc/src/contacts/events.js.map +1 -1
- package/out-tsc/src/lightbox/Lightbox.js +4 -0
- package/out-tsc/src/lightbox/Lightbox.js.map +1 -1
- package/out-tsc/src/list/TembaMenu.js +0 -1
- package/out-tsc/src/list/TembaMenu.js.map +1 -1
- package/out-tsc/src/markdown.js +33 -0
- package/out-tsc/src/markdown.js.map +1 -0
- package/out-tsc/src/select/Select.js +6 -1
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/textinput/TextInput.js +1 -1
- package/out-tsc/src/textinput/TextInput.js.map +1 -1
- package/out-tsc/src/thumbnail/Thumbnail.js +128 -81
- package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -1
- package/out-tsc/src/utils/index.js +9 -11
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +109 -358
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/src/webchat/index.js +17 -0
- package/out-tsc/src/webchat/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/temba-webchat.js +2 -0
- package/out-tsc/temba-webchat.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +1 -0
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-lightbox.test.js +4 -4
- package/out-tsc/test/temba-lightbox.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
- package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
- package/screenshots/truth/contacts/contact-active-default.png +0 -0
- package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
- package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
- package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
- package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
- package/screenshots/truth/lightbox/img-zoomed.png +0 -0
- package/screenshots/truth/lightbox/img.png +0 -0
- package/src/chat/Chat.ts +791 -0
- package/src/completion/helpers.ts +2 -40
- package/src/compose/Compose.ts +6 -2
- package/src/contacts/ContactChat.ts +609 -59
- package/src/contacts/events.ts +1 -1068
- package/src/lightbox/Lightbox.ts +5 -0
- package/src/list/TembaMenu.ts +0 -1
- package/src/markdown.ts +41 -0
- package/src/select/Select.ts +5 -1
- package/src/textinput/TextInput.ts +1 -1
- package/src/thumbnail/Thumbnail.ts +130 -81
- package/src/utils/index.ts +12 -13
- package/src/webchat/WebChat.ts +196 -413
- package/src/webchat/index.ts +23 -1
- package/static/css/temba-components.css +2 -0
- package/temba-modules.ts +2 -2
- package/temba-webchat.ts +2 -0
- package/test/temba-contact-chat.test.ts +1 -0
- package/test/temba-lightbox.test.ts +4 -4
- package/test-assets/contacts/history.json +1 -56
- package/out-tsc/src/contacts/ContactHistory.js +0 -691
- package/out-tsc/src/contacts/ContactHistory.js.map +0 -1
- package/out-tsc/test/temba-contact-history.test.js +0 -69
- package/out-tsc/test/temba-contact-history.test.js.map +0 -1
- package/src/contacts/ContactHistory.ts +0 -875
- package/test/temba-contact-history.test.ts +0 -107
|
@@ -1,875 +0,0 @@
|
|
|
1
|
-
import { css } from 'lit';
|
|
2
|
-
import { property } from 'lit/decorators.js';
|
|
3
|
-
import { html, TemplateResult } from 'lit-html';
|
|
4
|
-
import { Contact, CustomEventType, Ticket } from '../interfaces';
|
|
5
|
-
import { RapidElement } from '../RapidElement';
|
|
6
|
-
import { Asset, getAssets, getClasses, postJSON, throttle } from '../utils';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
AirtimeTransferredEvent,
|
|
10
|
-
CampaignFiredEvent,
|
|
11
|
-
ChannelEvent,
|
|
12
|
-
ContactEvent,
|
|
13
|
-
ContactGroupsEvent,
|
|
14
|
-
ContactHistoryPage,
|
|
15
|
-
ContactLanguageChangedEvent,
|
|
16
|
-
EmailSentEvent,
|
|
17
|
-
ErrorMessageEvent,
|
|
18
|
-
EventGroup,
|
|
19
|
-
Events,
|
|
20
|
-
FlowEvent,
|
|
21
|
-
getEventGroupType,
|
|
22
|
-
getEventStyles,
|
|
23
|
-
LabelsAddedEvent,
|
|
24
|
-
MsgEvent,
|
|
25
|
-
NameChangedEvent,
|
|
26
|
-
OptinRequestedEvent,
|
|
27
|
-
renderAirtimeTransferredEvent,
|
|
28
|
-
renderCallStartedEvent,
|
|
29
|
-
renderCampaignFiredEvent,
|
|
30
|
-
renderChannelEvent,
|
|
31
|
-
renderContactGroupsEvent,
|
|
32
|
-
renderContactLanguageChangedEvent,
|
|
33
|
-
renderContactURNsChanged,
|
|
34
|
-
renderEmailSent,
|
|
35
|
-
renderErrorMessage,
|
|
36
|
-
renderFlowEvent,
|
|
37
|
-
renderLabelsAdded,
|
|
38
|
-
renderMsgEvent,
|
|
39
|
-
renderNameChanged,
|
|
40
|
-
renderNoteCreated,
|
|
41
|
-
renderOptinRequested,
|
|
42
|
-
renderResultEvent,
|
|
43
|
-
renderTicketAction,
|
|
44
|
-
renderTicketAssigned,
|
|
45
|
-
renderUpdateEvent,
|
|
46
|
-
renderWebhookEvent,
|
|
47
|
-
TicketEvent,
|
|
48
|
-
UpdateFieldEvent,
|
|
49
|
-
UpdateResultEvent,
|
|
50
|
-
URNsChangedEvent,
|
|
51
|
-
WebhookEvent
|
|
52
|
-
} from './events';
|
|
53
|
-
import {
|
|
54
|
-
fetchContactHistory,
|
|
55
|
-
MAX_CHAT_REFRESH,
|
|
56
|
-
MIN_CHAT_REFRESH,
|
|
57
|
-
SCROLL_THRESHOLD
|
|
58
|
-
} from './helpers';
|
|
59
|
-
import { Lightbox } from '../lightbox/Lightbox';
|
|
60
|
-
import { Store } from '../store/Store';
|
|
61
|
-
|
|
62
|
-
// when images load, make sure we are on the bottom of the scroll window if necessary
|
|
63
|
-
export const loadHandler = function (event) {
|
|
64
|
-
const target = event.target as HTMLElement;
|
|
65
|
-
const events = this.host.getEventsPane();
|
|
66
|
-
if (target.tagName == 'IMG') {
|
|
67
|
-
if (!this.host.showMessageAlert) {
|
|
68
|
-
if (
|
|
69
|
-
events.scrollTop > target.offsetTop - 1000 &&
|
|
70
|
-
target.offsetTop > events.scrollHeight - 500
|
|
71
|
-
) {
|
|
72
|
-
this.host.scrollToBottom();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export class ContactHistory extends RapidElement {
|
|
79
|
-
public httpComplete: Promise<void | ContactHistoryPage>;
|
|
80
|
-
private store: Store;
|
|
81
|
-
|
|
82
|
-
public constructor() {
|
|
83
|
-
super();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
connectedCallback() {
|
|
87
|
-
super.connectedCallback();
|
|
88
|
-
this.shadowRoot.addEventListener('load', loadHandler, true);
|
|
89
|
-
this.store = document.querySelector('temba-store') as Store;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
disconnectedCallback() {
|
|
93
|
-
super.disconnectedCallback();
|
|
94
|
-
this.shadowRoot.removeEventListener('load', loadHandler, true);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private getTicketForEvent(event: TicketEvent) {
|
|
98
|
-
return this.getTicket((event as TicketEvent).ticket.uuid);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private getTicket(uuid: string) {
|
|
102
|
-
return (this.tickets || []).find((ticket) => ticket.uuid === uuid);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
static get styles() {
|
|
106
|
-
return css`
|
|
107
|
-
${getEventStyles()}
|
|
108
|
-
|
|
109
|
-
.wrapper {
|
|
110
|
-
border: 0px solid green;
|
|
111
|
-
display: flex;
|
|
112
|
-
flex-direction: column;
|
|
113
|
-
align-items: items-stretch;
|
|
114
|
-
flex-grow: 1;
|
|
115
|
-
min-height: 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.events {
|
|
119
|
-
overflow-y: scroll;
|
|
120
|
-
overflow-x: hidden;
|
|
121
|
-
background: #fff;
|
|
122
|
-
display: flex;
|
|
123
|
-
flex-direction: column;
|
|
124
|
-
flex-grow: 1;
|
|
125
|
-
min-height: 0;
|
|
126
|
-
padding-top: 3em;
|
|
127
|
-
padding-bottom: 1em;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
temba-loading {
|
|
131
|
-
align-self: center;
|
|
132
|
-
margin-top: 0.025em;
|
|
133
|
-
position: absolute;
|
|
134
|
-
z-index: 250;
|
|
135
|
-
padding-top: 1em;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.new-messages-container {
|
|
139
|
-
display: flex;
|
|
140
|
-
z-index: 1;
|
|
141
|
-
background: pink;
|
|
142
|
-
margin-bottom: 0px;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.new-messages {
|
|
146
|
-
pointer-events: none;
|
|
147
|
-
margin: 0 auto;
|
|
148
|
-
margin-top: 0em;
|
|
149
|
-
margin-bottom: -2.5em;
|
|
150
|
-
padding: 0.25em 1em;
|
|
151
|
-
border-radius: var(--curvature);
|
|
152
|
-
background: var(--color-primary-dark);
|
|
153
|
-
color: var(--color-text-light);
|
|
154
|
-
opacity: 0;
|
|
155
|
-
cursor: pointer;
|
|
156
|
-
transition: all var(--transition-speed) ease-in-out;
|
|
157
|
-
box-shadow: rgb(0 0 0 / 15%) 0px 3px 3px 0px;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.new-messages.expanded {
|
|
161
|
-
margin-top: -2.5em;
|
|
162
|
-
margin-bottom: 0.5em;
|
|
163
|
-
pointer-events: auto;
|
|
164
|
-
opacity: 1;
|
|
165
|
-
pointer: cursor;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.scroll-title {
|
|
169
|
-
display: flex;
|
|
170
|
-
flex-direction: column;
|
|
171
|
-
z-index: 2;
|
|
172
|
-
border-top-left-radius: var(--curvature);
|
|
173
|
-
overflow: hidden;
|
|
174
|
-
box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.15);
|
|
175
|
-
background: rgb(240, 240, 240);
|
|
176
|
-
padding: 1em 1.2em;
|
|
177
|
-
font-size: 1.2em;
|
|
178
|
-
font-weight: 400;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.attachment img {
|
|
182
|
-
cursor: pointer;
|
|
183
|
-
}
|
|
184
|
-
`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
@property({ type: Object })
|
|
188
|
-
contact: Contact;
|
|
189
|
-
|
|
190
|
-
@property({ type: String })
|
|
191
|
-
uuid: string;
|
|
192
|
-
|
|
193
|
-
@property({ type: String })
|
|
194
|
-
agent: string;
|
|
195
|
-
|
|
196
|
-
@property({ type: Array })
|
|
197
|
-
eventGroups: EventGroup[] = [];
|
|
198
|
-
|
|
199
|
-
@property({ type: Boolean })
|
|
200
|
-
refreshing = false;
|
|
201
|
-
|
|
202
|
-
@property({ type: Boolean })
|
|
203
|
-
fetching = false;
|
|
204
|
-
|
|
205
|
-
@property({ type: Boolean })
|
|
206
|
-
complete = false;
|
|
207
|
-
|
|
208
|
-
@property({ type: String })
|
|
209
|
-
endpoint: string;
|
|
210
|
-
|
|
211
|
-
@property({ type: Boolean })
|
|
212
|
-
debug = false;
|
|
213
|
-
|
|
214
|
-
@property({ type: Boolean })
|
|
215
|
-
showMessageAlert = false;
|
|
216
|
-
|
|
217
|
-
@property({ attribute: false, type: Object })
|
|
218
|
-
mostRecentEvent: ContactEvent;
|
|
219
|
-
|
|
220
|
-
@property({ type: String })
|
|
221
|
-
ticket: string = null;
|
|
222
|
-
|
|
223
|
-
@property({ type: String })
|
|
224
|
-
endDate: string = null;
|
|
225
|
-
|
|
226
|
-
@property({ type: Array })
|
|
227
|
-
tickets: Ticket[] = null;
|
|
228
|
-
|
|
229
|
-
ticketEvents: { [uuid: string]: TicketEvent } = {};
|
|
230
|
-
|
|
231
|
-
nextBefore: number;
|
|
232
|
-
nextAfter: number;
|
|
233
|
-
lastHeight = 0;
|
|
234
|
-
lastRefreshAdded: number;
|
|
235
|
-
refreshTimeout: any = null;
|
|
236
|
-
empty = false;
|
|
237
|
-
|
|
238
|
-
public firstUpdated(changedProperties: Map<string, any>) {
|
|
239
|
-
super.firstUpdated(changedProperties);
|
|
240
|
-
this.handleClose = this.handleClose.bind(this);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
public updated(changedProperties: Map<string, any>) {
|
|
244
|
-
super.updated(changedProperties);
|
|
245
|
-
|
|
246
|
-
// fire an event if we get a new event
|
|
247
|
-
if (
|
|
248
|
-
changedProperties.has('mostRecentEvent') &&
|
|
249
|
-
changedProperties.get('mostRecentEvent') &&
|
|
250
|
-
this.mostRecentEvent
|
|
251
|
-
) {
|
|
252
|
-
this.fireCustomEvent(CustomEventType.Refreshed);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (changedProperties.has('endDate')) {
|
|
256
|
-
if (this.refreshTimeout && this.endDate) {
|
|
257
|
-
window.clearTimeout(this.refreshTimeout);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// if we don't have an endpoint infer one
|
|
262
|
-
if (changedProperties.has('uuid')) {
|
|
263
|
-
if (this.uuid == null) {
|
|
264
|
-
this.reset();
|
|
265
|
-
} else {
|
|
266
|
-
const endpoint = `/contact/history/${this.uuid}/?_format=json`;
|
|
267
|
-
|
|
268
|
-
if (this.endpoint !== endpoint) {
|
|
269
|
-
this.reset();
|
|
270
|
-
|
|
271
|
-
if (this.endDate) {
|
|
272
|
-
const before = new Date(this.endDate);
|
|
273
|
-
this.nextBefore = before.getTime() * 1000 + 1000;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
this.endpoint = endpoint;
|
|
277
|
-
this.refreshTickets();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (changedProperties.has('ticket')) {
|
|
283
|
-
this.endpoint = null;
|
|
284
|
-
this.requestUpdate('uuid');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
changedProperties.has('refreshing') &&
|
|
289
|
-
this.refreshing &&
|
|
290
|
-
this.endpoint &&
|
|
291
|
-
!this.endDate
|
|
292
|
-
) {
|
|
293
|
-
const after = (this.getLastEventTime() - 1) * 1000;
|
|
294
|
-
let forceOpen = false;
|
|
295
|
-
|
|
296
|
-
fetchContactHistory(false, this.endpoint, this.ticket, null, after)
|
|
297
|
-
.then((results: ContactHistoryPage) => {
|
|
298
|
-
if (results.events && results.events.length > 0) {
|
|
299
|
-
this.updateMostRecent(results.events[0]);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// keep track of any ticket events
|
|
303
|
-
results.events.forEach((event: ContactEvent) => {
|
|
304
|
-
if (event.type === Events.TICKET_OPENED) {
|
|
305
|
-
const ticketEvent = event as TicketEvent;
|
|
306
|
-
this.ticketEvents[ticketEvent.ticket.uuid] = ticketEvent;
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
const fetchedEvents = results.events.reverse();
|
|
311
|
-
|
|
312
|
-
// dedupe any events we get from the server
|
|
313
|
-
// TODO: perhaps make this a little less crazy
|
|
314
|
-
let removed = 0;
|
|
315
|
-
this.eventGroups.forEach((g) => {
|
|
316
|
-
const before = g.events.length;
|
|
317
|
-
g.events = g.events.filter(
|
|
318
|
-
(prev) =>
|
|
319
|
-
!fetchedEvents.find((fetched) => {
|
|
320
|
-
return (
|
|
321
|
-
prev.created_on == fetched.created_on &&
|
|
322
|
-
prev.type === fetched.type
|
|
323
|
-
);
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
removed += before - g.events.length;
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
this.lastRefreshAdded = fetchedEvents.length - removed;
|
|
330
|
-
|
|
331
|
-
// reflow our most recent event group in case it merges with our new groups
|
|
332
|
-
const previousGroups = [...this.eventGroups];
|
|
333
|
-
|
|
334
|
-
if (this.eventGroups.length > 0) {
|
|
335
|
-
const sliced = previousGroups.splice(
|
|
336
|
-
previousGroups.length - 1,
|
|
337
|
-
1
|
|
338
|
-
)[0];
|
|
339
|
-
|
|
340
|
-
forceOpen = sliced.open;
|
|
341
|
-
if (sliced.events.length > 0) {
|
|
342
|
-
fetchedEvents.splice(0, 0, ...sliced.events);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const grouped = this.getEventGroups(fetchedEvents);
|
|
347
|
-
if (grouped.length) {
|
|
348
|
-
if (forceOpen) {
|
|
349
|
-
grouped[grouped.length - 1].open = forceOpen;
|
|
350
|
-
}
|
|
351
|
-
this.eventGroups = [...previousGroups, ...grouped].filter(
|
|
352
|
-
(group) => group.events.length > 0
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
this.refreshing = false;
|
|
356
|
-
this.scheduleRefresh();
|
|
357
|
-
})
|
|
358
|
-
.catch(() => {
|
|
359
|
-
this.refreshing = false;
|
|
360
|
-
this.scheduleRefresh();
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (changedProperties.has('fetching') && this.fetching) {
|
|
365
|
-
if (!this.nextBefore) {
|
|
366
|
-
this.nextBefore = new Date().getTime() * 1000 - 1000;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
this.httpComplete = fetchContactHistory(
|
|
370
|
-
this.empty,
|
|
371
|
-
this.endpoint,
|
|
372
|
-
this.ticket,
|
|
373
|
-
this.nextBefore,
|
|
374
|
-
this.nextAfter
|
|
375
|
-
).then((results: ContactHistoryPage) => {
|
|
376
|
-
// see if we have a new event
|
|
377
|
-
if (results.events && results.events.length > 0) {
|
|
378
|
-
this.updateMostRecent(results.events[0]);
|
|
379
|
-
|
|
380
|
-
// keep track of any ticket events
|
|
381
|
-
results.events.forEach((event: ContactEvent) => {
|
|
382
|
-
if (event.type === Events.TICKET_OPENED) {
|
|
383
|
-
const ticketEvent = event as TicketEvent;
|
|
384
|
-
this.ticketEvents[ticketEvent.ticket.uuid] = ticketEvent;
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
let forceOpen = false;
|
|
390
|
-
const fetchedEvents = results.events ? results.events.reverse() : [];
|
|
391
|
-
|
|
392
|
-
// reflow our last event group in case it merges with our new groups
|
|
393
|
-
if (this.eventGroups.length > 0) {
|
|
394
|
-
const sliced = this.eventGroups.splice(0, 1)[0];
|
|
395
|
-
forceOpen = sliced.open;
|
|
396
|
-
fetchedEvents.push(...sliced.events);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const grouped = this.getEventGroups(fetchedEvents);
|
|
400
|
-
if (grouped.length) {
|
|
401
|
-
if (forceOpen) {
|
|
402
|
-
grouped[grouped.length - 1].open = forceOpen;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
this.eventGroups = [...grouped, ...this.eventGroups];
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (results.next_before === this.nextBefore) {
|
|
409
|
-
this.complete = true;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
this.nextBefore = results.next_before;
|
|
413
|
-
this.nextAfter = results.next_after;
|
|
414
|
-
this.fetching = false;
|
|
415
|
-
this.empty = false;
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (changedProperties.has('refreshing') && !this.refreshing) {
|
|
420
|
-
if (this.lastRefreshAdded > 0) {
|
|
421
|
-
const events = this.getEventsPane();
|
|
422
|
-
|
|
423
|
-
// if we are near the bottom, push us to the bottom to show new stuff
|
|
424
|
-
if (this.lastHeight > 0) {
|
|
425
|
-
const addedHeight = events.scrollHeight - this.lastHeight;
|
|
426
|
-
|
|
427
|
-
const distanceFromBottom =
|
|
428
|
-
events.scrollHeight -
|
|
429
|
-
events.scrollTop -
|
|
430
|
-
addedHeight -
|
|
431
|
-
events.clientHeight;
|
|
432
|
-
|
|
433
|
-
if (distanceFromBottom < 500) {
|
|
434
|
-
this.scrollToBottom();
|
|
435
|
-
} else {
|
|
436
|
-
this.showMessageAlert = true;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (this.eventGroups.length > 0) {
|
|
441
|
-
this.lastHeight = events.scrollHeight;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (
|
|
447
|
-
changedProperties.has('fetching') &&
|
|
448
|
-
!this.fetching &&
|
|
449
|
-
changedProperties.get('fetching') !== undefined
|
|
450
|
-
) {
|
|
451
|
-
const events = this.getEventsPane();
|
|
452
|
-
|
|
453
|
-
if (this.lastHeight && events.scrollHeight > this.lastHeight) {
|
|
454
|
-
const scrollTop =
|
|
455
|
-
events.scrollTop + events.scrollHeight - this.lastHeight;
|
|
456
|
-
events.scrollTop = scrollTop;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// scroll to the bottom if it's our first fetch
|
|
460
|
-
if (!this.lastHeight) {
|
|
461
|
-
this.scrollToBottom();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// don't record our scroll height until we have history
|
|
465
|
-
if (this.eventGroups.length > 0) {
|
|
466
|
-
this.lastHeight = events.scrollHeight;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (changedProperties.has('endpoint') && this.endpoint) {
|
|
471
|
-
this.fetching = true;
|
|
472
|
-
this.empty = true;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
private refreshTickets() {
|
|
477
|
-
if (this.ticket) {
|
|
478
|
-
let url = `/api/v2/tickets.json?contact=${this.uuid}`;
|
|
479
|
-
if (this.ticket) {
|
|
480
|
-
url = `${url}&ticket=${this.ticket}`;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
getAssets(url).then((tickets: Ticket[]) => {
|
|
484
|
-
this.tickets = tickets.reverse();
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
public getEventsPane() {
|
|
490
|
-
return this.getDiv('.events');
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
public scrollToBottom(smooth = false) {
|
|
494
|
-
const events = this.getEventsPane();
|
|
495
|
-
events.scrollTo({
|
|
496
|
-
top: events.scrollHeight,
|
|
497
|
-
behavior: smooth ? 'smooth' : 'auto'
|
|
498
|
-
});
|
|
499
|
-
this.showMessageAlert = false;
|
|
500
|
-
|
|
501
|
-
window.setTimeout(() => {
|
|
502
|
-
events.scrollTo({
|
|
503
|
-
top: events.scrollHeight,
|
|
504
|
-
behavior: smooth ? 'smooth' : 'auto'
|
|
505
|
-
});
|
|
506
|
-
}, 0);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
public refresh(): void {
|
|
510
|
-
this.scheduleRefresh(500);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
private getEventGroups(events: ContactEvent[]): EventGroup[] {
|
|
514
|
-
const grouped: EventGroup[] = [];
|
|
515
|
-
let eventGroup: EventGroup = undefined;
|
|
516
|
-
for (const event of events) {
|
|
517
|
-
const currentEventGroupType = getEventGroupType(event, this.ticket);
|
|
518
|
-
// see if we need a new event group
|
|
519
|
-
if (!eventGroup || eventGroup.type !== currentEventGroupType) {
|
|
520
|
-
// we have a new type, save our last group
|
|
521
|
-
if (eventGroup) {
|
|
522
|
-
grouped.push(eventGroup);
|
|
523
|
-
}
|
|
524
|
-
eventGroup = {
|
|
525
|
-
open: false,
|
|
526
|
-
events: [event],
|
|
527
|
-
type: currentEventGroupType
|
|
528
|
-
};
|
|
529
|
-
} else {
|
|
530
|
-
// our event matches the current group, stuff it in there
|
|
531
|
-
eventGroup.events.push(event);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (eventGroup && eventGroup.events.length > 0) {
|
|
536
|
-
grouped.push(eventGroup);
|
|
537
|
-
}
|
|
538
|
-
return grouped;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
private scheduleRefresh(wait = -1) {
|
|
542
|
-
if (this.endDate) {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
let refreshWait = wait;
|
|
547
|
-
|
|
548
|
-
if (wait === -1) {
|
|
549
|
-
const lastEventTime = this.getLastEventTime();
|
|
550
|
-
refreshWait = Math.max(
|
|
551
|
-
Math.min((new Date().getTime() - lastEventTime) / 2, MAX_CHAT_REFRESH),
|
|
552
|
-
MIN_CHAT_REFRESH
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// cancel any outstanding timeout
|
|
557
|
-
if (wait > -1 && this.refreshTimeout) {
|
|
558
|
-
window.clearTimeout(this.refreshTimeout);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
this.refreshTimeout = window.setTimeout(() => {
|
|
562
|
-
if (this.refreshing) {
|
|
563
|
-
this.scheduleRefresh();
|
|
564
|
-
this.refreshing = false;
|
|
565
|
-
} else {
|
|
566
|
-
this.refreshing = true;
|
|
567
|
-
}
|
|
568
|
-
}, refreshWait);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
private reset() {
|
|
572
|
-
this.endpoint = null;
|
|
573
|
-
this.tickets = null;
|
|
574
|
-
this.ticketEvents = {};
|
|
575
|
-
this.eventGroups = [];
|
|
576
|
-
this.fetching = false;
|
|
577
|
-
this.complete = false;
|
|
578
|
-
this.nextBefore = null;
|
|
579
|
-
this.nextAfter = null;
|
|
580
|
-
this.lastHeight = 0;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
private handleEventGroupShow(event: MouseEvent) {
|
|
584
|
-
const grouping = event.currentTarget as HTMLDivElement;
|
|
585
|
-
const groupIndex = parseInt(grouping.getAttribute('data-group-index'));
|
|
586
|
-
const eventGroup =
|
|
587
|
-
this.eventGroups[this.eventGroups.length - groupIndex - 1];
|
|
588
|
-
eventGroup.open = true;
|
|
589
|
-
this.requestUpdate('eventGroups');
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
private handleEventGroupHide(event: MouseEvent) {
|
|
593
|
-
event.preventDefault();
|
|
594
|
-
event.stopPropagation();
|
|
595
|
-
|
|
596
|
-
const grouping = event.currentTarget as HTMLDivElement;
|
|
597
|
-
const groupIndex = parseInt(grouping.getAttribute('data-group-index'));
|
|
598
|
-
const eventGroup =
|
|
599
|
-
this.eventGroups[this.eventGroups.length - groupIndex - 1];
|
|
600
|
-
|
|
601
|
-
eventGroup.open = false;
|
|
602
|
-
|
|
603
|
-
this.requestUpdate('eventGroups');
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
private handleScroll() {
|
|
607
|
-
const events = this.getEventsPane();
|
|
608
|
-
if (events.scrollTop <= SCROLL_THRESHOLD) {
|
|
609
|
-
if (this.eventGroups.length > 0 && !this.fetching && !this.complete) {
|
|
610
|
-
this.fetching = true;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
private updateMostRecent(newEvent: ContactEvent) {
|
|
616
|
-
if (
|
|
617
|
-
!this.mostRecentEvent ||
|
|
618
|
-
this.mostRecentEvent.type !== newEvent.type ||
|
|
619
|
-
this.mostRecentEvent.created_on !== newEvent.created_on
|
|
620
|
-
) {
|
|
621
|
-
this.mostRecentEvent = newEvent;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
private getLastEventTime(): number {
|
|
626
|
-
const mostRecentGroup = this.eventGroups[this.eventGroups.length - 1];
|
|
627
|
-
if (mostRecentGroup) {
|
|
628
|
-
const mostRecentEvent =
|
|
629
|
-
mostRecentGroup.events[mostRecentGroup.events.length - 1];
|
|
630
|
-
return new Date(mostRecentEvent.created_on).getTime();
|
|
631
|
-
}
|
|
632
|
-
return 0;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
public renderEvent(event: ContactEvent): any {
|
|
636
|
-
switch (event.type) {
|
|
637
|
-
case Events.IVR_CREATED:
|
|
638
|
-
case Events.MESSAGE_CREATED:
|
|
639
|
-
case Events.MESSAGE_RECEIVED:
|
|
640
|
-
case Events.BROADCAST_CREATED:
|
|
641
|
-
if ((event as MsgEvent).created_by) {
|
|
642
|
-
(event as MsgEvent).created_by = this.store.getUser(
|
|
643
|
-
(event as MsgEvent).created_by.email
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return renderMsgEvent(event as MsgEvent);
|
|
648
|
-
|
|
649
|
-
case Events.FLOW_ENTERED:
|
|
650
|
-
case Events.FLOW_EXITED:
|
|
651
|
-
return renderFlowEvent(event as FlowEvent);
|
|
652
|
-
|
|
653
|
-
case Events.RUN_RESULT_CHANGED:
|
|
654
|
-
return renderResultEvent(event as UpdateResultEvent);
|
|
655
|
-
|
|
656
|
-
case Events.CONTACT_FIELD_CHANGED:
|
|
657
|
-
return renderUpdateEvent(event as UpdateFieldEvent);
|
|
658
|
-
|
|
659
|
-
case Events.CONTACT_NAME_CHANGED:
|
|
660
|
-
return renderNameChanged(event as NameChangedEvent);
|
|
661
|
-
|
|
662
|
-
case Events.CONTACT_URNS_CHANGED:
|
|
663
|
-
return renderContactURNsChanged(event as URNsChangedEvent);
|
|
664
|
-
|
|
665
|
-
case Events.EMAIL_SENT:
|
|
666
|
-
return renderEmailSent(event as EmailSentEvent);
|
|
667
|
-
|
|
668
|
-
case Events.INPUT_LABELS_ADDED:
|
|
669
|
-
return renderLabelsAdded(event as LabelsAddedEvent);
|
|
670
|
-
|
|
671
|
-
case Events.TICKET_OPENED: {
|
|
672
|
-
return renderTicketAction(event as TicketEvent, 'opened', !this.ticket);
|
|
673
|
-
}
|
|
674
|
-
case Events.TICKET_NOTE_ADDED:
|
|
675
|
-
return renderNoteCreated(event as TicketEvent);
|
|
676
|
-
|
|
677
|
-
case Events.TICKET_ASSIGNED:
|
|
678
|
-
return renderTicketAssigned(event as TicketEvent);
|
|
679
|
-
case Events.TICKET_REOPENED: {
|
|
680
|
-
return renderTicketAction(
|
|
681
|
-
event as TicketEvent,
|
|
682
|
-
'reopened',
|
|
683
|
-
!this.ticket
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
case Events.TICKET_CLOSED:
|
|
687
|
-
return renderTicketAction(event as TicketEvent, 'closed', !this.ticket);
|
|
688
|
-
|
|
689
|
-
case Events.ERROR:
|
|
690
|
-
case Events.FAILURE:
|
|
691
|
-
return renderErrorMessage(event as ErrorMessageEvent);
|
|
692
|
-
case Events.CONTACT_GROUPS_CHANGED:
|
|
693
|
-
return renderContactGroupsEvent(event as ContactGroupsEvent);
|
|
694
|
-
case Events.WEBHOOK_CALLED:
|
|
695
|
-
return renderWebhookEvent(event as WebhookEvent);
|
|
696
|
-
case Events.AIRTIME_TRANSFERRED:
|
|
697
|
-
return renderAirtimeTransferredEvent(event as AirtimeTransferredEvent);
|
|
698
|
-
case Events.CALL_STARTED:
|
|
699
|
-
return renderCallStartedEvent();
|
|
700
|
-
case Events.CAMPAIGN_FIRED:
|
|
701
|
-
return renderCampaignFiredEvent(event as CampaignFiredEvent);
|
|
702
|
-
case Events.CHANNEL_EVENT:
|
|
703
|
-
return renderChannelEvent(event as ChannelEvent);
|
|
704
|
-
case Events.CONTACT_LANGUAGE_CHANGED:
|
|
705
|
-
return renderContactLanguageChangedEvent(
|
|
706
|
-
event as ContactLanguageChangedEvent
|
|
707
|
-
);
|
|
708
|
-
case Events.OPTIN_REQUESTED:
|
|
709
|
-
return renderOptinRequested(event as OptinRequestedEvent);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return html`<temba-icon
|
|
713
|
-
name="alert-triangle"
|
|
714
|
-
style="fill:var(--color-error)"
|
|
715
|
-
></temba-icon>
|
|
716
|
-
<div class="description">${event.type}</div>`;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
private handleClose(uuid: string) {
|
|
720
|
-
this.httpComplete = postJSON(`/api/v2/ticket_actions.json`, {
|
|
721
|
-
tickets: [uuid],
|
|
722
|
-
action: 'close'
|
|
723
|
-
})
|
|
724
|
-
.then(() => {
|
|
725
|
-
this.refreshTickets();
|
|
726
|
-
this.refresh();
|
|
727
|
-
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
728
|
-
ticket: { uuid, status: 'closed' }
|
|
729
|
-
});
|
|
730
|
-
})
|
|
731
|
-
.catch((response: any) => {
|
|
732
|
-
console.error(response);
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
public checkForAgentAssignmentEvent(agent: string) {
|
|
737
|
-
this.httpComplete = getAssets(
|
|
738
|
-
`/api/v2/tickets.json?uuid=${this.ticket}`
|
|
739
|
-
).then((assets: Asset[]) => {
|
|
740
|
-
if (assets.length === 1) {
|
|
741
|
-
const ticket = assets[0] as Ticket;
|
|
742
|
-
if (ticket.assignee && ticket.assignee.email === agent) {
|
|
743
|
-
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
744
|
-
ticket: { uuid: this.ticket, assigned: 'self' }
|
|
745
|
-
});
|
|
746
|
-
} else {
|
|
747
|
-
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
748
|
-
ticket: {
|
|
749
|
-
uuid: this.ticket,
|
|
750
|
-
assigned: ticket.assignee ? ticket.assignee : null
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
public getEventHandlers() {
|
|
759
|
-
return [
|
|
760
|
-
{
|
|
761
|
-
event: 'scroll',
|
|
762
|
-
method: throttle(this.handleScroll, 50)
|
|
763
|
-
}
|
|
764
|
-
];
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
/** Check if a ticket event is no longer represented in a session */
|
|
768
|
-
private isPurged(ticket: Ticket): boolean {
|
|
769
|
-
return !this.ticketEvents[ticket.uuid];
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
private handleEventClicked(event) {
|
|
773
|
-
const ele = event.target as HTMLDivElement;
|
|
774
|
-
if (ele.tagName == 'IMG') {
|
|
775
|
-
// if we have one, show in our lightbox
|
|
776
|
-
const lightbox = document.querySelector('temba-lightbox') as Lightbox;
|
|
777
|
-
if (lightbox) {
|
|
778
|
-
lightbox.showElement(ele);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
private renderEventContainer(event: ContactEvent) {
|
|
784
|
-
const renderedEvent = html`
|
|
785
|
-
<div
|
|
786
|
-
@click=${this.handleEventClicked}
|
|
787
|
-
class="${this.ticket ? 'active-ticket' : ''} event ${event.type}"
|
|
788
|
-
>
|
|
789
|
-
${this.renderEvent(event)}
|
|
790
|
-
</div>
|
|
791
|
-
${this.debug ? html`<pre>${JSON.stringify(event, null, 2)}</pre>` : null}
|
|
792
|
-
`;
|
|
793
|
-
return renderedEvent;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
public render(): TemplateResult {
|
|
797
|
-
return html`
|
|
798
|
-
${
|
|
799
|
-
this.fetching
|
|
800
|
-
? html`<temba-loading units="5" size="10"></temba-loading>`
|
|
801
|
-
: html`<div style="height:0em"></div>`
|
|
802
|
-
}
|
|
803
|
-
<div class="events" @scroll=${this.handleScroll}>
|
|
804
|
-
${this.eventGroups.map((eventGroup: EventGroup, index: number) => {
|
|
805
|
-
const grouping = getEventGroupType(eventGroup.events[0], this.ticket);
|
|
806
|
-
const groupIndex = this.eventGroups.length - index - 1;
|
|
807
|
-
|
|
808
|
-
const classes = getClasses({
|
|
809
|
-
grouping: true,
|
|
810
|
-
[grouping]: true,
|
|
811
|
-
expanded: eventGroup.open
|
|
812
|
-
});
|
|
813
|
-
return html`<div class="${classes}">
|
|
814
|
-
${grouping === 'verbose'
|
|
815
|
-
? html`<div
|
|
816
|
-
class="event-count"
|
|
817
|
-
@click=${this.handleEventGroupShow}
|
|
818
|
-
data-group-index="${groupIndex}"
|
|
819
|
-
>
|
|
820
|
-
${eventGroup.open
|
|
821
|
-
? html`<temba-icon
|
|
822
|
-
@click=${this.handleEventGroupHide}
|
|
823
|
-
data-group-index="${groupIndex}"
|
|
824
|
-
name="x"
|
|
825
|
-
clickable
|
|
826
|
-
></temba-icon>`
|
|
827
|
-
: html`${eventGroup.events.length}
|
|
828
|
-
${eventGroup.events.length === 1
|
|
829
|
-
? html`event`
|
|
830
|
-
: html`events`} `}
|
|
831
|
-
</div>`
|
|
832
|
-
: null}
|
|
833
|
-
|
|
834
|
-
<div class="items">
|
|
835
|
-
${eventGroup.events.map((event: ContactEvent) => {
|
|
836
|
-
if (
|
|
837
|
-
event.type === Events.TICKET_ASSIGNED &&
|
|
838
|
-
(event as TicketEvent).note
|
|
839
|
-
) {
|
|
840
|
-
const noteEvent = { ...event };
|
|
841
|
-
noteEvent.type = Events.TICKET_NOTE_ADDED;
|
|
842
|
-
|
|
843
|
-
return html`${this.renderEventContainer(
|
|
844
|
-
noteEvent
|
|
845
|
-
)}${this.renderEventContainer(event)}`;
|
|
846
|
-
} else {
|
|
847
|
-
return this.renderEventContainer(event);
|
|
848
|
-
}
|
|
849
|
-
})}
|
|
850
|
-
</div>
|
|
851
|
-
</div>`;
|
|
852
|
-
})}
|
|
853
|
-
</div>
|
|
854
|
-
|
|
855
|
-
${
|
|
856
|
-
this.contact && this.contact.status === 'active'
|
|
857
|
-
? html`<div class="new-messages-container">
|
|
858
|
-
<div
|
|
859
|
-
@click=${() => {
|
|
860
|
-
this.scrollToBottom(true);
|
|
861
|
-
}}
|
|
862
|
-
class="new-messages ${getClasses({
|
|
863
|
-
expanded: this.showMessageAlert
|
|
864
|
-
})}"
|
|
865
|
-
>
|
|
866
|
-
New Messages
|
|
867
|
-
</div>
|
|
868
|
-
</div>`
|
|
869
|
-
: null
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
</div>
|
|
873
|
-
`;
|
|
874
|
-
}
|
|
875
|
-
}
|