@nyaruka/temba-components 0.137.0 → 0.138.4

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.
Files changed (88) hide show
  1. package/.devcontainer/Dockerfile +0 -9
  2. package/.devcontainer/devcontainer.json +8 -3
  3. package/.github/workflows/build.yml +6 -1
  4. package/.github/workflows/cla.yml +1 -1
  5. package/.github/workflows/publish.yml +6 -1
  6. package/CHANGELOG.md +42 -0
  7. package/dist/locales/es.js +5 -5
  8. package/dist/locales/es.js.map +1 -1
  9. package/dist/locales/fr.js +5 -5
  10. package/dist/locales/fr.js.map +1 -1
  11. package/dist/locales/locale-codes.js +11 -2
  12. package/dist/locales/locale-codes.js.map +1 -1
  13. package/dist/locales/pt.js +5 -5
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +445 -278
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +16 -8
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  19. package/out-tsc/src/flow/CanvasMenu.js +33 -15
  20. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  21. package/out-tsc/src/flow/CanvasNode.js +49 -24
  22. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  23. package/out-tsc/src/flow/Editor.js +583 -70
  24. package/out-tsc/src/flow/Editor.js.map +1 -1
  25. package/out-tsc/src/flow/NodeTypeSelector.js +13 -11
  26. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
  27. package/out-tsc/src/flow/Plumber.js +110 -64
  28. package/out-tsc/src/flow/Plumber.js.map +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_field.js +5 -1
  30. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  31. package/out-tsc/src/list/RunList.js +2 -1
  32. package/out-tsc/src/list/RunList.js.map +1 -1
  33. package/out-tsc/src/list/TicketList.js +2 -1
  34. package/out-tsc/src/list/TicketList.js.map +1 -1
  35. package/out-tsc/src/locales/es.js +5 -5
  36. package/out-tsc/src/locales/es.js.map +1 -1
  37. package/out-tsc/src/locales/fr.js +5 -5
  38. package/out-tsc/src/locales/fr.js.map +1 -1
  39. package/out-tsc/src/locales/locale-codes.js +11 -2
  40. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  41. package/out-tsc/src/locales/pt.js +5 -5
  42. package/out-tsc/src/locales/pt.js.map +1 -1
  43. package/out-tsc/src/simulator/Simulator.js +11 -4
  44. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  45. package/out-tsc/src/store/AppState.js +17 -2
  46. package/out-tsc/src/store/AppState.js.map +1 -1
  47. package/out-tsc/test/temba-contact-fields.test.js +3 -3
  48. package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
  49. package/out-tsc/test/temba-flow-editor-node.test.js +3 -1
  50. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  51. package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
  52. package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
  53. package/out-tsc/test/temba-flow-editor.test.js +14 -10
  54. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  55. package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
  56. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  57. package/out-tsc/test/temba-flow-plumber.test.js +6 -0
  58. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  59. package/out-tsc/test/temba-select.test.js +1 -0
  60. package/out-tsc/test/temba-select.test.js.map +1 -1
  61. package/package.json +1 -1
  62. package/screenshots/truth/floating-tab/gray.png +0 -0
  63. package/screenshots/truth/floating-tab/green.png +0 -0
  64. package/screenshots/truth/floating-tab/purple.png +0 -0
  65. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  66. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  67. package/src/display/FloatingTab.ts +18 -8
  68. package/src/flow/CanvasMenu.ts +38 -16
  69. package/src/flow/CanvasNode.ts +62 -29
  70. package/src/flow/Editor.ts +699 -74
  71. package/src/flow/NodeTypeSelector.ts +13 -11
  72. package/src/flow/Plumber.ts +123 -69
  73. package/src/flow/actions/set_contact_field.ts +5 -1
  74. package/src/list/RunList.ts +2 -1
  75. package/src/list/TicketList.ts +2 -1
  76. package/src/locales/es.ts +18 -13
  77. package/src/locales/fr.ts +18 -13
  78. package/src/locales/locale-codes.ts +11 -2
  79. package/src/locales/pt.ts +18 -13
  80. package/src/simulator/Simulator.ts +11 -5
  81. package/src/store/AppState.ts +18 -2
  82. package/test/temba-contact-fields.test.ts +8 -3
  83. package/test/temba-flow-editor-node.test.ts +3 -1
  84. package/test/temba-flow-editor-revisions.test.ts +134 -0
  85. package/test/temba-flow-editor.test.ts +16 -10
  86. package/test/temba-flow-plumber-connections.test.ts +7 -1
  87. package/test/temba-flow-plumber.test.ts +6 -0
  88. package/test/temba-select.test.ts +1 -0
@@ -3,11 +3,11 @@ import { html } from 'lit-html';
3
3
  import { css, unsafeCSS } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  import { getStore } from '../store/Store';
6
- import { fromStore, zustand } from '../store/AppState';
6
+ import { fromStore, zustand, FLOW_SPEC_VERSION } from '../store/AppState';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { repeat } from 'lit-html/directives/repeat.js';
9
9
  import { CustomEventType } from '../interfaces';
10
- import { generateUUID, postJSON } from '../utils';
10
+ import { generateUUID, postJSON, fetchResults, getClasses } from '../utils';
11
11
  import { ACTION_CONFIG, NODE_CONFIG } from './config';
12
12
  import { ACTION_GROUP_METADATA } from './types';
13
13
  import { Plumber } from './Plumber';
@@ -110,6 +110,29 @@ export class Editor extends RapidElement {
110
110
  transition: none !important;
111
111
  }
112
112
 
113
+ #canvas.viewing-revision {
114
+ pointer-events: none;
115
+ }
116
+
117
+ #canvas.read-only svg {
118
+ pointer-events: none;
119
+ }
120
+
121
+ #grid.viewing-revision {
122
+ background-color: #fff9fc;
123
+ background-image: radial-gradient(
124
+ circle,
125
+ rgba(166, 38, 164, 0.2) 1px,
126
+ transparent 1px
127
+ );
128
+ }
129
+
130
+ #grid.viewing-revision temba-flow-node,
131
+ #grid.viewing-revision svg.jtk-connector,
132
+ #grid.viewing-revision .activity-overlay {
133
+ opacity: 0.5;
134
+ }
135
+
113
136
  body .jtk-endpoint {
114
137
  width: initial;
115
138
  height: initial;
@@ -164,12 +187,28 @@ export class Editor extends RapidElement {
164
187
  stroke-width: 3px;
165
188
  }
166
189
 
190
+ body #canvas.read-only-connections svg.jtk-connector.jtk-hover path {
191
+ stroke: var(--color-connectors) !important;
192
+ }
193
+
167
194
  body .plumb-connector.jtk-hover .plumb-arrow {
168
195
  fill: var(--color-success) !important;
169
196
  stroke-width: 0px;
170
197
  z-index: 10;
171
198
  }
172
199
 
200
+ body
201
+ #canvas.read-only-connections
202
+ .plumb-connector.jtk-hover
203
+ .plumb-arrow {
204
+ fill: var(--color-connectors) !important;
205
+ ponter-events: none;
206
+ }
207
+
208
+ body #canvas.read-only-connections svg {
209
+ pointer-events: none;
210
+ }
211
+
173
212
  /* Activity overlays on connections */
174
213
  .jtk-overlay.activity-overlay {
175
214
  background: #f3f3f3;
@@ -458,6 +497,18 @@ export class Editor extends RapidElement {
458
497
  width: 100%;
459
498
  }
460
499
 
500
+ .revert-button {
501
+ background: var(--color-primary-dark);
502
+ border: none;
503
+ color: #fff;
504
+ padding: 6px 10px;
505
+ border-radius: var(--curvature);
506
+ font-size: 11px;
507
+ font-weight: 600;
508
+ cursor: pointer;
509
+ transition: opacity 0.2s ease;
510
+ }
511
+
461
512
  .auto-translate-button {
462
513
  background: var(--color-primary-dark);
463
514
  border: none;
@@ -534,6 +585,7 @@ export class Editor extends RapidElement {
534
585
  this.targetId = null;
535
586
  this.sourceId = null;
536
587
  this.dragFromNodeId = null;
588
+ this.originalConnectionTargetId = null;
537
589
  this.isValidTarget = true;
538
590
  this.localizationWindowHidden = true;
539
591
  this.translationFilters = {
@@ -544,6 +596,11 @@ export class Editor extends RapidElement {
544
596
  this.autoTranslating = false;
545
597
  this.autoTranslateModel = null;
546
598
  this.autoTranslateError = null;
599
+ this.revisionsWindowHidden = true;
600
+ this.revisions = [];
601
+ this.viewingRevision = null;
602
+ this.isLoadingRevisions = false;
603
+ this.preRevertState = null;
547
604
  this.translationCache = new Map();
548
605
  // NodeEditor state - handles both node and action editing
549
606
  this.editingNode = null;
@@ -558,6 +615,10 @@ export class Editor extends RapidElement {
558
615
  this.actionDragTargetNodeUuid = null;
559
616
  // Track previous target node to clear placeholder when moving between nodes
560
617
  this.previousActionDragTargetNodeUuid = null;
618
+ // Connection placeholder state for dropping connections on empty canvas
619
+ this.connectionPlaceholder = null;
620
+ // Track pending connection when dropping on canvas
621
+ this.pendingCanvasConnection = null;
561
622
  this.canvasMouseDown = false;
562
623
  // Bound event handlers to maintain proper 'this' context
563
624
  this.boundMouseMove = this.handleMouseMove.bind(this);
@@ -573,36 +634,81 @@ export class Editor extends RapidElement {
573
634
  if (changes.has('flow')) {
574
635
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
575
636
  }
576
- this.plumber.on('connection:drag', (info) => {
577
- this.dragFromNodeId = document
578
- .getElementById(info.sourceId)
579
- .closest('.node').id;
580
- this.sourceId = info.sourceId;
637
+ this.plumber.on('connection:drag', (connection) => {
638
+ // console.log('connection:drag', connection);
639
+ this.dragFromNodeId =
640
+ connection.data.nodeId ||
641
+ document.getElementById(connection.sourceId).closest('.node').id;
642
+ this.sourceId = connection.sourceId;
643
+ this.originalConnectionTargetId = connection.target.id;
581
644
  });
582
- this.plumber.on('connection:abort', () => {
583
- this.makeConnection();
645
+ this.plumber.on('connection:abort', (info) => {
646
+ // console.log('Connection aborted', info);
647
+ this.makeConnection(info);
584
648
  });
585
- this.plumber.on('connection:detach', () => {
586
- this.makeConnection();
649
+ this.plumber.on('connection:detach', (info) => {
650
+ // console.log('Connection detached', info);
651
+ this.makeConnection(info);
587
652
  });
588
653
  }
589
- makeConnection() {
654
+ makeConnection(info) {
590
655
  if (this.sourceId && this.targetId && this.isValidTarget) {
591
- this.plumber.connectIds(this.dragFromNodeId, this.sourceId, this.targetId);
592
- getStore()
593
- .getState()
594
- .updateConnection(this.dragFromNodeId, this.sourceId, this.targetId);
595
- setTimeout(() => {
596
- this.plumber.repaintEverything();
597
- }, 100);
656
+ // going to the same target, just put it back
657
+ if (info.target.id === this.targetId) {
658
+ this.plumber.connectIds(this.dragFromNodeId, this.sourceId, this.targetId);
659
+ }
660
+ // otherwise update the connection
661
+ else {
662
+ getStore()
663
+ .getState()
664
+ .updateConnection(this.dragFromNodeId, this.sourceId, this.targetId);
665
+ }
666
+ }
667
+ else if (this.connectionPlaceholder &&
668
+ this.connectionPlaceholder.visible &&
669
+ this.sourceId) {
670
+ // Snap the placeholder position to grid
671
+ const snappedPosition = {
672
+ left: snapToGrid(this.connectionPlaceholder.position.left),
673
+ top: snapToGrid(this.connectionPlaceholder.position.top)
674
+ };
675
+ // Update the placeholder to the snapped position
676
+ this.connectionPlaceholder.position = snappedPosition;
677
+ // Store the pending connection info
678
+ this.pendingCanvasConnection = {
679
+ fromNodeId: this.dragFromNodeId,
680
+ exitId: this.sourceId,
681
+ position: snappedPosition
682
+ };
683
+ // Show the context menu just below the placeholder
684
+ const canvas = this.querySelector('#canvas');
685
+ if (canvas) {
686
+ const canvasRect = canvas.getBoundingClientRect();
687
+ const menuX = canvasRect.left + snappedPosition.left - 40; // center horizontally
688
+ const menuY = canvasRect.top + snappedPosition.top + 80; // just below placeholder
689
+ const canvasMenu = this.querySelector('temba-canvas-menu');
690
+ if (canvasMenu) {
691
+ canvasMenu.show(menuX, menuY, {
692
+ x: snappedPosition.left,
693
+ y: snappedPosition.top
694
+ }, false); // Don't show sticky note option for connection drops
695
+ }
696
+ }
697
+ // Request update to render the connection line
698
+ this.requestUpdate();
699
+ // Don't clear placeholder or connection info yet - keep them for menu interaction
700
+ return;
598
701
  }
599
702
  // Clean up visual feedback
600
703
  document.querySelectorAll('temba-flow-node').forEach((node) => {
601
704
  node.classList.remove('connection-target-valid', 'connection-target-invalid');
602
705
  });
603
- this.sourceId = null;
706
+ // Clear connection state (but keep sourceId/dragFromNodeId if we have a pending connection)
707
+ if (!this.pendingCanvasConnection) {
708
+ this.sourceId = null;
709
+ this.dragFromNodeId = null;
710
+ }
604
711
  this.targetId = null;
605
- this.dragFromNodeId = null;
606
712
  this.isValidTarget = true;
607
713
  }
608
714
  updated(changes) {
@@ -693,10 +799,11 @@ export class Editor extends RapidElement {
693
799
  }
694
800
  }, SAVE_QUIET_TIME);
695
801
  }
696
- saveChanges() {
802
+ saveChanges(definitionOverride) {
803
+ const definition = definitionOverride || this.definition;
697
804
  // post the flow definition to the server
698
- getStore()
699
- .postJSON(`/flow/revisions/${this.flow}/`, this.definition)
805
+ return getStore()
806
+ .postJSON(`/flow/revisions/${this.flow}/`, definition)
700
807
  .then((response) => {
701
808
  var _b;
702
809
  // Update flow info and revision with the response data
@@ -708,6 +815,10 @@ export class Editor extends RapidElement {
708
815
  if (((_b = response.json.revision) === null || _b === void 0 ? void 0 : _b.revision) !== undefined) {
709
816
  state.setRevision(response.json.revision.revision);
710
817
  }
818
+ // if the revisions window is open, refresh the list
819
+ if (!this.revisionsWindowHidden) {
820
+ this.fetchRevisions();
821
+ }
711
822
  }
712
823
  })
713
824
  .catch((error) => {
@@ -751,18 +862,12 @@ export class Editor extends RapidElement {
751
862
  clearTimeout(this.activityTimer);
752
863
  }
753
864
  this.activityTimer = window.setTimeout(() => {
754
- this.fetchActivityData();
865
+ // this.fetchActivityData();
755
866
  }, this.activityInterval);
756
867
  });
757
868
  }
758
869
  handleLanguageChange(languageCode) {
759
870
  zustand.getState().setLanguageCode(languageCode);
760
- // Repaint connections after language change since node sizes can change
761
- if (this.plumber) {
762
- requestAnimationFrame(() => {
763
- this.plumber.repaintEverything();
764
- });
765
- }
766
871
  }
767
872
  disconnectedCallback() {
768
873
  super.disconnectedCallback();
@@ -810,6 +915,16 @@ export class Editor extends RapidElement {
810
915
  this.handleNodeTypeSelection(event);
811
916
  }
812
917
  });
918
+ // Listen for canvas menu cancel (close without selection)
919
+ this.addEventListener(CustomEventType.Canceled, (event) => {
920
+ const target = event.target;
921
+ if (target.tagName === 'TEMBA-CANVAS-MENU') {
922
+ this.handleCanvasMenuClosed();
923
+ }
924
+ else if (target.tagName === 'TEMBA-NODE-TYPE-SELECTOR') {
925
+ this.handleNodeTypeSelectorClosed();
926
+ }
927
+ });
813
928
  // Listen for action drag events from nodes
814
929
  this.addEventListener(CustomEventType.DragExternal, this.handleActionDragExternal.bind(this));
815
930
  this.addEventListener(CustomEventType.DragInternal, this.handleActionDragInternal.bind(this));
@@ -832,6 +947,8 @@ export class Editor extends RapidElement {
832
947
  // ignore right clicks
833
948
  if (event.button !== 0)
834
949
  return;
950
+ if (this.isReadOnly())
951
+ return;
835
952
  const element = event.currentTarget;
836
953
  // Only start dragging if clicking on the element itself, not on exits or other interactive elements
837
954
  const target = event.target;
@@ -893,6 +1010,8 @@ export class Editor extends RapidElement {
893
1010
  }
894
1011
  handleCanvasMouseDown(event) {
895
1012
  var _b;
1013
+ if (this.isReadOnly())
1014
+ return;
896
1015
  const target = event.target;
897
1016
  if (target.id === 'canvas' || target.id === 'grid') {
898
1017
  // Ignore clicks on exits
@@ -951,12 +1070,8 @@ export class Editor extends RapidElement {
951
1070
  });
952
1071
  }
953
1072
  deleteNodes(uuids) {
954
- // Clean up jsPlumb connections for nodes before removing them
955
- uuids.forEach((uuid) => {
956
- this.plumber.removeNodeConnections(uuid);
957
- });
958
- // Now remove them from the definition
959
- if (uuids.length > 0 && this.plumber) {
1073
+ // Remove nodes from the definition - CanvasNode will handle plumber cleanup
1074
+ if (uuids.length > 0) {
960
1075
  getStore().getState().removeNodes(uuids);
961
1076
  }
962
1077
  }
@@ -1101,6 +1216,94 @@ export class Editor extends RapidElement {
1101
1216
  </div>
1102
1217
  </div>`;
1103
1218
  }
1219
+ renderConnectionPlaceholder() {
1220
+ if (!this.connectionPlaceholder || !this.connectionPlaceholder.visible)
1221
+ return '';
1222
+ const { position } = this.connectionPlaceholder;
1223
+ // Render connection line when we have a pending connection (after drop)
1224
+ let svgPath = null;
1225
+ if (this.sourceId && this.dragFromNodeId && this.pendingCanvasConnection) {
1226
+ const sourceElement = document.getElementById(this.sourceId);
1227
+ if (sourceElement) {
1228
+ const sourceRect = sourceElement.getBoundingClientRect();
1229
+ const canvas = this.querySelector('#canvas');
1230
+ const canvasRect = canvas.getBoundingClientRect();
1231
+ // Source point (bottom center of exit)
1232
+ const sourceX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
1233
+ const sourceY = sourceRect.bottom - canvasRect.top;
1234
+ // Target point (top center of placeholder)
1235
+ const targetX = position.left + 100; // 100 is half the placeholder width (200px)
1236
+ const targetY = position.top;
1237
+ // Use jsPlumb FlowchartConnector parameters: stub [20, 10], cornerRadius 5
1238
+ const stubStart = 20;
1239
+ const stubEnd = 10;
1240
+ const cornerRadius = 5;
1241
+ // Calculate flowchart path with corners
1242
+ const verticalStart = sourceY + stubStart;
1243
+ const verticalEnd = targetY - stubEnd;
1244
+ const midY = (verticalStart + verticalEnd) / 2;
1245
+ // Build path with rounded corners (flowchart style)
1246
+ let pathData = `M ${sourceX} ${sourceY} L ${sourceX} ${verticalStart}`;
1247
+ if (sourceX !== targetX) {
1248
+ // Horizontal segment needed
1249
+ if (Math.abs(verticalEnd - verticalStart) > cornerRadius * 2) {
1250
+ // Enough space for corners
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
+ `;
1287
+ }
1288
+ }
1289
+ return html `${svgPath}
1290
+ <div
1291
+ class="connection-placeholder"
1292
+ style="position: absolute; left: ${position.left}px; top: ${position.top}px; opacity: 0.6; pointer-events: none; z-index: 10000;"
1293
+ >
1294
+ <div
1295
+ class="node execute-actions"
1296
+ style="outline: 3px dashed var(--color-primary, #3b82f6); outline-offset: 2px; border-radius: var(--curvature); min-width: 200px;"
1297
+ >
1298
+ <div class="empty-node-placeholder" style="height: 60px;"></div>
1299
+ <div class="action-exits">
1300
+ <div class="exit-wrapper">
1301
+ <div class="exit"></div>
1302
+ </div>
1303
+ </div>
1304
+ </div>
1305
+ </div>`;
1306
+ }
1104
1307
  /**
1105
1308
  * Checks for node collisions and reflows nodes as needed.
1106
1309
  * Nodes are only moved downward to resolve collisions.
@@ -1180,11 +1383,33 @@ export class Editor extends RapidElement {
1180
1383
  else {
1181
1384
  targetNode.classList.add('connection-target-invalid');
1182
1385
  }
1386
+ // Hide connection placeholder when over a node
1387
+ this.connectionPlaceholder = null;
1183
1388
  }
1184
1389
  else {
1185
1390
  this.targetId = null;
1186
1391
  this.isValidTarget = true;
1392
+ // Show connection placeholder when over empty canvas
1393
+ // Calculate position: horizontally centered at mouse, vertically just below mouse
1394
+ const canvas = this.querySelector('#canvas');
1395
+ if (canvas) {
1396
+ const canvasRect = canvas.getBoundingClientRect();
1397
+ const relativeX = event.clientX - canvasRect.left;
1398
+ const relativeY = event.clientY - canvasRect.top;
1399
+ // offset the placeholder so it's centered horizontally and just below the mouse
1400
+ const placeholderWidth = 200; // approximate node width
1401
+ const placeholderOffset = 20; // distance below mouse cursor
1402
+ this.connectionPlaceholder = {
1403
+ position: {
1404
+ left: relativeX - placeholderWidth / 2,
1405
+ top: relativeY + placeholderOffset
1406
+ },
1407
+ visible: true
1408
+ };
1409
+ }
1187
1410
  }
1411
+ // Force update to show/hide placeholder
1412
+ this.requestUpdate();
1188
1413
  }
1189
1414
  // Handle item dragging
1190
1415
  if (!this.isMouseDown || !this.currentDragItem)
@@ -1372,6 +1597,10 @@ export class Editor extends RapidElement {
1372
1597
  store.getState().expandCanvas(maxWidth, maxHeight);
1373
1598
  }
1374
1599
  handleCanvasContextMenu(event) {
1600
+ if (this.isReadOnly()) {
1601
+ event.preventDefault();
1602
+ return;
1603
+ }
1375
1604
  // Check if we right-clicked on empty canvas space
1376
1605
  const target = event.target;
1377
1606
  if (target.id !== 'canvas') {
@@ -1409,6 +1638,11 @@ export class Editor extends RapidElement {
1409
1638
  left: selection.position.x,
1410
1639
  top: selection.position.y
1411
1640
  });
1641
+ // Clear all pending connection state and placeholder
1642
+ this.pendingCanvasConnection = null;
1643
+ this.connectionPlaceholder = null;
1644
+ this.sourceId = null;
1645
+ this.dragFromNodeId = null;
1412
1646
  }
1413
1647
  else {
1414
1648
  // Show node type selector
@@ -1416,10 +1650,43 @@ export class Editor extends RapidElement {
1416
1650
  if (selector) {
1417
1651
  selector.show(selection.action, selection.position);
1418
1652
  }
1653
+ // Note: we don't clear pendingCanvasConnection or placeholder here,
1654
+ // they will be used in handleNodeTypeSelection
1655
+ }
1656
+ }
1657
+ cleanUpConnection() {
1658
+ if (this.isCreatingNewNode) {
1659
+ this.isCreatingNewNode = false;
1660
+ this.pendingNodePosition = null;
1419
1661
  }
1662
+ // see if we need to put our connection back
1663
+ if (this.originalConnectionTargetId) {
1664
+ this.plumber.connectIds(this.dragFromNodeId, this.sourceId, this.originalConnectionTargetId);
1665
+ this.originalConnectionTargetId = null;
1666
+ }
1667
+ // Menu closed without selection - clear placeholder and pending connection
1668
+ if (this.pendingCanvasConnection) {
1669
+ this.pendingCanvasConnection = null;
1670
+ this.connectionPlaceholder = null;
1671
+ this.sourceId = null;
1672
+ this.dragFromNodeId = null;
1673
+ this.originalConnectionTargetId = null;
1674
+ }
1675
+ }
1676
+ handleCanvasMenuClosed() {
1677
+ this.cleanUpConnection();
1678
+ }
1679
+ handleNodeTypeSelectorClosed() {
1680
+ this.cleanUpConnection();
1420
1681
  }
1421
1682
  handleNodeTypeSelection(event) {
1422
1683
  const selection = event.detail;
1684
+ // Check if we have a pending canvas connection (from dropping on empty canvas)
1685
+ if (this.pendingCanvasConnection) {
1686
+ // Don't clear the placeholder yet - keep it visible while editing
1687
+ // The position is already stored in pendingCanvasConnection
1688
+ // Fall through to normal node creation flow below
1689
+ }
1423
1690
  // Check if we're adding an action to an existing node
1424
1691
  if (this.addActionToNodeUuid) {
1425
1692
  // Find the existing node
@@ -1480,19 +1747,23 @@ export class Editor extends RapidElement {
1480
1747
  };
1481
1748
  }
1482
1749
  const tempNodeUI = {
1483
- position: {
1484
- left: selection.position.x,
1485
- top: selection.position.y
1486
- },
1750
+ position: this.pendingCanvasConnection
1751
+ ? this.pendingCanvasConnection.position
1752
+ : {
1753
+ left: selection.position.x,
1754
+ top: selection.position.y
1755
+ },
1487
1756
  type: nodeType,
1488
1757
  config: {}
1489
1758
  };
1490
1759
  // Mark that we're creating a new node and store the position
1491
1760
  this.isCreatingNewNode = true;
1492
- this.pendingNodePosition = {
1493
- left: selection.position.x,
1494
- top: selection.position.y
1495
- };
1761
+ this.pendingNodePosition = this.pendingCanvasConnection
1762
+ ? this.pendingCanvasConnection.position
1763
+ : {
1764
+ left: selection.position.x,
1765
+ top: selection.position.y
1766
+ };
1496
1767
  // Open the node editor with the temporary node
1497
1768
  this.editingNode = tempNode;
1498
1769
  this.editingNodeUI = tempNodeUI;
@@ -1576,6 +1847,17 @@ export class Editor extends RapidElement {
1576
1847
  };
1577
1848
  // Add the node to the store
1578
1849
  store.getState().addNode(updatedNode, nodeUI);
1850
+ // If we have a pending canvas connection, connect it to this new node
1851
+ if (this.pendingCanvasConnection) {
1852
+ store
1853
+ .getState()
1854
+ .updateConnection(this.pendingCanvasConnection.fromNodeId, this.pendingCanvasConnection.exitId, updatedNode.uuid);
1855
+ // Clear the pending connection and placeholder
1856
+ this.pendingCanvasConnection = null;
1857
+ this.connectionPlaceholder = null;
1858
+ this.sourceId = null;
1859
+ this.dragFromNodeId = null;
1860
+ }
1579
1861
  // Reset the creation flags
1580
1862
  this.isCreatingNewNode = false;
1581
1863
  this.pendingNodePosition = null;
@@ -1585,11 +1867,12 @@ export class Editor extends RapidElement {
1585
1867
  });
1586
1868
  }
1587
1869
  else {
1870
+ const uuid = this.editingNode.uuid;
1588
1871
  // Update existing node in the store
1589
- (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
1872
+ (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(uuid, updatedNode);
1590
1873
  // Check for collisions and reflow in case node size changed
1591
1874
  requestAnimationFrame(() => {
1592
- this.checkCollisionsAndReflow([this.editingNode.uuid]);
1875
+ this.checkCollisionsAndReflow([uuid]);
1593
1876
  });
1594
1877
  }
1595
1878
  }
@@ -1602,10 +1885,7 @@ export class Editor extends RapidElement {
1602
1885
  }
1603
1886
  handleActionEditCanceled() {
1604
1887
  // If we were creating a new node, just discard it
1605
- if (this.isCreatingNewNode) {
1606
- this.isCreatingNewNode = false;
1607
- this.pendingNodePosition = null;
1608
- }
1888
+ this.cleanUpConnection();
1609
1889
  this.closeNodeEditor();
1610
1890
  }
1611
1891
  handleNodeSaved(updatedNode, uiConfig) {
@@ -1621,6 +1901,17 @@ export class Editor extends RapidElement {
1621
1901
  };
1622
1902
  // Add the node to the store
1623
1903
  store.getState().addNode(updatedNode, nodeUI);
1904
+ // If we have a pending canvas connection, connect it to this new node
1905
+ if (this.pendingCanvasConnection) {
1906
+ store
1907
+ .getState()
1908
+ .updateConnection(this.pendingCanvasConnection.fromNodeId, this.pendingCanvasConnection.exitId, updatedNode.uuid);
1909
+ // Clear the pending connection and placeholder
1910
+ this.pendingCanvasConnection = null;
1911
+ this.connectionPlaceholder = null;
1912
+ this.sourceId = null;
1913
+ this.dragFromNodeId = null;
1914
+ }
1624
1915
  // Reset the creation flags
1625
1916
  this.isCreatingNewNode = false;
1626
1917
  this.pendingNodePosition = null;
@@ -1658,11 +1949,7 @@ export class Editor extends RapidElement {
1658
1949
  this.closeNodeEditor();
1659
1950
  }
1660
1951
  handleNodeEditCanceled() {
1661
- // If we were creating a new node, just discard it
1662
- if (this.isCreatingNewNode) {
1663
- this.isCreatingNewNode = false;
1664
- this.pendingNodePosition = null;
1665
- }
1952
+ this.cleanUpConnection();
1666
1953
  this.closeNodeEditor();
1667
1954
  }
1668
1955
  getNodeAtPosition(mouseX, mouseY) {
@@ -1921,8 +2208,8 @@ export class Editor extends RapidElement {
1921
2208
  const languageLocalization = this.getLanguageLocalization(languageCode);
1922
2209
  const bundles = [];
1923
2210
  this.definition.nodes.forEach((node) => {
1924
- var _b, _c, _d, _e;
1925
- node.actions.forEach((action) => {
2211
+ var _b, _c, _d, _e, _f;
2212
+ (_b = node.actions) === null || _b === void 0 ? void 0 : _b.forEach((action) => {
1926
2213
  const config = ACTION_CONFIG[action.type];
1927
2214
  if (!(config === null || config === void 0 ? void 0 : config.localizable) || config.localizable.length === 0) {
1928
2215
  return;
@@ -1944,14 +2231,14 @@ export class Editor extends RapidElement {
1944
2231
  if (!includeCategories) {
1945
2232
  return;
1946
2233
  }
1947
- const nodeUI = (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes) === null || _c === void 0 ? void 0 : _c[node.uuid];
2234
+ const nodeUI = (_d = (_c = this.definition._ui) === null || _c === void 0 ? void 0 : _c.nodes) === null || _d === void 0 ? void 0 : _d[node.uuid];
1948
2235
  const nodeType = nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.type;
1949
2236
  if (!nodeType) {
1950
2237
  return;
1951
2238
  }
1952
2239
  const nodeConfig = NODE_CONFIG[nodeType];
1953
2240
  if ((nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.localizable) === 'categories' &&
1954
- ((_e = (_d = node.router) === null || _d === void 0 ? void 0 : _d.categories) === null || _e === void 0 ? void 0 : _e.length)) {
2241
+ ((_f = (_e = node.router) === null || _e === void 0 ? void 0 : _e.categories) === null || _f === void 0 ? void 0 : _f.length)) {
1955
2242
  const categoryTranslations = node.router.categories.flatMap((category) => this.findTranslations('category', category.uuid, ['name'], category, languageLocalization));
1956
2243
  if (categoryTranslations.length > 0) {
1957
2244
  bundles.push({
@@ -2048,6 +2335,7 @@ export class Editor extends RapidElement {
2048
2335
  return;
2049
2336
  }
2050
2337
  this.localizationWindowHidden = false;
2338
+ this.revisionsWindowHidden = true;
2051
2339
  const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
2052
2340
  if (!alreadySelected) {
2053
2341
  this.handleLanguageChange(languages[0].code);
@@ -2255,6 +2543,199 @@ export class Editor extends RapidElement {
2255
2543
  }
2256
2544
  this.autoTranslating = false;
2257
2545
  }
2546
+ handleRevisionsTabClick() {
2547
+ if (this.revisionsWindowHidden) {
2548
+ this.fetchRevisions();
2549
+ this.revisionsWindowHidden = false;
2550
+ this.localizationWindowHidden = true; // Close other window
2551
+ }
2552
+ }
2553
+ handleRevisionsWindowClosed() {
2554
+ this.resetRevisionsScroll();
2555
+ this.revisionsWindowHidden = true;
2556
+ if (this.viewingRevision) {
2557
+ this.handleCancelRevisionView();
2558
+ }
2559
+ }
2560
+ resetRevisionsScroll() {
2561
+ var _b;
2562
+ const list = (_b = this.querySelector('#revisions-window').shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.body');
2563
+ if (list) {
2564
+ list.scrollTop = 0;
2565
+ }
2566
+ }
2567
+ async fetchRevisions() {
2568
+ this.isLoadingRevisions = true;
2569
+ try {
2570
+ const results = await fetchResults(`/flow/revisions/${this.flow}/?version=${FLOW_SPEC_VERSION}`);
2571
+ this.revisions = results.slice(1);
2572
+ }
2573
+ catch (e) {
2574
+ console.error('Error fetching revisions', e);
2575
+ }
2576
+ finally {
2577
+ this.isLoadingRevisions = false;
2578
+ }
2579
+ }
2580
+ async handleRevisionClick(revision) {
2581
+ var _b, _c;
2582
+ if (((_b = this.viewingRevision) === null || _b === void 0 ? void 0 : _b.id) === revision.id) {
2583
+ return;
2584
+ }
2585
+ if (!this.viewingRevision) {
2586
+ // Save current state first
2587
+ this.preRevertState = {
2588
+ definition: this.definition,
2589
+ dirtyDate: this.dirtyDate
2590
+ };
2591
+ }
2592
+ this.viewingRevision = revision;
2593
+ this.isLoadingRevisions = true;
2594
+ (_c = this.plumber) === null || _c === void 0 ? void 0 : _c.reset();
2595
+ try {
2596
+ await getStore()
2597
+ .getState()
2598
+ .fetchRevision(`/flow/revisions/${this.flow}`, revision.id.toString());
2599
+ }
2600
+ catch (e) {
2601
+ console.error('Error fetching revision details', e);
2602
+ this.handleCancelRevisionView();
2603
+ }
2604
+ finally {
2605
+ this.isLoadingRevisions = false;
2606
+ }
2607
+ }
2608
+ handleCancelRevisionView() {
2609
+ var _b;
2610
+ (_b = this.plumber) === null || _b === void 0 ? void 0 : _b.reset();
2611
+ if (this.preRevertState) {
2612
+ const currentInfo = getStore().getState().flowInfo;
2613
+ getStore().getState().setFlowContents({
2614
+ definition: this.preRevertState.definition,
2615
+ info: currentInfo
2616
+ });
2617
+ if (this.preRevertState.dirtyDate) {
2618
+ getStore().getState().setDirtyDate(this.preRevertState.dirtyDate);
2619
+ }
2620
+ }
2621
+ else {
2622
+ // Fallback if no pre-revert definition
2623
+ getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
2624
+ }
2625
+ this.viewingRevision = null;
2626
+ this.preRevertState = null;
2627
+ }
2628
+ async handleRevertClick() {
2629
+ var _b;
2630
+ if (!this.viewingRevision || !this.preRevertState)
2631
+ return;
2632
+ (_b = this.plumber) === null || _b === void 0 ? void 0 : _b.reset();
2633
+ // Use the content of the viewing revision (this.definition)
2634
+ // but the revision number of the current head (preRevertState)
2635
+ // so the server accepts it as a valid update
2636
+ const definitionToSave = {
2637
+ ...this.definition,
2638
+ revision: this.preRevertState.definition.revision
2639
+ };
2640
+ await this.saveChanges(definitionToSave);
2641
+ this.viewingRevision = null;
2642
+ this.preRevertState = null;
2643
+ this.revisionsWindowHidden = true;
2644
+ const revisionsWindow = document.getElementById('revisions-window');
2645
+ revisionsWindow.handleClose();
2646
+ // Refresh revisions list to show the new one
2647
+ this.fetchRevisions();
2648
+ // Fetch the latest version of the flow to ensure the store is up to date
2649
+ getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
2650
+ }
2651
+ renderRevisionsTab() {
2652
+ return html `
2653
+ <temba-floating-tab
2654
+ id="revisions-tab"
2655
+ icon="revisions"
2656
+ label="Revisions"
2657
+ color="rgb(142, 94, 167)"
2658
+ top="105"
2659
+ .hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
2660
+ @temba-button-clicked=${this.handleRevisionsTabClick}
2661
+ ></temba-floating-tab>
2662
+ `;
2663
+ }
2664
+ renderRevisionsWindow() {
2665
+ return html `
2666
+ <temba-floating-window
2667
+ id="revisions-window"
2668
+ header="Revisions"
2669
+ .width=${360}
2670
+ .maxHeight=${600}
2671
+ .top=${75}
2672
+ color="rgb(142, 94, 167)"
2673
+ .hidden=${this.revisionsWindowHidden}
2674
+ @temba-dialog-hidden=${this.handleRevisionsWindowClosed}
2675
+ >
2676
+ <div class="localization-window-content">
2677
+ <div
2678
+ class="revisions-list"
2679
+ style="display:flex; flex-direction:column; gap:8px; overflow-y:auto; padding-bottom:10px;"
2680
+ >
2681
+ ${this.isLoadingRevisions && !this.revisions.length
2682
+ ? html `<temba-loading></temba-loading>`
2683
+ : this.revisions.map((rev) => {
2684
+ var _b;
2685
+ const isSelected = ((_b = this.viewingRevision) === null || _b === void 0 ? void 0 : _b.id) === rev.id;
2686
+ return html `
2687
+ <div
2688
+ class="revision-item ${isSelected ? 'selected' : ''}"
2689
+ style="padding:8px; border-radius:4px; cursor:pointer; background:${isSelected
2690
+ ? '#f0f6ff' // Light blue bg for selected
2691
+ : '#f9fafb'}; border:1px solid ${isSelected ? '#a4cafe' : '#e5e7eb'}; transition: all 0.2s ease;"
2692
+ @click=${() => this.handleRevisionClick(rev)}
2693
+ >
2694
+ <div
2695
+ style="display:flex; justify-content:space-between; align-items:center;"
2696
+ >
2697
+ <div
2698
+ class="revision-header"
2699
+ style="margin-bottom: 2px;"
2700
+ >
2701
+ <div
2702
+ style="font-weight:600; font-size:13px; color:#111827;"
2703
+ >
2704
+ <temba-date value=${rev.created_on} display="duration"></temba-date>
2705
+
2706
+ </div>
2707
+ <div style="font-size:11px; color:#6b7280;">
2708
+ ${rev.user.name || rev.user.username}
2709
+ </div>
2710
+ </div>
2711
+ ${isSelected
2712
+ ? html `<button
2713
+ class="revert-button"
2714
+ @click=${this.handleRevertClick}
2715
+ >
2716
+ Revert
2717
+ </button>`
2718
+ : html ``}
2719
+
2720
+ </button>
2721
+ </div>
2722
+
2723
+ ${rev.comment
2724
+ ? html `<div
2725
+ style="font-size:12px; color:#4b5563; margin-top:4px;"
2726
+ >
2727
+ ${rev.comment}
2728
+ </div>`
2729
+ : ''}
2730
+
2731
+ </div>
2732
+ `;
2733
+ })}
2734
+ </div>
2735
+ </div>
2736
+ </temba-floating-window>
2737
+ `;
2738
+ }
2258
2739
  renderLocalizationWindow() {
2259
2740
  var _b, _c, _d;
2260
2741
  const languages = this.getLocalizationLanguages();
@@ -2481,6 +2962,9 @@ export class Editor extends RapidElement {
2481
2962
  behavior: 'smooth'
2482
2963
  });
2483
2964
  }
2965
+ isReadOnly() {
2966
+ return this.viewingRevision !== null || this.isTranslating;
2967
+ }
2484
2968
  render() {
2485
2969
  var _b, _c;
2486
2970
  // we have to embed our own style since we are in light DOM
@@ -2489,17 +2973,24 @@ export class Editor extends RapidElement {
2489
2973
  ${unsafeCSS(CanvasNode.styles.cssText)}
2490
2974
  </style>`;
2491
2975
  const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
2492
- return html `${style} ${this.renderLocalizationWindow()}
2493
- ${this.renderAutoTranslateDialog()}
2976
+ return html `${style} ${this.renderRevisionsWindow()}
2977
+ ${this.renderLocalizationWindow()} ${this.renderAutoTranslateDialog()}
2494
2978
  <div id="editor">
2495
2979
  <div
2496
2980
  id="grid"
2981
+ class="${this.viewingRevision ? 'viewing-revision' : ''}"
2497
2982
  style="min-width:100%;width:${this.canvasSize.width}px; height:${this
2498
2983
  .canvasSize.height}px"
2499
2984
  >
2500
- <div id="canvas">
2985
+ <div
2986
+ id="canvas"
2987
+ class="${getClasses({
2988
+ 'viewing-revision': !!this.viewingRevision,
2989
+ 'read-only-connections': !!this.viewingRevision || this.isTranslating
2990
+ })}"
2991
+ >
2501
2992
  ${this.definition
2502
- ? repeat(this.definition.nodes, (node) => node.uuid, (node, index) => {
2993
+ ? repeat([...this.definition.nodes].sort((a, b) => a.uuid.localeCompare(b.uuid)), (node) => node.uuid, (node) => {
2503
2994
  var _b, _c, _d;
2504
2995
  const position = ((_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid]) === null || _c === void 0 ? void 0 : _c.position) || {
2505
2996
  left: 0,
@@ -2509,7 +3000,8 @@ export class Editor extends RapidElement {
2509
3000
  ((_d = this.currentDragItem) === null || _d === void 0 ? void 0 : _d.uuid) === node.uuid;
2510
3001
  const selected = this.selectedItems.has(node.uuid);
2511
3002
  // first node is the flow start (nodes are sorted by position)
2512
- const isFlowStart = index === 0;
3003
+ const isFlowStart = this.definition.nodes.length > 0 &&
3004
+ this.definition.nodes[0].uuid === node.uuid;
2513
3005
  return html `<temba-flow-node
2514
3006
  class="draggable ${dragging ? 'dragging' : ''} ${selected
2515
3007
  ? 'selected'
@@ -2545,6 +3037,7 @@ export class Editor extends RapidElement {
2545
3037
  ></temba-sticky-note>`;
2546
3038
  })}
2547
3039
  ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
3040
+ ${this.renderConnectionPlaceholder()}
2548
3041
  </div>
2549
3042
  </div>
2550
3043
  </div>
@@ -2561,11 +3054,13 @@ export class Editor extends RapidElement {
2561
3054
  : ''}
2562
3055
 
2563
3056
  <temba-canvas-menu></temba-canvas-menu>
2564
- <temba-node-type-selector
2565
- .flowType=${this.flowType}
2566
- .features=${this.features}
2567
- ></temba-node-type-selector>
2568
- ${this.renderLocalizationTab()} `;
3057
+ ${!this.viewingRevision
3058
+ ? html `<temba-node-type-selector
3059
+ .flowType=${this.flowType}
3060
+ .features=${this.features}
3061
+ ></temba-node-type-selector>`
3062
+ : ''}
3063
+ ${this.renderRevisionsTab()} ${this.renderLocalizationTab()} `;
2569
3064
  }
2570
3065
  }
2571
3066
  __decorate([
@@ -2628,6 +3123,9 @@ __decorate([
2628
3123
  __decorate([
2629
3124
  state()
2630
3125
  ], Editor.prototype, "dragFromNodeId", void 0);
3126
+ __decorate([
3127
+ state()
3128
+ ], Editor.prototype, "originalConnectionTargetId", void 0);
2631
3129
  __decorate([
2632
3130
  state()
2633
3131
  ], Editor.prototype, "isValidTarget", void 0);
@@ -2652,6 +3150,18 @@ __decorate([
2652
3150
  __decorate([
2653
3151
  state()
2654
3152
  ], Editor.prototype, "autoTranslateError", void 0);
3153
+ __decorate([
3154
+ state()
3155
+ ], Editor.prototype, "revisionsWindowHidden", void 0);
3156
+ __decorate([
3157
+ state()
3158
+ ], Editor.prototype, "revisions", void 0);
3159
+ __decorate([
3160
+ state()
3161
+ ], Editor.prototype, "viewingRevision", void 0);
3162
+ __decorate([
3163
+ state()
3164
+ ], Editor.prototype, "isLoadingRevisions", void 0);
2655
3165
  __decorate([
2656
3166
  state()
2657
3167
  ], Editor.prototype, "editingNode", void 0);
@@ -2676,4 +3186,7 @@ __decorate([
2676
3186
  __decorate([
2677
3187
  state()
2678
3188
  ], Editor.prototype, "actionDragTargetNodeUuid", void 0);
3189
+ __decorate([
3190
+ state()
3191
+ ], Editor.prototype, "connectionPlaceholder", void 0);
2679
3192
  //# sourceMappingURL=Editor.js.map