DefaultEdge.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import {
  2. BaseEdge,
  3. Edge,
  4. EdgeLabelRenderer,
  5. EdgeProps,
  6. getSmoothStepPath,
  7. Position,
  8. useReactFlow,
  9. } from '@xyflow/react'
  10. import { ArrowLeft, ArrowRight } from 'lucide-react'
  11. import { useState } from 'react'
  12. import { Badge, cn } from 'ui'
  13. import { useSchemaGraphContext } from './SchemaGraphContext'
  14. import { EdgeData } from './Schemas.constants'
  15. import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState'
  16. import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'
  17. export const DefaultEdge = ({
  18. id,
  19. animated,
  20. data,
  21. deletable,
  22. selectable,
  23. source,
  24. sourceX,
  25. sourceY,
  26. sourceHandleId,
  27. sourcePosition = Position.Bottom,
  28. target,
  29. targetX,
  30. targetY,
  31. targetHandleId,
  32. targetPosition = Position.Top,
  33. selected,
  34. pathOptions,
  35. ...props
  36. }: EdgeProps<Edge<EdgeData>>) => {
  37. const { isDownloading } = useSchemaGraphContext()
  38. const [edgePath, labelX, labelY] = getSmoothStepPath({
  39. sourceX,
  40. sourceY,
  41. targetX,
  42. targetY,
  43. sourcePosition,
  44. targetPosition,
  45. borderRadius: pathOptions?.borderRadius,
  46. offset: pathOptions?.offset,
  47. stepPosition: pathOptions?.stepPosition,
  48. })
  49. return (
  50. <>
  51. <BaseEdge
  52. id={id}
  53. path={edgePath}
  54. className={cn(selected ? 'stroke-brand!' : isDownloading ? 'stroke-black!' : undefined)}
  55. stroke="#000000"
  56. {...props}
  57. />
  58. {data && selected ? (
  59. <EdgeRelationInfo
  60. source={source}
  61. target={target}
  62. edgePath={edgePath}
  63. labelX={labelX}
  64. labelY={labelY}
  65. sourceX={sourceX}
  66. targetX={targetX}
  67. data={data}
  68. />
  69. ) : null}
  70. </>
  71. )
  72. }
  73. const EdgeRelationInfo = ({
  74. data,
  75. source,
  76. target,
  77. labelX,
  78. labelY,
  79. targetX,
  80. sourceX,
  81. }: {
  82. data: EdgeData
  83. edgePath: string
  84. source: string
  85. target: string
  86. labelX: number
  87. labelY: number
  88. sourceX: number
  89. targetX: number
  90. }) => {
  91. const [show, setShow] = useState(false)
  92. const reactFlowInstance = useReactFlow()
  93. const checkIfShouldBeDisplayed = useStaticEffectEvent(
  94. (relationInfoElement: HTMLDivElement | null) => {
  95. if (!relationInfoElement) return
  96. const sourceNode = reactFlowInstance.getNode(source)
  97. const targetNode = reactFlowInstance.getNode(target)
  98. if (!sourceNode || !targetNode) return
  99. const relationInfoRect = relationInfoElement.getBoundingClientRect()
  100. // Get the origin position of the relation information badge in the ReactFlow coordinates
  101. const relationInfoOriginPositionInReactFlow = reactFlowInstance.screenToFlowPosition({
  102. x: relationInfoRect.x,
  103. y: relationInfoRect.y,
  104. })
  105. // Get the end position (origin + dimensions) of the relation information badge in the ReactFlow coordinates
  106. const relationInfoTargetPositionInReactFlow = reactFlowInstance.screenToFlowPosition({
  107. x: relationInfoRect.x + relationInfoRect.width,
  108. y: relationInfoRect.y + relationInfoRect.height,
  109. })
  110. // Create a ReactFlow Rect from the computed position above
  111. const relationInfoReactFlowRect = {
  112. x: relationInfoOriginPositionInReactFlow.x,
  113. y: relationInfoOriginPositionInReactFlow.y,
  114. width: relationInfoTargetPositionInReactFlow.x - relationInfoOriginPositionInReactFlow.x,
  115. height: relationInfoTargetPositionInReactFlow.y - relationInfoOriginPositionInReactFlow.y,
  116. }
  117. // Check whether the relation information badge is intersecting with either the source or target node
  118. const isNodeIntersectingWithSource = reactFlowInstance.isNodeIntersecting(
  119. sourceNode,
  120. relationInfoReactFlowRect
  121. )
  122. const isNodeIntersectingWithTarget = reactFlowInstance.isNodeIntersecting(
  123. targetNode,
  124. relationInfoReactFlowRect
  125. )
  126. // If it is, hide it as they are too close
  127. setShow(!isNodeIntersectingWithSource && !isNodeIntersectingWithTarget)
  128. }
  129. )
  130. return (
  131. <EdgeLabelRenderer>
  132. <Badge
  133. ref={checkIfShouldBeDisplayed}
  134. className={cn(
  135. 'absolute pointer-events-auto z-50 p-1 rounded-[4px] gap-1 outline outline-1 outline-brand',
  136. show ? 'opacity-100' : 'opacity-0'
  137. )}
  138. style={{
  139. transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
  140. }}
  141. >
  142. {
  143. // Show the columns in the order of the schema instead of the Postgre relation order
  144. sourceX < targetX ? (
  145. <>
  146. <EdgeNodeData
  147. schema={data.sourceSchemaName}
  148. table={data.sourceName}
  149. column={data.sourceColumnName}
  150. />
  151. <ArrowRight size={12} />
  152. <EdgeNodeData
  153. schema={data.targetSchemaName}
  154. table={data.targetName}
  155. column={data.targetColumnName}
  156. />
  157. </>
  158. ) : (
  159. <>
  160. <EdgeNodeData
  161. schema={data.targetSchemaName}
  162. table={data.targetName}
  163. column={data.targetColumnName}
  164. />
  165. <ArrowLeft size={12} />
  166. <EdgeNodeData
  167. schema={data.sourceSchemaName}
  168. table={data.sourceName}
  169. column={data.sourceColumnName}
  170. />
  171. </>
  172. )
  173. }
  174. </Badge>
  175. </EdgeLabelRenderer>
  176. )
  177. }
  178. const EdgeNodeData = ({
  179. schema,
  180. table,
  181. column,
  182. }: {
  183. schema: string
  184. table: string
  185. column: string
  186. }) => {
  187. const { selectedSchema } = useQuerySchemaState()
  188. return (
  189. <Badge className="normal-case text-[8px]">
  190. {selectedSchema === schema ? '' : `${schema}.`}
  191. {table}.{column}
  192. </Badge>
  193. )
  194. }