/** * Metadata Popup functionality for Gallery * * Handles showing metadata in small popups overlaid on plot thumbnails */ class MetadataPopup { constructor() { this.activePopup = null; this.init(); } init() { // Close popup when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.metadata-btn') && !e.target.closest('.metadata-popup')) { this.hidePopup(); } }); // Close popup on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this.hidePopup(); } }); } showPopup(button, plotName, metadata) { // Hide any existing popup this.hidePopup(); // Create popup element const popup = document.createElement('div'); popup.className = 'metadata-popup'; popup.innerHTML = this.formatMetadata(plotName, metadata); // Position popup relative to button const rect = button.getBoundingClientRect(); popup.style.position = 'fixed'; popup.style.left = rect.left + 'px'; popup.style.top = (rect.bottom + 5) + 'px'; popup.style.zIndex = '1000'; // Add to DOM document.body.appendChild(popup); this.activePopup = popup; // Adjust position if popup goes off screen setTimeout(() => { const popupRect = popup.getBoundingClientRect(); // Adjust horizontal position if (popupRect.right > window.innerWidth) { popup.style.left = (rect.right - popupRect.width) + 'px'; } // Adjust vertical position if (popupRect.bottom > window.innerHeight) { popup.style.top = (rect.top - popupRect.height - 5) + 'px'; } }, 0); // Animate in requestAnimationFrame(() => { popup.classList.add('show'); }); } hidePopup() { if (this.activePopup) { this.activePopup.classList.remove('show'); setTimeout(() => { if (this.activePopup && this.activePopup.parentNode) { this.activePopup.parentNode.removeChild(this.activePopup); } this.activePopup = null; }, 200); } } formatMetadata(plotName, metadata) { if (!metadata || Object.keys(metadata).length === 0) { return `

${plotName}

No metadata available

`; } let html = `

${plotName}

`; // Show priority fields first const priorityFields = ['title', 'description', 'plot_type', 'experiment']; const processedKeys = new Set(); // Display priority fields first for (const key of priorityFields) { if (metadata[key] !== undefined) { html += this.formatMetadataField(key, metadata[key]); processedKeys.add(key); } } // Show file info if available if (metadata.file_info) { html += `
File Information
`; html += this.formatMetadataField('File Size', metadata.file_info.size); if (metadata.file_info.extension) { html += this.formatMetadataField('Format', metadata.file_info.extension); } processedKeys.add('file_info'); } // Show timestamps if available if (metadata.timestamps) { html += `
Timestamps
`; if (metadata.timestamps.created_human) { html += this.formatMetadataField('Created', metadata.timestamps.created_human); } if (metadata.timestamps.modified_human) { html += this.formatMetadataField('Modified', metadata.timestamps.modified_human); } processedKeys.add('timestamps'); } // Show extracted info if available if (metadata.extracted_info && Object.keys(metadata.extracted_info).length > 0) { html += `
Plot Details
`; for (const [key, value] of Object.entries(metadata.extracted_info)) { html += this.formatMetadataField(key, value); } processedKeys.add('extracted_info'); } // Display other fields (excluding generation info unless it's the only data) const otherKeys = Object.keys(metadata).filter(key => !processedKeys.has(key) && key !== 'generation' ); if (otherKeys.length > 0) { html += `
Additional Information
`; for (const key of otherKeys) { html += this.formatMetadataField(key, metadata[key]); } } // Show generation info last if there's no other meaningful data if (processedKeys.size <= 2 && metadata.generation) { html += `
Generation Info
`; if (metadata.generation.generation_time) { const genDate = new Date(metadata.generation.generation_time); html += this.formatMetadataField('Generated', genDate.toLocaleString()); } } html += '
'; return html; } formatMetadataField(key, value) { const displayKey = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); let formattedValue; if (value === null || value === undefined) { formattedValue = 'null'; } else if (typeof value === 'object') { if (Array.isArray(value)) { if (value.length <= 3) { formattedValue = value.map(item => `${item}`).join(' '); } else { formattedValue = `${value.slice(0, 3).map(item => `${item}`).join(' ')} +${value.length - 3} more`; } } else { // Show object as compact JSON for small objects, or just key count for large ones const keys = Object.keys(value); if (keys.length <= 3) { formattedValue = '' + JSON.stringify(value) + ''; } else { formattedValue = `Object with ${keys.length} properties`; } } } else { // Truncate long strings const str = String(value); formattedValue = str.length > 50 ? str.substring(0, 47) + '...' : str; } return `
${displayKey}: ${formattedValue}
`; } } // Global instance window.metadataPopup = new MetadataPopup(); // Global function for template usage window.showMetadataPopup = function(button, plotName, metadata) { window.metadataPopup.showPopup(button, plotName, metadata); };