fix merge conflicts

This commit is contained in:
Al Duncanson 2018-11-04 18:34:46 -05:00
commit d926c107c0
23 changed files with 1622 additions and 666 deletions

3
.gitignore vendored
View File

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

91
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",
@ -3027,6 +3051,16 @@
"sha.js": "^2.4.8"
}
},
"create-react-class": {
"version": "15.6.3",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
"requires": {
"fbjs": "^0.8.9",
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@ -3543,6 +3577,40 @@
}
}
},
"disqus-react": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/disqus-react/-/disqus-react-1.0.5.tgz",
"integrity": "sha1-go6hhr0kHF4qbf66tRNeUqLZHRE=",
"requires": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
},
"dependencies": {
"react": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz",
"integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=",
"requires": {
"create-react-class": "^15.6.0",
"fbjs": "^0.8.9",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.0",
"prop-types": "^15.5.10"
}
},
"react-dom": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz",
"integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=",
"requires": {
"fbjs": "^0.8.9",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.0",
"prop-types": "^15.5.10"
}
}
}
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -6076,8 +6144,7 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"optional": true
"bundled": true
},
"aproba": {
"version": "1.2.0",
@ -6429,8 +6496,7 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"optional": true
"bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6477,7 +6543,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6516,13 +6581,11 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"optional": true
"bundled": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"optional": true
"bundled": true
}
}
},
@ -9529,6 +9592,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",
@ -11871,6 +11939,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",
"disqus-react": "^1.0.5",
"firebase": "^5.5.2",
"firebase-admin": "^6.0.0",
"flamelink": "^0.19.2",
"google-maps-react": "^2.0.2",
"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

@ -2,11 +2,49 @@ 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;
}
}
#disqus_thread {
width: 89% !important;
margin: 0 auto;
}
.sighting-list {
height: calc(50vh - 64px);
overflow-y: scroll;
}
@media (min-width: 960px) {
.sighting-list {
height: calc(100vh - 64px);
overflow-y: scroll;
}
}
.sighting-details-content {
width: 89%;
margin: 330px auto 0 auto;
}
.sighting-detail-google-map-container > div {
width: 100% !important;
height: 300px !important;
}
@media (min-width: 960px) {
.sighting-detail-google-map-container > div {
width: calc(100% - 50% - 120px) !important;
height: 300px !important;
}
}

View File

@ -2,7 +2,6 @@ import { Component } from 'react';
class Flamelink extends Component {
render() {
return(null);
}
}

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: 'Home',
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>
Marten Tracker
</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);

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,12 +1,18 @@
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';
import Modal from '@material-ui/core/Modal';
import Typography from '@material-ui/core/Typography';
/**
* Styles that the different
@ -14,80 +20,162 @@ import GoogleMap from '../components/ReportMap';
* in. Mostly used for spacing.
*/
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
},
textField: {
marginLeft: theme.spacing.unit * 2,
marginRight: theme.spacing.unit,
marginTop: theme.spacing.unit * 2,
flexBasis: 280,
width: '90%'
},
button: {
marginLeft: theme.spacing.unit * 2,
marginRight: theme.spacing.unit,
marginTop: theme.spacing.unit * 2,
},
dense: {
marginTop: 30,
},
menu: {
width: 200,
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
textField: {
marginLeft: theme.spacing.unit * 2,
marginRight: theme.spacing.unit,
marginTop: theme.spacing.unit * 2,
flexBasis: 280,
width: '90%'
},
button: {
marginLeft: theme.spacing.unit * 2,
marginRight: theme.spacing.unit,
marginTop: theme.spacing.unit * 2,
},
dense: {
marginTop: 30,
},
close: {
padding: theme.spacing.unit / 2,
},
icon: {
fontSize: 20,
marginRight: theme.spacing.unit,
},
message: {
display: 'flex',
alignItems: 'center',
},
menu: {
width: 200,
},
paper: {
position: 'absolute',
width: theme.spacing.unit * 50,
backgroundColor: theme.palette.background.paper,
boxShadow: theme.shadows[5],
padding: theme.spacing.unit * 4,
}
});
/**
* 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.
* @param {*} date, Date passed in.
*/
function formatDate(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('-');
}
/**
* 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',
},
{
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 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',
},
];
/**
@ -97,246 +185,378 @@ const sightingTypes = [
*/
const confidenceLevels = [
{
value: '1',
label: '1 - Strongly unconfident',
value: '1',
label: '1 - Strongly disagree',
},
{
value: '2',
label: '2 - Unconfident',
value: '2',
label: '2 - Disagree',
},
{
value: '3',
label: '3 - Somewhat confident',
value: '3',
label: '3 - Neutral',
},
{
value: '4',
label: '4 - Confident',
value: '4',
label: '4 - Agree',
},
{
value: '5',
label: '5 - Very confident',
value: '5',
label: '5 - Strongly agree',
},
];
];
/**
* The form component.
*/
class ReportForm extends React.Component {
/**
* Component contructor. Currently
* only used to bind event
* handlers.
*/
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
}
/**
* State of form components.
*/
state = {
date: formatDate(new Date()),
time: '00:00',
type: 'visual',
confidence: '1',
desc: '',
lat: '',
lng: ''
};
/**
* Handles state change in form
* components.
*/
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
/*
* Get the coordinates
*
*/
getCoordinates = (lat,lng) => {
let latitude = lat;
let longitude = lng;
this.setState({
lat: latitude,
lng: longitude
});
}
/**
* Event listener for form.
* When the form is submitted,
* this function passes the
* data along to Firebase.
*/
handleSubmit(e) {
e.preventDefault();
const sightingsRef = firebase.database().ref('sightings');
const sighting = {
type: this.state.type,
confidence: this.state.confidence,
date: this.state.date,
time: this.state.time,
desc: this.state.desc,
lat: this.state.lat,
lng: this.state.lng
/**
* Component contructor. Currently
* only used to bind event
* handlers.
*/
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
}
sightingsRef.push(sighting);
this.setState({
date: formatDate(new Date()),
time: '00:00',
type: 'visual',
confidence: '1',
desc: '',
lat: '',
lng: ''
});
};
/**
* The render method for this component.
*/
render() {
const { classes } = this.props;
/**
* The actual form.
* Function for formatting the
* year as a string that
* Material UI can use.
* @param {*} date, Date passed in.
*/
getYear = date => {
var d = new Date(date),
year = d.getFullYear();
return year;
}
/**
* Function for formatting the
* month as a string that
* Material UI can use.
* @param {*} date, Date passed in.
*/
getMonth = date => {
var d = new Date(date),
month = d.getMonth() + 1;
month = month.toString();
if (month.length === 1) {
month = "0" + month;
}
return month;
}
/**
* State of form components.
*/
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>
state = {
month: this.getMonth(new Date()),
year: this.getYear(new Date()),
time: 'unknown',
type: 'visual',
confidence: '1',
desc: '',
lat: '',
lng: '',
open: false,
openModal: false,
hasModalOpened: false
};
<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>
handleModalOpen = () => !this.state.hasModalOpened ? this.setState({ openModal: true, hasModalOpened: true }) : null;
<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>
handleModalClose = () => {
this.setState({ openModal: false });
};
<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>
getModalStyle = () => {
return {
top: `25%`,
left: `75%`,
transform: `translate(-25%, -75%)`,
};
}
<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,
}}
/>
/**
* Handles state change in form
* components.
*/
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
/**
* Handles closing the toast.
*/
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
/*
* Get the coordinates
*
*/
getCoordinates = (lat, lng) => {
let latitude = lat;
let longitude = lng;
this.setState({
lat: latitude,
lng: longitude
});
}
/**
* Event listener for form.
* When the form is submitted,
* this function passes the
* data along to Firebase.
*/
handleSubmit(e) {
e.preventDefault();
const sightingsRef = firebase.database().ref('sightings');
const sighting = {
type: this.state.type,
confidence: this.state.confidence,
date: this.state.year + '-' + this.state.month,
time: this.state.time,
desc: this.state.desc,
lat: this.state.lat,
lng: this.state.lng
}
sightingsRef.push(sighting);
this.setState({
year: this.getYear(new Date()),
month: this.getMonth(new Date()),
time: 'unknown',
type: 'visual',
confidence: '1',
desc: '',
lat: '',
lng: '',
open: true
});
};
/**
* The render method for this component.
*/
render() {
const { classes } = this.props;
/**
* The actual form.
*/
return (
<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>
<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 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 item xs={12} md={6} onMouseEnter={this.handleModalOpen}>
<GoogleMap onClick={this.getCoordinates}/>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.state.openModal}
onClose={this.handleModalClose}
>
<div style={this.getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title">
Need a little help?
</Typography>
<Typography variant="subheading" id="simple-modal-description">
Click on the map to drop a pin!
</Typography>
</div>
</Modal>
</Grid>
</Grid>
</Grid>
</form>
</Fragment>
);
}
</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 = {
classes: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ReportForm);

View File

@ -9,7 +9,7 @@ const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM';
const mapStyles = {
width: '100%',
height: '100%'
}
};
export class MapContainer extends Component {
@ -18,22 +18,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
}
}
);
}
}
@ -52,7 +52,7 @@ export class MapContainer extends Component {
showingInfoWindow: true
});
}
// When the user clicks on the map, if a info window is visible then close it
// and 'unactive' that marker
onMapClick = (props, map, e) => {
@ -68,13 +68,13 @@ export class MapContainer extends Component {
lat: e.latLng.lat(),
lng: e.latLng.lng()
}
})
});
let lat = e.latLng.lat();
let lng = e.latLng.lng();
let lng = e.latLng.lng();
if (this.props.onClick) {
this.props.onClick(lat,lng);
this.props.onClick(lat, lng);
}
}
@ -98,36 +98,36 @@ export class MapContainer extends Component {
return (
// Render the Google Map, Marker, and InfoWindow components
<div className = "report-google-map-container">
<div className="report-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.markerLatLng }
<Marker
position={this.state.markerLatLng}
/>
<Marker
position = { this.state.myLatLng }
onClick = { this.onMarkerClick }
title = { 'You are here' }
name = { '' }
<Marker
position={this.state.myLatLng}
onClick={this.onMarkerClick}
title={'You are here'}
name={''}
/>
<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.title }
<Typography variant="display1" gutterBottom>
{this.state.selectedPlace.title}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.name }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.name}
</Typography>
</Fragment>
</InfoWindow>
@ -138,6 +138,4 @@ export class MapContainer extends Component {
}
// Send the Google Map API Key with the MapContainer component
export default GoogleApiWrapper({
apiKey: (API_KEY)
})(MapContainer)
export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer);

View File

@ -0,0 +1,160 @@
import React, { Component, Fragment } from 'react';
import Disqus from 'disqus-react';
import moment from 'moment';
import SightingDetailMap from './SightingDetailMap';
/**
* 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 disagree',
},
{
value: '2',
label: '2 - Disagree',
},
{
value: '3',
label: '3 - Neutral',
},
{
value: '4',
label: '4 - Agree',
},
{
value: '5',
label: '5 - Strongly agree',
},
];
class SightingDetail extends Component {
/**
* Gets formatted type value.
*/
getType = item => {
for (var i = 0; i < sightingTypes.length; i++) {
if (sightingTypes[i].value === item) {
return sightingTypes[i].label;
}
}
}
/**
* Gets formatted time value.
*/
getTime = item => {
for (var i = 0; i < timeTypes.length; i++) {
if (timeTypes[i].value === item) {
return timeTypes[i].label;
}
}
}
/**
* Gets formatted confidence value.
*/
getConfidence = item => {
for (var i = 0; i < confidenceLevels.length; i++) {
if (confidenceLevels[i].value === item) {
return confidenceLevels[i].label;
}
}
}
formatDate = date => {
return (moment(date, "YYYY-MM").format("MMMM YYYY").toString());
}
render() {
const disqusShortname = 'https-marten-application-netlify-com';
const disqusConfig = {
url: `http://localhost:3000/${this.props.detail.id}`,
identifier: this.props.detail.id,
title: this.props.detail.id
};
return (
<Fragment>
<SightingDetailMap lat={this.props.detail.lat} lng={this.props.detail.lng} />
<div className='sighting-details-content'>
<p><b>Type:</b> {this.getType(this.props.detail.type)}</p>
<p><b>When:</b> {this.formatDate(this.props.detail.date)}, {this.getTime(this.props.detail.time)}</p>
<p><b>Where:</b> {this.props.detail.lat} degrees N, and {this.props.detail.lng} degrees E</p>
<p><b>I am confident of my sighting:</b> {this.getConfidence(this.props.detail.confidence)}</p>
<hr/>
<p>{`${this.props.detail.desc}`}</p>
</div>
<Disqus.DiscussionEmbed shortname={disqusShortname} config={disqusConfig} />
</Fragment>
);
}
}
export default SightingDetail;

View File

@ -0,0 +1,36 @@
import React, { Component } from 'react';
import { Map, Marker, GoogleApiWrapper } from 'google-maps-react';
// Google Maps API Key
const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM';
// Map container styles
const mapStyles = {
width: '100%',
height: '100%'
};
export class MapContainer extends Component {
render() {
return (
// Render the Google Map, Marker, and InfoWindow components
<div className="sighting-detail-google-map-container">
<Map
style={mapStyles}
google={this.props.google}
initialCenter={{ lat: this.props.lat, lng: this.props.lng }}
center={{ lat: this.props.lat, lng: this.props.lng }}
defaultZoom={15}>
<Marker
position={{ lat: this.props.lat, lng: this.props.lng }}
/>
</Map>
</div>
);
}
}
// Send the Google Map API Key with the MapContainer component
export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer);

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';
@ -10,7 +11,95 @@ const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM';
const mapStyles = {
width: '100%',
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 disagree',
},
{
value: '2',
label: '2 - Disagree',
},
{
value: '3',
label: '3 - Neutral',
},
{
value: '4',
label: '4 - Agree',
},
{
value: '5',
label: '5 - Strongly agree',
},
];
export class MapContainer extends Component {
@ -19,26 +108,59 @@ 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
}
);
});
}
}
/**
* Gets formatted type value.
*/
getType = item => {
for (var i = 0; i < sightingTypes.length; i++) {
if (sightingTypes[i].value === item) {
return sightingTypes[i].label;
}
}
}
/**
* Gets formatted time value.
*/
getTime = item => {
for (var i = 0; i < timeTypes.length; i++) {
if (timeTypes[i].value === item) {
return timeTypes[i].label;
}
}
}
/**
* Gets formatted confidence value.
*/
getConfidence = item => {
for (var i = 0; i < confidenceLevels.length; i++) {
if (confidenceLevels[i].value === item) {
return confidenceLevels[i].label;
}
}
}
// When the component has mounted to the DOM, get the user's location
componentDidMount() {
this.getLocation();
@ -104,9 +226,12 @@ export class MapContainer extends Component {
default:
break
}
return pinIcon
}
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
@ -119,42 +244,43 @@ export class MapContainer extends Component {
activeMarker: {},
selectedPlace: {},
sightings: []
}
};
render() {
const {google} = this.props;
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) => {
let pinIcon = this.sightingIcon(sighting.type)
{this.state.sightings.map((sighting) => {
let pinIcon = this.sightingIcon(sighting.type)
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: ' + this.getType(sighting.type)}
date={<Fragment><b>Date:</b> {this.formatDate(sighting.date)}</Fragment>}
time={<Fragment><b>Time:</b> {this.getTime(sighting.time)}</Fragment>}
confidence={<Fragment><b>I am confident of my sighting:</b> {this.getConfidence(sighting.confidence)}</Fragment>}
description={<Fragment><b>Description:</b> {sighting.desc}</Fragment>}
icon={{
url: pinIcon,
anchor: new google.maps.Point(32,32),
@ -165,24 +291,24 @@ export class MapContainer extends Component {
})}
<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.date}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.date }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.time}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.time }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.confidence}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.description }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.description}
</Typography>
</Fragment>
</InfoWindow>
@ -193,6 +319,4 @@ export class MapContainer extends Component {
}
// Send the Google Map API Key with the MapContainer component
export default GoogleApiWrapper({
apiKey: (API_KEY)
})(MapContainer)
export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer);

View File

@ -1,232 +1,95 @@
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
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';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import SightingDetail from './SightingDetail';
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,
},
});
class ViewSightings extends Component {
/**
* 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',
},
];
componentDidMount() {
const sightingsRef = firebase.database().ref('sightings');
/**
* Gets formatted confidence value.
*/
function getConfidence(item) {
for (var i = 0; i < confidenceLevels.length; i++) {
if (confidenceLevels[i].value === item) {
return confidenceLevels[i].label;
sightingsRef.on('value', (snapshot) => {
let sightings = snapshot.val();
let newState = [];
for (let sighting in sightings) {
newState.unshift({
id: sighting,
lat: sightings[sighting].lat,
lng: sightings[sighting].lng,
desc: sightings[sighting].desc,
type: sightings[sighting].type,
confidence: sightings[sighting].confidence,
date: sightings[sighting].date,
time: sightings[sighting].time
});
}
}
this.setState({
sightings: newState
});
});
}
/**
* 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);
getDetail = (id, lat, lng, desc, type, confidence, date, time) => {
this.setState({
selectedSighting: {
id: id,
lat: lat,
lng: lng,
desc: desc,
type: type,
confidence: confidence,
date: date,
time: time
},
clicked: true
})
}
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,
});
sightings: [],
selectedSighting: {
id: null,
lat: null,
lng: null,
desc: null,
type: null,
confidence: null,
date: null,
time: null
},
clicked: false
};
/**
* 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;
render() {
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 container>
<Grid item xs={12} md={6} className='sighting-list'>
<Fragment>
<List>
{
this.state.sightings.map((sighting) => {
return (
<ListItem button key={ sighting.id } onClick={() => this.getDetail(sighting.id, sighting.lat, sighting.lng, sighting.desc, sighting.type, sighting.confidence, sighting.date, sighting.time)}>
<ListItemText primary={`${sighting.desc}`}/>
</ListItem>
);
})
}
</List>
</Fragment>
</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 item xs={12} md={6} className='sighting-details'>
{this.state.clicked === true && <SightingDetail detail={ this.state.selectedSighting }/>}
</Grid>
</Grid>
</Fragment>
)
);
}
}
ViewSightings.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ViewSightings);
export default 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,13 +1,10 @@
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>
<ViewSightings/>
</Typography>
<ViewSightings/>
);
}
}