Confirm before syncing photo with key command

This commit is contained in:
Sam Becker 2025-04-27 22:04:27 -05:00
parent c8080e9cf2
commit 84ff9885cc
13 changed files with 55 additions and 20 deletions

View File

@ -115,8 +115,7 @@ export default function AdminPhotosTable({
{canEdit &&
<EditButton path={pathForAdminPhotoEdit(photo)} />}
<PhotoSyncButton
photoId={photo.id}
photoTitle={titleForPhoto(photo)}
photo={photo}
onSyncComplete={invalidateSwr}
isSyncingExternal={photoIdsSyncing.includes(photo.id)}
hasAiTextGeneration={hasAiTextGeneration}

View File

@ -6,10 +6,11 @@ import { ComponentProps, useRef, useState } from 'react';
import Tooltip from '@/components/Tooltip';
import clsx from 'clsx/lite';
import useScrollIntoView from '@/utility/useScrollIntoView';
import { Photo } from '@/photo';
import { syncPhotoConfirmText } from './confirm';
export default function PhotoSyncButton({
photoId,
photoTitle,
photo,
onSyncComplete,
className,
isSyncingExternal,
@ -19,11 +20,10 @@ export default function PhotoSyncButton({
shouldToast,
shouldScrollIntoViewOnExternalSync,
}: {
photoId: string
photoTitle?: string
photo: Photo
onSyncComplete?: () => 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');
}
})

14
src/admin/confirm.ts Normal file
View File

@ -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(' ');
};

View File

@ -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
/>
);

View File

@ -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
/>
);

View File

@ -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
/>
);

View File

@ -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
/>
);

View File

@ -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}
/>}
/>
<AnimateItems

View File

@ -27,6 +27,7 @@ export default function PhotoHeader({
indexNumber,
count,
dateRange,
hasAiTextGeneration,
includeShareButton,
...categories
}: {
@ -38,6 +39,7 @@ export default function PhotoHeader({
indexNumber?: number
count?: number
dateRange?: PhotoDateRange
hasAiTextGeneration: boolean
includeShareButton?: boolean
} & PhotoSetCategory) {
const { isGridHighDensity } = useAppState();
@ -62,6 +64,7 @@ export default function PhotoHeader({
<PhotoPrevNextActions {...{
photo: selectedPhoto,
photos,
hasAiTextGeneration,
...categories,
}} />;

View File

@ -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 });

View File

@ -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
/>
);

View File

@ -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}
/>
);
}

View File

@ -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
/>
);