Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: show my location and zoom buttons in MapView #79

Merged
merged 19 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
],
"maxBounds": [
[
4.64034,
52.25168
4.8339104,
52.4703485
],
[
5.10737,
52.50536
5.0247001,
52.6141075
]
]
}
Expand Down
2,261 changes: 803 additions & 1,458 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@utrecht/component-library-react": "7.2.0",
"@utrecht/design-tokens": "2.1.1",
"@utrecht/radio-group-react": "1.0.3",
"@utrecht/select-combobox-react": "1.0.1",
"@vitejs/plugin-react": "4.3.3",
"autoprefixer": "10.4.20",
"axios": "1.7.7",
Expand Down
148 changes: 134 additions & 14 deletions src/app/[locale]/incident/add/components/MapDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Dialog from '@radix-ui/react-dialog'
import React, { useEffect, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import Map, {
MapLayerMouseEvent,
Marker,
Expand All @@ -9,10 +9,27 @@ import Map, {
import { useTranslations } from 'next-intl'
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
import { useFormStore } from '@/store/form_store'
import { Heading, Icon } from '@/components/index'
import {
Heading,
Icon,
AlertDialog,
Paragraph,
Button,
FormField,
ListboxOptionProps,
// SelectCombobox,
} from '@/components'
import { useConfig } from '@/hooks/useConfig'
import { Button } from '@/components/index'
import { IconMapPinFilled } from '@tabler/icons-react'
import {
IconCurrentLocation,
IconMapPinFilled,
IconMinus,
IconPlus,
} from '@tabler/icons-react'
import { ButtonGroup } from '@/components'
import { isCoordinateInsideMaxBound } from '@/lib/utils/map'
import { getSuggestedAddresses } from '@/services/location/address'
import { getServerConfig } from '@/services/config/config'

type MapDialogProps = {
trigger: React.ReactElement
Expand All @@ -21,9 +38,14 @@ type MapDialogProps = {
const MapDialog = ({ trigger }: MapDialogProps) => {
const t = useTranslations('describe-add.map')
const [marker, setMarker] = useState<[number, number] | []>([])
const [outsideMaxBoundError, setOutsideMaxBoundError] = useState<
string | null
>(null)
const [addressOptions, setAddressOptions] = useState<ListboxOptionProps[]>([])
const { formState, updateForm } = useFormStore()
const { dialogMap } = useMap()
const { loading, config } = useConfig()
const dialogRef = useRef<HTMLDialogElement>(null)

const [viewState, setViewState] = useState<ViewState>({
latitude: 0,
Expand Down Expand Up @@ -61,17 +83,53 @@ const MapDialog = ({ trigger }: MapDialogProps) => {
setMarker([formState.coordinates[0], formState.coordinates[1]])
}, [formState.coordinates])

// Handle click on map (flyTo position, after this set the marker position and after this set the view state)
const handleMapClick = (event: MapLayerMouseEvent) => {
// Update position, flyTo position, after this set the marker position
const updatePosition = (lat: number, lng: number) => {
if (dialogMap) {
dialogMap.flyTo({
center: [event.lngLat.lng, event.lngLat.lat],
center: [lng, lat],
speed: 0.5,
zoom: 18,
})
}

setMarker([event.lngLat.lat, event.lngLat.lng])
setMarker([lat, lng])
}

// Handle click on map
const handleMapClick = (event: MapLayerMouseEvent) => {
updatePosition(event.lngLat.lat, event.lngLat.lng)
}

// set current location of user
const setCurrentLocation = () => {
navigator.geolocation.getCurrentPosition(
(position) => {
const isInsideMaxBound = isCoordinateInsideMaxBound(
position.coords.latitude,
position.coords.longitude,
config
? config.base.map.maxBounds
: [
[0, 0],
[0, 0],
]
)

if (isInsideMaxBound) {
updatePosition(position.coords.latitude, position.coords.longitude)
setOutsideMaxBoundError(null)
return
}

setOutsideMaxBoundError(t('outside_max_bound_error'))
dialogRef.current?.showModal()
},
(locationError) => {
setOutsideMaxBoundError(locationError.message)
dialogRef.current?.showModal()
}
)
}

return (
Expand All @@ -82,14 +140,54 @@ const MapDialog = ({ trigger }: MapDialogProps) => {
<Dialog.Content className="fixed inset-0 bg-white z-[1000] grid grid-cols-1 md:grid-cols-3 utrecht-theme">
justiandevs marked this conversation as resolved.
Show resolved Hide resolved
<VisuallyHidden.Root>
{/* TODO: Overleggen welke titel hier het meest vriendelijk is voor de gebruiker, multi-language support integreren */}
<Dialog.Title>Locatie kiezen</Dialog.Title>
<Dialog.Description>
Kies een locatie op de kaart voor de locatie van uw melding.
</Dialog.Description>
<Dialog.Title>{t('dialog_title')}</Dialog.Title>
<Dialog.Description>{t('dialog_description')}</Dialog.Description>
</VisuallyHidden.Root>
<AlertDialog type="error" ref={dialogRef} style={{ marginTop: 128 }}>
<form method="dialog" className="map-alert-dialog__content">
<Paragraph>{outsideMaxBoundError}</Paragraph>
<ButtonGroup>
<Button
appearance="secondary-action-button"
hint="danger"
onClick={() => dialogRef.current?.close()}
>
{t('close_alert_notification')}
</Button>
</ButtonGroup>
</form>
</AlertDialog>
<div className="col-span-1 p-4 flex flex-col justify-between gap-4">
<div>
<Heading level={1}>{t('map_heading')}</Heading>
{/*<FormField*/}
{/* label={t('address_search_label')}*/}
{/* input={*/}
{/* <SelectCombobox*/}
{/* name="address"*/}
{/* options={addressOptions}*/}
{/* type="search"*/}
{/* onChange={async (evt: any) => {*/}
{/* const municipality = (await getServerConfig())['base'][*/}
{/* 'municipality'*/}
{/* ]*/}
{/* const apiCall = await getSuggestedAddresses(*/}
{/* evt.target.value,*/}
{/* municipality*/}
{/* )*/}

{/* // TODO: Prevent out-of-order responses showing up*/}
{/* setAddressOptions([])*/}
{/* setAddressOptions(*/}
{/* apiCall.response.docs.map((item) => ({*/}
{/* children: item.weergavenaam,*/}
{/* value: item.weergavenaam,*/}
{/* }))*/}
{/* )*/}
{/* }}*/}
{/* />*/}
{/* }*/}
{/*></FormField>*/}
</div>
<div>
<Dialog.Close
Expand All @@ -98,13 +196,15 @@ const MapDialog = ({ trigger }: MapDialogProps) => {
updateForm({ ...formState, coordinates: marker })
}
>
<Button appearance="primary-action-button">Kies locatie</Button>
<Button appearance="primary-action-button">
justiandevs marked this conversation as resolved.
Show resolved Hide resolved
{t('choose_location')}
</Button>
</Dialog.Close>
</div>
</div>
{/* TODO: Implement state if loading, and no config is found */}
{config && (
<div className="col-span-1 md:col-span-2">
<div className="col-span-1 md:col-span-2 relative">
<Map
{...viewState}
id="dialogMap"
Expand All @@ -126,6 +226,26 @@ const MapDialog = ({ trigger }: MapDialogProps) => {
</Marker>
)}
</Map>
<div className="map-location-group">
<Button onClick={() => setCurrentLocation()}>
<IconCurrentLocation />
{t('current_location')}
</Button>
</div>
<ButtonGroup direction="column" className="map-zoom-button-group">
<Button
className="map-zoom-button"
onClick={() => dialogMap?.flyTo({ zoom: viewState.zoom + 1 })}
>
<IconPlus />
</Button>
<Button
className="map-zoom-button"
onClick={() => dialogMap?.flyTo({ zoom: viewState.zoom - 1 })}
>
<IconMinus />
</Button>
</ButtonGroup>
</div>
)}
</Dialog.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import { PublicQuestion } from '@/types/form'
import { MapProvider } from 'react-map-gl/maplibre'
import { useFormContext } from 'react-hook-form'
import React, { useEffect, useState } from 'react'
import { Button, Paragraph } from '@/components/index'
import {
Button,
Paragraph,
LinkButton,
Fieldset,
FieldsetLegend,
} from '@/components/index'
import { useFormStore } from '@/store/form_store'
import { getNearestAddressByCoordinate } from '@/services/location/address'
import { useConfig } from '@/hooks/useConfig'
import { isCoordinates } from '@/lib/utils/map'
import { Fieldset, FieldsetLegend, LinkButton } from '@/components/index'
import { useTranslations } from 'next-intl'
import { FormFieldErrorMessage } from '@/components'

Expand Down Expand Up @@ -58,7 +63,9 @@ export const LocationSelect = ({ field }: LocationSelectProps) => {
)}

<div className="relative w-full">
<LocationMap />
<div style={{ minHeight: 200, height: 200 }}>
<LocationMap />
</div>
<Paragraph>{address}</Paragraph>
<MapProvider>
<MapDialog
Expand Down
2 changes: 1 addition & 1 deletion src/app/[locale]/incident/components/Stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export const Stepper = ({}: StepperProps) => {
}

return (
<Button onClick={() => resetState()} asChild>
<Button onClick={() => resetState()}>
<NextLinkWrapper href={'/incident'}>
{t('new_notification')}
</NextLinkWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const IncidentSummaryForm = () => {
location: {
geometrie: {
type: _NestedLocationModel.type.POINT,
coordinates: formState.coordinates,
coordinates: [formState.coordinates[1], formState.coordinates[0]],
},
},
// @ts-ignore
Expand Down
47 changes: 47 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,53 @@
border: 1px solid rgb(0, 32, 92);
}

/* map */
.map-location-group {
position: absolute;
left: 1em;
top: 1em;
display: flex;
flex-direction: column;
gap: 8px;
}

.map-location-alert {
padding: 12px 16px !important;
}

.map-zoom-button-group {
position: absolute;
right: 1em;
bottom: 1em;
gap: 0 !important;
}

.map-alert-dialog__content {
display: flex;
flex-direction: column;
gap: 12px;
}

.utrecht-alert__content-closable {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}

.utrecht-alert-closable__icon {
rotate: 45deg;
--utrecht-icon-size: 24px;
color: var(--_utrecht-alert-color) !important;
}

.map-zoom-button {
padding-block-end: 0 !important;
padding-block-start: 0 !important;
padding-inline-end: 0 !important;
padding-inline-start: 0 !important;
}

/* map */
.map-marker-icon {
--utrecht-icon-size: 42px;
Expand Down
5 changes: 4 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type * from '@utrecht/component-library-react/dist/css-module'
import { Select } from '@/components/ui'
import { Container } from '@/components/layout'

Expand All @@ -23,10 +24,12 @@ export {
Logo,
Paragraph,
PreHeading,
LinkButton,
Icon,
RadioButton,
LinkButton,
AlertDialog,
} from '@utrecht/component-library-react/dist/css-module'

export { RadioGroup } from '@utrecht/radio-group-react'
export { CheckboxGroup } from '@utrecht/checkbox-group-react'
// export { SelectCombobox } from '@/components/ui/SelectCombobox'
2 changes: 1 addition & 1 deletion src/components/ui/NextLinkWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link as DesignSystemLink } from '@/components'
import type { LinkProps as DesignSystemLinkProps } from '@utrecht/component-library-react/dist/Link'
import type { LinkProps as DesignSystemLinkProps } from '@utrecht/component-library-react/dist'
import clsx from 'clsx'
import { Link as NextLink, usePathname } from '@/routing/navigation'
import React, { ForwardedRef, forwardRef } from 'react'
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/SelectCombobox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// 'use client'
//
// export { SelectCombobox } from '@utrecht/select-combobox-react/dist/css'
11 changes: 11 additions & 0 deletions src/lib/utils/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ export const isCoordinates = (arg: unknown): arg is [number, number] => {
typeof arg[1] === 'number'
)
}

export const isCoordinateInsideMaxBound = (
lat: number,
lng: number,
maxBounds: [[number, number], [number, number]]
): boolean => {
const [minLng, minLat] = maxBounds[0]
const [maxLng, maxLat] = maxBounds[1]

return lat >= minLat && lat <= maxLat && lng >= minLng && lng <= maxLng
}
Loading
Loading