Refine admin tag editor

This commit is contained in:
Sam Becker 2023-10-06 20:41:42 -05:00
parent 1fd41462e7
commit 02fbf0a2e0
7 changed files with 84 additions and 20 deletions

View File

@ -6,6 +6,7 @@
"exif", "exif",
"hgetall", "hgetall",
"hset", "hset",
"Lightbox",
"nanoids", "nanoids",
"nextjs", "nextjs",
"qaub", "qaub",

View File

@ -1,12 +1,13 @@
import AdminChildPage from '@/components/AdminChildPage'; import AdminChildPage from '@/components/AdminChildPage';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { getPhotosCached } from '@/cache'; import { getPhotosCached, getPhotosTagCountCached } from '@/cache';
import TagForm from '@/tag/TagForm'; import TagForm from '@/tag/TagForm';
import { PATH_ADMIN, PATH_ADMIN_TAGS } from '@/site/paths'; import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/site/paths';
import { getPhotosTagCount } from '@/services/postgres';
import PhotoTag from '@/tag/PhotoTag'; import PhotoTag from '@/tag/PhotoTag';
import { photoQuantityText } from '@/photo'; import { photoLabelForCount } from '@/photo';
import PhotoGrid from '@/photo/PhotoGrid'; import PhotoLightbox from '@/photo/PhotoLightbox';
const MAX_PHOTO_TO_SHOW = 6;
export const runtime = 'edge'; export const runtime = 'edge';
@ -19,8 +20,8 @@ export default async function PhotoPageEdit({ params: { tag } }: Props) {
count, count,
photos, photos,
] = await Promise.all([ ] = await Promise.all([
getPhotosTagCount(tag), getPhotosTagCountCached(tag),
getPhotosCached({ tag }), getPhotosCached({ tag, limit: MAX_PHOTO_TO_SHOW }),
]); ]);
if (count === 0) { redirect(PATH_ADMIN); } if (count === 0) { redirect(PATH_ADMIN); }
@ -32,18 +33,21 @@ export default async function PhotoPageEdit({ params: { tag } }: Props) {
breadcrumb={<div className="flex item gap-2"> breadcrumb={<div className="flex item gap-2">
<PhotoTag {...{ tag }} /> <PhotoTag {...{ tag }} />
<div className="text-dim uppercase"> <div className="text-dim uppercase">
{photoQuantityText(count, false)} <span>{count}</span>
<span className="hidden xs:inline-block">
&nbsp;
{photoLabelForCount(count)}
</span>
</div> </div>
</div>} </div>}
> >
<div className="space-y-8"> <TagForm {...{ tag, photos }}>
<PhotoGrid <PhotoLightbox
photos={photos.slice(0, 12)} {...{ count, photos }}
animate={false} maxPhotosToShow={MAX_PHOTO_TO_SHOW}
small moreLink={pathForTag(tag)}
/> />
<TagForm {...{ tag, photos }} /> </TagForm>
</div>
</AdminChildPage> </AdminChildPage>
); );
}; };

View File

@ -20,7 +20,10 @@ function AdminChildPage({
contentMain={ contentMain={
<div className="space-y-6"> <div className="space-y-6">
{backPath && {backPath &&
<div className="flex gap-3 items-center h-9"> <div className={cc(
'flex flex-wrap items-center gap-x-1.5 sm:gap-x-3 gap-y-1',
'h-9',
)}>
<Link <Link
href={backPath} href={backPath}
className="flex gap-1.5 items-center" className="flex gap-1.5 items-center"

View File

@ -15,6 +15,7 @@ export default function PhotoGrid({
animateOnFirstLoadOnly, animateOnFirstLoadOnly,
staggerOnFirstLoadOnly = true, staggerOnFirstLoadOnly = true,
showMorePath, showMorePath,
additionalTile,
small, small,
}: { }: {
photos: Photo[] photos: Photo[]
@ -26,6 +27,7 @@ export default function PhotoGrid({
animateOnFirstLoadOnly?: boolean animateOnFirstLoadOnly?: boolean
staggerOnFirstLoadOnly?: boolean staggerOnFirstLoadOnly?: boolean
showMorePath?: string showMorePath?: string
additionalTile?: JSX.Element
small?: boolean small?: boolean
}) { }) {
return ( return (
@ -34,7 +36,7 @@ export default function PhotoGrid({
className={cc( className={cc(
'grid gap-1', 'grid gap-1',
small small
? 'grid-cols-4 xs:grid-cols-6' ? 'grid-cols-3 xs:grid-cols-6'
: 'grid-cols-2 sm:grid-cols-4 md:grid-cols-3 lg:grid-cols-4', : 'grid-cols-2 sm:grid-cols-4 md:grid-cols-3 lg:grid-cols-4',
'items-center', 'items-center',
)} )}
@ -51,7 +53,7 @@ export default function PhotoGrid({
tag={tag} tag={tag}
camera={camera} camera={camera}
selected={photo.id === selectedPhoto?.id} selected={photo.id === selectedPhoto?.id}
/>)} />).concat(additionalTile ?? [])}
/> />
{showMorePath && {showMorePath &&
<MorePhotos path={showMorePath} />} <MorePhotos path={showMorePath} />}

View File

@ -0,0 +1,51 @@
import { cc } from '@/utility/css';
import { Photo } from '.';
import PhotoGrid from './PhotoGrid';
import Link from 'next/link';
export default function PhotoLightbox({
count,
photos,
maxPhotosToShow = 6,
moreLink,
}: {
count: number
photos: Photo[]
maxPhotosToShow?: number
moreLink: string
}) {
const photoCountToShow = maxPhotosToShow < count
? maxPhotosToShow - 1
: maxPhotosToShow;
const countNotShown = count - photoCountToShow;
const showOverageTile = countNotShown > 0;
return (
<div className={cc(
'border dark:border-gray-800 p-1.5 lg:p-2 rounded-md',
'bg-gray-50 dark:bg-gray-950',
)}>
<PhotoGrid
photos={photos.slice(0, photoCountToShow)}
animate={false}
additionalTile={showOverageTile
? <Link
href={moreLink}
className={cc(
'flex flex-col items-center justify-center',
'gap-0.5 lg:gap-1',
)}
>
<div className="text-[1.1rem] lg:text-[1.5rem]">
+{countNotShown}
</div>
<div className="text-dim">More</div>
</Link>
: undefined}
small
/>
</div>
);
}

View File

@ -154,7 +154,7 @@ export const translatePhotoId = (id: string) =>
export const titleForPhoto = (photo: Photo) => export const titleForPhoto = (photo: Photo) =>
photo.title || 'Untitled'; photo.title || 'Untitled';
const photoLabelForCount = (count: number) => export const photoLabelForCount = (count: number) =>
count === 1 ? 'Photo' : 'Photos'; count === 1 ? 'Photo' : 'Photos';
export const photoQuantityText = (count: number, includeParentheses = true) => export const photoQuantityText = (count: number, includeParentheses = true) =>

View File

@ -4,14 +4,16 @@ import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import Link from 'next/link'; import Link from 'next/link';
import { PATH_ADMIN_TAGS } from '@/site/paths'; import { PATH_ADMIN_TAGS } from '@/site/paths';
import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import FieldSetWithStatus from '@/components/FieldSetWithStatus';
import { useMemo, useState } from 'react'; import { ReactNode, useMemo, useState } from 'react';
import { renamePhotoTagGloballyAction } from '@/photo/actions'; import { renamePhotoTagGloballyAction } from '@/photo/actions';
import { parameterize } from '@/utility/string'; import { parameterize } from '@/utility/string';
export default function TagForm({ export default function TagForm({
tag, tag,
children,
}: { }: {
tag: string tag: string
children?: ReactNode
}) { }) {
const [updatedTagRaw, setUpdatedTagRaw] = useState(tag); const [updatedTagRaw, setUpdatedTagRaw] = useState(tag);
@ -49,6 +51,7 @@ export default function TagForm({
hidden hidden
readOnly readOnly
/> />
{children}
<div className="flex gap-3"> <div className="flex gap-3">
<Link <Link
className="button" className="button"