Skip to content

Commit

Permalink
Rewrite to react
Browse files Browse the repository at this point in the history
  • Loading branch information
raven-wing authored Nov 29, 2023
1 parent 3fbe0b5 commit 36de7ee
Show file tree
Hide file tree
Showing 10 changed files with 1,114 additions and 12,239 deletions.
13,057 changes: 942 additions & 12,115 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@problematy/goodmap",
"version": "0.1.8",
"version": "0.2.0",
"engines": {
"node": ">=16.0.0"
},
Expand All @@ -13,7 +13,8 @@
"serve": "webpack-dev-server --mode development",
"test": "jest --config jest.config.js",
"coverage": "jest --coverage --config jest.config.js",
"lint": "eslint ."
"lint-fix": "eslint src/**/*.jsx tests/**/*.jsx --fix",
"lint": "eslint src/**/*.jsx tests/**/*.jsx"
},
"files": [
"dist"
Expand All @@ -34,6 +35,7 @@
"@types/leaflet": "^1.9.0",
"babel-jest": "^28.1.1",
"babel-loader": "^8.2.5",
"css-loader": "^6.8.1",
"eslint": "^8.30.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
Expand All @@ -45,17 +47,26 @@
"jest": "^28.1.1",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.8.1",
"style-loader": "^3.3.3",
"terser-webpack-plugin": "^5.3.6",
"url-loader": "^4.1.1",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.6",
"@mui/material": "^5.14.6",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
"npm": "^10.2.4",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",
"react-leaflet-custom-control": "^1.3.5"
}
}
3 changes: 2 additions & 1 deletion src/components/Checkbox/Checkbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';

export const Checkbox = ({ name, translation, categoryName, onClick }) => (
<div className="form-check" onClick={onClick}>
<div className="form-check">
<label htmlFor={name}>
{translation}
<input
onClick={onClick}
className={`form-check-input filter ${categoryName}`}
type="checkbox"
id={name}
Expand Down
62 changes: 23 additions & 39 deletions src/components/Map/Map.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import * as ReactDOMServer from 'react-dom/server';
import ReactDOM from 'react-dom/client';
import React from 'react';
import Leaflet from 'leaflet';
import 'leaflet.markercluster';

import { Marker, Popup } from 'react-leaflet';

import { httpService } from '../../services/http/httpService';
import { FiltersForm } from '../FiltersForm/FiltersForm';
import { MarkerPopup } from '../MarkerPopup/MarkerPopup';
import { createBaseMap } from './createBaseMap';

let markers = Leaflet.markerClusterGroup();
let mainMap = null;
import { MapComponent } from './createBaseMap';

function onLocationFound(e, _map, locationMarker, circleMarker) {
const radius = e.accuracy / 2;
locationMarker.setLatLng(e.latlng);
circleMarker.setLatLng(e.latlng).setRadius(radius);
}
const mapPlaceholder = ReactDOM.createRoot(document.getElementById('map'));
const filtersPlaceholder = ReactDOM.createRoot(document.getElementById('filter-form'));

function getSelectedCheckboxesOfCategory(filterType) {
const checkedBoxesTypes = document.querySelectorAll(`.filter.${filterType}:checked`);
Expand All @@ -27,43 +20,34 @@ function getSelectedCheckboxesOfCategory(filterType) {
return types;
}

function getNewMarkers(categories) {
const markersCluster = Leaflet.markerClusterGroup();
export async function getNewMarkers(categories) {
const allCheckboxes = categories.map(([categoryString]) =>
getSelectedCheckboxesOfCategory(categoryString),
);
const filtersUrlQueryString = allCheckboxes.filter(n => n).join('&');

httpService
.getLocations(filtersUrlQueryString)
.then(response => {
response.map(x =>
Leaflet.marker(x.position)
.addTo(markersCluster)
.bindPopup(ReactDOMServer.renderToStaticMarkup(<MarkerPopup place={x} />)),
);
})
.catch(error => console.error(error));

return markersCluster;
const locations = await httpService.getLocations(filtersUrlQueryString);
return locations.map(location => (
<Marker position={location.position}>
<Popup>
<MarkerPopup place={location} />
</Popup>
</Marker>
));
}

export const repaintMarkers = categories => {
mainMap.removeLayer(markers);
markers = getNewMarkers(categories);
mainMap.addLayer(markers);
};
export async function repaintMarkers(categories) {
try {
const newMarkers = await getNewMarkers(categories);
const mainMap = <MapComponent markers={newMarkers} />;
mapPlaceholder.render(mainMap);
} catch (error) {
console.error('Error repainting markers:', error);

Check warning on line 44 in src/components/Map/Map.jsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
}
}

export const Map = async () => {
const categories = await httpService.getCategories();
mainMap = createBaseMap(onLocationFound);

mainMap.addLayer(markers);
getNewMarkers(categories);

httpService.getCategoriesData().then(categoriesData => {
const parsedCategoriesData = categoriesData.map(categoryData => categoryData[0]);
const filtersPlaceholder = ReactDOM.createRoot(document.getElementById('filter-form'));

repaintMarkers(parsedCategoriesData);
filtersPlaceholder.render(
Expand Down
105 changes: 76 additions & 29 deletions src/components/Map/components/LocationControl/LocationControl.jsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,87 @@
import Leaflet from 'leaflet';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Marker, CircleMarker, useMap } from 'react-leaflet';
import { Button, Box } from '@mui/material';
import MyLocationIcon from '@mui/icons-material/MyLocation';
import Control from 'react-leaflet-custom-control';
import ReactDOMServer from 'react-dom/server';
import L from 'leaflet';

const LocationIcon = () => {
const icon = document.createElement('img');
icon.style.width = '22px';
icon.style.height = '22px';
icon.src =
'https://cdn3.iconfinder.com/data/icons/google-material-design-icons/48/ic_my_location_48px-512.png';
const LocationButton = ({ userPosition }) => {
const map = useMap();

return icon;
const handleFlyToLocationClick = () => {
map.flyTo(userPosition, map.getZoom());
};

return (
<Control prepend position="bottomright">
<Button onClick={handleFlyToLocationClick}>
<Box
sx={{
boxShadow: 1.3,
border: 0.1,
color: 'black',
padding: 0.5,
background: 'white',
}}
>
<MyLocationIcon sx={{ color: 'black', fontSize: 22 }} />
</Box>
</Button>
</Control>
);
};

Leaflet.Control.Button = Leaflet.Control.extend({
options: {
position: 'bottomright',
},
const createLocationIcon = () => {
const locationIconJSX = <MyLocationIcon sx={{ color: 'black', fontSize: 22 }} />;
const svgLocationIcon = ReactDOMServer.renderToString(locationIconJSX);

return L.divIcon({
html: svgLocationIcon,
iconSize: [22, 22],
iconAnchor: [11, 11],
popupAnchor: [0, -11],
className: 'location-icon',
});
};

onLocationFoundCallback: (coordinates, map) =>
map.flyTo([coordinates.coords.latitude, coordinates.coords.longitude], 15),
const LocationControl = () => {
const [userPosition, setUserPosition] = useState(null);
const map = useMap();

onAdd: function (map) {
const container = Leaflet.DomUtil.create('div', 'leaflet-bar leaflet-control');
const button = Leaflet.DomUtil.create('a', 'leaflet-control-button', container);
const locationIcon = LocationIcon();
const handleLocationFound = e => {
setUserPosition(e.latlng);
map.flyTo(e.latlng, map.getZoom());
};

container.title = 'Twoja lokalizacja';
button.appendChild(locationIcon);
map.once('locationfound', handleLocationFound);

Leaflet.DomEvent.disableClickPropagation(button);
Leaflet.DomEvent.on(button, 'click', () => {
navigator.geolocation.getCurrentPosition(coordinates =>
this.onLocationFoundCallback(coordinates, map),
);
});
map.locate({ setView: false, maxZoom: 16, watch: true });

return container;
},
if (!userPosition) {
return null;
}

onRemove: function () {},
const { lat, lng } = userPosition;
const radius = userPosition.accuracy / 2 || 0;

return (
<>
<LocationButton userPosition={userPosition} />
<CircleMarker center={[lat, lng]} radius={radius} />
<Marker position={userPosition} icon={createLocationIcon()} />
</>
);
};

export { LocationControl };

const positionType = PropTypes.shape({
lat: PropTypes.number.isRequired,
lng: PropTypes.number.isRequired,
});

LocationButton.propTypes = {
userPosition: positionType.isRequired,
};

This file was deleted.

32 changes: 0 additions & 32 deletions src/components/Map/createBaseMap.js

This file was deleted.

37 changes: 37 additions & 0 deletions src/components/Map/createBaseMap.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';

import { MapContainer, TileLayer } from 'react-leaflet';
import MarkerClusterGroup from 'react-leaflet-cluster';

import { LocationControl } from './components/LocationControl/LocationControl';
import { mapConfig } from './map.config';

export const MapComponent = ({ markers }) => (
<MapContainer
center={mapConfig.initialMapCoordinates}
zoom={mapConfig.initialMapZoom}
scrollWheelZoom
style={{ height: '100%' }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&amp;copy <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
maxZoom={mapConfig.maxMapZoom}
/>

<MarkerClusterGroup>{markers}</MarkerClusterGroup>

<LocationControl />
</MapContainer>
);

MapComponent.propTypes = {
markers: PropTypes.arrayOf(
PropTypes.shape({
props: PropTypes.shape({
position: PropTypes.arrayOf(PropTypes.number),
}),
}),
).isRequired,
};
8 changes: 5 additions & 3 deletions src/services/http/httpService.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ export const httpService = {
);
},

getLocations: filtersUrlParams =>
fetch(`${DATA}?${filtersUrlParams}`, {
getLocations: async filtersUrlParams => {
const response = await fetch(`${DATA}?${filtersUrlParams}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json()),
});
return response.json();
},

getLanguages: () => fetch(LANGUAGES).then(response => response.json()),
};
Loading

0 comments on commit 36de7ee

Please sign in to comment.