diff options
| -rw-r--r-- | mobile/package.json | 4 | ||||
| -rw-r--r-- | mobile/screens/Form1.js | 200 | ||||
| -rw-r--r-- | web/models/mockpoints.js | 10 | ||||
| -rw-r--r-- | web/models/points.js | 10 | ||||
| -rw-r--r-- | web/public/javascripts/map.js | 98 | ||||
| -rw-r--r-- | web/public/stylesheets/style.css | 32 | ||||
| -rw-r--r-- | web/routes/index.js | 11 | ||||
| -rw-r--r-- | web/views/map.pug | 21 |
8 files changed, 238 insertions, 148 deletions
diff --git a/mobile/package.json b/mobile/package.json index 488e885..80a591e 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -15,8 +15,8 @@ "expo": "^32.0.0", "react": "16.5.0", "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz", - "react-native-elements": "^1.1.0", - "react-navigation": "^3.0.9" + "react-navigation": "^3.0.9", + "tcomb-form-native": "^0.6.20" }, "devDependencies": { "babel-preset-expo": "^5.0.0", diff --git a/mobile/screens/Form1.js b/mobile/screens/Form1.js index 2a52499..a14d647 100644 --- a/mobile/screens/Form1.js +++ b/mobile/screens/Form1.js @@ -1,9 +1,81 @@ import React, { Component } from 'react'; -import {Text, Alert, AppRegistry, StyleSheet, View } from 'react-native'; -import { Button, Input, CheckBox } from 'react-native-elements'; +import { StyleSheet, Button, View, KeyboardAvoidingView } from 'react-native'; import { Constants } from 'expo'; +import t from 'tcomb-form-native'; + +const Form = t.form.Form; + +var Gender = t.enums({ + "Male" : "Male", + "Female": "Female", + "Unsure": "Can't say for sure", +}); + +var AgeRanges = t.enums({ + "0-20" : "Under 20 years", + "20-30": "20 - 30 years", + "30-50": "30 - 50 years", + "50+" : "Over 50 years", +}); + +var Races = t.enums({ + "White" : "European or White", + "eAsian" : "East Asian", + "sAsian" : "South Asian", + "Black" : "Black or African American", + "Aboriginal": "Aboriginal", + "Other" : "Other", +}); + +const Report = t.struct({ + gender : Gender, + age : AgeRanges, + race : Races, + longhair : t.Boolean, + longbeard : t.Boolean, + extra : t.maybe(t.String) +}); + +const options = { + label: "Help us identify them!", + fields: { + gender: { + auto: 'none', + nullOption: {value: '', text: "Gender"}, + error: "Please pick an option for this field", + }, + age: { + auto: 'none', + nullOption: {value: '', text: "Guess their age"}, + error: "Please pick an option for this field" + }, + race: { + auto: 'none', + nullOption: {value: '', text: "Race"}, + error: "Please pick an option for this field", + }, + longhair: { + label: "Long hair?" + }, + longbeard: { + label: "Long beard?" + }, + extra: { + label: "Distinctive features", + help: "How would you identify them in a crowd? Tattoos, peircings, holding a sign etc.", + }, + }, +}; export default class Form1 extends Component { + + constructor(props) { + super(props); + this.state = { + value: null, + }; + } + getApiUrl() { var releaseChannel = Constants.manifest.releaseChannel; if (releaseChannel === undefined) return Constants.manifest.extra.apiUrl.dev @@ -11,83 +83,81 @@ export default class Form1 extends Component { if (releaseChannel.indexOf('staging') !== -1) return Constants.manifest.extra.apiUrl.staging } - _onPressButton1() { + _onSubmitPress(values) { const { navigation } = this.props; const coordinates = navigation.getParam('coordinates'); - var uri = this.getApiUrl(); - fetch(uri, { + var url = this.getApiUrl(); + fetch(url, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ - 'coordinates': coordinates, - 'type': "Point", - 'isInjured': this.state.injured, - 'reasonForHelp': this.state.why, - 'ageRange': this.state.age, - 'clothingDescription': this.state.appearance - }), - }); - // TODO: handle the response from the serve and decide what to display - // based on that - // for now just to back to the home page - this.props.navigation.navigate('GreetingPage'); -} - _onPressButton2() { - Alert.alert('Continue:') + type : "Point", + coordinates : coordinates, + gender : values.gender, + age : values.age, + race : values.race, + longhair : values.longhair, + longbeard : values.longbeard, + extra : (values.extra) ? values.extra : "" + }), + }); + //// TODO: handle the response from the serve and decide what to display + //// based on that + //// for now just to back to the home page + alert("Thank you for your contribution."); + this.props.navigation.navigate('GreetingPage'); } - constructor(props) { - super(props); - this.state = { - age: '', - appearance: '', - injured: false, - why: '' - }; + + handleSubmit() { + var value = this.refs.form.getValue(); + if (value) { + this._onSubmitPress(value); + this.clearForm(); + } + } + + onChange(value) { + this.setState({ value }); + } + + clearForm() { + this.setState({ value: null }); } render() { return ( - <View style={{padding: 40}}> - <Text>Age:</Text> - <Input - style={{height: 40}} - placeholder="Estimate is fine" - onChangeText={(age) => this.setState({age})} - /> - <Text>Appearance:</Text> - <Input - style={{height: 40}} - placeholder="What are they wearing?" - onChangeText={(appearance) => this.setState({appearance})} - /> - <CheckBox - center - title="Are they injured?" - checked={this.state.injured} - onPress={() => this.setState({ injured: !this.state.injured })} - /> - <Text>Reason for help?</Text> - <Input - style={{height: 40}} - placeholder="Please keep it short" - onChangeText={(why) => this.setState({why})} - /> - <View style={{padding: 40}}> - <Button - buttonStyle={{backgroundColor:"green"}} - onPress={() => this._onPressButton1()} - title="Submit!" - /> - <Button - containerStyle={{paddingTop: 10}} - onPress={() => this.props.navigation.navigate('GreetingPage')} - title="Go Back" - /> + <KeyboardAvoidingView + style={styles.container} + behavior="position" + > + <Form + ref="form" // assign a reference + type={Report} + options={options} + value={this.state.value} + onChange={this.onChange.bind(this)} + /> + <View style={{flexDirection: 'row'}}> + <View style={{flex:1 , marginRight:10}} > + <Button title="Back" onPress={() => this.props.navigation.navigate('GreetingPage')} /> + </View> + <View style={{flex:1}} > + <Button title="Submit" color="#841584" onPress={() => this.handleSubmit()} /> + </View> </View> - </View> + </KeyboardAvoidingView> ); } } + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + marginTop: 50, + padding: 20, + backgroundColor: '#ffffff', + }, +}); diff --git a/web/models/mockpoints.js b/web/models/mockpoints.js index ff5e771..ac9ecd4 100644 --- a/web/models/mockpoints.js +++ b/web/models/mockpoints.js @@ -5,10 +5,12 @@ var Schema = mongoose.Schema; var dumbJsonSchema = new Schema({ type: String, coordinates: Array, - ageRange: String, - clothingDescription: String, - isInjured: Boolean, - reasonForHelp: String + gender: String, + age: String, + race: String, + longhair: Boolean, + longbeard: Boolean, + extra: String }); // Mongoose Model definition diff --git a/web/models/points.js b/web/models/points.js index 3a07c82..d27f2a7 100644 --- a/web/models/points.js +++ b/web/models/points.js @@ -5,10 +5,12 @@ var Schema = mongoose.Schema; var JsonSchema = new Schema({ type: String, coordinates: Array, - ageRange: String, - clothingDescription: String, - isInjured: Boolean, - reasonForHelp: String + gender: String, + age: String, + race: String, + longhair: Boolean, + longbeard: Boolean, + extra: String }); // Mongoose Model definition diff --git a/web/public/javascripts/map.js b/web/public/javascripts/map.js index 3f33bcf..8d4fc3b 100644 --- a/web/public/javascripts/map.js +++ b/web/public/javascripts/map.js @@ -3,6 +3,15 @@ // attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' // }).addTo(map); +const Races = { + "White" : "European or White", + "eAsian" : "East Asian", + "sAsian" : "South Asian", + "Black" : "Black or African American", + "Aboriginal": "Aboriginal", + "Other" : "Other", +}; + function plotPointsOnMap(points) { L.geoJson(points, { pointToLayer: function (feature, latlng) { @@ -10,7 +19,7 @@ function plotPointsOnMap(points) { latlngbounds.extend(latlng); return L.marker(latlng); } - }).on('click', showDetails).addTo(map) + }).on('click', showDetails).addTo(map); // rezoom the map so that all the markers fit in the view, add 20% padding so // that marker dont cut off @@ -21,60 +30,55 @@ function plotPointsOnMap(points) { // e is the event info function showDetails(e) { // layer.feature.geometry gives you access to all the fields - let layer = e.layer - - let sideBar = document.getElementById('sidebar') + let layer = e.layer; + + let sideBar = document.getElementById('sidebar'); if (getComputedStyle(sideBar).visibility === 'hidden') { - sideBar.style.visibility = 'visible' + sideBar.style.visibility = 'visible'; } - let point = document.getElementById('point') - - //get the previous input text from the previous point - let prevUserInput = point.getElementsByClassName('user-input') - - // remove the previous point text - while (prevUserInput.length !== 0) { - prevUserInput[0].parentNode.removeChild(prevUserInput[0]) + // remove the previous report text + let report = document.getElementById('report'); + let prevUserInputs = report.getElementsByClassName('user-input'); + for (let i=0; i < prevUserInputs.length; i++) { + prevUserInputs[i].innerHTML = ""; } - let pointBreaks = point.getElementsByClassName('point-break') + // put gender of the person + let genderTextSpan = document.getElementById('report-gender'); + genderTextSpan.className = 'user-input'; + let genderText = document.createTextNode(layer.feature.geometry['gender']); + genderTextSpan.appendChild(genderText); // put age range of person - let ageRangeTextSpan = document.createElement('span') - ageRangeTextSpan.className = 'user-input' - let ageRangeText = document.createTextNode(layer.feature.geometry['ageRange']) - ageRangeTextSpan.appendChild(ageRangeText) - pointBreaks[0].parentNode.insertBefore(ageRangeTextSpan, pointBreaks[0]) - - // put clothing description of person - let clothingDescTextSpan = document.createElement('span') - clothingDescTextSpan.className = 'user-input' - let clothingDescText = document.createTextNode(layer.feature.geometry['clothingDescription']) - clothingDescTextSpan.appendChild(clothingDescText) - pointBreaks[1].parentNode.insertBefore(clothingDescTextSpan, pointBreaks[1]) - - // put whether person is injured or not - let isInjured = layer.feature.geometry['isInjured'] - let injurySpan = document.createElement('span') - injurySpan.className = 'user-input' - if (isInjured) { - injurySpan.appendChild(document.createTextNode('Injured')) - injurySpan.style.color = 'red' - } - else { - injurySpan.appendChild(document.createTextNode('Not injured')) - injurySpan.style.color = 'green' - } - pointBreaks[2].parentNode.insertBefore(injurySpan, pointBreaks[2]) - - // put reason for help - let helpReasonTextSpan = document.createElement('span') - helpReasonTextSpan.className = 'user-input' - let helpReasonText = document.createTextNode(layer.feature.geometry['reasonForHelp']) - helpReasonTextSpan.appendChild(helpReasonText) - point.appendChild(helpReasonTextSpan) + let ageRangeTextSpan = document.getElementById('report-age-range'); + ageRangeTextSpan.className = 'user-input'; + let ageRangeText = document.createTextNode( + " ~ " + layer.feature.geometry['age'] + " years"); + ageRangeTextSpan.appendChild(ageRangeText); + + // put race of person + let raceTextSpan = document.getElementById('report-race'); + raceTextSpan.className = 'user-input'; + let raceText = document.createTextNode( + Races[layer.feature.geometry['race']] + ); + raceTextSpan.appendChild(raceText); + + // put other attributes + let otherAttrTextSpan = document.getElementById('report-other'); + otherAttrTextSpan.className = 'user-input'; + otherAttrTextSpan.innerHTML = + "Long hair? " + (layer.feature.geometry['longhair'] ? "Yes" : "No") + + "<br>" + + "Long beard? " + (layer.feature.geometry['longbeard'] ? "Yes" : "No"); + + // put extra info + let extraTextSpan = document.getElementById('report-distinctive'); + extraTextSpan.className = 'user-input'; + let extraText = document.createTextNode(layer.feature.geometry['extra']); + extraTextSpan.appendChild(extraText); let closeBtn = document.getElementById('close-btn') closeBtn.addEventListener('click', closeDetails) diff --git a/web/public/stylesheets/style.css b/web/public/stylesheets/style.css index d8618cd..ece8da4 100644 --- a/web/public/stylesheets/style.css +++ b/web/public/stylesheets/style.css @@ -21,8 +21,6 @@ a { left: 5px; z-index: 2; width: 350px; - height: 275px; - max-height: 275px; position: relative; float: left; background-color: lightgrey; @@ -34,11 +32,11 @@ a { margin-top: 10px; margin-bottom: 10px; } -#point-container { - overflow: auto; - max-height: 195px; +#report-container { + overflow: auto; + max-height: 500px; } -#point { +#report { background-color: #0CC5EA; width: 90%; margin-left: auto; @@ -46,9 +44,27 @@ a { margin-bottom: 5px; padding: 5px; border-radius: 10px; + display: flex; + justify-content: flex-end; + flex-direction: column; +} +#report strong { + font-size: 22px; + font-style: italic; + margin: auto; + padding: 5px; +} +#report span { + background-color: white; + padding: 15px; + border-radius: 10px; + font-weight: lighter; + font-size: large; + text-align: center; + overflow-wrap: break-word; + word-wrap: break-word; } #close-btn-container { - position: fixed; height: 30px; top: 250px; background-color: lightgrey; @@ -64,4 +80,4 @@ a { margin-right: 20px; border-radius: 10px; width: 100px; -}
\ No newline at end of file +} diff --git a/web/routes/index.js b/web/routes/index.js index b2ba91f..23fcf36 100644 --- a/web/routes/index.js +++ b/web/routes/index.js @@ -23,14 +23,7 @@ router.get('/map', function(req,res) { }); router.post('/mobilerequest', function(req, res) { - var data = { - 'coordinates': req.body.coordinates, - 'type': req.body.type, - 'isInjured': req.body.isInjured, - 'reasonForHelp': req.body.reasonForHelp, - 'ageRange': req.body.ageRange, - 'clothingDescription': req.body.clothingDescription - } + var data = req.body; Points.save_request(data, function(err, result) { if (err) { console.log(`err inserting mobile request into db: ${err}`); @@ -44,7 +37,7 @@ router.post('/mobilerequest', function(req, res) { res.status(200).send({'status': 'success', 'data': data}); } }); - + }); diff --git a/web/views/map.pug b/web/views/map.pug index f5837b6..484899c 100644 --- a/web/views/map.pug +++ b/web/views/map.pug @@ -2,15 +2,18 @@ extends layout.pug block content #sidebar h1#details-header Details - #point-container - #point - strong Age range: - br.point-break - strong Clothing description: - br.point-break - strong Injury status: - br.point-break - strong Reason for help: + #report-container + #report + strong Gender + span#report-gender + strong Age Range + span#report-age-range + strong Race + span#report-race + strong Other Attributes + span#report-other + strong Distinctive Features + span#report-distinctive #close-btn-container button#close-btn Close |
