@nyaruka/temba-components 0.41.7 → 0.42.1

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 (122) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/demo/index.html +22 -0
  3. package/dist/{973a6cc2.js → dae3a0ce.js} +398 -158
  4. package/dist/index.js +398 -158
  5. package/dist/sw.js +1 -1
  6. package/dist/sw.js.map +1 -1
  7. package/dist/templates/components-body.html +1 -1
  8. package/dist/templates/components-head.html +1 -1
  9. package/out-tsc/src/aliaseditor/AliasEditor.js +2 -1
  10. package/out-tsc/src/aliaseditor/AliasEditor.js.map +1 -1
  11. package/out-tsc/src/button/Button.js +3 -3
  12. package/out-tsc/src/button/Button.js.map +1 -1
  13. package/out-tsc/src/charcount/CharCount.js +5 -4
  14. package/out-tsc/src/charcount/CharCount.js.map +1 -1
  15. package/out-tsc/src/completion/Completion.js +5 -0
  16. package/out-tsc/src/completion/Completion.js.map +1 -1
  17. package/out-tsc/src/compose/Compose.js +532 -0
  18. package/out-tsc/src/compose/Compose.js.map +1 -0
  19. package/out-tsc/src/contacts/ContactChat.js +73 -69
  20. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  21. package/out-tsc/src/contacts/ContactHistory.js +1 -1
  22. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  23. package/out-tsc/src/contacts/events.js +1 -0
  24. package/out-tsc/src/contacts/events.js.map +1 -1
  25. package/out-tsc/src/interfaces.js +2 -0
  26. package/out-tsc/src/interfaces.js.map +1 -1
  27. package/out-tsc/src/textinput/TextInput.js +3 -1
  28. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  29. package/out-tsc/src/utils/index.js +10 -0
  30. package/out-tsc/src/utils/index.js.map +1 -1
  31. package/out-tsc/src/vectoricon/index.js +1 -0
  32. package/out-tsc/src/vectoricon/index.js.map +1 -1
  33. package/out-tsc/temba-modules.js +2 -0
  34. package/out-tsc/temba-modules.js.map +1 -1
  35. package/out-tsc/test/temba-compose.test.js +432 -0
  36. package/out-tsc/test/temba-compose.test.js.map +1 -0
  37. package/out-tsc/test/temba-contact-chat.test.js +219 -47
  38. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  39. package/out-tsc/test/temba-contact-history.test.js +7 -3
  40. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  41. package/out-tsc/test/utils.test.js +12 -8
  42. package/out-tsc/test/utils.test.js.map +1 -1
  43. package/package.json +1 -1
  44. package/screenshots/truth/compose/attachments-and-send-button.png +0 -0
  45. package/screenshots/truth/compose/attachments-no-send-button.png +0 -0
  46. package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
  47. package/screenshots/truth/compose/attachments-with-all-files.png +0 -0
  48. package/screenshots/truth/compose/attachments-with-failure-files.png +0 -0
  49. package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
  50. package/screenshots/truth/compose/attachments-with-success-files.png +0 -0
  51. package/screenshots/truth/compose/chatbox-attachments-counter-and-send-button.png +0 -0
  52. package/screenshots/truth/compose/chatbox-attachments-counter-no-send-button.png +0 -0
  53. package/screenshots/truth/compose/chatbox-attachments-no-counter-and-send-button.png +0 -0
  54. package/screenshots/truth/compose/chatbox-attachments-no-counter-no-send-button.png +0 -0
  55. package/screenshots/truth/compose/chatbox-counter-and-send-button.png +0 -0
  56. package/screenshots/truth/compose/chatbox-counter-no-send-button.png +0 -0
  57. package/screenshots/truth/compose/chatbox-no-counter-and-send-button.png +0 -0
  58. package/screenshots/truth/compose/chatbox-no-counter-no-send-button.png +0 -0
  59. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
  60. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files.png +0 -0
  61. package/screenshots/truth/compose/chatbox-no-text-attachments-with-failure-files.png +0 -0
  62. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
  63. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files.png +0 -0
  64. package/screenshots/truth/compose/chatbox-with-text-and-click-send.png +0 -0
  65. package/screenshots/truth/compose/chatbox-with-text-and-hit-enter.png +0 -0
  66. package/screenshots/truth/compose/chatbox-with-text-and-spaces.png +0 -0
  67. package/screenshots/truth/compose/chatbox-with-text-and-url.png +0 -0
  68. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-click-send.png +0 -0
  69. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
  70. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files.png +0 -0
  71. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
  72. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
  73. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files.png +0 -0
  74. package/screenshots/truth/compose/chatbox-with-text-attachments-with-failure-files.png +0 -0
  75. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
  76. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
  77. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files.png +0 -0
  78. package/screenshots/truth/compose/chatbox-with-text-no-spaces.png +0 -0
  79. package/screenshots/truth/compose/chatbox-with-text.png +0 -0
  80. package/screenshots/truth/contacts/badges.png +0 -0
  81. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  82. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  83. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  84. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  85. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  86. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  87. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  88. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  89. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  90. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  91. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  92. package/screenshots/truth/contacts/contact-active-ticket-closed-show-reopen-button.png +0 -0
  93. package/screenshots/truth/contacts/contact-active-ticket-open-show-chatbox.png +0 -0
  94. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  95. package/screenshots/truth/contacts/contact-archived-ticket-closed-hide-chatbox.png +0 -0
  96. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  97. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  98. package/screenshots/truth/contacts/history.png +0 -0
  99. package/screenshots/truth/counter/summary.png +0 -0
  100. package/screenshots/truth/counter/text.png +0 -0
  101. package/screenshots/truth/counter/unicode-variables.png +0 -0
  102. package/screenshots/truth/counter/unicode.png +0 -0
  103. package/screenshots/truth/counter/variable.png +0 -0
  104. package/src/aliaseditor/AliasEditor.ts +2 -1
  105. package/src/button/Button.ts +3 -3
  106. package/src/charcount/CharCount.ts +5 -4
  107. package/src/completion/Completion.ts +4 -0
  108. package/src/compose/Compose.ts +585 -0
  109. package/src/contacts/ContactChat.ts +75 -78
  110. package/src/contacts/ContactHistory.ts +1 -1
  111. package/src/contacts/events.ts +1 -0
  112. package/src/interfaces.ts +2 -0
  113. package/src/textinput/TextInput.ts +3 -1
  114. package/src/utils/index.ts +12 -0
  115. package/src/vectoricon/index.ts +1 -0
  116. package/static/css/temba-components.css +6 -0
  117. package/temba-modules.ts +2 -0
  118. package/test/temba-compose.test.ts +633 -0
  119. package/test/temba-contact-chat.test.ts +354 -110
  120. package/test/temba-contact-history.test.ts +10 -5
  121. package/test/utils.test.ts +24 -9
  122. package/screenshots/truth/contacts/history-expanded.png +0 -0
@@ -0,0 +1,585 @@
1
+ import { TemplateResult, html, css } from 'lit';
2
+ import { FormElement } from '../FormElement';
3
+ import { property } from 'lit/decorators.js';
4
+ import { Icon } from '../vectoricon';
5
+ import { CustomEventType } from '../interfaces';
6
+ import {
7
+ formatFileSize,
8
+ formatFileType,
9
+ getClasses,
10
+ postFormData,
11
+ truncate,
12
+ WebResponse,
13
+ } from '../utils';
14
+ import { Completion } from '../completion/Completion';
15
+
16
+ export interface Attachment {
17
+ uuid: string;
18
+ content_type: string;
19
+ type: string; //deprecated
20
+ url: string;
21
+ name: string;
22
+ size: number;
23
+ error: string;
24
+ }
25
+
26
+ export class Compose extends FormElement {
27
+ static get styles() {
28
+ return css`
29
+ .container {
30
+ display: flex;
31
+ flex-direction: column;
32
+ justify-content: space-between;
33
+ position: relative;
34
+
35
+ border-radius: var(--curvature-widget);
36
+ background: var(--color-widget-bg);
37
+ border: 1px solid var(--color-widget-border);
38
+ transition: all ease-in-out var(--transition-speed);
39
+ box-shadow: var(--widget-box-shadow);
40
+ caret-color: var(--input-caret);
41
+ padding: var(--temba-textinput-padding);
42
+ }
43
+ .container:focus-within {
44
+ border-color: var(--color-focus);
45
+ background: var(--color-widget-bg-focused);
46
+ box-shadow: var(--widget-box-shadow-focused);
47
+ }
48
+
49
+ .drop-mask {
50
+ opacity: 0;
51
+ pointer-events: none;
52
+ position: absolute;
53
+ z-index: 1;
54
+ height: 100%;
55
+ width: 100%;
56
+ bottom: 0;
57
+ right: 0;
58
+ background: rgba(210, 243, 184, 0.8);
59
+ border-radius: var(--curvature-widget);
60
+ margin: -0.5em;
61
+ padding: 0.5em;
62
+ transition: opacity ease-in-out var(--transition-speed);
63
+ display: flex;
64
+ align-items: center;
65
+ text-align: center;
66
+ }
67
+
68
+ .highlight .drop-mask {
69
+ opacity: 1;
70
+ }
71
+
72
+ .drop-mask > div {
73
+ margin: auto;
74
+ border-radius: var(--curvature-widget);
75
+ font-weight: 400;
76
+ color: rgba(0, 0, 0, 0.5);
77
+ }
78
+
79
+ .items {
80
+ }
81
+
82
+ temba-completion {
83
+ margin-left: 0.3em;
84
+ margin-top: 0.3em;
85
+ --color-widget-border: none;
86
+ --curvature-widget: none;
87
+ --widget-box-shadow: none;
88
+ --widget-box-shadow-focused: none;
89
+ --temba-textinput-padding: 0;
90
+ }
91
+
92
+ .attachments {
93
+ display: flex;
94
+ flex-direction: column;
95
+ }
96
+ .attachments-list {
97
+ display: flex;
98
+ flex-direction: row;
99
+ flex-wrap: wrap;
100
+ }
101
+ .attachment-item {
102
+ background: rgba(100, 100, 100, 0.1);
103
+ border-radius: 2px;
104
+ margin: 0.3em;
105
+ display: flex;
106
+ color: var(--color-widget-text);
107
+ }
108
+ .attachment-item.error {
109
+ background: rgba(250, 0, 0, 0.1);
110
+ color: rgba(250, 0, 0, 0.75);
111
+ }
112
+ .remove-item {
113
+ cursor: pointer !important;
114
+ padding: 3px 6px;
115
+ border-right: 1px solid rgba(100, 100, 100, 0.2);
116
+ margin-top: 1px;
117
+ background: rgba(100, 100, 100, 0.05);
118
+ }
119
+
120
+ .remove-item:hover {
121
+ background: rgba(100, 100, 100, 0.1);
122
+ }
123
+
124
+ .remove-item.error:hover {
125
+ background: rgba(250, 0, 0, 0.1);
126
+ }
127
+
128
+ .remove-item.error {
129
+ background: rgba(250, 0, 0, 0.05);
130
+ color: rgba(250, 0, 0, 0.75);
131
+ }
132
+ .attachment-name {
133
+ align-self: center;
134
+ font-size: 12px;
135
+ padding: 2px 8px;
136
+ }
137
+
138
+ .actions {
139
+ display: flex;
140
+ justify-content: space-between;
141
+ align-items: center;
142
+ margin-left: 0.25em;
143
+ padding: 0.2em;
144
+ }
145
+
146
+ #upload-files {
147
+ display: none;
148
+ }
149
+ .upload-label {
150
+ display: flex;
151
+ align-items: center;
152
+ }
153
+ .upload-icon {
154
+ color: rgb(102, 102, 102);
155
+ }
156
+ .actions-right {
157
+ display: flex;
158
+ align-items: center;
159
+ }
160
+ temba-charcount {
161
+ margin-right: 5px;
162
+ overflow: hidden;
163
+ --temba-charcount-counts-margin-top: 0px;
164
+ --temba-charcount-summary-margin-top: 0px;
165
+ --temba-charcount-summary-position: fixed;
166
+ --temba-charcount-summary-right: 105px;
167
+ --temba-charcount-summary-bottom: 105px;
168
+ }
169
+ temba-button {
170
+ --button-y: 1px;
171
+ --button-x: 12px;
172
+ }
173
+ .send-error {
174
+ color: rgba(250, 0, 0, 0.75);
175
+ font-size: var(--help-text-size);
176
+ }
177
+ `;
178
+ }
179
+
180
+ @property({ type: Boolean })
181
+ chatbox: boolean;
182
+
183
+ @property({ type: Boolean })
184
+ attachments: boolean;
185
+
186
+ @property({ type: Boolean })
187
+ counter: boolean;
188
+
189
+ @property({ type: Boolean })
190
+ pendingDrop: boolean;
191
+
192
+ @property({ type: Boolean })
193
+ button: boolean;
194
+
195
+ @property({ type: String, attribute: false })
196
+ currentChat = '';
197
+
198
+ @property({ type: String })
199
+ accept = ''; //e.g. ".xls,.xlsx"
200
+
201
+ @property({ type: String, attribute: false })
202
+ endpoint = '/msgmedia/upload/';
203
+
204
+ @property({ type: Boolean, attribute: false })
205
+ uploading: boolean;
206
+
207
+ // values = valid and uploaded attachments
208
+ // errorValues = invalid and not-uploaded attachments
209
+ @property({ type: Array, attribute: false })
210
+ errorValues: Attachment[] = [];
211
+
212
+ @property({ type: String })
213
+ buttonName = 'Send';
214
+
215
+ @property({ type: Boolean, attribute: false })
216
+ buttonDisabled = true;
217
+
218
+ @property({ type: String, attribute: false })
219
+ buttonError = '';
220
+
221
+ public constructor() {
222
+ super();
223
+ }
224
+
225
+ public updated(changes: Map<string, any>): void {
226
+ super.updated(changes);
227
+
228
+ if (
229
+ changes.has('currentChat') ||
230
+ changes.has('values') ||
231
+ changes.has('buttonError')
232
+ ) {
233
+ this.toggleButton();
234
+ }
235
+ }
236
+
237
+ firstUpdated(): void {
238
+ const completion = this.shadowRoot.querySelector(
239
+ 'temba-completion'
240
+ ) as Completion;
241
+ if (completion) {
242
+ window.setTimeout(() => {
243
+ completion.click();
244
+ }, 0);
245
+ }
246
+ }
247
+
248
+ public reset(): void {
249
+ this.currentChat = '';
250
+ this.values = [];
251
+ this.errorValues = [];
252
+ this.buttonError = '';
253
+ }
254
+
255
+ private handleChatboxChange(evt: Event) {
256
+ const completion = evt.target as Completion;
257
+ const textInput = completion.textInputElement;
258
+ this.currentChat = textInput.value;
259
+ this.preventDefaults(evt);
260
+ }
261
+
262
+ private handleDragEnter(evt: DragEvent): void {
263
+ this.highlight(evt);
264
+ }
265
+
266
+ private handleDragOver(evt: DragEvent): void {
267
+ this.highlight(evt);
268
+ }
269
+
270
+ private handleDragLeave(evt: DragEvent): void {
271
+ this.unhighlight(evt);
272
+ }
273
+
274
+ private handleDrop(evt: DragEvent): void {
275
+ this.unhighlight(evt);
276
+
277
+ const dt = evt.dataTransfer;
278
+ if (dt) {
279
+ const files = dt.files;
280
+ this.uploadFiles(files);
281
+ }
282
+ }
283
+
284
+ private preventDefaults(evt: Event): void {
285
+ evt.preventDefault();
286
+ evt.stopPropagation();
287
+ }
288
+
289
+ private highlight(evt: DragEvent): void {
290
+ this.pendingDrop = true;
291
+ this.preventDefaults(evt);
292
+ }
293
+
294
+ private unhighlight(evt: DragEvent): void {
295
+ this.pendingDrop = false;
296
+ this.preventDefaults(evt);
297
+ }
298
+
299
+ private handleAddAttachments(): void {
300
+ this.dispatchEvent(new Event('change'));
301
+ }
302
+
303
+ private handleUploadFileChanged(evt: Event): void {
304
+ const target = evt.target as HTMLInputElement;
305
+ const files = target.files;
306
+ this.uploadFiles(files);
307
+ }
308
+
309
+ public uploadFiles(files: FileList): void {
310
+ let filesToUpload = [];
311
+ if (this.values && this.values.length > 0) {
312
+ //remove duplicate files that have already been uploaded
313
+ filesToUpload = [...files].filter(file => {
314
+ const index = this.values.findIndex(
315
+ value => value.name === file.name && value.size === file.size
316
+ );
317
+ if (index === -1) {
318
+ return file;
319
+ }
320
+ });
321
+ } else {
322
+ filesToUpload = [...files];
323
+ }
324
+ filesToUpload.map(fileToUpload => {
325
+ this.uploadFile(fileToUpload);
326
+ });
327
+ }
328
+
329
+ private uploadFile(file: File): void {
330
+ this.uploading = true;
331
+
332
+ const url = this.endpoint;
333
+ const payload = new FormData();
334
+ payload.append('file', file);
335
+ postFormData(url, payload)
336
+ .then((response: WebResponse) => {
337
+ if (response.json.error) {
338
+ this.addErrorValue(file, response.json.error);
339
+ } else {
340
+ const attachment = response.json as Attachment;
341
+ if (attachment) {
342
+ this.addValue(attachment);
343
+ this.fireCustomEvent(CustomEventType.AttachmentAdded, attachment);
344
+ }
345
+ }
346
+ })
347
+ .catch((error: string) => {
348
+ console.log(error);
349
+ this.addErrorValue(file, error);
350
+ })
351
+ .finally(() => {
352
+ this.uploading = false;
353
+ });
354
+ }
355
+
356
+ private addErrorValue(file: File, error: string) {
357
+ const errorValue = {
358
+ uuid: Math.random().toString(36).slice(2, 6),
359
+ content_type: file.type,
360
+ type: file.type,
361
+ name: file.name,
362
+ url: file.name,
363
+ size: file.size,
364
+ error: error,
365
+ };
366
+ this.errorValues.push(errorValue);
367
+ this.requestUpdate('errorValues');
368
+ }
369
+ public removeErrorValue(valueToRemove: any) {
370
+ this.errorValues = this.errorValues.filter(
371
+ (value: any) => value !== valueToRemove
372
+ );
373
+ this.requestUpdate('errorValues');
374
+ }
375
+
376
+ private handleRemoveAttachment(evt: Event): void {
377
+ const target = evt.target as HTMLDivElement;
378
+
379
+ const attachment = this.values.find(({ uuid }) => uuid === target.id);
380
+ if (attachment) {
381
+ this.removeValue(attachment);
382
+ this.fireCustomEvent(CustomEventType.AttachmentRemoved, attachment);
383
+ }
384
+ const errorAttachment = this.errorValues.find(
385
+ ({ uuid }) => uuid === target.id
386
+ );
387
+ if (errorAttachment) {
388
+ this.removeErrorValue(errorAttachment);
389
+ this.fireCustomEvent(CustomEventType.AttachmentRemoved, attachment);
390
+ }
391
+ }
392
+
393
+ public toggleButton() {
394
+ if (this.button) {
395
+ if (this.buttonError && this.buttonError.length > 0) {
396
+ this.buttonDisabled = true;
397
+ } else {
398
+ const chatboxEmpty = this.currentChat.trim().length === 0;
399
+ const attachmentsEmpty = this.values.length === 0;
400
+ if (this.chatbox && this.attachments) {
401
+ this.buttonDisabled = chatboxEmpty && attachmentsEmpty;
402
+ } else if (this.chatbox) {
403
+ this.buttonDisabled = chatboxEmpty;
404
+ } else if (this.attachments) {
405
+ this.buttonDisabled = attachmentsEmpty;
406
+ } else {
407
+ this.buttonDisabled = true;
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ private handleSendClick() {
414
+ this.handleSend();
415
+ }
416
+
417
+ private handleSendEnter(evt: KeyboardEvent) {
418
+ if (evt.key === 'Enter' && !evt.shiftKey) {
419
+ const chat = evt.target as Completion;
420
+ if (!chat.hasVisibleOptions()) {
421
+ this.handleSend();
422
+ }
423
+ this.preventDefaults(evt);
424
+ }
425
+ }
426
+
427
+ private handleSend() {
428
+ if (!this.buttonDisabled) {
429
+ this.buttonDisabled = true;
430
+ const name = this.buttonName;
431
+ this.fireCustomEvent(CustomEventType.ButtonClicked, { name });
432
+ }
433
+ }
434
+
435
+ private handleSendBlur() {
436
+ if (this.buttonError.length > 0) {
437
+ this.buttonError = '';
438
+ }
439
+ }
440
+
441
+ public render(): TemplateResult {
442
+ return html`
443
+ <div
444
+ class=${getClasses({ container: true, highlight: this.pendingDrop })}
445
+ @dragenter="${this.handleDragEnter}"
446
+ @dragover="${this.handleDragOver}"
447
+ @dragleave="${this.handleDragLeave}"
448
+ @drop="${this.handleDrop}"
449
+ >
450
+ <div class="drop-mask"><div>Upload Attachment</div></div>
451
+
452
+ ${this.chatbox
453
+ ? html`<div class="items chatbox">${this.getChatbox()}</div>`
454
+ : null}
455
+ ${this.attachments
456
+ ? html`<div class="items attachments">${this.getAttachments()}</div>`
457
+ : null}
458
+ <div class="items actions">${this.getActions()}</div>
459
+ </div>
460
+ `;
461
+ }
462
+
463
+ private getChatbox(): TemplateResult {
464
+ return html` <temba-completion
465
+ value=${this.currentChat}
466
+ gsm
467
+ textarea
468
+ autogrow
469
+ @change=${this.handleChatboxChange}
470
+ @keydown=${this.handleSendEnter}
471
+ placeholder="Write something here"
472
+ @blur=${this.handleSendBlur}
473
+ >
474
+ </temba-completion>`;
475
+ }
476
+
477
+ private getAttachments(): TemplateResult {
478
+ return html`
479
+ ${(this.values && this.values.length > 0) ||
480
+ (this.errorValues && this.errorValues.length > 0)
481
+ ? html` <div class="attachments-list">
482
+ ${this.values.map(attachment => {
483
+ return html` <div class="attachment-item">
484
+ <div
485
+ class="remove-item"
486
+ @click="${this.handleRemoveAttachment}"
487
+ >
488
+ <temba-icon
489
+ id="${attachment.uuid}"
490
+ name="${Icon.delete_small}"
491
+ ></temba-icon>
492
+ </div>
493
+ <div class="attachment-name">
494
+ <span
495
+ title="${attachment.name} (${formatFileSize(
496
+ attachment.size,
497
+ 2
498
+ )}) ${attachment.type}"
499
+ >${truncate(attachment.name, 25)}
500
+ (${formatFileSize(attachment.size, 0)})
501
+ ${formatFileType(attachment.type)}</span
502
+ >
503
+ </div>
504
+ </div>`;
505
+ })}
506
+ ${this.errorValues.map(errorAttachment => {
507
+ return html` <div class="attachment-item error">
508
+ <div
509
+ class="remove-item error"
510
+ @click="${this.handleRemoveAttachment}"
511
+ >
512
+ <temba-icon
513
+ id="${errorAttachment.uuid}"
514
+ name="${Icon.delete_small}"
515
+ ></temba-icon>
516
+ </div>
517
+ <div class="attachment-name">
518
+ <span
519
+ title="${errorAttachment.name} (${formatFileSize(
520
+ 0,
521
+ 0
522
+ )}) - Attachment failed - ${errorAttachment.error}"
523
+ >${truncate(errorAttachment.name, 25)}
524
+ (${formatFileSize(0, 0)}) - Attachment failed</span
525
+ >
526
+ </div>
527
+ </div>`;
528
+ })}
529
+ </div>`
530
+ : null}
531
+ `;
532
+ }
533
+
534
+ private getActions(): TemplateResult {
535
+ return html`
536
+ <div class="actions-left">
537
+ ${this.attachments ? this.getUploader() : null}
538
+ </div>
539
+ <div class="actions-center"></div>
540
+ <div class="actions-right">
541
+ ${this.buttonError
542
+ ? html`<div class="send-error">${this.buttonError}</div>`
543
+ : null}
544
+ ${this.counter ? this.getCounter() : null}
545
+ ${this.button ? this.getButton() : null}
546
+ </div>
547
+ `;
548
+ }
549
+
550
+ private getUploader(): TemplateResult {
551
+ if (this.uploading) {
552
+ return html`<temba-loading units="3" size="12"></temba-loading>`;
553
+ } else {
554
+ return html` <input
555
+ type="file"
556
+ id="upload-files"
557
+ multiple
558
+ accept="${this.accept}"
559
+ @change="${this.handleUploadFileChanged}"
560
+ />
561
+ <label class="actions-left upload-label" for="upload-files">
562
+ <temba-icon
563
+ class="upload-icon"
564
+ name="${Icon.attachment}"
565
+ @click="${this.handleAddAttachments}"
566
+ clickable
567
+ ></temba-icon>
568
+ </label>`;
569
+ }
570
+ }
571
+
572
+ private getCounter(): TemplateResult {
573
+ return html`<temba-charcount text="${this.currentChat}"></temba-charcount>`;
574
+ }
575
+
576
+ private getButton(): TemplateResult {
577
+ return html` <temba-button
578
+ id="send-button"
579
+ name=${this.buttonName}
580
+ @click=${this.handleSendClick}
581
+ ?disabled=${this.buttonDisabled}
582
+ @blur=${this.handleSendBlur}
583
+ ></temba-button>`;
584
+ }
585
+ }