From 364dbe0fd9f9f777cc6741902c664fb3a26edb46 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Tue, 29 Jun 2021 15:54:46 +0100 Subject: [PATCH] Removes expensive immer current calls --- components/canvas/defs.tsx | 15 +++++++ components/canvas/hovered-shape.tsx | 1 - state/commands/group.ts | 34 ++++++++-------- state/commands/style.ts | 8 ++-- state/commands/transform-single.ts | 4 +- state/sessions/arrow-session.ts | 5 +-- state/sessions/brush-session.ts | 13 +++--- state/sessions/draw-session.ts | 7 ++-- state/sessions/edit-session.ts | 4 +- state/sessions/handle-session.ts | 4 +- state/sessions/rotate-session.ts | 11 +---- state/sessions/transform-session.ts | 9 +---- state/sessions/transform-single-session.ts | 5 +-- state/sessions/translate-session.ts | 30 ++++++-------- state/state.ts | 6 +-- utils/tld.ts | 47 +++++++++++++++++++++- 16 files changed, 118 insertions(+), 85 deletions(-) diff --git a/components/canvas/defs.tsx b/components/canvas/defs.tsx index b1ea98329..0db32b429 100644 --- a/components/canvas/defs.tsx +++ b/components/canvas/defs.tsx @@ -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 { + {shapeIdsToRender.map((id) => ( ))} @@ -41,9 +43,22 @@ function Def({ id }: { id: string }) { function ExpandDef() { const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) + return ( ) } + +function ShadowDef() { + return ( + + + + ) +} + +const StyledShadow = styled('feDropShadow', { + floodColor: '$selected', +}) diff --git a/components/canvas/hovered-shape.tsx b/components/canvas/hovered-shape.tsx index b3e60dfe3..cb05e9250 100644 --- a/components/canvas/hovered-shape.tsx +++ b/components/canvas/hovered-shape.tsx @@ -37,7 +37,6 @@ function HoveredShape({ id }: { id: string }) { const StyledHoverShape = styled('use', { stroke: '$selected', - filter: 'url(#expand)', opacity: 0.1, }) diff --git a/state/commands/group.ts b/state/commands/group.ts index d441c5f25..382be5e64 100644 --- a/state/commands/group.ts +++ b/state/commands/group.ts @@ -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 diff --git a/state/commands/style.ts b/state/commands/style.ts index be5d017b5..4c320d1da 100644 --- a/state/commands/style.ts +++ b/state/commands/style.ts @@ -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 ): 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, diff --git a/state/commands/transform-single.ts b/state/commands/transform-single.ts index 58d54d2ee..6e8bb32d5 100644 --- a/state/commands/transform-single.ts +++ b/state/commands/transform-single.ts @@ -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, diff --git a/state/sessions/arrow-session.ts b/state/sessions/arrow-session.ts index 879c46537..da545861b 100644 --- a/state/sessions/arrow-session.ts +++ b/state/sessions/arrow-session.ts @@ -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, diff --git a/state/sessions/brush-session.ts b/state/sessions/brush-session.ts index b3953faf1..de9971ad7 100644 --- a/state/sessions/brush-session.ts +++ b/state/sessions/brush-session.ts @@ -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) + }, }, ] }) diff --git a/state/sessions/draw-session.ts b/state/sessions/draw-session.ts index a756aaa80..c36d64493 100644 --- a/state/sessions/draw-session.ts +++ b/state/sessions/draw-session.ts @@ -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, diff --git a/state/sessions/edit-session.ts b/state/sessions/edit-session.ts index addeb4646..41b31b29a 100644 --- a/state/sessions/edit-session.ts +++ b/state/sessions/edit-session.ts @@ -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, diff --git a/state/sessions/handle-session.ts b/state/sessions/handle-session.ts index 471e06991..6500be207 100644 --- a/state/sessions/handle-session.ts +++ b/state/sessions/handle-session.ts @@ -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, diff --git a/state/sessions/rotate-session.ts b/state/sessions/rotate-session.ts index 19585d72f..3e1497f47 100644 --- a/state/sessions/rotate-session.ts +++ b/state/sessions/rotate-session.ts @@ -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 diff --git a/state/sessions/transform-session.ts b/state/sessions/transform-session.ts index d328b3f19..3f4e475bd 100644 --- a/state/sessions/transform-session.ts +++ b/state/sessions/transform-session.ts @@ -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 diff --git a/state/sessions/transform-single-session.ts b/state/sessions/transform-single-session.ts index b5d7db5dc..d98a110ef 100644 --- a/state/sessions/transform-single-session.ts +++ b/state/sessions/transform-single-session.ts @@ -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 { diff --git a/state/sessions/translate-session.ts b/state/sessions/translate-session.ts index 6edfbbd65..d1a3ef5c4 100644 --- a/state/sessions/translate-session.ts +++ b/state/sessions/translate-session.ts @@ -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, } diff --git a/state/state.ts b/state/state.ts index c155840fa..0552926c6 100644 --- a/state/state.ts +++ b/state/state.ts @@ -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) { diff --git a/utils/tld.ts b/utils/tld.ts index b653593ea..91b76d313 100644 --- a/utils/tld.ts +++ b/utils/tld.ts @@ -326,6 +326,40 @@ export default class ProjectUtils { /* ----------------- Shapes Related ----------------- */ + /** + * Get a deep-cloned + * @param data + * @param fn + */ + static getSelectedBranchSnapshot( + data: Data, + fn: (shape: T) => K + ): ({ id: string } & K)[] + static getSelectedBranchSnapshot(data: Data): Shape[] + static getSelectedBranchSnapshot< + K, + F extends (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, @@ -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