Finalize key commands
This commit is contained in:
parent
5180ea6276
commit
8d91804eb9
@ -25,6 +25,7 @@ import InsightsIndicatorDot from './insights/InsightsIndicatorDot';
|
|||||||
import IconFavs from '@/components/icons/IconFavs';
|
import IconFavs from '@/components/icons/IconFavs';
|
||||||
import IconEdit from '@/components/icons/IconEdit';
|
import IconEdit from '@/components/icons/IconEdit';
|
||||||
import { photoNeedsToBeSynced } from '@/photo/sync';
|
import { photoNeedsToBeSynced } from '@/photo/sync';
|
||||||
|
import { KEY_COMMANDS } from '@/photo/key-commands';
|
||||||
|
|
||||||
export default function AdminPhotoMenu({
|
export default function AdminPhotoMenu({
|
||||||
photo,
|
photo,
|
||||||
@ -53,7 +54,7 @@ export default function AdminPhotoMenu({
|
|||||||
className="translate-x-[0.5px]"
|
className="translate-x-[0.5px]"
|
||||||
/>,
|
/>,
|
||||||
href: pathForAdminPhotoEdit(photo.id),
|
href: pathForAdminPhotoEdit(photo.id),
|
||||||
...showKeyCommands && { keyCommand: 'E' },
|
...showKeyCommands && { keyCommand: KEY_COMMANDS.edit },
|
||||||
}];
|
}];
|
||||||
if (includeFavorite) {
|
if (includeFavorite) {
|
||||||
sectionMain.push({
|
sectionMain.push({
|
||||||
@ -67,7 +68,11 @@ export default function AdminPhotoMenu({
|
|||||||
photo.id,
|
photo.id,
|
||||||
shouldRedirectFav,
|
shouldRedirectFav,
|
||||||
).then(() => revalidatePhoto?.(photo.id)),
|
).then(() => revalidatePhoto?.(photo.id)),
|
||||||
...showKeyCommands && { keyCommand: isFav ? 'X' : 'P' },
|
...showKeyCommands && {
|
||||||
|
keyCommand: isFav
|
||||||
|
? KEY_COMMANDS.unfavorite
|
||||||
|
: KEY_COMMANDS.favorite,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sectionMain.push({
|
sectionMain.push({
|
||||||
@ -78,7 +83,7 @@ export default function AdminPhotoMenu({
|
|||||||
/>,
|
/>,
|
||||||
href: photo.url,
|
href: photo.url,
|
||||||
hrefDownloadName: downloadFileNameForPhoto(photo),
|
hrefDownloadName: downloadFileNameForPhoto(photo),
|
||||||
...showKeyCommands && { keyCommand: 'D' },
|
...showKeyCommands && { keyCommand: KEY_COMMANDS.download },
|
||||||
});
|
});
|
||||||
sectionMain.push({
|
sectionMain.push({
|
||||||
label: 'Sync',
|
label: 'Sync',
|
||||||
@ -96,6 +101,7 @@ export default function AdminPhotoMenu({
|
|||||||
/>,
|
/>,
|
||||||
action: () => syncPhotoAction(photo.id)
|
action: () => syncPhotoAction(photo.id)
|
||||||
.then(() => revalidatePhoto?.(photo.id)),
|
.then(() => revalidatePhoto?.(photo.id)),
|
||||||
|
...showKeyCommands && { keyCommand: KEY_COMMANDS.sync },
|
||||||
});
|
});
|
||||||
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = [{
|
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = [{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
@ -117,6 +123,10 @@ export default function AdminPhotoMenu({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
...showKeyCommands && {
|
||||||
|
keyCommandModifier: KEY_COMMANDS.delete[0],
|
||||||
|
keyCommand: KEY_COMMANDS.delete[1],
|
||||||
|
},
|
||||||
}];
|
}];
|
||||||
return [sectionMain, sectionDelete];
|
return [sectionMain, sectionDelete];
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import clsx from 'clsx/lite';
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { KEY_COMMANDS } from '@/photo/key-commands';
|
||||||
|
|
||||||
export type SwitcherSelection = 'feed' | 'grid' | 'admin';
|
export type SwitcherSelection = 'feed' | 'grid' | 'admin';
|
||||||
|
|
||||||
@ -38,13 +39,13 @@ export default function AppViewSwitcher({
|
|||||||
|
|
||||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
switch (e.key.toLocaleUpperCase()) {
|
switch (e.key.toLocaleUpperCase()) {
|
||||||
case 'F':
|
case KEY_COMMANDS.feed:
|
||||||
if (pathname !== PATH_FEED_INFERRED) { refHrefFeed.current?.click(); }
|
if (pathname !== PATH_FEED_INFERRED) { refHrefFeed.current?.click(); }
|
||||||
break;
|
break;
|
||||||
case 'G':
|
case KEY_COMMANDS.grid:
|
||||||
if (pathname !== PATH_GRID_INFERRED) { refHrefGrid.current?.click(); }
|
if (pathname !== PATH_GRID_INFERRED) { refHrefGrid.current?.click(); }
|
||||||
break;
|
break;
|
||||||
case 'A':
|
case KEY_COMMANDS.admin:
|
||||||
if (isUserSignedIn) { setIsAdminMenuOpen(true); }
|
if (isUserSignedIn) { setIsAdminMenuOpen(true); }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@ export default function AppViewSwitcher({
|
|||||||
active={currentSelection === 'feed'}
|
active={currentSelection === 'feed'}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: 'Feed',
|
content: 'Feed',
|
||||||
keyCommand: 'F',
|
keyCommand: KEY_COMMANDS.feed,
|
||||||
}}
|
}}
|
||||||
noPadding
|
noPadding
|
||||||
/>;
|
/>;
|
||||||
@ -74,7 +75,7 @@ export default function AppViewSwitcher({
|
|||||||
active={currentSelection === 'grid'}
|
active={currentSelection === 'grid'}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: 'Grid',
|
content: 'Grid',
|
||||||
keyCommand: 'G',
|
keyCommand: KEY_COMMANDS.grid,
|
||||||
}}
|
}}
|
||||||
noPadding
|
noPadding
|
||||||
/>;
|
/>;
|
||||||
@ -105,7 +106,7 @@ export default function AppViewSwitcher({
|
|||||||
/>}
|
/>}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: !isAdminMenuOpen ? 'Admin Menu' : undefined,
|
content: !isAdminMenuOpen ? 'Admin Menu' : undefined,
|
||||||
keyCommand: !isAdminMenuOpen ? 'A' : undefined,
|
keyCommand: !isAdminMenuOpen ? KEY_COMMANDS.admin : undefined,
|
||||||
}}
|
}}
|
||||||
noPadding
|
noPadding
|
||||||
/>}
|
/>}
|
||||||
@ -116,8 +117,8 @@ export default function AppViewSwitcher({
|
|||||||
onClick={() => setIsCommandKOpen?.(true)}
|
onClick={() => setIsCommandKOpen?.(true)}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: 'Search',
|
content: 'Search',
|
||||||
keyCommand: 'K',
|
keyCommandModifier: KEY_COMMANDS.search[0],
|
||||||
keyCommandModifier: '⌘',
|
keyCommand: KEY_COMMANDS.search[1],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Switcher>
|
</Switcher>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import clsx from 'clsx/lite';
|
import clsx from 'clsx/lite';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { HiMiniBackspace } from 'react-icons/hi2';
|
||||||
|
import { PiCommandBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export default function KeyCommand({
|
export default function KeyCommand({
|
||||||
children,
|
children,
|
||||||
@ -10,9 +12,12 @@ export default function KeyCommand({
|
|||||||
modifier?: '⌘' | '⌥' | '⇧' | '⌃' | '⏎'
|
modifier?: '⌘' | '⌥' | '⇧' | '⌃' | '⏎'
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const keys = useMemo(() =>
|
const keys = useMemo(() => {
|
||||||
modifier ? [modifier, ...children] : [...children],
|
const childrenFormatted = children === 'BACKSPACE'
|
||||||
[modifier, children]);
|
? '⌫'
|
||||||
|
: children;
|
||||||
|
return modifier ? [modifier, ...childrenFormatted] : [...childrenFormatted];
|
||||||
|
}, [modifier, children]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={clsx('inline-flex items-center gap-0.5', className)}>
|
<span className={clsx('inline-flex items-center gap-0.5', className)}>
|
||||||
@ -20,12 +25,17 @@ export default function KeyCommand({
|
|||||||
<span
|
<span
|
||||||
key={key}
|
key={key}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-1 rounded-sm shadow-xs',
|
'inline-flex items-center justify-center',
|
||||||
'text-gray-600 bg-gray-200/90',
|
'px-1 h-4 rounded-sm text-xs font-medium',
|
||||||
'dark:text-gray-300 dark:bg-gray-600/55',
|
'text-gray-500/90 bg-gray-200/70',
|
||||||
|
'dark:text-gray-300/90 dark:bg-gray-600/50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{key}
|
{key === '⌘'
|
||||||
|
? <PiCommandBold />
|
||||||
|
: key === '⌫'
|
||||||
|
? <HiMiniBackspace className="text-[13px]" />
|
||||||
|
: key}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -139,6 +139,7 @@ export default function PhotoDetailPage({
|
|||||||
shouldShareRecipe={recipe !== undefined}
|
shouldShareRecipe={recipe !== undefined}
|
||||||
shouldShareFocalLength={focal !== undefined}
|
shouldShareFocalLength={focal !== undefined}
|
||||||
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
|
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
|
||||||
|
showAdminKeyCommands
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import ShareButton from '@/share/ShareButton';
|
|||||||
import AnimateItems from '@/components/AnimateItems';
|
import AnimateItems from '@/components/AnimateItems';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
|
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
|
||||||
import PhotoPrevNext from './PhotoPrevNext';
|
import PhotoPrevNextActions from './PhotoPrevNextActions';
|
||||||
import PhotoLink from './PhotoLink';
|
import PhotoLink from './PhotoLink';
|
||||||
import ResponsiveText from '@/components/primitives/ResponsiveText';
|
import ResponsiveText from '@/components/primitives/ResponsiveText';
|
||||||
import { useAppState } from '@/state/AppState';
|
import { useAppState } from '@/state/AppState';
|
||||||
@ -59,7 +59,7 @@ export default function PhotoHeader({
|
|||||||
: 'photo-detail';
|
: 'photo-detail';
|
||||||
|
|
||||||
const renderPrevNext =
|
const renderPrevNext =
|
||||||
<PhotoPrevNext {...{
|
<PhotoPrevNextActions {...{
|
||||||
photo: selectedPhoto,
|
photo: selectedPhoto,
|
||||||
photos,
|
photos,
|
||||||
...categories,
|
...categories,
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export default function PhotoLarge({
|
|||||||
shouldShareFocalLength,
|
shouldShareFocalLength,
|
||||||
includeFavoriteInAdminMenu,
|
includeFavoriteInAdminMenu,
|
||||||
onVisible,
|
onVisible,
|
||||||
|
showAdminKeyCommands,
|
||||||
}: {
|
}: {
|
||||||
photo: Photo
|
photo: Photo
|
||||||
className?: string
|
className?: string
|
||||||
@ -101,6 +102,7 @@ export default function PhotoLarge({
|
|||||||
shouldShareFocalLength?: boolean
|
shouldShareFocalLength?: boolean
|
||||||
includeFavoriteInAdminMenu?: boolean
|
includeFavoriteInAdminMenu?: boolean
|
||||||
onVisible?: () => void
|
onVisible?: () => void
|
||||||
|
showAdminKeyCommands?: boolean
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const refZoomControls = useRef<ZoomControlsRef>(null);
|
const refZoomControls = useRef<ZoomControlsRef>(null);
|
||||||
@ -252,7 +254,7 @@ export default function PhotoLarge({
|
|||||||
revalidatePhoto,
|
revalidatePhoto,
|
||||||
includeFavorite: includeFavoriteInAdminMenu,
|
includeFavorite: includeFavoriteInAdminMenu,
|
||||||
ariaLabel: `Admin menu for '${titleForPhoto(photo)}' photo`,
|
ariaLabel: `Admin menu for '${titleForPhoto(photo)}' photo`,
|
||||||
showKeyCommands: true,
|
showKeyCommands: showAdminKeyCommands,
|
||||||
}} />;
|
}} />;
|
||||||
|
|
||||||
const largePhotoContainerClassName = clsx(
|
const largePhotoContainerClassName = clsx(
|
||||||
|
|||||||
@ -16,17 +16,22 @@ import { clsx } from 'clsx/lite';
|
|||||||
import { FiChevronLeft, FiChevronRight } from 'react-icons/fi';
|
import { FiChevronLeft, FiChevronRight } from 'react-icons/fi';
|
||||||
import useNavigateOrRunActionWithToast
|
import useNavigateOrRunActionWithToast
|
||||||
from '@/components/useNavigateOrRunActionWithToast';
|
from '@/components/useNavigateOrRunActionWithToast';
|
||||||
import { toggleFavoritePhotoAction } from './actions';
|
import {
|
||||||
|
deletePhotoAction,
|
||||||
|
syncPhotoAction,
|
||||||
|
toggleFavoritePhotoAction,
|
||||||
|
} from './actions';
|
||||||
import { isPhotoFav } from '@/tag';
|
import { isPhotoFav } from '@/tag';
|
||||||
import Tooltip from '@/components/Tooltip';
|
import Tooltip from '@/components/Tooltip';
|
||||||
import { ALLOW_PUBLIC_DOWNLOADS } from '@/app/config';
|
import { ALLOW_PUBLIC_DOWNLOADS } from '@/app/config';
|
||||||
import { downloadFileFromBrowser } from '@/utility/url';
|
import { downloadFileFromBrowser } from '@/utility/url';
|
||||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||||
|
import { KEY_COMMANDS } from './key-commands';
|
||||||
|
|
||||||
const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 };
|
const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 };
|
||||||
const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 };
|
const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 };
|
||||||
|
|
||||||
export default function PhotoPrevNext({
|
export default function PhotoPrevNextActions({
|
||||||
photo,
|
photo,
|
||||||
photos = [],
|
photos = [],
|
||||||
className,
|
className,
|
||||||
@ -49,9 +54,7 @@ export default function PhotoPrevNext({
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const toggleFavorite = useCallback(() => {
|
const toggleFavorite = useCallback(() => {
|
||||||
if (photo?.id) {
|
if (photo?.id) { return toggleFavoritePhotoAction(photo.id); }
|
||||||
return toggleFavoritePhotoAction(photo.id);
|
|
||||||
}
|
|
||||||
}, [photo?.id]);
|
}, [photo?.id]);
|
||||||
|
|
||||||
const navigateToPhotoEdit = useNavigateOrRunActionWithToast({
|
const navigateToPhotoEdit = useNavigateOrRunActionWithToast({
|
||||||
@ -69,6 +72,22 @@ export default function PhotoPrevNext({
|
|||||||
toastMessage: `Unfavoriting ${photoTitle} ...`,
|
toastMessage: `Unfavoriting ${photoTitle} ...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const syncPhoto = useNavigateOrRunActionWithToast({
|
||||||
|
pathOrAction: useCallback(() => {
|
||||||
|
if (photo?.id) { return syncPhotoAction(photo.id); }
|
||||||
|
}, [photo?.id]),
|
||||||
|
toastMessage: `Syncing ${photoTitle} ...`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const deletePhoto = useNavigateOrRunActionWithToast({
|
||||||
|
pathOrAction: useCallback(() => {
|
||||||
|
if (photo?.id && photo.url) {
|
||||||
|
return deletePhotoAction(photo.id, photo.url, true);
|
||||||
|
}
|
||||||
|
}, [photo?.id, photo?.url]),
|
||||||
|
toastMessage: `Deleting ${photoTitle} ...`,
|
||||||
|
});
|
||||||
|
|
||||||
const refPrevious = useRef<HTMLAnchorElement | null>(null);
|
const refPrevious = useRef<HTMLAnchorElement | null>(null);
|
||||||
const refNext = useRef<HTMLAnchorElement | null>(null);
|
const refNext = useRef<HTMLAnchorElement | null>(null);
|
||||||
|
|
||||||
@ -85,36 +104,36 @@ export default function PhotoPrevNext({
|
|||||||
|
|
||||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
switch (e.key.toUpperCase()) {
|
switch (e.key.toUpperCase()) {
|
||||||
case 'ARROWLEFT':
|
case KEY_COMMANDS.prev[0]:
|
||||||
case 'J':
|
case KEY_COMMANDS.prev[1]:
|
||||||
if (pathPrevious) {
|
if (pathPrevious) {
|
||||||
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
||||||
refPrevious.current?.click();
|
refPrevious.current?.click();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ARROWRIGHT':
|
case KEY_COMMANDS.next[0]:
|
||||||
case 'L':
|
case KEY_COMMANDS.next[1]:
|
||||||
if (pathNext) {
|
if (pathNext) {
|
||||||
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
||||||
refNext.current?.click();
|
refNext.current?.click();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'E':
|
case KEY_COMMANDS.edit:
|
||||||
if (isUserSignedIn) {
|
if (isUserSignedIn) {
|
||||||
navigateToPhotoEdit();
|
navigateToPhotoEdit();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case KEY_COMMANDS.favorite:
|
||||||
if (isUserSignedIn && photo && !isPhotoFav(photo)) {
|
if (isUserSignedIn && photo && !isPhotoFav(photo)) {
|
||||||
favoritePhoto();
|
favoritePhoto();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'X':
|
case KEY_COMMANDS.unfavorite:
|
||||||
if (isUserSignedIn && photo && isPhotoFav(photo)) {
|
if (isUserSignedIn && photo && isPhotoFav(photo)) {
|
||||||
unfavoritePhoto();
|
unfavoritePhoto();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'D':
|
case KEY_COMMANDS.download:
|
||||||
if (
|
if (
|
||||||
(isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) &&
|
(isUserSignedIn || ALLOW_PUBLIC_DOWNLOADS) &&
|
||||||
downloadUrl &&
|
downloadUrl &&
|
||||||
@ -123,6 +142,16 @@ export default function PhotoPrevNext({
|
|||||||
downloadFileFromBrowser(downloadUrl, downloadFileName);
|
downloadFileFromBrowser(downloadUrl, downloadFileName);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case KEY_COMMANDS.sync:
|
||||||
|
if (isUserSignedIn) {
|
||||||
|
syncPhoto();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_COMMANDS.delete[1]:
|
||||||
|
if (e.metaKey && isUserSignedIn) {
|
||||||
|
deletePhoto();
|
||||||
|
}
|
||||||
|
break;
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
setNextPhotoAnimation,
|
setNextPhotoAnimation,
|
||||||
@ -135,6 +164,8 @@ export default function PhotoPrevNext({
|
|||||||
unfavoritePhoto,
|
unfavoritePhoto,
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
downloadFileName,
|
downloadFileName,
|
||||||
|
syncPhoto,
|
||||||
|
deletePhoto,
|
||||||
]);
|
]);
|
||||||
useKeydownHandler({ onKeyDown });
|
useKeydownHandler({ onKeyDown });
|
||||||
|
|
||||||
@ -152,7 +183,7 @@ export default function PhotoPrevNext({
|
|||||||
)}>
|
)}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={previousPhoto ? 'Previous' : undefined}
|
content={previousPhoto ? 'Previous' : undefined}
|
||||||
keyCommand="J"
|
keyCommand={previousPhoto ? KEY_COMMANDS.prev[0] : undefined}
|
||||||
>
|
>
|
||||||
<PhotoLink
|
<PhotoLink
|
||||||
{...categories}
|
{...categories}
|
||||||
@ -172,7 +203,7 @@ export default function PhotoPrevNext({
|
|||||||
</span>
|
</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={nextPhoto ? 'Next' : undefined}
|
content={nextPhoto ? 'Next' : undefined}
|
||||||
keyCommand="L"
|
keyCommand={nextPhoto ? KEY_COMMANDS.next[0] : undefined}
|
||||||
>
|
>
|
||||||
<PhotoLink
|
<PhotoLink
|
||||||
{...categories}
|
{...categories}
|
||||||
14
src/photo/key-commands.ts
Normal file
14
src/photo/key-commands.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const KEY_COMMANDS = {
|
||||||
|
feed: 'F',
|
||||||
|
grid: 'G',
|
||||||
|
admin: 'A',
|
||||||
|
prev: ['J', 'ARROWLEFT'],
|
||||||
|
next: ['L', 'ARROWRIGHT'],
|
||||||
|
edit: 'E',
|
||||||
|
favorite: 'P',
|
||||||
|
unfavorite: 'X',
|
||||||
|
download: 'D',
|
||||||
|
sync: 'S',
|
||||||
|
search: ['⌘', 'K'],
|
||||||
|
delete: ['⌘', 'BACKSPACE'],
|
||||||
|
} as const;
|
||||||
Loading…
Reference in New Issue
Block a user