Allow admins to select photos from /grid

This commit is contained in:
Sam Becker 2024-07-07 23:27:36 -05:00
parent 7f8e2d7a3d
commit 3f0b9e7b27
9 changed files with 117 additions and 27 deletions

View File

@ -0,0 +1,35 @@
'use client';
import Note from '@/components/Note';
import LoaderButton from '@/components/primitives/LoaderButton';
import SiteGrid from '@/components/SiteGrid';
import { useAppState } from '@/state/AppState';
import clsx from 'clsx';
export default function AdminBatchEditPanel() {
const {
isUserSignedIn,
selectedPhotoIds = [],
setSelectedPhotoIds,
} = useAppState();
return isUserSignedIn && selectedPhotoIds.length > 0
? <SiteGrid
className="mb-5 sticky top-0 z-10 -mt-2 pt-2"
contentMain={<Note
color="gray"
className={clsx(
'backdrop-blur-lg !border-transparent',
'!bg-gray-200/70 dark:!bg-gray-800/70'
)}
cta={<LoaderButton
onClick={() => setSelectedPhotoIds?.([])}
primary
>
Clear
</LoaderButton>}
>
{selectedPhotoIds.length} photos selected
</Note>} />
: null;
}

View File

@ -17,6 +17,7 @@ import Nav from '@/site/Nav';
import Footer from '@/site/Footer';
import CommandK from '@/site/CommandK';
import SwrConfigClient from '../state/SwrConfigClient';
import AdminBatchEditPanel from '@/admin/AdminBatchEditPanel';
import '../site/globals.css';
import '../site/sonner.css';
@ -84,6 +85,7 @@ export default function RootLayout({
'lg:mx-6 lg:mb-6',
)}>
<Nav siteDomainOrTitle={SITE_DOMAIN_OR_TITLE} />
<AdminBatchEditPanel />
<div className={clsx(
'min-h-[16rem] sm:min-h-[30rem]',
'mb-12',

View File

@ -10,9 +10,11 @@ export default function Note({
color = 'blue',
icon,
animate,
cta,
}: {
icon?: ReactNode
animate?: boolean
cta?: ReactNode
} & ComponentProps<typeof Container>) {
return (
<AnimateItems
@ -35,9 +37,13 @@ export default function Note({
className="translate-x-[0.5px] translate-y-[0.5px]"
/>}
</span>
<span className="text-sm">
<span className="text-sm grow">
{children}
</span>
{cta &&
<span className="translate-x-1">
{cta}
</span>}
</div>
</Container>,
]}

View File

@ -1,3 +1,5 @@
'use client';
import { Photo } from '.';
import PhotoMedium from './PhotoMedium';
import { clsx } from 'clsx/lite';
@ -5,6 +7,7 @@ import AnimateItems from '@/components/AnimateItems';
import { Camera } from '@/camera';
import { FilmSimulation } from '@/simulation';
import { GRID_ASPECT_RATIO, HIGH_DENSITY_GRID } from '@/site/config';
import { useAppState } from '@/state/AppState';
export default function PhotoGrid({
photos,
@ -21,6 +24,7 @@ export default function PhotoGrid({
staggerOnFirstLoadOnly = true,
additionalTile,
small,
canSelect,
onLastPhotoVisible,
onAnimationComplete,
}: {
@ -38,9 +42,16 @@ export default function PhotoGrid({
staggerOnFirstLoadOnly?: boolean
additionalTile?: JSX.Element
small?: boolean
canSelect?: boolean
onLastPhotoVisible?: () => void
onAnimationComplete?: () => void
}) {
const {
isUserSignedIn,
selectedPhotoIds = [],
setSelectedPhotoIds,
} = useAppState();
return (
<AnimateItems
className={clsx(
@ -60,12 +71,14 @@ export default function PhotoGrid({
animateOnFirstLoadOnly={animateOnFirstLoadOnly}
staggerOnFirstLoadOnly={staggerOnFirstLoadOnly}
onAnimationComplete={onAnimationComplete}
items={photos.map((photo, index) =>
<div
items={photos.map((photo, index) =>{
const isSelected = selectedPhotoIds.includes(photo.id);
return <div
key={photo.id}
className={GRID_ASPECT_RATIO !== 0
? 'flex relative overflow-hidden'
: undefined}
className={clsx(
GRID_ASPECT_RATIO !== 0 && 'flex relative overflow-hidden',
'group',
)}
style={{
...GRID_ASPECT_RATIO !== 0 && {
aspectRatio: GRID_ASPECT_RATIO,
@ -87,7 +100,37 @@ export default function PhotoGrid({
: undefined,
}}
/>
</div>).concat(additionalTile ?? [])}
{canSelect && isUserSignedIn &&
<>
{/* Admin Select Border */}
<div className={clsx(
'absolute w-full h-full pointer-events-none',
)}>
<div
className={clsx(
'w-full h-full border-black dark:border-white',
isSelected && 'border-4',
)}
/>
</div>
{/* Admin Select Action */}
<div className="absolute top-0 right-0">
<input
type="checkbox"
className={clsx(
'absolute top-2 right-2',
!isSelected && 'hidden group-hover:block',
)}
checked={isSelected}
onChange={() => setSelectedPhotoIds?.(isSelected
? selectedPhotoIds.filter(id => id !== photo.id)
: selectedPhotoIds.concat(photo.id),
)}
/>
</div>
</>}
</div>;
}).concat(additionalTile ?? [])}
itemKeys={photos.map(photo => photo.id)
.concat(additionalTile ? ['more'] : [])}
/>

View File

@ -1,14 +1,11 @@
'use client';
import SiteGrid from '@/components/SiteGrid';
import { Photo } from '.';
import PhotoGrid from './PhotoGrid';
import PhotoGridInfinite from './PhotoGridInfinite';
import { Camera } from '@/camera';
import { clsx } from 'clsx/lite';
import AnimateItems from '@/components/AnimateItems';
import { FilmSimulation } from '@/simulation';
import { useCallback, useState } from 'react';
import { ComponentProps, useCallback, useState } from 'react';
export default function PhotoGridContainer({
cacheKey,
@ -21,18 +18,13 @@ export default function PhotoGridContainer({
animateOnFirstLoadOnly,
header,
sidebar,
canSelect,
}: {
cacheKey: string
photos: Photo[]
count: number
tag?: string
camera?: Camera
simulation?: FilmSimulation
focal?: number
animateOnFirstLoadOnly?: boolean
header?: JSX.Element
sidebar?: JSX.Element
}) {
} & ComponentProps<typeof PhotoGrid>) {
const [
shouldAnimateDynamicItems,
setShouldAnimateDynamicItems,
@ -63,6 +55,7 @@ export default function PhotoGridContainer({
focal,
animateOnFirstLoadOnly,
onAnimationComplete,
canSelect,
}} />
{count > initialOffset &&
<PhotoGridInfinite {...{
@ -74,6 +67,7 @@ export default function PhotoGridContainer({
simulation,
focal,
animateOnFirstLoadOnly,
canSelect,
}} />}
</div>
</div>}

View File

@ -1,10 +1,9 @@
'use client';
import { Camera } from '@/camera';
import { INFINITE_SCROLL_GRID_MULTIPLE } from '.';
import InfinitePhotoScroll from './InfinitePhotoScroll';
import PhotoGrid from './PhotoGrid';
import { FilmSimulation } from '@/simulation';
import { ComponentProps } from 'react';
export default function PhotoGridInfinite({
cacheKey,
@ -15,16 +14,11 @@ export default function PhotoGridInfinite({
simulation,
focal,
animateOnFirstLoadOnly,
canSelect,
}: {
cacheKey: string
initialOffset: number
canStart?: boolean
tag?: string
camera?: Camera
simulation?: FilmSimulation
focal?: number
animateOnFirstLoadOnly?: boolean
}) {
} & Omit<ComponentProps<typeof PhotoGrid>, 'photos'>) {
return (
<InfinitePhotoScroll
cacheKey={cacheKey}
@ -44,6 +38,7 @@ export default function PhotoGridInfinite({
focal,
onLastPhotoVisible,
animateOnFirstLoadOnly,
canSelect,
}} />}
</InfinitePhotoScroll>
);

View File

@ -1,3 +1,5 @@
'use client';
import { Tags } from '@/tag';
import { Photo } from '.';
import { Cameras } from '@/camera';
@ -5,6 +7,8 @@ import { FilmSimulations } from '@/simulation';
import { PATH_GRID } from '@/site/paths';
import PhotoGridSidebar from './PhotoGridSidebar';
import PhotoGridContainer from './PhotoGridContainer';
import { useEffect } from 'react';
import { useAppState } from '@/state/AppState';
export default function PhotoGridPage({
photos,
@ -19,6 +23,10 @@ export default function PhotoGridPage({
cameras: Cameras
simulations: FilmSimulations
}) {
const { setSelectedPhotoIds } = useAppState();
useEffect(() => () => setSelectedPhotoIds?.([]), [setSelectedPhotoIds]);
return (
<PhotoGridContainer
cacheKey={`page-${PATH_GRID}`}
@ -32,6 +40,7 @@ export default function PhotoGridPage({
photosCount,
}} />
</div>}
canSelect
/>
);
}

View File

@ -22,6 +22,8 @@ export interface AppStateContext {
adminUpdateTimes?: Date[]
registerAdminUpdate?: () => void
hiddenPhotosCount?: number
selectedPhotoIds?: string[]
setSelectedPhotoIds?: Dispatch<SetStateAction<string[]>>
// DEBUG
arePhotosMatted?: boolean
setArePhotosMatted?: Dispatch<SetStateAction<boolean>>

View File

@ -34,6 +34,8 @@ export default function AppStateProvider({
useState<Date[]>([]);
const [hiddenPhotosCount, setHiddenPhotosCount] =
useState(0);
const [selectedPhotoIds, setSelectedPhotoIds] =
useState<string[]>([]);
// DEBUG
const [arePhotosMatted, setArePhotosMatted] =
useState(MATTE_PHOTOS);
@ -92,6 +94,8 @@ export default function AppStateProvider({
adminUpdateTimes,
registerAdminUpdate,
hiddenPhotosCount,
selectedPhotoIds,
setSelectedPhotoIds,
// DEBUG
arePhotosMatted,
setArePhotosMatted,