init
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// Tiny DOM helpers: element builder, toasts and a confirm dialog.
|
||||
|
||||
/** Build an element. Props: class, on<Event> handlers, booleans, attributes. */
|
||||
export function h(tag, props, ...kids) {
|
||||
const el = document.createElement(tag);
|
||||
for (const [k, v] of Object.entries(props ?? {})) {
|
||||
if (v == null || v === false) continue;
|
||||
if (k === 'class') el.className = v;
|
||||
else if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2).toLowerCase(), v);
|
||||
else if (v === true) el.setAttribute(k, '');
|
||||
else el.setAttribute(k, v);
|
||||
}
|
||||
el.append(...kids.flat(Infinity).filter((c) => c != null && c !== false));
|
||||
return el;
|
||||
}
|
||||
|
||||
export function clear(el) {
|
||||
el.replaceChildren();
|
||||
return el;
|
||||
}
|
||||
|
||||
export function toast(message, kind = 'info') {
|
||||
const host = document.getElementById('toasts');
|
||||
const t = h('div', { class: `toast ${kind}`, role: kind === 'err' ? 'alert' : null }, message);
|
||||
host.append(t);
|
||||
setTimeout(() => {
|
||||
t.classList.add('out');
|
||||
setTimeout(() => t.remove(), 400);
|
||||
}, kind === 'err' ? 8000 : 4500);
|
||||
}
|
||||
|
||||
/** Resolve true if the user confirms. */
|
||||
export function confirmDialog({ title, body, confirmText = 'CONFIRM', cancelText = 'ABORT', danger = false }) {
|
||||
const dlg = document.getElementById('dialog');
|
||||
return new Promise((resolve) => {
|
||||
const finish = (v) => {
|
||||
dlg.close();
|
||||
resolve(v);
|
||||
};
|
||||
clear(dlg).append(
|
||||
h('div', { class: `panel dialog ${danger ? 'danger' : ''}` },
|
||||
h('div', { class: 'panel-title' }, title),
|
||||
h('div', { class: 'panel-body' }, ...[].concat(body).map((p) => h('p', null, p))),
|
||||
h('div', { class: 'actions' },
|
||||
h('button', { class: 'btn', type: 'button', onclick: () => finish(false) }, cancelText),
|
||||
h('button', { class: `btn ${danger ? 'danger' : 'primary'}`, type: 'button', onclick: () => finish(true) }, confirmText),
|
||||
),
|
||||
),
|
||||
);
|
||||
dlg.oncancel = () => resolve(false);
|
||||
dlg.showModal();
|
||||
});
|
||||
}
|
||||
|
||||
export function download(filename, bytes, type = 'application/octet-stream') {
|
||||
const url = URL.createObjectURL(new Blob([bytes], { type }));
|
||||
const a = h('a', { href: url, download: filename });
|
||||
document.body.append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
}
|
||||
|
||||
export function pad(n, width, ch = '0') {
|
||||
return String(n).padStart(width, ch);
|
||||
}
|
||||
|
||||
export function plural(n, one, many = `${one}S`) {
|
||||
return `${n} ${n === 1 ? one : many}`;
|
||||
}
|
||||
Reference in New Issue
Block a user