Make photo titles optional
This commit is contained in:
parent
8f984e69ef
commit
c4044801a1
@ -63,7 +63,10 @@ export default async function AdminPage() {
|
|||||||
href={routeForPhoto(photo)}
|
href={routeForPhoto(photo)}
|
||||||
className="grow flex items-center gap-2"
|
className="grow flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{photo.title}
|
{photo.title ||
|
||||||
|
<span className="text-gray-400 dark:text-gray-500">
|
||||||
|
Untitled
|
||||||
|
</span>}
|
||||||
{photo.priorityOrder !== null &&
|
{photo.priorityOrder !== null &&
|
||||||
<span className={cc(
|
<span className={cc(
|
||||||
'text-xs leading-none px-1.5 py-1 rounded-sm',
|
'text-xs leading-none px-1.5 py-1 rounded-sm',
|
||||||
@ -101,7 +104,7 @@ function AdminGrid ({
|
|||||||
title: string,
|
title: string,
|
||||||
children: ReactNode,
|
children: ReactNode,
|
||||||
}) {
|
}) {
|
||||||
return <div className="space-y-2">
|
return <div className="space-y-4">
|
||||||
<div className="font-bold">
|
<div className="font-bold">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,11 @@ import { PropsWithChildren } from 'react';
|
|||||||
import AnimateItems from '@/components/AnimateItems';
|
import AnimateItems from '@/components/AnimateItems';
|
||||||
import PhotoLinks from '@/photo/PhotoLinks';
|
import PhotoLinks from '@/photo/PhotoLinks';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { ogImageDescriptionForPhoto, ogImageUrlForPhoto } from '@/photo';
|
import {
|
||||||
|
ogImageDescriptionForPhoto,
|
||||||
|
ogImageUrlForPhoto,
|
||||||
|
titleForPhoto,
|
||||||
|
} from '@/photo';
|
||||||
import PhotoGrid from '@/photo/PhotoGrid';
|
import PhotoGrid from '@/photo/PhotoGrid';
|
||||||
import PhotoLarge from '@/photo/PhotoLarge';
|
import PhotoLarge from '@/photo/PhotoLarge';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
@ -24,7 +28,7 @@ export async function generateMetadata(
|
|||||||
|
|
||||||
if (!photo) { return {}; }
|
if (!photo) { return {}; }
|
||||||
|
|
||||||
const title = photo.title;
|
const title = titleForPhoto(photo);
|
||||||
const description = ogImageDescriptionForPhoto(photo);
|
const description = ogImageDescriptionForPhoto(photo);
|
||||||
const images = ogImageUrlForPhoto(photo);
|
const images = ogImageUrlForPhoto(photo);
|
||||||
|
|
||||||
|
|||||||
@ -72,9 +72,8 @@ export default function PhotoForm({
|
|||||||
};
|
};
|
||||||
}, [url, type]);
|
}, [url, type]);
|
||||||
|
|
||||||
const isFormValid =
|
const isFormValid = FORM_METADATA_ENTRIES.every(([key, { required }]) =>
|
||||||
Boolean(formData.blurData) &&
|
!required || Boolean(formData[key]));
|
||||||
Boolean(formData.title);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 max-w-[38rem]">
|
<div className="space-y-8 max-w-[38rem]">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Photo } from '.';
|
import { Photo, titleForPhoto } from '.';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import ImageLarge from '@/components/ImageLarge';
|
import ImageLarge from '@/components/ImageLarge';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
@ -30,7 +30,7 @@ export default function PhotoLarge({
|
|||||||
contentMain={
|
contentMain={
|
||||||
<ImageLarge
|
<ImageLarge
|
||||||
className="w-full"
|
className="w-full"
|
||||||
alt={photo.title ?? 'Photo'}
|
alt={titleForPhoto(photo)}
|
||||||
href={routeForPhoto(photo)}
|
href={routeForPhoto(photo)}
|
||||||
src={photo.url}
|
src={photo.url}
|
||||||
aspectRatio={photo.aspectRatio}
|
aspectRatio={photo.aspectRatio}
|
||||||
@ -50,7 +50,7 @@ export default function PhotoLarge({
|
|||||||
href={routeForPhoto(photo)}
|
href={routeForPhoto(photo)}
|
||||||
className="font-bold uppercase"
|
className="font-bold uppercase"
|
||||||
>
|
>
|
||||||
{photo.title}
|
{titleForPhoto(photo)}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="uppercase">
|
<div className="uppercase">
|
||||||
{photo.make} {photo.model}
|
{photo.make} {photo.model}
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Photo, ogImageDescriptionForPhoto, ogImageUrlForPhoto } from '@/photo';
|
import {
|
||||||
|
Photo,
|
||||||
|
ogImageDescriptionForPhoto,
|
||||||
|
ogImageUrlForPhoto,
|
||||||
|
titleForPhoto,
|
||||||
|
} from '@/photo';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BiError } from 'react-icons/bi';
|
import { BiError } from 'react-icons/bi';
|
||||||
@ -111,7 +116,7 @@ export default function PhotoOGTile({
|
|||||||
'border-t border-gray-200 dark:border-gray-800',
|
'border-t border-gray-200 dark:border-gray-800',
|
||||||
)}>
|
)}>
|
||||||
<div className="text-gray-800 dark:text-white font-medium">
|
<div className="text-gray-800 dark:text-white font-medium">
|
||||||
{photo.title}
|
{titleForPhoto(photo)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-500">
|
<div className="text-gray-500">
|
||||||
{ogImageDescriptionForPhoto(photo)}
|
{ogImageDescriptionForPhoto(photo)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Photo } from '.';
|
import { Photo, titleForPhoto } from '.';
|
||||||
import ImageSmall from '@/components/ImageSmall';
|
import ImageSmall from '@/components/ImageSmall';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
@ -24,7 +24,7 @@ export default function PhotoSmall({
|
|||||||
aspectRatio={photo.aspectRatio}
|
aspectRatio={photo.aspectRatio}
|
||||||
blurData={photo.blurData}
|
blurData={photo.blurData}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
alt={photo.title ?? 'Photo'}
|
alt={titleForPhoto(photo)}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Photo } from '.';
|
import { Photo, titleForPhoto } from '.';
|
||||||
import ImageTiny from '@/components/ImageTiny';
|
import ImageTiny from '@/components/ImageTiny';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
@ -27,7 +27,7 @@ export default function PhotoTiny({
|
|||||||
src={photo.url}
|
src={photo.url}
|
||||||
aspectRatio={photo.aspectRatio}
|
aspectRatio={photo.aspectRatio}
|
||||||
blurData={photo.blurData}
|
blurData={photo.blurData}
|
||||||
alt={photo.title ?? 'Photo'}
|
alt={titleForPhoto(photo)}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,13 +18,13 @@ type FormMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FORM_METADATA: Record<keyof PhotoFormData, FormMeta> = {
|
const FORM_METADATA: Record<keyof PhotoFormData, FormMeta> = {
|
||||||
title: { label: 'title', required: true },
|
title: { label: 'title' },
|
||||||
id: { label: 'id', readOnly: true, hideIfEmpty: true },
|
id: { label: 'id', readOnly: true, hideIfEmpty: true },
|
||||||
idShort: { label: 'short id', readOnly: true, hideIfEmpty: true },
|
idShort: { label: 'short id', readOnly: true, hideIfEmpty: true },
|
||||||
url: { label: 'url', readOnly: true },
|
url: { label: 'url', readOnly: true },
|
||||||
extension: { label: 'extension', readOnly: true },
|
extension: { label: 'extension', readOnly: true },
|
||||||
aspectRatio: { label: 'aspect ratio', readOnly: true },
|
aspectRatio: { label: 'aspect ratio', readOnly: true },
|
||||||
blurData: { label: 'blur data', readOnly: true },
|
blurData: { label: 'blur data', readOnly: true, required: true },
|
||||||
make: { label: 'camera make' },
|
make: { label: 'camera make' },
|
||||||
model: { label: 'camera model' },
|
model: { label: 'camera model' },
|
||||||
focalLength: { label: 'focal length' },
|
focalLength: { label: 'focal length' },
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { getNextImageUrlForRequest } from '@/utility/image';
|
import { getNextImageUrlForRequest } from '@/utility/image';
|
||||||
import { Photo } from '..';
|
import { Photo, titleForPhoto } from '..';
|
||||||
|
|
||||||
const IMAGE_WIDTH = 400;
|
const IMAGE_WIDTH = 400;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export default function PhotoGridImageResponse({
|
|||||||
request,
|
request,
|
||||||
IMAGE_WIDTH,
|
IMAGE_WIDTH,
|
||||||
)}
|
)}
|
||||||
alt={photo.title}
|
alt={titleForPhoto(photo)}
|
||||||
width={IMAGE_WIDTH}
|
width={IMAGE_WIDTH}
|
||||||
height={IMAGE_WIDTH / photo.aspectRatio}
|
height={IMAGE_WIDTH / photo.aspectRatio}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Photo } from '..';
|
import { Photo, titleForPhoto } from '..';
|
||||||
import { getNextImageUrlForRequest } from '@/utility/image';
|
import { getNextImageUrlForRequest } from '@/utility/image';
|
||||||
import { formatModelShort } from '@/utility/exif';
|
import { formatModelShort } from '@/utility/exif';
|
||||||
import { AiFillApple } from 'react-icons/ai';
|
import { AiFillApple } from 'react-icons/ai';
|
||||||
@ -34,7 +34,7 @@ export default function PhotoOGImageResponse({
|
|||||||
)}
|
)}
|
||||||
width={width}
|
width={width}
|
||||||
height={width / photo.aspectRatio}
|
height={width / photo.aspectRatio}
|
||||||
alt={photo.title}
|
alt={titleForPhoto(photo)}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@ -140,3 +140,6 @@ export const translatePhotoId = (shortId: string) => {
|
|||||||
const id = PHOTO_ID_FORWARDING_TABLE[shortId] || shortId;
|
const id = PHOTO_ID_FORWARDING_TABLE[shortId] || shortId;
|
||||||
return id.length === 22 ? translator.toUUID(id) : id;
|
return id.length === 22 ? translator.toUUID(id) : id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const titleForPhoto = (photo: Photo) =>
|
||||||
|
photo.title || 'Untitled';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user