import { api } from '../api.js'; export class FileList extends HTMLElement { constructor() { super(); this.currentFolderId = null; this.files = []; this.folders = []; this.selectedFiles = new Set(); this.selectedFolders = new Set(); } async connectedCallback() { await this.loadContents(null); } async loadContents(folderId) { this.currentFolderId = folderId; try { this.folders = await api.listFolders(folderId); this.files = await api.listFiles(folderId); this.selectedFiles.clear(); this.selectedFolders.clear(); this.render(); } catch (error) { console.error('Failed to load contents:', error); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to load contents: ' + error.message, type: 'error' } })); } } setFiles(files) { this.files = files; this.folders = []; this.selectedFiles.clear(); this.selectedFolders.clear(); this.render(); } render() { const hasSelected = this.selectedFiles.size > 0 || this.selectedFolders.size > 0; const allFilesSelected = this.files.length > 0 && this.selectedFiles.size === this.files.length; const allFoldersSelected = this.folders.length > 0 && this.selectedFolders.size === this.folders.length; const allSelected = (this.files.length + this.folders.length) > 0 && allFilesSelected && allFoldersSelected; this.innerHTML = `

Files

${this.folders.map(folder => this.renderFolder(folder)).join('')} ${this.files.map(file => this.renderFile(file)).join('')}
`; this.attachListeners(); } renderFolder(folder) { const isSelected = this.selectedFolders.has(folder.id); const starIcon = folder.is_starred ? '★' : '☆'; // Filled star or empty star const starAction = folder.is_starred ? 'unstar-folder' : 'star-folder'; return `
📁
${folder.name}
`; } renderFile(file) { const isSelected = this.selectedFiles.has(file.id); const icon = this.getFileIcon(file.mime_type); const size = this.formatFileSize(file.size); const starIcon = file.is_starred ? '★' : '☆'; // Filled star or empty star const starAction = file.is_starred ? 'unstar-file' : 'star-file'; return `
${icon}
${file.name}
${size}
`; } getFileIcon(mimeType) { if (mimeType.startsWith('image/')) return '📷'; if (mimeType.startsWith('video/')) return '🎥'; if (mimeType.startsWith('audio/')) return '🎵'; if (mimeType.includes('pdf')) return '📄'; if (mimeType.includes('text')) return '📄'; return '📄'; } 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'; } attachListeners() { this.querySelector('#upload-btn')?.addEventListener('click', () => { this.dispatchEvent(new CustomEvent('upload-request')); }); this.querySelector('#create-folder-btn')?.addEventListener('click', async () => { await this.handleCreateFolder(); }); this.querySelectorAll('.folder-item').forEach(item => { item.addEventListener('dblclick', () => { const folderId = parseInt(item.dataset.folderId); this.dispatchEvent(new CustomEvent('folder-open', { detail: { folderId } })); }); }); this.querySelectorAll('.file-item:not(.folder-item)').forEach(item => { item.addEventListener('click', (e) => { if (!e.target.classList.contains('action-btn') && !e.target.classList.contains('select-item')) { const fileId = parseInt(item.dataset.fileId); const file = this.files.find(f => f.id === fileId); this.dispatchEvent(new CustomEvent('photo-click', { detail: { photo: file }, bubbles: true })); } }); }); this.querySelectorAll('.action-btn').forEach(btn => { btn.addEventListener('click', async (e) => { e.stopPropagation(); const action = btn.dataset.action; const id = parseInt(btn.dataset.id); await this.handleAction(action, id); }); }); this.querySelectorAll('.select-item').forEach(checkbox => { checkbox.addEventListener('change', (e) => { const type = e.target.dataset.type; const id = parseInt(e.target.dataset.id); this.toggleSelectItem(type, id, e.target.checked); }); }); this.querySelector('#select-all')?.addEventListener('change', (e) => { this.toggleSelectAll(e.target.checked); }); this.querySelector('#batch-delete-btn')?.addEventListener('click', () => this.handleBatchAction('delete')); this.querySelector('#batch-move-btn')?.addEventListener('click', () => this.handleBatchAction('move')); this.querySelector('#batch-copy-btn')?.addEventListener('click', () => this.handleBatchAction('copy')); this.querySelector('#batch-star-btn')?.addEventListener('click', () => this.handleBatchAction('star')); this.querySelector('#batch-unstar-btn')?.addEventListener('click', () => this.handleBatchAction('unstar')); this.updateBatchActionVisibility(); } toggleSelectItem(type, id, checked) { if (type === 'file') { if (checked) { this.selectedFiles.add(id); } else { this.selectedFiles.delete(id); } } else if (type === 'folder') { if (checked) { this.selectedFolders.add(id); } else { this.selectedFolders.delete(id); } } this.updateBatchActionVisibility(); } toggleSelectAll(checked) { this.selectedFiles.clear(); this.selectedFolders.clear(); if (checked) { this.files.forEach(file => this.selectedFiles.add(file.id)); this.folders.forEach(folder => this.selectedFolders.add(folder.id)); } this.render(); // Re-render to update checkboxes } updateBatchActionVisibility() { const batchActionsDiv = this.querySelector('.batch-actions'); if (batchActionsDiv) { if (this.selectedFiles.size > 0 || this.selectedFolders.size > 0) { batchActionsDiv.style.display = 'flex'; } else { batchActionsDiv.style.display = 'none'; } } } async handleBatchAction(action) { if ((this.selectedFiles.size === 0 && this.selectedFolders.size === 0)) { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'No items selected for batch operation.', type: 'info' } })); return; } if (!confirm(`Are you sure you want to ${action} ${this.selectedFiles.size + this.selectedFolders.size} items?`)) { return; } try { let targetFolderId = null; if (action === 'move' || action === 'copy') { const folderName = prompt('Enter target folder ID (leave empty for root):'); if (folderName !== null) { targetFolderId = folderName === '' ? null : parseInt(folderName); if (folderName !== '' && isNaN(targetFolderId)) { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Invalid folder ID.', type: 'error' } })); return; } } else { return; // User cancelled } } if (this.selectedFiles.size > 0) { await api.batchFileOperations(action, Array.from(this.selectedFiles), targetFolderId); } if (this.selectedFolders.size > 0) { await api.batchFolderOperations(action, Array.from(this.selectedFolders), targetFolderId); } document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: `Batch ${action} successful!`, type: 'success' } })); await this.loadContents(this.currentFolderId); // Reload contents } catch (error) { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: `Batch ${action} failed: ` + error.message, type: 'error' } })); } } triggerCreateFolder() { this.handleCreateFolder(); } async handleCreateFolder() { const name = prompt('Enter folder name:'); if (name) { try { await api.createFolder(name, this.currentFolderId); await this.loadContents(this.currentFolderId); } catch (error) { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to create folder: ' + error.message, type: 'error' } })); } } } async handleAction(action, id) { try { switch (action) { case 'download': const blob = await api.downloadFile(id); const file = this.files.find(f => f.id === id); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.name; a.click(); URL.revokeObjectURL(url); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File downloaded successfully!', type: 'success' } })); break; case 'rename': const newName = prompt('Enter new name:'); if (newName) { await api.renameFile(id, newName); await this.loadContents(this.currentFolderId); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File renamed successfully!', type: 'success' } })); } break; case 'delete': if (confirm('Are you sure you want to delete this file?')) { await api.deleteFile(id); await this.loadContents(this.currentFolderId); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File deleted successfully!', type: 'success' } })); } break; case 'delete-folder': if (confirm('Are you sure you want to delete this folder?')) { await api.deleteFolder(id); await this.loadContents(this.currentFolderId); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Folder deleted successfully!', type: 'success' } })); } break; case 'share': this.dispatchEvent(new CustomEvent('share-request', { detail: { fileId: id } })); break; case 'star-file': await api.starFile(id); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File starred successfully!', type: 'success' } })); await this.loadContents(this.currentFolderId); break; case 'unstar-file': await api.unstarFile(id); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File unstarred successfully!', type: 'success' } })); await this.loadContents(this.currentFolderId); break; case 'star-folder': await api.starFolder(id); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Folder starred successfully!', type: 'success' } })); await this.loadContents(this.currentFolderId); break; case 'unstar-folder': await api.unstarFolder(id); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Folder unstarred successfully!', type: 'success' } })); await this.loadContents(this.currentFolderId); break; } } catch (error) { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Action failed: ' + error.message, type: 'error' } })); } } } customElements.define('file-list', FileList);