diff --git a/assets/css/main.css b/assets/css/main.css index 32a44c7..771752c 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -18,5 +18,11 @@ @import url('./metadata.css'); @import url('./export.css'); -/* Responsive design */ +/* View controls - must come after grid.css to override */ +@import url('./view-controls.css'); + +/* View override - force grid layout to work */ +@import url('./view-override.css'); + +/* Responsive design - last to override everything */ @import url('./responsive.css'); diff --git a/assets/css/responsive.css b/assets/css/responsive.css index 7b2a7a8..04f7bda 100644 --- a/assets/css/responsive.css +++ b/assets/css/responsive.css @@ -28,4 +28,21 @@ .search-container { max-width: 100%; } + + /* View controls responsive */ + .view-controls { + margin-right: 0.5rem !important; + padding: 8px 12px !important; + } + + .view-btn { + min-width: 40px !important; + min-height: 40px !important; + padding: 8px 12px !important; + } + + .view-btn svg { + width: 16px !important; + height: 16px !important; + } } diff --git a/assets/css/view-controls.css b/assets/css/view-controls.css new file mode 100644 index 0000000..9ceece9 --- /dev/null +++ b/assets/css/view-controls.css @@ -0,0 +1,310 @@ +/* ======================================== + VIEW CONTROLS AND LAYOUT MODES + ======================================== */ + +/* 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; +} + +.view-btn { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 12px 16px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 1.2rem; + color: var(--text-color); + min-width: 48px; + min-height: 48px; + display: flex; + align-items: center; + justify-content: center; +} + +.view-btn svg { + width: 18px; + height: 18px; +} + +.view-btn:hover { + background: var(--button-bg); + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.view-btn.active { + background: var(--button-bg); + color: white; + border-color: var(--button-bg); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Plot Container Base Styles */ +.plot-container { + margin: 1rem 0; + transition: all 0.3s ease; + clear: both; +} + +.plot-container .plot-item { + transition: all 0.2s ease; + border-radius: 8px; + overflow: hidden; + background: var(--card-bg); + border: 1px solid transparent; +} + +.plot-container .plot-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + border-color: var(--border-color); +} + +.plot-container .plot-link { + color: var(--link-color); + text-decoration: none; + display: block; +} + +.plot-container .plot-thumbnail { + width: 100%; + height: auto; + border-radius: 6px; + transition: all 0.2s ease; +} + +.plot-container .plot-info { + padding: 0.8rem; +} + +.plot-container .plot-name { + word-wrap: break-word; + word-break: break-word; + hyphens: auto; + font-size: 0.9rem; + line-height: 1.3; + font-weight: 500; + color: var(--text-color); +} + +/* Grid View - Override any conflicting styles */ +.plot-container.grid-view { + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important; + gap: 1.2rem !important; + padding: 1rem 0 !important; +} + +.plot-container.grid-view .plot-item, +.plot-container.grid-view .grid-item { + text-align: center !important; + background: var(--card-bg) !important; + border-radius: 8px !important; + padding: 0.8rem !important; + transition: all 0.2s ease !important; + border: 1px solid transparent !important; + display: block !important; + width: auto !important; + max-width: none !important; +} + +.plot-container.grid-view .plot-item:hover, +.plot-container.grid-view .grid-item:hover { + transform: translateY(-2px) !important; + box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important; + border-color: var(--border-color) !important; +} + +.plot-container.grid-view .plot-link { + display: block !important; + color: var(--link-color) !important; + text-decoration: none !important; +} + +.plot-container.grid-view .plot-thumbnail { + max-width: 100% !important; + height: auto !important; + border: 1px solid var(--border-color) !important; + display: block !important; + width: 100% !important; + object-fit: contain !important; + border-radius: 6px !important; +} + +.plot-container.grid-view .plot-info { + padding: 0.8rem 0 0 0 !important; +} + +.plot-container.grid-view .plot-name { + max-height: 3.9rem !important; + overflow: hidden !important; + margin-top: 0.8rem !important; + display: block !important; + word-wrap: break-word !important; + word-break: break-word !important; + hyphens: auto !important; + font-size: 0.9rem !important; + line-height: 1.3 !important; + font-weight: 500 !important; + color: var(--text-color) !important; + text-align: center !important; +} + +/* Large List View */ +.plot-container.list-large-view { + display: flex !important; + flex-direction: column !important; + gap: 0.8rem !important; +} + +.plot-container.list-large-view .plot-item { + display: flex !important; + align-items: center !important; + padding: 1rem !important; + gap: 1rem !important; +} + +.plot-container.list-large-view .plot-link { + flex-shrink: 0 !important; + width: 120px !important; + height: 90px !important; + overflow: hidden !important; + border-radius: 6px !important; + border: 1px solid var(--border-color) !important; +} + +.plot-container.list-large-view .plot-thumbnail { + width: 100% !important; + height: 100% !important; + object-fit: cover !important; +} + +.plot-container.list-large-view .plot-info { + flex: 1 !important; + padding: 0 !important; + text-align: left !important; +} + +.plot-container.list-large-view .plot-name { + font-size: 1rem !important; + line-height: 1.4 !important; + max-height: none !important; + overflow: visible !important; +} + +/* Compact List View */ +.plot-container.list-compact-view { + display: flex !important; + flex-direction: column !important; + gap: 0.4rem !important; +} + +.plot-container.list-compact-view .plot-item { + display: flex !important; + align-items: center !important; + padding: 0.6rem 1rem !important; + gap: 0.8rem !important; + border-radius: 4px !important; +} + +.plot-container.list-compact-view .plot-link { + flex: 1 !important; + display: flex !important; + align-items: center !important; +} + +.plot-container.list-compact-view .plot-thumbnail { + display: none !important; +} + +.plot-container.list-compact-view .plot-info { + padding: 0 !important; + flex: 1 !important; + text-align: left !important; +} + +.plot-container.list-compact-view .plot-name { + font-size: 0.95rem !important; + line-height: 1.2 !important; + margin: 0 !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; +} + +/* Highlight effect for all views */ +.plot-item.highlighted { + border-color: var(--button-bg); + box-shadow: 0 0 15px rgba(0, 120, 212, 0.3); + transform: translateY(-2px); + animation: highlightPulse 2s ease-in-out; +} + +@keyframes highlightPulse { + 0%, 100% { transform: translateY(-2px) scale(1); } + 50% { transform: translateY(-2px) scale(1.02); } +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .view-controls { + width: 100%; + margin-left: 0; + margin-right: 0; + } + + .plot-container.grid-view { + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 1rem; + } + + .plot-container.list-large-view .plot-link { + width: 80px; + height: 60px; + } + + .plot-container.list-large-view .plot-item { + padding: 0.8rem; + } + + .plot-container.list-compact-view .plot-item { + padding: 0.5rem 0.8rem; + } +} + +@media (max-width: 480px) { + .view-controls { + gap: 4px; + } + + .view-btn { + padding: 6px 8px; + font-size: 1rem; + min-width: 32px; + } + + .plot-container.grid-view { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .plot-container.list-large-view .plot-link { + width: 60px; + height: 45px; + } +} diff --git a/assets/css/view-override.css b/assets/css/view-override.css new file mode 100644 index 0000000..315fe0f --- /dev/null +++ b/assets/css/view-override.css @@ -0,0 +1,54 @@ +/* ======================================== + VIEW OVERRIDE - Ensure grid view works + ======================================== */ + +/* 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; + padding: 1rem 0 !important; +} + +/* Force grid items to be proper tiles */ +body .plot-container.grid-view .plot-item, +body .plot-container.grid-view .grid-item { + display: block !important; + width: auto !important; + max-width: none !important; + text-align: center !important; + background: var(--card-bg) !important; + border-radius: 8px !important; + padding: 0.8rem !important; + border: 1px solid transparent !important; +} + +/* Ensure thumbnails are properly sized */ +body .plot-container.grid-view .plot-thumbnail { + width: 100% !important; + height: auto !important; + max-width: 100% !important; + display: block !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + object-fit: cover !important; + aspect-ratio: 4/3; +} + +/* Force plot info styling */ +body .plot-container.grid-view .plot-info { + padding: 0.5rem 0 0 0 !important; + text-align: center !important; +} + +body .plot-container.grid-view .plot-name { + font-size: 0.85rem !important; + line-height: 1.2 !important; + max-height: 2.4rem !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + display: -webkit-box !important; + -webkit-line-clamp: 2 !important; + line-clamp: 2 !important; + -webkit-box-orient: vertical !important; +} diff --git a/assets/js/gallery-app.js b/assets/js/gallery-app.js index 935294e..b24eec6 100644 --- a/assets/js/gallery-app.js +++ b/assets/js/gallery-app.js @@ -9,7 +9,7 @@ import { RecentPlotsManager } from './recent-plots-manager.js'; import { ComparisonManager } from './comparison-manager.js'; import { StatsManager } from './stats-manager.js'; import { KeyboardManager } from './keyboard-manager.js'; -import { ExportManager } from './export-manager.js'; +import { ViewManager } from './view-manager.js'; import { Utils } from './utils.js'; /** @@ -29,7 +29,7 @@ export class GalleryApp { this.recentPlotsManager = new RecentPlotsManager(this.MAX_RECENT_PLOTS); this.comparisonManager = new ComparisonManager(); this.statsManager = new StatsManager(); - this.exportManager = new ExportManager(); + this.viewManager = new ViewManager(); this.keyboardManager = new KeyboardManager(this); this.utils = Utils; @@ -38,7 +38,7 @@ export class GalleryApp { window.searchManager = this.searchManager; window.recentPlotsManager = this.recentPlotsManager; window.comparisonManager = this.comparisonManager; - window.exportManager = this.exportManager; + window.viewManager = this.viewManager; window.utils = this.utils; this.init(); diff --git a/assets/js/keyboard-manager.js b/assets/js/keyboard-manager.js index 5936d62..cc7ad00 100644 --- a/assets/js/keyboard-manager.js +++ b/assets/js/keyboard-manager.js @@ -39,6 +39,13 @@ export class KeyboardManager { } } + if (e.ctrlKey && e.key === 'v') { + e.preventDefault(); + if (this.app.viewManager) { + this.app.viewManager.cycleView(); + } + } + if (e.key === 'F5') { e.preventDefault(); if (this.app.utils && this.app.utils.refreshGallery) { diff --git a/assets/js/view-manager.js b/assets/js/view-manager.js new file mode 100644 index 0000000..c35381a --- /dev/null +++ b/assets/js/view-manager.js @@ -0,0 +1,203 @@ +/** + * View Controls Manager - handles switching between grid, list-large, and list-compact views + */ +export class ViewManager { + constructor() { + this.currentView = 'grid'; + this.init(); + } + + /** + * Initialize view controls + */ + init() { + this.setupViewButtons(); + this.loadSavedView(); + this.updateControlsVisibility(); + } + + /** + * Update visibility of view controls based on plot content + */ + updateControlsVisibility() { + const plotContainer = document.getElementById('plotContainer'); + const viewControls = document.querySelector('.view-controls'); + + if (!plotContainer || !viewControls) return; + + const hasPlots = plotContainer.children.length > 0; + viewControls.style.display = hasPlots ? 'flex' : 'none'; + } + + /** + * Setup view toggle buttons + */ + 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); + }); + }); + } + + /** + * 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); + + // Remove current view class + plotContainer.classList.remove( + 'grid-view', + 'list-large-view', + 'list-compact-view' + ); + + // Add new view class + switch (viewMode) { + case 'grid': + plotContainer.classList.add('grid-view'); + break; + case 'list-large': + plotContainer.classList.add('list-large-view'); + break; + case 'list-compact': + plotContainer.classList.add('list-compact-view'); + break; + default: + plotContainer.classList.add('grid-view'); + viewMode = 'grid'; + } + + console.log('ViewManager: Plot container classes after:', plotContainer.className); + + // Update button states + viewButtons.forEach(btn => { + if (btn.getAttribute('data-view') === viewMode) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); + + // Save the preference + this.currentView = viewMode; + this.saveViewPreference(viewMode); + + // Trigger any necessary layout updates + this.onViewChanged(viewMode); + } + + /** + * Save view preference to localStorage + */ + saveViewPreference(viewMode) { + try { + localStorage.setItem('gallery-view-mode', viewMode); + } catch (e) { + // Ignore localStorage errors + } + } + + /** + * Load saved view preference + */ + loadSavedView() { + try { + const savedView = localStorage.getItem('gallery-view-mode'); + if (savedView && ['grid', 'list-large', 'list-compact'].includes(savedView)) { + this.switchView(savedView); + } + } catch (e) { + // Ignore localStorage errors, use default + } + } + + /** + * Handle view change events - can be extended for additional functionality + */ + onViewChanged(viewMode) { + // Dispatch custom event for other components that might need to know + const event = new CustomEvent('viewChanged', { + detail: { viewMode } + }); + document.dispatchEvent(event); + + // Update any other UI elements that depend on view mode + this.updateUIForView(viewMode); + } + + /** + * Update UI elements based on current view + */ + updateUIForView(viewMode) { + // You can add view-specific UI updates here + // For example, adjusting search result highlighting, etc. + + // Update any tooltips or help text + const viewButtons = document.querySelectorAll('.view-btn'); + viewButtons.forEach(btn => { + const btnView = btn.getAttribute('data-view'); + if (btnView === viewMode) { + btn.style.transform = 'scale(1.05)'; + } else { + btn.style.transform = 'scale(1)'; + } + }); + } + + /** + * Get current view mode + */ + getCurrentView() { + return this.currentView; + } + + /** + * Refresh controls visibility (call this when gallery content changes) + */ + refresh() { + this.updateControlsVisibility(); + } + + /** + * Check if current view is grid mode + */ + isGridView() { + return this.currentView === 'grid'; + } + + /** + * Check if current view is list mode (either variant) + */ + isListView() { + return this.currentView === 'list-large' || this.currentView === 'list-compact'; + } + + /** + * Cycle through view modes (useful for keyboard shortcuts) + */ + cycleView() { + const views = ['grid', 'list-large', 'list-compact']; + const currentIndex = views.indexOf(this.currentView); + const nextIndex = (currentIndex + 1) % views.length; + this.switchView(views[nextIndex]); + } +} diff --git a/templates/gallery.html b/templates/gallery.html index 6de9970..927df73 100644 --- a/templates/gallery.html +++ b/templates/gallery.html @@ -34,17 +34,51 @@
- -
+ + {% if items %} +
+ + + +
+ {% endif %} + + +
{% for item in items %} -
- - {{ item.name }} +
+ + {{ item.name }} - -
{{ item.name }}
+
+
{{ item.name }}
+
{% endfor %}
@@ -120,6 +154,10 @@ Toggle theme Ctrl+T
+
+ Toggle view + Ctrl+V +
Help ?