@nyaruka/temba-components 0.132.0 → 0.134.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 +31 -1
- package/demo/components/flow/example.html +1 -0
- package/demo/components/webchat/example.html +1 -1
- package/demo/static/css/tailwind.css +30019 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +2 -11
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +555 -476
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +248 -95
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/TembaUser.js +3 -3
- package/out-tsc/src/display/TembaUser.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +132 -58
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +183 -58
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/utils.js +141 -0
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +1 -2
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +1 -0
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +3 -2
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +184 -205
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +2 -11
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +34 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/store/Store.js +5 -5
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/utils.js +3 -3
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +22 -9
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +6 -5
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/actions/send_broadcast.test.js +9 -4
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +1 -1
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-floating-window.test.js +0 -2
- package/out-tsc/test/temba-floating-window.test.js.map +1 -1
- package/out-tsc/test/temba-flow-collision.test.js +673 -0
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +195 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-utils-uuid.test.js +45 -1
- package/out-tsc/test/temba-utils-uuid.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -2
- 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_broadcast/render/with-attachments.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/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/screenshots/truth/floating-tab/default.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/hover.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/src/display/Chat.ts +331 -135
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/TembaUser.ts +3 -2
- package/src/events.ts +12 -12
- package/src/flow/CanvasNode.ts +140 -57
- package/src/flow/Editor.ts +240 -58
- package/src/flow/utils.ts +207 -1
- package/src/interfaces.ts +7 -0
- package/src/layout/FloatingWindow.ts +1 -3
- package/src/list/ContentMenu.ts +1 -0
- package/src/list/SortableList.ts +3 -2
- package/src/live/ContactChat.ts +195 -221
- package/src/locales/es.ts +13 -18
- package/src/locales/fr.ts +13 -18
- package/src/locales/locale-codes.ts +2 -11
- package/src/locales/pt.ts +13 -18
- package/src/store/AppState.ts +43 -0
- package/src/store/Store.ts +5 -5
- package/src/utils.ts +3 -3
- package/src/webchat/WebChat.ts +24 -10
- package/test/ActionHelper.ts +13 -5
- package/test/actions/send_broadcast.test.ts +4 -2
- package/test/temba-contact-chat.test.ts +1 -1
- package/test/temba-floating-window.test.ts +0 -2
- package/test/temba-flow-collision.test.ts +833 -0
- package/test/temba-flow-editor-node.test.ts +224 -0
- package/test/temba-utils-uuid.test.ts +61 -1
- package/test/utils.test.ts +7 -2
- package/test-assets/contacts/history.json +22 -9
- package/web-test-runner.config.mjs +3 -3
|
@@ -12,6 +12,7 @@ import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
|
12
12
|
import { ACTION_GROUP_METADATA } from './types';
|
|
13
13
|
import { Plumber } from './Plumber';
|
|
14
14
|
import { CanvasNode } from './CanvasNode';
|
|
15
|
+
import { getNodeBounds, calculateReflowPositions, nodesOverlap } from './utils';
|
|
15
16
|
export function snapToGrid(value) {
|
|
16
17
|
const snapped = Math.round(value / 20) * 20;
|
|
17
18
|
return Math.max(snapped, 0);
|
|
@@ -87,8 +88,6 @@ export class Editor extends RapidElement {
|
|
|
87
88
|
);
|
|
88
89
|
background-size: 20px 20px;
|
|
89
90
|
background-position: 10px 10px;
|
|
90
|
-
box-shadow: inset -5px 0 10px rgba(0, 0, 0, 0.05);
|
|
91
|
-
border-top: 1px solid #e0e0e0;
|
|
92
91
|
width: 100%;
|
|
93
92
|
display: flex;
|
|
94
93
|
}
|
|
@@ -107,6 +106,7 @@ export class Editor extends RapidElement {
|
|
|
107
106
|
|
|
108
107
|
#canvas > .dragging {
|
|
109
108
|
z-index: 99999 !important;
|
|
109
|
+
transition: none !important;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
body .jtk-endpoint {
|
|
@@ -553,11 +553,18 @@ export class Editor extends RapidElement {
|
|
|
553
553
|
saveChanges() {
|
|
554
554
|
// post the flow definition to the server
|
|
555
555
|
getStore()
|
|
556
|
-
.postJSON(`/flow/revisions/${this.flow}
|
|
556
|
+
.postJSON(`/flow/revisions/${this.flow}/`, this.definition)
|
|
557
557
|
.then((response) => {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
558
|
+
var _b;
|
|
559
|
+
// Update flow info and revision with the response data
|
|
560
|
+
if (response.json) {
|
|
561
|
+
const state = getStore().getState();
|
|
562
|
+
if (response.json.info) {
|
|
563
|
+
state.setFlowInfo(response.json.info);
|
|
564
|
+
}
|
|
565
|
+
if (((_b = response.json.revision) === null || _b === void 0 ? void 0 : _b.revision) !== undefined) {
|
|
566
|
+
state.setRevision(response.json.revision.revision);
|
|
567
|
+
}
|
|
561
568
|
}
|
|
562
569
|
})
|
|
563
570
|
.catch((error) => {
|
|
@@ -604,6 +611,8 @@ export class Editor extends RapidElement {
|
|
|
604
611
|
this.addEventListener(CustomEventType.AddActionRequested, this.handleAddActionRequested.bind(this));
|
|
605
612
|
// Listen for node edit requests from flow nodes
|
|
606
613
|
this.addEventListener(CustomEventType.NodeEditRequested, this.handleNodeEditRequested.bind(this));
|
|
614
|
+
// Listen for node deletion events
|
|
615
|
+
this.addEventListener(CustomEventType.NodeDeleted, this.handleNodeDeleted.bind(this));
|
|
607
616
|
// Listen for canvas menu selections
|
|
608
617
|
this.addEventListener(CustomEventType.Selection, (event) => {
|
|
609
618
|
const target = event.target;
|
|
@@ -905,6 +914,60 @@ export class Editor extends RapidElement {
|
|
|
905
914
|
</div>
|
|
906
915
|
</div>`;
|
|
907
916
|
}
|
|
917
|
+
/**
|
|
918
|
+
* Checks for node collisions and reflows nodes as needed.
|
|
919
|
+
* Nodes are only moved downward to resolve collisions.
|
|
920
|
+
*
|
|
921
|
+
* @param movedNodeUuids - UUIDs of nodes that were just moved/dropped
|
|
922
|
+
* @param droppedNodeUuid - UUID of the specific node that was dropped (if applicable)
|
|
923
|
+
* @param dropTargetBounds - Bounds of the node that was dropped onto (if applicable)
|
|
924
|
+
*/
|
|
925
|
+
checkCollisionsAndReflow(movedNodeUuids, droppedNodeUuid = null, dropTargetBounds = null) {
|
|
926
|
+
var _b;
|
|
927
|
+
if (!this.definition)
|
|
928
|
+
return;
|
|
929
|
+
// Get all node bounds (only for actual nodes, not stickies)
|
|
930
|
+
const allBounds = [];
|
|
931
|
+
for (const node of this.definition.nodes) {
|
|
932
|
+
const nodeUI = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid];
|
|
933
|
+
if (!(nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.position))
|
|
934
|
+
continue;
|
|
935
|
+
const bounds = getNodeBounds(node.uuid, nodeUI.position);
|
|
936
|
+
if (bounds) {
|
|
937
|
+
allBounds.push(bounds);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// Check if we need to determine midpoint priority for a dropped node
|
|
941
|
+
let targetHasPriority = false;
|
|
942
|
+
if (droppedNodeUuid && dropTargetBounds) {
|
|
943
|
+
const droppedBounds = allBounds.find((b) => b.uuid === droppedNodeUuid);
|
|
944
|
+
if (droppedBounds) {
|
|
945
|
+
// Check if the bottom of the dropped node is below the midpoint of the target
|
|
946
|
+
// If bottom is above midpoint, dropped node gets preference (targetHasPriority = false)
|
|
947
|
+
// If bottom is below midpoint, target gets preference (targetHasPriority = true)
|
|
948
|
+
const droppedBottom = droppedBounds.bottom;
|
|
949
|
+
const targetMidpoint = dropTargetBounds.top + dropTargetBounds.height / 2;
|
|
950
|
+
targetHasPriority = droppedBottom > targetMidpoint;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// Calculate reflow positions for each moved node
|
|
954
|
+
const allReflowPositions = {};
|
|
955
|
+
for (const movedUuid of movedNodeUuids) {
|
|
956
|
+
const movedBounds = allBounds.find((b) => b.uuid === movedUuid);
|
|
957
|
+
if (!movedBounds)
|
|
958
|
+
continue;
|
|
959
|
+
// Calculate reflow for this moved node
|
|
960
|
+
const reflowPositions = calculateReflowPositions(movedUuid, movedBounds, allBounds, droppedNodeUuid === movedUuid ? targetHasPriority : false);
|
|
961
|
+
// Merge into all reflow positions
|
|
962
|
+
for (const [uuid, position] of reflowPositions.entries()) {
|
|
963
|
+
allReflowPositions[uuid] = position;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// If there are positions to update, apply them
|
|
967
|
+
if (Object.keys(allReflowPositions).length > 0) {
|
|
968
|
+
getStore().getState().updateCanvasPositions(allReflowPositions);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
908
971
|
handleMouseMove(event) {
|
|
909
972
|
// Handle selection box drawing
|
|
910
973
|
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
@@ -1024,9 +1087,48 @@ export class Editor extends RapidElement {
|
|
|
1024
1087
|
});
|
|
1025
1088
|
if (Object.keys(newPositions).length > 0) {
|
|
1026
1089
|
getStore().getState().updateCanvasPositions(newPositions);
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1090
|
+
// Check for collisions and reflow nodes after updating positions
|
|
1091
|
+
// Filter to only check nodes (not stickies)
|
|
1092
|
+
const nodeUuids = itemsToMove.filter((uuid) => this.definition.nodes.find((node) => node.uuid === uuid));
|
|
1093
|
+
if (nodeUuids.length > 0) {
|
|
1094
|
+
// Allow DOM to update before checking collisions
|
|
1095
|
+
setTimeout(() => {
|
|
1096
|
+
var _b, _c;
|
|
1097
|
+
// If only one node was moved, detect which node it might have been dropped onto
|
|
1098
|
+
let droppedNodeUuid = null;
|
|
1099
|
+
let dropTargetBounds = null;
|
|
1100
|
+
if (nodeUuids.length === 1) {
|
|
1101
|
+
droppedNodeUuid = nodeUuids[0];
|
|
1102
|
+
const droppedNodeUI = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[droppedNodeUuid];
|
|
1103
|
+
if (droppedNodeUI === null || droppedNodeUI === void 0 ? void 0 : droppedNodeUI.position) {
|
|
1104
|
+
const droppedBounds = getNodeBounds(droppedNodeUuid, droppedNodeUI.position);
|
|
1105
|
+
if (droppedBounds) {
|
|
1106
|
+
// Find which node (if any) the dropped node overlaps with
|
|
1107
|
+
for (const node of this.definition.nodes) {
|
|
1108
|
+
if (node.uuid === droppedNodeUuid)
|
|
1109
|
+
continue;
|
|
1110
|
+
const nodeUI = (_c = this.definition._ui) === null || _c === void 0 ? void 0 : _c.nodes[node.uuid];
|
|
1111
|
+
if (!(nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.position))
|
|
1112
|
+
continue;
|
|
1113
|
+
const targetBounds = getNodeBounds(node.uuid, nodeUI.position);
|
|
1114
|
+
if (targetBounds &&
|
|
1115
|
+
nodesOverlap(droppedBounds, targetBounds)) {
|
|
1116
|
+
dropTargetBounds = targetBounds;
|
|
1117
|
+
break; // Use the first overlapping node
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
this.checkCollisionsAndReflow(nodeUuids, droppedNodeUuid, dropTargetBounds);
|
|
1124
|
+
}, 0);
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
// No nodes moved, just repaint connections
|
|
1128
|
+
setTimeout(() => {
|
|
1129
|
+
this.plumber.repaintEverything();
|
|
1130
|
+
}, 0);
|
|
1131
|
+
}
|
|
1030
1132
|
}
|
|
1031
1133
|
this.selectedItems.clear();
|
|
1032
1134
|
}
|
|
@@ -1255,6 +1357,12 @@ export class Editor extends RapidElement {
|
|
|
1255
1357
|
this.editingNode = event.detail.node;
|
|
1256
1358
|
this.editingNodeUI = event.detail.nodeUI;
|
|
1257
1359
|
}
|
|
1360
|
+
handleNodeDeleted(event) {
|
|
1361
|
+
const nodeUuid = event.detail.uuid;
|
|
1362
|
+
if (nodeUuid) {
|
|
1363
|
+
this.deleteNodes([nodeUuid]);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1258
1366
|
handleActionSaved(updatedAction) {
|
|
1259
1367
|
var _b, _c;
|
|
1260
1368
|
if (this.editingNode && this.editingAction) {
|
|
@@ -1284,23 +1392,18 @@ export class Editor extends RapidElement {
|
|
|
1284
1392
|
// Reset the creation flags
|
|
1285
1393
|
this.isCreatingNewNode = false;
|
|
1286
1394
|
this.pendingNodePosition = null;
|
|
1287
|
-
//
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1395
|
+
// Check for collisions and reflow
|
|
1396
|
+
requestAnimationFrame(() => {
|
|
1397
|
+
this.checkCollisionsAndReflow([updatedNode.uuid]);
|
|
1398
|
+
});
|
|
1293
1399
|
}
|
|
1294
1400
|
else {
|
|
1295
1401
|
// Update existing node in the store
|
|
1296
1402
|
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1297
|
-
//
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
this.plumber.repaintEverything();
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1403
|
+
// Check for collisions and reflow in case node size changed
|
|
1404
|
+
requestAnimationFrame(() => {
|
|
1405
|
+
this.checkCollisionsAndReflow([this.editingNode.uuid]);
|
|
1406
|
+
});
|
|
1304
1407
|
}
|
|
1305
1408
|
}
|
|
1306
1409
|
this.closeNodeEditor();
|
|
@@ -1334,6 +1437,10 @@ export class Editor extends RapidElement {
|
|
|
1334
1437
|
// Reset the creation flags
|
|
1335
1438
|
this.isCreatingNewNode = false;
|
|
1336
1439
|
this.pendingNodePosition = null;
|
|
1440
|
+
// Check for collisions and reflow
|
|
1441
|
+
requestAnimationFrame(() => {
|
|
1442
|
+
this.checkCollisionsAndReflow([updatedNode.uuid]);
|
|
1443
|
+
});
|
|
1337
1444
|
}
|
|
1338
1445
|
else {
|
|
1339
1446
|
// This is an existing node - update it
|
|
@@ -1355,12 +1462,9 @@ export class Editor extends RapidElement {
|
|
|
1355
1462
|
if (uiConfig) {
|
|
1356
1463
|
(_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().updateNodeUIConfig(updatedNode.uuid, uiConfig);
|
|
1357
1464
|
}
|
|
1358
|
-
|
|
1359
|
-
// Repaint jsplumb connections in case node size changed
|
|
1360
|
-
if (this.plumber) {
|
|
1361
|
-
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
1465
|
+
// Check for collisions and reflow in case node size changed
|
|
1362
1466
|
requestAnimationFrame(() => {
|
|
1363
|
-
this.
|
|
1467
|
+
this.checkCollisionsAndReflow([this.editingNode.uuid]);
|
|
1364
1468
|
});
|
|
1365
1469
|
}
|
|
1366
1470
|
}
|
|
@@ -1409,7 +1513,7 @@ export class Editor extends RapidElement {
|
|
|
1409
1513
|
return { left, top };
|
|
1410
1514
|
}
|
|
1411
1515
|
handleActionDragExternal(event) {
|
|
1412
|
-
const { action, nodeUuid, actionIndex, mouseX, mouseY, actionHeight = 60 } = event.detail;
|
|
1516
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY, actionHeight = 60, isLastAction = false } = event.detail;
|
|
1413
1517
|
// Check if mouse is over another execute_actions node
|
|
1414
1518
|
const targetNode = this.getNodeAtPosition(mouseX, mouseY);
|
|
1415
1519
|
if (targetNode && targetNode !== nodeUuid) {
|
|
@@ -1472,23 +1576,38 @@ export class Editor extends RapidElement {
|
|
|
1472
1576
|
this.previousActionDragTargetNodeUuid = null;
|
|
1473
1577
|
}
|
|
1474
1578
|
this.actionDragTargetNodeUuid = null;
|
|
1475
|
-
// Tell source node to hide ghost (we're not over a valid target)
|
|
1476
1579
|
const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
|
|
1477
|
-
if
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1580
|
+
// Show canvas drop preview only if this is NOT the last action
|
|
1581
|
+
// Last actions can only be dropped on other nodes, not on canvas
|
|
1582
|
+
if (!isLastAction) {
|
|
1583
|
+
// Hide ghost when showing canvas preview (for canvas drops)
|
|
1584
|
+
if (sourceElement) {
|
|
1585
|
+
sourceElement.dispatchEvent(new CustomEvent('action-hide-ghost', {
|
|
1586
|
+
detail: {},
|
|
1587
|
+
bubbles: false
|
|
1588
|
+
}));
|
|
1589
|
+
}
|
|
1590
|
+
// Don't snap to grid for preview - let it follow cursor smoothly
|
|
1591
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
|
|
1592
|
+
this.canvasDropPreview = {
|
|
1593
|
+
action,
|
|
1594
|
+
nodeUuid,
|
|
1595
|
+
actionIndex,
|
|
1596
|
+
position,
|
|
1597
|
+
actionHeight
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
// For last action, keep ghost visible (can't drop on canvas)
|
|
1602
|
+
if (sourceElement) {
|
|
1603
|
+
sourceElement.dispatchEvent(new CustomEvent('action-show-ghost', {
|
|
1604
|
+
detail: {},
|
|
1605
|
+
bubbles: false
|
|
1606
|
+
}));
|
|
1607
|
+
}
|
|
1608
|
+
// Clear any existing preview for last action
|
|
1609
|
+
this.canvasDropPreview = null;
|
|
1482
1610
|
}
|
|
1483
|
-
// Don't snap to grid for preview - let it follow cursor smoothly
|
|
1484
|
-
const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
|
|
1485
|
-
this.canvasDropPreview = {
|
|
1486
|
-
action,
|
|
1487
|
-
nodeUuid,
|
|
1488
|
-
actionIndex,
|
|
1489
|
-
position,
|
|
1490
|
-
actionHeight
|
|
1491
|
-
};
|
|
1492
1611
|
// Force re-render to update preview position
|
|
1493
1612
|
this.requestUpdate();
|
|
1494
1613
|
}
|
|
@@ -1508,8 +1627,8 @@ export class Editor extends RapidElement {
|
|
|
1508
1627
|
this.actionDragTargetNodeUuid = null;
|
|
1509
1628
|
}
|
|
1510
1629
|
handleActionDropExternal(event) {
|
|
1511
|
-
var _b, _c
|
|
1512
|
-
const { action, nodeUuid, actionIndex, mouseX, mouseY } = event.detail;
|
|
1630
|
+
var _b, _c;
|
|
1631
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY, isLastAction = false } = event.detail;
|
|
1513
1632
|
// Check if we're dropping on an existing execute_actions node
|
|
1514
1633
|
const targetNodeUuid = this.actionDragTargetNodeUuid;
|
|
1515
1634
|
if (targetNodeUuid && targetNodeUuid !== nodeUuid) {
|
|
@@ -1532,6 +1651,13 @@ export class Editor extends RapidElement {
|
|
|
1532
1651
|
this.actionDragTargetNodeUuid = null;
|
|
1533
1652
|
return;
|
|
1534
1653
|
}
|
|
1654
|
+
// If this is the last action and we're not dropping on another node, do nothing
|
|
1655
|
+
// Last actions can only be moved to other nodes, not dropped on canvas
|
|
1656
|
+
if (isLastAction) {
|
|
1657
|
+
this.canvasDropPreview = null;
|
|
1658
|
+
this.actionDragTargetNodeUuid = null;
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1535
1661
|
// Not dropping on another node, create a new one on canvas
|
|
1536
1662
|
// Snap to grid for the final drop position
|
|
1537
1663
|
const position = this.calculateCanvasDropPosition(mouseX, mouseY, true);
|
|
@@ -1542,12 +1668,13 @@ export class Editor extends RapidElement {
|
|
|
1542
1668
|
const updatedActions = originalNode.actions.filter((_a, idx) => idx !== actionIndex);
|
|
1543
1669
|
// if no actions remain, delete the node
|
|
1544
1670
|
if (updatedActions.length === 0) {
|
|
1545
|
-
|
|
1671
|
+
// Use deleteNodes to properly clean up Plumber connections before removing
|
|
1672
|
+
this.deleteNodes([nodeUuid]);
|
|
1546
1673
|
}
|
|
1547
1674
|
else {
|
|
1548
1675
|
// update the node
|
|
1549
1676
|
const updatedNode = { ...originalNode, actions: updatedActions };
|
|
1550
|
-
(
|
|
1677
|
+
(_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().updateNode(nodeUuid, updatedNode);
|
|
1551
1678
|
}
|
|
1552
1679
|
// create a new execute_actions node with the dropped action
|
|
1553
1680
|
const newNode = {
|
|
@@ -1566,16 +1693,14 @@ export class Editor extends RapidElement {
|
|
|
1566
1693
|
config: {}
|
|
1567
1694
|
};
|
|
1568
1695
|
// add the new node
|
|
1569
|
-
(
|
|
1696
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().addNode(newNode, newNodeUI);
|
|
1570
1697
|
// clear the preview
|
|
1571
1698
|
this.canvasDropPreview = null;
|
|
1572
1699
|
this.actionDragTargetNodeUuid = null;
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1700
|
+
// Check for collisions and reflow after adding new node
|
|
1701
|
+
requestAnimationFrame(() => {
|
|
1702
|
+
this.checkCollisionsAndReflow([newNode.uuid]);
|
|
1703
|
+
});
|
|
1579
1704
|
}
|
|
1580
1705
|
getLocalizationLanguages() {
|
|
1581
1706
|
if (!this.definition) {
|
|
@@ -1975,7 +2100,7 @@ export class Editor extends RapidElement {
|
|
|
1975
2100
|
header="Translations"
|
|
1976
2101
|
.width=${360}
|
|
1977
2102
|
.maxHeight=${600}
|
|
1978
|
-
.top=${
|
|
2103
|
+
.top=${170}
|
|
1979
2104
|
color="#6b7280"
|
|
1980
2105
|
.hidden=${this.localizationWindowHidden}
|
|
1981
2106
|
@temba-dialog-hidden=${this.handleLocalizationWindowClosed}
|
|
@@ -2133,6 +2258,7 @@ export class Editor extends RapidElement {
|
|
|
2133
2258
|
icon="language"
|
|
2134
2259
|
label="Translate Flow"
|
|
2135
2260
|
color="#6b7280"
|
|
2261
|
+
top="180"
|
|
2136
2262
|
.hidden=${!this.localizationWindowHidden}
|
|
2137
2263
|
@temba-button-clicked=${this.handleLocalizationTabClick}
|
|
2138
2264
|
></temba-floating-tab>
|
|
@@ -2172,7 +2298,7 @@ export class Editor extends RapidElement {
|
|
|
2172
2298
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
2173
2299
|
uuid=${node.uuid}
|
|
2174
2300
|
data-node-uuid=${node.uuid}
|
|
2175
|
-
style="left:${position.left}px; top:${position.top}px"
|
|
2301
|
+
style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
|
|
2176
2302
|
.plumber=${this.plumber}
|
|
2177
2303
|
.node=${node}
|
|
2178
2304
|
.ui=${this.definition._ui.nodes[node.uuid]}
|
|
@@ -2192,8 +2318,7 @@ export class Editor extends RapidElement {
|
|
|
2192
2318
|
? 'selected'
|
|
2193
2319
|
: ''}"
|
|
2194
2320
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
2195
|
-
style="left:${position.left}px; top:${position.top}px;
|
|
2196
|
-
position.top}"
|
|
2321
|
+
style="left:${position.left}px; top:${position.top}px;"
|
|
2197
2322
|
uuid=${uuid}
|
|
2198
2323
|
.data=${sticky}
|
|
2199
2324
|
.dragging=${dragging}
|