@nyaruka/temba-components 0.156.2 → 0.156.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.
- package/CHANGELOG.md +21 -0
- package/dist/temba-components.js +560 -391
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Chat.ts +19 -17
- package/src/events/eventRenderers.ts +6 -1
- package/src/flow/CanvasNode.ts +69 -2
- package/src/flow/Editor.ts +260 -510
- package/src/flow/EditorToolbar.ts +566 -0
- package/src/flow/FlowSearch.ts +42 -10
- package/src/flow/MessageTable.ts +332 -122
- package/src/flow/NodeEditor.ts +29 -4
- package/src/flow/StickyNote.ts +29 -5
- package/src/flow/actions/say_msg.ts +1 -0
- package/src/flow/actions/send_broadcast.ts +48 -0
- package/src/flow/actions/send_email.ts +42 -1
- package/src/flow/actions/send_msg.ts +1 -0
- package/src/flow/actions/set_contact_language.ts +4 -13
- package/src/flow/actions/set_run_result.ts +1 -0
- package/src/flow/nodes/shared-rules.ts +1 -0
- package/src/flow/nodes/shared.ts +1 -0
- package/src/flow/nodes/wait_for_audio.ts +1 -0
- package/src/flow/utils.ts +11 -0
- package/src/simulator/Simulator.ts +17 -12
- package/temba-modules.ts +2 -0
- package/web-test-runner.config.mjs +2 -0
package/src/flow/Editor.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { TEMBA_COMPONENTS_VERSION } from '../version';
|
|
32
32
|
import {
|
|
33
33
|
formatIssueMessage,
|
|
34
|
+
getLanguageDisplayName,
|
|
34
35
|
getNodeBounds,
|
|
35
36
|
calculateReflowPositions,
|
|
36
37
|
isRightClick,
|
|
@@ -70,8 +71,8 @@ import { Dialog } from '../layout/Dialog';
|
|
|
70
71
|
import { CanvasMenu, CanvasMenuSelection } from './CanvasMenu';
|
|
71
72
|
import { NodeTypeSelector, NodeTypeSelection } from './NodeTypeSelector';
|
|
72
73
|
import { FloatingWindow } from '../layout/FloatingWindow';
|
|
73
|
-
import { Icon } from '../Icons';
|
|
74
74
|
import { FlowSearch, SearchResult } from './FlowSearch';
|
|
75
|
+
import { PRIMARY_LANGUAGE_OPTION_VALUE } from './EditorToolbar';
|
|
75
76
|
|
|
76
77
|
export function findNodeForExit(
|
|
77
78
|
definition: FlowDefinition,
|
|
@@ -134,7 +135,15 @@ interface LocalizationUpdate {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
|
|
137
|
-
|
|
138
|
+
export type ToolbarAction =
|
|
139
|
+
| { action: 'view-change'; view: 'flow' | 'table' }
|
|
140
|
+
| { action: 'zoom-in' }
|
|
141
|
+
| { action: 'zoom-out' }
|
|
142
|
+
| { action: 'zoom-to-fit' }
|
|
143
|
+
| { action: 'zoom-to-full' }
|
|
144
|
+
| { action: 'revisions' }
|
|
145
|
+
| { action: 'search' }
|
|
146
|
+
| { action: 'language-change'; isPrimary?: boolean; languageCode?: string };
|
|
138
147
|
const EMPTY_FLOW_ISSUES: FlowIssue[] = [];
|
|
139
148
|
|
|
140
149
|
// How long the pending-changes auto-save countdown runs (in ms).
|
|
@@ -268,6 +277,13 @@ export class Editor extends RapidElement {
|
|
|
268
277
|
private currentDragIsCopy = false;
|
|
269
278
|
private dragStartPos = { x: 0, y: 0 };
|
|
270
279
|
|
|
280
|
+
// Mid-drag shift toggle: remember originals so we can switch between move/copy
|
|
281
|
+
private originalDragItem: DraggableItem | null = null;
|
|
282
|
+
private originalSelectedItems: Set<string> | null = null;
|
|
283
|
+
|
|
284
|
+
// Drag hint tooltip
|
|
285
|
+
private dragHintTimer: ReturnType<typeof setTimeout> | null = null;
|
|
286
|
+
|
|
271
287
|
// Public getter for drag state
|
|
272
288
|
public get dragging(): boolean {
|
|
273
289
|
return this.isDragging;
|
|
@@ -458,9 +474,6 @@ export class Editor extends RapidElement {
|
|
|
458
474
|
@property({ type: Boolean, reflect: true, attribute: 'message-view' })
|
|
459
475
|
showMessageTable = false;
|
|
460
476
|
|
|
461
|
-
@state()
|
|
462
|
-
private showLanguageOptions = false;
|
|
463
|
-
|
|
464
477
|
@state()
|
|
465
478
|
private isCreatingNewNode = false;
|
|
466
479
|
|
|
@@ -506,16 +519,8 @@ export class Editor extends RapidElement {
|
|
|
506
519
|
private getAvailableLanguages(): Array<{ code: string; name: string }> {
|
|
507
520
|
// Use languages from workspace if available
|
|
508
521
|
if (this.workspace?.languages && this.workspace.languages.length > 0) {
|
|
509
|
-
const languageNames = new Intl.DisplayNames(['en'], { type: 'language' });
|
|
510
522
|
return this.workspace.languages
|
|
511
|
-
.map((code) => {
|
|
512
|
-
try {
|
|
513
|
-
const name = languageNames.of(code);
|
|
514
|
-
return name ? { code, name } : { code, name: code };
|
|
515
|
-
} catch {
|
|
516
|
-
return { code, name: code };
|
|
517
|
-
}
|
|
518
|
-
})
|
|
523
|
+
.map((code) => ({ code, name: getLanguageDisplayName(code) }))
|
|
519
524
|
.filter((lang) => lang.code && lang.name);
|
|
520
525
|
}
|
|
521
526
|
|
|
@@ -539,6 +544,8 @@ export class Editor extends RapidElement {
|
|
|
539
544
|
private boundMouseUp = this.handleMouseUp.bind(this);
|
|
540
545
|
private boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
|
|
541
546
|
private boundKeyDown = this.handleKeyDown.bind(this);
|
|
547
|
+
private boundKeyUp = this.handleKeyUp.bind(this);
|
|
548
|
+
private boundWindowBlur = this.handleWindowBlur.bind(this);
|
|
542
549
|
private boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
|
|
543
550
|
private boundWheel = this.handleWheel.bind(this);
|
|
544
551
|
private boundTouchMove = this.handleTouchMove.bind(this);
|
|
@@ -557,188 +564,6 @@ export class Editor extends RapidElement {
|
|
|
557
564
|
min-height: 0;
|
|
558
565
|
}
|
|
559
566
|
|
|
560
|
-
.editor-toolbar {
|
|
561
|
-
--toolbar-control-height: 28px;
|
|
562
|
-
--toolbar-translation-control-height: 28px;
|
|
563
|
-
display: flex;
|
|
564
|
-
align-items: center;
|
|
565
|
-
padding: 6px 12px;
|
|
566
|
-
background: #fff;
|
|
567
|
-
border-bottom: 1px solid #e8e8e8;
|
|
568
|
-
flex-shrink: 0;
|
|
569
|
-
gap: 8px;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
.toolbar-left {
|
|
573
|
-
display: flex;
|
|
574
|
-
align-items: center;
|
|
575
|
-
gap: 2px;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.toolbar-right {
|
|
579
|
-
display: flex;
|
|
580
|
-
align-items: center;
|
|
581
|
-
gap: 2px;
|
|
582
|
-
margin-left: auto;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
.toolbar-btn {
|
|
586
|
-
width: var(--toolbar-control-height);
|
|
587
|
-
height: var(--toolbar-control-height);
|
|
588
|
-
border: none;
|
|
589
|
-
background: transparent;
|
|
590
|
-
border-radius: var(--curvature);
|
|
591
|
-
cursor: pointer;
|
|
592
|
-
display: flex;
|
|
593
|
-
align-items: center;
|
|
594
|
-
justify-content: center;
|
|
595
|
-
padding: 0;
|
|
596
|
-
color: #888;
|
|
597
|
-
font-size: 16px;
|
|
598
|
-
line-height: 1;
|
|
599
|
-
outline: none;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.toolbar-btn:focus {
|
|
603
|
-
outline: none;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
.toolbar-btn:focus-visible {
|
|
607
|
-
outline: 2px solid #0064c8;
|
|
608
|
-
outline-offset: 2px;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
.toolbar-btn:hover {
|
|
612
|
-
background: rgba(0, 0, 0, 0.06);
|
|
613
|
-
color: #555;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
.toolbar-btn:disabled {
|
|
617
|
-
opacity: 0.3;
|
|
618
|
-
cursor: default;
|
|
619
|
-
background: transparent;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
.toolbar-btn.active {
|
|
623
|
-
background: rgba(0, 100, 200, 0.1);
|
|
624
|
-
color: #0064c8;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
.toolbar-btn.active:hover {
|
|
628
|
-
background: rgba(0, 100, 200, 0.15);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
.toolbar-tip {
|
|
632
|
-
display: flex;
|
|
633
|
-
align-items: center;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
.toolbar-divider {
|
|
637
|
-
width: 1px;
|
|
638
|
-
height: 16px;
|
|
639
|
-
background: #e0e0e0;
|
|
640
|
-
margin: 0 4px;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
.toolbar-group {
|
|
644
|
-
display: flex;
|
|
645
|
-
align-items: center;
|
|
646
|
-
gap: 4px;
|
|
647
|
-
height: var(--toolbar-control-height);
|
|
648
|
-
box-sizing: border-box;
|
|
649
|
-
padding: 0 3px;
|
|
650
|
-
border: 1px solid #d7dce2;
|
|
651
|
-
border-radius: calc(var(--curvature) + 2px);
|
|
652
|
-
background: #f7f9fb;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
.toolbar-group-divider {
|
|
656
|
-
width: 1px;
|
|
657
|
-
height: 18px;
|
|
658
|
-
background: #d7dce2;
|
|
659
|
-
margin: 0 2px;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
.toolbar-language {
|
|
663
|
-
position: relative;
|
|
664
|
-
display: flex;
|
|
665
|
-
align-items: center;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
.toolbar-language-group {
|
|
669
|
-
display: flex;
|
|
670
|
-
align-items: center;
|
|
671
|
-
gap: 6px;
|
|
672
|
-
margin-left: 2px;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
.toolbar-zoom-group {
|
|
676
|
-
gap: 2px;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
.language-pill {
|
|
680
|
-
display: flex;
|
|
681
|
-
align-items: center;
|
|
682
|
-
gap: 6px;
|
|
683
|
-
background: #e9eef4;
|
|
684
|
-
color: #0064c8;
|
|
685
|
-
height: var(--toolbar-translation-control-height);
|
|
686
|
-
padding: 0 8px;
|
|
687
|
-
border-radius: var(--curvature);
|
|
688
|
-
box-sizing: border-box;
|
|
689
|
-
font-size: 13px;
|
|
690
|
-
font-weight: 400;
|
|
691
|
-
white-space: nowrap;
|
|
692
|
-
cursor: pointer;
|
|
693
|
-
--icon-color: #0064c8;
|
|
694
|
-
--icon-size: 16px;
|
|
695
|
-
border: none;
|
|
696
|
-
outline: none;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
.language-pill:hover {
|
|
700
|
-
filter: brightness(1.04);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.language-pill.primary {
|
|
704
|
-
background: #fff;
|
|
705
|
-
border: 1px solid #d7dce2;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
.language-pill-caret {
|
|
709
|
-
margin-left: 1px;
|
|
710
|
-
--icon-color: currentColor;
|
|
711
|
-
--icon-size: 12px;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
.language-percent {
|
|
715
|
-
display: inline-block;
|
|
716
|
-
font-size: 12px;
|
|
717
|
-
font-weight: 700;
|
|
718
|
-
line-height: 1;
|
|
719
|
-
color: #0064c8;
|
|
720
|
-
white-space: nowrap;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
.toolbar-zoom-level {
|
|
724
|
-
font-size: 12px;
|
|
725
|
-
min-width: 40px;
|
|
726
|
-
text-align: center;
|
|
727
|
-
color: #555;
|
|
728
|
-
font-weight: 500;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
.toolbar-translation {
|
|
732
|
-
display: flex;
|
|
733
|
-
align-items: center;
|
|
734
|
-
gap: 4px;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.toolbar-btn.language-tool {
|
|
738
|
-
width: var(--toolbar-translation-control-height);
|
|
739
|
-
height: var(--toolbar-translation-control-height);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
567
|
#editor {
|
|
743
568
|
overflow: scroll;
|
|
744
569
|
flex: 1;
|
|
@@ -806,6 +631,10 @@ export class Editor extends RapidElement {
|
|
|
806
631
|
transition: none !important;
|
|
807
632
|
}
|
|
808
633
|
|
|
634
|
+
#canvas.shift-held {
|
|
635
|
+
--shift-held-cursor: copy;
|
|
636
|
+
}
|
|
637
|
+
|
|
809
638
|
#canvas.viewing-revision {
|
|
810
639
|
pointer-events: none;
|
|
811
640
|
}
|
|
@@ -1040,6 +869,10 @@ export class Editor extends RapidElement {
|
|
|
1040
869
|
border-radius: var(--curvature);
|
|
1041
870
|
}
|
|
1042
871
|
|
|
872
|
+
.draggable.selected.drag-copy {
|
|
873
|
+
outline: none;
|
|
874
|
+
}
|
|
875
|
+
|
|
1043
876
|
/* Language banner replaced by toolbar language selector */
|
|
1044
877
|
|
|
1045
878
|
.localization-window-content {
|
|
@@ -1431,6 +1264,36 @@ export class Editor extends RapidElement {
|
|
|
1431
1264
|
opacity: 0.8;
|
|
1432
1265
|
}
|
|
1433
1266
|
|
|
1267
|
+
.drag-hint {
|
|
1268
|
+
position: absolute;
|
|
1269
|
+
bottom: 40px;
|
|
1270
|
+
left: 50%;
|
|
1271
|
+
transform: translateX(-50%);
|
|
1272
|
+
z-index: 100000;
|
|
1273
|
+
pointer-events: none;
|
|
1274
|
+
background: rgba(255, 255, 255, 0.5);
|
|
1275
|
+
backdrop-filter: blur(6px);
|
|
1276
|
+
color: #555;
|
|
1277
|
+
font-size: 18px;
|
|
1278
|
+
font-weight: 300;
|
|
1279
|
+
padding: 10px 32px;
|
|
1280
|
+
border-radius: 10px;
|
|
1281
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
1282
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
1283
|
+
white-space: nowrap;
|
|
1284
|
+
display: none;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.drag-hint.visible {
|
|
1288
|
+
display: block;
|
|
1289
|
+
animation: drag-hint-in 0.15s ease forwards;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
@keyframes drag-hint-in {
|
|
1293
|
+
from { opacity: 0; }
|
|
1294
|
+
to { opacity: 1; }
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1434
1297
|
.reflow-card {
|
|
1435
1298
|
position: absolute;
|
|
1436
1299
|
top: 16px;
|
|
@@ -2054,6 +1917,8 @@ export class Editor extends RapidElement {
|
|
|
2054
1917
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
2055
1918
|
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
2056
1919
|
document.removeEventListener('keydown', this.boundKeyDown);
|
|
1920
|
+
document.removeEventListener('keyup', this.boundKeyUp);
|
|
1921
|
+
window.removeEventListener('blur', this.boundWindowBlur);
|
|
2057
1922
|
document.removeEventListener('touchmove', this.boundTouchMove);
|
|
2058
1923
|
document.removeEventListener('touchend', this.boundTouchEnd);
|
|
2059
1924
|
document.removeEventListener('touchcancel', this.boundTouchCancel);
|
|
@@ -2086,6 +1951,8 @@ export class Editor extends RapidElement {
|
|
|
2086
1951
|
document.addEventListener('mouseup', this.boundMouseUp);
|
|
2087
1952
|
document.addEventListener('mousedown', this.boundGlobalMouseDown);
|
|
2088
1953
|
document.addEventListener('keydown', this.boundKeyDown);
|
|
1954
|
+
document.addEventListener('keyup', this.boundKeyUp);
|
|
1955
|
+
window.addEventListener('blur', this.boundWindowBlur);
|
|
2089
1956
|
document.addEventListener('touchmove', this.boundTouchMove, {
|
|
2090
1957
|
passive: false
|
|
2091
1958
|
});
|
|
@@ -2419,7 +2286,44 @@ export class Editor extends RapidElement {
|
|
|
2419
2286
|
}
|
|
2420
2287
|
}
|
|
2421
2288
|
|
|
2289
|
+
private showDragHint(): void {
|
|
2290
|
+
if (this.isReadOnly()) return;
|
|
2291
|
+
const hint = this.querySelector('#drag-hint') as HTMLElement;
|
|
2292
|
+
if (!hint) return;
|
|
2293
|
+
this.dragHintTimer = setTimeout(() => {
|
|
2294
|
+
hint.classList.add('visible');
|
|
2295
|
+
this.dragHintTimer = null;
|
|
2296
|
+
}, 600);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
private hideDragHint(): void {
|
|
2300
|
+
if (this.dragHintTimer) {
|
|
2301
|
+
clearTimeout(this.dragHintTimer);
|
|
2302
|
+
this.dragHintTimer = null;
|
|
2303
|
+
}
|
|
2304
|
+
const hint = this.querySelector('#drag-hint') as HTMLElement;
|
|
2305
|
+
if (hint) {
|
|
2306
|
+
hint.classList.remove('visible');
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2422
2310
|
private handleKeyDown(event: KeyboardEvent): void {
|
|
2311
|
+
if (event.key === 'Shift') {
|
|
2312
|
+
this.querySelector('#canvas')?.classList.add('shift-held');
|
|
2313
|
+
|
|
2314
|
+
// Toggle to copy mode mid-drag
|
|
2315
|
+
if (this.isDragging && !this.currentDragIsCopy) {
|
|
2316
|
+
this.hideDragHint();
|
|
2317
|
+
this.performShiftDragCopy();
|
|
2318
|
+
// Clone elements aren't in the DOM until Lit re-renders;
|
|
2319
|
+
// schedule position update after the next frame.
|
|
2320
|
+
requestAnimationFrame(() => {
|
|
2321
|
+
this.markCopyElements();
|
|
2322
|
+
this.updateDragPositions();
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2423
2327
|
// Cmd/Ctrl+F opens flow search (unless a dialog is already open)
|
|
2424
2328
|
if ((event.metaKey || event.ctrlKey) && event.key === 'f') {
|
|
2425
2329
|
event.preventDefault();
|
|
@@ -2449,6 +2353,28 @@ export class Editor extends RapidElement {
|
|
|
2449
2353
|
}
|
|
2450
2354
|
}
|
|
2451
2355
|
|
|
2356
|
+
private handleKeyUp(event: KeyboardEvent): void {
|
|
2357
|
+
if (event.key === 'Shift') {
|
|
2358
|
+
this.querySelector('#canvas')?.classList.remove('shift-held');
|
|
2359
|
+
|
|
2360
|
+
// Toggle back to move mode mid-drag
|
|
2361
|
+
if (this.isDragging && this.currentDragIsCopy) {
|
|
2362
|
+
this.revertShiftDragCopy();
|
|
2363
|
+
requestAnimationFrame(() => this.updateDragPositions());
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
private handleWindowBlur(): void {
|
|
2369
|
+
this.querySelector('#canvas')?.classList.remove('shift-held');
|
|
2370
|
+
|
|
2371
|
+
// Revert copy mode if blur happens mid-drag (keyup may never fire)
|
|
2372
|
+
if (this.isDragging && this.currentDragIsCopy) {
|
|
2373
|
+
this.revertShiftDragCopy();
|
|
2374
|
+
requestAnimationFrame(() => this.updateDragPositions());
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2452
2378
|
// --- Flow settings cookie (LRU, max 50 flows) ---
|
|
2453
2379
|
|
|
2454
2380
|
static MAX_FLOW_SETTINGS = 50;
|
|
@@ -3673,9 +3599,15 @@ export class Editor extends RapidElement {
|
|
|
3673
3599
|
this.isDragging = true;
|
|
3674
3600
|
this.startAutoScroll();
|
|
3675
3601
|
|
|
3676
|
-
|
|
3602
|
+
// Snapshot the original drag context before any copy occurs
|
|
3603
|
+
this.originalDragItem = { ...this.currentDragItem };
|
|
3604
|
+
this.originalSelectedItems = new Set(this.selectedItems);
|
|
3605
|
+
|
|
3606
|
+
if (this.shiftDragCopy || event.shiftKey) {
|
|
3677
3607
|
this.performShiftDragCopy();
|
|
3678
3608
|
this.shiftDragCopy = false;
|
|
3609
|
+
} else {
|
|
3610
|
+
this.showDragHint();
|
|
3679
3611
|
}
|
|
3680
3612
|
}
|
|
3681
3613
|
|
|
@@ -3686,14 +3618,14 @@ export class Editor extends RapidElement {
|
|
|
3686
3618
|
}
|
|
3687
3619
|
|
|
3688
3620
|
private performShiftDragCopy(): void {
|
|
3689
|
-
if (!this.
|
|
3621
|
+
if (!this.originalDragItem) return;
|
|
3690
3622
|
|
|
3691
|
-
//
|
|
3623
|
+
// Always use the original items as the source for copying
|
|
3692
3624
|
const itemsToCopy =
|
|
3693
|
-
this.
|
|
3694
|
-
this.
|
|
3695
|
-
? Array.from(this.
|
|
3696
|
-
: [this.
|
|
3625
|
+
this.originalSelectedItems?.has(this.originalDragItem.uuid) &&
|
|
3626
|
+
(this.originalSelectedItems?.size ?? 0) > 1
|
|
3627
|
+
? Array.from(this.originalSelectedItems!)
|
|
3628
|
+
: [this.originalDragItem.uuid];
|
|
3697
3629
|
|
|
3698
3630
|
if (itemsToCopy.length === 0) return;
|
|
3699
3631
|
|
|
@@ -3709,19 +3641,44 @@ export class Editor extends RapidElement {
|
|
|
3709
3641
|
}
|
|
3710
3642
|
this.currentDragIsCopy = true;
|
|
3711
3643
|
|
|
3644
|
+
// Snap original items back to their start positions.
|
|
3645
|
+
// Set position while 'dragging' class is still applied so
|
|
3646
|
+
// transitions are disabled and the move is instant.
|
|
3647
|
+
for (const uuid of itemsToCopy) {
|
|
3648
|
+
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
3649
|
+
const type =
|
|
3650
|
+
element?.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';
|
|
3651
|
+
const position = this.getPosition(uuid, type);
|
|
3652
|
+
if (element && position) {
|
|
3653
|
+
element.style.left = `${position.left}px`;
|
|
3654
|
+
element.style.top = `${position.top}px`;
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
this.plumber.revalidate(itemsToCopy);
|
|
3658
|
+
// Force layout so the position is committed with transitions
|
|
3659
|
+
// disabled, then remove the dragging class.
|
|
3660
|
+
for (const uuid of itemsToCopy) {
|
|
3661
|
+
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
3662
|
+
if (element) {
|
|
3663
|
+
// Reading offsetHeight forces a synchronous layout
|
|
3664
|
+
void element.offsetHeight;
|
|
3665
|
+
element.classList.remove('dragging');
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3712
3669
|
// Update drag item to reference the copy
|
|
3713
|
-
const newDragUuid = uuidMapping[this.
|
|
3670
|
+
const newDragUuid = uuidMapping[this.originalDragItem.uuid];
|
|
3714
3671
|
if (newDragUuid) {
|
|
3715
3672
|
this.currentDragItem = {
|
|
3716
|
-
...this.
|
|
3673
|
+
...this.originalDragItem,
|
|
3717
3674
|
uuid: newDragUuid
|
|
3718
3675
|
};
|
|
3719
3676
|
}
|
|
3720
3677
|
|
|
3721
3678
|
// Update selected items to reference copies
|
|
3722
|
-
if (this.
|
|
3679
|
+
if ((this.originalSelectedItems?.size ?? 0) > 1) {
|
|
3723
3680
|
const newSelectedItems = new Set<string>();
|
|
3724
|
-
for (const uuid of this.
|
|
3681
|
+
for (const uuid of this.originalSelectedItems!) {
|
|
3725
3682
|
const newUuid = uuidMapping[uuid];
|
|
3726
3683
|
newSelectedItems.add(newUuid || uuid);
|
|
3727
3684
|
}
|
|
@@ -3729,6 +3686,43 @@ export class Editor extends RapidElement {
|
|
|
3729
3686
|
}
|
|
3730
3687
|
}
|
|
3731
3688
|
|
|
3689
|
+
private markCopyElements(): void {
|
|
3690
|
+
for (const uuid of this.copiedItemUuids) {
|
|
3691
|
+
const el = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
3692
|
+
el?.classList.add('drag-copy');
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
private revertShiftDragCopy(): void {
|
|
3697
|
+
if (!this.originalDragItem) return;
|
|
3698
|
+
|
|
3699
|
+
// Remove the cloned items
|
|
3700
|
+
if (this.copiedItemUuids.length > 0) {
|
|
3701
|
+
const nodeUuids = this.copiedItemUuids.filter((uuid) =>
|
|
3702
|
+
this.definition.nodes.some((n) => n.uuid === uuid)
|
|
3703
|
+
);
|
|
3704
|
+
const stickyUuids = this.copiedItemUuids.filter(
|
|
3705
|
+
(uuid) => this.definition._ui?.stickies?.[uuid]
|
|
3706
|
+
);
|
|
3707
|
+
|
|
3708
|
+
if (nodeUuids.length > 0) {
|
|
3709
|
+
getStore().getState().removeNodes(nodeUuids);
|
|
3710
|
+
}
|
|
3711
|
+
if (stickyUuids.length > 0) {
|
|
3712
|
+
getStore().getState().removeStickyNotes(stickyUuids);
|
|
3713
|
+
}
|
|
3714
|
+
this.copiedItemUuids = [];
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
this.currentDragIsCopy = false;
|
|
3718
|
+
|
|
3719
|
+
// Restore drag context to originals
|
|
3720
|
+
this.currentDragItem = { ...this.originalDragItem };
|
|
3721
|
+
if (this.originalSelectedItems) {
|
|
3722
|
+
this.selectedItems = new Set(this.originalSelectedItems);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3732
3726
|
private updateDragPositions(): void {
|
|
3733
3727
|
if (!this.currentDragItem || !this.lastPointerPos) return;
|
|
3734
3728
|
|
|
@@ -3760,11 +3754,15 @@ export class Editor extends RapidElement {
|
|
|
3760
3754
|
element.style.left = `${position.left + deltaX}px`;
|
|
3761
3755
|
element.style.top = `${position.top + deltaY}px`;
|
|
3762
3756
|
element.classList.add('dragging');
|
|
3757
|
+
if (this.currentDragIsCopy) {
|
|
3758
|
+
element.classList.add('drag-copy');
|
|
3759
|
+
}
|
|
3763
3760
|
}
|
|
3764
3761
|
}
|
|
3765
3762
|
});
|
|
3766
3763
|
|
|
3767
3764
|
this.plumber.revalidate(itemsToMove);
|
|
3765
|
+
|
|
3768
3766
|
}
|
|
3769
3767
|
|
|
3770
3768
|
private startAutoScroll(): void {
|
|
@@ -3913,10 +3911,10 @@ export class Editor extends RapidElement {
|
|
|
3913
3911
|
const newPosition = { left: snappedLeft, top: snappedTop };
|
|
3914
3912
|
newPositions[uuid] = newPosition;
|
|
3915
3913
|
|
|
3916
|
-
// Remove dragging
|
|
3914
|
+
// Remove dragging/copy classes
|
|
3917
3915
|
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
3918
3916
|
if (element) {
|
|
3919
|
-
element.classList.remove('dragging');
|
|
3917
|
+
element.classList.remove('dragging', 'drag-copy');
|
|
3920
3918
|
element.style.left = `${snappedLeft}px`;
|
|
3921
3919
|
element.style.top = `${snappedTop}px`;
|
|
3922
3920
|
}
|
|
@@ -3950,11 +3948,14 @@ export class Editor extends RapidElement {
|
|
|
3950
3948
|
}
|
|
3951
3949
|
|
|
3952
3950
|
// Reset all drag state
|
|
3951
|
+
this.hideDragHint();
|
|
3953
3952
|
this.isDragging = false;
|
|
3954
3953
|
this.isMouseDown = false;
|
|
3955
3954
|
this.shiftDragCopy = false;
|
|
3956
3955
|
this.currentDragIsCopy = false;
|
|
3957
3956
|
this.currentDragItem = null;
|
|
3957
|
+
this.originalDragItem = null;
|
|
3958
|
+
this.originalSelectedItems = null;
|
|
3958
3959
|
this.canvasMouseDown = false;
|
|
3959
3960
|
this.autoScrollDeltaX = 0;
|
|
3960
3961
|
this.autoScrollDeltaY = 0;
|
|
@@ -6223,108 +6224,10 @@ export class Editor extends RapidElement {
|
|
|
6223
6224
|
`;
|
|
6224
6225
|
}
|
|
6225
6226
|
|
|
6226
|
-
private
|
|
6227
|
-
if (this.showLanguageOptions) {
|
|
6228
|
-
this.showLanguageOptions = false;
|
|
6229
|
-
return;
|
|
6230
|
-
}
|
|
6231
|
-
this.showLanguageOptions = true;
|
|
6232
|
-
// Close on next click anywhere outside
|
|
6233
|
-
requestAnimationFrame(() => {
|
|
6234
|
-
const close = () => {
|
|
6235
|
-
this.showLanguageOptions = false;
|
|
6236
|
-
document.removeEventListener('click', close);
|
|
6237
|
-
};
|
|
6238
|
-
document.addEventListener('click', close, { once: true });
|
|
6239
|
-
});
|
|
6240
|
-
}
|
|
6241
|
-
|
|
6242
|
-
private handleLanguageOptionSelected(event: CustomEvent): void {
|
|
6243
|
-
if (!this.showLanguageOptions) return;
|
|
6244
|
-
const selected = event.detail?.selected;
|
|
6245
|
-
if (selected?.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
6246
|
-
this.handleLanguageChange(this.definition?.language || '');
|
|
6247
|
-
} else if (selected?.value) {
|
|
6248
|
-
this.handleLanguageChange(selected.value);
|
|
6249
|
-
}
|
|
6250
|
-
this.showLanguageOptions = false;
|
|
6251
|
-
}
|
|
6252
|
-
|
|
6253
|
-
private renderToolbarTip(
|
|
6254
|
-
text: string | TemplateResult,
|
|
6255
|
-
content: TemplateResult
|
|
6256
|
-
): TemplateResult {
|
|
6257
|
-
const tipContent = text;
|
|
6258
|
-
return html`
|
|
6259
|
-
<temba-tip
|
|
6260
|
-
class="toolbar-tip"
|
|
6261
|
-
.text=${typeof tipContent === 'string' ? tipContent : ''}
|
|
6262
|
-
.content=${typeof tipContent === 'string' ? null : tipContent}
|
|
6263
|
-
position="top"
|
|
6264
|
-
>
|
|
6265
|
-
${content}
|
|
6266
|
-
</temba-tip>
|
|
6267
|
-
`;
|
|
6268
|
-
}
|
|
6269
|
-
|
|
6270
|
-
private isMacPlatform(): boolean {
|
|
6271
|
-
return (
|
|
6272
|
-
typeof navigator !== 'undefined' &&
|
|
6273
|
-
/Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
|
6274
|
-
);
|
|
6275
|
-
}
|
|
6276
|
-
|
|
6277
|
-
private getSearchShortcutLabel(): string {
|
|
6278
|
-
return this.isMacPlatform() ? '⌘F' : 'Ctrl+F';
|
|
6279
|
-
}
|
|
6280
|
-
|
|
6281
|
-
private renderToolbarShortcutLabel(
|
|
6282
|
-
label: string,
|
|
6283
|
-
shortcut: string
|
|
6284
|
-
): TemplateResult {
|
|
6285
|
-
return html`<span style="display:inline-flex; align-items:center; gap:8px;">
|
|
6286
|
-
<span>${label}</span>
|
|
6287
|
-
<kbd>${shortcut}</kbd>
|
|
6288
|
-
</span>`;
|
|
6289
|
-
}
|
|
6290
|
-
|
|
6291
|
-
private renderToolbarLanguageOption(
|
|
6292
|
-
option: { name: string; value: string; percent?: number },
|
|
6293
|
-
selected: boolean
|
|
6294
|
-
): TemplateResult {
|
|
6295
|
-
if (option.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
6296
|
-
const primaryBackground = selected ? '#e1e8ef' : '#edf1f5';
|
|
6297
|
-
return html`
|
|
6298
|
-
<div
|
|
6299
|
-
style="display:flex; align-items:center; justify-content:space-between; gap:8px; background:${primaryBackground}; color:#2f3f52; border-radius:4px; padding:6px 10px;"
|
|
6300
|
-
>
|
|
6301
|
-
<span>${option.name}</span>
|
|
6302
|
-
<span
|
|
6303
|
-
style="display:inline-flex; align-items:center; border-radius:999px; background:rgba(47, 63, 82, 0.12); color:#2f3f52; font-size:10px; font-weight:700; line-height:1; padding:3px 7px;"
|
|
6304
|
-
>Original</span
|
|
6305
|
-
>
|
|
6306
|
-
</div>
|
|
6307
|
-
`;
|
|
6308
|
-
}
|
|
6309
|
-
|
|
6310
|
-
return html`
|
|
6311
|
-
<div
|
|
6312
|
-
style="display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px;"
|
|
6313
|
-
>
|
|
6314
|
-
<span>${option.name}</span>
|
|
6315
|
-
<span style="font-size:11px; font-weight:600; color:#5f6b7a;"
|
|
6316
|
-
>${option.percent ?? 0}%</span
|
|
6317
|
-
>
|
|
6318
|
-
</div>
|
|
6319
|
-
`;
|
|
6320
|
-
}
|
|
6321
|
-
|
|
6322
|
-
private renderToolbar(): TemplateResult {
|
|
6227
|
+
private renderToolbarElement(): TemplateResult {
|
|
6323
6228
|
const languages = this.getLocalizationLanguages();
|
|
6324
6229
|
const availableLanguages = this.getAvailableLanguages();
|
|
6325
6230
|
const baseLanguage = this.definition?.language;
|
|
6326
|
-
const languageOptionCount = (baseLanguage ? 1 : 0) + languages.length;
|
|
6327
|
-
const showLanguageControls = languageOptionCount > 1;
|
|
6328
6231
|
const baseLanguageName =
|
|
6329
6232
|
availableLanguages.find((lang) => lang.code === baseLanguage)?.name ||
|
|
6330
6233
|
baseLanguage ||
|
|
@@ -6346,11 +6249,6 @@ export class Editor extends RapidElement {
|
|
|
6346
6249
|
const percent = Math.round(
|
|
6347
6250
|
(progress.localized / Math.max(progress.total, 1)) * 100
|
|
6348
6251
|
);
|
|
6349
|
-
const hasTranslations = progress.total > 0;
|
|
6350
|
-
const showLocalizationTools = Boolean(activeLanguage);
|
|
6351
|
-
const searchTargetLabel = this.showMessageTable
|
|
6352
|
-
? 'Search table'
|
|
6353
|
-
: 'Search flow';
|
|
6354
6252
|
const languageOptions = [
|
|
6355
6253
|
{
|
|
6356
6254
|
name: baseLanguageName,
|
|
@@ -6372,205 +6270,56 @@ export class Editor extends RapidElement {
|
|
|
6372
6270
|
];
|
|
6373
6271
|
|
|
6374
6272
|
return html`
|
|
6375
|
-
<
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
'Table View',
|
|
6391
|
-
html`
|
|
6392
|
-
<button
|
|
6393
|
-
class="toolbar-btn ${this.showMessageTable ? 'active' : ''}"
|
|
6394
|
-
@click=${() => { this.showMessageTable = true; }}
|
|
6395
|
-
aria-label="Table View"
|
|
6396
|
-
>
|
|
6397
|
-
<temba-icon name=${Icon.quick_replies} size="1"></temba-icon>
|
|
6398
|
-
</button>
|
|
6399
|
-
`
|
|
6400
|
-
)}
|
|
6401
|
-
${showLanguageControls
|
|
6402
|
-
? html`
|
|
6403
|
-
<div class="toolbar-divider"></div>
|
|
6404
|
-
<div class="toolbar-language-group">
|
|
6405
|
-
<div class="toolbar-language">
|
|
6406
|
-
${this.renderToolbarTip(
|
|
6407
|
-
'Change language',
|
|
6408
|
-
html`
|
|
6409
|
-
<button
|
|
6410
|
-
class="language-pill ${isBaseSelected ? 'primary' : ''}"
|
|
6411
|
-
id="language-btn"
|
|
6412
|
-
@click=${this.handleLanguageIconClick}
|
|
6413
|
-
aria-label="Change language"
|
|
6414
|
-
>
|
|
6415
|
-
<temba-icon name=${Icon.language}></temba-icon>
|
|
6416
|
-
<span>${currentLanguage.name}</span>
|
|
6417
|
-
${!isBaseSelected
|
|
6418
|
-
? html`<span class="language-percent">${percent}%</span>`
|
|
6419
|
-
: ''}
|
|
6420
|
-
<temba-icon
|
|
6421
|
-
class="language-pill-caret"
|
|
6422
|
-
name=${this.showLanguageOptions
|
|
6423
|
-
? Icon.arrow_up
|
|
6424
|
-
: Icon.arrow_down}
|
|
6425
|
-
></temba-icon>
|
|
6426
|
-
</button>
|
|
6427
|
-
`
|
|
6428
|
-
)}
|
|
6429
|
-
<temba-options
|
|
6430
|
-
.anchorTo=${this.querySelector('#language-btn') as HTMLElement}
|
|
6431
|
-
.options=${languageOptions}
|
|
6432
|
-
.renderOption=${this.renderToolbarLanguageOption}
|
|
6433
|
-
?visible=${this.showLanguageOptions}
|
|
6434
|
-
@temba-selection=${this.handleLanguageOptionSelected}
|
|
6435
|
-
style="--temba-options-option-margin:4px; --temba-options-option-padding:0; --temba-options-option-radius:4px;"
|
|
6436
|
-
min-width="230"
|
|
6437
|
-
></temba-options>
|
|
6438
|
-
</div>
|
|
6439
|
-
${showLocalizationTools
|
|
6440
|
-
? this.renderToolbarTranslationTools(hasTranslations)
|
|
6441
|
-
: ''}
|
|
6442
|
-
</div>
|
|
6443
|
-
`
|
|
6444
|
-
: ''}
|
|
6445
|
-
</div>
|
|
6446
|
-
<div class="toolbar-right">
|
|
6447
|
-
${!this.showMessageTable ? html`
|
|
6448
|
-
${this.renderToolbarTip(
|
|
6449
|
-
'Zoom to fit',
|
|
6450
|
-
html`
|
|
6451
|
-
<button
|
|
6452
|
-
class="toolbar-btn"
|
|
6453
|
-
@click=${this.zoomToFit}
|
|
6454
|
-
?disabled=${!this.zoomInitialized || this.zoomFitted}
|
|
6455
|
-
aria-label="Zoom to fit"
|
|
6456
|
-
>
|
|
6457
|
-
<temba-icon name=${Icon.zoom_fit} size="1"></temba-icon>
|
|
6458
|
-
</button>
|
|
6459
|
-
`
|
|
6460
|
-
)}
|
|
6461
|
-
<div class="toolbar-divider"></div>
|
|
6462
|
-
${this.renderToolbarTip(
|
|
6463
|
-
'Zoom out',
|
|
6464
|
-
html`
|
|
6465
|
-
<button
|
|
6466
|
-
class="toolbar-btn"
|
|
6467
|
-
@click=${this.zoomOut}
|
|
6468
|
-
?disabled=${!this.zoomInitialized || this.zoom <= 0.3}
|
|
6469
|
-
aria-label="Zoom out"
|
|
6470
|
-
>
|
|
6471
|
-
−
|
|
6472
|
-
</button>
|
|
6473
|
-
`
|
|
6474
|
-
)}
|
|
6475
|
-
<span class="toolbar-zoom-level">${this.zoomInitialized ? `${Math.round(this.zoom * 100)}%` : ''}</span>
|
|
6476
|
-
${this.renderToolbarTip(
|
|
6477
|
-
'Zoom in',
|
|
6478
|
-
html`
|
|
6479
|
-
<button
|
|
6480
|
-
class="toolbar-btn"
|
|
6481
|
-
@click=${this.zoomIn}
|
|
6482
|
-
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
6483
|
-
aria-label="Zoom in"
|
|
6484
|
-
>
|
|
6485
|
-
+
|
|
6486
|
-
</button>
|
|
6487
|
-
`
|
|
6488
|
-
)}
|
|
6489
|
-
<div class="toolbar-divider"></div>
|
|
6490
|
-
${this.renderToolbarTip(
|
|
6491
|
-
'Zoom to 100%',
|
|
6492
|
-
html`
|
|
6493
|
-
<button
|
|
6494
|
-
class="toolbar-btn"
|
|
6495
|
-
@click=${this.zoomToFull}
|
|
6496
|
-
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
6497
|
-
aria-label="Zoom to 100%"
|
|
6498
|
-
>
|
|
6499
|
-
<temba-icon name=${Icon.zoom_in} size="1"></temba-icon>
|
|
6500
|
-
</button>
|
|
6501
|
-
`
|
|
6502
|
-
)}
|
|
6503
|
-
<div class="toolbar-divider"></div>
|
|
6504
|
-
` : ''}
|
|
6505
|
-
${this.renderToolbarTip(
|
|
6506
|
-
'Revisions',
|
|
6507
|
-
html`
|
|
6508
|
-
<button
|
|
6509
|
-
class="toolbar-btn ${!this.revisionsWindowHidden
|
|
6510
|
-
? 'active'
|
|
6511
|
-
: ''}"
|
|
6512
|
-
@click=${this.handleRevisionsTabClick}
|
|
6513
|
-
aria-label="Revisions"
|
|
6514
|
-
>
|
|
6515
|
-
<temba-icon
|
|
6516
|
-
name=${this.isSaving ? 'progress_spinner' : 'revisions'}
|
|
6517
|
-
size="1"
|
|
6518
|
-
?spin=${this.isSaving}
|
|
6519
|
-
></temba-icon>
|
|
6520
|
-
</button>
|
|
6521
|
-
`
|
|
6522
|
-
)}
|
|
6523
|
-
<div class="toolbar-divider"></div>
|
|
6524
|
-
${this.renderToolbarTip(
|
|
6525
|
-
this.renderToolbarShortcutLabel(
|
|
6526
|
-
searchTargetLabel,
|
|
6527
|
-
this.getSearchShortcutLabel()
|
|
6528
|
-
),
|
|
6529
|
-
html`
|
|
6530
|
-
<button
|
|
6531
|
-
class="toolbar-btn"
|
|
6532
|
-
@click=${this.openFlowSearch}
|
|
6533
|
-
?disabled=${!!this.viewingRevision}
|
|
6534
|
-
aria-label=${searchTargetLabel}
|
|
6535
|
-
>
|
|
6536
|
-
<temba-icon name=${Icon.search} size="1"></temba-icon>
|
|
6537
|
-
</button>
|
|
6538
|
-
`
|
|
6539
|
-
)}
|
|
6540
|
-
</div>
|
|
6541
|
-
</div>
|
|
6273
|
+
<temba-editor-toolbar
|
|
6274
|
+
?message-view=${this.showMessageTable}
|
|
6275
|
+
.zoom=${this.zoom}
|
|
6276
|
+
?zoom-initialized=${this.zoomInitialized}
|
|
6277
|
+
?zoom-fitted=${this.zoomFitted}
|
|
6278
|
+
?revisions-active=${!this.revisionsWindowHidden}
|
|
6279
|
+
?is-saving=${this.isSaving}
|
|
6280
|
+
?search-disabled=${!!this.viewingRevision}
|
|
6281
|
+
.languageOptions=${languageOptions}
|
|
6282
|
+
current-language-name=${currentLanguage.name}
|
|
6283
|
+
?is-base-language=${isBaseSelected}
|
|
6284
|
+
.languagePercent=${percent}
|
|
6285
|
+
?show-localization-tools=${Boolean(activeLanguage)}
|
|
6286
|
+
@temba-button-clicked=${this.handleToolbarAction}
|
|
6287
|
+
></temba-editor-toolbar>
|
|
6542
6288
|
`;
|
|
6543
6289
|
}
|
|
6544
6290
|
|
|
6545
|
-
private
|
|
6546
|
-
const
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6291
|
+
private handleToolbarAction(e: CustomEvent): void {
|
|
6292
|
+
const detail = e.detail as ToolbarAction;
|
|
6293
|
+
switch (detail.action) {
|
|
6294
|
+
case 'view-change':
|
|
6295
|
+
this.showMessageTable = detail.view === 'table';
|
|
6296
|
+
break;
|
|
6297
|
+
case 'zoom-in':
|
|
6298
|
+
this.zoomIn();
|
|
6299
|
+
break;
|
|
6300
|
+
case 'zoom-out':
|
|
6301
|
+
this.zoomOut();
|
|
6302
|
+
break;
|
|
6303
|
+
case 'zoom-to-fit':
|
|
6304
|
+
this.zoomToFit();
|
|
6305
|
+
break;
|
|
6306
|
+
case 'zoom-to-full':
|
|
6307
|
+
this.zoomToFull();
|
|
6308
|
+
break;
|
|
6309
|
+
case 'revisions':
|
|
6310
|
+
this.handleRevisionsTabClick();
|
|
6311
|
+
break;
|
|
6312
|
+
case 'search':
|
|
6313
|
+
this.openFlowSearch();
|
|
6314
|
+
break;
|
|
6315
|
+
case 'language-change':
|
|
6316
|
+
if (detail.isPrimary) {
|
|
6317
|
+
this.handleLanguageChange(this.definition?.language || '');
|
|
6318
|
+
} else if (detail.languageCode) {
|
|
6319
|
+
this.handleLanguageChange(detail.languageCode);
|
|
6320
|
+
}
|
|
6321
|
+
break;
|
|
6322
|
+
}
|
|
6574
6323
|
}
|
|
6575
6324
|
|
|
6576
6325
|
/**
|
|
@@ -6729,7 +6478,7 @@ export class Editor extends RapidElement {
|
|
|
6729
6478
|
return html`${style} ${this.renderIssuesWindow()}
|
|
6730
6479
|
${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
|
|
6731
6480
|
<div id="editor-container">
|
|
6732
|
-
${this.
|
|
6481
|
+
${this.renderToolbarElement()}
|
|
6733
6482
|
<div id="editor">
|
|
6734
6483
|
${this.showMessageTable
|
|
6735
6484
|
? html`<temba-message-table></temba-message-table>`
|
|
@@ -6860,6 +6609,7 @@ export class Editor extends RapidElement {
|
|
|
6860
6609
|
`}
|
|
6861
6610
|
</div>
|
|
6862
6611
|
${this.renderPendingCard()}
|
|
6612
|
+
<div class="drag-hint" id="drag-hint">Hold ⇧ to duplicate</div>
|
|
6863
6613
|
</div>
|
|
6864
6614
|
<div class="loupe" id="loupe">
|
|
6865
6615
|
<div class="loupe-content" id="loupe-content"></div>
|