@nyaruka/temba-components 0.106.0 → 0.107.1

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.
@@ -0,0 +1,76 @@
1
+ import { html, PropertyValueMap, TemplateResult } from 'lit';
2
+ import { RapidElement } from '../RapidElement';
3
+ import { property } from 'lit/decorators.js';
4
+ import { fetchResults } from '../utils';
5
+
6
+ export class FlowStartProgress extends RapidElement {
7
+ @property({ type: String })
8
+ uuid: string;
9
+
10
+ @property({ type: Number })
11
+ started: number;
12
+
13
+ @property({ type: Number })
14
+ total: number;
15
+
16
+ @property({ type: Number })
17
+ refreshes: number = 0;
18
+
19
+ @property({ type: String })
20
+ eta: string;
21
+
22
+ public updated(
23
+ changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
24
+ ): void {
25
+ super.updated(changes);
26
+ if (changes.has('uuid')) {
27
+ this.refresh();
28
+ }
29
+ }
30
+
31
+ public refresh(): void {
32
+ fetchResults(`/api/v2/flow_starts.json?uuid=${this.uuid}`).then(
33
+ (data: any) => {
34
+ if (data.length > 0) {
35
+ this.refreshes++;
36
+ const start = data[0];
37
+ this.started = start.progress.started;
38
+ this.total = start.progress.total;
39
+
40
+ const elapsed =
41
+ new Date().getTime() - new Date(start.created_on).getTime();
42
+ const rate = this.started / elapsed;
43
+
44
+ // calculate the estimated time of arrival
45
+ const eta = new Date(
46
+ new Date().getTime() + (this.total - this.started) / rate
47
+ );
48
+
49
+ // Don't bother with estimates months out
50
+ const nextMonth = new Date();
51
+ nextMonth.setMonth(nextMonth.getMonth() + 2);
52
+ if (eta > nextMonth) {
53
+ this.eta = null;
54
+ } else {
55
+ this.eta = eta.toISOString();
56
+ }
57
+
58
+ if (this.started < this.total) {
59
+ // refresh with a backoff up to 1 minute
60
+ setTimeout(() => {
61
+ this.refresh();
62
+ }, Math.min(1000 * this.refreshes, 60000));
63
+ }
64
+ }
65
+ }
66
+ );
67
+ }
68
+
69
+ public render(): TemplateResult {
70
+ return html`<temba-progress
71
+ total=${this.total}
72
+ current=${this.started}
73
+ eta=${this.eta}
74
+ ></temba-progress>`;
75
+ }
76
+ }
@@ -0,0 +1,179 @@
1
+ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
+ import { RapidElement } from '../RapidElement';
3
+ import { property } from 'lit/decorators.js';
4
+
5
+ export class ProgressBar extends RapidElement {
6
+ static styles = css`
7
+ .wrapper {
8
+ display: flex;
9
+ box-sizing: content-box;
10
+ background: #f1f1f1;
11
+ border-radius: var(--curvature);
12
+ box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.05);
13
+ }
14
+
15
+ .meter {
16
+ flex-grow: 1;
17
+ display: flex;
18
+ box-sizing: content-box;
19
+ position: relative;
20
+ border-radius: var(--curvature);
21
+ border-top-right-radius: 0;
22
+ border-bottom-right-radius: 0;
23
+ padding: 4px;
24
+ min-height: 6px;
25
+ }
26
+ .meter > span {
27
+ display: block;
28
+ height: 100%;
29
+ border-radius: var(--curvature);
30
+ background-color: var(--color-primary-dark);
31
+ background-image: linear-gradient(
32
+ center bottom,
33
+ rgb(43, 194, 83) 37%,
34
+ rgb(84, 240, 83) 69%
35
+ );
36
+
37
+ position: relative;
38
+ overflow: hidden;
39
+ min-width: 3px;
40
+ }
41
+
42
+ .meter > span:after,
43
+ .animate > span > span {
44
+ content: '';
45
+ position: absolute;
46
+ top: 0;
47
+ left: 0;
48
+ bottom: 0;
49
+ right: 0;
50
+ background-image: linear-gradient(
51
+ -45deg,
52
+ rgba(255, 255, 255, 0.2) 25%,
53
+ transparent 25%,
54
+ transparent 50%,
55
+ rgba(255, 255, 255, 0.2) 50%,
56
+ rgba(255, 255, 255, 0.2) 75%,
57
+ transparent 75%,
58
+ transparent
59
+ );
60
+ z-index: 1;
61
+ background-size: 50px 50px;
62
+ animation: move 16s linear infinite;
63
+ border-top-right-radius: var(--curvature);
64
+ border-bottom-right-radius: var(--curvature);
65
+ border-top-left-radius: var(--curvature);
66
+ border-bottom-left-radius: var(--curvature);
67
+ overflow: hidden;
68
+ }
69
+
70
+ .animate > span:after {
71
+ display: none;
72
+ }
73
+
74
+ @keyframes move {
75
+ 0% {
76
+ background-position: 0 0;
77
+ }
78
+ 100% {
79
+ background-position: 50px 50px;
80
+ }
81
+ }
82
+
83
+ .complete {
84
+ transition: width 2s;
85
+ }
86
+
87
+ .incomplete {
88
+ flex-grow: 1;
89
+ }
90
+
91
+ .etc {
92
+ font-size: 0.7em;
93
+ background: rgba(0, 0, 0, 0.07);
94
+ font-weight: bold;
95
+ white-space: nowrap;
96
+ border-top-right-radius: var(--curvature);
97
+ border-bottom-right-radius: var(--curvature);
98
+ color: rgba(0, 0, 0, 0.5);
99
+ align-self: center;
100
+ padding: 2px 6px;
101
+ }
102
+
103
+ .meter.done > span:after,
104
+ .done .animate > span > span {
105
+ display: none;
106
+ }
107
+
108
+ .meter.done > span {
109
+ background: rgb(var(--success-rgb));
110
+ }
111
+ `;
112
+
113
+ @property({ type: Number })
114
+ total = 100;
115
+
116
+ @property({ type: Number })
117
+ current = 0;
118
+
119
+ @property({ type: Number })
120
+ pct = 0;
121
+
122
+ @property({ type: Boolean })
123
+ done = false;
124
+
125
+ @property({ type: String })
126
+ eta: string;
127
+
128
+ @property({ type: String, attribute: false })
129
+ estimatedCompletionDate: Date;
130
+
131
+ @property({ type: Boolean })
132
+ showEstimatedCompletion = false;
133
+
134
+ @property({ type: Boolean })
135
+ showPercentage = false;
136
+
137
+ public updated(
138
+ changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
139
+ ): void {
140
+ if (changes.has('eta') && this.eta) {
141
+ this.estimatedCompletionDate = new Date(this.eta);
142
+ this.showEstimatedCompletion = this.estimatedCompletionDate > new Date();
143
+ }
144
+
145
+ if (changes.has('current')) {
146
+ const pct = Math.floor(Math.min((this.current / this.total) * 100, 100));
147
+ if (Number.isNaN(pct)) {
148
+ this.showPercentage = false;
149
+ } else {
150
+ this.pct = pct;
151
+ this.showPercentage = true;
152
+ }
153
+
154
+ this.done = this.pct >= 100;
155
+ }
156
+ }
157
+
158
+ public render(): TemplateResult {
159
+ return html`<div class="wrapper">
160
+ <div class="meter ${this.done ? 'done' : ''}">
161
+ <span class="complete" style="flex-basis: ${this.pct}%"></span>
162
+ <div class="incomplete"></div>
163
+ </div>
164
+
165
+ ${this.showPercentage || this.showEstimatedCompletion
166
+ ? html`<div class="etc">
167
+ ${this.estimatedCompletionDate &&
168
+ this.showEstimatedCompletion &&
169
+ !this.done
170
+ ? html`<temba-date
171
+ value="${this.estimatedCompletionDate.toISOString()}"
172
+ display="countdown"
173
+ ></temba-date>`
174
+ : html`${this.pct}%`}
175
+ </div>`
176
+ : null}
177
+ </div>`;
178
+ }
179
+ }
@@ -289,6 +289,34 @@ export class Store extends RapidElement {
289
289
  });
290
290
  }
291
291
 
292
+ public shiftAndRound(duration, unit: string, singular: string) {
293
+ const value = Math.round(duration.shiftTo(unit).get(unit));
294
+ if (value == 1) {
295
+ return `1 ${singular}`;
296
+ } else {
297
+ return `${value} ${unit}`;
298
+ }
299
+ }
300
+
301
+ public getCountdown(futureDate: DateTime) {
302
+ const duration = futureDate.diff(DateTime.now());
303
+ const comps = duration.rescale();
304
+
305
+ if (comps.months > 0) {
306
+ return '> 1 month';
307
+ }
308
+
309
+ if (comps.days > 1) {
310
+ return `~ ${this.shiftAndRound(comps, 'days', 'day')}`;
311
+ }
312
+
313
+ if (comps.hours > 0) {
314
+ return `~ ${this.shiftAndRound(comps, 'hours', 'hour')}`;
315
+ }
316
+
317
+ return `~ ${this.shiftAndRound(comps, 'minutes', 'minute')}`;
318
+ }
319
+
292
320
  public getShortDuration(scheduled: DateTime, compareDate: DateTime = null) {
293
321
  const now = compareDate || DateTime.now();
294
322
  return scheduled
package/temba-modules.ts CHANGED
@@ -59,6 +59,8 @@ import { Chat } from './src/chat/Chat';
59
59
  import { MediaPicker } from './src/mediapicker/MediaPicker';
60
60
  import { ContactNotepad } from './src/contacts/ContactNotepad';
61
61
  import { OutboxMonitor } from './src/outboxmonitor/OutboxMonitor';
62
+ import { ProgressBar } from './src/progress/ProgressBar';
63
+ import { FlowStartProgress } from './src/progress/FlowStartProgress';
62
64
 
63
65
  export function addCustomElement(name: string, comp: any) {
64
66
  if (!window.customElements.get(name)) {
@@ -128,3 +130,5 @@ addCustomElement('temba-chat', Chat);
128
130
  addCustomElement('temba-media-picker', MediaPicker);
129
131
  addCustomElement('temba-contact-notepad', ContactNotepad);
130
132
  addCustomElement('temba-outbox-monitor', OutboxMonitor);
133
+ addCustomElement('temba-progress', ProgressBar);
134
+ addCustomElement('temba-flowstart-progress', FlowStartProgress);