Optimize EXIF capture on edit

This commit is contained in:
Sam Becker 2025-03-06 23:21:35 -08:00
parent 2520170639
commit d6da955bf4
7 changed files with 27 additions and 20 deletions

View File

@ -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",

3
pnpm-lock.yaml generated
View File

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

View File

@ -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={<IconGrSync
icon={<LuDatabaseBackup
size={16}
className={clsx(
'translate-y-[0.5px] translate-x-[0.5px]',
'sm:translate-x-[-0.5px]',

View File

@ -8,7 +8,7 @@ import PhotoForm from './form/PhotoForm';
import { Tags } from '@/tag';
import AiButton from './ai/AiButton';
import usePhotoFormParent from './form/usePhotoFormParent';
import ExifSyncButton from '@/admin/ExifSyncButton';
import ExifCaptureButton from '@/admin/ExifCaptureButton';
import { useState } from 'react';
import { Recipes } from '@/recipe';
@ -57,7 +57,7 @@ export default function PhotoEditPageClient({
<div className="flex gap-2">
{hasAiTextGeneration &&
<AiButton {...{ aiContent, shouldConfirm: hasTextContent }} />}
<ExifSyncButton
<ExifCaptureButton
photoUrl={photo.url}
onSync={setUpdatedExifData}
/>

View File

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

View File

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

View File

@ -1,13 +0,0 @@
type SimpleObject = Record<string, string>;
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;
};