This commit is contained in:
Manav Rathi
2025-03-05 19:56:46 +05:30
parent a18db13899
commit 22cd3763b7
2 changed files with 75 additions and 19 deletions

View File

@@ -396,7 +396,7 @@ const FileViewer: React.FC<FileViewerProps> = ({
// //
// Not memoized since it uses the frequently changing `activeAnnotatedFile`. // Not memoized since it uses the frequently changing `activeAnnotatedFile`.
const handleDownloadMenuAction = () => { const handleDownloadMenuAction = () => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
onDownload!(activeAnnotatedFile!.file); onDownload!(activeAnnotatedFile!.file);
}; };
@@ -413,9 +413,9 @@ const FileViewer: React.FC<FileViewerProps> = ({
[], [],
); );
const handleMoreMenuClose = useCallback(() => { const handleMoreMenuCloseIfNeeded = useCallback(() => {
setMoreMenuAnchorEl((el) => { setMoreMenuAnchorEl((el) => {
resetMoreMenuButtonOnMenuClose(el); if (el) resetMoreMenuButtonOnMenuClose(el);
return null; return null;
}); });
}, []); }, []);
@@ -423,11 +423,11 @@ const FileViewer: React.FC<FileViewerProps> = ({
const handleConfirmDelete = useMemo(() => { const handleConfirmDelete = useMemo(() => {
return onDelete return onDelete
? () => { ? () => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
showConfirmDelete(); showConfirmDelete();
} }
: undefined; : undefined;
}, [onDelete, showConfirmDelete, handleMoreMenuClose]); }, [onDelete, showConfirmDelete, handleMoreMenuCloseIfNeeded]);
// Not memoized since it uses the frequently changing `activeAnnotatedFile`. // Not memoized since it uses the frequently changing `activeAnnotatedFile`.
const handleDelete = async () => { const handleDelete = async () => {
@@ -455,8 +455,8 @@ const FileViewer: React.FC<FileViewerProps> = ({
}; };
// Not memoized since it uses the frequently changing `activeAnnotatedFile`. // Not memoized since it uses the frequently changing `activeAnnotatedFile`.
const handleCopyImage = () => { const handleCopyImage = useCallback(() => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
// Safari does not copy if we do not call `navigator.clipboard.write` // Safari does not copy if we do not call `navigator.clipboard.write`
// synchronously within the click event handler, but it does supports // synchronously within the click event handler, but it does supports
// passing a promise in lieu of the blob. // passing a promise in lieu of the blob.
@@ -467,16 +467,16 @@ const FileViewer: React.FC<FileViewerProps> = ({
}), }),
]) ])
.catch(onGenericError); .catch(onGenericError);
}; }, [onGenericError, handleMoreMenuCloseIfNeeded, activeImageURL]);
const handleEditImage = useMemo(() => { const handleEditImage = useMemo(() => {
return onSaveEditedImageCopy return onSaveEditedImageCopy
? () => { ? () => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
setOpenImageEditor(true); setOpenImageEditor(true);
} }
: undefined; : undefined;
}, [onSaveEditedImageCopy, handleMoreMenuClose]); }, [onSaveEditedImageCopy, handleMoreMenuCloseIfNeeded]);
const handleImageEditorClose = useCallback( const handleImageEditorClose = useCallback(
() => setOpenImageEditor(false), () => setOpenImageEditor(false),
@@ -616,22 +616,49 @@ const FileViewer: React.FC<FileViewerProps> = ({
}, []); }, []);
const handleToggleFullscreen = useCallback(() => { const handleToggleFullscreen = useCallback(() => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
void ( void (
document.fullscreenElement document.fullscreenElement
? document.exitFullscreen() ? document.exitFullscreen()
: document.body.requestFullscreen() : document.body.requestFullscreen()
).then(updateFullscreenStatus); ).then(updateFullscreenStatus);
}, [handleMoreMenuClose, updateFullscreenStatus]); }, [handleMoreMenuCloseIfNeeded, updateFullscreenStatus]);
const handleShortcuts = useCallback(() => { const handleShortcuts = useCallback(() => {
handleMoreMenuClose(); handleMoreMenuCloseIfNeeded();
showShortcuts(); showShortcuts();
}, [handleMoreMenuClose, showShortcuts]); }, [handleMoreMenuCloseIfNeeded, showShortcuts]);
const performKeyAction = useCallback(
(action): FileViewerPhotoSwipeDelegate["performKeyAction"] => {
switch (action) {
case "delete":
handleConfirmDelete?.();
break;
case "copy":
if (activeImageURL) handleCopyImage();
break;
case "toggle-fullscreen":
handleToggleFullscreen();
break;
}
},
[
handleConfirmDelete,
activeImageURL,
handleCopyImage,
handleToggleFullscreen,
],
);
// Initial value of delegate. // Initial value of delegate.
if (!delegateRef.current) { if (!delegateRef.current) {
delegateRef.current = { getFiles, isFavorite, toggleFavorite }; delegateRef.current = {
getFiles,
isFavorite,
toggleFavorite,
performKeyAction,
};
} }
// Updates to delegate callbacks. // Updates to delegate callbacks.
@@ -640,7 +667,8 @@ const FileViewer: React.FC<FileViewerProps> = ({
delegate.getFiles = getFiles; delegate.getFiles = getFiles;
delegate.isFavorite = isFavorite; delegate.isFavorite = isFavorite;
delegate.toggleFavorite = toggleFavorite; delegate.toggleFavorite = toggleFavorite;
}, [getFiles, isFavorite, toggleFavorite]); delegate.performKeyAction = performKeyAction;
}, [getFiles, isFavorite, toggleFavorite, performKeyAction]);
// Notify the listeners, if any, for updates to files or favorites. // Notify the listeners, if any, for updates to files or favorites.
useEffect(() => { useEffect(() => {
@@ -717,7 +745,7 @@ const FileViewer: React.FC<FileViewerProps> = ({
/> />
<MoreMenu <MoreMenu
open={!!moreMenuAnchorEl} open={!!moreMenuAnchorEl}
onClose={handleMoreMenuClose} onClose={handleMoreMenuCloseIfNeeded}
anchorEl={moreMenuAnchorEl} anchorEl={moreMenuAnchorEl}
id={moreMenuID} id={moreMenuID}
slotProps={{ slotProps={{

View File

@@ -73,6 +73,15 @@ export interface FileViewerPhotoSwipeDelegate {
* > remain in the disabled state (until the file viewer is closed). * > remain in the disabled state (until the file viewer is closed).
*/ */
toggleFavorite: (annotatedFile: FileViewerAnnotatedFile) => Promise<void>; toggleFavorite: (annotatedFile: FileViewerAnnotatedFile) => Promise<void>;
/**
* Called when the user triggers a potential action using a keyboard
* shortcut.
*
* The caller does not check if the action is valid in the current context,
* so the delegate must validate and only then perform the action if it is
* appropriate.
*/
performKeyAction: (action: "delete" | "copy" | "toggle-fullscreen") => void;
} }
type FileViewerPhotoSwipeOptions = Pick< type FileViewerPhotoSwipeOptions = Pick<
@@ -507,8 +516,16 @@ export class FileViewerPhotoSwipe {
const handleToggleFavorite = () => void toggleFavorite(); const handleToggleFavorite = () => void toggleFavorite();
const handleToggleFavoriteIfEnabled = () => {
if (haveUser) handleToggleFavorite();
};
const handleDownload = () => onDownload(currentAnnotatedFile()); const handleDownload = () => onDownload(currentAnnotatedFile());
const handleDownloadIfEnabled = () => {
if (!!currentFileAnnotation().showDownload) handleDownload();
};
const showIf = (element: HTMLElement, condition: boolean) => const showIf = (element: HTMLElement, condition: boolean) =>
condition condition
? element.classList.remove("pswp__hidden") ? element.classList.remove("pswp__hidden")
@@ -684,16 +701,27 @@ export class FileViewerPhotoSwipe {
return element; return element;
}); });
// Some actions routed via the delegate
const handleDelete = () => delegate.performKeyAction("delete");
const handleCopy = () => delegate.performKeyAction("copy");
const handleToggleFullscreen = () =>
delegate.performKeyAction("toggle-fullscreen");
pswp.on("keydown", (e, z) => { pswp.on("keydown", (e, z) => {
const key = e.originalEvent.key ?? ""; const key = e.originalEvent.key ?? "";
const cb = (() => { const cb = (() => {
switch (key.toLowerCase()) { switch (key.toLowerCase()) {
case "l": case "l":
return handleToggleFavorite; return handleToggleFavoriteIfEnabled;
case "d": case "d":
return handleDownload; return handleDownloadIfEnabled;
case "i": case "i":
return handleViewInfo; return handleViewInfo;
case "f":
return handleToggleFullscreen;
} }
return undefined; return undefined;
})(); })();