Skip to content

Commit

Permalink
Grid track table - complete functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
nukeop committed Aug 30, 2024
1 parent fe50cca commit 7b8b200
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 40 deletions.
10 changes: 6 additions & 4 deletions packages/ui/lib/components/Button/styles.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

@import "../../common.scss";

button.nuclear.ui.button {
position: relative;

background: $bglight;
color: $white;
white-space: nowrap;
margin: 0;

&:hover {
background: mix($bglight, $bgdark, 90%);
Expand Down Expand Up @@ -53,7 +53,7 @@ button.nuclear.ui.button {
"purple": $purple,
"pink": $pink,
"orange": $orange,
"red": $red,
"red": $red
);
@each $color in $colors {
$button-color: map-get($colorvars, $color);
Expand Down Expand Up @@ -88,7 +88,9 @@ button.nuclear.ui.button {
padding: 0;
margin: 0 0.5em;

&:hover, &:focus, &:active {
&:hover,
&:focus,
&:active {
background: transparent;
}

Expand Down
27 changes: 27 additions & 0 deletions packages/ui/lib/components/GridTrackTable/Cells/DeleteCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { CellProps } from 'react-table';
import cx from 'classnames';

import styles from '../styles.scss';
import { Track } from '../../../types';
import { TrackTableExtraProps } from '../../TrackTable/types';
import Button from '../../Button';

export const DeleteCell: React.FC<CellProps<Track> & TrackTableExtraProps<Track>> = ({
cell,
row,
onDelete
}) => <div
{...cell.getCellProps()}
className={cx(styles.delete_cell)}
data-testid='delete-cell'>
<Button
data-testid='delete-button'
basic
borderless
circular
size='tiny'
icon='times'
onClick={() => onDelete(row.original, row.index)}
/>
</div>;
28 changes: 28 additions & 0 deletions packages/ui/lib/components/GridTrackTable/Cells/FavoriteCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { CellProps } from 'react-table';
import cx from 'classnames';

import { TrackTableExtraProps } from '../../TrackTable/types';
import { Track } from '../../../types';
import Button from '../../Button';
import styles from '../styles.scss';

export const FavoriteCell: React.FC<CellProps<Track> & TrackTableExtraProps<Track>> = ({
cell,
row,
value,
onAddToFavorites,
onRemoveFromFavorites
}) => <div
{...cell.getCellProps()}
className={cx(styles.grid_track_table_cell, styles.favorite_cell)}
data-testid='favorite-cell'>
<Button
data-testid='favorite-button'
basic
borderless
circular
size='tiny' icon={value ? 'heart' : 'heart outline'}
onClick={() => value ? onRemoveFromFavorites(row.original) : onAddToFavorites(row.original)}
/>
</div>;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PositionCell: React.FC<CellProps<Track> & TrackTableExtraProps<Trac
onPlay
}) => <div
{...cell.getCellProps()}
className={cx(styles.position_cell, styles.narrow)}
className={cx(styles.grid_track_table_cell, styles.position_cell)}
data-testid='position-cell'
>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const TextCell: React.FC<CellProps<Track>> = ({
value
}) => <div
{...cell.getCellProps()}
className={cx(styles.track_table_cell, styles.text_cell)}
className={cx(styles.grid_track_table_cell, styles.text_cell)}
>
{value}
</div>;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ThumbnailCell: React.FC<CellProps<Track>> = ({
cell,
value
}) => <div
className={cx(styles.track_table_cell, styles.thumbnail_cell)}
className={cx(styles.grid_track_table_cell, styles.thumbnail_cell)}
{...cell.getCellProps()}
>
<img className={styles.thumbnail_cell_thumbnail} src={value} />
Expand Down
82 changes: 82 additions & 0 deletions packages/ui/lib/components/GridTrackTable/Cells/TitleCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import { CellProps } from 'react-table';
import cx from 'classnames';

import { TrackTableColumn, TrackTableExtraProps } from '../../TrackTable/types';
import { Track } from '../../../types';
import Button from '../../Button';
import TrackPopup from '../../TrackPopup';
import styles from '../styles.scss';

export const TitleCell: React.FC<CellProps<Track> & TrackTableExtraProps<Track>> = ({
cell,
row,
value,

onPlay,
onPlayNext,
onAddToQueue,
onAddToFavorites,
onAddToPlaylist,
onCreatePlaylist,
onAddToDownloads,
playlists,
popupActionStrings
}) => <div
{...cell.getCellProps()}
data-testid='title-cell'
className={cx(styles.grid_track_table_cell, styles.text_cell, styles.title_cell)}
>
<span className={styles.title_cell_content}>
<span className={styles.title_cell_value}>
{value}
</span>
<span className={styles.title_cell_buttons}>
<Button
className={styles.title_cell_button}
basic
borderless
circular
size='tiny'
icon='plus'
onClick={() => onAddToQueue(row.original)}
data-testid='add-to-queue'
/>

<TrackPopup
trigger={
<Button
className={styles.title_cell_button}
basic
borderless
circular
size='tiny'
icon='ellipsis horizontal'
data-testid='track-popup-trigger'
/>
}
thumb={row.values[TrackTableColumn.Thumbnail]}
title={row.values[TrackTableColumn.Title]}
artist={row.values[TrackTableColumn.Artist]}
playlists={playlists}

onPlayNow={() => onPlay(row.original)}
onPlayNext={() => onPlayNext(row.original)}
onAddToQueue={() => onAddToQueue(row.original)}
onAddToFavorites={() => onAddToFavorites(row.original)}
onAddToPlaylist={(playlist: { name: string }) => onAddToPlaylist(row.original, playlist)}
onCreatePlaylist={(options: { name: string }) => onCreatePlaylist(row.original, options)}
onAddToDownloads={() => onAddToDownloads(row.original)}

withPlayNow={Boolean(onPlay)}
withPlayNext={Boolean(onPlayNext)}
withAddToQueue={Boolean(onAddToQueue)}
withAddToFavorites={Boolean(onAddToFavorites)}
withAddToPlaylist={Boolean(onAddToFavorites)}
withAddToDownloads={Boolean(onAddToDownloads) && !row.original.local}

strings={popupActionStrings}
/>
</span>
</span>
</div>;
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const GridTrackTableRow = memo(<T extends Track>({ index, style, data }:
<div
data-test-id='track-table-row'
className={cx(
styles.track_table_row,
styles.grid_track_table_row,
{ [styles.is_dragging]: draggableSnapshot.isDragging }
)}
ref={draggableProvided.innerRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const GridTrackTableRowClone: <T extends Track>(props: GridTrackTableRowC
return <div
ref={provided.innerRef}
className={cx(
styles.track_table_row,
styles.grid_track_table_row,
styles.is_dragging
)}
{...row.getRowProps()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import cx from 'classnames';
import { Track } from '../../../types';
import styles from '../styles.scss';

type TextHeaderProps = {
type TextHeaderProps<T extends Track> = {
className?: string;
column: ColumnInstance<Track> & UseSortByColumnProps<Track>;
column: ColumnInstance<T> & UseSortByColumnProps<T>;
header: string | React.ReactNode;
isCentered?: boolean;
'data-testid'?: string;
};

export const TextHeader: React.FC<TextHeaderProps> = ({
export const TextHeader: <T extends Track>(props: TextHeaderProps<T>) => React.ReactElement<TextHeaderProps<T>> = ({
className,
column,
header,
Expand Down
74 changes: 64 additions & 10 deletions packages/ui/lib/components/GridTrackTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Column, Row, TableInstance, TableOptions, TableState, UseSortByInstanceProps, UseSortByState, useRowSelect, useSortBy, useTable } from 'react-table';
import React, { useMemo, memo } from 'react';
import { Column, Row, TableInstance, TableOptions, TableState, UseGlobalFiltersInstanceProps, UseSortByInstanceProps, UseSortByState, useGlobalFilter, useRowSelect, useSortBy, useTable } from 'react-table';
import React, { useMemo, memo, useState } from 'react';
import { DragDropContext, DragDropContextProps, Draggable, Droppable } from 'react-beautiful-dnd';
import { FixedSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
Expand All @@ -20,6 +20,11 @@ import { SelectionHeader } from './Headers/SelectionHeader';
import { formatDuration } from '../../utils';
import { PositionCell } from './Cells/PositionCell';
import { GridTrackTableRowClone } from './GridTrackTableRowClone';
import { DeleteCell } from './Cells/DeleteCell';
import { FavoriteCell } from './Cells/FavoriteCell';
import { TitleCell } from './Cells/TitleCell';
import { Input } from 'semantic-ui-react';
import Button from '../Button';

export type GridTrackTableProps<T extends Track> = {
className?: string;
Expand All @@ -32,6 +37,8 @@ export type GridTrackTableProps<T extends Track> = {
& TrackTableSettings
& TrackTableExtraProps<T>;

type ColumnWithWidth<T extends Track> = Column<T> & { columnWidth: string; };

export const GridTrackTable = <T extends Track>({
className,
tracks,
Expand Down Expand Up @@ -62,6 +69,11 @@ export const GridTrackTable = <T extends Track>({
}: GridTrackTableProps<T>) => {
const shouldDisplayDuration = displayDuration && tracks.every(track => Boolean(track.duration));
const columns = useMemo(() => [
displayDeleteButton && {
id: TrackTableColumn.Delete,
Cell: DeleteCell,
columnWidth: '3em'
},
displayPosition && {
id: TrackTableColumn.Position,
Header: ({ column }) => <TextHeader
Expand All @@ -73,21 +85,27 @@ export const GridTrackTable = <T extends Track>({
Cell: PositionCell,
enableSorting: true,
columnWidth: '4em'
},
} as Column<T>,
displayThumbnail && {
id: TrackTableColumn.Thumbnail,
Header: ({ column }) => <TextHeader column={column} header={thumbnailHeader} />,
accessor: (track: T) => getTrackThumbnail(track) || artPlaceholder,
Cell: ThumbnailCell,
columnWidth: '3em'
},
displayFavorite && {
id: TrackTableColumn.Favorite,
accessor: isTrackFavorite,
Cell: FavoriteCell,
columnWidth: '3em'
},
{
id: TrackTableColumn.Title,
Header: ({ column }) => <TextHeader column={column} header={titleHeader} />,
accessor: (track: T) => track.title ?? track.name,
Cell: TextCell,
Cell: TitleCell,
enableSorting: true,
columnWidth: '1fr'
columnWidth: 'minmax(8em, 1fr)'
},
displayArtist && {
id: TrackTableColumn.Artist,
Expand All @@ -106,7 +124,7 @@ export const GridTrackTable = <T extends Track>({
enableSorting: true,
Cell: TextCell,
columnWidth: '1fr'
},
} as Column<T>,
shouldDisplayDuration && {
id: TrackTableColumn.Duration,
Header: ({ column }) => <TextHeader column={column} header={durationHeader} />,
Expand Down Expand Up @@ -135,18 +153,54 @@ export const GridTrackTable = <T extends Track>({
const initialState: Partial<TableState<T> & UseSortByState<T>> = {
sortBy: [{ id: TrackTableColumn.Title, desc: false }]
};
const table = useTable<T>({ columns, data, initialState }, useSortBy, useRowSelect) as (TableInstance<T> & UseSortByInstanceProps<T>);
const table = useTable<T>({ columns, data, initialState }, useGlobalFilter, useSortBy, useRowSelect) as (TableInstance<T> & UseSortByInstanceProps<T> & UseGlobalFiltersInstanceProps<T>);
const [globalFilter, setGlobalFilterState] = useState(''); // Required, because useGlobalFilter does not provide a way to get the current filter value

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

const onFilterClick = () => {
setGlobalFilter('');
setGlobalFilterState('');
};

const gridTemplateColumns = columns.map(column => column.columnWidth ?? '1fr').join(' ');
const gridTemplateColumns = columns.map((column: ColumnWithWidth<T>) => column.columnWidth ?? '1fr').join(' ');

// Disabled when there are selected rows, or when sorted by anything other than position
const isDragDisabled = !onDragEnd || table.selectedFlatRows.length > 0 || table.state.sortBy[0]?.id !== TrackTableColumn.Position;

return <div className={styles.track_table_wrapper}>
{
searchable &&
<div className={styles.track_table_filter_row}>
<Input
data-testid='track-table-filter-input'
type='text'
placeholder={extraProps.strings.filterInputPlaceholder}
className={styles.track_table_filter_input}
onChange={(e) => {
setGlobalFilter(e.target.value);
setGlobalFilterState(e.target.value);
}}
value={globalFilter}
/>
<Button
className={styles.track_table_filter_button}
onClick={onFilterClick}
borderless
color={'blue'}
icon='filter'
/>
</div>
}
<div
className={styles.track_table}
className={styles.grid_track_table}
{...getTableProps()}
>
<div className={styles.track_table_head}>
Expand Down
Loading

0 comments on commit 7b8b200

Please sign in to comment.