diff --git a/client/package.json b/client/package.json index bc0f19a..d47afb3 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.56", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", diff --git a/client/src/App.tsx b/client/src/App.tsx index 2ec6189..97a261a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,12 +1,23 @@ import React, { useState, useEffect } from 'react'; -import { Box, Button } from '@material-ui/core'; +import { Box, Button, Paper } from '@material-ui/core'; import * as serverApi from './api'; -import SongTable, { Entry as SongEntry } from './components/SongTable'; -import ArtistTable, { Entry as ArtistEntry } from './components/ArtistTable'; import EditSongDialog, { SongProperties } from './components/EditSongDialog'; import EditArtistDialog, { ArtistProperties } from './components/EditArtistDialog'; +import AppBar from './components/AppBar'; +import ItemList from './components/ItemList'; +import ItemListItem from './components/ItemListItem'; +import { SongDisplayItem, LoadingSongDisplayItem } from './types/DisplayItem'; + +interface SongEntry { + +} + +interface ArtistEntry { + name: String, + id: Number, +} function App() { const [songs, setSongs] = useState([]); @@ -22,61 +33,61 @@ function App() { }); const updateSongs = () => { - return fetch(serverApi.ListSongsEndpoint) - .then((response: any) => response.json()) - .then((result: serverApi.ListSongsResponse) => { - setSongs(result.map((item: serverApi.ListSongsResponseItem) => { - return { - title: item.title, - artistName: item.artistName - }; - })); - }); + return fetch(serverApi.QuerySongsEndpoint) + // .then((response: any) => response.json()) + // .then((result: serverApi.ListSongsResponse) => { + // setSongs(result.map((item: serverApi.ListSongsResponseItem) => { + // return { + // title: item.title, + // artistName: item.artistName + // }; + // })); + // }); } const fetchArtists = () => { - return fetch(serverApi.ListArtistsEndpoint) - .then((response: any) => response.json()) - .then((result: serverApi.ListArtistsResponse) => { - return result.map((item: serverApi.ListArtistsResponseItem) => { - return { - name: item.name, - id: item.id, - }; - }); - }); + return fetch(serverApi.QueryArtistsEndpoint) + // .then((response: any) => response.json()) + // .then((result: serverApi.ListArtistsResponse) => { + // return result.map((item: serverApi.ListArtistsResponseItem) => { + // return { + // name: item.name, + // id: item.id, + // }; + // }); + // }); } const updateArtists = () => { return fetchArtists() - .then((artists: any[]) => { - setArtists(artists); - }); + // .then((artists: any[]) => { + // setArtists(artists); + // }); } - const createSong = (p:SongProperties) => { - if(!p.artistId) { + const createSong = (p: SongProperties) => { + if (!p.artistId) { throw "Undefined artist ID for song to be created."; } - const request:serverApi.CreateSongRequest = { + const request: serverApi.CreateSongRequest = { title: p.title, - artistId: p.artistId, + artistIds: [p.artistId], } const requestOpts = { method: 'POST', - headers: {'Content-Type': 'application/json'}, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }; return fetch(serverApi.CreateSongEndpoint, requestOpts) } - const createArtist = (p:ArtistProperties) => { - const request:serverApi.CreateArtistRequest = { + const createArtist = (p: ArtistProperties) => { + const request: serverApi.CreateArtistRequest = { name: p.name, } const requestOpts = { method: 'POST', - headers: {'Content-Type': 'application/json'}, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }; return fetch(serverApi.CreateArtistEndpoint, requestOpts) @@ -92,8 +103,8 @@ function App() { const onSubmitCreateSongDialog = () => { createSong(songDialogProperties) - .then(updateSongs) - .then(() => { setEditSongDialogOpen(false); }) + .then(updateSongs) + .then(() => { setEditSongDialogOpen(false); }) } const openCreateArtistDialog = () => { @@ -105,29 +116,37 @@ function App() { const onSubmitCreateArtistDialog = () => { createArtist(artistDialogProperties) - .then(updateArtists) - .then(() => { setEditArtistDialogOpen(false); }) + .then(updateArtists) + .then(() => { setEditArtistDialogOpen(false); }) } useEffect(() => { updateSongs(); }, []); useEffect(() => { updateArtists(); }, []); + const testSong: SongDisplayItem = { + title: "My Song", + artistNames: ["First dude", "Second dude"] + }; + + const testLoadingSong: LoadingSongDisplayItem = { + loadingSong: true + }; + return (
+ + + + + + + - - - - { setEditSongDialogOpen(false); }} - onChangeSongProperties={(props:SongProperties) => setSongDialogProperties(props)} + onChangeSongProperties={(props: SongProperties) => setSongDialogProperties(props)} songProperties={songDialogProperties} onSubmit={onSubmitCreateSongDialog} artists={artists} @@ -135,7 +154,7 @@ function App() { { setEditArtistDialogOpen(false); }} - onChangeArtistProperties={(props:ArtistProperties) => setArtistDialogProperties(props)} + onChangeArtistProperties={(props: ArtistProperties) => setArtistDialogProperties(props)} artistProperties={artistDialogProperties} onSubmit={onSubmitCreateArtistDialog} /> diff --git a/client/src/components/AppBar.tsx b/client/src/components/AppBar.tsx new file mode 100644 index 0000000..25d665b --- /dev/null +++ b/client/src/components/AppBar.tsx @@ -0,0 +1,101 @@ +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'; + +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 default function AppBar() { + const classes = useStyles(); + + return ( +
+ + + + + + MuDBase +
+
+ +
+ +
+
+
+
+ ); +} diff --git a/client/src/components/ItemList.tsx b/client/src/components/ItemList.tsx new file mode 100644 index 0000000..422224d --- /dev/null +++ b/client/src/components/ItemList.tsx @@ -0,0 +1,24 @@ +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/ItemListItem.tsx b/client/src/components/ItemListItem.tsx new file mode 100644 index 0000000..9f67b2f --- /dev/null +++ b/client/src/components/ItemListItem.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { DisplayItem, isSong, isLoadingSong } from '../types/DisplayItem'; +import ItemListLoadedSongItem from './ItemListLoadedSongItem'; +import ItemListLoadingSongItem from './ItemListLoadingSongItem'; + +export interface IProps { + item: DisplayItem +} + +export default function ItemListItem(props: IProps) { + return <> + {isSong(props.item) && } + {isLoadingSong(props.item) && } + +} diff --git a/client/src/components/ItemListLoadedSongItem.tsx b/client/src/components/ItemListLoadedSongItem.tsx new file mode 100644 index 0000000..982a8f1 --- /dev/null +++ b/client/src/components/ItemListLoadedSongItem.tsx @@ -0,0 +1,30 @@ +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 { 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 + + + + + + ); +} diff --git a/client/src/components/ItemListLoadingSongItem.tsx b/client/src/components/ItemListLoadingSongItem.tsx new file mode 100644 index 0000000..361ded6 --- /dev/null +++ b/client/src/components/ItemListLoadingSongItem.tsx @@ -0,0 +1,22 @@ +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 new file mode 100644 index 0000000..f3586b5 --- /dev/null +++ b/client/src/components/ItemListSongItem.tsx @@ -0,0 +1,18 @@ +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/types/DisplayItem.tsx b/client/src/types/DisplayItem.tsx new file mode 100644 index 0000000..49af9dd --- /dev/null +++ b/client/src/types/DisplayItem.tsx @@ -0,0 +1,18 @@ +export interface SongDisplayItem { + title:String, + artistNames:String[], +} + +export interface LoadingSongDisplayItem { + loadingSong: boolean, +} + +export type DisplayItem = SongDisplayItem | LoadingSongDisplayItem; + +export function isSong(item: DisplayItem): item is SongDisplayItem { + return "title" in item; +} + +export function isLoadingSong(item: DisplayItem): item is LoadingSongDisplayItem { + return "loadingSong" in item; +} \ No newline at end of file diff --git a/client/yarn.lock b/client/yarn.lock index 0ed8022..1071228 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1321,6 +1321,13 @@ react-is "^16.8.0" react-transition-group "^4.4.0" +"@material-ui/icons@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.9.1.tgz#fdeadf8cb3d89208945b33dbc50c7c616d0bd665" + integrity sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/lab@^4.0.0-alpha.56": version "4.0.0-alpha.56" resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34"