Skip to content

Commit

Permalink
feature: add sortable table to Songs page
Browse files Browse the repository at this point in the history
  • Loading branch information
alxndr committed May 27, 2022
1 parent 9122976 commit f640fc6
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 43 deletions.
87 changes: 80 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"react-autosuggest": "^10.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-table": "7.8.0",
"react-tabs": "^4.2.1",
"react-tippy": "^1.4.0",
"slugify": "^1.6.5"
Expand Down
13 changes: 12 additions & 1 deletion src/pages/songs.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.songs > main {
.layout.songs > main {
padding: 1em;
}

Expand All @@ -14,6 +14,17 @@
}
}

.layout.songs table thead {
text-align: center;
}
.layout.songs table tbody {
font-size: 0.9em;
}
.songs__performed-table__column-sort {
padding: 0 1em;
cursor: pointer;
}

.songs__list > li {
margin: 0 0.5em 0.5em 0;
}
Expand Down
117 changes: 82 additions & 35 deletions src/pages/songs.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react'
import React, {useMemo} from 'react'
import {useRowState, useSortBy, useTable} from 'react-table'
import {Tooltip} from 'react-tippy'
import {graphql, Link, StaticQuery} from 'gatsby'

import {filter, groupBy, prop, propEq, sortBy} from 'ramda'
import {Tooltip} from 'react-tippy'

import Layout from '../components/layout'
import SEO from '../components/seo'

import './songs.css'

const removeCertainSongs = ({title}) => title && !['[unknown]', 'Drums', 'Jam'].includes(title)
const removeCertainSongs = ({title}) => title && !['[unknown]', 'Drums', 'Jam', 'Space'].includes(title)
const sortByTitle = sortBy(prop('title')) // TODO ignore "A", "The", etc

function Author({name}) {
Expand All @@ -31,69 +31,116 @@ function PerformanceCount({perfIds, text = 'performed'}) {
</span>
</Tooltip>
}
function SongLink({data: {author, core_gd, cover_gd, id, suite, title, performances}}) {
function SongLink({data: {author, core_gd, cover_gd, id, suite, title, performances}, full}) {
return <Link to={`/song/${id}`}>
"{title}"
{' '}
{suite && <span className="song__suite">{suite} suite</span>}
{' '}
{(!core_gd || cover_gd) && <Author name={author} />}
{full && <>
{' '}
{suite && <span className="song__suite">{suite} suite</span>}
{' '}
{(!core_gd || cover_gd) && <Author name={author} />}
</>}
</Link>
}

function SortableTable({columns, data}) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
...rest
} = useTable({columns, data}, useRowState, useSortBy)
return <table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup =>
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column =>
<th {...column.getHeaderProps(column.getSortByToggleProps())} style={{borderBottom: 'solid 3px gray', background: 'aliceblue', fontWeight: 'bold'}}>
{column.render('Header')}
<span className="songs__performed-table__column-sort">
{column.isSorted ? column.isSortedDesc ? '⬇︎' : '⬆︎' : '⇅'}
</span>
</th>
)}
</tr>
)}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row) // no return value; mutates `row`?
return <tr {...row.getRowProps()}>
{row.cells.map(cell =>
<td {...cell.getCellProps()}>
{cell.column?.id === 'title'
? <SongLink data={cell.row.original} />
: cell.render('Cell')}
</td>
)}
</tr>
})}
</tbody>
</table>
}

function SongsComponent({data: {allSongsCsv: {nodes: songs}, allTeasesCsv: {nodes: teases}}}) {
const groupedByPerformed = groupBy(
(songData) => Boolean(songData.performances),
songs.filter(removeCertainSongs)
)
const groupedByPerformed = groupBy((songData) => Boolean(songData.performances), songs.filter(removeCertainSongs))
const allSongIdsFromTeases = teases.reduce((a,e) => a.concat(e.song_id), [])
const groupedByTeased = groupBy(
songData => allSongIdsFromTeases.includes(songData.id),
groupedByPerformed[false]
const groupedByTeased = groupBy(songData => allSongIdsFromTeases.includes(songData.id), groupedByPerformed[false])

const performedData = useMemo(
() => groupedByPerformed[true].map(songData => ({
...songData,
performances: String(songData.performances).split(':').length,
suite: songData.suite,
author: songData.author,
})),
[]
)
const performedColumns = useMemo(
() => [
{ Header: "title", accessor: "title" },
{ Header: "author", accessor: "author" },
{ Header: "suite", accessor: "suite" },
{ Header: "plays", accessor: "performances" },
],
[]
)

return <Layout className="songs">
<SEO
title="JRAD songs played / teased"
title="JRAD songs played or teased"
description="Repertoire of songs and teases performed by Joe Russo's Almost Dead, plus setlists of each concert"
/>

<div className="tableofcontents">
<div id="songs__toc" className="tableofcontents">
<p>Table of Contents</p>
<ol>
<li><a href="#songs__headline--performed">Songs Performed / Jammed</a></li>
<li><a href="#songs__headline--teased">Songs Teased</a></li>
<li><a href="#songs__headline--notyet">Not Yet Played from the GD Repertoire</a></li>
<li><a href="#songs__performed-headline">Songs Performed / Jammed</a></li>
<li><a href="#songs__teased-headline">Songs Teased</a></li>
<li><a href="#songs__notyet-headline">Not Yet Played from the GD Repertoire</a></li>
</ol>
</div>

<h1 href="#songs__headline--performed">Songs Performed / Jammed</h1>
<h1 href="#songs__performed-headline">Songs Performed / Jammed</h1>
<p>These songs have been performed in their entirety, or played as an extended theme by the entire band.</p>
<ul className="songs__list">
{sortByTitle(groupedByPerformed[true])
.map(songData =>
<li key={songData.id}>
<SongLink data={songData} />
<PerformanceCount perfIds={String(songData.performances).split(':')} />
</li>
)
}
</ul>
<SortableTable columns={performedColumns} data={performedData} />

<h1 id="songs__headline--teased">Songs Teased</h1>
<h1 id="songs__teased-headline">Songs Teased</h1>
<p>These are songs which have been hinted at by one or more members of the band while playing another song.</p>
<ul className="songs__list">
{sortByTitle(groupedByTeased[true])
.map(songData =>
<li key={songData.id}>
<SongLink data={songData} />
<SongLink data={songData} full={true} />
<PerformanceCount text="teased" perfIds={filter(propEq('song_id', songData.id))(teases).map((row) => row.performance_id)} />
</li>
)
}
</ul>

<h1 id="songs__headline--notyet">Not Yet Played from the GD Repertoire</h1>
<h1 id="songs__notyet-headline">Not Yet Played from the GD Repertoire</h1>
<p>This is an incomplete list of songs which the Grateful Dead or their members recorded or played live (either together or in other projects), but have been neither played nor teased by JRAD...</p>
<ul className="songs__list">
{sortByTitle(groupedByTeased[false])
Expand Down

0 comments on commit f640fc6

Please sign in to comment.