@nyaruka/temba-components 0.135.9 → 0.136.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/CHANGELOG.md +16 -0
- package/demo/components/webchat/example.html +4 -2
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1323 -317
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +2 -1
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +11 -0
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +224 -2
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +320 -1
- package/out-tsc/src/flow/Plumber.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/FloatingWindow.js +30 -8
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +1827 -0
- package/out-tsc/src/simulator/Simulator.js.map +1 -0
- package/out-tsc/src/store/AppState.js +33 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils.js +48 -0
- package/out-tsc/src/utils.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-flow-editor.test.js +1 -1
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +3 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +3 -1
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-simulator.test.js +642 -0
- package/out-tsc/test/temba-simulator.test.js.map +1 -0
- package/out-tsc/test/utils.test.js +1 -1
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/simulator/after-message-sent.png +0 -0
- package/screenshots/truth/simulator/after-reset.png +0 -0
- package/screenshots/truth/simulator/attachment-menu.png +0 -0
- package/screenshots/truth/simulator/context-expanded.png +0 -0
- package/screenshots/truth/simulator/context-explorer-open.png +0 -0
- package/screenshots/truth/simulator/event-info.png +0 -0
- package/screenshots/truth/simulator/image-attachment.png +0 -0
- package/screenshots/truth/simulator/open-initial.png +0 -0
- package/screenshots/truth/simulator/quick-replies.png +0 -0
- package/src/Icons.ts +2 -1
- package/src/flow/CanvasNode.ts +12 -0
- package/src/flow/Editor.ts +240 -1
- package/src/flow/Plumber.ts +371 -2
- package/src/interfaces.ts +2 -1
- package/src/layout/FloatingWindow.ts +36 -11
- package/src/simulator/Simulator.ts +2008 -0
- package/src/store/AppState.ts +53 -0
- package/src/utils.ts +53 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/route.svg +1 -0
- package/static/svg/work/used/route.svg +3 -0
- package/temba-modules.ts +2 -0
- package/test/temba-flow-editor.test.ts +1 -1
- package/test/temba-flow-plumber-connections.test.ts +4 -1
- package/test/temba-flow-plumber.test.ts +4 -1
- package/test/temba-simulator.test.ts +866 -0
- package/test/utils.test.ts +1 -1
|
@@ -169,6 +169,125 @@ export class Editor extends RapidElement {
|
|
|
169
169
|
z-index: 10;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/* Activity overlays on connections */
|
|
173
|
+
.jtk-overlay.activity-overlay {
|
|
174
|
+
background: #f3f3f3;
|
|
175
|
+
border: 1px solid #d9d9d9;
|
|
176
|
+
color: #333;
|
|
177
|
+
border-radius: 4px;
|
|
178
|
+
padding: 2px 4px;
|
|
179
|
+
font-size: 10px;
|
|
180
|
+
font-weight: 600;
|
|
181
|
+
line-height: 0.9;
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
z-index: 500;
|
|
184
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Active contact count on nodes */
|
|
188
|
+
.active-count {
|
|
189
|
+
position: absolute;
|
|
190
|
+
background: var(--color-primary-dark, #3498db);
|
|
191
|
+
border: 1px solid var(--color-primary-darker, #2980b9);
|
|
192
|
+
border-radius: 12px;
|
|
193
|
+
padding: 3px 5px;
|
|
194
|
+
color: #fff;
|
|
195
|
+
font-weight: 500;
|
|
196
|
+
top: -10px;
|
|
197
|
+
left: -10px;
|
|
198
|
+
font-size: 13px;
|
|
199
|
+
min-width: 22px;
|
|
200
|
+
text-align: center;
|
|
201
|
+
z-index: 600;
|
|
202
|
+
line-height: 1;
|
|
203
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Recent contacts popup */
|
|
207
|
+
@keyframes popupBounceIn {
|
|
208
|
+
0% {
|
|
209
|
+
transform: scale(0.8);
|
|
210
|
+
opacity: 0;
|
|
211
|
+
}
|
|
212
|
+
50% {
|
|
213
|
+
transform: scale(1.05);
|
|
214
|
+
}
|
|
215
|
+
100% {
|
|
216
|
+
transform: scale(1);
|
|
217
|
+
opacity: 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.recent-contacts-popup {
|
|
222
|
+
display: none;
|
|
223
|
+
position: absolute;
|
|
224
|
+
width: 200px;
|
|
225
|
+
background: #f3f3f3;
|
|
226
|
+
border-radius: 10px;
|
|
227
|
+
box-shadow: 0 1px 3px 1px rgba(130, 130, 130, 0.2);
|
|
228
|
+
z-index: 1015;
|
|
229
|
+
transform-origin: top center;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.recent-contacts-popup.show {
|
|
233
|
+
display: block;
|
|
234
|
+
animation: popupBounceIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.recent-contacts-popup .popup-title {
|
|
238
|
+
background: #999;
|
|
239
|
+
color: #fff;
|
|
240
|
+
padding: 6px 0;
|
|
241
|
+
text-align: center;
|
|
242
|
+
border-top-left-radius: 10px;
|
|
243
|
+
border-top-right-radius: 10px;
|
|
244
|
+
font-size: 12px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.recent-contacts-popup .no-contacts-message {
|
|
248
|
+
padding: 15px;
|
|
249
|
+
text-align: center;
|
|
250
|
+
color: #999;
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.recent-contacts-popup .contact-row {
|
|
255
|
+
padding: 8px 10px;
|
|
256
|
+
border-top: 1px solid #e0e0e0;
|
|
257
|
+
text-align: left;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.recent-contacts-popup .contact-row:last-child {
|
|
261
|
+
border-bottom-left-radius: 10px;
|
|
262
|
+
border-bottom-right-radius: 10px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.recent-contacts-popup .contact-name {
|
|
266
|
+
display: block;
|
|
267
|
+
font-weight: 500;
|
|
268
|
+
font-size: 12px;
|
|
269
|
+
color: var(--color-link-primary, #1d4ed8);
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.recent-contacts-popup .contact-name:hover {
|
|
274
|
+
text-decoration: underline;
|
|
275
|
+
color: var(--color-link-primary, #1d4ed8);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.recent-contacts-popup .contact-operand {
|
|
279
|
+
padding-top: 3px;
|
|
280
|
+
font-size: 11px;
|
|
281
|
+
color: #666;
|
|
282
|
+
word-wrap: break-word;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.recent-contacts-popup .contact-time {
|
|
286
|
+
padding-top: 3px;
|
|
287
|
+
font-size: 10px;
|
|
288
|
+
color: #999;
|
|
289
|
+
}
|
|
290
|
+
|
|
172
291
|
/* Connection dragging feedback */
|
|
173
292
|
body svg.jtk-connector.jtk-dragging {
|
|
174
293
|
z-index: 99999 !important;
|
|
@@ -399,6 +518,8 @@ export class Editor extends RapidElement {
|
|
|
399
518
|
this.saveTimer = null;
|
|
400
519
|
this.flowType = 'message';
|
|
401
520
|
this.features = [];
|
|
521
|
+
this.activityTimer = null;
|
|
522
|
+
this.activityInterval = 100; // Start with 100ms interval for fast initial load
|
|
402
523
|
// Drag state
|
|
403
524
|
this.isDragging = false;
|
|
404
525
|
this.isMouseDown = false;
|
|
@@ -446,7 +567,7 @@ export class Editor extends RapidElement {
|
|
|
446
567
|
}
|
|
447
568
|
firstUpdated(changes) {
|
|
448
569
|
super.firstUpdated(changes);
|
|
449
|
-
this.plumber = new Plumber(this.querySelector('#canvas'));
|
|
570
|
+
this.plumber = new Plumber(this.querySelector('#canvas'), this);
|
|
450
571
|
this.setupGlobalEventListeners();
|
|
451
572
|
if (changes.has('flow')) {
|
|
452
573
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
@@ -484,7 +605,7 @@ export class Editor extends RapidElement {
|
|
|
484
605
|
this.isValidTarget = true;
|
|
485
606
|
}
|
|
486
607
|
updated(changes) {
|
|
487
|
-
var _b, _c, _d;
|
|
608
|
+
var _b, _c, _d, _e;
|
|
488
609
|
super.updated(changes);
|
|
489
610
|
if (changes.has('canvasSize')) {
|
|
490
611
|
// console.log('Setting canvas size', this.canvasSize);
|
|
@@ -505,6 +626,27 @@ export class Editor extends RapidElement {
|
|
|
505
626
|
this.translationFilters = normalizedFilters;
|
|
506
627
|
}
|
|
507
628
|
this.translationCache.clear();
|
|
629
|
+
// Start fetching activity data when definition is loaded
|
|
630
|
+
if ((_e = this.definition) === null || _e === void 0 ? void 0 : _e.uuid) {
|
|
631
|
+
this.startActivityFetching();
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (changes.has('simulatorActive')) {
|
|
635
|
+
if (this.simulatorActive) {
|
|
636
|
+
// Stop polling when simulator becomes active
|
|
637
|
+
this.stopActivityFetching();
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
// Resume polling and refresh activity when simulator closes
|
|
641
|
+
this.activityInterval = 100; // Reset to fast initial interval
|
|
642
|
+
this.startActivityFetching();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (changes.has('activityData')) {
|
|
646
|
+
// Update plumber with new activity data
|
|
647
|
+
if (this.plumber) {
|
|
648
|
+
this.plumber.setActivityData(this.activityData);
|
|
649
|
+
}
|
|
508
650
|
}
|
|
509
651
|
if (changes.has('dirtyDate')) {
|
|
510
652
|
if (this.dirtyDate) {
|
|
@@ -572,6 +714,46 @@ export class Editor extends RapidElement {
|
|
|
572
714
|
});
|
|
573
715
|
getStore().getState().setDirtyDate(null);
|
|
574
716
|
}
|
|
717
|
+
startActivityFetching() {
|
|
718
|
+
// Don't start if simulator is active
|
|
719
|
+
if (this.simulatorActive) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Fetch immediately
|
|
723
|
+
this.fetchActivityData();
|
|
724
|
+
}
|
|
725
|
+
stopActivityFetching() {
|
|
726
|
+
if (this.activityTimer !== null) {
|
|
727
|
+
clearTimeout(this.activityTimer);
|
|
728
|
+
this.activityTimer = null;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
fetchActivityData() {
|
|
732
|
+
var _b;
|
|
733
|
+
if (!((_b = this.definition) === null || _b === void 0 ? void 0 : _b.uuid)) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
// Don't fetch if simulator is active
|
|
737
|
+
if (this.simulatorActive) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const activityEndpoint = `/flow/activity/${this.definition.uuid}/`;
|
|
741
|
+
const store = getStore();
|
|
742
|
+
if (!store) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const state = store.getState();
|
|
746
|
+
state.fetchActivity(activityEndpoint).then(() => {
|
|
747
|
+
// Schedule next fetch with exponential backoff (max 5 minutes)
|
|
748
|
+
this.activityInterval = Math.min(60000 * 5, this.activityInterval + 100);
|
|
749
|
+
if (this.activityTimer !== null) {
|
|
750
|
+
clearTimeout(this.activityTimer);
|
|
751
|
+
}
|
|
752
|
+
this.activityTimer = window.setTimeout(() => {
|
|
753
|
+
this.fetchActivityData();
|
|
754
|
+
}, this.activityInterval);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
575
757
|
handleLanguageChange(languageCode) {
|
|
576
758
|
zustand.getState().setLanguageCode(languageCode);
|
|
577
759
|
// Repaint connections after language change since node sizes can change
|
|
@@ -587,6 +769,10 @@ export class Editor extends RapidElement {
|
|
|
587
769
|
clearTimeout(this.saveTimer);
|
|
588
770
|
this.saveTimer = null;
|
|
589
771
|
}
|
|
772
|
+
if (this.activityTimer !== null) {
|
|
773
|
+
clearTimeout(this.activityTimer);
|
|
774
|
+
this.activityTimer = null;
|
|
775
|
+
}
|
|
590
776
|
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
591
777
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
592
778
|
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
@@ -2264,6 +2450,36 @@ export class Editor extends RapidElement {
|
|
|
2264
2450
|
></temba-floating-tab>
|
|
2265
2451
|
`;
|
|
2266
2452
|
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Focus on a specific node by smoothly scrolling it to the center of the canvas
|
|
2455
|
+
*/
|
|
2456
|
+
focusNode(nodeUuid) {
|
|
2457
|
+
const nodeElement = this.querySelector(`temba-flow-node[uuid="${nodeUuid}"]`);
|
|
2458
|
+
if (!nodeElement) {
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
const editor = this.querySelector('#editor');
|
|
2462
|
+
if (!editor) {
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
// Get the editor's dimensions and scroll position
|
|
2466
|
+
const editorRect = editor.getBoundingClientRect();
|
|
2467
|
+
const editorCenterX = editorRect.width / 2;
|
|
2468
|
+
const editorCenterY = editorRect.height / 2;
|
|
2469
|
+
// Get node position relative to the editor's scroll container
|
|
2470
|
+
const nodeRect = nodeElement.getBoundingClientRect();
|
|
2471
|
+
const nodeCenterX = nodeElement.offsetLeft + nodeRect.width / 2;
|
|
2472
|
+
const nodeCenterY = nodeElement.offsetTop + nodeRect.height / 2;
|
|
2473
|
+
// Calculate the scroll position needed to center the node
|
|
2474
|
+
const targetScrollX = nodeCenterX - editorCenterX;
|
|
2475
|
+
const targetScrollY = nodeCenterY - editorCenterY;
|
|
2476
|
+
// Smooth scroll the editor container to the target position
|
|
2477
|
+
editor.scrollTo({
|
|
2478
|
+
left: Math.max(0, targetScrollX),
|
|
2479
|
+
top: Math.max(0, targetScrollY),
|
|
2480
|
+
behavior: 'smooth'
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2267
2483
|
render() {
|
|
2268
2484
|
var _b, _c;
|
|
2269
2485
|
// we have to embed our own style since we are in light DOM
|
|
@@ -2364,6 +2580,9 @@ __decorate([
|
|
|
2364
2580
|
__decorate([
|
|
2365
2581
|
fromStore(zustand, (state) => state.flowDefinition)
|
|
2366
2582
|
], Editor.prototype, "definition", void 0);
|
|
2583
|
+
__decorate([
|
|
2584
|
+
fromStore(zustand, (state) => state.simulatorActive)
|
|
2585
|
+
], Editor.prototype, "simulatorActive", void 0);
|
|
2367
2586
|
__decorate([
|
|
2368
2587
|
fromStore(zustand, (state) => state.canvasSize)
|
|
2369
2588
|
], Editor.prototype, "canvasSize", void 0);
|
|
@@ -2379,6 +2598,9 @@ __decorate([
|
|
|
2379
2598
|
__decorate([
|
|
2380
2599
|
fromStore(zustand, (state) => state.workspace)
|
|
2381
2600
|
], Editor.prototype, "workspace", void 0);
|
|
2601
|
+
__decorate([
|
|
2602
|
+
fromStore(zustand, (state) => state.getCurrentActivity())
|
|
2603
|
+
], Editor.prototype, "activityData", void 0);
|
|
2382
2604
|
__decorate([
|
|
2383
2605
|
state()
|
|
2384
2606
|
], Editor.prototype, "isDragging", void 0);
|