From 30d96bfd91dc986d2ff72fe9b19818ef4dc3cd8c Mon Sep 17 00:00:00 2001 From: Anton Tranelis Date: Sun, 13 Oct 2024 16:27:32 +0200 Subject: [PATCH] dynamic router and context loading only if needed --- src/Components/AppShell/AppShell.tsx | 70 ++------- src/Components/AppShell/ContextWrapper.tsx | 98 +++++++++++++ src/Components/Map/UtopiaMap.tsx | 162 +-------------------- src/Components/Map/UtopiaMapInner.tsx | 131 +++++++++++++++++ 4 files changed, 247 insertions(+), 214 deletions(-) create mode 100644 src/Components/AppShell/ContextWrapper.tsx create mode 100644 src/Components/Map/UtopiaMapInner.tsx diff --git a/src/Components/AppShell/AppShell.tsx b/src/Components/AppShell/AppShell.tsx index 034c989b..890142d8 100644 --- a/src/Components/AppShell/AppShell.tsx +++ b/src/Components/AppShell/AppShell.tsx @@ -1,71 +1,21 @@ import * as React from 'react' import NavBar from './NavBar' -import { BrowserRouter } from 'react-router-dom' -import { ToastContainer } from 'react-toastify' -import { QuestsProvider } from '../Gaming/hooks/useQuests' -import { AssetsProvider } from './hooks/useAssets' import { SetAssetsApi } from './SetAssetsApi' import { AssetsApi } from '../../types' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { PermissionsProvider } from '../Map/hooks/usePermissions' -import { TagsProvider } from '../Map/hooks/useTags' -import { FilterProvider } from '../Map/hooks/useFilter' -import { ItemsProvider } from '../Map/hooks/useItems' -import { LayersProvider } from '../Map/hooks/useLayers' -import { LeafletRefsProvider } from '../Map/hooks/useLeafletRefs' -import { SelectPositionProvider } from '../Map/hooks/useSelectPosition' -import { ClusterRefProvider } from '../Map/hooks/useClusterRef' import 'react-toastify/dist/ReactToastify.css'; +import { ContextWrapper } from './ContextWrapper'; export function AppShell({ appName, children, assetsApi, userType }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi, userType: string }) { - - // Create a client - const queryClient = new QueryClient() - - return ( - - - - - - - - - - - - - - -
- -
- {children} -
-
-
-
-
-
-
-
-
-
-
-
-
-
- + +
+ + +
+ {children} +
+
+
) } diff --git a/src/Components/AppShell/ContextWrapper.tsx b/src/Components/AppShell/ContextWrapper.tsx new file mode 100644 index 00000000..f971a574 --- /dev/null +++ b/src/Components/AppShell/ContextWrapper.tsx @@ -0,0 +1,98 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ToastContainer } from 'react-toastify' +import { QuestsProvider } from '../Gaming/hooks/useQuests' +import { ClusterRefProvider } from '../Map/hooks/useClusterRef' +import { FilterProvider } from '../Map/hooks/useFilter' +import { ItemsProvider } from '../Map/hooks/useItems' +import { LayersProvider } from '../Map/hooks/useLayers' +import { LeafletRefsProvider } from '../Map/hooks/useLeafletRefs' +import { PermissionsProvider } from '../Map/hooks/usePermissions' +import { SelectPositionProvider } from '../Map/hooks/useSelectPosition' +import { TagsProvider } from '../Map/hooks/useTags' +import { AssetsProvider } from './hooks/useAssets' +import { useContext, createContext } from 'react'; +import { BrowserRouter as Router, useLocation } from 'react-router-dom'; + + +// Helper context to determine if the ContextWrapper is already present. +const ContextCheckContext = createContext(false); + +export const ContextWrapper = ({ children }) => { + const isWrapped = useContext(ContextCheckContext); + + // Check if we are already inside a Router + let location; + try { + location = useLocation(); + } catch (e) { + location = null; + } + + // Case 1: Only the Router is missing, but ContextWrapper is already provided + if (!location && isWrapped) { + console.log("Router is not present, but ContextWrapper is already provided. Wrapping with Router only."); + return ( + + {children} + + ); + } + + // Case 2: Neither Router nor ContextWrapper is present + if (!location && !isWrapped) { + console.log("Router and ContextWrapper are not present. Wrapping both."); + return ( + + + + {children} + + + + ); + } + + // Case 3: Only ContextWrapper is missing + if (location && !isWrapped) { + console.log("ContextWrapper is not present. Wrapping ContextWrapper."); + return ( + + {children} + + ); + } + + // Case 4: Both Router and ContextWrapper are already present + console.log("Router and ContextWrapper are already present."); + return children; +}; + +export const Wrappers = ({ children }) => { + const queryClient = new QueryClient(); + + return ( + + + + + + + + + + + + {children} + + + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/src/Components/Map/UtopiaMap.tsx b/src/Components/Map/UtopiaMap.tsx index f4df7297..e4e4ab3b 100644 --- a/src/Components/Map/UtopiaMap.tsx +++ b/src/Components/Map/UtopiaMap.tsx @@ -1,159 +1,13 @@ -import { TileLayer, MapContainer, useMapEvents, GeoJSON } from "react-leaflet"; -import "leaflet/dist/leaflet.css"; -import * as React from "react"; -import { UtopiaMapProps } from "../../types" -import "./UtopiaMap.css" -import { LatLng } from "leaflet"; -import MarkerClusterGroup from 'react-leaflet-cluster' -import AddButton from "./Subcomponents/AddButton"; -import { useEffect, useState } from "react"; -import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup"; -import { SearchControl } from "./Subcomponents/Controls/SearchControl"; -import { Control } from "./Subcomponents/Controls/Control"; -import { BrowserRouter, Outlet, useLocation } from "react-router-dom"; -import { TagsControl } from "./Subcomponents/Controls/TagsControl"; -import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from "./hooks/useSelectPosition"; -import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef"; -import { Feature, Geometry as GeoJSONGeometry } from 'geojson'; -import { FilterControl } from "./Subcomponents/Controls/FilterControl"; -import { LayerControl } from "./Subcomponents/Controls/LayerControl"; -import { useLayers } from "./hooks/useLayers"; -import { useAddVisibleLayer } from "./hooks/useFilter"; -import { GratitudeControl } from "./Subcomponents/Controls/GratitudeControl"; -import { SelectPosition } from "./Subcomponents/SelectPosition"; - -const mapDivRef = React.createRef(); - -export const Router = ({ children }) => { - let location; - try { - location = useLocation(); - } catch (e) { - location = null; - } - - if (!location) { - return ( - - {children} - - ); - } - return children; - }; - - -function UtopiaMap({ - height = "500px", - width = "100%", - center = [50.6, 9.5], - zoom = 10, - children, - geo, - showFilterControl = false, - showGratitudeControl = false, - showLayerControl = true -} - : UtopiaMapProps) { - - function MapEventListener() { - useMapEvents({ - click: (e) => { - resetMetaTags(); - console.log(e.latlng.lat + ',' + e.latlng.lng); - selectNewItemPosition && setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }) - }, - moveend: () => { - } - }) - return null - } - - const resetMetaTags = () => { - let params = new URLSearchParams(window.location.search); - if (!location.pathname.includes("/item/")) { - window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`) - } - document.title = document.title.split("-")[0]; - document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title); - document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`); - } - - - const selectNewItemPosition = useSelectPosition(); - const setSelectNewItemPosition = useSetSelectPosition(); - const setClusterRef = useSetClusterRef(); - const clusterRef = useClusterRef(); - const setMapClicked = useSetMapClicked(); - - const [itemFormPopup, setItemFormPopup] = useState(null); - - - - - const layers = useLayers(); - const addVisibleLayer = useAddVisibleLayer(); - - useEffect(() => { - layers.map(l => addVisibleLayer(l)) - - }, [layers]) - - - - - const onEachFeature = (feature: Feature, layer: L.Layer) => { - if (feature.properties) { - layer.bindPopup(feature.properties.name); - } - } +import { UtopiaMapProps } from "../../types"; +import { ContextWrapper } from "../AppShell/ContextWrapper"; +import { UtopiaMapInner } from "./UtopiaMapInner"; +function UtopiaMap(props: UtopiaMapProps) { return ( - -
- - - - - - - - {/*{!embedded && (*/} - {/* */} - {/*)}*/} - {showFilterControl && } - {/*todo: needed layer handling is located LayerControl*/} - {showLayerControl && } - {showGratitudeControl && } - - - setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}> - { - React.Children.toArray(children).map((child) => - React.isValidElement<{ setItemFormPopup: React.Dispatch>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject }>(child) ? - React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup, clusterRef: clusterRef }) : child - ) - } - - {geo && { - selectNewItemPosition && e.layer!.closePopup(); - selectNewItemPosition && setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }) - }, - }} />} - - - - {selectNewItemPosition != null && - - } -
- -
+ + + ); } -export { UtopiaMap }; \ No newline at end of file +export { UtopiaMap }; diff --git a/src/Components/Map/UtopiaMapInner.tsx b/src/Components/Map/UtopiaMapInner.tsx new file mode 100644 index 00000000..67f6e341 --- /dev/null +++ b/src/Components/Map/UtopiaMapInner.tsx @@ -0,0 +1,131 @@ +import { TileLayer, MapContainer, useMapEvents, GeoJSON } from "react-leaflet"; +import "leaflet/dist/leaflet.css"; +import * as React from "react"; +import { UtopiaMapProps } from "../../types"; +import "./UtopiaMap.css"; +import { LatLng } from "leaflet"; +import MarkerClusterGroup from 'react-leaflet-cluster'; +import AddButton from "./Subcomponents/AddButton"; +import { useEffect, useState } from "react"; +import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup"; +import { SearchControl } from "./Subcomponents/Controls/SearchControl"; +import { Control } from "./Subcomponents/Controls/Control"; +import { Outlet } from "react-router-dom"; +import { TagsControl } from "./Subcomponents/Controls/TagsControl"; +import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from "./hooks/useSelectPosition"; +import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef"; +import { Feature, Geometry as GeoJSONGeometry } from 'geojson'; +import { FilterControl } from "./Subcomponents/Controls/FilterControl"; +import { LayerControl } from "./Subcomponents/Controls/LayerControl"; +import { useLayers } from "./hooks/useLayers"; +import { useAddVisibleLayer } from "./hooks/useFilter"; +import { GratitudeControl } from "./Subcomponents/Controls/GratitudeControl"; +import { SelectPosition } from "./Subcomponents/SelectPosition"; + +const mapDivRef = React.createRef(); + +export function UtopiaMapInner({ + height = "500px", + width = "100%", + center = [50.6, 9.5], + zoom = 10, + children, + geo, + showFilterControl = false, + showGratitudeControl = false, + showLayerControl = true +}: UtopiaMapProps) { + + // Hooks that rely on contexts, called after ContextWrapper is provided + const selectNewItemPosition = useSelectPosition(); + const setSelectNewItemPosition = useSetSelectPosition(); + const setClusterRef = useSetClusterRef(); + const clusterRef = useClusterRef(); + const setMapClicked = useSetMapClicked(); + const [itemFormPopup, setItemFormPopup] = useState(null); + + const layers = useLayers(); + const addVisibleLayer = useAddVisibleLayer(); + + useEffect(() => { + layers.forEach(layer => addVisibleLayer(layer)); + }, [layers]); + + function MapEventListener() { + useMapEvents({ + click: (e) => { + resetMetaTags(); + console.log(e.latlng.lat + ',' + e.latlng.lng); + if (selectNewItemPosition) { + setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }); + } + }, + moveend: () => { } + }); + return null; + } + + const resetMetaTags = () => { + let params = new URLSearchParams(window.location.search); + if (!window.location.pathname.includes("/item/")) { + window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`); + } + document.title = document.title.split("-")[0]; + document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title); + document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`); + }; + + const onEachFeature = (feature: Feature, layer: L.Layer) => { + if (feature.properties) { + layer.bindPopup(feature.properties.name); + } + }; + + return ( +
+ + + + + + + + {showFilterControl && } + {showLayerControl && } + {showGratitudeControl && } + + + setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}> + { + React.Children.toArray(children).map((child) => + React.isValidElement<{ setItemFormPopup: React.Dispatch>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject }>(child) + ? React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup, clusterRef: clusterRef }) + : child + ) + } + + {geo && ( + { + if (selectNewItemPosition) { + e.layer!.closePopup(); + setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }); + } + }, + }} + /> + )} + + + + {selectNewItemPosition != null && } +
+ ); +}