46 lines
2.3 KiB
TypeScript
46 lines
2.3 KiB
TypeScript
import { t } from '../i18n';
|
|
import { bytes, num } from '../lib/format';
|
|
import { useData } from '../store';
|
|
import { Icon, type IconName } from './icons';
|
|
|
|
const ICON: Record<string, [IconName, string]> = { job: ['refresh', 'tone-bg-accent'], ok: ['check', 'tone-bg-ok'], bad: ['alertCircle', 'tone-bg-bad'], info: ['info', 'tone-bg-info'] };
|
|
|
|
/** Background jobs and results, bottom right. Jobs show progress and a Cancel button; results close by themselves. */
|
|
export function Toasts() {
|
|
const d = useData();
|
|
return (
|
|
<div className="toasts" aria-live="polite" aria-relevant="additions text">
|
|
{d.toasts.map(x => {
|
|
const [icon, cls] = ICON[x.kind]!;
|
|
const p = x.progress;
|
|
const big = p && p.total > 10_000;
|
|
return (
|
|
<div key={x.id} className="toast" role={x.kind === 'bad' ? 'alert' : 'status'}>
|
|
<div className="toast-top">
|
|
<span className={`ti ${cls}`}><Icon name={icon} className={x.kind === 'job' ? 'spin' : undefined} /></span>
|
|
<div className="tt"><b>{x.title}</b>{x.detail && <span>{x.detail}</span>}</div>
|
|
<button className="btn btn-ghost btn-sm btn-icon" onClick={() => d.dismiss(x.id)} aria-label={t('common.close')}><Icon name="x" /></button>
|
|
</div>
|
|
{x.kind === 'job' && (
|
|
<>
|
|
{p ? (
|
|
<div className="prog">
|
|
<div className="bar"><span style={{ width: `${Math.min(100, (p.done / Math.max(1, p.total)) * 100)}%` }} /></div>
|
|
<div className="c"><span>{big ? `${bytes(p.done)} / ${bytes(p.total)}` : `${num(p.done)} / ${num(p.total)}`}</span><span>{Math.round((p.done / Math.max(1, p.total)) * 100)}%</span></div>
|
|
</div>
|
|
) : <div className="bar indet"><span /></div>}
|
|
{x.lines && x.lines.length > 1 && (
|
|
<details className="toast-log">
|
|
<summary className="small muted">{t('toast.log', { count: x.lines.length })}</summary>
|
|
<pre className="log">{x.lines.join('\n')}</pre>
|
|
</details>
|
|
)}
|
|
{x.job && <div className="toast-actions"><button className="btn btn-sm" onClick={() => d.cancelJob(x.job!)}>{t('common.cancel')}</button></div>}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|