Skip to content

Commit

Permalink
Merge pull request #21
Browse files Browse the repository at this point in the history
Add most of the Flood layers
  • Loading branch information
clementprdhomme authored Nov 6, 2024
2 parents 560deb3 + 421ffdb commit 481ac79
Show file tree
Hide file tree
Showing 29 changed files with 819 additions and 33 deletions.
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
"@radix-ui/react-label": "2.1.0",
"@radix-ui/react-popover": "1.1.2",
"@radix-ui/react-radio-group": "1.2.1",
"@radix-ui/react-select": "2.1.2",
"@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-slider": "1.2.1",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-switch": "1.1.1",
"@radix-ui/react-tabs": "1.1.1",
"@radix-ui/react-tooltip": "1.1.3",
"@t3-oss/env-nextjs": "0.11.1",
"@tanstack/react-query": "5.59.16",
Expand All @@ -46,6 +48,7 @@
"react-map-gl": "7.1.7",
"tailwind-merge": "2.5.4",
"tailwindcss-animate": "1.0.7",
"tailwindcss-border-image": "1.1.2",
"typescript-eslint": "8.9.0",
"zod": "3.23.8"
},
Expand Down
11 changes: 11 additions & 0 deletions client/public/assets/images/border-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions client/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ body {
@layer base {
:root {
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
Expand Down
156 changes: 156 additions & 0 deletions client/src/components/dataset-card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"use client";

import { useCallback, useMemo, useState } from "react";

import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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;
defaultLayerId: number | undefined;
layers: DatasetLayersDataItem[];
}

const DatasetCard = ({ id, name, defaultLayerId, layers }: DatasetCardProps) => {
const [layersConfiguration, { addLayer, updateLayer, removeLayer }] = useMapLayers();

const defaultSelectedLayerId = useMemo(
() => getDefaultSelectedLayerId(defaultLayerId, layers, layersConfiguration),
[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) {
return false;
}

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) {
return;
}

if (!active) {
removeLayer(selectedLayerId);
} else {
addLayer(selectedLayerId, { ["return-period"]: selectedReturnPeriod });
}
},
[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, ["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, setSelectedReturnPeriod, isDatasetActive, updateLayer],
);

return (
<div className="p-4 border-image-[url(/assets/images/border-image.svg)] border-slice-10 border-image-width-2.5 border-outset-[5px] border-repeat-round">
<div className="flex items-start justify-between gap-4">
<Label htmlFor={`${id}-toggle`} className="text-[20px]">
{name}
</Label>
<div className="pt-1">
<Switch id={`${id}-toggle`} checked={isDatasetActive} onCheckedChange={onToggleDataset} />
</div>
</div>
<div className="mt-1 flex flex-col gap-1.5">
<Select
value={selectedLayerId !== undefined ? `${selectedLayerId}` : ""}
onValueChange={onChangeSelectedLayer}
>
<SelectTrigger aria-label="Layer">
<SelectValue placeholder="Select a layer" />
</SelectTrigger>
<SelectContent>
{layers.map((layer) => (
<SelectItem key={layer.id} value={`${layer.id}`}>
{layer.attributes?.name}
</SelectItem>
))}
</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>
);
};

export default DatasetCard;
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),
};
};
9 changes: 7 additions & 2 deletions client/src/components/map/layer-manager/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ interface LayerManagerItemProps {
const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => {
const config = useLayerConfig(id, settings);

if (!config) {
if (!config?.styles) {
return null;
}

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
13 changes: 12 additions & 1 deletion client/src/components/map/legend/item/basic-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@ const BasicLegend = (data: BasicLegendProps) => {
}

if (data.items.length === 1) {
return <Square item={data.items[0]} className="h-3" />;
const item = data.items[0];

if (item.value !== null) {
return (
<div className="flex items-start gap-2">
<Square item={item} className="relative top-0.5 h-3 w-5 shrink-0" />
<div className="text-2xs text-gray-500">{item.value}</div>
</div>
);
} else {
return <Square item={item} className="h-3" />;
}
}

return (
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/map/legend/item/values.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const Values = (data: ValuesProps) => {
title={item.value}
className={cn({
"flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap": true,
"text-left": index === 0,
"text-center": index > 0 && index + 1 < data.items!.length,
"text-right": index + 1 === data.items!.length,
"text-left": data.type === "gradient" && index === 0,
"text-center":
data.type !== "gradient" || (index > 0 && index + 1 < data.items!.length),
"text-right": data.type === "gradient" && index + 1 === data.items!.length,
})}
>
{item.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const NavigationDesktop = () => {
<SidebarTrigger className="absolute right-0 top-6 z-10 translate-x-1/2 transition-transform group-data-[state=collapsed]:translate-x-full [&_svg]:rotate-90 group-data-[state=collapsed]:[&_svg]:-rotate-90" />
</SidebarHeader>
<SidebarContent className="overflow-auto">
<div className="bg-rhino-blue-900 px-10 pb-6 text-white">
<div className="bg-rhino-blue-900 px-10 pb-8 text-white">
<Intro />
</div>
<MainPanel />
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/navigation/navigation-mobile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const NavigationMobile = () => {
<>
<Sheet modal={false} open={tab === Tab.Main || tab === Tab.Location}>
<SheetOverlay className="!bottom-[68px]" />
<SheetContent side="bottom" className="bottom-[68px] h-[calc(100%_-_68px)]">
<SheetContent
side="bottom"
className="bottom-[68px] h-[calc(100%_-_68px)] overflow-auto pb-0"
>
<SheetHeader className="text-center">
<SheetTitle className="sr-only">{TABS[tab].name}</SheetTitle>
<Intro showDescription={tab === Tab.Main} />
Expand Down
9 changes: 9 additions & 0 deletions client/src/components/panels/drought/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const DroughtPanel = () => {
return (
<div className="min-h-screen lg:min-h-0">
<div className="py-11 text-center font-semibold">Coming soon!</div>
</div>
);
};

export default DroughtPanel;
Loading

0 comments on commit 481ac79

Please sign in to comment.