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 useShapeDef from 'hooks/useShape'
import useShapesToRender from 'hooks/useShapesToRender'
import styled from 'styles'
export default function Defs(): JSX.Element {
const shapeIdsToRender = useShapesToRender()
@ -15,6 +16,7 @@ export default function Defs(): JSX.Element {
<DotCircle id="dot" r={4} />
<Handle id="handle" r={4} />
<ExpandDef />
<ShadowDef />
{shapeIdsToRender.map((id) => (
<Def key={id} id={id} />
))}
@ -41,9 +43,22 @@ function Def({ id }: { id: string }) {
function ExpandDef() {
const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
return (
<filter id="expand">
<feMorphology operator="dilate" radius={2 / zoom} />
</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', {
stroke: '$selected',
filter: 'url(#expand)',
opacity: 0.1,
})

View file

@ -1,26 +1,34 @@
import Command from './command'
import history from '../history'
import { Data, GroupShape, ShapeType } from 'types'
import { getCommonBounds } from 'utils'
import { current } from 'immer'
import { deepClone, getCommonBounds } from 'utils'
import tld from 'utils/tld'
import { createShape, getShapeUtils } from 'state/shape-utils'
import commands from '.'
export default function groupCommand(data: Data): void {
const cData = current(data)
const { currentPageId } = cData
const { currentPageId } = data
const oldSelectedIds = tld.getSelectedIds(cData)
const oldSelectedIds = tld.getSelectedIds(data)
const initialShapes = tld
.getSelectedShapes(cData)
.getSelectedShapes(data)
.sort((a, b) => a.childIndex - b.childIndex)
.map((shape) => deepClone(shape))
const isAllSameParent = initialShapes.every(
(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
const initialShapeIds = initialShapes.map((s) => s.id)
@ -34,19 +42,11 @@ export default function groupCommand(data: Data): void {
if (isAllSameParent) {
const parentId = initialShapes[0].parentId
if (parentId === currentPageId) {
// Create the new group on the current page
newGroupParentId = currentPageId
} else {
// Are all of the parent's children selected?
const parent = tld.getShape(data, parentId) as GroupShape
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
}
// Create the new group as a child of the shapes' current parent group
newGroupParentId = parentId
}
} else {
// 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 { Data, ShapeStyles } from 'types'
import tld from 'utils/tld'
import { setToArray } from 'utils'
import { deepClone, setToArray } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
import { current } from 'immer'
export default function styleCommand(
data: Data,
styles: Partial<ShapeStyles>
): void {
const cData = current(data)
const page = tld.getPage(cData)
const page = tld.getPage(data)
const selectedIds = setToArray(tld.getSelectedIds(data))
const shapesToStyle = selectedIds
.flatMap((id) => tld.getDocumentBranch(data, id))
.map((id) => page.shapes[id])
.map((id) => deepClone(page.shapes[id]))
history.execute(
data,

View file

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

View file

@ -2,8 +2,7 @@ import { ArrowShape, Data } from 'types'
import vec from 'utils/vec'
import BaseSession from './base-session'
import commands from 'state/commands'
import { current } from 'immer'
import { getBoundsFromPoints, setToArray } from 'utils'
import { deepClone, getBoundsFromPoints, setToArray } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
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
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 {
id,

View file

@ -1,8 +1,7 @@
import { current } from 'immer'
import { Bounds, Data, ShapeType } from 'types'
import BaseSession from './base-session'
import { getShapeUtils } from 'state/shape-utils'
import { getBoundsFromPoints, setToArray } from 'utils'
import { deepClone, getBoundsFromPoints, setToArray } from 'utils'
import vec from 'utils/vec'
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
export function getBrushSnapshot(data: Data) {
const cData = current(data)
const cData = data
const { selectedIds } = tld.getPageState(cData)
const shapesToTest = tld
@ -76,6 +75,7 @@ export function getBrushSnapshot(data: Data) {
.filter(
(shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId))
)
.map(deepClone)
return {
selectedIds: setToArray(selectedIds),
@ -84,9 +84,10 @@ export function getBrushSnapshot(data: Data) {
return [
shape.id,
{
selectId: tld.getTopParentId(cData, shape.id),
test: (bounds: Bounds) =>
getShapeUtils(shape).hitTestBounds(shape, bounds),
selectId: tld.getTopParentId(data, shape.id),
test: (bounds: Bounds) => {
return getShapeUtils(shape).hitTestBounds(shape, bounds)
},
},
]
})

View file

@ -1,8 +1,7 @@
import { current } from 'immer'
import { Data, DrawShape } from 'types'
import BaseSession from './base-session'
import { getShapeUtils } from 'state/shape-utils'
import { getBoundsFromPoints } from 'utils'
import { deepClone, getBoundsFromPoints } from 'utils'
import tld from 'utils/tld'
import vec from 'utils/vec'
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
export function getDrawSnapshot(data: Data, shapeId: string) {
const page = tld.getPage(current(data))
const { points, point } = page.shapes[shapeId] as DrawShape
const page = tld.getPage(data)
const { points, point } = deepClone(page.shapes[shapeId]) as DrawShape
return {
id: shapeId,

View file

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

View file

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

View file

@ -2,12 +2,10 @@ import { Data, ShapeType } from 'types'
import vec from 'utils/vec'
import BaseSession from './base-session'
import commands from 'state/commands'
import { current } from 'immer'
import {
clampToRotationToSegments,
getBoundsCenter,
getCommonBounds,
setToArray,
} from 'utils'
import tld from 'utils/tld'
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
export function getRotateSnapshot(data: Data) {
const cData = current(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 initialShapes = tld.getSelectedBranchSnapshot(data)
const hasUnlockedShapes = initialShapes.length > 0

View file

@ -11,7 +11,6 @@ import {
getCommonBounds,
getRelativeTransformedBoundingBox,
getTransformedBoundingBox,
setToArray,
} from 'utils'
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
export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
const { currentPageId } = data
const page = tld.getPage(data)
const initialShapes = setToArray(tld.getSelectedIds(data))
.flatMap((id) =>
tld.getDocumentBranch(data, id).map((id) => page.shapes[id])
)
.filter((shape) => !shape.isLocked)
.map((shape) => deepClone(shape))
const initialShapes = tld.getSelectedBranchSnapshot(data)
const hasUnlockedShapes = initialShapes.length > 0

View file

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

View file

@ -2,7 +2,6 @@ import { Data, GroupShape, Shape, ShapeType } from 'types'
import vec from 'utils/vec'
import BaseSession from './base-session'
import commands from 'state/commands'
import { current } from 'immer'
import { uniqueId } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
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
export function getTranslateSnapshot(data: Data) {
const cData = current(data)
const page = tld.getPage(data)
// Get selected shapes
// 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 selectedShapes = tld.getSelectedShapeSnapshot(data)
const hasUnlockedShapes = selectedShapes.length > 0
const parents = Array.from(
const initialParents = Array.from(
new Set(selectedShapes.map((s) => s.parentId)).values()
)
.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 {
hasUnlockedShapes,
currentPageId: data.currentPageId,
initialParents: parents.map(({ id, children }) => ({ id, children })),
initialParents,
initialShapes: selectedShapes.map(({ id, point, parentId }) => ({
id,
point,
@ -211,9 +206,8 @@ export function getTranslateSnapshot(data: Data) {
const clone: Shape = {
...shape,
id: uniqueId(),
parentId: shape.parentId,
childIndex: tld.getChildIndexAbove(cData, shape.id),
childIndex: tld.getChildIndexAbove(data, shape.id),
isGenerated: false,
}

View file

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

View file

@ -326,6 +326,40 @@ export default class ProjectUtils {
/* ----------------- 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<K>(
data: Data,
@ -337,7 +371,7 @@ export default class ProjectUtils {
>(data: Data, fn?: F): (Shape | K)[] {
const copies = this.getSelectedShapes(data)
.filter((shape) => !shape.isLocked)
.map((shape) => deepClone(shape))
.map(deepClone)
if (fn !== undefined) {
return copies.map((shape) => ({ id: shape.id, ...fn(shape) }))
@ -346,6 +380,17 @@ export default class ProjectUtils {
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.
* @param data