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 &&