mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
individual item URLS basics
This commit is contained in:
parent
1f675ccf3c
commit
58380f6b77
@ -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>
|
||||
);
|
||||
})
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -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='© <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='© <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>
|
||||
|
||||
95
src/Components/Map/hooks/useLeafletRefs.tsx
Normal file
95
src/Components/Map/hooks/useLeafletRefs.tsx
Normal 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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user