Ability to add, remove, update radius and location of chatrooms with a leaflet
This commit is contained in:
parent
042e3b9862
commit
acff469ba0
@ -3,3 +3,36 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font: 14px/16px Arial, Helvetica, sans-serif;
|
||||||
|
background: white;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info h4 {
|
||||||
|
margin: 0 0 5px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
text-align: left;
|
||||||
|
line-height: 18px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend i {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
float: left;
|
||||||
|
margin-right: 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
@ -7,7 +7,9 @@ import { useApi } from './utils/use_api';
|
|||||||
import { useJwtRefresh } from './utils/use_jwt_refresh';
|
import { useJwtRefresh } from './utils/use_jwt_refresh';
|
||||||
import { RolesContext } from './utils/roles_context';
|
import { RolesContext } from './utils/roles_context';
|
||||||
import { parseJwt } from './utils/parse_jwt';
|
import { parseJwt } from './utils/parse_jwt';
|
||||||
|
import { Toaster } from 'react-hot-toast';
|
||||||
import './app.css';
|
import './app.css';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const [authToken, setAuthToken] = useState(null);
|
const [authToken, setAuthToken] = useState(null);
|
||||||
@ -38,6 +40,7 @@ export const App = () => {
|
|||||||
<AuthContext.Provider value={[authToken, setAuthToken]}>
|
<AuthContext.Provider value={[authToken, setAuthToken]}>
|
||||||
<ApiContext.Provider value={api}>
|
<ApiContext.Provider value={api}>
|
||||||
<RolesContext.Provider value={jwtPayload.roles}>
|
<RolesContext.Provider value={jwtPayload.roles}>
|
||||||
|
<Toaster position="top-center" />
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Router />
|
<Router />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
@ -4,6 +4,7 @@ import { ApiContext } from '../../utils/api_context';
|
|||||||
import { AuthContext } from '../../utils/auth_context';
|
import { AuthContext } from '../../utils/auth_context';
|
||||||
import { RolesContext } from '../../utils/roles_context';
|
import { RolesContext } from '../../utils/roles_context';
|
||||||
import { Button } from '../common/button';
|
import { Button } from '../common/button';
|
||||||
|
import { Map } from '../map/_map';
|
||||||
import { Ping } from './ping';
|
import { Ping } from './ping';
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
@ -33,19 +34,19 @@ export const Home = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<>
|
||||||
<h1>Welcome {user.firstName}</h1>
|
<div className="p-4">
|
||||||
<Button type="button" onClick={logout}>
|
<h1>Welcome {user.firstName}</h1>
|
||||||
Logout
|
<Button type="button" onClick={logout}>
|
||||||
</Button>
|
Logout
|
||||||
{roles.includes('admin') && (
|
|
||||||
<Button type="button" onClick={() => navigate('/admin')}>
|
|
||||||
Admin
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
{roles.includes('admin') && (
|
||||||
<section>
|
<Button type="button" onClick={() => navigate('/admin')}>
|
||||||
<Ping />
|
Admin
|
||||||
</section>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
<Map user={user} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
47
client/components/map/_map.jsx
Normal file
47
client/components/map/_map.jsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { MapContainer, TileLayer } from 'react-leaflet';
|
||||||
|
import '@geoman-io/leaflet-geoman-free';
|
||||||
|
import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css';
|
||||||
|
import Geoman from './chat_room_geoman';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { Legend } from './legend';
|
||||||
|
|
||||||
|
export const Map = ({ user, zoom }) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [position, setPosition] = useState({});
|
||||||
|
const [positionWatcher, setPositionWatcher] = useState();
|
||||||
|
|
||||||
|
zoom = zoom || 18;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
setPositionWatcher(
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
const { latitude: lat, longitude: lng } = pos.coords;
|
||||||
|
setPosition({ lat, lng });
|
||||||
|
setLoading(false);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
toast.error(err.message);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
if (!loading) {
|
||||||
|
return (
|
||||||
|
<MapContainer center={position} zoom={zoom} minZoom={15}>
|
||||||
|
<TileLayer
|
||||||
|
url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
attribution='© OpenStreetMap | © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
maxZoom={19}
|
||||||
|
/>
|
||||||
|
<Legend />
|
||||||
|
<Geoman joinRoom={console.log} userPos={position} user={user} />
|
||||||
|
</MapContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div>Getting current location...</div>;
|
||||||
|
};
|
175
client/components/map/chat_room_geoman.jsx
Normal file
175
client/components/map/chat_room_geoman.jsx
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { useLeafletContext } from '@react-leaflet/core';
|
||||||
|
import L from 'leaflet';
|
||||||
|
import markerIconPng from 'leaflet/dist/images/marker-icon.png';
|
||||||
|
import { useEffect, useContext } from 'react';
|
||||||
|
import { ApiContext } from '../../utils/api_context';
|
||||||
|
import { AuthContext } from '../../utils/auth_context';
|
||||||
|
|
||||||
|
const userPositionBubble = {
|
||||||
|
color: 'black',
|
||||||
|
fillColor: 'black',
|
||||||
|
fillOpacity: 0.6,
|
||||||
|
weight: 5,
|
||||||
|
pmIgnore: true,
|
||||||
|
radius: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinable = {
|
||||||
|
color: 'green',
|
||||||
|
weight: 1,
|
||||||
|
pmIgnore: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const unjoinable = {
|
||||||
|
color: 'red',
|
||||||
|
weight: 1,
|
||||||
|
pmIgnore: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const editable = {
|
||||||
|
color: 'blue',
|
||||||
|
weight: 1,
|
||||||
|
pmIgnore: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const icon = new L.Icon({ iconUrl: markerIconPng, iconSize: [25, 41], iconAnchor: [12, 41] });
|
||||||
|
|
||||||
|
const haversine = (p1, p2) => {
|
||||||
|
const degreesToRadians = (degrees) => degrees * (Math.PI / 180);
|
||||||
|
const delta = { lat: degreesToRadians(p2.lat - p1.lat), lng: degreesToRadians(p2.lng - p1.lng) };
|
||||||
|
const a =
|
||||||
|
Math.sin(delta.lat / 2) * Math.sin(delta.lat / 2) +
|
||||||
|
Math.cos(degreesToRadians(p1.lat)) *
|
||||||
|
Math.cos(degreesToRadians(p2.lat)) *
|
||||||
|
Math.sin(delta.lng / 2) *
|
||||||
|
Math.sin(delta.lng / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
const r = 6371 * 1000;
|
||||||
|
return r * c;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GeoMan code is heavily adapted from this codesandbox: https://codesandbox.io/s/394eq
|
||||||
|
export const Geoman = ({ user, userPos, joinRoom }) => {
|
||||||
|
const context = useLeafletContext();
|
||||||
|
const api = useContext(ApiContext);
|
||||||
|
const circleAndMarkerFromChatroom = (chatRoom) => {
|
||||||
|
const circle = new L.Circle(chatRoom.center, chatRoom.radius);
|
||||||
|
const marker = new L.Marker(chatRoom.center, { pmIgnore: !chatRoom.isEditable, icon });
|
||||||
|
circle.setStyle(
|
||||||
|
chatRoom.isEditable
|
||||||
|
? editable
|
||||||
|
: haversine(userPos, { lat: chatRoom.latitude, lng: chatRoom.longitude }) < chatRoom.radius
|
||||||
|
? joinable
|
||||||
|
: unjoinable,
|
||||||
|
);
|
||||||
|
marker.addEventListener('click', () => {
|
||||||
|
console.log(chatRoom.id);
|
||||||
|
console.log(haversine(userPos, { lat: chatRoom.latitude, lng: chatRoom.longitude }), chatRoom.radius, userPos);
|
||||||
|
});
|
||||||
|
if (!!chatRoom.isEditable) {
|
||||||
|
[circle, marker].map((x) => {
|
||||||
|
x.on('pm:edit', (e) => {
|
||||||
|
const coords = e.target.getLatLng();
|
||||||
|
marker.setLatLng(coords);
|
||||||
|
circle.setLatLng(coords);
|
||||||
|
api.put(`/chat_rooms/${chatRoom.id}`, {
|
||||||
|
...chatRoom,
|
||||||
|
latitude: coords.lat,
|
||||||
|
longitude: coords.lng,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
x.on('pm:remove', (e) => {
|
||||||
|
context.map.removeLayer(marker);
|
||||||
|
context.map.removeLayer(circle);
|
||||||
|
|
||||||
|
api.del(`/chat_rooms/${chatRoom.id}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
circle.on('pm:drag', (e) => {
|
||||||
|
marker.setLatLng(e.target.getLatLng());
|
||||||
|
});
|
||||||
|
marker.on('pm:drag', (e) => {
|
||||||
|
circle.setLatLng(e.target.getLatLng());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
[circle, marker].map((x) => x.addTo(context.map));
|
||||||
|
return [circle, marker];
|
||||||
|
};
|
||||||
|
|
||||||
|
const reRender = async () => {
|
||||||
|
const layersToRemove = [];
|
||||||
|
context.map.eachLayer((layer) => {
|
||||||
|
if (layer instanceof L.Circle || layer instanceof L.Marker) {
|
||||||
|
layersToRemove.push(layer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await api.get(`/chat_rooms?lat=${userPos.lat}&lng=${userPos.lng}`);
|
||||||
|
res.map((x) => {
|
||||||
|
circleAndMarkerFromChatroom({
|
||||||
|
center: [x.latitude, x.longitude],
|
||||||
|
...x,
|
||||||
|
isEditable: user && x.userId == user.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
layersToRemove.map((x) => context.map.removeLayer(x));
|
||||||
|
|
||||||
|
const userLocationCircle = new L.Circle(userPos, 5);
|
||||||
|
userLocationCircle.setStyle(userPositionBubble);
|
||||||
|
userLocationCircle.addTo(context.map);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (context) {
|
||||||
|
reRender();
|
||||||
|
}
|
||||||
|
}, [userPos]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const leafletContainer = context.layerContainer || context.map;
|
||||||
|
leafletContainer.pm.addControls({
|
||||||
|
drawMarker: false,
|
||||||
|
editControls: true,
|
||||||
|
dragMode: true,
|
||||||
|
cutPolygon: false,
|
||||||
|
removalMode: true,
|
||||||
|
rotateMode: false,
|
||||||
|
splitMode: false,
|
||||||
|
drawPolyline: false,
|
||||||
|
drawRectangle: false,
|
||||||
|
drawPolygon: false,
|
||||||
|
drawCircleMarker: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
|
||||||
|
|
||||||
|
leafletContainer.on('pm:create', async (e) => {
|
||||||
|
if (e.layer && e.layer.pm) {
|
||||||
|
const shape = e;
|
||||||
|
context.map.removeLayer(shape.layer);
|
||||||
|
|
||||||
|
const { lat: latitude, lng: longitude } = shape.layer.getLatLng();
|
||||||
|
const chatRoom = await api.post('/chat_rooms', {
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
radius: shape.layer.getRadius(),
|
||||||
|
});
|
||||||
|
reRender();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leafletContainer.on('pm:remove', (e) => {
|
||||||
|
console.log('object removed');
|
||||||
|
// console.log(leafletContainer.pm.getGeomanLayers(true).toGeoJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
leafletContainer.pm.removeControls();
|
||||||
|
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
|
||||||
|
};
|
||||||
|
}, [context]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Geoman;
|
28
client/components/map/legend.jsx
Normal file
28
client/components/map/legend.jsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import L from 'leaflet';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLeafletContext } from '@react-leaflet/core';
|
||||||
|
|
||||||
|
export const Legend = () => {
|
||||||
|
const context = useLeafletContext();
|
||||||
|
useEffect(() => {
|
||||||
|
const legend = L.control({ position: 'topright' });
|
||||||
|
|
||||||
|
legend.onAdd = () => {
|
||||||
|
const div = L.DomUtil.create('div', 'info legend');
|
||||||
|
let labels = [];
|
||||||
|
|
||||||
|
labels.push('<i style="background:black"></i><span>Current position</span>');
|
||||||
|
labels.push('<i style="background:red"></i><span>Unjoinable</span>');
|
||||||
|
labels.push('<i style="background:green"></i><span>Joinable</span>');
|
||||||
|
labels.push('<i style="background:blue"></i><span>Editable</span>');
|
||||||
|
|
||||||
|
div.innerHTML = labels.join('<br>');
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { map } = context;
|
||||||
|
legend.addTo(map);
|
||||||
|
}, [context]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { AuthContext } from '../utils/auth_context';
|
|||||||
import { SignIn } from './sign_in/_sign_in';
|
import { SignIn } from './sign_in/_sign_in';
|
||||||
import { SignUp } from './sign_up/_sign_up';
|
import { SignUp } from './sign_up/_sign_up';
|
||||||
import { Admin } from './admin/_admin';
|
import { Admin } from './admin/_admin';
|
||||||
|
import { Map } from './map/_map';
|
||||||
|
|
||||||
export const Router = () => {
|
export const Router = () => {
|
||||||
const [authToken] = useContext(AuthContext);
|
const [authToken] = useContext(AuthContext);
|
||||||
@ -18,6 +19,7 @@ export const Router = () => {
|
|||||||
<Route path="admin" element={<Admin />} />
|
<Route path="admin" element={<Admin />} />
|
||||||
<Route path="signin" element={<SignIn />} />
|
<Route path="signin" element={<SignIn />} />
|
||||||
<Route path="signup" element={<SignUp />} />
|
<Route path="signup" element={<SignUp />} />
|
||||||
|
<Route path="map" element={<Map />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
BIN
client/files/marker.png
Normal file
BIN
client/files/marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
@ -15,8 +15,12 @@
|
|||||||
"tailwindcss": "^2.2.19"
|
"tailwindcss": "^2.2.19"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@geoman-io/leaflet-geoman-free": "^2.11.4",
|
||||||
|
"leaflet": "^1.7.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-hot-toast": "^2.2.0",
|
||||||
|
"react-leaflet": "^3.2.5",
|
||||||
"react-router-dom": "6",
|
"react-router-dom": "6",
|
||||||
"socket.io-client": "^4.4.1"
|
"socket.io-client": "^4.4.1"
|
||||||
}
|
}
|
||||||
|
233
client/yarn.lock
233
client/yarn.lock
@ -30,6 +30,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
"@geoman-io/leaflet-geoman-free@^2.11.4":
|
||||||
|
version "2.11.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.11.4.tgz#4a43fa8d3d5d2bca751135b775c19c6cc0063699"
|
||||||
|
integrity sha512-uWfgaGDhrtoCMHdHi2oNVKb8WXFMQvyNnan1sS/+Yn5jMPuhijWFyAjy0G5kTCamXhGXg4vUvlEpiRSrBwewKg==
|
||||||
|
dependencies:
|
||||||
|
"@turf/boolean-contains" "^6.5.0"
|
||||||
|
"@turf/kinks" "^6.5.0"
|
||||||
|
"@turf/line-intersect" "^6.5.0"
|
||||||
|
"@turf/line-split" "^6.5.0"
|
||||||
|
lodash "4.17.21"
|
||||||
|
polygon-clipping "0.15.3"
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
@ -603,6 +615,11 @@
|
|||||||
chrome-trace-event "^1.0.2"
|
chrome-trace-event "^1.0.2"
|
||||||
nullthrows "^1.1.1"
|
nullthrows "^1.1.1"
|
||||||
|
|
||||||
|
"@react-leaflet/core@^1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-1.1.1.tgz#827fd05bb542cf874116176d8ef48d5b12163f81"
|
||||||
|
integrity sha512-7PGLWa9MZ5x/cWy8EH2VzI4T8q5WpuHbixzCDXqixP/WyqwIrg5NDUPgYuFnB4IEIZF+6nA265mYzswFo/h1Pw==
|
||||||
|
|
||||||
"@socket.io/base64-arraybuffer@~1.0.2":
|
"@socket.io/base64-arraybuffer@~1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61"
|
resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61"
|
||||||
@ -623,6 +640,161 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||||
|
|
||||||
|
"@turf/bbox@*", "@turf/bbox@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-6.5.0.tgz#bec30a744019eae420dac9ea46fb75caa44d8dc5"
|
||||||
|
integrity sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/bearing@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/bearing/-/bearing-6.5.0.tgz#462a053c6c644434bdb636b39f8f43fb0cd857b0"
|
||||||
|
integrity sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/boolean-contains@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz#f802e7432fb53109242d5bf57393ef2f53849bbf"
|
||||||
|
integrity sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==
|
||||||
|
dependencies:
|
||||||
|
"@turf/bbox" "^6.5.0"
|
||||||
|
"@turf/boolean-point-in-polygon" "^6.5.0"
|
||||||
|
"@turf/boolean-point-on-line" "^6.5.0"
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/boolean-point-in-polygon@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz#6d2e9c89de4cd2e4365004c1e51490b7795a63cf"
|
||||||
|
integrity sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/boolean-point-on-line@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz#a8efa7bad88760676f395afb9980746bc5b376e9"
|
||||||
|
integrity sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/destination@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/destination/-/destination-6.5.0.tgz#30a84702f9677d076130e0440d3223ae503fdae1"
|
||||||
|
integrity sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/distance@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/distance/-/distance-6.5.0.tgz#21f04d5f86e864d54e2abde16f35c15b4f36149a"
|
||||||
|
integrity sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/helpers@6.x", "@turf/helpers@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-6.5.0.tgz#f79af094bd6b8ce7ed2bd3e089a8493ee6cae82e"
|
||||||
|
integrity sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==
|
||||||
|
|
||||||
|
"@turf/invariant@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-6.5.0.tgz#970afc988023e39c7ccab2341bd06979ddc7463f"
|
||||||
|
integrity sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/kinks@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/kinks/-/kinks-6.5.0.tgz#80e7456367535365012f658cf1a988b39a2c920b"
|
||||||
|
integrity sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/line-intersect@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/line-intersect/-/line-intersect-6.5.0.tgz#dea48348b30c093715d2195d2dd7524aee4cf020"
|
||||||
|
integrity sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
"@turf/line-segment" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
geojson-rbush "3.x"
|
||||||
|
|
||||||
|
"@turf/line-segment@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/line-segment/-/line-segment-6.5.0.tgz#ee73f3ffcb7c956203b64ed966d96af380a4dd65"
|
||||||
|
integrity sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/line-split@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/line-split/-/line-split-6.5.0.tgz#116d7fbf714457878225187f5820ef98db7b02c2"
|
||||||
|
integrity sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==
|
||||||
|
dependencies:
|
||||||
|
"@turf/bbox" "^6.5.0"
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
"@turf/line-intersect" "^6.5.0"
|
||||||
|
"@turf/line-segment" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
"@turf/nearest-point-on-line" "^6.5.0"
|
||||||
|
"@turf/square" "^6.5.0"
|
||||||
|
"@turf/truncate" "^6.5.0"
|
||||||
|
geojson-rbush "3.x"
|
||||||
|
|
||||||
|
"@turf/meta@6.x", "@turf/meta@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-6.5.0.tgz#b725c3653c9f432133eaa04d3421f7e51e0418ca"
|
||||||
|
integrity sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/nearest-point-on-line@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz#8e1cd2cdc0b5acaf4c8d8b3b33bb008d3cb99e7b"
|
||||||
|
integrity sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==
|
||||||
|
dependencies:
|
||||||
|
"@turf/bearing" "^6.5.0"
|
||||||
|
"@turf/destination" "^6.5.0"
|
||||||
|
"@turf/distance" "^6.5.0"
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/invariant" "^6.5.0"
|
||||||
|
"@turf/line-intersect" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/square@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/square/-/square-6.5.0.tgz#ab43eef99d39c36157ab5b80416bbeba1f6b2122"
|
||||||
|
integrity sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==
|
||||||
|
dependencies:
|
||||||
|
"@turf/distance" "^6.5.0"
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
|
||||||
|
"@turf/truncate@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@turf/truncate/-/truncate-6.5.0.tgz#c3a16cad959f1be1c5156157d5555c64b19185d8"
|
||||||
|
integrity sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==
|
||||||
|
dependencies:
|
||||||
|
"@turf/helpers" "^6.5.0"
|
||||||
|
"@turf/meta" "^6.5.0"
|
||||||
|
|
||||||
|
"@types/geojson@7946.0.8":
|
||||||
|
version "7946.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
|
||||||
|
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
@ -1188,6 +1360,17 @@ function-bind@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||||
|
|
||||||
|
geojson-rbush@3.x:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/geojson-rbush/-/geojson-rbush-3.2.0.tgz#8b543cf0d56f99b78faf1da52bb66acad6dfc290"
|
||||||
|
integrity sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==
|
||||||
|
dependencies:
|
||||||
|
"@turf/bbox" "*"
|
||||||
|
"@turf/helpers" "6.x"
|
||||||
|
"@turf/meta" "6.x"
|
||||||
|
"@types/geojson" "7946.0.8"
|
||||||
|
rbush "^3.0.1"
|
||||||
|
|
||||||
get-port@^4.2.0:
|
get-port@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
|
resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
|
||||||
@ -1226,6 +1409,11 @@ globals@^13.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest "^0.20.2"
|
type-fest "^0.20.2"
|
||||||
|
|
||||||
|
goober@^2.1.1:
|
||||||
|
version "2.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.8.tgz#e592c04d093cb38f77b38cfcb012b7811c85765e"
|
||||||
|
integrity sha512-S0C85gCzcfFCMSdjD/CxyQMt1rbf2qEg6hmDzxk2FfD7+7Ogk55m8ZFUMtqNaZM4VVX/qaU9AzSORG+Gf4ZpAQ==
|
||||||
|
|
||||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||||
version "4.2.8"
|
version "4.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
||||||
@ -1423,6 +1611,11 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
leaflet@^1.7.1:
|
||||||
|
version "1.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
|
||||||
|
integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
|
||||||
|
|
||||||
lilconfig@^2.0.3:
|
lilconfig@^2.0.3:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
|
||||||
@ -1459,7 +1652,7 @@ lodash.uniq@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||||
|
|
||||||
lodash@^4.17.21:
|
lodash@4.17.21, lodash@^4.17.21:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
@ -1684,6 +1877,13 @@ picomatch@^2.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
|
polygon-clipping@0.15.3:
|
||||||
|
version "0.15.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/polygon-clipping/-/polygon-clipping-0.15.3.tgz#0215840438470ba2e9e6593625e4ea5c1087b4b7"
|
||||||
|
integrity sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==
|
||||||
|
dependencies:
|
||||||
|
splaytree "^3.1.0"
|
||||||
|
|
||||||
postcss-calc@^8.2.0:
|
postcss-calc@^8.2.0:
|
||||||
version "8.2.4"
|
version "8.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
|
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
|
||||||
@ -2002,6 +2202,18 @@ quick-lru@^5.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||||
|
|
||||||
|
quickselect@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
|
||||||
|
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
|
||||||
|
|
||||||
|
rbush@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf"
|
||||||
|
integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==
|
||||||
|
dependencies:
|
||||||
|
quickselect "^2.0.0"
|
||||||
|
|
||||||
react-dom@^17.0.2:
|
react-dom@^17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||||
@ -2011,6 +2223,20 @@ react-dom@^17.0.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
scheduler "^0.20.2"
|
||||||
|
|
||||||
|
react-hot-toast@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.2.0.tgz#ab6f4caed4214b9534f94bb8cfaaf21b051e62b9"
|
||||||
|
integrity sha512-248rXw13uhf/6TNDVzagX+y7R8J183rp7MwUMNkcrBRyHj/jWOggfXTGlM8zAOuh701WyVW+eUaWG2LeSufX9g==
|
||||||
|
dependencies:
|
||||||
|
goober "^2.1.1"
|
||||||
|
|
||||||
|
react-leaflet@^3.2.5:
|
||||||
|
version "3.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-3.2.5.tgz#bec0bfab9dd8c2b030ea630f7a0687a60322ca7d"
|
||||||
|
integrity sha512-Z3KZ+4SijsRbbrt2I1a3ZDY6+V6Pm91eYTdxTN18G6IOkFRsJo1BuSPLFnyFrlF3WDjQFPEcTPkEgD1VEeAoBg==
|
||||||
|
dependencies:
|
||||||
|
"@react-leaflet/core" "^1.1.1"
|
||||||
|
|
||||||
react-refresh@^0.9.0:
|
react-refresh@^0.9.0:
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
|
||||||
@ -2179,6 +2405,11 @@ source-map@~0.7.2:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||||
|
|
||||||
|
splaytree@^3.1.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-3.1.1.tgz#e1bc8e68e64ef5a9d5f09d36e6d9f3621795a438"
|
||||||
|
integrity sha512-9FaQ18FF0+sZc/ieEeXHt+Jw2eSpUgUtTLDYB/HXKWvhYVyOc7h1hzkn5MMO3GPib9MmXG1go8+OsBBzs/NMww==
|
||||||
|
|
||||||
stable@^0.1.8:
|
stable@^0.1.8:
|
||||||
version "0.1.8"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
||||||
|
@ -3,17 +3,19 @@ import { APP_GUARD } from '@nestjs/core';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { config } from './database/config';
|
import { config } from './database/config';
|
||||||
|
import { ChatRoomModule } from './modules/chat_room.module';
|
||||||
import { UsersModule } from './modules/users.module';
|
import { UsersModule } from './modules/users.module';
|
||||||
import { PingGateway } from './providers/gateways/ping.gateway';
|
import { PingGateway } from './providers/gateways/ping.gateway';
|
||||||
import { AuthGuard } from './providers/guards/auth.guard';
|
import { AuthGuard } from './providers/guards/auth.guard';
|
||||||
import { RolesGuard } from './providers/guards/roles.guard';
|
import { RolesGuard } from './providers/guards/roles.guard';
|
||||||
|
import { ChatRoomService } from './providers/services/chat_room.service';
|
||||||
import { JwtService } from './providers/services/jwt.service';
|
import { JwtService } from './providers/services/jwt.service';
|
||||||
import { RolesService } from './providers/services/roles.service';
|
import { RolesService } from './providers/services/roles.service';
|
||||||
import { UsersService } from './providers/services/users.service';
|
import { UsersService } from './providers/services/users.service';
|
||||||
import { GuardUtil } from './providers/util/guard.util';
|
import { GuardUtil } from './providers/util/guard.util';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forRoot(config), UsersModule],
|
imports: [TypeOrmModule.forRoot(config), UsersModule, ChatRoomModule],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
PingGateway,
|
PingGateway,
|
||||||
@ -21,6 +23,7 @@ import { GuardUtil } from './providers/util/guard.util';
|
|||||||
RolesService,
|
RolesService,
|
||||||
JwtService,
|
JwtService,
|
||||||
GuardUtil,
|
GuardUtil,
|
||||||
|
ChatRoomService,
|
||||||
{ provide: APP_GUARD, useClass: AuthGuard }, // auth guard should come before roles guard
|
{ provide: APP_GUARD, useClass: AuthGuard }, // auth guard should come before roles guard
|
||||||
{ provide: APP_GUARD, useClass: RolesGuard }, // otherwise users won't be authenticated before roles check
|
{ provide: APP_GUARD, useClass: RolesGuard }, // otherwise users won't be authenticated before roles check
|
||||||
],
|
],
|
||||||
|
@ -1,12 +1,55 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
||||||
|
import { JwtBody } from 'server/decorators/jwt_body.decorator';
|
||||||
|
import { JwtBodyDto } from 'server/dto/jwt_body.dto';
|
||||||
import { ChatRoomService } from 'server/providers/services/chat_room.service';
|
import { ChatRoomService } from 'server/providers/services/chat_room.service';
|
||||||
|
import { UsersService } from 'server/providers/services/users.service';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class ChatRoomController {
|
export class ChatRoomController {
|
||||||
constructor(private chatRoomService: ChatRoomService) {}
|
constructor(private chatRoomService: ChatRoomService, private usersService: UsersService) {}
|
||||||
|
|
||||||
@Get('/chat_rooms')
|
@Get('/chat_rooms')
|
||||||
async get() {
|
async get(@Query() query: any) {
|
||||||
return await this.chatRoomService.all();
|
console.log(query);
|
||||||
|
return await this.chatRoomService.near(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/chat_rooms')
|
||||||
|
async create(@JwtBody() jwtBody: JwtBodyDto, @Body() chatRoom: any) {
|
||||||
|
chatRoom.user = await this.usersService.find(jwtBody.userId);
|
||||||
|
console.log(jwtBody);
|
||||||
|
return await this.chatRoomService.create(chatRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async authorized(jwtBody: JwtBodyDto, chatRoom: any) {
|
||||||
|
const user = await this.usersService.find(jwtBody.userId);
|
||||||
|
if (user.id !== chatRoom.user.id) {
|
||||||
|
return {
|
||||||
|
error: 'You are not the owner of this chat room',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/chat_rooms/:id')
|
||||||
|
async update(@JwtBody() jwtBody: JwtBodyDto, @Param('id') id: number, @Body() chatRoom: any) {
|
||||||
|
console.log(id);
|
||||||
|
const chat_room = await this.chatRoomService.findById(id, ['user']);
|
||||||
|
if (!(await this.authorized(jwtBody, chat_room))) {
|
||||||
|
return chat_room;
|
||||||
|
}
|
||||||
|
chat_room.latitude = chatRoom.latitude;
|
||||||
|
chat_room.longitude = chatRoom.longitude;
|
||||||
|
chat_room.radius = chatRoom.radius;
|
||||||
|
return await this.chatRoomService.save(chat_room);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/chat_rooms/:id')
|
||||||
|
async delete(@JwtBody() jwtBody: JwtBodyDto, @Param('id') id: number) {
|
||||||
|
const chat_room = await this.chatRoomService.findById(id, ['user']);
|
||||||
|
if (!(await this.authorized(jwtBody, chat_room))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await this.chatRoomService.remove(chat_room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ export class UsersController {
|
|||||||
@Get('/users/me')
|
@Get('/users/me')
|
||||||
async getCurrentUser(@JwtBody() jwtBody: JwtBodyDto) {
|
async getCurrentUser(@JwtBody() jwtBody: JwtBodyDto) {
|
||||||
const user = await this.usersService.find(jwtBody.userId);
|
const user = await this.usersService.find(jwtBody.userId);
|
||||||
|
delete user.passwordHash;
|
||||||
return { user };
|
return { user };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
|
||||||
|
|
||||||
export class AddChatRoom1648605030863 implements MigrationInterface {
|
export class AddChatRoom1648605030863 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.createTable(
|
await queryRunner.createTable(
|
||||||
new Table({
|
new Table({
|
||||||
name: 'chatroom',
|
name: 'chat_room',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'id',
|
||||||
@ -13,8 +13,8 @@ export class AddChatRoom1648605030863 implements MigrationInterface {
|
|||||||
isGenerated: true,
|
isGenerated: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'userId',
|
||||||
type: 'text',
|
type: 'int',
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -35,9 +35,19 @@ export class AddChatRoom1648605030863 implements MigrationInterface {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'chat_room',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.dropTable('chatroom');
|
await queryRunner.dropTable('chat_room');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddDistanceFunction1648669551959 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// From https://stackoverflow.com/questions/61135374/postgresql-calculate-distance-between-two-points-without-using-postgis
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION calculate_distance(lat1 float, lon1 float, lat2 float, lon2 float, units varchar)
|
||||||
|
RETURNS float AS $dist$
|
||||||
|
DECLARE
|
||||||
|
dist float = 0;
|
||||||
|
radlat1 float;
|
||||||
|
radlat2 float;
|
||||||
|
theta float;
|
||||||
|
radtheta float;
|
||||||
|
BEGIN
|
||||||
|
IF lat1 = lat2 OR lon1 = lon2
|
||||||
|
THEN RETURN dist;
|
||||||
|
ELSE
|
||||||
|
radlat1 = pi() * lat1 / 180;
|
||||||
|
radlat2 = pi() * lat2 / 180;
|
||||||
|
theta = lon1 - lon2;
|
||||||
|
radtheta = pi() * theta / 180;
|
||||||
|
dist = sin(radlat1) * sin(radlat2) + cos(radlat1) * cos(radlat2) * cos(radtheta);
|
||||||
|
|
||||||
|
IF dist > 1 THEN dist = 1; END IF;
|
||||||
|
|
||||||
|
dist = acos(dist);
|
||||||
|
dist = dist * 180 / pi();
|
||||||
|
dist = dist * 60 * 1.1515;
|
||||||
|
|
||||||
|
IF units = 'K' THEN dist = dist * 1.609344; END IF;
|
||||||
|
IF units = 'N' THEN dist = dist * 0.8684; END IF;
|
||||||
|
|
||||||
|
RETURN dist;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$dist$ LANGUAGE plpgsql;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DROP FUNCTION IF EXISTS calculate_distance(lat1 float, lon1 float, lat2 float, lon2 float, units varchar);`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,11 @@
|
|||||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class ChatRoom {
|
export class ChatRoom {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
latitude: number;
|
latitude: number;
|
||||||
|
|
||||||
@ -16,4 +14,7 @@ export class ChatRoom {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
radius: number;
|
radius: number;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.chatRooms)
|
||||||
|
user: User;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
||||||
|
import { ChatRoom } from './chat_room.entity';
|
||||||
import { RefreshToken } from './refresh_token.entity';
|
import { RefreshToken } from './refresh_token.entity';
|
||||||
import { UserRole } from './user_role.entity';
|
import { UserRole } from './user_role.entity';
|
||||||
|
|
||||||
@ -24,4 +25,7 @@ export class User {
|
|||||||
|
|
||||||
@OneToMany(() => UserRole, (userRole) => userRole.user, { cascade: true })
|
@OneToMany(() => UserRole, (userRole) => userRole.user, { cascade: true })
|
||||||
userRoles: UserRole[];
|
userRoles: UserRole[];
|
||||||
|
|
||||||
|
@OneToMany(() => ChatRoom, (chatRoom) => chatRoom.user)
|
||||||
|
chatRooms: ChatRoom[];
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { ChatRoomController } from 'server/controllers/chat_room.controller';
|
import { ChatRoomController } from 'server/controllers/chat_room.controller';
|
||||||
import { ChatRoom } from 'server/entities/chat_room.entity';
|
import { ChatRoom } from 'server/entities/chat_room.entity';
|
||||||
import { ChatRoomService } from 'server/providers/services/chat_room.service';
|
import { ChatRoomService } from 'server/providers/services/chat_room.service';
|
||||||
|
import { UsersService } from 'server/providers/services/users.service';
|
||||||
|
import { UsersModule } from './users.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([ChatRoom])],
|
imports: [TypeOrmModule.forFeature([ChatRoom]), UsersModule],
|
||||||
controllers: [ChatRoomController],
|
controllers: [ChatRoomController],
|
||||||
providers: [ChatRoomService],
|
providers: [ChatRoomService, UsersService],
|
||||||
exports: [TypeOrmModule],
|
exports: [TypeOrmModule],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class ChatRoomModule {}
|
||||||
|
@ -11,9 +11,10 @@ import { RefreshTokensController } from 'server/controllers/refresh_tokens.contr
|
|||||||
import { Role } from 'server/entities/role.entity';
|
import { Role } from 'server/entities/role.entity';
|
||||||
import { RolesService } from 'server/providers/services/roles.service';
|
import { RolesService } from 'server/providers/services/roles.service';
|
||||||
import { UserRole } from 'server/entities/user_role.entity';
|
import { UserRole } from 'server/entities/user_role.entity';
|
||||||
|
import { ChatRoom } from 'server/entities/chat_room.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, RefreshToken, Role, UserRole])],
|
imports: [TypeOrmModule.forFeature([User, RefreshToken, Role, UserRole, ChatRoom])],
|
||||||
controllers: [SessionsController, UsersController, RefreshTokensController],
|
controllers: [SessionsController, UsersController, RefreshTokensController],
|
||||||
providers: [UsersService, RolesService, RefreshTokensService, JwtService],
|
providers: [UsersService, RolesService, RefreshTokensService, JwtService],
|
||||||
exports: [TypeOrmModule],
|
exports: [TypeOrmModule],
|
||||||
|
@ -18,7 +18,21 @@ export class ChatRoomService {
|
|||||||
return this.chatRoomRepository.find();
|
return this.chatRoomRepository.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
findById(id: number) {
|
near({ lat, lng }: { lat: number; lng: number }) {
|
||||||
return this.chatRoomRepository.findOne(id);
|
return this.chatRoomRepository.query(
|
||||||
|
`SELECT * FROM chat_room WHERE calculate_distance(latitude, longitude, ${lat}, ${lng}, 'M') < 5`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
findById(id: number, relations: string[] = []) {
|
||||||
|
return this.chatRoomRepository.findOne(id, { relations });
|
||||||
|
}
|
||||||
|
|
||||||
|
save(chatRoom: ChatRoom) {
|
||||||
|
return this.chatRoomRepository.save(chatRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(chatRoom: ChatRoom) {
|
||||||
|
return this.chatRoomRepository.remove(chatRoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user