126 lines
3.3 KiB
JavaScript
126 lines
3.3 KiB
JavaScript
(function () {
|
||
"use strict";
|
||
|
||
const banners = new Map();
|
||
const GAP = 12;
|
||
const DEFAULT_ID = "default";
|
||
|
||
function normalizeId(id) {
|
||
return String(id || DEFAULT_ID);
|
||
}
|
||
|
||
function getBanner(id) {
|
||
id = normalizeId(id);
|
||
|
||
if (banners.has(id)) return banners.get(id).element;
|
||
|
||
const banner = document.createElement("div");
|
||
banner.className = "operation-banner";
|
||
banner.dataset.operationBannerId = id;
|
||
banner.setAttribute("role", "status");
|
||
banner.setAttribute("aria-live", "polite");
|
||
|
||
banner.innerHTML = `
|
||
<div class="ob-row">
|
||
<div class="ob-icon" aria-hidden="true">ℹ</div>
|
||
<div class="ob-content"></div>
|
||
<button class="ob-close" type="button" aria-label="Chiudi">×</button>
|
||
</div>
|
||
<div class="ob-progress-label" aria-live="polite"></div>
|
||
<div class="ob-progress">
|
||
<div class="ob-progress-fill"></div>
|
||
</div>
|
||
`;
|
||
|
||
banner.querySelector(".ob-close").addEventListener("click", () => hide(id));
|
||
document.body.appendChild(banner);
|
||
|
||
banners.set(id, { element: banner, timer: null });
|
||
return banner;
|
||
}
|
||
|
||
function clearTimer(entry) {
|
||
if (entry && entry.timer !== null) {
|
||
clearTimeout(entry.timer);
|
||
entry.timer = null;
|
||
}
|
||
}
|
||
|
||
function reposition() {
|
||
let bottom = 20;
|
||
|
||
for (const entry of [...banners.values()]) {
|
||
const banner = entry.element;
|
||
|
||
if (!banner.classList.contains("is-visible")) continue;
|
||
|
||
banner.style.bottom = `${bottom}px`;
|
||
bottom += banner.getBoundingClientRect().height + GAP;
|
||
}
|
||
}
|
||
|
||
function hide(id = DEFAULT_ID) {
|
||
id = normalizeId(id);
|
||
const entry = banners.get(id);
|
||
if (!entry) return;
|
||
|
||
clearTimer(entry);
|
||
entry.element.classList.remove("is-visible", "is-busy");
|
||
reposition();
|
||
}
|
||
|
||
function show(message, options = {}) {
|
||
const id = normalizeId(options.id);
|
||
const banner = getBanner(id);
|
||
const entry = banners.get(id);
|
||
|
||
clearTimer(entry);
|
||
|
||
const type = options.type || "info";
|
||
const busy = options.busy === true;
|
||
const sticky = options.sticky === true;
|
||
|
||
const icons = {
|
||
info: "ℹ",
|
||
upload: "↑",
|
||
download: "↓",
|
||
success: "✓",
|
||
error: "!"
|
||
};
|
||
|
||
banner.classList.remove(
|
||
"is-info", "is-upload", "is-download", "is-success", "is-error"
|
||
);
|
||
banner.classList.add("is-" + type);
|
||
banner.classList.toggle("is-busy", busy);
|
||
|
||
banner.querySelector(".ob-icon").textContent = icons[type] || icons.info;
|
||
banner.querySelector(".ob-content").textContent = String(message || "");
|
||
banner.classList.add("is-visible");
|
||
|
||
reposition();
|
||
|
||
if (!busy && !sticky) {
|
||
const duration = Number.isFinite(options.duration)
|
||
? options.duration
|
||
: (type === "error" ? 6000 : 3500);
|
||
|
||
entry.timer = setTimeout(() => hide(id), duration);
|
||
}
|
||
}
|
||
|
||
function setProgress(id, value) {
|
||
id = normalizeId(id);
|
||
const banner = getBanner(id);
|
||
const percent = Math.max(0, Math.min(100, Number(value) || 0));
|
||
|
||
banner.classList.add("is-determinate");
|
||
banner.classList.remove("is-busy");
|
||
|
||
banner.querySelector(".ob-progress-fill").style.width = `${percent}%`;
|
||
banner.querySelector(".ob-progress-label").textContent = `${Math.round(percent)}%`;
|
||
reposition();
|
||
}
|
||
|
||
window.operationBanner = { show, hide, setProgress };
|
||
})();
|