Skip to content

Commit

Permalink
Station groups (#89)
Browse files Browse the repository at this point in the history
* add inset map

* add margins for the inset map

* refactor redux logic to prepare for grouping

* fix redux filter logic

* implemented station hierarchy and multi-level zoom

* finish ocean region summary card

* add station cards & adjust map styling

* fixed bug related to simultaneous scrolling actions

* add station pin icon

* fix lint errors

* fix typos
  • Loading branch information
GalMunGral authored Oct 4, 2023
1 parent bc37361 commit 7d15660
Show file tree
Hide file tree
Showing 26 changed files with 1,431 additions and 534 deletions.
72 changes: 55 additions & 17 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { StrictMode, Suspense } from 'react';
import React, { FC, StrictMode, Suspense } from 'react';
// eslint-disable-next-line import/no-unresolved
import { createRoot } from 'react-dom/client';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
Expand All @@ -7,6 +7,8 @@ import CssBaseline from '@mui/material/CssBaseline';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';

import axios from 'axios';
import { Feature, MultiPolygon, Polygon } from '@turf/turf';
import { getData } from './store/api';
import { DataActionDispatcherContext, DataStateContext } from './store/contexts';
import { dataReducers } from './store/reducers';
Expand All @@ -15,29 +17,65 @@ import { theme } from './theme';
import routes from './routes';
import Loading from './components/Loading';

import faoAreasUrl from './files/fao_areas.geojson';
import { normalizeFaoAreaGeometry } from './components/Map/utils';

window.API_PATH = `${window.API_SERVER}/api/v1`;
window.API_FONTS = `${window.API_SERVER}/fonts`;

const App = (): JSX.Element => {
const App: FC = () => {
const [dataState, dataActionDispatcher] = React.useReducer(dataReducers, dataStateInitialValue);
const [initialized, setInitialized] = React.useState(false);

React.useEffect(function initialize() {
axios.get(faoAreasUrl).then((res) => {
const features = res.data.features as Array<
Feature<
Polygon | MultiPolygon,
{
F_AREA: string;
NAME_EN: string;
OCEAN: string;
}
>
>;
const data = features
.map<FAOArea>(({ geometry, properties: { F_AREA, NAME_EN, OCEAN } }) => ({
code: Number(F_AREA),
name: NAME_EN,
ocean: OCEAN,
geometry: normalizeFaoAreaGeometry(geometry)
}))
.sort((a, b) => a.name.localeCompare(b.name));

React.useEffect(() => {
getData<SpeciesSummary[]>(
'species/all/?order_by=matched_canonical_full_name',
(species) => {
dataActionDispatcher({ type: 'updateAllSpecies', species });
},
() => undefined
);
getData<StationSummary[]>(
'stations/all/?order_by=order',
(stations) => {
dataActionDispatcher({ type: 'updateStations', stations });
},
() => undefined
);
dataActionDispatcher({
type: 'loadFAOAreas',
faoAreas: data
});

Promise.all([
getData<StationSummary[]>(
'stations/all/?order_by=order',
(stations) => {
dataActionDispatcher({ type: 'loadStations', stations });
},
console.error
),
getData<SpeciesSummary[]>(
'species/all/?order_by=matched_canonical_full_name',
(species) => {
dataActionDispatcher({ type: 'updateAllSpecies', species });
},
console.error
)
]).then(() => {
setInitialized(true);
});
}, console.error);
}, []);

if (!initialized) return null;

return (
<StrictMode>
<Router>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Explore/LeftSidebar/InsetMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const InsetMap: FC = () => {
source: 'shadow',
type: 'fill',
paint: {
'fill-color': theme.palette.explore.main,
'fill-color': 'black',
'fill-opacity': 0.4
}
} as maplibregl.FillLayerSpecification);
Expand Down
158 changes: 158 additions & 0 deletions src/components/Explore/LeftSidebar/StationDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { useCallback, useContext } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Icon from '@mui/material/Icon';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';

import { DataActionDispatcherContext, DataStateContext } from '../../../store/contexts';
import { useStationDetails } from '../../../utils/hooks';
import DownloadButton from '../../DownloadButton';
import Loading from '../../Loading';
import TabsGroup from '../../TabsGroup';
import StationDetails from '../../Station/Details';
import StationEnvironment from '../../Station/Environment';
import StationSpecies from '../../Station/Species';
import StationText from '../../Station/Text';

const StationDetail = () => {
const dataActionDispatcher = useContext(DataActionDispatcherContext);
const { filteredStations, selectedFaoArea, selectedStation } = useContext(DataStateContext);

const selectedStationDetails = useStationDetails(selectedStation?.name);

const StationPanel = React.useCallback(
() => (selectedStationDetails ? <StationDetails station={selectedStationDetails} /> : null),
[selectedStationDetails]
);
const EnvironmentPanel = React.useCallback(
() => (selectedStationDetails ? <StationEnvironment station={selectedStationDetails} /> : null),
[selectedStationDetails]
);
const SpeciesPanel = React.useCallback(
() => (selectedStationDetails ? <StationSpecies station={selectedStationDetails} /> : null),
[selectedStationDetails]
);
const TextPanel = React.useCallback(
() => (selectedStationDetails ? <StationText station={selectedStationDetails} /> : null),
[selectedStationDetails]
);

const onNavigate = useCallback(
(navigate_to: string) => {
if (!selectedStation) return;
if (!selectedFaoArea || selectedFaoArea?.code !== selectedStation.fao_area) {
// throw '[Invalid State]: A station can only be selected after a FAO area is selected!';
return;
}
const group = filteredStations.find((g) => g.faoArea.code === selectedFaoArea.code);
if (!group) {
throw new Error('[Invalid State]: FAO area can only be selected from filtered results!');
}
const stations = group.stations;
const index = stations.findIndex((station) => station.name === selectedStation?.name);
const newIndex =
navigate_to === 'forward'
? (index + 1 + stations.length) % stations.length
: (index - 1 + stations.length) % stations.length;
dataActionDispatcher({
type: 'updateSelectedStation',
station: stations[newIndex]
});
},
[selectedStation, selectedFaoArea, filteredStations]
);

return (
<Box
sx={{
width: selectedStationDetails ? 478 : 0,
pointerEvents: 'all'
}}
>
{selectedStationDetails ? (
<Box
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
background: '#fff',
p: 1,
zIndex: 1,
boxShadow: '1px 0 5px gray'
}}
>
<Stack direction="row">
{selectedFaoArea ? (
<IconButton size="medium" sx={{ mx: 'auto' }} onClick={() => onNavigate('backward')}>
<Icon baseClassName="icons">arrow_back</Icon>
</IconButton>
) : null}
<Typography variant="h5" align="center" sx={{ mx: 'auto' }}>
Station {selectedStationDetails?.name}
</Typography>
{selectedFaoArea ? (
<IconButton size="medium" sx={{ mx: 'auto' }} onClick={() => onNavigate('forward')}>
<Icon baseClassName="icons">arrow_forward</Icon>
</IconButton>
) : null}
</Stack>
{selectedStationDetails ? (
<>
<TabsGroup
sx={{ flexGrow: 1 }}
initialPanel="Station"
panels={[
{
Panel: StationPanel,
label: 'Station'
},
{
Panel: EnvironmentPanel,
label: 'Environment'
},
{
Panel: SpeciesPanel,
label: 'Species'
},
{
Panel: TextPanel,
label: 'Text'
}
]}
/>
<Stack direction="column" spacing={2} sx={{ padding: 1 }}>
<Stack direction="row" spacing={1} justifyContent="space-between">
<Button
variant="outlined"
size="small"
onClick={() =>
dataActionDispatcher({ type: 'updateSelectedStation', station: null })
}
>
Go Back
</Button>
<DownloadButton
data={selectedStationDetails}
filename={`Station-${selectedStationDetails.name}-details`}
message="Download Data"
/>
<DownloadButton
data={selectedStationDetails.species}
filename={`Station-${selectedStationDetails.name}-Species`}
message="Download All Species"
/>
</Stack>
</Stack>
</>
) : (
<Loading />
)}
</Box>
) : null}
</Box>
);
};

export default StationDetail;
Loading

0 comments on commit 7d15660

Please sign in to comment.