sensors/mapper/src/App.js

258 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import * as leaflet from 'leaflet';
import { MapContainer, Polyline, TileLayer, useMap } from 'react-leaflet';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import axios from 'axios';
import moment from 'moment-timezone';
import RangeSlider from 'react-range-slider-input';
import './App.css';
import 'leaflet/dist/leaflet.css';
import 'react-range-slider-input/dist/style.css';
let tzcache = {};
// num: number of steps per duration
// secs: number of seconds per step
const durations = [
{id: 0, len: 'Day', win: '1m', full: '1 min', delta: [1, 'days'], format: 'HH', num: 1440, secs: 60},
{id: 1, len: 'Week', win: '3m', full: '3 min', delta: [7, 'days'], format: 'HH', num: 3360, secs: 180},
{id: 2, len: 'Month', win: '10m', full: '10 min', delta: [1, 'months'], format: 'D', num: 4380, secs: 600},
{id: 3, len: 'Year', win: '2h', full: '2 hour', delta: [1, 'years'], format: 'M/D', num: 4380, secs: 7200},
];
function useSensor(measurement, name, end, duration) {
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
const get = async() => {
setLoading(true);
try {
const api_key = localStorage.getItem('api_key', 'null');
const params = { end: end.unix(), duration: duration.len.toLowerCase(), window: duration.win, api_key: api_key };
const res = await axios.get(
'https://sensors-api.dns.t0.vc/history/'+measurement+'/'+name,
{ params: params },
);
setData((d) => (res.data));
setLoading(false);
} catch (error) {
console.log(error);
}
};
get();
}, [end, duration]);
return [data, loading];
};
function Owntracks({end, duration, range}) {
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
const coords = data.length ? data.filter(x => !range || (x.time >= range[0] && x.time <= range[1])).map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
const handleSubmit = (e) => {
e.preventDefault();
const api_key = e.target[0].value;
localStorage.setItem('api_key', api_key);
}
return (
<>
{loading ?
<p>Loading...</p>
:
coords.length ?
<MapContainer center={coords[coords.length-1]} zoom={13} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://maptiles.p.rapidapi.com/en/map/v1/{z}/{x}/{y}.png?rapidapi-key=4375b0b1d8msh0c9e7fa3efb9adfp1769dfjsnd603a0387fea'
/>
<Polyline pathOptions={{color: 'blue'}} positions={coords} />
</MapContainer>
:
<>
<p>No data</p>
<form onSubmit={handleSubmit}>
<p>
<input placeholder='API key' />
</p>
</form>
</>
}
</>
);
}
function Graphs({end, duration, range}) {
return (
<div className='container'>
<Owntracks end={end} duration={duration} range={range} />
</div>
);
}
function Menu({duration, setDuration, end, setEnd, range, setRange}) {
const [submenu, setSubmenu] = useState(false);
const [showRange, setShowRange] = useState(false);
const chooseDuration = (x) => {
setSubmenu(false);
setRange(false);
setDuration(x);
};
const chooseEnd = (x) => {
setSubmenu(false);
const newEnd = x.add(...duration.delta);
setRange(false);
setEnd(newEnd);
};
const chooseNow = (x) => {
setSubmenu(false);
setRange(false);
setEnd(moment());
};
const next = () => {
setSubmenu(false);
setRange(false);
setEnd(prevEnd => moment(prevEnd).add(...duration.delta));
}
const prev = () => {
setSubmenu(false);
setRange(false);
setEnd(prevEnd => moment(prevEnd).subtract(...duration.delta));
}
const onSlider = (slider) => {
console.log(slider);
// good luck remembering how this works
const lowOffset = slider[0] * duration.secs - duration.num * duration.secs;
const highOffset = slider[1] * duration.secs - duration.num * duration.secs;
const low = moment.unix(end.unix() + lowOffset);
const high = moment.unix(end.unix() + highOffset);
const lowStr = low.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
const highStr = high.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
console.log(lowStr, highStr);
setRange([lowStr, highStr]);
}
return (
<div className='menu'>
{showRange && <div className='range'>
{moment(range[0]).format('lll')} - {moment(range[1]).format('lll')}
</div>}
{submenu ?
<div className='submenu'>
{submenu === 'end' &&
<>
<div className='submenu-header'>
<h2>Choose start date:</h2>
<button onClick={() => setSubmenu(false)}>×</button>
</div>
<div className='datepicker'>
<Datetime
input={false}
timeFormat={false}
onChange={(x) => chooseEnd(x)}
/>
</div>
<button onClick={chooseNow}>Jump to Now</button>
</>
}
{submenu === 'duration' &&
<>
<div className='submenu-header'>
<h2>Choose duration:</h2>
<button onClick={() => setSubmenu(false)}>×</button>
</div>
{durations.map(x =>
<button key={x.id} onClick={() => chooseDuration(x)}>Last {x.len} ({x.full} data)</button>
)}
</>
}
</div>
:
<div className='time-slider'>
<RangeSlider
min={0}
max={duration.num}
defaultValue={[0, duration.num]}
onInput={onSlider}
onThumbDragStart={() => setShowRange(true)}
onThumbDragEnd={() => setShowRange(false)}
onRangeDragStart={() => setShowRange(true)}
onRangeDragEnd={() => setShowRange(false)}
/>
</div>
}
<div className='menu-container'>
<button onClick={() => prev()}>&lt;</button>
<button
onClick={() => setSubmenu('end')}
className={submenu === 'end' ? 'active' : ''}
>
{moment(end).subtract(duration.delta[0], duration.delta[1]).format('ddd MMM DD')}
</button>
<button
onClick={() => setSubmenu('duration')}
className={submenu === 'duration' ? 'active' : ''}
>
{duration.len} / {duration.win}
</button>
<button onClick={() => next()}>&gt;</button>
</div>
</div>
);
}
function App() {
const [duration, setDuration] = useState(durations[0]);
const [end, setEnd] = useState(moment());
const [range, setRange] = useState(false);
return (
<div>
<Menu
duration={duration}
setDuration={setDuration}
end={end}
setEnd={setEnd}
range={range}
setRange={setRange}
/>
<Graphs
end={end}
duration={duration}
range={range}
/>
</div>
);
}
export default App;