'use client'; import { useCallback, useEffect, useState } from 'react'; import { FORM_METADATA_ENTRIES, PhotoFormData, convertFormKeysToLabels, getFormErrors, isFormValid, } from '.'; import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import { createPhotoAction, updatePhotoAction } from '../actions'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; import CanvasBlurCapture from '@/components/CanvasBlurCapture'; import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths'; import { generateLocalNaivePostgresString, generateLocalPostgresString, } from '@/utility/date'; import { toastSuccess, toastWarning } from '@/toast'; import { getDimensionsFromSize } from '@/utility/size'; import ImageBlurFallback from '@/components/ImageBlurFallback'; import { BLUR_ENABLED } from '@/site/config'; import { Tags, sortTagsObjectWithoutFavs } from '@/tag'; import { formatCount, formatCountDescriptive } from '@/utility/string'; const THUMBNAIL_SIZE = 300; export default function PhotoForm({ initialPhotoForm, updatedExifData, type = 'create', uniqueTags, debugBlur, onTitleChange, onFormStatusChange, }: { initialPhotoForm: Partial updatedExifData?: Partial type?: 'create' | 'edit' uniqueTags?: Tags debugBlur?: boolean onTitleChange?: (updatedTitle: string) => void onFormStatusChange?: (pending: boolean) => void }) { const [formData, setFormData] = useState>(initialPhotoForm); const [formErrors, setFormErrors] = useState(getFormErrors(initialPhotoForm)); // Update form when EXIF data // is refreshed by parent useEffect(() => { if (Object.keys(updatedExifData ?? {}).length > 0) { const changedKeys: (keyof PhotoFormData)[] = []; setFormData(currentForm => { Object.entries(updatedExifData ?? {}) .forEach(([key, value]) => { if (currentForm[key as keyof PhotoFormData] !== value) { changedKeys.push(key as keyof PhotoFormData); } }); return { ...currentForm, ...updatedExifData, }; }); if (changedKeys.length > 0) { const fields = convertFormKeysToLabels(changedKeys); toastSuccess( `Updated EXIF fields: ${fields.join(', ')}`, 8000, ); } else { toastWarning('No new EXIF data found'); } } }, [updatedExifData]); const { width, height, } = getDimensionsFromSize(THUMBNAIL_SIZE, formData.aspectRatio); // Generate local date strings when // none can be extracted from EXIF useEffect(() => { if (!formData.takenAt || !formData.takenAtNaive) { setFormData(data => ({ ...data, ...!formData.takenAt && { takenAt: generateLocalPostgresString(), }, ...!formData.takenAtNaive && { takenAtNaive: generateLocalNaivePostgresString(), }, })); } }, [formData.takenAt, formData.takenAtNaive]); const url = formData.url ?? ''; const updateBlurData = useCallback((blurData: string) => { if (BLUR_ENABLED) { setFormData(data => ({ ...data, blurData, })); } }, []); return (
{debugBlur && formData.blurData && blur}
blur()} className="space-y-6" > {FORM_METADATA_ENTRIES( sortTagsObjectWithoutFavs(uniqueTags ?? []) .map(({ tag, count }) => ({ value: tag, annotation: formatCount(count), annotationAria: formatCountDescriptive(count, 'tagged'), })) ) .map(([key, { label, note, required, selectOptions, selectOptionsDefaultLabel, tagOptions, readOnly, validate, validateStringMaxLength, capitalize, hideIfEmpty, shouldHide, loadingMessage, type, }]) => ( (!hideIfEmpty || formData[key]) && !shouldHide?.(formData) ) && { setFormData({ ...formData, [key]: value }); if (validate) { setFormErrors({ ...formErrors, [key]: validate(value) }); } else if (validateStringMaxLength !== undefined) { setFormErrors({ ...formErrors, [key]: value.length > validateStringMaxLength ? `${validateStringMaxLength} characters or less` : undefined, }); } if (key === 'title') { onTitleChange?.(value.trim()); } }} selectOptions={selectOptions} selectOptionsDefaultLabel={selectOptionsDefaultLabel} tagOptions={tagOptions} required={required} readOnly={readOnly} capitalize={capitalize} placeholder={loadingMessage && !formData[key] ? loadingMessage : undefined} loading={loadingMessage && !formData[key] ? true : false} type={type} />)}
Cancel {type === 'create' ? 'Create' : 'Update'}
); };