Fixes a perf-killing deepCompare in context menu
Was deep comparing an array of actual selected shapes, rather than selected shape ids
This commit is contained in:
parent
ff58073d12
commit
d1a3860bb1
10 changed files with 105 additions and 99 deletions
|
@ -17,49 +17,31 @@
|
|||
"name": "Arrow",
|
||||
"parentId": "page1",
|
||||
"childIndex": 16,
|
||||
"point": [
|
||||
100,
|
||||
100
|
||||
],
|
||||
"point": [100, 100],
|
||||
"rotation": 0,
|
||||
"isAspectRatioLocked": false,
|
||||
"isLocked": false,
|
||||
"isHidden": false,
|
||||
"bend": 0,
|
||||
"points": [
|
||||
[
|
||||
6.503619523263069,
|
||||
281.09020634582345
|
||||
],
|
||||
[
|
||||
0,
|
||||
0
|
||||
]
|
||||
[6.503619523263069, 281.09020634582345],
|
||||
[0, 0]
|
||||
],
|
||||
"handles": {
|
||||
"start": {
|
||||
"id": "start",
|
||||
"index": 0,
|
||||
"point": [
|
||||
6.503619523263069,
|
||||
281.09020634582345
|
||||
]
|
||||
"point": [6.503619523263069, 281.09020634582345]
|
||||
},
|
||||
"end": {
|
||||
"id": "end",
|
||||
"index": 1,
|
||||
"point": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
"point": [0, 0]
|
||||
},
|
||||
"bend": {
|
||||
"id": "bend",
|
||||
"index": 2,
|
||||
"point": [
|
||||
3.2518097616315345,
|
||||
140.54510317291172
|
||||
]
|
||||
"point": [3.2518097616315345, 140.54510317291172]
|
||||
}
|
||||
},
|
||||
"decorations": {
|
||||
|
@ -82,14 +64,8 @@
|
|||
"name": "Rectangle",
|
||||
"parentId": "page1",
|
||||
"childIndex": 24,
|
||||
"point": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
67.22075383450237,
|
||||
72.92795609221832
|
||||
],
|
||||
"point": [0, 0],
|
||||
"size": [67.22075383450237, 72.92795609221832],
|
||||
"radius": 2,
|
||||
"rotation": 0,
|
||||
"isAspectRatioLocked": false,
|
||||
|
@ -117,11 +93,8 @@
|
|||
"id": "page1",
|
||||
"selectedIds": {},
|
||||
"camera": {
|
||||
"point": [
|
||||
-776.1126994855964,
|
||||
-404.44260065511594
|
||||
],
|
||||
"point": [-776.1126994855964, -404.44260065511594],
|
||||
"zoom": 0.260047458798192
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ import {
|
|||
import {
|
||||
commandKey,
|
||||
deepCompareArrays,
|
||||
getSelectedShapes,
|
||||
getSelectedIds,
|
||||
getShape,
|
||||
isMobile,
|
||||
setToArray,
|
||||
} from 'utils'
|
||||
import state, { useSelector } from 'state'
|
||||
import {
|
||||
|
@ -79,22 +81,25 @@ export default function ContextMenu({
|
|||
}: {
|
||||
children: React.ReactNode
|
||||
}): JSX.Element {
|
||||
const selectedShapes = useSelector(
|
||||
(s) => getSelectedShapes(s.data),
|
||||
const selectedShapeIds = useSelector(
|
||||
(s) => setToArray(getSelectedIds(s.data)),
|
||||
deepCompareArrays
|
||||
)
|
||||
|
||||
const rContent = useRef<HTMLDivElement>(null)
|
||||
|
||||
const hasGroupSelectd = selectedShapes.some((s) => s.type === ShapeType.Group)
|
||||
const hasTwoOrMore = selectedShapes.length > 1
|
||||
const hasThreeOrMore = selectedShapes.length > 2
|
||||
const hasGroupSelected = useSelector((s) =>
|
||||
selectedShapeIds.some((id) => getShape(s.data, id).type === ShapeType.Group)
|
||||
)
|
||||
|
||||
const hasTwoOrMore = selectedShapeIds.length > 1
|
||||
const hasThreeOrMore = selectedShapeIds.length > 2
|
||||
|
||||
return (
|
||||
<_ContextMenu.Root>
|
||||
<_ContextMenu.Trigger>{children}</_ContextMenu.Trigger>
|
||||
<StyledContent ref={rContent} isMobile={isMobile()}>
|
||||
{selectedShapes.length ? (
|
||||
{selectedShapeIds.length ? (
|
||||
<>
|
||||
{/* <Button onSelect={() => state.send('COPIED')}>
|
||||
<span>Copy</span>
|
||||
|
@ -119,10 +124,10 @@ export default function ContextMenu({
|
|||
</kbd>
|
||||
</Button>
|
||||
<StyledDivider />
|
||||
{hasGroupSelectd ||
|
||||
{hasGroupSelected ||
|
||||
(hasTwoOrMore && (
|
||||
<>
|
||||
{hasGroupSelectd && (
|
||||
{hasGroupSelected && (
|
||||
<Button onSelect={() => state.send('UNGROUPED')}>
|
||||
<span>Ungroup</span>
|
||||
<kbd>
|
||||
|
|
|
@ -2,31 +2,22 @@ import { getShapeStyle } from 'state/shape-styles'
|
|||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import React, { memo } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import { deepCompareArrays, getCurrentCamera, getPage } from 'utils'
|
||||
import { getCurrentCamera } from 'utils'
|
||||
import { DotCircle, Handle } from './misc'
|
||||
import useShapeDef from 'hooks/useShape'
|
||||
import useShapesToRender from 'hooks/useShapesToRender'
|
||||
|
||||
export default function Defs(): JSX.Element {
|
||||
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
||||
|
||||
const currentPageShapeIds = useSelector(({ data }) => {
|
||||
return Object.values(getPage(data).shapes)
|
||||
.filter(Boolean)
|
||||
.filter((shape) => !getShapeUtils(shape).isForeignObject)
|
||||
.sort((a, b) => a.childIndex - b.childIndex)
|
||||
.map((shape) => shape.id)
|
||||
}, deepCompareArrays)
|
||||
const shapeIdsToRender = useShapesToRender()
|
||||
|
||||
return (
|
||||
<defs>
|
||||
{currentPageShapeIds.map((id) => (
|
||||
{shapeIdsToRender.map((id) => (
|
||||
<Def key={id} id={id} />
|
||||
))}
|
||||
<DotCircle id="dot" r={4} />
|
||||
<Handle id="handle" r={4} />
|
||||
<filter id="expand">
|
||||
<feMorphology operator="dilate" radius={2 / zoom} />
|
||||
</filter>
|
||||
<ExpandDef />
|
||||
</defs>
|
||||
)
|
||||
}
|
||||
|
@ -43,3 +34,12 @@ const Def = memo(function Def({ id }: { id: string }) {
|
|||
{ id, ...style }
|
||||
)
|
||||
})
|
||||
|
||||
function ExpandDef() {
|
||||
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
||||
return (
|
||||
<filter id="expand">
|
||||
<feMorphology operator="dilate" radius={2 / zoom} />
|
||||
</filter>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { useSelector } from 'state'
|
||||
import { Bounds, PageState } from 'types'
|
||||
import {
|
||||
deepCompareArrays,
|
||||
getPage,
|
||||
getViewport,
|
||||
boundsCollide,
|
||||
boundsContain,
|
||||
} from 'utils'
|
||||
import Shape from './shape'
|
||||
import usePageShapes from 'hooks/usePageShapes'
|
||||
|
||||
/*
|
||||
On each state change, compare node ids of all shapes
|
||||
|
@ -18,30 +10,8 @@ here; and still cheaper than any other pattern I've found.
|
|||
|
||||
const noOffset = [0, 0]
|
||||
|
||||
const viewportCache = new WeakMap<PageState, Bounds>()
|
||||
|
||||
export default function Page(): JSX.Element {
|
||||
const currentPageShapeIds = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
const pageState = s.data.pageStates[page.id]
|
||||
|
||||
if (!viewportCache.has(pageState)) {
|
||||
const viewport = getViewport(s.data)
|
||||
viewportCache.set(pageState, viewport)
|
||||
}
|
||||
|
||||
const viewport = viewportCache.get(pageState)
|
||||
|
||||
return s.values.currentShapes
|
||||
.filter((shape) => {
|
||||
const shapeBounds = getShapeUtils(shape).getBounds(shape)
|
||||
return (
|
||||
boundsContain(viewport, shapeBounds) ||
|
||||
boundsCollide(viewport, shapeBounds)
|
||||
)
|
||||
})
|
||||
.map((shape) => shape.id)
|
||||
}, deepCompareArrays)
|
||||
const currentPageShapeIds = usePageShapes()
|
||||
|
||||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||
|
||||
|
|
36
hooks/usePageShapes.ts
Normal file
36
hooks/usePageShapes.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useSelector } from 'state'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { PageState, Bounds } from 'types'
|
||||
import {
|
||||
boundsCollide,
|
||||
boundsContain,
|
||||
deepCompareArrays,
|
||||
getPage,
|
||||
getViewport,
|
||||
} from 'utils'
|
||||
|
||||
const viewportCache = new WeakMap<PageState, Bounds>()
|
||||
|
||||
export default function usePageShapes(): string[] {
|
||||
return useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
const pageState = s.data.pageStates[page.id]
|
||||
|
||||
if (!viewportCache.has(pageState)) {
|
||||
const viewport = getViewport(s.data)
|
||||
viewportCache.set(pageState, viewport)
|
||||
}
|
||||
|
||||
const viewport = viewportCache.get(pageState)
|
||||
|
||||
return s.values.currentShapes
|
||||
.filter((shape) => {
|
||||
const shapeBounds = getShapeUtils(shape).getBounds(shape)
|
||||
return (
|
||||
boundsContain(viewport, shapeBounds) ||
|
||||
boundsCollide(viewport, shapeBounds)
|
||||
)
|
||||
})
|
||||
.map((shape) => shape.id)
|
||||
}, deepCompareArrays)
|
||||
}
|
13
hooks/useShapesToRender.ts
Normal file
13
hooks/useShapesToRender.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { useSelector } from 'state'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { deepCompareArrays, getPage } from 'utils'
|
||||
|
||||
export default function useShapesToRender(): string[] {
|
||||
return useSelector(
|
||||
(s) =>
|
||||
Object.values(getPage(s.data).shapes)
|
||||
.filter((shape) => shape && !getShapeUtils(shape).isForeignObject)
|
||||
.map((shape) => shape.id),
|
||||
deepCompareArrays
|
||||
)
|
||||
}
|
|
@ -92,4 +92,4 @@
|
|||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PointerInfo } from 'types'
|
||||
import { DrawShape, PointerInfo } from 'types'
|
||||
import {
|
||||
getCameraZoom,
|
||||
getCurrentCamera,
|
||||
|
@ -31,9 +31,14 @@ export function fastDrawUpdate(info: PointerInfo): void {
|
|||
|
||||
const selectedId = setToArray(getSelectedIds(data))[0]
|
||||
|
||||
const shape = data.document.pages[data.currentPageId].shapes[selectedId]
|
||||
const shape = data.document.pages[data.currentPageId].shapes[
|
||||
selectedId
|
||||
] as DrawShape
|
||||
|
||||
data.document.pages[data.currentPageId].shapes[selectedId] = { ...shape }
|
||||
;(data.document.pages[data.currentPageId].shapes[selectedId] as DrawShape) = {
|
||||
...shape,
|
||||
points: [...shape.points],
|
||||
}
|
||||
|
||||
state.forceData(freeze(data))
|
||||
}
|
||||
|
|
|
@ -104,7 +104,10 @@ export default class BrushSession extends BaseSession {
|
|||
// Update the points and update the shape's parents.
|
||||
const shape = getShape(data, snapshot.id) as DrawShape
|
||||
|
||||
getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
|
||||
// Note: Normally we would want to spread the points to create a new
|
||||
// array, however we create the new array in hacks/fastDrawUpdate.
|
||||
getShapeUtils(shape).setProperty(shape, 'points', this.points)
|
||||
|
||||
updateParents(data, [shape.id])
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
},
|
||||
|
||||
shouldRender(shape, prev) {
|
||||
// return true
|
||||
return shape.points !== prev.points || shape.style !== prev.style
|
||||
},
|
||||
|
||||
|
@ -61,7 +60,9 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
|
||||
if (points.length > 0 && points.length < 3) {
|
||||
return (
|
||||
<circle id={id} r={+styles.strokeWidth * 0.618} fill={styles.stroke} />
|
||||
<g id={id}>
|
||||
<circle r={+styles.strokeWidth * 0.618} fill={styles.stroke} />
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue