/** * EasyStream Upload Progress Widget * * Displays real-time upload progress for files being uploaded * Features: * - Multiple simultaneous uploads * - Real-time progress bars * - Processing status indicators * - Cancel uploads * - Auto-hide when complete */ class UploadProgressWidget { constructor(options = {}) { this.options = { apiUrl: options.apiUrl || '/api/upload/progress.php', pollInterval: options.pollInterval || 1000, // Poll every second autoHide: options.autoHide !== false, hideDelay: options.hideDelay || 3000, ...options }; this.uploads = new Map(); this.widget = null; this.pollTimer = null; this.init(); } init() { this.createWidget(); this.loadExistingUploads(); } createWidget() { const widget = document.createElement('div'); widget.className = 'upload-progress-widget'; widget.innerHTML = `
`; document.body.appendChild(widget); this.widget = widget; // Collapse functionality widget.querySelector('.collapse-btn').addEventListener('click', () => { widget.classList.toggle('collapsed'); widget.querySelector('.collapse-btn').textContent = widget.classList.contains('collapsed') ? '+' : '−'; }); this.injectStyles(); } injectStyles() { const style = document.createElement('style'); style.textContent = ` .upload-progress-widget { position: fixed; bottom: 20px; right: 20px; width: 380px; max-height: 500px; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15); z-index: 9999; display: none; flex-direction: column; overflow: hidden; } .upload-progress-widget.visible { display: flex; } .upload-progress-widget.collapsed .upload-widget-body { display: none; } .upload-widget-header { padding: 16px 20px; background: #f9f9f9; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } .upload-widget-header h3 { margin: 0; font-size: 16px; font-weight: 600; } .collapse-btn { background: none; border: none; font-size: 20px; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .upload-widget-body { overflow-y: auto; max-height: 440px; } .upload-list { padding: 12px; } .upload-item { padding: 12px; margin-bottom: 12px; background: #f9f9f9; border-radius: 8px; border: 1px solid #e0e0e0; } .upload-item.completed { background: #e8f5e9; border-color: #4caf50; } .upload-item.failed { background: #ffebee; border-color: #f44336; } .upload-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; } .upload-filename { font-weight: 500; font-size: 14px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .upload-cancel { background: none; border: none; color: #f44336; cursor: pointer; padding: 0 4px; font-size: 18px; } .upload-status { font-size: 12px; color: #666; margin-bottom: 8px; } .upload-progress-bar { height: 6px; background: #e0e0e0; border-radius: 3px; overflow: hidden; margin-bottom: 4px; } .upload-progress-fill { height: 100%; background: linear-gradient(90deg, #065fd4, #1e88e5); transition: width 0.3s ease; position: relative; } .upload-progress-fill.processing { background: linear-gradient(90deg, #f57c00, #ff9800); animation: pulse 1.5s ease-in-out infinite; } .upload-progress-fill.completed { background: #4caf50; } .upload-progress-fill.failed { background: #f44336; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } .upload-percent { font-size: 12px; color: #666; text-align: right; } .upload-error { font-size: 12px; color: #f44336; margin-top: 4px; } @media (max-width: 480px) { .upload-progress-widget { width: calc(100% - 40px); right: 20px; left: 20px; } } @media (prefers-color-scheme: dark) { .upload-progress-widget { background: #212121; } .upload-widget-header { background: #2a2a2a; border-bottom-color: #303030; } .upload-item { background: #2a2a2a; border-color: #303030; } } `; document.head.appendChild(style); } addUpload(uploadId, filename, fileType) { this.uploads.set(uploadId, { id: uploadId, filename: filename, fileType: fileType, status: 'uploading', percent: 0 }); this.renderUpload(uploadId); this.show(); this.startPolling(); } renderUpload(uploadId) { const upload = this.uploads.get(uploadId); if (!upload) return; const list = this.widget.querySelector('.upload-list'); let item = list.querySelector(`[data-upload-id="${uploadId}"]`); if (!item) { item = document.createElement('div'); item.className = 'upload-item'; item.dataset.uploadId = uploadId; list.appendChild(item); } const statusText = this.getStatusText(upload.status, upload.processingStep); const progressClass = upload.status === 'processing' || upload.status === 'encoding' ? 'processing' : upload.status === 'completed' ? 'completed' : upload.status === 'failed' ? 'failed' : ''; item.className = `upload-item ${upload.status}`; item.innerHTML = `