Add key commands to admin photo menu
This commit is contained in:
parent
9bf0665243
commit
5180ea6276
@ -30,11 +30,13 @@ export default function AdminPhotoMenu({
|
||||
photo,
|
||||
revalidatePhoto,
|
||||
includeFavorite = true,
|
||||
showKeyCommands,
|
||||
...props
|
||||
}: Omit<ComponentProps<typeof MoreMenu>, 'sections'> & {
|
||||
photo: Photo
|
||||
revalidatePhoto?: RevalidatePhoto
|
||||
includeFavorite?: boolean
|
||||
showKeyCommands?: boolean
|
||||
}) {
|
||||
const { isUserSignedIn, registerAdminUpdate } = useAppState();
|
||||
|
||||
@ -51,6 +53,7 @@ export default function AdminPhotoMenu({
|
||||
className="translate-x-[0.5px]"
|
||||
/>,
|
||||
href: pathForAdminPhotoEdit(photo.id),
|
||||
...showKeyCommands && { keyCommand: 'E' },
|
||||
}];
|
||||
if (includeFavorite) {
|
||||
sectionMain.push({
|
||||
@ -64,6 +67,7 @@ export default function AdminPhotoMenu({
|
||||
photo.id,
|
||||
shouldRedirectFav,
|
||||
).then(() => revalidatePhoto?.(photo.id)),
|
||||
...showKeyCommands && { keyCommand: isFav ? 'X' : 'P' },
|
||||
});
|
||||
}
|
||||
sectionMain.push({
|
||||
@ -74,6 +78,7 @@ export default function AdminPhotoMenu({
|
||||
/>,
|
||||
href: photo.url,
|
||||
hrefDownloadName: downloadFileNameForPhoto(photo),
|
||||
...showKeyCommands && { keyCommand: 'D' },
|
||||
});
|
||||
sectionMain.push({
|
||||
label: 'Sync',
|
||||
@ -116,6 +121,7 @@ export default function AdminPhotoMenu({
|
||||
return [sectionMain, sectionDelete];
|
||||
}, [
|
||||
photo,
|
||||
showKeyCommands,
|
||||
includeFavorite,
|
||||
isFav,
|
||||
shouldRedirectFav,
|
||||
|
||||
@ -2,10 +2,17 @@
|
||||
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { ReactNode, useEffect, useState, useTransition } from 'react';
|
||||
import {
|
||||
ComponentProps,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import LoaderButton from '../primitives/LoaderButton';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { downloadFileFromBrowser } from '@/utility/url';
|
||||
import KeyCommand from '../primitives/KeyCommand';
|
||||
|
||||
export default function MoreMenuItem({
|
||||
label,
|
||||
@ -19,6 +26,8 @@ export default function MoreMenuItem({
|
||||
action,
|
||||
dismissMenu,
|
||||
shouldPreventDefault = true,
|
||||
keyCommand,
|
||||
keyCommandModifier,
|
||||
}: {
|
||||
label: string
|
||||
labelComplex?: ReactNode
|
||||
@ -31,6 +40,8 @@ export default function MoreMenuItem({
|
||||
action?: () => Promise<void | boolean> | void
|
||||
dismissMenu?: () => void
|
||||
shouldPreventDefault?: boolean
|
||||
keyCommand?: string
|
||||
keyCommandModifier?: ComponentProps<typeof KeyCommand>['modifier']
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
@ -66,8 +77,8 @@ export default function MoreMenuItem({
|
||||
<DropdownMenu.Item
|
||||
disabled={isLoading}
|
||||
className={clsx(
|
||||
'flex items-center h-8.5',
|
||||
'pl-2 pr-3 py-2 rounded-sm',
|
||||
'flex items-center h-8.5 gap-4',
|
||||
'px-2 py-2 rounded-sm',
|
||||
'select-none hover:outline-hidden',
|
||||
getColorClasses(),
|
||||
'whitespace-nowrap',
|
||||
@ -122,7 +133,7 @@ export default function MoreMenuItem({
|
||||
isLoading={isLoading || isPending}
|
||||
hideTextOnMobile={false}
|
||||
styleAs="link-without-hover"
|
||||
className="translate-y-[0.5px] text-sm"
|
||||
className="translate-y-[0.5px] text-sm grow"
|
||||
classNameIcon="translate-y-[-0.5px]!"
|
||||
>
|
||||
<span>
|
||||
@ -133,6 +144,10 @@ export default function MoreMenuItem({
|
||||
{annotation}
|
||||
</span>}
|
||||
</LoaderButton>
|
||||
{keyCommand &&
|
||||
<KeyCommand modifier={keyCommandModifier}>
|
||||
{keyCommand}
|
||||
</KeyCommand>}
|
||||
</DropdownMenu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import useClickInsideOutside from '@/utility/useClickInsideOutside';
|
||||
import KeyCommand from './KeyCommand';
|
||||
export default function TooltipPrimitive({
|
||||
content: contentProp,
|
||||
children,
|
||||
className,
|
||||
classNameTrigger: classNameTriggerProp,
|
||||
sideOffset = 10,
|
||||
@ -18,9 +19,9 @@ export default function TooltipPrimitive({
|
||||
color,
|
||||
keyCommand,
|
||||
keyCommandModifier,
|
||||
children,
|
||||
}: {
|
||||
content?: ReactNode
|
||||
children: ReactNode
|
||||
className?: string
|
||||
classNameTrigger?: string
|
||||
sideOffset?: number
|
||||
@ -30,7 +31,6 @@ export default function TooltipPrimitive({
|
||||
color?: ComponentProps<typeof MenuSurface>['color']
|
||||
keyCommand?: string
|
||||
keyCommandModifier?: ComponentProps<typeof KeyCommand>['modifier']
|
||||
children: ReactNode
|
||||
}) {
|
||||
const refTrigger = useRef<HTMLButtonElement>(null);
|
||||
const refContent = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -252,6 +252,7 @@ export default function PhotoLarge({
|
||||
revalidatePhoto,
|
||||
includeFavorite: includeFavoriteInAdminMenu,
|
||||
ariaLabel: `Admin menu for '${titleForPhoto(photo)}' photo`,
|
||||
showKeyCommands: true,
|
||||
}} />;
|
||||
|
||||
const largePhotoContainerClassName = clsx(
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
Photo,
|
||||
downloadFileNameForPhoto,
|
||||
getNextPhoto,
|
||||
getPreviousPhoto,
|
||||
} from '@/photo';
|
||||
@ -18,8 +19,9 @@ import useNavigateOrRunActionWithToast
|
||||
import { toggleFavoritePhotoAction } from './actions';
|
||||
import { isPhotoFav } from '@/tag';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
|
||||
const LISTENER_KEYUP = 'keyup';
|
||||
import { ALLOW_PUBLIC_DOWNLOADS } from '@/app/config';
|
||||
import { downloadFileFromBrowser } from '@/utility/url';
|
||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||
|
||||
const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 };
|
||||
const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 };
|
||||
@ -34,17 +36,17 @@ export default function PhotoPrevNext({
|
||||
photos?: Photo[]
|
||||
className?: string
|
||||
} & PhotoSetCategory) {
|
||||
const {
|
||||
setNextPhotoAnimation,
|
||||
shouldRespondToKeyboardCommands,
|
||||
isUserSignedIn,
|
||||
} = useAppState();
|
||||
const { setNextPhotoAnimation, isUserSignedIn } = useAppState();
|
||||
|
||||
const photoTitle = photo
|
||||
? photo.title
|
||||
? `'${photo.title}'`
|
||||
: 'photo'
|
||||
: undefined;
|
||||
const downloadUrl = photo?.url;
|
||||
const downloadFileName = photo
|
||||
? downloadFileNameForPhoto(photo)
|
||||
: undefined;
|
||||
|
||||
const toggleFavorite = useCallback(() => {
|
||||
if (photo?.id) {
|
||||
@ -81,54 +83,60 @@ export default function PhotoPrevNext({
|
||||
? pathForPhoto({ photo: nextPhoto, ...categories })
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldRespondToKeyboardCommands) {
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
switch (e.key.toUpperCase()) {
|
||||
case 'ARROWLEFT':
|
||||
case 'J':
|
||||
if (pathPrevious) {
|
||||
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
||||
refPrevious.current?.click();
|
||||
}
|
||||
break;
|
||||
case 'ARROWRIGHT':
|
||||
case 'L':
|
||||
if (pathNext) {
|
||||
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
||||
refNext.current?.click();
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
if (isUserSignedIn) { navigateToPhotoEdit(); }
|
||||
break;
|
||||
case 'P':
|
||||
if (isUserSignedIn && photo && !isPhotoFav(photo)) {
|
||||
favoritePhoto();
|
||||
}
|
||||
break;
|
||||
case 'X':
|
||||
if (isUserSignedIn && photo && isPhotoFav(photo)) {
|
||||
unfavoritePhoto();
|
||||
}
|
||||
break;
|
||||
};
|
||||
};
|
||||
window.addEventListener(LISTENER_KEYUP, onKeyUp);
|
||||
return () => window.removeEventListener(LISTENER_KEYUP, onKeyUp);
|
||||
}
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
switch (e.key.toUpperCase()) {
|
||||
case 'ARROWLEFT':
|
||||
case 'J':
|
||||
if (pathPrevious) {
|
||||
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
||||
refPrevious.current?.click();
|
||||
}
|
||||
break;
|
||||
case 'ARROWRIGHT':
|
||||
case 'L':
|
||||
if (pathNext) {
|
||||
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
||||
refNext.current?.click();
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
if (isUserSignedIn) {
|
||||
navigateToPhotoEdit();
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
if (isUserSignedIn && photo && !isPhotoFav(photo)) {
|
||||
favoritePhoto();
|
||||
}
|
||||
break;
|
||||
case 'X':
|
||||
if (isUserSignedIn && photo && isPhotoFav(photo)) {
|
||||
unfavoritePhoto();
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
if (
|
||||
(isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) &&
|
||||
downloadUrl &&
|
||||
downloadFileName
|
||||
) {
|
||||
downloadFileFromBrowser(downloadUrl, downloadFileName);
|
||||
}
|
||||
break;
|
||||
};
|
||||
}, [
|
||||
shouldRespondToKeyboardCommands,
|
||||
setNextPhotoAnimation,
|
||||
pathPrevious,
|
||||
pathNext,
|
||||
isUserSignedIn,
|
||||
photoTitle,
|
||||
navigateToPhotoEdit,
|
||||
photo,
|
||||
favoritePhoto,
|
||||
unfavoritePhoto,
|
||||
downloadUrl,
|
||||
downloadFileName,
|
||||
]);
|
||||
useKeydownHandler({ onKeyDown });
|
||||
|
||||
return (
|
||||
<div className={clsx(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user