Skip to content

Commit

Permalink
Merge pull request #372 from dataforgoodfr/marthe-front
Browse files Browse the repository at this point in the history
feat: copy coordinates + restore map 3D rotation + edit vessel tooltip info
  • Loading branch information
marthevienne authored Dec 17, 2024
2 parents 035c4d1 + 0f2dad3 commit 6f009e6
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 12 deletions.
2 changes: 1 addition & 1 deletion frontend/components/core/map/deck-gl-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ export default function DeckGLMap({ zones, onHover }: DeckGLMapProps) {
<DeckGL
viewState={viewState}
controller={{
dragRotate: false,
dragRotate: true,
touchRotate: false,
keyboard: false,
}}
Expand Down
31 changes: 28 additions & 3 deletions frontend/components/core/map/main-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"
import type { PickingInfo } from "@deck.gl/core"
import { useShallow } from "zustand/react/shallow"

import { VesselPosition, VesselPositions } from "@/types/vessel"
import { VesselPosition } from "@/types/vessel"
import { ZoneWithGeometry } from "@/types/zone"
import { useTrackModeOptionsStore } from "@/libs/stores"
import { useMapStore } from "@/libs/stores/map-store"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
import MapVesselTooltip from "@/components/ui/map-vessel-tooltip"
import MapZoneTooltip from "@/components/ui/map-zone-tooltip"

Expand Down Expand Up @@ -52,6 +58,12 @@ export default function MainMap({ zones }: MainMapProps) {
} | null>(null)

const [hoverInfo, setHoverInfo] = useState<PickingInfo | null>(null)
const [previousCoordinates, setPreviousCoordinates] =
useState<string>("-°N; -°E")

const copyText = useCallback((text: string) => {
navigator.clipboard.writeText(text)
}, [])

const isVesselTracked = (vesselId: number) => {
return trackedVesselIDs.includes(vesselId)
Expand All @@ -60,9 +72,10 @@ export default function MainMap({ zones }: MainMapProps) {
const coordinates = useMemo(() => {
if (!hoverInfo) return "-°N; -°E"
const coordinate = hoverInfo.coordinate
if (!coordinate) return "-°N; -°E"
if (!coordinate) return previousCoordinates
const latitude = coordinate[1].toFixed(3)
const longitude = coordinate[0].toFixed(3)
setPreviousCoordinates(`${latitude}°N; ${longitude}°E`)
return `${latitude}°N; ${longitude}°E`
}, [hoverInfo])

Expand Down Expand Up @@ -114,7 +127,19 @@ export default function MainMap({ zones }: MainMapProps) {

return (
<div className="relative size-full">
<MemoizedDeckGLMap zones={zones} onHover={onMapHover} />
<ContextMenu>
<ContextMenuTrigger>
<MemoizedDeckGLMap zones={zones} onHover={onMapHover} />
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
className="bg-background"
onClick={() => copyText(coordinates)}
>
Copy coordinates
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<CoordonatesIndicator coordinates={coordinates} />
{tooltipPosition && activePosition && (
<MapVesselTooltip
Expand Down
198 changes: 198 additions & 0 deletions frontend/components/ui/context-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react"

import { cn } from "@/libs/utils"

const ContextMenu = ContextMenuPrimitive.Root

const ContextMenuTrigger = ContextMenuPrimitive.Trigger

const ContextMenuGroup = ContextMenuPrimitive.Group

const ContextMenuPortal = ContextMenuPrimitive.Portal

const ContextMenuSub = ContextMenuPrimitive.Sub

const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup

const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName

const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName

const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName

const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName

const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName

const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName

const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName

const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName

const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"

export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
15 changes: 8 additions & 7 deletions frontend/components/ui/map-vessel-tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Image from "next/image"
import { VesselPosition } from "@/types/vessel"
import { Button } from "@/components/ui/button"
import Tooltip from "@/components/ui/custom/tooltip"
const getCountryISO2 = require("country-iso-3-to-2")

export interface MapVesselTooltipProps {
top: number
Expand All @@ -24,7 +25,7 @@ const MapVesselTooltip = ({
onSelect,
}: MapVesselTooltipProps) => {
const {
vessel: { mmsi, ship_name, imo, length },
vessel: { mmsi, ship_name, imo, length, type, country_iso3 },
timestamp,
} = vesselInfo

Expand Down Expand Up @@ -54,11 +55,11 @@ const MapVesselTooltip = ({
/>
<Image
className="absolute bottom-[-11px] left-5 z-0 rounded-sm shadow-md"
src="/flags/fr.svg"
src={`https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/flags/4x3/${getCountryISO2(country_iso3)}.svg`.toLowerCase()}
alt="country flag"
width={36}
height={24}
/>
width={40}
height={40}
/>
</div>
</div>
<div className="flex flex-col gap-8 rounded-b-lg bg-white p-5">
Expand All @@ -73,10 +74,10 @@ const MapVesselTooltip = ({
<div className="flex flex-col gap-2">
<div className="flex flex-col">
<p className="text-background">
<span className="font-bold">Vessel type</span> Fishing Vessel
<span className="font-bold">Type:</span> {type}
</p>
<p className="text-background">
<span className="font-bold">Vessel size:</span> {length} meters
<span className="font-bold">Length:</span> {length} m
</p>
</div>
<div className="flex flex-col">
Expand Down
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@js-temporal/polyfill": "^0.4.4",
"@loaders.gl/core": "^4.2.0",
"@loaders.gl/obj": "^4.2.0",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
Expand All @@ -34,6 +35,7 @@
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"country-iso-3-to-2": "^1.1.1",
"date-fns": "^3.6.0",
"deck.gl": "^9.0.36",
"framer-motion": "^11.1.3",
Expand Down Expand Up @@ -71,4 +73,4 @@
"tailwindcss": "^3.3.2",
"typescript": "^4.9.5"
}
}
}

0 comments on commit 6f009e6

Please sign in to comment.