diff --git a/lib/src/Components/AppShell/ContextWrapper.tsx b/lib/src/Components/AppShell/ContextWrapper.tsx
index c4245b52..93fc8429 100644
--- a/lib/src/Components/AppShell/ContextWrapper.tsx
+++ b/lib/src/Components/AppShell/ContextWrapper.tsx
@@ -7,7 +7,6 @@ import { QuestsProvider } from '#components/Gaming/hooks/useQuests'
import { ClusterRefProvider } from '#components/Map/hooks/useClusterRef'
import { FilterProvider } from '#components/Map/hooks/useFilter'
import { ItemsProvider } from '#components/Map/hooks/useItems'
-import { LayersProvider } from '#components/Map/hooks/useLayers'
import { LeafletRefsProvider } from '#components/Map/hooks/useLeafletRefs'
import { PermissionsProvider } from '#components/Map/hooks/usePermissions'
import { PopupFormProvider } from '#components/Map/hooks/usePopupForm'
@@ -59,40 +58,38 @@ export const Wrappers = ({ children }) => {
return (
-
+
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
-
+
)
diff --git a/lib/src/Components/Item/PopupView.tsx b/lib/src/Components/Item/PopupView.tsx
index 6eea6e43..5495cfc5 100644
--- a/lib/src/Components/Item/PopupView.tsx
+++ b/lib/src/Components/Item/PopupView.tsx
@@ -7,8 +7,9 @@ import {
useIsLayerVisible,
useIsGroupTypeVisible,
useVisibleGroupType,
+ useAllVisibleLayersInitialized,
} from '#components/Map/hooks/useFilter'
-import { useItems, useAllItemsLoaded } from '#components/Map/hooks/useItems'
+import { useItems } from '#components/Map/hooks/useItems'
import { useAddMarker, useAddPopup, useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
import { useSetMarkerClicked, useSelectPosition } from '#components/Map/hooks/useSelectPosition'
import { useGetItemTags, useAllTagsLoaded, useTags } from '#components/Map/hooks/useTags'
@@ -44,7 +45,8 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => {
const leafletRefs = useLeafletRefs()
const allTagsLoaded = useAllTagsLoaded()
- const allItemsLoaded = useAllItemsLoaded()
+
+ const allVisibleLayersInitialized = useAllVisibleLayersInitialized()
const setMarkerClicked = useSetMarkerClicked()
const selectPosition = useSelectPosition()
@@ -103,7 +105,7 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => {
})
}
- if (allTagsLoaded && allItemsLoaded) {
+ if (allTagsLoaded && allVisibleLayersInitialized) {
item.text?.match(hashTagRegex)?.map((tag) => {
if (
!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase()) &&
diff --git a/lib/src/Components/Map/Layer.tsx b/lib/src/Components/Map/Layer.tsx
index 6d54af71..5ae163c4 100644
--- a/lib/src/Components/Map/Layer.tsx
+++ b/lib/src/Components/Map/Layer.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import { useSetItemsApi, useSetItemsData } from './hooks/useItems'
import { useAddTag } from './hooks/useTags'
@@ -43,52 +43,98 @@ export const Layer = ({
const [newTagsToAdd] = useState([])
const [tagsReady] = useState(false)
+ const initializeWithData = useCallback(() => {
+ if (!data) return
+ setItemsData({
+ data,
+ children,
+ name,
+ menuIcon,
+ menuText,
+ menuColor,
+ markerIcon,
+ markerShape,
+ markerDefaultColor,
+ markerDefaultColor2,
+ api,
+ itemType,
+ userProfileLayer,
+ customEditLink,
+ customEditParameter,
+ // eslint-disable-next-line camelcase
+ public_edit_items,
+ listed,
+ })
+ }, [
+ api,
+ children,
+ customEditLink,
+ customEditParameter,
+ data,
+ itemType,
+ listed,
+ markerDefaultColor,
+ markerDefaultColor2,
+ markerIcon,
+ markerShape,
+ menuColor,
+ menuIcon,
+ menuText,
+ name,
+ // eslint-disable-next-line camelcase
+ public_edit_items,
+ setItemsData,
+ userProfileLayer,
+ ])
+
+ const initializeWithApi = useCallback(() => {
+ if (!api) return
+ setItemsApi({
+ data,
+ children,
+ name,
+ menuIcon,
+ menuText,
+ menuColor,
+ markerIcon,
+ markerShape,
+ markerDefaultColor,
+ markerDefaultColor2,
+ api,
+ itemType,
+ userProfileLayer,
+ customEditLink,
+ customEditParameter,
+ // eslint-disable-next-line camelcase
+ public_edit_items,
+ listed,
+ })
+ }, [
+ api,
+ children,
+ customEditLink,
+ customEditParameter,
+ data,
+ itemType,
+ listed,
+ markerDefaultColor,
+ markerDefaultColor2,
+ markerIcon,
+ markerShape,
+ menuColor,
+ menuIcon,
+ menuText,
+ name,
+ // eslint-disable-next-line camelcase
+ public_edit_items,
+ setItemsApi,
+ userProfileLayer,
+ ])
+
useEffect(() => {
- data &&
- setItemsData({
- data,
- children,
- name,
- menuIcon,
- menuText,
- menuColor,
- markerIcon,
- markerShape,
- markerDefaultColor,
- markerDefaultColor2,
- api,
- itemType,
- userProfileLayer,
- // Can we just use editCallback for all cases?
- customEditLink,
- customEditParameter,
- // eslint-disable-next-line camelcase
- public_edit_items,
- listed,
- })
- api &&
- setItemsApi({
- data,
- children,
- name,
- menuIcon,
- menuText,
- menuColor,
- markerIcon,
- markerShape,
- markerDefaultColor,
- markerDefaultColor2,
- api,
- itemType,
- userProfileLayer,
- customEditLink,
- customEditParameter,
- // eslint-disable-next-line camelcase
- public_edit_items,
- listed,
- })
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [data, api])
+ if (data) initializeWithData()
+ if (api) initializeWithApi()
+ }, [data, api, initializeWithData, initializeWithApi])
useEffect(() => {
if (tagsReady) {
diff --git a/lib/src/Components/Map/Subcomponents/AddButton.tsx b/lib/src/Components/Map/Subcomponents/AddButton.tsx
index 5781a1ab..c6277b47 100644
--- a/lib/src/Components/Map/Subcomponents/AddButton.tsx
+++ b/lib/src/Components/Map/Subcomponents/AddButton.tsx
@@ -3,7 +3,7 @@
import SVG from 'react-inlinesvg'
import PlusSVG from '#assets/plus.svg'
-import { useLayers } from '#components/Map/hooks/useLayers'
+import { useLayers } from '#components/Map/hooks/useItems'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
export default function AddButton({
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
index 10180de0..016678e3 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
@@ -3,7 +3,7 @@ import SVG from 'react-inlinesvg'
import LayerSVG from '#assets/layer.svg'
import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter'
-import { useLayers } from '#components/Map/hooks/useLayers'
+import { useLayers } from '#components/Map/hooks/useItems'
export function LayerControl({ expandLayerControl = false }: { expandLayerControl: boolean }) {
const [open, setOpen] = useState(expandLayerControl)
diff --git a/lib/src/Components/Map/UtopiaMapInner.tsx b/lib/src/Components/Map/UtopiaMapInner.tsx
index d197f92a..68a49177 100644
--- a/lib/src/Components/Map/UtopiaMapInner.tsx
+++ b/lib/src/Components/Map/UtopiaMapInner.tsx
@@ -24,7 +24,7 @@ import {
useResetFilterTags,
useToggleVisibleLayer,
} from './hooks/useFilter'
-import { useLayers } from './hooks/useLayers'
+import { useLayers } from './hooks/useItems'
import { useLeafletRefs } from './hooks/useLeafletRefs'
import { usePopupForm } from './hooks/usePopupForm'
import {
@@ -44,6 +44,7 @@ import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
import { SelectPosition } from './Subcomponents/SelectPosition'
import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson'
+import { LayerProps } from '#types/LayerProps'
export function UtopiaMapInner({
children,
@@ -85,9 +86,8 @@ export function UtopiaMapInner({
useTheme(defaultTheme)
useEffect(() => {
- layers.forEach((layer) => addVisibleLayer(layer))
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [layers])
+ layers.forEach((layer: LayerProps) => addVisibleLayer(layer))
+ }, [addVisibleLayer, layers])
const setAppState = useSetAppState()
diff --git a/lib/src/Components/Map/hooks/useFilter.tsx b/lib/src/Components/Map/hooks/useFilter.tsx
index d7733f04..0bfa8a94 100644
--- a/lib/src/Components/Map/hooks/useFilter.tsx
+++ b/lib/src/Components/Map/hooks/useFilter.tsx
@@ -7,7 +7,7 @@
import { useCallback, useReducer, createContext, useContext, useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
-import { useLayers } from './useLayers'
+import { useLayers, useLayerState } from './useItems'
import useWindowDimensions from './useWindowDimension'
import type { LayerProps } from '#types/LayerProps'
@@ -350,3 +350,12 @@ export const useVisibleGroupType = (): UseFilterManagerResult['visibleGroupTypes
const { visibleGroupTypes } = useContext(FilterContext)
return visibleGroupTypes
}
+
+export const useAllVisibleLayersInitialized = (): boolean => {
+ const { visibleLayers } = useContext(FilterContext)
+ const layers = useLayerState()
+ return visibleLayers.every((layer) => {
+ const foundLayer = layers.find((l) => l.props.name === layer.name)
+ return foundLayer ? foundLayer.isInitialized : false
+ })
+}
diff --git a/lib/src/Components/Map/hooks/useItems.tsx b/lib/src/Components/Map/hooks/useItems.tsx
index 0fb1af78..13f8126a 100644
--- a/lib/src/Components/Map/hooks/useItems.tsx
+++ b/lib/src/Components/Map/hooks/useItems.tsx
@@ -5,15 +5,24 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-misused-promises */
-import { useCallback, useReducer, createContext, useContext, useState } from 'react'
+import { useCallback, useReducer, createContext, useContext } from 'react'
import { toast } from 'react-toastify'
-import { useAddLayer } from './useLayers'
-
import type { Item } from '#types/Item'
import type { LayerProps } from '#types/LayerProps'
+type LayerState = {
+ props: LayerProps
+ isInitialized: boolean
+}[]
+
+interface State {
+ layers: LayerState
+ items: Item[]
+}
+
type ActionType =
+ | { type: 'ADD_LAYER'; layer: LayerProps; items: Item[] }
| { type: 'ADD'; item: Item }
| { type: 'UPDATE'; item: Item }
| { type: 'REMOVE'; item: Item }
@@ -22,6 +31,7 @@ type ActionType =
type UseItemManagerResult = ReturnType
const ItemContext = createContext({
+ layers: [],
items: [],
addItem: () => {},
updateItem: () => {},
@@ -29,10 +39,13 @@ const ItemContext = createContext({
resetItems: () => {},
setItemsApi: () => {},
setItemsData: () => {},
- allItemsLoaded: false,
})
-function useItemsManager(initialItems: Item[]): {
+function useItemsManager(
+ initialItems: Item[],
+ initialLayers: LayerState,
+): {
+ layers: LayerState
items: Item[]
addItem: (item: Item) => void
updateItem: (item: Item) => void
@@ -40,39 +53,62 @@ function useItemsManager(initialItems: Item[]): {
resetItems: (layer: LayerProps) => void
setItemsApi: (layer: LayerProps) => void
setItemsData: (layer: LayerProps) => void
- allItemsLoaded: boolean
} {
- const addLayer = useAddLayer()
-
- const [allItemsLoaded, setallItemsLoaded] = useState(false)
-
- const [items, dispatch] = useReducer((state: Item[], action: ActionType) => {
- switch (action.type) {
- case 'ADD':
- // eslint-disable-next-line no-case-declarations
- const exist = state.find((item) => item.id === action.item.id)
- if (!exist) {
- return [...state, action.item]
- } else return state
- case 'UPDATE':
- return state.map((item) => {
- if (item.id === action.item.id) {
- return action.item
+ const [{ items, layers }, dispatch] = useReducer(
+ (state: State, action: ActionType) => {
+ switch (action.type) {
+ case 'ADD_LAYER':
+ return {
+ layers: [
+ ...state.layers,
+ {
+ props: action.layer,
+ isInitialized: true,
+ },
+ ],
+ items: [
+ ...state.items,
+ ...action.items.map((item) => ({ ...item, layer: action.layer })),
+ ],
}
- return item
- })
- case 'REMOVE':
- return state.filter((item) => item !== action.item)
- case 'RESET':
- return state.filter((item) => item.layer?.name !== action.layer.name)
- default:
- throw new Error()
- }
- }, initialItems)
+ case 'ADD':
+ // eslint-disable-next-line no-case-declarations
+ const exist = state.items.find((item) => item.id === action.item.id)
+ if (!exist) {
+ return {
+ ...state,
+ items: [...state.items, action.item],
+ }
+ } else return state
+ case 'UPDATE':
+ return {
+ ...state,
+ items: state.items.map((item) => {
+ if (item.id === action.item.id) {
+ return action.item
+ }
+ return item
+ }),
+ }
+ case 'REMOVE':
+ return {
+ ...state,
+ items: state.items.filter((item) => item !== action.item),
+ }
+ case 'RESET':
+ return {
+ ...state,
+ items: state.items.filter((item) => item.layer?.name !== action.layer.name),
+ }
+ default:
+ throw new Error()
+ }
+ },
+ { items: initialItems, layers: initialLayers } as State,
+ )
const setItemsApi = useCallback(async (layer: LayerProps) => {
- addLayer(layer)
- const result = await toast.promise(layer.api!.getItems(), {
+ const items = await toast.promise(layer.api!.getItems(), {
pending: `loading ${layer.name} ...`,
success: `${layer.name} loaded`,
error: {
@@ -81,22 +117,12 @@ function useItemsManager(initialItems: Item[]): {
},
},
})
- result.map((item) => {
- dispatch({ type: 'ADD', item: { ...item, layer } })
- return null
- })
- setallItemsLoaded(true)
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ dispatch({ type: 'ADD_LAYER', layer, items })
}, [])
const setItemsData = useCallback((layer: LayerProps) => {
- addLayer(layer)
- layer.data?.map((item) => {
- dispatch({ type: 'ADD', item: { ...item, layer } })
- return null
- })
- setallItemsLoaded(true)
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ if (!layer.data) return
+ dispatch({ type: 'ADD_LAYER', layer, items: layer.data })
}, [])
const addItem = useCallback(async (item: Item) => {
@@ -129,21 +155,24 @@ function useItemsManager(initialItems: Item[]): {
return {
items,
+ layers,
updateItem,
addItem,
removeItem,
resetItems,
setItemsApi,
setItemsData,
- allItemsLoaded,
}
}
export const ItemsProvider: React.FunctionComponent<{
initialItems: Item[]
+ initialLayers: LayerState
children?: React.ReactNode
-}> = ({ initialItems, children }) => (
- {children}
+}> = ({ initialItems, initialLayers, children }) => (
+
+ {children}
+
)
export const useItems = (): Item[] => {
@@ -181,7 +210,12 @@ export const useSetItemsData = (): UseItemManagerResult['setItemsData'] => {
return setItemsData
}
-export const useAllItemsLoaded = (): UseItemManagerResult['allItemsLoaded'] => {
- const { allItemsLoaded } = useContext(ItemContext)
- return allItemsLoaded
+export const useLayers = (): LayerProps[] => {
+ const { layers } = useContext(ItemContext)
+ return layers.map((layer) => layer.props)
+}
+
+export const useLayerState = (): LayerState => {
+ const { layers } = useContext(ItemContext)
+ return layers
}
diff --git a/lib/src/Components/Map/hooks/useLayers.tsx b/lib/src/Components/Map/hooks/useLayers.tsx
deleted file mode 100644
index 963063b0..00000000
--- a/lib/src/Components/Map/hooks/useLayers.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useCallback, useReducer, createContext, useContext } from 'react'
-
-import type { LayerProps } from '#types/LayerProps'
-
-interface ActionType {
- type: 'ADD LAYER'
- layer: LayerProps
-}
-
-type UseItemManagerResult = ReturnType
-
-const LayerContext = createContext({
- layers: [],
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- addLayer: () => {},
-})
-
-function useLayerManager(initialLayers: LayerProps[]): {
- layers: LayerProps[]
- addLayer: (layer: LayerProps) => void
-} {
- const [layers, dispatch] = useReducer((state: LayerProps[], action: ActionType) => {
- switch (action.type) {
- case 'ADD LAYER':
- // eslint-disable-next-line no-case-declarations
- const exist = state.find((layer) => layer.name === action.layer.name)
- if (!exist) {
- return [...state, action.layer]
- } else return state
- default:
- throw new Error()
- }
- }, initialLayers)
-
- const addLayer = useCallback((layer: LayerProps) => {
- dispatch({
- type: 'ADD LAYER',
- layer,
- })
- }, [])
-
- return { layers, addLayer }
-}
-
-export const LayersProvider: React.FunctionComponent<{
- initialLayers: LayerProps[]
- children?: React.ReactNode
-}> = ({ initialLayers, children }: { initialLayers: LayerProps[]; children?: React.ReactNode }) => (
- {children}
-)
-
-export const useLayers = (): LayerProps[] => {
- const { layers } = useContext(LayerContext)
- return layers
-}
-
-export const useAddLayer = (): UseItemManagerResult['addLayer'] => {
- const { addLayer } = useContext(LayerContext)
- return addLayer
-}
diff --git a/lib/src/Components/Map/hooks/useMyProfile.ts b/lib/src/Components/Map/hooks/useMyProfile.ts
index e94c82ed..f8f2c05a 100644
--- a/lib/src/Components/Map/hooks/useMyProfile.ts
+++ b/lib/src/Components/Map/hooks/useMyProfile.ts
@@ -1,15 +1,18 @@
import { useAuth } from '#components/Auth/useAuth'
-import { useItems, useAllItemsLoaded } from './useItems'
+import { useItems, useLayerState } from './useItems'
export const useMyProfile = () => {
const items = useItems()
- const allItemsLoaded = useAllItemsLoaded()
+ const layers = useLayerState()
const user = useAuth().user
- // allItemsLoaded is not reliable, so we check if items.length > 0
- const isMyProfileLoaded = allItemsLoaded && items.length > 0 && !!user
+ const isUserProfileLayerLoaded = layers.some(
+ (layer) => layer.props.userProfileLayer && layer.isInitialized,
+ )
+
+ const isMyProfileLoaded = isUserProfileLayerLoaded && !!user
// Find the user's profile item
const myProfile = items.find(
diff --git a/lib/src/Components/Profile/ProfileForm.tsx b/lib/src/Components/Profile/ProfileForm.tsx
index b4d82862..62e5850b 100644
--- a/lib/src/Components/Profile/ProfileForm.tsx
+++ b/lib/src/Components/Profile/ProfileForm.tsx
@@ -6,7 +6,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '#components/Auth/useAuth'
import { useItems, useUpdateItem, useAddItem } from '#components/Map/hooks/useItems'
-import { useLayers } from '#components/Map/hooks/useLayers'
+import { useLayers } from '#components/Map/hooks/useItems'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { MapOverlayPage } from '#components/Templates'
diff --git a/lib/src/Components/Profile/ProfileView.tsx b/lib/src/Components/Profile/ProfileView.tsx
index 498f0347..7f3eda93 100644
--- a/lib/src/Components/Profile/ProfileView.tsx
+++ b/lib/src/Components/Profile/ProfileView.tsx
@@ -14,7 +14,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { useClusterRef } from '#components/Map/hooks/useClusterRef'
import { useItems, useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems'
-import { useLayers } from '#components/Map/hooks/useLayers'
+import { useLayers } from '#components/Map/hooks/useItems'
import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useSelectPosition, useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
diff --git a/lib/src/Components/Templates/OverlayItemsIndexPage.tsx b/lib/src/Components/Templates/OverlayItemsIndexPage.tsx
index 094ec95f..5557c2fb 100644
--- a/lib/src/Components/Templates/OverlayItemsIndexPage.tsx
+++ b/lib/src/Components/Templates/OverlayItemsIndexPage.tsx
@@ -11,7 +11,7 @@ import { useAuth } from '#components/Auth/useAuth'
import { TextInput } from '#components/Input'
import { useFilterTags } from '#components/Map/hooks/useFilter'
import { useAddItem, useItems, useRemoveItem } from '#components/Map/hooks/useItems'
-import { useLayers } from '#components/Map/hooks/useLayers'
+import { useLayers } from '#components/Map/hooks/useItems'
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { Control } from '#components/Map/Subcomponents/Controls/Control'
import { SearchControl } from '#components/Map/Subcomponents/Controls/SearchControl'