diff --git a/assets/css/metadata-section.css b/assets/css/metadata-section.css index 5824a9c..b3f7fb9 100644 --- a/assets/css/metadata-section.css +++ b/assets/css/metadata-section.css @@ -60,6 +60,129 @@ display: none; /* Hidden by default */ } +/* Metadata Header with File Path */ +.metadata-header { + padding: 1rem; + background: var(--card-bg); + border-bottom: 1px solid var(--border-color); +} + +.metadata-file-info { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} + +.file-path-label { + font-weight: 600; + color: var(--text-color); + white-space: nowrap; + font-size: 0.9rem; +} + +.file-path { + background: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 0.4rem 0.6rem; + font-family: 'Courier New', monospace; + font-size: 0.8rem; + color: var(--link-color); + flex: 1; + min-width: 200px; + word-break: break-all; + user-select: all; +} + +.copy-path-btn { + background: var(--link-color); + color: white; + border: none; + border-radius: 4px; + padding: 0.4rem 0.8rem; + cursor: pointer; + font-size: 0.8rem; + transition: all 0.2s ease; + white-space: nowrap; + font-weight: 500; +} + +.copy-path-btn:hover { + background: var(--link-hover); + transform: translateY(-1px); +} + +.copy-path-btn:active { + transform: scale(0.95); +} + +.copy-path-btn.copied { + background: #4CAF50; + transform: scale(1.05); +} + +/* Tip Icon with Hover Tooltip */ +.tip-icon { + cursor: help; + font-size: 1.2rem; + position: relative; + display: inline-block; + margin-left: 0.25rem; + opacity: 0.8; + transition: opacity 0.2s ease; +} + +.tip-icon:hover { + opacity: 1; +} + +/* Custom tooltip for tip icon */ +.tip-icon::after { + content: attr(title); + position: absolute; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + background: #333; + color: white; + padding: 0.75rem; + border-radius: 6px; + font-size: 0.85rem; + white-space: normal; + width: 280px; + text-align: left; + z-index: 1000; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + line-height: 1.4; + font-weight: normal; + font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); +} + +/* Tooltip arrow */ +.tip-icon::before { + content: ''; + position: absolute; + bottom: 115%; + left: 50%; + transform: translateX(-50%); + border: 6px solid transparent; + border-top-color: #333; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + z-index: 1001; +} + +.tip-icon:hover::after, +.tip-icon:hover::before { + opacity: 1; + visibility: visible; +} + /* Grid Layout */ .metadata-grid { padding: 1rem; @@ -160,4 +283,14 @@ .metadata-key { white-space: normal; } + + .metadata-file-info { + flex-direction: column; + align-items: stretch; + gap: 0.5rem; + } + + .file-path { + min-width: auto; + } } diff --git a/assets/js/metadata-section.js b/assets/js/metadata-section.js index 70e52ac..f262411 100644 --- a/assets/js/metadata-section.js +++ b/assets/js/metadata-section.js @@ -50,6 +50,67 @@ function expandText(button) { } } +// Copy metadata file path to clipboard +async function copyMetadataPath() { + const pathElement = document.getElementById('metadata-file-path'); + const copyBtn = document.querySelector('.copy-path-btn'); + + if (!pathElement || !copyBtn) { + console.log('Path element or copy button not found'); + return; + } + + const path = pathElement.textContent; + console.log('Attempting to copy path:', path); + + try { + // Try using the modern clipboard API + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(path); + } else { + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = path; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + document.execCommand('copy'); + textArea.remove(); + } + + // Visual feedback + const originalText = copyBtn.innerHTML; + copyBtn.innerHTML = '✅ Copied!'; + copyBtn.classList.add('copied'); + + setTimeout(() => { + copyBtn.innerHTML = originalText; + copyBtn.classList.remove('copied'); + }, 2000); + + console.log('Path copied successfully'); + + } catch (err) { + console.error('Failed to copy path: ', err); + + // Show error feedback + const originalText = copyBtn.innerHTML; + copyBtn.innerHTML = '❌ Failed'; + + setTimeout(() => { + copyBtn.innerHTML = originalText; + }, 2000); + } +} + +// Make functions globally available +window.toggleMetadataSection = toggleMetadataSection; +window.expandText = expandText; +window.copyMetadataPath = copyMetadataPath; + // Initialize on page load document.addEventListener('DOMContentLoaded', function() { console.log('Metadata section script loaded'); diff --git a/generate_gallery.py b/generate_gallery.py index 3eae8b5..d12191f 100644 --- a/generate_gallery.py +++ b/generate_gallery.py @@ -27,7 +27,8 @@ from orchestration.metadata import ( load_folder_metadata, merge_metadata, resolve_metadata_for_plot, - save_metadata_cache + save_metadata_cache, + get_metadata_file_path ) @@ -73,7 +74,7 @@ def convert_pdf_to_png(pdf_path: Path) -> None: else: print(f"PDF {pdf_path.name} is newer than PNG, reconverting...") - print(f"Converting {pdf_path} → {png_path}") + print(f"Converting\n\t{pdf_path}\n → {png_path}") subprocess.run([ "convert", "-density", str(CONFIG.png_dpi), @@ -143,7 +144,7 @@ def build_gallery(source_dir: Path, web_dir: Path, web_png = web_dir / png_file.name if needs_update(pdf_file, web_pdf): - print(f"Copying {pdf_file} to {web_pdf}") + print(f"Copying\n\t{pdf_file}\n → {web_pdf}") shutil.copy2(pdf_file, web_pdf) else: print(f"Skipping {pdf_file.name} (up to date)") @@ -229,7 +230,9 @@ def build_gallery(source_dir: Path, web_dir: Path, ui=CONFIG.ui, stats=stats, folder_metadata=current_metadata, - assets_path=assets_path + assets_path=assets_path, + source_dir=str(source_dir), + metadata_file_path=get_metadata_file_path(source_dir) ) f.write(rendered_html) @@ -409,7 +412,9 @@ def main(clean_first: bool = False) -> None: ui=CONFIG.ui, stats=stats, folder_metadata={}, - assets_path=assets_path + assets_path=assets_path, + source_dir=str(source_path.parent), + metadata_file_path=get_metadata_file_path(source_path.parent) )) print(f"Generated {output_html}") diff --git a/orchestration/metadata.py b/orchestration/metadata.py index a6bfb84..00bf445 100644 --- a/orchestration/metadata.py +++ b/orchestration/metadata.py @@ -50,7 +50,7 @@ def load_metadata_file(metadata_path: Path) -> Dict[str, Any]: def load_folder_metadata(folder_path: Path) -> Dict[str, Any]: """ - Load folder-level metadata from meta.yaml, meta.json, or metadata.json. + Load folder-level metadata from metadata.yaml, metadata.yml, or metadata.json. Args: folder_path: Path to the folder to check for metadata @@ -58,8 +58,8 @@ def load_folder_metadata(folder_path: Path) -> Dict[str, Any]: Returns: Dictionary containing the folder metadata """ - # Try YAML first, then JSON (including metadata.json for backwards compatibility) - for filename in ['meta.yaml', 'meta.yml', 'meta.json', 'metadata.json']: + # Try YAML first, then JSON for backwards compatibility + for filename in ['metadata.yaml', 'metadata.yml', 'metadata.json']: metadata_path = folder_path / filename if metadata_path.exists(): return load_metadata_file(metadata_path) @@ -67,6 +67,29 @@ def load_folder_metadata(folder_path: Path) -> Dict[str, Any]: return {} +def get_metadata_file_path(folder_path: Path) -> str: + """ + Get the metadata file path for a folder. Returns existing file if found, + otherwise suggests metadata.yaml (preferred format). + + Args: + folder_path: Path to the folder to check for metadata + + Returns: + String path to the metadata file (existing or suggested) + """ + # Preferred order: YAML first, then JSON + preferred_files = ['metadata.yaml', 'metadata.yml', 'metadata.json'] + + for filename in preferred_files: + metadata_path = folder_path / filename + if metadata_path.exists(): + return str(metadata_path) + + # If no file exists, suggest metadata.yaml (preferred format) + return str(folder_path / 'metadata.yaml') + + def merge_metadata( parent_metadata: Dict[str, Any], child_metadata: Dict[str, Any] diff --git a/templates/gallery.html b/templates/gallery.html index 7a5d2f4..da2ea98 100644 --- a/templates/gallery.html +++ b/templates/gallery.html @@ -44,6 +44,19 @@