@nyaruka/temba-components 0.136.1 → 0.138.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/demo/components/webchat/example.html +2 -2
  3. package/dist/temba-components.js +692 -622
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/display/Chat.js +123 -44
  6. package/out-tsc/src/display/Chat.js.map +1 -1
  7. package/out-tsc/src/display/FloatingTab.js +2 -2
  8. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  9. package/out-tsc/src/events/eventRenderers.js +442 -0
  10. package/out-tsc/src/events/eventRenderers.js.map +1 -0
  11. package/out-tsc/src/flow/CanvasNode.js +45 -24
  12. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  13. package/out-tsc/src/flow/Editor.js +308 -18
  14. package/out-tsc/src/flow/Editor.js.map +1 -1
  15. package/out-tsc/src/flow/NodeEditor.js +0 -1
  16. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  17. package/out-tsc/src/flow/Plumber.js +110 -64
  18. package/out-tsc/src/flow/Plumber.js.map +1 -1
  19. package/out-tsc/src/list/ShortcutList.js +1 -1
  20. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  21. package/out-tsc/src/live/ContactChat.js +12 -321
  22. package/out-tsc/src/live/ContactChat.js.map +1 -1
  23. package/out-tsc/src/simulator/Simulator.js +439 -575
  24. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  25. package/out-tsc/src/store/AppState.js +12 -2
  26. package/out-tsc/src/store/AppState.js.map +1 -1
  27. package/out-tsc/test/temba-flow-editor-node.test.js +2 -1
  28. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  29. package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
  30. package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
  31. package/out-tsc/test/temba-flow-editor.test.js +14 -10
  32. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  33. package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
  34. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  35. package/out-tsc/test/temba-flow-plumber.test.js +6 -0
  36. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  37. package/out-tsc/test/temba-simulator.test.js +51 -32
  38. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  39. package/package.json +1 -1
  40. package/screenshots/truth/contacts/chat-failure.png +0 -0
  41. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  42. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  43. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  44. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  45. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  46. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  47. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  48. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  49. package/screenshots/truth/simulator/after-reset.png +0 -0
  50. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  51. package/screenshots/truth/simulator/context-expanded.png +0 -0
  52. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  53. package/screenshots/truth/simulator/event-info.png +0 -0
  54. package/screenshots/truth/simulator/image-attachment.png +0 -0
  55. package/screenshots/truth/simulator/open-initial.png +0 -0
  56. package/screenshots/truth/simulator/quick-replies.png +0 -0
  57. package/src/display/Chat.ts +123 -44
  58. package/src/display/FloatingTab.ts +2 -2
  59. package/src/events/eventRenderers.ts +527 -0
  60. package/src/flow/CanvasNode.ts +54 -29
  61. package/src/flow/Editor.ts +360 -19
  62. package/src/flow/NodeEditor.ts +0 -1
  63. package/src/flow/Plumber.ts +123 -69
  64. package/src/list/ShortcutList.ts +1 -1
  65. package/src/live/ContactChat.ts +17 -376
  66. package/src/simulator/Simulator.ts +498 -617
  67. package/src/store/AppState.ts +13 -2
  68. package/test/temba-flow-editor-node.test.ts +2 -1
  69. package/test/temba-flow-editor-revisions.test.ts +134 -0
  70. package/test/temba-flow-editor.test.ts +16 -10
  71. package/test/temba-flow-plumber-connections.test.ts +7 -1
  72. package/test/temba-flow-plumber.test.ts +6 -0
  73. package/test/temba-simulator.test.ts +64 -34
@@ -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';
@@ -90,6 +90,7 @@ export class Editor extends RapidElement {
90
90
  background-position: 10px 10px;
91
91
  width: 100%;
92
92
  display: flex;
93
+ padding-top: 20px;
93
94
  }
94
95
 
95
96
  #canvas {
@@ -109,6 +110,29 @@ export class Editor extends RapidElement {
109
110
  transition: none !important;
110
111
  }
111
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
+
112
136
  body .jtk-endpoint {
113
137
  width: initial;
114
138
  height: initial;
@@ -163,12 +187,28 @@ export class Editor extends RapidElement {
163
187
  stroke-width: 3px;
164
188
  }
165
189
 
190
+ body #canvas.read-only-connections svg.jtk-connector.jtk-hover path {
191
+ stroke: var(--color-connectors) !important;
192
+ }
193
+
166
194
  body .plumb-connector.jtk-hover .plumb-arrow {
167
195
  fill: var(--color-success) !important;
168
196
  stroke-width: 0px;
169
197
  z-index: 10;
170
198
  }
171
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
+
172
212
  /* Activity overlays on connections */
173
213
  .jtk-overlay.activity-overlay {
174
214
  background: #f3f3f3;
@@ -190,13 +230,13 @@ export class Editor extends RapidElement {
190
230
  background: #3498db;
191
231
  border: 1px solid #2980b9;
192
232
  border-radius: 12px;
193
- padding: 3px 5px;
233
+ padding: 3px 6px;
194
234
  color: #fff;
195
235
  font-weight: 500;
196
236
  top: -10px;
197
237
  left: -10px;
198
238
  font-size: 13px;
199
- min-width: 22px;
239
+
200
240
  text-align: center;
201
241
  z-index: 600;
202
242
  line-height: 1;
@@ -457,6 +497,18 @@ export class Editor extends RapidElement {
457
497
  width: 100%;
458
498
  }
459
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
+
460
512
  .auto-translate-button {
461
513
  background: var(--color-primary-dark);
462
514
  border: none;
@@ -543,6 +595,11 @@ export class Editor extends RapidElement {
543
595
  this.autoTranslating = false;
544
596
  this.autoTranslateModel = null;
545
597
  this.autoTranslateError = null;
598
+ this.revisionsWindowHidden = true;
599
+ this.revisions = [];
600
+ this.viewingRevision = null;
601
+ this.isLoadingRevisions = false;
602
+ this.preRevertState = null;
546
603
  this.translationCache = new Map();
547
604
  // NodeEditor state - handles both node and action editing
548
605
  this.editingNode = null;
@@ -692,10 +749,11 @@ export class Editor extends RapidElement {
692
749
  }
693
750
  }, SAVE_QUIET_TIME);
694
751
  }
695
- saveChanges() {
752
+ saveChanges(definitionOverride) {
753
+ const definition = definitionOverride || this.definition;
696
754
  // post the flow definition to the server
697
- getStore()
698
- .postJSON(`/flow/revisions/${this.flow}/`, this.definition)
755
+ return getStore()
756
+ .postJSON(`/flow/revisions/${this.flow}/`, definition)
699
757
  .then((response) => {
700
758
  var _b;
701
759
  // Update flow info and revision with the response data
@@ -707,6 +765,10 @@ export class Editor extends RapidElement {
707
765
  if (((_b = response.json.revision) === null || _b === void 0 ? void 0 : _b.revision) !== undefined) {
708
766
  state.setRevision(response.json.revision.revision);
709
767
  }
768
+ // if the revisions window is open, refresh the list
769
+ if (!this.revisionsWindowHidden) {
770
+ this.fetchRevisions();
771
+ }
710
772
  }
711
773
  })
712
774
  .catch((error) => {
@@ -750,7 +812,7 @@ export class Editor extends RapidElement {
750
812
  clearTimeout(this.activityTimer);
751
813
  }
752
814
  this.activityTimer = window.setTimeout(() => {
753
- this.fetchActivityData();
815
+ // this.fetchActivityData();
754
816
  }, this.activityInterval);
755
817
  });
756
818
  }
@@ -831,6 +893,8 @@ export class Editor extends RapidElement {
831
893
  // ignore right clicks
832
894
  if (event.button !== 0)
833
895
  return;
896
+ if (this.isReadOnly())
897
+ return;
834
898
  const element = event.currentTarget;
835
899
  // Only start dragging if clicking on the element itself, not on exits or other interactive elements
836
900
  const target = event.target;
@@ -892,6 +956,8 @@ export class Editor extends RapidElement {
892
956
  }
893
957
  handleCanvasMouseDown(event) {
894
958
  var _b;
959
+ if (this.isReadOnly())
960
+ return;
895
961
  const target = event.target;
896
962
  if (target.id === 'canvas' || target.id === 'grid') {
897
963
  // Ignore clicks on exits
@@ -953,6 +1019,7 @@ export class Editor extends RapidElement {
953
1019
  // Clean up jsPlumb connections for nodes before removing them
954
1020
  uuids.forEach((uuid) => {
955
1021
  this.plumber.removeNodeConnections(uuid);
1022
+ this.plumber.removeAllEndpoints(uuid);
956
1023
  });
957
1024
  // Now remove them from the definition
958
1025
  if (uuids.length > 0 && this.plumber) {
@@ -1371,6 +1438,10 @@ export class Editor extends RapidElement {
1371
1438
  store.getState().expandCanvas(maxWidth, maxHeight);
1372
1439
  }
1373
1440
  handleCanvasContextMenu(event) {
1441
+ if (this.isReadOnly()) {
1442
+ event.preventDefault();
1443
+ return;
1444
+ }
1374
1445
  // Check if we right-clicked on empty canvas space
1375
1446
  const target = event.target;
1376
1447
  if (target.id !== 'canvas') {
@@ -2047,6 +2118,7 @@ export class Editor extends RapidElement {
2047
2118
  return;
2048
2119
  }
2049
2120
  this.localizationWindowHidden = false;
2121
+ this.revisionsWindowHidden = true;
2050
2122
  const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
2051
2123
  if (!alreadySelected) {
2052
2124
  this.handleLanguageChange(languages[0].code);
@@ -2254,6 +2326,199 @@ export class Editor extends RapidElement {
2254
2326
  }
2255
2327
  this.autoTranslating = false;
2256
2328
  }
2329
+ handleRevisionsTabClick() {
2330
+ if (this.revisionsWindowHidden) {
2331
+ this.fetchRevisions();
2332
+ this.revisionsWindowHidden = false;
2333
+ this.localizationWindowHidden = true; // Close other window
2334
+ }
2335
+ }
2336
+ handleRevisionsWindowClosed() {
2337
+ this.resetRevisionsScroll();
2338
+ this.revisionsWindowHidden = true;
2339
+ if (this.viewingRevision) {
2340
+ this.handleCancelRevisionView();
2341
+ }
2342
+ }
2343
+ resetRevisionsScroll() {
2344
+ var _b;
2345
+ const list = (_b = this.querySelector('#revisions-window').shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.body');
2346
+ if (list) {
2347
+ list.scrollTop = 0;
2348
+ }
2349
+ }
2350
+ async fetchRevisions() {
2351
+ this.isLoadingRevisions = true;
2352
+ try {
2353
+ const results = await fetchResults(`/flow/revisions/${this.flow}/?version=${FLOW_SPEC_VERSION}`);
2354
+ this.revisions = results.slice(1);
2355
+ }
2356
+ catch (e) {
2357
+ console.error('Error fetching revisions', e);
2358
+ }
2359
+ finally {
2360
+ this.isLoadingRevisions = false;
2361
+ }
2362
+ }
2363
+ async handleRevisionClick(revision) {
2364
+ var _b, _c;
2365
+ if (((_b = this.viewingRevision) === null || _b === void 0 ? void 0 : _b.id) === revision.id) {
2366
+ return;
2367
+ }
2368
+ if (!this.viewingRevision) {
2369
+ // Save current state first
2370
+ this.preRevertState = {
2371
+ definition: this.definition,
2372
+ dirtyDate: this.dirtyDate
2373
+ };
2374
+ }
2375
+ this.viewingRevision = revision;
2376
+ this.isLoadingRevisions = true;
2377
+ (_c = this.plumber) === null || _c === void 0 ? void 0 : _c.reset();
2378
+ try {
2379
+ await getStore()
2380
+ .getState()
2381
+ .fetchRevision(`/flow/revisions/${this.flow}`, revision.id.toString());
2382
+ }
2383
+ catch (e) {
2384
+ console.error('Error fetching revision details', e);
2385
+ this.handleCancelRevisionView();
2386
+ }
2387
+ finally {
2388
+ this.isLoadingRevisions = false;
2389
+ }
2390
+ }
2391
+ handleCancelRevisionView() {
2392
+ var _b;
2393
+ (_b = this.plumber) === null || _b === void 0 ? void 0 : _b.reset();
2394
+ if (this.preRevertState) {
2395
+ const currentInfo = getStore().getState().flowInfo;
2396
+ getStore().getState().setFlowContents({
2397
+ definition: this.preRevertState.definition,
2398
+ info: currentInfo
2399
+ });
2400
+ if (this.preRevertState.dirtyDate) {
2401
+ getStore().getState().setDirtyDate(this.preRevertState.dirtyDate);
2402
+ }
2403
+ }
2404
+ else {
2405
+ // Fallback if no pre-revert definition
2406
+ getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
2407
+ }
2408
+ this.viewingRevision = null;
2409
+ this.preRevertState = null;
2410
+ }
2411
+ async handleRevertClick() {
2412
+ var _b;
2413
+ if (!this.viewingRevision || !this.preRevertState)
2414
+ return;
2415
+ (_b = this.plumber) === null || _b === void 0 ? void 0 : _b.reset();
2416
+ // Use the content of the viewing revision (this.definition)
2417
+ // but the revision number of the current head (preRevertState)
2418
+ // so the server accepts it as a valid update
2419
+ const definitionToSave = {
2420
+ ...this.definition,
2421
+ revision: this.preRevertState.definition.revision
2422
+ };
2423
+ await this.saveChanges(definitionToSave);
2424
+ this.viewingRevision = null;
2425
+ this.preRevertState = null;
2426
+ this.revisionsWindowHidden = true;
2427
+ const revisionsWindow = document.getElementById('revisions-window');
2428
+ revisionsWindow.handleClose();
2429
+ // Refresh revisions list to show the new one
2430
+ this.fetchRevisions();
2431
+ // Fetch the latest version of the flow to ensure the store is up to date
2432
+ getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
2433
+ }
2434
+ renderRevisionsTab() {
2435
+ return html `
2436
+ <temba-floating-tab
2437
+ id="revisions-tab"
2438
+ icon="revisions"
2439
+ label="Revisions"
2440
+ color="rgb(142, 94, 167)"
2441
+ top="105"
2442
+ .hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
2443
+ @temba-button-clicked=${this.handleRevisionsTabClick}
2444
+ ></temba-floating-tab>
2445
+ `;
2446
+ }
2447
+ renderRevisionsWindow() {
2448
+ return html `
2449
+ <temba-floating-window
2450
+ id="revisions-window"
2451
+ header="Revisions"
2452
+ .width=${360}
2453
+ .maxHeight=${600}
2454
+ .top=${75}
2455
+ color="rgb(142, 94, 167)"
2456
+ .hidden=${this.revisionsWindowHidden}
2457
+ @temba-dialog-hidden=${this.handleRevisionsWindowClosed}
2458
+ >
2459
+ <div class="localization-window-content">
2460
+ <div
2461
+ class="revisions-list"
2462
+ style="display:flex; flex-direction:column; gap:8px; overflow-y:auto; padding-bottom:10px;"
2463
+ >
2464
+ ${this.isLoadingRevisions && !this.revisions.length
2465
+ ? html `<temba-loading></temba-loading>`
2466
+ : this.revisions.map((rev) => {
2467
+ var _b;
2468
+ const isSelected = ((_b = this.viewingRevision) === null || _b === void 0 ? void 0 : _b.id) === rev.id;
2469
+ return html `
2470
+ <div
2471
+ class="revision-item ${isSelected ? 'selected' : ''}"
2472
+ style="padding:8px; border-radius:4px; cursor:pointer; background:${isSelected
2473
+ ? '#f0f6ff' // Light blue bg for selected
2474
+ : '#f9fafb'}; border:1px solid ${isSelected ? '#a4cafe' : '#e5e7eb'}; transition: all 0.2s ease;"
2475
+ @click=${() => this.handleRevisionClick(rev)}
2476
+ >
2477
+ <div
2478
+ style="display:flex; justify-content:space-between; align-items:center;"
2479
+ >
2480
+ <div
2481
+ class="revision-header"
2482
+ style="margin-bottom: 2px;"
2483
+ >
2484
+ <div
2485
+ style="font-weight:600; font-size:13px; color:#111827;"
2486
+ >
2487
+ <temba-date value=${rev.created_on} display="duration"></temba-date>
2488
+
2489
+ </div>
2490
+ <div style="font-size:11px; color:#6b7280;">
2491
+ ${rev.user.name || rev.user.username}
2492
+ </div>
2493
+ </div>
2494
+ ${isSelected
2495
+ ? html `<button
2496
+ class="revert-button"
2497
+ @click=${this.handleRevertClick}
2498
+ >
2499
+ Revert
2500
+ </button>`
2501
+ : html ``}
2502
+
2503
+ </button>
2504
+ </div>
2505
+
2506
+ ${rev.comment
2507
+ ? html `<div
2508
+ style="font-size:12px; color:#4b5563; margin-top:4px;"
2509
+ >
2510
+ ${rev.comment}
2511
+ </div>`
2512
+ : ''}
2513
+
2514
+ </div>
2515
+ `;
2516
+ })}
2517
+ </div>
2518
+ </div>
2519
+ </temba-floating-window>
2520
+ `;
2521
+ }
2257
2522
  renderLocalizationWindow() {
2258
2523
  var _b, _c, _d;
2259
2524
  const languages = this.getLocalizationLanguages();
@@ -2480,6 +2745,9 @@ export class Editor extends RapidElement {
2480
2745
  behavior: 'smooth'
2481
2746
  });
2482
2747
  }
2748
+ isReadOnly() {
2749
+ return this.viewingRevision !== null || this.isTranslating;
2750
+ }
2483
2751
  render() {
2484
2752
  var _b, _c;
2485
2753
  // we have to embed our own style since we are in light DOM
@@ -2488,17 +2756,24 @@ export class Editor extends RapidElement {
2488
2756
  ${unsafeCSS(CanvasNode.styles.cssText)}
2489
2757
  </style>`;
2490
2758
  const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
2491
- return html `${style} ${this.renderLocalizationWindow()}
2492
- ${this.renderAutoTranslateDialog()}
2759
+ return html `${style} ${this.renderRevisionsWindow()}
2760
+ ${this.renderLocalizationWindow()} ${this.renderAutoTranslateDialog()}
2493
2761
  <div id="editor">
2494
2762
  <div
2495
2763
  id="grid"
2764
+ class="${this.viewingRevision ? 'viewing-revision' : ''}"
2496
2765
  style="min-width:100%;width:${this.canvasSize.width}px; height:${this
2497
2766
  .canvasSize.height}px"
2498
2767
  >
2499
- <div id="canvas">
2768
+ <div
2769
+ id="canvas"
2770
+ class="${getClasses({
2771
+ 'viewing-revision': !!this.viewingRevision,
2772
+ 'read-only-connections': !!this.viewingRevision || this.isTranslating
2773
+ })}"
2774
+ >
2500
2775
  ${this.definition
2501
- ? repeat(this.definition.nodes, (node) => node.uuid, (node, index) => {
2776
+ ? repeat([...this.definition.nodes].sort((a, b) => a.uuid.localeCompare(b.uuid)), (node) => node.uuid, (node) => {
2502
2777
  var _b, _c, _d;
2503
2778
  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) || {
2504
2779
  left: 0,
@@ -2508,7 +2783,8 @@ export class Editor extends RapidElement {
2508
2783
  ((_d = this.currentDragItem) === null || _d === void 0 ? void 0 : _d.uuid) === node.uuid;
2509
2784
  const selected = this.selectedItems.has(node.uuid);
2510
2785
  // first node is the flow start (nodes are sorted by position)
2511
- const isFlowStart = index === 0;
2786
+ const isFlowStart = this.definition.nodes.length > 0 &&
2787
+ this.definition.nodes[0].uuid === node.uuid;
2512
2788
  return html `<temba-flow-node
2513
2789
  class="draggable ${dragging ? 'dragging' : ''} ${selected
2514
2790
  ? 'selected'
@@ -2560,11 +2836,13 @@ export class Editor extends RapidElement {
2560
2836
  : ''}
2561
2837
 
2562
2838
  <temba-canvas-menu></temba-canvas-menu>
2563
- <temba-node-type-selector
2564
- .flowType=${this.flowType}
2565
- .features=${this.features}
2566
- ></temba-node-type-selector>
2567
- ${this.renderLocalizationTab()} `;
2839
+ ${!this.viewingRevision
2840
+ ? html `<temba-node-type-selector
2841
+ .flowType=${this.flowType}
2842
+ .features=${this.features}
2843
+ ></temba-node-type-selector>`
2844
+ : ''}
2845
+ ${this.renderRevisionsTab()} ${this.renderLocalizationTab()} `;
2568
2846
  }
2569
2847
  }
2570
2848
  __decorate([
@@ -2651,6 +2929,18 @@ __decorate([
2651
2929
  __decorate([
2652
2930
  state()
2653
2931
  ], Editor.prototype, "autoTranslateError", void 0);
2932
+ __decorate([
2933
+ state()
2934
+ ], Editor.prototype, "revisionsWindowHidden", void 0);
2935
+ __decorate([
2936
+ state()
2937
+ ], Editor.prototype, "revisions", void 0);
2938
+ __decorate([
2939
+ state()
2940
+ ], Editor.prototype, "viewingRevision", void 0);
2941
+ __decorate([
2942
+ state()
2943
+ ], Editor.prototype, "isLoadingRevisions", void 0);
2654
2944
  __decorate([
2655
2945
  state()
2656
2946
  ], Editor.prototype, "editingNode", void 0);