individual item URLS basics

This commit is contained in:
Anton 2023-09-14 08:31:41 +02:00
parent 1f675ccf3c
commit 58380f6b77
7 changed files with 193 additions and 56 deletions

View File

@ -1,12 +1,15 @@
import * as React from 'react'
import { Marker, Tooltip } from 'react-leaflet'
import { Marker, Tooltip, useMap, useMapEvents } from 'react-leaflet'
import { Item, LayerProps } from '../../types'
import MarkerIconFactory from '../../Utils/MarkerIconFactory'
import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
import { useItems, useResetItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
import { useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
import { useEffect, useState } from 'react'
import { ItemFormPopupProps, ItemFormPopup } from './Subcomponents/ItemFormPopup'
import { useFilterTags, useSearchPhrase } from './hooks/useFilter'
import { useGetItemTags } from './hooks/useTags'
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
import { Popup } from 'leaflet'
export const Layer = (props: LayerProps) => {
@ -18,32 +21,63 @@ export const Layer = (props: LayerProps) => {
const items = useItems();
const setItemsApi = useSetItemsApi();
const setItemsData = useSetItemsData();
const resetItems = useResetItems();
const getItemTags = useGetItemTags();
const addMarker = useAddMarker();
const addPopup = useAddPopup();
const leafletRefs = useLeafletRefs();
const searchPhrase = useSearchPhrase();
const map = useMap();
useEffect(() => {
resetItems(props);
props.data && setItemsData(props);
props.api && setItemsApi(props);
}, [props.data, props.api])
const openPopup = () => {
if (window.location.pathname.split("/")[1] == props.name) {
const id = window.location.pathname.split("/")[2]
const marker = leafletRefs[id]?.marker;
if (marker && marker != null) {
marker !== null && props.clusterRef?.current?.zoomToShowLayer(marker, () => {
marker.openPopup();
});
}
}
}
useMapEvents({
popupopen: (e) => {
const item = Object.entries(leafletRefs).find(r => r[1].popup == e.popup)?.[1].item;
if(item?.layer?.name == props.name )
window.history.pushState({},"",`/${props.name}/${item.id}`)
},
})
useEffect(() => {
openPopup();
}, [leafletRefs])
return (
<>
{items &&
items.
filter(item => item.layer?.name === props.name)?.
filter(item =>
// filterTags.length == 0 ? item : item.tags?.some(tag => filterTags.some(filterTag => filterTag.id === tag.id)))?.
filterTags.length == 0 ? item : filterTags.every(tag => item.tags?.some(filterTag => filterTag.id === tag.id)))?.
filterTags.length == 0 ? item : filterTags.every(tag => getItemTags(item).some(filterTag => filterTag.id === tag.id)))?.
filter(item => {
return searchPhrase === ''
? item :
item.name.toLowerCase().includes(searchPhrase.toLowerCase())
}).
map((item: Item) => {
const tags = item.tags;
const tags = getItemTags(item);
let color1 = "#666";
let color2 = "RGBA(35, 31, 32, 0.2)";
@ -54,20 +88,29 @@ export const Layer = (props: LayerProps) => {
color2 = tags[1].color;
}
return (
<Marker icon={MarkerIconFactory(props.markerShape, color1, color2, props.markerIcon)} key={item.id} position={[item.position.coordinates[1], item.position.coordinates[0]]}>
<Marker ref={(r) => {
if (!(item.id in leafletRefs))
r && addMarker(item, r);
}} icon={MarkerIconFactory(props.markerShape, color1, color2, props.markerIcon)} key={item.id} position={[item.position.coordinates[1], item.position.coordinates[0]]}>
{
(props.children && React.Children.toArray(props.children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemView") ?
React.Children.toArray(props.children).map((child) =>
React.isValidElement(child) && child.props.__TYPE === "ItemView" ?
<ItemViewPopup key={item.id} item={item} setItemFormPopup={props.setItemFormPopup} >{child}</ItemViewPopup>
<ItemViewPopup ref={(r) => {
if (!(item.id in leafletRefs))
r && addPopup(item, r as Popup);
}} key={item.id + item.name} item={item} setItemFormPopup={props.setItemFormPopup} >{child}</ItemViewPopup>
: ""
)
:
<>
<ItemViewPopup item={item} setItemFormPopup={props.setItemFormPopup} />
<ItemViewPopup key={item.id + item.name} ref={(r) => {
if (!(item.id in leafletRefs))
r && addPopup(item, r as Popup);
}} item={item} setItemFormPopup={props.setItemFormPopup} />
</>)
}
<Tooltip offset={[0,-38]} direction='top'>{item.name}</Tooltip>
<Tooltip offset={[0, -38]} direction='top'>{item.name}</Tooltip>
</Marker>
);
})

View File

@ -5,7 +5,7 @@ import { ItemFormPopupProps } from "../ItemFormPopup";
import { LatLng } from "leaflet";
import { Item } from "../../../../types";
import { toast } from "react-toastify";
import { useHasUserPermission, usePermissions } from "../../hooks/usePermissions";
import { useHasUserPermission } from "../../hooks/usePermissions";
@ -19,7 +19,6 @@ export function HeaderView({ item, setItemFormPopup }: {
const map = useMap();
const hasUserPermission = useHasUserPermission();
const permissions = usePermissions();
const removeItemFromMap = async (event: React.MouseEvent<HTMLElement>) => {
@ -48,11 +47,6 @@ export function HeaderView({ item, setItemFormPopup }: {
setItemFormPopup({ position: new LatLng(item.position.coordinates[1], item.position.coordinates[0]), layer: item.layer!, item: item, setItemFormPopup: setItemFormPopup })
}
console.log(item.layer.api.collectionName);
console.log(permissions);
console.log( hasUserPermission(item.api?.collectionName!,"update") );
return (
<div className='tw-grid tw-grid-cols-6 tw-pb-2'>

View File

@ -2,9 +2,9 @@ import * as React from 'react'
import { Item } from '../../../../types'
import { useAddTag, useTags } from '../../hooks/useTags';
import reactStringReplace from 'react-string-replace';
import { useAddFilterTag, useResetFilterTags } from '../../hooks/useFilter';
import { useAddFilterTag } from '../../hooks/useFilter';
import { hashTagRegex } from '../../../../Utils/HashTagRegex';
import { fixUrls, mailRegex, urlRegex } from '../../../../Utils/ReplaceURLs';
import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs';
import { useMap } from 'react-leaflet';
import { randomColor } from '../../../../Utils/RandomColor';
import { useEffect, useRef } from 'react';
@ -16,7 +16,6 @@ export const TextView = ({ item }: { item?: Item }) => {
const groupRef = useRef(null);
const addFilterTag = useAddFilterTag();
const resetFilterTags = useResetFilterTags();
const map = useMap();

View File

@ -1,21 +1,21 @@
import * as React from 'react'
import { Popup as LeafletPopup, useMap } from 'react-leaflet'
import { Popup as LeafletPopup} from 'react-leaflet'
import { Item } from '../../../types'
import { ItemFormPopupProps } from './ItemFormPopup'
import { HeaderView } from './ItemPopupComponents/HeaderView'
import { TextView } from './ItemPopupComponents/TextView'
export interface ItemViewPopupProps {
item: Item,
children?: React.ReactNode;
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
}
export const ItemViewPopup = (props: ItemViewPopupProps) => {
const item: Item = props.item;
export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: any) => {
return (
<LeafletPopup maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}>
<LeafletPopup ref={ref} maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}>
<div className='tw-bg-base-100 tw-text-base-content'>
<HeaderView item={props.item} setItemFormPopup={props.setItemFormPopup} />
<div className='tw-overflow-y-auto tw-max-h-72'>
@ -35,5 +35,5 @@ export const ItemViewPopup = (props: ItemViewPopupProps) => {
</div>
</LeafletPopup>
)
}
})

View File

@ -14,6 +14,7 @@ import { LayersProvider } from "./hooks/useLayers";
import { FilterProvider } from "./hooks/useFilter";
import { FilterControl } from "./Subcomponents/FilterControl";
import { PermissionsProvider } from "./hooks/usePermissions";
import { LeafletRefsProvider } from "./hooks/useLeafletRefs";
export interface MapEventListenerProps {
@ -25,7 +26,7 @@ export interface MapEventListenerProps {
function MapEventListener(props: MapEventListenerProps) {
useMapEvents({
click: (e) => {
window.history.pushState({}, "", "/");
console.log(e.latlng.lat + ',' + e.latlng.lng);
if (props.selectNewItemPosition != null) {
props.setItemFormPopup({ layer: props.selectNewItemPosition, position: e.latlng })
@ -34,8 +35,8 @@ function MapEventListener(props: MapEventListenerProps) {
},
resize: () => {
console.log("resize");
},
}
})
return null
}
@ -53,6 +54,7 @@ function UtopiaMap({
const [selectNewItemPosition, setSelectNewItemPosition] = useState<LayerProps | null>(null);
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null);
const clusterRef = React.useRef();
return (
@ -61,33 +63,35 @@ function UtopiaMap({
<PermissionsProvider initialPermissions={[]}>
<FilterProvider initialTags={[]}>
<ItemsProvider initialItems={[]}>
<div className={(selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined)}>
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={center} zoom={zoom} zoomControl={false}>
<FilterControl></FilterControl>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://tile.osmand.net/hd/{z}/{x}/{y}.png" />
<MarkerClusterGroup showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
{
React.Children.toArray(children).map((child) =>
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null }>(child) ?
React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup }) : child
)
}
</MarkerClusterGroup>
<MapEventListener setSelectNewItemPosition={setSelectNewItemPosition} selectNewItemPosition={selectNewItemPosition} setItemFormPopup={setItemFormPopup} />
</MapContainer>
<AddButton setSelectNewItemPosition={setSelectNewItemPosition}></AddButton>
{selectNewItemPosition != null &&
<div className="tw-button tw-z-1000 tw-absolute tw-right-5 tw-top-4 tw-drop-shadow-md">
<div className="tw-alert tw-bg-base-100 tw-text-base-content">
<div>
<span>Select {selectNewItemPosition.name} position!</span>
<LeafletRefsProvider initialLeafletRefs={{}}>
<div className={(selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined)}>
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={center} zoom={zoom} zoomControl={false}>
<FilterControl></FilterControl>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://tile.osmand.net/hd/{z}/{x}/{y}.png" />
<MarkerClusterGroup ref={clusterRef} 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) ?
React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup, clusterRef: clusterRef }) : child
)
}
</MarkerClusterGroup>
<MapEventListener setSelectNewItemPosition={setSelectNewItemPosition} selectNewItemPosition={selectNewItemPosition} setItemFormPopup={setItemFormPopup} />
</MapContainer>
<AddButton setSelectNewItemPosition={setSelectNewItemPosition}></AddButton>
{selectNewItemPosition != null &&
<div className="tw-button tw-z-1000 tw-absolute tw-right-5 tw-top-4 tw-drop-shadow-md">
<div className="tw-alert tw-bg-base-100 tw-text-base-content">
<div>
<span>Select {selectNewItemPosition.name} position!</span>
</div>
</div>
</div>
</div>
}
</div>
}
</div>
</LeafletRefsProvider>
</ItemsProvider>
</FilterProvider>
</PermissionsProvider>

View File

@ -0,0 +1,95 @@
import { useCallback, useReducer, createContext, useContext, useEffect } from "react";
import * as React from "react";
import { Item } from "../../../types";
import { Marker, Popup } from "leaflet";
type LeafletRef = {
item: Item,
marker: Marker,
popup: Popup
}
type ActionType =
| { type: "ADD_MARKER"; item: Item, marker: Marker }
| { type: "ADD_POPUP"; item: Item, popup: Popup }
;
type UseLeafletRefsManagerResult = ReturnType<typeof useLeafletRefsManager>;
const LeafletRefsContext = createContext<UseLeafletRefsManagerResult>({
leafletRefs: {},
addMarker: () => { },
addPopup: () => { },
});
function useLeafletRefsManager(initialLeafletRefs: {}): {
leafletRefs: Record<string,LeafletRef>;
addMarker: (item: Item, marker: Marker) => void;
addPopup: (item: Item, popup: Popup) => void;
} {
const [leafletRefs, dispatch] = useReducer((state: Record<string,LeafletRef>, action: ActionType) => {
switch (action.type) {
case "ADD_MARKER":
return {
...state,
[action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }}
case "ADD_POPUP":
return {
...state,
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }}
default:
throw new Error();
}
}, initialLeafletRefs);
const addMarker = useCallback((item: Item, marker: Marker) => {
dispatch({
type: "ADD_MARKER",
item,
marker
});
}, []);
const addPopup = useCallback((item: Item, popup: Popup) => {
dispatch({
type: "ADD_POPUP",
item,
popup
});
}, []);
return { leafletRefs, addMarker, addPopup };
}
export const LeafletRefsProvider: React.FunctionComponent<{
initialLeafletRefs: {}, children?: React.ReactNode
}> = ({ initialLeafletRefs, children }) => (
<LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}>
{children}
</LeafletRefsContext.Provider>
);
export const useLeafletRefs = (): Record<string,LeafletRef> => {
const { leafletRefs } = useContext(LeafletRefsContext);
return leafletRefs;
};
export const useAddMarker = (): UseLeafletRefsManagerResult["addMarker"] => {
const { addMarker } = useContext(LeafletRefsContext);
return addMarker;
};
export const useAddPopup = (): UseLeafletRefsManagerResult["addPopup"] => {
const { addPopup } = useContext(LeafletRefsContext);
return addPopup;
};

View File

@ -1,4 +1,4 @@
import { LatLng } from "leaflet";
import { LatLng, LatLngTuple, latLng } from "leaflet";
import { ItemFormPopupProps } from "./Components/Map/Subcomponents/ItemFormPopup";
export interface UtopiaMapProps {
@ -22,7 +22,8 @@ export interface LayerProps {
markerDefaultColor: string,
api?: ItemsApi<any>,
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>,
itemFormPopup?: ItemFormPopupProps | null
itemFormPopup?: ItemFormPopupProps | null,
clusterRef?: React.MutableRefObject<any>
}
export class Item {
@ -36,6 +37,7 @@ export class Item {
end?: string;
api?: ItemsApi<any>;
tags?: Tag[];
layer?: LayerProps;
[key: string]: any;
constructor(id:string,name:string,text:string,position:Geometry, layer?: LayerProps, api?: ItemsApi<any>){
this.id = id;