@nyaruka/temba-components 0.139.0 → 0.140.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/.github/workflows/cla.yml +1 -1
- package/.github/workflows/copilot-setup-steps.yml +6 -1
- package/CHANGELOG.md +17 -0
- package/demo/data/flows/sample-flow.json +24 -0
- package/dist/temba-components.js +562 -296
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +10 -7
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/Dropdown.js +3 -1
- package/out-tsc/src/display/Dropdown.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +3 -3
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +163 -5
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +64 -22
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +142 -8
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +118 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +13 -4
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/audio-player.js +112 -0
- package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +43 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +57 -4
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +86 -3
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -3
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
- package/out-tsc/src/flow/nodes/terminal.js +7 -0
- package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/operators.js +21 -5
- package/out-tsc/src/flow/operators.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +79 -3
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +4 -2
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +49 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js +52 -7
- package/out-tsc/src/layout/Dialog.js.map +1 -1
- package/out-tsc/src/live/TembaChart.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +10 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +89 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/actions/play_audio.test.js +118 -0
- package/out-tsc/test/actions/play_audio.test.js.map +1 -0
- package/out-tsc/test/actions/say_msg.test.js +158 -0
- package/out-tsc/test/actions/say_msg.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
- package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-collision.test.js +261 -6
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +6 -6
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +13 -7
- package/src/display/Dropdown.ts +3 -1
- package/src/display/FloatingTab.ts +3 -3
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasNode.ts +69 -23
- package/src/flow/Editor.ts +156 -13
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/StickyNote.ts +14 -4
- package/src/flow/actions/audio-player.ts +127 -0
- package/src/flow/actions/enter_flow.ts +44 -0
- package/src/flow/actions/play_audio.ts +64 -5
- package/src/flow/actions/say_msg.ts +94 -4
- package/src/flow/config.ts +11 -3
- package/src/flow/nodes/shared-rules.ts +1 -1
- package/src/flow/nodes/terminal.ts +9 -0
- package/src/flow/nodes/wait_for_audio.ts +88 -0
- package/src/flow/nodes/wait_for_dial.ts +176 -0
- package/src/flow/nodes/wait_for_digits.ts +86 -2
- package/src/flow/nodes/wait_for_menu.ts +209 -3
- package/src/flow/operators.ts +23 -5
- package/src/flow/types.ts +23 -1
- package/src/flow/utils.ts +82 -3
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +64 -1
- package/src/interfaces.ts +2 -1
- package/src/layout/Dialog.ts +53 -7
- package/src/live/TembaChart.ts +1 -1
- package/src/simulator/Simulator.ts +13 -4
- package/src/store/AppState.ts +105 -1
- package/src/store/flow-definition.d.ts +2 -0
- package/test/actions/play_audio.test.ts +155 -0
- package/test/actions/say_msg.test.ts +196 -0
- package/test/nodes/wait_for_audio.test.ts +182 -0
- package/test/nodes/wait_for_dial.test.ts +382 -0
- package/test/nodes/wait_for_digits.test.ts +233 -109
- package/test/nodes/wait_for_menu.test.ts +383 -0
- package/test/temba-flow-collision.test.ts +286 -6
- package/test/temba-node-type-selector.test.ts +6 -6
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { css, html } from 'lit';
|
|
3
3
|
import { RapidElement } from '../RapidElement';
|
|
4
|
-
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { property, state } from 'lit/decorators.js';
|
|
5
5
|
import { getClasses } from '../utils';
|
|
6
6
|
import { WebChatIcon } from '../webchat';
|
|
7
7
|
var ThumbnailContentType;
|
|
@@ -27,6 +27,11 @@ export class Thumbnail extends RapidElement {
|
|
|
27
27
|
this.preview = true;
|
|
28
28
|
// cached tile URL for location thumbnails
|
|
29
29
|
this.tileUrl = '';
|
|
30
|
+
// audio player state
|
|
31
|
+
this.audio = null;
|
|
32
|
+
this.audioPlaying = false;
|
|
33
|
+
this.audioProgress = 0;
|
|
34
|
+
this.audioDuration = 0;
|
|
30
35
|
}
|
|
31
36
|
static get styles() {
|
|
32
37
|
return css `
|
|
@@ -72,11 +77,57 @@ export class Thumbnail extends RapidElement {
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
.thumb.document,
|
|
75
|
-
.thumb.audio,
|
|
76
80
|
.thumb.video {
|
|
77
81
|
border: 1px solid #eee;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
.audio-player {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 6px;
|
|
88
|
+
padding: 6px 8px;
|
|
89
|
+
background: rgba(0, 0, 0, 0.05);
|
|
90
|
+
border-radius: var(--curvature);
|
|
91
|
+
cursor: default;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.audio-play-btn {
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
color: #666;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.audio-play-btn:hover {
|
|
103
|
+
color: #333;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.audio-progress-bar {
|
|
107
|
+
flex: 1;
|
|
108
|
+
height: 3px;
|
|
109
|
+
background: #ddd;
|
|
110
|
+
border-radius: 2px;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
min-width: 60px;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.audio-progress-fill {
|
|
117
|
+
height: 100%;
|
|
118
|
+
background: var(--color-primary, #2387ca);
|
|
119
|
+
border-radius: 2px;
|
|
120
|
+
transition: width 0.15s linear;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.audio-time {
|
|
124
|
+
font-size: 11px;
|
|
125
|
+
color: #999;
|
|
126
|
+
flex-shrink: 0;
|
|
127
|
+
min-width: 28px;
|
|
128
|
+
text-align: right;
|
|
129
|
+
}
|
|
130
|
+
|
|
80
131
|
.wrapper:hover .thumb.icon {
|
|
81
132
|
}
|
|
82
133
|
|
|
@@ -109,6 +160,58 @@ export class Thumbnail extends RapidElement {
|
|
|
109
160
|
}
|
|
110
161
|
`;
|
|
111
162
|
}
|
|
163
|
+
handleAudioPlayClick(e) {
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
if (!this.audio) {
|
|
166
|
+
this.audio = new Audio(this.url);
|
|
167
|
+
this.audio.addEventListener('timeupdate', () => {
|
|
168
|
+
if (this.audio.duration) {
|
|
169
|
+
this.audioProgress = this.audio.currentTime / this.audio.duration;
|
|
170
|
+
this.audioDuration = this.audio.duration;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
this.audio.addEventListener('ended', () => {
|
|
174
|
+
this.audioPlaying = false;
|
|
175
|
+
this.audioProgress = 0;
|
|
176
|
+
});
|
|
177
|
+
this.audio.addEventListener('error', () => {
|
|
178
|
+
this.audioPlaying = false;
|
|
179
|
+
this.audioProgress = 0;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (this.audioPlaying) {
|
|
183
|
+
this.audio.pause();
|
|
184
|
+
this.audioPlaying = false;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.audio.play().catch(() => {
|
|
188
|
+
this.audioPlaying = false;
|
|
189
|
+
});
|
|
190
|
+
this.audioPlaying = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
handleProgressClick(e) {
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
if (!this.audio || !this.audio.duration)
|
|
196
|
+
return;
|
|
197
|
+
const bar = e.currentTarget;
|
|
198
|
+
const rect = bar.getBoundingClientRect();
|
|
199
|
+
const pct = (e.clientX - rect.left) / rect.width;
|
|
200
|
+
this.audio.currentTime = pct * this.audio.duration;
|
|
201
|
+
}
|
|
202
|
+
formatTime(seconds) {
|
|
203
|
+
const s = Math.floor(seconds);
|
|
204
|
+
const m = Math.floor(s / 60);
|
|
205
|
+
const rem = s % 60;
|
|
206
|
+
return `${m}:${rem.toString().padStart(2, '0')}`;
|
|
207
|
+
}
|
|
208
|
+
disconnectedCallback() {
|
|
209
|
+
super.disconnectedCallback();
|
|
210
|
+
if (this.audio) {
|
|
211
|
+
this.audio.pause();
|
|
212
|
+
this.audio = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
112
215
|
// convert lat/lng to tile coordinates for OSM
|
|
113
216
|
latLngToTile(lat, lng, zoom) {
|
|
114
217
|
const n = Math.pow(2, zoom);
|
|
@@ -198,6 +301,9 @@ export class Thumbnail extends RapidElement {
|
|
|
198
301
|
const osmUrl = `https://www.openstreetmap.org/?mlat=${this.latitude}&mlon=${this.longitude}#map=15/${this.latitude}/${this.longitude}`;
|
|
199
302
|
window.open(osmUrl, '_blank');
|
|
200
303
|
}
|
|
304
|
+
else if (this.contentType === ThumbnailContentType.AUDIO) {
|
|
305
|
+
// audio has inline controls, no click action needed
|
|
306
|
+
}
|
|
201
307
|
else {
|
|
202
308
|
window.open(this.url, '_blank');
|
|
203
309
|
}
|
|
@@ -209,10 +315,14 @@ export class Thumbnail extends RapidElement {
|
|
|
209
315
|
window.open(this.url, '_blank');
|
|
210
316
|
}
|
|
211
317
|
render() {
|
|
318
|
+
var _a;
|
|
212
319
|
return html `
|
|
213
320
|
<div
|
|
214
321
|
@click=${this.handleThumbnailClicked.bind(this)}
|
|
215
322
|
class="${getClasses({ wrapper: true, zoom: this.zoom })}"
|
|
323
|
+
style="${this.contentType === ThumbnailContentType.AUDIO
|
|
324
|
+
? 'cursor: default;'
|
|
325
|
+
: ''}"
|
|
216
326
|
url=${this.url}
|
|
217
327
|
>
|
|
218
328
|
${this.contentType === ThumbnailContentType.IMAGE && this.preview
|
|
@@ -220,14 +330,53 @@ export class Thumbnail extends RapidElement {
|
|
|
220
330
|
class="observe thumb ${this.contentType}"
|
|
221
331
|
src="${this.url}"
|
|
222
332
|
></img></div>`
|
|
223
|
-
:
|
|
333
|
+
: this.contentType === ThumbnailContentType.AUDIO
|
|
334
|
+
? html `<div class="audio-player">
|
|
335
|
+
<div class="audio-play-btn" @click=${this.handleAudioPlayClick}>
|
|
336
|
+
${this.audioPlaying
|
|
337
|
+
? html `<svg
|
|
338
|
+
viewBox="0 0 24 24"
|
|
339
|
+
width="14"
|
|
340
|
+
height="14"
|
|
341
|
+
fill="currentColor"
|
|
342
|
+
>
|
|
343
|
+
<rect x="5" y="3" width="4" height="18" />
|
|
344
|
+
<rect x="15" y="3" width="4" height="18" />
|
|
345
|
+
</svg>`
|
|
346
|
+
: html `<svg
|
|
347
|
+
viewBox="0 0 24 24"
|
|
348
|
+
width="14"
|
|
349
|
+
height="14"
|
|
350
|
+
fill="currentColor"
|
|
351
|
+
>
|
|
352
|
+
<polygon points="6,3 20,12 6,21" />
|
|
353
|
+
</svg>`}
|
|
354
|
+
</div>
|
|
355
|
+
<div
|
|
356
|
+
class="audio-progress-bar"
|
|
357
|
+
@click=${this.handleProgressClick}
|
|
358
|
+
>
|
|
359
|
+
<div
|
|
360
|
+
class="audio-progress-fill"
|
|
361
|
+
style="width: ${this.audioProgress * 100}%"
|
|
362
|
+
></div>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="audio-time">
|
|
365
|
+
${this.audioDuration
|
|
366
|
+
? this.formatTime(this.audioPlaying || this.audioProgress > 0
|
|
367
|
+
? ((_a = this.audio) === null || _a === void 0 ? void 0 : _a.currentTime) || 0
|
|
368
|
+
: this.audioDuration)
|
|
369
|
+
: ''}
|
|
370
|
+
</div>
|
|
371
|
+
</div>`
|
|
372
|
+
: html `
|
|
224
373
|
${this.contentType === ThumbnailContentType.LOCATION
|
|
225
|
-
|
|
374
|
+
? html `<img
|
|
226
375
|
style="height:125px;margin-bottom:-3px;border-radius:var(--curvature);"
|
|
227
376
|
src="${this.tileUrl}"
|
|
228
377
|
alt="Location preview"
|
|
229
378
|
/>`
|
|
230
|
-
|
|
379
|
+
: html `<div
|
|
231
380
|
style="padding:1em; background:rgba(0,0,0,.05);border-radius:var(--curvature);"
|
|
232
381
|
>
|
|
233
382
|
<temba-icon
|
|
@@ -267,4 +416,13 @@ __decorate([
|
|
|
267
416
|
__decorate([
|
|
268
417
|
property({ type: String, attribute: false })
|
|
269
418
|
], Thumbnail.prototype, "tileUrl", void 0);
|
|
419
|
+
__decorate([
|
|
420
|
+
state()
|
|
421
|
+
], Thumbnail.prototype, "audioPlaying", void 0);
|
|
422
|
+
__decorate([
|
|
423
|
+
state()
|
|
424
|
+
], Thumbnail.prototype, "audioProgress", void 0);
|
|
425
|
+
__decorate([
|
|
426
|
+
state()
|
|
427
|
+
], Thumbnail.prototype, "audioDuration", void 0);
|
|
270
428
|
//# sourceMappingURL=Thumbnail.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thumbnail.js","sourceRoot":"","sources":["../../../src/display/Thumbnail.ts"],"names":[],"mappings":"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","sourcesContent":["import { PropertyValueMap, css, html } from 'lit';\nimport { RapidElement } from '../RapidElement';\nimport { property } from 'lit/decorators.js';\nimport { getClasses } from '../utils';\nimport { Lightbox } from './Lightbox';\nimport { WebChatIcon } from '../webchat';\n\nenum ThumbnailContentType {\n IMAGE = 'image',\n AUDIO = 'audio',\n VIDEO = 'video',\n DOCUMENT = 'document',\n LOCATION = 'location',\n OTHER = 'other'\n}\n\nconst ThumbnailIcons = {\n [ThumbnailContentType.IMAGE]: WebChatIcon.attachment_image,\n [ThumbnailContentType.AUDIO]: WebChatIcon.attachment_audio,\n [ThumbnailContentType.VIDEO]: WebChatIcon.attachment_video,\n [ThumbnailContentType.DOCUMENT]: WebChatIcon.attachment_document,\n [ThumbnailContentType.OTHER]: WebChatIcon.attachment\n};\n\nexport class Thumbnail extends RapidElement {\n static get styles() {\n return css`\n :host {\n display: inline;\n }\n\n .wrapper {\n padding: var(--thumb-padding, 0.4em);\n background: var(--thumb-background, #fff);\n box-shadow: var(--widget-box-shadow);\n cursor: pointer;\n border-radius: calc(var(--curvature) * 1.5);\n border: 0px solid #f3f3f3;\n }\n\n .wrapper.zoom {\n border: none;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: hidden !important;\n }\n\n .zoom .thumb {\n border-radius: 0px !important;\n width: calc(var(--thumb-size, 4em) + 0.8em);\n max-height: calc(90vh - 10em);\n }\n\n .thumb {\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n border-radius: var(--curvature);\n max-height: calc(var(--thumb-size, 4em) * 2);\n max-width: calc(var(--thumb-size, 4em) * 2);\n height: var(--thumb-size, 4em);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 400;\n color: var(--thumb-icon, #bbb);\n }\n\n .thumb.document,\n .thumb.audio,\n .thumb.video {\n border: 1px solid #eee;\n }\n\n .wrapper:hover .thumb.icon {\n }\n\n .viewer {\n display: block;\n }\n\n .zoom .viewer {\n display: block;\n }\n\n .download {\n display: none;\n position: absolute;\n right: 0em;\n bottom: 0em;\n border-radius: var(--curvature);\n transform: scale(0.2) translate(3em, 3em);\n padding: 0.4em;\n }\n\n .zoom .download {\n display: block;\n background: rgba(0, 0, 0, 0.5);\n }\n\n .zoom .download:hover {\n background: rgba(0, 0, 0, 0.6);\n cursor: pointer;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n @property({ type: String })\n attachment: string;\n\n @property({ type: Number })\n ratio: number = 0;\n\n @property({ type: Boolean })\n preview: boolean = true;\n\n @property({ type: Boolean, attribute: false })\n zoom: boolean;\n\n @property({ type: String, attribute: true })\n contentType: string;\n\n @property({ type: Number, attribute: false })\n latitude: number;\n\n @property({ type: Number, attribute: false })\n longitude: number;\n\n // cached tile URL for location thumbnails\n @property({ type: String, attribute: false })\n private tileUrl: string = '';\n\n // convert lat/lng to tile coordinates for OSM\n private latLngToTile(lat: number, lng: number, zoom: number) {\n const n = Math.pow(2, zoom);\n const x = Math.floor(((lng + 180) / 360) * n);\n const latRad = (lat * Math.PI) / 180;\n const y = Math.floor(\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) *\n n\n );\n return { x, y, z: zoom };\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n\n if (\n changes.has('contentType') &&\n this.contentType === ThumbnailContentType.IMAGE\n ) {\n const toObserve = this.shadowRoot.querySelector('.observe');\n if (toObserve) {\n new ResizeObserver((e, observer) => {\n if (toObserve.clientHeight > 0 && toObserve.clientWidth > 0) {\n this.ratio = toObserve.clientHeight / toObserve.clientWidth;\n this.preview =\n this.ratio === 0 || (this.ratio > 0.25 && this.ratio <= 1.5);\n observer.disconnect();\n }\n }).observe(toObserve);\n }\n }\n\n // convert our attachment to a url and label\n if (changes.has('attachment')) {\n if (this.attachment) {\n const splitIndex = this.attachment.indexOf(':');\n if (splitIndex === -1) {\n this.url = this.attachment;\n } else {\n const contentType = this.attachment.substring(0, splitIndex);\n this.url = this.attachment.substring(splitIndex + 1);\n\n if (contentType.startsWith('image')) {\n this.contentType = ThumbnailContentType.IMAGE;\n } else if (contentType.startsWith('audio')) {\n this.contentType = ThumbnailContentType.AUDIO;\n } else if (contentType.startsWith('video')) {\n this.contentType = ThumbnailContentType.VIDEO;\n } else if (contentType.startsWith('application')) {\n this.contentType = ThumbnailContentType.DOCUMENT;\n } else if (contentType.startsWith('geo')) {\n this.contentType = ThumbnailContentType.LOCATION;\n // Parse coordinates from URL which is already stripped of \"geo:\" prefix\n // Format is now just: lat,lng\n const coords = this.url.match(/^([^,]+),([^,]+)/);\n if (coords) {\n this.latitude = parseFloat(coords[1]);\n this.longitude = parseFloat(coords[2]);\n }\n } else {\n this.contentType = ThumbnailContentType.OTHER;\n }\n }\n }\n }\n\n // calculate tile URL when latitude/longitude changes\n if (changes.has('latitude') || changes.has('longitude')) {\n if (\n this.latitude !== undefined &&\n this.longitude !== undefined &&\n !isNaN(this.latitude) &&\n !isNaN(this.longitude)\n ) {\n const tile = this.latLngToTile(this.latitude, this.longitude, 13);\n this.tileUrl = `https://tile.openstreetmap.org/${tile.z}/${tile.x}/${tile.y}.png`;\n } else {\n this.tileUrl = '';\n }\n }\n }\n\n public handleThumbnailClicked() {\n if (this.contentType === ThumbnailContentType.IMAGE && this.preview) {\n window.setTimeout(() => {\n const lightbox = document.querySelector('temba-lightbox') as Lightbox;\n lightbox.showElement(this);\n }, 100);\n } else if (this.contentType === ThumbnailContentType.LOCATION) {\n // open location in openstreetmap\n const osmUrl = `https://www.openstreetmap.org/?mlat=${this.latitude}&mlon=${this.longitude}#map=15/${this.latitude}/${this.longitude}`;\n window.open(osmUrl, '_blank');\n } else {\n window.open(this.url, '_blank');\n }\n }\n\n public handleDownload(e: Event) {\n e.stopPropagation();\n e.preventDefault();\n\n // open this.url in another tab\n window.open(this.url, '_blank');\n }\n\n public render() {\n return html`\n <div\n @click=${this.handleThumbnailClicked.bind(this)}\n class=\"${getClasses({ wrapper: true, zoom: this.zoom })}\"\n url=${this.url}\n >\n ${this.contentType === ThumbnailContentType.IMAGE && this.preview\n ? html`<div class=\"\"><div class=\"download\" @click=${this.handleDownload.bind(\n this\n )}><temba-icon size=\"1\" style=\"color:#fff;\" name=\"download\"></temba-icon></div><img\n class=\"observe thumb ${this.contentType}\"\n src=\"${this.url}\"\n ></img></div>`\n : html`\n ${this.contentType === ThumbnailContentType.LOCATION\n ? html`<img\n style=\"height:125px;margin-bottom:-3px;border-radius:var(--curvature);\"\n src=\"${this.tileUrl}\"\n alt=\"Location preview\"\n />`\n : html`<div\n style=\"padding:1em; background:rgba(0,0,0,.05);border-radius:var(--curvature);\"\n >\n <temba-icon\n size=\"1.5\"\n name=\"${ThumbnailIcons[this.contentType]}\"\n ></temba-icon>\n </div>`}\n `}\n </div>\n `;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Thumbnail.js","sourceRoot":"","sources":["../../../src/display/Thumbnail.ts"],"names":[],"mappings":"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","sourcesContent":["import { PropertyValueMap, css, html } from 'lit';\nimport { RapidElement } from '../RapidElement';\nimport { property, state } from 'lit/decorators.js';\nimport { getClasses } from '../utils';\nimport { Lightbox } from './Lightbox';\nimport { WebChatIcon } from '../webchat';\n\nenum ThumbnailContentType {\n IMAGE = 'image',\n AUDIO = 'audio',\n VIDEO = 'video',\n DOCUMENT = 'document',\n LOCATION = 'location',\n OTHER = 'other'\n}\n\nconst ThumbnailIcons = {\n [ThumbnailContentType.IMAGE]: WebChatIcon.attachment_image,\n [ThumbnailContentType.AUDIO]: WebChatIcon.attachment_audio,\n [ThumbnailContentType.VIDEO]: WebChatIcon.attachment_video,\n [ThumbnailContentType.DOCUMENT]: WebChatIcon.attachment_document,\n [ThumbnailContentType.OTHER]: WebChatIcon.attachment\n};\n\nexport class Thumbnail extends RapidElement {\n static get styles() {\n return css`\n :host {\n display: inline;\n }\n\n .wrapper {\n padding: var(--thumb-padding, 0.4em);\n background: var(--thumb-background, #fff);\n box-shadow: var(--widget-box-shadow);\n cursor: pointer;\n border-radius: calc(var(--curvature) * 1.5);\n border: 0px solid #f3f3f3;\n }\n\n .wrapper.zoom {\n border: none;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: hidden !important;\n }\n\n .zoom .thumb {\n border-radius: 0px !important;\n width: calc(var(--thumb-size, 4em) + 0.8em);\n max-height: calc(90vh - 10em);\n }\n\n .thumb {\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n border-radius: var(--curvature);\n max-height: calc(var(--thumb-size, 4em) * 2);\n max-width: calc(var(--thumb-size, 4em) * 2);\n height: var(--thumb-size, 4em);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 400;\n color: var(--thumb-icon, #bbb);\n }\n\n .thumb.document,\n .thumb.video {\n border: 1px solid #eee;\n }\n\n .audio-player {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n background: rgba(0, 0, 0, 0.05);\n border-radius: var(--curvature);\n cursor: default;\n }\n\n .audio-play-btn {\n cursor: pointer;\n color: #666;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .audio-play-btn:hover {\n color: #333;\n }\n\n .audio-progress-bar {\n flex: 1;\n height: 3px;\n background: #ddd;\n border-radius: 2px;\n overflow: hidden;\n min-width: 60px;\n cursor: pointer;\n }\n\n .audio-progress-fill {\n height: 100%;\n background: var(--color-primary, #2387ca);\n border-radius: 2px;\n transition: width 0.15s linear;\n }\n\n .audio-time {\n font-size: 11px;\n color: #999;\n flex-shrink: 0;\n min-width: 28px;\n text-align: right;\n }\n\n .wrapper:hover .thumb.icon {\n }\n\n .viewer {\n display: block;\n }\n\n .zoom .viewer {\n display: block;\n }\n\n .download {\n display: none;\n position: absolute;\n right: 0em;\n bottom: 0em;\n border-radius: var(--curvature);\n transform: scale(0.2) translate(3em, 3em);\n padding: 0.4em;\n }\n\n .zoom .download {\n display: block;\n background: rgba(0, 0, 0, 0.5);\n }\n\n .zoom .download:hover {\n background: rgba(0, 0, 0, 0.6);\n cursor: pointer;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n @property({ type: String })\n attachment: string;\n\n @property({ type: Number })\n ratio: number = 0;\n\n @property({ type: Boolean })\n preview: boolean = true;\n\n @property({ type: Boolean, attribute: false })\n zoom: boolean;\n\n @property({ type: String, attribute: true })\n contentType: string;\n\n @property({ type: Number, attribute: false })\n latitude: number;\n\n @property({ type: Number, attribute: false })\n longitude: number;\n\n // cached tile URL for location thumbnails\n @property({ type: String, attribute: false })\n private tileUrl: string = '';\n\n // audio player state\n private audio: HTMLAudioElement | null = null;\n\n @state()\n private audioPlaying = false;\n\n @state()\n private audioProgress = 0;\n\n @state()\n private audioDuration = 0;\n\n private handleAudioPlayClick(e: Event) {\n e.stopPropagation();\n\n if (!this.audio) {\n this.audio = new Audio(this.url);\n this.audio.addEventListener('timeupdate', () => {\n if (this.audio.duration) {\n this.audioProgress = this.audio.currentTime / this.audio.duration;\n this.audioDuration = this.audio.duration;\n }\n });\n this.audio.addEventListener('ended', () => {\n this.audioPlaying = false;\n this.audioProgress = 0;\n });\n this.audio.addEventListener('error', () => {\n this.audioPlaying = false;\n this.audioProgress = 0;\n });\n }\n\n if (this.audioPlaying) {\n this.audio.pause();\n this.audioPlaying = false;\n } else {\n this.audio.play().catch(() => {\n this.audioPlaying = false;\n });\n this.audioPlaying = true;\n }\n }\n\n private handleProgressClick(e: MouseEvent) {\n e.stopPropagation();\n if (!this.audio || !this.audio.duration) return;\n const bar = e.currentTarget as HTMLElement;\n const rect = bar.getBoundingClientRect();\n const pct = (e.clientX - rect.left) / rect.width;\n this.audio.currentTime = pct * this.audio.duration;\n }\n\n private formatTime(seconds: number): string {\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const rem = s % 60;\n return `${m}:${rem.toString().padStart(2, '0')}`;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.audio) {\n this.audio.pause();\n this.audio = null;\n }\n }\n\n // convert lat/lng to tile coordinates for OSM\n private latLngToTile(lat: number, lng: number, zoom: number) {\n const n = Math.pow(2, zoom);\n const x = Math.floor(((lng + 180) / 360) * n);\n const latRad = (lat * Math.PI) / 180;\n const y = Math.floor(\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) *\n n\n );\n return { x, y, z: zoom };\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n\n if (\n changes.has('contentType') &&\n this.contentType === ThumbnailContentType.IMAGE\n ) {\n const toObserve = this.shadowRoot.querySelector('.observe');\n if (toObserve) {\n new ResizeObserver((e, observer) => {\n if (toObserve.clientHeight > 0 && toObserve.clientWidth > 0) {\n this.ratio = toObserve.clientHeight / toObserve.clientWidth;\n this.preview =\n this.ratio === 0 || (this.ratio > 0.25 && this.ratio <= 1.5);\n observer.disconnect();\n }\n }).observe(toObserve);\n }\n }\n\n // convert our attachment to a url and label\n if (changes.has('attachment')) {\n if (this.attachment) {\n const splitIndex = this.attachment.indexOf(':');\n if (splitIndex === -1) {\n this.url = this.attachment;\n } else {\n const contentType = this.attachment.substring(0, splitIndex);\n this.url = this.attachment.substring(splitIndex + 1);\n\n if (contentType.startsWith('image')) {\n this.contentType = ThumbnailContentType.IMAGE;\n } else if (contentType.startsWith('audio')) {\n this.contentType = ThumbnailContentType.AUDIO;\n } else if (contentType.startsWith('video')) {\n this.contentType = ThumbnailContentType.VIDEO;\n } else if (contentType.startsWith('application')) {\n this.contentType = ThumbnailContentType.DOCUMENT;\n } else if (contentType.startsWith('geo')) {\n this.contentType = ThumbnailContentType.LOCATION;\n // Parse coordinates from URL which is already stripped of \"geo:\" prefix\n // Format is now just: lat,lng\n const coords = this.url.match(/^([^,]+),([^,]+)/);\n if (coords) {\n this.latitude = parseFloat(coords[1]);\n this.longitude = parseFloat(coords[2]);\n }\n } else {\n this.contentType = ThumbnailContentType.OTHER;\n }\n }\n }\n }\n\n // calculate tile URL when latitude/longitude changes\n if (changes.has('latitude') || changes.has('longitude')) {\n if (\n this.latitude !== undefined &&\n this.longitude !== undefined &&\n !isNaN(this.latitude) &&\n !isNaN(this.longitude)\n ) {\n const tile = this.latLngToTile(this.latitude, this.longitude, 13);\n this.tileUrl = `https://tile.openstreetmap.org/${tile.z}/${tile.x}/${tile.y}.png`;\n } else {\n this.tileUrl = '';\n }\n }\n }\n\n public handleThumbnailClicked() {\n if (this.contentType === ThumbnailContentType.IMAGE && this.preview) {\n window.setTimeout(() => {\n const lightbox = document.querySelector('temba-lightbox') as Lightbox;\n lightbox.showElement(this);\n }, 100);\n } else if (this.contentType === ThumbnailContentType.LOCATION) {\n // open location in openstreetmap\n const osmUrl = `https://www.openstreetmap.org/?mlat=${this.latitude}&mlon=${this.longitude}#map=15/${this.latitude}/${this.longitude}`;\n window.open(osmUrl, '_blank');\n } else if (this.contentType === ThumbnailContentType.AUDIO) {\n // audio has inline controls, no click action needed\n } else {\n window.open(this.url, '_blank');\n }\n }\n\n public handleDownload(e: Event) {\n e.stopPropagation();\n e.preventDefault();\n\n // open this.url in another tab\n window.open(this.url, '_blank');\n }\n\n public render() {\n return html`\n <div\n @click=${this.handleThumbnailClicked.bind(this)}\n class=\"${getClasses({ wrapper: true, zoom: this.zoom })}\"\n style=\"${this.contentType === ThumbnailContentType.AUDIO\n ? 'cursor: default;'\n : ''}\"\n url=${this.url}\n >\n ${this.contentType === ThumbnailContentType.IMAGE && this.preview\n ? html`<div class=\"\"><div class=\"download\" @click=${this.handleDownload.bind(\n this\n )}><temba-icon size=\"1\" style=\"color:#fff;\" name=\"download\"></temba-icon></div><img\n class=\"observe thumb ${this.contentType}\"\n src=\"${this.url}\"\n ></img></div>`\n : this.contentType === ThumbnailContentType.AUDIO\n ? html`<div class=\"audio-player\">\n <div class=\"audio-play-btn\" @click=${this.handleAudioPlayClick}>\n ${this.audioPlaying\n ? html`<svg\n viewBox=\"0 0 24 24\"\n width=\"14\"\n height=\"14\"\n fill=\"currentColor\"\n >\n <rect x=\"5\" y=\"3\" width=\"4\" height=\"18\" />\n <rect x=\"15\" y=\"3\" width=\"4\" height=\"18\" />\n </svg>`\n : html`<svg\n viewBox=\"0 0 24 24\"\n width=\"14\"\n height=\"14\"\n fill=\"currentColor\"\n >\n <polygon points=\"6,3 20,12 6,21\" />\n </svg>`}\n </div>\n <div\n class=\"audio-progress-bar\"\n @click=${this.handleProgressClick}\n >\n <div\n class=\"audio-progress-fill\"\n style=\"width: ${this.audioProgress * 100}%\"\n ></div>\n </div>\n <div class=\"audio-time\">\n ${this.audioDuration\n ? this.formatTime(\n this.audioPlaying || this.audioProgress > 0\n ? this.audio?.currentTime || 0\n : this.audioDuration\n )\n : ''}\n </div>\n </div>`\n : html`\n ${this.contentType === ThumbnailContentType.LOCATION\n ? html`<img\n style=\"height:125px;margin-bottom:-3px;border-radius:var(--curvature);\"\n src=\"${this.tileUrl}\"\n alt=\"Location preview\"\n />`\n : html`<div\n style=\"padding:1em; background:rgba(0,0,0,.05);border-radius:var(--curvature);\"\n >\n <temba-icon\n size=\"1.5\"\n name=\"${ThumbnailIcons[this.contentType]}\"\n ></temba-icon>\n </div>`}\n `}\n </div>\n `;\n }\n}\n"]}
|
|
@@ -111,6 +111,12 @@ export class CanvasNode extends RapidElement {
|
|
|
111
111
|
pointer-events: none !important;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/* Issue indicators - hatched red title bar */
|
|
115
|
+
.action-content.has-issues .cn-title,
|
|
116
|
+
.node.has-issues > .router .cn-title {
|
|
117
|
+
background: repeating-linear-gradient(120deg, tomato, tomato 6px, #ff7056 0, #ff7056 18px) !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
.action.sortable {
|
|
115
121
|
display: flex;
|
|
116
122
|
align-items: stretch;
|
|
@@ -472,10 +478,14 @@ export class CanvasNode extends RapidElement {
|
|
|
472
478
|
// make our initial connections
|
|
473
479
|
// We use setTimeout to allow for DOM updates to complete before querying for exits
|
|
474
480
|
this.connectionTimeout = window.setTimeout(() => {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
481
|
+
var _b;
|
|
482
|
+
// Terminal nodes have no visible exits
|
|
483
|
+
if (((_b = this.ui) === null || _b === void 0 ? void 0 : _b.type) !== 'terminal') {
|
|
484
|
+
for (const exit of this.node.exits) {
|
|
485
|
+
this.plumber.makeSource(exit.uuid);
|
|
486
|
+
if (exit.destination_uuid) {
|
|
487
|
+
this.plumber.connectIds(this.node.uuid, exit.uuid, exit.destination_uuid);
|
|
488
|
+
}
|
|
479
489
|
}
|
|
480
490
|
}
|
|
481
491
|
// Note: revalidation is handled by plumber's processPendingConnections which calls repaintEverything
|
|
@@ -771,6 +781,10 @@ export class CanvasNode extends RapidElement {
|
|
|
771
781
|
}
|
|
772
782
|
this.requestUpdate();
|
|
773
783
|
}
|
|
784
|
+
getTopCenter(el) {
|
|
785
|
+
const rect = el.getBoundingClientRect();
|
|
786
|
+
return { x: rect.left + rect.width / 2, y: rect.top };
|
|
787
|
+
}
|
|
774
788
|
handleActionMouseDown(event, action) {
|
|
775
789
|
// Don't handle clicks on the remove button, drag handle, or when action is in removing state
|
|
776
790
|
const target = event.target;
|
|
@@ -812,10 +826,17 @@ export class CanvasNode extends RapidElement {
|
|
|
812
826
|
// Only fire the action edit event if we haven't dragged beyond the threshold
|
|
813
827
|
// AND either there's no Editor parent (test case) or the Editor didn't drag the node
|
|
814
828
|
if (distance <= DRAG_THRESHOLD && (!editor || !editorWasDragging)) {
|
|
829
|
+
// Use top-center of the action element as the dialog origin
|
|
830
|
+
const actionEl = event.currentTarget;
|
|
831
|
+
const origin = actionEl
|
|
832
|
+
? this.getTopCenter(actionEl)
|
|
833
|
+
: { x: event.clientX, y: event.clientY };
|
|
815
834
|
// Fire event to request action editing
|
|
816
835
|
this.fireCustomEvent(CustomEventType.ActionEditRequested, {
|
|
817
836
|
action,
|
|
818
|
-
nodeUuid: this.node.uuid
|
|
837
|
+
nodeUuid: this.node.uuid,
|
|
838
|
+
originX: origin.x,
|
|
839
|
+
originY: origin.y
|
|
819
840
|
});
|
|
820
841
|
}
|
|
821
842
|
}
|
|
@@ -914,18 +935,24 @@ export class CanvasNode extends RapidElement {
|
|
|
914
935
|
// Using literal 5 instead of DRAG_THRESHOLD since it's not imported
|
|
915
936
|
// Fire event to request node editing if the node has a router
|
|
916
937
|
if (this.node.router) {
|
|
938
|
+
// Use top-center of the node as the dialog origin
|
|
939
|
+
const origin = this.getTopCenter(this);
|
|
917
940
|
// If router node has exactly one action, open the action editor directly
|
|
918
941
|
if (this.node.actions && this.node.actions.length === 1) {
|
|
919
942
|
this.fireCustomEvent(CustomEventType.ActionEditRequested, {
|
|
920
943
|
action: this.node.actions[0],
|
|
921
|
-
nodeUuid: this.node.uuid
|
|
944
|
+
nodeUuid: this.node.uuid,
|
|
945
|
+
originX: origin.x,
|
|
946
|
+
originY: origin.y
|
|
922
947
|
});
|
|
923
948
|
}
|
|
924
949
|
else {
|
|
925
950
|
// Otherwise open the node editor as before
|
|
926
951
|
this.fireCustomEvent(CustomEventType.NodeEditRequested, {
|
|
927
952
|
node: this.node,
|
|
928
|
-
nodeUI: this.ui
|
|
953
|
+
nodeUI: this.ui,
|
|
954
|
+
originX: origin.x,
|
|
955
|
+
originY: origin.y
|
|
929
956
|
});
|
|
930
957
|
}
|
|
931
958
|
}
|
|
@@ -1061,17 +1088,15 @@ export class CanvasNode extends RapidElement {
|
|
|
1061
1088
|
this.requestUpdate();
|
|
1062
1089
|
}
|
|
1063
1090
|
renderTitle(config, action, index, isRemoving = false) {
|
|
1064
|
-
var _b, _c, _d, _e;
|
|
1091
|
+
var _b, _c, _d, _e, _f;
|
|
1065
1092
|
const color = config.group
|
|
1066
1093
|
? (_b = ACTION_GROUP_METADATA[config.group]) === null || _b === void 0 ? void 0 : _b.color
|
|
1067
1094
|
: '#aaaaaa';
|
|
1095
|
+
const isTerminal = ((_c = this.ui) === null || _c === void 0 ? void 0 : _c.type) === 'terminal';
|
|
1068
1096
|
return html `<div class="cn-title" style="background:${color}">
|
|
1069
|
-
${
|
|
1070
|
-
? html `<
|
|
1071
|
-
|
|
1072
|
-
name="sort"
|
|
1073
|
-
></temba-icon>`
|
|
1074
|
-
: ((_e = (_d = this.node) === null || _d === void 0 ? void 0 : _d.actions) === null || _e === void 0 ? void 0 : _e.length) > 1
|
|
1097
|
+
${isTerminal
|
|
1098
|
+
? html `<div class="title-spacer"></div>`
|
|
1099
|
+
: ((_d = this.ui) === null || _d === void 0 ? void 0 : _d.type) === 'execute_actions' || ((_f = (_e = this.node) === null || _e === void 0 ? void 0 : _e.actions) === null || _f === void 0 ? void 0 : _f.length) > 1
|
|
1075
1100
|
? html `<temba-icon
|
|
1076
1101
|
class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
|
|
1077
1102
|
name="sort"
|
|
@@ -1080,7 +1105,9 @@ export class CanvasNode extends RapidElement {
|
|
|
1080
1105
|
|
|
1081
1106
|
<div class="name">${isRemoving ? 'Remove?' : config.name}</div>
|
|
1082
1107
|
<div
|
|
1083
|
-
class="remove-button ${this.isReadOnly()
|
|
1108
|
+
class="remove-button ${isTerminal || this.isReadOnly()
|
|
1109
|
+
? 'read-only-hidden'
|
|
1110
|
+
: ''}"
|
|
1084
1111
|
@click=${(e) => this.handleActionRemoveClick(e, action, index)}
|
|
1085
1112
|
title="Remove action"
|
|
1086
1113
|
>
|
|
@@ -1165,7 +1192,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1165
1192
|
return localizedAction;
|
|
1166
1193
|
}
|
|
1167
1194
|
renderAction(node, action, index) {
|
|
1168
|
-
var _b, _c, _d;
|
|
1195
|
+
var _b, _c, _d, _e;
|
|
1169
1196
|
const config = ACTION_CONFIG[action.type];
|
|
1170
1197
|
const isRemoving = this.actionRemovingState.has(action.uuid);
|
|
1171
1198
|
const isLocalizable = (config === null || config === void 0 ? void 0 : config.localizable) && config.localizable.length > 0;
|
|
@@ -1176,6 +1203,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1176
1203
|
// Get the localized action if translating
|
|
1177
1204
|
const displayAction = this.getLocalizedAction(action);
|
|
1178
1205
|
if (config) {
|
|
1206
|
+
const hasIssues = (_e = this.issuesByAction) === null || _e === void 0 ? void 0 : _e.has(action.uuid);
|
|
1179
1207
|
const classes = [
|
|
1180
1208
|
'action',
|
|
1181
1209
|
'sortable',
|
|
@@ -1189,7 +1217,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1189
1217
|
.join(' ');
|
|
1190
1218
|
return html `<div class="${classes}" id="action-${index}">
|
|
1191
1219
|
<div
|
|
1192
|
-
class="action-content"
|
|
1220
|
+
class="action-content ${hasIssues ? 'has-issues' : ''}"
|
|
1193
1221
|
@mousedown=${(e) => !isDisabled && this.handleActionMouseDown(e, action)}
|
|
1194
1222
|
@mouseup=${(e) => !isDisabled && this.handleActionMouseUp(e, action)}
|
|
1195
1223
|
style="cursor: ${isDisabled ? 'not-allowed' : 'pointer'}"
|
|
@@ -1320,7 +1348,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1320
1348
|
return this.viewingRevision || this.isTranslating;
|
|
1321
1349
|
}
|
|
1322
1350
|
render() {
|
|
1323
|
-
var _b;
|
|
1351
|
+
var _b, _c, _d, _e;
|
|
1324
1352
|
if (!this.node || !this.ui) {
|
|
1325
1353
|
return html `<div class="node">Loading...</div>`;
|
|
1326
1354
|
}
|
|
@@ -1332,13 +1360,17 @@ export class CanvasNode extends RapidElement {
|
|
|
1332
1360
|
!this.includeCategoriesInTranslation;
|
|
1333
1361
|
// Get active contact count for this node
|
|
1334
1362
|
const activeCount = (((_b = this.activity) === null || _b === void 0 ? void 0 : _b.nodes) && this.activity.nodes[this.node.uuid]) || 0;
|
|
1363
|
+
// Check for node-level issues or action-level issues on any action in this node
|
|
1364
|
+
const nodeHasIssues = ((_c = this.issuesByNode) === null || _c === void 0 ? void 0 : _c.has(this.node.uuid)) ||
|
|
1365
|
+
((_d = this.node.actions) === null || _d === void 0 ? void 0 : _d.some((a) => { var _b; return (_b = this.issuesByAction) === null || _b === void 0 ? void 0 : _b.has(a.uuid); }));
|
|
1335
1366
|
return html `
|
|
1336
1367
|
<div
|
|
1337
1368
|
id="${this.node.uuid}"
|
|
1338
1369
|
class=${getClasses({
|
|
1339
1370
|
node: true,
|
|
1340
1371
|
'execute-actions': this.ui.type === 'execute_actions',
|
|
1341
|
-
'non-localizable': isNodeDisabled
|
|
1372
|
+
'non-localizable': isNodeDisabled,
|
|
1373
|
+
'has-issues': nodeHasIssues
|
|
1342
1374
|
})}
|
|
1343
1375
|
style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
|
|
1344
1376
|
>
|
|
@@ -1347,7 +1379,9 @@ export class CanvasNode extends RapidElement {
|
|
|
1347
1379
|
${activeCount.toLocaleString()}
|
|
1348
1380
|
</div>`
|
|
1349
1381
|
: ''}
|
|
1350
|
-
${nodeConfig &&
|
|
1382
|
+
${nodeConfig &&
|
|
1383
|
+
nodeConfig.type !== 'execute_actions' &&
|
|
1384
|
+
nodeConfig.type !== 'terminal'
|
|
1351
1385
|
? html `<div class="router" style="position: relative;">
|
|
1352
1386
|
<div
|
|
1353
1387
|
@mousedown=${(e) => this.handleNodeMouseDown(e)}
|
|
@@ -1360,7 +1394,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1360
1394
|
: null}
|
|
1361
1395
|
</div>
|
|
1362
1396
|
</div>`
|
|
1363
|
-
: this.node.actions.length > 0
|
|
1397
|
+
: ((_e = this.node.actions) === null || _e === void 0 ? void 0 : _e.length) > 0
|
|
1364
1398
|
? this.ui.type === 'execute_actions'
|
|
1365
1399
|
? html `<temba-sortable-list
|
|
1366
1400
|
dragHandle="drag-handle"
|
|
@@ -1388,7 +1422,9 @@ export class CanvasNode extends RapidElement {
|
|
|
1388
1422
|
${this.renderRouter(this.node.router, this.ui)}
|
|
1389
1423
|
${this.renderCategories(this.node)}
|
|
1390
1424
|
</div>`
|
|
1391
|
-
:
|
|
1425
|
+
: this.ui.type === 'terminal'
|
|
1426
|
+
? ''
|
|
1427
|
+
: html `<div class="action-exits">
|
|
1392
1428
|
${repeat(this.node.exits, (exit) => exit.uuid, (exit) => this.renderExit(exit))}
|
|
1393
1429
|
</div>`}
|
|
1394
1430
|
${this.ui.type === 'execute_actions' && !this.isReadOnly()
|
|
@@ -1431,4 +1467,10 @@ __decorate([
|
|
|
1431
1467
|
__decorate([
|
|
1432
1468
|
fromStore(zustand, (state) => state.getCurrentActivity())
|
|
1433
1469
|
], CanvasNode.prototype, "activity", void 0);
|
|
1470
|
+
__decorate([
|
|
1471
|
+
fromStore(zustand, (state) => state.issuesByNode)
|
|
1472
|
+
], CanvasNode.prototype, "issuesByNode", void 0);
|
|
1473
|
+
__decorate([
|
|
1474
|
+
fromStore(zustand, (state) => state.issuesByAction)
|
|
1475
|
+
], CanvasNode.prototype, "issuesByAction", void 0);
|
|
1434
1476
|
//# sourceMappingURL=CanvasNode.js.map
|