Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions apps/geoportal/src/app/components/GeoportalMap/GeoportalMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,13 @@ import {
getLayersIdle,
getShowHamburgerMenu,
setLayersIdle,
setMaplibreMaps,
} from "../../store/slices/mapping.ts";
import { getUIMode, UIMode } from "../../store/slices/ui.ts";
import {
getUIMode,
UIMode,
getTriggerFeatureInfoUpdate,
} from "../../store/slices/ui.ts";

import LoginForm from "../LoginForm.tsx";
import { useModelSelectionDispatcher } from "../../hooks/useModelSelectionDispatcher.ts";
Expand Down Expand Up @@ -215,6 +220,7 @@ export const GeoportalMap = ({ height, width, allow3d }: MapProps) => {
const [shouldUpdateFeatureInfo, setShouldUpdateFeatureInfo] =
useState<boolean>(false);
const layersIdle = useSelector(getLayersIdle);
const triggerFeatureInfoUpdate = useSelector(getTriggerFeatureInfoUpdate);

const version = getApplicationVersion(versionData);

Expand Down Expand Up @@ -528,10 +534,11 @@ export const GeoportalMap = ({ height, width, allow3d }: MapProps) => {
);

useEffect(() => {
if (shouldUpdateFeatureInfo) updateFeatureInfoLeaflet();
if (shouldUpdateFeatureInfo || triggerFeatureInfoUpdate > 0)
updateFeatureInfoLeaflet();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [shouldUpdateFeatureInfo]);
}, [shouldUpdateFeatureInfo, triggerFeatureInfoUpdate]);

const topicMapLocationChangedHandler = useCallback(
(p: { lat: number; lng: number; zoom: number }) => {
Expand Down Expand Up @@ -588,6 +595,7 @@ export const GeoportalMap = ({ height, width, allow3d }: MapProps) => {
selectedFeature,
leafletMap: getLeafletMap(),
maplibreMapsRef,
setMaplibreMaps: (entry) => dispatch(setMaplibreMaps(entry)),
}),
[
uiMode,
Expand All @@ -596,6 +604,7 @@ export const GeoportalMap = ({ height, width, allow3d }: MapProps) => {
selectedFeature,
getLeafletMap,
maplibreMapsRef,
setMaplibreMaps,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ export const useCreateCismapLayers = (
selectedFeature,
leafletMap,
maplibreMapsRef,
setMaplibreMaps,
}: {
mode: UIMode;
dispatch: Dispatch;
zoom: number;
selectedFeature: any;
leafletMap: LeafletMap;
maplibreMapsRef?: React.MutableRefObject<Map<string, any>>;
setMaplibreMaps?: (entry: { id: string; map: any }) => void;
}
) => {
const [globalHits, setGlobalHits] = useState({});
Expand Down Expand Up @@ -272,6 +274,9 @@ export const useCreateCismapLayers = (
if (maplibreMapsRef) {
maplibreMapsRef.current.set(layer.id, map);
}
if (setMaplibreMaps) {
setMaplibreMaps({ id: layer.id, map });
}
},
onStyleIdle: (e) => {
setIdleLayers((old) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ const createVectorFeature = async (
geometry: selectedVectorFeature.geometry,
id: layer.id,
vectorId: selectedVectorFeature.id,
sourceFeature: selectedVectorFeature,
showMarker:
selectedVectorFeature.geometry.type === "Polygon" ||
selectedVectorFeature.geometry.type === "MultiPolygon",
Expand Down
51 changes: 49 additions & 2 deletions apps/geoportal/src/app/components/layers/GeoportalLayerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,34 @@ import { useDispatch, useSelector } from "react-redux";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

import { faEye, faEyeSlash, faX } from "@fortawesome/free-solid-svg-icons";
import {
faEye,
faEyeSlash,
faFilter,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import L from "leaflet";

import { TopicMapContext } from "react-cismap/contexts/TopicMapContextProvider";

import type { Layer } from "@carma/types";
import { cn, getHashParams } from "@carma-commons/utils";
import type { FilterInfo } from "@carma-mapping/components";

import { updateInfoElementsAfterRemovingFeature } from "../../store/slices/features";
import {
changeVisibility,
getClickFromInfoView,
getLayers,
getSelectedLayerIndex,
getActiveFilterLayerID,
getShowLeftScrollButton,
removeLayer,
setClickFromInfoView,
setSelectedLayerIndex,
setSelectedLayerIndexNoSelection,
setActiveFilterLayerID,
setShowLeftScrollButton,
setShowRightScrollButton,
toggleUseInFeatureInfo,
Expand All @@ -40,7 +48,7 @@ import "./pulsing.css";
import "./tabs.css";

import { LayerButton, LayerIcon } from "@carma-mapping/components";
import { Spin } from "antd";
import { Badge, Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import { useLayerLoading } from "@carma-mapping/utils";

Expand All @@ -51,6 +59,7 @@ interface LayerButtonProps {
icon?: string;
layer: Layer;
background?: boolean;
filterInfo?: FilterInfo;
}

const GeoportalLayerButton = ({
Expand All @@ -60,6 +69,7 @@ const GeoportalLayerButton = ({
icon,
layer,
background,
filterInfo,
}: LayerButtonProps) => {
const { ref, inView } = useInView({
threshold: 0.99,
Expand All @@ -84,6 +94,7 @@ const GeoportalLayerButton = ({
const showLayerHideButtons = useSelector(getUIShowLayerHideButtons);
const showLeftScrollButton = useSelector(getShowLeftScrollButton);
const clickFromInfoView = useSelector(getClickFromInfoView);
const activeFilterLayerID = useSelector(getActiveFilterLayerID);
const mode = useSelector(getUIMode);
const showSettings = index === selectedLayerIndex;
const layers = useSelector(getLayers);
Expand Down Expand Up @@ -202,6 +213,42 @@ const GeoportalLayerButton = ({
{!background && (
<>
<span className="text-base ml-1">{title}</span>
{layer.filterConfig && (
<button
id={`filterLayerButton-${id}`}
className={cn("px-1.5 flex items-center justify-center")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
dispatch(
setActiveFilterLayerID(
activeFilterLayerID === id ? null : id
)
);
}}
>
<Badge
count={
filterInfo && !filterInfo.isShowingAll
? filterInfo.activeCount
: 0
}
size="small"
color={"#4b5563"}
>
<FontAwesomeIcon
icon={faFilter}
className={cn(
"text-sm",
activeFilterLayerID === id
? "text-[#1677ff]"
: "text-gray-600 hover:text-gray-500"
)}
/>
</Badge>
</button>
)}

<button
id={`removeLayerButton-${id}`}
className="hover:text-gray-500 text-gray-600 px-1.5 flex items-center justify-center"
Expand Down
93 changes: 92 additions & 1 deletion apps/geoportal/src/app/components/layers/LayerWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */

import { useContext, useEffect } from "react";
import { useContext, useEffect, useMemo, useState, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";

import {
Expand All @@ -27,21 +27,38 @@ import { useWindowSize } from "@uidotdev/usehooks";
import { TopicMapContext } from "react-cismap/contexts/TopicMapContextProvider";

import { cn } from "@carma-commons/utils";
import {
createFilterButtons,
type FilterInfo,
} from "@carma-mapping/components";

import { AppDispatch } from "../../store";
import {
getBackgroundLayer,
getLayers,
getSelectedLayerIndex,
getSelectedLayerIndexIsNoSelection,
getActiveFilterLayerID,
getShowLeftScrollButton,
getShowRightScrollButton,
setLayers,
setSelectedLayerIndex,
setSelectedLayerIndexNoSelection,
setShowLeftScrollButton,
setShowRightScrollButton,
getMaplibreMaps,
} from "../../store/slices/mapping";
import {
getFeatures,
getSelectedFeature,
getSecondaryInfoBoxElements,
setSelectedFeature as setSelectedFeatureAction,
} from "../../store/slices/features";
import {
getUIMode,
triggerFeatureInfoUpdateAction,
UIMode,
} from "../../store/slices/ui";
import GeoportalLayerButton from "./GeoportalLayerButton";
import SecondaryView from "./SecondaryView";

Expand All @@ -52,13 +69,25 @@ const LayerWrapper = () => {
const { routedMapRef } = useContext<typeof TopicMapContext>(TopicMapContext);
const size = useWindowSize();

const [layerFilterInfo, setLayerFilterInfo] = useState<
Record<string, FilterInfo>
>({});

const filterComponentsCache = useRef<Map<string, any>>(new Map());

const layers = useSelector(getLayers);
const backgroundLayer = useSelector(getBackgroundLayer);
const maplibreMaps = useSelector(getMaplibreMaps);
const selectedFeature = useSelector(getSelectedFeature);
const uiMode = useSelector(getUIMode);

const selectedLayerIndex = useSelector(getSelectedLayerIndex);
const isNoSelectionIndex = useSelector(getSelectedLayerIndexIsNoSelection);
const showLeftScrollButton = useSelector(getShowLeftScrollButton);
const showRightScrollButton = useSelector(getShowRightScrollButton);
const activeFilterLayerID = useSelector(getActiveFilterLayerID);

const isModeFeatureInfo = uiMode === UIMode.FEATURE_INFO;

const { isOver, setNodeRef } = useDroppable({
id: "droppable",
Expand Down Expand Up @@ -100,6 +129,34 @@ const LayerWrapper = () => {
}
}, [size]);

// Create filter components for all layers that have filterConfig
// We render all but hide inactive ones to preserve state
// Cache components by layer ID to preserve state when layers are reordered
const filterComponents = useMemo(() => {
const cache = filterComponentsCache.current;

const currentLayerIds = new Set(layers.map((l) => l.id));
for (const [id] of cache) {
if (!currentLayerIds.has(id)) {
cache.delete(id);
}
}

layers.forEach((layer) => {
if (layer.filterConfig && !cache.has(layer.id)) {
cache.set(layer.id, {
id: layer.id,
Component: createFilterButtons(layer.filterConfig),
});
}
});

// Return only components for layers that currently exist and have filterConfig
return layers
.filter((layer) => layer.filterConfig && cache.has(layer.id))
.map((layer) => cache.get(layer.id));
}, [layers]);

console.debug("RENDER: LayerWrapper selectedLayerIndex", selectedLayerIndex);

return (
Expand Down Expand Up @@ -189,6 +246,7 @@ const LayerWrapper = () => {
: undefined
}
layer={layer}
filterInfo={layerFilterInfo[layer.id]}
/>
))}
</SortableContext>
Expand All @@ -199,6 +257,39 @@ const LayerWrapper = () => {
</div>
</DndContext>

{filterComponents.map((filterEntry) => {
const isActive = filterEntry.id === activeFilterLayerID;
const maplibreMap = maplibreMaps
? maplibreMaps.find((entry) => entry.id === filterEntry.id)?.map ??
null
: null;
const FilterComponent = filterEntry.Component;
return (
<div
key={filterEntry.id}
className={cn(
"pt-3 w-full flex items-center justify-center",
!isActive && "hidden"
)}
>
<FilterComponent
maplibreMap={maplibreMap}
selectedFeature={selectedFeature}
skipFeatureMatchCheck={isModeFeatureInfo}
setSelectedFeature={(feature) => {
dispatch(setSelectedFeatureAction(feature));
}}
onFilterChange={(info: FilterInfo) => {
setLayerFilterInfo((prev) => ({
...prev,
[filterEntry.id]: info,
}));
dispatch(triggerFeatureInfoUpdateAction());
}}
/>
</div>
);
})}
{!isNoSelectionIndex && <SecondaryView />}
</>
);
Expand Down
12 changes: 11 additions & 1 deletion apps/geoportal/src/app/components/layers/SecondaryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ const SecondaryView = forwardRef<Ref, SecondaryViewProps>(({}, ref) => {
const openBaseLayerViewButtons = document.querySelectorAll(
'[id^="openBaseLayerView"]'
);
const filterLayerButtons = document.querySelectorAll(
'[id^="filterLayerButton-"]'
);

openBaseLayerViewButtons.forEach((layerButton, i) => {
if (layerButton.contains(event.target as Node)) {
Expand All @@ -108,6 +111,13 @@ const SecondaryView = forwardRef<Ref, SecondaryViewProps>(({}, ref) => {
}
});

filterLayerButtons.forEach((filterButton) => {
if (filterButton.contains(event.target as Node)) {
returnFunction = true;
return;
}
});

const foundElement = findElementByIdRecursive(
event.target as Element,
"openBaseLayerView"
Expand Down Expand Up @@ -168,7 +178,7 @@ const SecondaryView = forwardRef<Ref, SecondaryViewProps>(({}, ref) => {
onClick={() => {
dispatch(setSelectedLayerIndexNoSelection());
}}
className="pt-4 w-full"
className="pt-3 w-full"
>
<div className="flex items-center justify-center w-full">
<div
Expand Down
Loading
Loading