Add tagging to photos
This commit is contained in:
parent
c01b4b02d1
commit
3c78cb2024
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user