diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f823a8e7..08196e09 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -64,4 +64,11 @@ justify-content: center; background: transparent; border: none; + border-radius: 50%; +} + +.photo-marker img { + border-radius: 50%; + width: 48px; + height: 48px; } diff --git a/app/controllers/api/v1/photos_controller.rb b/app/controllers/api/v1/photos_controller.rb index fba900c2..93f17208 100644 --- a/app/controllers/api/v1/photos_controller.rb +++ b/app/controllers/api/v1/photos_controller.rb @@ -4,8 +4,32 @@ class Api::V1::PhotosController < ApiController def index @photos = Rails.cache.fetch("photos_#{params[:start_date]}_#{params[:end_date]}", expires_in: 1.day) do Immich::RequestPhotos.new(current_api_user, start_date: params[:start_date], end_date: params[:end_date]).call - end + end.reject { |photo| photo['type'].downcase == 'video' } render json: @photos, status: :ok end + + def thumbnail + response = Rails.cache.fetch("photo_thumbnail_#{params[:id]}", expires_in: 1.day) do + HTTParty.get( + "#{current_api_user.settings['immich_url']}/api/assets/#{params[:id]}/thumbnail?size=preview", + headers: { + 'x-api-key' => current_api_user.settings['immich_api_key'], + 'accept' => 'application/octet-stream' + } + ) + end + + if response.success? + send_data( + response.body, + type: 'image/jpeg', + disposition: 'inline', + status: :ok + ) + else + Rails.logger.error "Failed to fetch thumbnail: #{response.code} - #{response.body}" + render json: { error: 'Failed to fetch thumbnail' }, status: response.code + end + end end diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 4153624d..8a54ea99 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -782,7 +782,10 @@ export default class extends Controller { this.layerControl = L.control.layers(this.baseMaps(), layerControl).addTo(this.map); } - async fetchAndDisplayPhotos(startDate, endDate) { + async fetchAndDisplayPhotos(startDate, endDate, retryCount = 0) { + const MAX_RETRIES = 3; + const RETRY_DELAY = 3000; // 3 seconds + try { const params = new URLSearchParams({ api_key: this.apiKey, @@ -796,46 +799,57 @@ export default class extends Controller { } const photos = await response.json(); - - // Clear existing photo markers this.photoMarkers.clearLayers(); - // Create markers for each photo with coordinates - photos.forEach(photo => { - if (photo.exifInfo?.latitude && photo.exifInfo?.longitude) { - const marker = L.marker([photo.exifInfo.latitude, photo.exifInfo.longitude], { - icon: L.divIcon({ - className: 'photo-marker', - html: `
- 📷 -
`, - iconSize: [24, 24] - }) - }); + photos.forEach(photo => this.createPhotoMarker(photo)); - // Add popup with photo information - const popupContent = ` -
-

${photo.originalFileName}

-

Taken: ${new Date(photo.localDateTime).toLocaleString()}

-

Location: ${photo.exifInfo.city}, ${photo.exifInfo.state}, ${photo.exifInfo.country}

- ${photo.type === 'VIDEO' ? '🎥 Video' : '📷 Photo'} -
- `; - marker.bindPopup(popupContent); - - this.photoMarkers.addLayer(marker); - } - }); - - // Add the layer group to the map if it's not already added if (!this.map.hasLayer(this.photoMarkers)) { this.photoMarkers.addTo(this.map); } } 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(() => { + this.fetchAndDisplayPhotos(startDate, endDate, retryCount + 1); + }, RETRY_DELAY); + } else { + showFlashMessage('error', 'Failed to fetch photos after multiple attempts'); + } } } + + createPhotoMarker(photo) { + if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return; + + const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}`; + + const icon = L.divIcon({ + className: 'photo-marker', + html: ``, + iconSize: [48, 48] + }); + + const marker = L.marker( + [photo.exifInfo.latitude, photo.exifInfo.longitude], + { icon } + ); + + const popupContent = ` +
+ ${photo.originalFileName} +

${photo.originalFileName}

+

Taken: ${new Date(photo.localDateTime).toLocaleString()}

+

Location: ${photo.exifInfo.city}, ${photo.exifInfo.state}, ${photo.exifInfo.country}

+ ${photo.type === 'VIDEO' ? '🎥 Video' : '📷 Photo'} +
+ `; + marker.bindPopup(popupContent); + + this.photoMarkers.addLayer(marker); + } } diff --git a/config/routes.rb b/config/routes.rb index a7dd1f79..dc7730b0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -78,6 +78,12 @@ Rails.application.routes.draw do namespace :countries do resources :borders, only: :index end + + resources :photos do + member do + get 'thumbnail', constraints: { id: %r{[^/]+} } + end + end end end end