Start the measurements 🔆♨️📈

🍉
master
Daniel da Silva 8 years ago
commit 3cf0e60008

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e # halt script on error
echo "Get ready, we're pushing to gh-pages!"
cd dist
git init
git config user.name "Travis-CI"
git config user.email "travis-ci@danielfdsilva.com"
git add .
git commit -m "CI deploy to gh-pages"
git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e # halt script on error
# If deploying, don't balk on lint errors
if [ $TRAVIS_PULL_REQUEST = "false" ] && [ $TRAVIS_BRANCH = ${DEPLOY_BRANCH} ]; then
npm run lint || true
else
npm run lint
fi

@ -0,0 +1,29 @@
{
"extends": ["semistandard"],
"env": {
"es6": true,
"browser": true
},
"plugins": [
"react"
],
"ecmaFeatures": {
"jsx": true
},
rules: {
'react/display-name': 1 ,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-undef': 2,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-danger': 0,
'react/no-deprecated': 2,
'react/no-did-mount-set-state': [2, 'allow-in-func'],
'react/no-did-update-set-state': [2, 'allow-in-func'],
'react/no-direct-mutation-state': 2,
'react/no-is-mounted': 2,
'react/no-unknown-property': 2,
'react/prop-types': 2,
'react/react-in-jsx-scope': 2
}
}

1
.gitattributes vendored

@ -0,0 +1 @@
* text=auto

118
.gitignore vendored

@ -0,0 +1,118 @@
################################################
############### .gitignore ##################
################################################
#
# This file is only relevant if you are using git.
#
# Files which match the splat patterns below will
# be ignored by git. This keeps random crap and
# and sensitive credentials from being uploaded to
# your repository. It allows you to configure your
# app for your machine without accidentally
# committing settings which will smash the local
# settings of other developers on your team.
#
# Some reasonable defaults are included below,
# but, of course, you should modify/extend/prune
# to fit your needs!
################################################
################################################
# Local Configuration
#
# Explicitly ignore files which contain:
#
# 1. Sensitive information you'd rather not push to
# your git repository.
# e.g., your personal API keys or passwords.
#
# 2. Environment-specific configuration
# Basically, anything that would be annoying
# to have to change every time you do a
# `git pull`
# e.g., your local development database, or
# the S3 bucket you're using for file uploads
# development.
#
################################################
app/assets/scripts/config/local.js
################################################
# Dependencies
#
# When releasing a production app, you may
# consider including your node_modules and
# bower_components directory in your git repo,
# but during development, its best to exclude it,
# since different developers may be working on
# different kernels, where dependencies would
# need to be recompiled anyway.
#
# More on that here about node_modules dir:
# http://www.futurealoof.com/posts/nodemodules-in-git.html
# (credit Mikeal Rogers, @mikeal)
#
# About bower_components dir, you can see this:
# http://addyosmani.com/blog/checking-in-front-end-dependencies/
# (credit Addy Osmani, @addyosmani)
#
################################################
node_modules
bower_components
.sass-cache
test/bower_components
app/assets/styles/_collecticons.scss
################################################
# Node.js / NPM
#
# Common files generated by Node, NPM, and the
# related ecosystem.
################################################
lib-cov
*.seed
*.log
*.out
*.pid
npm-debug.log
################################################
# Apidocs
#
# Common files generated by apidocs
################################################
################################################
# Miscellaneous
#
# Common files generated by text editors,
# operating systems, file systems, etc.
################################################
*~
*#
.DS_STORE
.netbeans
nbproject
.idea
.resources
.node_history
temp
tmp
.tmp
dist
_README.md

@ -0,0 +1 @@
v4.2.2

@ -0,0 +1,39 @@
language: node_js
node_js:
- "4.2"
env:
global:
- CXX=g++-4.8
- GH_REF=github.com/developmentseed/sense.git
- DEPLOY_BRANCH=master
- secure: "sxDPVseehEH9G/JMuM1E7hsSnGeG4Ju3poWDbp/3Q/Y2Phx/U5IYOQ9e1kggafBi61JOsi8/cySnmiC7c0wP/EHoMnh9gcyAGPdTU4N1UVfZ7xM0eIqm4LQrHBHM0A9ghr3ZhevRIX07LqcNjC0kUR9RX/mlQUvTy/725SzKmQIE/nCWxJwLG5GYRytE/SyX8pzOhuCgIsuBeebpC1Dl91AZmofcym7Lawezvr0ZE1ZcP3wMiZHTCQ8mBIMzd7mSD8rdiZH/QggVPtis5aCSDomOdiKIxxpDM3Mj1J42xtSiEIhRlU9nk3smb9Auioql+BzV5bE8a4oW1D66c320Yb6SAnkSM90a+g0NlS1Z6blNjJ4N8L4QCmVHH4S+AyiXgqccrZiRl/2tsoKE+ZoBFvzIxmOzOQUqGVKX47hQq7QLHFKLCaJIx2XKhHPrfUhi752+5rYFG01teIkOP0usac34qM96dCxOodmC6xUY2YpBWboIwSr3nhQtLBs2fUw23PlgaDdgGpOyv3Pporwnp1Js99UZXtSBDWHcaFxjWnp3gEUdWwMwR69DMOzu6Udm9RMj3WtPm7G5fz9E5pPlVjiBFgsLaUXV3WVNwH/G1zrstl5vFAGyUzI1iJeHTGvncD/HT21uMHNkl6u5VA2+n97W8+J8pvxefQ+Plja7+gM="
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
cache:
apt: true
directories:
- node_modules
before_install:
- chmod +x ./.build_scripts/deploy.sh
- chmod +x ./.build_scripts/lint.sh
before_script:
- ./.build_scripts/lint.sh
script:
- npm run build
deploy:
provider: script
skip_cleanup: true
script: .build_scripts/deploy.sh
on:
branch: ${DEPLOY_BRANCH}

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

@ -0,0 +1,36 @@
# Devseed Sense
Simple dashboard that taps into the [opensensemap](http://opensensemap.org/) api to show the measurements for a specific [sensebox](www.sensebox.de/en/).
![devseed-sense-dashboard.png](devseed-sense-dashboard.png)
## Development environment
To set up the development environment for this website, you'll need to install the following on your system:
- Node (v4.2.x) & Npm ([nvm](https://github.com/creationix/nvm) usage is advised)
> The versions mentioned are the ones used during development. It could work with newer ones.
Run `nvm use` to activate the correct version.
After these basic requirements are met, run the following commands in the website's folder:
```
$ npm install
```
### Getting started
```
$ npm run serve
```
Compiles the sass files, javascript, and launches the server making the site available at `http://localhost:1337/`
The system will watch files and execute tasks whenever one of them changes.
The site will automatically refresh since it is bundled with livereload.
The current code will show the values for the [DS Lisbon sensebox](http://opensensemap.org/#/explore/570629b945fd40c8197462fb).
This can be changed by setting the correct ids in `config/production.js`
### Other commands
Compile the files to the `dist` folder ready for production.
```
$ npm run build
```

@ -0,0 +1 @@
sense.devseed.com

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path d="M288,128h-64v141.3l105.4,105.3l45.2-45.3L288,242.7V128z M256,0C114.6,0,0,114.6,0,256s114.6,256,256,256 s256-114.6,256-256S397.4,0,256,0z M256,448c-106,0-192-86-192-192S150,64,256,64s192,86,192,192S362,448,256,448z"/>
</svg>

After

Width:  |  Height:  |  Size: 703 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path fill="#D04003" d="M512 32c0-17.7-14.3-32-32-32h-448c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32v-448z"/><path fill="#fff" d="M289.5 434.7c-.1-10.3 2.6-19.2 7-28.5-9.2 8.1-12.8 17.3-16.2 29.8.9-14.6 8-110.4 8-115.2 24.5-70 13.6-279 44.8-305.1-36 6.5-48.2 43.3-59.7 72.5-11.4-34.3-2.4-56.1-40.2-68.1 30.2 25.6 24.2 47.6 30.4 83-18.1-23.5-12.5-52.8-33.3-76.8-5.9-6.1-25.5-21-25.5-21 0 .9 15.7 32.3 18.6 48.9 2.9 16.6 9.8 49.8 8.8 62 2.6 67.8 19.6 133.5 31.3 200 2 11.4 7.8 44.5 7.8 44.5l1.3 13.8c-2.1-9.1-4.1-17.3-5.2-20.8-3.4-10-11.8-35.4-13.7-45.8-28.5-58.6-80.6-173.5-150.2-190.4 39.5 35 82 81.2 91.5 125.8-21.2-29.8-104.6-93.4-138.5-116.6 63.9 66.3 115.2 135 170.8 208.7 16.2 21 36.3 63.3 37.7 72.9 1.1 7.1 2.4 33.6 3.2 49.9-31.1-75.1-48.1-113.2-105.5-146.8-9.8-5.2-40.2-15.7-40.2-15.7 13.8 15.2 30.7 16.1 43.3 37.3-26.1-17.1-53.2-20.1-83-21.1 95.7 49.5 147.5 63.3 179.7 158.4 2.6 15.6-1.4 27.8-1 41.8h21.2c-.1-10 .1-19.6 0-21.6 1.1-6.6 1.3-26.6 4.2-32.7 10-25.4 26.2-37.4 48.8-52.2-20.3 6.6-30.6 4-46.2 29.1zm94.5-224.2c-7.8 7.4-31.3 37.2-31.3 37.2 1.3-42 11.9-87 38.7-118.4-37.3 24.4-58.3 84.6-68 121.4-3.8 14.8-47.9 100.7-27.4 146.7 4-16.8 13.5-33.1 24-47.6 4.9-6.6 25.5-39.3 46-86 18.1-47 47.8-74.5 89.6-97.8-31.6 4.8-49.6 23-71.6 44.5zm7.3-81.2z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

@ -0,0 +1,44 @@
import fetch from 'isomorphic-fetch';
import * as actions from './action-types';
import config from '../config';
// //////////////////////////////////////////////////////////////////////////
// // Fetch Section Access Thunk
function requestSensorData (sensor) {
return {
type: actions[`REQUEST_SENSOR_DATA_${sensor.toUpperCase()}`]
};
}
function receiveSensorData (sensor, json) {
return {
type: actions[`RECEIVE_SENSOR_DATA_${sensor.toUpperCase()}`],
data: json,
receivedAt: Date.now()
};
}
export function fetchSensorData (sensor, toDate) {
return dispatch => {
dispatch(requestSensorData(sensor));
let sensorId = config.senseBox[`sensorId--${sensor}`];
return fetch(`${config.api}/boxes/${config.senseBox.id}/data/${sensorId}?from-date=${toDate}`)
.then(response => {
if (response.status >= 400) {
throw new Error('Bad response');
}
return response.json();
})
.then(json => {
dispatch(receiveSensorData(sensor, json));
// setTimeout(() => {
// dispatch(receiveSensorData(sensor, json));
// }, Math.ceil(Math.random() * 5000));
}, e => {
console.log('e', e);
return dispatch(receiveSensorData(null, null, 'Data not available'));
});
};
}

@ -0,0 +1,15 @@
'use strict';
export const REQUEST_SENSOR_DATA_TEMPERATURE = 'REQUEST_SENSOR_DATA_TEMPERATURE';
export const RECEIVE_SENSOR_DATA_TEMPERATURE = 'RECEIVE_SENSOR_DATA_TEMPERATURE';
export const REQUEST_SENSOR_DATA_PRESSURE = 'REQUEST_SENSOR_DATA_PRESSURE';
export const RECEIVE_SENSOR_DATA_PRESSURE = 'RECEIVE_SENSOR_DATA_PRESSURE';
export const REQUEST_SENSOR_DATA_LUMINOSITY = 'REQUEST_SENSOR_DATA_LUMINOSITY';
export const RECEIVE_SENSOR_DATA_LUMINOSITY = 'RECEIVE_SENSOR_DATA_LUMINOSITY';
export const REQUEST_SENSOR_DATA_UV = 'REQUEST_SENSOR_DATA_UV';
export const RECEIVE_SENSOR_DATA_UV = 'RECEIVE_SENSOR_DATA_UV';
export const REQUEST_SENSOR_DATA_HUMIDITY = 'REQUEST_SENSOR_DATA_HUMIDITY';
export const RECEIVE_SENSOR_DATA_HUMIDITY = 'RECEIVE_SENSOR_DATA_HUMIDITY';

@ -0,0 +1,444 @@
'use strict';
import React from 'react';
import d3 from 'd3';
import _ from 'lodash';
// import Popover from '../../utils/popover';
var LineChart = React.createClass({
displayName: 'LineChart',
propTypes: {
className: React.PropTypes.string,
data: React.PropTypes.array,
axisLineVal: React.PropTypes.number,
axisLineMax: React.PropTypes.number,
axisLineMin: React.PropTypes.number,
dataUnitSuffix: React.PropTypes.string
},
chart: null,
onWindowResize: function () {
this.chart.checkSize();
},
componentDidMount: function () {
// console.log('LineChart componentDidMount');
// Debounce event.
this.onWindowResize = _.debounce(this.onWindowResize, 200);
window.addEventListener('resize', this.onWindowResize);
this.chart = Chart();
d3.select(this.refs.container).call(this.chart
.data(this.props.data)
.axisLineVal(this.props.axisLineVal)
.axisValueMax(this.props.axisLineMax)
.axisValueMin(this.props.axisLineMin)
.dataUnitSuffix(this.props.dataUnitSuffix));
},
componentWillUnmount: function () {
// console.log('LineChart componentWillUnmount');
window.removeEventListener('resize', this.onWindowResize);
this.chart.destroy();
},
componentDidUpdate: function (prevProps/* prevState */) {
console.log('LineChart componentDidUpdate');
this.chart.pauseUpdate();
if (prevProps.data !== this.props.data) {
this.chart.data(this.props.data);
}
if (prevProps.axisLineVal !== this.props.axisLineVal) {
this.chart.axisLineVal(this.props.axisLineVal);
}
if (prevProps.axisLineMax !== this.props.axisLineMax) {
this.chart.axisValueMax(this.props.axisLineMax);
}
if (prevProps.axisLineMin !== this.props.axisLineMin) {
this.chart.axisValueMin(this.props.axisLineMin);
}
if (prevProps.dataUnitSuffix !== this.props.dataUnitSuffix) {
this.chart.dataUnitSuffix(this.props.dataUnitSuffix);
}
this.chart.continueUpdate();
},
render: function () {
return (
<div className={this.props.className} ref='container'></div>
);
}
});
module.exports = LineChart;
var Chart = function (options) {
// Data related variables for which we have getters and setters.
var _data = null;
var _axisLineVal, _axisValueMin, _axisValueMax, _dataUnitSuffix;
// Pause
var _pauseUpdate = false;
// Containters
var $el, $svg;
// Var declaration.
var margin = {top: 16, right: 32, bottom: 32, left: 24};
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// width and height refer to the data canvas. To know the svg size the margins
// must be added.
var _width, _height;
// Draw functions.
var line;
// Update functions.
var updateData, upateSize;
// X scale. Range updated in function.
var x = d3.time.scale();
// Y scale. Range updated in function.
var y = d3.scale.linear();
// Used for the zoom translate bounds.
var minX;
// Zoom
var zoom = d3.behavior
.zoom()
.scaleExtent([1, 1]);
// Line function for the delimit the area.
line = d3.svg.line()
.x(d => x(d.timestep))
.y(d => y(d.value));
// Define xAxis function.
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickSize(0)
.tickFormat(d3.time.format('%H:%M'));
// .ticks(3);
function _calcSize () {
_width = parseInt($el.style('width'), 10) - margin.left - margin.right;
_height = parseInt($el.style('height'), 10) - margin.top - margin.bottom;
}
function chartFn (selection) {
$el = selection;
var layers = {
line: function () {
// lines.
let the_line = $dataCanvas.selectAll('.data-line')
.data([_data]);
// Handle new.
the_line.enter()
.append('path')
.attr('clip-path', 'url(#clip)');
// Update current.
the_line
.attr('d', d => line(d))
.attr('class', d => `data-line`);
// Remove old.
the_line.exit()
.remove();
},
minMax: function () {
let [sDate, eDate] = x.domain();
let f = (o) => {
let timestamp = o.timestep.getTime();
return timestamp >= sDate.getTime() && timestamp <= eDate.getTime();
};
// Min Max.
let sorted = _(_data).filter(f).sortBy('value').value();
if (!sorted.length) {
return;
}
let min = sorted[0];
let max = _.last(sorted);
let edgeG = $dataCanvas.selectAll('.edges')
.data([0])
.enter().append('g')
.attr('class', 'edges');
edgeG.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '-0.25em')
.attr('class', 'edge edge-max');
edgeG.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '1em')
.attr('class', 'edge edge-min');
$dataCanvas.select('.edge.edge-max')
.datum(max)
.attr('x', d => x(d.timestep))
.attr('y', d => y(d.value))
.text(d => d.value + _dataUnitSuffix);
$dataCanvas.select('.edge.edge-min')
.datum(min)
.attr('x', d => x(d.timestep))
.attr('y', d => y(d.value))
.text(d => d.value + _dataUnitSuffix);
},
xAxis: function () {
// Append Axis.
// X axis.
let xAx = $svg.selectAll('.x.axis')
.data([0]);
xAx.enter().append('g')
.attr('class', 'x axis')
.append('text')
.attr('class', 'label')
.attr('text-anchor', 'start');
xAx
.attr('transform', `translate(${margin.left},${_height + margin.top + 16})`)
.call(xAxis);
},
yAxis: function () {
// Append Axis.
// Y axis
let yAx = $svg.selectAll('.y.axis')
.data([0]);
let yAxEnter = yAx.enter().append('g')
.attr('class', 'y axis');
yAxEnter.append('text')
.attr('class', 'label')
.attr('text-anchor', 'end');
yAxEnter.append('line')
.attr('class', 'line');
yAx.select('.label')
.attr('y', y(_axisLineVal) + margin.top)
.attr('x', _width + margin.left + margin.right)
.attr('dy', '1em')
.text(_axisLineVal + _dataUnitSuffix);
yAx.select('.line')
.attr('x1', 0)
.attr('y1', y(_axisLineVal) + margin.top)
.attr('x2', _width + margin.left + margin.right)
.attr('y2', y(_axisLineVal) + margin.top);
},
days: function () {
// Compute days for the days steps.
let dateCopyDay = d => {
let n = new Date(d.getTime());
n.setHours(0);
n.setMinutes(0);
n.setSeconds(0);
n.setMilliseconds(0);
return n;
};
let eDay = dateCopyDay(_.last(_data).timestep);
let dt = dateCopyDay(_data[0].timestep);
let daySteps = [dt];
while (true) {
dt = d3.time.day.offset(dateCopyDay(dt), 1);
daySteps.push(dt);
if (dt.getTime() >= eDay.getTime()) {
break;
}
}
let selection = $dataCanvas.selectAll('.days')
.data([0]);
selection.enter().append('g')
.attr('class', 'days');
let $days = selection.selectAll('.day-tick')
.data(daySteps);
$days.enter()
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'day-tick');
$days
.attr('x', d => x(d))
.attr('y', _height + margin.top)
.text(d => `${d.getDate()} ${months[d.getMonth()]}`);
}
};
upateSize = function () {
$svg
.attr('width', _width + margin.left + margin.right)
.attr('height', _height + margin.top + margin.bottom);
$dataCanvas
.attr('width', _width)
.attr('height', _height);
$svg.select('#clip rect')
.attr('width', _width + margin.left)
.attr('height', _height);
// Update scale ranges.
x.range([0, _width]);
y.range([_height, 0]);
// Recalculate the minX and zoom since scale changed.
minX = x(_data[0].timestep);
zoom.x(x);
// Redraw.
layers.line();
layers.minMax();
layers.days();
layers.xAxis();
layers.yAxis();
};
updateData = function () {
if (!_data || _pauseUpdate) {
return;
}
// Update scale domains.
let eDate = _.last(_data).timestep;
let sDate = d3.time.day.offset(eDate, -1);
x.domain([sDate, eDate]);
// Since the data is stacked the last element will contain the
// highest values)
y.domain([_axisValueMin, _axisValueMax]);
// Recalculate the minX and zoom since scale changed.
minX = x(_data[0].timestep);
zoom.x(x);
// Redraw.
layers.line();
layers.minMax();
layers.days();
layers.xAxis();
layers.yAxis();
};
// -----------------------------------------------------------------
// INIT.
$svg = $el.append('svg')
.attr('class', 'chart')
.style('display', 'block');
// Datacanvas
var $dataCanvas = $svg.append('g')
.attr('class', 'data-canvas')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
$svg.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('x', -margin.left) // Compensate for the dataCanvas translate.
.attr('y', 0);
$svg
.attr('cursor', 'move')
.call(zoom)
.on('mousewheel.zoom', null)
.on('DOMMouseScroll.zoom', null);
zoom.on('zoom', function () {
// Bound translate.
let [tx, ty] = zoom.translate();
tx = Math.max(tx, 0);
tx = Math.min(tx, Math.abs(minX) - margin.right);
tx = Math.round(tx);
zoom.translate([tx, ty]);
layers.line();
layers.minMax();
layers.days();
layers.xAxis();
});
_calcSize();
upateSize();
updateData();
}
chartFn.checkSize = function () {
_calcSize();
upateSize();
return chartFn;
};
chartFn.destroy = function () {
// Cleanup.
};
// --------------------------------------------
// Getters and setters.
chartFn.data = function (d) {
if (!arguments.length) return _data;
_data = _.cloneDeep(d);
if (typeof updateData === 'function') updateData();
return chartFn;
};
chartFn.axisLineVal = function (d) {
if (!arguments.length) return _axisLineVal;
_axisLineVal = d;
if (typeof updateData === 'function') updateData();
return chartFn;
};
chartFn.axisValueMin = function (d) {
if (!arguments.length) return _axisValueMin;
_axisValueMin = d;
if (typeof updateData === 'function') updateData();
return chartFn;
};
chartFn.axisValueMax = function (d) {
if (!arguments.length) return _axisValueMax;
_axisValueMax = d;
if (typeof updateData === 'function') updateData();
return chartFn;
};
chartFn.dataUnitSuffix = function (d) {
if (!arguments.length) return _dataUnitSuffix;
_dataUnitSuffix = d;
if (typeof updateData === 'function') updateData();
return chartFn;
};
chartFn.pauseUpdate = function () {
_pauseUpdate = true;
return chartFn;
};
chartFn.continueUpdate = function () {
_pauseUpdate = false;
if (typeof updateData === 'function') updateData();
return chartFn;
};
return chartFn;
};

@ -0,0 +1,77 @@
'use strict';
import React from 'react';
import ChartLine from './charts/chart-line';
import { numDisplay, formatDate } from '../utils/format';
var SensorWidget = React.createClass({
displayName: 'SensorWidget',
propTypes: {
fetching: React.PropTypes.bool,
fetched: React.PropTypes.bool,
className: React.PropTypes.string,
title: React.PropTypes.string,
lastReading: React.PropTypes.object,
avgs: React.PropTypes.object,
plotData: React.PropTypes.array,
axisLineVal: React.PropTypes.number,
axisLineMax: React.PropTypes.number,
axisLineMin: React.PropTypes.number,
unit: React.PropTypes.string
},
render: function () {
let {
className,
fetching, fetched,
title,
lastReading, avgs,
plotData,
axisLineVal, axisLineMax, axisLineMin,
unit } = this.props;
if (!fetched && !fetching) {
return null;
}
return (
<article className={'card ' + className}>
<header className='card__header'>
<div className='card__headline'>
<h1 className='card__title'>{title} {fetching ? '...' : null}</h1>
<dl className='stats'>
<dd className='stats__label'>Last update</dd>
<dt className='stats__date'>{lastReading !== null ? formatDate(lastReading.timestep) : '--'}</dt>
<dd className='stats__label'>Current temperature</dd>
<dt className='stats__value'>{lastReading !== null ? numDisplay(lastReading.value, 1) : '--'}{unit}</dt>
</dl>
</div>
</header>
<div className='card__body'>
<div className='infographic'>
{plotData.length ? (
<div className='line-chart-wrapper'>
<ChartLine
className='line-chart'
axisLineVal={axisLineVal}
axisLineMax={axisLineMax}
axisLineMin={axisLineMin}
dataUnitSuffix={unit}
data={plotData} />
</div>
) : null}
{!plotData.length && fetching ? <p className='card__loading'>Loading Data...</p> : null}
</div>
<div className='metrics'>
<ul className='metrics__list'>
<li><strong>{avgs !== null ? numDisplay(avgs.today, 1, unit) : '--'}</strong> avg today</li>
<li><strong>{avgs !== null ? numDisplay(avgs.yesterday, 1, unit) : '--'}</strong> avg yesterday</li>
</ul>
</div>
</div>
</article>
);
}
});
module.exports = SensorWidget;

@ -0,0 +1,30 @@
'use strict';
var _ = require('lodash');
/*
* App configuration.
*
* Uses settings in config/production.js, with any properties set by
* config/staging.js or config/local.js overriding them depending upon the
* environment.
*
* This file should not be modified. Instead, modify one of:
*
* - config/production.js
* Production settings (base).
* - config/staging.js
* Overrides to production if ENV is staging.
* - config/local.js
* Overrides if local.js exists.
* This last file is gitignored, so you can safely change it without
* polluting the repo.
*/
var configurations = require('./config/*.js', {mode: 'hash'});
var config = configurations.local || {};
if (process.env.DS_ENV === 'staging') {
_.defaultsDeep(config, configurations.staging);
}
_.defaultsDeep(config, configurations.production);
module.exports = config;

@ -0,0 +1,17 @@
'use strict';
/*
* App config for production.
*/
module.exports = {
environment: 'production',
api: 'http://opensensemap.org:8000',
senseBox: {
id: '570629b945fd40c8197462fb',
'sensorId--uv': '570629b945fd40c8197462fd',
'sensorId--luminosity': '570629b945fd40c8197462fe',
'sensorId--pressure': '570629b945fd40c8197462ff',
'sensorId--humidity': '570629b945fd40c819746300',
'sensorId--temperature': '570629b945fd40c819746301'
}
};

@ -0,0 +1,8 @@
'use strict';
/*
* App config overrides for staging.
*/
module.exports = {
environment: 'staging'
};

@ -0,0 +1,48 @@
'use strict';
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { syncHistory } from 'react-router-redux';
import reducer from './reducers/reducer';
// import UhOh from './views/uhoh';
import App from './views/app';
import Home from './views/home';
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false });
// Sync dispatched route actions to the history
const reduxRouterMiddleware = syncHistory(appHistory);
const finalCreateStore = compose(
applyMiddleware(reduxRouterMiddleware, thunkMiddleware)
)(createStore);
const store = finalCreateStore(reducer);
render((
<Provider store={store}>
<Router history={appHistory}>
<Route path='*' component={App}>
<IndexRoute component={Home}/>
</Route>
</Router>
</Provider>
), document.querySelector('#site-canvas'));
// render((
// <Provider store={store}>
// <Router history={appHistory}>
// <Route path='/' component={App}>
// <IndexRoute component={Home}/>
// </Route>
// <Route path='*' component={App}>
// <IndexRoute component={UhOh}/>
// </Route>
// </Router>
// </Provider>
// ), document.querySelector('#site-canvas'));

@ -0,0 +1,40 @@
import _ from 'lodash';
import { combineReducers } from 'redux';
import { routeReducer } from 'react-router-redux';
import * as actions from '../actions/action-types';
const sensorReducerFactory = function (sensor) {
return function (state = {fetching: false, fetched: false, data: null}, action) {
let s = sensor.toUpperCase();
switch (action.type) {
case actions[`REQUEST_SENSOR_DATA_${s}`]:
console.log(`REQUEST_SENSOR_DATA_${s}`);
state = _.cloneDeep(state);
state.fetching = true;
break;
case actions[`RECEIVE_SENSOR_DATA_${s}`]:
console.log(`RECEIVE_SENSOR_DATA_${s}`);
state = _.cloneDeep(state);
state.data = action.data;
state.fetching = false;
state.fetched = true;
break;
}
return state;
};
};
const sensorUv = sensorReducerFactory('uv');
const sensorLuminosity = sensorReducerFactory('luminosity');
const sensorPressure = sensorReducerFactory('pressure');
const sensorHumidity = sensorReducerFactory('humidity');
const sensorTemperature = sensorReducerFactory('temperature');
export default combineReducers({
routing: routeReducer,
sensorUv,
sensorLuminosity,
sensorPressure,
sensorHumidity,
sensorTemperature
});

@ -0,0 +1,22 @@
'use strict';
module.exports.numDisplay = function (n, dec = 2, suffix = '', nan = '--') {
if (isNaN(n)) {
return nan;
}
let s = n.toString();
s = (s.indexOf('.') === -1) ? s : s.substr(0, s.indexOf('.') + dec + 1);
return s + suffix;
};
module.exports.formatDate = function (date) {
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
let hour = date.getHours();
hour = hour < 10 ? `0${hour}` : hour;
let minute = date.getMinutes();
minute = minute < 10 ? `0${minute}` : minute;
return `${months[date.getMonth()]} ${date.getDate()}, ${hour}:${minute}`;
};
module.exports.round = function (n, dec = 2) {
return +module.exports.numDisplay(n, dec);
};

@ -0,0 +1,33 @@
'use strict';
import React from 'react';
var App = React.createClass({
displayName: 'App',
propTypes: {
dispatch: React.PropTypes.func,
children: React.PropTypes.object
},
render: function () {
return (
<div>
<header className='site-header' role='banner'>
<div className='inner'>
<div className='site-headline'>
<h1 className='site-title'>Devseed Sense Lisbon
{/* <a href='/' title='Visit homepage'>Glacial Inferno</a> */}
</h1>
</div>
</div>
</header>
<main className='site-body' role='main'>
{this.props.children}
</main>
</div>
);
}
});
module.exports = App;

@ -0,0 +1,263 @@
'use strict';
import React from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import SensorWidget from '../components/sensor-widget';
import { fetchSensorData } from '../actions/action-creators';
import { round } from '../utils/format';
const getTime = function (str) {
let date = new Date(str);
return Math.floor(date.getTime() / 1000);
};
const sensorProps = React.PropTypes.shape({
fetched: React.PropTypes.bool,
fetching: React.PropTypes.bool,
data: React.PropTypes.array
});
var Home = React.createClass({
displayName: 'Home',
propTypes: {
_requestSensorData: React.PropTypes.func,
sensorUv: sensorProps,
sensorLuminosity: sensorProps,
sensorPressure: sensorProps,
sensorHumidity: sensorProps,
sensorTemperature: sensorProps
},
// Having measurements every minute is too much. Group them.
// Max seconds between reading for them to be considered part
// of the same group.
_mTimeThreshold: 120,
_mGroupSize: 6, // Equal to 10 measurements per hour.
_fetchInterval: null,
// In seconds.
_fetchRate: 300, // 5 min
prepareData: function (rawData) {
var data = null;
if (rawData) {
data = [];
rawData[0].value = +rawData[0].value;
let bucket = [rawData[0]];
for (var i = 1; i < rawData.length; i++) {
rawData[i].value = +rawData[i].value;
let prevTime = getTime(rawData[i - 1].createdAt);
let currTime = getTime(rawData[i].createdAt);
// Having measurements every minute is too much. Group them.
// To make sure that the grouped measurements are all around the same
// time there can't be more than X seconds difference between them.
if (currTime - prevTime > this._mTimeThreshold || bucket.length === this._mGroupSize) {
let f = {
createdAt: _.last(bucket).createdAt,
value: +round(_.meanBy(bucket, 'value'))
};
data.push(f);
bucket = [];
}
bucket.push(rawData[i]);
}
// After the loop finished there may still be data to process.
// if bucket.length < this._mGroupSize for example.
let f = {
createdAt: _.last(bucket).createdAt,
value: +round(_.meanBy(bucket, 'value'))
};
data.push(f);
}
let startToday = new Date();
startToday.setHours(0);
startToday.setMinutes(0);
startToday.setSeconds(0);
startToday = Math.floor(startToday.getTime() / 1000);
let startYesterday = startToday - (60 * 60 * 24);
let dataAll = [];
let dataToday = [];
let dataYesterday = [];
_.forEach(data, o => {
let date = new Date(o.createdAt);
let time = Math.floor(date.getTime() / 1000);
dataAll.push({
timestep: date,
value: +o.value
});
if (time >= startToday) {
dataToday.push({
timestep: date,
value: +o.value
});
}
if (time < startToday && time >= startYesterday) {
dataYesterday.push({
timestep: date,
value: +o.value
});
}
});
let avgs = {
today: _.meanBy(dataToday, 'value'),
yesterday: _.meanBy(dataYesterday, 'value')
};
let last = _.last(dataAll) || null;
return {
data: dataAll,
last,
avgs
};
},
fetchData: function () {
let daysAgo3 = (new Date()).getTime() - (60 * 60 * 24 * 3 * 1000);
daysAgo3 = new Date(daysAgo3).toISOString();
this.props._requestSensorData('temperature', daysAgo3);
this.props._requestSensorData('humidity', daysAgo3);
this.props._requestSensorData('uv', daysAgo3);
this.props._requestSensorData('luminosity', daysAgo3);
this.props._requestSensorData('pressure', daysAgo3);
},
componentDidMount: function () {
this.fetchData();
this._fetchInterval = setInterval(() => {
this.fetchData();
}, this._fetchRate * 1000);
},
componentWillUnmount: function () {
if (this._fetchInterval) {
clearInterval(this._fetchInterval);
}
},
render: function () {
let sensorTemperatureData = this.prepareData(this.props.sensorTemperature.data);
let sensorHumidityData = this.prepareData(this.props.sensorHumidity.data);
let sensorUvData = this.prepareData(this.props.sensorUv.data);
let sensorLuminosityData = this.prepareData(this.props.sensorLuminosity.data);
let sensorPressureData = this.prepareData(this.props.sensorPressure.data);
return (
<section className='page'>
<header className='page__header'>
<div className='inner'>
<div className='page__headline'>
<h1 className='page__title'>Sense Dashboard</h1>
</div>
</div>
</header>
<div className='page__body'>
<section className='page__content'>
<div className='inner'>
<SensorWidget
className='card--temp'
fetching={this.props.sensorTemperature.fetching}
fetched={this.props.sensorTemperature.fetched}
title='Temperature'
lastReading={sensorTemperatureData.last}
avgs={sensorTemperatureData.avgs}
plotData={sensorTemperatureData.data}
axisLineMax={35}
axisLineVal={20}
axisLineMin={4}
unit=' ºC'
/>
<SensorWidget
className='card--hum'
fetching={this.props.sensorHumidity.fetching}
fetched={this.props.sensorHumidity.fetched}
title='Humidity'
lastReading={sensorHumidityData.last}
avgs={sensorHumidityData.avgs}
plotData={sensorHumidityData.data}
axisLineMax={100}
axisLineVal={50}
axisLineMin={10}
unit=' %'
/>
<SensorWidget
className='card--uv'
fetching={this.props.sensorUv.fetching}
fetched={this.props.sensorUv.fetched}
title='Uv light'
lastReading={sensorUvData.last}
avgs={sensorUvData.avgs}
plotData={sensorUvData.data}
axisLineMax={5000}
axisLineVal={250}
axisLineMin={0}
unit=' μW/cm²'
/>
<SensorWidget
className='card--lux'
fetching={this.props.sensorLuminosity.fetching}
fetched={this.props.sensorLuminosity.fetched}
title='Luminosity'
lastReading={sensorLuminosityData.last}
avgs={sensorLuminosityData.avgs}
plotData={sensorLuminosityData.data}
axisLineMax={135000}
axisLineVal={50000}
axisLineMin={0}
unit=' lx'
/>
<SensorWidget
className='card--press'
fetching={this.props.sensorPressure.fetching}
fetched={this.props.sensorPressure.fetched}
title='Air Pressure'
lastReading={sensorPressureData.last}
avgs={sensorPressureData.avgs}
plotData={sensorPressureData.data}
axisLineMax={1020}
axisLineVal={1010}
axisLineMin={1000}
unit=' hPa'
/>
</div>
</section>
</div>
</section>
);
}
});
// /////////////////////////////////////////////////////////////////// //
// Connect functions
function selector (state) {
return {
sensorUv: state.sensorUv,
sensorLuminosity: state.sensorLuminosity,
sensorPressure: state.sensorPressure,
sensorHumidity: state.sensorHumidity,
sensorTemperature: state.sensorTemperature
};
}
function dispatcher (dispatch) {
return {
_requestSensorData: (sensor, toDate) => dispatch(fetchSensorData(sensor, toDate))
};
}
module.exports = connect(selector, dispatcher)(Home);

@ -0,0 +1,29 @@
'use strict';
import React from 'react';
var UhOh = React.createClass({
displayName: 'UhOh',
render: function () {
return (
<section className='page'>
<header className='page__header'>
<div className='inner'>
<div className='page__headline'>
<h1 className='page-title'>404 Not found</h1>
</div>
</div>
</header>
<div className='page__body'>
<div className='inner'>
<div className='page__content'>
<p>UhOh that is a bummer.</p>
</div>
</div>
</div>
</section>
);
}
});
module.exports = UhOh;

@ -0,0 +1,282 @@
/* ==========================================================================
Base
========================================================================== */
html {
box-sizing: border-box;
font-size: 16px;
}
*, *:before, *:after, input[type="search"] {
box-sizing: inherit;
}
html, body {
height: 100%;
}
body {
background: #fff;
color: $base-font-color;
font-size: 1rem;
line-height: 1.5;
font-family: $base-font-family;
font-weight: $base-font-weight;
font-style: $base-font-style;
min-width: $row-min-width;
}
/* Links
========================================================================== */
a {
cursor: pointer;
color: $link-color;
text-decoration: none;
transition: opacity 0.24s ease 0s;
}
a:visited {
color: $link-color;
}
a:hover {
opacity: 0.64;
outline: none;
}
a:active {
outline: none;
transform: translate(0, 1px);
}
/* Rows
========================================================================== */
.row {
@extend .clearfix;
padding-left: $global-spacing;
padding-right: $global-spacing;
@include media(small-up) {
padding-left: $global-spacing * 2;
padding-right: $global-spacing * 2;
}
@include media(xlarge-up) {
padding-left: $global-spacing * 4;
padding-right: $global-spacing * 4;
}
}
.row--centered {
max-width: $row-max-width;
margin-left: auto;
margin-right: auto;
}
/* ==========================================================================
Structure
========================================================================== */
/* Header
========================================================================== */
.site-header {
position: absolute;
width: 100%;
z-index: 1000;
background-color: #fff;
color: $base-color;
padding: $global-spacing 0;
box-shadow: inset 0 -1px 0 0 rgba($base-color, 0.12);
> .inner {
// @extend .row, .row--centered;
padding: 0 2rem;
}
@include media(medium-up) {
padding: ($global-spacing * 2) 0;
}
}
/* Headline */
.page__header {
padding-top: 5rem;
@include media(medium-up) {
padding-top: 8rem;
}
}
.page__title {
@extend .visually-hidden;
}
.site-headline {
@include col(12/12);
@include media(medium-up) {
@include col(6/12);
}
}
.site-title {
float: left;
margin: 0;
line-height: 1;
font-size: 1.25rem;
text-transform: uppercase;
@include media(medium-up) {
font-size: 1.75rem;
}
a {
display: block;
}
* {
vertical-align: top;
display: inline-block;
}
img {
width: auto;
height: 1rem;
}
span {
@extend .visually-hidden;
}
}
/* Body
========================================================================== */
.page__content {
> .inner {
// @extend .row, .row--centered;
padding: 0 2rem;
}
}
.card {
background: #fff;
box-shadow: 0 4px 0 0 rgba($base-color, 0.08), 0 0 0 1px $base-alpha-color;
border-radius: $global-radius;
position: relative;
overflow: hidden;
margin-bottom: 2rem;
@include media(large-up) {
@include col(6/12, $cycle: 2);
}
@include media(xlarge-up) {
@include col(4/12, $cycle: 3, $uncycle: 2);
}
&__header {
@extend .antialiased;
position: absolute;
padding: $global-spacing;
color: #fff;
top: 0;
right: 0;
left: 0;
}
&__title {
padding-right: 3rem;
font-size: 1.375rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
&__loading {
padding: 3rem 0;
text-align: center;
color: #fff;
text-transform: uppercase;
}
.infographic {
padding: 5rem 1rem 1rem 1rem;
}
.stats {
&__label {
@extend .visually-hidden;
}
&__value {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.375rem;
font-weight: $base-font-bold;
}
&__date {
font-size: 0.875rem;
line-height: 1rem;
color: rgba(#fff, 0.80);
&:before {
font-size: 1rem;
vertical-align: top;
margin-right: 0.5rem;
@extend %collecticons-clock;
}
}
}
.metrics {
padding: 1rem;
&__list {
@extend .clearfix;
li {
@include col(6/12);
text-align: center;
&:not(:last-child) {
box-shadow: 1px 0 0 0 rgba($base-color, 0.16);
}
}
strong {
font-size: 1.625rem;
display: block;
}
}
}
&.card--temp {
.infographic {
background: linear-gradient(#FF5722, #E64A19);
}
}
&.card--hum {
.infographic {
background: linear-gradient(#009688, #0097A7);
}
}
&.card--uv {
.infographic {
background: linear-gradient(#7B1FA2, #9C27B0);
}
}
&.card--lux {
.infographic {
background: linear-gradient(#FFA000, #FFC107);
}
}
&.card--press {
.infographic {
background: linear-gradient(#616161, #607D8B);
}
}
}
/* Footer
========================================================================== */

@ -0,0 +1,45 @@
/* Footer
========================================================================== */
svg.chart {
// Avoids that the chart grows because of the gap left by inline elements.
display: block;
}
.line-chart-wrapper {
min-height: 10rem;
}
.line-chart {
.data-line {
stroke: #fff;
stroke-width: 2px;
fill: none;
}
.edge {
font-size: 0.75rem;
fill: rgba(#fff, 0.8);
}
.days {
font-size: 0.75rem;
fill: rgba(#fff, 0.8);
}
.x.axis text {
font-size: 0.75rem;
fill: rgba(#fff, 0.8);
}
.y.axis {
.line {
stroke: rgba(#fff, 0.8);
stroke-dasharray: 4 4;
}
.label {
@extend .antialiased;
fill: rgba(#fff, 0.8);
font-size: 0.75rem;
}
}
}

@ -0,0 +1,24 @@
/* ==========================================================================
Functions
========================================================================== */
/* Range function
========================================================================== */
/**
* Define ranges for various things, like media queries.
*/
@function lower-bound($range){
@if length($range) <= 0 {
@return 0;
}
@return nth($range,1);
}
@function upper-bound($range) {
@if length($range) < 2 {
@return 999999999999;
}
@return nth($range, 2);
}

@ -0,0 +1,143 @@
/* ==========================================================================
Media queries
========================================================================== */
@mixin media($arg) {
@if $arg == screen {
@media #{$screen} { @content; }
}
@if $arg == landscape {
@media #{$screen} and (orientation: landscape) { @content; }
}
@if $arg == portrait {
@media #{$screen} and (orientation: portrait) { @content; }
}
@if $arg == xsmall-up {
@media #{$screen} and (min-width: lower-bound($xsmall-range)) { @content; }
}
@if $arg == xsmall-only {
@media #{$screen} and (max-width: upper-bound($xsmall-range)) { @content; }
}
@if $arg == small-up {
@media #{$screen} and (min-width: lower-bound($small-range)) { @content; }
}
@if $arg == small-only {
@media #{$screen} and (min-width: lower-bound($small-range)) and (max-width: upper-bound($small-range)) { @content; }
}
@if $arg == medium-up {
@media #{$screen} and (min-width: lower-bound($medium-range)) { @content; }
}
@if $arg == medium-down {
@media #{$screen} and (max-width: upper-bound($medium-range)) { @content; }
}
@if $arg == medium-only {
@media #{$screen} and (min-width: lower-bound($medium-range)) and (max-width: upper-bound($medium-range)) { @content; }
}
@if $arg == large-up {
@media #{$screen} and (min-width: lower-bound($large-range)) { @content; }
}
@if $arg == large-only {
@media #{$screen} and (min-width: lower-bound($large-range)) and (max-width: upper-bound($large-range)) { @content; }
}
@if $arg == xlarge-up {
@media #{$screen} and (min-width: lower-bound($xlarge-range)) { @content; }
}
@if $arg == xlarge-only {
@media #{$screen} and (min-width: lower-bound($xlarge-range)) and (max-width: upper-bound($xlarge-range)) { @content; }
}
@if $arg == xxlarge-up {
@media #{$screen} and (min-width: lower-bound($xxlarge-range)) { @content; }
}
}
/* ==========================================================================
Typography
========================================================================== */
@mixin heading($font-size, $max-media: small-up) {
font-size: $font-size;
line-height: $font-size + 0.5;
@if $max-media == medium-up or $max-media == large-up or $max-media == xlarge-up {
@include media(medium-up) {
$font-size: $font-size + 0.25;
font-size: $font-size;
line-height: $font-size + 0.5;
}
}
@if $max-media == large-up or $max-media == xlarge-up {
@include media(large-up) {
$font-size: $font-size + 0.25;
font-size: $font-size;
line-height: $font-size + 0.5;
}
}
@if $max-media == xlarge-up {
@include media(xlarge-up) {
$font-size: $font-size + 0.25;
font-size: $font-size;
line-height: $font-size + 0.5;
}
}
}
/* ==========================================================================
Buttons
========================================================================== */
@mixin button-variation($style, $color) {
@if $style == "filled" {
background-color: $color;
box-shadow: inset 0 0 0 1px rgba($base-color, 0.16);
&, &:visited {
color: #fff;
}
&:hover {
background-color: shade($color, 8%);
}
.drop--open > &,
&.button--active,
&.button--active:hover,
&:active {
background-color: shade($color, 16%);
}
}
@else if $style == "outline" {
background-color: rgba($color, 0);
box-shadow: inset 0 0 0 1px $color;
&, &:visited {
color: $color;
}
&:hover {
background-color: rgba($color, 0.08);
}
.open > &,
&.active,
&.active:hover,
&:active {
background-color: rgba($color, 0.16);
}
}
@else if $style == "unbounded" {
background-color: rgba($color, 0);
&, &:visited {
color: $color;
}
&:hover {
background-color: rgba($color, 0.08);
}
.open > &,
&.active,
&.active:hover,
&:active {
color: shade($color, 48%);
}
}
@else {
@error "Invalid style property for button variation.";
}
}

@ -0,0 +1,425 @@
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
// h1 {
// font-size: 2em;
// margin: 0.67em 0;
// }
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

@ -0,0 +1,143 @@
/**
* Reset ==============================================================
* Based on http://meyerweb.com/eric/tools/css/reset
*/
html,
body,
/* Structures */
div,
span,
applet,
object,
iframe,
/* Text */
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
font,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
/* Lists */
dl,
dt,
dd,
ol,
ul,
li,
/* Forms */
fieldset,
form,
input,
select,
textarea,
label,
legend,
/* Tables */
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
line-height: inherit;
}
ol,
ul,
.item-list ul,
.item-list ul li {
list-style: none;
}
blockquote,
q { quotes: none; }
blockquote:before,
blockquote:after,
q:before, q:after {
content:'';
content: none;
}
/* remember to define focus styles! */
:focus { outline: 0; }
/* remember to highlight inserts somehow! */
ins { text-decoration: none; }
del { text-decoration: line-through; }
button,
html input[type=button],
input[type=reset],
input[type=submit] {
-webkit-appearance: button;
outline: none;
cursor: pointer;
font: inherit;
height: auto;
width: auto;
margin: 0;
}
input, select, textarea {
font: inherit;
height: auto;
width: auto;
margin: 0;
}
input[type="search"] {
-webkit-appearance: none;
}
legend,
label {
font: inherit;
color: inherit;
}

@ -0,0 +1,105 @@
/* ==========================================================================
Utils
========================================================================== */
/* Font smoothing
========================================================================== */
/**
* Antialiased font smoothing works best for light text on a dark background.
* Apply to single elements instead of globally to body.
* Note this only applies to webkit-based desktop browsers and Firefox 25 (and later) on the Mac.
*/
.antialiased {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Truncated text
========================================================================== */
.truncated {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Hidden content
========================================================================== */
/* Hide from both screenreaders and browsers */
.hidden {
display: none !important;
visibility: hidden;
}
/* Hide only visually, but have it available for screenreaders */
.visually-hidden {
border: 0 none;
clip: rect(0px, 0px, 0px, 0px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
/**
* Extends the .visually-hidden class to allow the element
* to be focusable when navigated to via the keyboard
*/
.visually-hidden.focusable:active,
.visually-hidden.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
/* Undo visually-hidden */
.visually-hidden-undo {
position: inherit;
overflow: visible;
height: auto;
width: auto;
margin: auto;
}
/* Hide visually and from screenreaders, but maintain layout */
.invisible {
visibility: hidden;
}
/* Clearfix
========================================================================== */
.clearfix {
&:before,
&:after {
content: " ";
display: table;
}
&:after {
clear: both;
}
}
/* Disabled
========================================================================== */
.disabled {
opacity: 0.48;
pointer-events: none;
cursor: not-allowed;
}

@ -0,0 +1,70 @@
/* ==========================================================================
Colors
========================================================================== */
$base-color: #2c3e50; // Midnight blue
$primary-color: #16a085; // Belize Hole
$secondary-color: #3498db; // Purple
// $tertiary-color: #ffffff; // Undefined
$danger-color: #ea4f54; // Red
$success-color: $primary-color; // lime
$warning-color: #f6d55f; // Yellow
$info-color: #008CBA; // Blue
$link-color: $primary-color;
$base-alpha-color: rgba($base-color, 0.12);
/* ==========================================================================
Typography
========================================================================== */
/**
* $base-* refers to the base styles when there's nothing else to override them.
*/
$base-font-color: $base-color;
$base-font-family: "Exo 2", "Helvetica Neue", Helvetica, Arial, sans-serif;
$base-font-style: normal;
$base-font-light: 300;
$base-font-regular: 400;
$base-font-bold: 700;
$base-font-weight: $base-font-regular;
$code-font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
/* ==========================================================================
Decoration
========================================================================== */
/* Border radius */
$global-radius: 0.25rem;
$semi-radius: $global-radius/2;
$global-rounded: 60rem;
/* ==========================================================================
Spacing and media queries
========================================================================== */
/* Rows */
$row-min-width: 320px;
$row-max-width: 1280px;
$jeet-max-width: $row-max-width; // reset jeet.gs max width
/* Media queries */
$xsmall-range: (0, 543px);
$small-range: (544px, 767px);
$medium-range: (768px, 991px);
$large-range: (992px, 1199px);
$xlarge-range: (1200px);
$screen: "only screen";
/* Spacing */
$global-spacing: 1rem;
$section-spacing: 4rem;

@ -0,0 +1,55 @@
/* ==========================================================================
Main
========================================================================== */
/**
* Following Necolas' Principles of writing consistent, idiomatic CSS:
* https://github.com/necolas/idiomatic-css
*/
/* Charset */
@charset "UTF-8";
/* Libraries
========================================================================== */
/**
* Using Bourbon for extended SASS functionality:
* http://bourbon.io/
*/
@import "bourbon";
/**
* Using Jeet, a grid system designed for humans:
* http://jeet.gs/
*/
@import "jeet/index";
/* Reboot
========================================================================== */
@import "normalize";
@import "reset";
/* Settings
========================================================================== */
@import "variables";
@import "functions";
@import "mixins";
/* Main
========================================================================== */
@import "collecticons";
@import "charts";
@import "base";
/* Utilities */
@import "utils";

@ -0,0 +1,63 @@
<!doctype html>
<!--[if lt IE 10]> <html class="lt-ie10 no-js" lang=""> <![endif]-->
<!--[if gt IE 9]><!--> <html class="no-js" lang=""> <!--<![endif]-->
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Devseed sense dashboards" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Devseed Sense - Lisbon</title>
<!-- Twitter -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@developmentseed" />
<meta name="twitter:title" content="Devseed Sense - Lisbon">
<meta name="twitter:description" content="Devseed sense dashboards." />
<meta name="twitter:image:src" content="assets/graphics/meta/default-meta-image.png" /
<!--/ Twitter -->
<!-- OG -->
<meta property="og:site_name" content="Devseed Sense - Lisbon" />
<meta property="og:title" content="Devseed Sense - Lisbon" />
<meta property="og:url" content="" />
<meta property="og:type" content="website" />
<meta property="og:description" content="Devseed sense dashboards." />
<meta property="og:image" content="assets/graphics/meta/default-meta-image.png" />
<!--/ OG -->
<link rel="shortcut icon" href="assets/graphics/meta/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="assets/graphics/meta/apple-touch-icon.png" />
<link rel="apple-touch-icon" sizes="57x57" href="assets/graphics/meta/apple-touch-icon-57x57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="assets/graphics/meta/apple-touch-icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="assets/graphics/meta/apple-touch-icon-76x76.png" />
<link rel="apple-touch-icon" sizes="114x114" href="assets/graphics/meta/apple-touch-icon-114x114.png" />
<link rel="apple-touch-icon" sizes="120x120" href="assets/graphics/meta/apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon" sizes="144x144" href="assets/graphics/meta/apple-touch-icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="assets/graphics/meta/apple-touch-icon-152x152.png" />
<!-- build:css assets/styles/main.css -->
<link rel="stylesheet" href="assets/styles/main.css">
<!-- endbuild -->
<link rel="author" type="text/plain" href="humans.txt" />
<link href='https://fonts.googleapis.com/css?family=Exo+2:400,400italic,300italic,300,700,700italic' rel='stylesheet' type='text/css'>
</head>
<body>
<!--[if lt IE 10]>
<div id="nocando">
<h1>No can do!</h1>
<p>You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</div>
<![endif]-->
<div id="site-canvas"><!-- Content goes inside #site-canvas --></div>
<!-- build:js assets/scripts/vendor.js -->
<script src="assets/scripts/vendor.js"></script>
<!-- endbuild -->
<!-- build:js assets/scripts/bundle.js -->
<script src="assets/scripts/bundle.js"></script>
<!-- endbuild -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

@ -0,0 +1,251 @@
'use strict';
var fs = require('fs');
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var del = require('del');
var browserSync = require('browser-sync');
var reload = browserSync.reload;
var watchify = require('watchify');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var sourcemaps = require('gulp-sourcemaps');
var gutil = require('gulp-util');
var exit = require('gulp-exit');
var rev = require('gulp-rev');
var revReplace = require('gulp-rev-replace');
var notifier = require('node-notifier');
var cp = require('child_process');
// /////////////////////////////////////////////////////////////////////////////
// --------------------------- Variables -------------------------------------//
// ---------------------------------------------------------------------------//
// The package.json
var pkg;
// Environment
// Set the correct environment, which controls what happens in config.js
if (!process.env.DS_ENV) {
if (!process.env.TRAVIS_BRANCH || process.env.TRAVIS_BRANCH !== process.env.DEPLOY_BRANCH) {
process.env.DS_ENV = 'staging';
} else {
process.env.DS_ENV = 'production';
}
}
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
// /////////////////////////////////////////////////////////////////////////////
// ------------------------- Helper functions --------------------------------//
// ---------------------------------------------------------------------------//
function readPackage () {
pkg = JSON.parse(fs.readFileSync('package.json'));
}
readPackage();
// /////////////////////////////////////////////////////////////////////////////
// ------------------------- Callable tasks ----------------------------------//
// ---------------------------------------------------------------------------//
gulp.task('default', ['clean'], function () {
gulp.start('build');
});
gulp.task('serve', ['vendorScripts', 'javascript', 'styles', 'fonts'], function () {
browserSync({
port: 1337,
server: {
baseDir: ['.tmp', 'app'],
routes: {
'/node_modules': './node_modules'
}
}
});
// watch for changes
gulp.watch([
'app/*.html',
'app/assets/graphics/**/*',
'.tmp/assets/fonts/**/*'
]).on('change', reload);
gulp.watch('app/assets/styles/**/*.scss', ['styles']);
gulp.watch('app/assets/fonts/**/*', ['fonts']);
gulp.watch('package.json', ['vendorScripts']);
});
gulp.task('clean', function () {
return del(['.tmp', 'dist'])
.then(function () {
$.cache.clearAll();
});
});
gulp.task('build', ['vendorScripts', 'javascript', 'collecticons'], function () {
gulp.start(['html', 'images', 'fonts', 'extras'], function () {
return gulp.src('dist/**/*')
.pipe($.size({title: 'build', gzip: true}))
.pipe(exit());
});
});
// /////////////////////////////////////////////////////////////////////////////
// ------------------------- Browserify tasks --------------------------------//
// ------------------- (Not to be called directly) ---------------------------//
// ---------------------------------------------------------------------------//
// Compiles the user's script files to bundle.js.
// When including the file in the index.html we need to refer to bundle.js not
// main.js
gulp.task('javascript', function () {
var watcher = watchify(browserify({
entries: ['./app/assets/scripts/main.js'],
debug: true,
cache: {},
packageCache: {},
fullPaths: true
}));
function bundler () {
if (pkg.dependencies) {
watcher.external(Object.keys(pkg.dependencies));
}
return watcher.bundle()
.on('error', function (e) {
notifier.notify({
title: 'Oops! Browserify errored:',
message: e.message
});
console.log('Javascript error:', e);
// Allows the watch to continue.
this.emit('end');
})
.pipe(source('bundle.js'))
.pipe(buffer())
// Source maps.
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('.tmp/assets/scripts'))
.pipe(reload({stream: true}));
}
watcher
.on('log', gutil.log)
.on('update', bundler);
return bundler();
});
// Vendor scripts. Basically all the dependencies in the package.js.
// Therefore be careful and keep the dependencies clean.
gulp.task('vendorScripts', function () {
// Ensure package is updated.
readPackage();
var vb = browserify({
debug: true,
require: pkg.dependencies ? Object.keys(pkg.dependencies) : []
});
return vb.bundle()
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
.pipe(source('vendor.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('.tmp/assets/scripts/'))
.pipe(reload({stream: true}));
});
// /////////////////////////////////////////////////////////////////////////////
// ------------------------- Collecticon tasks -------------------------------//
// --------------------- (Font generation related) ---------------------------//
// ---------------------------------------------------------------------------//
gulp.task('collecticons', function (done) {
var args = [
'node_modules/collecticons-processor/bin/collecticons.js',
'compile',
'app/assets/graphics/collecticons/',
'--font-embed',
'--font-dest', 'app/assets/fonts',
'--font-name', 'collecticons',
'--font-types', 'woff',
'--style-format', 'sass',
'--style-dest', 'app/assets/styles/',
'--style-name', 'collecticons',
'--class-name', 'collecticons',
'--no-standalone',
'--no-preview'
];
return cp.spawn('node', args, {stdio: 'inherit'})
.on('close', done);
});
// //////////////////////////////////////////////////////////////////////////////
// --------------------------- Helper tasks -----------------------------------//
// ----------------------------------------------------------------------------//
gulp.task('styles', function () {
return gulp.src('app/assets/styles/main.scss')
.pipe($.plumber(function (e) {
notifier.notify({
title: 'Oops! Sass errored:',
message: e.message
});
console.log('Sass error:', e.toString());
// Allows the watch to continue.
this.emit('end');
}))
.pipe($.sourcemaps.init())
.pipe($.sass({
outputStyle: 'expanded',
precision: 10,
includePaths: ['.'].concat(require('node-bourbon').includePaths).concat(['node_modules/jeet/scss'])
}))
.pipe($.sourcemaps.write())
.pipe(gulp.dest('.tmp/assets/styles'))
.pipe(reload({stream: true}));
});
gulp.task('html', ['styles'], function () {
return gulp.src('app/*.html')
.pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
.pipe($.if('*.js', $.uglify()))
.pipe($.if('*.css', $.csso()))
.pipe($.if(/\.(css|js)$/, rev()))
.pipe(revReplace())
.pipe(gulp.dest('dist'));
});
gulp.task('images', function () {
return gulp.src('app/assets/graphics/**/*')
.pipe($.cache($.imagemin({
progressive: true,
interlaced: true,
// don't remove IDs from SVGs, they are often used
// as hooks for embedding and styling
svgoPlugins: [{cleanupIDs: false}]
})))
.pipe(gulp.dest('dist/assets/graphics'));
});
gulp.task('fonts', function () {
return gulp.src('app/assets/fonts/**/*')
.pipe(gulp.dest('.tmp/assets/fonts'))
.pipe(gulp.dest('dist/assets/fonts'));
});
gulp.task('extras', function () {
return gulp.src([
'app/**/*',
'!app/*.html',
'!app/assets/graphics/**',
'!app/assets/vendor/**',
'!app/assets/styles/**',
'!app/assets/scripts/**'
], {
dot: true
}).pipe(gulp.dest('dist'));
});

@ -0,0 +1,93 @@
{
"name": "devseed-sense",
"version": "1.0.0",
"description": "Devseed sense dashboards",
"repository": {
"type": "git",
"url": "https://github.com/developmentseed/sense"
},
"author": {
"name": "Development Seed",
"url": "https://developmentseed.org"
},
"license": "WTFPL",
"bugs": {
"url": "https://github.com/developmentseed/sense/issues"
},
"homepage": "http://sense.devseed.org",
"scripts": {
"postinstall": "[ -f app/assets/scripts/config/local.js ] || echo 'module.exports = {};' > app/assets/scripts/config/local.js",
"serve": "gulp collecticons && gulp serve",
"build": "NODE_ENV=production gulp",
"lint": "eslint app/assets/scripts/ --ext .js",
"test": "echo \"No tests\" && exit 0"
},
"browserify": {
"transform": [
[
"babelify",
{
"presets": [
"es2015",
"react"
]
}
],
"require-globify",
"envify"
]
},
"devDependencies": {
"babel": "^6.5.2",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
"babelify": "^7.2.0",
"browser-sync": "^2.11.1",
"browserify": "^13.0.0",
"collecticons-processor": "^2.0.0",
"del": "^2.2.0",
"envify": "^3.4.0",
"eslint": "^1.0.0",
"eslint-config-semistandard": "^5.0.0",
"eslint-config-standard": "^4.0.0",
"eslint-plugin-promise": "^1.0.8",
"eslint-plugin-react": "^3.16.1",
"eslint-plugin-standard": "^1.3.2",
"gulp": "^3.9.1",
"gulp-cache": "^0.4.2",
"gulp-csso": "^1.0.1",
"gulp-exit": "0.0.2",
"gulp-if": "^2.0.0",
"gulp-imagemin": "^2.4.0",
"gulp-load-plugins": "^1.2.0",
"gulp-plumber": "^1.1.0",
"gulp-rev": "^7.0.0",
"gulp-rev-replace": "^0.4.3",
"gulp-sass": "^2.2.0",
"gulp-size": "^2.0.0",
"gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.5.2",
"gulp-useref": "^3.0.5",
"gulp-util": "^3.0.7",
"jeet": "^6.1.2",
"node-bourbon": "^4.2.3",
"node-notifier": "^4.5.0",
"require-globify": "^1.3.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.7.0"
},
"dependencies": {
"babel-polyfill": "^6.3.14",
"d3": "^3.5.16",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.10.0",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-redux": "^4.3.0",
"react-router": "^2.0.0-rc5",
"react-router-redux": "^2.0.4",
"redux": "^3.2.1",
"redux-thunk": "^1.0.3"
}
}
Loading…
Cancel
Save