fix(source): enhancing layer control (#223)

* 3.0.82

* version number

* 3.0.84

* 3.0.85

* 3.0.86

* 3.0.87

* 3.0.88

* url layer parameter

* layer control customizing

* 3.0.89

* 3.0.90

* 3.0.91

* typing

* 3.0.92

* fixing bug and imports

* 3.0.93

* fixing bug on profile-item-create caused by PR 185
This commit is contained in:
Anton Tranelis 2025-05-23 10:04:34 +02:00 committed by GitHub
parent 82b1f39141
commit ed9906ae2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 103 additions and 45 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.86", "version": "3.0.93",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.86", "version": "3.0.93",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@heroicons/react": "^2.0.17", "@heroicons/react": "^2.0.17",

View File

@ -1,6 +1,6 @@
{ {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.86", "version": "3.0.93",
"description": "Reuseable React Components to build mapping apps for real life communities and networks", "description": "Reuseable React Components to build mapping apps for real life communities and networks",
"repository": "https://github.com/utopia-os/utopia-ui", "repository": "https://github.com/utopia-os/utopia-ui",
"homepage": "https://utopia-os.org/", "homepage": "https://utopia-os.org/",

View File

@ -5,8 +5,8 @@ import LayerSVG from '#assets/layer.svg'
import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter' import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter'
import { useLayers } from '#components/Map/hooks/useLayers' import { useLayers } from '#components/Map/hooks/useLayers'
export function LayerControl() { export function LayerControl({ expandLayerControl = false }: { expandLayerControl: boolean }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(expandLayerControl)
const layers = useLayers() const layers = useLayers()

View File

@ -101,9 +101,7 @@ export function ItemFormPopup(props: Props) {
setSpinner(false) setSpinner(false)
map.closePopup() map.closePopup()
} else { } else {
const item = items.find( const item = items.find((i) => i.user_created?.id === user?.id && i.layer === popupForm.layer)
(i) => i.user_created?.id === user?.id && i.layer?.id === popupForm.layer.id,
)
const uuid = crypto.randomUUID() const uuid = crypto.randomUUID()
let success = false let success = false

View File

@ -1,8 +1,6 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter' import { useSetTagData, useSetTagApi } from './hooks/useTags'
import { useSetTagData, useSetTagApi, useTags } from './hooks/useTags'
import type { ItemsApi } from '#types/ItemsApi' import type { ItemsApi } from '#types/ItemsApi'
import type { Tag } from '#types/Tag' import type { Tag } from '#types/Tag'
@ -42,36 +40,5 @@ export function Tags({ data, api }: { data?: Tag[]; api?: ItemsApi<Tag> }) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, data]) }, [api, data])
const location = useLocation()
const addFilterTag = useAddFilterTag()
const resetFilterTags = useResetFilterTags()
const tags = useTags()
const filterTags = useFilterTags()
useEffect(() => {
const params = new URLSearchParams(location.search)
const urlTags = params.get('tags')
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
const decodedTagsArray = decodedTags.split(';')
if (
decodedTagsArray.some(
(ut) => !filterTags.find((ft) => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase()),
) ||
filterTags.some(
(ft) =>
!decodedTagsArray.find((ut) => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase()),
)
) {
resetFilterTags()
decodedTagsArray.map((urlTag) => {
const tag = tags.find((t) => t.name.toLocaleLowerCase() === urlTag.toLocaleLowerCase())
tag && addFilterTag(tag)
return null
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location, tags])
return <></> return <></>
} }

View File

@ -55,6 +55,7 @@ function UtopiaMap({
showThemeControl = false, showThemeControl = false,
defaultTheme, defaultTheme,
donationWidget, donationWidget,
expandLayerControl,
}: { }: {
/** height of the map (default '500px') */ /** height of the map (default '500px') */
height?: string height?: string
@ -82,6 +83,8 @@ function UtopiaMap({
defaultTheme?: string defaultTheme?: string
/** ask to donate to the Utopia Project OpenCollective campaign (default false) */ /** ask to donate to the Utopia Project OpenCollective campaign (default false) */
donationWidget?: boolean donationWidget?: boolean
/** open layer control on map initialisation */
expandLayerControl?: boolean
}) { }) {
return ( return (
<ContextWrapper> <ContextWrapper>
@ -100,6 +103,7 @@ function UtopiaMap({
donationWidget={donationWidget} donationWidget={donationWidget}
showThemeControl={showThemeControl} showThemeControl={showThemeControl}
defaultTheme={defaultTheme} defaultTheme={defaultTheme}
expandLayerControl={expandLayerControl}
> >
{children} {children}
</UtopiaMapInner> </UtopiaMapInner>

View File

@ -17,7 +17,13 @@ import { useTheme } from '#components/AppShell/hooks/useTheme'
import { containsUUID } from '#utils/ContainsUUID' import { containsUUID } from '#utils/ContainsUUID'
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef' import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
import { useAddVisibleLayer } from './hooks/useFilter' import {
useAddFilterTag,
useAddVisibleLayer,
useFilterTags,
useResetFilterTags,
useToggleVisibleLayer,
} from './hooks/useFilter'
import { useLayers } from './hooks/useLayers' import { useLayers } from './hooks/useLayers'
import { useLeafletRefs } from './hooks/useLeafletRefs' import { useLeafletRefs } from './hooks/useLeafletRefs'
import { usePopupForm } from './hooks/usePopupForm' import { usePopupForm } from './hooks/usePopupForm'
@ -26,6 +32,7 @@ import {
useSetMapClicked, useSetMapClicked,
useSetSelectPosition, useSetSelectPosition,
} from './hooks/useSelectPosition' } from './hooks/useSelectPosition'
import { useTags } from './hooks/useTags'
import AddButton from './Subcomponents/AddButton' import AddButton from './Subcomponents/AddButton'
import { Control } from './Subcomponents/Controls/Control' import { Control } from './Subcomponents/Controls/Control'
import { FilterControl } from './Subcomponents/Controls/FilterControl' import { FilterControl } from './Subcomponents/Controls/FilterControl'
@ -47,6 +54,7 @@ export function UtopiaMapInner({
showThemeControl = false, showThemeControl = false,
defaultTheme = '', defaultTheme = '',
donationWidget, donationWidget,
expandLayerControl,
}: { }: {
children?: React.ReactNode children?: React.ReactNode
geo?: GeoJsonObject geo?: GeoJsonObject
@ -56,6 +64,7 @@ export function UtopiaMapInner({
donationWidget?: boolean donationWidget?: boolean
showThemeControl?: boolean showThemeControl?: boolean
defaultTheme?: string defaultTheme?: string
expandLayerControl?: boolean
}) { }) {
const selectNewItemPosition = useSelectPosition() const selectNewItemPosition = useSelectPosition()
const setSelectNewItemPosition = useSetSelectPosition() const setSelectNewItemPosition = useSetSelectPosition()
@ -198,6 +207,63 @@ export function UtopiaMapInner({
} }
} }
const addFilterTag = useAddFilterTag()
const resetFilterTags = useResetFilterTags()
const tags = useTags()
const filterTags = useFilterTags()
useEffect(() => {
const params = new URLSearchParams(location.search)
const urlTags = params.get('tags')
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
const decodedTagsArray = decodedTags.split(';').filter(Boolean)
const urlDiffersFromState =
decodedTagsArray.some(
(ut) => !filterTags.find((ft) => ut.toLowerCase() === ft.name.toLowerCase()),
) ||
filterTags.some(
(ft) => !decodedTagsArray.find((ut) => ut.toLowerCase() === ft.name.toLowerCase()),
)
if (urlDiffersFromState) {
resetFilterTags()
decodedTagsArray.forEach((urlTag) => {
const match = tags.find((t) => t.name.toLowerCase() === urlTag.toLowerCase())
if (match) addFilterTag(match)
})
}
}, [location, tags, filterTags, addFilterTag, resetFilterTags])
const toggleVisibleLayer = useToggleVisibleLayer()
const allLayers = useLayers()
const initializedRef = useRef(false)
useEffect(() => {
if (initializedRef.current || allLayers.length === 0) return
const params = new URLSearchParams(location.search)
const urlLayersParam = params.get('layers')
if (!urlLayersParam) {
initializedRef.current = true
return
}
const urlLayerNames = urlLayersParam.split(',').filter(Boolean)
const layerNamesToHide = allLayers
.map((l) => l.name)
.filter((name) => !urlLayerNames.includes(name))
layerNamesToHide.forEach((name) => {
const match = allLayers.find((l) => l.name === name)
if (match) toggleVisibleLayer(match)
})
initializedRef.current = true
}, [location, allLayers, toggleVisibleLayer])
return ( return (
<div className={`tw:h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : ''}`}> <div className={`tw:h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : ''}`}>
<Outlet /> <Outlet />
@ -207,7 +273,7 @@ export function UtopiaMapInner({
</Control> </Control>
<Control position='bottomLeft' zIndex='999' absolute> <Control position='bottomLeft' zIndex='999' absolute>
{showFilterControl && <FilterControl />} {showFilterControl && <FilterControl />}
{showLayerControl && <LayerControl />} {showLayerControl && <LayerControl expandLayerControl={expandLayerControl ?? false} />}
{showGratitudeControl && <GratitudeControl />} {showGratitudeControl && <GratitudeControl />}
</Control> </Control>
<TileLayer <TileLayer

View File

@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
import { useCallback, useReducer, createContext, useContext, useState } from 'react' import { useCallback, useReducer, createContext, useContext, useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useLayers } from './useLayers' import { useLayers } from './useLayers'
@ -100,6 +100,28 @@ function useFilterManager(initialTags: Tag[]): {
} }
}, initialLayers) }, initialLayers)
const allLayers = useLayers()
useEffect(() => {
if (allLayers.length === 0) return
const visibleNames = visibleLayers.map((l) => l.name)
const allNames = allLayers.map((l) => l.name)
const params = new URLSearchParams(location.search)
const allVisible =
visibleNames.length === allNames.length &&
visibleNames.every((name) => allNames.includes(name))
if (allVisible) {
params.delete('layers')
} else {
params.set('layers', visibleNames.join(','))
}
navigate(`${location.pathname}?${params.toString()}`, { replace: true })
}, [visibleLayers, allLayers, navigate])
const [visibleGroupTypes, dispatchGroupTypes] = useReducer( const [visibleGroupTypes, dispatchGroupTypes] = useReducer(
(state: string[], action: ActionType) => { (state: string[], action: ActionType) => {
switch (action.type) { switch (action.type) {

View File

@ -17,4 +17,5 @@ export interface UtopiaMapProps {
infoText?: string infoText?: string
donationWidget?: boolean donationWidget?: boolean
defaultTheme?: string defaultTheme?: string
expandLayerControl?: boolean
} }