diff --git a/src/apps/search/map/MapFeaturesContext.tsx b/src/apps/search/map/MapFeaturesContext.tsx new file mode 100644 index 00000000..8e158177 --- /dev/null +++ b/src/apps/search/map/MapFeaturesContext.tsx @@ -0,0 +1,56 @@ +import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'; +import { Feature, FeatureCollection } from '@peripleo/peripleo'; +import { Typesense, useCachedHits } from '@performant-software/core-data'; +import { useSearchConfig } from '../SearchConfigContext'; + +interface MapFeaturesContextType { + places: FeatureCollection; + updatePlace(feature: Feature): void; +} + +const MapFeaturesContext = createContext(null); + +interface Props { + children: ReactNode; +} + +export const MapFeaturesContextProvider = ({ children }: Props) => { + const config = useSearchConfig(); + const hits = useCachedHits(); + + // May have to be an empty feature collection instead! + const [places, setPlaces] = useState(null); + + /** + * Memo-izes the data to be displayed on the map as a feature collection. + */ + useEffect(() => { + const options = config.map.cluster_radius ? { type: 'Point' } : undefined; + const featureCollection = Typesense.toFeatureCollection(hits, config.map.geometry, options); + + // TODO we could make this smarter! If a feature with the given ID already exists, + // we could keep it. (Assuming that the hits would never provide a higher-res version than + // what we already have.) + setPlaces(featureCollection); + }, [hits]); + + const updatePlace = (feature: Feature) => { + const updated = places.features.map(f => f.id === feature.id ? feature : f); + setPlaces({ type: 'FeatureCollection', features: updated }); + } + + return ( + + { children } + + ) +}; + +export const useCachedPlaces = () => { + return useContext(MapFeaturesContext); +} \ No newline at end of file diff --git a/src/apps/search/map/MapLayout.tsx b/src/apps/search/map/MapLayout.tsx index a1f49b63..7c255690 100644 --- a/src/apps/search/map/MapLayout.tsx +++ b/src/apps/search/map/MapLayout.tsx @@ -18,6 +18,7 @@ import { import PanelHistoryContext, { PanelHistoryEntryType } from './PanelHistoryContext'; import GeosearchFilter from '@apps/search/map/GeosearchFilter'; import { useSearchConfig } from '@apps/search/SearchConfigContext'; +import { MapFeaturesContextProvider } from './MapFeaturesContext'; const DEFAULT_MAX_ZOOM = 14; @@ -111,80 +112,82 @@ const MapLayout = () => { return ( -
- -
-
-
+
+ +
+
+
- - - + + + +
+ { view === Views.list && ( +
+ +
+ )}
- { view === Views.list && ( + { view === Views.table && (
- +
+ )} + { timeline && ( +
+
)} -
- { view === Views.table && ( -
- -
- )} - { timeline && (
-
- )} -
-
- + ); }; diff --git a/src/apps/search/map/MapView.tsx b/src/apps/search/map/MapView.tsx index 28b84ba5..d301e2d7 100644 --- a/src/apps/search/map/MapView.tsx +++ b/src/apps/search/map/MapView.tsx @@ -3,17 +3,18 @@ import Tooltip from '@apps/search/map/Tooltip'; import Map from '@components/Map'; import { SearchResultsLayer, - Typesense as TypesenseUtils, - useCachedHits, + // Typesense as TypesenseUtils, + // useCachedHits, useGeoSearch, useSearching } from '@performant-software/core-data'; import { HoverTooltip, useSelectionValue } from '@peripleo/maplibre'; -import { useCurrentRoute, useNavigate } from '@peripleo/peripleo'; +import { Feature, FeatureCollection, useCurrentRoute, useNavigate } from '@peripleo/peripleo'; import { parseFeature } from '@utils/search'; -import { useContext, useEffect, useMemo } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import _ from 'underscore'; import { useSearchConfig } from '@apps/search/SearchConfigContext'; +import { useCachedPlaces } from './MapFeaturesContext'; const MapView = () => { const config = useSearchConfig(); @@ -21,18 +22,20 @@ const MapView = () => { const navigate = useNavigate(); const { selected } = useSelectionValue() || {}; const route = useCurrentRoute(); - const hits = useCachedHits(); + // const hits = useCachedHits(); + const places = useCachedPlaces(); const searching = useSearching(); const { boundingBoxOptions, controlsClass } = useContext(MapSearchContext); /** * Memo-izes the data to be displayed on the map as a feature collection. - */ + * const data = useMemo(() => { const options = config.map.cluster_radius ? { type: 'Point' } : undefined; return TypesenseUtils.toFeatureCollection(hits, config.map.geometry, options); }, [hits]); + */ /** * If we're on the place detail page or refining results by the map view port, we'll suppress the auto-bounding box @@ -79,7 +82,7 @@ const MapView = () => { > { const navigate = useNavigate(); const config = useSearchConfig(); + const { updatePlace } = useCachedPlaces(); const { t } = useContext(TranslationContext); const { setSelected } = useSelection(); @@ -209,7 +211,7 @@ const { data: { people = [] } = {}, loading: peopleLoading } = useLoader(onLoadP /** * Memo-izes the geometry. - */ + * const geometry = useMemo(() => { if (props.resolveGeometry && item) { return props.resolveGeometry(item); @@ -220,6 +222,19 @@ const { data: { people = [] } = {}, loading: peopleLoading } = useLoader(onLoadP places.filter((place) => place.place_geometry) ); }, [item, places, props.resolveGeometry]); + */ + + useEffect(() => { + if (props.resolveGeometry && item) { + const highRes = props.resolveGeometry(item); + updatePlace(highRes); + } else if (!_.isEmpty(places.filter((place) => place.place_geometry))) { + const place = CoreDataUtils.toFeatureCollection( + places.filter((place) => place.place_geometry) + ); + updatePlace(place); + } + }, [item, places, props.resolveGeometry]) /** * Memo-izes the related media items. @@ -450,7 +465,7 @@ const { data: { people = [] } = {}, loading: peopleLoading } = useLoader(onLoadP /> )} - { geometry && ( + {/* geometry && ( - )} + ) */} { manifestUrl && (