You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
13 KiB
JavaScript
267 lines
13 KiB
JavaScript
import React from 'react';
|
|
import { Heading, Box, Flex, Icon, Link as CLink, Button } from '@chakra-ui/core';
|
|
import { Link, Redirect } from "react-router-dom";
|
|
import DayPicker from "react-day-picker";
|
|
import "react-day-picker/lib/style.css";
|
|
|
|
import { sources } from '../sources.json';
|
|
|
|
function addDays(date, days) {
|
|
var result = new Date(date);
|
|
result.setDate(result.getDate() + days);
|
|
return result;
|
|
}
|
|
|
|
export default class Location extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = { tides: [{ date: '', time: '', distance: 0 }],
|
|
waves: false,
|
|
weather: false,
|
|
locationIndex: 0,
|
|
loaded: false,
|
|
date: new Date(new Date().toLocaleDateString()),
|
|
id: '',
|
|
name: '',
|
|
nextTideable: '',
|
|
urls: { marine: '', weather: '' },
|
|
redirectToDay: false,
|
|
showDayPicker: false,
|
|
goodTides: [],
|
|
okTides: [],
|
|
tidesDate: new Date(2000, 1, 1)
|
|
};
|
|
}
|
|
|
|
update = () => {
|
|
fetch(`/api/location/${this.state.id}/date/` + this.state.date.toISOString())
|
|
.then(res => res.json())
|
|
.then(obj => {
|
|
const waves = obj.waves.length > 0 ? obj.waves
|
|
.sort((a, b) => new Date(a.date) - new Date(b.date))[0] : false;
|
|
const highestSwell = waves ? waves.swellDetails
|
|
.reduce((r, x) => x.height > r.height ? x : r, { height: -1 }) :
|
|
false;
|
|
const windHigher = waves ? waves.windHeight > highestSwell.height : false;
|
|
const w = obj.weather;
|
|
|
|
const nextTideable = new Date(obj.nextTideable.date);
|
|
|
|
this.setState({ tides: obj.tides,
|
|
waves: waves ?
|
|
{ height: windHigher ? waves.windHeight : highestSwell.height,
|
|
direction: windHigher ? '' : highestSwell.direction,
|
|
swell: obj.waves.map(w => w.swellText),
|
|
wind: obj.waves.map(w => w.windText)
|
|
} : false,
|
|
weather: w ?
|
|
{ wind: { speed: w.windSpeed,
|
|
direction: w.windDirection },
|
|
conditions: w.shortForecast,
|
|
high: w.temperature
|
|
} : false,
|
|
name: obj.name,
|
|
loaded: true,
|
|
nextTideable: nextTideable.getFullYear() + '/' +
|
|
(nextTideable.getMonth() + 1) + '/' +
|
|
nextTideable.getDate(),
|
|
urls: obj.urls,
|
|
redirectToDay: false
|
|
});
|
|
});
|
|
}
|
|
|
|
fetchTideability = () => {
|
|
fetch(`/api/location/${this.state.id}/tide/` + this.state.date.toISOString())
|
|
.then(res => res.json())
|
|
.then(obj => this.setState({
|
|
goodTides: obj.goodTides.map(x => new Date(x))
|
|
.filter(x => x.getHours() < 22 && x.getHours() > 5),
|
|
okTides: obj.okTides.map(x => new Date(x))
|
|
.concat(obj.goodTides.map(x => new Date(x))
|
|
.filter(x => x.getHours() >= 22 && x.getHours() <= 5)),
|
|
tidesDate: this.state.date
|
|
}));
|
|
}
|
|
|
|
componentDidMount() {
|
|
const { match: { params } } = this.props;
|
|
this.setState({ id: params.id,
|
|
date: params.y ? new Date(parseInt(params.y), parseInt(params.m) - 1, parseInt(params.d)) :
|
|
this.state.date },
|
|
() => {
|
|
this.update();
|
|
this.fetchTideability();
|
|
});
|
|
}
|
|
|
|
componentDidUpdate(oldProps) {
|
|
const { match: { params } } = this.props;
|
|
const oldParams = oldProps.match.params;
|
|
const date = '' + params.y + params.m + params.d;
|
|
const oldDate = '' + oldParams.y + oldParams.m + oldParams.d;
|
|
if (params.id !== oldProps.match.params.id ||
|
|
date !== oldDate) {
|
|
this.setState({ id: params.id, loaded: false,
|
|
date: params.y ? new Date(parseInt(params.y), parseInt(params.m) - 1, parseInt(params.d)) : this.state.date
|
|
},
|
|
() => {
|
|
this.update();
|
|
// if its been a month since we fetched good/okTides
|
|
if (params.id !== oldProps.match.params.id ||
|
|
Math.abs(this.state.date - this.state.tidesDate) >= 2588400000) {
|
|
this.fetchTideability();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
dayClick = day => {
|
|
this.setState({ redirectToDay: day });
|
|
}
|
|
|
|
toggleDayPicker = () => {
|
|
this.setState({ showDayPicker: !this.state.showDayPicker });
|
|
}
|
|
|
|
render() {
|
|
if (this.state.loaded && this.state.redirectToDay) {
|
|
return (<Redirect to={`/location/${this.state.id}/` +
|
|
this.state.redirectToDay.getFullYear() + '/' +
|
|
(this.state.redirectToDay.getMonth() + 1) + '/' +
|
|
this.state.redirectToDay.getDate()} />);
|
|
} else if (this.state.loaded) {
|
|
const sourceIndex = sources.map(source => source.id).indexOf(this.state.id);
|
|
const prevLocationObj = sources[sourceIndex === 0 ?
|
|
sources.length - 1 : sourceIndex - 1];
|
|
const nextLocationObj = sources[sourceIndex === sources.length - 1 ?
|
|
0 : sourceIndex + 1];
|
|
const prevLocation =
|
|
(
|
|
<Link to={`/location/${prevLocationObj.id}/` +
|
|
this.state.date.getFullYear() + '/' +
|
|
(this.state.date.getMonth() + 1) + '/' +
|
|
this.state.date.getDate()}>
|
|
<Icon name='arrow-left' />
|
|
</Link>
|
|
);
|
|
const nextLocation =
|
|
(
|
|
<Link to={`/location/${nextLocationObj.id}/` +
|
|
this.state.date.getFullYear() + '/' +
|
|
(this.state.date.getMonth() + 1) + '/' +
|
|
this.state.date.getDate()}>
|
|
<Icon name='arrow-right' />
|
|
</Link>
|
|
);
|
|
const nextDayDate = addDays(this.state.date, 1);
|
|
const nextDay = '/location/' + this.state.id + '/' +
|
|
nextDayDate.getFullYear() + '/' +
|
|
(nextDayDate.getMonth() + 1) + '/' +
|
|
nextDayDate.getDate();
|
|
const prevDayDate = addDays(this.state.date, -1);
|
|
// const today = new Date(new Date().toLocaleDateString());
|
|
const prevDay =
|
|
'/location/' + this.state.id + '/' +
|
|
prevDayDate.getFullYear() + '/' +
|
|
(prevDayDate.getMonth() + 1) + '/' +
|
|
prevDayDate.getDate();
|
|
const { weather } = this.state;
|
|
const tideMin = Math.min.apply(Math, this.state.tides.map(x => x.distance)),
|
|
tideColor = tideMin < -0.3 ? 'green' : tideMin < 0 ? 'orange' : 'black';
|
|
const waveColor = this.state.waves.height < 5 ? 'green' :
|
|
this.state.waves.height < 10 ? 'orange' : 'red';
|
|
const conditionsScore = RegExp('rain', 'g').test(weather.conditions) ? 5 : 0;
|
|
const tempScore = weather.high >= 60 ? 0 : weather.high >= 50 ? 5 : 10;
|
|
const windSpeed = weather.wind ? weather.wind.speed : "0";
|
|
const maxWind = Math.max.apply(Math, windSpeed.match(/[0-9]+/g).map(x => parseInt(x)));
|
|
const windScore = maxWind <= 10 ? 0 : maxWind <= 20 ? 5 : 10;
|
|
const weatherScore = Math.max(conditionsScore, tempScore, windScore);
|
|
const weatherColor = weatherScore < 5 ? 'green' : weatherScore === 5 ? 'orange' : 'red';
|
|
return (
|
|
<Box bg='blue.100' p={2}>
|
|
<Heading size='2xl' as='h1' marginBottom='0.5em'>
|
|
<Flex justify='space-between' align='center'>
|
|
<Box color='gray.400'>{prevLocation}</Box>
|
|
<Box color='red.800'>{this.state.name}</Box>
|
|
<Box color='gray.400'>{nextLocation}</Box>
|
|
</Flex>
|
|
</Heading>
|
|
<Heading size='m'>
|
|
<Flex justify='space-between' align='center' marginBottom='0.5em'>
|
|
<Box color='gray.400'>
|
|
<Link to={prevDay}><Icon name='arrow-left' /></Link>
|
|
</Box>
|
|
<Button onClick={this.toggleDayPicker}
|
|
variant='outline'>
|
|
{this.state.date.toLocaleDateString()}
|
|
</Button>
|
|
<Box color='gray.400'>
|
|
<Link to={nextDay}><Icon name='arrow-right' /></Link>
|
|
</Box>
|
|
</Flex>
|
|
{this.state.showDayPicker ? (
|
|
<Flex justify='center'>
|
|
<DayPicker month={this.state.date}
|
|
selectedDays={[this.state.date]}
|
|
onDayClick={this.dayClick}
|
|
modifiers={{ goodtides: this.state.goodTides,
|
|
oktides: this.state.okTides }}
|
|
/>
|
|
</Flex>
|
|
) : (<></>)}
|
|
<Flex w='100%' justify='center' marginBottom='0.5em'>
|
|
<Button variant='outline'>
|
|
<Link to={`/location/${this.state.id}/${this.state.nextTideable}`}>Next Tideable <Icon name='arrow-right' /></Link>
|
|
</Button>
|
|
</Flex>
|
|
</Heading>
|
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
|
<Heading as='h3' size='lg' color={tideColor + '.500'}>
|
|
<CLink href={this.state.urls.tide} isExternal>
|
|
Tide <Icon name='external-link' />
|
|
</CLink>
|
|
</Heading>
|
|
{this.state.tides.map((tide, i) => (
|
|
<Box key={i}>
|
|
Level: {tide.distance} ft
|
|
Time: {new Date(tide.date).toLocaleTimeString()}
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
{this.state.waves ? (
|
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
|
<Heading as='h3' size='lg' color={waveColor + '.500'}>
|
|
<CLink href={this.state.urls.marine} isExternal>
|
|
Waves <Icon name='external-link' />
|
|
</CLink>
|
|
</Heading>
|
|
<Flex direction='column'>
|
|
<Box>
|
|
Max waves: {this.state.waves.height}ft {this.state.waves.direction}
|
|
</Box>
|
|
</Flex>
|
|
</Box>
|
|
) : (<></>)}
|
|
{this.state.weather ? (
|
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
|
<Heading as='h3' size='lg' color={weatherColor + '.500'}>
|
|
<CLink href={this.state.urls.weather} isExternal>
|
|
Weather <Icon name='external-link' />
|
|
</CLink>
|
|
</Heading>
|
|
<Flex direction='column'>
|
|
<Box>Conditions: {weather.conditions}</Box>
|
|
<Box>High: {weather.high}</Box>
|
|
<Box>Wind: {weather.wind.speed} {weather.wind.direction}</Box>
|
|
</Flex>
|
|
</Box>
|
|
) : (<></>)}
|
|
</Box>
|
|
);
|
|
} else {
|
|
return ('Loading...');
|
|
}
|
|
}
|
|
}
|