@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
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { css, html, TemplateResult } from 'lit';
|
|
2
|
+
import { property, state } from 'lit/decorators.js';
|
|
3
|
+
import { RapidElement } from '../RapidElement';
|
|
4
|
+
import { CustomEventType } from '../interfaces';
|
|
5
|
+
import { Icon } from '../Icons';
|
|
6
|
+
|
|
7
|
+
export interface LanguageOption {
|
|
8
|
+
name: string;
|
|
9
|
+
value: string;
|
|
10
|
+
percent?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const PRIMARY_LANGUAGE_OPTION_VALUE = '__primary_language__';
|
|
14
|
+
|
|
15
|
+
export class EditorToolbar extends RapidElement {
|
|
16
|
+
static get styles() {
|
|
17
|
+
return css`
|
|
18
|
+
:host {
|
|
19
|
+
display: block;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.editor-toolbar {
|
|
23
|
+
--toolbar-control-height: 28px;
|
|
24
|
+
--toolbar-translation-control-height: 28px;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
padding: 6px 12px;
|
|
28
|
+
background: #fff;
|
|
29
|
+
border-bottom: 1px solid #e8e8e8;
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.toolbar-left {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 2px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.toolbar-right {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 2px;
|
|
44
|
+
margin-left: auto;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.toolbar-btn {
|
|
48
|
+
width: var(--toolbar-control-height);
|
|
49
|
+
height: var(--toolbar-control-height);
|
|
50
|
+
border: none;
|
|
51
|
+
background: transparent;
|
|
52
|
+
border-radius: var(--curvature);
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
padding: 0;
|
|
58
|
+
color: #888;
|
|
59
|
+
font-size: 16px;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
outline: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.toolbar-btn:focus {
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.toolbar-btn:focus-visible {
|
|
69
|
+
outline: 2px solid #0064c8;
|
|
70
|
+
outline-offset: 2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.toolbar-btn:hover {
|
|
74
|
+
background: rgba(0, 0, 0, 0.06);
|
|
75
|
+
color: #555;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.toolbar-btn:disabled {
|
|
79
|
+
opacity: 0.3;
|
|
80
|
+
cursor: default;
|
|
81
|
+
background: transparent;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.toolbar-btn.active {
|
|
85
|
+
background: rgba(0, 100, 200, 0.1);
|
|
86
|
+
color: #0064c8;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.toolbar-btn.active:hover {
|
|
90
|
+
background: rgba(0, 100, 200, 0.15);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.toolbar-tip {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.toolbar-divider {
|
|
99
|
+
width: 1px;
|
|
100
|
+
height: 16px;
|
|
101
|
+
background: #e0e0e0;
|
|
102
|
+
margin: 0 4px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.toolbar-group {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 4px;
|
|
109
|
+
height: var(--toolbar-control-height);
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
padding: 0 3px;
|
|
112
|
+
border: 1px solid #d7dce2;
|
|
113
|
+
border-radius: calc(var(--curvature) + 2px);
|
|
114
|
+
background: #f7f9fb;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.toolbar-group-divider {
|
|
118
|
+
width: 1px;
|
|
119
|
+
height: 18px;
|
|
120
|
+
background: #d7dce2;
|
|
121
|
+
margin: 0 2px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.toolbar-language {
|
|
125
|
+
position: relative;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.toolbar-language-group {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: 6px;
|
|
134
|
+
margin-left: 2px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.toolbar-zoom-group {
|
|
138
|
+
gap: 2px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.language-pill {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 6px;
|
|
145
|
+
background: #e9eef4;
|
|
146
|
+
color: #0064c8;
|
|
147
|
+
height: var(--toolbar-translation-control-height);
|
|
148
|
+
padding: 0 8px;
|
|
149
|
+
border-radius: var(--curvature);
|
|
150
|
+
box-sizing: border-box;
|
|
151
|
+
font-size: 13px;
|
|
152
|
+
font-weight: 400;
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
--icon-color: #0064c8;
|
|
156
|
+
--icon-size: 16px;
|
|
157
|
+
border: none;
|
|
158
|
+
outline: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.language-pill:hover {
|
|
162
|
+
filter: brightness(1.04);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.language-pill.primary {
|
|
166
|
+
background: #fff;
|
|
167
|
+
border: 1px solid #d7dce2;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.language-pill.complete {
|
|
171
|
+
background: #d4f5e0;
|
|
172
|
+
color: #1a7f37;
|
|
173
|
+
--icon-color: #1a7f37;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.language-pill-caret {
|
|
177
|
+
margin-left: 1px;
|
|
178
|
+
--icon-color: currentColor;
|
|
179
|
+
--icon-size: 12px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.language-percent {
|
|
183
|
+
display: inline-block;
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
font-weight: 700;
|
|
186
|
+
line-height: 1;
|
|
187
|
+
color: #0064c8;
|
|
188
|
+
white-space: nowrap;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.language-pill.complete .language-percent {
|
|
192
|
+
color: #1a7f37;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.toolbar-zoom-level {
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
min-width: 40px;
|
|
198
|
+
text-align: center;
|
|
199
|
+
color: #555;
|
|
200
|
+
font-weight: 500;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.toolbar-translation {
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 4px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.toolbar-btn.language-tool {
|
|
210
|
+
width: var(--toolbar-translation-control-height);
|
|
211
|
+
height: var(--toolbar-translation-control-height);
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@property({ type: Boolean, attribute: 'message-view' })
|
|
217
|
+
messageView = false;
|
|
218
|
+
|
|
219
|
+
@property({ type: Number })
|
|
220
|
+
zoom = 1.0;
|
|
221
|
+
|
|
222
|
+
@property({ type: Boolean, attribute: 'zoom-initialized' })
|
|
223
|
+
zoomInitialized = false;
|
|
224
|
+
|
|
225
|
+
@property({ type: Boolean, attribute: 'zoom-fitted' })
|
|
226
|
+
zoomFitted = false;
|
|
227
|
+
|
|
228
|
+
@property({ type: Boolean, attribute: 'revisions-active' })
|
|
229
|
+
revisionsActive = false;
|
|
230
|
+
|
|
231
|
+
@property({ type: Boolean, attribute: 'is-saving' })
|
|
232
|
+
isSaving = false;
|
|
233
|
+
|
|
234
|
+
@property({ type: Boolean, attribute: 'search-disabled' })
|
|
235
|
+
searchDisabled = false;
|
|
236
|
+
|
|
237
|
+
@property({ type: Array })
|
|
238
|
+
languageOptions: LanguageOption[] = [];
|
|
239
|
+
|
|
240
|
+
@property({ type: String, attribute: 'current-language-name' })
|
|
241
|
+
currentLanguageName = '';
|
|
242
|
+
|
|
243
|
+
@property({ type: Boolean, attribute: 'is-base-language' })
|
|
244
|
+
isBaseLanguage = true;
|
|
245
|
+
|
|
246
|
+
@property({ type: Number, attribute: 'language-percent' })
|
|
247
|
+
languagePercent = 0;
|
|
248
|
+
|
|
249
|
+
@property({ type: Boolean, attribute: 'show-localization-tools' })
|
|
250
|
+
showLocalizationTools = false;
|
|
251
|
+
|
|
252
|
+
@state()
|
|
253
|
+
private showLanguageOptions = false;
|
|
254
|
+
|
|
255
|
+
private fireToolbarAction(action: string, detail: any = {}): void {
|
|
256
|
+
this.fireCustomEvent(CustomEventType.ButtonClicked, { action, ...detail });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private handleLanguageIconClick(): void {
|
|
260
|
+
if (this.showLanguageOptions) {
|
|
261
|
+
this.showLanguageOptions = false;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.showLanguageOptions = true;
|
|
265
|
+
requestAnimationFrame(() => {
|
|
266
|
+
const close = () => {
|
|
267
|
+
this.showLanguageOptions = false;
|
|
268
|
+
document.removeEventListener('click', close);
|
|
269
|
+
};
|
|
270
|
+
document.addEventListener('click', close, { once: true });
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private handleLanguageOptionSelected(event: CustomEvent): void {
|
|
275
|
+
if (!this.showLanguageOptions) return;
|
|
276
|
+
const selected = event.detail?.selected;
|
|
277
|
+
if (selected?.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
278
|
+
this.fireToolbarAction('language-change', { isPrimary: true });
|
|
279
|
+
} else if (selected?.value) {
|
|
280
|
+
this.fireToolbarAction('language-change', {
|
|
281
|
+
languageCode: selected.value
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
this.showLanguageOptions = false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private isMacPlatform(): boolean {
|
|
288
|
+
return (
|
|
289
|
+
typeof navigator !== 'undefined' &&
|
|
290
|
+
/Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private getSearchShortcutLabel(): string {
|
|
295
|
+
return this.isMacPlatform() ? '⌘F' : 'Ctrl+F';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private renderTip(
|
|
299
|
+
text: string | TemplateResult,
|
|
300
|
+
content: TemplateResult
|
|
301
|
+
): TemplateResult {
|
|
302
|
+
return html`
|
|
303
|
+
<temba-tip
|
|
304
|
+
class="toolbar-tip"
|
|
305
|
+
.text=${typeof text === 'string' ? text : ''}
|
|
306
|
+
.content=${typeof text === 'string' ? null : text}
|
|
307
|
+
position="top"
|
|
308
|
+
>
|
|
309
|
+
${content}
|
|
310
|
+
</temba-tip>
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private renderShortcutLabel(
|
|
315
|
+
label: string,
|
|
316
|
+
shortcut: string
|
|
317
|
+
): TemplateResult {
|
|
318
|
+
return html`<span style="display:inline-flex; align-items:center; gap:8px;">
|
|
319
|
+
<span>${label}</span>
|
|
320
|
+
<kbd>${shortcut}</kbd>
|
|
321
|
+
</span>`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private renderLanguageOption(
|
|
325
|
+
option: LanguageOption,
|
|
326
|
+
selected: boolean
|
|
327
|
+
): TemplateResult {
|
|
328
|
+
if (option.value === PRIMARY_LANGUAGE_OPTION_VALUE) {
|
|
329
|
+
const primaryBackground = selected ? '#e1e8ef' : '#edf1f5';
|
|
330
|
+
return html`
|
|
331
|
+
<div
|
|
332
|
+
style="display:flex; align-items:center; justify-content:space-between; gap:8px; background:${primaryBackground}; color:#2f3f52; border-radius:4px; padding:6px 10px;"
|
|
333
|
+
>
|
|
334
|
+
<span>${option.name}</span>
|
|
335
|
+
<span
|
|
336
|
+
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;"
|
|
337
|
+
>Original</span
|
|
338
|
+
>
|
|
339
|
+
</div>
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const isComplete = option.percent === 100;
|
|
344
|
+
const optionBg = isComplete ? '#d4f5e0' : '';
|
|
345
|
+
const optionHoverBg = isComplete ? '#c0edce' : '';
|
|
346
|
+
const optionRadius = isComplete ? 'border-radius:4px;' : '';
|
|
347
|
+
const percentColor = isComplete ? 'color:#1a7f37;' : 'color:#5f6b7a;';
|
|
348
|
+
|
|
349
|
+
return html`
|
|
350
|
+
<div
|
|
351
|
+
style="display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px; ${optionBg ? `background:${optionBg};` : ''} ${optionRadius}"
|
|
352
|
+
@mouseenter=${isComplete
|
|
353
|
+
? (e: MouseEvent) => {
|
|
354
|
+
(e.currentTarget as HTMLElement).style.background = optionHoverBg;
|
|
355
|
+
}
|
|
356
|
+
: null}
|
|
357
|
+
@mouseleave=${isComplete
|
|
358
|
+
? (e: MouseEvent) => {
|
|
359
|
+
(e.currentTarget as HTMLElement).style.background = optionBg;
|
|
360
|
+
}
|
|
361
|
+
: null}
|
|
362
|
+
>
|
|
363
|
+
<span style="${isComplete ? 'color:#1a7f37;' : ''}"
|
|
364
|
+
>${option.name}</span
|
|
365
|
+
>
|
|
366
|
+
<span style="font-size:11px; font-weight:600; ${percentColor}"
|
|
367
|
+
>${option.percent ?? 0}%</span
|
|
368
|
+
>
|
|
369
|
+
</div>
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
public render(): TemplateResult {
|
|
374
|
+
const showLanguageControls = this.languageOptions.length > 1;
|
|
375
|
+
const searchTargetLabel = this.messageView
|
|
376
|
+
? 'Search table'
|
|
377
|
+
: 'Search flow';
|
|
378
|
+
|
|
379
|
+
return html`
|
|
380
|
+
<div class="editor-toolbar">
|
|
381
|
+
<div class="toolbar-left">
|
|
382
|
+
${this.renderTip(
|
|
383
|
+
'Flow View',
|
|
384
|
+
html`
|
|
385
|
+
<button
|
|
386
|
+
class="toolbar-btn ${!this.messageView ? 'active' : ''}"
|
|
387
|
+
@click=${() => this.fireToolbarAction('view-change', { view: 'flow' })}
|
|
388
|
+
aria-label="Flow View"
|
|
389
|
+
>
|
|
390
|
+
<temba-icon name="flow" size="1"></temba-icon>
|
|
391
|
+
</button>
|
|
392
|
+
`
|
|
393
|
+
)}
|
|
394
|
+
${this.renderTip(
|
|
395
|
+
'Table View',
|
|
396
|
+
html`
|
|
397
|
+
<button
|
|
398
|
+
class="toolbar-btn ${this.messageView ? 'active' : ''}"
|
|
399
|
+
@click=${() => this.fireToolbarAction('view-change', { view: 'table' })}
|
|
400
|
+
aria-label="Table View"
|
|
401
|
+
>
|
|
402
|
+
<temba-icon name=${Icon.quick_replies} size="1"></temba-icon>
|
|
403
|
+
</button>
|
|
404
|
+
`
|
|
405
|
+
)}
|
|
406
|
+
${showLanguageControls
|
|
407
|
+
? html`
|
|
408
|
+
<div class="toolbar-divider"></div>
|
|
409
|
+
<div class="toolbar-language-group">
|
|
410
|
+
<div class="toolbar-language">
|
|
411
|
+
${this.renderTip(
|
|
412
|
+
'Change language',
|
|
413
|
+
html`
|
|
414
|
+
<button
|
|
415
|
+
class="language-pill ${this.isBaseLanguage ? 'primary' : this.languagePercent === 100 ? 'complete' : ''}"
|
|
416
|
+
id="language-btn"
|
|
417
|
+
@click=${this.handleLanguageIconClick}
|
|
418
|
+
aria-label="Change language"
|
|
419
|
+
>
|
|
420
|
+
<temba-icon name=${Icon.language}></temba-icon>
|
|
421
|
+
<span>${this.currentLanguageName}</span>
|
|
422
|
+
${!this.isBaseLanguage
|
|
423
|
+
? html`<span class="language-percent"
|
|
424
|
+
>${this.languagePercent}%</span
|
|
425
|
+
>`
|
|
426
|
+
: ''}
|
|
427
|
+
<temba-icon
|
|
428
|
+
class="language-pill-caret"
|
|
429
|
+
name=${this.showLanguageOptions
|
|
430
|
+
? Icon.arrow_up
|
|
431
|
+
: Icon.arrow_down}
|
|
432
|
+
></temba-icon>
|
|
433
|
+
</button>
|
|
434
|
+
`
|
|
435
|
+
)}
|
|
436
|
+
<temba-options
|
|
437
|
+
.anchorTo=${this.shadowRoot?.querySelector('#language-btn') as HTMLElement}
|
|
438
|
+
.options=${this.languageOptions}
|
|
439
|
+
.renderOption=${this.renderLanguageOption}
|
|
440
|
+
?visible=${this.showLanguageOptions}
|
|
441
|
+
@temba-selection=${this.handleLanguageOptionSelected}
|
|
442
|
+
style="--temba-options-option-margin:4px; --temba-options-option-padding:0; --temba-options-option-radius:4px;"
|
|
443
|
+
min-width="230"
|
|
444
|
+
></temba-options>
|
|
445
|
+
</div>
|
|
446
|
+
${this.showLocalizationTools
|
|
447
|
+
? this.renderTranslationTools()
|
|
448
|
+
: ''}
|
|
449
|
+
</div>
|
|
450
|
+
`
|
|
451
|
+
: ''}
|
|
452
|
+
</div>
|
|
453
|
+
<div class="toolbar-right">
|
|
454
|
+
${!this.messageView
|
|
455
|
+
? html`
|
|
456
|
+
${this.renderTip(
|
|
457
|
+
'Zoom to fit',
|
|
458
|
+
html`
|
|
459
|
+
<button
|
|
460
|
+
class="toolbar-btn"
|
|
461
|
+
@click=${() => this.fireToolbarAction('zoom-to-fit')}
|
|
462
|
+
?disabled=${!this.zoomInitialized || this.zoomFitted}
|
|
463
|
+
aria-label="Zoom to fit"
|
|
464
|
+
>
|
|
465
|
+
<temba-icon
|
|
466
|
+
name=${Icon.zoom_fit}
|
|
467
|
+
size="1"
|
|
468
|
+
></temba-icon>
|
|
469
|
+
</button>
|
|
470
|
+
`
|
|
471
|
+
)}
|
|
472
|
+
<div class="toolbar-divider"></div>
|
|
473
|
+
${this.renderTip(
|
|
474
|
+
'Zoom out',
|
|
475
|
+
html`
|
|
476
|
+
<button
|
|
477
|
+
class="toolbar-btn"
|
|
478
|
+
@click=${() => this.fireToolbarAction('zoom-out')}
|
|
479
|
+
?disabled=${!this.zoomInitialized || this.zoom <= 0.3}
|
|
480
|
+
aria-label="Zoom out"
|
|
481
|
+
>
|
|
482
|
+
−
|
|
483
|
+
</button>
|
|
484
|
+
`
|
|
485
|
+
)}
|
|
486
|
+
<span class="toolbar-zoom-level"
|
|
487
|
+
>${this.zoomInitialized
|
|
488
|
+
? `${Math.round(this.zoom * 100)}%`
|
|
489
|
+
: ''}</span
|
|
490
|
+
>
|
|
491
|
+
${this.renderTip(
|
|
492
|
+
'Zoom in',
|
|
493
|
+
html`
|
|
494
|
+
<button
|
|
495
|
+
class="toolbar-btn"
|
|
496
|
+
@click=${() => this.fireToolbarAction('zoom-in')}
|
|
497
|
+
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
498
|
+
aria-label="Zoom in"
|
|
499
|
+
>
|
|
500
|
+
+
|
|
501
|
+
</button>
|
|
502
|
+
`
|
|
503
|
+
)}
|
|
504
|
+
<div class="toolbar-divider"></div>
|
|
505
|
+
${this.renderTip(
|
|
506
|
+
'Zoom to 100%',
|
|
507
|
+
html`
|
|
508
|
+
<button
|
|
509
|
+
class="toolbar-btn"
|
|
510
|
+
@click=${() => this.fireToolbarAction('zoom-to-full')}
|
|
511
|
+
?disabled=${!this.zoomInitialized || this.zoom >= 1.0}
|
|
512
|
+
aria-label="Zoom to 100%"
|
|
513
|
+
>
|
|
514
|
+
<temba-icon
|
|
515
|
+
name=${Icon.zoom_in}
|
|
516
|
+
size="1"
|
|
517
|
+
></temba-icon>
|
|
518
|
+
</button>
|
|
519
|
+
`
|
|
520
|
+
)}
|
|
521
|
+
<div class="toolbar-divider"></div>
|
|
522
|
+
`
|
|
523
|
+
: ''}
|
|
524
|
+
${this.renderTip(
|
|
525
|
+
'Revisions',
|
|
526
|
+
html`
|
|
527
|
+
<button
|
|
528
|
+
class="toolbar-btn ${this.revisionsActive ? 'active' : ''}"
|
|
529
|
+
@click=${() => this.fireToolbarAction('revisions')}
|
|
530
|
+
aria-label="Revisions"
|
|
531
|
+
>
|
|
532
|
+
<temba-icon
|
|
533
|
+
name=${this.isSaving ? 'progress_spinner' : 'revisions'}
|
|
534
|
+
size="1"
|
|
535
|
+
?spin=${this.isSaving}
|
|
536
|
+
></temba-icon>
|
|
537
|
+
</button>
|
|
538
|
+
`
|
|
539
|
+
)}
|
|
540
|
+
<div class="toolbar-divider"></div>
|
|
541
|
+
${this.renderTip(
|
|
542
|
+
this.renderShortcutLabel(
|
|
543
|
+
searchTargetLabel,
|
|
544
|
+
this.getSearchShortcutLabel()
|
|
545
|
+
),
|
|
546
|
+
html`
|
|
547
|
+
<button
|
|
548
|
+
class="toolbar-btn"
|
|
549
|
+
@click=${() => this.fireToolbarAction('search')}
|
|
550
|
+
?disabled=${this.searchDisabled}
|
|
551
|
+
aria-label=${searchTargetLabel}
|
|
552
|
+
>
|
|
553
|
+
<temba-icon name=${Icon.search} size="1"></temba-icon>
|
|
554
|
+
</button>
|
|
555
|
+
`
|
|
556
|
+
)}
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private renderTranslationTools(): TemplateResult {
|
|
563
|
+
// auto translate button hidden pending backend changes
|
|
564
|
+
return html``;
|
|
565
|
+
}
|
|
566
|
+
}
|
package/src/flow/StickyNote.ts
CHANGED
|
@@ -26,9 +26,13 @@ export class StickyNote extends RapidElement {
|
|
|
26
26
|
@property({ type: Boolean })
|
|
27
27
|
private removing = false;
|
|
28
28
|
|
|
29
|
-
// On touch devices, contenteditable starts false to prevent Apple
|
|
30
|
-
// Scribble from hijacking touches. It is set to true on explicit tap.
|
|
31
|
-
|
|
29
|
+
// On Apple touch devices, contenteditable starts false to prevent Apple
|
|
30
|
+
// Pencil Scribble from hijacking touches. It is set to true on explicit tap.
|
|
31
|
+
// Only Apple platforms have Scribble, so we avoid disabling contenteditable
|
|
32
|
+
// on Windows touchscreen laptops where it would block mouse-based editing.
|
|
33
|
+
private isTouchDevice =
|
|
34
|
+
navigator.maxTouchPoints > 0 &&
|
|
35
|
+
/iPad|iPhone|Macintosh/.test(navigator.userAgent);
|
|
32
36
|
private editingField: HTMLElement | null = null;
|
|
33
37
|
private removalTimeout: number | null = null;
|
|
34
38
|
|
|
@@ -88,6 +92,24 @@ export class StickyNote extends RapidElement {
|
|
|
88
92
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
:host(.drag-copy) .sticky-note {
|
|
96
|
+
outline: 3px dashed var(--color-primary, #3b82f6);
|
|
97
|
+
outline-offset: 2px;
|
|
98
|
+
opacity: 0.7;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.sticky-note.selected {
|
|
102
|
+
cursor: var(--shift-held-cursor, move) !important;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.sticky-note.selected .remove-button {
|
|
106
|
+
cursor: pointer !important;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sticky-note.selected [contenteditable] {
|
|
110
|
+
cursor: text !important;
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
/* Color themes */
|
|
92
114
|
.sticky-note.yellow {
|
|
93
115
|
--sticky-color: #fef08a;
|
|
@@ -191,7 +213,7 @@ export class StickyNote extends RapidElement {
|
|
|
191
213
|
/* Drag icon */
|
|
192
214
|
.sticky-title-container > .drag-handle {
|
|
193
215
|
--icon-color: var(--sticky-border-color);
|
|
194
|
-
cursor: move;
|
|
216
|
+
cursor: var(--shift-held-cursor, move);
|
|
195
217
|
max-width: 20px;
|
|
196
218
|
padding-left: 8px;
|
|
197
219
|
padding-top: 10px;
|
|
@@ -769,7 +791,9 @@ export class StickyNote extends RapidElement {
|
|
|
769
791
|
<div
|
|
770
792
|
class="sticky-note ${this.data.color} ${this.dragging
|
|
771
793
|
? 'dragging'
|
|
772
|
-
: ''} ${this.removing ? 'removing' : ''}
|
|
794
|
+
: ''} ${this.removing ? 'removing' : ''} ${this.selected
|
|
795
|
+
? 'selected'
|
|
796
|
+
: ''}"
|
|
773
797
|
style="${style}"
|
|
774
798
|
data-uuid="${this.uuid}"
|
|
775
799
|
>
|
|
@@ -8,17 +8,14 @@ import {
|
|
|
8
8
|
} from '../types';
|
|
9
9
|
import { Node, SetContactLanguage } from '../../store/flow-definition';
|
|
10
10
|
import { getStore } from '../../store/Store';
|
|
11
|
-
import { renderClamped } from '../utils';
|
|
11
|
+
import { getLanguageDisplayName, renderClamped } from '../utils';
|
|
12
12
|
|
|
13
13
|
export const set_contact_language: ActionConfig = {
|
|
14
14
|
name: 'Update Language',
|
|
15
15
|
group: ACTION_GROUPS.contacts,
|
|
16
16
|
flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
|
|
17
17
|
render: (_node: Node, action: SetContactLanguage) => {
|
|
18
|
-
const
|
|
19
|
-
type: 'language'
|
|
20
|
-
});
|
|
21
|
-
const name = languageNames.of(action.language) || action.language;
|
|
18
|
+
const name = getLanguageDisplayName(action.language);
|
|
22
19
|
return renderClamped(
|
|
23
20
|
html`Set to <strong>${name}</strong>`,
|
|
24
21
|
`Set to ${name}`
|
|
@@ -38,12 +35,9 @@ export const set_contact_language: ActionConfig = {
|
|
|
38
35
|
const store = getStore();
|
|
39
36
|
const workspace = store?.getState().workspace;
|
|
40
37
|
if (workspace?.languages && Array.isArray(workspace.languages)) {
|
|
41
|
-
const languageNames = new Intl.DisplayNames(['en'], {
|
|
42
|
-
type: 'language'
|
|
43
|
-
});
|
|
44
38
|
return workspace.languages.map((languageCode: string) => ({
|
|
45
39
|
value: languageCode,
|
|
46
|
-
name:
|
|
40
|
+
name: getLanguageDisplayName(languageCode)
|
|
47
41
|
}));
|
|
48
42
|
}
|
|
49
43
|
return [];
|
|
@@ -53,14 +47,11 @@ export const set_contact_language: ActionConfig = {
|
|
|
53
47
|
toFormData: (action: SetContactLanguage) => {
|
|
54
48
|
// Convert the language code back to the option object format expected by the form
|
|
55
49
|
if (action.language) {
|
|
56
|
-
const languageNames = new Intl.DisplayNames(['en'], {
|
|
57
|
-
type: 'language'
|
|
58
|
-
});
|
|
59
50
|
return {
|
|
60
51
|
language: [
|
|
61
52
|
{
|
|
62
53
|
value: action.language,
|
|
63
|
-
name:
|
|
54
|
+
name: getLanguageDisplayName(action.language)
|
|
64
55
|
}
|
|
65
56
|
],
|
|
66
57
|
uuid: action.uuid
|
package/src/flow/utils.ts
CHANGED
|
@@ -6,6 +6,17 @@ import { tokenize, TokenType } from '../excellent/tokenizer';
|
|
|
6
6
|
import { TOKEN_COLORS } from '../excellent/token-styles';
|
|
7
7
|
import { messageParser, sessionParser } from '../excellent/helpers';
|
|
8
8
|
|
|
9
|
+
const languageNames = new Intl.DisplayNames(['en'], { type: 'language' });
|
|
10
|
+
|
|
11
|
+
export function getLanguageDisplayName(code: string): string {
|
|
12
|
+
if (code === 'und') return 'Unknown';
|
|
13
|
+
try {
|
|
14
|
+
return languageNames.of(code) || code;
|
|
15
|
+
} catch {
|
|
16
|
+
return code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
const IS_MAC =
|
|
10
21
|
typeof navigator !== 'undefined' &&
|
|
11
22
|
/Mac|iPod|iPhone|iPad/.test(navigator.platform);
|