From 84ff9885ccf505f719e4850d684207e532d405be Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 27 Apr 2025 22:04:27 -0500 Subject: [PATCH] Confirm before syncing photo with key command --- src/admin/AdminPhotosTable.tsx | 3 +-- src/admin/PhotoSyncButton.tsx | 28 ++++++++++++---------------- src/admin/confirm.ts | 14 ++++++++++++++ src/camera/CameraHeader.tsx | 2 ++ src/film/FilmHeader.tsx | 2 ++ src/focal/FocalLengthHeader.tsx | 3 ++- src/lens/LensHeader.tsx | 2 ++ src/photo/PhotoDetailPage.tsx | 2 ++ src/photo/PhotoHeader.tsx | 3 +++ src/photo/PhotoPrevNextActions.tsx | 10 +++++++++- src/recipe/RecipeHeader.tsx | 2 ++ src/tag/HiddenHeader.tsx | 2 ++ src/tag/TagHeader.tsx | 2 ++ 13 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 src/admin/confirm.ts diff --git a/src/admin/AdminPhotosTable.tsx b/src/admin/AdminPhotosTable.tsx index 3fc11ace..794915ba 100644 --- a/src/admin/AdminPhotosTable.tsx +++ b/src/admin/AdminPhotosTable.tsx @@ -115,8 +115,7 @@ export default function AdminPhotosTable({ {canEdit && } void isSyncingExternal?: boolean - hasAiTextGeneration?: boolean + hasAiTextGeneration: boolean shouldConfirm?: boolean shouldToast?: boolean shouldScrollIntoViewOnExternalSync?: boolean @@ -32,13 +32,6 @@ export default function PhotoSyncButton({ const [isSyncing, setIsSyncing] = useState(false); - const confirmText = ['Overwrite']; - if (photoTitle) { confirmText.push(`"${photoTitle}"`); } - confirmText.push('data from original file?'); - if (hasAiTextGeneration) { confirmText.push( - 'AI text will be generated for undefined fields.'); } - confirmText.push('This action cannot be undone.'); - useScrollIntoView({ ref, shouldScrollIntoView: @@ -55,14 +48,17 @@ export default function PhotoSyncButton({ className="translate-y-[0.5px] translate-x-[0.5px]" />} onClick={() => { - if (!shouldConfirm || window.confirm(confirmText.join(' '))) { + if ( + !shouldConfirm || + window.confirm(syncPhotoConfirmText(photo, hasAiTextGeneration)) + ) { setIsSyncing(true); - syncPhotoAction(photoId) + syncPhotoAction(photo.id) .then(() => { onSyncComplete?.(); if (shouldToast) { - toastSuccess(photoTitle - ? `"${photoTitle}" data synced` + toastSuccess(photo.title + ? `"${photo.title}" data synced` : 'Data synced'); } }) diff --git a/src/admin/confirm.ts b/src/admin/confirm.ts new file mode 100644 index 00000000..c5112f5e --- /dev/null +++ b/src/admin/confirm.ts @@ -0,0 +1,14 @@ +import { Photo } from '@/photo'; + +export const syncPhotoConfirmText = ( + photo: Photo, + hasAiTextGeneration: boolean, +) => { + const confirmText = ['Sync']; + if (photo.title) { confirmText.push(`"${photo.title}"`); } + confirmText.push('data from original image file?'); + if (hasAiTextGeneration) { confirmText.push( + 'AI text will be generated for undefined fields.'); } + confirmText.push('This action cannot be undone.'); + return confirmText.join(' '); +}; diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx index f2d4e449..bfe4e577 100644 --- a/src/camera/CameraHeader.tsx +++ b/src/camera/CameraHeader.tsx @@ -3,6 +3,7 @@ import PhotoHeader from '@/photo/PhotoHeader'; import { Camera, cameraFromPhoto } from '.'; import PhotoCamera from './PhotoCamera'; import { descriptionForCameraPhotos } from './meta'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function CameraHeader({ camera: cameraProp, @@ -31,6 +32,7 @@ export default function CameraHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> ); diff --git a/src/film/FilmHeader.tsx b/src/film/FilmHeader.tsx index a9028a41..56dc1200 100644 --- a/src/film/FilmHeader.tsx +++ b/src/film/FilmHeader.tsx @@ -6,6 +6,7 @@ import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFilm from '@/film/PhotoFilm'; import { getRecipePropsFromPhotos } from '@/recipe'; import { useAppState } from '@/state/AppState'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function FilmHeader({ film, @@ -47,6 +48,7 @@ export default function FilmHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> ); diff --git a/src/focal/FocalLengthHeader.tsx b/src/focal/FocalLengthHeader.tsx index f3bcb19f..98b92d3d 100644 --- a/src/focal/FocalLengthHeader.tsx +++ b/src/focal/FocalLengthHeader.tsx @@ -2,7 +2,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { descriptionForFocalLengthPhotos } from '.'; import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFocalLength from './PhotoFocalLength'; - +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function FocalLengthHeader({ focal, photos, @@ -32,6 +32,7 @@ export default function FocalLengthHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> ); diff --git a/src/lens/LensHeader.tsx b/src/lens/LensHeader.tsx index ddd388e6..4c7f00ca 100644 --- a/src/lens/LensHeader.tsx +++ b/src/lens/LensHeader.tsx @@ -3,6 +3,7 @@ import PhotoHeader from '@/photo/PhotoHeader'; import { Lens, lensFromPhoto } from '.'; import PhotoLens from './PhotoLens'; import { descriptionForLensPhotos } from './meta'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function LensHeader({ lens: lensProp, @@ -31,6 +32,7 @@ export default function LensHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> ); diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index 251c0404..e257eea7 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -14,6 +14,7 @@ import PhotoHeader from './PhotoHeader'; import RecipeHeader from '@/recipe/RecipeHeader'; import { ReactNode } from 'react'; import LensHeader from '@/lens/LensHeader'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function PhotoDetailPage({ photo, @@ -113,6 +114,7 @@ export default function PhotoDetailPage({ selectedPhoto={photo} photos={photos} recipe={recipe} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} />} /> ; diff --git a/src/photo/PhotoPrevNextActions.tsx b/src/photo/PhotoPrevNextActions.tsx index ac345bd8..03166f63 100644 --- a/src/photo/PhotoPrevNextActions.tsx +++ b/src/photo/PhotoPrevNextActions.tsx @@ -30,6 +30,7 @@ import { import { downloadFileFromBrowser } from '@/utility/url'; import useKeydownHandler from '@/utility/useKeydownHandler'; import { KEY_COMMANDS } from './key-commands'; +import { syncPhotoConfirmText } from '@/admin/confirm'; const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 }; const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 }; @@ -38,11 +39,13 @@ export default function PhotoPrevNextActions({ photo, photos = [], className, + hasAiTextGeneration, ...categories }: { photo?: Photo photos?: Photo[] className?: string + hasAiTextGeneration: boolean } & PhotoSetCategory) { const { setNextPhotoAnimation, isUserSignedIn } = useAppState(); @@ -157,7 +160,11 @@ export default function PhotoPrevNextActions({ } break; case KEY_COMMANDS.sync: - if (isUserSignedIn) { + if ( + isUserSignedIn && + photo && + window.confirm(syncPhotoConfirmText(photo, hasAiTextGeneration)) + ) { syncPhoto(); } break; @@ -176,6 +183,7 @@ export default function PhotoPrevNextActions({ downloadFileName, syncPhoto, deletePhoto, + hasAiTextGeneration, ]); useKeydownHandler({ onKeyDown }); diff --git a/src/recipe/RecipeHeader.tsx b/src/recipe/RecipeHeader.tsx index cd95d752..08ef5761 100644 --- a/src/recipe/RecipeHeader.tsx +++ b/src/recipe/RecipeHeader.tsx @@ -5,6 +5,7 @@ import PhotoHeader from '@/photo/PhotoHeader'; import PhotoRecipe from './PhotoRecipe'; import { useAppState } from '@/state/AppState'; import { descriptionForRecipePhotos, getRecipePropsFromPhotos } from '.'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function RecipeHeader({ recipe, @@ -42,6 +43,7 @@ export default function RecipeHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> ); diff --git a/src/tag/HiddenHeader.tsx b/src/tag/HiddenHeader.tsx index 244013be..46e32738 100644 --- a/src/tag/HiddenHeader.tsx +++ b/src/tag/HiddenHeader.tsx @@ -1,6 +1,7 @@ import { Photo, photoQuantityText } from '@/photo'; import PhotoHeader from '@/photo/PhotoHeader'; import HiddenTag from './HiddenTag'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function HiddenHeader({ photos, @@ -22,6 +23,7 @@ export default function HiddenHeader({ selectedPhoto={selectedPhoto} indexNumber={indexNumber} count={count} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} /> ); } diff --git a/src/tag/TagHeader.tsx b/src/tag/TagHeader.tsx index 52931588..e1e91dfb 100644 --- a/src/tag/TagHeader.tsx +++ b/src/tag/TagHeader.tsx @@ -3,6 +3,7 @@ import PhotoTag from './PhotoTag'; import { descriptionForTaggedPhotos, isTagFavs } from '.'; import PhotoHeader from '@/photo/PhotoHeader'; import FavsTag from './FavsTag'; +import { AI_TEXT_GENERATION_ENABLED } from '@/app/config'; export default function TagHeader({ tag, @@ -32,6 +33,7 @@ export default function TagHeader({ indexNumber={indexNumber} count={count} dateRange={dateRange} + hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED} includeShareButton /> );