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 ( ); } type ProgressCallback = (message: string) => void; type Task = Promise | ((progress?: ProgressCallback) => Promise); export default async function showToast( task: Task | Error, { message = 'Bulk actions currently being processed.', doneMessage = 'Bulk action processing complete.', } = {}, ): Promise { const iconWrapper = ; const messageWrapper = {message}; const toast = (
{iconWrapper} {messageWrapper}
); 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(); } catch (error) { assertError(error); toast.classList.replace('Toast--loading', 'Toast--error'); updateToast(error.message); iconWrapper.firstChild!.replaceWith(); 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); }); } }