Add tagging to photos

This commit is contained in:
Sam Becker 2023-09-14 14:01:59 -05:00
parent c01b4b02d1
commit 3c78cb2024
7 changed files with 55 additions and 9 deletions

View File

@ -6,6 +6,7 @@ import { experimental_useFormStatus as useFormStatus } from 'react-dom';
export default function FieldSetWithStatus({
id,
label,
note,
value,
onChange,
required,
@ -15,6 +16,7 @@ export default function FieldSetWithStatus({
}: {
id: string
label: string
note?: string
value: string
onChange?: (value: string) => void
required?: boolean
@ -31,9 +33,13 @@ export default function FieldSetWithStatus({
htmlFor={id}
>
{label}
{note &&
<span className="text-gray-400 dark:text-gray-600">
({note})
</span>}
{required &&
<span className="text-gray-400 dark:text-gray-600">
(Required)
Required
</span>}
</label>
<input

View File

@ -105,13 +105,14 @@ export default function PhotoForm({
>
{FORM_METADATA_ENTRIES.map(([
key,
{ label, required, readOnly, hideIfEmpty },
{ label, note, required, readOnly, hideIfEmpty },
]) =>
(!hideIfEmpty || formData[key]) &&
<FieldSetWithStatus
key={key}
id={key}
label={label}
note={note}
value={formData[key] ?? ''}
onChange={value => setFormData({ ...formData, [key]: value })}
required={required}

View File

@ -5,6 +5,7 @@ import { cc } from '@/utility/css';
import Link from 'next/link';
import { routeForPhoto } from '@/site/routes';
import SharePhotoButton from './SharePhotoButton';
import { FaTag } from 'react-icons/fa';
export default function PhotoLarge({
photo,
@ -52,6 +53,17 @@ export default function PhotoLarge({
>
{titleForPhoto(photo)}
</Link>
{photo.tags.length > 0 &&
<div className="uppercase">
{photo.tags.map(tag =>
<div
className="flex items-center gap-x-1.5"
key={tag}
>
<FaTag size={11} />
<span>{tag}</span>
</div>)}
</div>}
<div className="uppercase">
{photo.make} {photo.model}
</div>

View File

@ -6,19 +6,22 @@ import {
} from '@/utility/date';
import { getOffsetFromExif } from '@/utility/exif';
import { toFixedNumber } from '@/utility/number';
import { convertStringToArray } from '@/utility/string';
export type PhotoFormData = Record<keyof PhotoDbInsert, string>;
type FormMeta = {
label: string,
required?: boolean,
readOnly?: boolean,
hideIfEmpty?: boolean,
hideTemporarily?: boolean,
label: string
note?: string
required?: boolean
readOnly?: boolean
hideIfEmpty?: boolean
hideTemporarily?: boolean
};
const FORM_METADATA: Record<keyof PhotoFormData, FormMeta> = {
title: { label: 'title' },
tags: { label: 'tags', note: 'comma-separated values' },
id: { label: 'id', readOnly: true, hideIfEmpty: true },
idShort: { label: 'short id', readOnly: true, hideIfEmpty: true },
url: { label: 'url', readOnly: true },
@ -51,6 +54,8 @@ export const convertPhotoToFormData = (
): PhotoFormData => {
const valueForKey = (key: keyof Photo, value: any) => {
switch (key) {
case 'tags':
return value?.join ? value.join(', ') : value;
case 'takenAt':
return value?.toISOString ? value.toISOString() : value;
default:
@ -106,6 +111,8 @@ export const convertFormDataToPhoto = (
return {
...photoForm,
// convert form strings to arrays
tags: convertStringToArray(photoForm.tags),
// Convert form strings to numbers
aspectRatio: toFixedNumber(parseFloat(photoForm.aspectRatio), 6),
focalLength: photoForm.focalLength

View File

@ -42,15 +42,17 @@ export interface PhotoDbInsert extends PhotoExif {
extension: string
blurData: string
title?: string
tags?: string[]
locationName?: string
priorityOrder?: number
}
// Raw db response
export interface PhotoDb extends Omit<PhotoDbInsert, 'takenAt'> {
export interface PhotoDb extends Omit<PhotoDbInsert, 'takenAt' | 'tags'> {
updatedAt: Date
createdAt: Date
takenAt: Date
tags: string[]
}
// Parsed db response
@ -72,6 +74,7 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
...photoDb,
idShort:
translator.fromUUID(photoDb.id),
tags: photoDb.tags ?? [],
focalLengthFormatted:
formatFocalLength(photoDb.focalLength),
focalLengthIn35MmFormatFormatted:

View File

@ -10,6 +10,10 @@ import { isValidUUID } from '@/utility/string';
const PHOTO_DEFAULT_LIMIT = 100;
export const convertArrayToPostgresString = (array?: string[]) => array
? `{${array.join(',')}}`
: null;
const sqlCreatePhotosTable = () =>
sql`
CREATE TABLE IF NOT EXISTS photos (
@ -19,6 +23,7 @@ const sqlCreatePhotosTable = () =>
aspect_ratio REAL DEFAULT 1.5,
blur_data TEXT,
title VARCHAR(255),
tags VARCHAR(255)[],
make VARCHAR(255),
model VARCHAR(255),
focal_length SMALLINT,
@ -47,6 +52,7 @@ export const sqlInsertPhotoIntoDb = (photo: PhotoDbInsert) => {
aspect_ratio,
blur_data,
title,
tags,
make,
model,
focal_length,
@ -69,6 +75,7 @@ export const sqlInsertPhotoIntoDb = (photo: PhotoDbInsert) => {
${photo.aspectRatio},
${photo.blurData},
${photo.title},
${convertArrayToPostgresString(photo.tags)},
${photo.make},
${photo.model},
${photo.focalLength},
@ -96,6 +103,7 @@ export const sqlUpdatePhotoInDb = (photo: PhotoDbInsert) =>
aspect_ratio=${photo.aspectRatio},
blur_data=${photo.blurData},
title=${photo.title},
tags=${convertArrayToPostgresString(photo.tags)},
make=${photo.make},
model=${photo.model},
focal_length=${photo.focalLength},

View File

@ -1,2 +1,11 @@
export const isValidUUID = (id: string): boolean =>
/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i.test(id);
/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i.test(id);
export const convertStringToArray = (
string?: string,
parameterize = true,
) => string
? string.split(',').map(tag => parameterize
? tag.trim().replaceAll(' ', '-').toLowerCase()
: tag.trim())
: undefined;