diff --git a/src/components/ExcalidrawMenu.tsx b/src/components/ExcalidrawMenu.tsx index 92359a41..c15a527c 100644 --- a/src/components/ExcalidrawMenu.tsx +++ b/src/components/ExcalidrawMenu.tsx @@ -3,14 +3,18 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { useCallback, memo } from 'react' +import { useCallback, useEffect, useRef, memo } from 'react' +import type { KeyboardEvent as ReactKeyboardEvent } from 'react' import { Icon } from '@mdi/react' import { mdiMonitorScreenshot, mdiImageMultiple } from '@mdi/js' -import { MainMenu } from '@nextcloud/excalidraw' +import { MainMenu, CaptureUpdateAction } from '@nextcloud/excalidraw' import { RecordingMenuItem } from './Recording' import { PresentationMenuItem } from './Presentation' import { CreatorMenuItem } from './CreatorMenuItem' import { t } from '@nextcloud/l10n' +import { useShallow } from 'zustand/react/shallow' +import type { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types' +import { useExcalidrawStore } from '../stores/useExcalidrawStore' interface RecordingState { isRecording: boolean @@ -60,6 +64,9 @@ interface ExcalidrawMenuProps { export const ExcalidrawMenu = memo(function ExcalidrawMenu({ fileNameWithoutExtension, recordingState, presentationState }: ExcalidrawMenuProps) { const isMacPlatform = typeof navigator !== 'undefined' && (navigator.userAgentData?.platform === 'macOS' || /Mac|iPhone|iPad/.test(navigator.platform ?? '')) + const { excalidrawAPI } = useExcalidrawStore(useShallow(state => ({ + excalidrawAPI: state.excalidrawAPI, + }))) const openExportDialog = useCallback(() => { // Trigger export by dispatching the keyboard shortcut to the Excalidraw canvas @@ -83,17 +90,99 @@ export const ExcalidrawMenu = memo(function ExcalidrawMenu({ fileNameWithoutExte }, [isMacPlatform]) const takeScreenshot = useCallback(() => { - const canvas = document.querySelector('.excalidraw__canvas') as HTMLCanvasElement - if (canvas) { - const dataUrl = canvas.toDataURL('image/png') - const downloadLink = document.createElement('a') - downloadLink.href = dataUrl - downloadLink.download = `${fileNameWithoutExtension} Screenshot.png` - document.body.appendChild(downloadLink) - downloadLink.click() + const canvas = document.querySelector('.excalidraw__canvas') as HTMLCanvasElement | null + if (!canvas) { + return } + + const excalidrawContainer = document.querySelector('.excalidraw') as HTMLElement | null + const previouslyFocused = document.activeElement as HTMLElement | null + + const dataUrl = canvas.toDataURL('image/png') + const downloadLink = document.createElement('a') + downloadLink.href = dataUrl + downloadLink.download = `${fileNameWithoutExtension} Screenshot.png` + document.body.appendChild(downloadLink) + downloadLink.click() + downloadLink.remove() + + const restoreFocus = () => { + const focusTarget + = previouslyFocused && previouslyFocused !== document.body + ? previouslyFocused + : excalidrawContainer + + if (focusTarget && typeof focusTarget.focus === 'function') { + try { + focusTarget.focus({ preventScroll: true }) + } catch { + focusTarget.focus() + } + } + } + + requestAnimationFrame(restoreFocus) }, [fileNameWithoutExtension]) + const takeScreenshotRef = useRef(takeScreenshot) + useEffect(() => { + takeScreenshotRef.current = takeScreenshot + }, [takeScreenshot]) + + const isMacPlatformRef = useRef(isMacPlatform) + useEffect(() => { + isMacPlatformRef.current = isMacPlatform + }, [isMacPlatform]) + + const registeredApiRef = useRef(null) + useEffect(() => { + if (!excalidrawAPI) { + registeredApiRef.current = null + return + } + + if (registeredApiRef.current === excalidrawAPI) { + return + } + + const screenshotShortcutAction = { + name: 'whiteboard-download-screenshot', + label: () => 'Download screenshot', + trackEvent: false, + viewMode: true, + keyTest: (event: KeyboardEvent | ReactKeyboardEvent) => { + if (event.repeat || !event.altKey) { + return false + } + + const shouldUseMetaKey = isMacPlatformRef.current + const hasRequiredModifier = shouldUseMetaKey ? event.metaKey : event.ctrlKey + if (!hasRequiredModifier) { + return false + } + + const keyCode = typeof event.code === 'string' ? event.code.toLowerCase() : '' + if (keyCode !== 'keys') { + return false + } + + const target = event.target + if (target instanceof Element && target.closest('input, textarea, [contenteditable="true"]')) { + return false + } + + return true + }, + perform: () => { + takeScreenshotRef.current() + return { captureUpdate: CaptureUpdateAction.NEVER } + }, + } as unknown as Parameters[0] + + excalidrawAPI.registerAction(screenshotShortcutAction) + registeredApiRef.current = excalidrawAPI + }, [excalidrawAPI]) + return ( @@ -107,7 +196,8 @@ export const ExcalidrawMenu = memo(function ExcalidrawMenu({ fileNameWithoutExte } - onSelect={takeScreenshot}> + onSelect={takeScreenshot} + shortcut={isMacPlatform ? '⌘+⌥+S' : 'Ctrl+Alt+S'}> {t('whiteboard', 'Download screenshot')}