Browse Source

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

Peng Xiao 2 years ago
parent
commit
0260f5da5d

+ 10 - 0
tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx

@@ -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 />
     </>

+ 1 - 23
tldraw/apps/tldraw-logseq/src/components/StatusBar/StatusBar.tsx

@@ -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}

+ 1 - 1
tldraw/packages/core/src/lib/TLSettings.ts

@@ -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)

+ 1 - 0
tldraw/packages/react/package.json

@@ -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"
   },

+ 74 - 26
tldraw/packages/react/src/components/ui/Grid/Grid.tsx

@@ -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" />
   )
 })

+ 7 - 0
tldraw/packages/react/src/hooks/useStylesheet.ts

@@ -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%;

+ 1 - 1
tldraw/yarn.lock

@@ -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==