Memoize admin menus

This commit is contained in:
Sam Becker 2025-05-03 11:28:16 -05:00
parent ed9aad1d39
commit aa43084212
2 changed files with 180 additions and 158 deletions

View File

@ -16,7 +16,7 @@ import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import AdminAppInfoIcon from './AdminAppInfoIcon'; import AdminAppInfoIcon from './AdminAppInfoIcon';
import { signOutAction } from '@/auth/actions'; import { signOutAction } from '@/auth/actions';
import { ComponentProps } from 'react'; import { ComponentProps, useMemo } from 'react';
import useIsKeyBeingPressed from '@/utility/useIsKeyBeingPressed'; import useIsKeyBeingPressed from '@/utility/useIsKeyBeingPressed';
import IconPhoto from '@/components/icons/IconPhoto'; import IconPhoto from '@/components/icons/IconPhoto';
import IconUpload from '@/components/icons/IconUpload'; import IconUpload from '@/components/icons/IconUpload';
@ -63,133 +63,149 @@ export default function AdminAppMenu({
const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed; const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed;
const sectionUpload: ComponentProps<typeof MoreMenuItem>[] = []; const sectionUpload: ComponentProps<typeof MoreMenuItem>[] =
const sectionMain: ComponentProps<typeof MoreMenuItem>[] = []; useMemo(() => ([{
const sectionSignOut: ComponentProps<typeof MoreMenuItem>[] = []; label: 'Upload Photos',
icon: <IconUpload
sectionUpload.push({
label: 'Upload Photos',
icon: <IconUpload
size={15}
className="translate-x-[0.5px] translate-y-[0.5px]"
/>,
annotation: isLoadingAdminData &&
<Spinner className="translate-y-[1.5px]" />,
action: startUpload,
});
if (uploadsCount) {
sectionMain.push({
label: 'Uploads',
annotation: `${uploadsCount}`,
icon: <IconFolder
size={16}
className="translate-x-[1px] translate-y-[1px]"
/>,
href: PATH_ADMIN_UPLOADS,
});
}
if (photosCountNeedSync) {
sectionMain.push({
label: 'Updates',
annotation: <>
<span className="mr-3">
{photosCountNeedSync}
</span>
<InsightsIndicatorDot
className="inline-block translate-y-[-1px]"
size="small"
/>
</>,
icon: <IconBroom
size={18}
className="translate-y-[-0.5px]"
/>,
href: PATH_ADMIN_PHOTOS_UPDATES,
});
}
if (photosCountTotal) {
sectionMain.push({
label: 'Manage Photos',
...photosCountTotal && {
annotation: `${photosCountTotal}`,
},
icon: <IconPhoto
size={15} size={15}
className="translate-x-[-0.5px] translate-y-[1px]" className="translate-x-[0.5px] translate-y-[0.5px]"
/>, />,
href: PATH_ADMIN_PHOTOS, annotation: isLoadingAdminData &&
}); <Spinner className="translate-y-[1.5px]" />,
} action: startUpload,
if (tagsCount) { }]), [isLoadingAdminData, startUpload]);
sectionMain.push({
label: 'Manage Tags', const sectionMain: ComponentProps<typeof MoreMenuItem>[] = useMemo(() => {
annotation: `${tagsCount}`, const items: ComponentProps<typeof MoreMenuItem>[] = [];
icon: <IconTag
size={15} if (uploadsCount) {
className="translate-y-[1.5px]" items.push({
/>, label: 'Uploads',
href: PATH_ADMIN_TAGS, annotation: `${uploadsCount}`,
}); icon: <IconFolder
}
if (recipesCount) {
sectionMain.push({
label: 'Manage Recipes',
annotation: `${recipesCount}`,
icon: <IconRecipe
size={17}
className="translate-x-[-0.5px] translate-y-[1px]"
/>,
href: PATH_ADMIN_RECIPES,
});
}
if (photosCountTotal) {
sectionMain.push({
label: isSelecting
? 'Exit Batch Edit'
: 'Batch Edit ...',
icon: isSelecting
? <IoCloseSharp
size={18}
className="translate-x-[-1px] translate-y-[1px]"
/>
: <IoMdCheckboxOutline
size={16} size={16}
className="translate-x-[-0.5px]" className="translate-x-[1px] translate-y-[1px]"
/>, />,
href: PATH_GRID_INFERRED, href: PATH_ADMIN_UPLOADS,
action: () => { });
if (isSelecting) { }
setSelectedPhotoIds?.(undefined); if (photosCountNeedSync) {
} else { items.push({
setSelectedPhotoIds?.([]); label: 'Updates',
} annotation: <>
if (document.activeElement instanceof HTMLElement) { <span className="mr-3">
document.activeElement.blur(); {photosCountNeedSync}
} </span>
}, <InsightsIndicatorDot
shouldPreventDefault: false, className="inline-block translate-y-[-1px]"
size="small"
/>
</>,
icon: <IconBroom
size={18}
className="translate-y-[-0.5px]"
/>,
href: PATH_ADMIN_PHOTOS_UPDATES,
});
}
if (photosCountTotal) {
items.push({
label: 'Manage Photos',
...photosCountTotal && {
annotation: `${photosCountTotal}`,
},
icon: <IconPhoto
size={15}
className="translate-x-[-0.5px] translate-y-[1px]"
/>,
href: PATH_ADMIN_PHOTOS,
});
}
if (tagsCount) {
items.push({
label: 'Manage Tags',
annotation: `${tagsCount}`,
icon: <IconTag
size={15}
className="translate-y-[1.5px]"
/>,
href: PATH_ADMIN_TAGS,
});
}
if (recipesCount) {
items.push({
label: 'Manage Recipes',
annotation: `${recipesCount}`,
icon: <IconRecipe
size={17}
className="translate-x-[-0.5px] translate-y-[1px]"
/>,
href: PATH_ADMIN_RECIPES,
});
}
if (photosCountTotal) {
items.push({
label: isSelecting
? 'Exit Batch Edit'
: 'Batch Edit ...',
icon: isSelecting
? <IoCloseSharp
size={18}
className="translate-x-[-1px] translate-y-[1px]"
/>
: <IoMdCheckboxOutline
size={16}
className="translate-x-[-0.5px]"
/>,
href: PATH_GRID_INFERRED,
action: () => {
if (isSelecting) {
setSelectedPhotoIds?.(undefined);
} else {
setSelectedPhotoIds?.([]);
}
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
},
shouldPreventDefault: false,
});
}
items.push({
label: showAppInsightsLink
? 'App Insights'
: 'App Configuration',
icon: <AdminAppInfoIcon
size="small"
className="translate-x-[-0.5px] translate-y-[0.5px]"
/>,
href: showAppInsightsLink
? PATH_ADMIN_INSIGHTS
: PATH_ADMIN_CONFIGURATION,
}); });
}
sectionMain.push({ return items;
label: showAppInsightsLink }, [
? 'App Insights' isSelecting,
: 'App Configuration', photosCountNeedSync,
icon: <AdminAppInfoIcon photosCountTotal,
size="small" recipesCount,
className="translate-x-[-0.5px] translate-y-[0.5px]" setSelectedPhotoIds,
/>, showAppInsightsLink,
href: showAppInsightsLink tagsCount,
? PATH_ADMIN_INSIGHTS uploadsCount,
: PATH_ADMIN_CONFIGURATION, ]);
});
sectionSignOut.push({ const sectionSignOut: ComponentProps<typeof MoreMenuItem>[] =
label: 'Sign Out', useMemo(() => ([{
icon: <IconSignOut size={15} />, label: 'Sign Out',
action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary), icon: <IconSignOut size={15} />,
}); action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary),
}]), [clearAuthStateAndRedirectIfNecessary]);
const sections = useMemo(() =>
[sectionUpload, sectionMain, sectionSignOut]
, [sectionUpload, sectionMain, sectionSignOut]);
return ( return (
<MoreMenu <MoreMenu
@ -232,11 +248,7 @@ export default function AdminAppMenu({
'[&>*>*]:translate-y-[6px]', '[&>*>*]:translate-y-[6px]',
!animateMenuClose && '[&>*>*]:duration-300', !animateMenuClose && '[&>*>*]:duration-300',
)} )}
sections={[ sections={sections}
sectionUpload,
sectionMain,
sectionSignOut,
]}
ariaLabel="Admin Menu" ariaLabel="Admin Menu"
/> />
); );

View File

@ -46,8 +46,8 @@ export default function AdminPhotoMenu({
const shouldRedirectFav = isPathFavs(path) && isFav; const shouldRedirectFav = isPathFavs(path) && isFav;
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path; const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path;
const sections = useMemo(() => { const sectionMain = useMemo(() => {
const sectionMain: ComponentProps<typeof MoreMenuItem>[] = [{ const items: ComponentProps<typeof MoreMenuItem>[] = [{
label: 'Edit', label: 'Edit',
icon: <IconEdit icon: <IconEdit
size={15} size={15}
@ -57,7 +57,7 @@ export default function AdminPhotoMenu({
...showKeyCommands && { keyCommand: KEY_COMMANDS.edit }, ...showKeyCommands && { keyCommand: KEY_COMMANDS.edit },
}]; }];
if (includeFavorite) { if (includeFavorite) {
sectionMain.push({ items.push({
label: isFav ? 'Unfavorite' : 'Favorite', label: isFav ? 'Unfavorite' : 'Favorite',
icon: <IconFavs icon: <IconFavs
size={14} size={14}
@ -75,7 +75,7 @@ export default function AdminPhotoMenu({
}, },
}); });
} }
sectionMain.push({ items.push({
label: 'Download', label: 'Download',
icon: <MdOutlineFileDownload icon: <MdOutlineFileDownload
size={17} size={17}
@ -85,7 +85,7 @@ export default function AdminPhotoMenu({
hrefDownloadName: downloadFileNameForPhoto(photo), hrefDownloadName: downloadFileNameForPhoto(photo),
...showKeyCommands && { keyCommand: KEY_COMMANDS.download }, ...showKeyCommands && { keyCommand: KEY_COMMANDS.download },
}); });
sectionMain.push({ items.push({
label: 'Sync', label: 'Sync',
labelComplex: <span className="inline-flex items-center gap-2"> labelComplex: <span className="inline-flex items-center gap-2">
<span>Sync</span> <span>Sync</span>
@ -103,32 +103,8 @@ export default function AdminPhotoMenu({
.then(() => revalidatePhoto?.(photo.id)), .then(() => revalidatePhoto?.(photo.id)),
...showKeyCommands && { keyCommand: KEY_COMMANDS.sync }, ...showKeyCommands && { keyCommand: KEY_COMMANDS.sync },
}); });
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = [{
label: 'Delete', return items;
icon: <BiTrash
size={15}
className="translate-x-[-1px]"
/>,
className: 'text-error *:hover:text-error',
color: 'red',
action: () => {
if (confirm(deleteConfirmationTextForPhoto(photo))) {
return deletePhotoAction(
photo.id,
photo.url,
shouldRedirectDelete,
).then(() => {
revalidatePhoto?.(photo.id, true);
registerAdminUpdate?.();
});
}
},
...showKeyCommands && {
keyCommandModifier: KEY_COMMANDS.delete[0],
keyCommand: KEY_COMMANDS.delete[1],
},
}];
return [sectionMain, sectionDelete];
}, [ }, [
photo, photo,
showKeyCommands, showKeyCommands,
@ -136,15 +112,49 @@ export default function AdminPhotoMenu({
isFav, isFav,
shouldRedirectFav, shouldRedirectFav,
revalidatePhoto, revalidatePhoto,
]);
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = useMemo(() => [{
label: 'Delete',
icon: <BiTrash
size={15}
className="translate-x-[-1px]"
/>,
className: 'text-error *:hover:text-error',
color: 'red',
action: () => {
if (confirm(deleteConfirmationTextForPhoto(photo))) {
return deletePhotoAction(
photo.id,
photo.url,
shouldRedirectDelete,
).then(() => {
revalidatePhoto?.(photo.id, true);
registerAdminUpdate?.();
});
}
},
...showKeyCommands && {
keyCommandModifier: KEY_COMMANDS.delete[0],
keyCommand: KEY_COMMANDS.delete[1],
},
}], [
photo,
showKeyCommands,
revalidatePhoto,
shouldRedirectDelete, shouldRedirectDelete,
registerAdminUpdate, registerAdminUpdate,
]); ]);
const sections = useMemo(() =>
[sectionMain, sectionDelete]
, [sectionMain, sectionDelete]);
return ( return (
isUserSignedIn isUserSignedIn
? <MoreMenu {...{ ? <MoreMenu {...{
sections,
...props, ...props,
sections,
}}/> }}/>
: null : null
); );