@nyaruka/temba-components 0.136.1 → 0.138.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/demo/components/webchat/example.html +2 -2
  3. package/dist/temba-components.js +692 -622
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/display/Chat.js +123 -44
  6. package/out-tsc/src/display/Chat.js.map +1 -1
  7. package/out-tsc/src/display/FloatingTab.js +2 -2
  8. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  9. package/out-tsc/src/events/eventRenderers.js +442 -0
  10. package/out-tsc/src/events/eventRenderers.js.map +1 -0
  11. package/out-tsc/src/flow/CanvasNode.js +45 -24
  12. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  13. package/out-tsc/src/flow/Editor.js +308 -18
  14. package/out-tsc/src/flow/Editor.js.map +1 -1
  15. package/out-tsc/src/flow/NodeEditor.js +0 -1
  16. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  17. package/out-tsc/src/flow/Plumber.js +110 -64
  18. package/out-tsc/src/flow/Plumber.js.map +1 -1
  19. package/out-tsc/src/list/ShortcutList.js +1 -1
  20. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  21. package/out-tsc/src/live/ContactChat.js +12 -321
  22. package/out-tsc/src/live/ContactChat.js.map +1 -1
  23. package/out-tsc/src/simulator/Simulator.js +439 -575
  24. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  25. package/out-tsc/src/store/AppState.js +12 -2
  26. package/out-tsc/src/store/AppState.js.map +1 -1
  27. package/out-tsc/test/temba-flow-editor-node.test.js +2 -1
  28. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  29. package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
  30. package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
  31. package/out-tsc/test/temba-flow-editor.test.js +14 -10
  32. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  33. package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
  34. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  35. package/out-tsc/test/temba-flow-plumber.test.js +6 -0
  36. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  37. package/out-tsc/test/temba-simulator.test.js +51 -32
  38. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  39. package/package.json +1 -1
  40. package/screenshots/truth/contacts/chat-failure.png +0 -0
  41. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  42. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  43. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  44. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  45. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  46. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  47. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  48. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  49. package/screenshots/truth/simulator/after-reset.png +0 -0
  50. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  51. package/screenshots/truth/simulator/context-expanded.png +0 -0
  52. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  53. package/screenshots/truth/simulator/event-info.png +0 -0
  54. package/screenshots/truth/simulator/image-attachment.png +0 -0
  55. package/screenshots/truth/simulator/open-initial.png +0 -0
  56. package/screenshots/truth/simulator/quick-replies.png +0 -0
  57. package/src/display/Chat.ts +123 -44
  58. package/src/display/FloatingTab.ts +2 -2
  59. package/src/events/eventRenderers.ts +527 -0
  60. package/src/flow/CanvasNode.ts +54 -29
  61. package/src/flow/Editor.ts +360 -19
  62. package/src/flow/NodeEditor.ts +0 -1
  63. package/src/flow/Plumber.ts +123 -69
  64. package/src/list/ShortcutList.ts +1 -1
  65. package/src/live/ContactChat.ts +17 -376
  66. package/src/simulator/Simulator.ts +498 -617
  67. package/src/store/AppState.ts +13 -2
  68. package/test/temba-flow-editor-node.test.ts +2 -1
  69. package/test/temba-flow-editor-revisions.test.ts +134 -0
  70. package/test/temba-flow-editor.test.ts +16 -10
  71. package/test/temba-flow-plumber-connections.test.ts +7 -1
  72. package/test/temba-flow-plumber.test.ts +6 -0
  73. package/test/temba-simulator.test.ts +64 -34
@@ -0,0 +1,527 @@
1
+ import { html, TemplateResult } from 'lit';
2
+ import {
3
+ AirtimeTransferredEvent,
4
+ CallEvent,
5
+ ChatStartedEvent,
6
+ ContactGroupsEvent,
7
+ ContactLanguageChangedEvent,
8
+ ContactStatusChangedEvent,
9
+ NameChangedEvent,
10
+ OptInEvent,
11
+ RunEvent,
12
+ TicketEvent,
13
+ UpdateFieldEvent,
14
+ URNsChangedEvent
15
+ } from '../events';
16
+ import { oxfordFn } from '../utils';
17
+
18
+ export enum Events {
19
+ AIRTIME_TRANSFERRED = 'airtime_transferred',
20
+ BROADCAST_CREATED = 'broadcast_created',
21
+ CALL_CREATED = 'call_created',
22
+ CALL_MISSED = 'call_missed',
23
+ CALL_RECEIVED = 'call_received',
24
+ CHAT_STARTED = 'chat_started',
25
+ CONTACT_FIELD_CHANGED = 'contact_field_changed',
26
+ CONTACT_GROUPS_CHANGED = 'contact_groups_changed',
27
+ CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
28
+ CONTACT_NAME_CHANGED = 'contact_name_changed',
29
+ CONTACT_STATUS_CHANGED = 'contact_status_changed',
30
+ CONTACT_URNS_CHANGED = 'contact_urns_changed',
31
+ EMAIL_CREATED = 'email_created',
32
+ EMAIL_SENT = 'email_sent',
33
+ ERROR = 'error',
34
+ FAILURE = 'failure',
35
+ FLOW_ENTERED = 'flow_entered',
36
+ INPUT_LABELS_ADDED = 'input_labels_added',
37
+ IVR_CREATED = 'ivr_created',
38
+ MSG_CREATED = 'msg_created',
39
+ MSG_RECEIVED = 'msg_received',
40
+ OPTIN_REQUESTED = 'optin_requested',
41
+ OPTIN_STARTED = 'optin_started',
42
+ OPTIN_STOPPED = 'optin_stopped',
43
+ RESTHOOK_CALLED = 'resthook_called',
44
+ RUN_ENDED = 'run_ended',
45
+ RUN_RESULT_CHANGED = 'run_result_changed',
46
+ RUN_STARTED = 'run_started',
47
+ SERVICE_CALLED = 'service_called',
48
+ SESSION_TRIGGERED = 'session_triggered',
49
+ TICKET_ASSIGNEE_CHANGED = 'ticket_assignee_changed',
50
+ TICKET_CLOSED = 'ticket_closed',
51
+ TICKET_NOTE_ADDED = 'ticket_note_added',
52
+ TICKET_OPENED = 'ticket_opened',
53
+ TICKET_REOPENED = 'ticket_reopened',
54
+ TICKET_TOPIC_CHANGED = 'ticket_topic_changed',
55
+ WARNING = 'warning',
56
+ WEBHOOK_CALLED = 'webhook_called'
57
+ }
58
+
59
+ const renderInfoList = (
60
+ singular: string,
61
+ plural: string,
62
+ items: any[]
63
+ ): TemplateResult => {
64
+ if (items.length === 1) {
65
+ return html`<div>${singular} <strong>${items[0].name}</strong></div>`;
66
+ } else {
67
+ const list = items.map((item) => item.name);
68
+ if (list.length === 2) {
69
+ return html`<div>
70
+ ${plural} <strong>${list[0]}</strong> and <strong>${list[1]}</strong>
71
+ </div>`;
72
+ } else {
73
+ const last = list.pop();
74
+ const middle = list.map(
75
+ (name, index) =>
76
+ html`<strong>${name}</strong>${index < list.length - 1 ? ', ' : ''}`
77
+ );
78
+ return html`<div>${plural} ${middle}, and <strong>${last}</strong></div>`;
79
+ }
80
+ }
81
+ };
82
+
83
+ export const renderRunEvent = (event: RunEvent): TemplateResult => {
84
+ let verb = 'Started';
85
+ if (event.type === Events.RUN_ENDED) {
86
+ if (event.status === 'completed') {
87
+ verb = 'Completed';
88
+ } else if (event.status === 'expired') {
89
+ verb = 'Expired from';
90
+ } else {
91
+ verb = 'Interrupted';
92
+ }
93
+ }
94
+
95
+ return html`<div>
96
+ ${verb}
97
+ <a href="/flow/editor/${event.flow.uuid}/"
98
+ ><strong>${event.flow.name}</strong></a
99
+ >
100
+ </div>`;
101
+ };
102
+
103
+ export const renderChatStartedEvent = (
104
+ event: ChatStartedEvent
105
+ ): TemplateResult => {
106
+ if (event.params) {
107
+ return html`<div>Chat referral</div>`;
108
+ } else {
109
+ return html`<div>Chat started</div>`;
110
+ }
111
+ };
112
+
113
+ export const renderUpdateEvent = (event: UpdateFieldEvent): TemplateResult => {
114
+ return event.value
115
+ ? html`<div>
116
+ Updated <strong>${event.field.name}</strong> to
117
+ <strong>${event.value.text}</strong>
118
+ </div>`
119
+ : html`<div>Cleared <strong>${event.field.name}</strong></div>`;
120
+ };
121
+
122
+ export const renderNameChanged = (event: NameChangedEvent): TemplateResult => {
123
+ return html`<div>
124
+ Updated <strong>name</strong> to <strong>${event.name}</strong>
125
+ </div>`;
126
+ };
127
+
128
+ export const renderContactURNsChanged = (
129
+ event: URNsChangedEvent
130
+ ): TemplateResult => {
131
+ return html`<div>
132
+ Updated <strong>URNs</strong> to
133
+ ${oxfordFn(
134
+ event.urns,
135
+ (urn: string) => html`<strong>${urn.split(':')[1].split('?')[0]}</strong>`
136
+ )}
137
+ </div>`;
138
+ };
139
+
140
+ export const renderTicketAction = (
141
+ event: TicketEvent,
142
+ action: string
143
+ ): TemplateResult => {
144
+ const ticketUUID = event.ticket?.uuid || event.ticket_uuid;
145
+
146
+ const actionNote = event.note
147
+ ? html`<div
148
+ style="width:85%; background: #fffac3; padding: 1em;margin-bottom: 1em;margin-top:1em; border: 1px solid #ffe97f;border-radius: var(--curvature);line-height: 1.2em; word-break: break-word;"
149
+ >
150
+ <div style="color: #8e830fff; font-size: 1em;margin-bottom:0.25em; ">
151
+ <strong>${event._user ? event._user.name : 'Someone'}</strong> added a
152
+ note
153
+ <temba-date
154
+ value=${event.created_on.toISOString()}
155
+ display="relative"
156
+ ></temba-date>
157
+ </div>
158
+ <div style="white-space: pre-wrap;">${event.note}</div>
159
+ </div>`
160
+ : null;
161
+
162
+ if (action === 'noted') {
163
+ return html`${actionNote}`;
164
+ }
165
+
166
+ const description = event._user
167
+ ? html`<div>
168
+ <strong>${event._user.name}</strong> ${action} a
169
+ <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
170
+ </div>`
171
+ : html`<div>
172
+ A
173
+ <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
174
+ was <strong>${action}</strong>
175
+ </div>`;
176
+
177
+ return html`<div style="${actionNote ? 'margin-bottom: 1em;' : ''}">
178
+ ${description}
179
+ </div>
180
+ ${actionNote}`;
181
+ };
182
+
183
+ export const renderTicketAssigneeChanged = (
184
+ event: TicketEvent
185
+ ): TemplateResult => {
186
+ if (event._user) {
187
+ if (event.assignee) {
188
+ return html`<div>
189
+ <strong>${event._user.name}</strong> assigned this ticket to
190
+ <strong>${event.assignee.name}</strong>
191
+ </div>`;
192
+ } else {
193
+ return html`<div>
194
+ <strong>${event._user.name}</strong> unassigned this ticket
195
+ </div>`;
196
+ }
197
+ } else {
198
+ if (event.assignee) {
199
+ return html`<div>
200
+ This ticket was assigned to <strong>${event.assignee.name}</strong>
201
+ </div>`;
202
+ } else {
203
+ return html`<div>This ticket was unassigned</div>`;
204
+ }
205
+ }
206
+ };
207
+
208
+ export const renderTicketOpened = (event: TicketEvent): TemplateResult => {
209
+ return html`<div>${event.ticket.topic.name} ticket was opened</div>`;
210
+ };
211
+
212
+ export const renderContactGroupsEvent = (
213
+ event: ContactGroupsEvent
214
+ ): TemplateResult => {
215
+ const groupsEvent = event as ContactGroupsEvent;
216
+ if (groupsEvent.groups_added) {
217
+ return renderInfoList(
218
+ 'Added to group',
219
+ 'Added to groups',
220
+ groupsEvent.groups_added
221
+ );
222
+ } else if (groupsEvent.groups_removed) {
223
+ return renderInfoList(
224
+ 'Removed from group',
225
+ 'Removed from groups',
226
+ groupsEvent.groups_removed
227
+ );
228
+ }
229
+ };
230
+
231
+ export const renderAirtimeTransferredEvent = (
232
+ event: AirtimeTransferredEvent
233
+ ): TemplateResult => {
234
+ if (parseFloat(event.amount) === 0) {
235
+ return html`<div>Airtime transfer failed</div>`;
236
+ }
237
+ return html`<div>
238
+ Transferred <strong>${event.amount}</strong> ${event.currency} of airtime
239
+ </div>`;
240
+ };
241
+
242
+ export const renderContactLanguageChangedEvent = (
243
+ event: ContactLanguageChangedEvent
244
+ ): TemplateResult => {
245
+ return html`<div>
246
+ Language updated to <strong>${event.language}</strong>
247
+ </div>`;
248
+ };
249
+
250
+ export const renderContactStatusChangedEvent = (
251
+ event: ContactStatusChangedEvent
252
+ ): TemplateResult => {
253
+ return html`<div>Status updated to <strong>${event.status}</strong></div>`;
254
+ };
255
+
256
+ export const renderCallEvent = (event: CallEvent): TemplateResult => {
257
+ if (event.type === Events.CALL_CREATED) {
258
+ return html`<div>Call started</div>`;
259
+ } else if (event.type === Events.CALL_MISSED) {
260
+ return html`<div>Call missed</div>`;
261
+ } else if (event.type === Events.CALL_RECEIVED) {
262
+ return html`<div>Call answered</div>`;
263
+ }
264
+ };
265
+
266
+ export const renderOptInEvent = (event: OptInEvent): TemplateResult => {
267
+ if (event.type === Events.OPTIN_REQUESTED) {
268
+ return html`<div>
269
+ Requested opt-in for <strong>${event.optin.name}</strong>
270
+ </div>`;
271
+ } else if (event.type === Events.OPTIN_STARTED) {
272
+ return html`<div>Opted in to <strong>${event.optin.name}</strong></div>`;
273
+ } else if (event.type === Events.OPTIN_STOPPED) {
274
+ return html`<div>Opted out of <strong>${event.optin.name}</strong></div>`;
275
+ }
276
+ };
277
+
278
+ export const renderDiagnosticEvent = (
279
+ event: any,
280
+ _isSimulation: boolean = false
281
+ ): TemplateResult | null => {
282
+ if (event.text) {
283
+ let icon = '⚠️';
284
+ let bgColor = '#fff3cd';
285
+ let textColor = '#856404';
286
+
287
+ if (event.type === 'error') {
288
+ icon = '❗';
289
+ bgColor = '#fee3e3';
290
+ textColor = '#c01829';
291
+ } else if (event.type === 'failure') {
292
+ icon = '💥';
293
+ bgColor = '#fee3e3';
294
+ textColor = '#c01829';
295
+ }
296
+
297
+ return html`<div
298
+ style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: ${bgColor}; color: ${textColor}; border-radius: 12px; margin: 4px 18px;"
299
+ >
300
+ <span style="font-size: 16px; line-height: 1.4;">${icon}</span>
301
+ <span style="flex: 1; line-height: 1.4;">${event.text}</span>
302
+ </div>`;
303
+ }
304
+ return null;
305
+ };
306
+
307
+ export const renderRunResultChanged = (
308
+ event: any,
309
+ isSimulation: boolean = false
310
+ ): TemplateResult | null => {
311
+ const val = String(event.value);
312
+ const MAX_LEN = isSimulation ? 30 : 100;
313
+
314
+ if (val.length > MAX_LEN) {
315
+ const displayVal = val.substring(0, MAX_LEN) + '...';
316
+ return html`<div>
317
+ Set result <strong>${event.name}</strong> to "<span
318
+ title="${val}"
319
+ style="cursor: help; border-bottom: 1px dotted #999;"
320
+ >${displayVal}</span
321
+ >"
322
+ </div>`;
323
+ }
324
+ return html`<div>Set result <strong>${event.name}</strong> to "${val}"</div>`;
325
+ };
326
+
327
+ export const renderInputLabelsAdded = (event: any): TemplateResult | null => {
328
+ const labels = event.labels || [];
329
+ if (labels.length > 0) {
330
+ const labelList = labels.map((l: any) => l.name);
331
+ if (labelList.length === 1) {
332
+ return html`<div>
333
+ Message labeled with <strong>${labelList[0]}</strong>
334
+ </div>`;
335
+ } else {
336
+ const last = labelList.pop();
337
+ return html`<div>
338
+ Message labeled with
339
+ ${labelList.map(
340
+ (name: string, index: number) =>
341
+ html`<strong>${name}</strong>${index < labelList.length - 1
342
+ ? ', '
343
+ : ''}`
344
+ )}
345
+ and <strong>${last}</strong>
346
+ </div>`;
347
+ }
348
+ }
349
+ return null;
350
+ };
351
+
352
+ export const renderEmailSent = (event: any): TemplateResult | null => {
353
+ const recipients = event.to || event.addresses || [];
354
+ const subject = event.subject;
355
+ if (recipients.length > 0) {
356
+ const recipientList = recipients.join(', ');
357
+ return html`<div>
358
+ Sent email to <strong>${recipientList}</strong> with subject "${subject}"
359
+ </div>`;
360
+ }
361
+ return null;
362
+ };
363
+
364
+ export const renderBroadcastCreated = (event: any): TemplateResult | null => {
365
+ const translations = event.translations;
366
+ const baseLanguage = event.base_language;
367
+ if (translations && translations[baseLanguage]) {
368
+ return html`<div>
369
+ Sent broadcast: "${translations[baseLanguage].text}"
370
+ </div>`;
371
+ }
372
+ return html`<div>Sent broadcast</div>`;
373
+ };
374
+
375
+ export const renderSessionTriggered = (event: any): TemplateResult | null => {
376
+ const flow = event.flow;
377
+ if (flow) {
378
+ return html`<div>
379
+ Started somebody else in <strong>${flow.name}</strong>
380
+ </div>`;
381
+ }
382
+ return null;
383
+ };
384
+
385
+ export const renderResthookCalled = (event: any): TemplateResult | null => {
386
+ return html`<div>
387
+ Triggered flow event <strong>${event.resthook}</strong>
388
+ </div>`;
389
+ };
390
+
391
+ export const renderWebhookCalled = (event: any): TemplateResult | null => {
392
+ return html`<div>Called <strong>${event.url}</strong></div>`;
393
+ };
394
+
395
+ export const renderServiceCalled = (event: any): TemplateResult | null => {
396
+ const service = event.service;
397
+ if (service === 'classifier') {
398
+ return html`<div>Called classifier</div>`;
399
+ }
400
+ return html`<div>Called <strong>${service}</strong></div>`;
401
+ };
402
+
403
+ /**
404
+ * Unified event renderer that handles both simulation and contact chat contexts.
405
+ * @param event - The event to render
406
+ * @param isSimulation - Whether this is for simulation (true) or contact chat (false)
407
+ * @returns A template result or null if the event cannot be rendered
408
+ */
409
+ export const renderEvent = (
410
+ event: any,
411
+ isSimulation: boolean
412
+ ): TemplateResult | null => {
413
+ let content: TemplateResult | null = null;
414
+
415
+ switch (event.type) {
416
+ case Events.ERROR:
417
+ case Events.FAILURE:
418
+ case Events.WARNING:
419
+ content = renderDiagnosticEvent(event, isSimulation);
420
+ break;
421
+ case Events.AIRTIME_TRANSFERRED:
422
+ content = renderAirtimeTransferredEvent(event as AirtimeTransferredEvent);
423
+ break;
424
+ case Events.CALL_CREATED:
425
+ case Events.CALL_MISSED:
426
+ case Events.CALL_RECEIVED:
427
+ content = renderCallEvent(event as CallEvent);
428
+ break;
429
+ case Events.CHAT_STARTED:
430
+ content = renderChatStartedEvent(event as ChatStartedEvent);
431
+ break;
432
+ case Events.CONTACT_FIELD_CHANGED:
433
+ content = renderUpdateEvent(event as UpdateFieldEvent);
434
+ break;
435
+ case Events.CONTACT_GROUPS_CHANGED:
436
+ content = renderContactGroupsEvent(event as ContactGroupsEvent);
437
+ break;
438
+ case Events.CONTACT_LANGUAGE_CHANGED:
439
+ content = renderContactLanguageChangedEvent(
440
+ event as ContactLanguageChangedEvent
441
+ );
442
+ break;
443
+ case Events.CONTACT_NAME_CHANGED:
444
+ content = renderNameChanged(event as NameChangedEvent);
445
+ break;
446
+ case Events.CONTACT_STATUS_CHANGED:
447
+ content = renderContactStatusChangedEvent(
448
+ event as ContactStatusChangedEvent
449
+ );
450
+ break;
451
+ case Events.CONTACT_URNS_CHANGED:
452
+ content = renderContactURNsChanged(event as URNsChangedEvent);
453
+ break;
454
+ case Events.INPUT_LABELS_ADDED:
455
+ content = renderInputLabelsAdded(event);
456
+ break;
457
+ case Events.RUN_RESULT_CHANGED:
458
+ content = renderRunResultChanged(event, isSimulation);
459
+ break;
460
+ case Events.OPTIN_REQUESTED:
461
+ case Events.OPTIN_STARTED:
462
+ case Events.OPTIN_STOPPED:
463
+ content = renderOptInEvent(event as OptInEvent);
464
+ break;
465
+ case Events.RUN_STARTED:
466
+ case Events.RUN_ENDED:
467
+ case Events.FLOW_ENTERED:
468
+ content = renderRunEvent(event as RunEvent);
469
+ break;
470
+ case Events.EMAIL_CREATED:
471
+ case Events.EMAIL_SENT:
472
+ content = renderEmailSent(event);
473
+ break;
474
+ case Events.BROADCAST_CREATED:
475
+ content = renderBroadcastCreated(event);
476
+ break;
477
+ case Events.SESSION_TRIGGERED:
478
+ content = renderSessionTriggered(event);
479
+ break;
480
+ case Events.RESTHOOK_CALLED:
481
+ content = renderResthookCalled(event);
482
+ break;
483
+ case Events.WEBHOOK_CALLED:
484
+ content = renderWebhookCalled(event);
485
+ break;
486
+ case Events.SERVICE_CALLED:
487
+ content = renderServiceCalled(event);
488
+ break;
489
+ case Events.TICKET_ASSIGNEE_CHANGED:
490
+ content = renderTicketAssigneeChanged(event as TicketEvent);
491
+ break;
492
+ case Events.TICKET_CLOSED:
493
+ content = renderTicketAction(event as TicketEvent, 'closed');
494
+ break;
495
+ case Events.TICKET_OPENED:
496
+ content = renderTicketAction(event as TicketEvent, 'opened');
497
+ break;
498
+ case Events.TICKET_NOTE_ADDED:
499
+ content = renderTicketAction(event as TicketEvent, 'noted');
500
+ break;
501
+ case Events.TICKET_REOPENED:
502
+ content = renderTicketAction(event as TicketEvent, 'reopened');
503
+ break;
504
+ case Events.TICKET_TOPIC_CHANGED:
505
+ content = html`<div>
506
+ Topic changed to <strong>${(event as TicketEvent).topic.name}</strong>
507
+ </div>`;
508
+ break;
509
+ default:
510
+ return null;
511
+ }
512
+
513
+ if (content === null) {
514
+ return null;
515
+ }
516
+
517
+ // wrap in a div with appropriate font size
518
+ const fontSize = isSimulation ? '11px' : '14px';
519
+ return html`<div style="font-size: ${fontSize}">${content}</div>`;
520
+ };
521
+
522
+ /**
523
+ * @deprecated Use renderEvent(event, true) instead
524
+ */
525
+ export const renderSimulatorEvent = (event: any): TemplateResult | null => {
526
+ return renderEvent(event, true);
527
+ };