Add tags display.

pull/20/head
Sander Vocke 5 years ago
parent ff1ec095e1
commit b78f84ffd0
  1. 10
      client/src/components/Window.tsx
  2. 5
      client/src/components/querybuilder/QBSelectWithRequest.tsx
  3. 13
      client/src/components/tables/ResultsTable.tsx
  4. 16
      client/src/lib/stringifyList.tsx
  5. 74
      server/endpoints/QueryEndpointHandler.ts
  6. 44
      server/lib/dbToApi.ts

@ -30,6 +30,16 @@ export default function Window(props: any) {
getTitle: (song: any) => song.title,
getArtist: (song: any) => stringifyList(song.artists, (a: any) => a.name),
getAlbum: (song: any) => stringifyList(song.albums, (a: any) => a.name),
getTags: (song: any) => {
// Recursively resolve the name.
const resolveTag = (tag: any) => {
var r = [tag.name];
if(tag.parent) { r.unshift(resolveTag(tag.parent)); }
return r;
}
return song.tags.map((tag: any) => resolveTag(tag));
}
}
const doQuery = async (_query: QueryElem) => {

@ -28,7 +28,6 @@ export default function QBSelectWithRequest(props: IProps & any) {
const updateOptions = (forInput: string, options: any[]) => {
if (forInput === input) {
console.log("setting options.");
setOptions({
forInput: forInput,
options: options,
@ -37,11 +36,9 @@ export default function QBSelectWithRequest(props: IProps & any) {
}
const startRequest = (_input: string) => {
console.log('starting req', _input);
setInput(_input);
(async () => {
const newOptions = await getNewOptions(_input);
console.log('new options', newOptions);
updateOptions(_input, newOptions);
})();
};
@ -72,8 +69,6 @@ export default function QBSelectWithRequest(props: IProps & any) {
}
}
console.log("Render props:", props);
return (
<Autocomplete
{...restProps}

@ -1,10 +1,12 @@
import React from 'react';
import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody } from '@material-ui/core';
import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody, Chip } from '@material-ui/core';
import stringifyList from '../../lib/stringifyList';
export interface SongGetters {
getTitle: (song: any) => string,
getArtist: (song: any) => string,
getAlbum: (song: any) => string,
getTags: (song: any) => string[][], // Each tag is represented as a series of strings.
}
export interface IProps {
@ -28,18 +30,25 @@ export function SongTable(props: IProps) {
<TableCell align="left">Title</TableCell>
<TableCell align="left">Artist</TableCell>
<TableCell align="left">Album</TableCell>
<TableCell align="left">Tags</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.songs.map((song:any) => {
{props.songs.map((song: any) => {
const title = props.songGetters.getTitle(song);
const artist = props.songGetters.getArtist(song);
const album = props.songGetters.getAlbum(song);
const tags = props.songGetters.getTags(song).map((tag: string[]) => {
return <Chip label={stringifyList(tag, undefined, (idx: number, e: string) => {
return (idx === 0) ? e : " / " + e;
})} />
});
return <TableRow key={title}>
<TableCell align="left">{title}</TableCell>
<TableCell align="left">{artist}</TableCell>
<TableCell align="left">{album}</TableCell>
<TableCell align="left">{tags}</TableCell>
</TableRow>
})}
</TableBody>

@ -1,12 +1,20 @@
export default function stringifyList(
s: any[],
stringifyElem?: (e: any) => string,
stringifyConnect?: (idx: number, e: any) => string,
) {
const stringify = stringifyElem || ((e: any) => e);
if(!stringifyElem) {
stringifyElem = (e: any) => e;
}
if(!stringifyConnect) {
stringifyConnect = (idx: number, e: string) => {
return (idx === 0) ? e : ", " + e;
}
}
var r = "";
if (s.length > 0) { r += stringify(s[0]) }
for (let i = 1; i < s.length; i++) {
r += ", " + stringify(s[i]);
for (let i = 0; i < s.length; i++) {
r += stringifyConnect(i, stringifyElem(s[i]));
}
return r;

@ -2,6 +2,7 @@ import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
import asJson from '../lib/asJson';
import { toApiArtist, toApiTag, toApiAlbum, toApiSong } from '../lib/dbToApi';
enum ObjectType {
Song = 0,
@ -230,6 +231,21 @@ async function getLinkedObjects(knex: Knex, base: ObjectType, linked: ObjectType
return result;
}
// Resolve a tag into the full nested structure of its ancestors.
async function getFullTag(knex: Knex, tag: any): Promise<any> {
const resolveTag = async (t: any) => {
if (t['tags.parentId']) {
const parent = (await knex.select(objectColumns[ObjectType.Tag])
.from('tags')
.where({ [objectTables[ObjectType.Tag] + '.id']: t['tags.parentId'] }))[0];
t.parent = await resolveTag(parent);
}
return t;
}
return await resolveTag(tag);
}
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkQueryRequest(req.body)) {
const e: EndpointError = {
@ -295,7 +311,6 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
// For that we need to do further queries.
const songIdsPromise = (async () => {
const songs = await songsPromise;
console.log("Found songs:", songs);
const ids = songs.map((song: any) => song['songs.id']);
return ids;
})();
@ -306,7 +321,17 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
(async () => { return {}; })();
const songsTagsPromise: Promise<Record<number, any[]>> = (songLimit && songLimit > 0) ?
(async () => {
return await getLinkedObjects(knex, ObjectType.Song, ObjectType.Tag, await songIdsPromise);
const tagsPerSong: Record<number, any> = await getLinkedObjects(knex, ObjectType.Song, ObjectType.Tag, await songIdsPromise);
var result: Record<number, any> = {};
for (var key in tagsPerSong) {
const tags = tagsPerSong[key];
var fullTags: any[] = [];
for (var idx in tags) {
fullTags.push(await getFullTag(knex, tags[idx]));
}
result[key] = fullTags;
}
return result;
})() :
(async () => { return {}; })();
const songsAlbumsPromise: Promise<Record<number, any[]>> = (songLimit && songLimit > 0) ?
@ -336,52 +361,17 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
const response: api.QueryResponse = {
songs: songs.map((song: any) => {
return <api.SongDetails>{
songId: song['songs.id'],
title: song['songs.title'],
storeLinks: asJson(song['songs.storeLinks']),
artists: songsArtists[song['songs.id']].map((artist: any) => {
return <api.ArtistDetails>{
artistId: artist['artists.id'],
name: artist['artists.name'],
storeLinks: asJson(artist['artists.storeLinks']),
};
}),
tags: songsTags[song['songs.id']].map((tag: any) => {
return <api.TagDetails>{
tagId: tag['tags.id'],
name: tag['tags.name'],
};
}),
albums: songsAlbums[song['songs.id']].map((album: any) => {
return <api.AlbumDetails>{
albumId: album['albums.id'],
name: album['albums.name'],
storeLinks: asJson(album['albums.storeLinks']),
};
}),
}
const id = song['songs.id'];
return toApiSong(song, songsArtists[id], songsTags[id], songsAlbums[id]);
}),
artists: artists.map((artist: any) => {
return <api.ArtistDetails>{
artistId: artist['artists.id'],
name: artist['artists.name'],
storeLinks: asJson(artist['artists.storeLinks']),
}
return toApiArtist(artist);
}),
albums: albums.map((album: any) => {
return <api.AlbumDetails>{
albumId: album['albums.id'],
name: album['albums.name'],
storeLinks: asJson(album['albums.storeLinks']),
}
return toApiAlbum(album);
}),
tags: tags.map((tag: any) => {
return <api.TagDetails>{
tagId: tag['tags.id'],
name: tag['tags.name'],
parentId: tag['tags.parentId'],
}
return toApiTag(tag);
}),
}

@ -0,0 +1,44 @@
import * as api from '../../client/src/api';
import asJson from './asJson';
export function toApiTag(dbObj: any): api.TagDetails {
return <api.TagDetails>{
tagId: dbObj['tags.id'],
name: dbObj['tags.name'],
parentId: dbObj['tags.parentId'],
parent: dbObj.parent ? toApiTag(dbObj.parent) : undefined,
};
}
export function toApiArtist(dbObj: any) {
return <api.ArtistDetails>{
artistId: dbObj['artists.id'],
name: dbObj['artists.name'],
storeLinks: asJson(dbObj['artists.storeLinks']),
};
}
export function toApiSong(dbObj: any, artists: any[], tags: any[], albums: any[]) {
return <api.SongDetails>{
songId: dbObj['songs.id'],
title: dbObj['songs.title'],
storeLinks: asJson(dbObj['songs.storeLinks']),
artists: artists.map((artist: any) => {
return toApiArtist(artist);
}),
tags: tags.map((tag: any) => {
return toApiTag(tag);
}),
albums: albums.map((album: any) => {
return toApiAlbum(album);
}),
}
}
export function toApiAlbum(dbObj: any) {
return <api.AlbumDetails>{
albumId: dbObj['albums.id'],
name: dbObj['albums.name'],
storeLinks: asJson(dbObj['albums.storeLinks']),
};
}
Loading…
Cancel
Save