2020-08-16 18:45:15 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2020-10-07 21:27:30 +00:00
|
|
|
import { Layout, Select, Button } from 'antd';
|
2020-02-10 02:09:47 +00:00
|
|
|
import styled from 'styled-components';
|
2020-10-07 21:27:30 +00:00
|
|
|
|
|
|
|
import { fetchRoads, fetchCities, getShortestPath } from '../utils/requests';
|
|
|
|
import Map from './Map';
|
|
|
|
|
2020-02-10 02:09:47 +00:00
|
|
|
import 'antd/dist/antd.css';
|
|
|
|
import arrow from '../assets/arrow-right.png';
|
2020-02-09 23:12:39 +00:00
|
|
|
|
2020-10-07 21:27:30 +00:00
|
|
|
const { Header, Content } = Layout;
|
2020-02-09 18:40:34 +00:00
|
|
|
const { Option } = Select;
|
|
|
|
|
2020-10-07 21:27:30 +00:00
|
|
|
let highlightInitial = {
|
2020-10-08 14:02:30 +00:00
|
|
|
bruges: '#00856E',
|
|
|
|
ghent: '#00856E',
|
|
|
|
antwerp: '#00856E',
|
|
|
|
mechelen: '#00856E',
|
|
|
|
tournai: '#00856E',
|
|
|
|
brussels: '#00856E',
|
|
|
|
mons: '#00856E',
|
|
|
|
namur: '#00856E',
|
|
|
|
liege: '#00856E',
|
|
|
|
arlon: '#00856E',
|
2020-10-07 21:27:30 +00:00
|
|
|
};
|
|
|
|
|
2020-02-09 18:40:34 +00:00
|
|
|
function HomeView() {
|
2020-08-16 18:45:15 +00:00
|
|
|
const [startingPoint, setStartingPoint] = useState(1);
|
|
|
|
const [destination, setDestination] = useState(9);
|
|
|
|
const [errorSelect, setError] = useState({ message: '', flag: false });
|
|
|
|
const [shortestPath, setShortestPath] = useState();
|
|
|
|
const [cities, setCities] = useState([]);
|
2020-10-07 21:27:30 +00:00
|
|
|
const [highlighted, setHighlighted] = useState(highlightInitial);
|
|
|
|
|
2020-08-16 18:45:15 +00:00
|
|
|
useEffect(() => {
|
|
|
|
fetchCities()
|
|
|
|
.then((data) => setCities(data))
|
|
|
|
.catch((error) => console.log(error));
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const [roads, setRoads] = useState([]);
|
|
|
|
useEffect(() => {
|
|
|
|
fetchRoads()
|
|
|
|
.then((data) => setRoads(data))
|
|
|
|
.catch((error) => console.log(error));
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const [graph, setGraph] = useState({ nodes: [], edges: [] });
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
let nodes = cities.map((node) => ({
|
|
|
|
id: node.id,
|
|
|
|
label: node.name,
|
|
|
|
title: node.name,
|
|
|
|
}));
|
|
|
|
|
|
|
|
let edges = roads.map((edge) => ({
|
|
|
|
from: edge.start_city_id,
|
|
|
|
to: edge.end_city_id,
|
|
|
|
length: edge.distance * 2,
|
|
|
|
}));
|
|
|
|
|
|
|
|
setGraph({ nodes, edges });
|
|
|
|
}, [cities, roads, shortestPath]);
|
|
|
|
|
|
|
|
function handleStart(city_id) {
|
|
|
|
// eslint-disable-next-line
|
|
|
|
let [startingPoint] = cities.filter((city) => city.id == city_id);
|
|
|
|
setStartingPoint(startingPoint);
|
|
|
|
// Check if start and destination are the same
|
|
|
|
if (startingPoint === destination) {
|
|
|
|
setError({
|
|
|
|
message: 'The start and destination must be different.',
|
|
|
|
flag: true,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-10-07 21:27:30 +00:00
|
|
|
// Reset the error message and shown path
|
|
|
|
setHighlighted(highlightInitial);
|
2020-08-16 18:45:15 +00:00
|
|
|
setShortestPath();
|
|
|
|
setError({ flag: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleDestination(city_id) {
|
|
|
|
// eslint-disable-next-line
|
|
|
|
let [destination] = cities.filter((city) => city.id == city_id);
|
|
|
|
setDestination(destination);
|
|
|
|
// Check if start and destination are the same
|
|
|
|
if (startingPoint === destination) {
|
|
|
|
setError({
|
|
|
|
message: 'The start and destination must be different.',
|
|
|
|
flag: true,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-10-07 21:27:30 +00:00
|
|
|
// Reset the error message and shown path
|
|
|
|
setHighlighted(highlightInitial);
|
2020-08-16 18:45:15 +00:00
|
|
|
setShortestPath();
|
|
|
|
setError({ flag: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleSubmit() {
|
|
|
|
if (startingPoint === destination) {
|
|
|
|
setError({
|
|
|
|
message: 'Please select a start and a destination',
|
|
|
|
flag: true,
|
|
|
|
});
|
|
|
|
} else if (startingPoint && destination) {
|
|
|
|
getShortestPath(startingPoint.id, destination.id)
|
2020-10-07 21:27:30 +00:00
|
|
|
.then((data) => {
|
|
|
|
setShortestPath(data);
|
|
|
|
|
|
|
|
// Add cities to highlight
|
|
|
|
let updatedgHighlighted = {};
|
|
|
|
data.path.forEach((city) => {
|
2020-10-12 15:27:39 +00:00
|
|
|
updatedgHighlighted[city.name] = '#E63318';
|
2020-10-07 21:27:30 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
setHighlighted(updatedgHighlighted);
|
|
|
|
})
|
2020-08-16 18:45:15 +00:00
|
|
|
.catch((error) => console.log(error));
|
|
|
|
setError({ flag: false });
|
|
|
|
} else {
|
|
|
|
setError({
|
|
|
|
message: 'Please select a start and a destination',
|
|
|
|
flag: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2020-10-12 15:27:39 +00:00
|
|
|
<Header
|
|
|
|
style={{ backgroundColor: '#ededed', padding: '0 0 5px 0', display: 'flex', justifyContent: 'center' }}
|
|
|
|
>
|
2020-08-16 18:45:15 +00:00
|
|
|
<div style={{ margin: '0 auto', maxWidth: '800px' }}>
|
|
|
|
<InlineH1>Dijkstra | </InlineH1>
|
2020-10-12 15:27:39 +00:00
|
|
|
<InlineP>Find the shortest path between cities</InlineP>
|
2020-08-16 18:45:15 +00:00
|
|
|
</div>
|
|
|
|
</Header>
|
|
|
|
|
|
|
|
<Content>
|
2020-10-12 15:27:39 +00:00
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: 'flex',
|
|
|
|
justifyContent: 'center',
|
|
|
|
margin: '3vh 0 0 0',
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<h3>
|
|
|
|
Dockerized NodeJS backend (
|
|
|
|
<a href='https://git.ruihildt.xyz/ruihildt/dijkstra-backend'>
|
|
|
|
Git Source
|
|
|
|
</a>
|
|
|
|
) & React Frontend (
|
|
|
|
<a href='https://git.ruihildt.xyz/ruihildt/dijkstra-frontend'>
|
|
|
|
Git Source
|
2020-10-12 15:47:02 +00:00
|
|
|
</a>
|
2020-10-12 15:27:39 +00:00
|
|
|
) communicating through a rest API
|
|
|
|
</h3>
|
|
|
|
</div>
|
2020-08-16 18:45:15 +00:00
|
|
|
<Main
|
|
|
|
style={{
|
|
|
|
display: 'flex',
|
|
|
|
flexFlow: 'row wrap',
|
2020-10-07 21:27:30 +00:00
|
|
|
justifyContent: 'space-between',
|
2020-08-16 18:45:15 +00:00
|
|
|
}}
|
|
|
|
>
|
2020-10-12 15:27:39 +00:00
|
|
|
<div style={{ margin: '3vh 1vw' }}>
|
2020-08-16 18:45:15 +00:00
|
|
|
<Section>
|
|
|
|
<h2>Starting Point</h2>
|
|
|
|
<div>
|
|
|
|
<StyledSelect
|
|
|
|
defaultValue='Belgium'
|
|
|
|
disabled
|
|
|
|
></StyledSelect>
|
|
|
|
<Select
|
|
|
|
defaultValue='Select a city'
|
|
|
|
onChange={handleStart}
|
|
|
|
style={{ width: 139 }}
|
|
|
|
>
|
|
|
|
{cities.map((city) => (
|
|
|
|
<Option key={city.id}>
|
|
|
|
{city.name}
|
|
|
|
</Option>
|
|
|
|
))}
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
</Section>
|
|
|
|
<Section>
|
|
|
|
<h2>Destination</h2>
|
|
|
|
<div>
|
|
|
|
<StyledSelect
|
|
|
|
defaultValue='Belgium'
|
|
|
|
disabled
|
|
|
|
></StyledSelect>
|
|
|
|
<Select
|
|
|
|
defaultValue='Select a city'
|
|
|
|
onChange={handleDestination}
|
|
|
|
style={{ width: 139 }}
|
|
|
|
>
|
|
|
|
{cities.map((city) => (
|
|
|
|
<Option key={city.id}>
|
|
|
|
{city.name}
|
|
|
|
</Option>
|
|
|
|
))}
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
</Section>
|
|
|
|
<Section>
|
|
|
|
<Button type='primary' onClick={handleSubmit}>
|
|
|
|
Get shortest path between the cities
|
|
|
|
</Button>
|
|
|
|
{errorSelect.flag && <p>{errorSelect.message}</p>}
|
|
|
|
</Section>
|
2020-10-08 14:02:30 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
{graph.nodes.length > 0 && (
|
|
|
|
<div>
|
|
|
|
<div style={{ width: '600px' }}>
|
|
|
|
<Map highlighted={highlighted} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
2020-08-16 18:45:15 +00:00
|
|
|
|
|
|
|
{shortestPath && (
|
|
|
|
<Section>
|
|
|
|
<h2>
|
|
|
|
Shortest path from {startingPoint.name} to{' '}
|
|
|
|
{destination.name} ({shortestPath.distance}
|
|
|
|
km)
|
|
|
|
</h2>
|
|
|
|
<StyledP>
|
|
|
|
{shortestPath.path.map((city) => (
|
|
|
|
<StyledSpan
|
|
|
|
key={city.id}
|
2020-10-07 21:27:30 +00:00
|
|
|
className='cityPath'
|
2020-08-16 18:45:15 +00:00
|
|
|
>
|
|
|
|
{city.name}
|
|
|
|
</StyledSpan>
|
|
|
|
))}
|
|
|
|
</StyledP>
|
|
|
|
</Section>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</Main>
|
|
|
|
</Content>
|
|
|
|
</>
|
|
|
|
);
|
2020-02-09 18:40:34 +00:00
|
|
|
}
|
|
|
|
|
2020-08-16 18:45:15 +00:00
|
|
|
export default HomeView;
|
|
|
|
|
|
|
|
// STYLED COMPONENTS
|
|
|
|
|
|
|
|
const InlineP = styled.p`
|
|
|
|
display: inline;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const InlineH1 = styled.h1`
|
|
|
|
display: inline;
|
|
|
|
`;
|
2020-02-10 02:09:47 +00:00
|
|
|
|
|
|
|
const Main = styled.main`
|
2020-10-08 14:02:30 +00:00
|
|
|
max-width: 1000px;
|
2020-08-16 18:45:15 +00:00
|
|
|
margin: 0 auto;
|
|
|
|
`;
|
2020-02-10 02:09:47 +00:00
|
|
|
|
|
|
|
const Section = styled.section`
|
2020-08-16 18:45:15 +00:00
|
|
|
margin: 20px 0;
|
|
|
|
`;
|
2020-02-10 02:09:47 +00:00
|
|
|
|
|
|
|
const StyledSelect = styled(Select)`
|
2020-08-16 18:45:15 +00:00
|
|
|
margin-right: 10px;
|
|
|
|
`;
|
2020-02-10 02:09:47 +00:00
|
|
|
|
|
|
|
const StyledSpan = styled.span`
|
2020-08-16 18:45:15 +00:00
|
|
|
::after {
|
|
|
|
content: '';
|
|
|
|
background-image: url(${arrow});
|
|
|
|
background-size: 13px 17px;
|
|
|
|
display: inline-block;
|
|
|
|
width: 13px;
|
|
|
|
height: 17px;
|
|
|
|
margin-right: 10px;
|
|
|
|
margin-left: 10px;
|
|
|
|
padding-top: 10px;
|
|
|
|
bottom: -10px;
|
|
|
|
}
|
|
|
|
font-size: 1.5rem;
|
|
|
|
color: #40a9ff;
|
|
|
|
`;
|
2020-02-10 02:09:47 +00:00
|
|
|
|
|
|
|
const StyledP = styled.p`
|
2020-08-16 18:45:15 +00:00
|
|
|
span:last-child {
|
|
|
|
::after {
|
|
|
|
background-image: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|