Add song, album windows

pull/20/head
Sander Vocke 5 years ago
parent 4c4fe78258
commit 0dc6b889bb
  1. 1
      client/src/api.ts
  2. 27
      client/src/components/MainWindow.tsx
  3. 42
      client/src/components/tables/ResultsTable.tsx
  4. 101
      client/src/components/windows/AlbumWindow.tsx
  5. 2
      client/src/components/windows/ArtistWindow.tsx
  6. 2
      client/src/components/windows/QueryWindow.tsx
  7. 101
      client/src/components/windows/SongWindow.tsx
  8. 101
      client/src/components/windows/TagWindow.tsx
  9. 37
      client/src/components/windows/Windows.tsx
  10. 2
      server/endpoints/QueryEndpointHandler.ts

@ -81,6 +81,7 @@ export enum QueryElemProperty {
artistName = "artistName",
artistId = "artistId",
albumName = "albumName",
albumId = "albumId",
tagId = "tagId",
}
export enum OrderByType {

@ -2,10 +2,13 @@ import React, { useReducer, useState, Reducer } from 'react';
import { ThemeProvider, CssBaseline, createMuiTheme, withWidth } from '@material-ui/core';
import { grey } from '@material-ui/core/colors';
import AppBar from './appbar/AppBar';
import QueryWindow, { QueryWindowReducer, QueryWindowState } from './windows/QueryWindow';
import QueryWindow from './windows/QueryWindow';
import { NewTabProps } from './appbar/AddTabMenu';
import { newWindowState, newWindowReducer, WindowState, WindowType } from './windows/Windows';
import { newWindowState, newWindowReducer, WindowType } from './windows/Windows';
import ArtistWindow from './windows/ArtistWindow';
import AlbumWindow from './windows/AlbumWindow';
import TagWindow from './windows/TagWindow';
import SongWindow from './windows/SongWindow';
var _ = require('lodash');
const darkTheme = createMuiTheme({
@ -98,13 +101,29 @@ export default function MainWindow(props: any) {
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Album:
return <AlbumWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Tag:
return <TagWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Song:
return <SongWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
default:
throw new Error("Unimplemented window type");
}
});
console.log("State:", state)
return <ThemeProvider theme={darkTheme}>
<CssBaseline />
<AppBar

@ -4,12 +4,16 @@ import stringifyList from '../../lib/stringifyList';
import { MainWindowStateActions } from '../MainWindow';
import { newWindowReducer, WindowType } from '../windows/Windows';
import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
export interface SongGetters {
getTitle: (song: any) => string,
getId: (song: any) => number,
getArtistNames: (song: any) => string[],
getArtistIds: (song: any) => number[],
getAlbumNames: (song: any) => string[],
getAlbumIds: (song: any) => number[],
getTagNames: (song: any) => string[][], // Each tag is represented as a series of strings.
}
@ -46,7 +50,11 @@ export function SongTable(props: IProps) {
const artist = stringifyList(artistNames);
const mainArtistId = props.songGetters.getArtistIds(song)[0];
const mainArtistName = artistNames[0];
const album = stringifyList(props.songGetters.getAlbumNames(song));
const albumNames = props.songGetters.getAlbumNames(song);
const album = stringifyList(albumNames);
const mainAlbumName = albumNames[0];
const mainAlbumId = props.songGetters.getAlbumIds(song)[0];
const songId = props.songGetters.getId(song);
const tags = props.songGetters.getTagNames(song).map((tag: string[]) => {
return <Box ml={0.5} mr={0.5}>
<Chip size="small" label={stringifyList(tag, undefined, (idx: number, e: string) => {
@ -56,7 +64,6 @@ export function SongTable(props: IProps) {
});
const onClickArtist = () => {
console.log("onClick!")
props.mainDispatch({
type: MainWindowStateActions.AddTab,
tabState: {
@ -64,12 +71,37 @@ export function SongTable(props: IProps) {
artistId: mainArtistId,
metadata: null,
},
tabLabel: "Artist " + mainArtistId,
tabReducer: newWindowReducer[WindowType.Artist],
tabType: WindowType.Artist,
})
}
const onClickAlbum = () => {
props.mainDispatch({
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><AlbumIcon/>{mainAlbumName}</>,
albumId: mainAlbumId,
metadata: null,
},
tabReducer: newWindowReducer[WindowType.Album],
tabType: WindowType.Album,
})
}
const onClickSong = () => {
props.mainDispatch({
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><AudiotrackIcon/>{title}</>,
songId: songId,
metadata: null,
},
tabReducer: newWindowReducer[WindowType.Song],
tabType: WindowType.Song,
})
}
const TextCell = (props: any) => {
const classes = makeStyles({
button: {
@ -94,9 +126,9 @@ export function SongTable(props: IProps) {
}
return <TableRow key={title}>
<TextCell align="left">{title}</TextCell>
<TextCell align="left" _onClick={onClickSong}>{title}</TextCell>
<TextCell align="left" _onClick={onClickArtist}>{artist}</TextCell>
<TextCell align="left">{album}</TextCell>
<TextCell align="left" _onClick={onClickAlbum}>{album}</TextCell>
<TableCell padding="none" align="left" width="25%">
<Box display="flex" alignItems="center">
{tags}

@ -0,0 +1,101 @@
import React, { useEffect } from 'react';
import { Box, Typography } from '@material-ui/core';
import AlbumIcon from '@material-ui/icons/Album';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
export interface AlbumMetadata {
name: string,
}
export interface AlbumWindowState extends WindowState {
albumId: number,
metadata: AlbumMetadata | null,
}
export enum AlbumWindowStateActions {
SetMetadata = "SetMetadata",
}
export function AlbumWindowReducer(state: AlbumWindowState, action: any) {
switch (action.type) {
case AlbumWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
default:
throw new Error("Unimplemented AlbumWindow state update.")
}
}
export interface IProps {
state: AlbumWindowState,
dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
}
export async function getAlbumMetadata(id: number) {
const query = {
prop: serverApi.QueryElemProperty.albumId,
propOperand: id,
propOperator: serverApi.QueryFilterOp.Eq,
};
var q: serverApi.QueryRequest = {
query: query,
offsetsLimits: {
albumOffset: 0,
albumLimit: 1,
},
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();
let album = json.albums[0];
return {
name: album.name
}
})();
}
export default function AlbumWindow(props: IProps) {
let metadata = props.state.metadata;
useEffect(() => {
getAlbumMetadata(props.state.albumId)
.then((m: AlbumMetadata) => {
console.log("metadata", m);
props.dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
}, []);
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap">
<Box
m={1}
mt={4}
width="80%"
>
<AlbumIcon style={{ fontSize: 80 }}/>
</Box>
<Box
m={1}
width="80%"
>
{metadata && <Typography variant="h4">{metadata.name}</Typography>}
</Box>
</Box>
}

@ -22,7 +22,7 @@ export function ArtistWindowReducer(state: ArtistWindowState, action: any) {
case ArtistWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
default:
throw new Error("Unimplemented QueryWindow state update.")
throw new Error("Unimplemented ArtistWindow state update.")
}
}

@ -74,9 +74,11 @@ export default function QueryWindow(props: IProps) {
const songGetters = {
getTitle: (song: any) => song.title,
getId: (song: any) => song.songId,
getArtistNames: (song: any) => song.artists.map((a: any) => a.name),
getArtistIds: (song: any) => song.artists.map((a: any) => a.artistId),
getAlbumNames: (song: any) => song.albums.map((a: any) => a.name),
getAlbumIds: (song: any) => song.albums.map((a: any) => a.albumId),
getTagNames: (song: any) => {
// Recursively resolve the name.
const resolveTag = (tag: any) => {

@ -0,0 +1,101 @@
import React, { useEffect } from 'react';
import { Box, Typography } from '@material-ui/core';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
export interface SongMetadata {
title: string,
}
export interface SongWindowState extends WindowState {
songId: number,
metadata: SongMetadata | null,
}
export enum SongWindowStateActions {
SetMetadata = "SetMetadata",
}
export function SongWindowReducer(state: SongWindowState, action: any) {
switch (action.type) {
case SongWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
default:
throw new Error("Unimplemented SongWindow state update.")
}
}
export interface IProps {
state: SongWindowState,
dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
}
export async function getSongMetadata(id: number) {
const query = {
prop: serverApi.QueryElemProperty.songId,
propOperand: id,
propOperator: serverApi.QueryFilterOp.Eq,
};
var q: serverApi.QueryRequest = {
query: query,
offsetsLimits: {
songOffset: 0,
songLimit: 1,
},
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();
let song = json.songs[0];
return {
title: song.title
}
})();
}
export default function SongWindow(props: IProps) {
let metadata = props.state.metadata;
useEffect(() => {
getSongMetadata(props.state.songId)
.then((m: SongMetadata) => {
console.log("metadata", m);
props.dispatch({
type: SongWindowStateActions.SetMetadata,
value: m
});
})
}, []);
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap">
<Box
m={1}
mt={4}
width="80%"
>
<AudiotrackIcon style={{ fontSize: 80 }}/>
</Box>
<Box
m={1}
width="80%"
>
{metadata && <Typography variant="h4">{metadata.title}</Typography>}
</Box>
</Box>
}

@ -0,0 +1,101 @@
import React, { useEffect } from 'react';
import { Box, Typography } from '@material-ui/core';
import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
export interface TagMetadata {
name: string,
}
export interface TagWindowState extends WindowState {
tagId: number,
metadata: TagMetadata | null,
}
export enum TagWindowStateActions {
SetMetadata = "SetMetadata",
}
export function TagWindowReducer(state: TagWindowState, action: any) {
switch (action.type) {
case TagWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
default:
throw new Error("Unimplemented TagWindow state update.")
}
}
export interface IProps {
state: TagWindowState,
dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
}
export async function getTagMetadata(id: number) {
const query = {
prop: serverApi.QueryElemProperty.tagId,
propOperand: id,
propOperator: serverApi.QueryFilterOp.Eq,
};
var q: serverApi.QueryRequest = {
query: query,
offsetsLimits: {
tagOffset: 0,
tagLimit: 1,
},
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();
let tag = json.tags[0];
return {
name: tag.name
}
})();
}
export default function TagWindow(props: IProps) {
let metadata = props.state.metadata;
useEffect(() => {
getTagMetadata(props.state.tagId)
.then((m: TagMetadata) => {
console.log("metadata", m);
props.dispatch({
type: TagWindowStateActions.SetMetadata,
value: m
});
})
}, []);
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap">
<Box
m={1}
mt={4}
width="80%"
>
<LocalOfferIcon style={{ fontSize: 80 }}/>
</Box>
<Box
m={1}
width="80%"
>
{metadata && <Typography variant="h4">{metadata.name}</Typography>}
</Box>
</Box>
}

@ -1,12 +1,21 @@
import React from 'react';
import { QueryWindowReducer, QueryWindowState } from "./QueryWindow";
import { ArtistWindowReducer, ArtistWindowState } from "./ArtistWindow";
import { QueryWindowReducer } from "./QueryWindow";
import { ArtistWindowReducer } from "./ArtistWindow";
import SearchIcon from '@material-ui/icons/Search';
import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album';
import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import { SongWindowReducer } from './SongWindow';
import { AlbumWindowReducer } from './AlbumWindow';
import { TagWindowReducer } from './TagWindow';
export enum WindowType {
Query = "Query",
Artist = "Artist",
Album = "Album",
Tag = "Tag",
Song = "Song",
}
export interface WindowState {
@ -16,6 +25,9 @@ export interface WindowState {
export const newWindowReducer = {
[WindowType.Query]: QueryWindowReducer,
[WindowType.Artist]: ArtistWindowReducer,
[WindowType.Album]: AlbumWindowReducer,
[WindowType.Song]: SongWindowReducer,
[WindowType.Tag]: TagWindowReducer,
}
export const newWindowState = {
@ -33,5 +45,26 @@ export const newWindowState = {
artistId: 1,
metadata: null,
}
},
[WindowType.Album]: () => {
return {
tabLabel: <><AlbumIcon/>Album</>,
albumId: 1,
metadata: null,
}
},
[WindowType.Song]: () => {
return {
tabLabel: <><AudiotrackIcon/>Song</>,
songId: 1,
metadata: null,
}
},
[WindowType.Tag]: () => {
return {
tabLabel: <><LocalOfferIcon/>Tag</>,
tagId: 1,
metadata: null,
}
},
}

@ -14,6 +14,7 @@ enum ObjectType {
// To keep track of which database objects are needed to filter on
// certain properties.
const propertyObjects: Record<api.QueryElemProperty, ObjectType> = {
[api.QueryElemProperty.albumId]: ObjectType.Album,
[api.QueryElemProperty.albumName]: ObjectType.Album,
[api.QueryElemProperty.artistId]: ObjectType.Artist,
[api.QueryElemProperty.artistName]: ObjectType.Artist,
@ -101,6 +102,7 @@ function addLeafWhere(knexQuery: any, queryElem: api.QueryElem, type: WhereType)
[api.QueryElemProperty.artistName]: 'artists.name',
[api.QueryElemProperty.artistId]: 'artists.id',
[api.QueryElemProperty.albumName]: 'albums.name',
[api.QueryElemProperty.albumId]: 'albums.id',
[api.QueryElemProperty.tagId]: 'tags.id',
}

Loading…
Cancel
Save