/** * @fileoverview Manages the file view functionality for the room interface. * This file handles: * - Grid and list view rendering * - File sorting and organization * - Breadcrumb navigation * - File action buttons * - Multi-select functionality */ /** * @class ViewManager * @classdesc Manages the visual representation and interaction of files in the room interface. * Handles view switching, file rendering, sorting, and UI updates. */ import { FilePreview } from '../components/filePreview.js'; export class ViewManager { /** * Creates a new ViewManager instance. * @param {RoomManager} roomManager - The parent RoomManager instance */ constructor(roomManager) { console.log('[ViewManager] Initializing...'); this.roomManager = roomManager; this.currentView = 'grid'; this.sortColumn = 'name'; this.sortDirection = 'asc'; this.filePreview = new FilePreview({ containerId: 'filePreviewModal', onClose: () => { // Clean up any resources if needed } }); console.log('[ViewManager] Initialized with roomManager:', roomManager); } /** * Initializes the view with user preferences. * Fetches and applies the user's preferred view type. * @async */ async initializeView() { console.log('[ViewManager] Initializing view...'); try { const response = await fetch('/api/user/preferred_view'); console.log('[ViewManager] Preferences response status:', response.status); if (response.ok) { const data = await response.json(); console.log('[ViewManager] User preferences:', data); this.currentView = data.preferred_view || 'grid'; } this.toggleView(this.currentView); console.log('[ViewManager] View initialized with:', this.currentView); } catch (error) { console.error('[ViewManager] Error initializing view:', error); this.currentView = 'grid'; this.toggleView('grid'); } } /** * Toggles between grid and list views. * Updates UI and saves user preference. * @async * @param {string} view - The view type to switch to ('grid' or 'list') */ async toggleView(view) { console.log('[ViewManager] Toggling view to:', view); this.currentView = view; // Update UI document.getElementById('gridViewBtn').classList.toggle('active', view === 'grid'); document.getElementById('listViewBtn').classList.toggle('active', view === 'list'); document.getElementById('fileGrid').classList.toggle('list-view', view === 'list'); // Save preference try { const response = await fetch('/api/user/preferred_view', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, body: JSON.stringify({ preferred_view: view }) }); console.log('[ViewManager] Save preferences response status:', response.status); } catch (error) { console.error('[ViewManager] Error saving view preference:', error); } // Re-render files if we have any if (this.roomManager.fileManager.currentFiles.length > 0) { console.log('[ViewManager] Re-rendering files with new view'); await this.renderFiles(this.roomManager.fileManager.currentFiles); } } /** * Renders the file list in the current view mode. * @async * @param {Array} files - Array of file objects to render */ async renderFiles(files) { console.log('[ViewManager] Rendering files:', files); const fileGrid = document.getElementById('fileGrid'); if (!files || files.length === 0) { console.log('[ViewManager] No files to render'); fileGrid.innerHTML = '
No files in this folder
'; return; } // Sort files const sortedFiles = this.sortFiles(files); console.log('[ViewManager] Sorted files:', sortedFiles); if (this.currentView === 'list') { console.log('[ViewManager] Rendering list view'); await this.renderListView(sortedFiles); } else { console.log('[ViewManager] Rendering grid view'); await this.renderGridView(sortedFiles); } } /** * Renders the breadcrumb navigation. * Shows the current path and provides navigation controls. */ renderBreadcrumb() { console.log('[ViewManager] Rendering breadcrumb'); const breadcrumb = document.getElementById('breadcrumb'); const upBtn = document.getElementById('upBtn'); const path = this.roomManager.currentPath; // Always show the root node let html = ` `; if (!path) { console.log('[ViewManager] No path, showing only root'); breadcrumb.innerHTML = html; upBtn.style.display = 'none'; return; } console.log('[ViewManager] Building breadcrumb for path:', path); const parts = path.split('/').filter(Boolean); let currentPath = ''; parts.forEach((part, index) => { currentPath += '/' + part; html += ` ${part} `; }); breadcrumb.innerHTML = html; upBtn.style.display = 'inline-block'; upBtn.onclick = () => this.roomManager.navigateToParent(); upBtn.style.color = 'var(--primary-color)'; upBtn.style.borderColor = 'var(--primary-color)'; upBtn.onmouseover = () => { upBtn.style.backgroundColor = 'var(--primary-color)'; upBtn.style.color = 'white'; }; upBtn.onmouseout = () => { upBtn.style.backgroundColor = 'transparent'; upBtn.style.color = 'var(--primary-color)'; }; console.log('[ViewManager] Breadcrumb rendered'); } /** * Renders the file list in list view mode. * @async * @param {Array} files - Array of file objects to render */ async renderListView(files) { console.log('[ViewManager] Rendering list view'); const fileGrid = document.getElementById('fileGrid'); // Create table structure let html = ` `; files.forEach((file, index) => { console.log('[ViewManager] Rendering list item:', file); html += this.renderFileRow(file, index); }); html += '
Name Size Modified Actions
'; fileGrid.innerHTML = html; console.log('[ViewManager] List view rendered'); } /** * Renders the file list in grid view mode. * @async * @param {Array} files - Array of file objects to render */ async renderGridView(files) { console.log('[ViewManager] Rendering grid view'); const fileGrid = document.getElementById('fileGrid'); let html = `
Select All
`; files.forEach((file, index) => { console.log('[ViewManager] Rendering grid item:', file); html += this.renderFileCard(file, index); }); fileGrid.innerHTML = html; console.log('[ViewManager] Grid view rendered'); } /** * Renders a single file row for list view. * @param {Object} file - The file object to render * @param {number} index - The index of the file in the list * @returns {string} HTML string for the file row */ renderFileRow(file, index) { console.log('[ViewManager] Rendering file row:', { file, index }); const isFolder = file.type === 'folder'; const icon = isFolder ? 'fa-folder' : this.getFileIcon(file.name); const size = isFolder ? '-' : this.formatFileSize(file.size); const modified = new Date(file.modified).toLocaleString(); // Create file name element const name = document.createElement('span'); name.className = 'file-name'; name.textContent = file.displayName || file.name; return ` ${name.outerHTML} ${size} ${modified}
${this.renderFileActions(file, index)}
`; } /** * Renders a single file card for grid view. * @param {Object} file - The file object to render * @param {number} index - The index of the file in the list * @returns {string} HTML string for the file card */ renderFileCard(file, index) { console.log('[ViewManager] Rendering file card:', { file, index }); const isFolder = file.type === 'folder'; const icon = isFolder ? 'fa-folder' : this.getFileIcon(file.name); const size = isFolder ? '-' : this.formatFileSize(file.size); const modified = new Date(file.modified).toLocaleString(); // Create file name element const name = document.createElement('span'); name.className = 'file-name'; name.textContent = file.displayName || file.name; return `
${name.outerHTML}
${modified}
${size}
`; } /** * Renders the action buttons for a file. * @param {Object} file - The file object * @param {number} index - The index of the file in the list * @returns {string} HTML string for the action buttons */ renderFileActions(file, index) { console.log('[ViewManager] Rendering file actions:', { file, index }); const actions = []; if (file.type === 'folder') { actions.push(` `); } else { // Check if file type is supported for preview const extension = file.name.split('.').pop().toLowerCase(); const supportedTypes = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'mp4', 'webm', 'mp3', 'wav']; if (supportedTypes.includes(extension)) { actions.push(` `); } if (this.roomManager.canDownload) { actions.push(` `); } } if (this.roomManager.canRename) { actions.push(` `); } if (this.roomManager.canMove) { actions.push(` `); } // Add star button actions.push(` `); if (this.roomManager.canDelete) { actions.push(` `); } return actions.join(''); } /** * Sorts the file list based on current sort settings. * @param {Array} files - Array of file objects to sort * @returns {Array} Sorted array of file objects */ sortFiles(files) { console.log('[ViewManager] Sorting files:', { column: this.sortColumn, direction: this.sortDirection }); return [...files].sort((a, b) => { let comparison = 0; if (this.sortColumn === 'name') { comparison = a.name.localeCompare(b.name); } else if (this.sortColumn === 'size') { comparison = (a.size || 0) - (b.size || 0); } else if (this.sortColumn === 'modified') { comparison = new Date(a.modified) - new Date(b.modified); } return this.sortDirection === 'asc' ? comparison : -comparison; }); } /** * Gets the appropriate icon class for a file based on its extension. * @param {string} filename - The name of the file * @returns {string} Font Awesome icon class name */ getFileIcon(filename) { const extension = filename.split('.').pop().toLowerCase(); console.log('[ViewManager] Getting icon for file:', { filename, extension }); const iconMap = { pdf: 'fa-file-pdf', doc: 'fa-file-word', docx: 'fa-file-word', xls: 'fa-file-excel', xlsx: 'fa-file-excel', ppt: 'fa-file-powerpoint', pptx: 'fa-file-powerpoint', txt: 'fa-file-alt', jpg: 'fa-file-image', jpeg: 'fa-file-image', png: 'fa-file-image', gif: 'fa-file-image', zip: 'fa-file-archive', rar: 'fa-file-archive', mp3: 'fa-file-audio', mp4: 'fa-file-video' }; return iconMap[extension] || 'fa-file'; } /** * Formats a file size in bytes to a human-readable string. * @param {number} bytes - The file size in bytes * @returns {string} Formatted file size string */ formatFileSize(bytes) { if (!bytes) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } /** * Updates the multi-select UI based on current selection state. */ updateMultiSelectUI() { console.log('[ViewManager] Updating multi-select UI'); const selectedItems = this.roomManager.fileManager.getSelectedItems(); const hasSelection = selectedItems.length > 0; document.getElementById('downloadSelectedBtn').style.display = hasSelection && this.roomManager.canDownload ? 'flex' : 'none'; document.getElementById('deleteSelectedBtn').style.display = hasSelection && this.roomManager.canDelete ? 'flex' : 'none'; console.log('[ViewManager] Multi-select UI updated:', { hasSelection, selectedCount: selectedItems.length }); } async previewFile(index) { const file = this.roomManager.fileManager.currentFiles[index]; if (!file) return; const fileUrl = `/api/rooms/${this.roomManager.roomId}/files/${encodeURIComponent(file.name)}?path=${encodeURIComponent(file.path || '')}&preview=true`; await this.filePreview.previewFile({ name: file.name, url: fileUrl }); } }