@nyaruka/temba-components 0.89.0 → 0.91.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.
- package/.devcontainer/devcontainer.json +10 -2
- package/CHANGELOG.md +19 -0
- package/dist/temba-components.js +437 -443
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/compose/Compose.js +21 -340
- package/out-tsc/src/compose/Compose.js.map +1 -1
- package/out-tsc/src/mediapicker/MediaPicker.js +312 -0
- package/out-tsc/src/mediapicker/MediaPicker.js.map +1 -0
- package/out-tsc/src/templates/TemplateEditor.js +74 -4
- package/out-tsc/src/templates/TemplateEditor.js.map +1 -1
- package/out-tsc/src/thumbnail/Thumbnail.js +31 -29
- package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -1
- package/out-tsc/src/vectoricon/VectorIcon.js +0 -1
- package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +1 -0
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-compose.test.js +11 -12
- package/out-tsc/test/temba-compose.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/french.png +0 -0
- package/src/compose/Compose.ts +23 -378
- package/src/mediapicker/MediaPicker.ts +338 -0
- package/src/templates/TemplateEditor.ts +79 -4
- package/src/thumbnail/Thumbnail.ts +43 -39
- package/src/vectoricon/VectorIcon.ts +0 -1
- package/src/vectoricon/index.ts +1 -0
- package/temba-modules.ts +2 -0
- package/test/temba-compose.test.ts +13 -53
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { TemplateResult, css, html } from 'lit';
|
|
2
|
+
import { RapidElement } from '../RapidElement';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { Attachment } from '../interfaces';
|
|
5
|
+
import { Icon } from '../vectoricon';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_MEDIA_ENDPOINT,
|
|
8
|
+
WebResponse,
|
|
9
|
+
getClasses,
|
|
10
|
+
isImageAttachment,
|
|
11
|
+
postFormData
|
|
12
|
+
} from '../utils';
|
|
13
|
+
|
|
14
|
+
const verifyAccept = (type: string, accept: string): boolean => {
|
|
15
|
+
if (accept) {
|
|
16
|
+
const allowed = accept.split(',').map((x) => x.trim());
|
|
17
|
+
return (
|
|
18
|
+
allowed.includes(type) || allowed.includes(type.split('/')[0] + '/*')
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class MediaPicker extends RapidElement {
|
|
25
|
+
static get styles() {
|
|
26
|
+
return css`
|
|
27
|
+
.drop-mask {
|
|
28
|
+
border-radius: var(--curvature-widget);
|
|
29
|
+
transition: opacity ease-in-out var(--transition-speed);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.highlight .drop-mask {
|
|
33
|
+
background: rgba(210, 243, 184, 0.8);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.drop-mask > div {
|
|
37
|
+
margin: auto;
|
|
38
|
+
border-radius: var(--curvature-widget);
|
|
39
|
+
font-weight: 400;
|
|
40
|
+
color: rgba(0, 0, 0, 0.5);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.attachments {
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.attachments-list {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: row;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
padding: 0.2em;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.attachment-item {
|
|
54
|
+
padding: 0.4em;
|
|
55
|
+
padding-top: 1em;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.attachment-item.error {
|
|
59
|
+
background: #fff;
|
|
60
|
+
color: rgba(250, 0, 0, 0.75);
|
|
61
|
+
padding: 0.2em;
|
|
62
|
+
margin: 0.3em 0.5em;
|
|
63
|
+
border-radius: var(--curvature);
|
|
64
|
+
display: block;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.remove-item {
|
|
68
|
+
--icon-color: #ccc;
|
|
69
|
+
background: #fff;
|
|
70
|
+
border-radius: 99%;
|
|
71
|
+
transition: transform 200ms linear;
|
|
72
|
+
transform: scale(0);
|
|
73
|
+
display: block;
|
|
74
|
+
margin-bottom: -24px;
|
|
75
|
+
margin-left: 10px;
|
|
76
|
+
width: 1em;
|
|
77
|
+
height: 1em;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.attachment-item:hover .remove-item {
|
|
81
|
+
transform: scale(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.remove-item:hover {
|
|
85
|
+
--icon-color: #333;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.attachment-name {
|
|
90
|
+
align-self: center;
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
padding: 2px 8px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#upload-input {
|
|
96
|
+
display: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.upload-label {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.upload-icon {
|
|
105
|
+
color: rgb(102, 102, 102);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.add-attachment {
|
|
109
|
+
padding: 1em;
|
|
110
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
111
|
+
border-radius: var(--curvature);
|
|
112
|
+
color: #aaa;
|
|
113
|
+
margin: 0.5em;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.add-attachment:hover {
|
|
117
|
+
background-color: rgba(0, 0, 0, 0.07);
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@property({ type: String, attribute: false })
|
|
124
|
+
endpoint = DEFAULT_MEDIA_ENDPOINT;
|
|
125
|
+
|
|
126
|
+
@property({ type: Boolean })
|
|
127
|
+
pendingDrop: boolean;
|
|
128
|
+
|
|
129
|
+
@property({ type: String })
|
|
130
|
+
icon = Icon.add;
|
|
131
|
+
|
|
132
|
+
@property({ type: String })
|
|
133
|
+
accept = ''; //e.g. ".xls,.xlsx"
|
|
134
|
+
|
|
135
|
+
@property({ type: Number })
|
|
136
|
+
max = 3;
|
|
137
|
+
|
|
138
|
+
@property({ type: Array })
|
|
139
|
+
attachments: Attachment[] = [];
|
|
140
|
+
|
|
141
|
+
@property({ type: Boolean, attribute: false })
|
|
142
|
+
uploading: boolean;
|
|
143
|
+
|
|
144
|
+
public updated(changes: Map<string, any>): void {
|
|
145
|
+
super.updated(changes);
|
|
146
|
+
if (changes.has('attachments')) {
|
|
147
|
+
// wait one cycle to fire change for tests
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
this.dispatchEvent(new Event('change'));
|
|
150
|
+
}, 0);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private getAcceptableFiles(evt: DragEvent): File[] {
|
|
155
|
+
const dt = evt.dataTransfer;
|
|
156
|
+
if (dt) {
|
|
157
|
+
const files = [...dt.files];
|
|
158
|
+
return files.filter((file) => verifyAccept(file.type, this.accept));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private handleDragEnter(evt: DragEvent): void {
|
|
163
|
+
this.highlight(evt);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private handleDragOver(evt: DragEvent): void {
|
|
167
|
+
this.highlight(evt);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private handleDragLeave(evt: DragEvent): void {
|
|
171
|
+
this.unhighlight(evt);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private handleDrop(evt: DragEvent): void {
|
|
175
|
+
this.unhighlight(evt);
|
|
176
|
+
if (this.canAcceptAttachments()) {
|
|
177
|
+
this.uploadFiles(this.getAcceptableFiles(evt));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public canAcceptAttachments() {
|
|
182
|
+
return this.attachments.length < this.max;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private highlight(evt: DragEvent): void {
|
|
186
|
+
evt.preventDefault();
|
|
187
|
+
evt.stopPropagation();
|
|
188
|
+
if (this.canAcceptAttachments()) {
|
|
189
|
+
this.pendingDrop = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private unhighlight(evt: DragEvent): void {
|
|
194
|
+
evt.preventDefault();
|
|
195
|
+
evt.stopPropagation();
|
|
196
|
+
this.pendingDrop = false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private addCurrentAttachment(attachmentToAdd: any) {
|
|
200
|
+
this.attachments.push(attachmentToAdd);
|
|
201
|
+
this.requestUpdate('attachments');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private removeCurrentAttachment(attachmentToRemove: any) {
|
|
205
|
+
this.attachments = this.attachments.filter(
|
|
206
|
+
(currentAttachment) => currentAttachment !== attachmentToRemove
|
|
207
|
+
);
|
|
208
|
+
this.requestUpdate('attachments');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private handleRemoveFileClicked(evt: Event): void {
|
|
212
|
+
const target = evt.target as HTMLDivElement;
|
|
213
|
+
const currentAttachmentToRemove = this.attachments.find(
|
|
214
|
+
({ url }) => url === target.id
|
|
215
|
+
);
|
|
216
|
+
if (currentAttachmentToRemove) {
|
|
217
|
+
this.removeCurrentAttachment(currentAttachmentToRemove);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private handleUploadFileInputChanged(evt: Event): void {
|
|
222
|
+
const target = evt.target as HTMLInputElement;
|
|
223
|
+
const files = target.files;
|
|
224
|
+
this.uploadFiles([...files]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public uploadFiles(files: File[]): void {
|
|
228
|
+
let filesToUpload = [];
|
|
229
|
+
|
|
230
|
+
//remove duplicate files that have already been uploaded
|
|
231
|
+
filesToUpload = files.filter((file) => {
|
|
232
|
+
// check our file type against accepts
|
|
233
|
+
if (this.accept) {
|
|
234
|
+
if (!verifyAccept(file.type, this.accept)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const index = this.attachments.findIndex(
|
|
240
|
+
(value) => value.filename === file.name && value.size === file.size
|
|
241
|
+
);
|
|
242
|
+
if (index === -1) {
|
|
243
|
+
return file;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
filesToUpload.map((fileToUpload) => {
|
|
248
|
+
this.uploadFile(fileToUpload);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private uploadFile(file: File): void {
|
|
253
|
+
this.uploading = true;
|
|
254
|
+
|
|
255
|
+
const url = this.endpoint;
|
|
256
|
+
const payload = new FormData();
|
|
257
|
+
payload.append('file', file);
|
|
258
|
+
postFormData(url, payload)
|
|
259
|
+
.then((response: WebResponse) => {
|
|
260
|
+
if (this.attachments.length < this.max) {
|
|
261
|
+
const attachment = response.json as Attachment;
|
|
262
|
+
if (attachment) {
|
|
263
|
+
this.addCurrentAttachment(attachment);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
.catch((error: WebResponse) => {
|
|
268
|
+
let uploadError = '';
|
|
269
|
+
if (error.status === 400) {
|
|
270
|
+
uploadError = error.json.file[0];
|
|
271
|
+
} else {
|
|
272
|
+
uploadError = 'Server failure';
|
|
273
|
+
}
|
|
274
|
+
console.error(uploadError);
|
|
275
|
+
})
|
|
276
|
+
.finally(() => {
|
|
277
|
+
this.uploading = false;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private renderUploader(): TemplateResult {
|
|
282
|
+
if (this.uploading) {
|
|
283
|
+
return html`<temba-loading units="3" size="12"></temba-loading>`;
|
|
284
|
+
} else {
|
|
285
|
+
return this.attachments.length < this.max
|
|
286
|
+
? html`<input
|
|
287
|
+
type="file"
|
|
288
|
+
id="upload-input"
|
|
289
|
+
?multiple=${this.max > 1}
|
|
290
|
+
accept="${this.accept}"
|
|
291
|
+
@change="${this.handleUploadFileInputChanged}"
|
|
292
|
+
/>
|
|
293
|
+
<label
|
|
294
|
+
id="upload-label"
|
|
295
|
+
class="actions-left upload-label"
|
|
296
|
+
for="upload-input"
|
|
297
|
+
>
|
|
298
|
+
<div class="add-attachment">
|
|
299
|
+
<temba-icon name="${this.icon}" size="1.5"></temba-icon>
|
|
300
|
+
</div>
|
|
301
|
+
</label>`
|
|
302
|
+
: null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public render(): TemplateResult {
|
|
307
|
+
return html` <div
|
|
308
|
+
class=${getClasses({ container: true, highlight: this.pendingDrop })}
|
|
309
|
+
@dragenter="${this.handleDragEnter}"
|
|
310
|
+
@dragover="${this.handleDragOver}"
|
|
311
|
+
@dragleave="${this.handleDragLeave}"
|
|
312
|
+
@drop="${this.handleDrop}"
|
|
313
|
+
>
|
|
314
|
+
<div class="drop-mask">
|
|
315
|
+
<div class="attachments-list">
|
|
316
|
+
${this.attachments.map((validAttachment) => {
|
|
317
|
+
return html`<div class="attachment-item">
|
|
318
|
+
<temba-icon
|
|
319
|
+
class="remove-item"
|
|
320
|
+
@click="${this.handleRemoveFileClicked}"
|
|
321
|
+
id="${validAttachment.url}"
|
|
322
|
+
name="${Icon.delete_small}"
|
|
323
|
+
></temba-icon>
|
|
324
|
+
${isImageAttachment(validAttachment)
|
|
325
|
+
? html`<temba-thumbnail
|
|
326
|
+
url="${validAttachment.url}"
|
|
327
|
+
></temba-thumbnail>`
|
|
328
|
+
: html`<temba-thumbnail
|
|
329
|
+
label="${validAttachment.content_type.split('/')[1]}"
|
|
330
|
+
></temba-thumbnail>`}
|
|
331
|
+
</div>`;
|
|
332
|
+
})}
|
|
333
|
+
${this.renderUploader()}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>`;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -2,6 +2,7 @@ import { property } from 'lit/decorators.js';
|
|
|
2
2
|
import { FormElement } from '../FormElement';
|
|
3
3
|
import { TemplateResult, html, css, PropertyValueMap, LitElement } from 'lit';
|
|
4
4
|
import { CustomEventType } from '../interfaces';
|
|
5
|
+
import { MediaPicker } from '../mediapicker/MediaPicker';
|
|
5
6
|
|
|
6
7
|
interface Component {
|
|
7
8
|
name: string;
|
|
@@ -41,6 +42,11 @@ export class TemplateEditor extends FormElement {
|
|
|
41
42
|
padding: 1em;
|
|
42
43
|
margin-top: 1em;
|
|
43
44
|
}
|
|
45
|
+
|
|
46
|
+
.content {
|
|
47
|
+
margin-bottom: 1em;
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
.picker {
|
|
45
51
|
margin-bottom: 0.5em;
|
|
46
52
|
display: block;
|
|
@@ -69,7 +75,6 @@ export class TemplateEditor extends FormElement {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
.button-wrapper {
|
|
72
|
-
margin-top: 1em;
|
|
73
78
|
background: #f9f9f9;
|
|
74
79
|
border-radius: var(--curvature);
|
|
75
80
|
padding: 0.5em;
|
|
@@ -125,6 +130,9 @@ export class TemplateEditor extends FormElement {
|
|
|
125
130
|
border: 1px solid var(--color-widget-border);
|
|
126
131
|
padding: 1em;
|
|
127
132
|
line-height: 2.2em;
|
|
133
|
+
max-height: 50vh;
|
|
134
|
+
overflow-y: auto;
|
|
135
|
+
overflow-x: hidden;
|
|
128
136
|
}
|
|
129
137
|
`;
|
|
130
138
|
}
|
|
@@ -199,10 +207,31 @@ export class TemplateEditor extends FormElement {
|
|
|
199
207
|
});
|
|
200
208
|
}
|
|
201
209
|
|
|
210
|
+
private handleAttachmentsChanged(event: CustomEvent) {
|
|
211
|
+
const media = event.target as MediaPicker;
|
|
212
|
+
const index = parseInt(media.getAttribute('index'));
|
|
213
|
+
|
|
214
|
+
if (media.attachments.length === 0) {
|
|
215
|
+
this.variables[index] = '';
|
|
216
|
+
} else {
|
|
217
|
+
const attachment = media.attachments[0];
|
|
218
|
+
if (attachment.url && attachment.content_type) {
|
|
219
|
+
this.variables[index] = `${attachment.content_type}:${attachment.url}`;
|
|
220
|
+
} else {
|
|
221
|
+
this.variables[index] = ``;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
this.fireContentChange();
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
private handleVariableChanged(event: CustomEvent) {
|
|
203
228
|
const target = event.target as HTMLInputElement;
|
|
204
229
|
const variableIndex = parseInt(target.getAttribute('index'));
|
|
205
230
|
this.variables[variableIndex] = target.value;
|
|
231
|
+
this.fireContentChange();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private fireContentChange() {
|
|
206
235
|
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
207
236
|
template: this.selectedTemplate,
|
|
208
237
|
translation: this.translation,
|
|
@@ -216,9 +245,11 @@ export class TemplateEditor extends FormElement {
|
|
|
216
245
|
`{{(${Object.keys(component.variables || []).join('|')})}}`,
|
|
217
246
|
'g'
|
|
218
247
|
);
|
|
219
|
-
|
|
248
|
+
|
|
249
|
+
let variables = null;
|
|
250
|
+
const parts = component.content?.split(variableRegex) || [];
|
|
220
251
|
if (parts.length > 0) {
|
|
221
|
-
|
|
252
|
+
variables = parts.map((part, index) => {
|
|
222
253
|
if (index % 2 === 0) {
|
|
223
254
|
return html`<span class="text">${part}</span>`;
|
|
224
255
|
}
|
|
@@ -235,8 +266,52 @@ export class TemplateEditor extends FormElement {
|
|
|
235
266
|
placeholder="{{${part}}}"
|
|
236
267
|
></temba-completion>`;
|
|
237
268
|
});
|
|
238
|
-
|
|
269
|
+
} else {
|
|
270
|
+
variables = Object.values(component.variables).map((variableIndex) => {
|
|
271
|
+
const variableSpec = this.translation.variables[variableIndex];
|
|
272
|
+
if (
|
|
273
|
+
variableSpec.type === 'image' ||
|
|
274
|
+
variableSpec.type === 'document' ||
|
|
275
|
+
variableSpec.type === 'audio' ||
|
|
276
|
+
variableSpec.type === 'video'
|
|
277
|
+
) {
|
|
278
|
+
let attachments = [];
|
|
279
|
+
if (this.variables[variableIndex]) {
|
|
280
|
+
const parts = this.variables[variableIndex].split(':');
|
|
281
|
+
attachments = [{ url: parts[1], content_type: parts[0] }];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return html`<div
|
|
285
|
+
style="
|
|
286
|
+
display: flex;
|
|
287
|
+
align-items: center;
|
|
288
|
+
border-radius: var(--curvature);
|
|
289
|
+
${attachments.length === 0
|
|
290
|
+
? `background-color:rgba(255,0,0,.07);`
|
|
291
|
+
: ``}
|
|
292
|
+
"
|
|
293
|
+
>
|
|
294
|
+
<temba-media-picker
|
|
295
|
+
accept="${variableSpec.type === 'document'
|
|
296
|
+
? 'application/pdf'
|
|
297
|
+
: variableSpec.type + '/*'}"
|
|
298
|
+
max="1"
|
|
299
|
+
index=${variableIndex}
|
|
300
|
+
icon="attachment_${variableSpec.type}"
|
|
301
|
+
attachments=${JSON.stringify(attachments)}
|
|
302
|
+
@change=${this.handleAttachmentsChanged.bind(this)}
|
|
303
|
+
></temba-media-picker>
|
|
304
|
+
<div>
|
|
305
|
+
${attachments.length == 0
|
|
306
|
+
? html`Attach ${variableSpec.type} to continue`
|
|
307
|
+
: ''}
|
|
308
|
+
</div>
|
|
309
|
+
</div>`;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
239
312
|
}
|
|
313
|
+
|
|
314
|
+
return html`<div class="content">${variables}</div> `;
|
|
240
315
|
}
|
|
241
316
|
|
|
242
317
|
public renderComponents(components: Component[]): TemplateResult {
|
|
@@ -9,7 +9,7 @@ export class Thumbnail extends RapidElement {
|
|
|
9
9
|
static get styles() {
|
|
10
10
|
return css`
|
|
11
11
|
:host {
|
|
12
|
-
display: inline
|
|
12
|
+
display: inline;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.zooming.wrapper {
|
|
@@ -54,6 +54,26 @@ export class Thumbnail extends RapidElement {
|
|
|
54
54
|
|
|
55
55
|
public render() {
|
|
56
56
|
if (this.zooming) {
|
|
57
|
+
const styles = {
|
|
58
|
+
backgroundColor: '#fafafa',
|
|
59
|
+
backgroundSize: 'contain',
|
|
60
|
+
backgroundPosition: 'center',
|
|
61
|
+
backgroundRepeat: 'no-repeat',
|
|
62
|
+
maxHeight: 'var(--thumb-size, 4em)',
|
|
63
|
+
height: 'var(--thumb-size, 4em)',
|
|
64
|
+
width: 'var(--thumb-size, 4em)',
|
|
65
|
+
borderRadius: '0',
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
fontWeight: '400',
|
|
70
|
+
color: '#bbb'
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (this.url) {
|
|
74
|
+
styles['backgroundImage'] = `url(${this.url})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
return html`
|
|
58
78
|
<div
|
|
59
79
|
class="${getClasses({ wrapper: true })}"
|
|
@@ -63,30 +83,29 @@ export class Thumbnail extends RapidElement {
|
|
|
63
83
|
boxShadow: 'var(--widget-box-shadow)'
|
|
64
84
|
})}
|
|
65
85
|
>
|
|
66
|
-
<div
|
|
67
|
-
class="thumb"
|
|
68
|
-
style=${styleMap({
|
|
69
|
-
backgroundImage: `url(${this.url})`,
|
|
70
|
-
backgroundSize: 'contain',
|
|
71
|
-
backgroundPosition: 'center',
|
|
72
|
-
backgroundRepeat: 'no-repeat',
|
|
73
|
-
maxHeight: 'var(--thumb-size, 4em)',
|
|
74
|
-
height: 'var(--thumb-size, 4em)',
|
|
75
|
-
width: 'var(--thumb-size, 4em)',
|
|
76
|
-
borderRadius: '0',
|
|
77
|
-
|
|
78
|
-
display: 'flex',
|
|
79
|
-
alignItems: 'center',
|
|
80
|
-
justifyContent: 'center',
|
|
81
|
-
fontWeight: '400',
|
|
82
|
-
color: '#bbb'
|
|
83
|
-
})}
|
|
84
|
-
>
|
|
85
|
-
${this.label}
|
|
86
|
-
</div>
|
|
86
|
+
<div class="thumb" style=${styleMap(styles)}>${this.label}</div>
|
|
87
87
|
</div>
|
|
88
88
|
`;
|
|
89
89
|
} else {
|
|
90
|
+
const styles = {
|
|
91
|
+
backgroundColor: '#fafafa',
|
|
92
|
+
backgroundSize: 'cover',
|
|
93
|
+
backgroundPosition: 'center',
|
|
94
|
+
maxHeight: 'var(--thumb-size, 4em)',
|
|
95
|
+
height: 'var(--thumb-size, 4em)',
|
|
96
|
+
width: 'var(--thumb-size, 4em)',
|
|
97
|
+
borderRadius: 'var(--curvature)',
|
|
98
|
+
display: 'flex',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
justifyContent: 'center',
|
|
101
|
+
fontWeight: '400',
|
|
102
|
+
color: '#bbb'
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (this.url) {
|
|
106
|
+
styles['backgroundImage'] = `url(${this.url})`;
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
return html`
|
|
91
110
|
<div class="${getClasses({ wrapper: true })}" style=${styleMap({
|
|
92
111
|
padding: 'var(--thumb-padding, 0.4em)',
|
|
@@ -94,24 +113,9 @@ export class Thumbnail extends RapidElement {
|
|
|
94
113
|
borderRadius: 'var(--curvature)',
|
|
95
114
|
boxShadow: 'var(--widget-box-shadow)'
|
|
96
115
|
})}">
|
|
97
|
-
|
|
98
|
-
<div class="thumb" style=${styleMap({
|
|
99
|
-
backgroundImage: `url(${this.url})`,
|
|
100
|
-
backgroundSize: 'cover',
|
|
101
|
-
backgroundPosition: 'center',
|
|
102
|
-
maxHeight: 'var(--thumb-size, 4em)',
|
|
103
|
-
height: 'var(--thumb-size, 4em)',
|
|
104
|
-
width: 'var(--thumb-size, 4em)',
|
|
105
|
-
borderRadius: 'var(--curvature)',
|
|
106
|
-
|
|
107
|
-
display: 'flex',
|
|
108
|
-
alignItems: 'center',
|
|
109
|
-
justifyContent: 'center',
|
|
110
|
-
fontWeight: '400',
|
|
111
|
-
color: '#bbb'
|
|
112
|
-
})}>
|
|
116
|
+
<div class="thumb" style=${styleMap(styles)}>
|
|
113
117
|
${this.label}
|
|
114
|
-
|
|
118
|
+
</div>
|
|
115
119
|
</div>
|
|
116
120
|
`;
|
|
117
121
|
}
|
package/src/vectoricon/index.ts
CHANGED
package/temba-modules.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { Mask } from './src/mask/Mask';
|
|
|
56
56
|
import { TembaUser } from './src/user/TembaUser';
|
|
57
57
|
import { TemplateEditor } from './src/templates/TemplateEditor';
|
|
58
58
|
import { Toast } from './src/toast/Toast';
|
|
59
|
+
import { MediaPicker } from './src/mediapicker/MediaPicker';
|
|
59
60
|
|
|
60
61
|
export function addCustomElement(name: string, comp: any) {
|
|
61
62
|
if (!window.customElements.get(name)) {
|
|
@@ -122,3 +123,4 @@ addCustomElement('temba-mask', Mask);
|
|
|
122
123
|
addCustomElement('temba-user', TembaUser);
|
|
123
124
|
addCustomElement('temba-template-editor', TemplateEditor);
|
|
124
125
|
addCustomElement('temba-toast', Toast);
|
|
126
|
+
addCustomElement('temba-media-picker', MediaPicker);
|