blob: be186e47017a7101d34d13ef090ebc9366abc0e6 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
import delay from 'delay';
import React from 'dom-chef';
import {assertError} from 'ts-extras';
import {CheckIcon, StopIcon} from '@primer/octicons-react';
export function ToastSpinner(): JSX.Element {
return (
<svg className="Toast--spinner" viewBox="0 0 32 32" width="18" height="18">
<path fill="#959da5" d="M16 0 A16 16 0 0 0 16 32 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 16 28 A12 12 0 0 1 16 4"/>
<path fill="#ffffff" d="M16 0 A16 16 0 0 1 32 16 L28 16 A12 12 0 0 0 16 4z"/>
</svg>
);
}
type ProgressCallback = (message: string) => void;
type Task = Promise<unknown> | ((progress?: ProgressCallback) => Promise<unknown>);
export default async function showToast(
task: Task | Error,
{
message = 'Bulk actions currently being processed.',
doneMessage = 'Bulk action processing complete.',
} = {},
): Promise<void> {
const iconWrapper = <span className="Toast-icon"><ToastSpinner/></span>;
const messageWrapper = <span className="Toast-content">{message}</span>;
const toast = (
<div
role="log"
style={{zIndex: 101}}
className="rgh-toast position-fixed bottom-0 right-0 ml-5 mb-5 anim-fade-in fast Toast Toast--loading"
>
{iconWrapper}
{messageWrapper}
</div>
);
const updateToast = (message: string): void => {
messageWrapper.textContent = message;
};
document.body.append(toast);
await delay(30); // Without this, the Toast doesn't appear in time
try {
if (task instanceof Error) {
throw task;
}
// eslint-disable-next-line unicorn/prefer-ternary -- Naw man, that's less readable
if (typeof task === 'function') {
await task(updateToast);
} else {
await task;
}
toast.classList.replace('Toast--loading', 'Toast--success');
updateToast(doneMessage);
iconWrapper.firstChild!.replaceWith(<CheckIcon/>);
} catch (error) {
assertError(error);
toast.classList.replace('Toast--loading', 'Toast--error');
updateToast(error.message);
iconWrapper.firstChild!.replaceWith(<StopIcon/>);
throw error;
} finally {
// Without rAF the toast might be removed before the first page paint
// rAF also allows showToast to resolve as soon as task is done
requestAnimationFrame(() => {
setTimeout(() => {
toast.remove();
}, 3000);
});
}
}
|