@nyaruka/temba-components 0.138.6 → 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 +26 -0
- package/demo/data/flows/sample-flow.json +24 -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 +1112 -882
- 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 +25 -32
- 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/CanvasMenu.js +5 -3
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +70 -29
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +290 -239
- 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/Plumber.js +757 -403
- package/out-tsc/src/flow/Plumber.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 +213 -65
- 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 +2 -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/list/TicketList.js +4 -1
- package/out-tsc/src/list/TicketList.js.map +1 -1
- package/out-tsc/src/live/TembaChart.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/simulator/Simulator.js +10 -3
- 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-floating-tab.test.js +4 -6
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
- package/out-tsc/test/temba-flow-collision.test.js +473 -220
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +0 -2
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +102 -93
- package/out-tsc/test/temba-flow-plumber.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 +24 -33
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasMenu.ts +8 -3
- package/src/flow/CanvasNode.ts +75 -30
- package/src/flow/Editor.ts +336 -288
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/Plumber.ts +1011 -457
- 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 +238 -81
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +64 -1
- package/src/interfaces.ts +3 -1
- package/src/layout/Dialog.ts +53 -7
- package/src/list/TicketList.ts +4 -1
- package/src/live/TembaChart.ts +1 -1
- 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/simulator/Simulator.ts +13 -3
- 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-floating-tab.test.ts +4 -6
- package/test/temba-flow-collision.test.ts +495 -293
- package/test/temba-flow-editor.test.ts +0 -2
- package/test/temba-flow-plumber-connections.test.ts +97 -97
- package/test/temba-flow-plumber.test.ts +116 -103
- 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
|
@@ -8,15 +8,11 @@ import { RapidElement } from '../RapidElement';
|
|
|
8
8
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
9
9
|
import { CustomEventType } from '../interfaces';
|
|
10
10
|
import { generateUUID, postJSON, fetchResults, getClasses } from '../utils';
|
|
11
|
+
import { formatIssueMessage, getNodeBounds, calculateReflowPositions, snapToGrid } from './utils';
|
|
11
12
|
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
12
13
|
import { ACTION_GROUP_METADATA } from './types';
|
|
13
|
-
import { Plumber } from './Plumber';
|
|
14
|
+
import { Plumber, calculateFlowchartPath, ARROW_LENGTH, ARROW_HALF_WIDTH, CURSOR_GAP } from './Plumber';
|
|
14
15
|
import { CanvasNode } from './CanvasNode';
|
|
15
|
-
import { getNodeBounds, calculateReflowPositions, nodesOverlap } from './utils';
|
|
16
|
-
export function snapToGrid(value) {
|
|
17
|
-
const snapped = Math.round(value / 20) * 20;
|
|
18
|
-
return Math.max(snapped, 0);
|
|
19
|
-
}
|
|
20
16
|
export function findNodeForExit(definition, exitUuid) {
|
|
21
17
|
for (const node of definition.nodes) {
|
|
22
18
|
const exit = node.exits.find((e) => e.uuid === exitUuid);
|
|
@@ -34,7 +30,7 @@ const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
|
|
|
34
30
|
const DROP_PREVIEW_OFFSET_X = 20;
|
|
35
31
|
const DROP_PREVIEW_OFFSET_Y = 20;
|
|
36
32
|
export class Editor extends RapidElement {
|
|
37
|
-
//
|
|
33
|
+
// connection SVGs are appended directly to the canvas, so we need light DOM
|
|
38
34
|
createRenderRoot() {
|
|
39
35
|
return this;
|
|
40
36
|
}
|
|
@@ -78,6 +74,10 @@ export class Editor extends RapidElement {
|
|
|
78
74
|
-webkit-font-smoothing: antialiased;
|
|
79
75
|
}
|
|
80
76
|
|
|
77
|
+
temba-floating-tab {
|
|
78
|
+
--floating-tab-right: 15px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
81
|
#grid {
|
|
82
82
|
position: relative;
|
|
83
83
|
background-color: #f9f9f9;
|
|
@@ -128,100 +128,56 @@ export class Editor extends RapidElement {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
#grid.viewing-revision temba-flow-node,
|
|
131
|
-
#grid.viewing-revision svg.
|
|
132
|
-
#grid.viewing-revision .activity-overlay {
|
|
131
|
+
#grid.viewing-revision svg.plumb-connector {
|
|
133
132
|
opacity: 0.5;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
height: initial;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.jtk-endpoint {
|
|
142
|
-
z-index: 600;
|
|
143
|
-
opacity: 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.plumb-source {
|
|
147
|
-
z-index: 600;
|
|
148
|
-
cursor: pointer;
|
|
149
|
-
opacity: 0;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.plumb-source.connected {
|
|
153
|
-
border-radius: 50%;
|
|
154
|
-
pointer-events: none;
|
|
135
|
+
svg.plumb-connector {
|
|
136
|
+
z-index: 10;
|
|
155
137
|
}
|
|
156
138
|
|
|
157
|
-
.plumb-
|
|
158
|
-
|
|
139
|
+
svg.plumb-connector path {
|
|
140
|
+
stroke: var(--color-connectors);
|
|
141
|
+
stroke-width: 3px;
|
|
159
142
|
}
|
|
160
143
|
|
|
161
|
-
.plumb-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
fill: transparent;
|
|
144
|
+
svg.plumb-connector .plumb-arrow {
|
|
145
|
+
fill: var(--color-connectors);
|
|
146
|
+
stroke: none;
|
|
166
147
|
}
|
|
167
148
|
|
|
168
|
-
|
|
169
|
-
stroke: var(--color-
|
|
170
|
-
stroke-width: 3px;
|
|
149
|
+
svg.plumb-connector.hover path {
|
|
150
|
+
stroke: var(--color-success);
|
|
171
151
|
}
|
|
172
152
|
|
|
173
|
-
|
|
174
|
-
|
|
153
|
+
svg.plumb-connector.hover .plumb-arrow {
|
|
154
|
+
fill: var(--color-success);
|
|
175
155
|
}
|
|
176
156
|
|
|
177
|
-
|
|
178
|
-
fill: var(--color-connectors);
|
|
157
|
+
#canvas.read-only-connections svg.plumb-connector.hover path {
|
|
179
158
|
stroke: var(--color-connectors);
|
|
180
|
-
stroke-width: 0px !important;
|
|
181
|
-
margin-top: 6px;
|
|
182
|
-
z-index: 10;
|
|
183
159
|
}
|
|
184
160
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
stroke-width: 3px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
body #canvas.read-only-connections svg.jtk-connector.jtk-hover path {
|
|
191
|
-
stroke: var(--color-connectors) !important;
|
|
161
|
+
#canvas.read-only-connections svg.plumb-connector.hover .plumb-arrow {
|
|
162
|
+
fill: var(--color-connectors);
|
|
192
163
|
}
|
|
193
164
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
165
|
+
#canvas.read-only-connections svg.plumb-connector,
|
|
166
|
+
#canvas.read-only-connections svg.plumb-connector * {
|
|
167
|
+
pointer-events: none !important;
|
|
168
|
+
cursor: default !important;
|
|
198
169
|
}
|
|
199
170
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.plumb-connector.jtk-hover
|
|
203
|
-
.plumb-arrow {
|
|
204
|
-
fill: var(--color-connectors) !important;
|
|
205
|
-
ponter-events: none;
|
|
171
|
+
svg.plumb-connector.removing path {
|
|
172
|
+
stroke: var(--color-error);
|
|
206
173
|
}
|
|
207
174
|
|
|
208
|
-
|
|
209
|
-
|
|
175
|
+
svg.plumb-connector.removing .plumb-arrow {
|
|
176
|
+
fill: var(--color-error);
|
|
210
177
|
}
|
|
211
178
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
background: #f3f3f3;
|
|
215
|
-
border: 1px solid #d9d9d9;
|
|
216
|
-
color: #333;
|
|
217
|
-
border-radius: 4px;
|
|
218
|
-
padding: 2px 4px;
|
|
219
|
-
font-size: 10px;
|
|
220
|
-
font-weight: 600;
|
|
221
|
-
line-height: 0.9;
|
|
222
|
-
cursor: pointer;
|
|
223
|
-
z-index: 500;
|
|
224
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
179
|
+
svg.plumb-connector.dragging {
|
|
180
|
+
z-index: 99999;
|
|
225
181
|
}
|
|
226
182
|
|
|
227
183
|
/* Active contact count on nodes */
|
|
@@ -243,6 +199,30 @@ export class Editor extends RapidElement {
|
|
|
243
199
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
244
200
|
}
|
|
245
201
|
|
|
202
|
+
/* Activity overlay badges on connection exit stubs */
|
|
203
|
+
.activity-overlay {
|
|
204
|
+
position: absolute;
|
|
205
|
+
background: #f3f3f3;
|
|
206
|
+
border: 1px solid #d9d9d9;
|
|
207
|
+
color: #333;
|
|
208
|
+
border-radius: 4px;
|
|
209
|
+
padding: 2px 4px;
|
|
210
|
+
font-size: 10px;
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
line-height: 0.9;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
z-index: 10;
|
|
215
|
+
pointer-events: auto;
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
user-select: none;
|
|
218
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#grid.viewing-revision .activity-overlay {
|
|
222
|
+
opacity: 0.5;
|
|
223
|
+
pointer-events: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
246
226
|
/* Recent contacts popup */
|
|
247
227
|
@keyframes popupBounceIn {
|
|
248
228
|
0% {
|
|
@@ -312,7 +292,6 @@ export class Editor extends RapidElement {
|
|
|
312
292
|
|
|
313
293
|
.recent-contacts-popup .contact-name:hover {
|
|
314
294
|
text-decoration: underline;
|
|
315
|
-
color: var(--color-link-primary, #1d4ed8);
|
|
316
295
|
}
|
|
317
296
|
|
|
318
297
|
.recent-contacts-popup .contact-operand {
|
|
@@ -328,17 +307,6 @@ export class Editor extends RapidElement {
|
|
|
328
307
|
color: #999;
|
|
329
308
|
}
|
|
330
309
|
|
|
331
|
-
/* Connection dragging feedback */
|
|
332
|
-
body svg.jtk-connector.jtk-dragging {
|
|
333
|
-
z-index: 99999 !important;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.katavorio-drag-no-select svg.jtk-connector path,
|
|
337
|
-
.katavorio-drag-no-select svg.jtk-endpoint path {
|
|
338
|
-
pointer-events: none !important;
|
|
339
|
-
border: 1px solid purple;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
310
|
/* Connection target feedback */
|
|
343
311
|
temba-flow-node.connection-target-valid {
|
|
344
312
|
outline: 3px solid var(--color-success, #22c55e) !important;
|
|
@@ -368,10 +336,6 @@ export class Editor extends RapidElement {
|
|
|
368
336
|
border-radius: var(--curvature);
|
|
369
337
|
}
|
|
370
338
|
|
|
371
|
-
.jtk-floating-endpoint {
|
|
372
|
-
pointer-events: none;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
339
|
.localization-window-content {
|
|
376
340
|
display: flex;
|
|
377
341
|
flex-direction: column;
|
|
@@ -562,6 +526,26 @@ export class Editor extends RapidElement {
|
|
|
562
526
|
color: #9ca3af;
|
|
563
527
|
white-space: nowrap;
|
|
564
528
|
}
|
|
529
|
+
|
|
530
|
+
.issue-list-item {
|
|
531
|
+
display: flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
gap: 8px;
|
|
534
|
+
padding: 8px;
|
|
535
|
+
border-radius: 4px;
|
|
536
|
+
cursor: pointer;
|
|
537
|
+
font-size: 13px;
|
|
538
|
+
color: #333;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.issue-list-item:hover {
|
|
542
|
+
background: #fff5f5;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.issue-list-item temba-icon {
|
|
546
|
+
color: tomato;
|
|
547
|
+
flex-shrink: 0;
|
|
548
|
+
}
|
|
565
549
|
`;
|
|
566
550
|
}
|
|
567
551
|
constructor() {
|
|
@@ -587,6 +571,10 @@ export class Editor extends RapidElement {
|
|
|
587
571
|
this.dragFromNodeId = null;
|
|
588
572
|
this.originalConnectionTargetId = null;
|
|
589
573
|
this.isValidTarget = true;
|
|
574
|
+
// Canvas-relative source exit position (set at drag start)
|
|
575
|
+
this.connectionSourceX = null;
|
|
576
|
+
this.connectionSourceY = null;
|
|
577
|
+
this.issuesWindowHidden = true;
|
|
590
578
|
this.localizationWindowHidden = true;
|
|
591
579
|
this.translationFilters = {
|
|
592
580
|
categories: false
|
|
@@ -606,6 +594,7 @@ export class Editor extends RapidElement {
|
|
|
606
594
|
this.editingNode = null;
|
|
607
595
|
this.editingNodeUI = null;
|
|
608
596
|
this.editingAction = null;
|
|
597
|
+
this.dialogOrigin = null;
|
|
609
598
|
this.isCreatingNewNode = false;
|
|
610
599
|
this.pendingNodePosition = null;
|
|
611
600
|
// Canvas drop state for dragging actions to canvas
|
|
@@ -635,15 +624,13 @@ export class Editor extends RapidElement {
|
|
|
635
624
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
636
625
|
}
|
|
637
626
|
this.plumber.on('connection:drag', (connection) => {
|
|
638
|
-
|
|
639
|
-
this.dragFromNodeId =
|
|
640
|
-
connection.data.nodeId ||
|
|
641
|
-
document.getElementById(connection.sourceId).closest('.node').id;
|
|
627
|
+
this.dragFromNodeId = connection.data.nodeId;
|
|
642
628
|
this.sourceId = connection.sourceId;
|
|
629
|
+
this.connectionSourceX = connection.sourceX;
|
|
630
|
+
this.connectionSourceY = connection.sourceY;
|
|
643
631
|
this.originalConnectionTargetId = connection.target.id;
|
|
644
632
|
});
|
|
645
633
|
this.plumber.on('connection:abort', (info) => {
|
|
646
|
-
// console.log('Connection aborted', info);
|
|
647
634
|
this.makeConnection(info);
|
|
648
635
|
});
|
|
649
636
|
this.plumber.on('connection:detach', (info) => {
|
|
@@ -672,6 +659,7 @@ export class Editor extends RapidElement {
|
|
|
672
659
|
left: snapToGrid(this.connectionPlaceholder.position.left),
|
|
673
660
|
top: snapToGrid(this.connectionPlaceholder.position.top)
|
|
674
661
|
};
|
|
662
|
+
const isDragUp = !!this.connectionPlaceholder.dragUp;
|
|
675
663
|
// Update the placeholder to the snapped position
|
|
676
664
|
this.connectionPlaceholder.position = snappedPosition;
|
|
677
665
|
// Store the pending connection info
|
|
@@ -680,12 +668,14 @@ export class Editor extends RapidElement {
|
|
|
680
668
|
exitId: this.sourceId,
|
|
681
669
|
position: snappedPosition
|
|
682
670
|
};
|
|
683
|
-
// Show the context menu
|
|
671
|
+
// Show the context menu near the placeholder
|
|
684
672
|
const canvas = this.querySelector('#canvas');
|
|
685
673
|
if (canvas) {
|
|
686
674
|
const canvasRect = canvas.getBoundingClientRect();
|
|
687
|
-
const menuX = canvasRect.left + snappedPosition.left - 40;
|
|
688
|
-
const menuY =
|
|
675
|
+
const menuX = canvasRect.left + snappedPosition.left - 40;
|
|
676
|
+
const menuY = isDragUp
|
|
677
|
+
? canvasRect.top + snappedPosition.top + 74 // just below placeholder bottom
|
|
678
|
+
: canvasRect.top + snappedPosition.top + 80; // just below placeholder
|
|
689
679
|
const canvasMenu = this.querySelector('temba-canvas-menu');
|
|
690
680
|
if (canvasMenu) {
|
|
691
681
|
canvasMenu.show(menuX, menuY, {
|
|
@@ -706,6 +696,8 @@ export class Editor extends RapidElement {
|
|
|
706
696
|
// Clear connection state (but keep sourceId/dragFromNodeId if we have a pending connection)
|
|
707
697
|
if (!this.pendingCanvasConnection) {
|
|
708
698
|
this.sourceId = null;
|
|
699
|
+
this.connectionSourceX = null;
|
|
700
|
+
this.connectionSourceY = null;
|
|
709
701
|
this.dragFromNodeId = null;
|
|
710
702
|
}
|
|
711
703
|
this.targetId = null;
|
|
@@ -862,7 +854,7 @@ export class Editor extends RapidElement {
|
|
|
862
854
|
clearTimeout(this.activityTimer);
|
|
863
855
|
}
|
|
864
856
|
this.activityTimer = window.setTimeout(() => {
|
|
865
|
-
|
|
857
|
+
this.fetchActivityData();
|
|
866
858
|
}, this.activityInterval);
|
|
867
859
|
});
|
|
868
860
|
}
|
|
@@ -949,6 +941,7 @@ export class Editor extends RapidElement {
|
|
|
949
941
|
return;
|
|
950
942
|
if (this.isReadOnly())
|
|
951
943
|
return;
|
|
944
|
+
this.blurActiveContentEditable();
|
|
952
945
|
const element = event.currentTarget;
|
|
953
946
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
954
947
|
const target = event.target;
|
|
@@ -1008,10 +1001,22 @@ export class Editor extends RapidElement {
|
|
|
1008
1001
|
// We clicked on empty canvas space, start selection
|
|
1009
1002
|
this.handleCanvasMouseDown(event);
|
|
1010
1003
|
}
|
|
1004
|
+
blurActiveContentEditable() {
|
|
1005
|
+
var _b;
|
|
1006
|
+
let active = document.activeElement;
|
|
1007
|
+
while ((_b = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement) {
|
|
1008
|
+
active = active.shadowRoot.activeElement;
|
|
1009
|
+
}
|
|
1010
|
+
if (active instanceof HTMLElement &&
|
|
1011
|
+
active.getAttribute('contenteditable') === 'true') {
|
|
1012
|
+
active.blur();
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1011
1015
|
handleCanvasMouseDown(event) {
|
|
1012
1016
|
var _b;
|
|
1013
1017
|
if (this.isReadOnly())
|
|
1014
1018
|
return;
|
|
1019
|
+
this.blurActiveContentEditable();
|
|
1015
1020
|
const target = event.target;
|
|
1016
1021
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
1017
1022
|
// Ignore clicks on exits
|
|
@@ -1219,72 +1224,52 @@ export class Editor extends RapidElement {
|
|
|
1219
1224
|
renderConnectionPlaceholder() {
|
|
1220
1225
|
if (!this.connectionPlaceholder || !this.connectionPlaceholder.visible)
|
|
1221
1226
|
return '';
|
|
1222
|
-
const { position } = this.connectionPlaceholder;
|
|
1227
|
+
const { position, dragUp } = this.connectionPlaceholder;
|
|
1223
1228
|
// Render connection line when we have a pending connection (after drop)
|
|
1224
1229
|
let svgPath = null;
|
|
1225
|
-
if (this.sourceId &&
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
pathData += ` L ${sourceX} ${midY - cornerRadius}`;
|
|
1252
|
-
pathData += ` Q ${sourceX} ${midY}, ${sourceX + (targetX > sourceX ? cornerRadius : -cornerRadius)} ${midY}`;
|
|
1253
|
-
pathData += ` L ${targetX - (targetX > sourceX ? cornerRadius : -cornerRadius)} ${midY}`;
|
|
1254
|
-
pathData += ` Q ${targetX} ${midY}, ${targetX} ${midY + cornerRadius}`;
|
|
1255
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1256
|
-
}
|
|
1257
|
-
else {
|
|
1258
|
-
// Direct horizontal transition
|
|
1259
|
-
pathData += ` L ${targetX} ${verticalStart}`;
|
|
1260
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
else {
|
|
1264
|
-
// Straight vertical line
|
|
1265
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1266
|
-
}
|
|
1267
|
-
pathData += ` L ${targetX} ${targetY}`;
|
|
1268
|
-
svgPath = html `
|
|
1269
|
-
<svg
|
|
1270
|
-
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;"
|
|
1271
|
-
>
|
|
1272
|
-
<path
|
|
1273
|
-
d="${pathData}"
|
|
1274
|
-
fill="none"
|
|
1275
|
-
stroke="var(--color-connectors, #ccc)"
|
|
1276
|
-
stroke-width="3"
|
|
1277
|
-
class="plumb-connector"
|
|
1278
|
-
/>
|
|
1279
|
-
<polygon
|
|
1280
|
-
points="${targetX},${targetY} ${targetX - 6.5},${targetY -
|
|
1281
|
-
13} ${targetX + 6.5},${targetY - 13}"
|
|
1282
|
-
fill="var(--color-connectors, #ccc)"
|
|
1283
|
-
class="plumb-arrow"
|
|
1284
|
-
/>
|
|
1285
|
-
</svg>
|
|
1286
|
-
`;
|
|
1230
|
+
if (this.sourceId &&
|
|
1231
|
+
this.dragFromNodeId &&
|
|
1232
|
+
this.pendingCanvasConnection &&
|
|
1233
|
+
this.connectionSourceX != null &&
|
|
1234
|
+
this.connectionSourceY != null) {
|
|
1235
|
+
const sourceX = this.connectionSourceX;
|
|
1236
|
+
const sourceY = this.connectionSourceY;
|
|
1237
|
+
const targetX = position.left + 100;
|
|
1238
|
+
// When dragging up, connect to the placeholder bottom; otherwise to the top
|
|
1239
|
+
const targetY = dragUp ? position.top + 64 : position.top;
|
|
1240
|
+
const routeFace = dragUp
|
|
1241
|
+
? targetX < sourceX
|
|
1242
|
+
? 'left'
|
|
1243
|
+
: 'right'
|
|
1244
|
+
: 'top';
|
|
1245
|
+
const pathData = calculateFlowchartPath(sourceX, sourceY, targetX, targetY, 20, dragUp ? 0 : 10, 5, routeFace);
|
|
1246
|
+
const aw = ARROW_HALF_WIDTH;
|
|
1247
|
+
const al = ARROW_LENGTH;
|
|
1248
|
+
let arrowPoints;
|
|
1249
|
+
if (dragUp) {
|
|
1250
|
+
// Arrow tip pointing up, base at placeholder bottom
|
|
1251
|
+
arrowPoints = `${targetX},${targetY - al} ${targetX - aw},${targetY} ${targetX + aw},${targetY}`;
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
// Arrow pointing down into top of placeholder
|
|
1255
|
+
arrowPoints = `${targetX},${targetY} ${targetX - aw},${targetY - al} ${targetX + aw},${targetY - al}`;
|
|
1287
1256
|
}
|
|
1257
|
+
svgPath = html `
|
|
1258
|
+
<svg
|
|
1259
|
+
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;"
|
|
1260
|
+
>
|
|
1261
|
+
<path
|
|
1262
|
+
d="${pathData}"
|
|
1263
|
+
fill="none"
|
|
1264
|
+
stroke="var(--color-connectors, #ccc)"
|
|
1265
|
+
stroke-width="3"
|
|
1266
|
+
/>
|
|
1267
|
+
<polygon
|
|
1268
|
+
points="${arrowPoints}"
|
|
1269
|
+
fill="var(--color-connectors, #ccc)"
|
|
1270
|
+
/>
|
|
1271
|
+
</svg>
|
|
1272
|
+
`;
|
|
1288
1273
|
}
|
|
1289
1274
|
return html `${svgPath}
|
|
1290
1275
|
<div
|
|
@@ -1306,17 +1291,13 @@ export class Editor extends RapidElement {
|
|
|
1306
1291
|
}
|
|
1307
1292
|
/**
|
|
1308
1293
|
* Checks for node collisions and reflows nodes as needed.
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
* @param movedNodeUuids - UUIDs of nodes that were just moved/dropped
|
|
1312
|
-
* @param droppedNodeUuid - UUID of the specific node that was dropped (if applicable)
|
|
1313
|
-
* @param dropTargetBounds - Bounds of the node that was dropped onto (if applicable)
|
|
1294
|
+
* Sacred nodes (just moved/dropped) keep their positions while
|
|
1295
|
+
* other nodes are moved in the least-disruptive direction.
|
|
1314
1296
|
*/
|
|
1315
|
-
checkCollisionsAndReflow(
|
|
1297
|
+
checkCollisionsAndReflow(sacredNodeUuids) {
|
|
1316
1298
|
var _b;
|
|
1317
1299
|
if (!this.definition)
|
|
1318
1300
|
return;
|
|
1319
|
-
// Get all node bounds (only for actual nodes, not stickies)
|
|
1320
1301
|
const allBounds = [];
|
|
1321
1302
|
for (const node of this.definition.nodes) {
|
|
1322
1303
|
const nodeUI = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid];
|
|
@@ -1327,35 +1308,13 @@ export class Editor extends RapidElement {
|
|
|
1327
1308
|
allBounds.push(bounds);
|
|
1328
1309
|
}
|
|
1329
1310
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
const droppedBounds = allBounds.find((b) => b.uuid === droppedNodeUuid);
|
|
1334
|
-
if (droppedBounds) {
|
|
1335
|
-
// Check if the bottom of the dropped node is below the midpoint of the target
|
|
1336
|
-
// If bottom is above midpoint, dropped node gets preference (targetHasPriority = false)
|
|
1337
|
-
// If bottom is below midpoint, target gets preference (targetHasPriority = true)
|
|
1338
|
-
const droppedBottom = droppedBounds.bottom;
|
|
1339
|
-
const targetMidpoint = dropTargetBounds.top + dropTargetBounds.height / 2;
|
|
1340
|
-
targetHasPriority = droppedBottom > targetMidpoint;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
// Calculate reflow positions for each moved node
|
|
1344
|
-
const allReflowPositions = {};
|
|
1345
|
-
for (const movedUuid of movedNodeUuids) {
|
|
1346
|
-
const movedBounds = allBounds.find((b) => b.uuid === movedUuid);
|
|
1347
|
-
if (!movedBounds)
|
|
1348
|
-
continue;
|
|
1349
|
-
// Calculate reflow for this moved node
|
|
1350
|
-
const reflowPositions = calculateReflowPositions(movedUuid, movedBounds, allBounds, droppedNodeUuid === movedUuid ? targetHasPriority : false);
|
|
1351
|
-
// Merge into all reflow positions
|
|
1311
|
+
const reflowPositions = calculateReflowPositions(sacredNodeUuids, allBounds);
|
|
1312
|
+
if (reflowPositions.size > 0) {
|
|
1313
|
+
const positions = {};
|
|
1352
1314
|
for (const [uuid, position] of reflowPositions.entries()) {
|
|
1353
|
-
|
|
1315
|
+
positions[uuid] = position;
|
|
1354
1316
|
}
|
|
1355
|
-
|
|
1356
|
-
// If there are positions to update, apply them
|
|
1357
|
-
if (Object.keys(allReflowPositions).length > 0) {
|
|
1358
|
-
getStore().getState().updateCanvasPositions(allReflowPositions);
|
|
1317
|
+
getStore().getState().updateCanvasPositions(positions);
|
|
1359
1318
|
}
|
|
1360
1319
|
}
|
|
1361
1320
|
handleMouseMove(event) {
|
|
@@ -1390,21 +1349,37 @@ export class Editor extends RapidElement {
|
|
|
1390
1349
|
this.targetId = null;
|
|
1391
1350
|
this.isValidTarget = true;
|
|
1392
1351
|
// Show connection placeholder when over empty canvas
|
|
1393
|
-
// Calculate position: horizontally centered at mouse, vertically just below mouse
|
|
1394
1352
|
const canvas = this.querySelector('#canvas');
|
|
1395
1353
|
if (canvas) {
|
|
1396
1354
|
const canvasRect = canvas.getBoundingClientRect();
|
|
1397
1355
|
const relativeX = event.clientX - canvasRect.left;
|
|
1398
1356
|
const relativeY = event.clientY - canvasRect.top;
|
|
1399
|
-
|
|
1400
|
-
const
|
|
1401
|
-
const
|
|
1357
|
+
const placeholderWidth = 200;
|
|
1358
|
+
const placeholderHeight = 64;
|
|
1359
|
+
const arrowLength = ARROW_LENGTH;
|
|
1360
|
+
const cursorGap = CURSOR_GAP;
|
|
1361
|
+
// Determine if cursor is above the source exit using stored sourceY
|
|
1362
|
+
const dragUp = this.connectionSourceY != null
|
|
1363
|
+
? relativeY < this.connectionSourceY
|
|
1364
|
+
: false;
|
|
1365
|
+
let top;
|
|
1366
|
+
if (dragUp) {
|
|
1367
|
+
// Arrow points up: tip at cy + cursorGap.
|
|
1368
|
+
// Placeholder bottom should sit just above the arrow tip.
|
|
1369
|
+
top = relativeY + cursorGap - placeholderHeight;
|
|
1370
|
+
}
|
|
1371
|
+
else {
|
|
1372
|
+
// Arrow points down: tip at cy - cursorGap + arrowLength.
|
|
1373
|
+
// Placeholder top sits just below the arrow tip.
|
|
1374
|
+
top = relativeY - cursorGap + arrowLength;
|
|
1375
|
+
}
|
|
1402
1376
|
this.connectionPlaceholder = {
|
|
1403
1377
|
position: {
|
|
1404
1378
|
left: relativeX - placeholderWidth / 2,
|
|
1405
|
-
top
|
|
1379
|
+
top
|
|
1406
1380
|
},
|
|
1407
|
-
visible: true
|
|
1381
|
+
visible: true,
|
|
1382
|
+
dragUp
|
|
1408
1383
|
};
|
|
1409
1384
|
}
|
|
1410
1385
|
}
|
|
@@ -1505,34 +1480,7 @@ export class Editor extends RapidElement {
|
|
|
1505
1480
|
if (nodeUuids.length > 0) {
|
|
1506
1481
|
// Allow DOM to update before checking collisions
|
|
1507
1482
|
setTimeout(() => {
|
|
1508
|
-
|
|
1509
|
-
// If only one node was moved, detect which node it might have been dropped onto
|
|
1510
|
-
let droppedNodeUuid = null;
|
|
1511
|
-
let dropTargetBounds = null;
|
|
1512
|
-
if (nodeUuids.length === 1) {
|
|
1513
|
-
droppedNodeUuid = nodeUuids[0];
|
|
1514
|
-
const droppedNodeUI = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[droppedNodeUuid];
|
|
1515
|
-
if (droppedNodeUI === null || droppedNodeUI === void 0 ? void 0 : droppedNodeUI.position) {
|
|
1516
|
-
const droppedBounds = getNodeBounds(droppedNodeUuid, droppedNodeUI.position);
|
|
1517
|
-
if (droppedBounds) {
|
|
1518
|
-
// Find which node (if any) the dropped node overlaps with
|
|
1519
|
-
for (const node of this.definition.nodes) {
|
|
1520
|
-
if (node.uuid === droppedNodeUuid)
|
|
1521
|
-
continue;
|
|
1522
|
-
const nodeUI = (_c = this.definition._ui) === null || _c === void 0 ? void 0 : _c.nodes[node.uuid];
|
|
1523
|
-
if (!(nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.position))
|
|
1524
|
-
continue;
|
|
1525
|
-
const targetBounds = getNodeBounds(node.uuid, nodeUI.position);
|
|
1526
|
-
if (targetBounds &&
|
|
1527
|
-
nodesOverlap(droppedBounds, targetBounds)) {
|
|
1528
|
-
dropTargetBounds = targetBounds;
|
|
1529
|
-
break; // Use the first overlapping node
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
this.checkCollisionsAndReflow(nodeUuids, droppedNodeUuid, dropTargetBounds);
|
|
1483
|
+
this.checkCollisionsAndReflow(nodeUuids);
|
|
1536
1484
|
}, 0);
|
|
1537
1485
|
}
|
|
1538
1486
|
else {
|
|
@@ -1642,6 +1590,8 @@ export class Editor extends RapidElement {
|
|
|
1642
1590
|
this.pendingCanvasConnection = null;
|
|
1643
1591
|
this.connectionPlaceholder = null;
|
|
1644
1592
|
this.sourceId = null;
|
|
1593
|
+
this.connectionSourceX = null;
|
|
1594
|
+
this.connectionSourceY = null;
|
|
1645
1595
|
this.dragFromNodeId = null;
|
|
1646
1596
|
}
|
|
1647
1597
|
else {
|
|
@@ -1669,6 +1619,8 @@ export class Editor extends RapidElement {
|
|
|
1669
1619
|
this.pendingCanvasConnection = null;
|
|
1670
1620
|
this.connectionPlaceholder = null;
|
|
1671
1621
|
this.sourceId = null;
|
|
1622
|
+
this.connectionSourceX = null;
|
|
1623
|
+
this.connectionSourceY = null;
|
|
1672
1624
|
this.dragFromNodeId = null;
|
|
1673
1625
|
this.originalConnectionTargetId = null;
|
|
1674
1626
|
}
|
|
@@ -1779,6 +1731,10 @@ export class Editor extends RapidElement {
|
|
|
1779
1731
|
handleActionEditRequested(event) {
|
|
1780
1732
|
// For action editing, we set the action and find the corresponding node
|
|
1781
1733
|
this.editingAction = event.detail.action;
|
|
1734
|
+
this.dialogOrigin =
|
|
1735
|
+
event.detail.originX != null
|
|
1736
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
1737
|
+
: null;
|
|
1782
1738
|
// Find the node that contains this action
|
|
1783
1739
|
const nodeUuid = event.detail.nodeUuid;
|
|
1784
1740
|
const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
@@ -1814,6 +1770,10 @@ export class Editor extends RapidElement {
|
|
|
1814
1770
|
handleNodeEditRequested(event) {
|
|
1815
1771
|
this.editingNode = event.detail.node;
|
|
1816
1772
|
this.editingNodeUI = event.detail.nodeUI;
|
|
1773
|
+
this.dialogOrigin =
|
|
1774
|
+
event.detail.originX != null
|
|
1775
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
1776
|
+
: null;
|
|
1817
1777
|
}
|
|
1818
1778
|
handleNodeDeleted(event) {
|
|
1819
1779
|
const nodeUuid = event.detail.uuid;
|
|
@@ -1856,6 +1816,8 @@ export class Editor extends RapidElement {
|
|
|
1856
1816
|
this.pendingCanvasConnection = null;
|
|
1857
1817
|
this.connectionPlaceholder = null;
|
|
1858
1818
|
this.sourceId = null;
|
|
1819
|
+
this.connectionSourceX = null;
|
|
1820
|
+
this.connectionSourceY = null;
|
|
1859
1821
|
this.dragFromNodeId = null;
|
|
1860
1822
|
}
|
|
1861
1823
|
// Reset the creation flags
|
|
@@ -1882,6 +1844,7 @@ export class Editor extends RapidElement {
|
|
|
1882
1844
|
this.editingNode = null;
|
|
1883
1845
|
this.editingNodeUI = null;
|
|
1884
1846
|
this.editingAction = null;
|
|
1847
|
+
this.dialogOrigin = null;
|
|
1885
1848
|
}
|
|
1886
1849
|
handleActionEditCanceled() {
|
|
1887
1850
|
// If we were creating a new node, just discard it
|
|
@@ -1910,6 +1873,8 @@ export class Editor extends RapidElement {
|
|
|
1910
1873
|
this.pendingCanvasConnection = null;
|
|
1911
1874
|
this.connectionPlaceholder = null;
|
|
1912
1875
|
this.sourceId = null;
|
|
1876
|
+
this.connectionSourceX = null;
|
|
1877
|
+
this.connectionSourceY = null;
|
|
1913
1878
|
this.dragFromNodeId = null;
|
|
1914
1879
|
}
|
|
1915
1880
|
// Reset the creation flags
|
|
@@ -2336,6 +2301,7 @@ export class Editor extends RapidElement {
|
|
|
2336
2301
|
}
|
|
2337
2302
|
this.localizationWindowHidden = false;
|
|
2338
2303
|
this.revisionsWindowHidden = true;
|
|
2304
|
+
this.issuesWindowHidden = true;
|
|
2339
2305
|
const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
|
|
2340
2306
|
if (!alreadySelected) {
|
|
2341
2307
|
this.handleLanguageChange(languages[0].code);
|
|
@@ -2543,11 +2509,42 @@ export class Editor extends RapidElement {
|
|
|
2543
2509
|
}
|
|
2544
2510
|
this.autoTranslating = false;
|
|
2545
2511
|
}
|
|
2512
|
+
handleIssuesTabClick() {
|
|
2513
|
+
this.issuesWindowHidden = false;
|
|
2514
|
+
this.revisionsWindowHidden = true;
|
|
2515
|
+
this.localizationWindowHidden = true;
|
|
2516
|
+
}
|
|
2517
|
+
handleIssuesWindowClosed() {
|
|
2518
|
+
this.issuesWindowHidden = true;
|
|
2519
|
+
}
|
|
2520
|
+
handleIssueItemClick(issue) {
|
|
2521
|
+
var _b;
|
|
2522
|
+
const issuesWindow = document.getElementById('issues-window');
|
|
2523
|
+
issuesWindow === null || issuesWindow === void 0 ? void 0 : issuesWindow.handleClose();
|
|
2524
|
+
this.issuesWindowHidden = true;
|
|
2525
|
+
this.focusNode(issue.node_uuid);
|
|
2526
|
+
const node = this.definition.nodes.find((n) => n.uuid === issue.node_uuid);
|
|
2527
|
+
if (!node)
|
|
2528
|
+
return;
|
|
2529
|
+
if (issue.action_uuid) {
|
|
2530
|
+
const action = (_b = node.actions) === null || _b === void 0 ? void 0 : _b.find((a) => a.uuid === issue.action_uuid);
|
|
2531
|
+
if (action) {
|
|
2532
|
+
this.editingAction = action;
|
|
2533
|
+
this.editingNode = node;
|
|
2534
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
else {
|
|
2538
|
+
this.editingNode = node;
|
|
2539
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2546
2542
|
handleRevisionsTabClick() {
|
|
2547
2543
|
if (this.revisionsWindowHidden) {
|
|
2548
2544
|
this.fetchRevisions();
|
|
2549
2545
|
this.revisionsWindowHidden = false;
|
|
2550
|
-
this.
|
|
2546
|
+
this.issuesWindowHidden = true;
|
|
2547
|
+
this.localizationWindowHidden = true;
|
|
2551
2548
|
}
|
|
2552
2549
|
}
|
|
2553
2550
|
handleRevisionsWindowClosed() {
|
|
@@ -2648,6 +2645,51 @@ export class Editor extends RapidElement {
|
|
|
2648
2645
|
// Fetch the latest version of the flow to ensure the store is up to date
|
|
2649
2646
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
2650
2647
|
}
|
|
2648
|
+
renderIssuesTab() {
|
|
2649
|
+
var _b;
|
|
2650
|
+
if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length))
|
|
2651
|
+
return '';
|
|
2652
|
+
return html `
|
|
2653
|
+
<temba-floating-tab
|
|
2654
|
+
id="issues-tab"
|
|
2655
|
+
icon="alert_warning"
|
|
2656
|
+
label="Flow Issues"
|
|
2657
|
+
color="tomato"
|
|
2658
|
+
order="1"
|
|
2659
|
+
.hidden=${!this.issuesWindowHidden}
|
|
2660
|
+
@temba-button-clicked=${this.handleIssuesTabClick}
|
|
2661
|
+
></temba-floating-tab>
|
|
2662
|
+
`;
|
|
2663
|
+
}
|
|
2664
|
+
renderIssuesWindow() {
|
|
2665
|
+
var _b;
|
|
2666
|
+
if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length))
|
|
2667
|
+
return '';
|
|
2668
|
+
return html `
|
|
2669
|
+
<temba-floating-window
|
|
2670
|
+
id="issues-window"
|
|
2671
|
+
header="Flow Issues"
|
|
2672
|
+
.width=${360}
|
|
2673
|
+
.maxHeight=${600}
|
|
2674
|
+
.top=${75}
|
|
2675
|
+
color="tomato"
|
|
2676
|
+
.hidden=${this.issuesWindowHidden}
|
|
2677
|
+
@temba-dialog-hidden=${this.handleIssuesWindowClosed}
|
|
2678
|
+
>
|
|
2679
|
+
<div style="display:flex; flex-direction:column; gap:2px;">
|
|
2680
|
+
${this.flowIssues.map((issue) => html `
|
|
2681
|
+
<div
|
|
2682
|
+
class="issue-list-item"
|
|
2683
|
+
@click=${() => this.handleIssueItemClick(issue)}
|
|
2684
|
+
>
|
|
2685
|
+
<temba-icon name="alert_warning" size="1.2"></temba-icon>
|
|
2686
|
+
<span>${formatIssueMessage(issue)}</span>
|
|
2687
|
+
</div>
|
|
2688
|
+
`)}
|
|
2689
|
+
</div>
|
|
2690
|
+
</temba-floating-window>
|
|
2691
|
+
`;
|
|
2692
|
+
}
|
|
2651
2693
|
renderRevisionsTab() {
|
|
2652
2694
|
return html `
|
|
2653
2695
|
<temba-floating-tab
|
|
@@ -2655,7 +2697,7 @@ export class Editor extends RapidElement {
|
|
|
2655
2697
|
icon="revisions"
|
|
2656
2698
|
label="Revisions"
|
|
2657
2699
|
color="rgb(142, 94, 167)"
|
|
2658
|
-
|
|
2700
|
+
order="2"
|
|
2659
2701
|
.hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
|
|
2660
2702
|
@temba-button-clicked=${this.handleRevisionsTabClick}
|
|
2661
2703
|
></temba-floating-tab>
|
|
@@ -2926,7 +2968,7 @@ export class Editor extends RapidElement {
|
|
|
2926
2968
|
icon="language"
|
|
2927
2969
|
label="Translate Flow"
|
|
2928
2970
|
color="#6b7280"
|
|
2929
|
-
|
|
2971
|
+
order="3"
|
|
2930
2972
|
.hidden=${!this.localizationWindowHidden}
|
|
2931
2973
|
@temba-button-clicked=${this.handleLocalizationTabClick}
|
|
2932
2974
|
></temba-floating-tab>
|
|
@@ -2973,8 +3015,9 @@ export class Editor extends RapidElement {
|
|
|
2973
3015
|
${unsafeCSS(CanvasNode.styles.cssText)}
|
|
2974
3016
|
</style>`;
|
|
2975
3017
|
const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
|
|
2976
|
-
return html `${style} ${this.
|
|
2977
|
-
${this.
|
|
3018
|
+
return html `${style} ${this.renderIssuesWindow()}
|
|
3019
|
+
${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
|
|
3020
|
+
${this.renderAutoTranslateDialog()}
|
|
2978
3021
|
<div id="editor">
|
|
2979
3022
|
<div
|
|
2980
3023
|
id="grid"
|
|
@@ -3047,6 +3090,7 @@ export class Editor extends RapidElement {
|
|
|
3047
3090
|
.node=${this.editingNode}
|
|
3048
3091
|
.nodeUI=${this.editingNodeUI}
|
|
3049
3092
|
.action=${this.editingAction}
|
|
3093
|
+
.dialogOrigin=${this.dialogOrigin}
|
|
3050
3094
|
@temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
3051
3095
|
@temba-action-saved=${(e) => this.handleActionSaved(e.detail.action)}
|
|
3052
3096
|
@temba-node-edit-cancelled=${this.handleNodeEditCanceled}
|
|
@@ -3060,7 +3104,8 @@ export class Editor extends RapidElement {
|
|
|
3060
3104
|
.features=${this.features}
|
|
3061
3105
|
></temba-node-type-selector>`
|
|
3062
3106
|
: ''}
|
|
3063
|
-
${this.
|
|
3107
|
+
${this.renderIssuesTab()} ${this.renderRevisionsTab()}
|
|
3108
|
+
${this.renderLocalizationTab()} `;
|
|
3064
3109
|
}
|
|
3065
3110
|
}
|
|
3066
3111
|
__decorate([
|
|
@@ -3099,6 +3144,9 @@ __decorate([
|
|
|
3099
3144
|
__decorate([
|
|
3100
3145
|
fromStore(zustand, (state) => state.getCurrentActivity())
|
|
3101
3146
|
], Editor.prototype, "activityData", void 0);
|
|
3147
|
+
__decorate([
|
|
3148
|
+
fromStore(zustand, (state) => { var _b; return ((_b = state.flowInfo) === null || _b === void 0 ? void 0 : _b.issues) || []; })
|
|
3149
|
+
], Editor.prototype, "flowIssues", void 0);
|
|
3102
3150
|
__decorate([
|
|
3103
3151
|
state()
|
|
3104
3152
|
], Editor.prototype, "isDragging", void 0);
|
|
@@ -3129,6 +3177,9 @@ __decorate([
|
|
|
3129
3177
|
__decorate([
|
|
3130
3178
|
state()
|
|
3131
3179
|
], Editor.prototype, "isValidTarget", void 0);
|
|
3180
|
+
__decorate([
|
|
3181
|
+
state()
|
|
3182
|
+
], Editor.prototype, "issuesWindowHidden", void 0);
|
|
3132
3183
|
__decorate([
|
|
3133
3184
|
state()
|
|
3134
3185
|
], Editor.prototype, "localizationWindowHidden", void 0);
|