Add loading spinner and checkmark

This commit is contained in:
Eugene Burmakin 2024-11-26 17:36:22 +01:00
parent 428e927432
commit 3c6f2e5ce3
4 changed files with 101 additions and 7 deletions

View file

@ -72,3 +72,38 @@
width: 48px;
height: 48px;
}
.leaflet-loading-control {
padding: 5px;
border-radius: 4px;
box-shadow: 0 1px 5px rgba(0,0,0,0.2);
margin: 10px;
width: 32px;
height: 32px;
background: white;
}
.loading-spinner {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
color: gray;
}
.loading-spinner::before {
content: '🔵';
font-size: 18px;
animation: spinner 1s linear infinite;
}
.loading-spinner.done::before {
content: '✅';
animation: none;
}
@keyframes spinner {
to {
transform: rotate(360deg);
}
}

View file

@ -3,8 +3,12 @@
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.reject { |photo| photo['type'].downcase == 'video' }
Immich::RequestPhotos.new(
current_api_user,
start_date: params[:start_date],
end_date: params[:end_date]
).call.reject { |asset| asset['type'].downcase == 'video' }
end
render json: @photos, status: :ok
end

View file

@ -786,6 +786,18 @@ export default class extends Controller {
const MAX_RETRIES = 3;
const RETRY_DELAY = 3000; // 3 seconds
// 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' });
this.map.addControl(loadingControl);
try {
const params = new URLSearchParams({
api_key: this.apiKey,
@ -801,14 +813,42 @@ export default class extends Controller {
const photos = await response.json();
this.photoMarkers.clearLayers();
photos.forEach(photo => this.createPhotoMarker(photo));
// Create a promise for each photo to track when it's fully loaded
const photoLoadPromises = photos.map(photo => {
return new Promise((resolve) => {
const img = new Image();
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}`;
img.onload = () => {
this.createPhotoMarker(photo);
resolve();
};
img.onerror = () => {
console.error(`Failed to load photo ${photo.id}`);
resolve(); // Resolve anyway to not block other photos
};
img.src = thumbnailUrl;
});
});
// Wait for all photos to be loaded and rendered
await Promise.all(photoLoadPromises);
if (!this.map.hasLayer(this.photoMarkers)) {
this.photoMarkers.addTo(this.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));
} 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})`);
@ -818,6 +858,9 @@ export default class extends Controller {
} else {
showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
}
} finally {
// Remove loading control after the delay
this.map.removeControl(loadingControl);
}
}

View file

@ -12,7 +12,9 @@ class Immich::RequestPhotos
end
def call
retrieve_immich_data
data = retrieve_immich_data
time_framed_data(data)
end
private
@ -20,11 +22,14 @@ class Immich::RequestPhotos
def retrieve_immich_data
page = 1
data = []
max_pages = 100_000 # Prevent infinite loop
max_pages = 10_000 # Prevent infinite loop
while page <= max_pages
body = request_body(page)
response = JSON.parse(HTTParty.post(immich_api_base_url, headers: headers, body: body).body)
response = JSON.parse(
HTTParty.post(
immich_api_base_url, headers: headers, body: request_body(page)
).body
)
items = response.dig('assets', 'items')
@ -58,4 +63,11 @@ class Immich::RequestPhotos
body.merge(createdBefore: end_date)
end
def time_framed_data(data)
data.select do |photo|
photo['localDateTime'] >= start_date &&
(end_date.nil? || photo['localDateTime'] <= end_date)
end
end
end