[fix] minimap, common page bounds (#1770)

This PR fixes a bug that was introduced (by me) in #1751, where:
- the `commonBoundsOfAllShapesOnCurrentPage` would mutate the first
bounding box
- the render reactor would fire too often

### Change Type

- [x] `patch` — Bug fix

### Test Plan

1. Use the minimap
This commit is contained in:
Steve Ruiz 2023-07-27 17:01:01 +01:00 committed by GitHub
parent 7e4fb59a48
commit 1014a71abb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 29 additions and 47 deletions

View file

@ -4227,7 +4227,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const bounds = this.getMaskedPageBounds(shapeId) const bounds = this.getMaskedPageBounds(shapeId)
if (!bounds) return if (!bounds) return
if (!commonBounds) { if (!commonBounds) {
commonBounds = bounds commonBounds = bounds.clone()
} else { } else {
commonBounds = commonBounds.expand(bounds) commonBounds = commonBounds.expand(bounds)
} }

View file

@ -8,9 +8,9 @@ import {
intersectPolygonPolygon, intersectPolygonPolygon,
normalizeWheel, normalizeWheel,
setPointerCapture, setPointerCapture,
track, useComputed,
useContainer,
useEditor, useEditor,
useIsDarkMode,
useQuickReactor, useQuickReactor,
} from '@tldraw/editor' } from '@tldraw/editor'
import * as React from 'react' import * as React from 'react'
@ -22,30 +22,22 @@ export interface MinimapProps {
viewportFill: string viewportFill: string
} }
export const Minimap = track(function Minimap({ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
shapeFill,
selectFill,
viewportFill,
}: MinimapProps) {
const editor = useEditor() const editor = useEditor()
const rCanvas = React.useRef<HTMLCanvasElement>(null!) const rCanvas = React.useRef<HTMLCanvasElement>(null!)
const container = useContainer()
const rPointing = React.useRef(false) const rPointing = React.useRef(false)
const minimap = React.useMemo( const isDarkMode = useIsDarkMode()
() => new MinimapManager(editor, editor.instanceState.devicePixelRatio), const devicePixelRatio = useComputed('dpr', () => editor.instanceState.devicePixelRatio, [editor])
[editor] const presences = React.useMemo(() => editor.store.query.records('instance_presence'), [editor])
)
const isDarkMode = editor.user.isDarkMode const minimap = React.useMemo(() => new MinimapManager(editor), [editor])
React.useEffect(() => { React.useEffect(() => {
// Must check after render // Must check after render
const raf = requestAnimationFrame(() => { const raf = requestAnimationFrame(() => {
const style = getComputedStyle(container) const style = getComputedStyle(editor.getContainer())
minimap.colors = { minimap.colors = {
shapeFill: style.getPropertyValue(shapeFill).trim(), shapeFill: style.getPropertyValue(shapeFill).trim(),
@ -58,7 +50,7 @@ export const Minimap = track(function Minimap({
return () => { return () => {
cancelAnimationFrame(raf) cancelAnimationFrame(raf)
} }
}, [container, selectFill, shapeFill, viewportFill, minimap, isDarkMode]) }, [editor, selectFill, shapeFill, viewportFill, minimap, isDarkMode])
const onDoubleClick = React.useCallback( const onDoubleClick = React.useCallback(
(e: React.MouseEvent<HTMLCanvasElement>) => { (e: React.MouseEvent<HTMLCanvasElement>) => {
@ -163,17 +155,15 @@ export const Minimap = track(function Minimap({
// Update the minimap's dpr when the dpr changes // Update the minimap's dpr when the dpr changes
useQuickReactor( useQuickReactor(
'update dpr', 'update when dpr changes',
() => { () => {
const { const dpr = devicePixelRatio.value
instanceState: { devicePixelRatio }, minimap.setDpr(dpr)
} = editor
minimap.setDpr(devicePixelRatio)
const canvas = rCanvas.current as HTMLCanvasElement const canvas = rCanvas.current as HTMLCanvasElement
const rect = canvas.getBoundingClientRect() const rect = canvas.getBoundingClientRect()
const width = rect.width * devicePixelRatio const width = rect.width * dpr
const height = rect.height * devicePixelRatio const height = rect.height * dpr
// These must happen in order // These must happen in order
canvas.width = width canvas.width = width
@ -182,26 +172,19 @@ export const Minimap = track(function Minimap({
minimap.cvs = rCanvas.current minimap.cvs = rCanvas.current
}, },
[editor, minimap] [devicePixelRatio, minimap]
) )
const presences = React.useMemo(() => {
return editor.store.query.records('instance_presence')
}, [editor])
useQuickReactor( useQuickReactor(
'minimap render when pagebounds or collaborators changes', 'minimap render when pagebounds or collaborators changes',
() => { () => {
const { const { shapeIdsOnCurrentPage, viewportPageBounds, commonBoundsOfAllShapesOnCurrentPage } =
instanceState: { devicePixelRatio }, editor
viewportPageBounds,
commonBoundsOfAllShapesOnCurrentPage: allShapesCommonBounds,
} = editor
devicePixelRatio // dereference dpr so that it renders then, too const _dpr = devicePixelRatio.value
minimap.contentPageBounds = allShapesCommonBounds minimap.contentPageBounds = commonBoundsOfAllShapesOnCurrentPage
? Box2d.Expand(allShapesCommonBounds, viewportPageBounds) ? Box2d.Expand(commonBoundsOfAllShapesOnCurrentPage, viewportPageBounds)
: viewportPageBounds : viewportPageBounds
minimap.updateContentScreenBounds() minimap.updateContentScreenBounds()
@ -210,8 +193,9 @@ export const Minimap = track(function Minimap({
const allShapeBounds = [] as (Box2d & { id: TLShapeId })[] const allShapeBounds = [] as (Box2d & { id: TLShapeId })[]
editor.shapeIdsOnCurrentPage.forEach((id) => { shapeIdsOnCurrentPage.forEach((id) => {
let pageBounds = editor.getPageBounds(id)! as Box2d & { id: TLShapeId } let pageBounds = editor.getPageBounds(id) as Box2d & { id: TLShapeId }
if (!pageBounds) return
const pageMask = editor.getPageMask(id) const pageMask = editor.getPageMask(id)
@ -230,11 +214,7 @@ export const Minimap = track(function Minimap({
}) })
minimap.pageBounds = allShapeBounds minimap.pageBounds = allShapeBounds
// Collaborators
minimap.collaborators = presences.value minimap.collaborators = presences.value
minimap.render() minimap.render()
}, },
[editor, minimap] [editor, minimap]
@ -253,4 +233,4 @@ export const Minimap = track(function Minimap({
/> />
</div> </div>
) )
}) }

View file

@ -10,7 +10,9 @@ import {
} from '@tldraw/editor' } from '@tldraw/editor'
export class MinimapManager { export class MinimapManager {
constructor(public editor: Editor, private dpr: number) {} constructor(public editor: Editor) {}
dpr = 1
colors = { colors = {
shapeFill: 'rgba(144, 144, 144, .1)', shapeFill: 'rgba(144, 144, 144, .1)',
@ -212,7 +214,6 @@ export class MinimapManager {
const by = ry / 4 const by = ry / 4
// shapes // shapes
const shapesPath = new Path2D() const shapesPath = new Path2D()
const selectedPath = new Path2D() const selectedPath = new Path2D()
@ -234,6 +235,7 @@ export class MinimapManager {
clamp(ry, ay, pb.height / by) clamp(ry, ay, pb.height / by)
) )
} }
// Fill the shapes paths // Fill the shapes paths
ctx.fillStyle = shapeFill ctx.fillStyle = shapeFill
ctx.fill(shapesPath) ctx.fill(shapesPath)