// ============================================ // 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 ` `; }).join(''); }, renderPreviewView(filtered, container) { if (this.currentIndex === null || this.currentIndex >= filtered.length) { this.currentIndex = filtered.length > 0 ? 0 : null; } if (this.currentIndex === null) { container.innerHTML = ` `; return; } const img = filtered[this.currentIndex]; const categoryColor = Helpers.getCategoryColor(img.category); const imgPath = Helpers.getImagePreviewUrl(img.id) || `assets/images-preview/${img.category === 'good' ? 'Good' : img.category.toUpperCase()}/${img.filename.replace(/\.(tif|tiff)$/i, '.png')}`; const categoryLabel = Helpers.getCategoryLabel(img.category); const total = filtered.length; // Build detailed info panel content const imgWidth = 2560; const imgHeight = 2056; const resolution = `${imgWidth} × ${imgHeight} px`; container.innerHTML = ` `; // Bind navigation container.querySelector('[data-nav="prev"]')?.addEventListener('click', () => this.navigatePreview(-1)); container.querySelector('[data-nav="next"]')?.addEventListener('click', () => this.navigatePreview(1)); // Bind thumbnail navigation container.querySelectorAll('.gallery-preview__thumb').forEach(thumb => { thumb.addEventListener('click', () => { const index = parseInt(thumb.dataset.index); this.currentIndex = index; this.renderGrid(); }); }); }, renderPreviewThumbnails(filtered) { const maxThumbnails = 10; const start = Math.max(0, this.currentIndex - 4); const end = Math.min(filtered.length, start + maxThumbnails); return filtered.slice(start, end).map((img, idx) => { const actualIndex = start + idx; const isActive = actualIndex === this.currentIndex; const imgPath = Helpers.getImagePreviewUrl(img.id) || ''; const categoryIcon = Helpers.getCategoryIcon(img.category); return ` `; }).join(''); }, openAnnotation(id) { const img = this.images.find(i => i.id === id); if (!img) return; const imgPath = Helpers.getImagePreviewUrl(img.id); if (imgPath) { AnnotationTool.open(imgPath); } else { Toast.show('Image not available for annotation', 'error'); } }, navigatePreview(direction) { const filtered = this.getFilteredImages(); const newIndex = this.currentIndex + direction; if (newIndex >= 0 && newIndex < filtered.length) { this.currentIndex = newIndex; this.renderGrid(); } }, toggleImage(id, multi = false) { if (multi) { if (this.selectedImages.has(id)) this.selectedImages.delete(id); else this.selectedImages.add(id); } else { if (this.selectedImages.size === 1 && this.selectedImages.has(id)) { this.selectedImages.clear(); } else { this.selectedImages.clear(); this.selectedImages.add(id); } } this.renderGrid(); }, selectAll() { const filtered = this.getFilteredImages(); filtered.forEach(img => this.selectedImages.add(img.id)); this.renderGrid(); Toast.show(`${this.selectedImages.size} images selected`, 'info'); }, clearSelection() { this.selectedImages.clear(); this.renderGrid(); }, viewImage(id) { const img = this.images.find(i => i.id === id); if (!img) return; const imgPreview = Helpers.getImagePreviewUrl(img.id); const bodyHtml = `
Image Details
Filename ${img.filename}
Camera ${img.camera}
Category ${Helpers.getCategoryBadge(img.category)}
Size ${img.size}
Date ${Helpers.formatDate(img.date)}
Has Mask ${img.hasMask ? '' : ''}
Actions
`; App.openDetailPanel('Image Details', bodyHtml); }, changeCategory(id, newCategory) { const img = this.images.find(i => i.id === id); if (img) { img.category = newCategory; this.renderGrid(); Toast.show(`Category changed to ${Helpers.getCategoryLabel(newCategory)}`, 'success'); } } }; window.Gallery = Gallery;