This commit is contained in:
Andrés Uribe Stengel 2020-07-05 19:18:23 +02:00
parent 8501b21c7d
commit 7109a48130
22 changed files with 1687 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
.idea/
.vscode/
.vs/

View File

@ -0,0 +1,19 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze

1
.meteor/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
local

7
.meteor/.id Normal file
View File

@ -0,0 +1,7 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
r536tsv6d95.mwc4865k5bm

23
.meteor/packages Normal file
View File

@ -0,0 +1,23 @@
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.4.0 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.10.0 # The database Meteor supports right now
jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library
standard-minifier-css@1.6.0 # CSS minifier run for production mode
standard-minifier-js@2.6.0 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript@0.14.3 # Enable ECMAScript2015+ syntax in app code
typescript@3.7.6 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
autopublish@1.0.7 # Publish all data to the clients (for prototyping)
insecure@1.0.7 # Allow all DB writes from clients (for prototyping)
static-html

2
.meteor/platforms Normal file
View File

@ -0,0 +1,2 @@
server
browser

1
.meteor/release Normal file
View File

@ -0,0 +1 @@
METEOR@1.10.2

72
.meteor/versions Normal file
View File

@ -0,0 +1,72 @@
allow-deny@1.1.0
autopublish@1.0.7
autoupdate@1.6.0
babel-compiler@7.5.3
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.0.10
boilerplate-generator@1.7.0
caching-compiler@1.2.2
caching-html-compiler@1.1.3
callback-hook@1.3.0
check@1.3.1
ddp@1.4.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-server@2.3.1
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.5.2
ecmascript@0.14.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.10.0
ecmascript-runtime-server@0.9.0
ejson@1.1.1
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
id-map@1.1.0
insecure@1.0.7
inter-process-messaging@0.1.1
jquery@3.0.0
launch-screen@1.2.0
livedata@1.0.18
logging@1.1.20
meteor@1.9.3
meteor-base@1.4.0
minifier-css@1.5.1
minifier-js@2.6.0
minimongo@1.6.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.15.0
modules-runtime@0.12.0
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
npm-mongo@3.7.1
ordered-dict@1.1.0
promise@0.11.2
random@1.2.0
reactive-var@1.0.11
reload@1.3.0
retry@1.1.0
routepolicy@1.1.0
shell-server@0.5.0
socket-stream-client@0.3.0
spacebars-compiler@1.1.3
standard-minifier-css@1.6.0
standard-minifier-js@2.6.0
static-html@1.2.2
templating-tools@1.1.2
tracker@1.2.0
typescript@3.7.6
underscore@1.0.10
webapp@1.9.1
webapp-hashing@1.0.9

11
client/main.html Normal file
View File

@ -0,0 +1,11 @@
<!--
<script
src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
crossorigin>
</script>
-->
<body>
<div id="root"></div>
</body>

9
client/main.jsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import { Meteor } from 'meteor/meteor';
import ReactDOM from 'react-dom';
import App from '../imports/ui/App';
import 'bootstrap/dist/css/bootstrap.min.css';
Meteor.startup(() => {
ReactDOM.render(<App />, document.getElementById('root'));
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,3 @@
import { Mongo } from 'meteor/mongo';
export const PlantTypesCollection = new Mongo.Collection('plantTypes');

9
imports/ui/About.jsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react'
export default function About() {
return (
<div>
About
</div>
)
}

36
imports/ui/App.jsx Normal file
View File

@ -0,0 +1,36 @@
import React, { Suspense } from 'react'
import NavBar from './NavigationBar'
import Home from './Home'
import About from './About'
import Settings from './Settings'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
function App() {
const exampleData = [
{ name: 'Timestamp A', temperature: 38, humidity: 66, light: 75, moisture: 21 },
{ name: 'Timestamp B', temperature: 37, humidity: 65, light: 65, moisture: 22 },
{ name: 'Timestamp C', temperature: 39, humidity: 62, light: 55, moisture: 22 },
{ name: 'Timestamp D', temperature: 40, humidity: 61, light: 85, moisture: 21 },
{ name: 'Timestamp E', temperature: 40, humidity: 60, light: 80, moisture: 21 }
];
return (
<>
<NavBar/>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" exact>
<Home dummyData={exampleData}></Home>
</Route>
<Route path="/settings" component={Settings}></Route>
<Route path="/about" component={About}></Route>
</Switch>
</Suspense>
</Router>
</>
)
}
export default App;

51
imports/ui/Home.jsx Normal file
View File

@ -0,0 +1,51 @@
import React, {useEffect, useState} from 'react' // useState to rerender the view each time something changed
import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import SensorCardDeck from './SensorCardDeck'
export default function Home(props) {
var updateCardValues = () => {
var updatedValues = [
props.dummyData[props.dummyData.length - 1].temperature,
props.dummyData[props.dummyData.length - 1].humidity,
props.dummyData[props.dummyData.length - 1].light,
props.dummyData[props.dummyData.length - 1].moisture
];
return updatedValues;
}
// this data represents the sensor values, they should be initialized empty.. this is just for the simulation when it first renders
const [sensorCardValues, setSensorCardValues] = useState(updateCardValues);
const [dataHistory, setDataHistory] = useState(props.dummyData); // for init data
// runs when the app mounts
// if you give a prop to useEffect, it also runs each time this prop changes..
useEffect(() => {
console.log("data is changing, start rerender..")
setDataHistory(props.dummyData);
setSensorCardValues(updateCardValues);
}, [props.dummyData])
// render the data
// make sensorCarddeck responsive
return (
<>
<SensorCardDeck sensorCardValues={sensorCardValues}/>
<ResponsiveContainer width='100%' height={400}>
<LineChart data={dataHistory} margin={{ top: 60, right: 60, bottom: 30, left: 5 }}>
<Line type="monotone" dataKey="temperature" stroke="#10b5de" />
<Line type="monotone" dataKey="humidity" stroke="#ff6f00" />
<Line type="monotone" dataKey="light" stroke="#ffd500" />
<Line type="monotone" dataKey="moisture" stroke="#1c4399" />
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
</LineChart>
</ResponsiveContainer>
</>
)
}

View File

@ -0,0 +1,34 @@
import React from 'react'
import { Navbar, Nav, NavDropdown } from 'react-bootstrap';
export default function NavigationBar() {
return (
<Navbar collapseOnSelect expand="lg" bg="light" variant="light">
<Navbar.Brand href="/">
<img
src="SmartGarden.svg"
width="30"
height="30"
className="d-inline-block align-top"
rounded="true"
alt="SmartGarden"
/>{' '}
SmartGarden
</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/settings">Settings</Nav.Link>
<Nav.Link href="/about">About</Nav.Link>
<NavDropdown title="Dropdown" id="collasible-nav-dropdown">
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>
)
}

View File

@ -0,0 +1,33 @@
import React from 'react'
import { Card, CardDeck } from 'react-bootstrap';
export default function SensorCardDeck( {sensorCardValues} ) {
return (
<CardDeck>
<Card>
<Card.Body>
<Card.Title>Temperature</Card.Title>
<Card.Text>{sensorCardValues[0]} °C</Card.Text>
</Card.Body>
</Card>
<Card>
<Card.Body>
<Card.Title>Humidity</Card.Title>
<Card.Text>{sensorCardValues[1]} %</Card.Text>
</Card.Body>
</Card>
<Card>
<Card.Body>
<Card.Title>Light</Card.Title>
<Card.Text>{sensorCardValues[2]} H</Card.Text>
</Card.Body>
</Card>
<Card>
<Card.Body>
<Card.Title>Moisture</Card.Title>
<Card.Text>{sensorCardValues[3]} %</Card.Text>
</Card.Body>
</Card>
</CardDeck>
)
}

181
imports/ui/Settings.jsx Normal file
View File

@ -0,0 +1,181 @@
import React from 'react'
import { Button, Form, Container, Row, Col, Table } from 'react-bootstrap';
class Settings extends React.Component{
constructor(props){
super(props);
this.state = {
name: '',
type: '',
dirt: '',
plants: [],
types: ['Chile', 'Mint'], // hardcoded for now
dirts: ['Vegetable Soil', 'Potting Soil'], // hardcoded for now
updateId: -1,
btnName: 'submit'
}
}
componentDidMount(){
// fetch data from db here
}
render(){
return (
<>
<Container>
<Row className="justify-content-md-center">
<Col md="auto">
<h1>Settings</h1>
</Col>
</Row>
<Row className="justify-content-md-center">
<a>Configurate your plant here. Based on your settings the smart garden will automate your plant environment.</a>
</Row>
<br></br>
{
this.state.updateId >= 0 | this.state.plants.length === 0? // show the form only if we edit the plant or there is no plant set yet
<Form onSubmit={this.handleSubmit}>
<Form.Group controlId="exampleForm.ControlInput1">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
value={this.state.name}
autoFocus ref={(input) => {this.nameInput=(input)}}
placeholder="plant"
onChange={(e)=>this.setState({name:e.target.value})} />
</Form.Group>
<Form.Group controlId="exampleForm.ControlSelect1">
<Form.Label>Type</Form.Label>
<Form.Control
as="select"
type="text"
value={this.state.type}
onChange={(e)=>this.setState({type:e.target.value})}
>
<option></option>
{this.state.types.map((type, index) => {
return <option key={index} value={type}>{type}</option>
})}
</Form.Control>
</Form.Group>
<Form.Group controlId="exampleForm.ControlSelect1">
<Form.Label>Dirt</Form.Label>
<Form.Control
as="select"
type="text"
value={this.state.dirt}
onChange={(e)=>this.setState({dirt:e.target.value})}
>
<option></option>
{this.state.dirts.map((dirt, index) => {
return <option key={index}>{dirt}</option>
})}
</Form.Control>
</Form.Group>
<Button type={"submit"}>{this.state.btnName}</Button>
</Form>
:null
}
<Row className="justify-content-md-center">
<Col md={{ span: 4, offset: 4 }}></Col>
</Row>
<br></br>
{
this.state.plants.length !== 0?
<Table striped bordered hover>
<thead>
<tr>
<th>Plant name</th>
<th>Plant type</th>
<th>Dirt type</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{
this.state.plants.map((plant, index) => {
return <tr key={index}>
<td>{plant[0]}</td>
<td>{plant[1]}</td>
<td>{plant[2]}</td>
<td><Button type={"button"} onClick={() => this.editItem(plant[0], plant[1], plant[2], index)}>Edit</Button></td>
<td><Button type={"button"} onClick={() => this.removeItem(plant)}>Delete</Button></td>
</tr>
})
}
</tbody>
</Table>
: null
}
</Container>
</>
);
}
// search for arrow function for further syntax knowledge
handleSubmit = (e) => {
e.preventDefault();
if (this.state.updateId >= 0) { // check if it is in edit mode
this.state.plants[this.state.updateId][0] = this.state.name;
this.state.plants[this.state.updateId][1] = this.state.type;
this.state.plants[this.state.updateId][2] = this.state.dirt; // updates the name of the plant
this.setState({
updateId:-1, // Todo check docu
name:'', // reset inputtext again
btnName:'submit' // reset button to submit again
})
} else {
let newDataElement = [
this.state.name,
this.state.type,
this.state.dirt
]
console.log("name " + newDataElement[0])
console.log("type " + newDataElement[1])
console.log("dirt " + newDataElement[2])
this.setState(prevState =>({
plants:[...prevState.plants, newDataElement], // adds a new plant array
name:'' //to reset the inputfield
}))
return this.state.plants;
}
}
removeItem = (value) => {
this.setState({
plants:this.state.plants.filter((plant) => { // filter function add docu
return plant !== value;
}),
name:''
})
}
editItem = (editedName, editedType, editedDirt, index) => {
this.setState({
name:editedName,
type:editedType,
dirt:editedDirt,
btnName:'update',
updateId:index // should be edited to a boolean
})
}
}
export default Settings;

1122
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "smart_garden_server",
"private": true,
"scripts": {
"start": "meteor run",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
"dependencies": {
"@babel/runtime": "^7.8.3",
"bootstrap": "^4.5.0",
"jquery": "^3.4.1",
"meteor-node-stubs": "^1.0.0",
"react": "^16.13.1",
"react-bootstrap": "^1.0.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"recharts": "^1.8.5"
},
"meteor": {
"mainModule": {
"client": "client/main.jsx",
"server": "server/main.js"
},
"testModule": "tests/main.js"
}
}

8
server/main.js Normal file
View File

@ -0,0 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { PlantTypesCollection } from '/imports/api/plantTypes';
Meteor.startup(() => {
process.env.MONGO_URL = "mongodb://garden:99009911@cloud.timovolkmann.de:27017/Smart_Garden";
PlantTypesCollection.insert({plantType: "BAUM"});
});

20
tests/main.js Normal file
View File

@ -0,0 +1,20 @@
import assert from "assert";
describe("smart_garden_server", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
assert.strictEqual(name, "smart_garden_server");
});
if (Meteor.isClient) {
it("client is not server", function () {
assert.strictEqual(Meteor.isServer, false);
});
}
if (Meteor.isServer) {
it("server is not client", function () {
assert.strictEqual(Meteor.isClient, false);
});
}
});