Skip to content

Commit

Permalink
feat(client): Configurable layer return period
Browse files Browse the repository at this point in the history
  • Loading branch information
clementprdhomme committed Nov 6, 2024
1 parent a2fbadc commit 421ffdb
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 21 deletions.
79 changes: 61 additions & 18 deletions client/src/components/dataset-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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 (
Expand All @@ -89,7 +115,7 @@ const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) =>
<Switch id={`${id}-toggle`} checked={isDatasetActive} onCheckedChange={onToggleDataset} />
</div>
</div>
<div className="mt-1">
<div className="mt-1 flex flex-col gap-1.5">
<Select
value={selectedLayerId !== undefined ? `${selectedLayerId}` : ""}
onValueChange={onChangeSelectedLayer}
Expand All @@ -105,6 +131,23 @@ const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) =>
))}
</SelectContent>
</Select>
{!!layerReturnPeriods && (
<Select
value={selectedReturnPeriod !== undefined ? `${selectedReturnPeriod}` : ""}
onValueChange={onChangeSelectedReturnPeriod}
>
<SelectTrigger aria-label="Return period">
<SelectValue placeholder="Select a return period" />
</SelectTrigger>
<SelectContent>
{layerReturnPeriods.options.map((option) => (
<SelectItem key={option} value={`${option}`}>
{`${option}-year return period`}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
</div>
);
Expand Down
76 changes: 76 additions & 0 deletions client/src/components/dataset-card/utils.ts
Original file line number Diff line number Diff line change
@@ -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<typeof useMapLayers>[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<typeof useMapLayers>[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),
};
};
7 changes: 6 additions & 1 deletion client/src/components/map/layer-manager/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) =>
}

return (
<Source {...config.source}>
<Source
// The key ensures that if the URL of the source changes, Mapbox will correctly detect the
// change
key={config.source.url}
{...config.source}
>
{config.styles.map((style) => (
<Layer key={style.id} {...style} beforeId={beforeId} />
))}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/panels/flood/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="min-h-screen px-5 py-5 lg:min-h-0 lg:px-10">
Expand Down
3 changes: 3 additions & 0 deletions client/src/hooks/use-layer-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion client/src/hooks/use-map-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -17,12 +18,13 @@ export default function useMapLayers() {
);

const addLayer = useCallback(
(id: number) => {
(id: number, attributes?: Partial<z.infer<typeof schema>>) => {
setLayers((layers) => [
{
id,
visibility: true,
opacity: 1,
...attributes,
},
...layers,
]);
Expand Down
2 changes: 2 additions & 0 deletions client/src/types/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -13,6 +14,7 @@ export interface LayerConfig {
export interface LayerParamsConfigValue {
key: string;
default: unknown;
options?: unknown[];
}

export type LayerParamsConfig = LayerParamsConfigValue[];
Expand Down

0 comments on commit 421ffdb

Please sign in to comment.