Skip to content

Commit

Permalink
[pqdb-frontend] Various enhancements
Browse files Browse the repository at this point in the history
- Fixes layout for large tables
- Adds Welcome page
- Adds routing and URL params
- Makes QueryTable sortable
- Adds dark/light mode toggle
- Refactors framework for adding new views
  • Loading branch information
benj-zen committed Jun 3, 2020
1 parent 8bb5de8 commit 246ebc6
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 61 deletions.
2 changes: 2 additions & 0 deletions pqdb-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"dependencies": {
"@material-ui/core": "^4.9.14",
"@material-ui/icons": "^4.9.1",
"query-string": "^6.12.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-image-lightbox": "^5.1.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"sql.js": "^1.2.2"
},
Expand Down
130 changes: 85 additions & 45 deletions pqdb-frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React from 'react';
import {
CssBaseline, Grid, Box, Paper, List, ListItem, ListItemText, Button, IconButton, Drawer, CircularProgress
CssBaseline, Grid, Box, Paper, List, ListItem, ListItemText,
Button, IconButton, Drawer, CircularProgress, Container, Tooltip
} from '@material-ui/core';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import { Menu as MenuIcon } from '@material-ui/icons';
import {
Menu as MenuIcon, BrightnessHigh as LightIcon, Brightness4 as DarkIcon,
AccountTree as DiagramIcon
} from '@material-ui/icons';
import initSqlJs from "sql.js";
import Lightbox from 'react-image-lightbox';
import { CustomSQLQuery } from './components/views';
import { CustomSQLQuery, Welcome } from './components/views';
import { Route, Switch, Redirect } from 'react-router-dom';


import logo from './pqdb.svg';
import databaseDiagram from './tables.svg';
Expand All @@ -21,21 +27,23 @@ function getTheme(type) {
});
}

const themes = {
'light': getTheme('light'),
'dark': getTheme('dark')
}
const pathname = (key) => '/' + key;

// Add new views here
const views = {
"": {
name: "Welcome Page",
description: "Contains info about this website.",
view: Welcome
},
"raw_sql": {
name: "Custom SQL Query",
description: "Enter a custom database query and display the result in a table.",
view: (db) => <CustomSQLQuery db={db} />
view: CustomSQLQuery
}
};

const Progess = (props) => <Box display='flex' justifyContent="center"><CircularProgress /></Box>;
const Progress = (props) => <Box display='flex' justifyContent="center"><CircularProgress /></Box>;

const DrawerList = (props) =>
<List>
Expand All @@ -55,7 +63,6 @@ class App extends React.Component {
themeId: "light",
db: null,
lightBoxIsOpen: false,
selectedView: "raw_sql",
drawerOpen: false
};
}
Expand All @@ -69,6 +76,11 @@ class App extends React.Component {
.catch(err => this.setState({ error: err }));
}

componentWillUnmount() {
this.state.db.close();
}


loadDatabase(SQL) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'pqdb.sqlite', true);
Expand All @@ -81,49 +93,77 @@ class App extends React.Component {
xhr.send();
}

switchView(view) {
const history = this.props.history;
history.replace({
pathname: pathname(view)
});
this.setState({ drawerOpen: false });
}

toggleTheme() {
this.setState({ themeId: (this.state.themeId === 'light') ? 'dark' : 'light' });
}

render() {
return (
<React.Fragment>
<ThemeProvider key={this.state.themeId} theme={themes[this.state.themeId]}>
<CssBaseline />
{this.state.lightBoxIsOpen && (
<Lightbox
mainSrc={databaseDiagram}
onCloseRequest={() => this.setState({ lightBoxIsOpen: false })}

/>
)}
<Box p={2}>
<Grid container direction="column" spacing={2}>
<Grid item>
<Box display='flex' justifyContent="center">
<img src={logo} width="500em" height="192.5em" alt="Logo" />
</Box>
</Grid>
{
(this.state.db != null) ? <Grid item style={{ overflow: 'auto' }}>{views[this.state.selectedView].view(this.state.db)}</Grid> : <Progess />
}
<Grid item>
<ThemeProvider theme={getTheme(this.state.themeId)}>
<CssBaseline />
{this.state.lightBoxIsOpen && (
<Lightbox
mainSrc={databaseDiagram}
onCloseRequest={() => this.setState({ lightBoxIsOpen: false })}

/>
)}
<Box p={2}>
<Grid container direction="column" spacing={2}>
<Grid item>
<Box display='flex' justifyContent="center">
<img src={logo} width="500em" height="192.5em" alt="Logo" />
</Box>
</Grid>
{
(this.state.db != null) ?
<Grid container item>
<Switch>
{
Object.keys(views).map(key =>
<Route key={key} exact path={pathname(key)} render={(props) =>
React.createElement(views[key].view, { key: key + "?" + this.props.history.location.search, db: this.state.db, ...props })} />
)
}
<Route render={(props) => <Redirect to='/' />} />
</Switch>
</Grid> : <Progress />
}
<Grid item>
<Container maxWidth="md">
<Paper>
<Box p={2} display='flex' alignItems="center" justifyContent="center">
<Button onClick={() => this.setState({ lightBoxIsOpen: true })}>
Show Database Diagram
</Button>
<Box p={1} display='flex' alignItems="center" justifyContent="center">
<Tooltip title="Show database diagram">
<IconButton onClick={() => this.setState({ lightBoxIsOpen: true })}>
<DiagramIcon />
</IconButton>
</Tooltip>
<Tooltip title={"Switch to " + ((this.state.themeId === 'light') ? 'dark' : 'light') + " theme"}>
<IconButton onClick={() => this.toggleTheme()}>
{(this.state.themeId === 'light') ? <DarkIcon /> : <LightIcon />}
</IconButton>
</Tooltip>
</Box>
</Paper>
</Grid>
</Container>
</Grid>
<IconButton style={{ position: "absolute", top: 0, left: 0 }} onClick={() => this.setState({ drawerOpen: true })}>
<MenuIcon />
</IconButton>
</Box>
<Drawer anchor="left" open={this.state.drawerOpen} onClose={() => this.setState({ drawerOpen: false })}>
<DrawerList onClick={(key) => this.setState({ selectedView: key, drawerOpen: false })} />
</Drawer>
</ThemeProvider>
</React.Fragment>
</Grid>
<IconButton style={{ position: "absolute", top: 0, left: 0 }} onClick={() => this.setState({ drawerOpen: true })}>
<MenuIcon />
</IconButton>
</Box>
<Drawer anchor="left" open={this.state.drawerOpen} onClose={() => this.setState({ drawerOpen: false })}>
<DrawerList onClick={(view) => this.switchView(view)} />
</Drawer>
</ThemeProvider>
);
}
}
Expand Down
109 changes: 98 additions & 11 deletions pqdb-frontend/src/components/views.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import {
Grid, Box, Paper, TextField, Button,
Grid, Box, Paper, TextField, Button, Typography, Link, Container, List, ListItem, ListItemText,
Table, TableHead, TableRow, TableCell, TableContainer, TableBody, TableSortLabel
} from '@material-ui/core';
import { AccountTree as DiagramIcon } from '@material-ui/icons';
import qs from 'query-string';

function comparator(a, b, orderBy, isAsc) {
if (b[orderBy] < a[orderBy]) {
Expand Down Expand Up @@ -85,20 +87,103 @@ class QueryTable extends React.Component {
}
}

class Welcome extends React.Component {

constructor(props) {
super(props);
this.db = props.db;
this.state = {
enc: [],
sig: []
};
}

componentDidMount() {
var results = this.db.exec(
"SELECT name FROM scheme WHERE type='enc' ORDER BY name;" +
"SELECT name FROM scheme WHERE type='sig' ORDER BY name;"
);
var enc = results[0].values.map(row => row[0]);
var sig = results[1].values.map(row => row[0]);
this.setState({ enc: enc, sig: sig });
}

render() {
return (
<Container maxWidth="md">
<Paper>
<Box p={4}>
<Typography variant="h4" gutterBottom>Welcome!</Typography>
<Typography paragraph>
This is the frontend presenting data from <Link href="https://github.com/cryptoeng/pqdb/">https://github.com/cryptoeng/pqdb/</Link>. You can select different views by clicking the menu icon in the top left corner. The automatically generated database scheme can be shown by clicking the <DiagramIcon fontSize='inherit' color='action'/> button at the bottom of the page.
</Typography>
<Typography paragraph>
The page is written in <Link href="https://reactjs.org/">React</Link> and operates purely client site by loading an <Link href="https://www.sqlite.org/">SQLite</Link> database (located <Link href="pqdb.sqlite">here</Link>) which is generated from the data in pqdb.
</Typography>
<Typography paragraph>
Contributions are warmly welcomed, see <Link href="https://github.com/cryptoeng/pqdb#contribute">here</Link> for details.
</Typography>

<Typography variant='h5' align='center'>Available Schemes</Typography>
<Box mt={1} />
<Grid container justify="space-evenly">
<Grid item>
<Paper>
<Box p={4}>
<Typography variant="h6">Encryption Schemes</Typography>
<List>
{this.state.enc.map(name => <ListItem><ListItemText>{name}</ListItemText></ListItem>)}
</List>
</Box>
</Paper>
</Grid>
<Grid item>
<Paper>
<Box p={4}>
<Typography variant="h6">Signature Schemes</Typography>
<List>
{this.state.sig.map(name => <ListItem><ListItemText>{name}</ListItemText></ListItem>)}
</List>
</Box>
</Paper>
</Grid>
</Grid>
</Box>
</Paper>
</Container>
);
}
}

class CustomSQLQuery extends React.Component {
constructor(props) {
super(props);
this.db = props.db;
this.params = qs.parse(this.props.history.location.search);
var sqlInput = ('query' in this.params) ? this.params['query'] : '';

this.state = {
sqlInput: "",
executedSqlQuery: "",
sqlInput: sqlInput,
executedSqlQuery: '',
queryResult: null,
error: null,
}
}

componentDidMount() {
if (this.state.sqlInput !== '') this.executeSQLQuery();
}

executeSQLQuery() {
const history = this.props.history;
this.params.query = this.state.sqlInput;
history.replace({
pathname: history.location.pathname,
search: '?' + qs.stringify(this.params)
});

var sqlQuery = this.state.sqlInput;

try {
var results = this.db.exec(sqlQuery)[0];
this.setState({ queryResult: results, error: null, executedSqlQuery: sqlQuery });
Expand Down Expand Up @@ -131,23 +216,25 @@ class CustomSQLQuery extends React.Component {
<Grid item>
<Box display='flex' justifyContent="center">
<Button variant="contained" color="primary"
onClick={this.executeSQLQuery.bind(this)}>Run Query</Button>
onClick={() => this.executeSQLQuery()}>Run Query</Button>
</Box>
</Grid>
</Grid>
</Box>
</Paper>
</Grid>
<Grid item>
<Paper>
<Box p={2} display='flex' justifyContent="center">
<QueryTable key={this.state.executedSqlQuery} queryResult={this.state.queryResult} />
</Box>
</Paper>
<Grid container item>
<Container maxWidth='false' disableGutters='true'>
<Paper>
<Box p={2} display='flex' justifyContent="center">
<QueryTable key={this.state.executedSqlQuery} queryResult={this.state.queryResult} />
</Box>
</Paper>
</Container>
</Grid>
</Grid>
);
}
}

export { CustomSQLQuery };
export { CustomSQLQuery, Welcome };
6 changes: 5 additions & 1 deletion pqdb-frontend/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';

const AppWithRouter = withRouter(props => <App {...props} />);
ReactDOM.render(
<React.StrictMode>
<App />
<Router basename={process.env.PUBLIC_URL}>
<AppWithRouter />
</Router>
</React.StrictMode>,
document.getElementById('root')
);
Loading

0 comments on commit 246ebc6

Please sign in to comment.