Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
|
@ -1,11 +0,0 @@
|
|||
#!/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
|
|
@ -1,8 +0,0 @@
|
|||
#!/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
|
29
.eslintrc
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"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],
|
||||
"react/no-did-update-set-state": [2],
|
||||
"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
|
@ -1 +0,0 @@
|
|||
* text=auto
|
118
.gitignore
vendored
|
@ -1,118 +0,0 @@
|
|||
################################################
|
||||
############### .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
|
1
.nvmrc
|
@ -1 +0,0 @@
|
|||
v8.9.0
|
39
.travis.yml
|
@ -1,39 +0,0 @@
|
|||
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}
|
1
CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
sense.devseed.com
|
13
LICENSE
|
@ -1,13 +0,0 @@
|
|||
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.
|
36
README.md
|
@ -1,36 +0,0 @@
|
|||
# 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/).
|
||||
|
||||

|
||||
|
||||
## Development environment
|
||||
To set up the development environment for this website, you'll need to install the following on your system:
|
||||
|
||||
- Node (v8.9.x) & yarn ([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:
|
||||
```sh
|
||||
$ yarn install
|
||||
```
|
||||
|
||||
### Getting started
|
||||
|
||||
```sh
|
||||
$ yarn 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 [Begga sensebox](http://opensensemap.org/#/explore/5b26181b1fef04001b69093c).
|
||||
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
|
||||
```
|
|
@ -1,6 +0,0 @@
|
|||
RewriteEngine On
|
||||
|
||||
# enforce HTTPs
|
||||
RewriteCond %{SERVER_PORT} 80
|
||||
RewriteRule ^(.*)$ https://%{SERVER_NAME}/$1 [R,L]
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>openSenseMap data aggregation</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
details { padding: 10px; }
|
||||
th, td { padding: 10px; text-align: right; }
|
||||
tr:nth-child(odd) { background: #eee; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="title">loading ...</h1>
|
||||
<h4 id="subtitle"></h4>
|
||||
|
||||
<div>
|
||||
<button onclick="loadConfig('&phenomenon=Niederschlag&operation=sum')">Niederschlag</button>
|
||||
<button onclick="loadConfig('&phenomenon=Temperatur&operation=arithmeticMean')">ø Temperatur</button>
|
||||
<button onclick="loadConfig('&phenomenon=Temperatur&operation=max')">max. Temperatur</button>
|
||||
<button onclick="loadConfig('&phenomenon=Temperatur&operation=min')">min. Temperatur</button>
|
||||
<button onclick="loadConfig('&phenomenon=PM2.5&operation=max')">max. Feinstaub</button>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="loadConfig('&windowSize=30 days', reset=false)">Monat</button>
|
||||
<button onclick="loadConfig('&windowSize=1 week', reset=false)">Woche</button>
|
||||
<button onclick="loadConfig('&windowSize=1 day', reset=false)">Tag</button>
|
||||
<button onclick="loadConfig('&windowSize=1 hour', reset=false)">Stunde</button>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Advanced Usage</summary>
|
||||
<p>
|
||||
Shown data can be modified through the URL search query. Supported keys are:
|
||||
<code>boxId, phenomenon, start, end, windowSize, operation</code>
|
||||
For supported values check out the <a href="https://docs.opensensemap.org/#api-Statistics-descriptive" target="_blank">openSenseMap API docs</a>.
|
||||
</p>
|
||||
<p>
|
||||
Example:
|
||||
<code>
|
||||
?boxId=5b26181b1fef04001b69093c&phenomenon=Temperatur&operation=max&windowSize=14 days&start=2019-10-01T00:00:00.000Z
|
||||
</code>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<br/>
|
||||
|
||||
<div id="status"></div>
|
||||
|
||||
<table id="datatable"></table>
|
||||
|
||||
|
||||
<script>
|
||||
const defaultConfig = {
|
||||
boxId: '5b26181b1fef04001b69093c',
|
||||
phenomenon: 'Niederschlag',
|
||||
operation: 'sum',
|
||||
start: new Date('2019-04-01').toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
windowSize: '30 days',
|
||||
}
|
||||
|
||||
const config = Object.assign(defaultConfig, parseQuery())
|
||||
main(config).catch(console.error)
|
||||
|
||||
async function main (config) {
|
||||
try {
|
||||
const data = await fetchOsemDataAggregate(config)
|
||||
|
||||
const table = document.querySelector('#datatable')
|
||||
populateTable(table, data, config)
|
||||
|
||||
const title = `${data['boxName']} - ${data['phenomenon']}`
|
||||
document.querySelector('#title').innerText = title
|
||||
document.title = title
|
||||
const subtitle = `${config.operation} per ${config.windowSize}`
|
||||
document.querySelector('#subtitle').innerText = subtitle
|
||||
} catch (err) {
|
||||
document.querySelector('#status').innerText = err
|
||||
}
|
||||
}
|
||||
|
||||
function parseQuery () {
|
||||
const kvPairs = window.location.search
|
||||
.slice(1) // remove leading ?
|
||||
.split('&') // create list of { key: value } objects
|
||||
.map(kv => {
|
||||
[k,v] = kv.split('=')
|
||||
return { [k]: decodeURIComponent(v) }
|
||||
})
|
||||
|
||||
return Object.assign.apply(null, kvPairs) // combine objects
|
||||
}
|
||||
|
||||
async function fetchOsemDataAggregate (config) {
|
||||
const {
|
||||
boxId,
|
||||
phenomenon,
|
||||
operation,
|
||||
start,
|
||||
end,
|
||||
windowSize,
|
||||
} = config
|
||||
|
||||
const url = `https://api.opensensemap.org/statistics/descriptive?boxid=${boxId}&from-date=${start}&to-date=${end}&phenomenon=${phenomenon}&window=${windowSize}&operation=${operation}&columns=boxName,unit,phenomenon&format=json&download=false`
|
||||
const res = await fetch(url)
|
||||
return (await res.json())[0]
|
||||
}
|
||||
|
||||
function populateTable (table, data, config) {
|
||||
const dateFormat = d => new Date(d).toLocaleDateString('de')
|
||||
const valFormat = v => Math.round(v * (10 ** 2)) / (10 ** 2)
|
||||
const addRow = (left, right) => {
|
||||
const row = document.createElement('tr')
|
||||
row.innerHTML = `<td>${left}</td><td>${right}</td>`
|
||||
table.appendChild(row)
|
||||
}
|
||||
|
||||
const dates = Object.keys(data).filter(k => k.startsWith('20')).sort()
|
||||
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateFrom = dates[i]
|
||||
const dateTo = dates[i + 1] || new Date()
|
||||
const val = data[dates[i]]
|
||||
|
||||
const left = `${dateFormat(dateFrom)} - ${dateFormat(dateTo)}`
|
||||
const right = `${valFormat(val)} ${data['unit']}`
|
||||
addRow(left, right)
|
||||
}
|
||||
|
||||
// add aggregate of all values
|
||||
let aggregate = null
|
||||
switch (config.operation) {
|
||||
case 'sum':
|
||||
aggregate = dates.map(d => data[d]).reduce((s, v) => s += v, 0)
|
||||
break
|
||||
case 'arithmeticMean':
|
||||
aggregate = dates.map(d => data[d]).reduce((s, v) => s += v, 0) / dates.length
|
||||
break
|
||||
case 'max':
|
||||
aggregate = Math.max.apply(null, dates.map(d => data[d]))
|
||||
break
|
||||
case 'min':
|
||||
aggregate = Math.min.apply(null, dates.map(d => data[d]))
|
||||
break
|
||||
}
|
||||
if (aggregate)
|
||||
addRow(config.operation, `${valFormat(aggregate)} ${data['unit']}`)
|
||||
}
|
||||
|
||||
// click handler of the UI buttons.
|
||||
function loadConfig (queryString, reset = true) {
|
||||
if (reset)
|
||||
window.location.search = queryString // this assignment reloads the page
|
||||
else
|
||||
window.location.search += queryString
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,7 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 703 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210.3 210.3"><defs><clipPath id="a"><path d="M1776.56 314.36L1578 115.76l-198.6 198.6L1578 512.92l198.56-198.57"/></clipPath></defs><path d="M150.45 165c-11.77-11.13-27.6-18.02-45.07-18.02-17.6 0-33.56 6.96-45.34 18.2l45.1 45.12 45.3-45.3" fill="#4ebe4b"/><path d="M105.5 50.1c-14.2 0-27.3-4.68-37.92-12.5L105.16 0l37.92 37.92c-10.56 7.64-23.53 12.17-37.58 12.17" fill="#fff200"/><path d="M100.9 65.68c.67 4.1-2.1 7.9-6.16 8.55-4.07.65-7.9-2.12-8.53-6.16-.66-4.08 2.1-7.9 6.18-8.55 4.06-.66 7.9 2.1 8.53 6.16m-.02 31.42c.7 4.06-2.1 7.88-6.16 8.52-4.07.67-7.88-2.1-8.53-6.16-.65-4.08 2.12-7.9 6.17-8.55 4.07-.65 7.9 2.12 8.54 6.2m24.24 26.55c.64 4.06-2.1 7.9-6.2 8.55-4.07.64-7.88-2.1-8.53-6.17-.66-4.08 2.1-7.9 6.17-8.55 4.07-.66 7.88 2.12 8.56 6.18m7.44-41.49a7.44 7.44 0 0 1-6.2 8.53c-4.05.68-7.86-2.1-8.53-6.17-.66-4.06 2.13-7.9 6.18-8.54 4.07-.66 7.9 2.1 8.55 6.18m40.35 60.48l-13.62-13.62 14.1-14.1H145.2l-9.76-9.78 9.5-9.5h28.75l-14.4-14.4 13.66-13.62L210.3 105l-37.35 37.66" fill="#45beed"/><g clip-path="url(#a)" transform="matrix(.52953 0 0 -.52953 -730.44 271.592)"><path d="M1413.8 344.72l-8.6-8.58c6.06-6.06 9.03-13.9 9.05-21.83-.02-7.93-3-15.75-9.02-21.78-6.06-6.05-13.9-9.02-21.82-9.04-7.94.02-15.77 3-21.83 9.05-6.04 6.04-9.02 13.87-9.03 21.8 0 7.94 3 15.76 9.03 21.8l.3.3-.3-.3c6.06 6.05 13.9 9.03 21.83 9.04 7.94 0 15.76-2.98 21.8-9.03l8.6 8.58 8.57 8.57a54.931 54.931 0 0 1-38.97 16.14 55.05 55.05 0 0 1-39-16.14l.33.32-.32-.32a55.021 55.021 0 0 1-16.13-38.97c0-14.06 5.4-28.24 16.14-38.97a55.078 55.078 0 0 1 39-16.15 55.17 55.17 0 0 1 39 16.15c10.72 10.72 16.13 24.9 16.12 38.96 0 14.07-5.4 28.26-16.15 39l.32-.32-.33.3-8.58-8.56" fill="#4abeea"/><path d="M1444.18 375.13l-8.58-8.58c14.45-14.46 21.62-33.28 21.63-52.22 0-18.93-7.2-37.76-21.63-52.2-14.44-14.44-33.28-21.62-52.22-21.64-18.93 0-37.75 7.17-52.2 21.62-14.45 14.45-21.6 33.26-21.63 52.18.02 18.94 7.2 37.77 21.65 52.22 14.46 14.45 33.28 21.63 52.22 21.65 18.92-.02 37.72-7.18 52.18-21.62l8.58 8.58 8.58 8.58c-19.13 19.14-44.3 28.75-69.34 28.74-25.08 0-50.26-9.62-69.38-28.76-19.14-19.12-28.77-44.3-28.76-69.38 0-25.05 9.6-50.22 28.74-69.35 19.13-19.13 44.3-28.74 69.36-28.73 25.08 0 50.26 9.6 69.38 28.73 19.13 19.13 28.76 44.3 28.74 69.38.02 25.07-9.6 50.25-28.74 69.38l-8.58-8.57" fill="#4abeea"/><path d="M1474.57 405.52l-8.58-8.58c22.84-22.85 34.2-52.66 34.22-82.6 0-29.93-11.4-59.77-34.23-82.6-22.85-22.84-52.68-34.22-82.62-34.23-29.92.02-59.74 11.38-82.6 34.23-22.83 22.84-34.2 52.64-34.2 82.56 0 29.94 11.4 59.77 34.23 82.63 22.85 22.83 52.68 34.2 82.62 34.22 29.92 0 59.73-11.37 82.57-34.2l8.57 8.57 8.58 8.58c-27.5 27.54-63.68 41.34-99.73 41.33-36.07 0-72.25-13.82-99.77-41.34-27.54-27.54-41.36-63.73-41.35-99.8 0-36.06 13.8-72.23 41.33-99.74 27.52-27.53 63.7-41.34 99.75-41.33 36.07 0 72.25 13.8 99.77 41.33 27.53 27.53 41.35 63.7 41.34 99.8 0 36.05-13.8 72.22-41.35 99.74l.45-.44-.45.44-8.58-8.58" fill="#4abeea"/><path d="M1395.06 317.23a15.44 15.44 0 0 0-12.83-17.75c-8.44-1.35-16.37 4.42-17.72 12.8-1.36 8.47 4.4 16.4 12.84 17.74 8.44 1.37 16.37-4.38 17.72-12.8" fill="#45beed"/></g></svg>
|
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -1,50 +0,0 @@
|
|||
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 => {
|
||||
return json.sort((a, b) => {
|
||||
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||
});
|
||||
})
|
||||
.then(json => {
|
||||
dispatch(receiveSensorData(sensor, json));
|
||||
// setTimeout(() => {
|
||||
// dispatch(receiveSensorData(sensor, json));
|
||||
// }, Math.ceil(Math.random() * 2000));
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('e', e);
|
||||
return dispatch(receiveSensorData(sensor, null, 'Data not available'));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
'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';
|
||||
|
||||
export const REQUEST_SENSOR_DATA_PM10 = 'REQUEST_SENSOR_DATA_PM10';
|
||||
export const RECEIVE_SENSOR_DATA_PM10 = 'RECEIVE_SENSOR_DATA_PM10';
|
||||
|
||||
export const REQUEST_SENSOR_DATA_PM25 = 'REQUEST_SENSOR_DATA_PM25';
|
||||
export const RECEIVE_SENSOR_DATA_PM25 = 'RECEIVE_SENSOR_DATA_PM25';
|
||||
|
||||
export const REQUEST_SENSOR_DATA_WINDDIR = 'REQUEST_SENSOR_DATA_WINDDIR';
|
||||
export const RECEIVE_SENSOR_DATA_WINDDIR = 'RECEIVE_SENSOR_DATA_WINDDIR';
|
||||
|
||||
export const REQUEST_SENSOR_DATA_WINDSPEED = 'REQUEST_SENSOR_DATA_WINDSPEED';
|
||||
export const RECEIVE_SENSOR_DATA_WINDSPEED = 'RECEIVE_SENSOR_DATA_WINDSPEED';
|
||||
|
||||
export const REQUEST_SENSOR_DATA_RAIN = 'REQUEST_SENSOR_DATA_RAIN';
|
||||
export const RECEIVE_SENSOR_DATA_RAIN = 'RECEIVE_SENSOR_DATA_RAIN';
|
|
@ -1,459 +0,0 @@
|
|||
'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 () {
|
||||
// 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 () {
|
||||
window.removeEventListener('resize', this.onWindowResize);
|
||||
this.chart.destroy();
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps) {
|
||||
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')
|
||||
// Since this is going to be applied inside the dataCanvas
|
||||
// we have to compensate for the translate.
|
||||
.attr('x', -margin.left)
|
||||
.attr('y', -margin.top)
|
||||
// Add some top and bottom space to avoid clipping the path.
|
||||
.attr('width', _width + margin.left)
|
||||
.attr('height', _height + margin.top + margin.bottom);
|
||||
|
||||
// DEBUG:
|
||||
// To view the area taken by the #clip rect.
|
||||
// $dataCanvas.select('.data-canvas-shadow')
|
||||
// .attr('x', -margin.left)
|
||||
// .attr('y', -margin.top)
|
||||
// .attr('width', _width + margin.left)
|
||||
// .attr('height', _height + margin.top + margin.bottom);
|
||||
|
||||
// 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');
|
||||
|
||||
// DEBUG:
|
||||
// To view the area taken by the #clip rect.
|
||||
// $dataCanvas.append('rect')
|
||||
// .attr('class', 'data-canvas-shadow')
|
||||
// .style('fill', '#000')
|
||||
// .style('opacity', 0.16);
|
||||
|
||||
$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;
|
||||
};
|
|
@ -1,94 +0,0 @@
|
|||
'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,
|
||||
sums: 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, sums,
|
||||
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 ? <p className='card__loading'>
|
||||
{fetching ? 'Lade Daten...' : 'Keine Daten verfügbar'}
|
||||
</p> : null}
|
||||
</div>
|
||||
<div className='metrics'>
|
||||
<ul className='metrics__list'>
|
||||
{sums ? (<div>
|
||||
<li><strong>{numDisplay(sums.today, 1, unit)}</strong> ∑ heute</li>
|
||||
<li><strong>{numDisplay(sums.yesterday, 1, unit)}</strong> ∑ gestern</li>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{avgs ? (<div>
|
||||
<li><strong>{numDisplay(avgs.today, 1, unit)}</strong> ∅ heute</li>
|
||||
<li><strong>{numDisplay(avgs.yesterday, 1, unit)}</strong> ∅ gestern</li>
|
||||
</div>) : null}
|
||||
|
||||
{!(avgs || sums) ? (<div>
|
||||
<li><strong>--</strong></li>
|
||||
<li><strong>--</strong></li>
|
||||
</div>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SensorWidget;
|
|
@ -1,30 +0,0 @@
|
|||
'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;
|
|
@ -1,23 +0,0 @@
|
|||
'use strict';
|
||||
/*
|
||||
* App config for production.
|
||||
*/
|
||||
module.exports = {
|
||||
environment: 'production',
|
||||
api: 'https://api.opensensemap.org',
|
||||
title: 'Begga Weer',
|
||||
senseBox: {
|
||||
id: '5b26181b1fef04001b69093c',
|
||||
'sensorId--rain': '5cd6818eff898b001a7366d8',
|
||||
'sensorId--windSpeed': '5cd6818eff898b001a7366d7',
|
||||
'sensorId--windDir': '5cd6818eff898b001a7366d6',
|
||||
'sensorId--pm25': '5b26181b1fef04001b69093d',
|
||||
'sensorId--pm10': '5b26181b1fef04001b69093e',
|
||||
'sensorId--uv': '5b26181b1fef04001b69093f',
|
||||
'sensorId--luminosity': '5b26181b1fef04001b690940',
|
||||
'sensorId--pressure': '5b26181b1fef04001b690941',
|
||||
'sensorId--humidity': '5b26181b1fef04001b690942',
|
||||
'sensorId--temperature': '5b26181b1fef04001b690943'
|
||||
}
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
'use strict';
|
||||
/*
|
||||
* App config overrides for staging.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
environment: 'staging'
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
'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'));
|
|
@ -1,50 +0,0 @@
|
|||
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');
|
||||
const sensorPm10 = sensorReducerFactory('pm10');
|
||||
const sensorPm25 = sensorReducerFactory('pm25');
|
||||
const sensorWindDir = sensorReducerFactory('windDir');
|
||||
const sensorWindSpeed = sensorReducerFactory('windSpeed');
|
||||
const sensorRain = sensorReducerFactory('rain');
|
||||
|
||||
export default combineReducers({
|
||||
routing: routeReducer,
|
||||
sensorUv,
|
||||
sensorLuminosity,
|
||||
sensorPressure,
|
||||
sensorHumidity,
|
||||
sensorTemperature,
|
||||
sensorPm10,
|
||||
sensorPm25,
|
||||
sensorWindDir,
|
||||
sensorWindSpeed,
|
||||
sensorRain,
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
'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', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
|
||||
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);
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import config from '../config';
|
||||
|
||||
document.title += ` - ${config.title}`;
|
||||
|
||||
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'>
|
||||
<span className='site-title'>{config.title}</span>
|
||||
<span>
|
||||
<a target='_blank' href={'https://opensensemap.org/explore/' + config.senseBox.id}>🗺️ Karte</a>
|
||||
<br/>
|
||||
<a href='./aggregate.html'>Daten-Aggregation</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className='site-body' role='main'>
|
||||
{this.props.children}
|
||||
</main>
|
||||
<footer className='site-footer' role='footer'>
|
||||
<p>by <a href='https://developmentseed.org' title='Visit Development Seed website' target="_blank">Development Seed</a> using <a href={'https://opensensemap.org/explore/' + config.senseBox.id} title='Visit openSenseMap website' target="_blank">openSenseMap</a> data.</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = App;
|
|
@ -1,359 +0,0 @@
|
|||
'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,
|
||||
sensorPm10: sensorProps,
|
||||
sensorPm25: sensorProps,
|
||||
sensorWindDir: sensorProps,
|
||||
sensorWindSpeed: sensorProps,
|
||||
sensorRain: 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 && rawData.length) {
|
||||
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 sums = {
|
||||
today: _.sumBy(dataToday, 'value'),
|
||||
yesterday: _.sumBy(dataYesterday, 'value')
|
||||
};
|
||||
|
||||
let last = _.last(dataAll) || null;
|
||||
|
||||
return {
|
||||
data: dataAll,
|
||||
last,
|
||||
avgs,
|
||||
sums,
|
||||
};
|
||||
},
|
||||
|
||||
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);
|
||||
this.props._requestSensorData('pm10', daysAgo3);
|
||||
this.props._requestSensorData('pm25', daysAgo3);
|
||||
this.props._requestSensorData('windDir', daysAgo3);
|
||||
this.props._requestSensorData('windSpeed', daysAgo3);
|
||||
this.props._requestSensorData('rain', 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);
|
||||
let sensorPm10Data = this.prepareData(this.props.sensorPm10.data);
|
||||
let sensorPm25Data = this.prepareData(this.props.sensorPm25.data);
|
||||
let sensorWindDirData = this.prepareData(this.props.sensorWindDir.data);
|
||||
let sensorWindSpeedData = this.prepareData(this.props.sensorWindSpeed.data);
|
||||
let sensorRainData = this.prepareData(this.props.sensorRain.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='Temperatur'
|
||||
lastReading={sensorTemperatureData.last}
|
||||
avgs={sensorTemperatureData.avgs}
|
||||
plotData={sensorTemperatureData.data}
|
||||
axisLineMax={40}
|
||||
axisLineVal={20}
|
||||
axisLineMin={0}
|
||||
unit=' ºC'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--hum'
|
||||
fetching={this.props.sensorPressure.fetching}
|
||||
fetched={this.props.sensorPressure.fetched}
|
||||
title='Luftdruck'
|
||||
lastReading={sensorPressureData.last}
|
||||
avgs={sensorPressureData.avgs}
|
||||
plotData={sensorPressureData.data}
|
||||
axisLineMax={1030}
|
||||
axisLineVal={1000}
|
||||
axisLineMin={970}
|
||||
unit=' hPa'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--press'
|
||||
fetching={this.props.sensorHumidity.fetching}
|
||||
fetched={this.props.sensorHumidity.fetched}
|
||||
title='rel. Luftfeuchte'
|
||||
lastReading={sensorHumidityData.last}
|
||||
avgs={sensorHumidityData.avgs}
|
||||
plotData={sensorHumidityData.data}
|
||||
axisLineMax={100}
|
||||
axisLineVal={50}
|
||||
axisLineMin={10}
|
||||
unit=' %'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--press'
|
||||
fetching={this.props.sensorRain.fetching}
|
||||
fetched={this.props.sensorRain.fetched}
|
||||
title='Niederschlag'
|
||||
lastReading={sensorRainData.last}
|
||||
sums={sensorRainData.sums}
|
||||
plotData={sensorRainData.data}
|
||||
axisLineMax={1}
|
||||
axisLineVal={0.28}
|
||||
axisLineMin={0}
|
||||
unit=' L / m²'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--uv'
|
||||
fetching={this.props.sensorWindDir.fetching}
|
||||
fetched={this.props.sensorWindDir.fetched}
|
||||
title='Windrichtung'
|
||||
lastReading={sensorWindDirData.last}
|
||||
avgs={sensorWindDirData.avgs}
|
||||
plotData={sensorWindDirData.data}
|
||||
axisLineMax={360}
|
||||
axisLineVal={180}
|
||||
axisLineMin={0}
|
||||
unit=' °'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--uv'
|
||||
fetching={this.props.sensorWindSpeed.fetching}
|
||||
fetched={this.props.sensorWindSpeed.fetched}
|
||||
title='Windgeschwindigkeit'
|
||||
lastReading={sensorWindSpeedData.last}
|
||||
avgs={sensorWindSpeedData.avgs}
|
||||
plotData={sensorWindSpeedData.data}
|
||||
axisLineMax={20}
|
||||
axisLineVal={10}
|
||||
axisLineMin={0}
|
||||
unit=' km/h'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--lux'
|
||||
fetching={this.props.sensorUv.fetching}
|
||||
fetched={this.props.sensorUv.fetched}
|
||||
title='Uv Licht'
|
||||
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='Helligkeit'
|
||||
lastReading={sensorLuminosityData.last}
|
||||
avgs={sensorLuminosityData.avgs}
|
||||
plotData={sensorLuminosityData.data}
|
||||
axisLineMax={135000}
|
||||
axisLineVal={50000}
|
||||
axisLineMin={0}
|
||||
unit=' lx'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--pm'
|
||||
fetching={this.props.sensorPm10.fetching}
|
||||
fetched={this.props.sensorPm10.fetched}
|
||||
title='Feinstaub 10 μm'
|
||||
lastReading={sensorPm10Data.last}
|
||||
avgs={sensorPm10Data.avgs}
|
||||
plotData={sensorPm10Data.data}
|
||||
axisLineMax={25}
|
||||
axisLineVal={10}
|
||||
axisLineMin={0}
|
||||
unit=' μg/m³'
|
||||
/>
|
||||
|
||||
<SensorWidget
|
||||
className='card--pm'
|
||||
fetching={this.props.sensorPm25.fetching}
|
||||
fetched={this.props.sensorPm25.fetched}
|
||||
title='Feinstaub 2.5 μm'
|
||||
lastReading={sensorPm25Data.last}
|
||||
avgs={sensorPm25Data.avgs}
|
||||
plotData={sensorPm25Data.data}
|
||||
axisLineMax={25}
|
||||
axisLineVal={10}
|
||||
axisLineMin={0}
|
||||
unit=' μg/m³'
|
||||
/>
|
||||
|
||||
</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,
|
||||
sensorPm10: state.sensorPm10,
|
||||
sensorPm25: state.sensorPm25,
|
||||
sensorWindDir: state.sensorWindDir,
|
||||
sensorWindSpeed: state.sensorWindSpeed,
|
||||
sensorRain: state.sensorRain,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatcher (dispatch) {
|
||||
return {
|
||||
_requestSensorData: (sensor, toDate) => dispatch(fetchSensorData(sensor, toDate))
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(selector, dispatcher)(Home);
|
|
@ -1,29 +0,0 @@
|
|||
'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;
|
|
@ -1,307 +0,0 @@
|
|||
/* ==========================================================================
|
||||
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 column(12/12);
|
||||
@include media(medium-up) {
|
||||
@include column(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
|
||||
========================================================================== */
|
||||
|
||||
.site-body {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page__content {
|
||||
> .inner {
|
||||
// @extend .row, .row--centered;
|
||||
@extend .clearfix;
|
||||
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 column(6/12, $cycle: 2);
|
||||
}
|
||||
@include media(xlarge-up) {
|
||||
@include column(4/12, $cycle: 3);
|
||||
}
|
||||
|
||||
&__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 column(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(#0d60dd, rgb(52, 122, 196));
|
||||
}
|
||||
}
|
||||
|
||||
&.card--pm {
|
||||
.infographic {
|
||||
background: linear-gradient(#616161, #607D8B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer
|
||||
========================================================================== */
|
||||
|
||||
.site-footer {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
color: $base-color;
|
||||
padding: $global-spacing 0;
|
||||
box-shadow: inset 0 1px 0 0 rgba($base-color, 0.12);
|
||||
padding: 1rem 2rem;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
opacity: 0.64;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/* ==========================================================================
|
||||
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);
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
/* ==========================================================================
|
||||
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.";
|
||||
}
|
||||
}
|
|
@ -1,425 +0,0 @@
|
|||
/*! 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;
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/* ==========================================================================
|
||||
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;
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/* ==========================================================================
|
||||
Colors
|
||||
========================================================================== */
|
||||
$base-color: #2c3e50; // Midnight blue
|
||||
$primary-color: #31acaa; // 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;
|
|
@ -1,55 +0,0 @@
|
|||
/* ==========================================================================
|
||||
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/scss/index";
|
||||
|
||||
|
||||
/* Reboot
|
||||
========================================================================== */
|
||||
|
||||
@import "normalize";
|
||||
@import "reset";
|
||||
|
||||
/* Settings
|
||||
========================================================================== */
|
||||
|
||||
@import "variables";
|
||||
@import "functions";
|
||||
@import "mixins";
|
||||
|
||||
|
||||
/* Main
|
||||
========================================================================== */
|
||||
|
||||
@import "collecticons";
|
||||
@import "charts";
|
||||
@import "base";
|
||||
/* Utilities */
|
||||
|
||||
@import "utils";
|
|
@ -1,49 +0,0 @@
|
|||
<!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="openSenseMap measurement dashboard" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
||||
<title>openSenseMap dashboard</title>
|
||||
|
||||
<!-- OG -->
|
||||
<meta property="og:site_name" content="openSenseMap dashboard" />
|
||||
<meta property="og:title" content="openSenseMap dashboard" />
|
||||
<meta property="og:url" content="" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="latest sensor readings from openSenseMap.org" />
|
||||
<meta property="og:image" content="assets/graphics/meta/default-meta-image.png" />
|
||||
<!--/ OG -->
|
||||
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<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" />
|
||||
|
||||
<!-- 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>
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"short_name": "openSenseMap dashboard",
|
||||
"name": "openSenseMap dashboard",
|
||||
"icons": [
|
||||
{
|
||||
"src":"assets/graphics/meta/apple-touch-icon.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "./?utm_source=homescreen",
|
||||
"scoe": "./",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#4EAF47",
|
||||
"display": "standalone"
|
||||
}
|
1
assets/graphics/collecticons/clock.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" width="512" height="512" viewBox="0 0 512 512"><path d="M288 128h-64v141.3l105.4 105.3 45.2-45.3-86.6-86.6V128zM256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm0 448c-106 0-192-86-192-192S150 64 256 64s192 86 192 192-86 192-192 192z"/></svg>
|
After Width: | Height: | Size: 325 B |
1
assets/graphics/layout/devseed-logo-symbol.svg
Normal file
|
@ -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-32H32C14.3 0 0 14.3 0 32v448c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V32z"/><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.1zM384 210.5c-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 |
1
assets/graphics/layout/ds-logo-neg.svg
Normal file
After Width: | Height: | Size: 4.9 KiB |
1
assets/graphics/layout/ds-logo-pos.svg
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
assets/graphics/meta/apple-touch-icon-114x114.png
Executable file
After Width: | Height: | Size: 4.9 KiB |
BIN
assets/graphics/meta/apple-touch-icon-120x120.png
Executable file
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/graphics/meta/apple-touch-icon-144x144.png
Executable file
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/graphics/meta/apple-touch-icon-152x152.png
Executable file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/graphics/meta/apple-touch-icon-57x57.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/graphics/meta/apple-touch-icon-72x72.png
Executable file
After Width: | Height: | Size: 3.4 KiB |
BIN
assets/graphics/meta/apple-touch-icon-76x76.png
Executable file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/graphics/meta/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/graphics/meta/default-meta-image.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
assets/graphics/meta/favicon.ico
Executable file
After Width: | Height: | Size: 34 KiB |
1
assets/scripts/bundle-ddc4bffa28.js
Normal file
19
assets/scripts/vendor-df89164be8.js
Normal file
2
assets/styles/main-72efb634f0.css
Normal file
251
gulpfile.js
|
@ -1,251 +0,0 @@
|
|||
'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 log = require('fancy-log');
|
||||
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', 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', err => log('Browserify Error', err))
|
||||
.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'])
|
||||
}))
|
||||
.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'));
|
||||
});
|
57
index.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
<!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" />
|
||||
|
||||
<link rel="stylesheet" href="assets/styles/main-72efb634f0.css">
|
||||
<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>
|
||||
|
||||
<script src="assets/scripts/vendor-df89164be8.js"></script>
|
||||
<script src="assets/scripts/bundle-ddc4bffa28.js"></script>
|
||||
</body>
|
||||
</html>
|
96
package.json
|
@ -1,96 +0,0 @@
|
|||
{
|
||||
"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-cli": "^6.26.0",
|
||||
"babel-preset-es2015": "^6.5.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babelify": "^8.0.0",
|
||||
"browser-sync": "^2.11.1",
|
||||
"browserify": "^16.2.2",
|
||||
"collecticons-processor": "^3.1.0",
|
||||
"del": "^3.0.0",
|
||||
"envify": "^4.1.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-semistandard": "^12.0.1",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-react": "^7.9.1",
|
||||
"eslint-plugin-standard": "^3.1.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-cache": "^1.0.2",
|
||||
"gulp-csso": "^3.0.1",
|
||||
"gulp-exit": "0.0.2",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-load-plugins": "^1.2.0",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-rev": "^8.1.1",
|
||||
"gulp-rev-replace": "^0.4.3",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-useref": "^3.0.5",
|
||||
"jeet": "^7.2.0",
|
||||
"node-bourbon": "^4.2.3",
|
||||
"node-notifier": "^5.2.1",
|
||||
"require-globify": "^1.3.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"watchify": "^3.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.3.14",
|
||||
"d3": "^3.5.16",
|
||||
"fancy-log": "^1.3.2",
|
||||
"history": "^2.0.1",
|
||||
"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"
|
||||
}
|
||||
}
|