perf(editor): ⚡️ Improve graph transition perf
This commit is contained in:
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ``
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||
@@ -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 ?? []}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user