diff --git a/client/src/api.ts b/client/src/api.ts index 812ad32..3ef33f7 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -108,6 +108,7 @@ export interface QueryResponse { songs: SongDetails[], artists: ArtistDetails[], tags: TagDetails[], + albums: AlbumDetails[], } export interface OffsetsLimits { songOffset?: number, @@ -116,6 +117,8 @@ export interface OffsetsLimits { artistLimit?: number, tagOffset?: number, tagLimit?: number, + albumOffset?: number, + albumLimit?: number, } export function checkQueryElem(elem: any): boolean { if (elem.childrenOperator && elem.children) { diff --git a/client/src/components/Window.tsx b/client/src/components/Window.tsx index 52856da..3b13f01 100644 --- a/client/src/components/Window.tsx +++ b/client/src/components/Window.tsx @@ -5,6 +5,7 @@ import QueryBuilder from './querybuilder/QueryBuilder'; import * as serverApi from '../api'; import { SongTable } from './tables/ResultsTable'; import stringifyList from '../lib/stringifyList'; +import { getArtists, getSongTitles, getAlbums } from '../lib/query/Getters'; var _ = require('lodash'); const darkTheme = createMuiTheme({ @@ -13,76 +14,6 @@ const darkTheme = createMuiTheme({ }, }); -export async function getArtists(filter: string) { - const query = filter.length > 0 ? { - prop: serverApi.QueryElemProperty.artistName, - propOperand: filter, - propOperator: serverApi.QueryFilterOp.Like, - } : {}; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - artistOffset: 0, - artistLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - const names: string[] = json.artists.map((elem: any) => { return elem.name; }); - return [...new Set(names)]; - })(); -} - -export async function getSongTitles(filter: string) { - const query = filter.length > 0 ? { - prop: serverApi.QueryElemProperty.songTitle, - propOperand: filter, - propOperator: serverApi.QueryFilterOp.Like, - } : {}; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - songOffset: 0, - songLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - const titles: string[] = json.songs.map((elem: any) => { return elem.title; }); - return [...new Set(titles)]; - })(); -} - export default function Window(props: any) { interface ResultsFor { for: QueryElem, @@ -160,6 +91,7 @@ export default function Window(props: any) { requestFunctions={{ getArtists: getArtists, getSongTitles: getSongTitles, + getAlbums: getAlbums, }} /> diff --git a/client/src/components/querybuilder/QBAddElemMenu.tsx b/client/src/components/querybuilder/QBAddElemMenu.tsx index b0fc151..acce42d 100644 --- a/client/src/components/querybuilder/QBAddElemMenu.tsx +++ b/client/src/components/querybuilder/QBAddElemMenu.tsx @@ -59,5 +59,23 @@ export function QBAddElemMenu(props: MenuProps) { style={{ width: 300 }} /> + + { + onClose(); + props.onCreateQuery({ + a: QueryLeafBy.AlbumName, + leafOp: exact ? QueryLeafOp.Equals : QueryLeafOp.Like, + b: s + }); + }} + style={{ width: 300 }} + /> + } diff --git a/client/src/components/querybuilder/QBLeafElem.tsx b/client/src/components/querybuilder/QBLeafElem.tsx index f4904ea..d772b7e 100644 --- a/client/src/components/querybuilder/QBLeafElem.tsx +++ b/client/src/components/querybuilder/QBLeafElem.tsx @@ -51,6 +51,20 @@ export function QBQueryElemTitleLike(props: LeafProps) { /> } +export function QBQueryElemAlbumEquals(props: LeafProps) { + return +} + +export function QBQueryElemAlbumLike(props: LeafProps) { + return +} + export interface DeleteButtonProps { onClick?: (e: any) => void, } @@ -97,6 +111,20 @@ export function QBLeafElem(props: IProps) { {...props} extraElements={extraElements} /> + } else if (e.a == QueryLeafBy.AlbumName && + e.leafOp == QueryLeafOp.Equals && + typeof e.b == "string") { + return + } else if (e.a == QueryLeafBy.AlbumName && + e.leafOp == QueryLeafOp.Like && + typeof e.b == "string") { + return } if (e.a == QueryLeafBy.SongTitle && e.leafOp == QueryLeafOp.Equals && typeof e.b == "string") { diff --git a/client/src/components/querybuilder/QueryBuilder.tsx b/client/src/components/querybuilder/QueryBuilder.tsx index dc73d57..25fab68 100644 --- a/client/src/components/querybuilder/QueryBuilder.tsx +++ b/client/src/components/querybuilder/QueryBuilder.tsx @@ -7,6 +7,7 @@ import { QueryElem, addPlaceholders, removePlaceholders, simplify } from '../../ export interface Requests { getArtists: (filter: string) => Promise, + getAlbums: (filter: string) => Promise, getSongTitles: (filter: string) => Promise, } diff --git a/client/src/lib/query/Getters.tsx b/client/src/lib/query/Getters.tsx new file mode 100644 index 0000000..56c4f33 --- /dev/null +++ b/client/src/lib/query/Getters.tsx @@ -0,0 +1,106 @@ +import * as serverApi from '../../api'; + +export async function getArtists(filter: string) { + const query = filter.length > 0 ? { + prop: serverApi.QueryElemProperty.artistName, + propOperand: filter, + propOperator: serverApi.QueryFilterOp.Like, + } : {}; + + var q: serverApi.QueryRequest = { + query: query, + offsetsLimits: { + artistOffset: 0, + artistLimit: 100, + }, + ordering: { + orderBy: { + type: serverApi.OrderByType.Name, + }, + ascending: true, + }, + }; + + const requestOpts = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(q), + }; + + return (async () => { + const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) + let json: any = await response.json(); + const names: string[] = json.artists.map((elem: any) => { return elem.name; }); + return [...new Set(names)]; + })(); +} + +export async function getAlbums(filter: string) { + const query = filter.length > 0 ? { + prop: serverApi.QueryElemProperty.albumName, + propOperand: filter, + propOperator: serverApi.QueryFilterOp.Like, + } : {}; + + var q: serverApi.QueryRequest = { + query: query, + offsetsLimits: { + albumOffset: 0, + albumLimit: 100, + }, + ordering: { + orderBy: { + type: serverApi.OrderByType.Name, + }, + ascending: true, + }, + }; + + const requestOpts = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(q), + }; + + return (async () => { + const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) + let json: any = await response.json(); + const names: string[] = json.albums.map((elem: any) => { return elem.name; }); + return [...new Set(names)]; + })(); +} + +export async function getSongTitles(filter: string) { + const query = filter.length > 0 ? { + prop: serverApi.QueryElemProperty.songTitle, + propOperand: filter, + propOperator: serverApi.QueryFilterOp.Like, + } : {}; + + var q: serverApi.QueryRequest = { + query: query, + offsetsLimits: { + songOffset: 0, + songLimit: 100, + }, + ordering: { + orderBy: { + type: serverApi.OrderByType.Name, + }, + ascending: true, + }, + }; + + const requestOpts = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(q), + }; + + return (async () => { + const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) + let json: any = await response.json(); + const titles: string[] = json.songs.map((elem: any) => { return elem.title; }); + return [...new Set(titles)]; + })(); +} \ No newline at end of file diff --git a/client/src/lib/query/Query.tsx b/client/src/lib/query/Query.tsx index ad617b3..75f86e8 100644 --- a/client/src/lib/query/Query.tsx +++ b/client/src/lib/query/Query.tsx @@ -157,6 +157,7 @@ export function toApiQuery(q: QueryElem) : serverApi.Query { const propsMapping: any = { [QueryLeafBy.SongTitle]: serverApi.QueryElemProperty.songTitle, [QueryLeafBy.ArtistName]: serverApi.QueryElemProperty.artistName, + [QueryLeafBy.AlbumName]: serverApi.QueryElemProperty.albumName, } const leafOpsMapping: any = { [QueryLeafOp.Equals]: serverApi.QueryFilterOp.Eq, diff --git a/server/endpoints/QueryEndpointHandler.ts b/server/endpoints/QueryEndpointHandler.ts index 3be1197..6b37ea3 100644 --- a/server/endpoints/QueryEndpointHandler.ts +++ b/server/endpoints/QueryEndpointHandler.ts @@ -248,6 +248,8 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, const tagOffset = reqObject.offsetsLimits.tagOffset; const artistLimit = reqObject.offsetsLimits.artistLimit; const artistOffset = reqObject.offsetsLimits.artistOffset; + const albumLimit = reqObject.offsetsLimits.albumLimit; + const albumOffset = reqObject.offsetsLimits.albumOffset; const artistsPromise: Promise = (artistLimit && artistLimit > 0) ? constructQuery(knex, @@ -259,6 +261,16 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, ) : (async () => [])(); + const albumsPromise: Promise = (albumLimit && albumLimit > 0) ? + constructQuery(knex, + ObjectType.Album, + reqObject.query, + reqObject.ordering, + artistOffset || 0, + albumLimit + ) : + (async () => [])(); + const songsPromise: Promise = (songLimit && songLimit > 0) ? constructQuery(knex, ObjectType.Song, @@ -306,6 +318,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, const [ songs, artists, + albums, tags, songsArtists, songsTags, @@ -314,6 +327,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, await Promise.all([ songsPromise, artistsPromise, + albumsPromise, tagsPromise, songsArtistsPromise, songsTagsPromise, @@ -355,6 +369,13 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, storeLinks: asJson(artist['artists.storeLinks']), } }), + albums: albums.map((album: any) => { + return { + albumId: album['albums.id'], + name: album['albums.name'], + storeLinks: asJson(album['albums.storeLinks']), + } + }), tags: tags.map((tag: any) => { return { tagId: tag['tags.id'],