From 31196ffdf59ecd0d910158f52e40ea3ee59a48ef Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Mon, 3 Aug 2020 16:54:06 +0200 Subject: [PATCH] Shortened query keys. --- client/src/App.tsx | 26 ++--- client/src/components/AppBar.tsx | 6 +- client/src/components/ArtistTable.tsx | 35 ------- client/src/components/FilterControl.tsx | 134 +++++++++++++++++++++--- client/src/components/SongTable.tsx | 36 ------- client/src/types/Query.tsx | 71 +++++++++++-- 6 files changed, 190 insertions(+), 118 deletions(-) delete mode 100644 client/src/components/ArtistTable.tsx delete mode 100644 client/src/components/SongTable.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index bb39ca6..b8dde12 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -8,7 +8,7 @@ import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar'; import ItemList from './components/ItemList'; import ItemListItem from './components/ItemListItem'; import FilterControl from './components/FilterControl'; -import { SongQuery, toApiQuery, isSongQuery } from './types/Query'; +import { SongQuery, toApiQuery, isSongQuery, QueryKeys } from './types/Query'; import { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem'; import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg'; @@ -153,7 +153,7 @@ function AppBody() { if (!isSongQuery(songQuery)) { console.log("query"); queryParams.set('query', JSURL.stringify({ - 'titleLike': '' + [QueryKeys.TitleLike]: '' })); fixed = true; } @@ -183,7 +183,7 @@ function AppBody() { return; } - const query: SongQuery = songQuery || { 'titleLike': '' }; + const query: SongQuery = songQuery || { [QueryKeys.TitleLike]: '' }; setSongs([]); const request: serverApi.QuerySongsRequest = { query: toApiQuery(query), @@ -204,12 +204,8 @@ function AppBody() { const onAppBarTabChange = (value: AppBarActiveTab) => { switch (value) { - case AppBarActiveTab.Artists: { - history.push('/artists'); - break; - } - case AppBarActiveTab.Songs: { - history.push('/songs'); + case AppBarActiveTab.Query: { + history.push('/query'); break; } } @@ -218,9 +214,9 @@ function AppBody() { return (
- - - + + + { @@ -234,12 +230,6 @@ function AppBody() { - - - - - -
); diff --git a/client/src/components/AppBar.tsx b/client/src/components/AppBar.tsx index c173a64..a05de87 100644 --- a/client/src/components/AppBar.tsx +++ b/client/src/components/AppBar.tsx @@ -68,8 +68,7 @@ const useStyles = makeStyles((theme: Theme) => ); export enum ActiveTab { - Songs = 0, - Artists = 1, + Query = 0, } export interface IProps { @@ -108,8 +107,7 @@ export default function AppBar(props: IProps) { { props.onActiveTabChange(idx); }}> - - + diff --git a/client/src/components/ArtistTable.tsx b/client/src/components/ArtistTable.tsx deleted file mode 100644 index ceef43e..0000000 --- a/client/src/components/ArtistTable.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import MaterialTable from 'material-table'; - -export interface Entry { - name: String, - id: Number, -} - -export interface IProps { - artists: Entry[] -} - -export default function ArtistTable(props: IProps) { - const tableTitle = "Artists"; - const tableColumns = [ - { title: "Name", field: 'name' }, - ]; - const tableData = props.artists; - - const options = { - filtering: true, - paging: true, - pageSize: 100, - pageSizeOptions: [ 5, 10, 20, 50, 100 ] - }; - - return ( - - ); -} \ No newline at end of file diff --git a/client/src/components/FilterControl.tsx b/client/src/components/FilterControl.tsx index 7265873..107c894 100644 --- a/client/src/components/FilterControl.tsx +++ b/client/src/components/FilterControl.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactElement } from 'react'; import { TextField, @@ -13,10 +13,12 @@ import { ArtistQuery, isTitleQuery, isArtistQuery, - SongQuery + SongQuery, + isAndQuery, + isOrQuery, + QueryKeys, } from '../types/Query'; - interface TitleFilterControlProps { query: TitleQuery, onChangeQuery: (q: SongQuery) => void, @@ -24,9 +26,9 @@ interface TitleFilterControlProps { function TitleFilterControl(props: TitleFilterControlProps) { return props.onChangeQuery({ - titleLike: i.target.value + [QueryKeys.TitleLike]: i.target.value })} /> } @@ -38,35 +40,106 @@ interface ArtistFilterControlProps { function ArtistFilterControl(props: ArtistFilterControlProps) { return props.onChangeQuery({ - artistLike: i.target.value + [QueryKeys.ArtistLike]: i.target.value })} /> } +interface AndNodeControlProps { + query: any, + onChangeQuery: (q: SongQuery) => void, +} +function AndNodeControl(props: AndNodeControlProps) { + const onChangeSubQuery = (a: SongQuery, b: SongQuery) => { + props.onChangeQuery({ + [QueryKeys.AndQuerySignature]: true, + [QueryKeys.OperandA]: a, + [QueryKeys.OperandB]: b + }); + } + + return + {props.query && isAndQuery(props.query) && <> + And + { onChangeSubQuery(q, props.query.b); }} /> + { onChangeSubQuery(props.query.a, q); }} /> + } + ; +} + +interface OrNodeControlProps { + query: any, + onChangeQuery: (q: SongQuery) => void, +} +function OrNodeControl(props: OrNodeControlProps) { + const onChangeSubQuery = (a: SongQuery, b: SongQuery) => { + props.onChangeQuery({ + [QueryKeys.OrQuerySignature]: true, + [QueryKeys.OperandA]: a, + [QueryKeys.OperandB]: b + }); + } + + return + {props.query && isOrQuery(props.query) && <> + Or + { onChangeSubQuery(q, props.query.b); }} /> + { onChangeSubQuery(props.query.a, q); }} /> + } + ; +} + export interface IProps { query: SongQuery | undefined, onChangeQuery: (query: SongQuery) => void, } -export default function FilterControl(props: IProps) { - const selectOptions: string[] = ['Title', 'Artist']; - const selectOption: string = (props.query && isTitleQuery(props.query) && 'Title') || +export function FilterControlLeaf(props: IProps) { + const selectTypeOptions: string[] = ['Title', 'Artist']; + const selectTypeOption: string = (props.query && isTitleQuery(props.query) && 'Title') || (props.query && isArtistQuery(props.query) && 'Artist') || "Unknown"; + const selectInsertOptions: string[] = ['And', 'Or']; + const handleQueryOnChange = (event: any) => { switch (event.target.value) { case 'Title': { props.onChangeQuery({ - titleLike: '' + [QueryKeys.TitleLike]: '' }) break; } case 'Artist': { props.onChangeQuery({ - artistLike: '' + [QueryKeys.ArtistLike]: '' + }) + break; + } + } + } + + const handleInsertElem = (event: any) => { + switch (event.target.value) { + case 'And': { + props.onChangeQuery({ + [QueryKeys.AndQuerySignature]: true, + [QueryKeys.OperandA]: props.query || { [QueryKeys.TitleLike]: '' }, + [QueryKeys.OperandB]: { + [QueryKeys.TitleLike]: '' + } + }) + break; + } + case 'Or': { + props.onChangeQuery({ + [QueryKeys.OrQuerySignature]: true, + [QueryKeys.OperandA]: props.query || { [QueryKeys.TitleLike]: '' }, + [QueryKeys.OperandB]: { + [QueryKeys.TitleLike]: '' + } }) break; } @@ -74,15 +147,48 @@ export default function FilterControl(props: IProps) { } return + {/* The selector for inserting another element here. */} + + {/* The selector for the type of filter element. */} {props.query && isTitleQuery(props.query) && } {props.query && isArtistQuery(props.query) && } ; +} + +export function FilterControlNode(props: IProps) { + const selectTypeOptions: string[] = ['And', 'Or']; + const selectTypeOption: string = (props.query && isAndQuery(props.query) && 'And') || + (props.query && isOrQuery(props.query) && 'Or') || + "Unknown"; + + return <> + {props.query && isAndQuery(props.query) && } + {props.query && isOrQuery(props.query) && } + ; +} + +export default function FilterControl(props: IProps) { + const isLeaf = (query: SongQuery | undefined) => { + return query && (isTitleQuery(query) || isArtistQuery(query)); + } + const isNode = (query: SongQuery | undefined) => !isLeaf(query); + + return <> + {isLeaf(props.query) && } + {isNode(props.query) && } + } \ No newline at end of file diff --git a/client/src/components/SongTable.tsx b/client/src/components/SongTable.tsx deleted file mode 100644 index d22d19a..0000000 --- a/client/src/components/SongTable.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import MaterialTable from 'material-table'; - -export interface Entry { - title: String, - artistName: String -} - -export interface IProps { - songs: Entry[] -} - -export default function SongTable(props: IProps) { - const tableTitle = "Songs"; - const tableColumns = [ - { title: "Title", field: 'title' }, - { title: "Artist", field: 'artistName' }, - ]; - const tableData = props.songs; - - const options = { - filtering: true, - paging: true, - pageSize: 100, - pageSizeOptions: [ 5, 10, 20, 50, 100 ] - }; - - return ( - - ); -} \ No newline at end of file diff --git a/client/src/types/Query.tsx b/client/src/types/Query.tsx index 9b2836c..7ddb1dd 100644 --- a/client/src/types/Query.tsx +++ b/client/src/types/Query.tsx @@ -1,40 +1,89 @@ -import { SongQueryElemProperty, SongQueryFilterOp } from '../api'; +import { SongQueryElemProperty, SongQueryFilterOp, SongQueryElemOp } from '../api'; + +export enum QueryKeys { + TitleLike = 'tl', + ArtistLike = 'al', + AndQuerySignature = 'and', + OrQuerySignature = 'or', + OperandA = 'a', + OperandB = 'b', +} export interface TitleQuery { - titleLike: String + [QueryKeys.TitleLike]: String }; export function isTitleQuery(q: SongQuery): q is TitleQuery { - return "titleLike" in q; + return QueryKeys.TitleLike in q; } export function TitleToApiQuery(q: TitleQuery) { return { 'prop': SongQueryElemProperty.title, - 'propOperand': '%' + q.titleLike + '%', + 'propOperand': '%' + q[QueryKeys.TitleLike] + '%', 'propOperator': SongQueryFilterOp.Like, } } export interface ArtistQuery { - artistLike: String + [QueryKeys.ArtistLike]: String }; export function isArtistQuery(q: SongQuery): q is ArtistQuery { - return "artistLike" in q; + return QueryKeys.ArtistLike in q; } export function ArtistToApiQuery(q: ArtistQuery) { return { 'prop': SongQueryElemProperty.artistNames, - 'propOperand': '%' + q.artistLike + '%', + 'propOperand': '%' + q[QueryKeys.ArtistLike] + '%', 'propOperator': SongQueryFilterOp.Like, } } -export type SongQuery = TitleQuery | ArtistQuery; +export interface AndQuery { + [QueryKeys.AndQuerySignature]: any, + [QueryKeys.OperandA]: T, + [QueryKeys.OperandB]: T, +} +export function isAndQuery(q: SongQuery): q is AndQuery { + return QueryKeys.AndQuerySignature in q; +} +export function AndToApiQuery(q: AndQuery) { + return { + 'childrenOperator': SongQueryElemOp.And, + 'children': [ + toApiQuery(q.a), + toApiQuery(q.b), + ] + } +} + +export interface OrQuery { + [QueryKeys.OrQuerySignature]: any, + [QueryKeys.OperandA]: T, + [QueryKeys.OperandB]: T, +} +export function isOrQuery(q: SongQuery): q is OrQuery { + return QueryKeys.OrQuerySignature in q; +} +export function OrToApiQuery(q: OrQuery) { + return { + 'childrenOperator': SongQueryElemOp.Or, + 'children': [ + toApiQuery(q.a), + toApiQuery(q.b), + ] + } +} + +export type SongQuery = TitleQuery | ArtistQuery | AndQuery | OrQuery; + export function isSongQuery(q: any): q is SongQuery { return q != null && - (isTitleQuery(q) || isArtistQuery(q)); + (isTitleQuery(q) || isArtistQuery(q) || isAndQuery(q) || isOrQuery(q)); } -export function toApiQuery(q: SongQuery) { +export function toApiQuery(q: SongQuery): any { return (isTitleQuery(q) && TitleToApiQuery(q)) || - (isArtistQuery(q) && ArtistToApiQuery(q)) || {}; + (isArtistQuery(q) && ArtistToApiQuery(q)) || + (isAndQuery(q) && AndToApiQuery(q)) || + (isOrQuery(q) && OrToApiQuery(q)) || + {}; } \ No newline at end of file