From 421ffdb5b5113bc2666cdbb985e22a950052a1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 6 Nov 2024 10:56:31 +0100 Subject: [PATCH] feat(client): Configurable layer return period --- client/src/components/dataset-card/index.tsx | 79 ++++++++++++++----- client/src/components/dataset-card/utils.ts | 76 ++++++++++++++++++ .../src/components/map/layer-manager/item.tsx | 7 +- client/src/components/panels/flood/index.tsx | 2 +- client/src/hooks/use-layer-config.ts | 3 + client/src/hooks/use-map-layers.ts | 4 +- client/src/types/layer.ts | 2 + 7 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 client/src/components/dataset-card/utils.ts diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index 2f005e3..168811a 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -14,6 +14,8 @@ import { Switch } from "@/components/ui/switch"; import useMapLayers from "@/hooks/use-map-layers"; import { DatasetLayersDataItem } from "@/types/generated/strapi.schemas"; +import { getDefaultReturnPeriod, getDefaultSelectedLayerId, getReturnPeriods } from "./utils"; + interface DatasetCardProps { id: number; name: string; @@ -24,22 +26,18 @@ interface DatasetCardProps { const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) => { const [layersConfiguration, { addLayer, updateLayer, removeLayer }] = useMapLayers(); - const defaultSelectedLayerId = useMemo(() => { - // The ids of the layers that belong to the dataset - const datasetLayerIds = layers.map(({ id }) => id!); - // The ids of the layers active on the map, probably not from this dataset - const activeLayerIds = layersConfiguration.map(({ id }) => id!); - // The id of the layer that belongs to the dataset and is active, if any - const activeDatasetLayerId = datasetLayerIds.find((id) => activeLayerIds.includes(id)); - - if (activeDatasetLayerId) { - return activeDatasetLayerId; - } + const defaultSelectedLayerId = useMemo( + () => getDefaultSelectedLayerId(defaultLayerId, layers, layersConfiguration), + [layers, defaultLayerId, layersConfiguration], + ); - return defaultLayerId; - }, [layers, defaultLayerId, layersConfiguration]); + const defaultSelectedReturnPeriod = useMemo( + () => getDefaultReturnPeriod(defaultSelectedLayerId, layers, layersConfiguration), + [layers, layersConfiguration, defaultSelectedLayerId], + ); const [selectedLayerId, setSelectedLayerId] = useState(defaultSelectedLayerId); + const [selectedReturnPeriod, setSelectedReturnPeriod] = useState(defaultSelectedReturnPeriod); const isDatasetActive = useMemo(() => { if (selectedLayerId === undefined) { @@ -49,6 +47,11 @@ const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) => return layersConfiguration.findIndex(({ id }) => id === selectedLayerId) !== -1; }, [selectedLayerId, layersConfiguration]); + const layerReturnPeriods = useMemo( + () => getReturnPeriods(selectedLayerId, layers), + [layers, selectedLayerId], + ); + const onToggleDataset = useCallback( (active: boolean) => { if (selectedLayerId === undefined) { @@ -58,25 +61,48 @@ const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) => if (!active) { removeLayer(selectedLayerId); } else { - addLayer(selectedLayerId); + addLayer(selectedLayerId, { ["return-period"]: selectedReturnPeriod }); } }, - [selectedLayerId, addLayer, removeLayer], + [selectedLayerId, addLayer, removeLayer, selectedReturnPeriod], ); const onChangeSelectedLayer = useCallback( (stringId: string) => { const id = Number.parseInt(stringId); const previousId = selectedLayerId; + const returnPeriod = getDefaultReturnPeriod(id, layers, layersConfiguration); setSelectedLayerId(id); + setSelectedReturnPeriod(returnPeriod); + // If the dataset was active and the layer is changed, we replace the current layer by the new // one keeping all the same settings (visibility, opacity, etc.) if (isDatasetActive && previousId !== undefined) { - updateLayer(previousId, { id }); + updateLayer(previousId, { id, ["return-period"]: returnPeriod }); + } + }, + [ + selectedLayerId, + setSelectedLayerId, + isDatasetActive, + updateLayer, + layers, + layersConfiguration, + ], + ); + + const onChangeSelectedReturnPeriod = useCallback( + (stringReturnPeriod: string) => { + const returnPeriod = Number.parseInt(stringReturnPeriod); + + setSelectedReturnPeriod(returnPeriod); + + if (isDatasetActive && selectedLayerId !== undefined) { + updateLayer(selectedLayerId, { ["return-period"]: returnPeriod }); } }, - [selectedLayerId, setSelectedLayerId, isDatasetActive, updateLayer], + [selectedLayerId, setSelectedReturnPeriod, isDatasetActive, updateLayer], ); return ( @@ -89,7 +115,7 @@ const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) => -
+
+ {!!layerReturnPeriods && ( + + )}
); diff --git a/client/src/components/dataset-card/utils.ts b/client/src/components/dataset-card/utils.ts new file mode 100644 index 0000000..2d4e0b3 --- /dev/null +++ b/client/src/components/dataset-card/utils.ts @@ -0,0 +1,76 @@ +import useMapLayers from "@/hooks/use-map-layers"; +import { DatasetLayersDataItem } from "@/types/generated/strapi.schemas"; +import { LayerParamsConfig } from "@/types/layer"; + +export const getDefaultSelectedLayerId = ( + defaultLayerId: number | undefined, + layers: DatasetLayersDataItem[], + layersConfiguration: ReturnType[0], +) => { + // The ids of the layers that belong to the dataset + const datasetLayerIds = layers.map(({ id }) => id!); + // The ids of the layers active on the map, probably not from this dataset + const activeLayerIds = layersConfiguration.map(({ id }) => id!); + // The id of the layer that belongs to the dataset and is active, if any + const activeDatasetLayerId = datasetLayerIds.find((id) => activeLayerIds.includes(id)); + + if (activeDatasetLayerId) { + return activeDatasetLayerId; + } + + return defaultLayerId; +}; + +export const getDefaultReturnPeriod = ( + layerId: number | undefined, + layers: DatasetLayersDataItem[], + layersConfiguration: ReturnType[0], +) => { + const layerConfiguration = layersConfiguration.find(({ id }) => id === layerId); + + // If the layer is active and already has a selected return period, we return it + if (layerConfiguration?.["return-period"] !== undefined) { + return layerConfiguration["return-period"]; + } + + // Else we look for the default return period stored in `params_config` + const layer = layers.find(({ id }) => id === layerId); + const defaultReturnPeriod = ( + layer?.attributes!.params_config as LayerParamsConfig | undefined + )?.find(({ key }) => key === "return-period"); + + if ( + !defaultReturnPeriod || + defaultReturnPeriod.default === undefined || + defaultReturnPeriod.default === null + ) { + return undefined; + } + + return defaultReturnPeriod.default as number; +}; + +export const getReturnPeriods = (layerId: number | undefined, layers: DatasetLayersDataItem[]) => { + const layer = layers.find(({ id }) => id === layerId); + if (!layer) { + return undefined; + } + + const returnPeriod = (layer.attributes!.params_config as LayerParamsConfig | undefined)?.find( + ({ key }) => key === "return-period", + ); + if ( + !returnPeriod || + returnPeriod.default === undefined || + returnPeriod.default === null || + returnPeriod.options === undefined || + returnPeriod.options === null + ) { + return undefined; + } + + return { + defaultOption: returnPeriod.default as number, + options: [...(returnPeriod.options as number[])].sort((a, b) => a - b), + }; +}; diff --git a/client/src/components/map/layer-manager/item.tsx b/client/src/components/map/layer-manager/item.tsx index e89d81a..89daceb 100644 --- a/client/src/components/map/layer-manager/item.tsx +++ b/client/src/components/map/layer-manager/item.tsx @@ -17,7 +17,12 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => } return ( - + {config.styles.map((style) => ( ))} diff --git a/client/src/components/panels/flood/index.tsx b/client/src/components/panels/flood/index.tsx index 42e891d..a51a1c3 100644 --- a/client/src/components/panels/flood/index.tsx +++ b/client/src/components/panels/flood/index.tsx @@ -3,7 +3,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic"; const FloodPanel = () => { - const { data, isLoading } = useDatasetsBySubTopic("flood"); + const { data, isLoading } = useDatasetsBySubTopic("flood", ["name", "params_config"]); return (
diff --git a/client/src/hooks/use-layer-config.ts b/client/src/hooks/use-layer-config.ts index 27090cc..9f104b2 100644 --- a/client/src/hooks/use-layer-config.ts +++ b/client/src/hooks/use-layer-config.ts @@ -44,6 +44,9 @@ const resolveLayerConfig = ( setVisibility({ v }: { v: boolean }) { return v ? "visible" : "none"; }, + match({ input, outputs }: { input: unknown; outputs: [unknown, unknown][] }) { + return outputs.find(([value]) => input === value)?.[1]; + }, }, enumerations: { params: resolvedParamsConfig, diff --git a/client/src/hooks/use-map-layers.ts b/client/src/hooks/use-map-layers.ts index c55cbb9..1d18480 100644 --- a/client/src/hooks/use-map-layers.ts +++ b/client/src/hooks/use-map-layers.ts @@ -8,6 +8,7 @@ const schema = z.object({ id: z.number(), visibility: z.boolean(), opacity: z.number().min(0).max(1), + "return-period": z.number().int().optional(), }); export default function useMapLayers() { @@ -17,12 +18,13 @@ export default function useMapLayers() { ); const addLayer = useCallback( - (id: number) => { + (id: number, attributes?: Partial>) => { setLayers((layers) => [ { id, visibility: true, opacity: 1, + ...attributes, }, ...layers, ]); diff --git a/client/src/types/layer.ts b/client/src/types/layer.ts index 69ab1b5..93635dd 100644 --- a/client/src/types/layer.ts +++ b/client/src/types/layer.ts @@ -3,6 +3,7 @@ import { AnyLayer, AnySource, SkyLayer } from "react-map-gl"; export interface LayerSettings { visibility: boolean; opacity: number; + "return-period"?: number; } export interface LayerConfig { @@ -13,6 +14,7 @@ export interface LayerConfig { export interface LayerParamsConfigValue { key: string; default: unknown; + options?: unknown[]; } export type LayerParamsConfig = LayerParamsConfigValue[];