@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.
- package/CHANGELOG.md +19 -0
- package/demo/components/webchat/example.html +2 -2
- package/dist/temba-components.js +692 -622
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +123 -44
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +2 -2
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/events/eventRenderers.js +442 -0
- package/out-tsc/src/events/eventRenderers.js.map +1 -0
- package/out-tsc/src/flow/CanvasNode.js +45 -24
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +308 -18
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +0 -1
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +110 -64
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/list/ShortcutList.js +1 -1
- package/out-tsc/src/list/ShortcutList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +12 -321
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +439 -575
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +12 -2
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +2 -1
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
- package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +14 -10
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +6 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-simulator.test.js +51 -32
- package/out-tsc/test/temba-simulator.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/simulator/after-message-sent.png +0 -0
- package/screenshots/truth/simulator/after-reset.png +0 -0
- package/screenshots/truth/simulator/attachment-menu.png +0 -0
- package/screenshots/truth/simulator/context-expanded.png +0 -0
- package/screenshots/truth/simulator/context-explorer-open.png +0 -0
- package/screenshots/truth/simulator/event-info.png +0 -0
- package/screenshots/truth/simulator/image-attachment.png +0 -0
- package/screenshots/truth/simulator/open-initial.png +0 -0
- package/screenshots/truth/simulator/quick-replies.png +0 -0
- package/src/display/Chat.ts +123 -44
- package/src/display/FloatingTab.ts +2 -2
- package/src/events/eventRenderers.ts +527 -0
- package/src/flow/CanvasNode.ts +54 -29
- package/src/flow/Editor.ts +360 -19
- package/src/flow/NodeEditor.ts +0 -1
- package/src/flow/Plumber.ts +123 -69
- package/src/list/ShortcutList.ts +1 -1
- package/src/live/ContactChat.ts +17 -376
- package/src/simulator/Simulator.ts +498 -617
- package/src/store/AppState.ts +13 -2
- package/test/temba-flow-editor-node.test.ts +2 -1
- package/test/temba-flow-editor-revisions.test.ts +134 -0
- package/test/temba-flow-editor.test.ts +16 -10
- package/test/temba-flow-plumber-connections.test.ts +7 -1
- package/test/temba-flow-plumber.test.ts +6 -0
- package/test/temba-simulator.test.ts +64 -34
package/src/flow/Editor.ts
CHANGED
|
@@ -9,12 +9,31 @@ import {
|
|
|
9
9
|
NodeUI
|
|
10
10
|
} from '../store/flow-definition';
|
|
11
11
|
import { getStore } from '../store/Store';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
AppState,
|
|
14
|
+
fromStore,
|
|
15
|
+
zustand,
|
|
16
|
+
FLOW_SPEC_VERSION
|
|
17
|
+
} from '../store/AppState';
|
|
13
18
|
import { RapidElement } from '../RapidElement';
|
|
14
19
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
15
20
|
import { CustomEventType, Workspace } from '../interfaces';
|
|
16
|
-
import { generateUUID, postJSON } from '../utils';
|
|
21
|
+
import { generateUUID, postJSON, fetchResults, getClasses } from '../utils';
|
|
17
22
|
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
23
|
+
|
|
24
|
+
interface Revision {
|
|
25
|
+
id: number;
|
|
26
|
+
user: {
|
|
27
|
+
id: number;
|
|
28
|
+
username: string;
|
|
29
|
+
first_name: string;
|
|
30
|
+
last_name: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
};
|
|
33
|
+
created_on: string;
|
|
34
|
+
comment?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
import { ACTION_GROUP_METADATA } from './types';
|
|
19
38
|
import { Checkbox } from '../form/Checkbox';
|
|
20
39
|
|
|
@@ -30,6 +49,7 @@ import {
|
|
|
30
49
|
NodeBounds,
|
|
31
50
|
nodesOverlap
|
|
32
51
|
} from './utils';
|
|
52
|
+
import { FloatingWindow } from '../layout/FloatingWindow';
|
|
33
53
|
|
|
34
54
|
export function snapToGrid(value: number): number {
|
|
35
55
|
const snapped = Math.round(value / 20) * 20;
|
|
@@ -212,6 +232,23 @@ export class Editor extends RapidElement {
|
|
|
212
232
|
@state()
|
|
213
233
|
private autoTranslateError: string | null = null;
|
|
214
234
|
|
|
235
|
+
@state()
|
|
236
|
+
private revisionsWindowHidden = true;
|
|
237
|
+
|
|
238
|
+
@state()
|
|
239
|
+
private revisions: Revision[] = [];
|
|
240
|
+
|
|
241
|
+
@state()
|
|
242
|
+
private viewingRevision: Revision | null = null;
|
|
243
|
+
|
|
244
|
+
@state()
|
|
245
|
+
private isLoadingRevisions = false;
|
|
246
|
+
|
|
247
|
+
private preRevertState: {
|
|
248
|
+
definition: FlowDefinition;
|
|
249
|
+
dirtyDate: Date | null;
|
|
250
|
+
} | null = null;
|
|
251
|
+
|
|
215
252
|
private translationCache = new Map<string, string>();
|
|
216
253
|
|
|
217
254
|
// NodeEditor state - handles both node and action editing
|
|
@@ -309,6 +346,7 @@ export class Editor extends RapidElement {
|
|
|
309
346
|
background-position: 10px 10px;
|
|
310
347
|
width: 100%;
|
|
311
348
|
display: flex;
|
|
349
|
+
padding-top: 20px;
|
|
312
350
|
}
|
|
313
351
|
|
|
314
352
|
#canvas {
|
|
@@ -328,6 +366,29 @@ export class Editor extends RapidElement {
|
|
|
328
366
|
transition: none !important;
|
|
329
367
|
}
|
|
330
368
|
|
|
369
|
+
#canvas.viewing-revision {
|
|
370
|
+
pointer-events: none;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#canvas.read-only svg {
|
|
374
|
+
pointer-events: none;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#grid.viewing-revision {
|
|
378
|
+
background-color: #fff9fc;
|
|
379
|
+
background-image: radial-gradient(
|
|
380
|
+
circle,
|
|
381
|
+
rgba(166, 38, 164, 0.2) 1px,
|
|
382
|
+
transparent 1px
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#grid.viewing-revision temba-flow-node,
|
|
387
|
+
#grid.viewing-revision svg.jtk-connector,
|
|
388
|
+
#grid.viewing-revision .activity-overlay {
|
|
389
|
+
opacity: 0.5;
|
|
390
|
+
}
|
|
391
|
+
|
|
331
392
|
body .jtk-endpoint {
|
|
332
393
|
width: initial;
|
|
333
394
|
height: initial;
|
|
@@ -382,12 +443,28 @@ export class Editor extends RapidElement {
|
|
|
382
443
|
stroke-width: 3px;
|
|
383
444
|
}
|
|
384
445
|
|
|
446
|
+
body #canvas.read-only-connections svg.jtk-connector.jtk-hover path {
|
|
447
|
+
stroke: var(--color-connectors) !important;
|
|
448
|
+
}
|
|
449
|
+
|
|
385
450
|
body .plumb-connector.jtk-hover .plumb-arrow {
|
|
386
451
|
fill: var(--color-success) !important;
|
|
387
452
|
stroke-width: 0px;
|
|
388
453
|
z-index: 10;
|
|
389
454
|
}
|
|
390
455
|
|
|
456
|
+
body
|
|
457
|
+
#canvas.read-only-connections
|
|
458
|
+
.plumb-connector.jtk-hover
|
|
459
|
+
.plumb-arrow {
|
|
460
|
+
fill: var(--color-connectors) !important;
|
|
461
|
+
ponter-events: none;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
body #canvas.read-only-connections svg {
|
|
465
|
+
pointer-events: none;
|
|
466
|
+
}
|
|
467
|
+
|
|
391
468
|
/* Activity overlays on connections */
|
|
392
469
|
.jtk-overlay.activity-overlay {
|
|
393
470
|
background: #f3f3f3;
|
|
@@ -409,13 +486,13 @@ export class Editor extends RapidElement {
|
|
|
409
486
|
background: #3498db;
|
|
410
487
|
border: 1px solid #2980b9;
|
|
411
488
|
border-radius: 12px;
|
|
412
|
-
padding: 3px
|
|
489
|
+
padding: 3px 6px;
|
|
413
490
|
color: #fff;
|
|
414
491
|
font-weight: 500;
|
|
415
492
|
top: -10px;
|
|
416
493
|
left: -10px;
|
|
417
494
|
font-size: 13px;
|
|
418
|
-
|
|
495
|
+
|
|
419
496
|
text-align: center;
|
|
420
497
|
z-index: 600;
|
|
421
498
|
line-height: 1;
|
|
@@ -676,6 +753,18 @@ export class Editor extends RapidElement {
|
|
|
676
753
|
width: 100%;
|
|
677
754
|
}
|
|
678
755
|
|
|
756
|
+
.revert-button {
|
|
757
|
+
background: var(--color-primary-dark);
|
|
758
|
+
border: none;
|
|
759
|
+
color: #fff;
|
|
760
|
+
padding: 6px 10px;
|
|
761
|
+
border-radius: var(--curvature);
|
|
762
|
+
font-size: 11px;
|
|
763
|
+
font-weight: 600;
|
|
764
|
+
cursor: pointer;
|
|
765
|
+
transition: opacity 0.2s ease;
|
|
766
|
+
}
|
|
767
|
+
|
|
679
768
|
.auto-translate-button {
|
|
680
769
|
background: var(--color-primary-dark);
|
|
681
770
|
border: none;
|
|
@@ -894,10 +983,11 @@ export class Editor extends RapidElement {
|
|
|
894
983
|
}, SAVE_QUIET_TIME);
|
|
895
984
|
}
|
|
896
985
|
|
|
897
|
-
private saveChanges(): void {
|
|
986
|
+
private saveChanges(definitionOverride?: FlowDefinition): Promise<void> {
|
|
987
|
+
const definition = definitionOverride || this.definition;
|
|
898
988
|
// post the flow definition to the server
|
|
899
|
-
getStore()
|
|
900
|
-
.postJSON(`/flow/revisions/${this.flow}/`,
|
|
989
|
+
return getStore()
|
|
990
|
+
.postJSON(`/flow/revisions/${this.flow}/`, definition)
|
|
901
991
|
.then((response) => {
|
|
902
992
|
// Update flow info and revision with the response data
|
|
903
993
|
if (response.json) {
|
|
@@ -910,6 +1000,11 @@ export class Editor extends RapidElement {
|
|
|
910
1000
|
if (response.json.revision?.revision !== undefined) {
|
|
911
1001
|
state.setRevision(response.json.revision.revision);
|
|
912
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
// if the revisions window is open, refresh the list
|
|
1005
|
+
if (!this.revisionsWindowHidden) {
|
|
1006
|
+
this.fetchRevisions();
|
|
1007
|
+
}
|
|
913
1008
|
}
|
|
914
1009
|
})
|
|
915
1010
|
.catch((error) => {
|
|
@@ -960,7 +1055,7 @@ export class Editor extends RapidElement {
|
|
|
960
1055
|
}
|
|
961
1056
|
|
|
962
1057
|
this.activityTimer = window.setTimeout(() => {
|
|
963
|
-
this.fetchActivityData();
|
|
1058
|
+
// this.fetchActivityData();
|
|
964
1059
|
}, this.activityInterval);
|
|
965
1060
|
});
|
|
966
1061
|
}
|
|
@@ -1072,6 +1167,8 @@ export class Editor extends RapidElement {
|
|
|
1072
1167
|
// ignore right clicks
|
|
1073
1168
|
if (event.button !== 0) return;
|
|
1074
1169
|
|
|
1170
|
+
if (this.isReadOnly()) return;
|
|
1171
|
+
|
|
1075
1172
|
const element = event.currentTarget as HTMLElement;
|
|
1076
1173
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
1077
1174
|
const target = event.target as HTMLElement;
|
|
@@ -1141,6 +1238,8 @@ export class Editor extends RapidElement {
|
|
|
1141
1238
|
}
|
|
1142
1239
|
|
|
1143
1240
|
private handleCanvasMouseDown(event: MouseEvent): void {
|
|
1241
|
+
if (this.isReadOnly()) return;
|
|
1242
|
+
|
|
1144
1243
|
const target = event.target as HTMLElement;
|
|
1145
1244
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
1146
1245
|
// Ignore clicks on exits
|
|
@@ -1214,6 +1313,7 @@ export class Editor extends RapidElement {
|
|
|
1214
1313
|
// Clean up jsPlumb connections for nodes before removing them
|
|
1215
1314
|
uuids.forEach((uuid) => {
|
|
1216
1315
|
this.plumber.removeNodeConnections(uuid);
|
|
1316
|
+
this.plumber.removeAllEndpoints(uuid);
|
|
1217
1317
|
});
|
|
1218
1318
|
|
|
1219
1319
|
// Now remove them from the definition
|
|
@@ -1734,6 +1834,11 @@ export class Editor extends RapidElement {
|
|
|
1734
1834
|
}
|
|
1735
1835
|
|
|
1736
1836
|
private handleCanvasContextMenu(event: MouseEvent): void {
|
|
1837
|
+
if (this.isReadOnly()) {
|
|
1838
|
+
event.preventDefault();
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1737
1842
|
// Check if we right-clicked on empty canvas space
|
|
1738
1843
|
const target = event.target as HTMLElement;
|
|
1739
1844
|
if (target.id !== 'canvas') {
|
|
@@ -2653,6 +2758,7 @@ export class Editor extends RapidElement {
|
|
|
2653
2758
|
}
|
|
2654
2759
|
|
|
2655
2760
|
this.localizationWindowHidden = false;
|
|
2761
|
+
this.revisionsWindowHidden = true;
|
|
2656
2762
|
|
|
2657
2763
|
const alreadySelected = languages.some(
|
|
2658
2764
|
(lang) => lang.code === this.languageCode
|
|
@@ -2915,6 +3021,223 @@ export class Editor extends RapidElement {
|
|
|
2915
3021
|
this.autoTranslating = false;
|
|
2916
3022
|
}
|
|
2917
3023
|
|
|
3024
|
+
private handleRevisionsTabClick(): void {
|
|
3025
|
+
if (this.revisionsWindowHidden) {
|
|
3026
|
+
this.fetchRevisions();
|
|
3027
|
+
this.revisionsWindowHidden = false;
|
|
3028
|
+
this.localizationWindowHidden = true; // Close other window
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
private handleRevisionsWindowClosed(): void {
|
|
3033
|
+
this.resetRevisionsScroll();
|
|
3034
|
+
this.revisionsWindowHidden = true;
|
|
3035
|
+
if (this.viewingRevision) {
|
|
3036
|
+
this.handleCancelRevisionView();
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
private resetRevisionsScroll() {
|
|
3041
|
+
const list =
|
|
3042
|
+
this.querySelector('#revisions-window').shadowRoot?.querySelector(
|
|
3043
|
+
'.body'
|
|
3044
|
+
);
|
|
3045
|
+
if (list) {
|
|
3046
|
+
list.scrollTop = 0;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
private async fetchRevisions() {
|
|
3051
|
+
this.isLoadingRevisions = true;
|
|
3052
|
+
try {
|
|
3053
|
+
const results = await fetchResults(
|
|
3054
|
+
`/flow/revisions/${this.flow}/?version=${FLOW_SPEC_VERSION}`
|
|
3055
|
+
);
|
|
3056
|
+
this.revisions = results.slice(1);
|
|
3057
|
+
} catch (e) {
|
|
3058
|
+
console.error('Error fetching revisions', e);
|
|
3059
|
+
} finally {
|
|
3060
|
+
this.isLoadingRevisions = false;
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
private async handleRevisionClick(revision: Revision) {
|
|
3065
|
+
if (this.viewingRevision?.id === revision.id) {
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
if (!this.viewingRevision) {
|
|
3070
|
+
// Save current state first
|
|
3071
|
+
this.preRevertState = {
|
|
3072
|
+
definition: this.definition,
|
|
3073
|
+
dirtyDate: this.dirtyDate
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
this.viewingRevision = revision;
|
|
3078
|
+
this.isLoadingRevisions = true;
|
|
3079
|
+
this.plumber?.reset();
|
|
3080
|
+
|
|
3081
|
+
try {
|
|
3082
|
+
await getStore()
|
|
3083
|
+
.getState()
|
|
3084
|
+
.fetchRevision(`/flow/revisions/${this.flow}`, revision.id.toString());
|
|
3085
|
+
} catch (e) {
|
|
3086
|
+
console.error('Error fetching revision details', e);
|
|
3087
|
+
this.handleCancelRevisionView();
|
|
3088
|
+
} finally {
|
|
3089
|
+
this.isLoadingRevisions = false;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
private handleCancelRevisionView() {
|
|
3094
|
+
this.plumber?.reset();
|
|
3095
|
+
if (this.preRevertState) {
|
|
3096
|
+
const currentInfo = getStore().getState().flowInfo;
|
|
3097
|
+
getStore().getState().setFlowContents({
|
|
3098
|
+
definition: this.preRevertState.definition,
|
|
3099
|
+
info: currentInfo
|
|
3100
|
+
});
|
|
3101
|
+
if (this.preRevertState.dirtyDate) {
|
|
3102
|
+
getStore().getState().setDirtyDate(this.preRevertState.dirtyDate);
|
|
3103
|
+
}
|
|
3104
|
+
} else {
|
|
3105
|
+
// Fallback if no pre-revert definition
|
|
3106
|
+
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
this.viewingRevision = null;
|
|
3110
|
+
this.preRevertState = null;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
private async handleRevertClick() {
|
|
3114
|
+
if (!this.viewingRevision || !this.preRevertState) return;
|
|
3115
|
+
this.plumber?.reset();
|
|
3116
|
+
|
|
3117
|
+
// Use the content of the viewing revision (this.definition)
|
|
3118
|
+
// but the revision number of the current head (preRevertState)
|
|
3119
|
+
// so the server accepts it as a valid update
|
|
3120
|
+
const definitionToSave = {
|
|
3121
|
+
...this.definition,
|
|
3122
|
+
revision: this.preRevertState.definition.revision
|
|
3123
|
+
};
|
|
3124
|
+
|
|
3125
|
+
await this.saveChanges(definitionToSave);
|
|
3126
|
+
this.viewingRevision = null;
|
|
3127
|
+
this.preRevertState = null;
|
|
3128
|
+
this.revisionsWindowHidden = true;
|
|
3129
|
+
|
|
3130
|
+
const revisionsWindow = document.getElementById(
|
|
3131
|
+
'revisions-window'
|
|
3132
|
+
) as FloatingWindow;
|
|
3133
|
+
revisionsWindow.handleClose();
|
|
3134
|
+
|
|
3135
|
+
// Refresh revisions list to show the new one
|
|
3136
|
+
this.fetchRevisions();
|
|
3137
|
+
|
|
3138
|
+
// Fetch the latest version of the flow to ensure the store is up to date
|
|
3139
|
+
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
private renderRevisionsTab(): TemplateResult | string {
|
|
3143
|
+
return html`
|
|
3144
|
+
<temba-floating-tab
|
|
3145
|
+
id="revisions-tab"
|
|
3146
|
+
icon="revisions"
|
|
3147
|
+
label="Revisions"
|
|
3148
|
+
color="rgb(142, 94, 167)"
|
|
3149
|
+
top="105"
|
|
3150
|
+
.hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
|
|
3151
|
+
@temba-button-clicked=${this.handleRevisionsTabClick}
|
|
3152
|
+
></temba-floating-tab>
|
|
3153
|
+
`;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
private renderRevisionsWindow(): TemplateResult | string {
|
|
3157
|
+
return html`
|
|
3158
|
+
<temba-floating-window
|
|
3159
|
+
id="revisions-window"
|
|
3160
|
+
header="Revisions"
|
|
3161
|
+
.width=${360}
|
|
3162
|
+
.maxHeight=${600}
|
|
3163
|
+
.top=${75}
|
|
3164
|
+
color="rgb(142, 94, 167)"
|
|
3165
|
+
.hidden=${this.revisionsWindowHidden}
|
|
3166
|
+
@temba-dialog-hidden=${this.handleRevisionsWindowClosed}
|
|
3167
|
+
>
|
|
3168
|
+
<div class="localization-window-content">
|
|
3169
|
+
<div
|
|
3170
|
+
class="revisions-list"
|
|
3171
|
+
style="display:flex; flex-direction:column; gap:8px; overflow-y:auto; padding-bottom:10px;"
|
|
3172
|
+
>
|
|
3173
|
+
${this.isLoadingRevisions && !this.revisions.length
|
|
3174
|
+
? html`<temba-loading></temba-loading>`
|
|
3175
|
+
: this.revisions.map((rev) => {
|
|
3176
|
+
const isSelected = this.viewingRevision?.id === rev.id;
|
|
3177
|
+
return html`
|
|
3178
|
+
<div
|
|
3179
|
+
class="revision-item ${isSelected ? 'selected' : ''}"
|
|
3180
|
+
style="padding:8px; border-radius:4px; cursor:pointer; background:${
|
|
3181
|
+
isSelected
|
|
3182
|
+
? '#f0f6ff' // Light blue bg for selected
|
|
3183
|
+
: '#f9fafb'
|
|
3184
|
+
}; border:1px solid ${
|
|
3185
|
+
isSelected ? '#a4cafe' : '#e5e7eb'
|
|
3186
|
+
}; transition: all 0.2s ease;"
|
|
3187
|
+
@click=${() => this.handleRevisionClick(rev)}
|
|
3188
|
+
>
|
|
3189
|
+
<div
|
|
3190
|
+
style="display:flex; justify-content:space-between; align-items:center;"
|
|
3191
|
+
>
|
|
3192
|
+
<div
|
|
3193
|
+
class="revision-header"
|
|
3194
|
+
style="margin-bottom: 2px;"
|
|
3195
|
+
>
|
|
3196
|
+
<div
|
|
3197
|
+
style="font-weight:600; font-size:13px; color:#111827;"
|
|
3198
|
+
>
|
|
3199
|
+
<temba-date value=${
|
|
3200
|
+
rev.created_on
|
|
3201
|
+
} display="duration"></temba-date>
|
|
3202
|
+
|
|
3203
|
+
</div>
|
|
3204
|
+
<div style="font-size:11px; color:#6b7280;">
|
|
3205
|
+
${rev.user.name || rev.user.username}
|
|
3206
|
+
</div>
|
|
3207
|
+
</div>
|
|
3208
|
+
${
|
|
3209
|
+
isSelected
|
|
3210
|
+
? html`<button
|
|
3211
|
+
class="revert-button"
|
|
3212
|
+
@click=${this.handleRevertClick}
|
|
3213
|
+
>
|
|
3214
|
+
Revert
|
|
3215
|
+
</button>`
|
|
3216
|
+
: html``
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
</button>
|
|
3220
|
+
</div>
|
|
3221
|
+
|
|
3222
|
+
${
|
|
3223
|
+
rev.comment
|
|
3224
|
+
? html`<div
|
|
3225
|
+
style="font-size:12px; color:#4b5563; margin-top:4px;"
|
|
3226
|
+
>
|
|
3227
|
+
${rev.comment}
|
|
3228
|
+
</div>`
|
|
3229
|
+
: ''
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
</div>
|
|
3233
|
+
`;
|
|
3234
|
+
})}
|
|
3235
|
+
</div>
|
|
3236
|
+
</div>
|
|
3237
|
+
</temba-floating-window>
|
|
3238
|
+
`;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
2918
3241
|
private renderLocalizationWindow(): TemplateResult | string {
|
|
2919
3242
|
const languages = this.getLocalizationLanguages();
|
|
2920
3243
|
if (!languages.length) {
|
|
@@ -3166,6 +3489,10 @@ export class Editor extends RapidElement {
|
|
|
3166
3489
|
});
|
|
3167
3490
|
}
|
|
3168
3491
|
|
|
3492
|
+
private isReadOnly(): boolean {
|
|
3493
|
+
return this.viewingRevision !== null || this.isTranslating;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3169
3496
|
public render(): TemplateResult {
|
|
3170
3497
|
// we have to embed our own style since we are in light DOM
|
|
3171
3498
|
const style = html`<style>
|
|
@@ -3175,20 +3502,30 @@ export class Editor extends RapidElement {
|
|
|
3175
3502
|
|
|
3176
3503
|
const stickies = this.definition?._ui?.stickies || {};
|
|
3177
3504
|
|
|
3178
|
-
return html`${style} ${this.
|
|
3179
|
-
${this.renderAutoTranslateDialog()}
|
|
3505
|
+
return html`${style} ${this.renderRevisionsWindow()}
|
|
3506
|
+
${this.renderLocalizationWindow()} ${this.renderAutoTranslateDialog()}
|
|
3180
3507
|
<div id="editor">
|
|
3181
3508
|
<div
|
|
3182
3509
|
id="grid"
|
|
3510
|
+
class="${this.viewingRevision ? 'viewing-revision' : ''}"
|
|
3183
3511
|
style="min-width:100%;width:${this.canvasSize.width}px; height:${this
|
|
3184
3512
|
.canvasSize.height}px"
|
|
3185
3513
|
>
|
|
3186
|
-
<div
|
|
3514
|
+
<div
|
|
3515
|
+
id="canvas"
|
|
3516
|
+
class="${getClasses({
|
|
3517
|
+
'viewing-revision': !!this.viewingRevision,
|
|
3518
|
+
'read-only-connections':
|
|
3519
|
+
!!this.viewingRevision || this.isTranslating
|
|
3520
|
+
})}"
|
|
3521
|
+
>
|
|
3187
3522
|
${this.definition
|
|
3188
3523
|
? repeat(
|
|
3189
|
-
this.definition.nodes,
|
|
3524
|
+
[...this.definition.nodes].sort((a, b) =>
|
|
3525
|
+
a.uuid.localeCompare(b.uuid)
|
|
3526
|
+
),
|
|
3190
3527
|
(node) => node.uuid,
|
|
3191
|
-
(node
|
|
3528
|
+
(node) => {
|
|
3192
3529
|
const position = this.definition._ui?.nodes[node.uuid]
|
|
3193
3530
|
?.position || {
|
|
3194
3531
|
left: 0,
|
|
@@ -3202,7 +3539,9 @@ export class Editor extends RapidElement {
|
|
|
3202
3539
|
const selected = this.selectedItems.has(node.uuid);
|
|
3203
3540
|
|
|
3204
3541
|
// first node is the flow start (nodes are sorted by position)
|
|
3205
|
-
const isFlowStart =
|
|
3542
|
+
const isFlowStart =
|
|
3543
|
+
this.definition.nodes.length > 0 &&
|
|
3544
|
+
this.definition.nodes[0].uuid === node.uuid;
|
|
3206
3545
|
|
|
3207
3546
|
return html`<temba-flow-node
|
|
3208
3547
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
@@ -3262,10 +3601,12 @@ export class Editor extends RapidElement {
|
|
|
3262
3601
|
: ''}
|
|
3263
3602
|
|
|
3264
3603
|
<temba-canvas-menu></temba-canvas-menu>
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3604
|
+
${!this.viewingRevision
|
|
3605
|
+
? html`<temba-node-type-selector
|
|
3606
|
+
.flowType=${this.flowType}
|
|
3607
|
+
.features=${this.features}
|
|
3608
|
+
></temba-node-type-selector>`
|
|
3609
|
+
: ''}
|
|
3610
|
+
${this.renderRevisionsTab()} ${this.renderLocalizationTab()} `;
|
|
3270
3611
|
}
|
|
3271
3612
|
}
|
package/src/flow/NodeEditor.ts
CHANGED