Merge pull request #3814 from tylershuster/weather

weather: change API
This commit is contained in:
matildepark 2020-11-24 14:51:20 -05:00 committed by GitHub
commit 3765a8a767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 113 deletions

View File

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

View File

@ -1,14 +1,43 @@
import React from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react'; import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import Tile from './tile'; 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 { export default class WeatherTile extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
latlong: '', location: '',
manualEntry: false, manualEntry: false,
error: false error: false
}; };
@ -17,89 +46,45 @@ export default class WeatherTile extends React.Component {
// geolocation and manual input functions // geolocation and manual input functions
locationSubmit() { locationSubmit() {
navigator.geolocation.getCurrentPosition((res) => { navigator.geolocation.getCurrentPosition((res) => {
const latlng = `${res.coords.latitude},${res.coords.longitude}`; const location = `${res.coords.latitude},${res.coords.longitude}`;
this.setState({ this.setState({
latlng location
}, (err) => { }, (err) => {
console.log(err); console.log(err);
}, { maximumAge: Infinity, timeout: 10000 }); }, { maximumAge: Infinity, timeout: 10000 });
this.props.api.launch.weather(latlng); this.props.api.launch.weather(location);
this.setState({ manualEntry: !this.state.manualEntry }); this.setState({ manualEntry: !this.state.manualEntry });
}); });
} }
manualLocationSubmit() { manualLocationSubmit(event) {
event.preventDefault(); event.preventDefault();
const gpsInput = document.getElementById('gps'); const location = document.getElementById('location').value;
const latlngNoSpace = gpsInput.value.replace(/\s+/g, ''); this.setState({ location }, (err) => {
const latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g;
if (latlngParse.test(latlngNoSpace)) {
const latlng = latlngNoSpace;
this.setState({ latlng }, (err) => {
console.log(err); console.log(err);
}, { maximumAge: Infinity, timeout: 10000 }); }, { maximumAge: Infinity, timeout: 10000 });
this.props.api.launch.weather(latlng); this.props.api.launch.weather(location);
this.setState({ manualEntry: !this.state.manualEntry }); 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) { // set appearance based on weather
case 'clear-day': colorFromCondition(data) {
weatherStyle = { bg: '#E9F5FF', text: '#333' }; let weatherDesc = data['current-condition'][0].weatherDesc[0].value;
break; return weatherStyleMap[weatherDesc] || weatherStyleMap.default;
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;
} }
// all tile views // all tile views
renderWrapper(child, renderWrapper(child, backgroundColor = 'white') {
weatherStyle = { bg: 'white', text: 'black' }
) {
return ( return (
<Tile bg={weatherStyle.bg}> <ErrorBoundary>
<Tile bg='white' backgroundColor={backgroundColor}>
{child} {child}
</Tile> </Tile>
</ErrorBoundary>
); );
} }
renderManualEntry() { renderManualEntry(data) {
let secureCheck; let secureCheck;
let error; let error;
if (this.state.error === true) { if (this.state.error === true) {
@ -114,6 +99,10 @@ export default class WeatherTile extends React.Component {
</Text> </Text>
); );
} }
let locationName;
if ('nearest-area' in data) {
locationName = data['nearest-area'][0].areaName[0].value;
}
return this.renderWrapper( return this.renderWrapper(
<Box <Box
display='flex' display='flex'
@ -132,21 +121,13 @@ export default class WeatherTile extends React.Component {
</Text> </Text>
{secureCheck} {secureCheck}
<Text pb={1} mb='auto'> <Text pb={1} mb='auto'>
Please enter your{' '} Please enter your location.
<BaseAnchor {locationName ? ` Current location is near ${locationName}.` : ''}
borderBottom='1px solid'
color='black'
href="https://latitudeandlongitude.org/"
target="_blank"
>
latitude and longitude
</BaseAnchor>
.
</Text> </Text>
{error} {error}
<Box mt='auto' display='flex' marginBlockEnd='0'> <Box mt='auto' display='flex' marginBlockEnd='0'>
<BaseInput <BaseInput
id="gps" id="location"
size="10" size="10"
width='100%' width='100%'
color='black' color='black'
@ -154,11 +135,11 @@ export default class WeatherTile extends React.Component {
backgroundColor='transparent' backgroundColor='transparent'
border='0' border='0'
type="text" type="text"
placeholder="29.55, -95.08" autoFocus
placeholder="GPS, ZIP, City"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); this.manualLocationSubmit(e);
this.manualLocationSubmit(e.target.value);
} }
}} }}
/> />
@ -171,7 +152,7 @@ export default class WeatherTile extends React.Component {
fontSize='0' fontSize='0'
border='0' border='0'
type="submit" type="submit"
onClick={() => this.manualLocationSubmit()} onClick={this.manualLocationSubmit.bind(this)}
value="->" value="->"
/> />
</Box> </Box>
@ -182,7 +163,6 @@ export default class WeatherTile extends React.Component {
renderNoData() { renderNoData() {
return this.renderWrapper( return this.renderWrapper(
<Box <Box
bg='white'
display='flex' display='flex'
flexDirection='column' flexDirection='column'
justifyContent='space-between' justifyContent='space-between'
@ -200,14 +180,16 @@ export default class WeatherTile extends React.Component {
); );
} }
renderWithData(data, weatherStyle) { renderWithData(data) {
const c = data.currently; const locationName = data['nearest-area'][0].areaName[0].value;
const d = data.daily.data[0]; 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 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'); let sunriseDiff = sunrise.diff(moment(), 'hours');
if (sunriseDiff > 24) { if (sunriseDiff > 24) {
@ -220,6 +202,10 @@ export default class WeatherTile extends React.Component {
? `Sun sets in ${sunsetDiff}h` ? `Sun sets in ${sunsetDiff}h`
: `Sun rises in ${sunriseDiff}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( return this.renderWrapper(
<Box <Box
width='100%' width='100%'
@ -227,12 +213,12 @@ export default class WeatherTile extends React.Component {
display='flex' display='flex'
flexDirection='column' flexDirection='column'
alignItems='space-between' alignItems='space-between'
title={`${locationName} Weather`}
> >
<Text color={weatherStyle.text}> <Text>
<Icon icon='Weather' color={weatherStyle.text} display='inline' style={{ position: 'relative', top: '.3em' }} /> <Icon icon='Weather' display='inline' style={{ position: 'relative', top: '.3em' }} />
Weather Weather
<Text <Text
color={weatherStyle.text}
cursor='pointer' cursor='pointer'
onClick={() => onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry }) this.setState({ manualEntry: !this.state.manualEntry })
@ -248,42 +234,56 @@ export default class WeatherTile extends React.Component {
display="flex" display="flex"
flexDirection="column" flexDirection="column"
> >
<Text color={weatherStyle.text}>{c.summary}</Text> <Text>{c.weatherDesc[0].value.replace(/([a-z])([A-Z])/g, '$1 $2')}</Text>
<Text color={weatherStyle.text}>{Math.round(c.temperature)}°</Text> <Text>{temp}</Text>
<Text color={weatherStyle.text}>{nextSolarEvent}</Text> <Text>{nextSolarEvent}</Text>
</Box> </Box>
</Box> </Box>, bg);
, weatherStyle);
} }
render() { render() {
const data = this.props.weather ? this.props.weather : {}; const data = this.props.weather ? this.props.weather : {};
if (this.state.manualEntry === true) { if (this.state.manualEntry === true) {
return this.renderManualEntry(); return this.renderManualEntry(data);
} }
if ('currently' in data && 'daily' in data) { if ('currently' in data) { // Old weather source
const weatherStyle = this.setColors(data); this.props.api.launch.weather(this.props.location);
return this.renderWithData(data, weatherStyle); }
if ('current-condition' in data && 'weather' in data) {
return this.renderWithData(data);
} }
if (this.props.location) { if (this.props.location) {
return this.renderWrapper(( return this.renderWrapper(
<Box <Box
p='2'
width='100%' width='100%'
height='100%' height='100%'
backgroundColor='white' backgroundColor='white'
color='black' color='black'
display="flex"
flexDirection="column"
justifyContent="flex-start"
> >
<Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} /> <Text><Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
<Text>Weather</Text> <Text width='100%' display='flex' flexDirection='column' mt={1}>
<Text pt='2' width='100%' display='flex' flexDirection='column'>
Loading, please check again later... Loading, please check again later...
</Text> </Text>
<Text mt="auto">
Set new location{' '}
<Text
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
</Text>
</Text>
</Box> </Box>
)); );
} }
return this.renderNoData(); return this.renderNoData();
} }