refactor(whiteboard): using canvas to render the whiteboard grid dots

pull/7336/head
Peng Xiao 2022-11-17 16:50:45 +08:00
parent 8bf59a564b
commit 0260f5da5d
7 changed files with 94 additions and 50 deletions

View File

@ -84,6 +84,15 @@ export const DevTools = observer(() => {
.map(p => p.join(''))
.join('|')
const originPoint = canvasAnchorRef.current
? ReactDOM.createPortal(
<svg className="tl-renderer-dev-tools tl-grid">
<circle cx={point[0] * zoom} cy={point[1] * zoom} r="4" fill="red" />
</svg>,
canvasAnchorRef.current
)
: null
const rendererStatus = statusbarAnchorRef.current
? ReactDOM.createPortal(
<div
@ -101,6 +110,7 @@ export const DevTools = observer(() => {
return (
<>
{originPoint}
{rendererStatus}
<HistoryStack />
</>

View File

@ -1,33 +1,11 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react'
import { observer } from 'mobx-react-lite'
import { useApp } from '@tldraw/react'
import { observer } from 'mobx-react-lite'
import type { Shape } from '../../lib'
export const StatusBar = observer(function StatusBar() {
const app = useApp<Shape>()
React.useEffect(() => {
const canvas = document.querySelector<HTMLElement>('.logseq-tldraw-wrapper .tl-canvas')
const actionBar = document.querySelector<HTMLElement>('.logseq-tldraw-wrapper .tl-action-bar')
if (canvas) {
canvas.style.height = 'calc(100% - 32px)'
}
if (actionBar) {
actionBar.style.marginBottom = '32px'
}
return () => {
if (canvas) {
canvas.style.height = '100%'
}
if (actionBar) {
actionBar.style.marginBottom = '0px'
}
}
})
return (
<div className="tl-statusbar">
{app.selectedTool.id} | {app.selectedTool.currentState.id}

View File

@ -13,7 +13,7 @@ export class TLSettings implements TLSettingsProps {
}
@observable mode: 'dark' | 'light' = 'light'
@observable showGrid = !isSafari()
@observable showGrid = true
@action update(props: Partial<TLSettingsProps>): void {
Object.assign(this, props)

View File

@ -41,6 +41,7 @@
"mobx": "^6.6.2",
"mobx-react-lite": "^3.4.0",
"mousetrap": "^1.6.5",
"polished": "^4.2.2",
"rbush": "^3.0.1",
"uuid": "^8.0.0"
},

View File

@ -1,5 +1,7 @@
import { modulate } from '@tldraw/core'
import { observer } from 'mobx-react-lite'
import { transparentize } from 'polished'
import React from 'react'
import { useRendererContext } from '../../../hooks'
import type { TLGridProps } from '../../../types'
@ -15,35 +17,81 @@ export const Grid = observer(function Grid({ size }: TLGridProps) {
const {
viewport: {
camera: { point, zoom },
bounds,
},
} = useRendererContext()
return (
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
{STEPS.map(([min, mid, _size], i) => {
const s = _size * size * zoom
const ref = React.useRef<HTMLCanvasElement>(null)
// Use useEffect will cause the render flickering
React.useLayoutEffect(() => {
if (ref.current) {
const canvas = ref.current
if (canvas?.getContext) {
const fillColor = getComputedStyle(canvas)
.getPropertyValue('--ls-quaternary-background-color')
.trim()
const ctx = canvas.getContext('2d')
if (ctx && fillColor) {
const { width, height } = canvas
// fill the canvas with dots
ctx.clearRect(0, 0, width, height)
const xo = point[0] * zoom
const yo = point[1] * zoom
const gxo = xo > 0 ? xo % s : s + (xo % s)
const gyo = yo > 0 ? yo % s : s + (yo % s)
const opacity = zoom < mid ? modulate(zoom, [min, mid], [0, 1]) : 1
return (
<pattern
key={`grid-pattern-${i}`}
id={`grid-${i}`}
width={s}
height={s}
patternUnits="userSpaceOnUse"
>
<circle className={`tl-grid-dot`} cx={gxo} cy={gyo} r={1.5} opacity={opacity} />
</pattern>
)
})}
</defs>
{STEPS.map((_, i) => (
<rect key={`grid-rect-${i}`} width="100%" height="100%" fill={`url(#grid-${i})`} />
))}
</svg>
STEPS.forEach(([min, mid, _size]) => {
const s = _size * size * zoom
const gxo = xo > 0 ? xo % s : s + (xo % s)
const gyo = yo > 0 ? yo % s : s + (yo % s)
const opacity = zoom < mid ? modulate(zoom, [min, mid], [0, 1], true) : 1
ctx.fillStyle = transparentize(1 - opacity, fillColor)
if (opacity < 0.5 || s < 32) return
for (let i = gyo; i < height; i += s) {
for (let j = gxo; j < width; j += s) {
ctx.beginPath()
ctx.arc(j, i, 1.5, 0, 2 * Math.PI)
ctx.closePath()
ctx.fill()
}
}
// Pattern should have better performance, but I cannot make the offset correctly ...
// for (let i = 0; i < height; i += _size) {
// const y = i * _size
// for (let j = 0; j < width; j += _size) {
// const x = j * _size
// ctx.fillRect(x, y, _size, _size)
// }
// }
// const pattern = document.createElement('canvas').getContext('2d')
// if (pattern) {
// const s = _size * size * zoom
// if (s < 1) {
// return
// }
// const xo = point[0] * zoom
// const yo = point[1] * zoom
// const gxo = xo > 0 ? xo % s : s + (xo % s)
// const gyo = yo > 0 ? yo % s : s + (yo % s)
// const opacity = zoom < mid ? modulate(zoom, [min, mid], [0, 1]) : 1
// pattern.canvas.width = s
// pattern.canvas.height = s
// pattern.beginPath()
// pattern.arc(gxo, gyo, 1.5, 0, 2 * Math.PI)
// pattern.fillStyle = transparentize(1 - opacity, fillColor)
// pattern.fill()
// pattern.closePath()
// ctx.fillStyle = ctx.createPattern(pattern.canvas, 'repeat')!
// ctx.fillRect(0, 0, width, height)
// }
})
}
}
}
}, [point[0], point[1], zoom, bounds.width, bounds.height])
return (
<canvas ref={ref} width={bounds.width} height={bounds.height} className="tl-grid-canvas" />
)
})

View File

@ -421,6 +421,13 @@ const tlcss = css`
color: var(--tl-background);
}
.tl-grid-canvas {
position: absolute;
touch-action: none;
pointer-events: none;
user-select: none;
}
.tl-grid {
position: absolute;
width: 100%;

View File

@ -3669,7 +3669,7 @@ pirates@^4.0.1:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
polished@^4.0.0:
polished@^4.0.0, polished@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1"
integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==