Removes expensive immer current calls

This commit is contained in:
Steve Ruiz 2021-06-29 15:54:46 +01:00
parent e4b154950f
commit 364dbe0fd9
16 changed files with 118 additions and 85 deletions

View file

@ -6,6 +6,7 @@ import tld from 'utils/tld'
import { DotCircle, Handle } from './misc' import { DotCircle, Handle } from './misc'
import useShapeDef from 'hooks/useShape' import useShapeDef from 'hooks/useShape'
import useShapesToRender from 'hooks/useShapesToRender' import useShapesToRender from 'hooks/useShapesToRender'
import styled from 'styles'
export default function Defs(): JSX.Element { export default function Defs(): JSX.Element {
const shapeIdsToRender = useShapesToRender() const shapeIdsToRender = useShapesToRender()
@ -15,6 +16,7 @@ export default function Defs(): JSX.Element {
<DotCircle id="dot" r={4} /> <DotCircle id="dot" r={4} />
<Handle id="handle" r={4} /> <Handle id="handle" r={4} />
<ExpandDef /> <ExpandDef />
<ShadowDef />
{shapeIdsToRender.map((id) => ( {shapeIdsToRender.map((id) => (
<Def key={id} id={id} /> <Def key={id} id={id} />
))} ))}
@ -41,9 +43,22 @@ function Def({ id }: { id: string }) {
function ExpandDef() { function ExpandDef() {
const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
return ( return (
<filter id="expand"> <filter id="expand">
<feMorphology operator="dilate" radius={2 / zoom} /> <feMorphology operator="dilate" radius={2 / zoom} />
</filter> </filter>
) )
} }
function ShadowDef() {
return (
<filter id="hover">
<StyledShadow dx="0" dy="0" stdDeviation="1.2" floodOpacity="1" />
</filter>
)
}
const StyledShadow = styled('feDropShadow', {
floodColor: '$selected',
})

View file

@ -37,7 +37,6 @@ function HoveredShape({ id }: { id: string }) {
const StyledHoverShape = styled('use', { const StyledHoverShape = styled('use', {
stroke: '$selected', stroke: '$selected',
filter: 'url(#expand)',
opacity: 0.1, opacity: 0.1,
}) })

View file

@ -1,26 +1,34 @@
import Command from './command' import Command from './command'
import history from '../history' import history from '../history'
import { Data, GroupShape, ShapeType } from 'types' import { Data, GroupShape, ShapeType } from 'types'
import { getCommonBounds } from 'utils' import { deepClone, getCommonBounds } from 'utils'
import { current } from 'immer'
import tld from 'utils/tld' import tld from 'utils/tld'
import { createShape, getShapeUtils } from 'state/shape-utils' import { createShape, getShapeUtils } from 'state/shape-utils'
import commands from '.' import commands from '.'
export default function groupCommand(data: Data): void { export default function groupCommand(data: Data): void {
const cData = current(data) const { currentPageId } = data
const { currentPageId } = cData
const oldSelectedIds = tld.getSelectedIds(cData) const oldSelectedIds = tld.getSelectedIds(data)
const initialShapes = tld const initialShapes = tld
.getSelectedShapes(cData) .getSelectedShapes(data)
.sort((a, b) => a.childIndex - b.childIndex) .sort((a, b) => a.childIndex - b.childIndex)
.map((shape) => deepClone(shape))
const isAllSameParent = initialShapes.every( const isAllSameParent = initialShapes.every(
(shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId (shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
) )
// Do we need to ungroup the selected shapes shapes, rather than group them?
if (isAllSameParent && initialShapes[0]?.parentId !== currentPageId) {
const parent = tld.getShape(data, initialShapes[0]?.parentId) as GroupShape
if (parent.children.length === initialShapes.length) {
commands.ungroup(data)
return
}
}
let newGroupParentId: string let newGroupParentId: string
const initialShapeIds = initialShapes.map((s) => s.id) const initialShapeIds = initialShapes.map((s) => s.id)
@ -34,19 +42,11 @@ export default function groupCommand(data: Data): void {
if (isAllSameParent) { if (isAllSameParent) {
const parentId = initialShapes[0].parentId const parentId = initialShapes[0].parentId
if (parentId === currentPageId) { if (parentId === currentPageId) {
// Create the new group on the current page
newGroupParentId = currentPageId newGroupParentId = currentPageId
} else { } else {
// Are all of the parent's children selected? // Create the new group as a child of the shapes' current parent group
const parent = tld.getShape(data, parentId) as GroupShape newGroupParentId = parentId
if (parent.children.length === initialShapes.length) {
// !!! Hey! We're not going any further. We need to ungroup those shapes.
commands.ungroup(data)
return
} else {
// Make the group inside of the current group
newGroupParentId = parentId
}
} }
} else { } else {
// Find the least-deep parent among the shapes and add the group as a child // Find the least-deep parent among the shapes and add the group as a child

View file

@ -2,22 +2,20 @@ import Command from './command'
import history from '../history' import history from '../history'
import { Data, ShapeStyles } from 'types' import { Data, ShapeStyles } from 'types'
import tld from 'utils/tld' import tld from 'utils/tld'
import { setToArray } from 'utils' import { deepClone, setToArray } from 'utils'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { current } from 'immer'
export default function styleCommand( export default function styleCommand(
data: Data, data: Data,
styles: Partial<ShapeStyles> styles: Partial<ShapeStyles>
): void { ): void {
const cData = current(data) const page = tld.getPage(data)
const page = tld.getPage(cData)
const selectedIds = setToArray(tld.getSelectedIds(data)) const selectedIds = setToArray(tld.getSelectedIds(data))
const shapesToStyle = selectedIds const shapesToStyle = selectedIds
.flatMap((id) => tld.getDocumentBranch(data, id)) .flatMap((id) => tld.getDocumentBranch(data, id))
.map((id) => page.shapes[id]) .map((id) => deepClone(page.shapes[id]))
history.execute( history.execute(
data, data,

View file

@ -1,9 +1,9 @@
import Command from './command' import Command from './command'
import history from '../history' import history from '../history'
import { Data } from 'types' import { Data } from 'types'
import { current } from 'immer'
import { TransformSingleSnapshot } from 'state/sessions/transform-single-session' import { TransformSingleSnapshot } from 'state/sessions/transform-single-session'
import tld from 'utils/tld' import tld from 'utils/tld'
import { deepClone } from 'utils'
export default function transformSingleCommand( export default function transformSingleCommand(
data: Data, data: Data,
@ -11,7 +11,7 @@ export default function transformSingleCommand(
after: TransformSingleSnapshot, after: TransformSingleSnapshot,
isCreating: boolean isCreating: boolean
): void { ): void {
const shape = current(tld.getPage(data).shapes[after.id]) const shape = deepClone(tld.getPage(data).shapes[after.id])
history.execute( history.execute(
data, data,

View file

@ -2,8 +2,7 @@ import { ArrowShape, Data } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer' import { deepClone, getBoundsFromPoints, setToArray } from 'utils'
import { getBoundsFromPoints, setToArray } from 'utils'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import tld from 'utils/tld' import tld from 'utils/tld'
@ -110,7 +109,7 @@ export default class ArrowSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getArrowSnapshot(data: Data, id: string) { export function getArrowSnapshot(data: Data, id: string) {
const initialShape = tld.getPage(current(data)).shapes[id] as ArrowShape const initialShape = deepClone(tld.getPage(data).shapes[id]) as ArrowShape
return { return {
id, id,

View file

@ -1,8 +1,7 @@
import { current } from 'immer'
import { Bounds, Data, ShapeType } from 'types' import { Bounds, Data, ShapeType } from 'types'
import BaseSession from './base-session' import BaseSession from './base-session'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { getBoundsFromPoints, setToArray } from 'utils' import { deepClone, getBoundsFromPoints, setToArray } from 'utils'
import vec from 'utils/vec' import vec from 'utils/vec'
import tld from 'utils/tld' import tld from 'utils/tld'
@ -67,7 +66,7 @@ export default class BrushSession extends BaseSession {
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getBrushSnapshot(data: Data) { export function getBrushSnapshot(data: Data) {
const cData = current(data) const cData = data
const { selectedIds } = tld.getPageState(cData) const { selectedIds } = tld.getPageState(cData)
const shapesToTest = tld const shapesToTest = tld
@ -76,6 +75,7 @@ export function getBrushSnapshot(data: Data) {
.filter( .filter(
(shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId)) (shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId))
) )
.map(deepClone)
return { return {
selectedIds: setToArray(selectedIds), selectedIds: setToArray(selectedIds),
@ -84,9 +84,10 @@ export function getBrushSnapshot(data: Data) {
return [ return [
shape.id, shape.id,
{ {
selectId: tld.getTopParentId(cData, shape.id), selectId: tld.getTopParentId(data, shape.id),
test: (bounds: Bounds) => test: (bounds: Bounds) => {
getShapeUtils(shape).hitTestBounds(shape, bounds), return getShapeUtils(shape).hitTestBounds(shape, bounds)
},
}, },
] ]
}) })

View file

@ -1,8 +1,7 @@
import { current } from 'immer'
import { Data, DrawShape } from 'types' import { Data, DrawShape } from 'types'
import BaseSession from './base-session' import BaseSession from './base-session'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { getBoundsFromPoints } from 'utils' import { deepClone, getBoundsFromPoints } from 'utils'
import tld from 'utils/tld' import tld from 'utils/tld'
import vec from 'utils/vec' import vec from 'utils/vec'
import commands from 'state/commands' import commands from 'state/commands'
@ -140,8 +139,8 @@ export default class DrawSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getDrawSnapshot(data: Data, shapeId: string) { export function getDrawSnapshot(data: Data, shapeId: string) {
const page = tld.getPage(current(data)) const page = tld.getPage(data)
const { points, point } = page.shapes[shapeId] as DrawShape const { points, point } = deepClone(page.shapes[shapeId]) as DrawShape
return { return {
id: shapeId, id: shapeId,

View file

@ -1,9 +1,9 @@
import { Data, Shape } from 'types' import { Data, Shape } from 'types'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import tld from 'utils/tld' import tld from 'utils/tld'
import { deepClone } from 'utils'
export default class EditSession extends BaseSession { export default class EditSession extends BaseSession {
snapshot: EditSnapshot snapshot: EditSnapshot
@ -35,7 +35,7 @@ export default class EditSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getEditSnapshot(data: Data) { export function getEditSnapshot(data: Data) {
const initialShape = tld.getSelectedShapes(current(data))[0] const initialShape = deepClone(tld.getSelectedShapes(data)[0])
return { return {
currentPageId: data.currentPageId, currentPageId: data.currentPageId,

View file

@ -2,9 +2,9 @@ import { Data } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer'
import tld from 'utils/tld' import tld from 'utils/tld'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { deepClone } from 'utils'
export default class HandleSession extends BaseSession { export default class HandleSession extends BaseSession {
delta = [0, 0] delta = [0, 0]
@ -69,7 +69,7 @@ export function getHandleSnapshot(
shapeId: string, shapeId: string,
handleId: string handleId: string
) { ) {
const initialShape = tld.getPage(current(data)).shapes[shapeId] const initialShape = deepClone(tld.getShape(data, shapeId))
return { return {
currentPageId: data.currentPageId, currentPageId: data.currentPageId,

View file

@ -2,12 +2,10 @@ import { Data, ShapeType } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer'
import { import {
clampToRotationToSegments, clampToRotationToSegments,
getBoundsCenter, getBoundsCenter,
getCommonBounds, getCommonBounds,
setToArray,
} from 'utils' } from 'utils'
import tld from 'utils/tld' import tld from 'utils/tld'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
@ -95,14 +93,7 @@ export default class RotateSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getRotateSnapshot(data: Data) { export function getRotateSnapshot(data: Data) {
const cData = current(data) const initialShapes = tld.getSelectedBranchSnapshot(data)
const page = tld.getPage(cData)
const initialShapes = setToArray(tld.getSelectedIds(data))
.flatMap((id) =>
tld.getDocumentBranch(cData, id).map((id) => page.shapes[id])
)
.filter((shape) => !shape.isLocked)
const hasUnlockedShapes = initialShapes.length > 0 const hasUnlockedShapes = initialShapes.length > 0

View file

@ -11,7 +11,6 @@ import {
getCommonBounds, getCommonBounds,
getRelativeTransformedBoundingBox, getRelativeTransformedBoundingBox,
getTransformedBoundingBox, getTransformedBoundingBox,
setToArray,
} from 'utils' } from 'utils'
export default class TransformSession extends BaseSession { export default class TransformSession extends BaseSession {
@ -117,14 +116,8 @@ export default class TransformSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getTransformSnapshot(data: Data, transformType: Edge | Corner) { export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
const { currentPageId } = data const { currentPageId } = data
const page = tld.getPage(data)
const initialShapes = setToArray(tld.getSelectedIds(data)) const initialShapes = tld.getSelectedBranchSnapshot(data)
.flatMap((id) =>
tld.getDocumentBranch(data, id).map((id) => page.shapes[id])
)
.filter((shape) => !shape.isLocked)
.map((shape) => deepClone(shape))
const hasUnlockedShapes = initialShapes.length > 0 const hasUnlockedShapes = initialShapes.length > 0

View file

@ -2,9 +2,8 @@ import { Data, Edge, Corner } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { getTransformedBoundingBox } from 'utils' import { deepClone, getTransformedBoundingBox } from 'utils'
import tld from 'utils/tld' import tld from 'utils/tld'
export default class TransformSingleSession extends BaseSession { export default class TransformSingleSession extends BaseSession {
@ -85,7 +84,7 @@ export function getTransformSingleSnapshot(
data: Data, data: Data,
transformType: Edge | Corner transformType: Edge | Corner
) { ) {
const shape = tld.getSelectedShapes(current(data))[0] const shape = deepClone(tld.getSelectedShapes(data)[0])
const bounds = getShapeUtils(shape).getBounds(shape) const bounds = getShapeUtils(shape).getBounds(shape)
return { return {

View file

@ -2,7 +2,6 @@ import { Data, GroupShape, Shape, ShapeType } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import BaseSession from './base-session' import BaseSession from './base-session'
import commands from 'state/commands' import commands from 'state/commands'
import { current } from 'immer'
import { uniqueId } from 'utils' import { uniqueId } from 'utils'
import { getShapeUtils } from 'state/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import tld from 'utils/tld' import tld from 'utils/tld'
@ -174,32 +173,28 @@ export default class TranslateSession extends BaseSession {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getTranslateSnapshot(data: Data) { export function getTranslateSnapshot(data: Data) {
const cData = current(data) const page = tld.getPage(data)
// Get selected shapes const selectedShapes = tld.getSelectedShapeSnapshot(data)
// Filter out the locked shapes
// Collect the branch children for each remaining shape
// Filter out doubles using a set
// End up with an array of ids for all of the shapes that will change
// Map into shapes from data snapshot
const page = tld.getPage(cData)
const selectedShapes = tld
.getSelectedShapes(cData)
.filter((shape) => !shape.isLocked)
const hasUnlockedShapes = selectedShapes.length > 0 const hasUnlockedShapes = selectedShapes.length > 0
const parents = Array.from( const initialParents = Array.from(
new Set(selectedShapes.map((s) => s.parentId)).values() new Set(selectedShapes.map((s) => s.parentId)).values()
) )
.filter((id) => id !== data.currentPageId) .filter((id) => id !== data.currentPageId)
.map((id) => page.shapes[id]) .map((id) => {
const shape = page.shapes[id]
return {
id: shape.id,
children: shape.children,
}
})
return { return {
hasUnlockedShapes, hasUnlockedShapes,
currentPageId: data.currentPageId, currentPageId: data.currentPageId,
initialParents: parents.map(({ id, children }) => ({ id, children })), initialParents,
initialShapes: selectedShapes.map(({ id, point, parentId }) => ({ initialShapes: selectedShapes.map(({ id, point, parentId }) => ({
id, id,
point, point,
@ -211,9 +206,8 @@ export function getTranslateSnapshot(data: Data) {
const clone: Shape = { const clone: Shape = {
...shape, ...shape,
id: uniqueId(), id: uniqueId(),
parentId: shape.parentId, parentId: shape.parentId,
childIndex: tld.getChildIndexAbove(cData, shape.id), childIndex: tld.getChildIndexAbove(data, shape.id),
isGenerated: false, isGenerated: false,
} }

View file

@ -7,7 +7,7 @@ import history from './history'
import storage from './storage' import storage from './storage'
import clipboard from './clipboard' import clipboard from './clipboard'
import * as Sessions from './sessions' import * as Sessions from './sessions'
import pusher from './coop/client-supa' import coopClient from './coop/client-pusher'
import commands from './commands' import commands from './commands'
import { import {
getCommonBounds, getCommonBounds,
@ -1162,7 +1162,7 @@ const state = createState({
}, },
sendRtCursorMove(data, payload: PointerInfo) { sendRtCursorMove(data, payload: PointerInfo) {
const point = tld.screenToWorld(payload.point, data) const point = tld.screenToWorld(payload.point, data)
pusher.moveCursor(data.currentPageId, point) coopClient.moveCursor(data.currentPageId, point)
}, },
moveRtCursor( moveRtCursor(
data, data,
@ -1214,7 +1214,7 @@ const state = createState({
}, },
connectToRoom(data, payload: { id: string }) { connectToRoom(data, payload: { id: string }) {
data.room = { id: payload.id, status: 'connecting', peers: {} } data.room = { id: payload.id, status: 'connecting', peers: {} }
pusher.connect(payload.id) coopClient.connect(payload.id)
}, },
resetPageState(data) { resetPageState(data) {

View file

@ -326,6 +326,40 @@ export default class ProjectUtils {
/* ----------------- Shapes Related ----------------- */ /* ----------------- Shapes Related ----------------- */
/**
* Get a deep-cloned
* @param data
* @param fn
*/
static getSelectedBranchSnapshot<K>(
data: Data,
fn: <T extends Shape>(shape: T) => K
): ({ id: string } & K)[]
static getSelectedBranchSnapshot(data: Data): Shape[]
static getSelectedBranchSnapshot<
K,
F extends <T extends Shape>(shape: T) => K
>(data: Data, fn?: F): (Shape | K)[] {
const page = this.getPage(data)
const copies = setToArray(this.getSelectedIds(data))
.flatMap((id) =>
this.getDocumentBranch(data, id).map((id) => page.shapes[id])
)
.filter((shape) => !shape.isLocked)
.map(deepClone)
if (fn !== undefined) {
return copies.map((shape) => ({ id: shape.id, ...fn(shape) }))
}
return copies
}
/**
* Get a deep-cloned array of shapes
* @param data
*/
static getSelectedShapeSnapshot(data: Data): Shape[] static getSelectedShapeSnapshot(data: Data): Shape[]
static getSelectedShapeSnapshot<K>( static getSelectedShapeSnapshot<K>(
data: Data, data: Data,
@ -337,7 +371,7 @@ export default class ProjectUtils {
>(data: Data, fn?: F): (Shape | K)[] { >(data: Data, fn?: F): (Shape | K)[] {
const copies = this.getSelectedShapes(data) const copies = this.getSelectedShapes(data)
.filter((shape) => !shape.isLocked) .filter((shape) => !shape.isLocked)
.map((shape) => deepClone(shape)) .map(deepClone)
if (fn !== undefined) { if (fn !== undefined) {
return copies.map((shape) => ({ id: shape.id, ...fn(shape) })) return copies.map((shape) => ({ id: shape.id, ...fn(shape) }))
@ -346,6 +380,17 @@ export default class ProjectUtils {
return copies return copies
} }
/**
* Get an array of all unique parentIds among a set of shapes.
* @param data
* @param shapes
*/
static getUniqueParentIds(data: Data, shapes: Shape[]): string[] {
return Array.from(new Set(shapes.map((s) => s.parentId)).values()).filter(
(id) => id !== data.currentPageId
)
}
/** /**
* Make an arbitrary change to shape. * Make an arbitrary change to shape.
* @param data * @param data