/** * Message Card Component * Status and alert messages for Bluetooth and TSCM modes */ const MessageCard = (function() { 'use strict'; // Message types and their styling const MESSAGE_TYPES = { info: { icon: ``, color: '#3b82f6', bgColor: 'rgba(59, 130, 246, 0.1)' }, success: { icon: ``, color: '#22c55e', bgColor: 'rgba(34, 197, 94, 0.1)' }, warning: { icon: ``, color: '#f59e0b', bgColor: 'rgba(245, 158, 11, 0.1)' }, error: { icon: ``, color: '#ef4444', bgColor: 'rgba(239, 68, 68, 0.1)' }, scanning: { icon: ``, color: '#06b6d4', bgColor: 'rgba(6, 182, 212, 0.1)' } }; /** * Escape HTML to prevent XSS */ function escapeHtml(text) { if (text === null || text === undefined) return ''; const div = document.createElement('div'); div.textContent = String(text); return div.innerHTML; } /** * Create a message card */ function createMessageCard(options) { const { type = 'info', title, message, details, actions, dismissible = true, autoHide = 0, id } = options; const config = MESSAGE_TYPES[type] || MESSAGE_TYPES.info; const card = document.createElement('div'); card.className = `message-card message-card-${type}`; if (id) card.id = id; card.style.setProperty('--message-color', config.color); card.style.setProperty('--message-bg', config.bgColor); card.innerHTML = `
${dismissible ? ` ` : ''} ${actions && actions.length > 0 ? ` ` : ''} `; // Dismiss handler if (dismissible) { card.querySelector('.message-card-dismiss').addEventListener('click', () => { card.classList.add('message-card-hiding'); setTimeout(() => card.remove(), 200); }); } // Action handlers if (actions && actions.length > 0) { actions.forEach(action => { if (action.handler) { const btn = action.id ? card.querySelector(`#${action.id}`) : card.querySelector('.message-action-btn'); if (btn) { btn.addEventListener('click', (e) => { action.handler(e, card); }); } } }); } // Auto-hide if (autoHide > 0) { setTimeout(() => { if (card.parentElement) { card.classList.add('message-card-hiding'); setTimeout(() => card.remove(), 200); } }, autoHide); } return card; } /** * Create a scanning status card */ function createScanningCard(options = {}) { const { backend = 'auto', adapter = 'hci0', deviceCount = 0, elapsed = 0, remaining = null } = options; return createMessageCard({ type: 'scanning', title: 'Scanning for Bluetooth devices...', message: `Backend: ${backend} | Adapter: ${adapter}`, details: `Found ${deviceCount} device${deviceCount !== 1 ? 's' : ''}` + (remaining !== null ? ` | ${Math.round(remaining)}s remaining` : ''), dismissible: false, id: 'btScanningStatus' }); } /** * Create a capability warning card */ function createCapabilityWarning(issues) { if (!issues || issues.length === 0) return null; return createMessageCard({ type: 'warning', title: 'Bluetooth Capability Issues', message: issues.join('. '), dismissible: true, actions: [ { label: 'Retry Check', handler: (e, card) => { card.remove(); if (typeof window.checkBtCapabilities === 'function') { window.checkBtCapabilities(); } } } ] }); } /** * Create a baseline status card */ function createBaselineCard(deviceCount, isSet = true) { if (isSet) { return createMessageCard({ type: 'success', title: 'Baseline Set', message: `${deviceCount} device${deviceCount !== 1 ? 's' : ''} saved as baseline`, details: 'New devices will be highlighted', dismissible: true, autoHide: 5000 }); } else { return createMessageCard({ type: 'info', title: 'No Baseline', message: 'Set a baseline to track new devices', dismissible: true, actions: [ { label: 'Set Baseline', primary: true, handler: () => { if (typeof window.setBtBaseline === 'function') { window.setBtBaseline(); } } } ] }); } } /** * Create a scan complete card */ function createScanCompleteCard(deviceCount, duration) { return createMessageCard({ type: 'success', title: 'Scan Complete', message: `Found ${deviceCount} device${deviceCount !== 1 ? 's' : ''} in ${Math.round(duration)}s`, dismissible: true, autoHide: 5000, actions: [ { label: 'Export Results', handler: () => { window.open('/api/bluetooth/export?format=csv', '_blank'); } } ] }); } /** * Create an error card */ function createErrorCard(error, retryHandler) { return createMessageCard({ type: 'error', title: 'Scan Error', message: error, dismissible: true, actions: retryHandler ? [ { label: 'Retry', primary: true, handler: retryHandler } ] : [] }); } /** * Show a message in a container */ function showMessage(container, options) { const card = createMessageCard(options); container.insertBefore(card, container.firstChild); return card; } /** * Remove a message by ID */ function removeMessage(id) { const card = document.getElementById(id); if (card) { card.classList.add('message-card-hiding'); setTimeout(() => card.remove(), 200); } } /** * Update scanning status */ function updateScanningStatus(options) { const existing = document.getElementById('btScanningStatus'); if (existing) { const details = existing.querySelector('.message-card-details'); if (details) { details.textContent = `Found ${options.deviceCount} device${options.deviceCount !== 1 ? 's' : ''}` + (options.remaining !== null ? ` | ${Math.round(options.remaining)}s remaining` : ''); } } } // Public API return { createMessageCard, createScanningCard, createCapabilityWarning, createBaselineCard, createScanCompleteCard, createErrorCard, showMessage, removeMessage, updateScanningStatus, MESSAGE_TYPES }; })(); // Make globally available window.MessageCard = MessageCard;