@nyaruka/temba-components 0.156.3 → 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 +11 -0
- package/dist/temba-components.js +395 -286
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/flow/CanvasNode.ts +20 -0
- package/src/flow/Editor.ts +260 -502
- package/src/flow/EditorToolbar.ts +566 -0
- package/src/flow/StickyNote.ts +29 -5
- package/src/flow/actions/set_contact_language.ts +4 -13
- package/src/flow/utils.ts +11 -0
- package/temba-modules.ts +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,198 +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.complete {
|
|
709
|
-
background: #d4f5e0;
|
|
710
|
-
color: #1a7f37;
|
|
711
|
-
--icon-color: #1a7f37;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
.language-pill-caret {
|
|
715
|
-
margin-left: 1px;
|
|
716
|
-
--icon-color: currentColor;
|
|
717
|
-
--icon-size: 12px;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
.language-percent {
|
|
721
|
-
display: inline-block;
|
|
722
|
-
font-size: 12px;
|
|
723
|
-
font-weight: 700;
|
|
724
|
-
line-height: 1;
|
|
725
|
-
color: #0064c8;
|
|
726
|
-
white-space: nowrap;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
.language-pill.complete .language-percent {
|
|
730
|
-
color: #1a7f37;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
.toolbar-zoom-level {
|
|
734
|
-
font-size: 12px;
|
|
735
|
-
min-width: 40px;
|
|
736
|
-
text-align: center;
|
|
737
|
-
color: #555;
|
|
738
|
-
font-weight: 500;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
.toolbar-translation {
|
|
742
|
-
display: flex;
|
|
743
|
-
align-items: center;
|
|
744
|
-
gap: 4px;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
.toolbar-btn.language-tool {
|
|
748
|
-
width: var(--toolbar-translation-control-height);
|
|
749
|
-
height: var(--toolbar-translation-control-height);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
567
|
#editor {
|
|
753
568
|
overflow: scroll;
|
|
754
569
|
flex: 1;
|
|
@@ -816,6 +631,10 @@ export class Editor extends RapidElement {
|
|
|
816
631
|
transition: none !important;
|
|
817
632
|
}
|
|
818
633
|
|
|
634
|
+
#canvas.shift-held {
|
|
635
|
+
--shift-held-cursor: copy;
|
|
636
|
+
}
|
|
637
|
+
|
|
819
638
|
#canvas.viewing-revision {
|
|
820
639
|
pointer-events: none;
|
|
821
640
|
}
|
|
@@ -1050,6 +869,10 @@ export class Editor extends RapidElement {
|
|
|
1050
869
|
border-radius: var(--curvature);
|
|
1051
870
|
}
|
|
1052
871
|
|
|
872
|
+
.draggable.selected.drag-copy {
|
|
873
|
+
outline: none;
|
|
874
|
+
}
|
|
875
|
+
|
|
1053
876
|
/* Language banner replaced by toolbar language selector */
|
|
1054
877
|
|
|
1055
878
|
.localization-window-content {
|
|
@@ -1441,6 +1264,36 @@ export class Editor extends RapidElement {
|
|
|
1441
1264
|
opacity: 0.8;
|
|
1442
1265
|
}
|
|
1443
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
|
+
|
|
1444
1297
|
.reflow-card {
|
|
1445
1298
|
position: absolute;
|
|
1446
1299
|
top: 16px;
|
|
@@ -2064,6 +1917,8 @@ export class Editor extends RapidElement {
|
|
|
2064
1917
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
2065
1918
|
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
2066
1919
|
document.removeEventListener('keydown', this.boundKeyDown);
|
|
1920
|
+
document.removeEventListener('keyup', this.boundKeyUp);
|
|
1921
|
+
window.removeEventListener('blur', this.boundWindowBlur);
|
|
2067
1922
|
document.removeEventListener('touchmove', this.boundTouchMove);
|
|
2068
1923
|
document.removeEventListener('touchend', this.boundTouchEnd);
|
|
2069
1924
|
document.removeEventListener('touchcancel', this.boundTouchCancel);
|
|
@@ -2096,6 +1951,8 @@ export class Editor extends RapidElement {
|
|
|
2096
1951
|
document.addEventListener('mouseup', this.boundMouseUp);
|
|
2097
1952
|
document.addEventListener('mousedown', this.boundGlobalMouseDown);
|
|
2098
1953
|
document.addEventListener('keydown', this.boundKeyDown);
|
|
1954
|
+
document.addEventListener('keyup', this.boundKeyUp);
|
|
1955
|
+
window.addEventListener('blur', this.boundWindowBlur);
|
|
2099
1956
|
document.addEventListener('touchmove', this.boundTouchMove, {
|
|
2100
1957
|
passive: false
|
|
2101
1958
|
});
|
|
@@ -2429,7 +2286,44 @@ export class Editor extends RapidElement {
|
|
|
2429
2286
|
}
|
|
2430
2287
|
}
|
|
2431
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
|
+
|
|
2432
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
|
+
|
|
2433
2327
|
// Cmd/Ctrl+F opens flow search (unless a dialog is already open)
|
|
2434
2328
|
if ((event.metaKey || event.ctrlKey) && event.key === 'f') {
|
|
2435
2329
|
event.preventDefault();
|
|
@@ -2459,6 +2353,28 @@ export class Editor extends RapidElement {
|
|
|
2459
2353
|
}
|
|
2460
2354
|
}
|
|
2461
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
|
+
|
|
2462
2378
|
// --- Flow settings cookie (LRU, max 50 flows) ---
|
|
2463
2379
|
|
|
2464
2380
|
static MAX_FLOW_SETTINGS = 50;
|
|
@@ -3683,9 +3599,15 @@ export class Editor extends RapidElement {
|
|
|
3683
3599
|
this.isDragging = true;
|
|
3684
3600
|
this.startAutoScroll();
|
|
3685
3601
|
|
|
3686
|
-
|
|
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) {
|
|
3687
3607
|
this.performShiftDragCopy();
|
|
3688
3608
|
this.shiftDragCopy = false;
|
|
3609
|
+
} else {
|
|
3610
|
+
this.showDragHint();
|
|
3689
3611
|
}
|
|
3690
3612
|
}
|
|
3691
3613
|
|
|
@@ -3696,14 +3618,14 @@ export class Editor extends RapidElement {
|
|
|
3696
3618
|
}
|
|
3697
3619
|
|
|
3698
3620
|
private performShiftDragCopy(): void {
|
|
3699
|
-
if (!this.
|
|
3621
|
+
if (!this.originalDragItem) return;
|
|
3700
3622
|
|
|
3701
|
-
//
|
|
3623
|
+
// Always use the original items as the source for copying
|
|
3702
3624
|
const itemsToCopy =
|
|
3703
|
-
this.
|
|
3704
|
-
this.
|
|
3705
|
-
? Array.from(this.
|
|
3706
|
-
: [this.
|
|
3625
|
+
this.originalSelectedItems?.has(this.originalDragItem.uuid) &&
|
|
3626
|
+
(this.originalSelectedItems?.size ?? 0) > 1
|
|
3627
|
+
? Array.from(this.originalSelectedItems!)
|
|
3628
|
+
: [this.originalDragItem.uuid];
|
|
3707
3629
|
|
|
3708
3630
|
if (itemsToCopy.length === 0) return;
|
|
3709
3631
|
|
|
@@ -3719,19 +3641,44 @@ export class Editor extends RapidElement {
|
|
|
3719
3641
|
}
|
|
3720
3642
|
this.currentDragIsCopy = true;
|
|
3721
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
|
+
|
|
3722
3669
|
// Update drag item to reference the copy
|
|
3723
|
-
const newDragUuid = uuidMapping[this.
|
|
3670
|
+
const newDragUuid = uuidMapping[this.originalDragItem.uuid];
|
|
3724
3671
|
if (newDragUuid) {
|
|
3725
3672
|
this.currentDragItem = {
|
|
3726
|
-
...this.
|
|
3673
|
+
...this.originalDragItem,
|
|
3727
3674
|
uuid: newDragUuid
|
|
3728
3675
|
};
|
|
3729
3676
|
}
|
|
3730
3677
|
|
|
3731
3678
|
// Update selected items to reference copies
|
|
3732
|
-
if (this.
|
|
3679
|
+
if ((this.originalSelectedItems?.size ?? 0) > 1) {
|
|
3733
3680
|
const newSelectedItems = new Set<string>();
|
|
3734
|
-
for (const uuid of this.
|
|
3681
|
+
for (const uuid of this.originalSelectedItems!) {
|
|
3735
3682
|
const newUuid = uuidMapping[uuid];
|
|
3736
3683
|
newSelectedItems.add(newUuid || uuid);
|
|
3737
3684
|
}
|
|
@@ -3739,6 +3686,43 @@ export class Editor extends RapidElement {
|
|
|
3739
3686
|
}
|
|
3740
3687
|
}
|
|
3741
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
|
+
|
|
3742
3726
|
private updateDragPositions(): void {
|
|
3743
3727
|
if (!this.currentDragItem || !this.lastPointerPos) return;
|
|
3744
3728
|
|
|
@@ -3770,11 +3754,15 @@ export class Editor extends RapidElement {
|
|
|
3770
3754
|
element.style.left = `${position.left + deltaX}px`;
|
|
3771
3755
|
element.style.top = `${position.top + deltaY}px`;
|
|
3772
3756
|
element.classList.add('dragging');
|
|
3757
|
+
if (this.currentDragIsCopy) {
|
|
3758
|
+
element.classList.add('drag-copy');
|
|
3759
|
+
}
|
|
3773
3760
|
}
|
|
3774
3761
|
}
|
|
3775
3762
|
});
|
|
3776
3763
|
|
|
3777
3764
|
this.plumber.revalidate(itemsToMove);
|
|
3765
|
+
|
|
3778
3766
|
}
|
|
3779
3767
|
|
|
3780
3768
|
private startAutoScroll(): void {
|
|
@@ -3923,10 +3911,10 @@ export class Editor extends RapidElement {
|
|
|
3923
3911
|
const newPosition = { left: snappedLeft, top: snappedTop };
|
|
3924
3912
|
newPositions[uuid] = newPosition;
|
|
3925
3913
|
|
|
3926
|
-
// Remove dragging
|
|
3914
|
+
// Remove dragging/copy classes
|
|
3927
3915
|
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
3928
3916
|
if (element) {
|
|
3929
|
-
element.classList.remove('dragging');
|
|
3917
|
+
element.classList.remove('dragging', 'drag-copy');
|
|
3930
3918
|
element.style.left = `${snappedLeft}px`;
|
|
3931
3919
|
element.style.top = `${snappedTop}px`;
|
|
3932
3920
|
}
|
|
@@ -3960,11 +3948,14 @@ export class Editor extends RapidElement {
|
|
|
3960
3948
|
}
|
|
3961
3949
|
|
|
3962
3950
|
// Reset all drag state
|
|
3951
|
+
this.hideDragHint();
|
|
3963
3952
|
this.isDragging = false;
|
|
3964
3953
|
this.isMouseDown = false;
|
|
3965
3954
|
this.shiftDragCopy = false;
|
|
3966
3955
|
this.currentDragIsCopy = false;
|
|
3967
3956
|
this.currentDragItem = null;
|
|
3957
|
+
this.originalDragItem = null;
|
|
3958
|
+
this.originalSelectedItems = null;
|
|
3968
3959
|
this.canvasMouseDown = false;
|
|
3969
3960
|
this.autoScrollDeltaX = 0;
|
|
3970
3961
|
this.autoScrollDeltaY = 0;
|
|
@@ -6233,116 +6224,10 @@ export class Editor extends RapidElement {
|
|
|
6233
6224
|
`;
|
|
6234
6225
|
}
|
|
6235
6226
|
|
|
6236
|
-
private
|
|
6237
|
-
if (this.showLanguageOptions) {
|
|
6238
|
-
this.showLanguageOptions = false;
|
|
6239
|
-
return;
|
|
6240
|
-
}
|
|
6241
|
-
this.showLanguageOptions = true;
|
|
6242
|
-
// Close on next click anywhere outside
|
|
6243
|
-
requestAnimationFrame(() => {
|
|
6244
|
-
const close = () => {
|
|
6245
|
-
this.showLanguageOptions = false;
|
|
6246
|
-
document.removeEventListener('click', close);
|
|
6247
|
-
};
|
|
6248
|
-
document.addEventListener('click', close, { once: true });
|
|
6249
|
-
});
|
|
6250
|
-
}
|
|
6251
|
-
|
|
6252
|
-
private handleLanguageOptionSelected(event: CustomEvent): void {
|
|
6253
|
-
if (!this.showLanguageOptions) return;
|
|
6254
|
-
const selected = event.detail?.selected;
|
|
6255
|
-
if (selected?.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
6256
|
-
this.handleLanguageChange(this.definition?.language || '');
|
|
6257
|
-
} else if (selected?.value) {
|
|
6258
|
-
this.handleLanguageChange(selected.value);
|
|
6259
|
-
}
|
|
6260
|
-
this.showLanguageOptions = false;
|
|
6261
|
-
}
|
|
6262
|
-
|
|
6263
|
-
private renderToolbarTip(
|
|
6264
|
-
text: string | TemplateResult,
|
|
6265
|
-
content: TemplateResult
|
|
6266
|
-
): TemplateResult {
|
|
6267
|
-
const tipContent = text;
|
|
6268
|
-
return html`
|
|
6269
|
-
<temba-tip
|
|
6270
|
-
class="toolbar-tip"
|
|
6271
|
-
.text=${typeof tipContent === 'string' ? tipContent : ''}
|
|
6272
|
-
.content=${typeof tipContent === 'string' ? null : tipContent}
|
|
6273
|
-
position="top"
|
|
6274
|
-
>
|
|
6275
|
-
${content}
|
|
6276
|
-
</temba-tip>
|
|
6277
|
-
`;
|
|
6278
|
-
}
|
|
6279
|
-
|
|
6280
|
-
private isMacPlatform(): boolean {
|
|
6281
|
-
return (
|
|
6282
|
-
typeof navigator !== 'undefined' &&
|
|
6283
|
-
/Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
|
6284
|
-
);
|
|
6285
|
-
}
|
|
6286
|
-
|
|
6287
|
-
private getSearchShortcutLabel(): string {
|
|
6288
|
-
return this.isMacPlatform() ? '⌘F' : 'Ctrl+F';
|
|
6289
|
-
}
|
|
6290
|
-
|
|
6291
|
-
private renderToolbarShortcutLabel(
|
|
6292
|
-
label: string,
|
|
6293
|
-
shortcut: string
|
|
6294
|
-
): TemplateResult {
|
|
6295
|
-
return html`<span style="display:inline-flex; align-items:center; gap:8px;">
|
|
6296
|
-
<span>${label}</span>
|
|
6297
|
-
<kbd>${shortcut}</kbd>
|
|
6298
|
-
</span>`;
|
|
6299
|
-
}
|
|
6300
|
-
|
|
6301
|
-
private renderToolbarLanguageOption(
|
|
6302
|
-
option: { name: string; value: string; percent?: number },
|
|
6303
|
-
selected: boolean
|
|
6304
|
-
): TemplateResult {
|
|
6305
|
-
if (option.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
6306
|
-
const primaryBackground = selected ? '#e1e8ef' : '#edf1f5';
|
|
6307
|
-
return html`
|
|
6308
|
-
<div
|
|
6309
|
-
style="display:flex; align-items:center; justify-content:space-between; gap:8px; background:${primaryBackground}; color:#2f3f52; border-radius:4px; padding:6px 10px;"
|
|
6310
|
-
>
|
|
6311
|
-
<span>${option.name}</span>
|
|
6312
|
-
<span
|
|
6313
|
-
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;"
|
|
6314
|
-
>Original</span
|
|
6315
|
-
>
|
|
6316
|
-
</div>
|
|
6317
|
-
`;
|
|
6318
|
-
}
|
|
6319
|
-
|
|
6320
|
-
const isComplete = option.percent === 100;
|
|
6321
|
-
const optionBg = isComplete ? '#d4f5e0' : '';
|
|
6322
|
-
const optionHoverBg = isComplete ? '#c0edce' : '';
|
|
6323
|
-
const optionRadius = isComplete ? 'border-radius:4px;' : '';
|
|
6324
|
-
const percentColor = isComplete ? 'color:#1a7f37;' : 'color:#5f6b7a;';
|
|
6325
|
-
|
|
6326
|
-
return html`
|
|
6327
|
-
<div
|
|
6328
|
-
style="display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px; ${optionBg ? `background:${optionBg};` : ''} ${optionRadius}"
|
|
6329
|
-
@mouseenter=${isComplete ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.background = optionHoverBg; } : null}
|
|
6330
|
-
@mouseleave=${isComplete ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.background = optionBg; } : null}
|
|
6331
|
-
>
|
|
6332
|
-
<span style="${isComplete ? 'color:#1a7f37;' : ''}">${option.name}</span>
|
|
6333
|
-
<span style="font-size:11px; font-weight:600; ${percentColor}"
|
|
6334
|
-
>${option.percent ?? 0}%</span
|
|
6335
|
-
>
|
|
6336
|
-
</div>
|
|
6337
|
-
`;
|
|
6338
|
-
}
|
|
6339
|
-
|
|
6340
|
-
private renderToolbar(): TemplateResult {
|
|
6227
|
+
private renderToolbarElement(): TemplateResult {
|
|
6341
6228
|
const languages = this.getLocalizationLanguages();
|
|
6342
6229
|
const availableLanguages = this.getAvailableLanguages();
|
|
6343
6230
|
const baseLanguage = this.definition?.language;
|
|
6344
|
-
const languageOptionCount = (baseLanguage ? 1 : 0) + languages.length;
|
|
6345
|
-
const showLanguageControls = languageOptionCount > 1;
|
|
6346
6231
|
const baseLanguageName =
|
|
6347
6232
|
availableLanguages.find((lang) => lang.code === baseLanguage)?.name ||
|
|
6348
6233
|
baseLanguage ||
|
|
@@ -6364,11 +6249,6 @@ export class Editor extends RapidElement {
|
|
|
6364
6249
|
const percent = Math.round(
|
|
6365
6250
|
(progress.localized / Math.max(progress.total, 1)) * 100
|
|
6366
6251
|
);
|
|
6367
|
-
const hasTranslations = progress.total > 0;
|
|
6368
|
-
const showLocalizationTools = Boolean(activeLanguage);
|
|
6369
|
-
const searchTargetLabel = this.showMessageTable
|
|
6370
|
-
? 'Search table'
|
|
6371
|
-
: 'Search flow';
|
|
6372
6252
|
const languageOptions = [
|
|
6373
6253
|
{
|
|
6374
6254
|
name: baseLanguageName,
|
|
@@ -6390,179 +6270,56 @@ export class Editor extends RapidElement {
|
|
|
6390
6270
|
];
|
|
6391
6271
|
|
|
6392
6272
|
return html`
|
|
6393
|
-
<
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
'Table View',
|
|
6409
|
-
html`
|
|
6410
|
-
<button
|
|
6411
|
-
class="toolbar-btn ${this.showMessageTable ? 'active' : ''}"
|
|
6412
|
-
@click=${() => { this.showMessageTable = true; }}
|
|
6413
|
-
aria-label="Table View"
|
|
6414
|
-
>
|
|
6415
|
-
<temba-icon name=${Icon.quick_replies} size="1"></temba-icon>
|
|
6416
|
-
</button>
|
|
6417
|
-
`
|
|
6418
|
-
)}
|
|
6419
|
-
${showLanguageControls
|
|
6420
|
-
? html`
|
|
6421
|
-
<div class="toolbar-divider"></div>
|
|
6422
|
-
<div class="toolbar-language-group">
|
|
6423
|
-
<div class="toolbar-language">
|
|
6424
|
-
${this.renderToolbarTip(
|
|
6425
|
-
'Change language',
|
|
6426
|
-
html`
|
|
6427
|
-
<button
|
|
6428
|
-
class="language-pill ${isBaseSelected ? 'primary' : percent === 100 ? 'complete' : ''}"
|
|
6429
|
-
id="language-btn"
|
|
6430
|
-
@click=${this.handleLanguageIconClick}
|
|
6431
|
-
aria-label="Change language"
|
|
6432
|
-
>
|
|
6433
|
-
<temba-icon name=${Icon.language}></temba-icon>
|
|
6434
|
-
<span>${currentLanguage.name}</span>
|
|
6435
|
-
${!isBaseSelected
|
|
6436
|
-
? html`<span class="language-percent">${percent}%</span>`
|
|
6437
|
-
: ''}
|
|
6438
|
-
<temba-icon
|
|
6439
|
-
class="language-pill-caret"
|
|
6440
|
-
name=${this.showLanguageOptions
|
|
6441
|
-
? Icon.arrow_up
|
|
6442
|
-
: Icon.arrow_down}
|
|
6443
|
-
></temba-icon>
|
|
6444
|
-
</button>
|
|
6445
|
-
`
|
|
6446
|
-
)}
|
|
6447
|
-
<temba-options
|
|
6448
|
-
.anchorTo=${this.querySelector('#language-btn') as HTMLElement}
|
|
6449
|
-
.options=${languageOptions}
|
|
6450
|
-
.renderOption=${this.renderToolbarLanguageOption}
|
|
6451
|
-
?visible=${this.showLanguageOptions}
|
|
6452
|
-
@temba-selection=${this.handleLanguageOptionSelected}
|
|
6453
|
-
style="--temba-options-option-margin:4px; --temba-options-option-padding:0; --temba-options-option-radius:4px;"
|
|
6454
|
-
min-width="230"
|
|
6455
|
-
></temba-options>
|
|
6456
|
-
</div>
|
|
6457
|
-
${showLocalizationTools
|
|
6458
|
-
? this.renderToolbarTranslationTools(hasTranslations)
|
|
6459
|
-
: ''}
|
|
6460
|
-
</div>
|
|
6461
|
-
`
|
|
6462
|
-
: ''}
|
|
6463
|
-
</div>
|
|
6464
|
-
<div class="toolbar-right">
|
|
6465
|
-
${!this.showMessageTable ? html`
|
|
6466
|
-
${this.renderToolbarTip(
|
|
6467
|
-
'Zoom to fit',
|
|
6468
|
-
html`
|
|
6469
|
-
<button
|
|
6470
|
-
class="toolbar-btn"
|
|
6471
|
-
@click=${this.zoomToFit}
|
|
6472
|
-
?disabled=${!this.zoomInitialized || this.zoomFitted}
|
|
6473
|
-
aria-label="Zoom to fit"
|
|
6474
|
-
>
|
|
6475
|
-
<temba-icon name=${Icon.zoom_fit} size="1"></temba-icon>
|
|
6476
|
-
</button>
|
|
6477
|
-
`
|
|
6478
|
-
)}
|
|
6479
|
-
<div class="toolbar-divider"></div>
|
|
6480
|
-
${this.renderToolbarTip(
|
|
6481
|
-
'Zoom out',
|
|
6482
|
-
html`
|
|
6483
|
-
<button
|
|
6484
|
-
class="toolbar-btn"
|
|
6485
|
-
@click=${this.zoomOut}
|
|
6486
|
-
?disabled=${!this.zoomInitialized || this.zoom <= 0.3}
|
|
6487
|
-
aria-label="Zoom out"
|
|
6488
|
-
>
|
|
6489
|
-
−
|
|
6490
|
-
</button>
|
|
6491
|
-
`
|
|
6492
|
-
)}
|
|
6493
|
-
<span class="toolbar-zoom-level">${this.zoomInitialized ? `${Math.round(this.zoom * 100)}%` : ''}</span>
|
|
6494
|
-
${this.renderToolbarTip(
|
|
6495
|
-
'Zoom in',
|
|
6496
|
-
html`
|
|
6497
|
-
<button
|
|
6498
|
-
class="toolbar-btn"
|
|
6499
|
-
@click=${this.zoomIn}
|
|
6500
|
-
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
6501
|
-
aria-label="Zoom in"
|
|
6502
|
-
>
|
|
6503
|
-
+
|
|
6504
|
-
</button>
|
|
6505
|
-
`
|
|
6506
|
-
)}
|
|
6507
|
-
<div class="toolbar-divider"></div>
|
|
6508
|
-
${this.renderToolbarTip(
|
|
6509
|
-
'Zoom to 100%',
|
|
6510
|
-
html`
|
|
6511
|
-
<button
|
|
6512
|
-
class="toolbar-btn"
|
|
6513
|
-
@click=${this.zoomToFull}
|
|
6514
|
-
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
6515
|
-
aria-label="Zoom to 100%"
|
|
6516
|
-
>
|
|
6517
|
-
<temba-icon name=${Icon.zoom_in} size="1"></temba-icon>
|
|
6518
|
-
</button>
|
|
6519
|
-
`
|
|
6520
|
-
)}
|
|
6521
|
-
<div class="toolbar-divider"></div>
|
|
6522
|
-
` : ''}
|
|
6523
|
-
${this.renderToolbarTip(
|
|
6524
|
-
'Revisions',
|
|
6525
|
-
html`
|
|
6526
|
-
<button
|
|
6527
|
-
class="toolbar-btn ${!this.revisionsWindowHidden
|
|
6528
|
-
? 'active'
|
|
6529
|
-
: ''}"
|
|
6530
|
-
@click=${this.handleRevisionsTabClick}
|
|
6531
|
-
aria-label="Revisions"
|
|
6532
|
-
>
|
|
6533
|
-
<temba-icon
|
|
6534
|
-
name=${this.isSaving ? 'progress_spinner' : 'revisions'}
|
|
6535
|
-
size="1"
|
|
6536
|
-
?spin=${this.isSaving}
|
|
6537
|
-
></temba-icon>
|
|
6538
|
-
</button>
|
|
6539
|
-
`
|
|
6540
|
-
)}
|
|
6541
|
-
<div class="toolbar-divider"></div>
|
|
6542
|
-
${this.renderToolbarTip(
|
|
6543
|
-
this.renderToolbarShortcutLabel(
|
|
6544
|
-
searchTargetLabel,
|
|
6545
|
-
this.getSearchShortcutLabel()
|
|
6546
|
-
),
|
|
6547
|
-
html`
|
|
6548
|
-
<button
|
|
6549
|
-
class="toolbar-btn"
|
|
6550
|
-
@click=${this.openFlowSearch}
|
|
6551
|
-
?disabled=${!!this.viewingRevision}
|
|
6552
|
-
aria-label=${searchTargetLabel}
|
|
6553
|
-
>
|
|
6554
|
-
<temba-icon name=${Icon.search} size="1"></temba-icon>
|
|
6555
|
-
</button>
|
|
6556
|
-
`
|
|
6557
|
-
)}
|
|
6558
|
-
</div>
|
|
6559
|
-
</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>
|
|
6560
6288
|
`;
|
|
6561
6289
|
}
|
|
6562
6290
|
|
|
6563
|
-
private
|
|
6564
|
-
|
|
6565
|
-
|
|
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
|
+
}
|
|
6566
6323
|
}
|
|
6567
6324
|
|
|
6568
6325
|
/**
|
|
@@ -6721,7 +6478,7 @@ export class Editor extends RapidElement {
|
|
|
6721
6478
|
return html`${style} ${this.renderIssuesWindow()}
|
|
6722
6479
|
${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
|
|
6723
6480
|
<div id="editor-container">
|
|
6724
|
-
${this.
|
|
6481
|
+
${this.renderToolbarElement()}
|
|
6725
6482
|
<div id="editor">
|
|
6726
6483
|
${this.showMessageTable
|
|
6727
6484
|
? html`<temba-message-table></temba-message-table>`
|
|
@@ -6852,6 +6609,7 @@ export class Editor extends RapidElement {
|
|
|
6852
6609
|
`}
|
|
6853
6610
|
</div>
|
|
6854
6611
|
${this.renderPendingCard()}
|
|
6612
|
+
<div class="drag-hint" id="drag-hint">Hold ⇧ to duplicate</div>
|
|
6855
6613
|
</div>
|
|
6856
6614
|
<div class="loupe" id="loupe">
|
|
6857
6615
|
<div class="loupe-content" id="loupe-content"></div>
|