'use client'; import { Photo } from '@/photo'; import AdminPhotosTable from '@/admin/AdminPhotosTable'; import Note from '@/components/Note'; import AdminChildPage from '@/components/AdminChildPage'; import { PATH_ADMIN_PHOTOS } from '@/app/path'; import { useEffect, useMemo, useRef, useState } from 'react'; import { syncPhotosAction } from '@/photo/actions'; import { useRouter } from 'next/navigation'; import ResponsiveText from '@/components/primitives/ResponsiveText'; import { LiaBroomSolid } from 'react-icons/lia'; import ProgressButton from '@/components/primitives/ProgressButton'; import ErrorNote from '@/components/ErrorNote'; import { getPhotosUpdateStatusText, isPhotoOnlyMissingColorData, } from '@/photo/update'; import IconBroom from '@/components/icons/IconBroom'; const SYNC_BATCH_SIZE_MAX = 3; export default function AdminPhotosUpdateClient({ photos, hasAiTextGeneration, }: { photos: Photo[] hasAiTextGeneration: boolean }) { // Use refs for non-reactive while loop state const photoIdsToSync = useRef(photos.map(photo => photo.id)); const errorRef = useRef(undefined); // Use state for updating progress button and error UI const [photoIdsSyncing, setPhotoIdsSyncing] = useState([]); const [error, setError] = useState(); const [progress, setProgress] = useState(0); const arePhotoIdsSyncing = photoIdsSyncing.length > 0; const router = useRouter(); const statusText = useMemo(() => getPhotosUpdateStatusText(photos), [photos]); useEffect(() => { if (photos.length === 0 && !error && !errorRef.current) { router.push(PATH_ADMIN_PHOTOS); } }, [photos.length, router, error]); return ( Updates ({photos.length}) } accessory={} hideText="never" progress={progress} tooltip={photos.length === 1 ? 'Update 1 photo' : `Update all ${photos.length} photos`} onClick={async () => { if (window.confirm([ 'Are you sure you want to sync', photos.length === 1 ? '1 photo?' : `all ${photos.length} photos?`, 'Browser must remain open while syncing.', 'This action cannot be undone.', ].join(' '))) { errorRef.current = undefined; setError(undefined); while (photoIdsToSync.current.length > 0) { const photoIds = photoIdsToSync.current .slice(0, SYNC_BATCH_SIZE_MAX); setPhotoIdsSyncing(photoIds); await syncPhotosAction(photoIds.map(id => ({ photoId: id, onlySyncColorData: isPhotoOnlyMissingColorData( photos.find(photo => photo.id === id), ), }))) .then(() => { photoIdsToSync.current = photoIdsToSync.current.filter( id => !photoIds.includes(id), ); setProgress( (photos.length - photoIdsToSync.current.length) / photos.length, ); router.refresh(); }) .catch(e => { errorRef.current = e; setError(e); }); if (errorRef.current) { break; } } setProgress(0); setPhotoIdsSyncing([]); router.refresh(); } }} isLoading={arePhotoIdsSyncing} disabled={photoIdsSyncing.length > 0} > {arePhotoIdsSyncing ? 'Updating ...' : 'Update All'} } >
{error && Issue syncing: {' '} {error.message} } } >
Photo updates: {statusText}
Sync to capture new EXIF fields, optimize image data, {' '} use AI to generate missing text (if configured)
); }