Tldraw/packages/tldraw/src/lib/ui/components/Minimap/DefaultMinimap.tsx

193 wiersze
4.5 KiB
TypeScript

import {
ANIMATION_MEDIUM_MS,
TLPointerEventInfo,
Vec,
getPointerInfo,
normalizeWheel,
releasePointerCapture,
setPointerCapture,
useEditor,
useIsDarkMode,
} from '@tldraw/editor'
import * as React from 'react'
import { MinimapManager } from './MinimapManager'
/** @public */
export function DefaultMinimap() {
const editor = useEditor()
const rCanvas = React.useRef<HTMLCanvasElement>(null!)
const rPointing = React.useRef(false)
const minimapRef = React.useRef<MinimapManager>()
React.useEffect(() => {
const minimap = new MinimapManager(editor, rCanvas.current)
minimapRef.current = minimap
return minimapRef.current.close
}, [editor])
const onDoubleClick = React.useCallback(
(e: React.MouseEvent<HTMLCanvasElement>) => {
if (!editor.getCurrentPageShapeIds().size) return
if (!minimapRef.current) return
const point = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX,
e.clientY,
false,
false
)
const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX,
e.clientY,
false,
true
)
minimapRef.current.originPagePoint.setTo(clampedPoint)
minimapRef.current.originPageCenter.setTo(editor.getViewportPageBounds().center)
editor.centerOnPoint(point, { duration: ANIMATION_MEDIUM_MS })
},
[editor]
)
const onPointerDown = React.useCallback(
(e: React.PointerEvent<HTMLCanvasElement>) => {
if (!minimapRef.current) return
const elm = e.currentTarget
setPointerCapture(elm, e)
if (!editor.getCurrentPageShapeIds().size) return
rPointing.current = true
minimapRef.current.isInViewport = false
const point = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX,
e.clientY,
false,
false
)
const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX,
e.clientY,
false,
true
)
const _vpPageBounds = editor.getViewportPageBounds()
minimapRef.current.isInViewport = _vpPageBounds.containsPoint(clampedPoint)
if (minimapRef.current.isInViewport) {
minimapRef.current.originPagePoint.setTo(clampedPoint)
minimapRef.current.originPageCenter.setTo(_vpPageBounds.center)
} else {
const delta = Vec.Sub(_vpPageBounds.center, _vpPageBounds.point)
const pagePoint = Vec.Add(point, delta)
minimapRef.current.originPagePoint.setTo(pagePoint)
minimapRef.current.originPageCenter.setTo(point)
editor.centerOnPoint(point, { duration: ANIMATION_MEDIUM_MS })
}
function release(e: PointerEvent) {
if (elm) {
releasePointerCapture(elm, e)
}
rPointing.current = false
document.body.removeEventListener('pointerup', release)
}
document.body.addEventListener('pointerup', release)
},
[editor]
)
const onPointerMove = React.useCallback(
(e: React.PointerEvent<HTMLCanvasElement>) => {
if (!minimapRef.current) return
const point = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX,
e.clientY,
e.shiftKey,
true
)
if (rPointing.current) {
if (minimapRef.current.isInViewport) {
const delta = minimapRef.current.originPagePoint
.clone()
.sub(minimapRef.current.originPageCenter)
editor.centerOnPoint(Vec.Sub(point, delta))
return
}
editor.centerOnPoint(point)
}
const pagePoint = minimapRef.current.getPagePoint(e.clientX, e.clientY)
const screenPoint = editor.pageToScreen(pagePoint)
const info: TLPointerEventInfo = {
type: 'pointer',
target: 'canvas',
name: 'pointer_move',
...getPointerInfo(e),
point: screenPoint,
isPen: editor.getInstanceState().isPenMode,
}
editor.dispatch(info)
},
[editor]
)
const onWheel = React.useCallback(
(e: React.WheelEvent<HTMLCanvasElement>) => {
const offset = normalizeWheel(e)
editor.dispatch({
type: 'wheel',
name: 'wheel',
delta: offset,
point: new Vec(e.clientX, e.clientY),
shiftKey: e.shiftKey,
altKey: e.altKey,
ctrlKey: e.metaKey || e.ctrlKey,
})
},
[editor]
)
const isDarkMode = useIsDarkMode()
React.useEffect(() => {
// need to wait a tick for next theme css to be applied
// otherwise the minimap will render with the wrong colors
setTimeout(() => {
minimapRef.current?.updateColors()
minimapRef.current?.render()
})
}, [isDarkMode])
return (
<div className="tlui-minimap">
<canvas
role="img"
aria-label="minimap"
ref={rCanvas}
className="tlui-minimap__canvas"
onDoubleClick={onDoubleClick}
onPointerMove={onPointerMove}
onPointerDown={onPointerDown}
onWheel={onWheel}
/>
</div>
)
}