feat(photos): add Photos timeline view with lightbox and infinite scroll
Backend: new GET /api/photos endpoint with cursor-based pagination that queries image/video files sorted by EXIF captured_at (falling back to created_at), joining file_metadata for sort dates. Frontend: dense photo grid grouped by day with lazy-loaded thumbnails, IntersectionObserver infinite scroll, multi-select with batch download/delete, and a full-screen lightbox with prev/next navigation, EXIF metadata display, and download/favorite/delete toolbar. Includes navigation wiring, CSS (with dark theme), and i18n translations for all 9 locales.
This commit is contained in:
@@ -271,7 +271,7 @@ function cacheElements() {
|
||||
elements.pageTitle = document.querySelector('.page-title');
|
||||
elements.actionsBar = document.querySelector('.actions-bar');
|
||||
elements.navItems = document.querySelectorAll('.nav-item');
|
||||
elements.trashBtn = document.querySelector('.nav-item:nth-child(5)'); // The trash nav item
|
||||
elements.trashBtn = document.querySelector('.nav-item:nth-child(6)'); // The trash nav item (after Photos)
|
||||
elements.searchInput = document.querySelector('.search-container input');
|
||||
}
|
||||
|
||||
@@ -436,7 +436,13 @@ function setupEventListeners() {
|
||||
switchToRecentFilesView();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if this is the photos item
|
||||
if (item.querySelector('span').getAttribute('data-i18n') === 'nav.photos') {
|
||||
switchToPhotosView();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the trash item
|
||||
if (item === elements.trashBtn) {
|
||||
// Hide shared view if active
|
||||
|
||||
@@ -73,7 +73,8 @@ const VIEW_FLAGS = {
|
||||
'shared': 'isSharedView',
|
||||
'recent': 'isRecentView',
|
||||
'favorites': 'isFavoritesView',
|
||||
'trash': 'isTrashView'
|
||||
'trash': 'isTrashView',
|
||||
'photos': 'isPhotosView'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -114,6 +115,11 @@ function setCurrentSection(section) {
|
||||
if (section !== 'shared' && window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
|
||||
// Hide photosView when switching to any other section
|
||||
if (section !== 'photos' && window.photosView) {
|
||||
window.photosView.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function switchToSharedView() {
|
||||
@@ -223,7 +229,30 @@ function switchToRecentFilesView() {
|
||||
}
|
||||
}
|
||||
|
||||
function switchToPhotosView() {
|
||||
setCurrentSection('photos');
|
||||
|
||||
// Hide breadcrumb
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
if (breadcrumb) breadcrumb.style.display = 'none';
|
||||
|
||||
// Hide actions-bar (photos has its own upload via selection bar)
|
||||
window.setActionsBarMode('hidden');
|
||||
|
||||
// Hide file containers
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
if (filesGrid) filesGrid.style.display = 'none';
|
||||
if (filesListView) filesListView.style.display = 'none';
|
||||
|
||||
// Show photos view
|
||||
if (window.photosView) {
|
||||
window.photosView.show();
|
||||
}
|
||||
}
|
||||
|
||||
window.switchToFilesView = switchToFilesView;
|
||||
window.switchToSharedView = switchToSharedView;
|
||||
window.switchToFavoritesView = switchToFavoritesView;
|
||||
window.switchToRecentFilesView = switchToRecentFilesView;
|
||||
window.switchToPhotosView = switchToPhotosView;
|
||||
|
||||
@@ -16,6 +16,7 @@ window.app = {
|
||||
isSharedView: false,
|
||||
isFavoritesView: false,
|
||||
isRecentView: false,
|
||||
isPhotosView: false,
|
||||
currentSection: 'files',
|
||||
isSearchMode: false,
|
||||
shareDialogItem: null,
|
||||
|
||||
Reference in New Issue
Block a user