From ba566126d53f1ed28f8d3ae0d7c605289e4aad0e Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Fri, 22 Oct 2021 00:47:57 +0200 Subject: [PATCH] Started adding results tables for items other than tracks. Buggy still. --- client/src/components/tables/ResultsTable.tsx | 309 +++++++++++++----- .../components/windows/album/AlbumWindow.tsx | 6 +- .../windows/artist/ArtistWindow.tsx | 6 +- .../components/windows/query/QueryWindow.tsx | 48 ++- .../src/components/windows/tag/TagWindow.tsx | 6 +- 5 files changed, 267 insertions(+), 108 deletions(-) diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx index 319aa37..ea902f3 100644 --- a/client/src/components/tables/ResultsTable.tsx +++ b/client/src/components/tables/ResultsTable.tsx @@ -2,34 +2,69 @@ import React from 'react'; import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody, Chip, Box, Button } from '@material-ui/core'; import stringifyList from '../../lib/stringifyList'; import { useHistory } from 'react-router'; -import { Artist, QueryResponseTrackDetails, Tag, Name } from '../../api/api'; +import { Artist, QueryResponseTrackDetails, Tag, Name, Id, TagDetails, QueryResponseArtistDetails, QueryResponseAlbumDetails } from '../../api/api'; +import { isTemplateHead } from 'typescript'; -function getTagNames (track: QueryResponseTrackDetails) : string[][] { +function getFullTagNames(item: any, + getTagName: (tag: any) => string, + getTagParent: (tag: any) => any, + getItemTags: (item: any) => any[]): string[][] { // Recursively resolve the name. const resolveTag = (tag: any) => { - var r = [tag.name]; - if (tag.parent) { r.unshift(resolveTag(tag.parent)); } + var r = [getTagName(tag)]; + const parent = getTagParent(tag); + if (parent) { r = resolveTag(parent).concat(r); } return r; } - return track.tags.map((tag: Tag) => resolveTag(tag)); + return (getItemTags(item) || []).map((tag: Tag) => resolveTag(tag)); } -function getTagIds (track: QueryResponseTrackDetails) : number[][] { - // Recursively resolve the id. +function getFullTagIds(item: any, + getTagId: (tag: any) => number, + getTagParent: (tag: any) => any, + getItemTags: (item: any) => any[]): number[][] { + // Recursively resolve the name. const resolveTag = (tag: any) => { - var r = [tag.tagId]; - if (tag.parent) { r.unshift(resolveTag(tag.parent)); } + var r = [getTagId(tag)]; + const parent = getTagParent(tag); + if (parent) { r = resolveTag(parent).concat(r); } return r; } - return track.tags.map((tag: any) => resolveTag(tag)); + return (getItemTags(item) || []).map((tag: Tag) => resolveTag(tag)); } -export default function TrackTable(props: { - tracks: QueryResponseTrackDetails[] +export enum ColumnType { + Text = 0, + Tags, +} + +export interface TextColumnData { + +} + +export interface TagsColumnData { + +} + +export interface ColumnDescription { + type: ColumnType, + title: string, + getText?: (item: any) => string, + getMaybeOnClick?: (item: any) => () => void, + getTags?: (item: any) => any[], + getTagName?: (tag: any) => string, + getTagId?: (tag: any) => number, + getTagParent?: (tag: any) => any, + getTagOnClick?: (tag: any) => () => void, +} + +export function RenderItem(props: { + columnDescription: ColumnDescription, + item: any }) { - const history = useHistory(); + let { columnDescription: cd, item } = props; const classes = makeStyles({ button: { @@ -38,6 +73,59 @@ export default function TrackTable(props: { paddingLeft: '0', textAlign: 'left', }, + })(); + + const TextCell = (props: any) => { + return + + ; + } + + switch (props.columnDescription.type) { + case ColumnType.Text: + const text = cd.getText && cd.getText(item) || "Unknown"; + const onClick = cd.getMaybeOnClick && cd.getMaybeOnClick(item) || null; + return {text} + break; + case ColumnType.Tags: + const tags: any[] = cd.getTags && cd.getTags(item) || []; + const fullTagNames: string[][] = getFullTagNames( + item, + cd.getTagName || (() => "Unknown"), + cd.getTagParent || (() => null), + cd.getTags || (() => []), + ); + return <>{fullTagNames.map((tag: string[], i: number) => { + const fullTag = stringifyList(tag, undefined, (idx: number, e: string) => { + return (idx === 0) ? e : " / " + e; + }) + return + + ; + })}; + break; + default: + throw 'Unknown column type'; + } +} + +export function ItemsTable(props: { + items: any[], + columns: ColumnDescription[], +}) { + const classes = makeStyles({ table: { minWidth: 650, }, @@ -48,83 +136,136 @@ export default function TrackTable(props: { - Title - Artist - Album - Tags + {props.columns.map((c: ColumnDescription) => + {c.title})} - {props.tracks.map((track: QueryResponseTrackDetails) => { - const name = track.name; - // TODO: display artists and albums separately! - const artistNames = track.artists - .filter( (a: Artist) => a.name ) - .map( (a: (Artist & Name)) => a.name ); - const artist = stringifyList(artistNames); - const mainArtistId = - (track.artists.length > 0 && track.artists[0].id) || undefined; - const album = track.album?.name || undefined; - const albumId = track.album?.id || undefined; - const trackId = track.id; - const tagIds = getTagIds(track); - - const onClickArtist = () => { - history.push('/artist/' + mainArtistId); - } - - const onClickAlbum = () => { - history.push('/album/' + albumId || ''); - } - - const onClickTrack = () => { - history.push('/track/' + trackId); - } - - const onClickTag = (id: number, name: string) => { - history.push('/tag/' + id); - } - - const tags = getTagNames(track).map((tag: string[], i: number) => { - const fullTag = stringifyList(tag, undefined, (idx: number, e: string) => { - return (idx === 0) ? e : " / " + e; - }) - return - onClickTag(tagIds[i][tagIds[i].length - 1], fullTag)} - /> - - }); - - const TextCell = (props: any) => { - return - - ; - } - - return - {name} - {artist} - {album ? {album} : } - - - {tags} - - - + {props.items.map((item: any, idx: number) => { + return + {props.columns.map((c: ColumnDescription) => + )} + ; })}
); +} + +export function TracksTable(props: { + tracks: QueryResponseTrackDetails[] +}) { + const history = useHistory(); + + return i.name, + getMaybeOnClick: (i: QueryResponseTrackDetails) => () => { + history.push('/track/' + i.id); + }, + }, + { + title: 'Artist', type: ColumnType.Text, + getText: (i: QueryResponseTrackDetails) => { + const artistNames = i.artists + .filter((a: Artist) => a.name) + .map((a: (Artist & Name)) => a.name); + return stringifyList(artistNames); + }, + getMaybeOnClick: (i: QueryResponseTrackDetails) => () => { + // TODO + const mainArtistId = + (i.artists.length > 0 && i.artists[0].id) || undefined; + history.push('/artist/' + mainArtistId || 'undefined'); + }, + }, + { + title: 'Album', type: ColumnType.Text, getText: (i: QueryResponseTrackDetails) => i.album?.name || "Unknown", + getMaybeOnClick: (i: QueryResponseTrackDetails) => () => { + history.push('/album/' + i.album?.id || 'undefined'); + }, + }, + { + title: 'Tags', type: ColumnType.Tags, + getTags: (i: QueryResponseTrackDetails) => i.tags, + getTagId: (t: Tag & Id) => t.id, + getTagName: (t: Tag & Name) => t.name, + getTagParent: (t: Tag & TagDetails) => t.parent, + getTagOnClick: (t: Tag & Id) => () => { history.push('/tag/' + t.id) } + } + ]} + /> +} + +export function ArtistsTable(props: { + artists: QueryResponseArtistDetails[] +}) { + const history = useHistory(); + + return i.name, + getMaybeOnClick: (i: QueryResponseArtistDetails) => () => { + history.push('/artist/' + i.id); + }, + }, + { + title: 'Tags', type: ColumnType.Tags, + getTags: (i: QueryResponseArtistDetails) => (i.tags || []), + getTagId: (t: Tag & Id) => t.id, + getTagName: (t: Tag & Name) => t.name, + getTagParent: (t: Tag & TagDetails) => t.parent, + getTagOnClick: (t: Tag & Id) => () => { history.push('/tag/' + t.id) } + } + ]} + /> +} + +export function AlbumsTable(props: { + albums: QueryResponseAlbumDetails[] +}) { + const history = useHistory(); + + return i.name, + getMaybeOnClick: (i: QueryResponseAlbumDetails) => () => { + history.push('/album/' + i.id); + }, + }, + { + title: 'Artist', type: ColumnType.Text, + getText: (i: QueryResponseAlbumDetails) => { + const artistNames = (i.artists || []) + .filter((a: Artist) => a.name) + .map((a: Artist) => a.name || "Unknown"); + return stringifyList(artistNames); + }, + getMaybeOnClick: (i: QueryResponseAlbumDetails) => () => { + // TODO + const mainArtistId = + ((i.artists || []).length > 0 && (i.artists || [])[0].id) || undefined; + history.push('/artist/' + mainArtistId || 'undefined'); + }, + }, + { + title: 'Tags', type: ColumnType.Tags, + getTags: (i: QueryResponseTrackDetails) => i.tags, + getTagId: (t: Tag & Id) => t.id, + getTagName: (t: Tag & Name) => t.name, + getTagParent: (t: Tag & TagDetails) => t.parent, + getTagOnClick: (t: Tag & Id) => () => { history.push('/tag/' + t.id) } + } + ]} + /> } \ No newline at end of file diff --git a/client/src/components/windows/album/AlbumWindow.tsx b/client/src/components/windows/album/AlbumWindow.tsx index 2b78ed5..004aa51 100644 --- a/client/src/components/windows/album/AlbumWindow.tsx +++ b/client/src/components/windows/album/AlbumWindow.tsx @@ -4,7 +4,7 @@ import AlbumIcon from '@material-ui/icons/Album'; import * as serverApi from '../../../api/api'; import { WindowState } from '../Windows'; import StoreLinkIcon, { whichStore } from '../../common/StoreLinkIcon'; -import TrackTable from '../../tables/ResultsTable'; +import { ColumnType, ItemsTable, TracksTable } from '../../tables/ResultsTable'; import { modifyAlbum, modifyTrack } from '../../../lib/saveChanges'; import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; import { queryAlbums, queryTracks } from '../../../lib/backend/queries'; @@ -163,9 +163,7 @@ export function AlbumWindowControlled(props: { Tracks in this album in your library: - {props.state.tracksOnAlbum && } + {props.state.tracksOnAlbum && } {!props.state.tracksOnAlbum && } {metadata && Tracks by this artist in your library: - {props.state.tracksByArtist && } + {props.state.tracksByArtist && } {!props.state.tracksByArtist && } {metadata && - {Object.values(resultsForQueries).map((r: ResultsForQuery | null) => <> - {r !== null && r.kind == QueryItemType.Tracks && } - {r !== null && r.kind == QueryItemType.Albums && <>Found {r.results.length} albums.} - {r !== null && r.kind == QueryItemType.Artists && <>Found {r.results.length} artists.} - {r !== null && r.kind == QueryItemType.Tags && <>Found {r.results.length} tags.} - {r === null && } - )} + {(() => { + var rr = Object.values(resultsForQueries); + rr = rr.sort((r: ResultsForQuery | null) => { + if (r === null) { return 99; } + return { + [QueryItemType.Tracks]: 0, + [QueryItemType.Albums]: 1, + [QueryItemType.Artists]: 2, + [QueryItemType.Tags]: 3 + }[r.kind]; + }); + // TODO: the sorting is not working + return rr.map((r: ResultsForQuery | null) => <> + {r !== null && r.kind == QueryItemType.Tracks && <> + Tracks + + } + {r !== null && r.kind == QueryItemType.Albums && <> + Albums + + } + {r !== null && r.kind == QueryItemType.Artists && <> + Artists + + } + {r !== null && r.kind == QueryItemType.Tags && <> + Tags + Found {r.results.length} tags. + } + {r === null && } + ); + })()} } \ No newline at end of file diff --git a/client/src/components/windows/tag/TagWindow.tsx b/client/src/components/windows/tag/TagWindow.tsx index aadb6c2..a354821 100644 --- a/client/src/components/windows/tag/TagWindow.tsx +++ b/client/src/components/windows/tag/TagWindow.tsx @@ -4,7 +4,7 @@ import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import * as serverApi from '../../../api/api'; import { WindowState } from '../Windows'; import StoreLinkIcon, { whichStore } from '../../common/StoreLinkIcon'; -import TrackTable from '../../tables/ResultsTable'; +import { ItemsTable, ColumnType, TracksTable } from '../../tables/ResultsTable'; import { modifyTag } from '../../../lib/backend/tags'; import { queryTags, queryTracks } from '../../../lib/backend/queries'; import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; @@ -172,9 +172,7 @@ export function TagWindowControlled(props: { Tracks with this tag in your library: - {props.state.tracksWithTag && } + {props.state.tracksWithTag && } {!props.state.tracksWithTag && } {metadata &&