Merge branch 'develop' into flamelink

This commit is contained in:
ajmaley 2018-10-27 13:06:05 -04:00
commit 2ab2b5842e
21 changed files with 1321 additions and 299 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules/
.vscode/
.netlify

55
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
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}>
<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>
<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>
{value === 0 && <Home/>}
{value === 1 && <Report/>}
{value === 2 && <SightingList/>}
{value === 3 && <Quiz/>}
{value === 4 && <ViewMap/>}
{value === 5 && <Info/>}
<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);

297
src/components/QuizGame.js Normal file
View File

@ -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);

View File

@ -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) {
function getYear(date) {
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
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,6 +98,90 @@ 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
@ -98,25 +190,25 @@ const sightingTypes = [
const confidenceLevels = [
{
value: '1',
label: '1 - Strongly unconfident',
label: '1 - Strongly disagree',
},
{
value: '2',
label: '2 - Unconfident',
label: '2 - Disagree',
},
{
value: '3',
label: '3 - Somewhat confident',
label: '3 - Neutral',
},
{
value: '4',
label: '4 - Confident',
label: '4 - Agree',
},
{
value: '5',
label: '5 - Very confident',
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,11 +249,22 @@ 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;
@ -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,13 +294,15 @@ 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
});
};
@ -213,7 +320,7 @@ class ReportForm extends React.Component {
<Fragment>
<form className={classes.container} autoComplete="off" onSubmit={this.handleSubmit}>
<Grid container>
<Grid item xs={6}>
<Grid item xs={12} md={6}>
<Grid container spacing={8}>
<Grid item xs={12}>
<TextField
@ -255,7 +362,7 @@ class ReportForm extends React.Component {
className: classes.menu,
},
}}
helperText="Please select confidence in sighting"
helperText="I am confident of my marten sighting"
>
{confidenceLevels.map(option => (
<MenuItem key={option.value} value={option.value}>
@ -267,31 +374,62 @@ class ReportForm extends React.Component {
<Grid item xs={12}>
<TextField
id="sighting-date"
id="sighting-time"
select
required
label="Sighting date"
name="sighting-date"
type="date"
value={this.state.date}
label="Sighting time"
name="sighting-time"
className={classes.textField}
onChange={this.handleChange('date')}
InputLabelProps={{
shrink: true,
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-time"
id="sighting-month"
select
required
label="Sighting time"
name="sighting-time"
type="time"
margin="normal"
value={this.state.time}
label="Sighting month"
name="sighting-month"
className={classes.textField}
onChange={this.handleChange('time')}
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,
}}
@ -325,11 +463,35 @@ class ReportForm extends React.Component {
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<GoogleMap onClick={this.getCoordinates}/>
<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>
);
}

View File

@ -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

View File

@ -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
@ -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' }
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>

View File

@ -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);

View File

@ -1,5 +1,6 @@
import firebase from 'firebase/app';
import 'firebase/database';
import 'firebase/storage';
const config = {
apiKey: "AIzaSyAYf9AbeYwLY892NRiQfn0AMtG9xIFAJbo",

View File

@ -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;

13
src/pages/QuizPage.js Normal file
View File

@ -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;

View File

@ -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>
);
}