frontend facelift with jsx and babel
parent
e7366ebfcb
commit
219c9136e6
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
["@babel/plugin-transform-react-jsx", {
|
||||
"pragma": "m",
|
||||
"pragmaFrag": "'['"
|
||||
}]
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,10 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/plugin-transform-react-jsx": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"babel-loader": "^8.1.0",
|
||||
"bulma": "^0.7.5",
|
||||
"chart.js": "^2.8.0",
|
||||
"css-loader": "^3.2.1",
|
||||
|
@ -20,11 +24,11 @@
|
|||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"mithril": "^2.0.4",
|
||||
"node-sass": "^4.13.0",
|
||||
"npm-watch": "^0.6.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"npm-watch": "^0.6.0"
|
||||
"webpack-cli": "^3.3.9"
|
||||
},
|
||||
"watch": {
|
||||
"sandbox-server": {
|
||||
|
|
|
@ -63,7 +63,7 @@ server = http.createServer(function(req, res) {
|
|||
res.write(JSON.stringify({
|
||||
"time": new Date(),
|
||||
"temperature": 24,
|
||||
"pressure": 1013.25,
|
||||
"pressure": 101361.7,
|
||||
"humidity": 55,
|
||||
"battery": {
|
||||
"voltage": 3.6,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import m from 'mithril';
|
||||
import Navbar from '../components/Navbar';
|
||||
|
||||
class Layout {
|
||||
view(vnode) {
|
||||
const { children } = vnode;
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout;
|
|
@ -0,0 +1,15 @@
|
|||
import m from 'mithril';
|
||||
|
||||
class Measurement {
|
||||
view(vnode) {
|
||||
const { title, value } = vnode.attrs;
|
||||
return (
|
||||
<div>
|
||||
<p class="heading">{title}</p>
|
||||
<p class="title">{value}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Measurement;
|
|
@ -0,0 +1,17 @@
|
|||
import m from 'mithril';
|
||||
|
||||
class MeasurementList {
|
||||
view(vnode) {
|
||||
return (
|
||||
<div class="level">
|
||||
{vnode.children.map( child => (
|
||||
<div class="level-item has-text-centered">
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MeasurementList;
|
|
@ -0,0 +1,88 @@
|
|||
import m from 'mithril';
|
||||
import { Link } from 'mithril/route'
|
||||
import UserPrefs from '../models/UserPrefs';
|
||||
import Weather from '../models/weather';
|
||||
|
||||
class Navbar {
|
||||
|
||||
oninit() {
|
||||
UserPrefs.load();
|
||||
}
|
||||
|
||||
view() {
|
||||
const batteryClass = 'is-success'; // TODO: determine from battery
|
||||
const batteryPercentage = 65;
|
||||
const batteryVoltage = 3.6;
|
||||
const batteryIsCharging = false;
|
||||
const menuActiveClass = Navbar.isMenuActive ? 'is-active' : '';
|
||||
|
||||
return (
|
||||
<nav class="navbar" role="navigation" aria-label="main-navigation">
|
||||
<div class="navbar-brand">
|
||||
<div class="navbar-item">
|
||||
<Link href="/">
|
||||
<img src="/favicon.ico" alt="uWeather" width="28" height="28" />
|
||||
</Link>
|
||||
</div>
|
||||
<div class="navbar-burger" role="button" aria-label="menu" aria-expanded="false" onclick={()=>{ Navbar.isMenuActive = !Navbar.isMenuActive}}>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class={`navbar-menu ${menuActiveClass}`}>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">
|
||||
<progress class={`progress is-small ${batteryClass}`}
|
||||
style="width: 32px;"
|
||||
value={batteryPercentage}
|
||||
max="100">
|
||||
{batteryPercentage}%
|
||||
</progress>
|
||||
</div>
|
||||
<div class="navbar-dropdown is-right">
|
||||
<div class="navbar-item">{batteryVoltage} volts</div>
|
||||
<div class="navbar-item">{batteryPercentage} %</div>
|
||||
<div class="navbar-item">{batteryIsCharging ? 'Charging' : 'Not charging'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">
|
||||
<a>🔧</a>
|
||||
</div>
|
||||
<div class="navbar-dropdown is-right">
|
||||
<div class="navbar-item checkbox">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="checkbox" checked={UserPrefs.prefs.useImperial} onclick={()=>{UserPrefs.set('useImperial',!UserPrefs.prefs.useImperial);}} />
|
||||
Convert to Imperial (°F, inHg)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item checkbox">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="checkbox" checked={UserPrefs.prefs.liveUpdate} onclick={()=>{UserPrefs.set('liveUpdate',!UserPrefs.prefs.liveUpdate);}} />
|
||||
Live update
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<button class="button is-small is-info" disabled={UserPrefs.prefs.liveUpdate} onclick={Weather.loadCurrent} >Refresh</button>
|
||||
</div>
|
||||
<hr class="navbar-divider"/>
|
||||
<a class="navbar-item" href="/setup">Setup</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Navbar.isMenuActive = false;
|
||||
|
||||
export default Navbar;
|
|
@ -1,6 +1,4 @@
|
|||
require('./style.scss');
|
||||
var m = require('mithril');
|
||||
var Weather = require("./models/weather.js");
|
||||
var CurrentView = require('./views/CurrentView');
|
||||
import m from 'mithril';
|
||||
import CurrentConditions from './views/CurrentConditions';
|
||||
|
||||
m.mount(document.body, CurrentView);
|
||||
m.mount(document.body, CurrentConditions);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
const UserPrefs = {
|
||||
|
||||
prefs: {
|
||||
useImperial: false,
|
||||
liveUpdate: true,
|
||||
},
|
||||
loaded: false,
|
||||
|
||||
load: function() {
|
||||
if (UserPrefs.loaded) return;
|
||||
const localPrefs = window.localStorage.getItem('user-prefs');
|
||||
if (localPrefs) {
|
||||
const prefsObj = JSON.parse(localPrefs);
|
||||
UserPrefs.prefs = { ...UserPrefs.prefs, ...prefsObj };
|
||||
}
|
||||
UserPrefs.loaded = true;
|
||||
},
|
||||
|
||||
set: function(name, value) {
|
||||
UserPrefs.prefs[name] = value;
|
||||
UserPrefs.save();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
window.localStorage.setItem('user-prefs', JSON.stringify(UserPrefs.prefs));
|
||||
}
|
||||
}
|
||||
|
||||
export default UserPrefs
|
|
@ -0,0 +1,88 @@
|
|||
import m from 'mithril';
|
||||
import Layout from '../components/Layout';
|
||||
import Weather from '../models/weather';
|
||||
import MeasurementList from '../components/MeasurementList';
|
||||
import Measurement from '../components/Measurement';
|
||||
import UserPrefs from '../models/UserPrefs';
|
||||
|
||||
|
||||
function localeTemperature(celsius)
|
||||
{
|
||||
if (UserPrefs.prefs.useImperial) {
|
||||
const f = cToF(celsius);
|
||||
return `${f.toFixed(1)} °F`;
|
||||
}
|
||||
return `${celsius.toFixed(1)} °C`;
|
||||
}
|
||||
|
||||
function localePressure(hpa)
|
||||
{
|
||||
if (UserPrefs.prefs.useImperial) {
|
||||
const inHg = hpa * 0.00029529980164712;
|
||||
return `${inHg.toFixed(2)} inHg`;
|
||||
}
|
||||
const millibar = hpa * 0.01;
|
||||
return `${millibar.toFixed(0)} mb`;
|
||||
}
|
||||
|
||||
function cToF(celsius)
|
||||
{
|
||||
return celsius * 1.8 + 32;
|
||||
}
|
||||
|
||||
function fToC(f)
|
||||
{
|
||||
return (f - 32) / 1.8;
|
||||
}
|
||||
|
||||
function feelsLike(celsius, rh)
|
||||
{
|
||||
const f = cToF(celsius);
|
||||
const hindex = -42.379 + (2.04901523 * f) + (10.14333127 * rh)
|
||||
- (0.22475541 * f * rh) - (6.83783e-3 * f * f)
|
||||
- (5.481717e-2 * rh * rh) + (1.22874e-3 * f * f * rh)
|
||||
+ (8.5282e-4 * f * rh * rh) - (1.99e-6 * f * f * rh * rh);
|
||||
return fToC(hindex);
|
||||
}
|
||||
|
||||
function dewPoint(celsius, rh)
|
||||
{
|
||||
const a = 6.1121;
|
||||
const b = 18.678;
|
||||
const c = 257.14;
|
||||
const d = 234.5;
|
||||
const e = 2.7182818284;
|
||||
|
||||
const trh = Math.log(rh / 100) + (b*celsius/(c + celsius));
|
||||
return (c * trh / (b-trh));
|
||||
}
|
||||
|
||||
class CurrentConditions {
|
||||
oninit() {
|
||||
return Weather.loadCurrent();
|
||||
}
|
||||
|
||||
view() {
|
||||
if (!Weather.current) {
|
||||
return (
|
||||
<Layout>
|
||||
<div class="notification">Loading current conditions</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Layout>
|
||||
<h1 class="title">Current Conditions</h1>
|
||||
<MeasurementList>
|
||||
<Measurement title="Temperature" value={localeTemperature(Weather.current.temperature)} />
|
||||
<Measurement title="Feels like" value={localeTemperature(feelsLike(Weather.current.temperature, Weather.current.humidity))} />
|
||||
<Measurement title="Pressure" value={localePressure(Weather.current.pressure)} />
|
||||
<Measurement title="Humidity" value={`${Weather.current.humidity}%`} />
|
||||
<Measurement title="Dew Point" value={localeTemperature(dewPoint(Weather.current.temperature, Weather.current.humidity))} />
|
||||
</MeasurementList>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CurrentConditions;
|
|
@ -11,7 +11,15 @@ module.exports = {
|
|||
filename: 'js/[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
rules: [
|
||||
{
|
||||
test:/\.js$/,
|
||||
exclude: /\/node_modules\//,
|
||||
use: {
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
|
|
Loading…
Reference in New Issue