Merge branch 'develop' into flamelink
This commit is contained in:
commit
2ab2b5842e
|
@ -1,2 +1,3 @@
|
|||
node_modules/
|
||||
.vscode/
|
||||
.vscode/
|
||||
.netlify
|
||||
|
|
|
@ -392,6 +392,30 @@
|
|||
"warning": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"@material-ui/icons": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz",
|
||||
"integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.0.0",
|
||||
"recompose": "^0.29.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"recompose": {
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz",
|
||||
"integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"change-emitter": "^0.1.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"symbol-observable": "^1.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mrmlnc/readdir-enhanced": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
||||
|
@ -9325,6 +9349,27 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"material-ui-icons": {
|
||||
"version": "1.0.0-beta.36",
|
||||
"resolved": "https://registry.npmjs.org/material-ui-icons/-/material-ui-icons-1.0.0-beta.36.tgz",
|
||||
"integrity": "sha512-7rS6b2EV5QXCB/gTi/Ac9Wbxd+h9EZv1Td3rLLJe4IER8mVHRgdqZccB3EsjW2DrJ7opdY1+8X3/vyrS7CQNpg==",
|
||||
"requires": {
|
||||
"recompose": "^0.26.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"recompose": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
|
||||
"integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
|
||||
"requires": {
|
||||
"change-emitter": "^0.1.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"symbol-observable": "^1.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"math-expression-evaluator": {
|
||||
"version": "1.2.17",
|
||||
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
|
||||
|
@ -9524,6 +9569,11 @@
|
|||
"integrity": "sha512-9DITV2YEMcw7XojdfvGl3gDD8J9QjZTJ7ZOUuSAkP+F3T6rDbzMJuPktxptsdHYEvZcmXrCD3LMOhdSAEq6zKA==",
|
||||
"optional": true
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.22.2",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
|
||||
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
@ -11866,6 +11916,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-quiz-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-quiz-component/-/react-quiz-component-0.2.0.tgz",
|
||||
"integrity": "sha512-oWUos0A4NtYNBNoSqAbjjpZC9ndEuw5SebYRDhX2EUv4I41iqWWVNMbY2Pu7qT6I7MuELfWHrpV+5p0XwaChaQ=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^3.1.0",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"firebase": "^5.5.2",
|
||||
"firebase-admin": "^6.0.0",
|
||||
"flamelink": "^0.19.2",
|
||||
"google-maps-react": "^2.0.2",
|
||||
"material-ui-icons": "^1.0.0-beta.36",
|
||||
"moment": "^2.22.2",
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-quiz-component": "0.2.0",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "1.1.5"
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-128154616-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-128154616-1');
|
||||
</script>
|
||||
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
|
@ -30,15 +41,16 @@
|
|||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<!-- Hotjar Tracking Code for https://marten-application.netlify.com/ -->
|
||||
<script>
|
||||
(function(h,o,t,j,a,r){
|
||||
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||
h._hjSettings={hjid:1066756,hjsv:6};
|
||||
a=o.getElementsByTagName('head')[0];
|
||||
r=o.createElement('script');r.async=1;
|
||||
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 608 KiB |
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
Binary file not shown.
After Width: | Height: | Size: 252 KiB |
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
After Width: | Height: | Size: 782 KiB |
12
readme.md
12
readme.md
|
@ -1,10 +1,13 @@
|
|||
# Marten Application
|
||||
============
|
||||
### Background
|
||||
Given some recent research into the American marten, there is a need for a web application that is able to document marten sightings. Graduate research has produced a model that predicts habitat suitability for the norther Lower Peninsula, but a lack of anything besides anecdotal evidence makes testing the model difficult. This application would allow for "citizen science". This is the outsourcing of research efforts to the public. This would allow people to log marten sightings for research purposes. The project will be a web platform and certain groups including hunters, hunting club members, wildlife managers and state park personnel will be targeted for citizen science. This application will hopefully help bolster research efforts for the American marten.
|
||||
Given some recent research into the American marten, there is a need for a web application that is able to document marten sightings. Graduate research has produced a model that predicts habitat suitability for the northern Lower Peninsula, but a lack of anything besides anecdotal evidence makes testing the model difficult. This application would allow for "citizen science". This is the outsourcing of research efforts to the public. This would allow people to log marten sightings for research purposes. The project will be a web platform and certain groups including hunters, hunting club members, wildlife managers and state park personnel will be targeted for citizen science. This application will hopefully help bolster research efforts for the American marten.
|
||||
|
||||
The application will allow for users to post marten sightings. They can attach photos and make comments on said photos. They also can include the location where the sighting occurred and rank their confidence in the sighting. This will all be stored in some type of database that can be queried for research purposes. We also will integrate a map API to allow for users to see where marten sightings have occurred.
|
||||
|
||||
### URL
|
||||
|
||||
[Marten Tracker](https://marten-application.netlify.com/ "Click here to see the application in action.")
|
||||
|
||||
### Intended Features
|
||||
* Ability to log marten sightings.
|
||||
* Include type of sighting on marten sighting post.
|
||||
|
@ -16,8 +19,7 @@ The application will allow for users to post marten sightings. They can attach p
|
|||
|
||||
### Technologies
|
||||
* ReactJS
|
||||
* PHP
|
||||
* Firebase
|
||||
* Google API
|
||||
* Amazon AWS
|
||||
* Possibly Grand Valley servers
|
||||
* Netlify
|
||||
* Flamelink CMS
|
||||
|
|
14
src/App.css
14
src/App.css
|
@ -2,11 +2,15 @@ body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.report-google-map-container > div {
|
||||
height: 92% !important;
|
||||
width: 50% !important;
|
||||
@media (min-width: 600px) {
|
||||
.sighting-google-map-container > div {
|
||||
width: calc(100% - 240px) !important;
|
||||
height: calc(100% - 64px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sighting-google-map-container > div {
|
||||
height: 92% !important;
|
||||
@media (min-width: 960px) {
|
||||
.report-google-map-container > div {
|
||||
width: calc(100% - 50% - 120px) !important;
|
||||
}
|
||||
}
|
|
@ -1,75 +1,209 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import AssignmentIcon from '@material-ui/icons/Assignment';
|
||||
import MapIcon from '@material-ui/icons/Map';
|
||||
import ListIcon from '@material-ui/icons/List';
|
||||
import SlideshowIcon from '@material-ui/icons/Slideshow';
|
||||
import Home from '../pages/Home';
|
||||
import ViewMap from '../pages/ViewMap';
|
||||
import Quiz from '../pages/Quiz';
|
||||
import Quiz from '../pages/QuizPage';
|
||||
import SightingList from '../pages/SightingList';
|
||||
import Report from '../pages/Report';
|
||||
import Info from '../pages/Info';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess';
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
|
||||
function TabContainer(props) {
|
||||
return (
|
||||
<Typography component="div" style={{ padding: 8 * 3 }}>
|
||||
{props.children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
TabContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
const drawerWidth = 240;
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
drawer: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
},
|
||||
nested: {
|
||||
paddingLeft: theme.spacing.unit * 4,
|
||||
},
|
||||
appBar: {
|
||||
marginLeft: drawerWidth,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
},
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: 20,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
toolbar: theme.mixins.toolbar,
|
||||
drawerPaper: {
|
||||
width: drawerWidth,
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
width: '60%'
|
||||
},
|
||||
});
|
||||
|
||||
class SimpleTabs extends React.Component {
|
||||
class ResponsiveDrawer extends React.Component {
|
||||
state = {
|
||||
value: 0,
|
||||
mobileOpen: false,
|
||||
key: '',
|
||||
open: false
|
||||
};
|
||||
|
||||
handleChange = (event, value) => {
|
||||
this.setState({ value });
|
||||
};
|
||||
handleDrawerToggle = () => {
|
||||
this.setState(state => ({ mobileOpen: !state.mobileOpen }));
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState(state => ({ open: !state.open }));
|
||||
}
|
||||
|
||||
nav = (text) => {
|
||||
this.setState({
|
||||
key: text
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { value } = this.state;
|
||||
const { classes, theme } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="static">
|
||||
<Tabs value={value} onChange={this.handleChange} centered>
|
||||
<Tab label="Home" />
|
||||
<Tab label="Report a Sighting"/>
|
||||
<Tab label="Sightings" />
|
||||
<Tab label="Trail-Cam Quiz" />
|
||||
<Tab label="View Map" />
|
||||
<Tab label="Marten Info" />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{value === 0 && <Home/>}
|
||||
{value === 1 && <Report/>}
|
||||
{value === 2 && <SightingList/>}
|
||||
{value === 3 && <Quiz/>}
|
||||
{value === 4 && <ViewMap/>}
|
||||
{value === 5 && <Info/>}
|
||||
</div>
|
||||
);
|
||||
const drawer = (
|
||||
<div>
|
||||
<div className={classes.toolbar} />
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem button key='Home' onClick={() => this.nav('Home')}>
|
||||
<ListItemIcon><HomeIcon /></ListItemIcon>
|
||||
<ListItemText primary='Home' />
|
||||
</ListItem>
|
||||
<ListItem button key='Report' onClick={() => this.nav('Report')}>
|
||||
<ListItemIcon><AssignmentIcon /></ListItemIcon>
|
||||
<ListItemText primary='Report' />
|
||||
</ListItem>
|
||||
<ListItem button key='Map' onClick={() => this.nav('Map')}>
|
||||
<ListItemIcon><MapIcon /></ListItemIcon>
|
||||
<ListItemText primary='Map' />
|
||||
</ListItem>
|
||||
<ListItem button key='List' onClick={() => this.nav('List')}>
|
||||
<ListItemIcon><ListIcon /></ListItemIcon>
|
||||
<ListItemText primary='List' />
|
||||
</ListItem>
|
||||
<ListItem button onClick={this.handleClick}>
|
||||
<ListItemIcon>
|
||||
<SlideshowIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText inset primary="Quiz" />
|
||||
{this.state.open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
<ListItem button className={classes.nested} onClick={() => this.nav('Easy-Quiz')}>
|
||||
<ListItemText inset primary="Easy" />
|
||||
</ListItem>
|
||||
<ListItem button className={classes.nested} onClick={() => this.nav('Medium-Quiz')}>
|
||||
<ListItemText inset primary="Medium" />
|
||||
</ListItem>
|
||||
<ListItem button className={classes.nested} onClick={() => this.nav('Hard-Quiz')}>
|
||||
<ListItemText inset primary="Hard" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Collapse>
|
||||
</List>
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="Open drawer"
|
||||
onClick={this.handleDrawerToggle}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="title" color="inherit" noWrap>
|
||||
The American Marten
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<nav className={classes.drawer}>
|
||||
<Hidden smUp implementation="css">
|
||||
<Drawer
|
||||
container={this.props.container}
|
||||
variant="temporary"
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={this.state.mobileOpen}
|
||||
onClose={this.handleDrawerToggle}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Hidden>
|
||||
<Hidden xsDown implementation="css">
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
variant="permanent"
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Hidden>
|
||||
</nav>
|
||||
<main className={classes.content}>
|
||||
<div className={classes.toolbar} />
|
||||
{this.state.key === 'Home' && <Home />}
|
||||
{this.state.key === 'Report' && <Report />}
|
||||
{this.state.key === 'Map' && <ViewMap />}
|
||||
{this.state.key === 'List' && <SightingList />}
|
||||
{this.state.key === 'Easy-Quiz' && <Quiz difficulty='Easy'/>}
|
||||
{this.state.key === 'Medium-Quiz' && <Quiz difficulty='Medium'/>}
|
||||
{this.state.key === 'Hard-Quiz' && <Quiz difficulty='Hard'/>}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTabs.propTypes = {
|
||||
ResponsiveDrawer.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
// Injected by the documentation to work in an iframe.
|
||||
// You won't need it on your project.
|
||||
container: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SimpleTabs);
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(ResponsiveDrawer);
|
|
@ -0,0 +1,297 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Quiz from 'react-quiz-component';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
|
||||
// Style for the tabs.
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
button: {
|
||||
margin: theme.spacing.unit,
|
||||
},
|
||||
rightIcon: {
|
||||
marginLeft: theme.spacing.unit,
|
||||
},
|
||||
});
|
||||
|
||||
class QuizGame extends React.Component {
|
||||
|
||||
/**
|
||||
* Shuffles a given array.
|
||||
* @param {*} array The array passed in.
|
||||
*/
|
||||
shuffleArray = array => {
|
||||
let shuffled = array;
|
||||
|
||||
var j, x, i;
|
||||
|
||||
for (i = shuffled.length - 1; i > 0; i--) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
x = shuffled[i];
|
||||
shuffled[i] = shuffled[j];
|
||||
shuffled[j] = x;
|
||||
}
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({
|
||||
difficulty: this.pickDifficulty(this.props.difficulty),
|
||||
key: Math.random()
|
||||
});
|
||||
}
|
||||
|
||||
easy = {
|
||||
"quizTitle": "Trail Cam Quiz: Easy",
|
||||
"questions": [
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Black bear",
|
||||
"Common wombat",
|
||||
"Raccoon",
|
||||
"White-tailed deer"
|
||||
],
|
||||
"correctAnswer": "1"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question2.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American beaver",
|
||||
"Muskrat",
|
||||
"Porcupine",
|
||||
"Woodchuck"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question3.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American badger",
|
||||
"Raccoon",
|
||||
"Striped skunk",
|
||||
"Virginia opossum"
|
||||
],
|
||||
"correctAnswer": "2"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question4.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Eastern fox squirrel",
|
||||
"Eastern gray squirrel",
|
||||
"Red squirrel",
|
||||
"Southern flying squirrel"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question5.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American Crow",
|
||||
"Black Vulture",
|
||||
"Turkey Vulture",
|
||||
"Northern Raven"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
medium = {
|
||||
"quizTitle": "Trail Cam Quiz: Medium",
|
||||
"questions": [
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Black bear",
|
||||
"Common wombat",
|
||||
"Raccoon",
|
||||
"White-tailed deer"
|
||||
],
|
||||
"correctAnswer": "1"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question2.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American beaver",
|
||||
"Muskrat",
|
||||
"Porcupine",
|
||||
"Woodchuck"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question3.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American badger",
|
||||
"Raccoon",
|
||||
"Striped skunk",
|
||||
"Virginia opossum"
|
||||
],
|
||||
"correctAnswer": "2"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question4.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Eastern fox squirrel",
|
||||
"Eastern gray squirrel",
|
||||
"Red squirrel",
|
||||
"Southern flying squirrel"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question5.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American Crow",
|
||||
"Black Vulture",
|
||||
"Turkey Vulture",
|
||||
"Northern Raven"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
hard = {
|
||||
"quizTitle": "Trail Cam Quiz: Hard",
|
||||
"questions": [
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Black bear",
|
||||
"Common wombat",
|
||||
"Raccoon",
|
||||
"White-tailed deer"
|
||||
],
|
||||
"correctAnswer": "1"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question2.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American beaver",
|
||||
"Muskrat",
|
||||
"Porcupine",
|
||||
"Woodchuck"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question3.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American badger",
|
||||
"Raccoon",
|
||||
"Striped skunk",
|
||||
"Virginia opossum"
|
||||
],
|
||||
"correctAnswer": "2"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question4.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"Eastern fox squirrel",
|
||||
"Eastern gray squirrel",
|
||||
"Red squirrel",
|
||||
"Southern flying squirrel"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
{
|
||||
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question5.jpg" alt=""></img></Fragment>,
|
||||
"questionType": "text",
|
||||
"answers": [
|
||||
"American Crow",
|
||||
"Black Vulture",
|
||||
"Turkey Vulture",
|
||||
"Northern Raven"
|
||||
],
|
||||
"correctAnswer": "3"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the
|
||||
* quiz data based on the difficulty
|
||||
* level passed into it.
|
||||
* @param {*} difficulty The difficulty setting passed in.
|
||||
*/
|
||||
pickDifficulty = difficulty => {
|
||||
let level
|
||||
|
||||
switch (difficulty) {
|
||||
case 'Easy':
|
||||
this.easy.questions = this.shuffleArray(this.easy.questions)
|
||||
level = this.easy
|
||||
break
|
||||
case 'Medium':
|
||||
this.medium.questions = this.shuffleArray(this.medium.questions)
|
||||
level = this.medium
|
||||
break
|
||||
case 'Hard':
|
||||
this.hard.questions = this.shuffleArray(this.hard.questions)
|
||||
level = this.hard
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return level
|
||||
}
|
||||
|
||||
// The state of the component.
|
||||
state = {
|
||||
difficulty: this.pickDifficulty(this.props.difficulty),
|
||||
key: Math.random()
|
||||
}
|
||||
|
||||
// Renders the quiz component.
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
// Tabs
|
||||
<div className={classes.root}>
|
||||
<Typography variant="headline" align="center">
|
||||
<Grid container justify="center">
|
||||
<Quiz quiz={this.state.difficulty} key={this.state.key} />
|
||||
</Grid>
|
||||
</Typography>
|
||||
<Typography align="center">
|
||||
<Button variant="contained" color="default" className={classes.button} onClick={this.reset}>
|
||||
Reset
|
||||
<RefreshIcon className={classes.rightIcon} />
|
||||
</Button>
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QuizGame.propTypes = {
|
||||
classes: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(QuizGame);
|
|
@ -1,9 +1,13 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import firebase from '../firebase.js';
|
||||
import GoogleMap from '../components/ReportMap';
|
||||
|
@ -16,7 +20,7 @@ import GoogleMap from '../components/ReportMap';
|
|||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
|
@ -33,6 +37,17 @@ const styles = theme => ({
|
|||
dense: {
|
||||
marginTop: 30,
|
||||
},
|
||||
close: {
|
||||
padding: theme.spacing.unit / 2,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 20,
|
||||
marginRight: theme.spacing.unit,
|
||||
},
|
||||
message: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
menu: {
|
||||
width: 200,
|
||||
},
|
||||
|
@ -40,22 +55,15 @@ const styles = theme => ({
|
|||
|
||||
/**
|
||||
* Function for formatting the
|
||||
* date as a string that
|
||||
* Material UI can use. We'll
|
||||
* also store the date like
|
||||
* this string in the database.
|
||||
* year as a string that
|
||||
* Material UI can use.
|
||||
* @param {*} date, Date passed in.
|
||||
*/
|
||||
function formatDate(date) {
|
||||
var d = new Date(date),
|
||||
month = '' + (d.getMonth() + 1),
|
||||
day = '' + d.getDate(),
|
||||
year = d.getFullYear();
|
||||
function getYear(date) {
|
||||
var d = new Date(date),
|
||||
year = d.getFullYear();
|
||||
|
||||
if (month.length < 2) month = '0' + month;
|
||||
if (day.length < 2) day = '0' + day;
|
||||
|
||||
return [year, month, day].join('-');
|
||||
return year;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,33 +98,117 @@ const sightingTypes = [
|
|||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Types of sightings. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const monthTypes = [
|
||||
{
|
||||
value: '01',
|
||||
label: 'January',
|
||||
},
|
||||
{
|
||||
value: '02',
|
||||
label: 'February',
|
||||
},
|
||||
{
|
||||
value: '03',
|
||||
label: 'March',
|
||||
},
|
||||
{
|
||||
value: '04',
|
||||
label: 'April',
|
||||
},
|
||||
{
|
||||
value: '05',
|
||||
label: 'May',
|
||||
},
|
||||
{
|
||||
value: '06',
|
||||
label: 'June',
|
||||
},
|
||||
{
|
||||
value: '07',
|
||||
label: 'July',
|
||||
},
|
||||
{
|
||||
value: '08',
|
||||
label: 'August',
|
||||
},
|
||||
{
|
||||
value: '09',
|
||||
label: 'September',
|
||||
},
|
||||
{
|
||||
value: '10',
|
||||
label: 'October',
|
||||
},
|
||||
{
|
||||
value: '11',
|
||||
label: 'November',
|
||||
},
|
||||
{
|
||||
value: '12',
|
||||
label: 'December',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Types of sightings. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const timeTypes = [
|
||||
{
|
||||
value: 'unknown',
|
||||
label: 'Unknown',
|
||||
},
|
||||
{
|
||||
value: 'morning',
|
||||
label: 'Morning',
|
||||
},
|
||||
{
|
||||
value: 'midday',
|
||||
label: 'Midday',
|
||||
},
|
||||
{
|
||||
value: 'evening',
|
||||
label: 'Evening',
|
||||
},
|
||||
{
|
||||
value: 'night',
|
||||
label: 'Night',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Levels of confidence. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const confidenceLevels = [
|
||||
{
|
||||
value: '1',
|
||||
label: '1 - Strongly unconfident',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '2 - Unconfident',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '3 - Somewhat confident',
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '4 - Confident',
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: '5 - Very confident',
|
||||
},
|
||||
];
|
||||
{
|
||||
value: '1',
|
||||
label: '1 - Strongly disagree',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '2 - Disagree',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '3 - Neutral',
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '4 - Agree',
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: '5 - Strongly agree',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The form component.
|
||||
|
@ -136,13 +228,15 @@ class ReportForm extends React.Component {
|
|||
* State of form components.
|
||||
*/
|
||||
state = {
|
||||
date: formatDate(new Date()),
|
||||
time: '00:00',
|
||||
month: '01',
|
||||
year: getYear(new Date()),
|
||||
time: 'unknown',
|
||||
type: 'visual',
|
||||
confidence: '1',
|
||||
desc: '',
|
||||
lat: '',
|
||||
lng: ''
|
||||
lng: '',
|
||||
open: false
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -155,17 +249,28 @@ class ReportForm extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles closing the toast.
|
||||
*/
|
||||
handleClose = (event, reason) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the coordinates
|
||||
*
|
||||
*/
|
||||
getCoordinates = (lat,lng) => {
|
||||
getCoordinates = (lat, lng) => {
|
||||
let latitude = lat;
|
||||
let longitude = lng;
|
||||
|
||||
this.setState({
|
||||
lat: latitude,
|
||||
lng: longitude
|
||||
lat: latitude,
|
||||
lng: longitude
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -181,7 +286,7 @@ class ReportForm extends React.Component {
|
|||
const sighting = {
|
||||
type: this.state.type,
|
||||
confidence: this.state.confidence,
|
||||
date: this.state.date,
|
||||
date: this.state.year + '-' + this.state.month,
|
||||
time: this.state.time,
|
||||
desc: this.state.desc,
|
||||
lat: this.state.lat,
|
||||
|
@ -189,16 +294,18 @@ class ReportForm extends React.Component {
|
|||
}
|
||||
sightingsRef.push(sighting);
|
||||
this.setState({
|
||||
date: formatDate(new Date()),
|
||||
time: '00:00',
|
||||
year: getYear(new Date()),
|
||||
month: '01',
|
||||
time: 'unknown',
|
||||
type: 'visual',
|
||||
confidence: '1',
|
||||
desc: '',
|
||||
lat: '',
|
||||
lng: ''
|
||||
lng: '',
|
||||
open: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The render method for this component.
|
||||
|
@ -210,129 +317,184 @@ class ReportForm extends React.Component {
|
|||
* The actual form.
|
||||
*/
|
||||
return (
|
||||
<Fragment>
|
||||
<form className={classes.container} autoComplete="off" onSubmit={this.handleSubmit}>
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<Grid container spacing={8}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="select-sighting-type"
|
||||
select
|
||||
required
|
||||
name="sighting-type"
|
||||
label="Select"
|
||||
className={classes.textField}
|
||||
value={this.state.type}
|
||||
onChange={this.handleChange('type')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
helperText="Please select type of sighting"
|
||||
>
|
||||
{sightingTypes.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="select-confidence"
|
||||
select
|
||||
required
|
||||
name="sighting-confidence"
|
||||
label="Select"
|
||||
className={classes.textField}
|
||||
value={this.state.confidence}
|
||||
onChange={this.handleChange('confidence')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
helperText="Please select confidence in sighting"
|
||||
>
|
||||
{confidenceLevels.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-date"
|
||||
required
|
||||
label="Sighting date"
|
||||
name="sighting-date"
|
||||
type="date"
|
||||
value={this.state.date}
|
||||
className={classes.textField}
|
||||
onChange={this.handleChange('date')}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-time"
|
||||
required
|
||||
label="Sighting time"
|
||||
name="sighting-time"
|
||||
type="time"
|
||||
margin="normal"
|
||||
value={this.state.time}
|
||||
className={classes.textField}
|
||||
onChange={this.handleChange('time')}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-description"
|
||||
required
|
||||
label="Description"
|
||||
name="sighting-desc"
|
||||
multiline
|
||||
rows="5"
|
||||
placeholder="Describe the sighting to the best of your ability."
|
||||
value={this.state.desc}
|
||||
className={classes.textField}
|
||||
onChange={this.handleChange('desc')}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Button variant="contained" type="submit" color="primary" className={classes.button}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<GoogleMap onClick={this.getCoordinates}/>
|
||||
</Grid>
|
||||
<Fragment>
|
||||
<form className={classes.container} autoComplete="off" onSubmit={this.handleSubmit}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid container spacing={8}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="select-sighting-type"
|
||||
select
|
||||
required
|
||||
name="sighting-type"
|
||||
label="Select"
|
||||
className={classes.textField}
|
||||
value={this.state.type}
|
||||
onChange={this.handleChange('type')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
helperText="Please select type of sighting"
|
||||
>
|
||||
{sightingTypes.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</form>
|
||||
</Fragment>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="select-confidence"
|
||||
select
|
||||
required
|
||||
name="sighting-confidence"
|
||||
label="Select"
|
||||
className={classes.textField}
|
||||
value={this.state.confidence}
|
||||
onChange={this.handleChange('confidence')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
helperText="I am confident of my marten sighting"
|
||||
>
|
||||
{confidenceLevels.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-time"
|
||||
select
|
||||
required
|
||||
label="Sighting time"
|
||||
name="sighting-time"
|
||||
className={classes.textField}
|
||||
value={this.state.time}
|
||||
onChange={this.handleChange('time')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{timeTypes.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-month"
|
||||
select
|
||||
required
|
||||
label="Sighting month"
|
||||
name="sighting-month"
|
||||
className={classes.textField}
|
||||
value={this.state.month}
|
||||
onChange={this.handleChange('month')}
|
||||
SelectProps={{
|
||||
MenuProps: {
|
||||
className: classes.menu,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{monthTypes.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-year"
|
||||
required
|
||||
label="Sighting year"
|
||||
name="sighting-year"
|
||||
value={this.state.year}
|
||||
type="number"
|
||||
className={classes.textField}
|
||||
onChange={this.handleChange('year')}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="sighting-description"
|
||||
required
|
||||
label="Description"
|
||||
name="sighting-desc"
|
||||
multiline
|
||||
rows="5"
|
||||
placeholder="Describe the sighting to the best of your ability."
|
||||
value={this.state.desc}
|
||||
className={classes.textField}
|
||||
onChange={this.handleChange('desc')}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Button variant="contained" type="submit" color="primary" className={classes.button}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<GoogleMap onClick={this.getCoordinates} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={this.state.open}
|
||||
autoHideDuration={6000}
|
||||
onClose={this.handleClose}
|
||||
ContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
message={<span id="message-id" className={classes.message}><CheckCircleIcon className={classes.icon}/>Report received.</span>}
|
||||
action={[
|
||||
<IconButton
|
||||
key="close"
|
||||
aria-label="Close"
|
||||
color="inherit"
|
||||
className={classes.close}
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>,
|
||||
]}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReportForm.propTypes = {
|
||||
|
|
|
@ -96,9 +96,6 @@ export class MapContainer extends Component {
|
|||
|
||||
render() {
|
||||
|
||||
// TODO: This line is used by the custom marker icon
|
||||
//const { google } = this.props;
|
||||
|
||||
return (
|
||||
// Render the Google Map, Marker, and InfoWindow components
|
||||
<div className = "report-google-map-container">
|
||||
|
@ -119,12 +116,6 @@ export class MapContainer extends Component {
|
|||
onClick = { this.onMarkerClick }
|
||||
title = { 'You are here' }
|
||||
name = { '' }
|
||||
// FIXME: fix custom icon
|
||||
// icon={{
|
||||
// url: "../images/marten-icon.png",
|
||||
// anchor: new google.maps.Point(32,32),
|
||||
// scaledSize: new google.maps.Size(64,64)
|
||||
// }}
|
||||
/>
|
||||
|
||||
<InfoWindow
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component, Fragment } from 'react';
|
||||
import { Map, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react';
|
||||
import moment from 'moment'
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import firebase from '../firebase.js';
|
||||
|
||||
|
@ -12,6 +13,128 @@ const mapStyles = {
|
|||
height: '100%'
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of sightings. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const sightingTypes = [
|
||||
{
|
||||
value: 'visual',
|
||||
label: 'Visual',
|
||||
},
|
||||
{
|
||||
value: 'roadkill',
|
||||
label: 'Roadkill',
|
||||
},
|
||||
{
|
||||
value: 'trapped',
|
||||
label: 'Trapped',
|
||||
},
|
||||
{
|
||||
value: 'viewed_tracks',
|
||||
label: 'Viewed Tracks',
|
||||
},
|
||||
{
|
||||
value: 'photo',
|
||||
label: 'Photo',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Types of sightings. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const timeTypes = [
|
||||
{
|
||||
value: 'unknown',
|
||||
label: 'Unknown',
|
||||
},
|
||||
{
|
||||
value: 'morning',
|
||||
label: 'Morning',
|
||||
},
|
||||
{
|
||||
value: 'midday',
|
||||
label: 'Midday',
|
||||
},
|
||||
{
|
||||
value: 'evening',
|
||||
label: 'Evening',
|
||||
},
|
||||
{
|
||||
value: 'night',
|
||||
label: 'Night',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Levels of confidence. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const confidenceLevels = [
|
||||
{
|
||||
value: '1',
|
||||
label: '1 - Strongly unconfident',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '2 - Unconfident',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '3 - Somewhat confident',
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '4 - Confident',
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: '5 - Very confident',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets formatted confidence value.
|
||||
*/
|
||||
function getConfidence(item) {
|
||||
for (var i = 0; i < confidenceLevels.length; i++) {
|
||||
if (confidenceLevels[i].value === item) {
|
||||
return confidenceLevels[i].label;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets formatted time value.
|
||||
*/
|
||||
function getTime(item) {
|
||||
for (var i = 0; i < timeTypes.length; i++) {
|
||||
if (timeTypes[i].value === item) {
|
||||
return timeTypes[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets formatted type value.
|
||||
*/
|
||||
function getType(item) {
|
||||
for (var i = 0; i < sightingTypes.length; i++) {
|
||||
if (sightingTypes[i].value === item) {
|
||||
return sightingTypes[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MapContainer extends Component {
|
||||
|
||||
// Get the user's location using Google's geolocation
|
||||
|
@ -19,22 +142,22 @@ export class MapContainer extends Component {
|
|||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition((position) => {
|
||||
this.setState({
|
||||
myLatLng: {
|
||||
lat: position.coords.latitude,
|
||||
lng: position.coords.longitude
|
||||
}
|
||||
myLatLng: {
|
||||
lat: position.coords.latitude,
|
||||
lng: position.coords.longitude
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
} else {
|
||||
// If browser doesn't support geolocation or if user does not allow it,
|
||||
// center map on Grand Rapids, Michigan
|
||||
this.setState({
|
||||
myLatLng: {
|
||||
lat: 42.9634,
|
||||
lng: 85.6681
|
||||
}
|
||||
myLatLng: {
|
||||
lat: 42.9634,
|
||||
lng: 85.6681
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +208,10 @@ export class MapContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
formatDate = date => {
|
||||
return (moment(date, "YYYY-MM").format("MMMM YYYY").toString())
|
||||
}
|
||||
|
||||
// Set the state of the component to contain user coordinates and initial
|
||||
// marker and info window information
|
||||
state = {
|
||||
|
@ -101,55 +228,55 @@ export class MapContainer extends Component {
|
|||
render() {
|
||||
return (
|
||||
// Render the Google Map, Marker, and InfoWindow components
|
||||
<div className = "sighting-google-map-container">
|
||||
<div className="sighting-google-map-container">
|
||||
<Map
|
||||
style = { mapStyles }
|
||||
google = { this.props.google }
|
||||
initialCenter = { this.state.myLatLng }
|
||||
center = { this.state.myLatLng }
|
||||
defaultZoom = { 15 }
|
||||
onClick = { this.onMapClick } >
|
||||
style={mapStyles}
|
||||
google={this.props.google}
|
||||
initialCenter={this.state.myLatLng}
|
||||
center={this.state.myLatLng}
|
||||
defaultZoom={15}
|
||||
onClick={this.onMapClick} >
|
||||
|
||||
<Marker
|
||||
position = { this.state.myLatLng }
|
||||
onClick = { this.onMarkerClick }
|
||||
type = { 'You are here' }
|
||||
<Marker
|
||||
position={this.state.myLatLng}
|
||||
onClick={this.onMarkerClick}
|
||||
type={'You are here'}
|
||||
/>
|
||||
|
||||
{ this.state.sightings.map((sighting) => {
|
||||
{this.state.sightings.map((sighting) => {
|
||||
return (
|
||||
<Marker
|
||||
key={ sighting.id }
|
||||
position={{ lat: sighting.lat, lng:sighting.lng }}
|
||||
onClick = { this.onMarkerClick }
|
||||
type = { 'Type: ' + sighting.type }
|
||||
confidence = { 'Confidence: ' + sighting.confidence }
|
||||
date = { 'Date: ' + sighting.date }
|
||||
time = { 'Time: ' + sighting.time }
|
||||
description = { 'Description: ' + sighting.desc }
|
||||
key = {sighting.id}
|
||||
position = {{ lat: sighting.lat, lng: sighting.lng }}
|
||||
onClick = {this.onMarkerClick}
|
||||
type = {'Type: ' + getType(sighting.type)}
|
||||
confidence = {<Fragment><b>Confidence:</b> {getConfidence(sighting.confidence)}</Fragment>}
|
||||
date = {<Fragment><b>Date:</b> {this.formatDate(sighting.date)}</Fragment>}
|
||||
time = {<Fragment><b>Time:</b> {getTime(sighting.time)}</Fragment>}
|
||||
description = {<Fragment><b>Description:</b> {sighting.desc}</Fragment>}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
<InfoWindow
|
||||
marker = { this.state.activeMarker }
|
||||
visible = { this.state.showingInfoWindow } >
|
||||
marker={this.state.activeMarker}
|
||||
visible={this.state.showingInfoWindow} >
|
||||
|
||||
<Fragment>
|
||||
<Typography variant = "display1" gutterBottom>
|
||||
{ this.state.selectedPlace.type }
|
||||
<Typography variant="display1" gutterBottom>
|
||||
{this.state.selectedPlace.type}
|
||||
</Typography>
|
||||
<Typography variant = "subheading" gutterBottom>
|
||||
{ this.state.selectedPlace.confidence }
|
||||
<Typography variant="subheading" gutterBottom>
|
||||
{this.state.selectedPlace.confidence}
|
||||
</Typography>
|
||||
<Typography variant = "subheading" gutterBottom>
|
||||
{ this.state.selectedPlace.date }
|
||||
<Typography variant="subheading" gutterBottom>
|
||||
{this.state.selectedPlace.date}
|
||||
</Typography>
|
||||
<Typography variant = "subheading" gutterBottom>
|
||||
{ this.state.selectedPlace.time }
|
||||
<Typography variant="subheading" gutterBottom>
|
||||
{this.state.selectedPlace.time}
|
||||
</Typography>
|
||||
<Typography variant = "subheading" gutterBottom>
|
||||
{ this.state.selectedPlace.description }
|
||||
<Typography variant="subheading" gutterBottom>
|
||||
{this.state.selectedPlace.description}
|
||||
</Typography>
|
||||
</Fragment>
|
||||
</InfoWindow>
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import firebase from '../firebase.js';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
...theme.mixins.gutters(),
|
||||
paddingTop: theme.spacing.unit * 2,
|
||||
paddingBottom: theme.spacing.unit * 2,
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
flexBasis: 280,
|
||||
},
|
||||
button: {
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
marginRight: theme.spacing.unit * 3,
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
},
|
||||
paper: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
marginRight: theme.spacing.unit,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Types of sightings. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const sightingTypes = [
|
||||
{
|
||||
value: 'visual',
|
||||
label: 'Visual',
|
||||
},
|
||||
{
|
||||
value: 'roadkill',
|
||||
label: 'Roadkill',
|
||||
},
|
||||
{
|
||||
value: 'trapped',
|
||||
label: 'Trapped',
|
||||
},
|
||||
{
|
||||
value: 'viewed_tracks',
|
||||
label: 'Viewed Tracks',
|
||||
},
|
||||
{
|
||||
value: 'photo',
|
||||
label: 'Photo',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Levels of confidence. Label is what is
|
||||
* viewed in the application, value is
|
||||
* what is stored in the database.
|
||||
*/
|
||||
const confidenceLevels = [
|
||||
{
|
||||
value: '1',
|
||||
label: '1 - Strongly unconfident',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '2 - Unconfident',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '3 - Somewhat confident',
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '4 - Confident',
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: '5 - Very confident',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets formatted confidence value.
|
||||
*/
|
||||
function getConfidence(item) {
|
||||
for (var i = 0; i < confidenceLevels.length; i++) {
|
||||
if (confidenceLevels[i].value === item) {
|
||||
return confidenceLevels[i].label;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets formatted type value.
|
||||
*/
|
||||
function getType(item) {
|
||||
for (var i = 0; i < sightingTypes.length; i++) {
|
||||
if (sightingTypes[i].value === item) {
|
||||
return sightingTypes[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ViewSightings extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
state = {
|
||||
id: '',
|
||||
type: 'N/A',
|
||||
confidence: 'N/A',
|
||||
date: 'N/A',
|
||||
time: 'N/A',
|
||||
desc: 'N/A',
|
||||
lat: 'N/A',
|
||||
lng: 'N/A'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles state change.
|
||||
*/
|
||||
handleChange = name => event => {
|
||||
this.setState({
|
||||
[name]: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles submit on search.
|
||||
*/
|
||||
handleSubmit(e){
|
||||
e.preventDefault();
|
||||
const itemSighting = firebase.database().ref("sightings/" + this.state.id);
|
||||
itemSighting.once("value").then((snapshot) => {
|
||||
// Die if there's no data for that ID.
|
||||
if (!snapshot.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = snapshot.val();
|
||||
|
||||
this.setState({
|
||||
date: data.date,
|
||||
time: data.time,
|
||||
type: getType(data.type),
|
||||
confidence: getConfidence(data.confidence),
|
||||
desc: data.desc,
|
||||
lat: data.lat,
|
||||
lng: data.lng
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render(){
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
/**
|
||||
* The below houses the search
|
||||
* and submit button along with
|
||||
* the sighting information
|
||||
* it pulls.
|
||||
*/
|
||||
<Fragment>
|
||||
<Grid container justify="center">
|
||||
<form className={classes.container} onSubmit={this.handleSubmit}>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
id="sighting-id"
|
||||
name="sighting-id"
|
||||
label="Input ID"
|
||||
value={this.state.id}
|
||||
margin="normal"
|
||||
onChange={this.handleChange('id')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="contained" type="submit" color="primary" className={classes.button}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={2}>
|
||||
<Typography variant="headline" component="h3">
|
||||
Sighting
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
<b>Type:</b> {this.state.type} {<br/>}
|
||||
<b>Confidence:</b> {this.state.confidence} {<br/>}
|
||||
<b>Date:</b> {this.state.date} {<br/>}
|
||||
<b>Time:</b> {this.state.time} {<br/>}
|
||||
<b>Latitude:</b> {this.state.lat} {<br/>}
|
||||
<b>Longitude:</b> {this.state.lng} {<br/>}
|
||||
<b>Description:</b> {this.state.desc}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ViewSightings.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ViewSightings);
|
|
@ -1,5 +1,6 @@
|
|||
import firebase from 'firebase/app';
|
||||
import 'firebase/database';
|
||||
import 'firebase/storage';
|
||||
|
||||
const config = {
|
||||
apiKey: "AIzaSyAYf9AbeYwLY892NRiQfn0AMtG9xIFAJbo",
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
class Quiz extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant='display1' align='center' gutterBottom>
|
||||
Quiz
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Quiz;
|
|
@ -0,0 +1,13 @@
|
|||
import React, { Component } from 'react';
|
||||
import QuizGame from '../components/QuizGame';
|
||||
|
||||
|
||||
class QuizPage extends Component {
|
||||
render() {
|
||||
return (
|
||||
<QuizGame difficulty={this.props.difficulty}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QuizPage;
|
|
@ -1,11 +1,12 @@
|
|||
import React, { Component } from 'react';
|
||||
import ViewSightings from '../components/ViewSightings.js';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
class Sighting extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant='display1' align='center' gutterBottom>
|
||||
Sightings
|
||||
<ViewSightings/>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue