@nyaruka/temba-components 0.135.9 → 0.136.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/demo/components/webchat/example.html +4 -2
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1351 -322
- 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/display/FloatingTab.js +2 -6
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +29 -1
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +229 -5
- 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 +1861 -0
- package/out-tsc/src/simulator/Simulator.js.map +1 -0
- package/out-tsc/src/store/AppState.js +66 -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-appstate-node-sorting.test.js +430 -0
- package/out-tsc/test/temba-appstate-node-sorting.test.js.map +1 -0
- package/out-tsc/test/temba-floating-tab.test.js +0 -9
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +262 -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/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.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/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
- 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/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/display/FloatingTab.ts +2 -7
- package/src/flow/CanvasNode.ts +30 -1
- package/src/flow/Editor.ts +246 -4
- package/src/flow/Plumber.ts +371 -2
- package/src/interfaces.ts +2 -1
- package/src/layout/FloatingWindow.ts +37 -12
- package/src/simulator/Simulator.ts +2061 -0
- package/src/store/AppState.ts +109 -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-appstate-node-sorting.test.ts +506 -0
- package/test/temba-floating-tab.test.ts +0 -11
- package/test/temba-flow-editor.test.ts +298 -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
package/src/flow/Editor.ts
CHANGED
|
@@ -125,9 +125,15 @@ export class Editor extends RapidElement {
|
|
|
125
125
|
@property({ type: Array })
|
|
126
126
|
public features: string[] = [];
|
|
127
127
|
|
|
128
|
+
private activityTimer: number | null = null;
|
|
129
|
+
private activityInterval = 100; // Start with 100ms interval for fast initial load
|
|
130
|
+
|
|
128
131
|
@fromStore(zustand, (state: AppState) => state.flowDefinition)
|
|
129
132
|
private definition!: FlowDefinition;
|
|
130
133
|
|
|
134
|
+
@fromStore(zustand, (state: AppState) => state.simulatorActive)
|
|
135
|
+
private simulatorActive!: boolean;
|
|
136
|
+
|
|
131
137
|
@fromStore(zustand, (state: AppState) => state.canvasSize)
|
|
132
138
|
private canvasSize!: { width: number; height: number };
|
|
133
139
|
|
|
@@ -143,6 +149,9 @@ export class Editor extends RapidElement {
|
|
|
143
149
|
@fromStore(zustand, (state: AppState) => state.workspace)
|
|
144
150
|
private workspace!: Workspace;
|
|
145
151
|
|
|
152
|
+
@fromStore(zustand, (state: AppState) => state.getCurrentActivity())
|
|
153
|
+
private activityData!: any;
|
|
154
|
+
|
|
146
155
|
// Drag state
|
|
147
156
|
@state()
|
|
148
157
|
private isDragging = false;
|
|
@@ -379,6 +388,125 @@ export class Editor extends RapidElement {
|
|
|
379
388
|
z-index: 10;
|
|
380
389
|
}
|
|
381
390
|
|
|
391
|
+
/* Activity overlays on connections */
|
|
392
|
+
.jtk-overlay.activity-overlay {
|
|
393
|
+
background: #f3f3f3;
|
|
394
|
+
border: 1px solid #d9d9d9;
|
|
395
|
+
color: #333;
|
|
396
|
+
border-radius: 4px;
|
|
397
|
+
padding: 2px 4px;
|
|
398
|
+
font-size: 10px;
|
|
399
|
+
font-weight: 600;
|
|
400
|
+
line-height: 0.9;
|
|
401
|
+
cursor: pointer;
|
|
402
|
+
z-index: 500;
|
|
403
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Active contact count on nodes */
|
|
407
|
+
.active-count {
|
|
408
|
+
position: absolute;
|
|
409
|
+
background: #3498db;
|
|
410
|
+
border: 1px solid #2980b9;
|
|
411
|
+
border-radius: 12px;
|
|
412
|
+
padding: 3px 5px;
|
|
413
|
+
color: #fff;
|
|
414
|
+
font-weight: 500;
|
|
415
|
+
top: -10px;
|
|
416
|
+
left: -10px;
|
|
417
|
+
font-size: 13px;
|
|
418
|
+
min-width: 22px;
|
|
419
|
+
text-align: center;
|
|
420
|
+
z-index: 600;
|
|
421
|
+
line-height: 1;
|
|
422
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Recent contacts popup */
|
|
426
|
+
@keyframes popupBounceIn {
|
|
427
|
+
0% {
|
|
428
|
+
transform: scale(0.8);
|
|
429
|
+
opacity: 0;
|
|
430
|
+
}
|
|
431
|
+
50% {
|
|
432
|
+
transform: scale(1.05);
|
|
433
|
+
}
|
|
434
|
+
100% {
|
|
435
|
+
transform: scale(1);
|
|
436
|
+
opacity: 1;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.recent-contacts-popup {
|
|
441
|
+
display: none;
|
|
442
|
+
position: absolute;
|
|
443
|
+
width: 200px;
|
|
444
|
+
background: #f3f3f3;
|
|
445
|
+
border-radius: 10px;
|
|
446
|
+
box-shadow: 0 1px 3px 1px rgba(130, 130, 130, 0.2);
|
|
447
|
+
z-index: 1015;
|
|
448
|
+
transform-origin: top center;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.recent-contacts-popup.show {
|
|
452
|
+
display: block;
|
|
453
|
+
animation: popupBounceIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.recent-contacts-popup .popup-title {
|
|
457
|
+
background: #999;
|
|
458
|
+
color: #fff;
|
|
459
|
+
padding: 6px 0;
|
|
460
|
+
text-align: center;
|
|
461
|
+
border-top-left-radius: 10px;
|
|
462
|
+
border-top-right-radius: 10px;
|
|
463
|
+
font-size: 12px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.recent-contacts-popup .no-contacts-message {
|
|
467
|
+
padding: 15px;
|
|
468
|
+
text-align: center;
|
|
469
|
+
color: #999;
|
|
470
|
+
font-size: 12px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.recent-contacts-popup .contact-row {
|
|
474
|
+
padding: 8px 10px;
|
|
475
|
+
border-top: 1px solid #e0e0e0;
|
|
476
|
+
text-align: left;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.recent-contacts-popup .contact-row:last-child {
|
|
480
|
+
border-bottom-left-radius: 10px;
|
|
481
|
+
border-bottom-right-radius: 10px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.recent-contacts-popup .contact-name {
|
|
485
|
+
display: block;
|
|
486
|
+
font-weight: 500;
|
|
487
|
+
font-size: 12px;
|
|
488
|
+
color: var(--color-link-primary, #1d4ed8);
|
|
489
|
+
cursor: pointer;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.recent-contacts-popup .contact-name:hover {
|
|
493
|
+
text-decoration: underline;
|
|
494
|
+
color: var(--color-link-primary, #1d4ed8);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.recent-contacts-popup .contact-operand {
|
|
498
|
+
padding-top: 3px;
|
|
499
|
+
font-size: 11px;
|
|
500
|
+
color: #666;
|
|
501
|
+
word-wrap: break-word;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.recent-contacts-popup .contact-time {
|
|
505
|
+
padding-top: 3px;
|
|
506
|
+
font-size: 10px;
|
|
507
|
+
color: #999;
|
|
508
|
+
}
|
|
509
|
+
|
|
382
510
|
/* Connection dragging feedback */
|
|
383
511
|
body svg.jtk-connector.jtk-dragging {
|
|
384
512
|
z-index: 99999 !important;
|
|
@@ -612,7 +740,7 @@ export class Editor extends RapidElement {
|
|
|
612
740
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
613
741
|
): void {
|
|
614
742
|
super.firstUpdated(changes);
|
|
615
|
-
this.plumber = new Plumber(this.querySelector('#canvas'));
|
|
743
|
+
this.plumber = new Plumber(this.querySelector('#canvas'), this);
|
|
616
744
|
this.setupGlobalEventListeners();
|
|
617
745
|
if (changes.has('flow')) {
|
|
618
746
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
@@ -692,6 +820,29 @@ export class Editor extends RapidElement {
|
|
|
692
820
|
}
|
|
693
821
|
|
|
694
822
|
this.translationCache.clear();
|
|
823
|
+
|
|
824
|
+
// Start fetching activity data when definition is loaded
|
|
825
|
+
if (this.definition?.uuid) {
|
|
826
|
+
this.startActivityFetching();
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (changes.has('simulatorActive')) {
|
|
831
|
+
if (this.simulatorActive) {
|
|
832
|
+
// Stop polling when simulator becomes active
|
|
833
|
+
this.stopActivityFetching();
|
|
834
|
+
} else {
|
|
835
|
+
// Resume polling and refresh activity when simulator closes
|
|
836
|
+
this.activityInterval = 100; // Reset to fast initial interval
|
|
837
|
+
this.startActivityFetching();
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (changes.has('activityData')) {
|
|
842
|
+
// Update plumber with new activity data
|
|
843
|
+
if (this.plumber) {
|
|
844
|
+
this.plumber.setActivityData(this.activityData);
|
|
845
|
+
}
|
|
695
846
|
}
|
|
696
847
|
|
|
697
848
|
if (changes.has('dirtyDate')) {
|
|
@@ -768,6 +919,52 @@ export class Editor extends RapidElement {
|
|
|
768
919
|
getStore().getState().setDirtyDate(null);
|
|
769
920
|
}
|
|
770
921
|
|
|
922
|
+
private startActivityFetching(): void {
|
|
923
|
+
// Don't start if simulator is active
|
|
924
|
+
if (this.simulatorActive) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
// Fetch immediately
|
|
928
|
+
this.fetchActivityData();
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
private stopActivityFetching(): void {
|
|
932
|
+
if (this.activityTimer !== null) {
|
|
933
|
+
clearTimeout(this.activityTimer);
|
|
934
|
+
this.activityTimer = null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
private fetchActivityData(): void {
|
|
939
|
+
if (!this.definition?.uuid) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Don't fetch if simulator is active
|
|
944
|
+
if (this.simulatorActive) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const activityEndpoint = `/flow/activity/${this.definition.uuid}/`;
|
|
949
|
+
const store = getStore();
|
|
950
|
+
if (!store) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const state = store.getState();
|
|
954
|
+
state.fetchActivity(activityEndpoint).then(() => {
|
|
955
|
+
// Schedule next fetch with exponential backoff (max 5 minutes)
|
|
956
|
+
this.activityInterval = Math.min(60000 * 5, this.activityInterval + 100);
|
|
957
|
+
|
|
958
|
+
if (this.activityTimer !== null) {
|
|
959
|
+
clearTimeout(this.activityTimer);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
this.activityTimer = window.setTimeout(() => {
|
|
963
|
+
this.fetchActivityData();
|
|
964
|
+
}, this.activityInterval);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
771
968
|
private handleLanguageChange(languageCode: string): void {
|
|
772
969
|
zustand.getState().setLanguageCode(languageCode);
|
|
773
970
|
|
|
@@ -785,6 +982,10 @@ export class Editor extends RapidElement {
|
|
|
785
982
|
clearTimeout(this.saveTimer);
|
|
786
983
|
this.saveTimer = null;
|
|
787
984
|
}
|
|
985
|
+
if (this.activityTimer !== null) {
|
|
986
|
+
clearTimeout(this.activityTimer);
|
|
987
|
+
this.activityTimer = null;
|
|
988
|
+
}
|
|
788
989
|
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
789
990
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
790
991
|
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
@@ -2755,7 +2956,7 @@ export class Editor extends RapidElement {
|
|
|
2755
2956
|
header="Translations"
|
|
2756
2957
|
.width=${360}
|
|
2757
2958
|
.maxHeight=${600}
|
|
2758
|
-
.top=${
|
|
2959
|
+
.top=${75}
|
|
2759
2960
|
color="#6b7280"
|
|
2760
2961
|
.hidden=${this.localizationWindowHidden}
|
|
2761
2962
|
@temba-dialog-hidden=${this.handleLocalizationWindowClosed}
|
|
@@ -2927,6 +3128,44 @@ export class Editor extends RapidElement {
|
|
|
2927
3128
|
`;
|
|
2928
3129
|
}
|
|
2929
3130
|
|
|
3131
|
+
/**
|
|
3132
|
+
* Focus on a specific node by smoothly scrolling it to the center of the canvas
|
|
3133
|
+
*/
|
|
3134
|
+
public focusNode(nodeUuid: string) {
|
|
3135
|
+
const nodeElement = this.querySelector(
|
|
3136
|
+
`temba-flow-node[uuid="${nodeUuid}"]`
|
|
3137
|
+
) as HTMLElement;
|
|
3138
|
+
if (!nodeElement) {
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
const editor = this.querySelector('#editor') as HTMLElement;
|
|
3143
|
+
if (!editor) {
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
// Get the editor's dimensions and scroll position
|
|
3148
|
+
const editorRect = editor.getBoundingClientRect();
|
|
3149
|
+
const editorCenterX = editorRect.width / 2;
|
|
3150
|
+
const editorCenterY = editorRect.height / 2;
|
|
3151
|
+
|
|
3152
|
+
// Get node position relative to the editor's scroll container
|
|
3153
|
+
const nodeRect = nodeElement.getBoundingClientRect();
|
|
3154
|
+
const nodeCenterX = nodeElement.offsetLeft + nodeRect.width / 2;
|
|
3155
|
+
const nodeCenterY = nodeElement.offsetTop + nodeRect.height / 2;
|
|
3156
|
+
|
|
3157
|
+
// Calculate the scroll position needed to center the node
|
|
3158
|
+
const targetScrollX = nodeCenterX - editorCenterX;
|
|
3159
|
+
const targetScrollY = nodeCenterY - editorCenterY;
|
|
3160
|
+
|
|
3161
|
+
// Smooth scroll the editor container to the target position
|
|
3162
|
+
editor.scrollTo({
|
|
3163
|
+
left: Math.max(0, targetScrollX),
|
|
3164
|
+
top: Math.max(0, targetScrollY),
|
|
3165
|
+
behavior: 'smooth'
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
|
|
2930
3169
|
public render(): TemplateResult {
|
|
2931
3170
|
// we have to embed our own style since we are in light DOM
|
|
2932
3171
|
const style = html`<style>
|
|
@@ -2949,7 +3188,7 @@ export class Editor extends RapidElement {
|
|
|
2949
3188
|
? repeat(
|
|
2950
3189
|
this.definition.nodes,
|
|
2951
3190
|
(node) => node.uuid,
|
|
2952
|
-
(node) => {
|
|
3191
|
+
(node, index) => {
|
|
2953
3192
|
const position = this.definition._ui?.nodes[node.uuid]
|
|
2954
3193
|
?.position || {
|
|
2955
3194
|
left: 0,
|
|
@@ -2962,10 +3201,13 @@ export class Editor extends RapidElement {
|
|
|
2962
3201
|
|
|
2963
3202
|
const selected = this.selectedItems.has(node.uuid);
|
|
2964
3203
|
|
|
3204
|
+
// first node is the flow start (nodes are sorted by position)
|
|
3205
|
+
const isFlowStart = index === 0;
|
|
3206
|
+
|
|
2965
3207
|
return html`<temba-flow-node
|
|
2966
3208
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
2967
3209
|
? 'selected'
|
|
2968
|
-
: ''}"
|
|
3210
|
+
: ''} ${isFlowStart ? 'flow-start' : ''}"
|
|
2969
3211
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
2970
3212
|
uuid=${node.uuid}
|
|
2971
3213
|
data-node-uuid=${node.uuid}
|