From 0a4753fde907c2b2d5ad15783d086ea25ee4a9ee Mon Sep 17 00:00:00 2001 From: Eric Newcomer Date: Wed, 25 Sep 2024 18:11:40 +0000 Subject: [PATCH 1/3] Switch to generic StartProgress widget --- demo/index.html | 1 + src/dialog/Modax.ts | 6 +- src/progress/FlowStartProgress.ts | 76 --------------- src/progress/ProgressBar.ts | 66 +++++++++---- src/progress/StartProgress.ts | 154 ++++++++++++++++++++++++++++++ src/utils/index.ts | 4 + temba-modules.ts | 4 +- 7 files changed, 212 insertions(+), 99 deletions(-) delete mode 100644 src/progress/FlowStartProgress.ts create mode 100644 src/progress/StartProgress.ts diff --git a/demo/index.html b/demo/index.html index 966b663d..9b23b683 100644 --- a/demo/index.html +++ b/demo/index.html @@ -153,6 +153,7 @@ +
diff --git a/src/dialog/Modax.ts b/src/dialog/Modax.ts index 84452c37..cb170795 100644 --- a/src/dialog/Modax.ts +++ b/src/dialog/Modax.ts @@ -381,7 +381,11 @@ export class Modax extends RapidElement { } private isDestructive(): boolean { - return this.endpoint && this.endpoint.indexOf('delete') > -1; + return ( + this.endpoint && + (this.endpoint.indexOf('delete') > -1 || + this.endpoint.indexOf('interrupt') > -1) + ); } private handleGotoStep(evt: MouseEvent) { diff --git a/src/progress/FlowStartProgress.ts b/src/progress/FlowStartProgress.ts deleted file mode 100644 index 5c4a8e88..00000000 --- a/src/progress/FlowStartProgress.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { html, PropertyValueMap, TemplateResult } from 'lit'; -import { RapidElement } from '../RapidElement'; -import { property } from 'lit/decorators.js'; -import { fetchResults } from '../utils'; - -export class FlowStartProgress extends RapidElement { - @property({ type: String }) - uuid: string; - - @property({ type: Number }) - started: number; - - @property({ type: Number }) - total: number; - - @property({ type: Number }) - refreshes: number = 0; - - @property({ type: String }) - eta: string; - - public updated( - changes: PropertyValueMap | Map - ): void { - super.updated(changes); - if (changes.has('uuid')) { - this.refresh(); - } - } - - public refresh(): void { - fetchResults(`/api/v2/flow_starts.json?uuid=${this.uuid}`).then( - (data: any) => { - if (data.length > 0) { - this.refreshes++; - const start = data[0]; - this.started = start.progress.started; - this.total = start.progress.total; - - const elapsed = - new Date().getTime() - new Date(start.created_on).getTime(); - const rate = this.started / elapsed; - - // calculate the estimated time of arrival - const eta = new Date( - new Date().getTime() + (this.total - this.started) / rate - ); - - // Don't bother with estimates months out - const nextMonth = new Date(); - nextMonth.setMonth(nextMonth.getMonth() + 2); - if (eta > nextMonth) { - this.eta = null; - } else { - this.eta = eta.toISOString(); - } - - if (this.started < this.total) { - // refresh with a backoff up to 1 minute - setTimeout(() => { - this.refresh(); - }, Math.min(1000 * this.refreshes, 60000)); - } - } - } - ); - } - - public render(): TemplateResult { - return html``; - } -} diff --git a/src/progress/ProgressBar.ts b/src/progress/ProgressBar.ts index 82ffd698..31688fff 100644 --- a/src/progress/ProgressBar.ts +++ b/src/progress/ProgressBar.ts @@ -8,8 +8,15 @@ export class ProgressBar extends RapidElement { display: flex; box-sizing: content-box; background: #f1f1f1; - border-radius: var(--curvature); + border-radius: calc(var(--curvature) * 1.3); box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.05); + overflow: hidden; + min-height: 1.5rem; + } + + .message { + padding: 0 0.25rem; + color: rgba(0, 0, 0, 0.4); } .meter { @@ -17,7 +24,6 @@ export class ProgressBar extends RapidElement { display: flex; box-sizing: content-box; position: relative; - border-radius: var(--curvature); border-top-right-radius: 0; border-bottom-right-radius: 0; padding: 4px; @@ -36,7 +42,6 @@ export class ProgressBar extends RapidElement { position: relative; overflow: hidden; - min-width: 3px; } .meter > span:after, @@ -59,7 +64,7 @@ export class ProgressBar extends RapidElement { ); z-index: 1; background-size: 50px 50px; - animation: move 16s linear infinite; + animation: move 8s linear infinite; border-top-right-radius: var(--curvature); border-bottom-right-radius: var(--curvature); border-top-left-radius: var(--curvature); @@ -80,24 +85,35 @@ export class ProgressBar extends RapidElement { } } - .complete { - transition: width 2s; + .meter .complete { + transition: flex-basis 2s; } - .incomplete { + .meter .incomplete { flex-grow: 1; } .etc { - font-size: 0.7em; + display: flex; + flex-direction: row; background: rgba(0, 0, 0, 0.07); font-weight: bold; white-space: nowrap; - border-top-right-radius: var(--curvature); - border-bottom-right-radius: var(--curvature); color: rgba(0, 0, 0, 0.5); align-self: center; - padding: 2px 6px; + padding: 0px 6px; + align-self: stretch; + align-items: center; + } + + .etc > div { + font-size: 0.7em; + } + + .wrapper *::last-child { + border-top-right-radius: var(--curvature); + border-bottom-right-radius: var(--curvature); + overflow: hidden; } .meter.done > span:after, @@ -134,6 +150,9 @@ export class ProgressBar extends RapidElement { @property({ type: Boolean }) showPercentage = false; + @property({ type: String }) + message: string; + public updated( changes: PropertyValueMap | Map ): void { @@ -156,24 +175,31 @@ export class ProgressBar extends RapidElement { } public render(): TemplateResult { - return html`
+ return html`
+ ${this.message + ? html`
${this.message}
` + : null}
${this.showPercentage || this.showEstimatedCompletion ? html`
- ${this.estimatedCompletionDate && - this.showEstimatedCompletion && - !this.done - ? html`` - : html`${this.pct}%`} +
+ ${this.estimatedCompletionDate && + this.showEstimatedCompletion && + !this.done + ? html`` + : html`${this.pct}%`} +
` : null} + +
`; } } diff --git a/src/progress/StartProgress.ts b/src/progress/StartProgress.ts new file mode 100644 index 00000000..47518299 --- /dev/null +++ b/src/progress/StartProgress.ts @@ -0,0 +1,154 @@ +import { css, html, PropertyValueMap, TemplateResult } from 'lit'; +import { RapidElement } from '../RapidElement'; +import { property } from 'lit/decorators.js'; +import { fetchResults, showModax } from '../utils'; + +export class StartProgress extends RapidElement { + static styles = css` + temba-icon[name='close'] { + cursor: pointer; + margin: 0 4px; + } + + temba-icon[name='close']:hover { + color: var(--color-primary-dark); + } + `; + @property({ type: String }) + id: string; + + @property({ type: Number }) + current: number; + + @property({ type: Number }) + total: number; + + @property({ type: Number }) + refreshes: number = 0; + + @property({ type: String }) + eta: string; + + @property({ type: Boolean }) + complete = false; + + @property({ type: String }) + message: string; + + @property({ type: String }) + statusEndpoint: string; + + @property({ type: String }) + interruptTitle: string; + + @property({ type: String }) + interruptEndpoint: string; + + public updated( + changes: PropertyValueMap | Map + ): void { + super.updated(changes); + if (changes.has('id')) { + this.refresh(); + } + + // Useful for simulating progress + /* + if (changes.has('current')) { + this.requestUpdate(); + setTimeout(() => { + this.current = this.current + 100000; + this.complete = this.current >= this.total; + this.message = null; + + if (this.complete) { + this.scheduleRemoval(); + } + }, 5000); + }*/ + } + + public interruptStart(): void { + showModax(this.interruptTitle, this.interruptEndpoint); + } + + public refresh(): void { + fetchResults(this.statusEndpoint).then((data: any) => { + if (data.length > 0) { + this.refreshes++; + const start = data[0]; + + this.current = start.progress.current; + this.total = start.progress.total; + + this.complete = + start.status == 'Completed' || + start.status == 'Failed' || + start.status == 'Interrupted' || + start.progress.current >= start.progress.total; + + if (start.status === 'queued') { + this.message = 'Waiting..'; + } else { + this.message = null; + } + + if (start.status === 'Started') { + const elapsed = + new Date().getTime() - new Date(start.modified_on).getTime(); + const rate = this.current / elapsed; + + // only calculate eta if the rate is actually reasonable + if (rate > 0.1) { + const eta = new Date( + new Date().getTime() + (this.total - this.current) / rate + ); + // Don't bother with estimates months out + const nextMonth = new Date(); + nextMonth.setMonth(nextMonth.getMonth() + 2); + if (eta > nextMonth) { + this.eta = null; + } else { + this.eta = eta.toISOString(); + } + } + } + + if (!this.complete && this.current < this.total) { + // refresh with a backoff up to 1 minute + setTimeout(() => { + this.refresh(); + }, Math.min(1000 * this.refreshes, 60000)); + } else { + this.complete = true; + } + + if (this.complete) { + this.scheduleRemoval(); + } + } + }); + } + + public scheduleRemoval(): void { + setTimeout(() => { + this.remove(); + }, 5000); + } + + public render(): TemplateResult { + return html` + ${!this.complete + ? html`` + : null} + `; + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index f7533fcb..7c643a97 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -826,3 +826,7 @@ export const hashCode = (s) => { return a & a; }, 0); }; + +export const showModax = (title: string, endpoint: string) => { + (window as any).showModax(title, endpoint); +}; diff --git a/temba-modules.ts b/temba-modules.ts index b85bfbee..9559e165 100644 --- a/temba-modules.ts +++ b/temba-modules.ts @@ -60,7 +60,7 @@ import { MediaPicker } from './src/mediapicker/MediaPicker'; import { ContactNotepad } from './src/contacts/ContactNotepad'; import { OutboxMonitor } from './src/outboxmonitor/OutboxMonitor'; import { ProgressBar } from './src/progress/ProgressBar'; -import { FlowStartProgress } from './src/progress/FlowStartProgress'; +import { StartProgress } from './src/progress/StartProgress'; export function addCustomElement(name: string, comp: any) { if (!window.customElements.get(name)) { @@ -131,4 +131,4 @@ addCustomElement('temba-media-picker', MediaPicker); addCustomElement('temba-contact-notepad', ContactNotepad); addCustomElement('temba-outbox-monitor', OutboxMonitor); addCustomElement('temba-progress', ProgressBar); -addCustomElement('temba-flowstart-progress', FlowStartProgress); +addCustomElement('temba-start-progress', StartProgress); From bbf9684577cbd86762976262e87072d85bb496aa Mon Sep 17 00:00:00 2001 From: Eric Newcomer Date: Wed, 25 Sep 2024 18:14:09 +0000 Subject: [PATCH 2/3] Use Queued status for waiting message --- src/progress/StartProgress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/progress/StartProgress.ts b/src/progress/StartProgress.ts index 47518299..28dfbec4 100644 --- a/src/progress/StartProgress.ts +++ b/src/progress/StartProgress.ts @@ -87,7 +87,7 @@ export class StartProgress extends RapidElement { start.status == 'Interrupted' || start.progress.current >= start.progress.total; - if (start.status === 'queued') { + if (start.status === 'Queued') { this.message = 'Waiting..'; } else { this.message = null; From a848a5d1515787e4e6c03e28c428f5a9ae352f63 Mon Sep 17 00:00:00 2001 From: Eric Newcomer Date: Wed, 25 Sep 2024 18:17:46 +0000 Subject: [PATCH 3/3] Soften progress curvature --- src/progress/ProgressBar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/progress/ProgressBar.ts b/src/progress/ProgressBar.ts index 31688fff..4b8f8945 100644 --- a/src/progress/ProgressBar.ts +++ b/src/progress/ProgressBar.ts @@ -8,7 +8,7 @@ export class ProgressBar extends RapidElement { display: flex; box-sizing: content-box; background: #f1f1f1; - border-radius: calc(var(--curvature) * 1.3); + border-radius: var(--curvature); box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.05); overflow: hidden; min-height: 1.5rem; @@ -32,7 +32,7 @@ export class ProgressBar extends RapidElement { .meter > span { display: block; height: 100%; - border-radius: var(--curvature); + border-radius: calc(var(--curvature) * 0.8); background-color: var(--color-primary-dark); background-image: linear-gradient( center bottom,