weather: changing API

added new colors

allow arbitrary location

Fixes #2634
This commit is contained in:
Tyler Brown Cifu Shuster 2020-10-27 16:53:06 -07:00
parent 6bb62d802b
commit 1a22a917c4
3 changed files with 91 additions and 106 deletions

View File

@ -83,7 +83,7 @@
?. ?=(%s -.jon)
[~ state]
=/ str=@t +.jon
=/ req=request:http (request-darksky str)
=/ req=request:http (request-wttr str)
=/ out *outbound-config:iris
=/ lismov=(list card)
[%pass /[(scot %da now.bol)] %arvo %i %request req out]~
@ -102,11 +102,11 @@
^- (list card)
[%give %fact ~[/all] %json !>((frond:enjs:format %location jon))]~
::
++ request-darksky
++ request-wttr
|= location=@t
^- request:http
=/ base 'https://api.darksky.net/forecast/634639c10670c7376dc66b6692fe57ca/'
=/ url=@t (cat 3 (cat 3 base location) '?units=auto')
=/ base 'https://wttr.in/'
=/ url=@t (cat 3 (cat 3 base location) '?format=j1')
=/ hed [['Accept' 'application/json']]~
[%'GET' url hed *(unit octs)]
::
@ -133,8 +133,9 @@
=/ jon=json
%+ frond:enjs:format %weather
%- pairs:enjs:format
:~ [%currently (~(got by p.u.ujon) 'currently')]
[%daily (~(got by p.u.ujon) 'daily')]
:~ [%current-condition (~(got by p.u.ujon) 'current_condition')]
[%weather (~(got by p.u.ujon) 'weather')]
[%nearest-area (~(got by p.u.ujon) 'nearest_area')]
==
:- [%give %fact ~[/all] %json !>(jon)]~
%= state
@ -146,7 +147,7 @@
|= [wir=wire err=(unit tang)]
^- (quip card _state)
?~ err
=/ req/request:http (request-darksky location)
=/ req/request:http (request-wttr location)
=/ out *outbound-config:iris
:_ state(timer `(add now.bol ~h3))
:~ [%pass /[(scot %da now.bol)] %arvo %i %request req out]

View File

@ -18,8 +18,8 @@ export default class LaunchApi extends BaseApi<StoreState> {
return this.launchAction({ 'change-is-shown': { name, isShown }});
}
weather(latlng: any) {
return this.action('weather', 'json', latlng);
weather(location: string) {
return this.action('weather', 'json', location);
}
private launchAction(data) {

View File

@ -1,14 +1,43 @@
import React from 'react';
import moment from 'moment';
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import Tile from './tile';
export const weatherStyleMap = {
Sunny: 'rgba(67, 169, 255, 0.4)',
PartlyCloudy: 'rgba(178, 211, 255, 0.33)',
Cloudy: 'rgba(136, 153, 176, 0.43)',
VeryCloudy: 'rgba(78, 90, 106, 0.43)',
Fog: 'rgba(100, 119, 128, 0.12)',
LightShowers: 'rgba(121, 148, 185, 0.33)',
LightSleetShowers: 'rgba(114, 130, 153, 0.33)',
LightSleet: 'rgba(155, 164, 177, 0.33)',
ThunderyShowers: 'rgba(53, 77, 103, 0.33)',
LightSnow: 'rgba(179, 182, 200, 0.33)',
HeavySnow: 'rgba(179, 182, 200, 0.33)',
LightRain: 'rgba(58, 79, 107, 0.33)',
HeavyShowers: 'rgba(36, 54, 77, 0.33)',
HeavyRain: 'rgba(5, 9, 13, 0.39)',
LightSnowShowers: 'rgba(174, 184, 198, 0.33)',
HeavySnowShowers: 'rgba(55, 74, 107, 0.33)',
ThunderyHeavyRain: 'rgba(45, 56, 66, 0.61)',
ThunderySnowShowers: 'rgba(40, 54, 79, 0.46)',
default: 'transparent'
};
const imperialCountries = [
'United States of America',
'Myanmar',
'Liberia',
];
export default class WeatherTile extends React.Component {
constructor(props) {
super(props);
this.state = {
latlong: '',
location: '',
manualEntry: false,
error: false
};
@ -17,89 +46,45 @@ export default class WeatherTile extends React.Component {
// geolocation and manual input functions
locationSubmit() {
navigator.geolocation.getCurrentPosition((res) => {
const latlng = `${res.coords.latitude},${res.coords.longitude}`;
const location = `${res.coords.latitude},${res.coords.longitude}`;
this.setState({
latlng
location
}, (err) => {
console.log(err);
}, { maximumAge: Infinity, timeout: 10000 });
this.props.api.launch.weather(latlng);
this.props.api.launch.weather(location);
this.setState({ manualEntry: !this.state.manualEntry });
});
}
manualLocationSubmit() {
manualLocationSubmit(event) {
event.preventDefault();
const gpsInput = document.getElementById('gps');
const latlngNoSpace = gpsInput.value.replace(/\s+/g, '');
const latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g;
if (latlngParse.test(latlngNoSpace)) {
const latlng = latlngNoSpace;
this.setState({ latlng }, (err) => {
const location = document.getElementById('location').value;
this.setState({ location }, (err) => {
console.log(err);
}, { maximumAge: Infinity, timeout: 10000 });
this.props.api.launch.weather(latlng);
this.props.api.launch.weather(location);
this.setState({ manualEntry: !this.state.manualEntry });
} else {
this.setState({ error: true });
return false;
}
}
// set appearance based on weather
setColors(data) {
let weatherStyle = {
bg: '',
text: ''
};
switch (data.currently.icon) {
case 'clear-day':
weatherStyle = { bg: '#E9F5FF', text: '#333' };
break;
case 'clear-night':
weatherStyle = { bg: '#14263C', text: '#fff' };
break;
case 'rain':
weatherStyle = { bg: '#2E1611', text: '#fff' };
break;
case 'snow':
weatherStyle = { bg: '#F9F9FB', text: '#333' };
break;
case 'sleet':
weatherStyle = { bg: '#EFF1F3', text: '#333' };
break;
case 'wind':
weatherStyle = { bg: '#F7FEF6', text: '#333' };
break;
case 'fog':
weatherStyle = { bg: '#504D44', text: '#fff' };
break;
case 'cloudy':
weatherStyle = { bg: '#EEF1F5', text: '#333' };
break;
case 'partly-cloudy-day':
weatherStyle = { bg: '#F3F6FA', text: '#333' };
break;
case 'partly-cloudy-night':
weatherStyle = { bg: '#283442', text: '#fff' };
break;
default:
weatherStyle = { bg: 'white', text: 'black' };
}
return weatherStyle;
// set appearance based on weather
colorFromCondition(data) {
let weatherDesc = data['current-condition'][0].weatherDesc[0].value;
return weatherStyleMap[weatherDesc] || weatherStyleMap.default;
}
// all tile views
renderWrapper(child,
weatherStyle = { bg: 'white', text: 'black' }
) {
renderWrapper(child, backgroundColor = 'white') {
return (
<Tile bg={weatherStyle.bg}>
{child}
</Tile>
<ErrorBoundary>
<Tile bg='white' backgroundColor={backgroundColor}>
{child}
</Tile>
</ErrorBoundary>
);
}
renderManualEntry() {
renderManualEntry(data) {
let secureCheck;
let error;
if (this.state.error === true) {
@ -114,6 +99,10 @@ export default class WeatherTile extends React.Component {
</Text>
);
}
let locationName;
if ('nearest-area' in data) {
locationName = data['nearest-area'][0].areaName[0].value;
}
return this.renderWrapper(
<Box
display='flex'
@ -132,21 +121,13 @@ export default class WeatherTile extends React.Component {
</Text>
{secureCheck}
<Text pb={1} mb='auto'>
Please enter your{' '}
<BaseAnchor
borderBottom='1px solid'
color='black'
href="https://latitudeandlongitude.org/"
target="_blank"
>
latitude and longitude
</BaseAnchor>
.
Please enter your location.
{locationName ? ` Current location is near ${locationName}.` : ''}
</Text>
{error}
<Box mt='auto' display='flex' marginBlockEnd='0'>
<BaseInput
id="gps"
id="location"
size="10"
width='100%'
color='black'
@ -154,11 +135,11 @@ export default class WeatherTile extends React.Component {
backgroundColor='transparent'
border='0'
type="text"
placeholder="29.55, -95.08"
autoFocus
placeholder="GPS, ZIP, City"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.manualLocationSubmit(e.target.value);
this.manualLocationSubmit(e);
}
}}
/>
@ -171,7 +152,7 @@ export default class WeatherTile extends React.Component {
fontSize='0'
border='0'
type="submit"
onClick={() => this.manualLocationSubmit()}
onClick={this.manualLocationSubmit.bind(this)}
value="->"
/>
</Box>
@ -182,7 +163,6 @@ export default class WeatherTile extends React.Component {
renderNoData() {
return this.renderWrapper(
<Box
bg='white'
display='flex'
flexDirection='column'
justifyContent='space-between'
@ -200,14 +180,16 @@ export default class WeatherTile extends React.Component {
);
}
renderWithData(data, weatherStyle) {
const c = data.currently;
const d = data.daily.data[0];
renderWithData(data) {
const locationName = data['nearest-area'][0].areaName[0].value;
const c = data['current-condition'][0];
const d = data['weather'][0];
const bg = this.colorFromCondition(data);
const sunset = moment.unix(d.sunsetTime);
const sunset = moment(d.date + ' ' + d.astronomy[0].sunset, 'YYYY-MM-DD hh:mm A');
const sunsetDiff = sunset.diff(moment(), 'hours');
const sunrise = moment.unix(d.sunriseTime);
const sunrise = moment(d.date + ' ' + d.astronomy[0].sunrise, 'YYYY-MM-DD hh:mm A');
let sunriseDiff = sunrise.diff(moment(), 'hours');
if (sunriseDiff > 24) {
@ -220,6 +202,10 @@ export default class WeatherTile extends React.Component {
? `Sun sets in ${sunsetDiff}h`
: `Sun rises in ${sunriseDiff}h`;
const temp = data['nearest-area'] && imperialCountries.includes(data['nearest-area'][0].country[0].value)
? `${Math.round(c.temp_F)}`
: `${Math.round(c.temp_C)}`;
return this.renderWrapper(
<Box
width='100%'
@ -227,12 +213,12 @@ export default class WeatherTile extends React.Component {
display='flex'
flexDirection='column'
alignItems='space-between'
title={`${locationName} Weather`}
>
<Text color={weatherStyle.text}>
<Icon icon='Weather' color={weatherStyle.text} display='inline' style={{ position: 'relative', top: '.3em' }} />
<Text>
<Icon icon='Weather' display='inline' style={{ position: 'relative', top: '.3em' }} />
Weather
<Text
color={weatherStyle.text}
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
@ -248,24 +234,22 @@ export default class WeatherTile extends React.Component {
display="flex"
flexDirection="column"
>
<Text color={weatherStyle.text}>{c.summary}</Text>
<Text color={weatherStyle.text}>{Math.round(c.temperature)}°</Text>
<Text color={weatherStyle.text}>{nextSolarEvent}</Text>
<Text>{c.weatherDesc[0].value.replace(/([a-z])([A-Z])/g, '$1 $2')}</Text>
<Text>{temp}</Text>
<Text>{nextSolarEvent}</Text>
</Box>
</Box>
, weatherStyle);
</Box>, bg);
}
render() {
const data = this.props.weather ? this.props.weather : {};
if (this.state.manualEntry === true) {
return this.renderManualEntry();
return this.renderManualEntry(data);
}
if ('currently' in data && 'daily' in data) {
const weatherStyle = this.setColors(data);
return this.renderWithData(data, weatherStyle);
if ('current-condition' in data && 'weather' in data) {
return this.renderWithData(data);
}
if (this.props.location) {