import { api } from '../api.js'; export class FileUploadView extends HTMLElement { constructor() { super(); this.folderId = null; this.handleEscape = this.handleEscape.bind(this); this.uploadItems = new Map(); } connectedCallback() { document.addEventListener('keydown', this.handleEscape); } disconnectedCallback() { document.removeEventListener('keydown', this.handleEscape); } setFolder(folderId) { this.folderId = folderId; this.render(); this.attachListeners(); this.openFileSelector(); } render() { const folderInfo = this.folderId ? `(Folder ID: ${this.folderId})` : '(Root)'; this.innerHTML = ` `; } openFileSelector() { const fileInput = this.querySelector('#file-input'); if (fileInput) { fileInput.click(); } } attachListeners() { const fileInput = this.querySelector('#file-input'); const backBtn = this.querySelector('#upload-back-btn'); if (backBtn) { backBtn.addEventListener('click', () => this.close()); } if (fileInput) { fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { const view = this.querySelector('.file-upload-view'); if (view) { view.style.display = 'flex'; } this.handleFiles(e.target.files); } else { this.close(); } }); fileInput.addEventListener('cancel', () => { this.close(); }); } } handleEscape(e) { if (e.key === 'Escape') { this.close(); } } close() { window.history.back(); } hide() { document.removeEventListener('keydown', this.handleEscape); this.remove(); } async handleFiles(files) { const uploadList = this.querySelector('#upload-list'); if (!uploadList) return; const uploadPromises = []; for (const file of files) { const itemId = `upload-${Date.now()}-${Math.random()}`; const item = document.createElement('div'); item.className = 'upload-item'; item.id = itemId; item.innerHTML = `
${file.name}
${this.formatFileSize(file.size)}
Uploading...
`; uploadList.appendChild(item); this.uploadItems.set(itemId, { element: item, progress: 0 }); const promise = this.uploadFile(file, itemId) .then(() => { setTimeout(() => { this.uploadItems.delete(itemId); item.remove(); }, 500); }) .catch(error => { const statusEl = item.querySelector('.upload-item-status'); if (statusEl) { statusEl.textContent = 'Failed: ' + error.message; statusEl.classList.add('error'); } }); uploadPromises.push(promise); } await Promise.all(uploadPromises); this.dispatchEvent(new CustomEvent('upload-complete', { bubbles: true })); setTimeout(() => { this.close(); }, 1500); } sortUploadList() { const uploadList = this.querySelector('#upload-list'); if (!uploadList) return; const items = Array.from(this.uploadItems.entries()) .sort((a, b) => b[1].progress - a[1].progress); uploadList.innerHTML = ''; items.forEach(([itemId, data]) => { uploadList.appendChild(data.element); }); } async uploadFile(file, itemId) { const formData = new FormData(); formData.append('file', file); if (this.folderId !== null && this.folderId !== undefined) { formData.append('folder_id', String(this.folderId)); } const xhr = new XMLHttpRequest(); return new Promise((resolve, reject) => { xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; const itemData = this.uploadItems.get(itemId); if (itemData) { itemData.progress = percentComplete; const progressFill = itemData.element.querySelector('.progress-fill'); if (progressFill) { progressFill.style.width = percentComplete + '%'; } this.sortUploadList(); } } }); xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { const itemData = this.uploadItems.get(itemId); if (itemData) { itemData.progress = 100; const progressFill = itemData.element.querySelector('.progress-fill'); const statusEl = itemData.element.querySelector('.upload-item-status'); if (progressFill) { progressFill.style.width = '100%'; } if (statusEl) { statusEl.textContent = 'Complete'; statusEl.classList.add('success'); } this.sortUploadList(); // Sort after completion to move completed items to top } resolve(JSON.parse(xhr.responseText)); } else { reject(new Error(xhr.statusText)); } }); xhr.addEventListener('error', () => reject(new Error('Upload failed'))); xhr.addEventListener('abort', () => reject(new Error('Upload aborted'))); xhr.open('POST', '/files/upload'); xhr.setRequestHeader('Authorization', `Bearer ${api.getToken()}`); xhr.send(formData); }); } formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB'; return (bytes / 1073741824).toFixed(1) + ' GB'; } } customElements.define('file-upload-view', FileUploadView);