dawarich/app/javascript/maps/photos.js
2025-08-01 14:14:46 +02:00

190 lines
6.2 KiB
JavaScript

// javascript/maps/photos.js
import L from "leaflet";
import { showFlashMessage } from "./helpers";
export async function fetchAndDisplayPhotos({ map, photoMarkers, apiKey, startDate, endDate, userSettings }, retryCount = 0) {
const MAX_RETRIES = 3;
const RETRY_DELAY = 3000; // 3 seconds
console.log('fetchAndDisplayPhotos called with:', {
startDate,
endDate,
retryCount,
photoMarkersExists: !!photoMarkers,
mapExists: !!map,
apiKeyExists: !!apiKey,
userSettingsExists: !!userSettings
});
// Create loading control
const LoadingControl = L.Control.extend({
onAdd: (map) => {
const container = L.DomUtil.create('div', 'leaflet-loading-control');
container.innerHTML = '<div class="loading-spinner"></div>';
return container;
}
});
const loadingControl = new LoadingControl({ position: 'topleft' });
map.addControl(loadingControl);
try {
const params = new URLSearchParams({
api_key: apiKey,
start_date: startDate,
end_date: endDate
});
console.log('Fetching photos from API:', `/api/v1/photos?${params}`);
const response = await fetch(`/api/v1/photos?${params}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}, response: ${response.body}`);
}
const photos = await response.json();
console.log('Photos API response:', { count: photos.length, photos });
photoMarkers.clearLayers();
const photoLoadPromises = photos.map(photo => {
return new Promise((resolve) => {
const img = new Image();
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${apiKey}&source=${photo.source}`;
img.onload = () => {
console.log('Photo thumbnail loaded, creating marker for:', photo.id);
createPhotoMarker(photo, userSettings, photoMarkers, apiKey);
resolve();
};
img.onerror = () => {
console.error(`Failed to load photo ${photo.id}`);
resolve(); // Resolve anyway to not block other photos
};
img.src = thumbnailUrl;
});
});
await Promise.all(photoLoadPromises);
console.log('All photo markers created, adding to map');
if (!map.hasLayer(photoMarkers)) {
photoMarkers.addTo(map);
console.log('Photos layer added to map');
} else {
console.log('Photos layer already on map');
}
// Show checkmark for 1 second before removing
const loadingSpinner = document.querySelector('.loading-spinner');
loadingSpinner.classList.add('done');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Photos loading completed successfully');
} catch (error) {
console.error('Error fetching photos:', error);
showFlashMessage('error', 'Failed to fetch photos');
if (retryCount < MAX_RETRIES) {
console.log(`Retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
setTimeout(() => {
fetchAndDisplayPhotos({ map, photoMarkers, apiKey, startDate, endDate, userSettings }, retryCount + 1);
}, RETRY_DELAY);
} else {
showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
}
} finally {
map.removeControl(loadingControl);
}
}
function getPhotoLink(photo, userSettings) {
switch (photo.source) {
case 'immich':
const startOfDay = new Date(photo.localDateTime);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(photo.localDateTime);
endOfDay.setHours(23, 59, 59, 999);
const queryParams = {
takenAfter: startOfDay.toISOString(),
takenBefore: endOfDay.toISOString()
};
const encodedQuery = encodeURIComponent(JSON.stringify(queryParams));
return `${userSettings.immich_url}/search?query=${encodedQuery}`;
case 'photoprism':
return `${userSettings.photoprism_url}/library/browse?view=cards&year=${photo.localDateTime.split('-')[0]}&month=${photo.localDateTime.split('-')[1]}&order=newest&public=true&quality=3`;
default:
return '#'; // Default or error case
}
}
function getSourceUrl(photo, userSettings) {
switch (photo.source) {
case 'photoprism':
return userSettings.photoprism_url;
case 'immich':
return userSettings.immich_url;
default:
return '#'; // Default or error case
}
}
export function createPhotoMarker(photo, userSettings, photoMarkers, apiKey) {
// Handle both data formats - check for exifInfo or direct lat/lng
const latitude = photo.latitude || photo.exifInfo?.latitude;
const longitude = photo.longitude || photo.exifInfo?.longitude;
console.log('Creating photo marker for:', {
photoId: photo.id,
latitude,
longitude,
hasExifInfo: !!photo.exifInfo,
hasDirectCoords: !!(photo.latitude && photo.longitude)
});
if (!latitude || !longitude) {
console.warn('Photo missing coordinates, skipping:', photo.id);
return;
}
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${apiKey}&source=${photo.source}`;
const icon = L.divIcon({
className: 'photo-marker',
html: `<img src="${thumbnailUrl}" style="width: 48px; height: 48px;">`,
iconSize: [48, 48]
});
const marker = L.marker(
[latitude, longitude],
{ icon }
);
const photo_link = getPhotoLink(photo, userSettings);
const source_url = getSourceUrl(photo, userSettings);
const popupContent = `
<div class="max-w-xs">
<a href="${photo_link}" target="_blank" onmouseover="this.firstElementChild.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';"
onmouseout="this.firstElementChild.style.boxShadow = '';">
<img src="${thumbnailUrl}"
class="mb-2 rounded"
style="transition: box-shadow 0.3s ease;"
alt="${photo.originalFileName}">
</a>
<h3 class="font-bold">${photo.originalFileName}</h3>
<p>Taken: ${new Date(photo.localDateTime).toLocaleString()}</p>
<p>Location: ${photo.city}, ${photo.state}, ${photo.country}</p>
<p>Source: <a href="${source_url}" target="_blank">${photo.source}</a></p>
${photo.type === 'VIDEO' ? '🎥 Video' : '📷 Photo'}
</div>
`;
marker.bindPopup(popupContent);
photoMarkers.addLayer(marker);
console.log('Photo marker added to layer group');
}