@nyaruka/temba-components 0.164.0 → 0.165.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 +13 -0
- package/DEV_DATA.md +11 -11
- package/README.md +4 -4
- package/TEST_OPTIMIZATION.md +8 -11
- package/dist/locales/es.js +4 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +4 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +4 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1555 -484
- package/dist/temba-components.js.map +1 -1
- package/orca/setup.sh +2 -2
- package/package.json +13 -18
- package/scripts/dev-data-sync.mjs +4 -4
- package/src/Icons.ts +3 -2
- package/src/display/ProgressBar.ts +15 -2
- package/src/form/Compose.ts +31 -1
- package/src/form/ContactSearch.ts +229 -126
- package/src/interfaces.ts +52 -0
- package/src/layout/Card.ts +25 -0
- package/src/layout/CardLayout.ts +3 -3
- package/src/layout/HeaderBar.ts +4 -1
- package/src/list/BroadcastList.ts +912 -0
- package/src/list/ContentList.ts +46 -10
- package/src/list/FieldList.ts +1057 -0
- package/src/list/SortableList.ts +7 -2
- package/src/list/TembaList.ts +8 -0
- package/src/list/TembaMenu.ts +5 -38
- package/src/live/CampaignEvents.ts +33 -19
- package/src/live/ContactChat.ts +7 -1
- package/src/live/ContactDetails.ts +20 -13
- package/src/live/ContactFieldEditor.ts +7 -3
- package/src/live/ContactStoreElement.ts +3 -1
- package/src/live/ContactTimeline.ts +28 -7
- package/src/locales/es.ts +4 -0
- package/src/locales/fr.ts +4 -0
- package/src/locales/pt.ts +4 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/packs/2-temba/star-filled.svg +3 -0
- package/static/svg/work/traced/star-filled.svg +1 -0
- package/static/svg/work/used/star-filled.svg +3 -0
- package/stress-test.js +3 -3
- package/temba-modules.ts +4 -0
- package/web-test-runner.config.mjs +1 -1
- package/xliff/es.xlf +12 -0
- package/xliff/fr.xlf +12 -0
- package/xliff/pt.xlf +12 -0
package/orca/setup.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Orca setup hook for a temba-components worktree.
|
|
4
4
|
# Symlinks shared utility files from nyaruka/utils, ensures the devcontainer
|
|
5
5
|
# is built/running (recreating it if the bind mounts are stale), and installs
|
|
6
|
-
#
|
|
6
|
+
# bun dependencies inside the worktree.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
9
|
# ./orca/setup.sh # run directly
|
|
@@ -75,7 +75,7 @@ esac
|
|
|
75
75
|
# Install dependencies inside the worktree.
|
|
76
76
|
docker exec "$CONTAINER_NAME" bash -c '
|
|
77
77
|
cd "/workspaces/worktrees/temba-components/'"$WORKSPACE_NAME"'"
|
|
78
|
-
|
|
78
|
+
bun install
|
|
79
79
|
'
|
|
80
80
|
|
|
81
81
|
echo "Worktree '$WORKSPACE_NAME' ready for development"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyaruka/temba-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.165.0",
|
|
4
4
|
"description": "Web components to support rapidpro and related projects",
|
|
5
5
|
"author": "Nyaruka <code@nyaruka.coim>",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,25 +9,25 @@
|
|
|
9
9
|
"license": "AGPL-3.0-only",
|
|
10
10
|
"repository": "https://github.com/nyaruka/temba-components/",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"start": "concurrently --kill-others --names tsc,web-dev-server \"
|
|
12
|
+
"start": "concurrently --kill-others --names tsc,web-dev-server \"bun run tsc:watch\" \"web-dev-server --app-index demo/index.html --node-resolve --watch --port 3010 --esbuild-target auto\"",
|
|
13
13
|
"tsc:watch": "tsc --watch",
|
|
14
|
-
"build": "rimraf dist &&
|
|
14
|
+
"build": "rimraf dist && bun run svg && tsc && rollup -c rollup.components.mjs ",
|
|
15
15
|
"build-wc": "rollup -c rollup.webchat.mjs",
|
|
16
|
-
"dev": "
|
|
17
|
-
"pre-commit": "
|
|
16
|
+
"dev": "bun run build && cp -R ./dist/* ../temba/rapidpro/node_modules/@nyaruka/temba-components/dist/ && cp -R ./dist/* ../floweditor/node_modules/@nyaruka/temba-components/dist/",
|
|
17
|
+
"pre-commit": "bun run locale:extract && bun run locale:build && bun run svg && git add ./src/Icons.ts ./static/svg/index.svg ./xliff ./src/locales && lint-staged",
|
|
18
18
|
"format:eslint": "eslint --ext .ts . --fix --ignore-path .gitignore --ignore-pattern \"src/locales/\"",
|
|
19
19
|
"format:prettier": "prettier \"**/*.js\" \"**/*.ts\" \"!src/locales/**\" --config .prettierrc --write --ignore-path .gitignore",
|
|
20
|
-
"format": "
|
|
20
|
+
"format": "bun run format:eslint && bun run format:prettier",
|
|
21
21
|
"test": "wtr --node-resolve",
|
|
22
22
|
"test:fast": "wtr --node-resolve --fast",
|
|
23
23
|
"test:coverage": "wtr --node-resolve --coverage",
|
|
24
24
|
"check-coverage": "node check-coverage.js",
|
|
25
|
-
"validate": "
|
|
25
|
+
"validate": "bun run format && bun run build && bun run test:coverage && bun run check-coverage",
|
|
26
26
|
"test:watch": "wtr --node-resolve --watch",
|
|
27
27
|
"stress-test": "node stress-test.js",
|
|
28
28
|
"svg": "rimraf static/svg/work && node svg.js --resolution=150 --output='./static/svg/index.svg' --usage='./src/Icons.ts'",
|
|
29
29
|
"svg-wc": "rimraf static/svg/work && node svg.js --resolution=150 --output='./static/svg/webchat.svg' --usage='./src/webchat/index.ts'",
|
|
30
|
-
"version": "
|
|
30
|
+
"version": "bun run build && auto-changelog -p && git add CHANGELOG.md",
|
|
31
31
|
"locale:extract": "lit-localize extract --config localize.config.json",
|
|
32
32
|
"locale:build": "echo 'Building localizations..' && lit-localize build --config localize.config.json > dist/locale.out 2>&1",
|
|
33
33
|
"dev-data:sync": "node scripts/dev-data-sync.mjs copy",
|
|
@@ -112,14 +112,9 @@
|
|
|
112
112
|
"typescript": "5.9.3",
|
|
113
113
|
"yargs": "^17.7.2"
|
|
114
114
|
},
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"@types/minimatch": "5.1.2",
|
|
121
|
-
"@types/chai": "4.3.20"
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
"packageManager": "pnpm@9.15.4"
|
|
115
|
+
"overrides": {
|
|
116
|
+
"@open-wc/testing-helpers": "3.0.0",
|
|
117
|
+
"@types/minimatch": "5.1.2",
|
|
118
|
+
"@types/chai": "4.3.20"
|
|
119
|
+
}
|
|
125
120
|
}
|
|
@@ -117,7 +117,7 @@ function showStatus() {
|
|
|
117
117
|
|
|
118
118
|
if (!fs.existsSync(DEV_DATA_DIR)) {
|
|
119
119
|
console.log('❌ No development data directory found');
|
|
120
|
-
console.log('💡 Run the dev server to initialize, or use "
|
|
120
|
+
console.log('💡 Run the dev server to initialize, or use "bun run dev-data:reset"');
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -175,8 +175,8 @@ switch (command) {
|
|
|
175
175
|
console.log(' reset/wipe - Wipe dev data and restore defaults');
|
|
176
176
|
console.log(' status - Show current status');
|
|
177
177
|
console.log('');
|
|
178
|
-
console.log('Usage:
|
|
179
|
-
console.log('
|
|
180
|
-
console.log('
|
|
178
|
+
console.log('Usage: bun run dev-data:copy');
|
|
179
|
+
console.log(' bun run dev-data:reset');
|
|
180
|
+
console.log(' bun run dev-data:status');
|
|
181
181
|
process.exit(1);
|
|
182
182
|
}
|
package/src/Icons.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
|
|
2
|
-
// for cache busting we dynamically generate a fingerprint, use
|
|
3
|
-
export const SVG_FINGERPRINT = '
|
|
2
|
+
// for cache busting we dynamically generate a fingerprint, use bun run svg to update
|
|
3
|
+
export const SVG_FINGERPRINT = '27198f759c371df944f8ec5de0a29dd6';
|
|
4
4
|
|
|
5
5
|
// only icons below are included in the sprite sheet
|
|
6
6
|
export enum Icon {
|
|
@@ -132,6 +132,7 @@ export enum Icon {
|
|
|
132
132
|
progress_spinner = 'refresh-cw-04',
|
|
133
133
|
refresh = 'refresh-cw-05',
|
|
134
134
|
featured = 'star-01',
|
|
135
|
+
featured_filled = 'star-filled',
|
|
135
136
|
quick_replies = 'dotpoints-01',
|
|
136
137
|
recording = 'microphone-01',
|
|
137
138
|
resend = 'refresh-cw-05',
|
|
@@ -108,6 +108,12 @@ export class ProgressBar extends RapidElement {
|
|
|
108
108
|
transition: flex-basis 2s;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/* At 0% an animated meter keeps a sliver of bar so its moving
|
|
112
|
+
stripes show the work is underway rather than a dead track. */
|
|
113
|
+
.meter.zero > span {
|
|
114
|
+
min-width: 1.25em;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
.meter .incomplete {
|
|
112
118
|
flex-grow: 1;
|
|
113
119
|
}
|
|
@@ -163,6 +169,11 @@ export class ProgressBar extends RapidElement {
|
|
|
163
169
|
@property({ type: Boolean })
|
|
164
170
|
showPercentage = false;
|
|
165
171
|
|
|
172
|
+
/** Suppresses the trailing percentage / countdown box outright —
|
|
173
|
+
* for compact placements where the meter alone is the display. */
|
|
174
|
+
@property({ type: Boolean })
|
|
175
|
+
hidePercentage = false;
|
|
176
|
+
|
|
166
177
|
@property({ type: String })
|
|
167
178
|
message: string;
|
|
168
179
|
|
|
@@ -194,7 +205,8 @@ export class ProgressBar extends RapidElement {
|
|
|
194
205
|
const meterClasses = [
|
|
195
206
|
'meter',
|
|
196
207
|
this.done ? 'done' : '',
|
|
197
|
-
this.animated ? '' : 'static'
|
|
208
|
+
this.animated ? '' : 'static',
|
|
209
|
+
this.animated && !this.done && this.pct === 0 ? 'zero' : ''
|
|
198
210
|
]
|
|
199
211
|
.filter(Boolean)
|
|
200
212
|
.join(' ');
|
|
@@ -208,7 +220,8 @@ export class ProgressBar extends RapidElement {
|
|
|
208
220
|
<div class="incomplete"></div>
|
|
209
221
|
</div>
|
|
210
222
|
|
|
211
|
-
${this.showPercentage || this.showEstimatedCompletion
|
|
223
|
+
${(this.showPercentage || this.showEstimatedCompletion) &&
|
|
224
|
+
!this.hidePercentage
|
|
212
225
|
? html`<div class="etc">
|
|
213
226
|
<div>
|
|
214
227
|
${this.estimatedCompletionDate &&
|
package/src/form/Compose.ts
CHANGED
|
@@ -406,11 +406,25 @@ export class Compose extends FieldElement {
|
|
|
406
406
|
if (!this.hasAttribute('tabindex')) {
|
|
407
407
|
this.setAttribute('tabindex', '-1');
|
|
408
408
|
}
|
|
409
|
+
// both phases: capture claims a send-Enter before the rich editor's
|
|
410
|
+
// own keydown inserts a newline (which would flash before the send
|
|
411
|
+
// clears it); bubble keeps Enter-to-send working from anywhere else
|
|
412
|
+
// in the compose after inner widgets have had their shot at it
|
|
413
|
+
this.addEventListener(
|
|
414
|
+
'keydown',
|
|
415
|
+
this.handleHostKeyDown as EventListener,
|
|
416
|
+
true
|
|
417
|
+
);
|
|
409
418
|
this.addEventListener('keydown', this.handleHostKeyDown as EventListener);
|
|
410
419
|
}
|
|
411
420
|
|
|
412
421
|
disconnectedCallback() {
|
|
413
422
|
super.disconnectedCallback();
|
|
423
|
+
this.removeEventListener(
|
|
424
|
+
'keydown',
|
|
425
|
+
this.handleHostKeyDown as EventListener,
|
|
426
|
+
true
|
|
427
|
+
);
|
|
414
428
|
this.removeEventListener(
|
|
415
429
|
'keydown',
|
|
416
430
|
this.handleHostKeyDown as EventListener
|
|
@@ -449,10 +463,23 @@ export class Compose extends FieldElement {
|
|
|
449
463
|
}
|
|
450
464
|
|
|
451
465
|
if (evt.key === 'Enter' && !evt.shiftKey) {
|
|
466
|
+
// an Enter confirming an IME composition belongs to the editor
|
|
467
|
+
if (evt.isComposing || evt.keyCode === 229) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
452
470
|
if (this.showShortcuts) {
|
|
453
471
|
return;
|
|
454
472
|
}
|
|
455
473
|
const editor = this.getMessageEditor();
|
|
474
|
+
// during capture only editor presses are claimed — anything else
|
|
475
|
+
// (quick replies, attachments) waits for the bubble so the widget
|
|
476
|
+
// it targets keeps first claim on the event
|
|
477
|
+
if (
|
|
478
|
+
evt.eventPhase === Event.CAPTURING_PHASE &&
|
|
479
|
+
!(editor && evt.composedPath().includes(editor))
|
|
480
|
+
) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
456
483
|
if (editor) {
|
|
457
484
|
const richEdit = editor.getRichEditor();
|
|
458
485
|
if (richEdit && richEdit.hasVisibleOptions()) {
|
|
@@ -460,7 +487,10 @@ export class Compose extends FieldElement {
|
|
|
460
487
|
}
|
|
461
488
|
}
|
|
462
489
|
evt.preventDefault();
|
|
463
|
-
|
|
490
|
+
// immediate: the handler is registered for both phases, and an
|
|
491
|
+
// at-target event would otherwise reach the second registration
|
|
492
|
+
// and send twice
|
|
493
|
+
evt.stopImmediatePropagation();
|
|
464
494
|
this.triggerSend();
|
|
465
495
|
}
|
|
466
496
|
};
|
|
@@ -226,12 +226,33 @@ export class ContactSearch extends FieldElement {
|
|
|
226
226
|
border: 1px solid var(--color-borders);
|
|
227
227
|
border-radius: var(--curvature);
|
|
228
228
|
}
|
|
229
|
+
|
|
230
|
+
.interrupt-confirm {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
margin-top: 0.5em;
|
|
234
|
+
margin-left: 0.6em;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
}
|
|
229
237
|
`;
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
@property({ type: Boolean })
|
|
233
241
|
in_a_flow: boolean;
|
|
234
242
|
|
|
243
|
+
// recipients are locked, e.g. starting a specific contact from their read page -
|
|
244
|
+
// no recipient editing, filters or query editing
|
|
245
|
+
@property({ type: Boolean })
|
|
246
|
+
fixed: boolean;
|
|
247
|
+
|
|
248
|
+
// name of the flow the fixed contact is currently in, if any
|
|
249
|
+
@property({ type: String, attribute: 'current_flow' })
|
|
250
|
+
currentFlow: string;
|
|
251
|
+
|
|
252
|
+
// whether the user has explicitly confirmed interrupting the current flow
|
|
253
|
+
@property({ type: Boolean })
|
|
254
|
+
interruptConfirmed = false;
|
|
255
|
+
|
|
235
256
|
@property({ type: Boolean })
|
|
236
257
|
started_previously: boolean;
|
|
237
258
|
|
|
@@ -310,6 +331,30 @@ export class ContactSearch extends FieldElement {
|
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
333
|
|
|
334
|
+
protected willUpdate(changedProperties: Map<string, any>) {
|
|
335
|
+
super.willUpdate(changedProperties);
|
|
336
|
+
|
|
337
|
+
// reconcile the in_a_flow exclusion; outside of fixed mode it is user-controlled
|
|
338
|
+
// via the filter checkbox, so we only ever clear it when the option goes away
|
|
339
|
+
if (
|
|
340
|
+
changedProperties.has('in_a_flow') ||
|
|
341
|
+
changedProperties.has('interruptConfirmed') ||
|
|
342
|
+
changedProperties.has('fixed') ||
|
|
343
|
+
changedProperties.has('currentFlow')
|
|
344
|
+
) {
|
|
345
|
+
if (!this.in_a_flow) {
|
|
346
|
+
delete this.exclusions['in_a_flow'];
|
|
347
|
+
} else if (this.fixed && this.currentFlow) {
|
|
348
|
+
// a fixed contact already in a flow stays excluded until interruption is confirmed
|
|
349
|
+
if (this.interruptConfirmed) {
|
|
350
|
+
delete this.exclusions['in_a_flow'];
|
|
351
|
+
} else {
|
|
352
|
+
this.exclusions['in_a_flow'] = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
313
358
|
public updated(changedProperties: Map<string, any>) {
|
|
314
359
|
super.updated(changedProperties);
|
|
315
360
|
|
|
@@ -317,12 +362,6 @@ export class ContactSearch extends FieldElement {
|
|
|
317
362
|
this.handleRecipientsChanged();
|
|
318
363
|
}
|
|
319
364
|
|
|
320
|
-
// if we remove the in_a_flow option, make sure it's not part of our exclusions
|
|
321
|
-
if (changedProperties.has('in_a_flow') && !this.in_a_flow) {
|
|
322
|
-
delete this.exclusions['in_a_flow'];
|
|
323
|
-
this.requestUpdate('exclusions');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
365
|
if (
|
|
327
366
|
(changedProperties.has('query') && this.advanced) ||
|
|
328
367
|
(changedProperties.has('refreshKey') && this.refreshKey !== '0')
|
|
@@ -455,6 +494,32 @@ export class ContactSearch extends FieldElement {
|
|
|
455
494
|
}
|
|
456
495
|
}
|
|
457
496
|
|
|
497
|
+
private async handleInterruptChanged(evt: any) {
|
|
498
|
+
const checkbox = evt.target as Checkbox;
|
|
499
|
+
this.interruptConfirmed = checkbox.checked;
|
|
500
|
+
|
|
501
|
+
// wait for willUpdate() to reconcile our exclusions
|
|
502
|
+
await this.updateComplete;
|
|
503
|
+
|
|
504
|
+
this.setValue({
|
|
505
|
+
advanced: this.advanced,
|
|
506
|
+
query: this.query,
|
|
507
|
+
exclusions: this.exclusions,
|
|
508
|
+
recipients: this.recipients
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// we already know whether our fixed contacts will be included so no need to re-run
|
|
512
|
+
// the search - just let the modal know, counting contacts only since a group entry
|
|
513
|
+
// doesn't tell us its membership size
|
|
514
|
+
const contactCount = this.recipients.filter(
|
|
515
|
+
(value: OmniOption) => value.type === 'contact'
|
|
516
|
+
).length;
|
|
517
|
+
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
518
|
+
...(this.summary || {}),
|
|
519
|
+
total: this.interruptConfirmed ? contactCount : 0
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
458
523
|
private handleExclusionChanged(evt: any) {
|
|
459
524
|
if (evt.target.tagName === 'TEMBA-CHECKBOX') {
|
|
460
525
|
const ex = JSON.stringify(this.exclusions);
|
|
@@ -487,7 +552,7 @@ export class ContactSearch extends FieldElement {
|
|
|
487
552
|
|
|
488
553
|
public renderWidget(): TemplateResult {
|
|
489
554
|
let summary: TemplateResult;
|
|
490
|
-
if (this.summary) {
|
|
555
|
+
if (this.summary && !this.fixed) {
|
|
491
556
|
if (!this.summary.error) {
|
|
492
557
|
const count = this.summary.total || 0;
|
|
493
558
|
|
|
@@ -544,132 +609,170 @@ export class ContactSearch extends FieldElement {
|
|
|
544
609
|
)}`;
|
|
545
610
|
}
|
|
546
611
|
|
|
612
|
+
const interruptConfirm =
|
|
613
|
+
this.fixed && this.currentFlow && this.in_a_flow && !this.fetching
|
|
614
|
+
? html`<div
|
|
615
|
+
class="interrupt-confirm"
|
|
616
|
+
@click=${this.handleActivityLabelClicked}
|
|
617
|
+
>
|
|
618
|
+
<temba-checkbox
|
|
619
|
+
name="interrupt"
|
|
620
|
+
?checked=${this.interruptConfirmed}
|
|
621
|
+
@change=${this.handleInterruptChanged}
|
|
622
|
+
></temba-checkbox>
|
|
623
|
+
<div>
|
|
624
|
+
${this.flow && this.flow.name === this.currentFlow
|
|
625
|
+
? html`${msg("It's okay to restart")}
|
|
626
|
+
<b>${this.currentFlow}</b> ${msg('from the beginning')}`
|
|
627
|
+
: html`${msg("It's okay to interrupt")}
|
|
628
|
+
<b>${this.currentFlow}</b> ${msg('and start this one')}`}
|
|
629
|
+
</div>
|
|
630
|
+
</div>`
|
|
631
|
+
: null;
|
|
632
|
+
|
|
547
633
|
return html`
|
|
548
634
|
${
|
|
549
|
-
this.
|
|
550
|
-
?
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
635
|
+
this.fixed
|
|
636
|
+
? null
|
|
637
|
+
: this.advanced
|
|
638
|
+
? html`<div class="query">
|
|
639
|
+
<temba-textinput
|
|
640
|
+
.helpText=${this.helpText}
|
|
641
|
+
.widgetOnly=${this.widgetOnly}
|
|
642
|
+
.errors=${this.errors}
|
|
643
|
+
name=${this.name}
|
|
644
|
+
.inputRoot=${this}
|
|
645
|
+
@input=${this.handleQueryChange}
|
|
646
|
+
placeholder=${this.placeholder}
|
|
647
|
+
.value=${this.query}
|
|
648
|
+
textarea
|
|
649
|
+
autogrow
|
|
650
|
+
>
|
|
651
|
+
</temba-textinput>
|
|
652
|
+
</div>`
|
|
653
|
+
: html`<temba-omnibox
|
|
654
|
+
placeholder="Search for contacts or groups"
|
|
655
|
+
widget_only=""
|
|
656
|
+
groups=""
|
|
657
|
+
contacts=""
|
|
658
|
+
label="Recipients"
|
|
659
|
+
help_text="The contacts to send the message to."
|
|
660
|
+
.errors=${this.errors}
|
|
661
|
+
id="recipients"
|
|
662
|
+
name="recipients"
|
|
663
|
+
.values=${this.recipients}
|
|
664
|
+
endpoint="/contact/omnibox/?"
|
|
665
|
+
@change=${this.handleRecipientsChanged}
|
|
666
|
+
>
|
|
667
|
+
</temba-omnibox>
|
|
668
|
+
|
|
669
|
+
${this.not_seen_since_days ||
|
|
670
|
+
this.in_a_flow ||
|
|
671
|
+
this.started_previously
|
|
672
|
+
? html`
|
|
673
|
+
<div class="filters">
|
|
674
|
+
<div
|
|
675
|
+
style="display:flex;font-size:1em;margin-bottom:0.5em"
|
|
676
|
+
>
|
|
677
|
+
<temba-icon size="1" name="filter"></temba-icon>
|
|
678
|
+
<div style="margin-left:0.5em">
|
|
679
|
+
Only include contacts who...
|
|
680
|
+
</div>
|
|
592
681
|
</div>
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
@click=${this.handleActivityLabelClicked}
|
|
607
|
-
>
|
|
608
|
-
<temba-checkbox
|
|
609
|
-
style="display:inline;"
|
|
610
|
-
name="not_seen_since_days"
|
|
611
|
-
?checked=${notSeenSinceDays}
|
|
612
|
-
@change=${this.handleExclusionChanged}
|
|
682
|
+
${this.in_a_flow
|
|
683
|
+
? html`<temba-checkbox
|
|
684
|
+
name="in_a_flow"
|
|
685
|
+
label="${msg('Are not currently in a flow')}"
|
|
686
|
+
?checked=${this.exclusions['in_a_flow']}
|
|
687
|
+
@change=${this.handleExclusionChanged}
|
|
688
|
+
></temba-checkbox>`
|
|
689
|
+
: null}
|
|
690
|
+
${this.not_seen_since_days
|
|
691
|
+
? html`
|
|
692
|
+
<div
|
|
693
|
+
class="activity-select"
|
|
694
|
+
@click=${this.handleActivityLabelClicked}
|
|
613
695
|
>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
696
|
+
<temba-checkbox
|
|
697
|
+
style="display:inline;"
|
|
698
|
+
name="not_seen_since_days"
|
|
699
|
+
?checked=${notSeenSinceDays}
|
|
700
|
+
@change=${this.handleExclusionChanged}
|
|
701
|
+
>
|
|
702
|
+
</temba-checkbox>
|
|
703
|
+
|
|
704
|
+
<div>
|
|
705
|
+
${msg('Have sent a message in the last')}
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
<temba-select
|
|
709
|
+
style="margin-left:0.5em"
|
|
710
|
+
class="small-select"
|
|
711
|
+
@change=${this.handleActivityLevelChanged}
|
|
712
|
+
?disabled=${!notSeenSinceDays}
|
|
713
|
+
>
|
|
714
|
+
<temba-option
|
|
715
|
+
name="90 days"
|
|
716
|
+
value="90"
|
|
717
|
+
?selected=${notSeenSinceDays === 90}
|
|
718
|
+
></temba-option>
|
|
719
|
+
<temba-option
|
|
720
|
+
name="180 days"
|
|
721
|
+
value="180"
|
|
722
|
+
?selected=${notSeenSinceDays === 180}
|
|
723
|
+
></temba-option>
|
|
724
|
+
<temba-option
|
|
725
|
+
name="Year"
|
|
726
|
+
value="365"
|
|
727
|
+
?selected=${notSeenSinceDays === 365}
|
|
728
|
+
></temba-option>
|
|
729
|
+
</temba-select>
|
|
730
|
+
<div></div>
|
|
618
731
|
</div>
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
?selected=${notSeenSinceDays === 180}
|
|
635
|
-
></temba-option>
|
|
636
|
-
<temba-option
|
|
637
|
-
name="Year"
|
|
638
|
-
value="365"
|
|
639
|
-
?selected=${notSeenSinceDays === 365}
|
|
640
|
-
></temba-option>
|
|
641
|
-
</temba-select>
|
|
642
|
-
<div></div>
|
|
643
|
-
</div>
|
|
644
|
-
`
|
|
645
|
-
: null}
|
|
646
|
-
${this.started_previously
|
|
647
|
-
? html`<temba-checkbox
|
|
648
|
-
name="started_previously"
|
|
649
|
-
label="${msg(
|
|
650
|
-
'Have not started this flow in the last 90 days'
|
|
651
|
-
)}"
|
|
652
|
-
?checked=${this.exclusions['started_previously']}
|
|
653
|
-
@change=${this.handleExclusionChanged}
|
|
654
|
-
></temba-checkbox>`
|
|
655
|
-
: null}
|
|
656
|
-
</div>
|
|
657
|
-
`
|
|
658
|
-
: null} `
|
|
732
|
+
`
|
|
733
|
+
: null}
|
|
734
|
+
${this.started_previously
|
|
735
|
+
? html`<temba-checkbox
|
|
736
|
+
name="started_previously"
|
|
737
|
+
label="${msg(
|
|
738
|
+
'Have not started this flow in the last 90 days'
|
|
739
|
+
)}"
|
|
740
|
+
?checked=${this.exclusions['started_previously']}
|
|
741
|
+
@change=${this.handleExclusionChanged}
|
|
742
|
+
></temba-checkbox>`
|
|
743
|
+
: null}
|
|
744
|
+
</div>
|
|
745
|
+
`
|
|
746
|
+
: null} `
|
|
659
747
|
}
|
|
660
748
|
</div>
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
749
|
+
${interruptConfirm}
|
|
750
|
+
${
|
|
751
|
+
this.fixed
|
|
752
|
+
? html`<div
|
|
753
|
+
class="results ${getClasses({
|
|
754
|
+
fetching: this.fetching,
|
|
755
|
+
initialized: this.fetching,
|
|
756
|
+
empty: !this.fetching
|
|
757
|
+
})}"
|
|
758
|
+
>
|
|
759
|
+
<temba-loading units="6" size="8"></temba-loading>
|
|
760
|
+
</div>`
|
|
761
|
+
: html`<div
|
|
762
|
+
class="results ${getClasses({
|
|
763
|
+
fetching: this.fetching,
|
|
764
|
+
initialized: this.initialized || this.fetching,
|
|
765
|
+
empty:
|
|
766
|
+
((this.summary && this.summary.error) || !this.summary) &&
|
|
767
|
+
!this.fetching
|
|
768
|
+
})}"
|
|
769
|
+
>
|
|
770
|
+
<temba-loading units="6" size="8"></temba-loading>
|
|
771
|
+
<div class="summary ${this.expanded ? 'expanded' : ''}">
|
|
772
|
+
${summary}
|
|
773
|
+
</div>
|
|
774
|
+
</div>`
|
|
775
|
+
}
|
|
673
776
|
${blockers}
|
|
674
777
|
${
|
|
675
778
|
!blockers && this.summary && this.summary.warnings
|