Shortened query keys.

pull/7/head
Sander Vocke 5 years ago
parent 678228d223
commit 31196ffdf5
  1. 26
      client/src/App.tsx
  2. 6
      client/src/components/AppBar.tsx
  3. 35
      client/src/components/ArtistTable.tsx
  4. 134
      client/src/components/FilterControl.tsx
  5. 36
      client/src/components/SongTable.tsx
  6. 71
      client/src/types/Query.tsx

@ -8,7 +8,7 @@ import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar';
import ItemList from './components/ItemList'; import ItemList from './components/ItemList';
import ItemListItem from './components/ItemListItem'; import ItemListItem from './components/ItemListItem';
import FilterControl from './components/FilterControl'; 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 { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem';
import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg'; import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg';
@ -153,7 +153,7 @@ function AppBody() {
if (!isSongQuery(songQuery)) { if (!isSongQuery(songQuery)) {
console.log("query"); console.log("query");
queryParams.set('query', JSURL.stringify({ queryParams.set('query', JSURL.stringify({
'titleLike': '' [QueryKeys.TitleLike]: ''
})); }));
fixed = true; fixed = true;
} }
@ -183,7 +183,7 @@ function AppBody() {
return; return;
} }
const query: SongQuery = songQuery || { 'titleLike': '' }; const query: SongQuery = songQuery || { [QueryKeys.TitleLike]: '' };
setSongs([]); setSongs([]);
const request: serverApi.QuerySongsRequest = { const request: serverApi.QuerySongsRequest = {
query: toApiQuery(query), query: toApiQuery(query),
@ -204,12 +204,8 @@ function AppBody() {
const onAppBarTabChange = (value: AppBarActiveTab) => { const onAppBarTabChange = (value: AppBarActiveTab) => {
switch (value) { switch (value) {
case AppBarActiveTab.Artists: { case AppBarActiveTab.Query: {
history.push('/artists'); history.push('/query');
break;
}
case AppBarActiveTab.Songs: {
history.push('/songs');
break; break;
} }
} }
@ -218,9 +214,9 @@ function AppBody() {
return ( return (
<div style={{ maxWidth: '100%' }}> <div style={{ maxWidth: '100%' }}>
<Switch> <Switch>
<Redirect exact from='/' to="/songs" /> <Redirect exact from='/' to="/query" />
<Route path='/songs'> <Route path='/query'>
<AppBar activeTab={AppBarActiveTab.Songs} onActiveTabChange={onAppBarTabChange} /> <AppBar activeTab={AppBarActiveTab.Query} onActiveTabChange={onAppBarTabChange} />
<FilterControl <FilterControl
query={songQuery} query={songQuery}
onChangeQuery={(squery: SongQuery) => { onChangeQuery={(squery: SongQuery) => {
@ -234,12 +230,6 @@ function AppBody() {
<SongList songs={songs} /> <SongList songs={songs} />
</Paper> </Paper>
</Route> </Route>
<Route path='/artists'>
<AppBar activeTab={AppBarActiveTab.Artists} onActiveTabChange={onAppBarTabChange} />
<Paper>
<ArtistList />
</Paper>
</Route>
</Switch> </Switch>
</div> </div>
); );

@ -68,8 +68,7 @@ const useStyles = makeStyles((theme: Theme) =>
); );
export enum ActiveTab { export enum ActiveTab {
Songs = 0, Query = 0,
Artists = 1,
} }
export interface IProps { export interface IProps {
@ -108,8 +107,7 @@ export default function AppBar(props: IProps) {
</div> </div>
</Toolbar> </Toolbar>
<Tabs value={props.activeTab} onChange={(evt:any, idx:any) => { props.onActiveTabChange(idx); }}> <Tabs value={props.activeTab} onChange={(evt:any, idx:any) => { props.onActiveTabChange(idx); }}>
<Tab label="Songs"/> <Tab label="Query"/>
<Tab label="Artists"/>
</Tabs> </Tabs>
</MuiAppBar> </MuiAppBar>
</div> </div>

@ -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 (
<MaterialTable
title={tableTitle}
columns={tableColumns}
data={tableData}
options={options}
/>
);
}

@ -1,4 +1,4 @@
import React from 'react'; import React, { ReactElement } from 'react';
import { import {
TextField, TextField,
@ -13,10 +13,12 @@ import {
ArtistQuery, ArtistQuery,
isTitleQuery, isTitleQuery,
isArtistQuery, isArtistQuery,
SongQuery SongQuery,
isAndQuery,
isOrQuery,
QueryKeys,
} from '../types/Query'; } from '../types/Query';
interface TitleFilterControlProps { interface TitleFilterControlProps {
query: TitleQuery, query: TitleQuery,
onChangeQuery: (q: SongQuery) => void, onChangeQuery: (q: SongQuery) => void,
@ -24,9 +26,9 @@ interface TitleFilterControlProps {
function TitleFilterControl(props: TitleFilterControlProps) { function TitleFilterControl(props: TitleFilterControlProps) {
return <TextField return <TextField
label="Title" label="Title"
value={props.query.titleLike} value={props.query[QueryKeys.TitleLike]}
onChange={(i: any) => props.onChangeQuery({ onChange={(i: any) => props.onChangeQuery({
titleLike: i.target.value [QueryKeys.TitleLike]: i.target.value
})} })}
/> />
} }
@ -38,35 +40,106 @@ interface ArtistFilterControlProps {
function ArtistFilterControl(props: ArtistFilterControlProps) { function ArtistFilterControl(props: ArtistFilterControlProps) {
return <TextField return <TextField
label="Name" label="Name"
value={props.query.artistLike} value={props.query[QueryKeys.ArtistLike]}
onChange={(i: any) => props.onChangeQuery({ onChange={(i: any) => 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 <Paper>
{props.query && isAndQuery(props.query) && <>
<Typography>And</Typography>
<FilterControl query={props.query.a} onChangeQuery={(q: SongQuery) => { onChangeSubQuery(q, props.query.b); }} />
<FilterControl query={props.query.b} onChangeQuery={(q: SongQuery) => { onChangeSubQuery(props.query.a, q); }} />
</>}
</Paper>;
}
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 <Paper>
{props.query && isOrQuery(props.query) && <>
<Typography>Or</Typography>
<FilterControl query={props.query.a} onChangeQuery={(q: SongQuery) => { onChangeSubQuery(q, props.query.b); }} />
<FilterControl query={props.query.b} onChangeQuery={(q: SongQuery) => { onChangeSubQuery(props.query.a, q); }} />
</>}
</Paper>;
}
export interface IProps { export interface IProps {
query: SongQuery | undefined, query: SongQuery | undefined,
onChangeQuery: (query: SongQuery) => void, onChangeQuery: (query: SongQuery) => void,
} }
export default function FilterControl(props: IProps) { export function FilterControlLeaf(props: IProps) {
const selectOptions: string[] = ['Title', 'Artist']; const selectTypeOptions: string[] = ['Title', 'Artist'];
const selectOption: string = (props.query && isTitleQuery(props.query) && 'Title') || const selectTypeOption: string = (props.query && isTitleQuery(props.query) && 'Title') ||
(props.query && isArtistQuery(props.query) && 'Artist') || (props.query && isArtistQuery(props.query) && 'Artist') ||
"Unknown"; "Unknown";
const selectInsertOptions: string[] = ['And', 'Or'];
const handleQueryOnChange = (event: any) => { const handleQueryOnChange = (event: any) => {
switch (event.target.value) { switch (event.target.value) {
case 'Title': { case 'Title': {
props.onChangeQuery({ props.onChangeQuery({
titleLike: '' [QueryKeys.TitleLike]: ''
}) })
break; break;
} }
case 'Artist': { case 'Artist': {
props.onChangeQuery({ 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; break;
} }
@ -74,15 +147,48 @@ export default function FilterControl(props: IProps) {
} }
return <Paper> return <Paper>
{/* The selector for inserting another element here. */}
<Select
onChange={handleInsertElem}
>
{selectInsertOptions.map((option: string) => {
return <MenuItem value={option}>{option}</MenuItem>
})}
</Select>
{/* The selector for the type of filter element. */}
<Select <Select
value={selectOption} value={selectTypeOption}
onChange={handleQueryOnChange} onChange={handleQueryOnChange}
> >
{selectOptions.map((option: string) => { {selectTypeOptions.map((option: string) => {
return <MenuItem value={option}>{option}</MenuItem> return <MenuItem value={option}>{option}</MenuItem>
})} })}
</Select> </Select>
{props.query && isTitleQuery(props.query) && <TitleFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />} {props.query && isTitleQuery(props.query) && <TitleFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />}
{props.query && isArtistQuery(props.query) && <ArtistFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />} {props.query && isArtistQuery(props.query) && <ArtistFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />}
</Paper>; </Paper>;
}
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) && <AndNodeControl {...props} />}
{props.query && isOrQuery(props.query) && <OrNodeControl {...props} />}
</>;
}
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) && <FilterControlLeaf {...props} />}
{isNode(props.query) && <FilterControlNode {...props} />}
</>
} }

@ -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 (
<MaterialTable
title={tableTitle}
columns={tableColumns}
data={tableData}
options={options}
/>
);
}

@ -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 { export interface TitleQuery {
titleLike: String [QueryKeys.TitleLike]: String
}; };
export function isTitleQuery(q: SongQuery): q is TitleQuery { export function isTitleQuery(q: SongQuery): q is TitleQuery {
return "titleLike" in q; return QueryKeys.TitleLike in q;
} }
export function TitleToApiQuery(q: TitleQuery) { export function TitleToApiQuery(q: TitleQuery) {
return { return {
'prop': SongQueryElemProperty.title, 'prop': SongQueryElemProperty.title,
'propOperand': '%' + q.titleLike + '%', 'propOperand': '%' + q[QueryKeys.TitleLike] + '%',
'propOperator': SongQueryFilterOp.Like, 'propOperator': SongQueryFilterOp.Like,
} }
} }
export interface ArtistQuery { export interface ArtistQuery {
artistLike: String [QueryKeys.ArtistLike]: String
}; };
export function isArtistQuery(q: SongQuery): q is ArtistQuery { export function isArtistQuery(q: SongQuery): q is ArtistQuery {
return "artistLike" in q; return QueryKeys.ArtistLike in q;
} }
export function ArtistToApiQuery(q: ArtistQuery) { export function ArtistToApiQuery(q: ArtistQuery) {
return { return {
'prop': SongQueryElemProperty.artistNames, 'prop': SongQueryElemProperty.artistNames,
'propOperand': '%' + q.artistLike + '%', 'propOperand': '%' + q[QueryKeys.ArtistLike] + '%',
'propOperator': SongQueryFilterOp.Like, 'propOperator': SongQueryFilterOp.Like,
} }
} }
export type SongQuery = TitleQuery | ArtistQuery; export interface AndQuery<T> {
[QueryKeys.AndQuerySignature]: any,
[QueryKeys.OperandA]: T,
[QueryKeys.OperandB]: T,
}
export function isAndQuery(q: SongQuery): q is AndQuery<SongQuery> {
return QueryKeys.AndQuerySignature in q;
}
export function AndToApiQuery(q: AndQuery<SongQuery>) {
return {
'childrenOperator': SongQueryElemOp.And,
'children': [
toApiQuery(q.a),
toApiQuery(q.b),
]
}
}
export interface OrQuery<T> {
[QueryKeys.OrQuerySignature]: any,
[QueryKeys.OperandA]: T,
[QueryKeys.OperandB]: T,
}
export function isOrQuery(q: SongQuery): q is OrQuery<SongQuery> {
return QueryKeys.OrQuerySignature in q;
}
export function OrToApiQuery(q: OrQuery<SongQuery>) {
return {
'childrenOperator': SongQueryElemOp.Or,
'children': [
toApiQuery(q.a),
toApiQuery(q.b),
]
}
}
export type SongQuery = TitleQuery | ArtistQuery | AndQuery<SongQuery> | OrQuery<SongQuery>;
export function isSongQuery(q: any): q is SongQuery { export function isSongQuery(q: any): q is SongQuery {
return q != null && 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)) || return (isTitleQuery(q) && TitleToApiQuery(q)) ||
(isArtistQuery(q) && ArtistToApiQuery(q)) || {}; (isArtistQuery(q) && ArtistToApiQuery(q)) ||
(isAndQuery(q) && AndToApiQuery(q)) ||
(isOrQuery(q) && OrToApiQuery(q)) ||
{};
} }
Loading…
Cancel
Save