perf(flow): ⚡️ Smooth panning even with complexe flow
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import { useEventListener } from '@chakra-ui/hooks'
|
||||
import assert from 'assert'
|
||||
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
||||
import { useGraph, ConnectingIds } from 'contexts/GraphContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
@ -18,7 +17,6 @@ export const DrawingEdge = () => {
|
||||
sourceEndpoints,
|
||||
targetEndpoints,
|
||||
blocksCoordinates,
|
||||
graphOffsetY,
|
||||
} = useGraph()
|
||||
const { createEdge } = useTypebot()
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
|
||||
@ -32,19 +30,21 @@ export const DrawingEdge = () => {
|
||||
if (!connectingIds) return 0
|
||||
return getEndpointTopOffset(
|
||||
sourceEndpoints,
|
||||
graphOffsetY,
|
||||
graphPosition.y,
|
||||
connectingIds.source.itemId ?? connectingIds.source.stepId
|
||||
)
|
||||
}, [connectingIds, sourceEndpoints, graphOffsetY])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [connectingIds, sourceEndpoints])
|
||||
|
||||
const targetTop = useMemo(() => {
|
||||
if (!connectingIds) return 0
|
||||
return getEndpointTopOffset(
|
||||
targetEndpoints,
|
||||
graphOffsetY,
|
||||
graphPosition.y,
|
||||
connectingIds.target?.stepId
|
||||
)
|
||||
}, [connectingIds, targetEndpoints, graphOffsetY])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [connectingIds, targetEndpoints])
|
||||
|
||||
const path = useMemo(() => {
|
||||
if (!sourceTop || !sourceBlockCoordinates) return ``
|
||||
@ -72,7 +72,7 @@ export const DrawingEdge = () => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
setMousePosition({
|
||||
x: e.clientX - graphPosition.x,
|
||||
y: e.clientY - graphPosition.y - headerHeight,
|
||||
y: e.clientY - graphPosition.y,
|
||||
})
|
||||
}
|
||||
useEventListener('mousemove', handleMouseMove)
|
||||
|
@ -24,7 +24,7 @@ export const DropOffEdge = ({
|
||||
onUnlockProPlanClick,
|
||||
}: Props) => {
|
||||
const { user } = useUser()
|
||||
const { sourceEndpoints, blocksCoordinates, graphOffsetY } = useGraph()
|
||||
const { sourceEndpoints, blocksCoordinates, graphPosition } = useGraph()
|
||||
const { publishedTypebot } = useTypebot()
|
||||
|
||||
const isUserOnFreePlan = isFreePlan(user)
|
||||
@ -60,7 +60,7 @@ export const DropOffEdge = ({
|
||||
() =>
|
||||
getEndpointTopOffset(
|
||||
sourceEndpoints,
|
||||
graphOffsetY,
|
||||
graphPosition.y,
|
||||
block?.steps[block.steps.length - 1].id
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -25,7 +25,7 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
||||
sourceEndpoints,
|
||||
targetEndpoints,
|
||||
blocksCoordinates,
|
||||
graphOffsetY,
|
||||
graphPosition,
|
||||
} = useGraph()
|
||||
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
@ -42,7 +42,7 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
||||
() =>
|
||||
getEndpointTopOffset(
|
||||
sourceEndpoints,
|
||||
graphOffsetY,
|
||||
graphPosition.y,
|
||||
getSourceEndpointId(edge)
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -50,16 +50,16 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
||||
)
|
||||
|
||||
const [targetTop, setTargetTop] = useState(
|
||||
getEndpointTopOffset(targetEndpoints, graphOffsetY, edge?.to.stepId)
|
||||
getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId)
|
||||
)
|
||||
useLayoutEffect(() => {
|
||||
setTargetTop(
|
||||
getEndpointTopOffset(targetEndpoints, graphOffsetY, edge?.to.stepId)
|
||||
getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId)
|
||||
)
|
||||
}, [
|
||||
targetBlockCoordinates?.y,
|
||||
targetEndpoints,
|
||||
graphOffsetY,
|
||||
graphPosition.y,
|
||||
edge?.to.stepId,
|
||||
])
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { Flex, FlexProps, useEventListener } from '@chakra-ui/react'
|
||||
import React, { useRef, useMemo, useEffect, useState } from 'react'
|
||||
import { blockWidth, useGraph } from 'contexts/GraphContext'
|
||||
import { BlockNode } from './Nodes/BlockNode/BlockNode'
|
||||
import {
|
||||
blockWidth,
|
||||
graphPositionDefaultValue,
|
||||
useGraph,
|
||||
} from 'contexts/GraphContext'
|
||||
import { useStepDnd } from 'contexts/GraphDndContext'
|
||||
import { Edges } from './Edges'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
||||
import { Block, DraggableStepType, PublicTypebot, Typebot } from 'models'
|
||||
import { DraggableStepType, PublicTypebot, Typebot } from 'models'
|
||||
import { generate } from 'short-uuid'
|
||||
import { AnswersCount } from 'services/analytics'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable'
|
||||
import GraphContent from './GraphContent'
|
||||
|
||||
declare const window: { chrome: unknown | undefined }
|
||||
|
||||
@ -30,19 +33,17 @@ export const Graph = ({
|
||||
const editorContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const { createBlock } = useTypebot()
|
||||
const {
|
||||
graphPosition,
|
||||
setGraphPosition,
|
||||
setGraphPosition: setGlobalGraphPosition,
|
||||
setOpenedStepId,
|
||||
updateBlockCoordinates,
|
||||
setGraphOffsetY,
|
||||
} = useGraph()
|
||||
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
|
||||
const [debouncedGraphPosition] = useDebounce(graphPosition, 200)
|
||||
const transform = useMemo(
|
||||
() =>
|
||||
`translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`,
|
||||
[graphPosition]
|
||||
)
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
editorContainerRef.current = document.getElementById(
|
||||
@ -53,10 +54,12 @@ export const Graph = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!graphContainerRef.current) return
|
||||
setGraphOffsetY(
|
||||
graphContainerRef.current.getBoundingClientRect().top +
|
||||
debouncedGraphPosition.y
|
||||
)
|
||||
const { top, left } = graphContainerRef.current.getBoundingClientRect()
|
||||
setGlobalGraphPosition({
|
||||
x: left + debouncedGraphPosition.x,
|
||||
y: top + debouncedGraphPosition.y,
|
||||
scale: 1,
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedGraphPosition])
|
||||
|
||||
@ -71,7 +74,6 @@ export const Graph = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleGlobalMouseUp = () => setIsMouseDown(false)
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
if (!typebot) return
|
||||
if (!draggedStep && !draggedStepType) return
|
||||
@ -98,26 +100,14 @@ export const Graph = ({
|
||||
|
||||
const handleClick = () => setOpenedStepId(undefined)
|
||||
|
||||
const handleMouseDown = () => setIsMouseDown(true)
|
||||
const handleMouseMove = (event: React.MouseEvent) => {
|
||||
if (!isMouseDown) return
|
||||
const { movementX, movementY } = event
|
||||
setGraphPosition({
|
||||
x: graphPosition.x + movementX / (window.chrome ? 2 : 1),
|
||||
y: graphPosition.y + movementY / (window.chrome ? 2 : 1),
|
||||
scale: 1,
|
||||
})
|
||||
}
|
||||
|
||||
useEventListener('wheel', handleMouseWheel, graphContainerRef.current)
|
||||
useEventListener('mousedown', handleCaptureMouseDown, undefined, {
|
||||
capture: true,
|
||||
})
|
||||
useEventListener('mouseup', handleMouseUp, graphContainerRef.current)
|
||||
useEventListener('mouseup', handleGlobalMouseUp)
|
||||
useEventListener('click', handleClick, editorContainerRef.current)
|
||||
|
||||
const onDrag = (event: DraggableEvent, draggableData: DraggableData) => {
|
||||
const onDrag = (_: DraggableEvent, draggableData: DraggableData) => {
|
||||
const { deltaX, deltaY } = draggableData
|
||||
setGraphPosition({
|
||||
x: graphPosition.x + deltaX,
|
||||
@ -128,13 +118,7 @@ export const Graph = ({
|
||||
|
||||
return (
|
||||
<DraggableCore onDrag={onDrag}>
|
||||
<Flex
|
||||
ref={graphContainerRef}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
position="relative"
|
||||
{...props}
|
||||
>
|
||||
<Flex ref={graphContainerRef} position="relative" {...props}>
|
||||
<Flex
|
||||
flex="1"
|
||||
w="full"
|
||||
@ -146,14 +130,10 @@ export const Graph = ({
|
||||
willChange="transform"
|
||||
transformOrigin="0px 0px 0px"
|
||||
>
|
||||
<Edges
|
||||
edges={typebot?.edges ?? []}
|
||||
<GraphContent
|
||||
answersCounts={answersCounts}
|
||||
onUnlockProPlanClick={onUnlockProPlanClick}
|
||||
/>
|
||||
{typebot?.blocks.map((block, idx) => (
|
||||
<BlockNode block={block as Block} blockIndex={idx} key={block.id} />
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</DraggableCore>
|
||||
|
31
apps/builder/components/shared/Graph/GraphContent.tsx
Normal file
31
apps/builder/components/shared/Graph/GraphContent.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { Block } from 'models'
|
||||
import React from 'react'
|
||||
import { AnswersCount } from 'services/analytics'
|
||||
import { Edges } from './Edges'
|
||||
import { BlockNode } from './Nodes/BlockNode'
|
||||
|
||||
type Props = {
|
||||
answersCounts?: AnswersCount[]
|
||||
onUnlockProPlanClick?: () => void
|
||||
}
|
||||
const MyComponent = ({ answersCounts, onUnlockProPlanClick }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
return (
|
||||
<>
|
||||
<Edges
|
||||
edges={typebot?.edges ?? []}
|
||||
answersCounts={answersCounts}
|
||||
onUnlockProPlanClick={onUnlockProPlanClick}
|
||||
/>
|
||||
{typebot?.blocks.map((block, idx) => (
|
||||
<BlockNode block={block as Block} blockIndex={idx} key={block.id} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Performance hack, never rerender when graph (parent) is panned
|
||||
const areEqual = () => true
|
||||
|
||||
export default React.memo(MyComponent, areEqual)
|
@ -73,8 +73,6 @@ const graphContext = createContext<{
|
||||
openedStepId?: string
|
||||
setOpenedStepId: Dispatch<SetStateAction<string | undefined>>
|
||||
isReadOnly: boolean
|
||||
graphOffsetY: number
|
||||
setGraphOffsetY: Dispatch<SetStateAction<number>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({
|
||||
@ -100,8 +98,6 @@ export const GraphProvider = ({
|
||||
const [blocksCoordinates, setBlocksCoordinates] = useState<BlocksCoordinates>(
|
||||
{}
|
||||
)
|
||||
const [graphOffsetY, setGraphOffsetY] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
setBlocksCoordinates(
|
||||
blocks.reduce(
|
||||
@ -153,8 +149,6 @@ export const GraphProvider = ({
|
||||
blocksCoordinates,
|
||||
updateBlockCoordinates,
|
||||
isReadOnly,
|
||||
graphOffsetY,
|
||||
setGraphOffsetY,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
Reference in New Issue
Block a user