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
This commit is contained in:
Aonan Li 2025-09-06 16:11:35 -07:00 committed by GitHub
parent 784c641174
commit 91f99508f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 98 additions and 85 deletions

View File

@ -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: <IconUpload
@ -165,8 +169,8 @@ export default function AdminAppMenu({
size={16}
className="translate-x-[-0.5px] translate-y-[0.5px]"
/>,
...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,

View File

@ -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<HTMLDivElement>(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(() => {

View File

@ -644,12 +644,18 @@ export default function CommandKClient({
? appText.admin.batchEdit
: appText.admin.batchExitEdit,
annotation: <IconLock narrow />,
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: <span className="flex items-center gap-3">
{appText.admin.appInsights}

View File

@ -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,7 +45,22 @@ 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 (
<div data-photo-grid>
<AnimateItems
className={clsx(
'grid',
@ -66,7 +80,7 @@ export default function PhotoGrid({
animateOnFirstLoadOnly={animateOnFirstLoadOnly}
staggerOnFirstLoadOnly={staggerOnFirstLoadOnly}
onAnimationComplete={onAnimationComplete}
items={photos.map((photo, index) =>{
items={photos.map((photo, index) => {
const isSelected = selectedPhotoIds?.includes(photo.id) ?? false;
return <div
key={photo.id}
@ -96,7 +110,7 @@ export default function PhotoGrid({
: undefined,
}}
/>
{isUserSignedIn && canSelect && selectedPhotoIds !== undefined &&
{isUserSignedIn && selectedPhotoIds !== undefined &&
<SelectTileOverlay
isSelected={isSelected}
onSelectChange={() => setSelectedPhotoIds?.(isSelected
@ -109,5 +123,6 @@ export default function PhotoGrid({
itemKeys={photos.map(photo => photo.id)
.concat(additionalTile ? ['more'] : [])}
/>
</div>
);
};

View File

@ -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 &&
<PhotoGridInfinite {...{
@ -67,7 +64,6 @@ export default function PhotoGridContainer({
...categories,
canStart: shouldAnimateDynamicItems,
animateOnFirstLoadOnly,
canSelect,
}} />}
</div>
</div>}

View File

@ -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,
}} />}
</InfinitePhotoScroll>
);

View File

@ -60,7 +60,6 @@ export default function PhotoGridPageClient({
}} />
</MaskedScroll>
}
canSelect
/>
);
}