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

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...');
}
}
}