// ============================================ // Gallery — Image Browser & Manager with Sample Images // ============================================ const Gallery = { images: [], selectedImages: new Set(), filters: { search: '', category: '', camera: '' }, viewMode: 'grid', // 'grid' or 'preview' currentIndex: null, init() { this.images = Helpers.deepClone(MockData.images); this.renderFilters(); this.renderGrid(); this.bindEvents(); this.updateViewToggle(); }, renderFilters() { const categorySelect = Helpers.$('#gallery-filter-category'); const cameraSelect = Helpers.$('#gallery-filter-camera'); if (categorySelect) { categorySelect.innerHTML = '' + MockData.categories.map(c => ``).join(''); } if (cameraSelect) { const cameras = [...new Set(this.images.map(i => i.camera))]; cameraSelect.innerHTML = '' + cameras.map(c => ``).join(''); } }, bindEvents() { // Search Helpers.$('#gallery-search').addEventListener('input', Helpers.debounce((e) => { this.filters.search = e.target.value; this.renderGrid(); })); // Filters Helpers.$('#gallery-filter-category').addEventListener('change', (e) => { this.filters.category = e.target.value; this.renderGrid(); }); Helpers.$('#gallery-filter-camera').addEventListener('change', (e) => { this.filters.camera = e.target.value; this.renderGrid(); }); // View toggle buttons Helpers.$('#btn-view-grid')?.addEventListener('click', () => { this.viewMode = 'grid'; this.currentIndex = null; this.updateViewToggle(); this.renderGrid(); }); Helpers.$('#btn-view-preview')?.addEventListener('click', () => { this.viewMode = 'preview'; this.currentIndex = 0; this.updateViewToggle(); this.renderGrid(); }); // Select all / clear Helpers.$('#btn-select-all-gallery')?.addEventListener('click', () => this.selectAll()); Helpers.$('#btn-clear-gallery')?.addEventListener('click', () => this.clearSelection()); // Annotate button Helpers.$('#btn-annotate-selected')?.addEventListener('click', () => { if (this.selectedImages.size === 0) { Toast.show('Select images to annotate', 'warning'); return; } Toast.show(`Annotating ${this.selectedImages.size} image(s)...`, 'info'); }); // Grid click delegation Helpers.$('#gallery-grid')?.addEventListener('click', (e) => { // Preview mode navigation handled in renderPreviewView if (e.target.closest('.gallery-preview__nav')) return; const item = e.target.closest('.gallery-item'); if (item) { const id = item.dataset.imageId; // Single click opens detail panel, Ctrl+Click for multi-select if (e.ctrlKey || e.metaKey) { this.toggleImage(id, true); } else { this.viewImage(id); } } }); // Keyboard navigation in preview mode document.addEventListener('keydown', (e) => { if (this.viewMode !== 'preview') return; if (e.key === 'ArrowLeft') this.navigatePreview(-1); if (e.key === 'ArrowRight') this.navigatePreview(1); if (e.key === 'Escape') { this.viewMode = 'grid'; this.currentIndex = null; this.updateViewToggle(); this.renderGrid(); } }); }, updateViewToggle() { const gridBtn = Helpers.$('#btn-view-grid'); const previewBtn = Helpers.$('#btn-view-preview'); if (gridBtn) gridBtn.classList.toggle('active', this.viewMode === 'grid'); if (previewBtn) previewBtn.classList.toggle('active', this.viewMode === 'preview'); }, getFilteredImages() { return this.images.filter(img => { if (this.filters.search) { const term = this.filters.search.toLowerCase(); if (!img.filename.toLowerCase().includes(term)) return false; } if (this.filters.category && img.category !== this.filters.category) return false; if (this.filters.camera && img.camera !== this.filters.camera) return false; return true; }); }, renderGrid() { const container = Helpers.$('#gallery-grid'); const countEl = Helpers.$('#gallery-count'); if (!container) return; const filtered = this.getFilteredImages(); if (countEl) countEl.textContent = `${filtered.length} images`; if (this.viewMode === 'grid') { this.renderGridView(filtered, container); } else { this.renderPreviewView(filtered, container); } }, renderGridView(filtered, container) { container.innerHTML = filtered.map(img => { const isSelected = this.selectedImages.has(img.id); const imgPreview = Helpers.getImagePreviewUrl(img.id); const fallbackSvg = Helpers.generatePlaceholderSVG(img, false); const categoryColor = Helpers.getCategoryColor(img.category); const categoryIcon = Helpers.getCategoryIcon(img.category); const shortName = img.filename.replace('.tif', '').replace('.tiff', '').substring(0, 18); return `
No images match your filters