@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
package/src/flow/Editor.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getStore } from '../store/Store';
|
|
12
12
|
import {
|
|
13
13
|
AppState,
|
|
14
|
+
FlowIssue,
|
|
14
15
|
fromStore,
|
|
15
16
|
zustand,
|
|
16
17
|
FLOW_SPEC_VERSION
|
|
@@ -19,6 +20,13 @@ import { RapidElement } from '../RapidElement';
|
|
|
19
20
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
20
21
|
import { CustomEventType, Workspace } from '../interfaces';
|
|
21
22
|
import { generateUUID, postJSON, fetchResults, getClasses } from '../utils';
|
|
23
|
+
import {
|
|
24
|
+
formatIssueMessage,
|
|
25
|
+
getNodeBounds,
|
|
26
|
+
calculateReflowPositions,
|
|
27
|
+
NodeBounds,
|
|
28
|
+
snapToGrid
|
|
29
|
+
} from './utils';
|
|
22
30
|
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
23
31
|
|
|
24
32
|
interface Revision {
|
|
@@ -37,25 +45,20 @@ interface Revision {
|
|
|
37
45
|
import { ACTION_GROUP_METADATA } from './types';
|
|
38
46
|
import { Checkbox } from '../form/Checkbox';
|
|
39
47
|
|
|
40
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
Plumber,
|
|
50
|
+
calculateFlowchartPath,
|
|
51
|
+
ARROW_LENGTH,
|
|
52
|
+
ARROW_HALF_WIDTH,
|
|
53
|
+
CURSOR_GAP
|
|
54
|
+
} from './Plumber';
|
|
41
55
|
import { CanvasNode } from './CanvasNode';
|
|
42
56
|
import { Dialog } from '../layout/Dialog';
|
|
43
|
-
|
|
57
|
+
|
|
44
58
|
import { CanvasMenu, CanvasMenuSelection } from './CanvasMenu';
|
|
45
59
|
import { NodeTypeSelector, NodeTypeSelection } from './NodeTypeSelector';
|
|
46
|
-
import {
|
|
47
|
-
getNodeBounds,
|
|
48
|
-
calculateReflowPositions,
|
|
49
|
-
NodeBounds,
|
|
50
|
-
nodesOverlap
|
|
51
|
-
} from './utils';
|
|
52
60
|
import { FloatingWindow } from '../layout/FloatingWindow';
|
|
53
61
|
|
|
54
|
-
export function snapToGrid(value: number): number {
|
|
55
|
-
const snapped = Math.round(value / 20) * 20;
|
|
56
|
-
return Math.max(snapped, 0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
62
|
export function findNodeForExit(
|
|
60
63
|
definition: FlowDefinition,
|
|
61
64
|
exitUuid: string
|
|
@@ -122,7 +125,7 @@ const DROP_PREVIEW_OFFSET_X = 20;
|
|
|
122
125
|
const DROP_PREVIEW_OFFSET_Y = 20;
|
|
123
126
|
|
|
124
127
|
export class Editor extends RapidElement {
|
|
125
|
-
//
|
|
128
|
+
// connection SVGs are appended directly to the canvas, so we need light DOM
|
|
126
129
|
createRenderRoot() {
|
|
127
130
|
return this;
|
|
128
131
|
}
|
|
@@ -172,6 +175,9 @@ export class Editor extends RapidElement {
|
|
|
172
175
|
@fromStore(zustand, (state: AppState) => state.getCurrentActivity())
|
|
173
176
|
private activityData!: any;
|
|
174
177
|
|
|
178
|
+
@fromStore(zustand, (state: AppState) => state.flowInfo?.issues || [])
|
|
179
|
+
private flowIssues!: FlowIssue[];
|
|
180
|
+
|
|
175
181
|
// Drag state
|
|
176
182
|
@state()
|
|
177
183
|
private isDragging = false;
|
|
@@ -212,6 +218,13 @@ export class Editor extends RapidElement {
|
|
|
212
218
|
@state()
|
|
213
219
|
private isValidTarget = true;
|
|
214
220
|
|
|
221
|
+
// Canvas-relative source exit position (set at drag start)
|
|
222
|
+
private connectionSourceX: number | null = null;
|
|
223
|
+
private connectionSourceY: number | null = null;
|
|
224
|
+
|
|
225
|
+
@state()
|
|
226
|
+
private issuesWindowHidden = true;
|
|
227
|
+
|
|
215
228
|
@state()
|
|
216
229
|
private localizationWindowHidden = true;
|
|
217
230
|
|
|
@@ -264,6 +277,8 @@ export class Editor extends RapidElement {
|
|
|
264
277
|
@state()
|
|
265
278
|
private editingAction: Action | null = null;
|
|
266
279
|
|
|
280
|
+
private dialogOrigin: { x: number; y: number } | null = null;
|
|
281
|
+
|
|
267
282
|
@state()
|
|
268
283
|
private isCreatingNewNode = false;
|
|
269
284
|
|
|
@@ -294,6 +309,7 @@ export class Editor extends RapidElement {
|
|
|
294
309
|
private connectionPlaceholder: {
|
|
295
310
|
position: FlowPosition;
|
|
296
311
|
visible: boolean;
|
|
312
|
+
dragUp?: boolean;
|
|
297
313
|
} | null = null;
|
|
298
314
|
|
|
299
315
|
// Track pending connection when dropping on canvas
|
|
@@ -351,6 +367,10 @@ export class Editor extends RapidElement {
|
|
|
351
367
|
-webkit-font-smoothing: antialiased;
|
|
352
368
|
}
|
|
353
369
|
|
|
370
|
+
temba-floating-tab {
|
|
371
|
+
--floating-tab-right: 15px;
|
|
372
|
+
}
|
|
373
|
+
|
|
354
374
|
#grid {
|
|
355
375
|
position: relative;
|
|
356
376
|
background-color: #f9f9f9;
|
|
@@ -401,100 +421,56 @@ export class Editor extends RapidElement {
|
|
|
401
421
|
}
|
|
402
422
|
|
|
403
423
|
#grid.viewing-revision temba-flow-node,
|
|
404
|
-
#grid.viewing-revision svg.
|
|
405
|
-
#grid.viewing-revision .activity-overlay {
|
|
424
|
+
#grid.viewing-revision svg.plumb-connector {
|
|
406
425
|
opacity: 0.5;
|
|
407
426
|
}
|
|
408
427
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
height: initial;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
.jtk-endpoint {
|
|
415
|
-
z-index: 600;
|
|
416
|
-
opacity: 0;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.plumb-source {
|
|
420
|
-
z-index: 600;
|
|
421
|
-
cursor: pointer;
|
|
422
|
-
opacity: 0;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
.plumb-source.connected {
|
|
426
|
-
border-radius: 50%;
|
|
427
|
-
pointer-events: none;
|
|
428
|
+
svg.plumb-connector {
|
|
429
|
+
z-index: 10;
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
.plumb-
|
|
431
|
-
|
|
432
|
+
svg.plumb-connector path {
|
|
433
|
+
stroke: var(--color-connectors);
|
|
434
|
+
stroke-width: 3px;
|
|
432
435
|
}
|
|
433
436
|
|
|
434
|
-
.plumb-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
cursor: pointer;
|
|
438
|
-
fill: transparent;
|
|
437
|
+
svg.plumb-connector .plumb-arrow {
|
|
438
|
+
fill: var(--color-connectors);
|
|
439
|
+
stroke: none;
|
|
439
440
|
}
|
|
440
441
|
|
|
441
|
-
|
|
442
|
-
stroke: var(--color-
|
|
443
|
-
stroke-width: 3px;
|
|
442
|
+
svg.plumb-connector.hover path {
|
|
443
|
+
stroke: var(--color-success);
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
446
|
+
svg.plumb-connector.hover .plumb-arrow {
|
|
447
|
+
fill: var(--color-success);
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
|
|
451
|
-
fill: var(--color-connectors);
|
|
450
|
+
#canvas.read-only-connections svg.plumb-connector.hover path {
|
|
452
451
|
stroke: var(--color-connectors);
|
|
453
|
-
stroke-width: 0px !important;
|
|
454
|
-
margin-top: 6px;
|
|
455
|
-
z-index: 10;
|
|
456
452
|
}
|
|
457
453
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
stroke-width: 3px;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
body #canvas.read-only-connections svg.jtk-connector.jtk-hover path {
|
|
464
|
-
stroke: var(--color-connectors) !important;
|
|
454
|
+
#canvas.read-only-connections svg.plumb-connector.hover .plumb-arrow {
|
|
455
|
+
fill: var(--color-connectors);
|
|
465
456
|
}
|
|
466
457
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
458
|
+
#canvas.read-only-connections svg.plumb-connector,
|
|
459
|
+
#canvas.read-only-connections svg.plumb-connector * {
|
|
460
|
+
pointer-events: none !important;
|
|
461
|
+
cursor: default !important;
|
|
471
462
|
}
|
|
472
463
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
.plumb-connector.jtk-hover
|
|
476
|
-
.plumb-arrow {
|
|
477
|
-
fill: var(--color-connectors) !important;
|
|
478
|
-
ponter-events: none;
|
|
464
|
+
svg.plumb-connector.removing path {
|
|
465
|
+
stroke: var(--color-error);
|
|
479
466
|
}
|
|
480
467
|
|
|
481
|
-
|
|
482
|
-
|
|
468
|
+
svg.plumb-connector.removing .plumb-arrow {
|
|
469
|
+
fill: var(--color-error);
|
|
483
470
|
}
|
|
484
471
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
background: #f3f3f3;
|
|
488
|
-
border: 1px solid #d9d9d9;
|
|
489
|
-
color: #333;
|
|
490
|
-
border-radius: 4px;
|
|
491
|
-
padding: 2px 4px;
|
|
492
|
-
font-size: 10px;
|
|
493
|
-
font-weight: 600;
|
|
494
|
-
line-height: 0.9;
|
|
495
|
-
cursor: pointer;
|
|
496
|
-
z-index: 500;
|
|
497
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
472
|
+
svg.plumb-connector.dragging {
|
|
473
|
+
z-index: 99999;
|
|
498
474
|
}
|
|
499
475
|
|
|
500
476
|
/* Active contact count on nodes */
|
|
@@ -516,6 +492,30 @@ export class Editor extends RapidElement {
|
|
|
516
492
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
517
493
|
}
|
|
518
494
|
|
|
495
|
+
/* Activity overlay badges on connection exit stubs */
|
|
496
|
+
.activity-overlay {
|
|
497
|
+
position: absolute;
|
|
498
|
+
background: #f3f3f3;
|
|
499
|
+
border: 1px solid #d9d9d9;
|
|
500
|
+
color: #333;
|
|
501
|
+
border-radius: 4px;
|
|
502
|
+
padding: 2px 4px;
|
|
503
|
+
font-size: 10px;
|
|
504
|
+
font-weight: 600;
|
|
505
|
+
line-height: 0.9;
|
|
506
|
+
cursor: pointer;
|
|
507
|
+
z-index: 10;
|
|
508
|
+
pointer-events: auto;
|
|
509
|
+
white-space: nowrap;
|
|
510
|
+
user-select: none;
|
|
511
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#grid.viewing-revision .activity-overlay {
|
|
515
|
+
opacity: 0.5;
|
|
516
|
+
pointer-events: none;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
519
|
/* Recent contacts popup */
|
|
520
520
|
@keyframes popupBounceIn {
|
|
521
521
|
0% {
|
|
@@ -585,7 +585,6 @@ export class Editor extends RapidElement {
|
|
|
585
585
|
|
|
586
586
|
.recent-contacts-popup .contact-name:hover {
|
|
587
587
|
text-decoration: underline;
|
|
588
|
-
color: var(--color-link-primary, #1d4ed8);
|
|
589
588
|
}
|
|
590
589
|
|
|
591
590
|
.recent-contacts-popup .contact-operand {
|
|
@@ -601,17 +600,6 @@ export class Editor extends RapidElement {
|
|
|
601
600
|
color: #999;
|
|
602
601
|
}
|
|
603
602
|
|
|
604
|
-
/* Connection dragging feedback */
|
|
605
|
-
body svg.jtk-connector.jtk-dragging {
|
|
606
|
-
z-index: 99999 !important;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
.katavorio-drag-no-select svg.jtk-connector path,
|
|
610
|
-
.katavorio-drag-no-select svg.jtk-endpoint path {
|
|
611
|
-
pointer-events: none !important;
|
|
612
|
-
border: 1px solid purple;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
603
|
/* Connection target feedback */
|
|
616
604
|
temba-flow-node.connection-target-valid {
|
|
617
605
|
outline: 3px solid var(--color-success, #22c55e) !important;
|
|
@@ -641,10 +629,6 @@ export class Editor extends RapidElement {
|
|
|
641
629
|
border-radius: var(--curvature);
|
|
642
630
|
}
|
|
643
631
|
|
|
644
|
-
.jtk-floating-endpoint {
|
|
645
|
-
pointer-events: none;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
632
|
.localization-window-content {
|
|
649
633
|
display: flex;
|
|
650
634
|
flex-direction: column;
|
|
@@ -835,6 +819,26 @@ export class Editor extends RapidElement {
|
|
|
835
819
|
color: #9ca3af;
|
|
836
820
|
white-space: nowrap;
|
|
837
821
|
}
|
|
822
|
+
|
|
823
|
+
.issue-list-item {
|
|
824
|
+
display: flex;
|
|
825
|
+
align-items: center;
|
|
826
|
+
gap: 8px;
|
|
827
|
+
padding: 8px;
|
|
828
|
+
border-radius: 4px;
|
|
829
|
+
cursor: pointer;
|
|
830
|
+
font-size: 13px;
|
|
831
|
+
color: #333;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.issue-list-item:hover {
|
|
835
|
+
background: #fff5f5;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.issue-list-item temba-icon {
|
|
839
|
+
color: tomato;
|
|
840
|
+
flex-shrink: 0;
|
|
841
|
+
}
|
|
838
842
|
`;
|
|
839
843
|
}
|
|
840
844
|
|
|
@@ -852,17 +856,15 @@ export class Editor extends RapidElement {
|
|
|
852
856
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
853
857
|
}
|
|
854
858
|
|
|
855
|
-
this.plumber.on('connection:drag', (connection:
|
|
856
|
-
|
|
857
|
-
this.dragFromNodeId =
|
|
858
|
-
connection.data.nodeId ||
|
|
859
|
-
document.getElementById(connection.sourceId).closest('.node').id;
|
|
859
|
+
this.plumber.on('connection:drag', (connection: any) => {
|
|
860
|
+
this.dragFromNodeId = connection.data.nodeId;
|
|
860
861
|
this.sourceId = connection.sourceId;
|
|
862
|
+
this.connectionSourceX = connection.sourceX;
|
|
863
|
+
this.connectionSourceY = connection.sourceY;
|
|
861
864
|
this.originalConnectionTargetId = connection.target.id;
|
|
862
865
|
});
|
|
863
866
|
|
|
864
867
|
this.plumber.on('connection:abort', (info) => {
|
|
865
|
-
// console.log('Connection aborted', info);
|
|
866
868
|
this.makeConnection(info);
|
|
867
869
|
});
|
|
868
870
|
|
|
@@ -898,6 +900,7 @@ export class Editor extends RapidElement {
|
|
|
898
900
|
left: snapToGrid(this.connectionPlaceholder.position.left),
|
|
899
901
|
top: snapToGrid(this.connectionPlaceholder.position.top)
|
|
900
902
|
};
|
|
903
|
+
const isDragUp = !!this.connectionPlaceholder.dragUp;
|
|
901
904
|
|
|
902
905
|
// Update the placeholder to the snapped position
|
|
903
906
|
this.connectionPlaceholder.position = snappedPosition;
|
|
@@ -909,12 +912,14 @@ export class Editor extends RapidElement {
|
|
|
909
912
|
position: snappedPosition
|
|
910
913
|
};
|
|
911
914
|
|
|
912
|
-
// Show the context menu
|
|
915
|
+
// Show the context menu near the placeholder
|
|
913
916
|
const canvas = this.querySelector('#canvas');
|
|
914
917
|
if (canvas) {
|
|
915
918
|
const canvasRect = canvas.getBoundingClientRect();
|
|
916
|
-
const menuX = canvasRect.left + snappedPosition.left - 40;
|
|
917
|
-
const menuY =
|
|
919
|
+
const menuX = canvasRect.left + snappedPosition.left - 40;
|
|
920
|
+
const menuY = isDragUp
|
|
921
|
+
? canvasRect.top + snappedPosition.top + 74 // just below placeholder bottom
|
|
922
|
+
: canvasRect.top + snappedPosition.top + 80; // just below placeholder
|
|
918
923
|
|
|
919
924
|
const canvasMenu = this.querySelector(
|
|
920
925
|
'temba-canvas-menu'
|
|
@@ -950,6 +955,8 @@ export class Editor extends RapidElement {
|
|
|
950
955
|
// Clear connection state (but keep sourceId/dragFromNodeId if we have a pending connection)
|
|
951
956
|
if (!this.pendingCanvasConnection) {
|
|
952
957
|
this.sourceId = null;
|
|
958
|
+
this.connectionSourceX = null;
|
|
959
|
+
this.connectionSourceY = null;
|
|
953
960
|
this.dragFromNodeId = null;
|
|
954
961
|
}
|
|
955
962
|
this.targetId = null;
|
|
@@ -1130,7 +1137,7 @@ export class Editor extends RapidElement {
|
|
|
1130
1137
|
}
|
|
1131
1138
|
|
|
1132
1139
|
this.activityTimer = window.setTimeout(() => {
|
|
1133
|
-
|
|
1140
|
+
this.fetchActivityData();
|
|
1134
1141
|
}, this.activityInterval);
|
|
1135
1142
|
});
|
|
1136
1143
|
}
|
|
@@ -1246,6 +1253,7 @@ export class Editor extends RapidElement {
|
|
|
1246
1253
|
if (event.button !== 0) return;
|
|
1247
1254
|
|
|
1248
1255
|
if (this.isReadOnly()) return;
|
|
1256
|
+
this.blurActiveContentEditable();
|
|
1249
1257
|
|
|
1250
1258
|
const element = event.currentTarget as HTMLElement;
|
|
1251
1259
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
@@ -1315,8 +1323,22 @@ export class Editor extends RapidElement {
|
|
|
1315
1323
|
this.handleCanvasMouseDown(event);
|
|
1316
1324
|
}
|
|
1317
1325
|
|
|
1326
|
+
private blurActiveContentEditable(): void {
|
|
1327
|
+
let active: Element | null = document.activeElement;
|
|
1328
|
+
while (active?.shadowRoot?.activeElement) {
|
|
1329
|
+
active = active.shadowRoot.activeElement;
|
|
1330
|
+
}
|
|
1331
|
+
if (
|
|
1332
|
+
active instanceof HTMLElement &&
|
|
1333
|
+
active.getAttribute('contenteditable') === 'true'
|
|
1334
|
+
) {
|
|
1335
|
+
active.blur();
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1318
1339
|
private handleCanvasMouseDown(event: MouseEvent): void {
|
|
1319
1340
|
if (this.isReadOnly()) return;
|
|
1341
|
+
this.blurActiveContentEditable();
|
|
1320
1342
|
|
|
1321
1343
|
const target = event.target as HTMLElement;
|
|
1322
1344
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
@@ -1570,86 +1592,71 @@ export class Editor extends RapidElement {
|
|
|
1570
1592
|
if (!this.connectionPlaceholder || !this.connectionPlaceholder.visible)
|
|
1571
1593
|
return '';
|
|
1572
1594
|
|
|
1573
|
-
const { position } = this.connectionPlaceholder;
|
|
1595
|
+
const { position, dragUp } = this.connectionPlaceholder;
|
|
1574
1596
|
|
|
1575
1597
|
// Render connection line when we have a pending connection (after drop)
|
|
1576
1598
|
let svgPath = null;
|
|
1577
|
-
if (
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (sourceX !== targetX) {
|
|
1607
|
-
// Horizontal segment needed
|
|
1608
|
-
if (Math.abs(verticalEnd - verticalStart) > cornerRadius * 2) {
|
|
1609
|
-
// Enough space for corners
|
|
1610
|
-
pathData += ` L ${sourceX} ${midY - cornerRadius}`;
|
|
1611
|
-
pathData += ` Q ${sourceX} ${midY}, ${
|
|
1612
|
-
sourceX + (targetX > sourceX ? cornerRadius : -cornerRadius)
|
|
1613
|
-
} ${midY}`;
|
|
1614
|
-
pathData += ` L ${
|
|
1615
|
-
targetX - (targetX > sourceX ? cornerRadius : -cornerRadius)
|
|
1616
|
-
} ${midY}`;
|
|
1617
|
-
pathData += ` Q ${targetX} ${midY}, ${targetX} ${
|
|
1618
|
-
midY + cornerRadius
|
|
1619
|
-
}`;
|
|
1620
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1621
|
-
} else {
|
|
1622
|
-
// Direct horizontal transition
|
|
1623
|
-
pathData += ` L ${targetX} ${verticalStart}`;
|
|
1624
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1625
|
-
}
|
|
1626
|
-
} else {
|
|
1627
|
-
// Straight vertical line
|
|
1628
|
-
pathData += ` L ${targetX} ${verticalEnd}`;
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
pathData += ` L ${targetX} ${targetY}`;
|
|
1599
|
+
if (
|
|
1600
|
+
this.sourceId &&
|
|
1601
|
+
this.dragFromNodeId &&
|
|
1602
|
+
this.pendingCanvasConnection &&
|
|
1603
|
+
this.connectionSourceX != null &&
|
|
1604
|
+
this.connectionSourceY != null
|
|
1605
|
+
) {
|
|
1606
|
+
const sourceX = this.connectionSourceX;
|
|
1607
|
+
const sourceY = this.connectionSourceY;
|
|
1608
|
+
const targetX = position.left + 100;
|
|
1609
|
+
// When dragging up, connect to the placeholder bottom; otherwise to the top
|
|
1610
|
+
const targetY = dragUp ? position.top + 64 : position.top;
|
|
1611
|
+
|
|
1612
|
+
const routeFace: 'top' | 'left' | 'right' = dragUp
|
|
1613
|
+
? targetX < sourceX
|
|
1614
|
+
? 'left'
|
|
1615
|
+
: 'right'
|
|
1616
|
+
: 'top';
|
|
1617
|
+
|
|
1618
|
+
const pathData = calculateFlowchartPath(
|
|
1619
|
+
sourceX,
|
|
1620
|
+
sourceY,
|
|
1621
|
+
targetX,
|
|
1622
|
+
targetY,
|
|
1623
|
+
20,
|
|
1624
|
+
dragUp ? 0 : 10,
|
|
1625
|
+
5,
|
|
1626
|
+
routeFace
|
|
1627
|
+
);
|
|
1632
1628
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
13} ${targetX + 6.5},${targetY - 13}"
|
|
1647
|
-
fill="var(--color-connectors, #ccc)"
|
|
1648
|
-
class="plumb-arrow"
|
|
1649
|
-
/>
|
|
1650
|
-
</svg>
|
|
1651
|
-
`;
|
|
1629
|
+
const aw = ARROW_HALF_WIDTH;
|
|
1630
|
+
const al = ARROW_LENGTH;
|
|
1631
|
+
let arrowPoints: string;
|
|
1632
|
+
if (dragUp) {
|
|
1633
|
+
// Arrow tip pointing up, base at placeholder bottom
|
|
1634
|
+
arrowPoints = `${targetX},${targetY - al} ${targetX - aw},${targetY} ${
|
|
1635
|
+
targetX + aw
|
|
1636
|
+
},${targetY}`;
|
|
1637
|
+
} else {
|
|
1638
|
+
// Arrow pointing down into top of placeholder
|
|
1639
|
+
arrowPoints = `${targetX},${targetY} ${targetX - aw},${targetY - al} ${
|
|
1640
|
+
targetX + aw
|
|
1641
|
+
},${targetY - al}`;
|
|
1652
1642
|
}
|
|
1643
|
+
|
|
1644
|
+
svgPath = html`
|
|
1645
|
+
<svg
|
|
1646
|
+
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;"
|
|
1647
|
+
>
|
|
1648
|
+
<path
|
|
1649
|
+
d="${pathData}"
|
|
1650
|
+
fill="none"
|
|
1651
|
+
stroke="var(--color-connectors, #ccc)"
|
|
1652
|
+
stroke-width="3"
|
|
1653
|
+
/>
|
|
1654
|
+
<polygon
|
|
1655
|
+
points="${arrowPoints}"
|
|
1656
|
+
fill="var(--color-connectors, #ccc)"
|
|
1657
|
+
/>
|
|
1658
|
+
</svg>
|
|
1659
|
+
`;
|
|
1653
1660
|
}
|
|
1654
1661
|
|
|
1655
1662
|
return html`${svgPath}
|
|
@@ -1673,22 +1680,13 @@ export class Editor extends RapidElement {
|
|
|
1673
1680
|
|
|
1674
1681
|
/**
|
|
1675
1682
|
* Checks for node collisions and reflows nodes as needed.
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
* @param movedNodeUuids - UUIDs of nodes that were just moved/dropped
|
|
1679
|
-
* @param droppedNodeUuid - UUID of the specific node that was dropped (if applicable)
|
|
1680
|
-
* @param dropTargetBounds - Bounds of the node that was dropped onto (if applicable)
|
|
1683
|
+
* Sacred nodes (just moved/dropped) keep their positions while
|
|
1684
|
+
* other nodes are moved in the least-disruptive direction.
|
|
1681
1685
|
*/
|
|
1682
|
-
private checkCollisionsAndReflow(
|
|
1683
|
-
movedNodeUuids: string[],
|
|
1684
|
-
droppedNodeUuid: string | null = null,
|
|
1685
|
-
dropTargetBounds: NodeBounds | null = null
|
|
1686
|
-
): void {
|
|
1686
|
+
private checkCollisionsAndReflow(sacredNodeUuids: string[]): void {
|
|
1687
1687
|
if (!this.definition) return;
|
|
1688
1688
|
|
|
1689
|
-
// Get all node bounds (only for actual nodes, not stickies)
|
|
1690
1689
|
const allBounds: NodeBounds[] = [];
|
|
1691
|
-
|
|
1692
1690
|
for (const node of this.definition.nodes) {
|
|
1693
1691
|
const nodeUI = this.definition._ui?.nodes[node.uuid];
|
|
1694
1692
|
if (!nodeUI?.position) continue;
|
|
@@ -1699,45 +1697,17 @@ export class Editor extends RapidElement {
|
|
|
1699
1697
|
}
|
|
1700
1698
|
}
|
|
1701
1699
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
if (droppedBounds) {
|
|
1707
|
-
// Check if the bottom of the dropped node is below the midpoint of the target
|
|
1708
|
-
// If bottom is above midpoint, dropped node gets preference (targetHasPriority = false)
|
|
1709
|
-
// If bottom is below midpoint, target gets preference (targetHasPriority = true)
|
|
1710
|
-
const droppedBottom = droppedBounds.bottom;
|
|
1711
|
-
const targetMidpoint =
|
|
1712
|
-
dropTargetBounds.top + dropTargetBounds.height / 2;
|
|
1713
|
-
targetHasPriority = droppedBottom > targetMidpoint;
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// Calculate reflow positions for each moved node
|
|
1718
|
-
const allReflowPositions: { [uuid: string]: FlowPosition } = {};
|
|
1719
|
-
|
|
1720
|
-
for (const movedUuid of movedNodeUuids) {
|
|
1721
|
-
const movedBounds = allBounds.find((b) => b.uuid === movedUuid);
|
|
1722
|
-
if (!movedBounds) continue;
|
|
1723
|
-
|
|
1724
|
-
// Calculate reflow for this moved node
|
|
1725
|
-
const reflowPositions = calculateReflowPositions(
|
|
1726
|
-
movedUuid,
|
|
1727
|
-
movedBounds,
|
|
1728
|
-
allBounds,
|
|
1729
|
-
droppedNodeUuid === movedUuid ? targetHasPriority : false
|
|
1730
|
-
);
|
|
1700
|
+
const reflowPositions = calculateReflowPositions(
|
|
1701
|
+
sacredNodeUuids,
|
|
1702
|
+
allBounds
|
|
1703
|
+
);
|
|
1731
1704
|
|
|
1732
|
-
|
|
1705
|
+
if (reflowPositions.size > 0) {
|
|
1706
|
+
const positions: { [uuid: string]: FlowPosition } = {};
|
|
1733
1707
|
for (const [uuid, position] of reflowPositions.entries()) {
|
|
1734
|
-
|
|
1708
|
+
positions[uuid] = position;
|
|
1735
1709
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
// If there are positions to update, apply them
|
|
1739
|
-
if (Object.keys(allReflowPositions).length > 0) {
|
|
1740
|
-
getStore().getState().updateCanvasPositions(allReflowPositions);
|
|
1710
|
+
getStore().getState().updateCanvasPositions(positions);
|
|
1741
1711
|
}
|
|
1742
1712
|
}
|
|
1743
1713
|
|
|
@@ -1780,23 +1750,41 @@ export class Editor extends RapidElement {
|
|
|
1780
1750
|
this.isValidTarget = true;
|
|
1781
1751
|
|
|
1782
1752
|
// Show connection placeholder when over empty canvas
|
|
1783
|
-
// Calculate position: horizontally centered at mouse, vertically just below mouse
|
|
1784
1753
|
const canvas = this.querySelector('#canvas');
|
|
1785
1754
|
if (canvas) {
|
|
1786
1755
|
const canvasRect = canvas.getBoundingClientRect();
|
|
1787
1756
|
const relativeX = event.clientX - canvasRect.left;
|
|
1788
1757
|
const relativeY = event.clientY - canvasRect.top;
|
|
1789
1758
|
|
|
1790
|
-
|
|
1791
|
-
const
|
|
1792
|
-
const
|
|
1759
|
+
const placeholderWidth = 200;
|
|
1760
|
+
const placeholderHeight = 64;
|
|
1761
|
+
const arrowLength = ARROW_LENGTH;
|
|
1762
|
+
const cursorGap = CURSOR_GAP;
|
|
1763
|
+
|
|
1764
|
+
// Determine if cursor is above the source exit using stored sourceY
|
|
1765
|
+
const dragUp =
|
|
1766
|
+
this.connectionSourceY != null
|
|
1767
|
+
? relativeY < this.connectionSourceY
|
|
1768
|
+
: false;
|
|
1769
|
+
|
|
1770
|
+
let top: number;
|
|
1771
|
+
if (dragUp) {
|
|
1772
|
+
// Arrow points up: tip at cy + cursorGap.
|
|
1773
|
+
// Placeholder bottom should sit just above the arrow tip.
|
|
1774
|
+
top = relativeY + cursorGap - placeholderHeight;
|
|
1775
|
+
} else {
|
|
1776
|
+
// Arrow points down: tip at cy - cursorGap + arrowLength.
|
|
1777
|
+
// Placeholder top sits just below the arrow tip.
|
|
1778
|
+
top = relativeY - cursorGap + arrowLength;
|
|
1779
|
+
}
|
|
1793
1780
|
|
|
1794
1781
|
this.connectionPlaceholder = {
|
|
1795
1782
|
position: {
|
|
1796
1783
|
left: relativeX - placeholderWidth / 2,
|
|
1797
|
-
top
|
|
1784
|
+
top
|
|
1798
1785
|
},
|
|
1799
|
-
visible: true
|
|
1786
|
+
visible: true,
|
|
1787
|
+
dragUp
|
|
1800
1788
|
};
|
|
1801
1789
|
}
|
|
1802
1790
|
}
|
|
@@ -1924,49 +1912,7 @@ export class Editor extends RapidElement {
|
|
|
1924
1912
|
if (nodeUuids.length > 0) {
|
|
1925
1913
|
// Allow DOM to update before checking collisions
|
|
1926
1914
|
setTimeout(() => {
|
|
1927
|
-
|
|
1928
|
-
let droppedNodeUuid: string | null = null;
|
|
1929
|
-
let dropTargetBounds: NodeBounds | null = null;
|
|
1930
|
-
|
|
1931
|
-
if (nodeUuids.length === 1) {
|
|
1932
|
-
droppedNodeUuid = nodeUuids[0];
|
|
1933
|
-
const droppedNodeUI = this.definition._ui?.nodes[droppedNodeUuid];
|
|
1934
|
-
|
|
1935
|
-
if (droppedNodeUI?.position) {
|
|
1936
|
-
const droppedBounds = getNodeBounds(
|
|
1937
|
-
droppedNodeUuid,
|
|
1938
|
-
droppedNodeUI.position
|
|
1939
|
-
);
|
|
1940
|
-
|
|
1941
|
-
if (droppedBounds) {
|
|
1942
|
-
// Find which node (if any) the dropped node overlaps with
|
|
1943
|
-
for (const node of this.definition.nodes) {
|
|
1944
|
-
if (node.uuid === droppedNodeUuid) continue;
|
|
1945
|
-
|
|
1946
|
-
const nodeUI = this.definition._ui?.nodes[node.uuid];
|
|
1947
|
-
if (!nodeUI?.position) continue;
|
|
1948
|
-
|
|
1949
|
-
const targetBounds = getNodeBounds(
|
|
1950
|
-
node.uuid,
|
|
1951
|
-
nodeUI.position
|
|
1952
|
-
);
|
|
1953
|
-
if (
|
|
1954
|
-
targetBounds &&
|
|
1955
|
-
nodesOverlap(droppedBounds, targetBounds)
|
|
1956
|
-
) {
|
|
1957
|
-
dropTargetBounds = targetBounds;
|
|
1958
|
-
break; // Use the first overlapping node
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
this.checkCollisionsAndReflow(
|
|
1966
|
-
nodeUuids,
|
|
1967
|
-
droppedNodeUuid,
|
|
1968
|
-
dropTargetBounds
|
|
1969
|
-
);
|
|
1915
|
+
this.checkCollisionsAndReflow(nodeUuids);
|
|
1970
1916
|
}, 0);
|
|
1971
1917
|
} else {
|
|
1972
1918
|
// No nodes moved, just repaint connections
|
|
@@ -2091,6 +2037,8 @@ export class Editor extends RapidElement {
|
|
|
2091
2037
|
this.pendingCanvasConnection = null;
|
|
2092
2038
|
this.connectionPlaceholder = null;
|
|
2093
2039
|
this.sourceId = null;
|
|
2040
|
+
this.connectionSourceX = null;
|
|
2041
|
+
this.connectionSourceY = null;
|
|
2094
2042
|
this.dragFromNodeId = null;
|
|
2095
2043
|
} else {
|
|
2096
2044
|
// Show node type selector
|
|
@@ -2126,6 +2074,8 @@ export class Editor extends RapidElement {
|
|
|
2126
2074
|
this.pendingCanvasConnection = null;
|
|
2127
2075
|
this.connectionPlaceholder = null;
|
|
2128
2076
|
this.sourceId = null;
|
|
2077
|
+
this.connectionSourceX = null;
|
|
2078
|
+
this.connectionSourceY = null;
|
|
2129
2079
|
this.dragFromNodeId = null;
|
|
2130
2080
|
this.originalConnectionTargetId = null;
|
|
2131
2081
|
}
|
|
@@ -2259,6 +2209,10 @@ export class Editor extends RapidElement {
|
|
|
2259
2209
|
private handleActionEditRequested(event: CustomEvent): void {
|
|
2260
2210
|
// For action editing, we set the action and find the corresponding node
|
|
2261
2211
|
this.editingAction = event.detail.action;
|
|
2212
|
+
this.dialogOrigin =
|
|
2213
|
+
event.detail.originX != null
|
|
2214
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
2215
|
+
: null;
|
|
2262
2216
|
|
|
2263
2217
|
// Find the node that contains this action
|
|
2264
2218
|
const nodeUuid = event.detail.nodeUuid;
|
|
@@ -2304,6 +2258,10 @@ export class Editor extends RapidElement {
|
|
|
2304
2258
|
private handleNodeEditRequested(event: CustomEvent): void {
|
|
2305
2259
|
this.editingNode = event.detail.node;
|
|
2306
2260
|
this.editingNodeUI = event.detail.nodeUI;
|
|
2261
|
+
this.dialogOrigin =
|
|
2262
|
+
event.detail.originX != null
|
|
2263
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
2264
|
+
: null;
|
|
2307
2265
|
}
|
|
2308
2266
|
|
|
2309
2267
|
private handleNodeDeleted(event: CustomEvent): void {
|
|
@@ -2362,6 +2320,8 @@ export class Editor extends RapidElement {
|
|
|
2362
2320
|
this.pendingCanvasConnection = null;
|
|
2363
2321
|
this.connectionPlaceholder = null;
|
|
2364
2322
|
this.sourceId = null;
|
|
2323
|
+
this.connectionSourceX = null;
|
|
2324
|
+
this.connectionSourceY = null;
|
|
2365
2325
|
this.dragFromNodeId = null;
|
|
2366
2326
|
}
|
|
2367
2327
|
|
|
@@ -2391,6 +2351,7 @@ export class Editor extends RapidElement {
|
|
|
2391
2351
|
this.editingNode = null;
|
|
2392
2352
|
this.editingNodeUI = null;
|
|
2393
2353
|
this.editingAction = null;
|
|
2354
|
+
this.dialogOrigin = null;
|
|
2394
2355
|
}
|
|
2395
2356
|
|
|
2396
2357
|
private handleActionEditCanceled(): void {
|
|
@@ -2431,6 +2392,8 @@ export class Editor extends RapidElement {
|
|
|
2431
2392
|
this.pendingCanvasConnection = null;
|
|
2432
2393
|
this.connectionPlaceholder = null;
|
|
2433
2394
|
this.sourceId = null;
|
|
2395
|
+
this.connectionSourceX = null;
|
|
2396
|
+
this.connectionSourceY = null;
|
|
2434
2397
|
this.dragFromNodeId = null;
|
|
2435
2398
|
}
|
|
2436
2399
|
|
|
@@ -3043,6 +3006,7 @@ export class Editor extends RapidElement {
|
|
|
3043
3006
|
|
|
3044
3007
|
this.localizationWindowHidden = false;
|
|
3045
3008
|
this.revisionsWindowHidden = true;
|
|
3009
|
+
this.issuesWindowHidden = true;
|
|
3046
3010
|
|
|
3047
3011
|
const alreadySelected = languages.some(
|
|
3048
3012
|
(lang) => lang.code === this.languageCode
|
|
@@ -3305,11 +3269,47 @@ export class Editor extends RapidElement {
|
|
|
3305
3269
|
this.autoTranslating = false;
|
|
3306
3270
|
}
|
|
3307
3271
|
|
|
3272
|
+
private handleIssuesTabClick(): void {
|
|
3273
|
+
this.issuesWindowHidden = false;
|
|
3274
|
+
this.revisionsWindowHidden = true;
|
|
3275
|
+
this.localizationWindowHidden = true;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
private handleIssuesWindowClosed(): void {
|
|
3279
|
+
this.issuesWindowHidden = true;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
private handleIssueItemClick(issue: FlowIssue): void {
|
|
3283
|
+
const issuesWindow = document.getElementById(
|
|
3284
|
+
'issues-window'
|
|
3285
|
+
) as FloatingWindow;
|
|
3286
|
+
issuesWindow?.handleClose();
|
|
3287
|
+
this.issuesWindowHidden = true;
|
|
3288
|
+
|
|
3289
|
+
this.focusNode(issue.node_uuid);
|
|
3290
|
+
|
|
3291
|
+
const node = this.definition.nodes.find((n) => n.uuid === issue.node_uuid);
|
|
3292
|
+
if (!node) return;
|
|
3293
|
+
|
|
3294
|
+
if (issue.action_uuid) {
|
|
3295
|
+
const action = node.actions?.find((a) => a.uuid === issue.action_uuid);
|
|
3296
|
+
if (action) {
|
|
3297
|
+
this.editingAction = action;
|
|
3298
|
+
this.editingNode = node;
|
|
3299
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
3300
|
+
}
|
|
3301
|
+
} else {
|
|
3302
|
+
this.editingNode = node;
|
|
3303
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3308
3307
|
private handleRevisionsTabClick(): void {
|
|
3309
3308
|
if (this.revisionsWindowHidden) {
|
|
3310
3309
|
this.fetchRevisions();
|
|
3311
3310
|
this.revisionsWindowHidden = false;
|
|
3312
|
-
this.
|
|
3311
|
+
this.issuesWindowHidden = true;
|
|
3312
|
+
this.localizationWindowHidden = true;
|
|
3313
3313
|
}
|
|
3314
3314
|
}
|
|
3315
3315
|
|
|
@@ -3423,6 +3423,51 @@ export class Editor extends RapidElement {
|
|
|
3423
3423
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
3424
3424
|
}
|
|
3425
3425
|
|
|
3426
|
+
private renderIssuesTab(): TemplateResult | string {
|
|
3427
|
+
if (!this.flowIssues?.length) return '';
|
|
3428
|
+
return html`
|
|
3429
|
+
<temba-floating-tab
|
|
3430
|
+
id="issues-tab"
|
|
3431
|
+
icon="alert_warning"
|
|
3432
|
+
label="Flow Issues"
|
|
3433
|
+
color="tomato"
|
|
3434
|
+
order="1"
|
|
3435
|
+
.hidden=${!this.issuesWindowHidden}
|
|
3436
|
+
@temba-button-clicked=${this.handleIssuesTabClick}
|
|
3437
|
+
></temba-floating-tab>
|
|
3438
|
+
`;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
private renderIssuesWindow(): TemplateResult | string {
|
|
3442
|
+
if (!this.flowIssues?.length) return '';
|
|
3443
|
+
return html`
|
|
3444
|
+
<temba-floating-window
|
|
3445
|
+
id="issues-window"
|
|
3446
|
+
header="Flow Issues"
|
|
3447
|
+
.width=${360}
|
|
3448
|
+
.maxHeight=${600}
|
|
3449
|
+
.top=${75}
|
|
3450
|
+
color="tomato"
|
|
3451
|
+
.hidden=${this.issuesWindowHidden}
|
|
3452
|
+
@temba-dialog-hidden=${this.handleIssuesWindowClosed}
|
|
3453
|
+
>
|
|
3454
|
+
<div style="display:flex; flex-direction:column; gap:2px;">
|
|
3455
|
+
${this.flowIssues.map(
|
|
3456
|
+
(issue) => html`
|
|
3457
|
+
<div
|
|
3458
|
+
class="issue-list-item"
|
|
3459
|
+
@click=${() => this.handleIssueItemClick(issue)}
|
|
3460
|
+
>
|
|
3461
|
+
<temba-icon name="alert_warning" size="1.2"></temba-icon>
|
|
3462
|
+
<span>${formatIssueMessage(issue)}</span>
|
|
3463
|
+
</div>
|
|
3464
|
+
`
|
|
3465
|
+
)}
|
|
3466
|
+
</div>
|
|
3467
|
+
</temba-floating-window>
|
|
3468
|
+
`;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3426
3471
|
private renderRevisionsTab(): TemplateResult | string {
|
|
3427
3472
|
return html`
|
|
3428
3473
|
<temba-floating-tab
|
|
@@ -3430,7 +3475,7 @@ export class Editor extends RapidElement {
|
|
|
3430
3475
|
icon="revisions"
|
|
3431
3476
|
label="Revisions"
|
|
3432
3477
|
color="rgb(142, 94, 167)"
|
|
3433
|
-
|
|
3478
|
+
order="2"
|
|
3434
3479
|
.hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
|
|
3435
3480
|
@temba-button-clicked=${this.handleRevisionsTabClick}
|
|
3436
3481
|
></temba-floating-tab>
|
|
@@ -3728,7 +3773,7 @@ export class Editor extends RapidElement {
|
|
|
3728
3773
|
icon="language"
|
|
3729
3774
|
label="Translate Flow"
|
|
3730
3775
|
color="#6b7280"
|
|
3731
|
-
|
|
3776
|
+
order="3"
|
|
3732
3777
|
.hidden=${!this.localizationWindowHidden}
|
|
3733
3778
|
@temba-button-clicked=${this.handleLocalizationTabClick}
|
|
3734
3779
|
></temba-floating-tab>
|
|
@@ -3786,8 +3831,9 @@ export class Editor extends RapidElement {
|
|
|
3786
3831
|
|
|
3787
3832
|
const stickies = this.definition?._ui?.stickies || {};
|
|
3788
3833
|
|
|
3789
|
-
return html`${style} ${this.
|
|
3790
|
-
${this.
|
|
3834
|
+
return html`${style} ${this.renderIssuesWindow()}
|
|
3835
|
+
${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
|
|
3836
|
+
${this.renderAutoTranslateDialog()}
|
|
3791
3837
|
<div id="editor">
|
|
3792
3838
|
<div
|
|
3793
3839
|
id="grid"
|
|
@@ -3877,6 +3923,7 @@ export class Editor extends RapidElement {
|
|
|
3877
3923
|
.node=${this.editingNode}
|
|
3878
3924
|
.nodeUI=${this.editingNodeUI}
|
|
3879
3925
|
.action=${this.editingAction}
|
|
3926
|
+
.dialogOrigin=${this.dialogOrigin}
|
|
3880
3927
|
@temba-node-saved=${(e: CustomEvent) =>
|
|
3881
3928
|
this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
3882
3929
|
@temba-action-saved=${(e: CustomEvent) =>
|
|
@@ -3892,6 +3939,7 @@ export class Editor extends RapidElement {
|
|
|
3892
3939
|
.features=${this.features}
|
|
3893
3940
|
></temba-node-type-selector>`
|
|
3894
3941
|
: ''}
|
|
3895
|
-
${this.
|
|
3942
|
+
${this.renderIssuesTab()} ${this.renderRevisionsTab()}
|
|
3943
|
+
${this.renderLocalizationTab()} `;
|
|
3896
3944
|
}
|
|
3897
3945
|
}
|