mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Merge pull request #81 from utopia-os/reorganize-map-and-layer
fix(source): cleanup maps and layers
This commit is contained in:
commit
c3edabdbb9
2434
examples/2-static-layers/package-lock.json
generated
2434
examples/2-static-layers/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,8 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1"
|
||||||
"utopia-ui": "^3.0.35"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
@ -8,8 +7,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import { Children, isValidElement, useEffect, useState } from 'react'
|
import { Children, isValidElement, useEffect, useState } from 'react'
|
||||||
import { Marker, Tooltip, useMap, useMapEvents } from 'react-leaflet'
|
import { Marker, Tooltip } from 'react-leaflet'
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { encodeTag } from '#utils/FormatTags'
|
import { encodeTag } from '#utils/FormatTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
import { getValue } from '#utils/GetValue'
|
||||||
@ -79,8 +77,6 @@ export const Layer = ({
|
|||||||
const addPopup = useAddPopup()
|
const addPopup = useAddPopup()
|
||||||
const leafletRefs = useLeafletRefs()
|
const leafletRefs = useLeafletRefs()
|
||||||
|
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const allTagsLoaded = useAllTagsLoaded()
|
const allTagsLoaded = useAllTagsLoaded()
|
||||||
const allItemsLoaded = useAllItemsLoaded()
|
const allItemsLoaded = useAllItemsLoaded()
|
||||||
|
|
||||||
@ -92,8 +88,6 @@ export const Layer = ({
|
|||||||
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
|
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
|
||||||
const [tagsReady, setTagsReady] = useState<boolean>(false)
|
const [tagsReady, setTagsReady] = useState<boolean>(false)
|
||||||
|
|
||||||
const map = useMap()
|
|
||||||
|
|
||||||
const isLayerVisible = useIsLayerVisible()
|
const isLayerVisible = useIsLayerVisible()
|
||||||
|
|
||||||
const isGroupTypeVisible = useIsGroupTypeVisible()
|
const isGroupTypeVisible = useIsGroupTypeVisible()
|
||||||
@ -170,64 +164,6 @@ export const Layer = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [data, api])
|
}, [data, api])
|
||||||
|
|
||||||
useMapEvents({
|
|
||||||
popupopen: (e) => {
|
|
||||||
const item = Object.entries(leafletRefs).find((r) => r[1].popup === e.popup)?.[1].item
|
|
||||||
if (item?.layer?.name === name && window.location.pathname.split('/')[1] !== item.id) {
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
|
||||||
if (!location.pathname.includes('/item/')) {
|
|
||||||
window.history.pushState(
|
|
||||||
{},
|
|
||||||
'',
|
|
||||||
`/${item.id}` + `${params.toString() !== '' ? `?${params}` : ''}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
let title = ''
|
|
||||||
if (item.name) title = item.name
|
|
||||||
else if (item.layer.itemNameField) title = getValue(item, item.layer.itemNameField)
|
|
||||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const openPopup = () => {
|
|
||||||
if (
|
|
||||||
window.location.pathname.split('/').length <= 1 ||
|
|
||||||
window.location.pathname.split('/')[1] === ''
|
|
||||||
) {
|
|
||||||
map.closePopup()
|
|
||||||
} else {
|
|
||||||
if (window.location.pathname.split('/')[1]) {
|
|
||||||
const id = window.location.pathname.split('/')[1]
|
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
|
||||||
const ref = leafletRefs[id]
|
|
||||||
if (ref?.marker && ref.item.layer?.name === name) {
|
|
||||||
ref.marker &&
|
|
||||||
clusterRef.hasLayer(ref.marker) &&
|
|
||||||
clusterRef?.zoomToShowLayer(ref.marker, () => {
|
|
||||||
ref.marker.openPopup()
|
|
||||||
})
|
|
||||||
let title = ''
|
|
||||||
if (ref.item.name) title = ref.item.name
|
|
||||||
else if (ref.item.layer.itemNameField)
|
|
||||||
title = getValue(ref.item.name, ref.item.layer.itemNameField)
|
|
||||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
|
||||||
document
|
|
||||||
.querySelector('meta[property="og:title"]')
|
|
||||||
?.setAttribute('content', ref.item.name)
|
|
||||||
document
|
|
||||||
.querySelector('meta[property="og:description"]')
|
|
||||||
?.setAttribute('content', ref.item.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
openPopup()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [leafletRefs, location])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tagsReady) {
|
if (tagsReady) {
|
||||||
const processedTags = {}
|
const processedTags = {}
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { LatLng } from 'leaflet'
|
||||||
|
import { MapContainer } from 'react-leaflet'
|
||||||
|
|
||||||
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||||
|
|
||||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||||
@ -7,10 +10,37 @@ import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
|||||||
// eslint-disable-next-line import/no-unassigned-import
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import 'react-toastify/dist/ReactToastify.css'
|
import 'react-toastify/dist/ReactToastify.css'
|
||||||
|
|
||||||
function UtopiaMap(props: UtopiaMapProps) {
|
function UtopiaMap({
|
||||||
|
height = '500px',
|
||||||
|
width = '100%',
|
||||||
|
center = [50.6, 9.5],
|
||||||
|
zoom = 10,
|
||||||
|
children,
|
||||||
|
geo,
|
||||||
|
showFilterControl = false,
|
||||||
|
showGratitudeControl = false,
|
||||||
|
showLayerControl = true,
|
||||||
|
infoText,
|
||||||
|
}: UtopiaMapProps) {
|
||||||
return (
|
return (
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<UtopiaMapInner {...props} />
|
<MapContainer
|
||||||
|
style={{ height, width }}
|
||||||
|
center={new LatLng(center[0], center[1])}
|
||||||
|
zoom={zoom}
|
||||||
|
zoomControl={false}
|
||||||
|
maxZoom={19}
|
||||||
|
>
|
||||||
|
<UtopiaMapInner
|
||||||
|
geo={geo}
|
||||||
|
showFilterControl={showFilterControl}
|
||||||
|
showGratitudeControl={showGratitudeControl}
|
||||||
|
showLayerControl={showLayerControl}
|
||||||
|
infoText={infoText}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</UtopiaMapInner>
|
||||||
|
</MapContainer>
|
||||||
</ContextWrapper>
|
</ContextWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,29 +6,24 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
import { LatLng } from 'leaflet'
|
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import { TileLayer, useMapEvents, GeoJSON, useMap } from 'react-leaflet'
|
||||||
Children,
|
|
||||||
cloneElement,
|
|
||||||
createRef,
|
|
||||||
isValidElement,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { TileLayer, MapContainer, useMapEvents, GeoJSON } from 'react-leaflet'
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import MarkerClusterGroup from 'react-leaflet-cluster'
|
import MarkerClusterGroup from 'react-leaflet-cluster'
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet, useLocation } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import './UtopiaMap.css'
|
import './UtopiaMap.css'
|
||||||
|
|
||||||
|
import { containsUUID } from '#utils/ContainsUUID'
|
||||||
|
import { getValue } from '#utils/GetValue'
|
||||||
|
|
||||||
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
|
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
|
||||||
import { useAddVisibleLayer } from './hooks/useFilter'
|
import { useAddVisibleLayer } from './hooks/useFilter'
|
||||||
import { useLayers } from './hooks/useLayers'
|
import { useLayers } from './hooks/useLayers'
|
||||||
|
import { useLeafletRefs } from './hooks/useLeafletRefs'
|
||||||
import {
|
import {
|
||||||
useSelectPosition,
|
useSelectPosition,
|
||||||
useSetMapClicked,
|
useSetMapClicked,
|
||||||
@ -48,13 +43,7 @@ import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
|||||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||||
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
||||||
|
|
||||||
const mapDivRef = createRef()
|
|
||||||
|
|
||||||
export function UtopiaMapInner({
|
export function UtopiaMapInner({
|
||||||
height = '500px',
|
|
||||||
width = '100%',
|
|
||||||
center = [50.6, 9.5],
|
|
||||||
zoom = 10,
|
|
||||||
children,
|
children,
|
||||||
geo,
|
geo,
|
||||||
showFilterControl = false,
|
showFilterControl = false,
|
||||||
@ -62,7 +51,6 @@ export function UtopiaMapInner({
|
|||||||
showLayerControl = true,
|
showLayerControl = true,
|
||||||
infoText,
|
infoText,
|
||||||
}: UtopiaMapProps) {
|
}: UtopiaMapProps) {
|
||||||
// Hooks that rely on contexts, called after ContextWrapper is provided
|
|
||||||
const selectNewItemPosition = useSelectPosition()
|
const selectNewItemPosition = useSelectPosition()
|
||||||
const setSelectNewItemPosition = useSetSelectPosition()
|
const setSelectNewItemPosition = useSetSelectPosition()
|
||||||
const setClusterRef = useSetClusterRef()
|
const setClusterRef = useSetClusterRef()
|
||||||
@ -72,6 +60,10 @@ export function UtopiaMapInner({
|
|||||||
|
|
||||||
const layers = useLayers()
|
const layers = useLayers()
|
||||||
const addVisibleLayer = useAddVisibleLayer()
|
const addVisibleLayer = useAddVisibleLayer()
|
||||||
|
const leafletRefs = useLeafletRefs()
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
const map = useMap()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
layers.forEach((layer) => addVisibleLayer(layer))
|
layers.forEach((layer) => addVisibleLayer(layer))
|
||||||
@ -105,9 +97,64 @@ export function UtopiaMapInner({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useMapEvents({
|
||||||
|
popupopen: (e) => {
|
||||||
|
const item = Object.entries(leafletRefs).find((r) => r[1].popup === e.popup)?.[1].item
|
||||||
|
if (window.location.pathname.split('/')[1] !== item?.id) {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
if (!location.pathname.includes('/item/')) {
|
||||||
|
window.history.pushState(
|
||||||
|
{},
|
||||||
|
'',
|
||||||
|
`/${item?.id}` + `${params.toString() !== '' ? `?${params}` : ''}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let title = ''
|
||||||
|
if (item?.name) title = item.name
|
||||||
|
else if (item?.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
|
||||||
|
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const openPopup = () => {
|
||||||
|
if (!containsUUID(window.location.pathname)) {
|
||||||
|
map.closePopup()
|
||||||
|
} else {
|
||||||
|
if (window.location.pathname.split('/')[1]) {
|
||||||
|
const id = window.location.pathname.split('/')[1]
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
|
const ref = leafletRefs[id]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (ref) {
|
||||||
|
clusterRef.hasLayer(ref.marker) &&
|
||||||
|
clusterRef?.zoomToShowLayer(ref.marker, () => {
|
||||||
|
ref.marker.openPopup()
|
||||||
|
})
|
||||||
|
let title = ''
|
||||||
|
if (ref.item.name) title = ref.item.name
|
||||||
|
else if (ref.item.layer?.itemNameField)
|
||||||
|
title = getValue(ref.item.name, ref.item.layer.itemNameField)
|
||||||
|
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||||
|
document
|
||||||
|
.querySelector('meta[property="og:title"]')
|
||||||
|
?.setAttribute('content', ref.item.name)
|
||||||
|
document
|
||||||
|
.querySelector('meta[property="og:description"]')
|
||||||
|
?.setAttribute('content', ref.item.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
openPopup()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [leafletRefs, location])
|
||||||
|
|
||||||
const resetMetaTags = () => {
|
const resetMetaTags = () => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
if (!window.location.pathname.includes('/item/')) {
|
if (!containsUUID(window.location.pathname)) {
|
||||||
window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
|
window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
|
||||||
}
|
}
|
||||||
document.title = document.title.split('-')[0]
|
document.title = document.title.split('-')[0]
|
||||||
@ -129,14 +176,6 @@ export function UtopiaMapInner({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}
|
className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}
|
||||||
>
|
|
||||||
<MapContainer
|
|
||||||
ref={mapDivRef}
|
|
||||||
style={{ height, width }}
|
|
||||||
center={new LatLng(center[0], center[1])}
|
|
||||||
zoom={zoom}
|
|
||||||
zoomControl={false}
|
|
||||||
maxZoom={19}
|
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Control position='topLeft' zIndex='1000' absolute>
|
<Control position='topLeft' zIndex='1000' absolute>
|
||||||
@ -185,7 +224,6 @@ export function UtopiaMapInner({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MapEventListener />
|
<MapEventListener />
|
||||||
</MapContainer>
|
|
||||||
<AddButton triggerAction={setSelectNewItemPosition} />
|
<AddButton triggerAction={setSelectNewItemPosition} />
|
||||||
{selectNewItemPosition != null && (
|
{selectNewItemPosition != null && (
|
||||||
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />
|
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />
|
||||||
|
|||||||
6
src/Utils/ContainsUUID.ts
Normal file
6
src/Utils/ContainsUUID.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function containsUUID(str: string): boolean {
|
||||||
|
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
uuidRegex.test(str) && console.log('contains UUID')
|
||||||
|
return uuidRegex.test(str)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user