mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
allow to place popups ontop of geojson elements
This commit is contained in:
parent
6b8e0562f3
commit
8bfef3c072
6
package-lock.json
generated
6
package-lock.json
generated
@ -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": [
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<any>,
|
||||
setItemFormPopup: React.Dispatch<React.SetStateAction<any>>
|
||||
}
|
||||
|
||||
|
||||
// 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<ItemFormPopupProps | null>(null);
|
||||
|
||||
@ -102,9 +69,9 @@ function UtopiaMap({
|
||||
|
||||
const onEachFeature = (feature: Feature<GeoJSONGeometry, any>, 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='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://tile.osmand.net/hd/{z}/{x}/{y}.png" />
|
||||
<MarkerClusterGroup ref={(r)=> setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
||||
<MarkerClusterGroup ref={(r) => setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
||||
{
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child) ?
|
||||
@ -133,8 +100,13 @@ function UtopiaMap({
|
||||
)
|
||||
}
|
||||
</MarkerClusterGroup>
|
||||
{geo && <GeoJSON data={geo} onEachFeature={onEachFeature}/>}
|
||||
<MapEventListener setSelectNewItemPosition={setSelectNewItemPosition} selectNewItemPosition={selectNewItemPosition} setItemFormPopup={setItemFormPopup} />
|
||||
{geo && <GeoJSON data={geo} onEachFeature={onEachFeature} eventHandlers={{
|
||||
click: (e) => {
|
||||
e.layer!.closePopup();
|
||||
selectNewItemPosition && setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup })
|
||||
},
|
||||
}} />}
|
||||
<MapEventListener />
|
||||
</MapContainer>
|
||||
<AddButton triggerAction={setSelectNewItemPosition}></AddButton>
|
||||
{selectNewItemPosition != null &&
|
||||
|
||||
@ -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<React.SetStateAction<ItemFormPopupProps | null>>
|
||||
}
|
||||
|
||||
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>;
|
||||
|
||||
@ -10,26 +18,46 @@ const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
||||
selectPosition: null,
|
||||
setSelectPosition: () => { },
|
||||
setMarkerClicked: () => { },
|
||||
setMapClicked: () => {},
|
||||
});
|
||||
|
||||
function useSelectPositionManager(): {
|
||||
selectPosition: Item | LayerProps | null;
|
||||
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>;
|
||||
setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>;
|
||||
setMapClicked: React.Dispatch<React.SetStateAction<PolygonClickedProps | undefined>>;
|
||||
} {
|
||||
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null);
|
||||
const [markerClicked, setMarkerClicked] = useState<Item | null>();
|
||||
const [mapClicked, setMapClicked] = useState<PolygonClickedProps>();
|
||||
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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user