nukeop/nuclear

View on GitHub
packages/ui/lib/components/TrackTable/index.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
/* eslint-disable react/jsx-key */
import React, { TableHTMLAttributes, useMemo } from 'react';
import cx from 'classnames';
import { useTable, Column, useRowSelect, useSortBy, HeaderGroup, UseSortByColumnProps, TableState, UseSortByState } from 'react-table';
import { isNumber, isString } from 'lodash';
import { DragDropContext, Droppable, Draggable, DragDropContextProps } from 'react-beautiful-dnd';

import DeleteCell from './Cells/DeleteCell';
import FavoriteCell from './Cells/FavoriteCell';
import PositionCell from './Cells/PositionCell';
import SelectionCell from './Cells/SelectionCell';
import ThumbnailCell from './Cells/ThumbnailCell';
import TitleCell from './Cells/TitleCell';
import TrackTableCell from './Cells/TrackTableCell';
import SelectionHeader from './Headers/SelectionHeader';
import ColumnHeader from './Headers/ColumnHeader';
import { getTrackThumbnail } from '../TrackRow';
import { TrackTableColumn, TrackTableExtraProps, TrackTableHeaders, TrackTableSettings, TrackTableStrings } from './types';
import styles from './styles.scss';
import artPlaceholder from '../../../resources/media/art_placeholder.png';
import { Track } from '../../types';
import { formatDuration } from '../..';
import { ThHTMLAttributes } from 'react';

export type TrackTableProps<T extends Track> = TrackTableExtraProps<T> &
  TrackTableHeaders &
  TrackTableSettings & {
    className?: string;
    tracks: T[];
    isTrackFavorite: (track: T) => boolean;
    onDragEnd?: DragDropContextProps['onDragEnd'];
    strings: TrackTableStrings;
    customColumns?: Column<T>[];
  }

function TrackTable<T extends Track>({
  className,
  tracks,
  customColumns = [],
  isTrackFavorite,
  onDragEnd,

  positionHeader,
  thumbnailHeader,
  artistHeader,
  titleHeader,
  albumHeader,
  durationHeader,

  displayHeaders = true,
  displayDeleteButton = true,
  displayPosition = true,
  displayThumbnail = true,
  displayFavorite = true,
  displayArtist = true,
  displayAlbum = true,
  displayDuration = true,
  displayCustom = true,
  selectable = true,

  ...extraProps
}: TrackTableProps<T>) {
  const shouldDisplayDuration = displayDuration && tracks.every(track => Boolean(track.duration));
  const columns = useMemo(() => [
    displayDeleteButton && {
      id: TrackTableColumn.Delete,
      Cell: DeleteCell
    },
    displayPosition && {
      id: TrackTableColumn.Position,
      Header: ({ column }) => <ColumnHeader column={column} header={positionHeader} data-testid='position-header' />,
      accessor: 'position',
      Cell: PositionCell,
      enableSorting: true
    },
    displayThumbnail && {
      id: TrackTableColumn.Thumbnail,
      Header: () => <span className={styles.center_aligned}>{thumbnailHeader}</span>,
      accessor: (track) => getTrackThumbnail(track) || artPlaceholder,
      Cell: ThumbnailCell()
    },
    displayFavorite && {
      id: TrackTableColumn.Favorite,
      accessor: isTrackFavorite,
      Cell: FavoriteCell
    },
    {
      id: TrackTableColumn.Title,
      Header: ({ column }) => <ColumnHeader column={column} header={titleHeader} />,
      accessor: (track) => track.title ?? track.name,
      Cell: TitleCell,
      enableSorting: true
    },
    displayArtist && {
      id: TrackTableColumn.Artist,
      Header: ({ column }) => <ColumnHeader column={column} header={artistHeader} />,
      accessor: (track) => isString(track.artist)
        ? track.artist
        : track.artist.name,
      Cell: TrackTableCell,
      enableSorting: true
    },
    displayAlbum && {
      id: TrackTableColumn.Album,
      Header: albumHeader,
      accessor: 'album',
      Cell: TrackTableCell
    },
    shouldDisplayDuration && {
      id: TrackTableColumn.Duration,
      Header: durationHeader,
      accessor: track => {
        if (isString(track.duration)) {
          return track.duration;
        } else if (isNumber(track.duration)) {
          return formatDuration(track.duration);
        } else {
          return null;
        }
      },
      Cell: TrackTableCell
    },
    ...customColumns,
    selectable && {
      id: TrackTableColumn.Selection,
      Header: SelectionHeader,
      Cell: SelectionCell
    }
  ].filter(Boolean) as Column<T>[], [displayDeleteButton, displayPosition, displayThumbnail, displayFavorite, isTrackFavorite, titleHeader, displayArtist, artistHeader, displayAlbum, albumHeader, shouldDisplayDuration, durationHeader, selectable, positionHeader, thumbnailHeader]);

  const data = useMemo(() => tracks, [tracks]);
  const initialState: Partial<TableState<T> & UseSortByState<T>> = {
    sortBy: [{ id: TrackTableColumn.Position, desc: false }]
  };

  const table = useTable<T>({ columns, data, initialState }, useSortBy, useRowSelect);

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = table;

  return <table {...getTableProps() as TableHTMLAttributes<HTMLTableElement>} className={cx(className, styles.track_table)}>
    {
      displayHeaders && <thead>
        {
          headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps() as TableHTMLAttributes<HTMLTableRowElement>}>
              {
                headerGroup.headers.map((column: (HeaderGroup<T> & UseSortByColumnProps<T>)) => (
                  <th {...column.getHeaderProps(column.getSortByToggleProps()) as ThHTMLAttributes<HTMLTableCellElement>}>
                    {column.render('Header', extraProps)}
                  </th>
                )
                )
              }
            </tr>
          ))
        }
      </thead>
    }
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId='track_table'>
        {(provided) => (
          <tbody
            ref={provided.innerRef}
            {...getTableBodyProps() as TableHTMLAttributes<HTMLTableSectionElement>}
            {...provided.droppableProps}
          >
            {
              rows.map(row => {
                prepareRow(row);
                return (
                  <Draggable
                    key={`${row.values[TrackTableColumn.Title]} ${row.index}`}
                    draggableId={`${row.values[TrackTableColumn.Title]} ${row.index}`}
                    index={row.index}
                    isDragDisabled={!onDragEnd}
                  >
                    {(provided, snapshot) => (
                      <tr
                        data-testid='track-table-row'
                        ref={provided.innerRef}
                        className={cx({ [styles.is_dragging]: snapshot.isDragging })}
                        {...row.getRowProps() as TableHTMLAttributes<HTMLTableRowElement>}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                      >
                        {row.cells.map((cell, i) => (cell.render('Cell', { ...extraProps, key: i })))}
                      </tr>
                    )}
                  </Draggable>
                );
              })
            }
            {provided.placeholder}
          </tbody>
        )}
      </Droppable>
    </DragDropContext>
  </table>;
}

export default TrackTable;