/** * OxiCloud - Recent Files Module * This file handles tracking and displaying recently accessed files */ // Recent Files Module const recent = { // Base key for storing recent files in localStorage (username is appended) STORAGE_KEY_PREFIX: 'oxicloud_recent_files', // Legacy key (pre-fix, shared across all users) LEGACY_STORAGE_KEY: 'oxicloud_recent_files', // Maximum number of recent files to store MAX_RECENT_FILES: 20, /** * Get the user-specific storage key for recent files. * Falls back to legacy global key if username is unavailable. * @returns {string} localStorage key scoped to the current user */ getStorageKey() { try { const userData = JSON.parse(localStorage.getItem('oxicloud_user') || '{}'); if (userData.username) { return `${this.STORAGE_KEY_PREFIX}_${userData.username}`; } } catch (e) { console.warn('Could not determine current user for recent files key'); } // Should not happen in normal flow — user must be logged in return this.LEGACY_STORAGE_KEY; }, /** * Initialize recent files module */ init() { console.log('Initializing recent files module'); this.migrateFromLegacyKey(); this.ensureRecentFilesStorage(); this.setupEventListeners(); }, /** * Migrate data from the old global key to the user-specific key. * This runs once: if the legacy key has data and the user-specific key * does not yet exist, the data is moved. */ migrateFromLegacyKey() { const userKey = this.getStorageKey(); // Only migrate if the key is actually user-specific if (userKey === this.LEGACY_STORAGE_KEY) return; const legacyData = localStorage.getItem(this.LEGACY_STORAGE_KEY); if (legacyData && !localStorage.getItem(userKey)) { console.log('Migrating recent files from legacy global key to user-specific key'); localStorage.setItem(userKey, legacyData); } // Always remove the legacy key so other users don't see stale data localStorage.removeItem(this.LEGACY_STORAGE_KEY); }, /** * Make sure the recent files storage is initialized */ ensureRecentFilesStorage() { const key = this.getStorageKey(); if (!localStorage.getItem(key)) { localStorage.setItem(key, JSON.stringify([])); } }, /** * Set up event listeners to track file access */ setupEventListeners() { // Listen for custom event when a file is accessed document.addEventListener('file-accessed', (event) => { if (event.detail && event.detail.file) { this.addRecentFile(event.detail.file); } }); }, /** * Add a file to recent files * @param {Object} file - File object containing id, name, folder_id, etc. */ addRecentFile(file) { // Don't add if no file or no ID if (!file || !file.id) { return; } // Get current recent files const recentFiles = this.getRecentFiles(); // Remove if file already exists in recent files const existingIndex = recentFiles.findIndex(item => item.id === file.id); if (existingIndex !== -1) { recentFiles.splice(existingIndex, 1); } // Add file with timestamp to the beginning of the array const fileWithTimestamp = { ...file, accessedAt: Date.now() }; recentFiles.unshift(fileWithTimestamp); // Keep only the most recent files (limit to MAX_RECENT_FILES) const trimmedFiles = recentFiles.slice(0, this.MAX_RECENT_FILES); // Save back to localStorage (user-scoped key) localStorage.setItem(this.getStorageKey(), JSON.stringify(trimmedFiles)); }, /** * Get recent files from localStorage * @returns {Array} Array of recent file objects with timestamps */ getRecentFiles() { try { const recentFilesJson = localStorage.getItem(this.getStorageKey()); return recentFilesJson ? JSON.parse(recentFilesJson) : []; } catch (error) { console.error('Error loading recent files:', error); return []; } }, /** * Clear all recent files */ clearRecentFiles() { localStorage.setItem(this.getStorageKey(), JSON.stringify([])); }, /** * Display recent files in the UI */ async displayRecentFiles() { try { const recentFiles = this.getRecentFiles(); // Clear existing content const filesGrid = document.getElementById('files-grid'); const filesListView = document.getElementById('files-list-view'); filesGrid.innerHTML = ''; filesListView.innerHTML = `
${window.i18n ? window.i18n.t('recent.empty_state') : 'No recent files'}
${window.i18n ? window.i18n.t('recent.empty_hint') : 'Files you open will appear here'}
`; filesGrid.appendChild(emptyState); return; } // Process each recent file for (const recentFile of recentFiles) { this.createRecentFileElement(recentFile, filesGrid, filesListView); } // Update file icons window.ui.updateFileIcons(); } catch (error) { console.error('Error displaying recent files:', error); window.ui.showNotification('Error', 'Error loading recent files'); } }, /** * Create a file element for a recent file * @param {Object} file - Recent file object * @param {HTMLElement} filesGrid - Grid view container * @param {HTMLElement} filesListView - List view container */ createRecentFileElement(file, filesGrid, filesListView) { // Determine icon and type let iconClass = 'fas fa-file'; let iconSpecialClass = ''; let typeLabel = window.i18n ? window.i18n.t('files.file_types.document') : 'Document'; if (file.mime_type) { if (file.mime_type.startsWith('image/')) { iconClass = 'fas fa-file-image'; iconSpecialClass = 'image-icon'; typeLabel = window.i18n ? window.i18n.t('files.file_types.image') : 'Image'; } else if (file.mime_type.startsWith('text/')) { iconClass = 'fas fa-file-alt'; iconSpecialClass = 'text-icon'; typeLabel = window.i18n ? window.i18n.t('files.file_types.text') : 'Text'; } else if (file.mime_type.startsWith('video/')) { iconClass = 'fas fa-file-video'; iconSpecialClass = 'video-icon'; typeLabel = window.i18n ? window.i18n.t('files.file_types.video') : 'Video'; } else if (file.mime_type.startsWith('audio/')) { iconClass = 'fas fa-file-audio'; iconSpecialClass = 'audio-icon'; typeLabel = window.i18n ? window.i18n.t('files.file_types.audio') : 'Audio'; } else if (file.mime_type === 'application/pdf') { iconClass = 'fas fa-file-pdf'; iconSpecialClass = 'pdf-icon'; typeLabel = window.i18n ? window.i18n.t('files.file_types.pdf') : 'PDF'; } } // Format size and date const fileSize = window.formatFileSize ? window.formatFileSize(file.size || 0) : '0 B'; const accessedDate = new Date(file.accessedAt); const formattedDate = accessedDate.toLocaleDateString() + ' ' + accessedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); // Grid view element const fileGridElement = document.createElement('div'); fileGridElement.className = 'file-card recent-item'; fileGridElement.dataset.fileId = file.id; fileGridElement.dataset.fileName = file.name; fileGridElement.dataset.folderId = file.folder_id || ""; fileGridElement.innerHTML = `