From 91f99508f7d535c67c7806bf4f48eaf83a695c43 Mon Sep 17 00:00:00 2001 From: Aonan Li Date: Sat, 6 Sep 2025 16:11:35 -0700 Subject: [PATCH] Start batch editing from the current page (#303) * Batch edit from the current page * Redirect to grid when batch editing a page without grid * Always clear selected ids on path change * Only clear ids when selecting * Use data-photo-grid to find grid * Update batch edit from command K * Not mount batch edit panel when not needed * Not clear selecting state when go to /grid * Use search param to force batch editing --- src/admin/AdminAppMenu.tsx | 15 +-- src/admin/AdminBatchEditPanelClient.tsx | 6 +- src/cmdk/CommandKClient.tsx | 18 ++-- src/photo/PhotoGrid.tsx | 137 +++++++++++++----------- src/photo/PhotoGridContainer.tsx | 4 - src/photo/PhotoGridInfinite.tsx | 2 - src/photo/PhotoGridPageClient.tsx | 1 - 7 files changed, 98 insertions(+), 85 deletions(-) diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index 799cef2b..27766084 100644 --- a/src/admin/AdminAppMenu.tsx +++ b/src/admin/AdminAppMenu.tsx @@ -57,20 +57,24 @@ export default function AdminAppMenu({ clearAuthStateAndRedirectIfNecessary, } = useAppState(); + const isSelecting = selectedPhotoIds !== undefined; + useEffect(() => { - if (pathname !== PATH_GRID_INFERRED) { + if (isSelecting) { setSelectedPhotoIds?.(undefined); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname, setSelectedPhotoIds]); const appText = useAppText(); - const isSelecting = selectedPhotoIds !== undefined; - const isAltPressed = useIsKeyBeingPressed('alt'); const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed; + const currentPageHasGrid = () => + document.querySelector('[data-photo-grid]') !== null; + const sectionUpload: MoreMenuSection = useMemo(() => ({ items: [{ label: appText.admin.uploadPhotos, icon: , - ...pathname !== PATH_GRID_INFERRED && { - href: PATH_GRID_INFERRED, + ...!currentPageHasGrid() && { + href: `${PATH_GRID_INFERRED}?batch=true`, }, action: () => { if (isSelecting) { @@ -192,7 +196,6 @@ export default function AdminAppMenu({ return { items }; }, [ - pathname, appText, isSelecting, photosCountNeedSync, diff --git a/src/admin/AdminBatchEditPanelClient.tsx b/src/admin/AdminBatchEditPanelClient.tsx index c8894049..88928b08 100644 --- a/src/admin/AdminBatchEditPanelClient.tsx +++ b/src/admin/AdminBatchEditPanelClient.tsx @@ -8,8 +8,6 @@ import { clsx } from 'clsx/lite'; import { IoCloseSharp } from 'react-icons/io5'; import { useEffect, useRef, useState } from 'react'; import { TAG_FAVS, Tags } from '@/tag'; -import { usePathname } from 'next/navigation'; -import { PATH_GRID_INFERRED } from '@/app/path'; import PhotoTagFieldset from './PhotoTagFieldset'; import { tagMultiplePhotosAction } from '@/photo/actions'; import { toastSuccess } from '@/toast'; @@ -28,8 +26,6 @@ export default function AdminBatchEditPanelClient({ }) { const refNote = useRef(null); - const pathname = usePathname(); - const { isUserSignedIn, selectedPhotoIds, @@ -156,7 +152,7 @@ export default function AdminBatchEditPanelClient({ const shouldShowPanel = isUserSignedIn && - pathname === PATH_GRID_INFERRED && + document.querySelector('[data-photo-grid]') !== null && selectedPhotoIds !== undefined; useEffect(() => { diff --git a/src/cmdk/CommandKClient.tsx b/src/cmdk/CommandKClient.tsx index d79fe6ca..bce31e16 100644 --- a/src/cmdk/CommandKClient.tsx +++ b/src/cmdk/CommandKClient.tsx @@ -644,12 +644,18 @@ export default function CommandKClient({ ? appText.admin.batchEdit : appText.admin.batchExitEdit, annotation: , - path: selectedPhotoIds === undefined - ? PATH_GRID_INFERRED - : undefined, - action: selectedPhotoIds === undefined - ? () => setSelectedPhotoIds?.([]) - : () => setSelectedPhotoIds?.(undefined), + action: () => { + if (selectedPhotoIds === undefined) { + const hasGrid = document.querySelector('[data-photo-grid]') !== null; + if (!hasGrid) { + router.push(`${PATH_GRID_INFERRED}?batch=true`); + return; + } + setSelectedPhotoIds?.([]); + } else { + setSelectedPhotoIds?.(undefined); + } + }, }, { label: {appText.admin.appInsights} diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx index 03b7acda..820f2a14 100644 --- a/src/photo/PhotoGrid.tsx +++ b/src/photo/PhotoGrid.tsx @@ -8,7 +8,8 @@ import AnimateItems from '@/components/AnimateItems'; import { GRID_ASPECT_RATIO } from '@/app/config'; import { useAppState } from '@/app/AppState'; import SelectTileOverlay from '@/components/SelectTileOverlay'; -import { ReactNode } from 'react'; +import { ReactNode, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; import { GRID_GAP_CLASSNAME } from '@/components'; export default function PhotoGrid({ @@ -21,7 +22,6 @@ export default function PhotoGrid({ staggerOnFirstLoadOnly = true, additionalTile, small, - canSelect, onLastPhotoVisible, onAnimationComplete, ...categories @@ -35,7 +35,6 @@ export default function PhotoGrid({ staggerOnFirstLoadOnly?: boolean additionalTile?: ReactNode small?: boolean - canSelect?: boolean onLastPhotoVisible?: () => void onAnimationComplete?: () => void } & PhotoSetCategory) { @@ -46,68 +45,84 @@ export default function PhotoGrid({ isGridHighDensity, } = useAppState(); + const searchParams = useSearchParams(); + const router = useRouter(); + + // Check for batch editing parameter on mount + useEffect(() => { + if (searchParams.get('batch') === 'true') { + setSelectedPhotoIds?.([]); + // Clean up the URL parameter + const url = new URL(window.location.href); + url.searchParams.delete('batch'); + router.replace(url.pathname + url.search); + } + }, [searchParams, setSelectedPhotoIds, router]); + return ( - { - const isSelected = selectedPhotoIds?.includes(photo.id) ?? false; - return
- + { + const isSelected = selectedPhotoIds?.includes(photo.id) ?? false; + return
- {isUserSignedIn && canSelect && selectedPhotoIds !== undefined && - setSelectedPhotoIds?.(isSelected - ? (selectedPhotoIds ?? []).filter(id => id !== photo.id) - : (selectedPhotoIds ?? []).concat(photo.id), + > + } -
; - }).concat(additionalTile ? <>{additionalTile} : [])} - itemKeys={photos.map(photo => photo.id) - .concat(additionalTile ? ['more'] : [])} - /> + {...{ + photo, + ...categories, + selected: photo.id === selectedPhoto?.id, + priority: prioritizeInitialPhotos ? index < 6 : undefined, + onVisible: index === photos.length - 1 + ? onLastPhotoVisible + : undefined, + }} + /> + {isUserSignedIn && selectedPhotoIds !== undefined && + setSelectedPhotoIds?.(isSelected + ? (selectedPhotoIds ?? []).filter(id => id !== photo.id) + : (selectedPhotoIds ?? []).concat(photo.id), + )} + />} +
; + }).concat(additionalTile ? <>{additionalTile} : [])} + itemKeys={photos.map(photo => photo.id) + .concat(additionalTile ? ['more'] : [])} + /> + ); }; diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx index 15c69fea..831d613d 100644 --- a/src/photo/PhotoGridContainer.tsx +++ b/src/photo/PhotoGridContainer.tsx @@ -19,7 +19,6 @@ export default function PhotoGridContainer({ animateOnFirstLoadOnly, header, sidebar, - canSelect, ...categories }: { cacheKey: string @@ -34,7 +33,6 @@ export default function PhotoGridContainer({ shouldAnimateDynamicItems, setShouldAnimateDynamicItems, ] = useState(false); - const onAnimationComplete = useCallback(() => setShouldAnimateDynamicItems(true), []); @@ -55,7 +53,6 @@ export default function PhotoGridContainer({ ...categories, animateOnFirstLoadOnly, onAnimationComplete, - canSelect, }} /> {count > photos.length && } } diff --git a/src/photo/PhotoGridInfinite.tsx b/src/photo/PhotoGridInfinite.tsx index cbc083dc..58da5065 100644 --- a/src/photo/PhotoGridInfinite.tsx +++ b/src/photo/PhotoGridInfinite.tsx @@ -14,7 +14,6 @@ export default function PhotoGridInfinite({ excludeFromFeeds, canStart, animateOnFirstLoadOnly, - canSelect, ...categories }: { cacheKey: string @@ -40,7 +39,6 @@ export default function PhotoGridInfinite({ canStart, onLastPhotoVisible, animateOnFirstLoadOnly, - canSelect, }} />} ); diff --git a/src/photo/PhotoGridPageClient.tsx b/src/photo/PhotoGridPageClient.tsx index e3f0c9ac..40062ff6 100644 --- a/src/photo/PhotoGridPageClient.tsx +++ b/src/photo/PhotoGridPageClient.tsx @@ -60,7 +60,6 @@ export default function PhotoGridPageClient({ }} /> } - canSelect /> ); }