This commit is contained in:
Al Duncanson 2018-10-27 18:07:30 -04:00
parent 71b6ce9ad7
commit 94f5222c61
7 changed files with 312 additions and 81 deletions

5
package-lock.json generated
View File

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

View File

@ -10,6 +10,7 @@
"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",

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>

View File

@ -3,21 +3,9 @@ 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';
/**
* Shuffles a given array.
* @param {*} array The array passed in.
*/
function shuffleArray(array) {
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
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 => ({
@ -25,13 +13,45 @@ const styles = theme => ({
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": shuffleArray([
"questions": [
{
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
"questionType": "text",
@ -87,12 +107,12 @@ class QuizGame extends React.Component {
],
"correctAnswer": "3"
},
])
]
}
medium = {
"quizTitle": "Trail Cam Quiz: Medium",
"questions": shuffleArray([
"questions": [
{
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
"questionType": "text",
@ -148,12 +168,12 @@ class QuizGame extends React.Component {
],
"correctAnswer": "3"
},
])
]
}
hard = {
"quizTitle": "Trail Cam Quiz: Hard",
"questions": shuffleArray([
"questions": [
{
"question": <Fragment>What animal is this?<br /><br /><img src="/quizimages/question1.jpg" alt=""></img></Fragment>,
"questionType": "text",
@ -209,20 +229,29 @@ class QuizGame extends React.Component {
],
"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:
@ -234,8 +263,8 @@ class QuizGame extends React.Component {
// The state of the component.
state = {
//difficulty: pickDifficulty(this.props.difficulty)
difficulty: this.pickDifficulty(this.props.difficulty)
difficulty: this.pickDifficulty(this.props.difficulty),
key: Math.random()
}
// Renders the quiz component.
@ -245,9 +274,17 @@ class QuizGame extends React.Component {
return (
// Tabs
<div className={classes.root}>
<Grid container justify="center">
<Quiz quiz={this.state.difficulty} />
</Grid>
<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>
);
}

View File

@ -4,6 +4,10 @@ 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';
@ -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,
},
@ -220,7 +235,8 @@ class ReportForm extends React.Component {
confidence: '1',
desc: '',
lat: '',
lng: ''
lng: '',
open: false
};
/**
@ -233,6 +249,17 @@ class ReportForm extends React.Component {
});
};
/**
* Handles closing the toast.
*/
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
/*
* Get the coordinates
*
@ -274,7 +301,8 @@ class ReportForm extends React.Component {
confidence: '1',
desc: '',
lat: '',
lng: ''
lng: '',
open: true
});
};
@ -440,6 +468,30 @@ class ReportForm extends React.Component {
</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

@ -1,5 +1,6 @@
import React, { Component, Fragment } from 'react';
import { Map, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react';
import moment from 'moment'
import Typography from '@material-ui/core/Typography';
import firebase from '../firebase.js';
@ -12,6 +13,128 @@ const mapStyles = {
height: '100%'
}
/**
* Types of sightings. Label is what is
* viewed in the application, value is
* what is stored in the database.
*/
const sightingTypes = [
{
value: 'visual',
label: 'Visual',
},
{
value: 'roadkill',
label: 'Roadkill',
},
{
value: 'trapped',
label: 'Trapped',
},
{
value: 'viewed_tracks',
label: 'Viewed Tracks',
},
{
value: 'photo',
label: 'Photo',
},
{
value: 'other',
label: 'Other',
},
];
/**
* Types of sightings. Label is what is
* viewed in the application, value is
* what is stored in the database.
*/
const timeTypes = [
{
value: 'unknown',
label: 'Unknown',
},
{
value: 'morning',
label: 'Morning',
},
{
value: 'midday',
label: 'Midday',
},
{
value: 'evening',
label: 'Evening',
},
{
value: 'night',
label: 'Night',
},
];
/**
* Levels of confidence. Label is what is
* viewed in the application, value is
* what is stored in the database.
*/
const confidenceLevels = [
{
value: '1',
label: '1 - Strongly unconfident',
},
{
value: '2',
label: '2 - Unconfident',
},
{
value: '3',
label: '3 - Somewhat confident',
},
{
value: '4',
label: '4 - Confident',
},
{
value: '5',
label: '5 - Very confident',
},
];
/**
* Gets formatted confidence value.
*/
function getConfidence(item) {
for (var i = 0; i < confidenceLevels.length; i++) {
if (confidenceLevels[i].value === item) {
return confidenceLevels[i].label;
}
}
}
/**
* Gets formatted time value.
*/
function getTime(item) {
for (var i = 0; i < timeTypes.length; i++) {
if (timeTypes[i].value === item) {
return timeTypes[i].label;
}
}
}
/**
* Gets formatted type value.
*/
function getType(item) {
for (var i = 0; i < sightingTypes.length; i++) {
if (sightingTypes[i].value === item) {
return sightingTypes[i].label;
}
}
}
export class MapContainer extends Component {
// Get the user's location using Google's geolocation
@ -19,22 +142,22 @@ export class MapContainer extends Component {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
this.setState({
myLatLng: {
lat: position.coords.latitude,
lng: position.coords.longitude
}
myLatLng: {
lat: position.coords.latitude,
lng: position.coords.longitude
}
}
);
})
} else {
// If browser doesn't support geolocation or if user does not allow it,
// center map on Grand Rapids, Michigan
this.setState({
myLatLng: {
lat: 42.9634,
lng: 85.6681
}
myLatLng: {
lat: 42.9634,
lng: 85.6681
}
}
);
}
}
@ -85,6 +208,10 @@ export class MapContainer extends Component {
}
}
formatDate = date => {
return (moment(date, "YYYY-MM").format("MMMM YYYY").toString())
}
// Set the state of the component to contain user coordinates and initial
// marker and info window information
state = {
@ -101,55 +228,55 @@ export class MapContainer extends Component {
render() {
return (
// Render the Google Map, Marker, and InfoWindow components
<div className = "sighting-google-map-container">
<div className="sighting-google-map-container">
<Map
style = { mapStyles }
google = { this.props.google }
initialCenter = { this.state.myLatLng }
center = { this.state.myLatLng }
defaultZoom = { 15 }
onClick = { this.onMapClick } >
style={mapStyles}
google={this.props.google}
initialCenter={this.state.myLatLng}
center={this.state.myLatLng}
defaultZoom={15}
onClick={this.onMapClick} >
<Marker
position = { this.state.myLatLng }
onClick = { this.onMarkerClick }
type = { 'You are here' }
<Marker
position={this.state.myLatLng}
onClick={this.onMarkerClick}
type={'You are here'}
/>
{ this.state.sightings.map((sighting) => {
{this.state.sightings.map((sighting) => {
return (
<Marker
key={ sighting.id }
position={{ lat: sighting.lat, lng:sighting.lng }}
onClick = { this.onMarkerClick }
type = { 'Type: ' + sighting.type }
confidence = { 'Confidence: ' + sighting.confidence }
date = { 'Date: ' + sighting.date }
time = { 'Time: ' + sighting.time }
description = { 'Description: ' + sighting.desc }
key = {sighting.id}
position = {{ lat: sighting.lat, lng: sighting.lng }}
onClick = {this.onMarkerClick}
type = {'Type: ' + getType(sighting.type)}
confidence = {<Fragment><b>Confidence:</b> {getConfidence(sighting.confidence)}</Fragment>}
date = {<Fragment><b>Date:</b> {this.formatDate(sighting.date)}</Fragment>}
time = {<Fragment><b>Time:</b> {getTime(sighting.time)}</Fragment>}
description = {<Fragment><b>Description:</b> {sighting.desc}</Fragment>}
/>
)
})}
<InfoWindow
marker = { this.state.activeMarker }
visible = { this.state.showingInfoWindow } >
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow} >
<Fragment>
<Typography variant = "display1" gutterBottom>
{ this.state.selectedPlace.type }
<Typography variant="display1" gutterBottom>
{this.state.selectedPlace.type}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.confidence }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.confidence}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.date }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.date}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.time }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.time}
</Typography>
<Typography variant = "subheading" gutterBottom>
{ this.state.selectedPlace.description }
<Typography variant="subheading" gutterBottom>
{this.state.selectedPlace.description}
</Typography>
</Fragment>
</InfoWindow>

View File

@ -3,9 +3,7 @@ import Grid from '@material-ui/core/Grid';
import firebase from '../firebase.js';
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 HomeIcon from '@material-ui/icons/Home';
import SightingDetail from './SightingDetail';
class ViewSightings extends Component {
@ -77,7 +75,6 @@ class ViewSightings extends Component {
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)}>
<ListItemIcon><HomeIcon/></ListItemIcon>
<ListItemText primary={`${sighting.desc}`}/>
</ListItem>
)