diff --git a/.vscode/settings.json b/.vscode/settings.json index 015e0894..d20808fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,6 +56,7 @@ "trpc", "Turbopack", "Unfavoriting", + "Unhiding", "unnest", "upstash", "UsKSGcbt", diff --git a/src/admin/AdminPhotoMenu.tsx b/src/admin/AdminPhotoMenu.tsx index 92c4882a..0303dc51 100644 --- a/src/admin/AdminPhotoMenu.tsx +++ b/src/admin/AdminPhotoMenu.tsx @@ -1,18 +1,24 @@ 'use client'; import { ComponentProps, useMemo } from 'react'; -import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths'; +import { + getPathComponents, + PATH_ROOT, + pathForAdminPhotoEdit, + pathForTag, +} from '@/app/paths'; import { deletePhotoAction, syncPhotoAction, toggleFavoritePhotoAction, + toggleHidePhotoAction, } from '@/photo/actions'; import { Photo, deleteConfirmationTextForPhoto, downloadFileNameForPhoto, } from '@/photo'; -import { isPathFavs, isPhotoFav } from '@/tag'; +import { isPathFavs, isPhotoFav, TAG_HIDDEN } from '@/tag'; import { usePathname } from 'next/navigation'; import { BiTrash } from 'react-icons/bi'; import MoreMenu from '@/components/more/MoreMenu'; @@ -27,6 +33,7 @@ import IconEdit from '@/components/icons/IconEdit'; import { photoNeedsToBeSynced } from '@/photo/sync'; import { KEY_COMMANDS } from '@/photo/key-commands'; import { useAppText } from '@/i18n/state/client'; +import IconHidden from '@/components/icons/IconHidden'; export default function AdminPhotoMenu({ photo, @@ -44,10 +51,18 @@ export default function AdminPhotoMenu({ const appText = useAppText(); - const isFav = isPhotoFav(photo); const path = usePathname(); + const pathComponents = getPathComponents(path); + const isOnPhotoDetail = pathComponents.photoId === photo.id; + const isFav = isPhotoFav(photo); 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 items: ComponentProps[] = [{ @@ -78,6 +93,22 @@ export default function AdminPhotoMenu({ }, }); } + items.push({ + label: photo.hidden ? appText.admin.unhide : appText.admin.hide, + icon: , + action: () => toggleHidePhotoAction( + photo.id, + redirectPathOnHideToggle, + ) + .then(() => revalidatePhoto?.(photo.id)), + ...showKeyCommands && { + keyCommand: KEY_COMMANDS.toggleHide, + }, + }); items.push({ label: appText.admin.download, icon: { + if (photo?.id) { return toggleHidePhotoAction(photo.id); } + }, [photo?.id]); + const navigateToPhotoEdit = useNavigateOrRunActionWithToast({ pathOrAction: photo ? pathForAdminPhotoEdit(photo) : undefined, toastMessage: `Editing ${photoTitle} ...`, @@ -81,6 +86,16 @@ export default function PhotoPrevNextActions({ toastMessage: `Unfavoriting ${photoTitle} ...`, }); + const hidePhoto = useNavigateOrRunActionWithToast({ + pathOrAction: toggleHidden, + toastMessage: `Hiding ${photoTitle} ...`, + }); + + const unhidePhoto = useNavigateOrRunActionWithToast({ + pathOrAction: toggleHidden, + toastMessage: `Unhiding ${photoTitle} ...`, + }); + const syncPhoto = useNavigateOrRunActionWithToast({ pathOrAction: useCallback(() => { if (photo?.id) { return syncPhotoAction(photo.id); } @@ -153,6 +168,15 @@ export default function PhotoPrevNextActions({ unfavoritePhoto(); } break; + case KEY_COMMANDS.toggleHide: + if (isUserSignedIn && photo) { + if (photo.hidden) { + unhidePhoto(); + } else { + hidePhoto(); + } + } + break; case KEY_COMMANDS.download: if ( (isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) && @@ -182,6 +206,8 @@ export default function PhotoPrevNextActions({ photo, favoritePhoto, unfavoritePhoto, + hidePhoto, + unhidePhoto, downloadUrl, downloadFileName, syncPhoto, diff --git a/src/photo/actions.ts b/src/photo/actions.ts index 298b569a..7fc9e7d2 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -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[]) => runAuthenticatedAdminServerAction(async () => { for (const photoId of photoIds) { diff --git a/src/photo/key-commands.ts b/src/photo/key-commands.ts index a0c20076..32b25724 100644 --- a/src/photo/key-commands.ts +++ b/src/photo/key-commands.ts @@ -7,6 +7,7 @@ export const KEY_COMMANDS = { edit: 'E', favorite: 'P', unfavorite: 'X', + toggleHide: 'H', download: 'D', sync: 'S', search: ['⌘', 'K'],