@nyaruka/temba-components 0.167.0 → 0.168.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/locales/es.js +3 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +3 -1
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +3 -1
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +2791 -1668
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/Icons.ts +2 -0
- package/src/display/Button.ts +8 -6
- package/src/display/Chat.ts +541 -191
- package/src/display/Dropdown.ts +40 -17
- package/src/display/Label.ts +25 -4
- package/src/display/TembaDate.ts +11 -2
- package/src/display/Tip.ts +157 -18
- package/src/events/eventRenderers.ts +678 -268
- package/src/events.ts +16 -1
- package/src/flow/NodeEditor.ts +82 -9
- package/src/flow/actions/add_contact_urn.ts +1 -0
- package/src/flow/actions/play_audio.ts +1 -0
- package/src/flow/actions/send_broadcast.ts +17 -1
- package/src/flow/actions/send_msg.ts +12 -1
- package/src/flow/actions/set_run_result.ts +1 -0
- package/src/flow/actions/start_session.ts +10 -1
- package/src/flow/nodes/shared-rules.ts +2 -0
- package/src/flow/nodes/split_by_airtime.ts +6 -0
- package/src/flow/nodes/split_by_expression.ts +1 -0
- package/src/flow/nodes/split_by_llm.ts +2 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +3 -1
- package/src/flow/nodes/split_by_random.ts +2 -1
- package/src/flow/nodes/split_by_resthook.ts +4 -1
- package/src/flow/nodes/split_by_webhook.ts +9 -1
- package/src/flow/types.ts +3 -0
- package/src/flow/utils.ts +49 -0
- package/src/form/DatePicker.ts +2 -1
- package/src/layout/Card.ts +9 -0
- package/src/layout/CardLayout.ts +426 -21
- package/src/layout/CardStack.ts +4 -1
- package/src/list/ContactList.ts +22 -17
- package/src/list/ContentList.ts +632 -29
- package/src/list/MsgList.ts +4 -2
- package/src/list/SortableList.ts +5 -1
- package/src/list/TembaMenu.ts +67 -6
- package/src/live/ContactChat.ts +655 -336
- package/src/live/ContactDetails.ts +961 -88
- package/src/live/ContactFieldEditor.ts +46 -11
- package/src/live/ContactFields.ts +57 -16
- package/src/live/ContactStoreElement.ts +15 -8
- package/src/locales/es.ts +3 -2
- package/src/locales/fr.ts +3 -2
- package/src/locales/pt.ts +3 -2
- package/src/simulator/Simulator.ts +28 -0
- package/src/styles/pillVariants.ts +13 -1
- package/web-test-runner.config.mjs +2 -0
- package/xliff/es.xlf +6 -3
- package/xliff/fr.xlf +6 -3
- package/xliff/pt.xlf +6 -3
|
@@ -37,12 +37,21 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
37
37
|
@property({ type: String })
|
|
38
38
|
iconClass = '';
|
|
39
39
|
|
|
40
|
+
@property({ type: String })
|
|
41
|
+
valueIcon = '';
|
|
42
|
+
|
|
43
|
+
@property({ type: String })
|
|
44
|
+
valueIconLabel = '';
|
|
45
|
+
|
|
40
46
|
@property({ type: String })
|
|
41
47
|
status: Status = Status.Ready;
|
|
42
48
|
|
|
43
49
|
@property({ type: Boolean })
|
|
44
50
|
disabled = false;
|
|
45
51
|
|
|
52
|
+
@property({ type: Boolean })
|
|
53
|
+
searchable = true;
|
|
54
|
+
|
|
46
55
|
@property({ type: Boolean })
|
|
47
56
|
dirty = false;
|
|
48
57
|
|
|
@@ -60,7 +69,7 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
60
69
|
--color-widget-bg: transparent;
|
|
61
70
|
--color-widget-bg-focused: #fff;
|
|
62
71
|
--widget-box-shadow: none;
|
|
63
|
-
padding-bottom: 0.6em;
|
|
72
|
+
padding-bottom: var(--contact-field-padding-bottom, 0.6em);
|
|
64
73
|
border-bottom: var(--contact-field-separator, 1px solid #ececec);
|
|
65
74
|
}
|
|
66
75
|
|
|
@@ -94,7 +103,7 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
.wrapper {
|
|
97
|
-
margin-bottom: 0.5em;
|
|
106
|
+
margin-bottom: var(--contact-field-wrapper-margin-bottom, 0.5em);
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
.field-label {
|
|
@@ -123,10 +132,19 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
.disabled .value {
|
|
126
|
-
color: var(--text-1);
|
|
135
|
+
color: var(--contact-field-value-color, var(--text-1));
|
|
127
136
|
margin-left: 0.25em;
|
|
128
137
|
margin-top: 0.35em;
|
|
129
138
|
min-height: 1.75em;
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 8px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.value-prefix {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
--icon-color: var(--contact-field-value-icon-color, var(--text-3));
|
|
130
148
|
}
|
|
131
149
|
|
|
132
150
|
.postfix {
|
|
@@ -337,9 +355,12 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
337
355
|
evt.stopPropagation();
|
|
338
356
|
}
|
|
339
357
|
|
|
340
|
-
public handleResponse(response: WebResponse) {
|
|
358
|
+
public handleResponse(response: WebResponse, savedValue?: string) {
|
|
341
359
|
if (response.status === 200) {
|
|
342
|
-
this.value =
|
|
360
|
+
this.value =
|
|
361
|
+
savedValue !== undefined
|
|
362
|
+
? savedValue
|
|
363
|
+
: response.json.fields?.[this.key] || '';
|
|
343
364
|
// this.status = Status.Success;
|
|
344
365
|
// on success lets go back to ready state for now
|
|
345
366
|
this.status = Status.Ready;
|
|
@@ -452,12 +473,14 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
452
473
|
@click=${this.handleIconClick}
|
|
453
474
|
>
|
|
454
475
|
${state}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
476
|
+
${this.searchable
|
|
477
|
+
? html`<temba-icon
|
|
478
|
+
class="search"
|
|
479
|
+
icon-action="search"
|
|
480
|
+
name="${Icon.search}"
|
|
481
|
+
animateclick="pulse"
|
|
482
|
+
></temba-icon>`
|
|
483
|
+
: null}
|
|
461
484
|
<temba-icon
|
|
462
485
|
class="copy"
|
|
463
486
|
icon-action="copy"
|
|
@@ -528,6 +551,18 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
528
551
|
>
|
|
529
552
|
<div class="label"><div class="name">${this.name}</div></div>
|
|
530
553
|
<div class="value">
|
|
554
|
+
${this.valueIcon
|
|
555
|
+
? html`<temba-tip
|
|
556
|
+
class="value-prefix"
|
|
557
|
+
text=${this.valueIconLabel}
|
|
558
|
+
position="top"
|
|
559
|
+
>
|
|
560
|
+
<temba-icon
|
|
561
|
+
name=${this.valueIcon}
|
|
562
|
+
aria-label=${this.valueIconLabel}
|
|
563
|
+
></temba-icon>
|
|
564
|
+
</temba-tip>`
|
|
565
|
+
: null}
|
|
531
566
|
${this.type === 'datetime'
|
|
532
567
|
? this.value
|
|
533
568
|
? html`<temba-date
|
|
@@ -10,7 +10,7 @@ import { getClasses, postJSON } from '../utils';
|
|
|
10
10
|
import { ContactFieldEditor } from './ContactFieldEditor';
|
|
11
11
|
import { ContactStoreElement } from './ContactStoreElement';
|
|
12
12
|
import { Checkbox } from '../form/Checkbox';
|
|
13
|
-
import { CustomEventType } from '../interfaces';
|
|
13
|
+
import { ContactField, CustomEventType } from '../interfaces';
|
|
14
14
|
|
|
15
15
|
const MIN_FOR_FILTER = 10;
|
|
16
16
|
|
|
@@ -123,13 +123,47 @@ export class ContactFields extends ContactStoreElement {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
private isAgent(): boolean {
|
|
126
|
-
return this.role === 'T';
|
|
126
|
+
return this.role === 'T' || this.role?.toLowerCase() === 'agent';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private getAgentAccess(field: ContactField): 'none' | 'view' | 'edit' {
|
|
130
|
+
const access = field.agent_access?.toLowerCase();
|
|
131
|
+
if (access === 'n' || access === 'none') return 'none';
|
|
132
|
+
if (access === 'e' || access === 'edit') return 'edit';
|
|
133
|
+
return 'view';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private canViewField(field: ContactField): boolean {
|
|
137
|
+
return !this.isAgent() || this.getAgentAccess(field) !== 'none';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private canEditField(field: ContactField): boolean {
|
|
141
|
+
return (
|
|
142
|
+
!this.disabled &&
|
|
143
|
+
(!this.isAgent() || this.getAgentAccess(field) === 'edit')
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private getVisibleFieldEntries(): [string, string][] {
|
|
148
|
+
if (!this.data || !this.fieldsReady) return [];
|
|
149
|
+
return Object.entries(this.data.fields).filter(
|
|
150
|
+
([key]: [string, string]) => {
|
|
151
|
+
const field = this.store.getContactField(key);
|
|
152
|
+
return !!field && this.canViewField(field);
|
|
153
|
+
}
|
|
154
|
+
);
|
|
127
155
|
}
|
|
128
156
|
|
|
129
157
|
public willUpdate(changed: PropertyValues): void {
|
|
130
158
|
super.willUpdate(changed);
|
|
131
|
-
if (
|
|
132
|
-
|
|
159
|
+
if (
|
|
160
|
+
(changed.has('data') ||
|
|
161
|
+
changed.has('fieldsReady') ||
|
|
162
|
+
changed.has('role')) &&
|
|
163
|
+
this.data &&
|
|
164
|
+
this.fieldsReady
|
|
165
|
+
) {
|
|
166
|
+
if (this.getVisibleFieldEntries().length <= MIN_FOR_FILTER) {
|
|
133
167
|
this.showAll = true;
|
|
134
168
|
}
|
|
135
169
|
}
|
|
@@ -139,19 +173,29 @@ export class ContactFields extends ContactStoreElement {
|
|
|
139
173
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
140
174
|
): void {
|
|
141
175
|
super.updated(changes);
|
|
142
|
-
if (
|
|
176
|
+
if (
|
|
177
|
+
(changes.has('data') ||
|
|
178
|
+
changes.has('fieldsReady') ||
|
|
179
|
+
changes.has('role')) &&
|
|
180
|
+
this.data &&
|
|
181
|
+
this.fieldsReady
|
|
182
|
+
) {
|
|
143
183
|
this.fireCustomEvent(CustomEventType.DetailsChanged, {
|
|
144
|
-
count:
|
|
184
|
+
count: this.getVisibleFieldEntries().filter(([, value]) => !!value)
|
|
185
|
+
.length
|
|
145
186
|
});
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
|
|
149
190
|
public handleFieldChanged(evt: InputEvent) {
|
|
150
191
|
const field = evt.currentTarget as ContactFieldEditor;
|
|
192
|
+
const definition = this.store.getContactField(field.key);
|
|
193
|
+
if (!definition || !this.canEditField(definition)) return;
|
|
194
|
+
|
|
151
195
|
const value = field.value;
|
|
152
196
|
|
|
153
197
|
// TODO: Use contact.postChanges instead of postJSON
|
|
154
|
-
postJSON(this.
|
|
198
|
+
postJSON(this.writeEndpoint + this.data.uuid, {
|
|
155
199
|
fields: { [field.key]: value }
|
|
156
200
|
})
|
|
157
201
|
.then((response: any) => {
|
|
@@ -179,9 +223,8 @@ export class ContactFields extends ContactStoreElement {
|
|
|
179
223
|
// guard against keys with no matching field definition (mid-load races
|
|
180
224
|
// or fields that have since been deleted) so a single missing
|
|
181
225
|
// definition can't throw and blank out the entire tab
|
|
182
|
-
const fieldsToShow =
|
|
183
|
-
|
|
184
|
-
.sort((a: [string, string], b: [string, string]) => {
|
|
226
|
+
const fieldsToShow = this.getVisibleFieldEntries().sort(
|
|
227
|
+
(a: [string, string], b: [string, string]) => {
|
|
185
228
|
const [ak] = a;
|
|
186
229
|
const [bk] = b;
|
|
187
230
|
const fieldA = this.store.getContactField(ak);
|
|
@@ -243,7 +286,8 @@ export class ContactFields extends ContactStoreElement {
|
|
|
243
286
|
}
|
|
244
287
|
|
|
245
288
|
return ak.localeCompare(bk);
|
|
246
|
-
}
|
|
289
|
+
}
|
|
290
|
+
);
|
|
247
291
|
|
|
248
292
|
if (fieldsToShow.length == 0) {
|
|
249
293
|
return html`<slot name="empty"></slot>`;
|
|
@@ -260,17 +304,14 @@ export class ContactFields extends ContactStoreElement {
|
|
|
260
304
|
type=${field.value_type}
|
|
261
305
|
@change=${this.handleFieldChanged}
|
|
262
306
|
timezone=${this.timezone}
|
|
263
|
-
?disabled=${
|
|
264
|
-
this.disabled
|
|
265
|
-
? true
|
|
266
|
-
: false}
|
|
307
|
+
?disabled=${!this.canEditField(field)}
|
|
267
308
|
></temba-contact-field>`;
|
|
268
309
|
});
|
|
269
310
|
|
|
270
311
|
return html`
|
|
271
312
|
<div class=${getClasses({ disabled: this.disabled })}>
|
|
272
313
|
<div class="fields ${this.showAll ? 'show-all' : ''}">${fields}</div>
|
|
273
|
-
${
|
|
314
|
+
${fieldsToShow.length >= MIN_FOR_FILTER
|
|
274
315
|
? html`<div class="toggle">
|
|
275
316
|
<div style="flex-grow: 1"></div>
|
|
276
317
|
<div>
|
|
@@ -18,14 +18,21 @@ export class ContactStoreElement extends EndpointMonitorElement {
|
|
|
18
18
|
@property({ type: Object, attribute: false })
|
|
19
19
|
data: Contact;
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
// Resolve each URN against a channel while retaining the user's priority
|
|
22
|
+
// order. Consumers can select the first channel-backed URN for messaging.
|
|
23
23
|
@property({ type: String })
|
|
24
|
-
endpoint = '/api/v2/contacts.json?expand_urns=true&uuid=';
|
|
24
|
+
endpoint = '/api/v2/contacts.json?expand_urns=true&urn_order=priority&uuid=';
|
|
25
|
+
|
|
26
|
+
// Writes go through the internal API, which responds with the same shape
|
|
27
|
+
// as the read endpoint above.
|
|
28
|
+
@property({ type: String })
|
|
29
|
+
writeEndpoint = '/api/internal/contacts.json?uuid=';
|
|
25
30
|
|
|
26
31
|
prepareData(data: any) {
|
|
27
|
-
if (data
|
|
28
|
-
data = data[0];
|
|
32
|
+
if (data) {
|
|
33
|
+
data = Array.isArray(data) ? data[0] : data;
|
|
34
|
+
}
|
|
35
|
+
if (data) {
|
|
29
36
|
data.groups.forEach((group: Group) => {
|
|
30
37
|
group.is_dynamic = this.store.isDynamicGroup(group.uuid);
|
|
31
38
|
});
|
|
@@ -53,16 +60,16 @@ export class ContactStoreElement extends EndpointMonitorElement {
|
|
|
53
60
|
// clear our cache so we don't have any races
|
|
54
61
|
this.store.removeFromCache(`${this.endpoint}${this.contact}`);
|
|
55
62
|
return this.store
|
|
56
|
-
.postJSON(`${this.
|
|
63
|
+
.postJSON(`${this.writeEndpoint}${this.contact}`, payload)
|
|
57
64
|
.then((response) => {
|
|
58
65
|
this.setContact(response.json);
|
|
59
66
|
});
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
public setContact(contact: any) {
|
|
69
|
+
public setContact(contact: any, contactId = this.contact) {
|
|
63
70
|
// make sure contact data is properly prepped
|
|
64
71
|
this.data = this.prepareData([contact]);
|
|
65
|
-
this.store.updateCache(`${this.endpoint}${
|
|
72
|
+
this.store.updateCache(`${this.endpoint}${contactId}`, this.data);
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
public willUpdate(changed: PropertyValues): void {
|
package/src/locales/es.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Re-generate this file by running lit-localize
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import {str} from '@lit/localize';
|
|
7
7
|
|
|
8
8
|
/* eslint-disable no-irregular-whitespace */
|
|
9
9
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
'scf1453991c986b25': `Tab para completar, enter para seleccionar`,
|
|
13
13
|
's73b4d70c02f4b4e0': `No options`,
|
|
14
14
|
'sbc913d7dc0f33877': `to add`,
|
|
15
|
-
'
|
|
15
|
+
's7722a91d3a512442': str`Last seen ${0}`,
|
|
16
|
+
's22965bb9808befb0': `Interrupt`,
|
|
16
17
|
's122d4de68bcfcdf4': `It's okay to restart`,
|
|
17
18
|
's3eb2567092b4d7c1': `from the beginning`,
|
|
18
19
|
's28f37776b3901438': `It's okay to interrupt`,
|
package/src/locales/fr.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Re-generate this file by running lit-localize
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import {str} from '@lit/localize';
|
|
7
7
|
|
|
8
8
|
/* eslint-disable no-irregular-whitespace */
|
|
9
9
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
's73b4d70c02f4b4e0': `No options`,
|
|
13
13
|
'scf1453991c986b25': `Tab to complete, enter to select`,
|
|
14
14
|
'sbc913d7dc0f33877': `to add`,
|
|
15
|
-
'
|
|
15
|
+
's7722a91d3a512442': str`Last seen ${0}`,
|
|
16
|
+
's22965bb9808befb0': `Interrupt`,
|
|
16
17
|
's122d4de68bcfcdf4': `It's okay to restart`,
|
|
17
18
|
's3eb2567092b4d7c1': `from the beginning`,
|
|
18
19
|
's28f37776b3901438': `It's okay to interrupt`,
|
package/src/locales/pt.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Re-generate this file by running lit-localize
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import {str} from '@lit/localize';
|
|
7
7
|
|
|
8
8
|
/* eslint-disable no-irregular-whitespace */
|
|
9
9
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
's73b4d70c02f4b4e0': `No options`,
|
|
13
13
|
'scf1453991c986b25': `Tab to complete, enter to select`,
|
|
14
14
|
'sbc913d7dc0f33877': `to add`,
|
|
15
|
-
'
|
|
15
|
+
's7722a91d3a512442': str`Last seen ${0}`,
|
|
16
|
+
's22965bb9808befb0': `Interrupt`,
|
|
16
17
|
's122d4de68bcfcdf4': `It's okay to restart`,
|
|
17
18
|
's3eb2567092b4d7c1': `from the beginning`,
|
|
18
19
|
's28f37776b3901438': `It's okay to interrupt`,
|
|
@@ -822,6 +822,10 @@ export class Simulator extends RapidElement {
|
|
|
822
822
|
private previousEventCount = 0;
|
|
823
823
|
private chat: Chat = null;
|
|
824
824
|
|
|
825
|
+
// bumped on reset so in-flight sprint responses started against the old
|
|
826
|
+
// session are discarded instead of polluting the restarted one
|
|
827
|
+
private sessionGeneration = 0;
|
|
828
|
+
|
|
825
829
|
@property({ type: Object })
|
|
826
830
|
private session: Session | null = null;
|
|
827
831
|
|
|
@@ -1207,10 +1211,17 @@ export class Simulator extends RapidElement {
|
|
|
1207
1211
|
}
|
|
1208
1212
|
};
|
|
1209
1213
|
|
|
1214
|
+
const generation = this.sessionGeneration;
|
|
1210
1215
|
try {
|
|
1211
1216
|
const response = await postJSON(this.endpoint, body);
|
|
1217
|
+
if (generation !== this.sessionGeneration) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1212
1220
|
this.updateRunContext(response.json as RunContext);
|
|
1213
1221
|
} catch (error) {
|
|
1222
|
+
if (generation !== this.sessionGeneration) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1214
1225
|
console.error('Failed to start simulation:', error);
|
|
1215
1226
|
const errorEvent = {
|
|
1216
1227
|
uuid: generateUUIDv7(),
|
|
@@ -1510,6 +1521,9 @@ export class Simulator extends RapidElement {
|
|
|
1510
1521
|
}
|
|
1511
1522
|
|
|
1512
1523
|
private async handleReset() {
|
|
1524
|
+
// invalidate any in-flight sprint responses from the old session
|
|
1525
|
+
this.sessionGeneration++;
|
|
1526
|
+
|
|
1513
1527
|
// reset simulation state
|
|
1514
1528
|
this.events = [];
|
|
1515
1529
|
this.session = null;
|
|
@@ -2068,15 +2082,29 @@ export class Simulator extends RapidElement {
|
|
|
2068
2082
|
}
|
|
2069
2083
|
};
|
|
2070
2084
|
|
|
2085
|
+
const generation = this.sessionGeneration;
|
|
2071
2086
|
try {
|
|
2072
2087
|
const response = await postJSON(this.endpoint, body);
|
|
2073
2088
|
|
|
2074
2089
|
// add a small delay before showing the reply to simulate typing
|
|
2075
2090
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
2076
2091
|
|
|
2092
|
+
// the simulation was reset while we were waiting, this reply belongs
|
|
2093
|
+
// to the old session. we deliberately don't touch `sprinting` here:
|
|
2094
|
+
// handleReset already cleared it when it bumped the generation (and
|
|
2095
|
+
// clearing it now could stomp a sprint the new session has started).
|
|
2096
|
+
// anything else that invalidates a generation must likewise reset
|
|
2097
|
+
// `sprinting` or the input stays disabled.
|
|
2098
|
+
if (generation !== this.sessionGeneration) {
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2077
2102
|
// pass null for msgInEvt since we already added it
|
|
2078
2103
|
this.updateRunContext(response.json as RunContext, null);
|
|
2079
2104
|
} catch (error) {
|
|
2105
|
+
if (generation !== this.sessionGeneration) {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2080
2108
|
console.error('Failed to resume simulation:', error);
|
|
2081
2109
|
const errorEvent = {
|
|
2082
2110
|
uuid: generateUUIDv7(),
|
|
@@ -43,7 +43,8 @@ export const PILL_TYPES: ReadonlySet<string> = new Set([
|
|
|
43
43
|
'keyword',
|
|
44
44
|
'channel',
|
|
45
45
|
'topic',
|
|
46
|
-
'campaign'
|
|
46
|
+
'campaign',
|
|
47
|
+
'removed'
|
|
47
48
|
]);
|
|
48
49
|
|
|
49
50
|
/** Default icon name for each pill variant. Used when a consumer
|
|
@@ -145,6 +146,17 @@ export const pillVariants = css`
|
|
|
145
146
|
border-color: #fde68a;
|
|
146
147
|
--icon-color: var(--field);
|
|
147
148
|
}
|
|
149
|
+
.pill-removed {
|
|
150
|
+
/* Removal pill — used when an entity is taken away (e.g. removed
|
|
151
|
+
from a group in the chat history). Fixed at the Tailwind red
|
|
152
|
+
ramp (red-100 bg / red-200 border / red-800 text) for the same
|
|
153
|
+
reason .pill-field is fixed at yellow: color-mixing a light red
|
|
154
|
+
into white washes out before it reads as "red". */
|
|
155
|
+
background: #fee2e2;
|
|
156
|
+
color: #991b1b;
|
|
157
|
+
border-color: #fecaca;
|
|
158
|
+
--icon-color: #991b1b;
|
|
159
|
+
}
|
|
148
160
|
.pill-keyword {
|
|
149
161
|
background: var(--sunken);
|
|
150
162
|
color: var(--text-1);
|
|
@@ -432,6 +432,8 @@ export default {
|
|
|
432
432
|
files: ['**/test/**/*.test.ts', '!**/test/utils.test.ts'],
|
|
433
433
|
nodeResolve: true,
|
|
434
434
|
concurrency: 4,
|
|
435
|
+
// screenshot-heavy test files can exceed the default 120s on slower CI runners
|
|
436
|
+
testsFinishTimeout: 240000,
|
|
435
437
|
filterBrowserLogs(log) {
|
|
436
438
|
return !log.args.some(
|
|
437
439
|
(arg) => typeof arg === 'string' && arg.includes('Lit is in dev mode')
|
package/xliff/es.xlf
CHANGED
|
@@ -24,9 +24,6 @@
|
|
|
24
24
|
<trans-unit id="sea4f08110bb8f15d">
|
|
25
25
|
<source>Remove</source>
|
|
26
26
|
</trans-unit>
|
|
27
|
-
<trans-unit id="s81f17cfc89a04338">
|
|
28
|
-
<source>Interrupt flow</source>
|
|
29
|
-
</trans-unit>
|
|
30
27
|
<trans-unit id="s122d4de68bcfcdf4">
|
|
31
28
|
<source>It's okay to restart</source>
|
|
32
29
|
</trans-unit>
|
|
@@ -39,6 +36,12 @@
|
|
|
39
36
|
<trans-unit id="s6838d20b10f7512a">
|
|
40
37
|
<source>and start this one</source>
|
|
41
38
|
</trans-unit>
|
|
39
|
+
<trans-unit id="s22965bb9808befb0">
|
|
40
|
+
<source>Interrupt</source>
|
|
41
|
+
</trans-unit>
|
|
42
|
+
<trans-unit id="s7722a91d3a512442">
|
|
43
|
+
<source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
|
|
44
|
+
</trans-unit>
|
|
42
45
|
</body>
|
|
43
46
|
</file>
|
|
44
47
|
</xliff>
|
package/xliff/fr.xlf
CHANGED
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
<trans-unit id="sea4f08110bb8f15d">
|
|
24
24
|
<source>Remove</source>
|
|
25
25
|
</trans-unit>
|
|
26
|
-
<trans-unit id="s81f17cfc89a04338">
|
|
27
|
-
<source>Interrupt flow</source>
|
|
28
|
-
</trans-unit>
|
|
29
26
|
<trans-unit id="s122d4de68bcfcdf4">
|
|
30
27
|
<source>It's okay to restart</source>
|
|
31
28
|
</trans-unit>
|
|
@@ -38,6 +35,12 @@
|
|
|
38
35
|
<trans-unit id="s6838d20b10f7512a">
|
|
39
36
|
<source>and start this one</source>
|
|
40
37
|
</trans-unit>
|
|
38
|
+
<trans-unit id="s22965bb9808befb0">
|
|
39
|
+
<source>Interrupt</source>
|
|
40
|
+
</trans-unit>
|
|
41
|
+
<trans-unit id="s7722a91d3a512442">
|
|
42
|
+
<source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
|
|
43
|
+
</trans-unit>
|
|
41
44
|
</body>
|
|
42
45
|
</file>
|
|
43
46
|
</xliff>
|
package/xliff/pt.xlf
CHANGED
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
<trans-unit id="sea4f08110bb8f15d">
|
|
24
24
|
<source>Remove</source>
|
|
25
25
|
</trans-unit>
|
|
26
|
-
<trans-unit id="s81f17cfc89a04338">
|
|
27
|
-
<source>Interrupt flow</source>
|
|
28
|
-
</trans-unit>
|
|
29
26
|
<trans-unit id="s122d4de68bcfcdf4">
|
|
30
27
|
<source>It's okay to restart</source>
|
|
31
28
|
</trans-unit>
|
|
@@ -38,6 +35,12 @@
|
|
|
38
35
|
<trans-unit id="s6838d20b10f7512a">
|
|
39
36
|
<source>and start this one</source>
|
|
40
37
|
</trans-unit>
|
|
38
|
+
<trans-unit id="s22965bb9808befb0">
|
|
39
|
+
<source>Interrupt</source>
|
|
40
|
+
</trans-unit>
|
|
41
|
+
<trans-unit id="s7722a91d3a512442">
|
|
42
|
+
<source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
|
|
43
|
+
</trans-unit>
|
|
41
44
|
</body>
|
|
42
45
|
</file>
|
|
43
46
|
</xliff>
|