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 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 (
<div style={{ maxWidth: '100%' }}>
<Switch>
<Redirect exact from='/' to="/songs" />
<Route path='/songs'>
<AppBar activeTab={AppBarActiveTab.Songs} onActiveTabChange={onAppBarTabChange} />
<Redirect exact from='/' to="/query" />
<Route path='/query'>
<AppBar activeTab={AppBarActiveTab.Query} onActiveTabChange={onAppBarTabChange} />
<FilterControl
query={songQuery}
onChangeQuery={(squery: SongQuery) => {
@ -234,12 +230,6 @@ function AppBody() {
<SongList songs={songs} />
</Paper>
</Route>
<Route path='/artists'>
<AppBar activeTab={AppBarActiveTab.Artists} onActiveTabChange={onAppBarTabChange} />
<Paper>
<ArtistList />
</Paper>
</Route>
</Switch>
</div>
);

@ -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) {
</div>
</Toolbar>
<Tabs value={props.activeTab} onChange={(evt:any, idx:any) => { props.onActiveTabChange(idx); }}>
<Tab label="Songs"/>
<Tab label="Artists"/>
<Tab label="Query"/>
</Tabs>
</MuiAppBar>
</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 {
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 <TextField
label="Title"
value={props.query.titleLike}
value={props.query[QueryKeys.TitleLike]}
onChange={(i: any) => props.onChangeQuery({
titleLike: i.target.value
[QueryKeys.TitleLike]: i.target.value
})}
/>
}
@ -38,35 +40,106 @@ interface ArtistFilterControlProps {
function ArtistFilterControl(props: ArtistFilterControlProps) {
return <TextField
label="Name"
value={props.query.artistLike}
value={props.query[QueryKeys.ArtistLike]}
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 {
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,11 +147,20 @@ export default function FilterControl(props: IProps) {
}
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
value={selectOption}
value={selectTypeOption}
onChange={handleQueryOnChange}
>
{selectOptions.map((option: string) => {
{selectTypeOptions.map((option: string) => {
return <MenuItem value={option}>{option}</MenuItem>
})}
</Select>
@ -86,3 +168,27 @@ export default function FilterControl(props: IProps) {
{props.query && isArtistQuery(props.query) && <ArtistFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />}
</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 {
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<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 {
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)) ||
{};
}
Loading…
Cancel
Save