Add server actions to get/override EXIF data
This commit is contained in:
parent
bf78ced898
commit
8bb5c2990b
@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { LegacyRef } from 'react';
|
import { LegacyRef } from 'react';
|
||||||
// @ts-ignore
|
|
||||||
import { useFormStatus } from 'react-dom';
|
import { useFormStatus } from 'react-dom';
|
||||||
import Spinner from './Spinner';
|
import Spinner from './Spinner';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { HTMLProps } from 'react';
|
import { HTMLProps } from 'react';
|
||||||
// @ts-ignore
|
|
||||||
import { useFormStatus } from 'react-dom';
|
import { useFormStatus } from 'react-dom';
|
||||||
import Spinner from './Spinner';
|
import Spinner from './Spinner';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
|
|||||||
@ -5,26 +5,40 @@ import { Photo } from '.';
|
|||||||
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
|
||||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
import { BiRefresh } from 'react-icons/bi';
|
import { BiRefresh } from 'react-icons/bi';
|
||||||
import { convertPhotoToFormData } from './form';
|
import { PhotoFormData, convertPhotoToFormData } from './form';
|
||||||
import PhotoForm from './PhotoForm';
|
import PhotoForm from './PhotoForm';
|
||||||
|
import { useFormState } from 'react-dom';
|
||||||
|
import { getExifDataAction } from './actions';
|
||||||
|
|
||||||
export default function PhotoEditPageClient({
|
export default function PhotoEditPageClient({
|
||||||
photo,
|
photo,
|
||||||
}: {
|
}: {
|
||||||
photo: Photo
|
photo: Photo
|
||||||
}) {
|
}) {
|
||||||
|
const [updatedExifData, action] = useFormState<Partial<PhotoFormData>>(
|
||||||
|
getExifDataAction,
|
||||||
|
{ url: photo.url},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminChildPage
|
<AdminChildPage
|
||||||
backPath={PATH_ADMIN_PHOTOS}
|
backPath={PATH_ADMIN_PHOTOS}
|
||||||
backLabel="Photos"
|
backLabel="Photos"
|
||||||
breadcrumb={photo.title || photo.id}
|
breadcrumb={photo.title || photo.id}
|
||||||
accessory={<SubmitButtonWithStatus icon={<BiRefresh size={18} />}>
|
accessory={
|
||||||
Refresh EXIF
|
<form action={action}>
|
||||||
</SubmitButtonWithStatus>}
|
<input name="photoUrl" value={photo.url} hidden readOnly />
|
||||||
|
<SubmitButtonWithStatus
|
||||||
|
icon={<BiRefresh size={18} className="translate-y-[-1.5px]" />}
|
||||||
|
>
|
||||||
|
Refresh EXIF
|
||||||
|
</SubmitButtonWithStatus>
|
||||||
|
</form>}
|
||||||
>
|
>
|
||||||
<PhotoForm
|
<PhotoForm
|
||||||
type="edit"
|
type="edit"
|
||||||
initialPhotoForm={convertPhotoToFormData(photo)}
|
initialPhotoForm={convertPhotoToFormData(photo)}
|
||||||
|
updatedExifData={updatedExifData}
|
||||||
/>
|
/>
|
||||||
</AdminChildPage>
|
</AdminChildPage>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,16 +23,26 @@ const THUMBNAIL_HEIGHT = 200;
|
|||||||
|
|
||||||
export default function PhotoForm({
|
export default function PhotoForm({
|
||||||
initialPhotoForm,
|
initialPhotoForm,
|
||||||
|
updatedExifData,
|
||||||
type = 'create',
|
type = 'create',
|
||||||
debugBlur,
|
debugBlur,
|
||||||
}: {
|
}: {
|
||||||
initialPhotoForm: Partial<PhotoFormData>
|
initialPhotoForm: Partial<PhotoFormData>
|
||||||
|
updatedExifData?: Partial<PhotoFormData>
|
||||||
type?: 'create' | 'edit'
|
type?: 'create' | 'edit'
|
||||||
debugBlur?: boolean
|
debugBlur?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [formData, setFormData] =
|
const [formData, setFormData] =
|
||||||
useState<Partial<PhotoFormData>>(initialPhotoForm);
|
useState<Partial<PhotoFormData>>(initialPhotoForm);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Update form when EXIF data is refreshed by parent
|
||||||
|
setFormData(currentForm => ({
|
||||||
|
...currentForm,
|
||||||
|
...updatedExifData,
|
||||||
|
}));
|
||||||
|
}, [updatedExifData]);
|
||||||
|
|
||||||
// Generate local date strings when
|
// Generate local date strings when
|
||||||
// none can be harvested from EXIF
|
// none can be harvested from EXIF
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -6,8 +6,14 @@ import {
|
|||||||
sqlDeletePhotoTagGlobally,
|
sqlDeletePhotoTagGlobally,
|
||||||
sqlUpdatePhoto,
|
sqlUpdatePhoto,
|
||||||
sqlRenamePhotoTagGlobally,
|
sqlRenamePhotoTagGlobally,
|
||||||
|
getPhoto,
|
||||||
} from '@/services/postgres';
|
} from '@/services/postgres';
|
||||||
import { convertFormDataToPhoto } from './form';
|
import {
|
||||||
|
PhotoFormData,
|
||||||
|
convertFormDataToPhotoDbInsert,
|
||||||
|
convertPhotoFormDataToPhotoDbInsert,
|
||||||
|
convertPhotoToFormData,
|
||||||
|
} from './form';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
convertUploadToPhoto,
|
convertUploadToPhoto,
|
||||||
@ -20,9 +26,10 @@ import {
|
|||||||
revalidatePhotosKey,
|
revalidatePhotosKey,
|
||||||
} from '@/cache';
|
} from '@/cache';
|
||||||
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS } from '@/site/paths';
|
||||||
|
import { extractFormDataFromUploadPath } from './server';
|
||||||
|
|
||||||
export async function createPhotoAction(formData: FormData) {
|
export async function createPhotoAction(formData: FormData) {
|
||||||
const photo = convertFormDataToPhoto(formData, true);
|
const photo = convertFormDataToPhotoDbInsert(formData, true);
|
||||||
|
|
||||||
const updatedUrl = await convertUploadToPhoto(photo.url, photo.id);
|
const updatedUrl = await convertUploadToPhoto(photo.url, photo.id);
|
||||||
|
|
||||||
@ -36,7 +43,7 @@ export async function createPhotoAction(formData: FormData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePhotoAction(formData: FormData) {
|
export async function updatePhotoAction(formData: FormData) {
|
||||||
const photo = convertFormDataToPhoto(formData);
|
const photo = convertFormDataToPhotoDbInsert(formData);
|
||||||
|
|
||||||
await sqlUpdatePhoto(photo);
|
await sqlUpdatePhoto(photo);
|
||||||
|
|
||||||
@ -84,7 +91,40 @@ export async function deleteBlobPhotoAction(formData: FormData) {
|
|||||||
if (formData.get('redirectToPhotos') === 'true') {
|
if (formData.get('redirectToPhotos') === 'true') {
|
||||||
redirect(PATH_ADMIN_PHOTOS);
|
redirect(PATH_ADMIN_PHOTOS);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export async function getExifDataAction(
|
||||||
|
photoFormPrevious: Partial<PhotoFormData>,
|
||||||
|
): Promise<Partial<PhotoFormData>> {
|
||||||
|
const { url } = photoFormPrevious;
|
||||||
|
if (url) {
|
||||||
|
const { photoForm } = await extractFormDataFromUploadPath(url);
|
||||||
|
if (photoForm) {
|
||||||
|
return photoForm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncPhotoExifDataAction(formData: FormData) {
|
||||||
|
const photoId = formData.get('photoId') as string;
|
||||||
|
if (photoId) {
|
||||||
|
const photo = await getPhoto(photoId);
|
||||||
|
if (photo) {
|
||||||
|
const {
|
||||||
|
photoForm: photoFormExif,
|
||||||
|
} = await extractFormDataFromUploadPath(photo.url);
|
||||||
|
if (photoFormExif) {
|
||||||
|
const photoFormDbInsert = convertPhotoFormDataToPhotoDbInsert({
|
||||||
|
...convertPhotoToFormData(photo),
|
||||||
|
...photoFormExif,
|
||||||
|
});
|
||||||
|
await sqlUpdatePhoto(photoFormDbInsert);
|
||||||
|
revalidatePhotosKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function syncCacheAction() {
|
export async function syncCacheAction() {
|
||||||
revalidateAllKeysAndPaths();
|
revalidateAllKeysAndPaths();
|
||||||
|
|||||||
@ -123,7 +123,7 @@ export const convertExifToFormData = (
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const convertFormDataToPhoto = (
|
export const convertFormDataToPhotoDbInsert = (
|
||||||
formData: FormData,
|
formData: FormData,
|
||||||
generateId?: boolean,
|
generateId?: boolean,
|
||||||
): PhotoDbInsert => {
|
): PhotoDbInsert => {
|
||||||
@ -178,3 +178,20 @@ export const convertFormDataToPhoto = (
|
|||||||
hidden: photoForm.hidden === 'true',
|
hidden: photoForm.hidden === 'true',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const convertPhotoFormDataToFormData = (
|
||||||
|
photoFormData: PhotoFormData,
|
||||||
|
) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const key in photoFormData) {
|
||||||
|
formData.append(key, photoFormData[key as keyof PhotoFormData]);
|
||||||
|
}
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertPhotoFormDataToPhotoDbInsert = (
|
||||||
|
photoFormData: PhotoFormData,
|
||||||
|
) => {
|
||||||
|
const formData = convertPhotoFormDataToFormData(photoFormData);
|
||||||
|
return convertFormDataToPhotoDbInsert(formData);
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user