mirror of
https://github.com/urbit/shrub.git
synced 2024-12-26 13:31:36 +03:00
commit
3765a8a767
@ -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]
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user