2
0

perf(editor): ️ Improve graph transition perf

This commit is contained in:
Baptiste Arnaud
2022-02-14 18:56:29 +01:00
parent e7d1f5d674
commit 714f7c8ce5
9 changed files with 61 additions and 42 deletions

View File

@@ -59,7 +59,6 @@ export const PreviewDrawer = () => {
useEffect(() => { useEffect(() => {
const onMessageFromBot = (event: MessageEvent) => { const onMessageFromBot = (event: MessageEvent) => {
console.log(event)
if (event.data.typebotInfo) { if (event.data.typebotInfo) {
toast({ description: event.data.typebotInfo }) toast({ description: event.data.typebotInfo })
} }

View File

@@ -18,6 +18,7 @@ export const DrawingEdge = () => {
sourceEndpoints, sourceEndpoints,
targetEndpoints, targetEndpoints,
blocksCoordinates, blocksCoordinates,
graphOffsetY,
} = useGraph() } = useGraph()
const { createEdge } = useTypebot() const { createEdge } = useTypebot()
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
@@ -30,22 +31,20 @@ export const DrawingEdge = () => {
const sourceTop = useMemo(() => { const sourceTop = useMemo(() => {
if (!connectingIds) return 0 if (!connectingIds) return 0
return getEndpointTopOffset( return getEndpointTopOffset(
graphPosition,
sourceEndpoints, sourceEndpoints,
graphOffsetY,
connectingIds.source.itemId ?? connectingIds.source.stepId connectingIds.source.itemId ?? connectingIds.source.stepId
) )
// eslint-disable-next-line react-hooks/exhaustive-deps }, [connectingIds, sourceEndpoints, graphOffsetY])
}, [graphPosition, sourceEndpoints, connectingIds])
const targetTop = useMemo(() => { const targetTop = useMemo(() => {
if (!connectingIds) return 0 if (!connectingIds) return 0
return getEndpointTopOffset( return getEndpointTopOffset(
graphPosition,
targetEndpoints, targetEndpoints,
graphOffsetY,
connectingIds.target?.stepId connectingIds.target?.stepId
) )
// eslint-disable-next-line react-hooks/exhaustive-deps }, [connectingIds, targetEndpoints, graphOffsetY])
}, [graphPosition, targetEndpoints, connectingIds])
const path = useMemo(() => { const path = useMemo(() => {
if (!sourceTop || !sourceBlockCoordinates) return `` if (!sourceTop || !sourceBlockCoordinates) return ``

View File

@@ -24,7 +24,7 @@ export const DropOffEdge = ({
onUnlockProPlanClick, onUnlockProPlanClick,
}: Props) => { }: Props) => {
const { user } = useUser() const { user } = useUser()
const { sourceEndpoints, graphPosition, blocksCoordinates } = useGraph() const { sourceEndpoints, blocksCoordinates, graphOffsetY } = useGraph()
const { publishedTypebot } = useTypebot() const { publishedTypebot } = useTypebot()
const isUserOnFreePlan = isFreePlan(user) const isUserOnFreePlan = isFreePlan(user)
@@ -59,13 +59,12 @@ export const DropOffEdge = ({
const sourceTop = useMemo( const sourceTop = useMemo(
() => () =>
getEndpointTopOffset( getEndpointTopOffset(
graphPosition,
sourceEndpoints, sourceEndpoints,
block?.steps[block.steps.length - 1].id, graphOffsetY,
true block?.steps[block.steps.length - 1].id
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[block?.steps, graphPosition, sourceEndpoints, blocksCoordinates] [block?.steps, sourceEndpoints, blocksCoordinates]
) )
const labelCoordinates = useMemo(() => { const labelCoordinates = useMemo(() => {

View File

@@ -20,9 +20,8 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
previewingEdge, previewingEdge,
sourceEndpoints, sourceEndpoints,
targetEndpoints, targetEndpoints,
graphPosition,
blocksCoordinates, blocksCoordinates,
isReadOnly, graphOffsetY,
} = useGraph() } = useGraph()
const isPreviewing = previewingEdge?.id === edge.id const isPreviewing = previewingEdge?.id === edge.id
@@ -34,25 +33,27 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
const sourceTop = useMemo( const sourceTop = useMemo(
() => () =>
getEndpointTopOffset( getEndpointTopOffset(
graphPosition,
sourceEndpoints, sourceEndpoints,
getSourceEndpointId(edge), graphOffsetY,
isReadOnly getSourceEndpointId(edge)
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[edge, graphPosition, sourceEndpoints, sourceBlockCoordinates?.y] [
sourceBlockCoordinates?.x,
sourceBlockCoordinates?.y,
edge,
sourceEndpoints,
]
) )
const targetTop = useMemo( const targetTop = useMemo(
() => () => getEndpointTopOffset(targetEndpoints, graphOffsetY, edge?.to.stepId),
getEndpointTopOffset(
graphPosition,
targetEndpoints,
edge?.to.stepId,
isReadOnly
),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[graphPosition, targetEndpoints, edge?.to.stepId, targetBlockCoordinates?.y] [
targetBlockCoordinates?.x,
targetBlockCoordinates?.y,
edge?.to.stepId,
targetEndpoints,
]
) )
const path = useMemo(() => { const path = useMemo(() => {

View File

@@ -24,6 +24,8 @@ export const Edges = ({
pos="absolute" pos="absolute"
left="0" left="0"
top="0" top="0"
pointerEvents="none"
shape-rendering="geometricPrecision"
> >
<DrawingEdge /> <DrawingEdge />
{edges.map((edge) => ( {edges.map((edge) => (

View File

@@ -9,6 +9,7 @@ import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
import { Block, DraggableStepType, PublicTypebot, Typebot } from 'models' import { Block, DraggableStepType, PublicTypebot, Typebot } from 'models'
import { generate } from 'short-uuid' import { generate } from 'short-uuid'
import { AnswersCount } from 'services/analytics' import { AnswersCount } from 'services/analytics'
import { useDebounce } from 'use-debounce'
export const Graph = ({ export const Graph = ({
typebot, typebot,
@@ -30,7 +31,9 @@ export const Graph = ({
setGraphPosition, setGraphPosition,
setOpenedStepId, setOpenedStepId,
updateBlockCoordinates, updateBlockCoordinates,
setGraphOffsetY,
} = useGraph() } = useGraph()
const [debouncedGraphPosition] = useDebounce(graphPosition, 200)
const transform = useMemo( const transform = useMemo(
() => () =>
`translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`, `translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`,
@@ -42,8 +45,18 @@ export const Graph = ({
editorContainerRef.current = document.getElementById( editorContainerRef.current = document.getElementById(
'editor-container' 'editor-container'
) as HTMLDivElement ) as HTMLDivElement
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
useEffect(() => {
if (!graphContainerRef.current) return
setGraphOffsetY(
graphContainerRef.current.getBoundingClientRect().top +
debouncedGraphPosition.y
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedGraphPosition])
const handleMouseWheel = (e: WheelEvent) => { const handleMouseWheel = (e: WheelEvent) => {
e.preventDefault() e.preventDefault()
const isPinchingTrackpad = e.ctrlKey const isPinchingTrackpad = e.ctrlKey
@@ -112,15 +125,19 @@ export const Graph = ({
ref={graphContainerRef} ref={graphContainerRef}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
position="relative"
{...props} {...props}
> >
<Flex <Flex
flex="1" flex="1"
boxSize={'200px'} w="full"
maxW={'200px'} h="full"
position="absolute"
style={{ style={{
transform, transform,
}} }}
willChange="transform"
transformOrigin="0px 0px 0px"
> >
<Edges <Edges
edges={typebot?.edges ?? []} edges={typebot?.edges ?? []}

View File

@@ -41,7 +41,7 @@ export type Node = Omit<Block, 'steps'> & {
})[] })[]
} }
const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 } export const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
export type ConnectingIds = { export type ConnectingIds = {
source: Source source: Source
@@ -73,6 +73,8 @@ const graphContext = createContext<{
openedStepId?: string openedStepId?: string
setOpenedStepId: Dispatch<SetStateAction<string | undefined>> setOpenedStepId: Dispatch<SetStateAction<string | undefined>>
isReadOnly: boolean isReadOnly: boolean
graphOffsetY: number
setGraphOffsetY: Dispatch<SetStateAction<number>>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
}>({ }>({
@@ -98,6 +100,7 @@ export const GraphProvider = ({
const [blocksCoordinates, setBlocksCoordinates] = useState<BlocksCoordinates>( const [blocksCoordinates, setBlocksCoordinates] = useState<BlocksCoordinates>(
{} {}
) )
const [graphOffsetY, setGraphOffsetY] = useState(0)
useEffect(() => { useEffect(() => {
setBlocksCoordinates( setBlocksCoordinates(
@@ -150,6 +153,8 @@ export const GraphProvider = ({
blocksCoordinates, blocksCoordinates,
updateBlockCoordinates, updateBlockCoordinates,
isReadOnly, isReadOnly,
graphOffsetY,
setGraphOffsetY,
}} }}
> >
{children} {children}

View File

@@ -8,7 +8,6 @@ import {
Coordinates, Coordinates,
} from 'contexts/GraphContext' } from 'contexts/GraphContext'
import { roundCorners } from 'svg-round-corners' import { roundCorners } from 'svg-round-corners'
import { headerHeight } from 'components/shared/TypebotHeader'
export const computeDropOffPath = ( export const computeDropOffPath = (
sourcePosition: Coordinates, sourcePosition: Coordinates,
@@ -274,20 +273,18 @@ export const computeEdgePathToMouse = ({
} }
export const getEndpointTopOffset = ( export const getEndpointTopOffset = (
graphPosition: Coordinates,
endpoints: IdMap<Endpoint>, endpoints: IdMap<Endpoint>,
endpointId?: string, graphOffsetY: number,
isAnalytics?: boolean endpointId?: string
): number | undefined => { ): number | undefined => {
if (!endpointId) return if (!endpointId) return
const endpointRef = endpoints[endpointId]?.ref const endpointRef = endpoints[endpointId]?.ref
if (!endpointRef) return if (!endpointRef?.current) return
const endpointHeight = 18
return ( return (
8 + endpointRef.current.getBoundingClientRect().top +
(endpointRef.current?.getBoundingClientRect().top ?? 0) - endpointHeight / 2 -
graphPosition.y - graphOffsetY
headerHeight -
(isAnalytics ? 60 : 0)
) )
} }

View File

@@ -6,7 +6,7 @@ const secretKey = process.env.ENCRYPTION_SECRET
export const encrypt = ( export const encrypt = (
data: object data: object
): { encryptedData: string; iv: string } => { ): { encryptedData: string; iv: string } => {
if (!secretKey) throw new Error(`SECRET is not in environment`) if (!secretKey) throw new Error(`ENCRYPTION_SECRET is not in environment`)
const iv = randomBytes(16) const iv = randomBytes(16)
const cipher = createCipheriv(algorithm, secretKey, iv) const cipher = createCipheriv(algorithm, secretKey, iv)
const dataString = JSON.stringify(data) const dataString = JSON.stringify(data)
@@ -20,7 +20,7 @@ export const encrypt = (
} }
export const decrypt = (encryptedData: string, auth: string): object => { export const decrypt = (encryptedData: string, auth: string): object => {
if (!secretKey) throw new Error(`SECRET is not in environment`) if (!secretKey) throw new Error(`ENCRYPTION_SECRET is not in environment`)
const [iv, tag] = auth.split('.') const [iv, tag] = auth.split('.')
const decipher = createDecipheriv( const decipher = createDecipheriv(
algorithm, algorithm,