@nyaruka/temba-components 0.89.0 → 0.90.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/.devcontainer/devcontainer.json +10 -2
- package/CHANGELOG.md +9 -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 +75 -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 +81 -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,12 +2,14 @@ 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;
|
|
8
9
|
type: string;
|
|
9
10
|
content: string;
|
|
10
11
|
variables: { [key: string]: number };
|
|
12
|
+
params: [{ type: string }];
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
interface Translation {
|
|
@@ -41,6 +43,11 @@ export class TemplateEditor extends FormElement {
|
|
|
41
43
|
padding: 1em;
|
|
42
44
|
margin-top: 1em;
|
|
43
45
|
}
|
|
46
|
+
|
|
47
|
+
.content {
|
|
48
|
+
margin-bottom: 1em;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
.picker {
|
|
45
52
|
margin-bottom: 0.5em;
|
|
46
53
|
display: block;
|
|
@@ -69,7 +76,6 @@ export class TemplateEditor extends FormElement {
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
.button-wrapper {
|
|
72
|
-
margin-top: 1em;
|
|
73
79
|
background: #f9f9f9;
|
|
74
80
|
border-radius: var(--curvature);
|
|
75
81
|
padding: 0.5em;
|
|
@@ -125,6 +131,9 @@ export class TemplateEditor extends FormElement {
|
|
|
125
131
|
border: 1px solid var(--color-widget-border);
|
|
126
132
|
padding: 1em;
|
|
127
133
|
line-height: 2.2em;
|
|
134
|
+
max-height: 50vh;
|
|
135
|
+
overflow-y: auto;
|
|
136
|
+
overflow-x: hidden;
|
|
128
137
|
}
|
|
129
138
|
`;
|
|
130
139
|
}
|
|
@@ -199,10 +208,31 @@ export class TemplateEditor extends FormElement {
|
|
|
199
208
|
});
|
|
200
209
|
}
|
|
201
210
|
|
|
211
|
+
private handleAttachmentsChanged(event: CustomEvent) {
|
|
212
|
+
const media = event.target as MediaPicker;
|
|
213
|
+
const index = parseInt(media.getAttribute('index'));
|
|
214
|
+
|
|
215
|
+
if (media.attachments.length === 0) {
|
|
216
|
+
this.variables[index] = '';
|
|
217
|
+
} else {
|
|
218
|
+
const attachment = media.attachments[0];
|
|
219
|
+
if (attachment.url && attachment.content_type) {
|
|
220
|
+
this.variables[index] = `${attachment.content_type}:${attachment.url}`;
|
|
221
|
+
} else {
|
|
222
|
+
this.variables[index] = ``;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
this.fireContentChange();
|
|
226
|
+
}
|
|
227
|
+
|
|
202
228
|
private handleVariableChanged(event: CustomEvent) {
|
|
203
229
|
const target = event.target as HTMLInputElement;
|
|
204
230
|
const variableIndex = parseInt(target.getAttribute('index'));
|
|
205
231
|
this.variables[variableIndex] = target.value;
|
|
232
|
+
this.fireContentChange();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fireContentChange() {
|
|
206
236
|
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
207
237
|
template: this.selectedTemplate,
|
|
208
238
|
translation: this.translation,
|
|
@@ -216,9 +246,11 @@ export class TemplateEditor extends FormElement {
|
|
|
216
246
|
`{{(${Object.keys(component.variables || []).join('|')})}}`,
|
|
217
247
|
'g'
|
|
218
248
|
);
|
|
219
|
-
|
|
249
|
+
|
|
250
|
+
let variables = null;
|
|
251
|
+
const parts = component.content?.split(variableRegex) || [];
|
|
220
252
|
if (parts.length > 0) {
|
|
221
|
-
|
|
253
|
+
variables = parts.map((part, index) => {
|
|
222
254
|
if (index % 2 === 0) {
|
|
223
255
|
return html`<span class="text">${part}</span>`;
|
|
224
256
|
}
|
|
@@ -235,8 +267,53 @@ export class TemplateEditor extends FormElement {
|
|
|
235
267
|
placeholder="{{${part}}}"
|
|
236
268
|
></temba-completion>`;
|
|
237
269
|
});
|
|
238
|
-
|
|
270
|
+
} else {
|
|
271
|
+
// no content, let's do params intead
|
|
272
|
+
variables = component.params.map((param) => {
|
|
273
|
+
if (
|
|
274
|
+
param.type === 'image' ||
|
|
275
|
+
param.type === 'document' ||
|
|
276
|
+
param.type === 'audio' ||
|
|
277
|
+
param.type === 'video'
|
|
278
|
+
) {
|
|
279
|
+
const index = Object.values(component.variables)[0];
|
|
280
|
+
let attachments = [];
|
|
281
|
+
if (this.variables[index]) {
|
|
282
|
+
const parts = this.variables[index].split(':');
|
|
283
|
+
attachments = [{ url: parts[1], content_type: parts[0] }];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return html`<div
|
|
287
|
+
style="
|
|
288
|
+
display: flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
border-radius: var(--curvature);
|
|
291
|
+
${attachments.length === 0
|
|
292
|
+
? `background-color:rgba(255,0,0,.07);`
|
|
293
|
+
: ``}
|
|
294
|
+
"
|
|
295
|
+
>
|
|
296
|
+
<temba-media-picker
|
|
297
|
+
accept="${param.type === 'document'
|
|
298
|
+
? 'application/pdf'
|
|
299
|
+
: param.type + '/*'}"
|
|
300
|
+
max="1"
|
|
301
|
+
index=${index}
|
|
302
|
+
icon="attachment_${param.type}"
|
|
303
|
+
attachments=${JSON.stringify(attachments)}
|
|
304
|
+
@change=${this.handleAttachmentsChanged.bind(this)}
|
|
305
|
+
></temba-media-picker>
|
|
306
|
+
<div>
|
|
307
|
+
${attachments.length == 0
|
|
308
|
+
? html`Attach ${param.type} to continue`
|
|
309
|
+
: ''}
|
|
310
|
+
</div>
|
|
311
|
+
</div>`;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
239
314
|
}
|
|
315
|
+
|
|
316
|
+
return html`<div class="content">${variables}</div> `;
|
|
240
317
|
}
|
|
241
318
|
|
|
242
319
|
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);
|