Spaces:
Running
Running
| /** | |
| * RecentModels - Manages and displays recently uploaded model history | |
| * Saves file name, size, and upload timestamp to localStorage on successful upload. | |
| * Displays up to 5 most recent models in a sidebar list. | |
| * Requirements: 23.1, 23.2, 23.3, 23.4, 23.5 | |
| */ | |
| class RecentModels { | |
| /** | |
| * @param {string} containerId - ID of the container element | |
| */ | |
| constructor(containerId) { | |
| this._containerId = containerId; | |
| this._container = document.getElementById(containerId); | |
| this._maxItems = 5; | |
| this._storageKey = (typeof CONFIG !== 'undefined' && CONFIG.STORAGE && CONFIG.STORAGE.RECENT_MODELS) | |
| ? CONFIG.STORAGE.RECENT_MODELS | |
| : 'onnx_explorer_recent_models'; | |
| if (!this._container) { | |
| console.warn(`[RecentModels] Container #${containerId} not found`); | |
| } | |
| this._setupEventListeners(); | |
| this.render(); | |
| } | |
| // βββ Private ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Listen for file:uploaded events to auto-save history. | |
| */ | |
| _setupEventListeners() { | |
| if (window.EventBus && typeof CONFIG !== 'undefined' && CONFIG.EVENTS) { | |
| window.EventBus.on(CONFIG.EVENTS.FILE_UPLOADED, (data) => { | |
| if (data && data.fileName) { | |
| const fileSize = (data.file && data.file.size) ? data.file.size : 0; | |
| this.addEntry(data.fileName, fileSize); | |
| } | |
| }); | |
| } | |
| } | |
| /** | |
| * Load history entries from localStorage. | |
| * @returns {Array<{fileName: string, fileSize: number, uploadTime: number}>} | |
| */ | |
| _loadHistory() { | |
| try { | |
| const raw = localStorage.getItem(this._storageKey); | |
| if (!raw) return []; | |
| const parsed = JSON.parse(raw); | |
| return Array.isArray(parsed) ? parsed : []; | |
| } catch (err) { | |
| console.error('[RecentModels] Failed to load history:', err); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Save history entries to localStorage. | |
| * @param {Array} entries | |
| */ | |
| _saveHistory(entries) { | |
| try { | |
| localStorage.setItem(this._storageKey, JSON.stringify(entries)); | |
| } catch (err) { | |
| console.error('[RecentModels] Failed to save history:', err); | |
| } | |
| } | |
| /** | |
| * Format a timestamp into a readable date/time string. | |
| * @param {number} timestamp | |
| * @returns {string} | |
| */ | |
| _formatTime(timestamp) { | |
| if (!timestamp) return 'Unknown'; | |
| try { | |
| const date = new Date(timestamp); | |
| return date.toLocaleString(); | |
| } catch (e) { | |
| return 'Unknown'; | |
| } | |
| } | |
| /** | |
| * Format file size using Formatters utility if available, otherwise fallback. | |
| * @param {number} bytes | |
| * @returns {string} | |
| */ | |
| _formatSize(bytes) { | |
| if (window.Formatters && window.Formatters.formatBytes) { | |
| return window.Formatters.formatBytes(bytes); | |
| } | |
| if (bytes == null || isNaN(bytes) || bytes === 0) return 'Unknown size'; | |
| const k = 1024; | |
| const sizes = ['B', 'KB', 'MB', 'GB']; | |
| const i = Math.min(Math.floor(Math.log(Math.abs(bytes)) / Math.log(k)), sizes.length - 1); | |
| return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; | |
| } | |
| /** | |
| * Escape HTML special characters. | |
| * @param {string} str | |
| * @returns {string} | |
| */ | |
| _escapeHtml(str) { | |
| const div = document.createElement('div'); | |
| div.appendChild(document.createTextNode(String(str))); | |
| return div.innerHTML; | |
| } | |
| /** | |
| * Show a re-upload guidance message when a history item is clicked. | |
| * @param {string} fileName | |
| */ | |
| _showReuploadMessage(fileName) { | |
| const escapedName = this._escapeHtml(fileName); | |
| const message = `To view "${escapedName}" again, please re-upload the file using the Upload button. Browsers cannot store file content in history.`; | |
| if (window.EventBus && typeof CONFIG !== 'undefined' && CONFIG.EVENTS) { | |
| window.EventBus.emit(CONFIG.EVENTS.ERROR_OCCURRED, { | |
| message: message, | |
| type: 'info' | |
| }); | |
| } | |
| if (window.ErrorDisplay && window.ErrorDisplay.show) { | |
| window.ErrorDisplay.show(message, 'info'); | |
| } | |
| } | |
| /** | |
| * Attach click handlers to history items and the clear button. | |
| */ | |
| _attachHandlers() { | |
| if (!this._container) return; | |
| // History item click handlers | |
| const items = this._container.querySelectorAll('.recent-model-item'); | |
| items.forEach((item) => { | |
| const handler = () => { | |
| const fileName = item.dataset.fileName; | |
| if (fileName) this._showReuploadMessage(fileName); | |
| }; | |
| item.addEventListener('click', handler); | |
| item.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| handler(); | |
| } | |
| }); | |
| }); | |
| // Clear history button | |
| const clearBtn = this._container.querySelector('.recent-models-clear-btn'); | |
| if (clearBtn) { | |
| clearBtn.addEventListener('click', () => this.clearHistory()); | |
| clearBtn.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| this.clearHistory(); | |
| } | |
| }); | |
| } | |
| } | |
| // βββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Add a new entry to the recent models history. | |
| * @param {string} fileName - Name of the uploaded file | |
| * @param {number} fileSize - Size in bytes | |
| */ | |
| addEntry(fileName, fileSize) { | |
| const entries = this._loadHistory(); | |
| const newEntry = { | |
| fileName: fileName, | |
| fileSize: fileSize || 0, | |
| uploadTime: Date.now() | |
| }; | |
| // Remove duplicate if same fileName already exists | |
| const filtered = entries.filter((e) => e.fileName !== fileName); | |
| // Add new entry at the beginning | |
| filtered.unshift(newEntry); | |
| // Keep only the most recent entries | |
| const trimmed = filtered.slice(0, this._maxItems); | |
| this._saveHistory(trimmed); | |
| this.render(); | |
| } | |
| /** | |
| * Render the recent models list into the container. | |
| */ | |
| render() { | |
| if (!this._container) return; | |
| const entries = this._loadHistory(); | |
| if (entries.length === 0) { | |
| this._container.innerHTML = '<p class="text-muted small">No recently uploaded models.</p>'; | |
| return; | |
| } | |
| let html = '<ul class="list-group list-group-flush mb-2" role="list" aria-label="Recent models">'; | |
| entries.forEach((entry) => { | |
| const name = this._escapeHtml(entry.fileName || 'Unknown'); | |
| const size = this._formatSize(entry.fileSize); | |
| const time = this._formatTime(entry.uploadTime); | |
| html += ` | |
| <li class="list-group-item list-group-item-action recent-model-item p-2" | |
| role="button" | |
| tabindex="0" | |
| data-file-name="${this._escapeHtml(entry.fileName || '')}" | |
| title="Click to see re-upload instructions" | |
| aria-label="${name} β ${size}, uploaded ${time}"> | |
| <div class="fw-semibold small text-truncate">${name}</div> | |
| <div class="text-muted" style="font-size: 0.75rem;"> | |
| <span>${size}</span> | |
| <span class="mx-1">Β·</span> | |
| <span>${time}</span> | |
| </div> | |
| </li>`; | |
| }); | |
| html += '</ul>'; | |
| html += ` | |
| <button class="btn btn-sm btn-outline-danger recent-models-clear-btn w-100" | |
| type="button" | |
| aria-label="Clear all recent model history"> | |
| <i class="fas fa-trash-alt me-1"></i>Clear History | |
| </button>`; | |
| this._container.innerHTML = html; | |
| this._attachHandlers(); | |
| } | |
| /** | |
| * Clear all history from localStorage and re-render. | |
| */ | |
| clearHistory() { | |
| try { | |
| localStorage.removeItem(this._storageKey); | |
| } catch (err) { | |
| console.error('[RecentModels] Failed to clear history:', err); | |
| } | |
| this.render(); | |
| } | |
| /** | |
| * Get the current history entries. | |
| * @returns {Array<{fileName: string, fileSize: number, uploadTime: number}>} | |
| */ | |
| getHistory() { | |
| return this._loadHistory(); | |
| } | |
| } | |
| window.RecentModels = RecentModels; | |