From d6da955bf470b43516ff43d35f7ae43fbc236f88 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 6 Mar 2025 23:21:35 -0800 Subject: [PATCH] Optimize EXIF capture on edit --- package.json | 1 + pnpm-lock.yaml | 3 +++ .../{ExifSyncButton.tsx => ExifCaptureButton.tsx} | 7 ++++--- src/photo/PhotoEditPageClient.tsx | 4 ++-- src/photo/form/PhotoForm.tsx | 13 +++++++++++-- src/photo/form/index.ts | 6 ++++++ src/utility/object.ts | 13 ------------- 7 files changed, 27 insertions(+), 20 deletions(-) rename src/admin/{ExifSyncButton.tsx => ExifCaptureButton.tsx} (87%) delete mode 100644 src/utility/object.ts diff --git a/package.json b/package.json index e61e182b..447bc4f4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "exifr": "^7.1.3", + "fast-deep-equal": "^3.1.3", "framer-motion": "^12.4.10", "nanoid": "^5.1.2", "next": "15.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27ca500a..e0cfb71b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: exifr: specifier: ^7.1.3 version: 7.1.3 + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 framer-motion: specifier: ^12.4.10 version: 12.4.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0) diff --git a/src/admin/ExifSyncButton.tsx b/src/admin/ExifCaptureButton.tsx similarity index 87% rename from src/admin/ExifSyncButton.tsx rename to src/admin/ExifCaptureButton.tsx index a5ddadce..780de1c4 100644 --- a/src/admin/ExifSyncButton.tsx +++ b/src/admin/ExifCaptureButton.tsx @@ -4,11 +4,11 @@ import LoaderButton from '@/components/primitives/LoaderButton'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import { getExifDataAction } from '@/photo/actions'; import { PhotoFormData } from '@/photo/form'; -import IconGrSync from '@/app/IconGrSync'; import { clsx } from 'clsx/lite'; import { ComponentProps, useState } from 'react'; +import { LuDatabaseBackup } from 'react-icons/lu'; -export default function ExifSyncButton({ +export default function ExifCaptureButton({ photoUrl, onSync, }: { @@ -27,7 +27,8 @@ export default function ExifSyncButton({ .then(onSync) .finally(() => setIsLoading(false)); }} - icon={ {hasAiTextGeneration && } - diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index 6f201c50..86703a7d 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { + FIELDS_WITH_JSON, FORM_METADATA_ENTRIES, PhotoFormData, convertFormKeysToLabels, @@ -31,6 +32,7 @@ import { BLUR_ENABLED, IS_PREVIEW } from '@/app/config'; import { PhotoDbInsert } from '..'; import ErrorNote from '@/components/ErrorNote'; import { convertRecipesForForm, Recipes } from '@/recipe'; +import deepEqual from 'fast-deep-equal/es6/react'; const THUMBNAIL_SIZE = 300; @@ -89,9 +91,16 @@ export default function PhotoForm({ const changedKeys: (keyof PhotoFormData)[] = []; setFormData(currentForm => { - Object.entries(updatedExifData ?? {}) + (Object.entries(updatedExifData ?? {}) as + [keyof PhotoFormData, string][]) .forEach(([key, value]) => { - if (currentForm[key as keyof PhotoFormData] !== value) { + let a = currentForm[key]; + let b = value; + if (FIELDS_WITH_JSON.includes(key)) { + a = JSON.parse(a ?? ''); + b = JSON.parse(b ?? ''); + } + if (!deepEqual(a, b)) { changedKeys.push(key as keyof PhotoFormData); } }); diff --git a/src/photo/form/index.ts b/src/photo/form/index.ts index 5b5c2518..b071bc08 100644 --- a/src/photo/form/index.ts +++ b/src/photo/form/index.ts @@ -63,6 +63,7 @@ type FormMeta = { tagOptionsLimit?: number tagOptionsLimitValidationMessage?: string shouldNotOverwriteWithNullDataOnSync?: boolean + isJson?: boolean }; const STRING_MAX_LENGTH_SHORT = 255; @@ -130,6 +131,7 @@ const FORM_METADATA = ( capitalize: false, shouldHide: ({ make }) => make !== MAKE_FUJIFILM, shouldNotOverwriteWithNullDataOnSync: true, + isJson: true, validate: value => { let validationMessage = undefined; if (value) { @@ -166,6 +168,10 @@ const FORM_METADATA = ( hidden: { label: 'hidden', type: 'checkbox' }, }); +export const FIELDS_WITH_JSON = Object.entries(FORM_METADATA()) + .filter(([_, meta]) => meta.isJson) + .map(([key]) => key as keyof PhotoFormData); + export const FIELDS_TO_NOT_OVERWRITE_WITH_NULL_DATA_ON_SYNC = Object.entries(FORM_METADATA()) .filter(([_, meta]) => meta.shouldNotOverwriteWithNullDataOnSync) diff --git a/src/utility/object.ts b/src/utility/object.ts deleted file mode 100644 index bc0d025e..00000000 --- a/src/utility/object.ts +++ /dev/null @@ -1,13 +0,0 @@ -type SimpleObject = Record; - -export const areSimpleObjectsEqual = ( - obj1: SimpleObject, - obj2: SimpleObject, -): boolean => { - const obj1Keys = Object.keys(obj1); - const obj2Keys = Object.keys(obj2); - - return obj1Keys.length === obj2Keys.length - ? obj1Keys.every((key) => obj1[key] === obj2[key]) - : false; -};