From 26096fb3d953ab6a18d1ac3df4bc10bf324e83ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 23 Oct 2024 16:06:03 +0200 Subject: [PATCH 1/8] Add the map settings button --- client/src/components/map/controls/index.tsx | 13 ++++++++++++ .../components/map/controls/map-settings.tsx | 21 +++++++++++++++++++ .../map/{controls.tsx => controls/zoom.tsx} | 6 +++--- client/src/components/ui/button.tsx | 2 +- client/src/svgs/globe.svg | 3 +++ 5 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 client/src/components/map/controls/index.tsx create mode 100644 client/src/components/map/controls/map-settings.tsx rename client/src/components/map/{controls.tsx => controls/zoom.tsx} (88%) create mode 100644 client/src/svgs/globe.svg diff --git a/client/src/components/map/controls/index.tsx b/client/src/components/map/controls/index.tsx new file mode 100644 index 0000000..96a0091 --- /dev/null +++ b/client/src/components/map/controls/index.tsx @@ -0,0 +1,13 @@ +import MapSettingsControls from "./map-settings"; +import ZoomControls from "./zoom"; + +const Controls = () => { + return ( +
+ + +
+ ); +}; + +export default Controls; diff --git a/client/src/components/map/controls/map-settings.tsx b/client/src/components/map/controls/map-settings.tsx new file mode 100644 index 0000000..315f1d1 --- /dev/null +++ b/client/src/components/map/controls/map-settings.tsx @@ -0,0 +1,21 @@ +import { PropsWithChildren } from "react"; + +import { Button } from "@/components/ui/button"; +import GlobeIcon from "@/svgs/globe.svg"; + +const MapSettingsPanel = ({ children }: PropsWithChildren) => { + return children; +}; + +const MapSettingsControls = () => { + return ( + + + + ); +}; + +export default MapSettingsControls; diff --git a/client/src/components/map/controls.tsx b/client/src/components/map/controls/zoom.tsx similarity index 88% rename from client/src/components/map/controls.tsx rename to client/src/components/map/controls/zoom.tsx index 5ed9138..e0e2745 100644 --- a/client/src/components/map/controls.tsx +++ b/client/src/components/map/controls/zoom.tsx @@ -5,14 +5,14 @@ import { Button } from "@/components/ui/button"; import MinusIcon from "@/svgs/minus.svg"; import PlusIcon from "@/svgs/plus.svg"; -const Controls = () => { +const ZoomControls = () => { const { current: map } = useMap(); const onClickZoomIn = useCallback(() => map?.zoomIn(), [map]); const onClickZoomOut = useCallback(() => map?.zoomOut(), [map]); return ( -
+
- + + + + + + + + ); }; diff --git a/client/src/components/panels/map-settings/index.tsx b/client/src/components/panels/map-settings/index.tsx new file mode 100644 index 0000000..8b7c7e7 --- /dev/null +++ b/client/src/components/panels/map-settings/index.tsx @@ -0,0 +1,5 @@ +const MapSettingsPanel = () => { + return
Coming soon!
; +}; + +export default MapSettingsPanel; diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx index 3722ca2..99cc045 100644 --- a/client/src/components/ui/button.tsx +++ b/client/src/components/ui/button.tsx @@ -12,7 +12,7 @@ const buttonVariants = cva( default: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950", yellow: - "bg-supernova-yellow-400 hover:bg-supernova-yellow-300 text-casper-blue-950 focus-visible:ring-casper-blue-400", + "bg-supernova-yellow-400 hover:bg-supernova-yellow-300 text-casper-blue-950 focus-visible:ring-casper-blue-400 data-[state=open]:bg-rhino-blue-900 data-[state=open]:hover:bg-rhino-blue-950 data-[state=open]:text-supernova-yellow-400", ghost: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950", }, From c309be0b822cf44605cd015ac5d66bb9a25ddd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 23 Oct 2024 17:24:50 +0200 Subject: [PATCH 4/8] Add the RadioGroup and Label components --- client/package.json | 2 + client/src/components/ui/label.tsx | 19 ++++ client/src/components/ui/radio-group.tsx | 57 +++++++++++ client/yarn.lock | 124 +++++++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 client/src/components/ui/label.tsx create mode 100644 client/src/components/ui/radio-group.tsx diff --git a/client/package.json b/client/package.json index cfa965f..1335698 100644 --- a/client/package.json +++ b/client/package.json @@ -11,7 +11,9 @@ "dependencies": { "@artsy/fresnel": "7.1.4", "@radix-ui/react-dialog": "1.1.2", + "@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-separator": "1.1.0", "@radix-ui/react-slot": "1.1.0", "@radix-ui/react-tooltip": "1.1.3", diff --git a/client/src/components/ui/label.tsx b/client/src/components/ui/label.tsx new file mode 100644 index 0000000..2b67082 --- /dev/null +++ b/client/src/components/ui/label.tsx @@ -0,0 +1,19 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva("peer-disabled:cursor-not-allowed peer-disabled:opacity-70"); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/client/src/components/ui/radio-group.tsx b/client/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..0eac5a1 --- /dev/null +++ b/client/src/components/ui/radio-group.tsx @@ -0,0 +1,57 @@ +"use client"; + +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import { cva, VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ; +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const radioGroupItemVariants = cva( + "peer aspect-square rounded-full border ring-offset-white transition-colors focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-casper-blue-400 disabled:cursor-not-allowed disabled:opacity-20", + { + variants: { + variant: { + default: + "border-rhino-blue-950 hover:bg-rhino-blue-50 data-[state=checked]:border-4 data-[state=checked]:hover:border-rhino-blue-900", + icon: "border-white flex items-center justify-center data-[state=checked]:border-rhino-blue-950", + }, + size: { + default: "h-3 w-3", + icon: "h-[26px] w-[26px]", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.PropsWithChildren> & + VariantProps +>(({ className, children, variant, size, ...props }, ref) => { + return ( + + {children} + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +const RadioGroupIndicator = RadioGroupPrimitive.Indicator; + +export { RadioGroup, RadioGroupItem, RadioGroupIndicator }; diff --git a/client/yarn.lock b/client/yarn.lock index ae0974a..2ebf3cd 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1670,6 +1670,28 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-collection@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-collection@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/fecb9f0871c827070a8794b39c7379fdc7d0855c4b05804f0b395eef39c37b2c2b6779865d6cb35d3bc74b6b380107bd8b3754d1730a34ea88913e6cd0eb84d4 + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-compose-refs@npm:1.1.0" @@ -1741,6 +1763,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-direction@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-direction@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/eb07d8cc3ae2388b824e0a11ae0e3b71fb0c49972b506e249cec9f27a5b7ef4305ee668c98b674833c92e842163549a83beb0a197dec1ec65774bdeeb61f932c + languageName: node + linkType: hard + "@radix-ui/react-dismissable-layer@npm:1.1.1": version: 1.1.1 resolution: "@radix-ui/react-dismissable-layer@npm:1.1.1" @@ -1813,6 +1848,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-label@npm:2.1.0": + version: 2.1.0 + resolution: "@radix-ui/react-label@npm:2.1.0" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/282d3b1b72ff14b431b3bb427d66d14253bbd30fad2437d8f4e7d5c0b6a41f6f7ed157460e02fb91b67b1c8cebc65f2c6fe1d3a32f4459d41238fc0fd4719875 + languageName: node + linkType: hard + "@radix-ui/react-popover@npm:1.1.2": version: 1.1.2 resolution: "@radix-ui/react-popover@npm:1.1.2" @@ -1933,6 +1987,61 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-radio-group@npm:1.2.1": + version: 1.2.1 + resolution: "@radix-ui/react-radio-group@npm:1.2.1" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-presence": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-roving-focus": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-use-previous": "npm:1.1.0" + "@radix-ui/react-use-size": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/dfd64762c45a4f33a4fd05161d6eb9b68ff5d0d7cbc26800549d589029dc1c0e23bfb631d88100ed7c155c1f1a0baf9f7e7352d5083b80e733b33f5faede9d26 + languageName: node + linkType: hard + +"@radix-ui/react-roving-focus@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-roving-focus@npm:1.1.0" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-collection": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/ce367d3033a12d639a8d445d2efa090aa4bc5a78125be568f8c8e4e59f30afd51b585a90031ec18cdba19afbaf1974633dbc0c2c3d2a14d9eb1bfea2ddbe5369 + languageName: node + linkType: hard + "@radix-ui/react-separator@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-separator@npm:1.1.0" @@ -2053,6 +2162,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-previous@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-previous@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/9787d24790d4e330715127f2f4db56c4cbed9b0a47f97e11a68582c08a356a53c1ec41c7537382f6fb8d0db25de152770f17430e8eaf0fa59705be97760acbad + languageName: node + linkType: hard + "@radix-ui/react-use-rect@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-use-rect@npm:1.1.0" @@ -3215,7 +3337,9 @@ __metadata: dependencies: "@artsy/fresnel": "npm:7.1.4" "@radix-ui/react-dialog": "npm:1.1.2" + "@radix-ui/react-label": "npm:2.1.0" "@radix-ui/react-popover": "npm:1.1.2" + "@radix-ui/react-radio-group": "npm:1.2.1" "@radix-ui/react-separator": "npm:1.1.0" "@radix-ui/react-slot": "npm:1.1.0" "@radix-ui/react-tooltip": "npm:1.1.3" From c51070740372006ce8368e25ac105f3f021e187f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 23 Oct 2024 17:25:09 +0200 Subject: [PATCH 5/8] Implement map settings panel --- client/public/.delete-later | 0 client/public/assets/images/basemap-dark.png | Bin 0 -> 1888 bytes client/public/assets/images/basemap-light.png | Bin 0 -> 1610 bytes .../assets/images/basemap-satellite.png | Bin 0 -> 1853 bytes client/src/app/layout.tsx | 2 +- client/src/components/map/constants.ts | 31 ++++++++++ client/src/components/map/types.ts | 10 +++ .../components/panels/map-settings/index.tsx | 58 +++++++++++++++++- client/src/svgs/globe-filled.svg | 3 + 9 files changed, 102 insertions(+), 2 deletions(-) delete mode 100644 client/public/.delete-later create mode 100644 client/public/assets/images/basemap-dark.png create mode 100644 client/public/assets/images/basemap-light.png create mode 100644 client/public/assets/images/basemap-satellite.png create mode 100644 client/src/components/map/types.ts create mode 100644 client/src/svgs/globe-filled.svg diff --git a/client/public/.delete-later b/client/public/.delete-later deleted file mode 100644 index e69de29..0000000 diff --git a/client/public/assets/images/basemap-dark.png b/client/public/assets/images/basemap-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd82d62d1e641766bc2c1b4708dcd4446862f9c GIT binary patch literal 1888 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 = { + light: { + name: "Light", + image: "/assets/images/basemap-light.png", + }, + dark: { + name: "Dark", + image: "/assets/images/basemap-dark.png", + }, + satellite: { + name: "Satellite", + image: "/assets/images/basemap-satellite.png", + }, +}; + +export const LABELS: Record = { + dark: { + name: "Dark", + }, + light: { + name: "Light", + }, +}; + +export const DEFAULT_MAP_SETTINGS = { + basemap: BasemapStyle.Light, + labels: LabelsStyle.Dark, +}; diff --git a/client/src/components/map/types.ts b/client/src/components/map/types.ts new file mode 100644 index 0000000..4a0891e --- /dev/null +++ b/client/src/components/map/types.ts @@ -0,0 +1,10 @@ +export enum BasemapStyle { + Light = "light", + Dark = "dark", + Satellite = "satellite", +} + +export enum LabelsStyle { + Dark = "dark", + Light = "light", +} diff --git a/client/src/components/panels/map-settings/index.tsx b/client/src/components/panels/map-settings/index.tsx index 8b7c7e7..c4c0bfc 100644 --- a/client/src/components/panels/map-settings/index.tsx +++ b/client/src/components/panels/map-settings/index.tsx @@ -1,5 +1,61 @@ +import Image from "next/image"; + +import { BASEMAPS, LABELS } from "@/components/map/constants"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import GlobeFilledIcon from "@/svgs/globe-filled.svg"; + const MapSettingsPanel = () => { - return
Coming soon!
; + return ( +
+
+ + + Map style + + + {Object.entries(BASEMAPS).map(([key, { name, image }]) => ( +
+ + + + +
+ ))} +
+
+
+ Labels + + {Object.entries(LABELS).map(([key, { name }]) => ( +
+ + +
+ ))} +
+ + +
+
+
+
+ ); }; export default MapSettingsPanel; diff --git a/client/src/svgs/globe-filled.svg b/client/src/svgs/globe-filled.svg new file mode 100644 index 0000000..08181f8 --- /dev/null +++ b/client/src/svgs/globe-filled.svg @@ -0,0 +1,3 @@ + + + From 604a9a7e777c7dc76ff285651a1e7b4abee5751c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 23 Oct 2024 17:44:26 +0200 Subject: [PATCH 6/8] Connect the UI to the URL --- client/src/components/map/constants.ts | 3 +++ client/src/components/map/types.ts | 1 + .../components/panels/map-settings/index.tsx | 24 ++++++++++++------- client/src/hooks/use-map-basemap.ts | 13 ++++++++++ client/src/hooks/use-map-labels.ts | 13 ++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 client/src/hooks/use-map-basemap.ts create mode 100644 client/src/hooks/use-map-labels.ts diff --git a/client/src/components/map/constants.ts b/client/src/components/map/constants.ts index 722904b..dd33fc6 100644 --- a/client/src/components/map/constants.ts +++ b/client/src/components/map/constants.ts @@ -39,6 +39,9 @@ export const LABELS: Record = { light: { name: "Light", }, + "": { + name: "No labels", + }, }; export const DEFAULT_MAP_SETTINGS = { diff --git a/client/src/components/map/types.ts b/client/src/components/map/types.ts index 4a0891e..d853aff 100644 --- a/client/src/components/map/types.ts +++ b/client/src/components/map/types.ts @@ -7,4 +7,5 @@ export enum BasemapStyle { export enum LabelsStyle { Dark = "dark", Light = "light", + NoLabels = "", } diff --git a/client/src/components/panels/map-settings/index.tsx b/client/src/components/panels/map-settings/index.tsx index c4c0bfc..4cc4474 100644 --- a/client/src/components/panels/map-settings/index.tsx +++ b/client/src/components/panels/map-settings/index.tsx @@ -1,11 +1,17 @@ import Image from "next/image"; import { BASEMAPS, LABELS } from "@/components/map/constants"; +import { BasemapStyle, LabelsStyle } from "@/components/map/types"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import useMapBasemap from "@/hooks/use-map-basemap"; +import useMapLabels from "@/hooks/use-map-labels"; import GlobeFilledIcon from "@/svgs/globe-filled.svg"; const MapSettingsPanel = () => { + const [basemap, setBasemap] = useMapBasemap(); + const [labels, setLabels] = useMapLabels(); + return (
@@ -13,7 +19,11 @@ const MapSettingsPanel = () => { Map style - + setBasemap(value as BasemapStyle)} + className="mt-2 flex flex-col gap-2" + > {Object.entries(BASEMAPS).map(([key, { name, image }]) => (
{
Labels - + setLabels(value as LabelsStyle)} + className="mt-1 flex items-center gap-6" + > {Object.entries(LABELS).map(([key, { name }]) => (
@@ -46,12 +60,6 @@ const MapSettingsPanel = () => {
))} -
- - -
diff --git a/client/src/hooks/use-map-basemap.ts b/client/src/hooks/use-map-basemap.ts new file mode 100644 index 0000000..76994fa --- /dev/null +++ b/client/src/hooks/use-map-basemap.ts @@ -0,0 +1,13 @@ +import { parseAsStringEnum, useQueryState } from "nuqs"; + +import { DEFAULT_MAP_SETTINGS } from "@/components/map/constants"; +import { BasemapStyle } from "@/components/map/types"; + +export default function useMapBasemap() { + return useQueryState( + "basemap", + parseAsStringEnum(Object.values(BasemapStyle)).withDefault( + DEFAULT_MAP_SETTINGS.basemap, + ), + ); +} diff --git a/client/src/hooks/use-map-labels.ts b/client/src/hooks/use-map-labels.ts new file mode 100644 index 0000000..8e99540 --- /dev/null +++ b/client/src/hooks/use-map-labels.ts @@ -0,0 +1,13 @@ +import { parseAsStringEnum, useQueryState } from "nuqs"; + +import { DEFAULT_MAP_SETTINGS } from "@/components/map/constants"; +import { LabelsStyle } from "@/components/map/types"; + +export default function useMapLabels() { + return useQueryState( + "labels", + parseAsStringEnum(Object.values(LabelsStyle)).withDefault( + DEFAULT_MAP_SETTINGS.labels, + ), + ); +} From 310bcd5fbda0a3ab23635b4b0bbac60525682fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Thu, 24 Oct 2024 09:16:18 +0200 Subject: [PATCH 7/8] Upgrade nuqs to v2 --- client/package.json | 2 +- client/src/app/layout.tsx | 5 ++++- client/yarn.lock | 22 ++++++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index 1335698..3736928 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,7 @@ "express": "4.21.1", "mapbox-gl": "3.7.0", "next": "14.2.15", - "nuqs": "1.20.0", + "nuqs": "2.0.3", "pino-http": "10.3.0", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 9bbb7d8..7f7934d 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -1,4 +1,5 @@ import { Jost, DM_Serif_Text } from "next/font/google"; +import { NuqsAdapter } from "nuqs/adapters/next/app"; import Head from "@/components/head"; @@ -35,7 +36,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); } diff --git a/client/yarn.lock b/client/yarn.lock index 2ebf3cd..5bc0c03 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -3363,7 +3363,7 @@ __metadata: jiti: "npm:1.21.6" mapbox-gl: "npm:3.7.0" next: "npm:14.2.15" - nuqs: "npm:1.20.0" + nuqs: "npm:2.0.3" pino-http: "npm:10.3.0" postcss: "npm:^8" prettier: "npm:3.3.3" @@ -6255,14 +6255,24 @@ __metadata: languageName: node linkType: hard -"nuqs@npm:1.20.0": - version: 1.20.0 - resolution: "nuqs@npm:1.20.0" +"nuqs@npm:2.0.3": + version: 2.0.3 + resolution: "nuqs@npm:2.0.3" dependencies: mitt: "npm:^3.0.1" peerDependencies: - next: ">=13.4 <14.0.2 || ^14.0.3" - checksum: 10c0/8e1b67a96c6fda21023924db03b31f3eecf387097ee23685f35fbd1118ff202d1f8f3efd5e1e45bb5736e701a13da0d6cefc98cbe813315dc6462c2e38e2c93f + "@remix-run/react": ">= 2" + next: ">= 14.2.0" + react: ">= 18.2.0" + react-router-dom: ">= 6" + peerDependenciesMeta: + "@remix-run/react": + optional: true + next: + optional: true + react-router-dom: + optional: true + checksum: 10c0/d37b95e10738d58351209518d19ba72a1e08733405b39dbcd64e9b28092ca627983506aa4a9f8504f51c2c729323c8fdfc3bf00cc8360e9b1e9bc85fcb44fba0 languageName: node linkType: hard From 0254e22d8c31f70c9f6347bf5603a7e783a06526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Thu, 24 Oct 2024 10:28:44 +0200 Subject: [PATCH 8/8] Connect UI to the map --- client/src/components/map/constants.ts | 8 ++--- client/src/components/map/index.tsx | 21 ++++++++----- client/src/hooks/use-apply-map-settings.ts | 19 ++++++++++++ client/src/utils/map.ts | 35 ++++++++++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 client/src/hooks/use-apply-map-settings.ts create mode 100644 client/src/utils/map.ts diff --git a/client/src/components/map/constants.ts b/client/src/components/map/constants.ts index dd33fc6..1180cca 100644 --- a/client/src/components/map/constants.ts +++ b/client/src/components/map/constants.ts @@ -33,12 +33,12 @@ export const BASEMAPS: Record = { }; export const LABELS: Record = { - dark: { - name: "Dark", - }, light: { name: "Light", }, + dark: { + name: "Dark", + }, "": { name: "No labels", }, @@ -46,5 +46,5 @@ export const LABELS: Record = { export const DEFAULT_MAP_SETTINGS = { basemap: BasemapStyle.Light, - labels: LabelsStyle.Dark, + labels: LabelsStyle.Light, }; diff --git a/client/src/components/map/index.tsx b/client/src/components/map/index.tsx index 3cc6ecc..e852e00 100644 --- a/client/src/components/map/index.tsx +++ b/client/src/components/map/index.tsx @@ -1,10 +1,11 @@ "use client"; -import { useCallback, useEffect, useMemo, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import ReactMapGL from "react-map-gl"; import { SIDEBAR_WIDTH } from "@/components/ui/sidebar"; import { env } from "@/env"; +import useApplyMapSettings from "@/hooks/use-apply-map-settings"; import useBreakpoint from "@/hooks/use-breakpoint"; import useIsSidebarExpanded from "@/hooks/use-is-sidebar-expanded"; import useMapBounds from "@/hooks/use-map-bounds"; @@ -18,8 +19,10 @@ import type { MapRef, LngLatLike } from "react-map-gl"; import "mapbox-gl/dist/mapbox-gl.css"; const Map = () => { - const isDesktop = useBreakpoint("xl", false, true); const mapRef = useRef(null); + const [map, setMap] = useState(null); + + const isDesktop = useBreakpoint("xl", false, true); const isSidebarExpanded = useIsSidebarExpanded(); const previousIsSidebarExpanded = usePrevious(isSidebarExpanded); @@ -44,17 +47,18 @@ const Map = () => { }, [bounds, isDesktop, isSidebarExpanded]); const onMove = useCallback(() => { - if (mapRef.current) { - setBounds(mapRef.current.getBounds()?.toArray() as [LngLatLike, LngLatLike]); - } - }, [mapRef, setBounds]); + setBounds(map?.getBounds()?.toArray() as [LngLatLike, LngLatLike]); + }, [map, setBounds]); // Update the position of the map based on the sidebar's state useEffect(() => { if (isSidebarExpanded !== previousIsSidebarExpanded) { - mapRef.current?.fitBounds(bounds, initialViewState.fitBoundsOptions); + map?.fitBounds(bounds, initialViewState.fitBoundsOptions); } - }, [isSidebarExpanded, previousIsSidebarExpanded, initialViewState, bounds]); + }, [map, isSidebarExpanded, previousIsSidebarExpanded, initialViewState, bounds]); + + // Apply the basemap and labels + useApplyMapSettings(map); return ( { mapStyle={env.NEXT_PUBLIC_MAPBOX_STYLE} onMove={onMove} logoPosition="bottom-right" + onLoad={() => setMap(mapRef.current)} > diff --git a/client/src/hooks/use-apply-map-settings.ts b/client/src/hooks/use-apply-map-settings.ts new file mode 100644 index 0000000..51f2951 --- /dev/null +++ b/client/src/hooks/use-apply-map-settings.ts @@ -0,0 +1,19 @@ +import { useEffect } from "react"; +import { MapRef } from "react-map-gl"; + +import { toggleGroupLayers } from "@/utils/map"; + +import useMapBasemap from "./use-map-basemap"; +import useMapLabels from "./use-map-labels"; + +export default function useApplyMapSettings(map: MapRef | null) { + const [basemap] = useMapBasemap(); + const [labels] = useMapLabels(); + + useEffect(() => { + if (map) { + toggleGroupLayers(map, "basemap-", (group) => group === `basemap-${basemap}`); + toggleGroupLayers(map, "labels-", (group) => group === `labels-${labels}`); + } + }, [map, basemap, labels]); +} diff --git a/client/src/utils/map.ts b/client/src/utils/map.ts new file mode 100644 index 0000000..1da6010 --- /dev/null +++ b/client/src/utils/map.ts @@ -0,0 +1,35 @@ +import { MapRef } from "react-map-gl"; + +/** + * Toggle on/off the layers of the groups matching the prefix based on the return value of the + * callback + */ +export const toggleGroupLayers = ( + map: MapRef, + groupPrefix: string, + callback: (groupName: string) => boolean, +) => { + const mapboxMap = map?.getMap(); + const mapboxStyle = map?.getStyle(); + + const { "mapbox:groups": mapboxGroups } = + (mapboxStyle?.metadata as { "mapbox:groups": Record }) ?? {}; + + const mapboxLayers = mapboxStyle?.layers ?? []; + + const groupsIdsMatchingPrefix = Object.entries(mapboxGroups ?? {}) + .filter(([, { name }]) => name.startsWith(groupPrefix)) + .map(([key]) => key); + + mapboxLayers.forEach((layer) => { + const { "mapbox:group": group } = (layer.metadata as { "mapbox:group": string }) ?? {}; + + if (groupsIdsMatchingPrefix.includes(group)) { + mapboxMap?.setLayoutProperty( + layer.id, + "visibility", + callback(mapboxGroups[group].name) ? "visible" : "none", + ); + } + }); +};