2
0

(editor) Actions on multiple groups

You can now select groups, move, copy, delete them easily

Closes #830, closes #1092
This commit is contained in:
Baptiste Arnaud
2024-01-23 10:31:31 +01:00
parent c08ab3d007
commit 00dcb135f3
32 changed files with 1043 additions and 282 deletions

View File

@@ -0,0 +1,90 @@
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react'
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect
type Options = boolean | (AddEventListenerOptions & { debug?: boolean })
// MediaQueryList Event based useEventListener interface
function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList>,
options?: Options
): void
// Window Event based useEventListener interface
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element?: undefined,
options?: Options
): void
// Element Event based useEventListener interface
function useEventListener<
K extends keyof HTMLElementEventMap,
T extends HTMLElement = HTMLDivElement
>(
eventName: K,
handler: (event: HTMLElementEventMap[K]) => void,
element: RefObject<T>,
options?: Options
): void
// Document Event based useEventListener interface
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document>,
options?: Options
): void
function useEventListener<
KW extends keyof WindowEventMap,
KH extends keyof HTMLElementEventMap,
KM extends keyof MediaQueryListEventMap,
T extends HTMLElement | MediaQueryList | void = void
>(
eventName: KW | KH | KM,
handler: (
event:
| WindowEventMap[KW]
| HTMLElementEventMap[KH]
| MediaQueryListEventMap[KM]
| Event
) => void,
element?: RefObject<T>,
options?: Options
) {
// Create a ref that stores handler
const savedHandler = useRef(handler)
useIsomorphicLayoutEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
// Define the listening target
if (element && !element.current) return
const targetElement: T | Window = element?.current ?? window
if (!(targetElement && targetElement.addEventListener)) return
if (options && typeof options !== 'boolean')
console.log('Add event listener', { eventName, element, options })
// Create event listener that calls handler function stored in ref
const listener: typeof handler = (event) => savedHandler.current(event)
targetElement.addEventListener(eventName, listener, options)
// Remove event listener on cleanup
return () => {
if (options && typeof options !== 'boolean')
console.log('Remove event listener')
targetElement.removeEventListener(eventName, listener, options)
}
}, [eventName, element, options])
}
export { useEventListener }

View File

@@ -0,0 +1,69 @@
import { useEventListener } from './useEventListener'
type Props = {
undo?: () => void
redo?: () => void
copy?: () => void
paste?: () => void
cut?: () => void
backspace?: () => void
}
export const useKeyboardShortcuts = ({
undo,
redo,
copy,
paste,
cut,
backspace,
}: Props) => {
const isUndoShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'z' && !event.shiftKey
const isRedoShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'z' && event.shiftKey
const isCopyShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'c'
const isPasteShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'v'
const isCutShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'x'
const isBackspaceShortcut = (event: KeyboardEvent) =>
event.key === 'Backspace'
useEventListener('keydown', (event: KeyboardEvent) => {
const target = event.target as HTMLElement | null
const isTyping =
target?.role === 'textbox' ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLInputElement
if (isTyping) return
if (undo && isUndoShortcut(event)) {
event.preventDefault()
undo()
}
if (redo && isRedoShortcut(event)) {
event.preventDefault()
redo()
}
if (copy && isCopyShortcut(event)) {
event.preventDefault()
copy()
}
if (paste && isPasteShortcut(event)) {
event.preventDefault()
paste()
}
if (cut && isCutShortcut(event)) {
event.preventDefault()
cut()
}
if (backspace && isBackspaceShortcut(event)) {
event.preventDefault()
backspace()
}
})
}

View File

@@ -1,18 +0,0 @@
import { useEventListener } from '@chakra-ui/react'
export const useUndoShortcut = (undo: () => void) => {
const isUndoShortcut = (event: KeyboardEvent) =>
(event.metaKey || event.ctrlKey) && event.key === 'z'
useEventListener('keydown', (event: KeyboardEvent) => {
const target = event.target as HTMLElement | null
const isTyping =
target?.role === 'textbox' ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLInputElement
if (isTyping) return
if (isUndoShortcut(event)) {
undo()
}
})
}