Add hide/unhide to admin photo menu

This commit is contained in:
Sam Becker 2025-05-26 23:59:51 -05:00
parent 489f48523b
commit b6d5f2ebb2
6 changed files with 80 additions and 4 deletions

View File

@ -56,6 +56,7 @@
"trpc", "trpc",
"Turbopack", "Turbopack",
"Unfavoriting", "Unfavoriting",
"Unhiding",
"unnest", "unnest",
"upstash", "upstash",
"UsKSGcbt", "UsKSGcbt",

View File

@ -1,18 +1,24 @@
'use client'; 'use client';
import { ComponentProps, useMemo } from 'react'; import { ComponentProps, useMemo } from 'react';
import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths'; import {
getPathComponents,
PATH_ROOT,
pathForAdminPhotoEdit,
pathForTag,
} from '@/app/paths';
import { import {
deletePhotoAction, deletePhotoAction,
syncPhotoAction, syncPhotoAction,
toggleFavoritePhotoAction, toggleFavoritePhotoAction,
toggleHidePhotoAction,
} from '@/photo/actions'; } from '@/photo/actions';
import { import {
Photo, Photo,
deleteConfirmationTextForPhoto, deleteConfirmationTextForPhoto,
downloadFileNameForPhoto, downloadFileNameForPhoto,
} from '@/photo'; } from '@/photo';
import { isPathFavs, isPhotoFav } from '@/tag'; import { isPathFavs, isPhotoFav, TAG_HIDDEN } from '@/tag';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { BiTrash } from 'react-icons/bi'; import { BiTrash } from 'react-icons/bi';
import MoreMenu from '@/components/more/MoreMenu'; import MoreMenu from '@/components/more/MoreMenu';
@ -27,6 +33,7 @@ import IconEdit from '@/components/icons/IconEdit';
import { photoNeedsToBeSynced } from '@/photo/sync'; import { photoNeedsToBeSynced } from '@/photo/sync';
import { KEY_COMMANDS } from '@/photo/key-commands'; import { KEY_COMMANDS } from '@/photo/key-commands';
import { useAppText } from '@/i18n/state/client'; import { useAppText } from '@/i18n/state/client';
import IconHidden from '@/components/icons/IconHidden';
export default function AdminPhotoMenu({ export default function AdminPhotoMenu({
photo, photo,
@ -44,10 +51,18 @@ export default function AdminPhotoMenu({
const appText = useAppText(); const appText = useAppText();
const isFav = isPhotoFav(photo);
const path = usePathname(); const path = usePathname();
const pathComponents = getPathComponents(path);
const isOnPhotoDetail = pathComponents.photoId === photo.id;
const isFav = isPhotoFav(photo);
const shouldRedirectFav = isPathFavs(path) && isFav; const shouldRedirectFav = isPathFavs(path) && isFav;
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path; const shouldRedirectDelete = isOnPhotoDetail;
const redirectPathOnHideToggle = isOnPhotoDetail
? photo.hidden
? pathForTag(TAG_HIDDEN)
: PATH_ROOT
: undefined;
const sectionMain = useMemo(() => { const sectionMain = useMemo(() => {
const items: ComponentProps<typeof MoreMenuItem>[] = [{ const items: ComponentProps<typeof MoreMenuItem>[] = [{
@ -78,6 +93,22 @@ export default function AdminPhotoMenu({
}, },
}); });
} }
items.push({
label: photo.hidden ? appText.admin.unhide : appText.admin.hide,
icon: <IconHidden
size={16}
className="translate-x-[-1px] translate-y-[1px]"
visible={photo.hidden}
/>,
action: () => toggleHidePhotoAction(
photo.id,
redirectPathOnHideToggle,
)
.then(() => revalidatePhoto?.(photo.id)),
...showKeyCommands && {
keyCommand: KEY_COMMANDS.toggleHide,
},
});
items.push({ items.push({
label: appText.admin.download, label: appText.admin.download,
icon: <MdOutlineFileDownload icon: <MdOutlineFileDownload
@ -115,6 +146,7 @@ export default function AdminPhotoMenu({
includeFavorite, includeFavorite,
isFav, isFav,
shouldRedirectFav, shouldRedirectFav,
redirectPathOnHideToggle,
revalidatePhoto, revalidatePhoto,
]); ]);

View File

@ -87,6 +87,8 @@ const TEXT = {
edit: 'Edit', edit: 'Edit',
favorite: 'Favorite', favorite: 'Favorite',
unfavorite: 'Unfavorite', unfavorite: 'Unfavorite',
hide: 'Hide',
unhide: 'Unhide',
download: 'Download', download: 'Download',
sync: 'Sync', sync: 'Sync',
delete: 'Delete', delete: 'Delete',

View File

@ -20,6 +20,7 @@ import {
deletePhotoAction, deletePhotoAction,
syncPhotoAction, syncPhotoAction,
toggleFavoritePhotoAction, toggleFavoritePhotoAction,
toggleHidePhotoAction,
} from './actions'; } from './actions';
import { isPhotoFav } from '@/tag'; import { isPhotoFav } from '@/tag';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';
@ -66,6 +67,10 @@ export default function PhotoPrevNextActions({
if (photo?.id) { return toggleFavoritePhotoAction(photo.id); } if (photo?.id) { return toggleFavoritePhotoAction(photo.id); }
}, [photo?.id]); }, [photo?.id]);
const toggleHidden = useCallback(() => {
if (photo?.id) { return toggleHidePhotoAction(photo.id); }
}, [photo?.id]);
const navigateToPhotoEdit = useNavigateOrRunActionWithToast({ const navigateToPhotoEdit = useNavigateOrRunActionWithToast({
pathOrAction: photo ? pathForAdminPhotoEdit(photo) : undefined, pathOrAction: photo ? pathForAdminPhotoEdit(photo) : undefined,
toastMessage: `Editing ${photoTitle} ...`, toastMessage: `Editing ${photoTitle} ...`,
@ -81,6 +86,16 @@ export default function PhotoPrevNextActions({
toastMessage: `Unfavoriting ${photoTitle} ...`, toastMessage: `Unfavoriting ${photoTitle} ...`,
}); });
const hidePhoto = useNavigateOrRunActionWithToast({
pathOrAction: toggleHidden,
toastMessage: `Hiding ${photoTitle} ...`,
});
const unhidePhoto = useNavigateOrRunActionWithToast({
pathOrAction: toggleHidden,
toastMessage: `Unhiding ${photoTitle} ...`,
});
const syncPhoto = useNavigateOrRunActionWithToast({ const syncPhoto = useNavigateOrRunActionWithToast({
pathOrAction: useCallback(() => { pathOrAction: useCallback(() => {
if (photo?.id) { return syncPhotoAction(photo.id); } if (photo?.id) { return syncPhotoAction(photo.id); }
@ -153,6 +168,15 @@ export default function PhotoPrevNextActions({
unfavoritePhoto(); unfavoritePhoto();
} }
break; break;
case KEY_COMMANDS.toggleHide:
if (isUserSignedIn && photo) {
if (photo.hidden) {
unhidePhoto();
} else {
hidePhoto();
}
}
break;
case KEY_COMMANDS.download: case KEY_COMMANDS.download:
if ( if (
(isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) && (isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) &&
@ -182,6 +206,8 @@ export default function PhotoPrevNextActions({
photo, photo,
favoritePhoto, favoritePhoto,
unfavoritePhoto, unfavoritePhoto,
hidePhoto,
unhidePhoto,
downloadUrl, downloadUrl,
downloadFileName, downloadFileName,
syncPhoto, syncPhoto,

View File

@ -265,6 +265,20 @@ export const toggleFavoritePhotoAction = async (
} }
}); });
export const toggleHidePhotoAction = async (
photoId: string,
redirectPath?: string,
) =>
runAuthenticatedAdminServerAction(async () => {
const photo = await getPhoto(photoId, true);
if (photo) {
photo.hidden = !photo.hidden;
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
revalidateAllKeysAndPaths();
}
if (redirectPath) { redirect(redirectPath); }
});
export const deletePhotosAction = async (photoIds: string[]) => export const deletePhotosAction = async (photoIds: string[]) =>
runAuthenticatedAdminServerAction(async () => { runAuthenticatedAdminServerAction(async () => {
for (const photoId of photoIds) { for (const photoId of photoIds) {

View File

@ -7,6 +7,7 @@ export const KEY_COMMANDS = {
edit: 'E', edit: 'E',
favorite: 'P', favorite: 'P',
unfavorite: 'X', unfavorite: 'X',
toggleHide: 'H',
download: 'D', download: 'D',
sync: 'S', sync: 'S',
search: ['⌘', 'K'], search: ['⌘', 'K'],