diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..47b059e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "flake8.args": [ + "--max-line-length=120", + "--ignore=W293,E123,W503", + ], +} \ No newline at end of file diff --git a/assets/css/floating-elements.css b/assets/css/floating-elements.css index 1c5bb2f..ae6a70a 100644 --- a/assets/css/floating-elements.css +++ b/assets/css/floating-elements.css @@ -39,21 +39,6 @@ color: white; } -.refresh-btn { - background: var(--success-color); - color: white; -} - -.refresh-btn:hover { - background: var(--success-hover); -} - -.refresh-btn:disabled { - background: var(--disabled-color); - cursor: not-allowed; - transform: none; -} - /* ======================================== KEYBOARD SHORTCUTS HELP ======================================== */ diff --git a/assets/css/folder-metadata.css b/assets/css/folder-metadata.css new file mode 100644 index 0000000..b41a242 --- /dev/null +++ b/assets/css/folder-metadata.css @@ -0,0 +1,203 @@ +/* ======================================== + FOLDER METADATA DISPLAY STYLES + ======================================== */ + +.folder-metadata-container { + margin: 2rem 0; + padding: 1.5rem; + background: var(--card-background); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.folder-metadata-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 2px solid var(--border-color); +} + +.folder-metadata-title { + margin: 0; + color: var(--text-color); + font-size: 1.25rem; + font-weight: 600; +} + +.folder-metadata-edit-btn { + background: var(--accent-color); + color: white; + border: none; + border-radius: 6px; + padding: 0.5rem 1rem; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.folder-metadata-edit-btn:hover { + background: var(--accent-color-dark); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +.folder-metadata-edit-btn:active { + transform: translateY(0); +} + +.folder-metadata-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1rem; +} + +.folder-metadata-item { + display: flex; + flex-direction: column; + background: var(--background); + border: 1px solid var(--border-color-light); + border-radius: 6px; + padding: 1rem; + transition: all 0.2s ease; +} + +.folder-metadata-item:hover { + background: var(--hover-background); + border-color: var(--accent-color); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.folder-metadata-key { + font-weight: 600; + color: var(--accent-color); + margin-bottom: 0.5rem; + font-size: 0.95rem; + text-transform: capitalize; + border-bottom: 1px solid var(--border-color-light); + padding-bottom: 0.25rem; +} + +.folder-metadata-value { + color: var(--text-color); + line-height: 1.5; + word-break: break-word; +} + +.folder-metadata-value a { + color: var(--accent-color); + text-decoration: none; + border-bottom: 1px dotted var(--accent-color); + transition: all 0.2s ease; +} + +.folder-metadata-value a:hover { + color: var(--accent-color-dark); + border-bottom-style: solid; +} + +.folder-metadata-list { + margin: 0; + padding-left: 1.5rem; +} + +.folder-metadata-list li { + margin-bottom: 0.25rem; +} + +.folder-metadata-nested { + background: var(--background-dark); + padding: 0.75rem; + border-radius: 4px; + border-left: 3px solid var(--accent-color); +} + +.folder-metadata-nested-item { + margin-bottom: 0.5rem; +} + +.folder-metadata-nested-item:last-child { + margin-bottom: 0; +} + +.folder-metadata-nested-item strong { + color: var(--accent-color); +} + +.folder-metadata-long-text { + display: inline; +} + +.folder-metadata-expand { + background: none; + border: none; + color: var(--accent-color); + cursor: pointer; + text-decoration: underline; + font-size: 0.9rem; + margin-left: 0.5rem; + padding: 0; +} + +.folder-metadata-expand:hover { + color: var(--accent-color-dark); +} + +.folder-metadata-full-text { + display: none; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .folder-metadata-container { + margin: 1rem 0; + padding: 1rem; + } + + .folder-metadata-grid { + grid-template-columns: 1fr; + gap: 0.75rem; + } + + .folder-metadata-header { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .folder-metadata-edit-btn { + align-self: flex-end; + } +} + +/* Dark theme adjustments */ +@media (prefers-color-scheme: dark) { + .folder-metadata-container { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + } + + .folder-metadata-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + } +} + +/* Animation for expanding text */ +.folder-metadata-full-text.show { + display: inline; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/assets/css/main.css b/assets/css/main.css index 771752c..3713837 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -16,11 +16,15 @@ @import url('./stats.css'); @import url('./comparison.css'); @import url('./metadata.css'); +@import url('./folder-metadata.css'); @import url('./export.css'); /* View controls - must come after grid.css to override */ @import url('./view-controls.css'); +/* Sort controls styling */ +@import url('./sort-controls.css'); + /* View override - force grid layout to work */ @import url('./view-override.css'); diff --git a/assets/css/responsive.css b/assets/css/responsive.css index 04f7bda..1ca81f5 100644 --- a/assets/css/responsive.css +++ b/assets/css/responsive.css @@ -30,9 +30,20 @@ } /* View controls responsive */ + .controls-container { + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .sort-controls { + justify-content: center; + } + .view-controls { margin-right: 0.5rem !important; padding: 8px 12px !important; + justify-content: center; } .view-btn { diff --git a/assets/css/sort-controls.css b/assets/css/sort-controls.css new file mode 100644 index 0000000..3df2439 --- /dev/null +++ b/assets/css/sort-controls.css @@ -0,0 +1,88 @@ +/* ======================================== + SORT CONTROLS STYLING + ======================================== */ + +/* Clean styling for sort controls */ +.sort-controls { + display: flex !important; + align-items: center !important; + gap: 8px !important; + padding: 12px 16px !important; + background: var(--tree-bg) !important; + border: 1px solid var(--border-color) !important; + border-radius: 8px !important; + margin-right: 1rem !important; +} + +.sort-label { + font-size: 0.9rem !important; + color: var(--text-color) !important; + margin-right: 8px !important; + font-weight: 500 !important; +} + +/* Button styling using theme variables */ +.sort-btn { + background: var(--card-bg) !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + padding: 8px 12px !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + font-size: 0.85rem !important; + color: var(--text-color) !important; + display: flex !important; + align-items: center !important; + gap: 4px !important; + outline: none !important; + text-decoration: none !important; + font-family: inherit !important; +} + +.sort-btn:hover { + background: var(--button-bg) !important; + color: white !important; + transform: translateY(-1px) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important; + border-color: var(--button-bg) !important; +} + +.sort-btn.active { + background: var(--button-bg) !important; + color: white !important; + border-color: var(--button-bg) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important; +} + +.sort-order-btn { + background: var(--card-bg) !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + padding: 8px 12px !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + font-size: 1rem !important; + color: var(--text-color) !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 36px !important; + outline: none !important; + text-decoration: none !important; + font-family: inherit !important; +} + +.sort-order-btn:hover { + background: var(--button-bg) !important; + color: white !important; + transform: translateY(-1px) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important; + border-color: var(--button-bg) !important; +} + +.sort-order-btn.active { + background: var(--button-bg) !important; + color: white !important; + border-color: var(--button-bg) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important; +} diff --git a/refresh_gallery.py b/assets/css/sort-debug.css old mode 100755 new mode 100644 similarity index 100% rename from refresh_gallery.py rename to assets/css/sort-debug.css diff --git a/assets/css/view-controls.css b/assets/css/view-controls.css index 9ceece9..b608505 100644 --- a/assets/css/view-controls.css +++ b/assets/css/view-controls.css @@ -2,20 +2,80 @@ VIEW CONTROLS AND LAYOUT MODES ======================================== */ +/* Controls Container */ +.controls-container { + display: flex; + justify-content: space-between; + align-items: center; + margin: 1rem 0; + gap: 2rem; + flex-wrap: wrap; +} + +/* Sort Controls */ +.sort-controls { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--tree-bg); + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.sort-label { + font-size: 0.9rem; + color: var(--text-color); + margin-right: 4px; + font-weight: 500; +} + +.sort-btn, .sort-order-btn { + background: var(--card-bg) !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + padding: 6px 12px !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + font-size: 0.85rem !important; + color: var(--text-color) !important; + display: flex !important; + align-items: center !important; + gap: 4px !important; + outline: none !important; + text-decoration: none !important; +} + +.sort-btn:hover, .sort-order-btn:hover { + background: var(--button-bg) !important; + color: white !important; + transform: translateY(-1px) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; +} + +.sort-btn.active, .sort-order-btn.active { + background: var(--button-bg) !important; + color: white !important; + border-color: var(--button-bg) !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; +} + +.sort-order-btn { + min-width: 32px !important; + justify-content: center !important; + font-size: 1rem !important; +} + /* View Controls */ .view-controls { display: flex; justify-content: flex-end; align-items: center; gap: 12px; - margin: 1rem 0; padding: 12px 16px; background: var(--tree-bg); border: 1px solid var(--border-color); border-radius: 8px; - width: fit-content; - margin-left: auto; - margin-right: 2rem; position: relative; z-index: 10; } @@ -103,6 +163,13 @@ color: var(--text-color); } +.plot-container .plot-date { + font-size: 0.8rem; + color: var(--text-secondary); + margin-top: 0.3rem; + opacity: 0.7; /* Slightly lower opacity for differentiation */ +} + /* Grid View - Override any conflicting styles */ .plot-container.grid-view { display: grid !important; @@ -199,6 +266,9 @@ flex: 1 !important; padding: 0 !important; text-align: left !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; } .plot-container.list-large-view .plot-name { @@ -206,6 +276,16 @@ line-height: 1.4 !important; max-height: none !important; overflow: visible !important; + flex: 1 !important; +} + +.plot-container.list-large-view .plot-date { + flex-shrink: 0 !important; + margin-left: 1rem !important; + margin-top: 0 !important; + font-size: 0.85rem !important; + color: var(--text-secondary) !important; + white-space: nowrap !important; } /* Compact List View */ @@ -237,6 +317,9 @@ padding: 0 !important; flex: 1 !important; text-align: left !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; } .plot-container.list-compact-view .plot-name { @@ -246,6 +329,16 @@ white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; + flex: 1 !important; +} + +.plot-container.list-compact-view .plot-date { + flex-shrink: 0 !important; + margin-left: 1rem !important; + margin-top: 0 !important; + font-size: 0.8rem !important; + color: var(--text-secondary) !important; + white-space: nowrap !important; } /* Highlight effect for all views */ diff --git a/assets/css/view-override.css b/assets/css/view-override.css index 315fe0f..e9bd47c 100644 --- a/assets/css/view-override.css +++ b/assets/css/view-override.css @@ -5,8 +5,8 @@ /* Force grid layout when grid-view class is present */ body .plot-container.grid-view { display: grid !important; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important; - gap: 1rem !important; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) !important; + gap: 1.2rem !important; padding: 1rem 0 !important; } diff --git a/assets/js/folder-metadata.js b/assets/js/folder-metadata.js new file mode 100644 index 0000000..6be3ce4 --- /dev/null +++ b/assets/js/folder-metadata.js @@ -0,0 +1,69 @@ +/** + * Folder Metadata functionality for Gallery + * + * Handles interactions with folder-level metadata display + */ + +// Function to toggle expansion of long metadata text +function toggleMetadataText(button) { + const longText = button.previousElementSibling; + const fullText = button.nextElementSibling; + + if (fullText.style.display === 'none') { + // Show full text + longText.style.display = 'none'; + fullText.style.display = 'inline'; + fullText.classList.add('show'); + button.textContent = 'Show less'; + } else { + // Show truncated text + longText.style.display = 'inline'; + fullText.style.display = 'none'; + fullText.classList.remove('show'); + button.textContent = 'Show more'; + } +} + +// Function to handle folder metadata editing (placeholder for future implementation) +function editFolderMetadata() { + // For now, just show an alert that this feature is coming soon + alert('Folder metadata editing functionality is coming soon!'); + + // Future implementation will: + // 1. Open an edit modal with form fields for each metadata key + // 2. Allow adding/removing metadata fields + // 3. Validate the input + // 4. Send updates to the server + // 5. Refresh the page or update the display dynamically +} + +// Initialize folder metadata functionality when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + console.log('Folder metadata functionality initialized'); + + // Add keyboard shortcuts for metadata editing (future feature) + document.addEventListener('keydown', function(e) { + // Ctrl+M for metadata editing + if (e.ctrlKey && e.key === 'm') { + e.preventDefault(); + const editBtn = document.querySelector('.folder-metadata-edit-btn'); + if (editBtn) { + editFolderMetadata(); + } + } + }); + + // Add accessibility improvements + const metadataItems = document.querySelectorAll('.folder-metadata-item'); + metadataItems.forEach(item => { + item.setAttribute('tabindex', '0'); + item.setAttribute('role', 'listitem'); + }); + + // Add ARIA labels for better accessibility + const metadataContainer = document.querySelector('.folder-metadata-container'); + if (metadataContainer) { + metadataContainer.setAttribute('role', 'region'); + metadataContainer.setAttribute('aria-label', 'Folder metadata information'); + } +}); diff --git a/assets/js/gallery-app.js b/assets/js/gallery-app.js index b24eec6..849286e 100644 --- a/assets/js/gallery-app.js +++ b/assets/js/gallery-app.js @@ -10,6 +10,7 @@ import { ComparisonManager } from './comparison-manager.js'; import { StatsManager } from './stats-manager.js'; import { KeyboardManager } from './keyboard-manager.js'; import { ViewManager } from './view-manager.js'; +import { SortManager } from './sort-manager.js'; import { Utils } from './utils.js'; /** @@ -30,6 +31,7 @@ export class GalleryApp { this.comparisonManager = new ComparisonManager(); this.statsManager = new StatsManager(); this.viewManager = new ViewManager(); + this.sortManager = new SortManager(); this.keyboardManager = new KeyboardManager(this); this.utils = Utils; @@ -39,6 +41,7 @@ export class GalleryApp { window.recentPlotsManager = this.recentPlotsManager; window.comparisonManager = this.comparisonManager; window.viewManager = this.viewManager; + window.sortManager = this.sortManager; window.utils = this.utils; this.init(); @@ -63,7 +66,6 @@ export class GalleryApp { // Backward compatibility methods toggleTheme() { this.themeManager.toggle(); } toggleSidebar() { this.recentPlotsManager.toggleSidebar(); } - refreshGallery() { Utils.refreshGallery(); } toggleCompareMode() { this.comparisonManager.toggleCompareMode(); } closeComparison() { this.comparisonManager.closeComparison(); } selectPlotForComparison(slot) { this.comparisonManager.selectPlotForComparison(slot); } diff --git a/assets/js/keyboard-manager.js b/assets/js/keyboard-manager.js index cc7ad00..c535a9c 100644 --- a/assets/js/keyboard-manager.js +++ b/assets/js/keyboard-manager.js @@ -46,10 +46,24 @@ export class KeyboardManager { } } - if (e.key === 'F5') { + if (e.ctrlKey && e.key === 'n') { e.preventDefault(); - if (this.app.utils && this.app.utils.refreshGallery) { - this.app.utils.refreshGallery(); + if (this.app.sortManager) { + this.app.sortManager.setSortType('name'); + } + } + + if (e.ctrlKey && e.key === 'm') { + e.preventDefault(); + if (this.app.sortManager) { + this.app.sortManager.setSortType('time'); + } + } + + if (e.ctrlKey && e.key === 'o') { + e.preventDefault(); + if (this.app.sortManager) { + this.app.sortManager.toggleSortOrder(); } } diff --git a/assets/js/main.js b/assets/js/main.js index adcfd02..8f924e6 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -10,12 +10,10 @@ let app; // Global functions for onclick handlers (backward compatibility) function toggleTheme() { app.toggleTheme(); } function toggleSidebar() { app.toggleSidebar(); } -function refreshGallery() { app.refreshGallery(); } // Make functions globally available window.toggleTheme = toggleTheme; window.toggleSidebar = toggleSidebar; -window.refreshGallery = refreshGallery; // Initialize application when DOM is ready document.addEventListener('DOMContentLoaded', function() { diff --git a/assets/js/sort-manager.js b/assets/js/sort-manager.js new file mode 100644 index 0000000..8ec9a85 --- /dev/null +++ b/assets/js/sort-manager.js @@ -0,0 +1,197 @@ +/** + * Sort Manager - handles sorting of plot items by name and creation time + */ +export class SortManager { + constructor() { + this.currentSort = 'name'; + this.currentOrder = 'asc'; + this.init(); + } + + /** + * Initialize sort controls + */ + init() { + // Use setTimeout to ensure DOM is ready + setTimeout(() => { + this.setupSortButtons(); + this.loadSavedSort(); + }, 100); + } + + /** + * Setup sort button event listeners + */ + setupSortButtons() { + const sortButtons = document.querySelectorAll('.sort-btn'); + const orderButton = document.querySelector('.sort-order-btn'); + + if (sortButtons.length === 0) { + setTimeout(() => this.setupSortButtons(), 500); + return; + } + + sortButtons.forEach((button) => { + const sortType = button.getAttribute('data-sort'); + + button.addEventListener('click', (e) => { + e.preventDefault(); + this.setSortType(sortType); + }); + }); + + if (orderButton) { + orderButton.addEventListener('click', (e) => { + e.preventDefault(); + this.toggleSortOrder(); + }); + } + + // Initialize button states + this.updateSortButtons(); + this.updateOrderButton(); + } + + /** + * Set the sort type (name or time) + */ + setSortType(sortType) { + if (sortType === this.currentSort) return; + + this.currentSort = sortType; + this.updateSortButtons(); + this.sortPlots(); + this.saveSortPreference(); + } + + /** + * Toggle sort order between ascending and descending + */ + toggleSortOrder() { + this.currentOrder = this.currentOrder === 'asc' ? 'desc' : 'asc'; + this.updateOrderButton(); + this.sortPlots(); + this.saveSortPreference(); + } + + /** + * Update visual state of sort buttons + */ + updateSortButtons() { + const sortButtons = document.querySelectorAll('.sort-btn'); + + sortButtons.forEach(btn => { + if (btn.getAttribute('data-sort') === this.currentSort) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); + } + + /** + * Update visual state of order button + */ + updateOrderButton() { + const orderButton = document.querySelector('.sort-order-btn'); + if (orderButton) { + orderButton.textContent = this.currentOrder === 'asc' ? '↑' : '↓'; + orderButton.setAttribute('data-order', this.currentOrder); + orderButton.title = `Sort Order: ${this.currentOrder === 'asc' ? 'Ascending' : 'Descending'}`; + } + } + + /** + * Sort the plot items + */ + sortPlots() { + const plotContainer = document.getElementById('plotContainer'); + if (!plotContainer) return; + + const plotItems = Array.from(plotContainer.children); + + plotItems.sort((a, b) => { + let valueA, valueB; + + if (this.currentSort === 'name') { + valueA = a.getAttribute('data-name') || ''; + valueB = b.getAttribute('data-name') || ''; + + // Natural sort for better number handling + const result = valueA.localeCompare(valueB, undefined, { + numeric: true, + sensitivity: 'base' + }); + return this.currentOrder === 'asc' ? result : -result; + } else if (this.currentSort === 'time') { + valueA = parseInt(a.getAttribute('data-time') || '0'); + valueB = parseInt(b.getAttribute('data-time') || '0'); + + const result = valueA - valueB; + return this.currentOrder === 'asc' ? result : -result; + } + + return 0; + }); + + // Re-append sorted items + plotItems.forEach(item => { + plotContainer.appendChild(item); + }); + } + + /** + * Save sort preferences to localStorage + */ + saveSortPreference() { + try { + localStorage.setItem('gallery-sort-type', this.currentSort); + localStorage.setItem('gallery-sort-order', this.currentOrder); + } catch (e) { + // Ignore localStorage errors + } + } + + /** + * Load saved sort preferences + */ + loadSavedSort() { + try { + const savedSort = localStorage.getItem('gallery-sort-type'); + const savedOrder = localStorage.getItem('gallery-sort-order'); + + if (savedSort && ['name', 'time'].includes(savedSort)) { + this.currentSort = savedSort; + } + + if (savedOrder && ['asc', 'desc'].includes(savedOrder)) { + this.currentOrder = savedOrder; + } + + this.updateSortButtons(); + this.updateOrderButton(); + + // Sort immediately if there are plots + setTimeout(() => this.sortPlots(), 100); + } catch (e) { + // Ignore localStorage errors, use defaults + } + } + + /** + * Get current sort settings + */ + getCurrentSort() { + return { + type: this.currentSort, + order: this.currentOrder + }; + } + + /** + * Refresh sorting (call this when plot content changes) + */ + refresh() { + this.sortPlots(); + } +} diff --git a/assets/js/utils.js b/assets/js/utils.js index f9817d4..3c5b432 100644 --- a/assets/js/utils.js +++ b/assets/js/utils.js @@ -95,50 +95,6 @@ export class Utils { } } - /** - * Refresh gallery by calling CGI script - */ - static async refreshGallery() { - const refreshBtn = document.getElementById('refreshBtn'); - if (!refreshBtn) return; - - refreshBtn.disabled = true; - refreshBtn.textContent = '⏳'; - - // Use the CGI script path from config if available - let cgiPath = '/cgi-bin/refresh_gallery.py'; - if (window.galleryConfig && window.galleryConfig.paths && window.galleryConfig.paths.cgi_script) { - cgiPath = window.galleryConfig.paths.cgi_script; - // Ensure it starts with a slash for fetch - if (!cgiPath.startsWith('/')) cgiPath = '/' + cgiPath; - } - - try { - const response = await fetch(cgiPath, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - } - }); - - if (response.ok) { - refreshBtn.textContent = '✅'; - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - throw new Error('Refresh failed'); - } - } catch (error) { - console.error('Error refreshing gallery:', error); - refreshBtn.textContent = '❌'; - setTimeout(() => { - refreshBtn.disabled = false; - refreshBtn.textContent = '🔄'; - }, 3000); - } - } - /** * Toggle keyboard shortcuts help display */ diff --git a/assets/js/view-manager.js b/assets/js/view-manager.js index c35381a..0d51208 100644 --- a/assets/js/view-manager.js +++ b/assets/js/view-manager.js @@ -21,12 +21,12 @@ export class ViewManager { */ updateControlsVisibility() { const plotContainer = document.getElementById('plotContainer'); - const viewControls = document.querySelector('.view-controls'); + const controlsContainer = document.querySelector('.controls-container'); - if (!plotContainer || !viewControls) return; + if (!plotContainer || !controlsContainer) return; const hasPlots = plotContainer.children.length > 0; - viewControls.style.display = hasPlots ? 'flex' : 'none'; + controlsContainer.style.display = hasPlots ? 'flex' : 'none'; } /** @@ -34,12 +34,10 @@ export class ViewManager { */ setupViewButtons() { const viewButtons = document.querySelectorAll('.view-btn'); - console.log('ViewManager: Found', viewButtons.length, 'view buttons'); viewButtons.forEach(button => { button.addEventListener('click', (e) => { const newView = button.getAttribute('data-view'); - console.log('ViewManager: Switching to view:', newView); this.switchView(newView); }); }); @@ -49,19 +47,12 @@ export class ViewManager { * Switch to a different view mode */ switchView(viewMode) { - console.log('ViewManager: switchView called with:', viewMode, 'current:', this.currentView); - if (viewMode === this.currentView) return; const plotContainer = document.getElementById('plotContainer'); const viewButtons = document.querySelectorAll('.view-btn'); - if (!plotContainer) { - console.error('ViewManager: plotContainer not found'); - return; - } - - console.log('ViewManager: Plot container found, classes before:', plotContainer.className); + if (!plotContainer) return; // Remove current view class plotContainer.classList.remove( @@ -86,8 +77,6 @@ export class ViewManager { viewMode = 'grid'; } - console.log('ViewManager: Plot container classes after:', plotContainer.className); - // Update button states viewButtons.forEach(btn => { if (btn.getAttribute('data-view') === viewMode) { diff --git a/debug_template.py b/debug_template.py deleted file mode 100644 index 89e9c8a..0000000 --- a/debug_template.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -from pathlib import Path -from jinja2 import Environment, FileSystemLoader - -# Test template rendering -env = Environment(loader=FileSystemLoader(".")) -template = env.get_template("templates/gallery.html") - -# Mock data -test_items = [{ - "name": "test_plot", - "pdf_href": "test_plot.pdf", - "png_href": "test_plot.png", - "metadata": {"key1": "value1", "key2": "value2"} -}] - -test_config = { - "paths": {"work_dir": "/work/kschmidt/web"}, - "ui": {"search_debounce_ms": 300, "max_recent_plots": 20} -} - -rendered = template.render( - title="Test Gallery", - items=test_items, - subdirs=["test_subdir"], - relpath="test/path", - paths=test_config["paths"], - ui=test_config["ui"], - stats={"file_count": 1, "folder_count": 1, "total_size": "100 KB", "total_size_bytes": 100000}, - folder_metadata={}, - assets_path="../assets" -) - -# Extract just the metadata button part -lines = rendered.split('\n') -for i, line in enumerate(lines): - if 'metadata-btn' in line: - print(f"Line {i}: {line.strip()}") - if i > 0: - print(f"Line {i-1}: {lines[i-1].strip()}") - if i < len(lines)-1: - print(f"Line {i+1}: {lines[i+1].strip()}") - break - -print("\n--- Assets path in template ---") -for i, line in enumerate(lines): - if 'assets_path' in line: - print(f"Line {i}: {line.strip()}") diff --git a/docs/EXPORT_FUNCTIONALITY.md b/docs/EXPORT_FUNCTIONALITY.md deleted file mode 100644 index 96669e7..0000000 --- a/docs/EXPORT_FUNCTIONALITY.md +++ /dev/null @@ -1,101 +0,0 @@ -# PDF Export Functionality - -The gallery now includes the ability to export multiple plots to a merged PDF with grid layout. - -## Features - -- Select up to 4 plots from any gallery page -- Automatic grid layout (1x1, 1x2, 2x2) -- Export to PDF with proper scaling -- Keyboard shortcuts for easy access - -## Usage - -### Selecting Plots - -1. Press **Ctrl+E** to enter selection mode -2. Click on up to 4 plot thumbnails to select them -3. Selected plots will show a checkmark overlay -4. A counter shows how many plots are selected (e.g., "2/4 selected") - -### Exporting - -1. After selecting plots, click the **📄** export button (appears in floating buttons) -2. The system will show export instructions with: - - JSON data for the export request - - Command to run the export script -3. Copy the JSON data and save it as `export_request.json` -4. Run the export command in your terminal - -### Keyboard Shortcuts - -- **Ctrl+E**: Toggle selection mode -- **Escape**: Clear all selections and exit selection mode - -## Export Methods - -The system supports two PDF merging methods: - -### Method 1: pdfjam (Recommended) -```bash -# Install on Ubuntu/Debian -sudo apt-get install texlive-extra-utils - -# Check if available -python export_plots.py --check-deps -``` - -### Method 2: Python libraries -```bash -# Install Python dependencies -pip install PyPDF2 reportlab - -# Check if available -python export_plots.py --check-deps -``` - -## Export Script Usage - -```bash -# Basic usage -python export_plots.py export_request.json - -# Specify output file -python export_plots.py export_request.json --output my_plots.pdf - -# Check available dependencies -python export_plots.py --check-deps -``` - -## JSON Request Format - -```json -{ - "plots": [ - "/path/to/plot1.pdf", - "/path/to/plot2.pdf" - ], - "layout": { - "rows": 1, - "cols": 2 - }, - "output_name": "merged_plots.pdf" -} -``` - -## Layout Options - -- **1 plot**: 1x1 grid -- **2 plots**: 1x2 grid (horizontal) -- **3 plots**: 2x2 grid (one empty slot) -- **4 plots**: 2x2 grid (full) - -## Technical Details - -The export functionality consists of: - -- **Frontend**: JavaScript selection UI and export manager -- **Backend**: Python script for PDF merging -- **CSS**: Styling for selection mode and overlays - -The system is designed to work without requiring a web server, using file-based communication between the browser and Python script. diff --git a/docs/METADATA_IMPLEMENTATION.md b/docs/METADATA_IMPLEMENTATION.md deleted file mode 100644 index 5bc2950..0000000 --- a/docs/METADATA_IMPLEMENTATION.md +++ /dev/null @@ -1,103 +0,0 @@ -# Metadata System Implementation Summary - -## What Was Implemented - -### 1. Core Metadata Module (`metadata.py`) -- **`load_metadata_file()`**: Loads YAML/JSON metadata files with error handling -- **`load_folder_metadata()`**: Discovers and loads folder-level metadata (meta.yaml/meta.json) -- **`merge_metadata()`**: Merges parent and child metadata with proper override behavior -- **`resolve_metadata_for_plot()`**: Resolves final metadata for individual plots -- **`save_metadata_cache()`**: Saves resolved metadata to cache files for performance - -### 2. Updated Gallery Generator (`generate_gallery.py`) -- **Hierarchical inheritance**: Folder metadata is inherited by subfolders and plots -- **Plot-specific overrides**: Individual plots can have their own metadata files -- **Template integration**: Metadata is passed to HTML templates for rendering -- **Cache generation**: `meta_cache.json` files are created in each output directory - -### 3. Configuration Updates (`config.py` and `config.yaml`) -- Added `MetadataConfig` class with caching and inheritance options -- Updated main `Config` class to include metadata settings -- Added metadata section to `config.yaml` - -### 4. Documentation and Examples -- **`METADATA_USAGE.md`**: Comprehensive documentation on using the metadata system -- **`examples/meta.yaml`**: Example folder metadata file -- **`examples/specific_plot.json`**: Example plot-specific metadata file -- **`validate_metadata.py`**: Utility script for validating metadata files - -## Key Features - -### Hierarchical Metadata Inheritance -``` -root_folder/ -├── meta.yaml # Base metadata for all plots -├── subfolder/ -│ ├── meta.yaml # Inherits from parent, can override -│ ├── plot1.pdf -│ ├── plot1.yaml # Plot-specific metadata -│ └── plot2.pdf # Uses folder metadata -``` - -### Flexible Format Support -- YAML files: `.yaml`, `.yml` -- JSON files: `.json` -- Automatic format detection based on file extension - -### Template Integration -- `folder_metadata`: Available in templates for folder-level metadata -- `item.metadata`: Available for each plot in the items loop -- Clean separation of concerns between data and presentation - -### Performance Optimization -- Metadata caching in `meta_cache.json` files -- Only reload when source files are newer than cache -- Efficient hierarchical resolution - -## Usage Examples - -### Basic Folder Metadata -```yaml -# meta.yaml -title: "Physics Analysis Results" -experiment: "CMS" -author: - name: "Researcher Name" - institution: "University" -tags: ["analysis", "physics"] -``` - -### Plot-specific Metadata -```yaml -# my_plot.yaml (for my_plot.pdf) -title: "Signal Region Analysis" -plot_type: "histogram" -variables: - x_axis: "mass" - y_axis: "events" -highlight: true -``` - -### Template Usage -```html -
{{ folder_metadata.description }}
- - -{% for item in items %} -