iterative/vscode-dvc

View on GitHub
webview/src/plots/components/comparisonTable/ComparisonTableRow.tsx

Summary

Maintainability
A
0 mins
Test Coverage
B
84%
import { ComparisonPlot } from 'dvc/src/plots/webview/contract'
import React, {
  useState,
  useEffect,
  useRef,
  Fragment,
  useLayoutEffect,
  RefObject
} from 'react'
import cx from 'classnames'
import { useSelector } from 'react-redux'
import styles from './styles.module.scss'
import { ComparisonTableCell } from './cell/ComparisonTableCell'
import { ComparisonTableMultiCell } from './cell/ComparisonTableMultiCell'
import { RowDropTarget } from './RowDropTarget'
import { Icon } from '../../../shared/components/Icon'
import { ChevronDown, ChevronRight } from '../../../shared/components/icons'
import { PlotsState } from '../../store'
import { CopyButton } from '../../../shared/components/copyButton/CopyButton'
import { isSelecting } from '../../../util/strings'
import Tooltip, {
  NORMAL_TOOLTIP_DELAY
} from '../../../shared/components/tooltip/Tooltip'
import { useDragAndDrop } from '../../../shared/hooks/useDragAndDrop'
import { DragDropItemWithTarget } from '../../../shared/components/dragDrop/DragDropItemWithTarget'

export interface ComparisonTableRowProps {
  path: string
  plots: ComparisonPlot[]
  nbColumns: number
  pinnedColumn: string
  onLayoutChange: () => void
  setOrder: (order: string[]) => void
  order: string[]
  bodyRef?: RefObject<HTMLTableSectionElement>
}

export const ComparisonTableRow: React.FC<ComparisonTableRowProps> = ({
  path,
  plots,
  nbColumns,
  pinnedColumn,
  onLayoutChange,
  setOrder,
  order,
  bodyRef
}) => {
  const plotsRowRef = useRef<HTMLTableRowElement>(null)
  const draggedId = useSelector(
    (state: PlotsState) => state.dragAndDrop.draggedRef?.itemId
  )
  const comparisonWidth = useSelector(
    (state: PlotsState) => state.comparison.width
  )
  const { disabledDragPlotIds, isInDragAndDropMode } = useSelector(
    (state: PlotsState) => state.comparison
  )
  const [isShown, setIsShown] = useState(true)
  const { target, isAfter, ...dragAndDropProps } = useDragAndDrop({
    disabledDropIds: disabledDragPlotIds,
    dropTarget: <RowDropTarget colSpan={nbColumns} />,
    group: 'comparison-table',
    id: path,
    onDragEnd: () => {},
    order,
    setOrder,
    type: <tbody />,
    vertical: true
  })

  useLayoutEffect(() => {
    onLayoutChange?.()
  })

  const toggleIsShownState = () => {
    if (isSelecting([path])) {
      return
    }
    setIsShown(!isShown)
  }

  const updateMultiImgHeight = (image: HTMLImageElement) => {
    const aspectRatio = image.naturalWidth / image.naturalHeight
    const width = image.clientWidth
    const calculatedHeight = Number.parseFloat((width / aspectRatio).toFixed(2))

    plotsRowRef.current?.style.setProperty(
      '--img-height',
      `${calculatedHeight}px`
    )
  }

  useEffect(() => {
    const img: HTMLImageElement | null | undefined =
      plotsRowRef.current?.querySelector(`.${styles.multiImageWrapper} img`)
    if (!img) {
      return
    }

    if (img.complete) {
      updateMultiImgHeight(img)
      return
    }
    img.addEventListener(
      'load',
      () => {
        updateMultiImgHeight(img)
      },
      { once: true }
    )
  }, [comparisonWidth])

  return (
    <DragDropItemWithTarget
      isAfter={isAfter}
      dropTarget={target || null}
      draggable={<Fragment />}
    >
      <tbody
        {...dragAndDropProps}
        data-testid="comparison-table-body"
        key={path}
        id={path}
        ref={bodyRef}
      >
        <tr>
          <td
            className={cx({ [styles.pinnedColumnCell]: pinnedColumn })}
            colSpan={pinnedColumn ? 1 : nbColumns}
          >
            <div className={styles.rowPath}>
              <button
                className={styles.rowToggler}
                onClick={toggleIsShownState}
              >
                <Icon icon={isShown ? ChevronDown : ChevronRight} />
                <Tooltip
                  content={path}
                  placement="bottom-start"
                  delay={NORMAL_TOOLTIP_DELAY}
                >
                  <span className={styles.pathText}>{path}</span>
                </Tooltip>
              </button>
              <CopyButton value={path} className={styles.copyButton} />
            </div>
          </td>
          {nbColumns > 1 && pinnedColumn && <td colSpan={nbColumns - 1}></td>}
        </tr>
        <tr ref={plotsRowRef}>
          {plots.map(plot => (
            <td
              key={path + plot.id}
              className={cx({
                [styles.pinnedColumnCell]: pinnedColumn === plot.id,
                [styles.draggedColumn]:
                  isInDragAndDropMode && draggedId === plot.id
              })}
            >
              <div
                data-testid="row-images"
                className={cx(styles.cell, { [styles.cellHidden]: !isShown })}
              >
                {plot.imgs.length > 1 ? (
                  <ComparisonTableMultiCell plot={plot} path={path} />
                ) : (
                  <ComparisonTableCell plot={plot} path={path} />
                )}
              </div>
            </td>
          ))}
        </tr>
      </tbody>
    </DragDropItemWithTarget>
  )
}