9740bc64c9
Continuous Deployment / Deploy to Production (push) Blocked by required conditions
Continuous Deployment / Rollback Deployment (push) Blocked by required conditions
Continuous Deployment / Post-deployment Monitoring (push) Blocked by required conditions
Continuous Deployment / Notify Deployment Status (push) Blocked by required conditions
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Continuous Deployment / Deploy to Staging (push) Waiting to run
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Security Report (push) Waiting to run
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
153 lines
5.1 KiB
JavaScript
153 lines
5.1 KiB
JavaScript
// Enhanced Toast Notification System
|
|
// Supports multiple types: success, error, warning, info
|
|
// Stacking, auto-dismiss, manual close, progress bar
|
|
|
|
export class ToastManager {
|
|
constructor() {
|
|
this.container = null;
|
|
this.toasts = [];
|
|
this.idCounter = 0;
|
|
}
|
|
|
|
init() {
|
|
this.container = document.createElement('div');
|
|
this.container.className = 'toast-container';
|
|
this.container.setAttribute('role', 'region');
|
|
this.container.setAttribute('aria-label', 'Notifications');
|
|
this.container.setAttribute('aria-live', 'polite');
|
|
document.body.appendChild(this.container);
|
|
}
|
|
|
|
show(message, options = {}) {
|
|
const {
|
|
type = 'info',
|
|
duration = 5000,
|
|
closable = true,
|
|
icon = null,
|
|
action = null
|
|
} = options;
|
|
|
|
if (!this.container) this.init();
|
|
|
|
const id = ++this.idCounter;
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.dataset.toastId = id;
|
|
|
|
const iconMap = {
|
|
success: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M13.5 4.5L6 12L2.5 8.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
error: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M12 4L4 12M4 4l8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
|
|
warning: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 5v4M8 11h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M7.13 2.22L1.09 12.5a1 1 0 00.87 1.5h12.08a1 1 0 00.87-1.5L8.87 2.22a1 1 0 00-1.74 0z" stroke="currentColor" stroke-width="1.5"/></svg>',
|
|
info: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 7v4M8 5h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
|
|
};
|
|
|
|
const displayIcon = icon || iconMap[type] || iconMap.info;
|
|
|
|
toast.innerHTML = `
|
|
<div class="toast-icon">${displayIcon}</div>
|
|
<div class="toast-content">
|
|
<span class="toast-message">${this.escapeHtml(message)}</span>
|
|
${action ? `<button class="toast-action">${this.escapeHtml(action.label)}</button>` : ''}
|
|
</div>
|
|
${closable ? '<button class="toast-dismiss" aria-label="Dismiss">×</button>' : ''}
|
|
${duration > 0 ? '<div class="toast-progress"><div class="toast-progress-bar"></div></div>' : ''}
|
|
`;
|
|
|
|
// Bind events
|
|
if (closable) {
|
|
toast.querySelector('.toast-dismiss').addEventListener('click', () => this.dismiss(id));
|
|
}
|
|
if (action?.onClick) {
|
|
toast.querySelector('.toast-action')?.addEventListener('click', () => {
|
|
action.onClick();
|
|
this.dismiss(id);
|
|
});
|
|
}
|
|
|
|
this.container.appendChild(toast);
|
|
|
|
// Trigger enter animation
|
|
requestAnimationFrame(() => toast.classList.add('toast-enter'));
|
|
|
|
// Auto-dismiss
|
|
let timeoutId = null;
|
|
if (duration > 0) {
|
|
const progressBar = toast.querySelector('.toast-progress-bar');
|
|
if (progressBar) {
|
|
progressBar.style.animationDuration = `${duration}ms`;
|
|
progressBar.classList.add('toast-progress-animate');
|
|
}
|
|
timeoutId = setTimeout(() => this.dismiss(id), duration);
|
|
}
|
|
|
|
// Pause on hover
|
|
toast.addEventListener('mouseenter', () => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
const bar = toast.querySelector('.toast-progress-bar');
|
|
if (bar) bar.style.animationPlayState = 'paused';
|
|
}
|
|
});
|
|
toast.addEventListener('mouseleave', () => {
|
|
if (duration > 0) {
|
|
const bar = toast.querySelector('.toast-progress-bar');
|
|
if (bar) bar.style.animationPlayState = 'running';
|
|
timeoutId = setTimeout(() => this.dismiss(id), duration / 2);
|
|
}
|
|
});
|
|
|
|
this.toasts.push({ id, toast, timeoutId });
|
|
return id;
|
|
}
|
|
|
|
dismiss(id) {
|
|
const index = this.toasts.findIndex(t => t.id === id);
|
|
if (index === -1) return;
|
|
|
|
const { toast, timeoutId } = this.toasts[index];
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
|
|
toast.classList.add('toast-exit');
|
|
toast.addEventListener('animationend', () => {
|
|
toast.remove();
|
|
}, { once: true });
|
|
|
|
this.toasts.splice(index, 1);
|
|
}
|
|
|
|
success(message, options = {}) {
|
|
return this.show(message, { ...options, type: 'success' });
|
|
}
|
|
|
|
error(message, options = {}) {
|
|
return this.show(message, { ...options, type: 'error', duration: options.duration || 8000 });
|
|
}
|
|
|
|
warning(message, options = {}) {
|
|
return this.show(message, { ...options, type: 'warning', duration: options.duration || 6000 });
|
|
}
|
|
|
|
info(message, options = {}) {
|
|
return this.show(message, { ...options, type: 'info' });
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
dispose() {
|
|
this.toasts.forEach(({ timeoutId }) => {
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
});
|
|
this.toasts = [];
|
|
if (this.container?.parentNode) {
|
|
this.container.parentNode.removeChild(this.container);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const toastManager = new ToastManager();
|