Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
pandananta committed Aug 22, 2018
2 parents 303a3f7 + b4a1df8 commit 61c9e83
Show file tree
Hide file tree
Showing 41 changed files with 8,422 additions and 502 deletions.
Binary file modified .DS_Store
Binary file not shown.
17 changes: 17 additions & 0 deletions .gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

node_modules
#!include:.gitignore
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ dist/
functions/lib/
js_scripts/
.env

config/firebase-gcp.yml
config/firebase-gmail.yml
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ yarn
expo-cli start
```

### Admin App
There are really basic html forms that allow a user to interact and experiment with firestore.
It's a simple static website, with javascript form index.js, and html in index.html. Use your
favorite http server from the root directory.

``` bash
python -m http.server
```

### SQL

Expand All @@ -34,10 +42,13 @@ cloud_sql_proxy -instances=<INSTANCE_CONNECTION_NAME>=tcp:5432 -credential_file=
```

### Cloud functions
Updating the environment variables used by cloud functions
Fillout the provided templates in the config directory and rename the files to not include -template.
run the following commands to set up enviroment variables, and deploy the functions to your firebase
cloud instance.

``` bash
cat $path-to-dotenv-file | sed 's/^/pg./' | xargs firebase functions:config:set
cat config/firebase-gcp.yml | sed 's/^/gcp./' | sed 's/: /=/' | xargs firebase functions:config:set
cat config/firebase-gmail.yml | sed 's/^/email./' | sed 's/: /=/' | xargs firebase functions:config:set
firebase deploy --only functions

```
Expand Down
5 changes: 5 additions & 0 deletions config/development.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
db_host=localhost
db_user=postgres
db_pass=postgres
db_name=dataCollect
db_port=5431
1 change: 1 addition & 0 deletions config/firebase-gcp-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cloud_functions_url:
2 changes: 2 additions & 0 deletions config/firebase-gmail-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
email:
password:
2 changes: 2 additions & 0 deletions config/firebase-gmail.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
email: [email protected]
password: 48tandoor
2 changes: 1 addition & 1 deletion deployment/develop.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
docker ps -a | grep swl-eng | awk '{ print $1 }' | xargs docker rm
packer build postgres.json
docker run -p 5432:5432 swl-eng/gehl-data-collector:v0.0.1 postgres
docker run -p 5431:5431 swl-eng/gehl-data-collector:v0.0.1 postgres -p 5431
9 changes: 8 additions & 1 deletion deployment/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ CREATE TABLE IF NOT EXISTS study
scale studyScale,
user_id UUID REFERENCES public.users(user_id) NOT NULL,
protocol_version TEXT NOT NULL,
dsl_definition JSON,
tablename VARCHAR(63),
notes TEXT
);


-- survey metadata
-- no user tied to this? I guess anonymous surveys are a thing, what about fake data?
-- TODO should id have default uuid function call?
CREATE TABLE survey (
CREATE TABLE IF NOT EXISTS survey (
study_id UUID references study(study_id) NOT NULL,
location_id UUID,
survey_id UUID PRIMARY KEY,
Expand All @@ -52,6 +54,11 @@ CREATE TABLE survey (

CREATE TYPE gender AS ENUM ('male', 'female', 'unknown');

CREATE TABLE IF NOT EXISTS surveyors (
survey_id UUID references survey(survey_id) NOT NULL,
user_id UUID references public.users(user_id) NOT NULL,
PRIMARY KEY(survey_id, user_id)
)

-- the protocol doesn't make use of what

Expand Down
2 changes: 1 addition & 1 deletion deployment/postgres.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"ENV POSTGRES_PASSWORD {{ user `pg_pass` }}",
"ENV POSTGRES_USER {{ user `pg_user` }}",
"ENV POSTGRES_DB {{ user `db_name` }}",
"EXPOSE 5432 5432"
"EXPOSE 5431 5431"
]
}
],
Expand Down
Binary file added expo_project/.DS_Store
Binary file not shown.
Binary file removed expo_project/assets/fonts/SpaceMono-Regular.ttf
Binary file not shown.
Binary file added expo_project/assets/fonts/monaco.ttf
Binary file not shown.
35 changes: 35 additions & 0 deletions expo_project/components/ColoredButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import PropTypes from "prop-types";
import React from "react";

import { StyleSheet, Text, TouchableOpacity } from "react-native";

class ColoredButton extends React.Component {
render() {
const { backgroundColor, color, onPress, label } = this.props;
return (
<TouchableOpacity
style={[styles.button, { backgroundColor }]}
onPress={onPress}
>
<Text style={[styles.text, { color }]}>{label}</Text>
</TouchableOpacity>
);
}
}

const styles = StyleSheet.create({
button: {
backgroundColor: "#5B93D9",
padding: 12,
marginVertical: 20,
justifyContent: "center",
alignItems: "center"
},
text: { fontWeight: "bold" }
});

ColoredButton.propTypes = {
color: PropTypes.string.isRequired
};

export default ColoredButton;
82 changes: 82 additions & 0 deletions expo_project/components/MapWithMarkers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from "prop-types";
import React from "react";
import { StyleSheet } from "react-native";
import { MapView } from "expo";
import PersonIcon from "./PersonIcon";

class MapWithMarkers extends React.Component {
constructor(props) {
super(props);
}

render() {
const {
markers,
activeMarkerId,
onMarkerDragEnd,
onMarkerPress,
onMapPress,
onMapLongPress
} = this.props;
return (
<MapView
style={styles.mapStyle}
onPress={onMapPress}
onLongPress={onMapLongPress}
showsUserLocation
followsUserLocation
zoomEnabled
rotateEnabled
pitchEnabled={false}
>
{markers.map(marker => {
const selected = marker.id === activeMarkerId;
// Update the key when selected or delected, so the marker re renders and centers itself based on the new child size
const key = marker.id + (selected ? "-selected" : "");
return (
<MapView.Marker
coordinate={marker.coordinate}
key={key}
identifier={marker.id}
stopPropagation
draggable
onDragEnd={onMarkerDragEnd}
onPress={() => onMarkerPress(marker.id)}
anchor={{ x: 0, y: 0 }}
calloutAnchor={{ x: 0, y: 0 }}
>
<PersonIcon
backgroundColor={marker.color}
size={selected ? 24 : 16}
shadow
/>
</MapView.Marker>
);
})}
</MapView>
);
}
}

const styles = StyleSheet.create({
mapStyle: { flex: 1 }
});

MapWithMarkers.propTypes = {
markers: PropTypes.arrayOf(
PropTypes.shape({
coordinate: PropTypes.any,
color: PropTypes.string,
title: PropTypes.string,
dateLabel: PropTypes.string,
id: PropTypes.string
})
).isRequired,
activeMarkerId: PropTypes.string,
onMarkerDragEnd: PropTypes.func.isRequired,
onMarkerPress: PropTypes.func.isRequired,
onMapPress: PropTypes.func.isRequired,
onMapLongPress: PropTypes.func.isRequired
};

export default MapWithMarkers;
132 changes: 132 additions & 0 deletions expo_project/components/MarkerCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import PropTypes from "prop-types";
import React from "react";

import PersonIcon from "./PersonIcon";

import { FlatList, StyleSheet, TouchableOpacity } from "react-native";

import * as _ from "lodash";

const CAROUSEL_ICON_SIZE = 50;
const CAROUSEL_ITEM_PADDING = 12;
const CAROUSEL_ITEM_LENGTH = CAROUSEL_ICON_SIZE + 2 * CAROUSEL_ITEM_PADDING;

const VIEWABILITY_CONFIG = { itemVisiblePercentThreshold: 100 };

class MarkerCarousel extends React.Component {
constructor(props) {
super(props);

this.state = {
viewableIndices: []
};
this.onViewableItemsChanged = this.onViewableItemsChanged.bind(this);
}

onViewableItemsChanged({ viewableItems }) {
const viewableIndices = _.map(viewableItems, "index");
this.setState({ viewableIndices });
}

componentDidUpdate(prevProps, prevState) {
// If user selects a marker, and it's not visible, scroll to it
// Note that Adding / removing markers trigger their own animation (see: onContentSizeChange)
// Therefore we stop if props.markers has changed
if (
this.props.markers === prevProps.markers &&
this.props.activeMarkerId !== prevProps.activeMarkerId
) {
const index = _.findIndex(this.props.markers, {
id: this.props.activeMarkerId
});
if (index > -1) {
// Only scroll if the new selection isn't already visible
if (!_.includes(this.state.viewableIndices, index)) {
this.flatList.scrollToIndex({
index,
viewPosition: 0.5,
animated: true
});
}
}
}
}

render() {
const { activeMarkerId, markers, onMarkerPress } = this.props;
return (
<FlatList
style={styles.container}
data={markers}
keyExtractor={item => item.id}
extraData={activeMarkerId}
horizontal
removeClippedSubviews
showsHorizontalScrollIndicator={false}
ref={ref => (this.flatList = ref)}
onContentSizeChange={(contentWidth, contentHeight) => {
if (markers.length > 1) {
// This is janky sometimes when there's only one item for some reason ...
this.flatList.scrollToEnd({ animated: true });
}
}}
getItemLayout={(data, index) => ({
length: CAROUSEL_ITEM_LENGTH,
offset: CAROUSEL_ITEM_LENGTH * index,
index
})}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={VIEWABILITY_CONFIG}
renderItem={({ item, index }) => {
const selected = item.id === activeMarkerId;
return (
<TouchableOpacity
Index={index}
style={[
styles.cell,
selected && { borderBottomColor: item.color }
]}
onPress={() => onMarkerPress(item.id)}
>
<PersonIcon
backgroundColor={item.color}
size={CAROUSEL_ICON_SIZE}
/>
</TouchableOpacity>
);
}}
/>
);
}
}

const styles = StyleSheet.create({
container: {
borderBottomColor: "rgba(0, 0, 0, 0.12)",
borderBottomWidth: 1
},
cell: {
padding: CAROUSEL_ITEM_PADDING,
// there's a border on selected cells, so put an inivisble border on all cells to keep cell height consistent
borderBottomColor: "transparent",
borderBottomWidth: 4,
justifyContent: "center",
alignItems: "center"
}
});

MarkerCarousel.propTypes = {
markers: PropTypes.arrayOf(
PropTypes.shape({
coordinate: PropTypes.any,
color: PropTypes.string,
title: PropTypes.string,
dateLabel: PropTypes.string,
id: PropTypes.string
})
).isRequired,
activeMarkerId: PropTypes.string,
onMarkerPress: PropTypes.func.isRequired
};

export default MarkerCarousel;
Loading

0 comments on commit 61c9e83

Please sign in to comment.