1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-03-05 07:55:23 +00:00

Merge pull request #3 from mgerb/react

React
This commit is contained in:
2016-09-18 13:21:42 -05:00
committed by GitHub
26 changed files with 462 additions and 134 deletions

View File

@@ -2,6 +2,7 @@
flex: 1; flex: 1;
flex-wrap: wrap; flex-wrap: wrap;
min-width: 0; min-width: 0;
min-height: 600px;
.post+ .post { .post+ .post {
margin-top: 2em; margin-top: 2em;
} }
@@ -14,6 +15,7 @@
width: 7em; width: 7em;
text-align: right; text-align: right;
margin-left: -8em; margin-left: -8em;
line-height: 2.5em;
} }
} }
.intro { .intro {

View File

@@ -7,4 +7,13 @@
border-color: #DADADA; border-color: #DADADA;
font-weight: 300; font-weight: 300;
margin-top: 1em; margin-top: 1em;
display: flex;
padding-right: calc(50% - 997px / 2);
padding-left: calc(50% - 997px / 2);
&:after,
&:before {
content: " ";
width: 1em;
}
} }

View File

@@ -0,0 +1,12 @@
.Header {
width: 100%;
background: url("../images/header.jpg");
background-size: cover;
height: 30em;
border-bottom: solid;
border-width: 1px;
border-color: #DADADA;
h1 {
text-align: center;
}
}

View File

@@ -25,7 +25,6 @@ h5,
h6 { h6 {
margin: 0; margin: 0;
font-weight: 400; font-weight: 400;
line-height: 1em;
} }
p { p {
@@ -55,7 +54,19 @@ hr {
border-top: 1px solid #eee; border-top: 1px solid #eee;
} }
.Footer, select{
background-color: #fff;
border: 1px solid #ccc;
border-radius: 0.2em;
height: 35px;
color: #555;
&:focus{
border-color: #66afe9;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
}
}
.Main { .Main {
display: flex; display: flex;
padding-right: calc(50% - 997px / 2); padding-right: calc(50% - 997px / 2);
@@ -71,23 +82,11 @@ hr {
padding-top: 1em; padding-top: 1em;
} }
.Header {
width: 100%;
background: url("../images/header.jpg");
background-size: cover;
height: 30em;
border-bottom: solid;
border-width: 1px;
border-color: #DADADA;
h1 {
text-align: center;
}
}
.Loading { .Loading {
display: flex; display: flex;
flex: 1; flex: 1;
justify-content: center; justify-content: center;
align-items: center;
} }
.btn { .btn {

View File

@@ -38,8 +38,8 @@ ReactDOM.render((
<Router history={history}> <Router history={history}>
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRoute component={Preview}/> <IndexRoute component={Preview}/>
<Route path="post/:category/:post" component={Post}/> <Route path="post(/:category)/:post" component={Post}/>
<Route path="sensor/:location/:year/:month" component={SensorInfo}/> <Route path="sensor/:location" component={SensorInfo}/>
</Route> </Route>
</Router> </Router>
</Provider> </Provider>

View File

@@ -7,7 +7,7 @@ export default class Footer extends React.Component {
render() { render() {
return ( return (
<div class="Footer"> <div class="Footer">
Site created and maintained by Mitchell Gerber Site created by Mitchell Gerber
</div> </div>
); );
} }

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import {bubble} from '../../assets/js/bubble'; import {bubble} from '../../assets/js/bubble';
import '../../assets/scss/Header.scss';
export default class Header extends React.Component { export default class Header extends React.Component {
componentDidMount() { componentDidMount() {
bubble(); bubble();

View File

@@ -21,7 +21,7 @@ export default class Post extends React.Component {
componentDidMount() { componentDidMount() {
const params = this.props.params; const params = this.props.params;
this.props.appActions.fetchPost(params.category, params.post); this.props.appActions.fetchPost(params.post, params.category);
} }
render() { render() {

View File

@@ -1,14 +1,108 @@
import React from 'react'; import React from 'react';
import {Link} from 'react-router';
export default class SensorInfo extends React.Component{ import Loading from '../utils/Loading';
import _chartjs from 'chart.js';
componentDidMount(){ import Chart from 'react-chartjs';
this.props.sensorActions.fetchSensorInfoYear('Grand Meadow', '2016'); import {
this.props.sensorActions.fetchSensorInfoMonth('Grand Meadow', '2016', 'May'); ChartOptions,
DataTemplate
}
from './chartOptions';
import './SensorInfo.scss';
const LineChart = Chart.Line;
export default class SensorInfo extends React.Component {
componentWillMount() {
let location = this.props.params.location;
this.props.sensorActions.fetchUniqueDates(location);
} }
render(){
return( componentWillUpdate(nextProps) {
<div class="Content">Test123</div> let currentLocation = this.props.params.location,
); nextLocation = nextProps.params.location;
if (currentLocation !== nextLocation){
this.props.sensorActions.fetchUniqueDates(nextLocation);
}
}
loadYearOptions = (date, index) => {
return (
<option key={index} value={index}>{date.year}</option>
);
}
loadMonthOptions = (date, index) => {
return (
<option key={index} value={index}>{date.monthname}</option>
);
}
onChange(event, type) {
let location = this.props.params.location,
sensor = this.props.sensor,
actions = this.props.sensorActions,
yearIndex = sensor.selectedYearIndex,
monthIndex = sensor.selectedMonthIndex;
if (type === 'year') {
yearIndex = parseInt(event.target.value);
monthIndex = 0;
}
else if (type === 'month') {
monthIndex = parseInt(event.target.value);
}
let year = sensor.uniqueDates[yearIndex].year;
let monthname = sensor.uniqueDates[yearIndex].months[monthIndex].monthname;
actions.setSelectedMonthIndex(monthIndex);
actions.setSelectedYearIndex(yearIndex);
this.props.sensorActions.fetchSensorInfoMonth(location, year, monthname);
}
filterData(data) {
let temp = JSON.parse(JSON.stringify(DataTemplate));
for (let d of data) {
let label = `${d.month}/${d.day}`;
temp.labels.push(label);
temp.datasets[0].data.push(d.maxtemp);
temp.datasets[1].data.push(d.mintemp);
}
return temp;
}
render() {
let sensor = this.props.sensor;
let data = this.filterData(sensor.info);
return (
<div class="SensorInfo">
{sensor.fetchedUniqueDates ?
<div class="selector-row">
<h2>{this.props.params.location}</h2>
<select onChange={(e) => {this.onChange(e, 'year')}}>
{sensor.uniqueDates.map(this.loadYearOptions)}
</select>
<select onChange={(e) => {this.onChange(e, 'month')}} value={sensor.selectedMonthIndex}>
{sensor.uniqueDates[sensor.selectedYearIndex].months.map(this.loadMonthOptions)}
</select>
</div>
: <Loading/>}
{sensor.fetchedUniqueDates && sensor.fetchedInfo
? <LineChart data={data} options={ChartOptions} redraw/>
: null}
{sensor.fetchedUniqueDates && sensor.fetchedInfo
? <div class="home"><Link to="/" class="link"><i class="fa fa-caret-left" aria-hidden="true"></i> Home</Link></div>
: null}
</div>
);
} }
} }

View File

@@ -0,0 +1,20 @@
.SensorInfo{
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
min-width: 0;
min-height: 600px;
width: 80%;
.selector-row{
display: flex;
justify-content: center;
margin-bottom: 2em;
select{
margin-left: 5px;
}
}
.home{
margin-top: 1em;
}
}

View File

@@ -20,8 +20,8 @@ export default class SensorList extends React.Component {
this.openLink = this.openLink.bind(this); this.openLink = this.openLink.bind(this);
} }
openLink(){ openLink(location){
browserHistory.push("/"); browserHistory.push(`/sensor/${location}`);
this.props.toggleOff(); this.props.toggleOff();
} }
@@ -29,13 +29,18 @@ export default class SensorList extends React.Component {
const date = new Date(sensor.updated); const date = new Date(sensor.updated);
return ( return (
<div key={index} class="row" onClick={this.openLink}> <div key={index} class="row" onClick={() => {this.openLink(sensor.location)}}>
<div class="item"> <div class="temperature">
<h1>{sensor.temperature}°f</h1> <h1>{sensor.temperature}°f</h1>
</div> </div>
<div class="item"> <div class="info">
<h3>{sensor.location}</h3> <h3>{sensor.location}</h3>
<span class="date">Updated: {date.toLocaleString('en-us', options)}</span> <span class="date">Updated: {date.toLocaleString('en-us', options)}
{Date.now() - date < 420000
? <span class="connected"> Connected</span>
: <span class="disconnected"> Disconnected</span>
}
</span>
</div> </div>
</div> </div>
); );

View File

@@ -9,11 +9,16 @@
padding: 1em; padding: 1em;
&:hover{ &:hover{
background-color: gray; background-color: #D1D1D1;
} }
.item + .item{ .temperature{
flex: .5;
}
.info{
margin-left: 1em; margin-left: 1em;
flex: 1;
} }
h2{ h2{
@@ -25,4 +30,11 @@
} }
} }
.connected{
color: green;
}
.disconnected{
color: red;
}
} }

View File

@@ -0,0 +1,30 @@
export const ChartOptions = {
responsive: true,
// scaleOverride: true,
//scaleSteps: 20,
// scaleStartValue: 0,
//scaleStepWidth: 5
};
export let DataTemplate = {
labels: [],
datasets: [{
label: "Max Temperature °F",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: []
}, {
label: "Min Temperature °F",
fillColor: "rgba(151,187,205,0)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: []
}]
};

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import {Link} from 'react-router';
import me from '../../../assets/images/me.jpg'; import me from '../../../assets/images/me.jpg';
@@ -12,7 +13,10 @@ export default class AboutMe extends React.Component{
<p> <p>
My name is Mitchell and I have a passion for software development. I am currently a software engineer and enjoy working on personal projects in my free time. My name is Mitchell and I have a passion for software development. I am currently a software engineer and enjoy working on personal projects in my free time.
</p> </p>
<p>
<i class="fa fa-home" aria-hidden="true"></i>
<Link to={"/"} class="link"> Home</Link>
</p>
<p> <p>
<i class="fa fa-envelope" aria-hidden="true"></i> <i class="fa fa-envelope" aria-hidden="true"></i>
<a class="link" href="mailto:mgerb42@gmail.com"> eMail</a> <a class="link" href="mailto:mgerb42@gmail.com"> eMail</a>
@@ -25,10 +29,12 @@ export default class AboutMe extends React.Component{
<i class="fa fa-github" aria-hidden="true"></i> <i class="fa fa-github" aria-hidden="true"></i>
<a class="link" href="https://github.com/mgerb" target="_blank"> GitHub</a> <a class="link" href="https://github.com/mgerb" target="_blank"> GitHub</a>
</p> </p>
{/*
<p> <p>
<i class="fa fa-wpforms" aria-hidden="true"> </i> <i class="fa fa-wpforms" aria-hidden="true"> </i>
<a href="/resume" class="link"> Resume</a> <a href="/resume" class="link"> Resume</a>
</p> </p>
*/}
</div> </div>
); );
} }

View File

@@ -44,10 +44,14 @@ export function fetchPreview() {
} }
} }
export function fetchPost(category, post) { //adjust url according to parameters
//mainly used to load md files in the parent directory within /posts
export function fetchPost(post, category = null) {
let url;
return (dispatch) => { return (dispatch) => {
dispatch(fetching()); dispatch(fetching());
return fetch(`/public/posts/${category}/${post}.md`) url = category !== null || typeof category === 'undefined' ? `/public/posts/${category}/${post}.md` : `/public/posts/${post}.md`;
return fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(response => { .then(response => {
dispatch(loadPost(response)); dispatch(loadPost(response));

View File

@@ -8,17 +8,31 @@ function loadSensorList(sensor_list){
} }
} }
function loadSensorInfoYear(sensor_info){ function loadSensorInfo(sensor_info){
return{ return{
type: types.LOAD_SENSOR_INFO_YEAR, type: types.LOAD_SENSOR_INFO,
sensor_info sensor_info
} }
} }
function loadSensorInfoMonth(sensor_info){ function loadUniqueDates(dates){
return{ return{
type: types.LOAD_SENSOR_INFO_MONTH, type: types.LOAD_UNIQUE_DATES,
sensor_info dates
}
}
export function setSelectedYearIndex(index){
return{
type: types.SET_SELECTED_YEAR_INDEX,
index
}
}
export function setSelectedMonthIndex(index){
return{
type: types.SET_SELECTED_MONTH_INDEX,
index
} }
} }
@@ -28,16 +42,16 @@ function fetchingList(){
} }
} }
function fetchingInfoYear(){ function fetchingInfo(){
return { return {
type: types.FETCHING_INFO_YEAR type: types.FETCHING_INFO
} }
} }
function fetchingInfoMonth(){ function fetchingUniqueDates(){
return { return {
type: types.FETCHING_INFO_MONTH type: types.FETCHING_UNIQUE_DATES
} }
} }
export function fetchSensorList(){ export function fetchSensorList(){
@@ -56,11 +70,11 @@ export function fetchSensorList(){
export function fetchSensorInfoYear(location, year){ export function fetchSensorInfoYear(location, year){
return (dispatch) => { return (dispatch) => {
dispatch(fetchingInfoYear()); dispatch(fetchingInfo());
return fetch(`/api/sensor/${location}/${year}`) return fetch(`/api/sensor/${location}/${year}`)
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
dispatch(loadSensorInfoYear(json)); dispatch(loadSensorInfo(json));
}) })
.catch(error => { .catch(error => {
console.log(error); console.log(error);
@@ -70,14 +84,38 @@ export function fetchSensorInfoYear(location, year){
export function fetchSensorInfoMonth(location, year, month){ export function fetchSensorInfoMonth(location, year, month){
return (dispatch) => { return (dispatch) => {
dispatch(fetchingInfoMonth()); dispatch(fetchingInfo());
return fetch(`/api/sensor/${location}/${year}/${month}`) return fetch(`/api/sensor/${location}/${year}/${month}`)
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
dispatch(loadSensorInfoMonth(json)); dispatch(loadSensorInfo(json));
}) })
.catch(error => { .catch(error => {
console.log(error); console.log(error);
}); });
} }
} }
//this is called to initialize the sensor info page
//reloads unique dates and resets indexes
//then fetches new data for chart
export function fetchUniqueDates(location){
return (dispatch) => {
dispatch(fetchingUniqueDates());
dispatch(setSelectedMonthIndex(0));
dispatch(setSelectedYearIndex(0));
return fetch(`/api/uniquedates/${location}`)
.then(response => response.json())
.then(json => {
dispatch(loadUniqueDates(json));
if(json.length > 0){
let year = json[0].year;
let month = json[0].months[0].monthname;
dispatch(fetchSensorInfoMonth(location, year, month));
}
})
.catch(error => {
console.log(error);
});
}
}

View File

@@ -1,10 +1,13 @@
//constants //constants
export const LOAD_SENSOR_LIST = 'LOAD_SENSOR_LIST'; export const LOAD_SENSOR_LIST = 'LOAD_SENSOR_LIST';
export const LOAD_SENSOR_INFO_YEAR = 'LOAD_SENSOR_INFO_YEAR'; export const LOAD_SENSOR_INFO = 'LOAD_SENSOR_INFO';
export const LOAD_SENSOR_INFO_MONTH = 'LOAD_SENSOR_INFO_MONTH'; export const LOAD_UNIQUE_DATES = 'LOAD_UNIQUE_DATES';
//fetching //fetching
export const FETCHING_LIST = 'FETCHING_LIST'; export const FETCHING_LIST = 'FETCHING_LIST';
export const FETCHING_INFO_YEAR = 'FETCHING_INFO_YEAR'; export const FETCHING_INFO = 'FETCHING_INFO_YEAR';
export const FETCHING_INFO_MONTH = 'FETCHING_INFO_MONTH'; export const FETCHING_UNIQUE_DATES = 'FETCHING_UNIQUE_DATES';
//indexes
export const SET_SELECTED_YEAR_INDEX = 'SET_SELECTED_YEAR_INDEX';
export const SET_SELECTED_MONTH_INDEX = 'SET_SELECTED_MONTH_INDEX';

View File

@@ -4,54 +4,69 @@ import * as types from '../constants/sensor';
//defaults - //defaults -
const defaultState = { const defaultState = {
list : [], list : [],
infoMonth: [], info: [],
infoYear: [], uniqueDates: {},
selectedYearIndex: 0,
selectedMonthIndex: 0,
fetchingList: false, fetchingList: false,
fetchingInfoMonth: false, fetchingInfo: false,
fetchingInfoYear: false, fetchingUniqueDates: false,
fetchedList: false, fetchedList: false,
fetchedInfoMonth: false, fetchedInfo: false,
fetchedInfoYear: false fetchedUniqueDates: false
}; };
//default reducer //default reducer
export default function app(state = defaultState, action) { export default function app(state = defaultState, action) {
switch(action.type){ switch(action.type){
//fetching functions - we use a fetching state to display loading images
case types.FETCHING_LIST: case types.FETCHING_LIST:
return Object.assign({}, state, { return Object.assign({}, state, {
fetchingList: true, fetchingList: true,
fetchedList: false fetchedList: false
}); });
case types.FETCHING_INFO_MONTH: case types.FETCHING_INFO:
return Object.assign({}, state, { return Object.assign({}, state, {
fetchingInfoMonth: true, fetchingInfo: true,
fetchedInfoMonth: false fetchedInfo: false
}); });
case types.FETCHING_INFO_YEAR: case types.FETCHING_UNIQUE_DATES:
return Object.assign({}, state, { return Object.assign({}, state, {
fetchingInfoYear: true, fetchingUniqueDates: true,
fetchedInfoYear: false fetchedUniqueDates: false
}); });
//other functions
case types.LOAD_SENSOR_LIST: case types.LOAD_SENSOR_LIST:
return Object.assign({}, state, { return Object.assign({}, state, {
list: action.sensor_list, list: action.sensor_list,
fetchingList: false, fetchingList: false,
fetchedList: true fetchedList: true
}); });
case types.LOAD_SENSOR_INFO_MONTH: case types.LOAD_SENSOR_INFO:
return Object.assign({}, state, { return Object.assign({}, state, {
infoMonth: action.sensor_info, info: action.sensor_info,
fetchingInfoMonth: false, fetchingInfo: false,
fetchedInfoMonth: true fetchedInfo: true
}); });
case types.LOAD_SENSOR_INFO_YEAR: case types.LOAD_UNIQUE_DATES:
return Object.assign({}, state, { return Object.assign({}, state, {
infoYear: action.sensor_info, uniqueDates: action.dates,
fetchingInfoYear: false, fetchingUniqueDates: false,
fetchedInfoYear: true fetchedUniqueDates: true
});
//indexes
case types.SET_SELECTED_YEAR_INDEX:
return Object.assign({}, state, {
selectedYearIndex: action.index
});
case types.SET_SELECTED_MONTH_INDEX:
return Object.assign({}, state, {
selectedMonthIndex: action.index
}); });
} }
//return present state if no actions get called //return present state if no actions get called

View File

@@ -6,7 +6,10 @@ import {syncHistoryWithStore} from 'react-router-redux';
import reducers from './reducers/reducers'; import reducers from './reducers/reducers';
const middleware = applyMiddleware(thunk, logger()); const debug = process.env.NODE_ENV !== "production";
//run redux logger if we are in dev mode
const middleware = debug ? applyMiddleware(thunk, logger()) : applyMiddleware(thunk);
//create the new store with default state as an empty object //create the new store with default state as an empty object
const store = createStore(reducers, {}, middleware); const store = createStore(reducers, {}, middleware);

View File

@@ -19,13 +19,15 @@ marked.setOptions({
} }
}); });
const dir = './posts/'; const rootDirectory = './posts/';
const json = { const json = {
posts: [] posts: []
}; };
//do everything synchronously to keep posts ordered //do everything synchronously to keep posts ordered
function parse_dir(dir, folder_name){ //we are not worried about execution time since this script only runs once when building
//ignores files that are not in a directory
function parse_dir(dir, folder_name = null){
const posts = fs.readdirSync(dir); const posts = fs.readdirSync(dir);
for(let post of posts){ for(let post of posts){
@@ -33,7 +35,7 @@ function parse_dir(dir, folder_name){
if(stats.isDirectory()){ if(stats.isDirectory()){
parse_dir(dir + post + '/', post); parse_dir(dir + post + '/', post);
} else { } else if(folder_name !== null){
const file = fs.readFileSync(dir+post, 'utf8'); const file = fs.readFileSync(dir+post, 'utf8');
const tokens = marked.lexer(file, null); const tokens = marked.lexer(file, null);
const temp = { const temp = {
@@ -49,7 +51,8 @@ function parse_dir(dir, folder_name){
} }
//recursively parse posts directory for all markdown files //recursively parse posts directory for all markdown files
parse_dir(dir, 'posts'); //folder defaults to null and immediate child files are not added to json
parse_dir(rootDirectory);
//sort posts by date //sort posts by date
json.posts.sort((a, b) => { json.posts.sort((a, b) => {

View File

@@ -7,11 +7,12 @@
"build": "webpack && babel-node metadata.js", "build": "webpack && babel-node metadata.js",
"c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback", "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback",
"check_gzip_size": "gzip -9 -c ./public/client.min.js | wc -c | numfmt --to=iec-i --suffix=B --padding=10", "check_gzip_size": "gzip -9 -c ./public/client.min.js | wc -c | numfmt --to=iec-i --suffix=B --padding=10",
"deploy" : "npm run get_dependencies && npm run prod && ./mywebsite", "deploy": "npm run get_dependencies && npm run prod && ./mywebsite",
"dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback", "dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback",
"get_dependencies": "go get ./server && npm install", "get_dependencies": "go get ./server && npm install",
"prod": "export NODE_ENV=production && webpack -p && babel-node metadata.js && go build ./server/mywebsite.go", "prod": "export NODE_ENV=production && webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors && babel-node metadata.js && go build ./server/mywebsite.go",
"prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js && go build ./server/mywebsite.go" "prod-win": "set NODE_ENV=production && webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors && babel-node metadata.js && go build ./server/mywebsite.go",
"watch": "webpack --watch --colors --progress"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,6 +25,7 @@
}, },
"homepage": "https://github.com/mgerb/mywebsite#readme", "homepage": "https://github.com/mgerb/mywebsite#readme",
"dependencies": { "dependencies": {
"autoprefixer": "^6.4.1",
"babel": "^6.5.2", "babel": "^6.5.2",
"babel-cli": "^6.11.4", "babel-cli": "^6.11.4",
"babel-core": "^6.13.2", "babel-core": "^6.13.2",
@@ -35,6 +37,7 @@
"babel-preset-es2015": "^6.13.2", "babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1", "babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0", "babel-preset-stage-0": "^6.5.0",
"chart.js": "^1.1.1",
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",
"file-loader": "^0.9.0", "file-loader": "^0.9.0",
@@ -44,7 +47,10 @@
"marked": "^0.3.6", "marked": "^0.3.6",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"node-sass": "^3.8.0", "node-sass": "^3.8.0",
"postcss-loader": "^0.13.0",
"react": "^15.3.0", "react": "^15.3.0",
"react-addons-transition-group": "^15.3.1",
"react-chartjs": "^0.8.0",
"react-dom": "^15.3.0", "react-dom": "^15.3.0",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-router": "^2.6.1", "react-router": "^2.6.1",

View File

@@ -61,6 +61,7 @@ func HandleSensorRequest(w http.ResponseWriter, r *http.Request, ps httprouter.P
storedData.Month = int(t.Month()) storedData.Month = int(t.Month())
storedData.MonthName = t.Month().String() storedData.MonthName = t.Month().String()
storedData.Year = t.Year() storedData.Year = t.Year()
storedData.Updated = t
err := storedData.StoreData() err := storedData.StoreData()
@@ -134,21 +135,7 @@ func HandleSensorByLocation(w http.ResponseWriter, r *http.Request, ps httproute
s, err := daily_sensor.GetAllSensorInfo(location) s, err := daily_sensor.GetAllSensorInfo(location)
var response string response := createResponse(s, err)
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
js, err := json.MarshalIndent(s, "", " ")
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
response = string(js)
}
}
fmt.Fprint(w, response) fmt.Fprint(w, response)
} }
@@ -162,21 +149,7 @@ func HandleSensorByLocationYear(w http.ResponseWriter, r *http.Request, ps httpr
s, err := daily_sensor.GetAllSensorInfoByYear(location, year) s, err := daily_sensor.GetAllSensorInfoByYear(location, year)
var response string response := createResponse(s, err)
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
js, err := json.MarshalIndent(s, "", " ")
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
response = string(js)
}
}
fmt.Fprint(w, response) fmt.Fprint(w, response)
} }
@@ -191,6 +164,19 @@ func HandleSensorByLocationMonth(w http.ResponseWriter, r *http.Request, ps http
s, err := daily_sensor.GetAllSensorInfoByMonth(location, year, monthname) s, err := daily_sensor.GetAllSensorInfoByMonth(location, year, monthname)
response := createResponse(s, err)
fmt.Fprint(w, response)
}
func HandleUniqueDates(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
location := ps.ByName("location")
w.Header().Set("Content-Type", "application/json")
s, err := daily_sensor.GetUniqueSensorDates(location)
var response string var response string
if err != nil { if err != nil {
@@ -209,3 +195,23 @@ func HandleSensorByLocationMonth(w http.ResponseWriter, r *http.Request, ps http
fmt.Fprint(w, response) fmt.Fprint(w, response)
} }
func createResponse(s []daily_sensor.Data, err error) string{
var response string
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
js, err := json.MarshalIndent(s, "", " ")
if err != nil {
log.Println(err)
response = "{message : \"Error loading data from database\""
} else {
response = string(js)
}
}
return response
}

View File

@@ -16,22 +16,24 @@ const (
type Data struct { type Data struct {
ID bson.ObjectId `bson:"_id,omitempty"` ID bson.ObjectId `bson:"_id,omitempty"`
MaxTemp float64 `json:"maxtemp" bson:"maxtemp"` MaxTemp float64 `json:"maxtemp,omitempty" bson:"maxtemp"`
MinTemp float64 `json:"mintemp" bson:"mintemp"` MinTemp float64 `json:"mintemp,omitempty" bson:"mintemp"`
Location string `json:"location" bson:"location"` Location string `json:"location,omitempty" bson:"location"`
Month int `json:"month" bson:"month"` Month int `json:"month,omitempty" bson:"month"`
MonthName string `json:"monthname" bson:"monthname"` MonthName string `json:"monthname,omitempty" bson:"monthname"`
Day int `json:"day" bson:"day"` Day int `json:"day,omitempty" bson:"day"`
Year int `json:"year" bson:"year"` Year int `json:"year,omitempty" bson:"year"`
Updated time.Time `json:"updated,omitempty" bson:"updated"`
} }
//convert struct to json string //convert struct to json string
func (s *Data) toJson() string { func (s *Data) ToJson() string {
b, err := json.MarshalIndent(s, "", " ") b, err := json.MarshalIndent(s, "", " ")
if err != nil { if err != nil {
log.Println(err.Error) log.Println(err)
return "{message : \"Error loading data from database\""
} }
return string(b) return string(b)
@@ -73,7 +75,7 @@ func (s *Data) UpdateData() error {
c := session.DB(db.Mongo.Info.Database).C(collection) c := session.DB(db.Mongo.Info.Database).C(collection)
colQuerier := bson.M{"location": s.Location, "month": s.Month, "monthname": s.MonthName, "day": s.Day, "year": s.Year} colQuerier := bson.M{"location": s.Location, "month": s.Month, "monthname": s.MonthName, "day": s.Day, "year": s.Year}
change := bson.M{"$set": bson.M{"maxtemp": s.MaxTemp, "mintemp": s.MinTemp}} change := bson.M{"$set": bson.M{"maxtemp": s.MaxTemp, "mintemp": s.MinTemp, "updated": time.Now()}}
err := c.Update(colQuerier, change) err := c.Update(colQuerier, change)
@@ -130,7 +132,6 @@ func GetAllSensorInfo(sensor_location string) ([]Data, error) {
c := session.DB(db.Mongo.Info.Database).C(collection) c := session.DB(db.Mongo.Info.Database).C(collection)
//err := c.Find(bson.M{"location": sensor_location}).Sort("-year, -month").All(&d)
err := c.Pipe([]bson.M{{"$match": bson.M{"location": sensor_location}}, err := c.Pipe([]bson.M{{"$match": bson.M{"location": sensor_location}},
{"$sort": bson.M{"year": -1, "month": 1}}}).All(&d) {"$sort": bson.M{"year": -1, "month": 1}}}).All(&d)
@@ -198,3 +199,55 @@ func GetAllSensorInfoByMonth(sensor_location string, year int, monthname string)
return d, errors.New("Query failed") return d, errors.New("Query failed")
} }
} }
//I need to find a better implementation of this
//MongoDB $addToSet creates an array of objecta
//this ends up being a different type than the Data struct above
//it would be nice to make all MongoDB queries load directly into a Data struct
/*
type UniqueDates struct{
Dates []Data `json:"dates" bson:"dates"`
}
*/
type Years struct {
Year int `json:"year", bson:"year"`
Months []Month `json:"months", bson:"months"`
}
type Month struct {
Month int `json:"month", bson:"month"`
MonthName string `json:"monthname", bson:"monthname"`
}
func GetUniqueSensorDates(sensor_location string) ([]Years, error){
d := []Years{}
if db.Mongo.Connected() == true {
session := db.Mongo.Session.Copy()
defer session.Close()
c := session.DB(db.Mongo.Info.Database).C(collection)
err := c.Pipe([]bson.M{bson.M{"$match": bson.M{"location": sensor_location}},
bson.M{"$group": bson.M{"_id": "$year", "months": bson.M{"$addToSet": bson.M{"month": "$month", "monthname": "$monthname"}}}},
bson.M{"$project": bson.M{"year": "$_id", "months": "$months"}},
bson.M{"$unwind": "$months"},
bson.M{"$sort": bson.M{"months.month": -1}},
bson.M{"$group": bson.M{"_id": "$year", "months": bson.M{"$push": "$months"}}},
bson.M{"$project": bson.M{"year": "$_id", "months": "$months"}},
}).All(&d)
if err != nil {
log.Println(err)
return d, nil
}
return d, nil
} else {
return d, errors.New("Query failed")
}
}

View File

@@ -59,9 +59,10 @@ func (s *Data) StoreData() error {
//handle queries for all sensors page //handle queries for all sensors page
type DataStore_AllSensors struct { type DataStore_AllSensors struct {
ID string `json:"location" bson:"_id"` ID bson.ObjectId `bson:"_id,omitempty"`
Temperature float64 `json:"temperature" bson:"temperature"` Location string `json:"location", bson:"location"`
Updated time.Time `json:"updated" bson:"updated"` Temperature float64 `json:"temperature" bson:"temperature"`
Updated time.Time `json:"updated" bson:"updated"`
} }
//get latest update from each unique sensor //get latest update from each unique sensor
@@ -78,7 +79,9 @@ func GetAllSensors() ([]DataStore_AllSensors, error) {
err := c.Pipe([]bson.M{{"$group": bson.M{"_id": "$location", "temperature": bson.M{"$last": "$temperature"}, err := c.Pipe([]bson.M{{"$group": bson.M{"_id": "$location", "temperature": bson.M{"$last": "$temperature"},
"updated": bson.M{"$last": "$updated"}}}, "updated": bson.M{"$last": "$updated"}}},
bson.M{"$sort": bson.M{"_id": 1}}}).All(&s) bson.M{"$sort": bson.M{"_id": 1}},
bson.M{"$project": bson.M{"location": "$_id", "temperature": "$temperature", "updated": "$updated"}},
}).All(&s)
if err != nil { if err != nil {
return s, nil return s, nil

View File

@@ -20,7 +20,8 @@ func Routes() *httprouter.Router {
r.GET("/api/sensor/:location", api.HandleSensorByLocation) r.GET("/api/sensor/:location", api.HandleSensorByLocation)
r.GET("/api/sensor/:location/:year", api.HandleSensorByLocationYear) r.GET("/api/sensor/:location/:year", api.HandleSensorByLocationYear)
r.GET("/api/sensor/:location/:year/:monthname", api.HandleSensorByLocationMonth) r.GET("/api/sensor/:location/:year/:monthname", api.HandleSensorByLocationMonth)
r.GET("/api/uniquedates/:location", api.HandleUniqueDates)
r.GET("/discord", controller.DiscordRedirect) r.GET("/discord", controller.DiscordRedirect)
r.GET("/vpn", controller.VPNRedirect) r.GET("/vpn", controller.VPNRedirect)
r.GET("/camera", controller.CameraRedirect) r.GET("/camera", controller.CameraRedirect)

View File

@@ -2,6 +2,7 @@ var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack'); var webpack = require('webpack');
var path = require('path'); var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin'); var HtmlWebpackPlugin = require('html-webpack-plugin');
var autoprefixer = require('autoprefixer');
module.exports = { module.exports = {
devtool: debug ? "inline-sourcemap" : null, devtool: debug ? "inline-sourcemap" : null,
@@ -17,7 +18,7 @@ module.exports = {
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'], plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
} }
}, },
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader"}, { test: /\.scss$/, loader: "style-loader!css-loader!postcss-loader!sass-loader"},
{ test: /\.css$/, loader: "style-loader!css-loader" }, { test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.png$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, { test: /\.png$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" },
{ test: /\.jpg$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, { test: /\.jpg$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" },
@@ -32,6 +33,7 @@ module.exports = {
} }
] ]
}, },
postcss: function(){ return [autoprefixer]},
output: { output: {
path: __dirname + "/public/", path: __dirname + "/public/",
publicPath: "/public/", publicPath: "/public/",