diff --git a/client/src/App.tsx b/client/src/App.tsx
index 4adecdb..bbe4c9c 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,137 +1,29 @@
-import React, { useEffect } from 'react';
+import React from 'react';
-import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar';
-import { Query, isQuery, QueryKeys, QueryOrdering, OrderKey, TypesIncluded, isTypesIncluded, isQueryOrdering } from './types/Query';
-import QueryBrowseWindow from './components/QueryBrowseWindow';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import {
HashRouter as Router,
Switch,
- Route,
- useHistory,
- useLocation,
- Redirect
+ Route
} from "react-router-dom";
-import { Typography } from '@material-ui/core';
import Mockup from './components/Mockup';
-
-const JSURL = require('jsurl');
-
-function fixQuery(q: any): Query {
- if (!isQuery(q)) {
- return {
- [QueryKeys.TitleLike]: ''
- };
- }
- return q;
-}
-
-function fixOrder(q: any): QueryOrdering {
- if (!isQueryOrdering(q)) {
- return {
- [QueryKeys.OrderBy]: {
- [QueryKeys.OrderKey]: OrderKey.Name,
- },
- [QueryKeys.Ascending]: true,
- };
- }
- return q;
-}
-
-function fixTypes(q: any): TypesIncluded {
- if (!isTypesIncluded(q)) {
- return {
- [QueryKeys.Songs]: true,
- [QueryKeys.Artists]: false,
- [QueryKeys.Tags]: false,
- };
- }
- return q;
-}
-
-function AppBody() {
- const history = useHistory();
- const location = useLocation();
- const queryParams = new URLSearchParams(location.search);
-
- const itemQuery: Query | undefined = JSURL.tryParse(queryParams.get('query'), undefined);
- const itemOrder: QueryOrdering | undefined = JSURL.tryParse(queryParams.get('order'), undefined);
- const itemTypes: TypesIncluded | undefined = JSURL.tryParse(queryParams.get('types'), undefined);
-
- const pushQuery = (
- q: Query,
- o: QueryOrdering,
- t: TypesIncluded
- ) => {
- const newParams = new URLSearchParams(location.search);
- newParams.set('query', JSURL.stringify(q));
- newParams.set('order', JSURL.stringify(o));
- newParams.set('types', JSURL.stringify(t));
- history.push({
- search: "?" + newParams.toString()
- })
- }
-
- useEffect(() => {
- const fq = fixQuery(itemQuery);
- const fo = fixOrder(itemOrder);
- const ft = fixTypes(itemTypes);
- if (fq !== itemQuery || fo !== itemOrder || ft !== itemTypes) {
- pushQuery(fq, fo, ft);
- return;
- }
- }, [ itemOrder, itemQuery, itemTypes ]);
-
- const onAppBarTabChange = (value: AppBarActiveTab) => {
- switch (value) {
- case AppBarActiveTab.Query: {
- history.push('/query');
- break;
- }
- }
- }
-
- const onQueryChange = (q: Query) => {
- pushQuery(q, fixOrder(itemOrder), fixTypes(itemTypes));
- }
- const onOrderChange = (o: QueryOrdering) => {
- pushQuery(fixQuery(itemQuery), o, fixTypes(itemTypes));
- }
- const onTypesChange = (t: TypesIncluded) => {
- pushQuery(fixQuery(itemQuery), fixOrder(itemOrder), t);
- }
-
- return (
-
- );
-}
-
-function AppMockup() {
- return
-}
+import Window from './components/Window';
function App() {
return (
-
+
+
+
+
+
+
+
+
+
);
diff --git a/client/src/components/AppBar.tsx b/client/src/components/AppBar.tsx
deleted file mode 100644
index a05de87..0000000
--- a/client/src/components/AppBar.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import React from 'react';
-import MuiAppBar from '@material-ui/core/AppBar';
-import Toolbar from '@material-ui/core/Toolbar';
-import IconButton from '@material-ui/core/IconButton';
-import Typography from '@material-ui/core/Typography';
-import InputBase from '@material-ui/core/InputBase';
-import { createStyles, fade, Theme, makeStyles } from '@material-ui/core/styles';
-import MenuIcon from '@material-ui/icons/Menu';
-import SearchIcon from '@material-ui/icons/Search';
-import Tabs from '@material-ui/core/Tabs';
-import Tab from '@material-ui/core/Tab';
-
-const useStyles = makeStyles((theme: Theme) =>
- createStyles({
- root: {
- flexGrow: 1,
- },
- menuButton: {
- marginRight: theme.spacing(2),
- },
- title: {
- flexGrow: 1,
- display: 'none',
- [theme.breakpoints.up('sm')]: {
- display: 'block',
- },
- },
- search: {
- position: 'relative',
- borderRadius: theme.shape.borderRadius,
- backgroundColor: fade(theme.palette.common.white, 0.15),
- '&:hover': {
- backgroundColor: fade(theme.palette.common.white, 0.25),
- },
- marginLeft: 0,
- width: '100%',
- [theme.breakpoints.up('sm')]: {
- marginLeft: theme.spacing(1),
- width: 'auto',
- },
- },
- searchIcon: {
- padding: theme.spacing(0, 2),
- height: '100%',
- position: 'absolute',
- pointerEvents: 'none',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- },
- inputRoot: {
- color: 'inherit',
- },
- inputInput: {
- padding: theme.spacing(1, 1, 1, 0),
- // vertical padding + font size from searchIcon
- paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
- transition: theme.transitions.create('width'),
- width: '100%',
- [theme.breakpoints.up('sm')]: {
- width: '12ch',
- '&:focus': {
- width: '20ch',
- },
- },
- },
- }),
-);
-
-export enum ActiveTab {
- Query = 0,
-}
-
-export interface IProps {
- activeTab: ActiveTab,
- onActiveTabChange: (tab:ActiveTab) => void
-}
-
-export default function AppBar(props: IProps) {
- const classes = useStyles();
-
- return (
-
-
-
-
-
-
- MuDBase
-
-
- { props.onActiveTabChange(idx); }}>
-
-
-
-
- );
-}
diff --git a/client/src/components/BrowseWindow.tsx b/client/src/components/BrowseWindow.tsx
deleted file mode 100644
index a117657..0000000
--- a/client/src/components/BrowseWindow.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-
-import { Paper } from '@material-ui/core';
-import { DisplayItem } from '../types/DisplayItem';
-import DraggableItemListItem from './DraggableItemListItem';
-import ItemList from './ItemList';
-import * as serverApi from '../api';
-import StoreIcon from '@material-ui/icons/Store';
-import { ReactComponent as GooglePlayIcon } from '../assets/googleplaymusic_icon.svg';
-
-type SongItem = serverApi.SongDetails;
-type ArtistItem = serverApi.ArtistDetails;
-export type Item = SongItem | ArtistItem;
-
-const getStoreIcon = (url: String) => {
- if (url.includes('play.google.com')) {
- return ;
- }
- return ;
-}
-
-function toDisplayItem(item: Item): DisplayItem | undefined {
- if (serverApi.isSongDetails(item)) {
- return {
- title: item.title,
- artistNames: (item.artists && item.artists.map((artist: serverApi.ArtistDetails) => {
- return artist.name;
- })) || ['Unknown'],
- tagNames: (item.tags && item.tags.map((tag: serverApi.TagDetails) => {
- return tag.name;
- })) || [],
- storeLinks: (item.storeLinks && item.storeLinks.map((url: String) => {
- return {
- icon: getStoreIcon(url),
- url: url
- }
- })) || [],
- }
- } else if (serverApi.isArtistDetails(item)) {
- return {
- name: item.name ? item.name : "Unknown",
- tagNames: [], // TODO
- storeLinks: (item.storeLinks && item.storeLinks.map((url: String) => {
- return {
- icon: getStoreIcon(url),
- url: url
- }
- })) || [],
- };
-
- }
- return undefined;
-}
-
-interface IProps {
- items: Item[]
-}
-
-export default function BrowseWindow(props: IProps) {
- return
-
- {props.items.map((item: Item) => {
- const di = toDisplayItem(item);
- return di && ;
- })}
-
- ;
-}
\ No newline at end of file
diff --git a/client/src/components/DraggableItemListItem.tsx b/client/src/components/DraggableItemListItem.tsx
deleted file mode 100644
index 318b0be..0000000
--- a/client/src/components/DraggableItemListItem.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import ItemListItem from './ItemListItem';
-import { useDrag } from 'react-dnd';
-import { dragTypes } from '../types/DragTypes';
-
-export default function DraggableItemListItem(props: any) {
- const [ /*{ isDragging: boolean }*/ , drag] = useDrag({
- item: { type: dragTypes.ListItem },
- collect: (monitor: any) => ({
- isDragging: !!monitor.isDragging(),
- }),
- });
-
- return
-
-
;
-}
\ No newline at end of file
diff --git a/client/src/components/EditArtistDialog.tsx b/client/src/components/EditArtistDialog.tsx
deleted file mode 100644
index 760f5bc..0000000
--- a/client/src/components/EditArtistDialog.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { Dialog, Grid, Typography, TextField, Button } from '@material-ui/core';
-
-var cloneDeep = require('lodash/cloneDeep');
-
-export interface ArtistProperties {
- name: String,
-}
-
-export interface IProps {
- dialogOpen: boolean,
- onClose?: () => void,
- onChangeArtistProperties?: (props: ArtistProperties) => void,
- artistProperties: ArtistProperties,
- onSubmit?: () => void,
-}
-
-export default function EditArtistDialog(props: IProps) {
- const onNameChange = (name: String) => {
- if (props.onChangeArtistProperties) {
- const p = cloneDeep(props.artistProperties);
- p.name = name;
- props.onChangeArtistProperties(p);
- }
- };
-
- return
-}
\ No newline at end of file
diff --git a/client/src/components/EditSongDialog.tsx b/client/src/components/EditSongDialog.tsx
deleted file mode 100644
index 70a60aa..0000000
--- a/client/src/components/EditSongDialog.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Dialog, Grid, Typography, TextField, Button } from '@material-ui/core';
-import { Autocomplete } from '@material-ui/lab';
-
-var cloneDeep = require('lodash/cloneDeep');
-
-export interface SongProperties {
- title: String,
- artistId: Number | undefined,
-}
-
-export interface ArtistProperties {
- name: String,
- id: Number,
-}
-
-export interface IProps {
- dialogOpen: boolean,
- onClose?: () => void,
- onChangeSongProperties?: (props: SongProperties) => void,
- songProperties: SongProperties,
- onSubmit?: () => void,
- artists: ArtistProperties[],
-}
-
-export default function EditSongDialog(props: IProps) {
- const onTitleChange = (title: String) => {
- if (props.onChangeSongProperties) {
- const p = cloneDeep(props.songProperties);
- p.title = title;
- props.onChangeSongProperties(p);
- }
- };
- const onArtistChange = (artist: Number | undefined) => {
- if (props.onChangeSongProperties) {
- const p = cloneDeep(props.songProperties);
- p.artistId = artist;
- props.onChangeSongProperties(p);
- }
- };
-
- return
-}
\ No newline at end of file
diff --git a/client/src/components/FilterControl.tsx b/client/src/components/FilterControl.tsx
deleted file mode 100644
index 510ac67..0000000
--- a/client/src/components/FilterControl.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import React from 'react';
-
-import {
- TextField,
- Paper,
- Select,
- MenuItem,
- Typography
-} from '@material-ui/core';
-
-import {
- TitleQuery,
- ArtistQuery,
- isTitleQuery,
- isArtistQuery,
- Query,
- isAndQuery,
- isOrQuery,
- QueryKeys,
-} from '../types/Query';
-
-interface TitleFilterControlProps {
- query: TitleQuery,
- onChangeQuery: (q: Query) => void,
-}
-function TitleFilterControl(props: TitleFilterControlProps) {
- return props.onChangeQuery({
- [QueryKeys.TitleLike]: i.target.value
- })}
- />
-}
-
-interface ArtistFilterControlProps {
- query: ArtistQuery,
- onChangeQuery: (q: Query) => void,
-}
-function ArtistFilterControl(props: ArtistFilterControlProps) {
- return props.onChangeQuery({
- [QueryKeys.ArtistLike]: i.target.value
- })}
- />
-}
-
-interface AndNodeControlProps {
- query: any,
- onChangeQuery: (q: Query) => void,
-}
-function AndNodeControl(props: AndNodeControlProps) {
- const onChangeSubQuery = (a: Query, b: Query) => {
- 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: Query) => void,
-}
-function OrNodeControl(props: OrNodeControlProps) {
- const onChangeSubQuery = (a: Query, b: Query) => {
- 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: Query | undefined,
- onChangeQuery: (query: Query) => void,
-}
-
-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({
- [QueryKeys.TitleLike]: ''
- })
- break;
- }
- case 'Artist': {
- props.onChangeQuery({
- [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;
- }
- }
- }
-
- 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) {
- return <>
- {props.query && isAndQuery(props.query) && }
- {props.query && isOrQuery(props.query) && }
- >;
-}
-
-export default function FilterControl(props: IProps) {
- const isLeaf = (query: Query | undefined) => {
- return query && (isTitleQuery(query) || isArtistQuery(query));
- }
- const isNode = (query: Query | undefined) => !isLeaf(query);
-
- return <>
- {isLeaf(props.query) && }
- {isNode(props.query) && }
- >
-}
\ No newline at end of file
diff --git a/client/src/components/ItemList.tsx b/client/src/components/ItemList.tsx
deleted file mode 100644
index 422224d..0000000
--- a/client/src/components/ItemList.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
-import List from '@material-ui/core/List';
-
-const useStyles = makeStyles((theme: Theme) =>
- createStyles({
- root: {
- flexGrow: 1,
- maxWidth: 752,
- },
- }),
-);
-
-export default function ItemList(props:any) {
- const classes = useStyles();
-
- return (
-
-
- {props.children}
-
-
- );
-}
diff --git a/client/src/components/ItemListArtistItem.tsx b/client/src/components/ItemListArtistItem.tsx
deleted file mode 100644
index b4458bb..0000000
--- a/client/src/components/ItemListArtistItem.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { useEffect } from 'react';
-import ItemListItem from './ItemListItem';
-import { ArtistDisplayItem, LoadingArtistDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- getDetails: () => Promise
-}
-
-export default function ItemListArtistItem(props: IProps) {
- const [ artist, setArtist ] = React.useState({ loadingArtist: true });
-
- useEffect(() => {
- props.getDetails()
- .then((details:ArtistDisplayItem) => { setArtist(details); });
- });
-
- return
-}
diff --git a/client/src/components/ItemListItem.tsx b/client/src/components/ItemListItem.tsx
deleted file mode 100644
index e4d1430..0000000
--- a/client/src/components/ItemListItem.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import { DisplayItem, isSong, isLoadingSong, isArtist, isLoadingArtist } from '../types/DisplayItem';
-import ItemListLoadedSongItem from './ItemListLoadedSongItem';
-import ItemListLoadingSongItem from './ItemListLoadingSongItem';
-import ItemListLoadedArtistItem from './ItemListLoadedArtistItem';
-import ItemListLoadingArtistItem from './ItemListLoadingArtistItem';
-
-export interface IProps {
- item: DisplayItem
-}
-
-export default function ItemListItem(props: IProps) {
- return <>
- {isSong(props.item) && }
- {isLoadingSong(props.item) && }
- {isArtist(props.item) && }
- {isLoadingArtist(props.item) && }
- >
-}
diff --git a/client/src/components/ItemListLoadedArtistItem.tsx b/client/src/components/ItemListLoadedArtistItem.tsx
deleted file mode 100644
index 2a8937b..0000000
--- a/client/src/components/ItemListLoadedArtistItem.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import ListItemText from '@material-ui/core/ListItemText';
-import GroupIcon from '@material-ui/icons/Group';
-import Chip from '@material-ui/core/Chip';
-
-import { ArtistDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- item: ArtistDisplayItem
-}
-
-export default function ItemListLoadedArtistItem(props: IProps) {
- return (
-
-
-
-
-
- {props.item.tagNames.map((tag: any) => {
- return
- })}
- {props.item.storeLinks.map((link: any) => {
- return
-
- {link.icon}
-
- ;
- })}
-
- );
-}
diff --git a/client/src/components/ItemListLoadedSongItem.tsx b/client/src/components/ItemListLoadedSongItem.tsx
deleted file mode 100644
index d3968a8..0000000
--- a/client/src/components/ItemListLoadedSongItem.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import ListItemText from '@material-ui/core/ListItemText';
-import MusicNoteIcon from '@material-ui/icons/MusicNote';
-import Chip from '@material-ui/core/Chip';
-
-import { SongDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- item: SongDisplayItem
-}
-
-export default function ItemListLoadedSongItem(props: IProps) {
- var artists = props.item.artistNames.length ? props.item.artistNames[0] : "Unknown";
- for (var i: number = 1; i < props.item.artistNames.length; i++) {
- artists = artists.concat(", " + props.item.artistNames[i]);
- }
-
- return (
-
-
-
-
-
- {props.item.tagNames.map((tag: any) => {
- return
- })}
- {props.item.storeLinks.map((link: any) => {
- return
-
- {link.icon}
-
- ;
- })}
-
- );
-}
diff --git a/client/src/components/ItemListLoadingArtistItem.tsx b/client/src/components/ItemListLoadingArtistItem.tsx
deleted file mode 100644
index 614030d..0000000
--- a/client/src/components/ItemListLoadingArtistItem.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import GroupIcon from '@material-ui/icons/Group';
-import CircularProgress from '@material-ui/core/CircularProgress';
-
-import { LoadingArtistDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- item: LoadingArtistDisplayItem
-}
-
-export default function ItemListLoadingArtistItem(props: IProps) {
- return (
-
-
-
-
-
-
- );
-}
diff --git a/client/src/components/ItemListLoadingSongItem.tsx b/client/src/components/ItemListLoadingSongItem.tsx
deleted file mode 100644
index 361ded6..0000000
--- a/client/src/components/ItemListLoadingSongItem.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import MusicNoteIcon from '@material-ui/icons/MusicNote';
-import CircularProgress from '@material-ui/core/CircularProgress';
-
-import { LoadingSongDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- item: LoadingSongDisplayItem
-}
-
-export default function ItemListLoadingSongItem(props: IProps) {
- return (
-
-
-
-
-
-
- );
-}
diff --git a/client/src/components/ItemListSongItem.tsx b/client/src/components/ItemListSongItem.tsx
deleted file mode 100644
index f3586b5..0000000
--- a/client/src/components/ItemListSongItem.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { useEffect } from 'react';
-import ItemListItem from './ItemListItem';
-import { SongDisplayItem, LoadingSongDisplayItem } from '../types/DisplayItem';
-
-export interface IProps {
- getDetails: () => Promise
-}
-
-export default function ItemListSongItem(props: IProps) {
- const [ song, setSong ] = React.useState({ loadingSong: true });
-
- useEffect(() => {
- props.getDetails()
- .then((details:SongDisplayItem) => { setSong(details); });
- });
-
- return
-}
diff --git a/client/src/components/QueryBrowseWindow.tsx b/client/src/components/QueryBrowseWindow.tsx
deleted file mode 100644
index 16dac6c..0000000
--- a/client/src/components/QueryBrowseWindow.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import React, { useState, useEffect } from 'react';
-
-import { Query, toApiQuery, QueryOrdering, TypesIncluded, QueryKeys, OrderKey } from '../types/Query';
-import FilterControl from './FilterControl';
-import * as serverApi from '../api';
-import BrowseWindow, { Item } from './BrowseWindow';
-import { FormControl, FormLabel, FormGroup, FormControlLabel, Checkbox, Select, MenuItem } from '@material-ui/core';
-
-const _ = require('lodash');
-
-interface ItemTypeCheckboxesProps {
- types: TypesIncluded,
- onChange: (types: TypesIncluded) => void;
-}
-
-function ItemTypeCheckboxes(props: ItemTypeCheckboxesProps) {
- const songChange = (v: any) => {
- props.onChange({
- [QueryKeys.Songs]: v.target.checked,
- [QueryKeys.Artists]: props.types[QueryKeys.Artists],
- [QueryKeys.Tags]: props.types[QueryKeys.Tags]
- });
- }
- const artistChange = (v: any) => {
- props.onChange({
- [QueryKeys.Songs]: props.types[QueryKeys.Songs],
- [QueryKeys.Artists]: v.target.checked,
- [QueryKeys.Tags]: props.types[QueryKeys.Tags]
- });
- }
- const tagChange = (v: any) => {
- props.onChange({
- [QueryKeys.Songs]: props.types[QueryKeys.Songs],
- [QueryKeys.Artists]: props.types[QueryKeys.Artists],
- [QueryKeys.Tags]: v.target.checked
- });
- }
-
- return
- Result types
-
- }
- label="Songs"
- />
- }
- label="Artists"
- />
- }
- label="Tags"
- />
-
- ;
-}
-
-interface OrderingWidgetProps {
- ordering: QueryOrdering,
- onChange: (o: QueryOrdering) => void;
-}
-
-function OrderingWidget(props: OrderingWidgetProps) {
- const onTypeChange = (e: any) => {
- props.onChange({
- [QueryKeys.OrderBy]: {
- [QueryKeys.OrderKey]: e.target.value,
- },
- [QueryKeys.Ascending]: props.ordering[QueryKeys.Ascending],
- });
- }
- const onAscendingChange = (e: any) => {
- props.onChange({
- [QueryKeys.OrderBy]: props.ordering[QueryKeys.OrderBy],
- [QueryKeys.Ascending]: (e.target.value === 'asc'),
- });
- }
-
- return
- Ordering
-
-
-
-
- ;
-}
-
-function toServerOrdering(o: QueryOrdering | undefined): serverApi.Ordering {
- if (!o) {
- return {
- orderBy: {
- type: serverApi.OrderByType.Name
- },
- ascending: true
- };
- }
-
- const keys = {
- [OrderKey.Name]: serverApi.OrderByType.Name,
- };
-
- return {
- orderBy: {
- type: keys[o[QueryKeys.OrderBy][QueryKeys.OrderKey]]
- },
- ascending: o[QueryKeys.Ascending],
- }
-}
-
-export interface IProps {
- query: Query | undefined,
- typesIncluded: TypesIncluded | undefined,
- resultOrder: QueryOrdering | undefined,
- onQueryChange: (q: Query) => void,
- onTypesChange: (t: TypesIncluded) => void,
- onOrderChange: (o: QueryOrdering) => void,
-}
-
-export default function QueryBrowseWindow(props: IProps) {
- const [songs, setSongs] = useState([]);
- const [artists, setArtists] = useState([]);
- //const [tags, setTags] = useState([]);
-
- var items: Item[] = [];
- props.typesIncluded && props.typesIncluded[QueryKeys.Songs] && items.push(...songs);
- props.typesIncluded && props.typesIncluded[QueryKeys.Artists] && items.push(...artists);
-
- useEffect(() => {
- if (!props.query) { return; }
- const q = _.cloneDeep(props.query);
- const r = _.cloneDeep(props.resultOrder);
- const t = _.cloneDeep(props.typesIncluded);
-
- const request: serverApi.QueryRequest = {
- query: toApiQuery(props.query),
- offsetsLimits: {
- songOffset: 0,
- songLimit: 5, // TODO
- artistOffset: 0,
- artistLimit: 5,
- tagOffset: 0,
- tagLimit: 5,
- },
- ordering: toServerOrdering(props.resultOrder),
- }
- const requestOpts = {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(request)
- };
- fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
- .then((response: any) => response.json())
- .then((json: any) => {
- const match = _.isEqual(q, props.query) && _.isEqual(r, props.resultOrder) && _.isEqual(t, props.typesIncluded);
- 'songs' in json && match && setSongs(json.songs);
- 'artists' in json && match && setArtists(json.artists);
- });
- }, [ props.query, props.resultOrder, props.typesIncluded ]);
-
- return <>
-
- Query
-
-
-
-
-
- >
-}
diff --git a/client/src/components/Window.tsx b/client/src/components/Window.tsx
new file mode 100644
index 0000000..f241ca6
--- /dev/null
+++ b/client/src/components/Window.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core';
+import { QBQueryElem } from './querybuilder/QBQueryElem';
+import { QueryLeafElem, QueryLeafBy, QueryLeafOp, QueryElem, QueryNodeElem, queryOr, queryAnd } from '../lib/Query';
+
+const darkTheme = createMuiTheme({
+ palette: {
+ type: 'dark'
+ },
+});
+
+export default function Window(props: any) {
+ let queens = new QueryLeafElem(
+ QueryLeafBy.ArtistName,
+ QueryLeafOp.Equals,
+ "Queens of the Stone Age"
+ );
+ let muse = new QueryLeafElem(
+ QueryLeafBy.ArtistName,
+ QueryLeafOp.Equals,
+ "Muse"
+ );
+ let dawnbros = new QueryLeafElem(
+ QueryLeafBy.ArtistName,
+ QueryLeafOp.Equals,
+ "Dawn Brothers"
+ );
+ let query = queryOr(
+ queryAnd(queens, muse),
+ dawnbros
+ );
+
+ return
+
+
+
+}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBAndBlock.tsx b/client/src/components/querybuilder/QBAndBlock.tsx
new file mode 100644
index 0000000..402a326
--- /dev/null
+++ b/client/src/components/querybuilder/QBAndBlock.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { Box, Paper } from '@material-ui/core';
+
+export default function QBAndBlock(props: any) {
+ return
+
+
+ {props.children.map((child: any) => {
+ return
+ {child}
+
+ })}
+
+
+
+}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBOrBlock.tsx b/client/src/components/querybuilder/QBOrBlock.tsx
new file mode 100644
index 0000000..deb09a9
--- /dev/null
+++ b/client/src/components/querybuilder/QBOrBlock.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Box, Typography } from '@material-ui/core';
+
+export default function QBOrBlock(props: any) {
+ const firstChild = Array.isArray(props.children) && props.children.length >= 1 ?
+ props.children[0] : undefined;
+
+ const otherChildren = Array.isArray(props.children) && props.children.length > 1 ?
+ props.children.slice(1) : [];
+
+ return
+
+ {firstChild}
+
+ {otherChildren.map((child: any) => {
+ return <>
+
+ Or
+
+
+ {child}
+
+ >;
+ })}
+
+}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBQueryElem.tsx b/client/src/components/querybuilder/QBQueryElem.tsx
new file mode 100644
index 0000000..1e71706
--- /dev/null
+++ b/client/src/components/querybuilder/QBQueryElem.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { QueryLeafElem, QueryNodeElem } from '../../lib/Query';
+import { QBQueryLeafElem } from './QBQueryLeafElem';
+import { QBQueryNodeElem } from './QBQueryNodeElem';
+
+export interface IProps {
+ elem: QueryLeafElem | QueryNodeElem,
+}
+
+export function QBQueryElem(props: IProps) {
+ let e = props.elem;
+
+ let renderLeaf = (l: any) => {
+ return
+ }
+
+ if (e instanceof QueryLeafElem) {
+ return renderLeaf(e);
+ } else if (e instanceof QueryNodeElem) {
+ return
+ }
+
+ throw "Unsupported query element";
+}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBQueryLeafElem.tsx b/client/src/components/querybuilder/QBQueryLeafElem.tsx
new file mode 100644
index 0000000..24dac07
--- /dev/null
+++ b/client/src/components/querybuilder/QBQueryLeafElem.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { QueryLeafElem, QueryLeafBy, QueryLeafOp, QueryNodeElem, QueryNodeOp } from '../../lib/Query';
+import { Chip, Typography } from '@material-ui/core';
+
+export interface LeafProps {
+ elem: QueryLeafElem
+}
+
+export function QBQueryElemArtistEquals(props: LeafProps) {
+ let e = props.elem;
+
+ const label =
+ By {e.b}
+ ;
+
+ return
+}
+
+export function QBQueryLeafElem(props: LeafProps) {
+ let e = props.elem;
+
+ if (e.a == QueryLeafBy.ArtistName &&
+ e.op == QueryLeafOp.Equals &&
+ typeof e.b == "string") {
+ return
+ }
+
+ throw "Unsupported leaf element";
+}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBQueryNodeElem.tsx b/client/src/components/querybuilder/QBQueryNodeElem.tsx
new file mode 100644
index 0000000..a5a5614
--- /dev/null
+++ b/client/src/components/querybuilder/QBQueryNodeElem.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import QBOrBlock from './QBOrBlock';
+import QBAndBlock from './QBAndBlock';
+import { QueryNodeElem, QueryNodeOp } from '../../lib/Query';
+
+export interface NodeProps {
+ elem: QueryNodeElem,
+ renderLeaf: (leaf: any) => any,
+}
+
+export function QBQueryNodeElem(props: NodeProps) {
+ let e = props.elem;
+
+ if (e.op == QueryNodeOp.And) {
+ return
+ {e.operands.map((o: any) => {
+ if(o instanceof QueryNodeElem) {
+ return
+ }
+ return props.renderLeaf(o);
+ })}
+
+ } else if (e.op == QueryNodeOp.Or) {
+ return
+ {e.operands.map((o: any) => {
+ if(o instanceof QueryNodeElem) {
+ return
+ }
+ return props.renderLeaf(o);
+ })}
+
+ }
+
+ throw "Unsupported node element";
+}
\ No newline at end of file
diff --git a/client/src/lib/Query.tsx b/client/src/lib/Query.tsx
new file mode 100644
index 0000000..f12578f
--- /dev/null
+++ b/client/src/lib/Query.tsx
@@ -0,0 +1,63 @@
+export enum QueryLeafBy {
+ ArtistName = 0,
+ AlbumName,
+ TagName,
+ SongTitle
+}
+
+export enum QueryLeafOp {
+ Equals = 0,
+ Like,
+}
+
+export type QueryLeafOperand = string | number;
+
+export class QueryLeafElem {
+ a: QueryLeafBy;
+ op: QueryLeafOp;
+ b: QueryLeafOperand;
+
+ constructor(
+ a: QueryLeafBy,
+ op: QueryLeafOp,
+ b: QueryLeafOperand
+ ) {
+ this.a = a;
+ this.op = op;
+ this.b = b;
+ }
+};
+
+export enum QueryNodeOp {
+ And = 0,
+ Or,
+}
+
+export class QueryNodeElem {
+ operands: QueryElem[];
+ op: QueryNodeOp;
+
+ constructor(
+ operands: QueryElem[],
+ op: QueryNodeOp
+ ) {
+ this.operands = operands;
+ this.op = op;
+ }
+}
+
+export function queryOr(...args: QueryElem[]) {
+ return new QueryNodeElem(
+ args,
+ QueryNodeOp.Or
+ );
+}
+
+export function queryAnd(...args: QueryElem[]) {
+ return new QueryNodeElem(
+ args,
+ QueryNodeOp.And
+ );
+}
+
+export type QueryElem = QueryLeafElem | QueryNodeElem;
\ No newline at end of file
diff --git a/client/src/types/DisplayItem.tsx b/client/src/types/DisplayItem.tsx
deleted file mode 100644
index bfa76d0..0000000
--- a/client/src/types/DisplayItem.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-export interface SongDisplayItem {
- title:String,
- artistNames:String[],
- tagNames:String[],
- storeLinks: {
- icon: JSX.Element,
- url: String,
- }[]
-}
-
-export interface LoadingSongDisplayItem {
- loadingSong: boolean,
-}
-
-export interface ArtistDisplayItem {
- name:String,
- tagNames:String[],
- storeLinks: {
- icon: JSX.Element,
- url: String,
- }[]
-}
-
-export interface LoadingArtistDisplayItem {
- loadingArtist: boolean,
-}
-
-export type DisplayItem = SongDisplayItem | LoadingSongDisplayItem | ArtistDisplayItem | LoadingArtistDisplayItem;
-
-export function isSong(item: DisplayItem): item is SongDisplayItem {
- return "title" in item;
-}
-
-export function isLoadingSong(item: DisplayItem): item is LoadingSongDisplayItem {
- return "loadingSong" in item;
-}
-
-export function isArtist(item: DisplayItem): item is ArtistDisplayItem {
- return "name" in item;
-}
-
-export function isLoadingArtist(item: DisplayItem): item is LoadingArtistDisplayItem {
- return "loadingArtist" in item;
-}
\ No newline at end of file
diff --git a/client/src/types/DragTypes.tsx b/client/src/types/DragTypes.tsx
deleted file mode 100644
index e30b478..0000000
--- a/client/src/types/DragTypes.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export const dragTypes = {
- ListItem: 'list item'
-}
\ No newline at end of file
diff --git a/client/src/types/Query.tsx b/client/src/types/Query.tsx
deleted file mode 100644
index b112b19..0000000
--- a/client/src/types/Query.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { QueryElemProperty, QueryFilterOp, QueryElemOp } from '../api';
-
-export enum QueryKeys {
- TitleLike = 'tl',
- ArtistLike = 'al',
- AndQuerySignature = 'and',
- OrQuerySignature = 'or',
- OperandA = 'a',
- OperandB = 'b',
- Name = 'n',
- ArtistRanking = 'an',
- TagRanking = 'tn',
- Songs = 's',
- Artists = 'at',
- Tags = 't',
- OrderBy = 'ob',
- OrderKey = 'ok',
- Ascending = 'asc'
-}
-
-export interface TitleQuery {
- [QueryKeys.TitleLike]: String
-};
-export function isTitleQuery(q: Query): q is TitleQuery {
- return QueryKeys.TitleLike in q;
-}
-export function TitleToApiQuery(q: TitleQuery) {
- return {
- 'prop': QueryElemProperty.songTitle,
- 'propOperand': '%' + q[QueryKeys.TitleLike] + '%',
- 'propOperator': QueryFilterOp.Like,
- }
-}
-
-export interface ArtistQuery {
- [QueryKeys.ArtistLike]: String
-};
-export function isArtistQuery(q: Query): q is ArtistQuery {
- return QueryKeys.ArtistLike in q;
-}
-export function ArtistToApiQuery(q: ArtistQuery) {
- return {
- 'prop': QueryElemProperty.artistName,
- 'propOperand': '%' + q[QueryKeys.ArtistLike] + '%',
- 'propOperator': QueryFilterOp.Like,
- }
-}
-
-export interface AndQuery {
- [QueryKeys.AndQuerySignature]: any,
- [QueryKeys.OperandA]: T,
- [QueryKeys.OperandB]: T,
-}
-export function isAndQuery(q: Query): q is AndQuery {
- return QueryKeys.AndQuerySignature in q;
-}
-export function AndToApiQuery(q: AndQuery) {
- return {
- 'childrenOperator': QueryElemOp.And,
- 'children': [
- toApiQuery(q.a),
- toApiQuery(q.b),
- ]
- }
-}
-
-export interface OrQuery {
- [QueryKeys.OrQuerySignature]: any,
- [QueryKeys.OperandA]: T,
- [QueryKeys.OperandB]: T,
-}
-export function isOrQuery(q: Query): q is OrQuery {
- return QueryKeys.OrQuerySignature in q;
-}
-export function OrToApiQuery(q: OrQuery) {
- return {
- 'childrenOperator': QueryElemOp.Or,
- 'children': [
- toApiQuery(q.a),
- toApiQuery(q.b),
- ]
- }
-}
-
-export type Query = TitleQuery | ArtistQuery | AndQuery | OrQuery;
-
-export enum OrderKey {
- Name = 'n',
-}
-
-export interface QueryOrdering {
- [QueryKeys.OrderBy]: {
- [QueryKeys.OrderKey]: OrderKey,
- }
- [QueryKeys.Ascending]: boolean,
-}
-
-export interface TypesIncluded {
- [QueryKeys.Songs]: boolean,
- [QueryKeys.Artists]: boolean,
- [QueryKeys.Tags]: boolean,
-}
-
-export function isQuery(q: any): q is Query {
- return q != null &&
- (isTitleQuery(q) || isArtistQuery(q) || isAndQuery(q) || isOrQuery(q));
-}
-
-export function isQueryOrdering(q: any): q is QueryOrdering {
- return q != null &&
- QueryKeys.OrderBy in q &&
- QueryKeys.OrderKey in q[QueryKeys.OrderBy] &&
- QueryKeys.Ascending in q;
-}
-
-export function isTypesIncluded(q: any): q is TypesIncluded {
- return q != null &&
- QueryKeys.Songs in q &&
- QueryKeys.Artists in q &&
- QueryKeys.Tags in q;
-}
-
-export function toApiQuery(q: Query): any {
- return (isTitleQuery(q) && TitleToApiQuery(q)) ||
- (isArtistQuery(q) && ArtistToApiQuery(q)) ||
- (isAndQuery(q) && AndToApiQuery(q)) ||
- (isOrQuery(q) && OrToApiQuery(q)) ||
- {};
-}
\ No newline at end of file