@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,312 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { css, html } from 'lit';
|
|
3
|
+
import { RapidElement } from '../RapidElement';
|
|
4
|
+
import { property } from 'lit/decorators.js';
|
|
5
|
+
import { Icon } from '../vectoricon';
|
|
6
|
+
import { DEFAULT_MEDIA_ENDPOINT, getClasses, isImageAttachment, postFormData } from '../utils';
|
|
7
|
+
const verifyAccept = (type, accept) => {
|
|
8
|
+
if (accept) {
|
|
9
|
+
const allowed = accept.split(',').map((x) => x.trim());
|
|
10
|
+
return (allowed.includes(type) || allowed.includes(type.split('/')[0] + '/*'));
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
export class MediaPicker extends RapidElement {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.endpoint = DEFAULT_MEDIA_ENDPOINT;
|
|
18
|
+
this.icon = Icon.add;
|
|
19
|
+
this.accept = ''; //e.g. ".xls,.xlsx"
|
|
20
|
+
this.max = 3;
|
|
21
|
+
this.attachments = [];
|
|
22
|
+
}
|
|
23
|
+
static get styles() {
|
|
24
|
+
return css `
|
|
25
|
+
.drop-mask {
|
|
26
|
+
border-radius: var(--curvature-widget);
|
|
27
|
+
transition: opacity ease-in-out var(--transition-speed);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.highlight .drop-mask {
|
|
31
|
+
background: rgba(210, 243, 184, 0.8);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.drop-mask > div {
|
|
35
|
+
margin: auto;
|
|
36
|
+
border-radius: var(--curvature-widget);
|
|
37
|
+
font-weight: 400;
|
|
38
|
+
color: rgba(0, 0, 0, 0.5);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.attachments {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.attachments-list {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: row;
|
|
47
|
+
flex-wrap: wrap;
|
|
48
|
+
padding: 0.2em;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.attachment-item {
|
|
52
|
+
padding: 0.4em;
|
|
53
|
+
padding-top: 1em;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.attachment-item.error {
|
|
57
|
+
background: #fff;
|
|
58
|
+
color: rgba(250, 0, 0, 0.75);
|
|
59
|
+
padding: 0.2em;
|
|
60
|
+
margin: 0.3em 0.5em;
|
|
61
|
+
border-radius: var(--curvature);
|
|
62
|
+
display: block;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.remove-item {
|
|
66
|
+
--icon-color: #ccc;
|
|
67
|
+
background: #fff;
|
|
68
|
+
border-radius: 99%;
|
|
69
|
+
transition: transform 200ms linear;
|
|
70
|
+
transform: scale(0);
|
|
71
|
+
display: block;
|
|
72
|
+
margin-bottom: -24px;
|
|
73
|
+
margin-left: 10px;
|
|
74
|
+
width: 1em;
|
|
75
|
+
height: 1em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.attachment-item:hover .remove-item {
|
|
79
|
+
transform: scale(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.remove-item:hover {
|
|
83
|
+
--icon-color: #333;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.attachment-name {
|
|
88
|
+
align-self: center;
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
padding: 2px 8px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#upload-input {
|
|
94
|
+
display: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.upload-label {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.upload-icon {
|
|
103
|
+
color: rgb(102, 102, 102);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.add-attachment {
|
|
107
|
+
padding: 1em;
|
|
108
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
109
|
+
border-radius: var(--curvature);
|
|
110
|
+
color: #aaa;
|
|
111
|
+
margin: 0.5em;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.add-attachment:hover {
|
|
115
|
+
background-color: rgba(0, 0, 0, 0.07);
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
updated(changes) {
|
|
121
|
+
super.updated(changes);
|
|
122
|
+
if (changes.has('attachments')) {
|
|
123
|
+
// wait one cycle to fire change for tests
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
this.dispatchEvent(new Event('change'));
|
|
126
|
+
}, 0);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
getAcceptableFiles(evt) {
|
|
130
|
+
const dt = evt.dataTransfer;
|
|
131
|
+
if (dt) {
|
|
132
|
+
const files = [...dt.files];
|
|
133
|
+
return files.filter((file) => verifyAccept(file.type, this.accept));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
handleDragEnter(evt) {
|
|
137
|
+
this.highlight(evt);
|
|
138
|
+
}
|
|
139
|
+
handleDragOver(evt) {
|
|
140
|
+
this.highlight(evt);
|
|
141
|
+
}
|
|
142
|
+
handleDragLeave(evt) {
|
|
143
|
+
this.unhighlight(evt);
|
|
144
|
+
}
|
|
145
|
+
handleDrop(evt) {
|
|
146
|
+
this.unhighlight(evt);
|
|
147
|
+
if (this.canAcceptAttachments()) {
|
|
148
|
+
this.uploadFiles(this.getAcceptableFiles(evt));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
canAcceptAttachments() {
|
|
152
|
+
return this.attachments.length < this.max;
|
|
153
|
+
}
|
|
154
|
+
highlight(evt) {
|
|
155
|
+
evt.preventDefault();
|
|
156
|
+
evt.stopPropagation();
|
|
157
|
+
if (this.canAcceptAttachments()) {
|
|
158
|
+
this.pendingDrop = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
unhighlight(evt) {
|
|
162
|
+
evt.preventDefault();
|
|
163
|
+
evt.stopPropagation();
|
|
164
|
+
this.pendingDrop = false;
|
|
165
|
+
}
|
|
166
|
+
addCurrentAttachment(attachmentToAdd) {
|
|
167
|
+
this.attachments.push(attachmentToAdd);
|
|
168
|
+
this.requestUpdate('attachments');
|
|
169
|
+
}
|
|
170
|
+
removeCurrentAttachment(attachmentToRemove) {
|
|
171
|
+
this.attachments = this.attachments.filter((currentAttachment) => currentAttachment !== attachmentToRemove);
|
|
172
|
+
this.requestUpdate('attachments');
|
|
173
|
+
}
|
|
174
|
+
handleRemoveFileClicked(evt) {
|
|
175
|
+
const target = evt.target;
|
|
176
|
+
const currentAttachmentToRemove = this.attachments.find(({ url }) => url === target.id);
|
|
177
|
+
if (currentAttachmentToRemove) {
|
|
178
|
+
this.removeCurrentAttachment(currentAttachmentToRemove);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
handleUploadFileInputChanged(evt) {
|
|
182
|
+
const target = evt.target;
|
|
183
|
+
const files = target.files;
|
|
184
|
+
this.uploadFiles([...files]);
|
|
185
|
+
}
|
|
186
|
+
uploadFiles(files) {
|
|
187
|
+
let filesToUpload = [];
|
|
188
|
+
//remove duplicate files that have already been uploaded
|
|
189
|
+
filesToUpload = files.filter((file) => {
|
|
190
|
+
// check our file type against accepts
|
|
191
|
+
if (this.accept) {
|
|
192
|
+
if (!verifyAccept(file.type, this.accept)) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const index = this.attachments.findIndex((value) => value.filename === file.name && value.size === file.size);
|
|
197
|
+
if (index === -1) {
|
|
198
|
+
return file;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
filesToUpload.map((fileToUpload) => {
|
|
202
|
+
this.uploadFile(fileToUpload);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
uploadFile(file) {
|
|
206
|
+
this.uploading = true;
|
|
207
|
+
const url = this.endpoint;
|
|
208
|
+
const payload = new FormData();
|
|
209
|
+
payload.append('file', file);
|
|
210
|
+
postFormData(url, payload)
|
|
211
|
+
.then((response) => {
|
|
212
|
+
if (this.attachments.length < this.max) {
|
|
213
|
+
const attachment = response.json;
|
|
214
|
+
if (attachment) {
|
|
215
|
+
this.addCurrentAttachment(attachment);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
.catch((error) => {
|
|
220
|
+
let uploadError = '';
|
|
221
|
+
if (error.status === 400) {
|
|
222
|
+
uploadError = error.json.file[0];
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
uploadError = 'Server failure';
|
|
226
|
+
}
|
|
227
|
+
console.error(uploadError);
|
|
228
|
+
})
|
|
229
|
+
.finally(() => {
|
|
230
|
+
this.uploading = false;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
renderUploader() {
|
|
234
|
+
if (this.uploading) {
|
|
235
|
+
return html `<temba-loading units="3" size="12"></temba-loading>`;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
return this.attachments.length < this.max
|
|
239
|
+
? html `<input
|
|
240
|
+
type="file"
|
|
241
|
+
id="upload-input"
|
|
242
|
+
?multiple=${this.max > 1}
|
|
243
|
+
accept="${this.accept}"
|
|
244
|
+
@change="${this.handleUploadFileInputChanged}"
|
|
245
|
+
/>
|
|
246
|
+
<label
|
|
247
|
+
id="upload-label"
|
|
248
|
+
class="actions-left upload-label"
|
|
249
|
+
for="upload-input"
|
|
250
|
+
>
|
|
251
|
+
<div class="add-attachment">
|
|
252
|
+
<temba-icon name="${this.icon}" size="1.5"></temba-icon>
|
|
253
|
+
</div>
|
|
254
|
+
</label>`
|
|
255
|
+
: null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
render() {
|
|
259
|
+
return html ` <div
|
|
260
|
+
class=${getClasses({ container: true, highlight: this.pendingDrop })}
|
|
261
|
+
@dragenter="${this.handleDragEnter}"
|
|
262
|
+
@dragover="${this.handleDragOver}"
|
|
263
|
+
@dragleave="${this.handleDragLeave}"
|
|
264
|
+
@drop="${this.handleDrop}"
|
|
265
|
+
>
|
|
266
|
+
<div class="drop-mask">
|
|
267
|
+
<div class="attachments-list">
|
|
268
|
+
${this.attachments.map((validAttachment) => {
|
|
269
|
+
return html `<div class="attachment-item">
|
|
270
|
+
<temba-icon
|
|
271
|
+
class="remove-item"
|
|
272
|
+
@click="${this.handleRemoveFileClicked}"
|
|
273
|
+
id="${validAttachment.url}"
|
|
274
|
+
name="${Icon.delete_small}"
|
|
275
|
+
></temba-icon>
|
|
276
|
+
${isImageAttachment(validAttachment)
|
|
277
|
+
? html `<temba-thumbnail
|
|
278
|
+
url="${validAttachment.url}"
|
|
279
|
+
></temba-thumbnail>`
|
|
280
|
+
: html `<temba-thumbnail
|
|
281
|
+
label="${validAttachment.content_type.split('/')[1]}"
|
|
282
|
+
></temba-thumbnail>`}
|
|
283
|
+
</div>`;
|
|
284
|
+
})}
|
|
285
|
+
${this.renderUploader()}
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
</div>`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
__decorate([
|
|
292
|
+
property({ type: String, attribute: false })
|
|
293
|
+
], MediaPicker.prototype, "endpoint", void 0);
|
|
294
|
+
__decorate([
|
|
295
|
+
property({ type: Boolean })
|
|
296
|
+
], MediaPicker.prototype, "pendingDrop", void 0);
|
|
297
|
+
__decorate([
|
|
298
|
+
property({ type: String })
|
|
299
|
+
], MediaPicker.prototype, "icon", void 0);
|
|
300
|
+
__decorate([
|
|
301
|
+
property({ type: String })
|
|
302
|
+
], MediaPicker.prototype, "accept", void 0);
|
|
303
|
+
__decorate([
|
|
304
|
+
property({ type: Number })
|
|
305
|
+
], MediaPicker.prototype, "max", void 0);
|
|
306
|
+
__decorate([
|
|
307
|
+
property({ type: Array })
|
|
308
|
+
], MediaPicker.prototype, "attachments", void 0);
|
|
309
|
+
__decorate([
|
|
310
|
+
property({ type: Boolean, attribute: false })
|
|
311
|
+
], MediaPicker.prototype, "uploading", void 0);
|
|
312
|
+
//# sourceMappingURL=MediaPicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaPicker.js","sourceRoot":"","sources":["../../../src/mediapicker/MediaPicker.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult, css, html } from 'lit';\nimport { RapidElement } from '../RapidElement';\nimport { property } from 'lit/decorators.js';\nimport { Attachment } from '../interfaces';\nimport { Icon } from '../vectoricon';\nimport {\n DEFAULT_MEDIA_ENDPOINT,\n WebResponse,\n getClasses,\n isImageAttachment,\n postFormData\n} from '../utils';\n\nconst verifyAccept = (type: string, accept: string): boolean => {\n if (accept) {\n const allowed = accept.split(',').map((x) => x.trim());\n return (\n allowed.includes(type) || allowed.includes(type.split('/')[0] + '/*')\n );\n }\n return true;\n};\n\nexport class MediaPicker extends RapidElement {\n static get styles() {\n return css`\n .drop-mask {\n border-radius: var(--curvature-widget);\n transition: opacity ease-in-out var(--transition-speed);\n }\n\n .highlight .drop-mask {\n background: rgba(210, 243, 184, 0.8);\n }\n\n .drop-mask > div {\n margin: auto;\n border-radius: var(--curvature-widget);\n font-weight: 400;\n color: rgba(0, 0, 0, 0.5);\n }\n\n .attachments {\n }\n\n .attachments-list {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n padding: 0.2em;\n }\n\n .attachment-item {\n padding: 0.4em;\n padding-top: 1em;\n }\n\n .attachment-item.error {\n background: #fff;\n color: rgba(250, 0, 0, 0.75);\n padding: 0.2em;\n margin: 0.3em 0.5em;\n border-radius: var(--curvature);\n display: block;\n }\n\n .remove-item {\n --icon-color: #ccc;\n background: #fff;\n border-radius: 99%;\n transition: transform 200ms linear;\n transform: scale(0);\n display: block;\n margin-bottom: -24px;\n margin-left: 10px;\n width: 1em;\n height: 1em;\n }\n\n .attachment-item:hover .remove-item {\n transform: scale(1);\n }\n\n .remove-item:hover {\n --icon-color: #333;\n cursor: pointer;\n }\n\n .attachment-name {\n align-self: center;\n font-size: 12px;\n padding: 2px 8px;\n }\n\n #upload-input {\n display: none;\n }\n\n .upload-label {\n display: flex;\n align-items: center;\n }\n\n .upload-icon {\n color: rgb(102, 102, 102);\n }\n\n .add-attachment {\n padding: 1em;\n background-color: rgba(0, 0, 0, 0.05);\n border-radius: var(--curvature);\n color: #aaa;\n margin: 0.5em;\n }\n\n .add-attachment:hover {\n background-color: rgba(0, 0, 0, 0.07);\n cursor: pointer;\n }\n `;\n }\n\n @property({ type: String, attribute: false })\n endpoint = DEFAULT_MEDIA_ENDPOINT;\n\n @property({ type: Boolean })\n pendingDrop: boolean;\n\n @property({ type: String })\n icon = Icon.add;\n\n @property({ type: String })\n accept = ''; //e.g. \".xls,.xlsx\"\n\n @property({ type: Number })\n max = 3;\n\n @property({ type: Array })\n attachments: Attachment[] = [];\n\n @property({ type: Boolean, attribute: false })\n uploading: boolean;\n\n public updated(changes: Map<string, any>): void {\n super.updated(changes);\n if (changes.has('attachments')) {\n // wait one cycle to fire change for tests\n setTimeout(() => {\n this.dispatchEvent(new Event('change'));\n }, 0);\n }\n }\n\n private getAcceptableFiles(evt: DragEvent): File[] {\n const dt = evt.dataTransfer;\n if (dt) {\n const files = [...dt.files];\n return files.filter((file) => verifyAccept(file.type, this.accept));\n }\n }\n\n private handleDragEnter(evt: DragEvent): void {\n this.highlight(evt);\n }\n\n private handleDragOver(evt: DragEvent): void {\n this.highlight(evt);\n }\n\n private handleDragLeave(evt: DragEvent): void {\n this.unhighlight(evt);\n }\n\n private handleDrop(evt: DragEvent): void {\n this.unhighlight(evt);\n if (this.canAcceptAttachments()) {\n this.uploadFiles(this.getAcceptableFiles(evt));\n }\n }\n\n public canAcceptAttachments() {\n return this.attachments.length < this.max;\n }\n\n private highlight(evt: DragEvent): void {\n evt.preventDefault();\n evt.stopPropagation();\n if (this.canAcceptAttachments()) {\n this.pendingDrop = true;\n }\n }\n\n private unhighlight(evt: DragEvent): void {\n evt.preventDefault();\n evt.stopPropagation();\n this.pendingDrop = false;\n }\n\n private addCurrentAttachment(attachmentToAdd: any) {\n this.attachments.push(attachmentToAdd);\n this.requestUpdate('attachments');\n }\n\n private removeCurrentAttachment(attachmentToRemove: any) {\n this.attachments = this.attachments.filter(\n (currentAttachment) => currentAttachment !== attachmentToRemove\n );\n this.requestUpdate('attachments');\n }\n\n private handleRemoveFileClicked(evt: Event): void {\n const target = evt.target as HTMLDivElement;\n const currentAttachmentToRemove = this.attachments.find(\n ({ url }) => url === target.id\n );\n if (currentAttachmentToRemove) {\n this.removeCurrentAttachment(currentAttachmentToRemove);\n }\n }\n\n private handleUploadFileInputChanged(evt: Event): void {\n const target = evt.target as HTMLInputElement;\n const files = target.files;\n this.uploadFiles([...files]);\n }\n\n public uploadFiles(files: File[]): void {\n let filesToUpload = [];\n\n //remove duplicate files that have already been uploaded\n filesToUpload = files.filter((file) => {\n // check our file type against accepts\n if (this.accept) {\n if (!verifyAccept(file.type, this.accept)) {\n return false;\n }\n }\n\n const index = this.attachments.findIndex(\n (value) => value.filename === file.name && value.size === file.size\n );\n if (index === -1) {\n return file;\n }\n });\n\n filesToUpload.map((fileToUpload) => {\n this.uploadFile(fileToUpload);\n });\n }\n\n private uploadFile(file: File): void {\n this.uploading = true;\n\n const url = this.endpoint;\n const payload = new FormData();\n payload.append('file', file);\n postFormData(url, payload)\n .then((response: WebResponse) => {\n if (this.attachments.length < this.max) {\n const attachment = response.json as Attachment;\n if (attachment) {\n this.addCurrentAttachment(attachment);\n }\n }\n })\n .catch((error: WebResponse) => {\n let uploadError = '';\n if (error.status === 400) {\n uploadError = error.json.file[0];\n } else {\n uploadError = 'Server failure';\n }\n console.error(uploadError);\n })\n .finally(() => {\n this.uploading = false;\n });\n }\n\n private renderUploader(): TemplateResult {\n if (this.uploading) {\n return html`<temba-loading units=\"3\" size=\"12\"></temba-loading>`;\n } else {\n return this.attachments.length < this.max\n ? html`<input\n type=\"file\"\n id=\"upload-input\"\n ?multiple=${this.max > 1}\n accept=\"${this.accept}\"\n @change=\"${this.handleUploadFileInputChanged}\"\n />\n <label\n id=\"upload-label\"\n class=\"actions-left upload-label\"\n for=\"upload-input\"\n >\n <div class=\"add-attachment\">\n <temba-icon name=\"${this.icon}\" size=\"1.5\"></temba-icon>\n </div>\n </label>`\n : null;\n }\n }\n\n public render(): TemplateResult {\n return html` <div\n class=${getClasses({ container: true, highlight: this.pendingDrop })}\n @dragenter=\"${this.handleDragEnter}\"\n @dragover=\"${this.handleDragOver}\"\n @dragleave=\"${this.handleDragLeave}\"\n @drop=\"${this.handleDrop}\"\n >\n <div class=\"drop-mask\">\n <div class=\"attachments-list\">\n ${this.attachments.map((validAttachment) => {\n return html`<div class=\"attachment-item\">\n <temba-icon\n class=\"remove-item\"\n @click=\"${this.handleRemoveFileClicked}\"\n id=\"${validAttachment.url}\"\n name=\"${Icon.delete_small}\"\n ></temba-icon>\n ${isImageAttachment(validAttachment)\n ? html`<temba-thumbnail\n url=\"${validAttachment.url}\"\n ></temba-thumbnail>`\n : html`<temba-thumbnail\n label=\"${validAttachment.content_type.split('/')[1]}\"\n ></temba-thumbnail>`}\n </div>`;\n })}\n ${this.renderUploader()}\n </div>\n </div>\n </div>`;\n }\n}\n"]}
|
|
@@ -17,6 +17,11 @@ export class TemplateEditor extends FormElement {
|
|
|
17
17
|
padding: 1em;
|
|
18
18
|
margin-top: 1em;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
.content {
|
|
22
|
+
margin-bottom: 1em;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
.picker {
|
|
21
26
|
margin-bottom: 0.5em;
|
|
22
27
|
display: block;
|
|
@@ -45,7 +50,6 @@ export class TemplateEditor extends FormElement {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
.button-wrapper {
|
|
48
|
-
margin-top: 1em;
|
|
49
53
|
background: #f9f9f9;
|
|
50
54
|
border-radius: var(--curvature);
|
|
51
55
|
padding: 0.5em;
|
|
@@ -101,6 +105,9 @@ export class TemplateEditor extends FormElement {
|
|
|
101
105
|
border: 1px solid var(--color-widget-border);
|
|
102
106
|
padding: 1em;
|
|
103
107
|
line-height: 2.2em;
|
|
108
|
+
max-height: 50vh;
|
|
109
|
+
overflow-y: auto;
|
|
110
|
+
overflow-x: hidden;
|
|
104
111
|
}
|
|
105
112
|
`;
|
|
106
113
|
}
|
|
@@ -142,10 +149,30 @@ export class TemplateEditor extends FormElement {
|
|
|
142
149
|
variables: this.variables
|
|
143
150
|
});
|
|
144
151
|
}
|
|
152
|
+
handleAttachmentsChanged(event) {
|
|
153
|
+
const media = event.target;
|
|
154
|
+
const index = parseInt(media.getAttribute('index'));
|
|
155
|
+
if (media.attachments.length === 0) {
|
|
156
|
+
this.variables[index] = '';
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const attachment = media.attachments[0];
|
|
160
|
+
if (attachment.url && attachment.content_type) {
|
|
161
|
+
this.variables[index] = `${attachment.content_type}:${attachment.url}`;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this.variables[index] = ``;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this.fireContentChange();
|
|
168
|
+
}
|
|
145
169
|
handleVariableChanged(event) {
|
|
146
170
|
const target = event.target;
|
|
147
171
|
const variableIndex = parseInt(target.getAttribute('index'));
|
|
148
172
|
this.variables[variableIndex] = target.value;
|
|
173
|
+
this.fireContentChange();
|
|
174
|
+
}
|
|
175
|
+
fireContentChange() {
|
|
149
176
|
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
150
177
|
template: this.selectedTemplate,
|
|
151
178
|
translation: this.translation,
|
|
@@ -153,11 +180,13 @@ export class TemplateEditor extends FormElement {
|
|
|
153
180
|
});
|
|
154
181
|
}
|
|
155
182
|
renderVariables(component) {
|
|
183
|
+
var _a;
|
|
156
184
|
// create a regex match based on the variable names
|
|
157
185
|
const variableRegex = new RegExp(`{{(${Object.keys(component.variables || []).join('|')})}}`, 'g');
|
|
158
|
-
|
|
186
|
+
let variables = null;
|
|
187
|
+
const parts = ((_a = component.content) === null || _a === void 0 ? void 0 : _a.split(variableRegex)) || [];
|
|
159
188
|
if (parts.length > 0) {
|
|
160
|
-
|
|
189
|
+
variables = parts.map((part, index) => {
|
|
161
190
|
if (index % 2 === 0) {
|
|
162
191
|
return html `<span class="text">${part}</span>`;
|
|
163
192
|
}
|
|
@@ -174,8 +203,50 @@ export class TemplateEditor extends FormElement {
|
|
|
174
203
|
placeholder="{{${part}}}"
|
|
175
204
|
></temba-completion>`;
|
|
176
205
|
});
|
|
177
|
-
return html `<div class="content">${variables}</div>`;
|
|
178
206
|
}
|
|
207
|
+
else {
|
|
208
|
+
// no content, let's do params intead
|
|
209
|
+
variables = component.params.map((param) => {
|
|
210
|
+
if (param.type === 'image' ||
|
|
211
|
+
param.type === 'document' ||
|
|
212
|
+
param.type === 'audio' ||
|
|
213
|
+
param.type === 'video') {
|
|
214
|
+
const index = Object.values(component.variables)[0];
|
|
215
|
+
let attachments = [];
|
|
216
|
+
if (this.variables[index]) {
|
|
217
|
+
const parts = this.variables[index].split(':');
|
|
218
|
+
attachments = [{ url: parts[1], content_type: parts[0] }];
|
|
219
|
+
}
|
|
220
|
+
return html `<div
|
|
221
|
+
style="
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
border-radius: var(--curvature);
|
|
225
|
+
${attachments.length === 0
|
|
226
|
+
? `background-color:rgba(255,0,0,.07);`
|
|
227
|
+
: ``}
|
|
228
|
+
"
|
|
229
|
+
>
|
|
230
|
+
<temba-media-picker
|
|
231
|
+
accept="${param.type === 'document'
|
|
232
|
+
? 'application/pdf'
|
|
233
|
+
: param.type + '/*'}"
|
|
234
|
+
max="1"
|
|
235
|
+
index=${index}
|
|
236
|
+
icon="attachment_${param.type}"
|
|
237
|
+
attachments=${JSON.stringify(attachments)}
|
|
238
|
+
@change=${this.handleAttachmentsChanged.bind(this)}
|
|
239
|
+
></temba-media-picker>
|
|
240
|
+
<div>
|
|
241
|
+
${attachments.length == 0
|
|
242
|
+
? html `Attach ${param.type} to continue`
|
|
243
|
+
: ''}
|
|
244
|
+
</div>
|
|
245
|
+
</div>`;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return html `<div class="content">${variables}</div> `;
|
|
179
250
|
}
|
|
180
251
|
renderComponents(components) {
|
|
181
252
|
const nonButtons = components
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplateEditor.js","sourceRoot":"","sources":["../../../src/templates/TemplateEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\nimport { FormElement } from '../FormElement';\nimport { TemplateResult, html, css, PropertyValueMap, LitElement } from 'lit';\nimport { CustomEventType } from '../interfaces';\n\ninterface Component {\n name: string;\n type: string;\n content: string;\n variables: { [key: string]: number };\n}\n\ninterface Translation {\n locale: string;\n status: string;\n channel: { uuid: string; name: string };\n components: Component[];\n variables: { type: string }[];\n}\n\ninterface Template {\n created_on: string;\n modified_on: string;\n name: string;\n translations: Translation[];\n uuid: string;\n}\n\nexport class TemplateEditor extends FormElement {\n static shadowRootOptions = {\n ...LitElement.shadowRootOptions,\n delegatesFocus: true\n };\n\n static get styles() {\n return css`\n .component {\n background: #fff;\n border: 1px solid var(--color-widget-border);\n border-radius: var(--curvature);\n padding: 1em;\n margin-top: 1em;\n }\n .picker {\n margin-bottom: 0.5em;\n display: block;\n }\n .param {\n display: flex;\n margin-bottom: 0.5em;\n align-items: center;\n }\n label {\n margin-right: 0.5em;\n }\n\n .content span {\n margin-right: 0.25em;\n }\n\n .error-message {\n padding-left: 0.5em;\n }\n\n .variable {\n display: inline-block;\n margin: 0.25em 0em;\n margin-right: 0.25em;\n }\n\n .button-wrapper {\n margin-top: 1em;\n background: #f9f9f9;\n border-radius: var(--curvature);\n padding: 0.5em;\n display: flex;\n flex-direction: column;\n }\n\n .button-header {\n font-weight: normal;\n margin-left: 0.25em;\n margin-bottom: -0.5em;\n font-size: 0.9em;\n color: #777;\n }\n\n .buttons {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .button {\n background: #fff;\n padding: 0.3em 1em;\n border: 1px solid #e6e6e6;\n border-radius: var(--curvature);\n min-height: 23px;\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-right: 0.5em;\n margin-top: 0.5em;\n align-items: center;\n }\n\n .button .display {\n margin-right: 0.5em;\n background: #f9f9f9;\n padding: 0.25em 1em;\n border-radius: var(--curvature);\n }\n\n temba-textinput,\n temba-completion {\n --temba-textinput-padding: 5px 5px;\n --temba-textinput-font-size: 0.9em;\n line-height: initial;\n }\n\n .template {\n background: #fff;\n border-radius: var(--curvature);\n border: 1px solid var(--color-widget-border);\n padding: 1em;\n line-height: 2.2em;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n // initial template uuid\n @property({ type: String })\n template: string;\n\n @property({ type: Object })\n selectedTemplate: Template;\n\n @property({ type: String })\n lang = 'eng-US';\n\n @property({ type: Array })\n variables: string[];\n\n @property({ type: Object, attribute: false })\n translation: Translation;\n\n @property({ type: Boolean })\n translating: boolean;\n\n public firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n }\n\n public updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n }\n\n private handleTemplateChanged(event: CustomEvent) {\n const prev = this.selectedTemplate;\n this.selectedTemplate = (event.target as any).values[0] as Template;\n const [lang, loc] = this.lang.split('-');\n if (this.selectedTemplate) {\n this.selectedTemplate.translations.forEach((translation) => {\n if (\n translation.locale === this.lang ||\n (!loc && translation.locale.split('-')[0] === lang)\n ) {\n this.translation = translation;\n // initialize our variables array\n const newVariables = new Array(\n (translation.variables || []).length\n ).fill('');\n\n if (!prev) {\n // copy our previous variables into newVariables\n if (this.variables) {\n this.variables.forEach((variable, index) => {\n newVariables[index] = variable;\n });\n }\n }\n this.variables = newVariables;\n }\n });\n } else {\n this.translation = null;\n }\n\n this.fireCustomEvent(CustomEventType.ContextChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n variables: this.variables\n });\n }\n\n private handleVariableChanged(event: CustomEvent) {\n const target = event.target as HTMLInputElement;\n const variableIndex = parseInt(target.getAttribute('index'));\n this.variables[variableIndex] = target.value;\n this.fireCustomEvent(CustomEventType.ContentChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n variables: this.variables\n });\n }\n\n private renderVariables(component: Component) {\n // create a regex match based on the variable names\n const variableRegex = new RegExp(\n `{{(${Object.keys(component.variables || []).join('|')})}}`,\n 'g'\n );\n const parts = component.content.split(variableRegex);\n if (parts.length > 0) {\n const variables = parts.map((part, index) => {\n if (index % 2 === 0) {\n return html`<span class=\"text\">${part}</span>`;\n }\n const variableIndex = component.variables[part];\n return html`<temba-completion\n class=\"variable\"\n type=\"text\"\n value=${variableIndex < this.variables.length\n ? this.variables[variableIndex]\n : null}\n @keyup=${this.handleVariableChanged}\n name=\"${component.name}\"\n index=\"${variableIndex}\"\n placeholder=\"{{${part}}}\"\n ></temba-completion>`;\n });\n return html`<div class=\"content\">${variables}</div>`;\n }\n }\n\n public renderComponents(components: Component[]): TemplateResult {\n const nonButtons = components\n .filter((comp) => !comp.type.startsWith('button/'))\n .map(\n (component) =>\n html`<div class=\"${component['name']}\">\n ${this.renderVariables(component)}\n </div>`\n );\n const buttonComponents = components.filter((comp) =>\n comp.type.startsWith('button/')\n );\n const buttons =\n buttonComponents.length > 0 ? this.renderButtons(buttonComponents) : null;\n return html`<div class=\"main\">${nonButtons}</div>\n <div class=\"buttons\">\n ${buttons}\n <div></div>\n </div>`;\n }\n\n public renderButtons(components): TemplateResult {\n const buttons = components.map((component) => {\n if (component.display) {\n return html`\n <div class=\"button\">\n <div class=\"display\">${component.display}</div>\n ${this.renderVariables(component)}\n </div>\n `;\n } else {\n return html`\n <div class=\"button\">${this.renderVariables(component)}</div>\n `;\n }\n });\n return html`<div class=\"button-wrapper\">\n <div class=\"button-header\">Template Buttons</div>\n <div class=\"buttons\">${buttons}</div>\n </div>`;\n }\n\n public render(): TemplateResult {\n let content = null;\n if (this.translation) {\n content = this.renderComponents(this.translation.components);\n } else {\n content = html`<div class=\"error-message\">\n No approved translation was found for current language.\n </div>`;\n }\n\n return html`\n <div>\n <temba-select\n searchable\n ?clearable=${!this.translating}\n ?disabled=${this.translating}\n valuekey=\"uuid\"\n class=\"picker\"\n value=\"${this.template}\"\n endpoint=\"${this.url}?comps_as_list=true\"\n shouldExclude=${(template) => template.status !== 'approved'}\n placeholder=\"Select a template\"\n @temba-content-changed=${this.swallowEvent}\n @change=${this.handleTemplateChanged}\n >\n </temba-select>\n\n ${this.template ? html` <div class=\"template\">${content}</div>` : null}\n </div>\n `;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TemplateEditor.js","sourceRoot":"","sources":["../../../src/templates/TemplateEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\nimport { FormElement } from '../FormElement';\nimport { TemplateResult, html, css, PropertyValueMap, LitElement } from 'lit';\nimport { CustomEventType } from '../interfaces';\nimport { MediaPicker } from '../mediapicker/MediaPicker';\n\ninterface Component {\n name: string;\n type: string;\n content: string;\n variables: { [key: string]: number };\n params: [{ type: string }];\n}\n\ninterface Translation {\n locale: string;\n status: string;\n channel: { uuid: string; name: string };\n components: Component[];\n variables: { type: string }[];\n}\n\ninterface Template {\n created_on: string;\n modified_on: string;\n name: string;\n translations: Translation[];\n uuid: string;\n}\n\nexport class TemplateEditor extends FormElement {\n static shadowRootOptions = {\n ...LitElement.shadowRootOptions,\n delegatesFocus: true\n };\n\n static get styles() {\n return css`\n .component {\n background: #fff;\n border: 1px solid var(--color-widget-border);\n border-radius: var(--curvature);\n padding: 1em;\n margin-top: 1em;\n }\n\n .content {\n margin-bottom: 1em;\n }\n\n .picker {\n margin-bottom: 0.5em;\n display: block;\n }\n .param {\n display: flex;\n margin-bottom: 0.5em;\n align-items: center;\n }\n label {\n margin-right: 0.5em;\n }\n\n .content span {\n margin-right: 0.25em;\n }\n\n .error-message {\n padding-left: 0.5em;\n }\n\n .variable {\n display: inline-block;\n margin: 0.25em 0em;\n margin-right: 0.25em;\n }\n\n .button-wrapper {\n background: #f9f9f9;\n border-radius: var(--curvature);\n padding: 0.5em;\n display: flex;\n flex-direction: column;\n }\n\n .button-header {\n font-weight: normal;\n margin-left: 0.25em;\n margin-bottom: -0.5em;\n font-size: 0.9em;\n color: #777;\n }\n\n .buttons {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .button {\n background: #fff;\n padding: 0.3em 1em;\n border: 1px solid #e6e6e6;\n border-radius: var(--curvature);\n min-height: 23px;\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-right: 0.5em;\n margin-top: 0.5em;\n align-items: center;\n }\n\n .button .display {\n margin-right: 0.5em;\n background: #f9f9f9;\n padding: 0.25em 1em;\n border-radius: var(--curvature);\n }\n\n temba-textinput,\n temba-completion {\n --temba-textinput-padding: 5px 5px;\n --temba-textinput-font-size: 0.9em;\n line-height: initial;\n }\n\n .template {\n background: #fff;\n border-radius: var(--curvature);\n border: 1px solid var(--color-widget-border);\n padding: 1em;\n line-height: 2.2em;\n max-height: 50vh;\n overflow-y: auto;\n overflow-x: hidden;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n // initial template uuid\n @property({ type: String })\n template: string;\n\n @property({ type: Object })\n selectedTemplate: Template;\n\n @property({ type: String })\n lang = 'eng-US';\n\n @property({ type: Array })\n variables: string[];\n\n @property({ type: Object, attribute: false })\n translation: Translation;\n\n @property({ type: Boolean })\n translating: boolean;\n\n public firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n }\n\n public updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n }\n\n private handleTemplateChanged(event: CustomEvent) {\n const prev = this.selectedTemplate;\n this.selectedTemplate = (event.target as any).values[0] as Template;\n const [lang, loc] = this.lang.split('-');\n if (this.selectedTemplate) {\n this.selectedTemplate.translations.forEach((translation) => {\n if (\n translation.locale === this.lang ||\n (!loc && translation.locale.split('-')[0] === lang)\n ) {\n this.translation = translation;\n // initialize our variables array\n const newVariables = new Array(\n (translation.variables || []).length\n ).fill('');\n\n if (!prev) {\n // copy our previous variables into newVariables\n if (this.variables) {\n this.variables.forEach((variable, index) => {\n newVariables[index] = variable;\n });\n }\n }\n this.variables = newVariables;\n }\n });\n } else {\n this.translation = null;\n }\n\n this.fireCustomEvent(CustomEventType.ContextChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n variables: this.variables\n });\n }\n\n private handleAttachmentsChanged(event: CustomEvent) {\n const media = event.target as MediaPicker;\n const index = parseInt(media.getAttribute('index'));\n\n if (media.attachments.length === 0) {\n this.variables[index] = '';\n } else {\n const attachment = media.attachments[0];\n if (attachment.url && attachment.content_type) {\n this.variables[index] = `${attachment.content_type}:${attachment.url}`;\n } else {\n this.variables[index] = ``;\n }\n }\n this.fireContentChange();\n }\n\n private handleVariableChanged(event: CustomEvent) {\n const target = event.target as HTMLInputElement;\n const variableIndex = parseInt(target.getAttribute('index'));\n this.variables[variableIndex] = target.value;\n this.fireContentChange();\n }\n\n private fireContentChange() {\n this.fireCustomEvent(CustomEventType.ContentChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n variables: this.variables\n });\n }\n\n private renderVariables(component: Component) {\n // create a regex match based on the variable names\n const variableRegex = new RegExp(\n `{{(${Object.keys(component.variables || []).join('|')})}}`,\n 'g'\n );\n\n let variables = null;\n const parts = component.content?.split(variableRegex) || [];\n if (parts.length > 0) {\n variables = parts.map((part, index) => {\n if (index % 2 === 0) {\n return html`<span class=\"text\">${part}</span>`;\n }\n const variableIndex = component.variables[part];\n return html`<temba-completion\n class=\"variable\"\n type=\"text\"\n value=${variableIndex < this.variables.length\n ? this.variables[variableIndex]\n : null}\n @keyup=${this.handleVariableChanged}\n name=\"${component.name}\"\n index=\"${variableIndex}\"\n placeholder=\"{{${part}}}\"\n ></temba-completion>`;\n });\n } else {\n // no content, let's do params intead\n variables = component.params.map((param) => {\n if (\n param.type === 'image' ||\n param.type === 'document' ||\n param.type === 'audio' ||\n param.type === 'video'\n ) {\n const index = Object.values(component.variables)[0];\n let attachments = [];\n if (this.variables[index]) {\n const parts = this.variables[index].split(':');\n attachments = [{ url: parts[1], content_type: parts[0] }];\n }\n\n return html`<div\n style=\"\n display: flex; \n align-items: center; \n border-radius: var(--curvature);\n ${attachments.length === 0\n ? `background-color:rgba(255,0,0,.07);`\n : ``}\n \"\n >\n <temba-media-picker\n accept=\"${param.type === 'document'\n ? 'application/pdf'\n : param.type + '/*'}\"\n max=\"1\"\n index=${index}\n icon=\"attachment_${param.type}\"\n attachments=${JSON.stringify(attachments)}\n @change=${this.handleAttachmentsChanged.bind(this)}\n ></temba-media-picker>\n <div>\n ${attachments.length == 0\n ? html`Attach ${param.type} to continue`\n : ''}\n </div>\n </div>`;\n }\n });\n }\n\n return html`<div class=\"content\">${variables}</div> `;\n }\n\n public renderComponents(components: Component[]): TemplateResult {\n const nonButtons = components\n .filter((comp) => !comp.type.startsWith('button/'))\n .map(\n (component) =>\n html`<div class=\"${component['name']}\">\n ${this.renderVariables(component)}\n </div>`\n );\n const buttonComponents = components.filter((comp) =>\n comp.type.startsWith('button/')\n );\n const buttons =\n buttonComponents.length > 0 ? this.renderButtons(buttonComponents) : null;\n return html`<div class=\"main\">${nonButtons}</div>\n <div class=\"buttons\">\n ${buttons}\n <div></div>\n </div>`;\n }\n\n public renderButtons(components): TemplateResult {\n const buttons = components.map((component) => {\n if (component.display) {\n return html`\n <div class=\"button\">\n <div class=\"display\">${component.display}</div>\n ${this.renderVariables(component)}\n </div>\n `;\n } else {\n return html`\n <div class=\"button\">${this.renderVariables(component)}</div>\n `;\n }\n });\n return html`<div class=\"button-wrapper\">\n <div class=\"button-header\">Template Buttons</div>\n <div class=\"buttons\">${buttons}</div>\n </div>`;\n }\n\n public render(): TemplateResult {\n let content = null;\n if (this.translation) {\n content = this.renderComponents(this.translation.components);\n } else {\n content = html`<div class=\"error-message\">\n No approved translation was found for current language.\n </div>`;\n }\n\n return html`\n <div>\n <temba-select\n searchable\n ?clearable=${!this.translating}\n ?disabled=${this.translating}\n valuekey=\"uuid\"\n class=\"picker\"\n value=\"${this.template}\"\n endpoint=\"${this.url}?comps_as_list=true\"\n shouldExclude=${(template) => template.status !== 'approved'}\n placeholder=\"Select a template\"\n @temba-content-changed=${this.swallowEvent}\n @change=${this.handleTemplateChanged}\n >\n </temba-select>\n\n ${this.template ? html` <div class=\"template\">${content}</div>` : null}\n </div>\n `;\n }\n}\n"]}
|
|
@@ -13,7 +13,7 @@ export class Thumbnail extends RapidElement {
|
|
|
13
13
|
static get styles() {
|
|
14
14
|
return css `
|
|
15
15
|
:host {
|
|
16
|
-
display: inline
|
|
16
|
+
display: inline;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.zooming.wrapper {
|
|
@@ -44,19 +44,8 @@ export class Thumbnail extends RapidElement {
|
|
|
44
44
|
}
|
|
45
45
|
render() {
|
|
46
46
|
if (this.zooming) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class="${getClasses({ wrapper: true })}"
|
|
50
|
-
style=${styleMap({
|
|
51
|
-
background: 'red',
|
|
52
|
-
borderRadius: '0',
|
|
53
|
-
boxShadow: 'var(--widget-box-shadow)'
|
|
54
|
-
})}
|
|
55
|
-
>
|
|
56
|
-
<div
|
|
57
|
-
class="thumb"
|
|
58
|
-
style=${styleMap({
|
|
59
|
-
backgroundImage: `url(${this.url})`,
|
|
47
|
+
const styles = {
|
|
48
|
+
backgroundColor: '#fafafa',
|
|
60
49
|
backgroundSize: 'contain',
|
|
61
50
|
backgroundPosition: 'center',
|
|
62
51
|
backgroundRepeat: 'no-repeat',
|
|
@@ -69,24 +58,26 @@ export class Thumbnail extends RapidElement {
|
|
|
69
58
|
justifyContent: 'center',
|
|
70
59
|
fontWeight: '400',
|
|
71
60
|
color: '#bbb'
|
|
61
|
+
};
|
|
62
|
+
if (this.url) {
|
|
63
|
+
styles['backgroundImage'] = `url(${this.url})`;
|
|
64
|
+
}
|
|
65
|
+
return html `
|
|
66
|
+
<div
|
|
67
|
+
class="${getClasses({ wrapper: true })}"
|
|
68
|
+
style=${styleMap({
|
|
69
|
+
background: 'red',
|
|
70
|
+
borderRadius: '0',
|
|
71
|
+
boxShadow: 'var(--widget-box-shadow)'
|
|
72
72
|
})}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
</div>
|
|
73
|
+
>
|
|
74
|
+
<div class="thumb" style=${styleMap(styles)}>${this.label}</div>
|
|
76
75
|
</div>
|
|
77
76
|
`;
|
|
78
77
|
}
|
|
79
78
|
else {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
padding: 'var(--thumb-padding, 0.4em)',
|
|
83
|
-
background: '#fff',
|
|
84
|
-
borderRadius: 'var(--curvature)',
|
|
85
|
-
boxShadow: 'var(--widget-box-shadow)'
|
|
86
|
-
})}">
|
|
87
|
-
|
|
88
|
-
<div class="thumb" style=${styleMap({
|
|
89
|
-
backgroundImage: `url(${this.url})`,
|
|
79
|
+
const styles = {
|
|
80
|
+
backgroundColor: '#fafafa',
|
|
90
81
|
backgroundSize: 'cover',
|
|
91
82
|
backgroundPosition: 'center',
|
|
92
83
|
maxHeight: 'var(--thumb-size, 4em)',
|
|
@@ -98,9 +89,20 @@ export class Thumbnail extends RapidElement {
|
|
|
98
89
|
justifyContent: 'center',
|
|
99
90
|
fontWeight: '400',
|
|
100
91
|
color: '#bbb'
|
|
101
|
-
}
|
|
92
|
+
};
|
|
93
|
+
if (this.url) {
|
|
94
|
+
styles['backgroundImage'] = `url(${this.url})`;
|
|
95
|
+
}
|
|
96
|
+
return html `
|
|
97
|
+
<div class="${getClasses({ wrapper: true })}" style=${styleMap({
|
|
98
|
+
padding: 'var(--thumb-padding, 0.4em)',
|
|
99
|
+
background: '#fff',
|
|
100
|
+
borderRadius: 'var(--curvature)',
|
|
101
|
+
boxShadow: 'var(--widget-box-shadow)'
|
|
102
|
+
})}">
|
|
103
|
+
<div class="thumb" style=${styleMap(styles)}>
|
|
102
104
|
${this.label}
|
|
103
|
-
|
|
105
|
+
</div>
|
|
104
106
|
</div>
|
|
105
107
|
`;
|
|
106
108
|
}
|