Verified Commit ca1a2caa authored by Marek Veselý's avatar Marek Veselý
Browse files

refactor: consolidate cause-and-effect rendering

parent 42e279da
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
import { Colors } from '@blueprintjs/colors'
import type { HierarchyLink } from 'd3-hierarchy'
import type { FC } from 'react'
import type { TreeNode } from '../dataHooks/useActionLogsRoot'
import { NODE_WIDTH } from './utils'

interface LinkProps {
  link: HierarchyLink<TreeNode>
}

export const Link: FC<LinkProps> = ({ link }) => {
  const x1 = link.source.x
  const y1 = link.source.y! + NODE_WIDTH / 2
  const x2 = link.target.x
  const y2 = link.target.y! - NODE_WIDTH / 2
  const midY = (y1 + y2) / 2
  const d = `M${y1},${x1}C${midY},${x1} ${midY},${x2} ${y2},${x2}`

  return (
    <path
      d={d}
      className='link'
      fill='none'
      stroke={Colors.GRAY3}
      strokeWidth={2}
    />
  )
}
+73 −0
Original line number Diff line number Diff line
import { Colors } from '@blueprintjs/colors'
import { Classes } from '@blueprintjs/core'
import { css } from '@emotion/css'
import type { HierarchyNode } from 'd3-hierarchy'
import type { FC } from 'react'
import type { TreeNode } from '../dataHooks/useActionLogsRoot'
import { NodeDetail } from './NodeDetail'
import { NodeTitle } from './NodeTitle'
import { NODE_WIDTH } from './utils'

const TEXT_PADDING = 4
const MIN_HEIGHT = 60
const DETAIL_HEIGHT = 24

const internalNode = css`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  align-items: center;
  text-align: center;
  justify-content: center;
  word-break: break-word;
  padding: 0.25rem;
`

const rectStyles = css`
  fill: ${Colors.LIGHT_GRAY4};
  stroke: ${Colors.GRAY3};
  .${Classes.DARK} & {
    fill: ${Colors.DARK_GRAY4};
  }
`

interface NodeProps {
  node: HierarchyNode<TreeNode>
  nodeIndex: number
}

export const Node: FC<NodeProps> = ({ node, nodeIndex }) => {
  const hasDetail = typeof node.data.data !== 'string'
  const totalHeight = MIN_HEIGHT + (hasDetail ? DETAIL_HEIGHT : 0)

  return (
    <g
      key={`node-${nodeIndex}`}
      className='node'
      transform={`translate(${node.y},${node.x})`}
    >
      <rect
        width={NODE_WIDTH}
        height={totalHeight}
        x={-NODE_WIDTH / 2}
        y={-totalHeight / 2}
        rx={4}
        className={rectStyles}
      />

      <foreignObject
        width={NODE_WIDTH - TEXT_PADDING * 2}
        height={totalHeight}
        x={-NODE_WIDTH / 2 + TEXT_PADDING}
        y={-totalHeight / 2}
      >
        <div className={internalNode}>
          <NodeTitle nodeData={node.data.data} />
          <NodeDetail nodeData={node.data.data} />
        </div>
      </foreignObject>
    </g>
  )
}
+27 −0
Original line number Diff line number Diff line
import { css } from '@emotion/css'
import type { FC } from 'react'
import { ActionLogIcon } from '../ActionLogTitle/ActionLogIcon'
import type { TreeNode } from '../dataHooks/useActionLogsRoot'

const actionLogDetail = css`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
`

interface NodeDetailProps {
  nodeData: TreeNode['data']
}

export const NodeDetail: FC<NodeDetailProps> = ({ nodeData }) => {
  if (typeof nodeData === 'string') {
    return null
  }
  return (
    <div className={actionLogDetail}>
      <ActionLogIcon actionLog={nodeData} />
      <div>{nodeData.logType}</div>
    </div>
  )
}
+28 −0
Original line number Diff line number Diff line
import { css } from '@emotion/css'
import type { FC } from 'react'
import { ActionLogContent } from '../ActionLogTitle/ActionLogContent'
import type { TreeNode } from '../dataHooks/useActionLogsRoot'

// ensure ellipsis for long titles and prevent layout breaking
const titleStyles = css`
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
`

interface NodeTitleProps {
  nodeData: TreeNode['data']
}

export const NodeTitle: FC<NodeTitleProps> = ({ nodeData }) => {
  if (typeof nodeData === 'string') {
    return <div className={titleStyles}>{nodeData}</div>
  }
  return (
    <div className={titleStyles}>
      <ActionLogContent actionLog={nodeData} />
    </div>
  )
}
+13 −9
Original line number Diff line number Diff line
import type { VariablesOf } from '@inject/graphql'
import {
  AnalystActionLogsSimpleSubscription,
  useSubscription,
} from '@inject/graphql'
import { hierarchy, tree } from 'd3-hierarchy'
import { select } from 'd3-selection'
import type { D3ZoomEvent, ZoomBehavior } from 'd3-zoom'
import { zoom, zoomIdentity } from 'd3-zoom'
import type { FC } from 'react'
import { useEffect, useRef } from 'react'

import type { VariablesOf } from '@inject/graphql'
import {
  AnalystActionLogsSimpleSubscription,
  useSubscription,
} from '@inject/graphql'
import type { TreeNode } from '../dataHooks/useActionLogsRoot'
import useActionLogsRoot from '../dataHooks/useActionLogsRoot'
import { renderLinks, renderNodes } from './utils'
import { Link } from './Link'
import { Node } from './Node'

type CauseAndEffectTreeProps = {
  teamId: string
@@ -74,8 +74,12 @@ export const CauseAndEffectTree: FC<CauseAndEffectTreeProps> = ({ teamId }) => {
      style={{ width: '100%', height: '100%', display: 'block' }}
    >
      <g className='zoom-container' ref={gRef}>
        {renderLinks(links)}
        {renderNodes(nodes)}
        {links.map((link, i) => (
          <Link key={`link-${i}`} link={link} />
        ))}
        {nodes.map((node, nodeIndex) => (
          <Node key={`node-${nodeIndex}`} node={node} nodeIndex={nodeIndex} />
        ))}
      </g>
    </svg>
  )
Loading