From 8bfef3c0726142198c3d57586d0cb67839299eca Mon Sep 17 00:00:00 2001 From: Anton Tranelis Date: Thu, 9 May 2024 11:03:49 +0200 Subject: [PATCH] allow to place popups ontop of geojson elements --- package-lock.json | 6 +- src/Components/Map/UtopiaMap.css | 4 +- src/Components/Map/UtopiaMap.tsx | 80 ++++++------------- .../Map/hooks/useSelectPosition.tsx | 55 ++++++++++++- 4 files changed, 81 insertions(+), 64 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2be1293..17008f16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -906,9 +906,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001547", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", - "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", + "version": "1.0.30001617", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", + "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", "dev": true, "funding": [ { diff --git a/src/Components/Map/UtopiaMap.css b/src/Components/Map/UtopiaMap.css index 4b59a0d7..868d7feb 100644 --- a/src/Components/Map/UtopiaMap.css +++ b/src/Components/Map/UtopiaMap.css @@ -1,13 +1,11 @@ .leaflet-container { text-align: left; - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); background-repeat: no-repeat; background-attachment: fixed; background-position: 50% 80%; } - - input { box-sizing: border-box; } diff --git a/src/Components/Map/UtopiaMap.tsx b/src/Components/Map/UtopiaMap.tsx index 5cb90b71..7716768f 100644 --- a/src/Components/Map/UtopiaMap.tsx +++ b/src/Components/Map/UtopiaMap.tsx @@ -1,7 +1,7 @@ import { TileLayer, MapContainer, useMapEvents, GeoJSON } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import * as React from "react"; -import { Geometry, Item, LayerProps, UtopiaMapProps } from "../../types" +import { UtopiaMapProps } from "../../types" import "./UtopiaMap.css" import { LatLng } from "leaflet"; import MarkerClusterGroup from 'react-leaflet-cluster' @@ -14,21 +14,10 @@ import { QuestControl } from "./Subcomponents/Controls/QuestControl"; import { Control } from "./Subcomponents/Controls/Control"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { TagsControl } from "./Subcomponents/Controls/TagsControl"; -import { useSelectPosition, useSetSelectPosition } from "./hooks/useSelectPosition"; -import { useUpdateItem } from "./hooks/useItems"; -import { toast } from "react-toastify"; +import { useSelectPosition, useSetMapClicked,useSetSelectPosition } from "./hooks/useSelectPosition"; import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef"; import { Feature, Geometry as GeoJSONGeometry } from 'geojson'; - - -export interface MapEventListenerProps { - selectNewItemPosition: LayerProps | Item | null, - setSelectNewItemPosition: React.Dispatch, - setItemFormPopup: React.Dispatch> -} - - // for refreshing map on resize (needs to be implemented) const mapDivRef = React.createRef(); @@ -38,60 +27,38 @@ function UtopiaMap({ center = [50.6, 9.5], zoom = 10, children, - geo} + geo } : UtopiaMapProps) { - function MapEventListener(props: MapEventListenerProps) { + function MapEventListener() { useMapEvents({ click: (e) => { - let params = new URLSearchParams(window.location.search); - window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`) - document.title = document.title.split("-")[0]; - document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title); - document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`); + resetMetaTags(); console.log(e.latlng.lat + ',' + e.latlng.lng); - if (selectNewItemPosition != null) { - if ('menuIcon' in selectNewItemPosition) { - props.setItemFormPopup({ layer: props.selectNewItemPosition, position: e.latlng }) - props.setSelectNewItemPosition(null) - } - if ('text' in selectNewItemPosition) { - const position = new Geometry(e.latlng.lng,e.latlng.lat); - itemUpdate({...selectNewItemPosition as Item, position: position }) - setSelectNewItemPosition(null); - } - } + selectNewItemPosition && setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }) }, moveend: (e) => { console.log(e); - }, - + } }) return null } - const itemUpdate = async (updatedItem: Item) => { - let success = false; - try { - await updatedItem?.layer?.api?.updateItem!({id: updatedItem.id, position: updatedItem.position }) - success = true; - } catch (error) { - toast.error(error.toString()); - } - if (success) { - updateItem(updatedItem) - toast.success("Item position updated"); - navigate("/" + updatedItem.layer?.name + "/" + updatedItem.id) - } + const resetMetaTags = () => { + let params = new URLSearchParams(window.location.search); + window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`) + document.title = document.title.split("-")[0]; + document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title); + document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`); } + const selectNewItemPosition = useSelectPosition(); const setSelectNewItemPosition = useSetSelectPosition(); const location = useLocation(); - const updateItem = useUpdateItem(); - const navigate = useNavigate(); const setClusterRef = useSetClusterRef(); const clusterRef = useClusterRef(); + const setMapClicked = useSetMapClicked(); const [itemFormPopup, setItemFormPopup] = useState(null); @@ -102,9 +69,9 @@ function UtopiaMap({ const onEachFeature = (feature: Feature, layer: L.Layer) => { if (feature.properties) { - layer.bindPopup(feature.properties.name); - console.log(feature); - + layer.bindPopup(feature.properties.name); + console.log(feature); + } } @@ -125,7 +92,7 @@ function UtopiaMap({ maxZoom={19} attribution='© OpenStreetMap contributors' url="https://tile.osmand.net/hd/{z}/{x}/{y}.png" /> - setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}> + setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}> { React.Children.toArray(children).map((child) => React.isValidElement<{ setItemFormPopup: React.Dispatch>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject }>(child) ? @@ -133,8 +100,13 @@ function UtopiaMap({ ) } - {geo && } - + {geo && { + e.layer!.closePopup(); + selectNewItemPosition && setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }) + }, + }} />} + {selectNewItemPosition != null && diff --git a/src/Components/Map/hooks/useSelectPosition.tsx b/src/Components/Map/hooks/useSelectPosition.tsx index 0b860405..464d0409 100644 --- a/src/Components/Map/hooks/useSelectPosition.tsx +++ b/src/Components/Map/hooks/useSelectPosition.tsx @@ -1,8 +1,16 @@ import { createContext, useContext, useEffect, useState } from "react"; -import { Item, LayerProps } from '../../../types'; +import { Geometry, Item, LayerProps } from '../../../types'; import { useUpdateItem } from "./useItems"; import { toast } from "react-toastify"; import { useHasUserPermission } from "./usePermissions"; +import { LatLng } from "leaflet"; +import { ItemFormPopupProps } from "../Subcomponents/ItemFormPopup"; +import { useNavigate } from "react-router-dom"; + +type PolygonClickedProps = { + position: LatLng + setItemFormPopup: React.Dispatch> +} type UseSelectPositionManagerResult = ReturnType; @@ -10,26 +18,46 @@ const SelectPositionContext = createContext({ selectPosition: null, setSelectPosition: () => { }, setMarkerClicked: () => { }, + setMapClicked: () => {}, }); function useSelectPositionManager(): { selectPosition: Item | LayerProps | null; setSelectPosition: React.Dispatch>; setMarkerClicked: React.Dispatch>; + setMapClicked: React.Dispatch>; } { const [selectPosition, setSelectPosition] = useState(null); const [markerClicked, setMarkerClicked] = useState(); + const [mapClicked, setMapClicked] = useState(); const updateItem = useUpdateItem(); const hasUserPermission = useHasUserPermission(); + useEffect(() => { if (selectPosition && markerClicked && 'text' in selectPosition) { - itemUpdate({ ...selectPosition, parent: markerClicked.id }) + itemUpdateParent({ ...selectPosition, parent: markerClicked.id }) } }, [markerClicked]) - const itemUpdate = async (updatedItem: Item) => { + useEffect(() => { + if (selectPosition != null) { + if ('menuIcon' in selectPosition) { + mapClicked && mapClicked.setItemFormPopup({ layer: selectPosition as LayerProps, position: mapClicked?.position }) + setSelectPosition(null) + } + if ('text' in selectPosition) { + const position = mapClicked?.position.lng && new Geometry(mapClicked?.position.lng, mapClicked?.position.lat); + position && itemUpdatePosition({ ...selectPosition as Item, position: position }) + setSelectPosition(null); + } + } + + }, [mapClicked]) + + + const itemUpdateParent = async (updatedItem: Item) => { if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, "update", markerClicked)) { let success = false; try { @@ -53,6 +81,20 @@ function useSelectPositionManager(): { } + const itemUpdatePosition = async (updatedItem: Item) => { + let success = false; + try { + await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, position: updatedItem.position }) + success = true; + } catch (error) { + toast.error(error.toString()); + } + if (success) { + updateItem(updatedItem) + toast.success("Item position updated"); + } + } + const linkItem = async (id: string) => { if (markerClicked) { let new_relations = markerClicked.relations || []; @@ -75,7 +117,7 @@ function useSelectPositionManager(): { } } } - return { selectPosition, setSelectPosition, setMarkerClicked }; + return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked }; } export const SelectPositionProvider: React.FunctionComponent<{ @@ -99,4 +141,9 @@ export const useSetSelectPosition = (): UseSelectPositionManagerResult["setSelec export const useSetMarkerClicked = (): UseSelectPositionManagerResult["setMarkerClicked"] => { const { setMarkerClicked } = useContext(SelectPositionContext); return setMarkerClicked; +} + +export const useSetMapClicked = (): UseSelectPositionManagerResult["setMapClicked"] => { + const { setMapClicked } = useContext(SelectPositionContext); + return setMapClicked; } \ No newline at end of file