Material Redesign #1

Merged
Sarsoo merged 6 commits from material into master 2020-01-29 12:33:42 +00:00
15 changed files with 430 additions and 396 deletions
Showing only changes of commit 3de07affa4 - Show all commits

View File

@ -1,25 +1,49 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import { Route, Link, Switch } from "react-router-dom";
const axios = require('axios'); import { Paper, Tabs, Tab} from '@material-ui/core';
import Lock from "./Lock.js"; import Lock from "./Lock.js";
import Functions from "./Functions.js"; import Functions from "./Functions.js";
import Tasks from "./Tasks.js"; import Tasks from "./Tasks.js";
class Admin extends Component { class Admin extends Component {
constructor(props){
super(props);
this.state = {
tab: 0
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(e, newValue){
this.setState({
tab: newValue
});
}
render(){ render(){
return ( return (
<div> <div>
<ul className="navbar" style={{width: "100%"}}> <Paper>
<li><Link to={`${this.props.match.url}/lock`}>Lock Accounts</Link></li> <Tabs
<li><Link to={`${this.props.match.url}/functions`}>Functions</Link></li> value={this.state.tab}
<li><Link to={`${this.props.match.url}/tasks`}>Tasks</Link></li> onChange={this.handleChange}
</ul> indicatorColor="primary"
centered
width="50%"
>
<Tab label="Lock Accounts" component={Link} to={`${this.props.match.url}/lock`} />
<Tab label="Functions" component={Link} to={`${this.props.match.url}/functions`} />
<Tab label="Tasks" component={Link} to={`${this.props.match.url}/tasks`} />
</Tabs>
</Paper>
<Switch>
<Route path={`${this.props.match.url}/lock`} component={Lock} /> <Route path={`${this.props.match.url}/lock`} component={Lock} />
<Route path={`${this.props.match.url}/functions`} component={Functions} /> <Route path={`${this.props.match.url}/functions`} component={Functions} />
<Route path={`${this.props.match.url}/tasks`} component={Tasks} /> <Route path={`${this.props.match.url}/tasks`} component={Tasks} />
</Switch>
</div> </div>
); );
} }

View File

@ -2,6 +2,7 @@ import React, { Component } from "react";
const axios = require('axios'); const axios = require('axios');
import showMessage from "../Toast.js" import showMessage from "../Toast.js"
import { Card, Button, ButtonGroup, CardContent, CardActions, Typography } from "@material-ui/core";
class Functions extends Component { class Functions extends Component {
@ -34,27 +35,20 @@ class Functions extends Component {
render () { render () {
return ( return (
<table className="app-table max-width"> <div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
<thead> <Card align="center">
<tr> <CardContent>
<th> <Typography variant="h4" color="textPrimary">Admin Functions</Typography>
<h1 className="text-no-select full-width center-text ui-text">Admin Functions</h1> </CardContent>
</th> <CardActions>
</tr> <ButtonGroup variant="contained" color="primary" className="full-width">
</thead> <Button className="full-width button" onClick={this.runAllUsers}>Run All Users</Button>
<tbody> <Button className="full-width button" onClick={this.runStats}>Run Stats</Button>
<tr> </ButtonGroup>
<td> </CardActions>
<button className="full-width button" onClick={this.runAllUsers}>Run All Users</button> </Card>
</td> </div>
</tr> );
<tr>
<td>
<button className="full-width button" onClick={this.runStats}>Run Stats</button>
</td>
</tr>
</tbody>
</table>);
} }
} }

View File

@ -1,8 +1,18 @@
import React, { Component } from "react"; import React, { Component } from "react";
const axios = require('axios'); const axios = require('axios');
import { Card, Button, CardActions, CardContent, Typography, Grid } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import showMessage from "../Toast.js" import showMessage from "../Toast.js"
const useStyles = makeStyles({
root: {
background: '#9e9e9e',
color: '#212121'
},
});
class Lock extends Component { class Lock extends Component {
constructor(props){ constructor(props){
@ -46,42 +56,38 @@ class Lock extends Component {
const loadingMessage = <p className="center-text text-no-select">loading...</p>; const loadingMessage = <p className="center-text text-no-select">loading...</p>;
return this.state.isLoading ? loadingMessage : return this.state.isLoading ? loadingMessage :
<div> <div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
<table className="app-table max-width"> <Card align="center">
<thead> <CardContent>
<tr> <Typography variant="h4" color="textPrimary">Account Locks</Typography>
<th colSpan='3'> <Grid container spacing={3}>
<h1 className="text-no-select"> { this.state.accounts.map((account) => <Row account={account} handler={this.handleLock}
Account Locks key= {account.username}/>) }
</h1> </Grid>
</th> </CardContent>
</tr> </Card>
</thead> </div>
<tbody>
{ this.state.accounts.map((account) => <Row account={account} handler={this.handleLock}
key= {account.username}/>) }
</tbody>
</table>
</div>;
} }
} }
function Row(props){ function Row(props){
const classes = useStyles();
return ( return (
<tr> <Grid item xs={12} sm={3} md={2}>
<td className="ui-text center-text text-no-select" style={{width: "40%"}}>{ props.account.username }</td> <Card variant="outlined" className={classes.root}>
<td className="ui-text center-text text-no-select" style={{width: "30%"}}> <CardContent>
{ props.account.last_login } <Typography variant="h5" color="textSecondary" className={classes.root}>{ props.account.username }</Typography>
</td> <Typography variant="body2" color="textSecondary" className={classes.root}>{ props.account.last_login }</Typography>
<td style={{width: "30%"}}> </CardContent>
<button className="button full-width" <CardActions>
onClick={(e) => props.handler(e, props.account.username, !props.account.locked)}> <Button className="full-width" color="secondary" variant="contained" aria-label="delete" onClick={(e) => props.handler(e, props.account.username, !props.account.locked)}>
{props.account.locked ? "Unlock" : "Lock"} {props.account.locked ? "Unlock" : "Lock"}
</button> </Button>
</td> </CardActions>
</tr> </Card>
</Grid>
); );
} }

View File

@ -1,6 +1,8 @@
import React, { Component } from "react"; import React, { Component } from "react";
const axios = require('axios'); const axios = require('axios');
import { Card, CardContent, Typography, Grid } from '@material-ui/core';
import showMessage from "../Toast.js" import showMessage from "../Toast.js"
class Tasks extends Component { class Tasks extends Component {
@ -30,41 +32,26 @@ class Tasks extends Component {
} }
render () { render () {
return ( return (
<table className="app-table max-width"> <div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
<thead> <Grid container spacing={4}>
<tr> { this.state.tasks.map((entry) => <TaskType url={entry.url} count={entry.count} times={entry.scheduled_times} key={entry.url}/>)}
<th> </Grid>
<h1 className="text-no-select full-width center-text ui-text">Running Tasks</h1> </div>
</th> );
</tr>
</thead>
{ this.state.tasks.map((entry) => <TaskType url={entry.url} count={entry.count} times={entry.scheduled_times} key={entry.url}/>)}
<tbody>
<tr>
<td className="text-no-select full-width center-text ui-text" colSpan='2'>
<b>{this.state.total_tasks}</b> Currently Running
</td>
</tr>
</tbody>
</table>);
} }
} }
function TaskType(props) { function TaskType(props) {
return ( return (
<tbody> <Grid item xs={12} sm={6} md={4}>
<tr> <Card align="center">
<td className="text-no-select full-width center-text ui-text" colSpan='2'> <CardContent>
{props.url}: {props.count} <Typography variant="body2" color="textSecondary">{props.url}: {props.count}</Typography>
</td> {props.times.map((entry) => <Typography variant="body2" color="textSecondary">{entry}</Typography>)}
</tr> </CardContent>
{props.times.map((entry) => <tr key={entry}> </Card>
<td colSpan='2' className="text-no-select full-width center-text ui-text"> </Grid>
{entry}
</td>
</tr>)}
</tbody>
); );
} }

View File

@ -1,21 +1,12 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { BrowserRouter as Route, Link} from "react-router-dom"; import { BrowserRouter as Route} from "react-router-dom";
import Count from "./Count.js"; import Count from "./Count.js";
class Maths extends Component { class Maths extends Component {
render() { render() {
return ( return <Route path={`${this.props.match.url}/count`} render={(props) => <Count {...props} name={this.props.match.params.name}/>} />;
<div>
<ul className="navbar" style={{width: "100%"}}>
<li><Link to={`${this.props.match.url}/count`}>count</Link></li>
</ul>
<Route path={`${this.props.match.url}/count`} render={(props) => <Count {...props} name={this.props.match.params.name}/>} />
</div>
);
} }
} }

View File

@ -21,7 +21,10 @@ class PieChart extends Component {
}, },
options: { options: {
legend : { legend : {
display : true display : true,
labels: {
fontColor: 'white'
}
}, },
elements: { elements: {
arc : { arc : {

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom"; import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import NotFound from "./Error/NotFound.js"; import NotFound from "./Error/NotFound.js";
@ -80,7 +80,6 @@ class MusicTools extends Component {
render(){ render(){
return ( return (
<Router> <Router>
<div className="card pad-12">
<ThemeProvider theme={GlobalTheme}> <ThemeProvider theme={GlobalTheme}>
<AppBar position="static"> <AppBar position="static">
<Toolbar> <Toolbar>
@ -143,7 +142,6 @@ class MusicTools extends Component {
</List> </List>
</div> </div>
</Drawer> </Drawer>
</ThemeProvider>
<div className="full-width"> <div className="full-width">
<Switch> <Switch>
<React.Suspense fallback={<LoadingMessage/>}> <React.Suspense fallback={<LoadingMessage/>}>
@ -157,7 +155,7 @@ class MusicTools extends Component {
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
</div> </div>
</div> </ThemeProvider>
</Router> </Router>
); );
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import PlaylistsView from "./PlaylistsList.js" import PlaylistsView from "./PlaylistsList.js"
import NewPlaylist from "./New.js"; import NewPlaylist from "./New.js";
@ -8,12 +8,7 @@ import ScratchView from "./ScratchView.js";
class Playlists extends Component { class Playlists extends Component {
render(){ render(){
return ( return (
<div> <div>
<ul className="navbar" style={{width: "100%"}}>
<li><Link to={`${this.props.match.url}/new`}>New</Link></li>
<li><Link to={`${this.props.match.url}/play`}>Play</Link></li>
</ul>
<Switch> <Switch>
<Route exact path={`${this.props.match.url}/`} component={PlaylistsView} /> <Route exact path={`${this.props.match.url}/`} component={PlaylistsView} />
<Route path={`${this.props.match.url}/new`} component={NewPlaylist} /> <Route path={`${this.props.match.url}/new`} component={NewPlaylist} />

View File

@ -1,6 +1,8 @@
import React, { Component } from "react"; import React, { Component } from "react";
const axios = require('axios'); const axios = require('axios');
import { Card, Button, FormControl, TextField, InputLabel, Select, CardActions, CardContent, Typography, Grid } from '@material-ui/core';
import showMessage from "../Toast.js" import showMessage from "../Toast.js"
class NewPlaylist extends Component { class NewPlaylist extends Component {
@ -81,44 +83,46 @@ class NewPlaylist extends Component {
render(){ render(){
return ( return (
<table className="app-table max-width"> <Card align="center">
<thead> <CardContent>
<tr> <Grid container>
<th colSpan="2"> <Grid item xs={12}>
<h1 className="ui-text center-text text-no-select">New Playlist</h1> <Typography variant="h3">New</Typography>
</th> </Grid>
</tr> <Grid item xs={4}>
</thead> <FormControl variant="filled">
<tbody> <InputLabel htmlFor="type-select">Type</InputLabel>
<tr> <Select
<td> native
<select className="full-width" name="type" onChange={this.handleInputChange}> value={this.state.type}
<option value="default">Default</option>
<option value="recents">Recents</option>
</select>
</td>
<td>
<input
className="full-width"
name="name"
type="text"
value={this.state.name}
onChange={this.handleInputChange} onChange={this.handleInputChange}
placeholder="Name"/> inputProps={{
</td> name: 'type',
</tr> id: 'type-select',
<tr> }}
<td colSpan="2"> >
<input type="submit" className="button full-width" onClick={this.handleSubmit} value="Create" /> <option value="default">Default</option>
</td> <option value="recents">Recents</option>
</tr> </Select>
<tr> </FormControl>
<td colSpan="2" className="ui-text text-no-select center-text"> </Grid>
<br></br>{this.state.description} <Grid item xs={8}>
</td> <TextField
</tr> label="Name"
</tbody> variant="outlined"
</table> onChange={this.handleInputChange}
name="name"
value={this.state.name} />
</Grid>
<Grid item xs={12}>
<Typography variant="body2" color="textSecondary">{ this.state.description }</Typography>
</Grid>
</Grid>
</CardContent>
<CardActions>
<Button variant="contained" color="primary" className="full-width" onClick={this.handleSubmit}>Create</Button>
</CardActions>
</Card>
); );
} }

View File

@ -3,11 +3,9 @@ import { Link } from "react-router-dom";
import { Button, ButtonGroup, Typography, Card, Grid, CircularProgress } from '@material-ui/core'; import { Button, ButtonGroup, Typography, Card, Grid, CircularProgress } from '@material-ui/core';
import CardActions from '@material-ui/core/CardActions'; import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent'; import CardContent from '@material-ui/core/CardContent';
import { ThemeProvider } from '@material-ui/core/styles';
const axios = require('axios'); const axios = require('axios');
import showMessage from "../Toast.js" import showMessage from "../Toast.js"
import GlobalTheme from "../Theme.js"
class PlaylistsView extends Component { class PlaylistsView extends Component {
@ -106,26 +104,32 @@ class PlaylistsView extends Component {
function PlaylistGrid(props){ function PlaylistGrid(props){
return ( return (
<ThemeProvider theme={GlobalTheme}>
<Grid container <Grid container
spacing={3} spacing={3}
direction="row" direction="row"
justify="flex-start" justify="flex-start"
alignItems="flex-start" alignItems="flex-start"
style={{padding: '24px'}}> style={{padding: '24px'}}>
<Grid item xs>
<ButtonGroup
color="primary"
orientation="vertical"
className="full-width">
<Button component={Link} to='playlists/new' >New</Button>
<Button onClick={props.handleRunAll}>Run All</Button>
</ButtonGroup>
</Grid>
{ props.playlists.length == 0 ? ( { props.playlists.length == 0 ? (
<Grid item item xs={12} sm={6} md={3}> <Grid item xs={12} sm={6} md={3}>
<Typography variant="h5" component="h2">No Playlists</Typography> <Typography variant="h5" component="h2">No Playlists</Typography>
</Grid> </Grid>
) : ( ) : (
props.playlists.map((playlist) => <PlaylistCard playlist={ playlist } props.playlists.map((playlist) => <PlaylistCard playlist={ playlist }
handleRunPlaylist={props.handleRunPlaylist} handleRunPlaylist={props.handleRunPlaylist}
handleDeletePlaylist={props.handleDeletePlaylist} handleDeletePlaylist={props.handleDeletePlaylist}
key={ playlist.name }/>) key={ playlist.name }/>)
)} )}
<Grid item xs><Button variant="contained" color="secondary" className="full-width" onClick={props.handleRunAll}>Run All</Button></Grid>
</Grid> </Grid>
</ThemeProvider>
); );
} }
@ -139,10 +143,12 @@ function PlaylistCard(props){
</Typography> </Typography>
</CardContent> </CardContent>
<CardActions> <CardActions>
<ButtonGroup color="primary"> <ButtonGroup
<Button variant="contained" component={Link} to={getPlaylistLink(props.playlist.name)}>View</Button> color="primary"
<Button variant="contained" onClick={(e) => props.handleRunPlaylist(props.playlist.name, e)}>Run</Button> variant="contained">
<Button variant="contained" onClick={(e) => props.handleDeletePlaylist(props.playlist.name, e)}>Delete</Button> <Button component={Link} to={getPlaylistLink(props.playlist.name)}>View</Button>
<Button onClick={(e) => props.handleRunPlaylist(props.playlist.name, e)}>Run</Button>
<Button onClick={(e) => props.handleDeletePlaylist(props.playlist.name, e)}>Delete</Button>
</ButtonGroup> </ButtonGroup>
</CardActions> </CardActions>
</Card> </Card>

View File

@ -1,9 +1,9 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { ThemeProvider, Typography } from "@material-ui/core";
const axios = require('axios'); const axios = require('axios');
import { Card, Button, CardActions, CardContent, Typography, Grid } from '@material-ui/core';
import showMessage from "../../Toast.js" import showMessage from "../../Toast.js"
import GlobalTheme from "../../Theme";
const LazyPieChart = React.lazy(() => import("../../Maths/PieChart")) const LazyPieChart = React.lazy(() => import("../../Maths/PieChart"))
@ -67,35 +67,35 @@ export class Count extends Component {
render() { render() {
return ( return (
<tbody> <div style={{margin: 'auto', marginTop: '20px'}}>
<tr> <Card align="center">
<td className="ui-text center-text text-no-select">Scrobble Count: <b>{this.state.playlist.lastfm_stat_count.toLocaleString()} / {this.state.playlist.lastfm_stat_percent}%</b></td> <CardContent>
</tr> <Grid container>
<tr> <Grid item xs={12}>
<td className="ui-text center-text text-no-select">Album Count: <b>{this.state.playlist.lastfm_stat_album_count.toLocaleString()} / {this.state.playlist.lastfm_stat_album_percent}%</b></td> <Typography variant="body2">Scrobble Count: <b>{this.state.playlist.lastfm_stat_count.toLocaleString()} / {this.state.playlist.lastfm_stat_percent}%</b></Typography>
</tr> </Grid>
<tr> <Grid item xs={12}>
<td className="ui-text center-text text-no-select">Artist Count: <b>{this.state.playlist.lastfm_stat_artist_count.toLocaleString()} / {this.state.playlist.lastfm_stat_artist_percent}%</b></td> <Typography variant="body2">Album Count: <b>{this.state.playlist.lastfm_stat_album_count.toLocaleString()} / {this.state.playlist.lastfm_stat_album_percent}%</b></Typography>
</tr> </Grid>
<tr> <Grid item xs={12}>
<td className="ui-text center-text text-no-select">Last Updated <b>{this.state.playlist.lastfm_stat_last_refresh.toLocaleString()}</b></td> <Typography variant="body2">Artist Count: <b>{this.state.playlist.lastfm_stat_artist_count.toLocaleString()} / {this.state.playlist.lastfm_stat_artist_percent}%</b></Typography>
</tr> </Grid>
<React.Suspense fallback={<LoadingMessage/>}> <Grid item xs={12}>
<tr> <Typography variant="body2">Last Updated <b>{this.state.playlist.lastfm_stat_last_refresh.toLocaleString()}</b></Typography>
<td> </Grid>
<LazyPieChart data={[{ <React.Suspense fallback={<LoadingMessage/>}>
"label": `${this.state.playlist.name} Tracks`, <Grid item xs={12} sm={12} md={4}>
"value": this.state.playlist.lastfm_stat_percent <LazyPieChart data={[{
},{ "label": `${this.state.playlist.name} Tracks`,
"label": 'Other', "value": this.state.playlist.lastfm_stat_percent
"value": 100 - this.state.playlist.lastfm_stat_percent },{
}]} "label": 'Other',
title={this.state.playlist.name}/> "value": 100 - this.state.playlist.lastfm_stat_percent
</td> }]}
</tr> title={this.state.playlist.name}/>
<tr> </Grid>
<td> <Grid item xs={12} sm={12} md={4}>
<LazyPieChart data={[{ <LazyPieChart data={[{
"label": `${this.state.playlist.name} Albums`, "label": `${this.state.playlist.name} Albums`,
"value": this.state.playlist.lastfm_stat_album_percent "value": this.state.playlist.lastfm_stat_album_percent
},{ },{
@ -103,31 +103,29 @@ export class Count extends Component {
"value": 100 - this.state.playlist.lastfm_stat_album_percent "value": 100 - this.state.playlist.lastfm_stat_album_percent
}]} }]}
title={this.state.playlist.name}/> title={this.state.playlist.name}/>
</td> </Grid>
</tr> <Grid item xs={12} sm={12} md={4}>
<tr> <LazyPieChart data={[{
<td> "label": `${this.state.playlist.name} Artists`,
<LazyPieChart data={[{ "value": this.state.playlist.lastfm_stat_artist_percent
"label": `${this.state.playlist.name} Artists`, },{
"value": this.state.playlist.lastfm_stat_artist_percent "label": 'Other',
},{ "value": 100 - this.state.playlist.lastfm_stat_artist_percent
"label": 'Other', }]}
"value": 100 - this.state.playlist.lastfm_stat_artist_percent title={this.state.playlist.name}/>
}]} </Grid>
title={this.state.playlist.name}/> </React.Suspense>
</td> </Grid>
</tr> </CardContent>
<tr> <CardActions>
<td colSpan="2"> <Button variant="contained" color="primary" className="full-width" onClick={this.updateStats}>Update</Button>
<button style={{width: "100%"}} className="button" onClick={this.updateStats}>Update</button> </CardActions>
</td> </Card>
</tr> </div>
</React.Suspense>
</tbody>
); );
} }
} }
function LoadingMessage(props) { function LoadingMessage(props) {
return <tr><td><ThemeProvider theme={GlobalTheme}><Typography variant="h5" component="h2" className="ui-text center-text">Loading...</Typography></ThemeProvider></td></tr>; return <Typography variant="h5" component="h2" className="ui-text center-text">Loading...</Typography>;
} }

View File

@ -1,10 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
const axios = require('axios'); const axios = require('axios');
import { Card, Paper, Button, CircularProgress, FormControl, TextField, Input, InputLabel, Select, Checkbox, FormControlLabel, IconButton, InputAdornment } from '@material-ui/core'; import { Card, Button, CircularProgress, FormControl, TextField, InputLabel, Select, Checkbox, FormControlLabel,
import { ThemeProvider } from '@material-ui/core/styles'; CardActions, CardContent, Typography, Grid } from '@material-ui/core';
import { Add, Delete } from '@material-ui/icons'; import { Delete } from '@material-ui/icons';
import GlobalTheme from "../../Theme.js" import { makeStyles } from '@material-ui/core/styles';
import showMessage from "../../Toast.js"; import showMessage from "../../Toast.js";
@ -38,6 +38,14 @@ var lastMonth = [
'november' 'november'
]; ];
const useStyles = makeStyles({
root: {
background: '#9e9e9e',
color: '#212121'
},
});
export class Edit extends Component{ export class Edit extends Component{
constructor(props){ constructor(props){
@ -398,155 +406,164 @@ export class Edit extends Component{
var date = new Date(); var date = new Date();
const table = ( const table = (
<ThemeProvider theme={GlobalTheme}> <div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
<Card> <Card align="center">
{ this.state.playlist_references.length > 0 && <tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>Managed</td></tr> } <CardContent>
{ this.state.playlist_references.length > 0 && <ListBlock handler={this.handleRemoveReference} list={this.state.playlist_references}/> } <Typography variant="h2" color="textPrimary">{this.state.name}</Typography>
<Grid container spacing={5}>
{ this.state.playlist_references.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Managed</Typography></Grid> }
{ this.state.playlist_references.length > 0 && <ListBlock handler={this.handleRemoveReference} list={this.state.playlist_references}/> }
{ this.state.parts.length > 0 && <tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>Spotify</td></tr> } { this.state.parts.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Spotify</Typography></Grid> }
{ this.state.parts.length > 0 && <ListBlock handler={this.handleRemovePart} list={this.state.parts}/> } { this.state.parts.length > 0 && <ListBlock handler={this.handleRemovePart} list={this.state.parts}/> }
<tr> <Grid item xs={12} ><Typography variant="body2" color="textSecondary">Spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive</Typography></Grid>
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}> <Grid item xs={8} sm={8} md={3}>
<br></br>Spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive <TextField
</td> name="newPlaylistName"
</tr> label="Spotify Playlist"
<FormControl> value={this.state.newPlaylistName}
<InputLabel htmlFor="newPlaylistName">Spotify Playlist</InputLabel> onChange={this.handleInputChange}
<Input
id="newPlaylistName" />
name="newPlaylistName" </Grid>
type="text" <Grid item xs={4} sm={4} md={3}>
value={this.state.newPlaylistName} <Button variant="contained" className="full-width" onClick={this.handleAddPart} style={{verticalAlign: 'middle'}}>Add</Button>
onChange={this.handleInputChange} </Grid>
endAdornment={ <Grid item xs={8} sm={8} md={3}>
<InputAdornment position="end"> <FormControl variant="filled" style={{verticalAlign: 'middle'}}>
<IconButton onClick={this.handleAddPart} > <InputLabel htmlFor="chart_range">Managed Playlist</InputLabel>
<Add/> <Select
</IconButton> native
</InputAdornment> value={this.state.newReferenceName}
onChange={this.handleInputChange}
inputProps={{
name: "newReferenceName",
id: "newReferenceName",
}}
>
{ this.state.playlists
.filter((entry) => entry.name != this.state.name)
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) }
</Select>
</FormControl>
</Grid>
<Grid item xs={4} sm={4} md={3}>
<Button variant="contained" className="full-width" onClick={this.handleAddReference} style={{verticalAlign: 'middle'}}>Add</Button>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox color="primary" checked={this.state.shuffle} onChange={this.handleShuffleChange} />
}
labelPlacement="bottom"
label="Shuffle"/>
<FormControlLabel
control={
<Checkbox color="primary" checked={this.state.include_recommendations} onChange={this.handleIncludeRecommendationsChange} />
}
labelPlacement="bottom"
label="Recommendations"/>
<FormControlLabel
control={
<Checkbox color="primary" checked={this.state.include_library_tracks} onChange={this.handleIncludeLibraryTracksChange} />
}
labelPlacement="bottom"
label="Library Tracks"/>
</Grid>
<Grid item xs={12}>
<TextField type="number"
name="recommendation_sample"
label="Recommendation Size"
value={this.state.recommendation_sample}
onChange={this.handleInputChange}></TextField>
</Grid>
{ this.state.type == 'fmchart' &&
<Grid item xs={12}>
<TextField type="number"
name="chart_limit"
label="Chart Size"
value={this.state.chart_limit}
onChange={this.handleInputChange}></TextField>
</Grid>
} }
/> { this.state.type == 'fmchart' &&
</FormControl> <Grid item xs={12}>
<tr> <FormControl variant="filled">
<td> <InputLabel htmlFor="chart_range">Chart Range</InputLabel>
<FormControl variant="filled"> <Select
<InputLabel htmlFor="chart_range">Managed Playlist</InputLabel> native
<Select value={this.state.chart_range}
native onChange={this.handleInputChange}
value={this.state.newReferenceName} inputProps={{
onChange={this.handleInputChange} name: "chart_range",
inputProps={{ id: "chart_range",
name: "newReferenceName", }}
id: "newReferenceName", >
}} <option value="WEEK">7 Day</option>
> <option value="MONTH">30 Day</option>
{ this.state.playlists <option value="QUARTER">90 Day</option>
.filter((entry) => entry.name != this.state.name) <option value="HALFYEAR">180 Day</option>
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) } <option value="YEAR">365 Day</option>
</Select> <option value="OVERALL">Overall</option>
</FormControl> </Select>
</td> </FormControl>
<td> </Grid>
<Button className="full-width" onClick={this.handleAddReference}>Add</Button> }
</td> { this.state.type == 'recents' &&
</tr> <Grid item xs={12}>
<FormControlLabel <TextField type="number"
control={ name="day_boundary"
<Checkbox checked={this.state.shuffle} onChange={this.handleShuffleChange} /> // className="full-width"
} label="Added Since (days)"
labelPlacement="bottom" value={this.state.day_boundary}
label="Shuffle"/> onChange={this.handleInputChange} />
<FormControlLabel </Grid>
control={ }
<Checkbox checked={this.state.include_recommendations} onChange={this.handleIncludeRecommendationsChange} /> { this.state.type == 'recents' &&
} <Grid item xs={12}>
labelPlacement="bottom" <FormControlLabel
label="Recommendations"/> control={
<FormControlLabel <Checkbox color="primary" checked={this.state.add_this_month} onChange={this.handleThisMonthChange} />
control={ }
<Checkbox checked={this.state.include_library_tracks} onChange={this.handleIncludeLibraryTracksChange} /> label="This Month"
} labelPlacement="bottom"
labelPlacement="bottom" />
label="Library Tracks"/> <FormControlLabel
<TextField type="number" control={
name="recommendation_sample" <Checkbox color="primary" checked={this.state.add_last_month} onChange={this.handleLastMonthChange} />
// className="full-width" }
label="Recommendation Size" label="Last Month"
value={this.state.recommendation_sample} labelPlacement="bottom"
onChange={this.handleInputChange}></TextField> />
</Grid>
{ this.state.type == 'fmchart' && }
<TextField type="number" <Grid item xs={12}>
name="chart_limit" <FormControl variant="filled">
// className="full-width" <InputLabel htmlFor="type-select">Type</InputLabel>
label="Chart Size" <Select
value={this.state.chart_limit} native
onChange={this.handleInputChange}></TextField> value={this.state.type}
} onChange={this.handleInputChange}
{ this.state.type == 'fmchart' && inputProps={{
<FormControl variant="filled"> name: 'type',
<InputLabel htmlFor="chart_range">Chart Range</InputLabel> id: 'type-select',
<Select }}
native >
value={this.state.chart_range} <option value="default">Default</option>
onChange={this.handleInputChange} <option value="recents">Recents</option>
inputProps={{ <option value="fmchart">Last.fm Chart</option>
name: "chart_range", </Select>
id: "chart_range", </FormControl>
}} </Grid>
> </Grid>
<option value="WEEK">7 Day</option> </CardContent>
<option value="MONTH">30 Day</option> <CardActions>
<option value="QUARTER">90 Day</option> <Button onClick={this.handleRun} variant="contained" color="primary" className="full-width" >Run</Button>
<option value="HALFYEAR">180 Day</option> </CardActions>
<option value="YEAR">365 Day</option>
<option value="OVERALL">Overall</option>
</Select>
</FormControl>
}
{ this.state.type == 'recents' &&
<TextField type="number"
name="day_boundary"
// className="full-width"
label="Added Since (days)"
value={this.state.day_boundary}
onChange={this.handleInputChange}></TextField>
}
{ this.state.type == 'recents' &&
<FormControlLabel
control={
<Checkbox checked={this.state.add_this_month} onChange={this.handleThisMonthChange} />
}
label="This Month"
/>
}
{ this.state.type == 'recents' &&
<FormControlLabel
control={
<Checkbox checked={this.state.add_last_month} onChange={this.handleLastMonthChange} />
}
label="Last Month"
/>
}
<FormControl variant="filled">
<InputLabel htmlFor="type-select">Type</InputLabel>
<Select
native
value={this.state.type}
onChange={this.handleInputChange}
inputProps={{
name: 'type',
id: 'type-select',
}}
>
<option value="default">Default</option>
<option value="recents">Recents</option>
<option value="fmchart">Last.fm Chart</option>
</Select>
</FormControl>
<Button onClick={this.handleRun} variant="contained" color="primary">Run</Button>
</Card> </Card>
</ThemeProvider> </div>
); );
return this.state.isLoading ? <CircularProgress /> : table; return this.state.isLoading ? <CircularProgress /> : table;
@ -559,18 +576,30 @@ function ReferenceEntry(props) {
} }
function ListBlock(props) { function ListBlock(props) {
return props.list.map((part) => <Row part={ part } key={ part } handler={props.handler}/>); return <Grid container
spacing={3}
direction="row"
justify="flex-start"
alignItems="flex-start"
style={{padding: '24px'}}>
{props.list.map((part) => <BlockGridItem part={ part } key={ part } handler={props.handler}/>)}
</Grid>
} }
function Row (props) { function BlockGridItem (props) {
const classes = useStyles();
return ( return (
<tr> <Grid item xs={12} sm={3} md={2}>
<td className="ui-text center-text text-no-select">{ props.part }</td> <Card variant="outlined" className={classes.root}>
<td> <CardContent>
<IconButton aria-label="delete" onClick={(e) => props.handler(props.part, e)}> <Typography variant="h5" color="textSecondary" className={classes.root}>{ props.part }</Typography>
<Delete /> </CardContent>
</IconButton> <CardActions>
</td> <Button className="full-width" color="secondary" variant="contained" aria-label="delete" onClick={(e) => props.handler(props.part, e)} startIcon={<Delete />}>
</tr> Delete
</Button>
</CardActions>
</Card>
</Grid>
); );
} }

View File

@ -1,10 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Route, Link, Switch } from "react-router-dom"; import { Route, Link, Switch } from "react-router-dom";
import { ThemeProvider } from '@material-ui/core/styles'; import { Paper, Tabs, Tab} from '@material-ui/core';
import { Paper, Tabs, Tab } from '@material-ui/core';
import GlobalTheme from "../../Theme.js"
import {Edit} from "./Edit.js"; import {Edit} from "./Edit.js";
import {Count} from "./Count.js"; import {Count} from "./Count.js";
@ -28,20 +25,18 @@ class View extends Component{
render() { render() {
return ( return (
<div> <div>
<ThemeProvider theme={GlobalTheme}> <Paper>
<Paper> <Tabs
<Tabs value={this.state.tab}
value={this.state.tab} onChange={this.handleChange}
onChange={this.handleChange} indicatorColor="primary"
indicatorColor="primary" centered
textColor="secondary" width="50%"
centered >
> <Tab label="Edit" component={Link} to={`${this.props.match.url}/edit`} />
<Tab label="Edit" component={Link} to={`${this.props.match.url}/edit`} /> <Tab label="Count" component={Link} to={`${this.props.match.url}/count`} />
<Tab label="Count" component={Link} to={`${this.props.match.url}/count`} /> </Tabs>
</Tabs> </Paper>
</Paper>
</ThemeProvider>
<Switch> <Switch>
<Route path={`${this.props.match.url}/edit`} render={(props) => <Edit {...props} name={this.props.match.params.name}/>} /> <Route path={`${this.props.match.url}/edit`} render={(props) => <Edit {...props} name={this.props.match.params.name}/>} />
<Route path={`${this.props.match.url}/count`} render={(props) => <Count {...props} name={this.props.match.params.name}/>} /> <Route path={`${this.props.match.url}/count`} render={(props) => <Count {...props} name={this.props.match.params.name}/>} />

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom"; import { BrowserRouter as Route, Link } from "react-router-dom";
import ChangePassword from "./ChangePassword.js"; import ChangePassword from "./ChangePassword.js";
import SpotifyLink from "./SpotifyLink.js"; import SpotifyLink from "./SpotifyLink.js";

View File

@ -5,8 +5,8 @@ let GlobalTheme = createMuiTheme({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
spacing: 20
}, },
spacing: 4,
typography: { typography: {
button: { button: {
fontSize: '1rem', fontSize: '1rem',
@ -17,16 +17,20 @@ let GlobalTheme = createMuiTheme({
spacing: 5 spacing: 5
}, },
card: { card: {
marginTop: 24,
display: 'flex', display: 'flex',
spacing: 5 spacing: 5
}, },
palette: { palette: {
type: 'dark', type: 'dark',
primary: { primary: {
main: '#1a237e', main: '#1976d2',
}, },
secondary: { secondary: {
main: '#2196f3', main: '#dc004e',
},
error: {
main: '#f44336'
} }
}, },
status: { status: {